hecks 2.8.0 → 2.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/exe/hecks +4 -6
- data/lib/hecks/adapters/driven/claude_code.rb +4 -104
- data/lib/hecks/adapters/driven/d1.rb +44 -237
- data/lib/hecks/adapters/driven/folder.rb +17 -76
- data/lib/hecks/adapters/driven/google_authentication.rb +15 -49
- data/lib/hecks/adapters/driven/governance_authorization.rb +23 -70
- data/lib/hecks/adapters/driven/heki/journal.rb +8 -41
- data/lib/hecks/adapters/driven/heki/saga_store.rb +12 -54
- data/lib/hecks/adapters/driven/heki/snapshot.rb +4 -12
- data/lib/hecks/adapters/driven/heki.rb +13 -42
- data/lib/hecks/adapters/driven/identity_registry.rb +3 -14
- data/lib/hecks/adapters/driven/in_memory_ordering.rb +9 -26
- data/lib/hecks/adapters/driven/in_process_key_vault.rb +2 -14
- data/lib/hecks/adapters/driven/lambda/client.rb +10 -51
- data/lib/hecks/adapters/driven/lambda.rb +10 -84
- data/lib/hecks/adapters/driven/local_storage.rb +5 -40
- data/lib/hecks/adapters/driven/memory.rb +17 -98
- data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +5 -21
- data/lib/hecks/adapters/driven/postgres/codec.rb +6 -28
- data/lib/hecks/adapters/driven/postgres/outbox.rb +3 -12
- data/lib/hecks/adapters/driven/postgres/reconnect.rb +4 -30
- data/lib/hecks/adapters/driven/postgres/schema_builder.rb +34 -108
- data/lib/hecks/adapters/driven/postgres.rb +145 -134
- data/lib/hecks/adapters/driven/postgres_era.adapter +4 -15
- data/lib/hecks/adapters/driven/prism.rb +7 -30
- data/lib/hecks/adapters/driven/sql_query_builder.rb +21 -121
- data/lib/hecks/adapters/driven/sqlite/codec.rb +8 -40
- data/lib/hecks/adapters/driven/sqlite/projection.rb +26 -82
- data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +40 -84
- data/lib/hecks/adapters/driven/sqlite.rb +128 -65
- data/lib/hecks/adapters/driven/tenant_provisioner.rb +5 -48
- data/lib/hecks/adapters/driven.rb +5 -36
- data/lib/hecks/adapters/driving/github_webhook.rb +8 -76
- data/lib/hecks/behaviors/dsl.rb +7 -25
- data/lib/hecks/behaviors/expectations.rb +31 -141
- data/lib/hecks/behaviors/ir.rb +7 -20
- data/lib/hecks/behaviors/rspec.rb +6 -17
- data/lib/hecks/behaviors/runner.rb +10 -34
- data/lib/hecks/behaviors.rb +4 -8
- data/lib/hecks/bluebook/aggregate.rb +9 -75
- data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +9 -38
- data/lib/hecks/bluebook/assembly/build.rb +4 -15
- data/lib/hecks/bluebook/assembly/contract.rb +15 -136
- data/lib/hecks/bluebook/assembly/contracts.rb +54 -174
- data/lib/hecks/bluebook/assembly/marks.rb +31 -223
- data/lib/hecks/bluebook/assembly/specializer.rb +6 -52
- data/lib/hecks/bluebook/assembly.rb +6 -45
- data/lib/hecks/bluebook/attribute.rb +12 -61
- data/lib/hecks/bluebook/behaviour/aggregate.rb +10 -39
- data/lib/hecks/bluebook/behaviour/attribute.rb +3 -14
- data/lib/hecks/bluebook/behaviour/chapter.rb +14 -42
- data/lib/hecks/bluebook/behaviour/command.rb +14 -66
- data/lib/hecks/bluebook/behaviour/domain_port.rb +4 -20
- data/lib/hecks/bluebook/behaviour/entity.rb +7 -27
- data/lib/hecks/bluebook/behaviour/hexagon.rb +23 -41
- data/lib/hecks/bluebook/behaviour/lifecycle.rb +15 -40
- data/lib/hecks/bluebook/behaviour/policy.rb +23 -52
- data/lib/hecks/bluebook/behaviour/process_manager.rb +30 -57
- data/lib/hecks/bluebook/behaviour/read_model.rb +25 -31
- data/lib/hecks/bluebook/behaviour/traits.rb +27 -64
- data/lib/hecks/bluebook/behaviour/value_object.rb +5 -24
- data/lib/hecks/bluebook/capabilities.rb +17 -49
- data/lib/hecks/bluebook/chapter.rb +10 -48
- data/lib/hecks/bluebook/command.rb +23 -81
- data/lib/hecks/bluebook/domain_port.rb +14 -63
- data/lib/hecks/bluebook/dsl/aggregate_builder/sealing.rb +50 -229
- data/lib/hecks/bluebook/dsl/aggregate_builder.rb +50 -377
- data/lib/hecks/bluebook/dsl/attribute_collector.rb +33 -204
- data/lib/hecks/bluebook/dsl/binding_proxy.rb +16 -69
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +86 -708
- data/lib/hecks/bluebook/dsl/bluebook_builder.rb +56 -170
- data/lib/hecks/bluebook/dsl/bootstrap_table.rb +1 -0
- data/lib/hecks/bluebook/dsl/command_builder.rb +74 -546
- data/lib/hecks/bluebook/dsl/const_shim.rb +8 -41
- data/lib/hecks/bluebook/dsl/domain_port_builder.rb +12 -87
- data/lib/hecks/bluebook/dsl/entity_builder.rb +44 -330
- data/lib/hecks/bluebook/dsl/generic_dispatch.rb +20 -269
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +26 -194
- data/lib/hecks/bluebook/dsl/identity_declaration.rb +13 -88
- data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +6 -20
- data/lib/hecks/bluebook/dsl/policy_builder.rb +16 -88
- data/lib/hecks/bluebook/dsl/port_builder.rb +2 -6
- data/lib/hecks/bluebook/dsl/port_operation_builder.rb +22 -96
- data/lib/hecks/bluebook/dsl/process_manager_builder.rb +31 -184
- data/lib/hecks/bluebook/dsl/query_builder.rb +11 -42
- data/lib/hecks/bluebook/dsl/read_model_builder.rb +75 -201
- data/lib/hecks/bluebook/dsl/rule_reference.rb +22 -163
- data/lib/hecks/bluebook/dsl/translation_builder.rb +28 -114
- data/lib/hecks/bluebook/dsl/value_object_builder.rb +10 -124
- data/lib/hecks/bluebook/dsl/word_gate.rb +16 -162
- data/lib/hecks/bluebook/dsl/world_builder.rb +19 -111
- data/lib/hecks/bluebook/entity.rb +13 -71
- data/lib/hecks/bluebook/expression/ast_json.rb +24 -190
- data/lib/hecks/bluebook/expression/ast_reader.rb +12 -54
- data/lib/hecks/bluebook/expression/canonical_form.rb +11 -88
- data/lib/hecks/bluebook/expression/evaluator.rb +47 -295
- data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +30 -147
- data/lib/hecks/bluebook/expression/resolver.rb +37 -660
- data/lib/hecks/bluebook/hexagon.rb +5 -28
- data/lib/hecks/bluebook/lifecycle.rb +5 -16
- data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +1 -7
- data/lib/hecks/bluebook/meta_validator/judge.rb +111 -361
- data/lib/hecks/bluebook/meta_validator/plan.rb +33 -162
- data/lib/hecks/bluebook/meta_validator/port_judge.rb +2 -7
- data/lib/hecks/bluebook/meta_validator/readings.rb +56 -342
- data/lib/hecks/bluebook/meta_validator/reconstruction.rb +24 -176
- data/lib/hecks/bluebook/meta_validator/shapes.rb +53 -267
- data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +43 -305
- data/lib/hecks/bluebook/meta_validator/translation_judge.rb +7 -43
- data/lib/hecks/bluebook/meta_validator/world_judge.rb +6 -18
- data/lib/hecks/bluebook/meta_validator.rb +79 -349
- data/lib/hecks/bluebook/model_check/client_profile.rb +6 -21
- data/lib/hecks/bluebook/model_check.rb +100 -597
- data/lib/hecks/bluebook/pattern_subset.rb +11 -69
- data/lib/hecks/bluebook/process_manager.rb +13 -83
- data/lib/hecks/bluebook/project_loader.rb +3 -39
- data/lib/hecks/bluebook/project_register.rb +12 -60
- data/lib/hecks/bluebook/query.rb +9 -48
- data/lib/hecks/bluebook/read_model.rb +57 -81
- data/lib/hecks/bluebook/reference.rb +7 -41
- data/lib/hecks/bluebook/smoke_test.rb +8 -98
- data/lib/hecks/bluebook/synthesizer.rb +13 -62
- data/lib/hecks/bluebook/translation.rb +26 -62
- data/lib/hecks/bluebook/value_object.rb +11 -36
- data/lib/hecks/bluebook.rb +5 -22
- data/lib/hecks/cache_dir.rb +4 -23
- data/lib/hecks/cli/document.rb +3 -21
- data/lib/hecks/cli/ir.rb +3 -5
- data/lib/hecks/cli/mcp.rb +2 -3
- data/lib/hecks/cli/mcp_door.rb +19 -114
- data/lib/hecks/cli/model_check.rb +18 -85
- data/lib/hecks/cli/project_cli.rb +7 -40
- data/lib/hecks/cli/project_diagrams.rb +0 -7
- data/lib/hecks/cli/run.rb +12 -58
- data/lib/hecks/cli/smoke_test.rb +2 -16
- data/lib/hecks/cli/stores.rb +1 -2
- data/lib/hecks/cli.rb +18 -24
- data/lib/hecks/construct.rb +8 -34
- data/lib/hecks/deploy/bluebook/deploy.bluebook +33 -142
- data/lib/hecks/deploy/bluebook/deploy.hecksagon +2 -9
- data/lib/hecks/embryonaut_bluebook/lock.rb +3 -35
- data/lib/hecks/embryonaut_bluebook/shape.rb +1 -17
- data/lib/hecks/embryonaut_bluebook/vendor.rb +8 -53
- data/lib/hecks/embryonaut_bluebook/vendor_cli.rb +4 -19
- data/lib/hecks/embryonaut_bluebook.rb +4 -71
- data/lib/hecks/facade/cli_door.rb +24 -106
- data/lib/hecks/facade/cli_runner.rb +28 -172
- data/lib/hecks/facade/command_request.rb +13 -33
- data/lib/hecks/facade/handle.rb +34 -121
- data/lib/hecks/facade/json_door.rb +31 -146
- data/lib/hecks/facade/surface/aggregate_door.rb +39 -104
- data/lib/hecks/facade/surface/chapter.rb +10 -54
- data/lib/hecks/facade/surface.rb +8 -23
- data/lib/hecks/facade.rb +10 -25
- data/lib/hecks/forms/app.rb +15 -70
- data/lib/hecks/forms/command_form_renderer.rb +10 -29
- data/lib/hecks/forms/field_renderer.rb +17 -156
- data/lib/hecks/forms/field_shape.rb +30 -222
- data/lib/hecks/forms/html.rb +12 -47
- data/lib/hecks/forms/index_renderer.rb +1 -4
- data/lib/hecks/forms/page.rb +3 -9
- data/lib/hecks/forms/params.rb +14 -60
- data/lib/hecks/forms/port_argument.rb +2 -19
- data/lib/hecks/forms/query_form_renderer.rb +4 -79
- data/lib/hecks/forms/record_renderer.rb +8 -75
- data/lib/hecks/forms/record_table.rb +7 -50
- data/lib/hecks/forms/reference_options.rb +3 -30
- data/lib/hecks/forms/value_object_shape.rb +8 -35
- data/lib/hecks/forms.rb +5 -33
- data/lib/hecks/fqn.rb +5 -12
- data/lib/hecks/framework/bluebook/compliance.bluebook +16 -65
- data/lib/hecks/framework/bluebook/console_settings.bluebook +47 -134
- data/lib/hecks/framework/bluebook/framework.hecksagon +5 -14
- data/lib/hecks/framework/bluebook/governance.bluebook +13 -34
- data/lib/hecks/framework/bluebook/identity.bluebook +11 -26
- data/lib/hecks/framework/bluebook/privacy.bluebook +8 -33
- data/lib/hecks/framework.rb +11 -80
- data/lib/hecks/freezer.rb +5 -39
- data/lib/hecks/grammar/expression.bluebook +15 -40
- data/lib/hecks/grammar/translation.bluebook +3 -6
- data/lib/hecks/grammar.rb +16 -41
- data/lib/hecks/ir.rb +14 -83
- data/lib/hecks/language/adapter.bluebook +2 -7
- data/lib/hecks/language/bluebook/aggregate.bluebook +56 -221
- data/lib/hecks/language/bluebook/attaches/paging.bluebook +6 -15
- data/lib/hecks/language/bluebook/bluebook.bluebook +15 -83
- data/lib/hecks/language/bluebook/bluebook.hecksagon +3 -15
- data/lib/hecks/language/bluebook/command.bluebook +27 -144
- data/lib/hecks/language/bluebook/entity.bluebook +24 -97
- data/lib/hecks/language/bluebook/policy.bluebook +16 -67
- data/lib/hecks/language/bluebook/process_manager.bluebook +28 -151
- data/lib/hecks/language/bluebook/projection.bluebook +126 -60
- data/lib/hecks/language/bluebook/query.bluebook +12 -43
- data/lib/hecks/language/bluebook/shape.bluebook +23 -73
- data/lib/hecks/language/bluebook/syntax.bluebook +51 -262
- data/lib/hecks/language/bluebook/vocabulary.bluebook +82 -197
- data/lib/hecks/language/hecksagon/adapter_binding.bluebook +3 -10
- data/lib/hecks/language/hecksagon/domain_port.bluebook +4 -19
- data/lib/hecks/language/hecksagon/hecksagon.bluebook +6 -25
- data/lib/hecks/language/hecksagon/port_operation.bluebook +3 -14
- data/lib/hecks/language/oidc.json +35 -0
- data/lib/hecks/language/port.bluebook +4 -17
- data/lib/hecks/language/translation/translation.bluebook +3 -9
- data/lib/hecks/language/translation/translation_aggregate.bluebook +5 -24
- data/lib/hecks/language/world/wiring.bluebook +1 -3
- data/lib/hecks/language/world/world.bluebook +5 -7
- data/lib/hecks/literal.rb +8 -50
- data/lib/hecks/mcp_door_scope.rb +9 -90
- data/lib/hecks/mcp_stdio_guard.rb +4 -41
- data/lib/hecks/naming.rb +29 -137
- data/lib/hecks/ports/access_control.rb +7 -23
- data/lib/hecks/ports/agent/answers.rb +5 -23
- data/lib/hecks/ports/agent.rb +47 -172
- data/lib/hecks/ports/authentication.rb +12 -39
- data/lib/hecks/ports/authorization.rb +16 -43
- data/lib/hecks/ports/clock.rb +5 -45
- data/lib/hecks/ports/extraction.rb +7 -12
- data/lib/hecks/ports/identity_assignment.rb +8 -29
- data/lib/hecks/ports/identity_generation.rb +6 -29
- data/lib/hecks/ports/identity_resolution.rb +2 -10
- data/lib/hecks/ports/key_vault.rb +9 -45
- data/lib/hecks/ports/persistence/append_only.rb +61 -64
- data/lib/hecks/ports/persistence/binding_policy.rb +4 -14
- data/lib/hecks/ports/persistence/codec_boundary.rb +20 -80
- data/lib/hecks/ports/persistence/null_saga_store.rb +4 -20
- data/lib/hecks/ports/persistence/plugin.rb +13 -42
- data/lib/hecks/ports/persistence/plugins/era/era_check.rb +38 -141
- data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +15 -115
- data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +16 -84
- data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +6 -25
- data/lib/hecks/ports/persistence/plugins/era/expected_era.rb +3 -22
- data/lib/hecks/ports/persistence/plugins/era/lineage.rb +55 -181
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/events.rb +77 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +12 -122
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/field_cache.rb +28 -128
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +73 -474
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/mint_transaction.rb +31 -124
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +45 -219
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/resumable_backfill.rb +13 -108
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +18 -75
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/transform_installer.rb +9 -49
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +6 -123
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/coverage_check.rb +23 -55
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +14 -72
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +7 -16
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +14 -115
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager.rb +13 -45
- data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +127 -554
- data/lib/hecks/ports/persistence/plugins/era/storage_shape.rb +13 -63
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +2 -17
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_one.rb +3 -14
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +13 -58
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +3 -17
- data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +9 -26
- data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +9 -25
- data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +18 -124
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +34 -123
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/renderer.rb +5 -12
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +3 -9
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +2 -14
- data/lib/hecks/ports/persistence/plugins/era.rb +4 -23
- data/lib/hecks/ports/persistence/postgres_dump/connection.rb +7 -23
- data/lib/hecks/ports/persistence/postgres_dump.rb +4 -35
- data/lib/hecks/ports/persistence/remote_runtime.rb +2 -24
- data/lib/hecks/ports/persistence/repository_factory.rb +8 -27
- data/lib/hecks/ports/persistence/state_codec.rb +23 -103
- data/lib/hecks/ports/persistence.rb +7 -14
- data/lib/hecks/ports/projection.rb +42 -84
- data/lib/hecks/ports/query/in_memory.rb +10 -58
- data/lib/hecks/ports/query/ordering.rb +11 -32
- data/lib/hecks/ports/query.rb +14 -37
- data/lib/hecks/ports.rb +2 -8
- data/lib/hecks/projections/bootstrap_table.rb +11 -43
- data/lib/hecks/projections/deploy/fargate/assembly.rb +2 -15
- data/lib/hecks/projections/deploy/fargate/cdn.rb +1 -48
- data/lib/hecks/projections/deploy/fargate/cdn_default.rb +2 -11
- data/lib/hecks/projections/deploy/fargate/check.rb +5 -14
- data/lib/hecks/projections/deploy/fargate/containers.rb +3 -78
- data/lib/hecks/projections/deploy/fargate/extras.rb +3 -44
- data/lib/hecks/projections/deploy/fargate/monitoring.rb +0 -44
- data/lib/hecks/projections/deploy/fargate/settings.rb +2 -54
- data/lib/hecks/projections/deploy/fargate/yaml.rb +13 -58
- data/lib/hecks/projections/deploy/fargate.rb +35 -202
- data/lib/hecks/projections/deploy/lambda.rb +150 -678
- data/lib/hecks/projections/deploy/preview/access_yaml.rb +1 -33
- data/lib/hecks/projections/deploy/preview/containers.rb +6 -111
- data/lib/hecks/projections/deploy/preview/routing_yaml.rb +4 -56
- data/lib/hecks/projections/deploy/preview/script.rb +2 -14
- data/lib/hecks/projections/deploy/preview/settings.rb +3 -33
- data/lib/hecks/projections/deploy/preview/task_yaml.rb +2 -38
- data/lib/hecks/projections/deploy/preview/template.rb +2 -58
- data/lib/hecks/projections/deploy/preview.rb +2 -67
- data/lib/hecks/projections/deploy/scripts/settings.rb +3 -25
- data/lib/hecks/projections/deploy/scripts.rb +4 -57
- data/lib/hecks/projections/deploy/shared.rb +47 -146
- data/lib/hecks/projections/deploy/smoke.rb +4 -61
- data/lib/hecks/projections/deploy/template_diff/comparer.rb +3 -25
- data/lib/hecks/projections/deploy/template_diff/loader.rb +2 -11
- data/lib/hecks/projections/deploy/template_diff.rb +5 -57
- data/lib/hecks/projections/diagrams.rb +20 -582
- data/lib/hecks/projections/glossary/html.rb +14 -130
- data/lib/hecks/projections/glossary/markdown.rb +8 -69
- data/lib/hecks/projections/glossary/mermaid.rb +9 -66
- data/lib/hecks/projections/glossary/sensitivity.rb +5 -34
- data/lib/hecks/projections/glossary/sentences.rb +14 -159
- data/lib/hecks/projections/glossary.rb +30 -222
- data/lib/hecks/projections/ir.rb +3 -10
- data/lib/hecks/projections/model/deviations.rb +15 -42
- data/lib/hecks/projections/model.rb +12 -51
- data/lib/hecks/projections/oidc.rb +12 -56
- data/lib/hecks/projections/parser_table.rb +9 -28
- data/lib/hecks/projections/reference.rb +2 -15
- data/lib/hecks/projections/rust_vocabulary.rb +12 -250
- data/lib/hecks/projections/shape.rb +3 -29
- data/lib/hecks/projections/statements.rb +7 -86
- data/lib/hecks/projections/vocabulary.rb +6 -22
- data/lib/hecks/projections.rb +2 -16
- data/lib/hecks/projector/cli_projector.rb +60 -375
- data/lib/hecks/projector/docs_projector.rb +10 -201
- data/lib/hecks/projector/exporter.rb +42 -134
- data/lib/hecks/projector/ir_projector.rb +2 -9
- data/lib/hecks/projector/narrate_projector.rb +15 -189
- data/lib/hecks/projector/target.rb +13 -76
- data/lib/hecks/projector.rb +30 -121
- data/lib/hecks/query_specification/common/comparators.rb +5 -26
- data/lib/hecks/query_specification/common/comparison.rb +35 -143
- data/lib/hecks/query_specification/common/dsl.rb +8 -24
- data/lib/hecks/query_specification/common/null_policy.rb +20 -70
- data/lib/hecks/query_specification/common/options.rb +2 -6
- data/lib/hecks/query_specification/common/where_clause.rb +2 -10
- data/lib/hecks/query_specification/field_path.rb +37 -77
- data/lib/hecks/query_specification/hop_path.rb +12 -76
- data/lib/hecks/release/gem_pin.rb +2 -26
- data/lib/hecks/release/runner/ci_publisher.rb +3 -6
- data/lib/hecks/release/runner/commands.rb +2 -4
- data/lib/hecks/release/runner/gem_publisher.rb +2 -3
- data/lib/hecks/release/runner/npm_publisher.rb +6 -20
- data/lib/hecks/release/runner/preflight.rb +4 -19
- data/lib/hecks/release/runner/tagger.rb +2 -9
- data/lib/hecks/release/runner.rb +3 -32
- data/lib/hecks/rendering.rb +4 -26
- data/lib/hecks/router/namespace_installer.rb +12 -36
- data/lib/hecks/router.rb +4 -8
- data/lib/hecks/runtime/aggregate_lock.rb +8 -33
- data/lib/hecks/runtime/boot_gates.rb +3 -18
- data/lib/hecks/runtime/caller.rb +19 -59
- data/lib/hecks/runtime/capability_graph.rb +5 -21
- data/lib/hecks/runtime/command_interpreter/argument_gate.rb +16 -78
- data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +23 -160
- data/lib/hecks/runtime/command_interpreter.rb +124 -306
- data/lib/hecks/runtime/command_rules/admissibility.rb +95 -366
- data/lib/hecks/runtime/command_rules/arithmetic.rb +66 -196
- data/lib/hecks/runtime/command_rules/authorization.rb +13 -45
- data/lib/hecks/runtime/command_rules/emission.rb +8 -21
- data/lib/hecks/runtime/command_rules/references.rb +23 -202
- data/lib/hecks/runtime/dependency_planning.rb +30 -102
- data/lib/hecks/runtime/dispatcher.rb +82 -241
- data/lib/hecks/runtime/entity_element.rb +40 -462
- data/lib/hecks/runtime/entity_interpreter.rb +64 -219
- data/lib/hecks/runtime/errors.rb +32 -115
- data/lib/hecks/runtime/event.rb +3 -25
- data/lib/hecks/runtime/identity.rb +18 -154
- data/lib/hecks/runtime/instance.rb +18 -150
- data/lib/hecks/runtime/interpreting.rb +11 -70
- data/lib/hecks/runtime/invocation.rb +28 -164
- data/lib/hecks/runtime/loader.rb +48 -186
- data/lib/hecks/runtime/outbox.rb +38 -261
- data/lib/hecks/runtime/policy_interpreter.rb +38 -200
- data/lib/hecks/runtime/port_operation_interpreter.rb +26 -87
- data/lib/hecks/runtime/query_interpreter.rb +45 -187
- data/lib/hecks/runtime/reaction_invocation.rb +36 -138
- data/lib/hecks/runtime/read_model_interpreter.rb +117 -208
- data/lib/hecks/runtime/rebuild_sweep.rb +8 -54
- data/lib/hecks/runtime/reference_hop.rb +8 -81
- data/lib/hecks/runtime/refusal_wording.rb +8 -89
- data/lib/hecks/runtime/registry/saga_persistence.rb +10 -102
- data/lib/hecks/runtime/registry/verification.rb +52 -225
- data/lib/hecks/runtime/registry/world_defaults.rb +4 -53
- data/lib/hecks/runtime/registry.rb +99 -244
- data/lib/hecks/runtime/remote_dispatcher.rb +37 -152
- data/lib/hecks/runtime/routing.rb +4 -15
- data/lib/hecks/runtime/saga_interpreter/correlation.rb +14 -61
- data/lib/hecks/runtime/saga_interpreter.rb +79 -263
- data/lib/hecks/runtime/saga_pending_dispatch.rb +2 -43
- data/lib/hecks/runtime/tenant_check.rb +6 -68
- data/lib/hecks/runtime/tenant_scope.rb +3 -28
- data/lib/hecks/runtime/value/admission.rb +16 -67
- data/lib/hecks/runtime/value/coercion.rb +155 -641
- data/lib/hecks/runtime/value/entity_list_coercion.rb +26 -219
- data/lib/hecks/runtime/value.rb +12 -84
- data/lib/hecks/runtime.rb +17 -65
- data/lib/hecks/storehouse.rb +141 -568
- data/lib/hecks/tenancy/bluebook/tenancy.bluebook +8 -32
- data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +6 -23
- data/lib/hecks/vendoring/git_environment.rb +2 -8
- data/lib/hecks/vendoring/git_source.rb +4 -10
- data/lib/hecks/vendoring.rb +7 -46
- data/lib/hecks/version.rb +3 -14
- data/lib/hecks.rb +26 -81
- data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +8 -46
- data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +11 -80
- data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +10 -99
- metadata +3 -2
|
@@ -8,46 +8,25 @@ require_relative "invariant_violation"
|
|
|
8
8
|
module Hecks
|
|
9
9
|
module Runtime
|
|
10
10
|
class Value
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
# `Value.for`, `Value.build`, … — `self` is the Value class.
|
|
11
|
+
# Class-side coercion engine for Value, extended in so its methods read
|
|
12
|
+
# as `Value.for`, `Value.build`, and so on.
|
|
14
13
|
module Coercion
|
|
15
|
-
# The
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
# them into a single closed list. `Attribute#list?`/`#optional?`
|
|
19
|
-
# are real predicates on the IR node itself (bluebook/attribute.rb);
|
|
20
|
-
# `:scalar` and `:composite` are not named predicates there — they
|
|
21
|
-
# fall out of whether `aggregate.value_object(attribute.type)`
|
|
22
|
-
# resolves to something, read directly in the `coerced =` line
|
|
23
|
-
# below — but the branch is exactly as real, so it gets a name here
|
|
24
|
-
# too rather than staying anonymous.
|
|
25
|
-
#
|
|
26
|
-
# A second runtime's kernel ports this method by hand (rust/src/
|
|
27
|
-
# kernel/attribute_shapes/*.rs — one file per name in this array,
|
|
28
|
-
# generated into a Rust enum by bin/project_kernel_capabilities so
|
|
29
|
-
# every match over it is compiler-checked exhaustive). If a fifth
|
|
30
|
-
# branch is ever added to `for_attribute`, add its name here in the
|
|
31
|
-
# same breath — this list is that port's only source of truth for
|
|
32
|
-
# "which shapes exist," and a shape missing from it is a shape the
|
|
33
|
-
# generated Rust enum, and therefore the kernel, can never learn
|
|
34
|
-
# about no matter how correct the Ruby below is.
|
|
14
|
+
# The complete set of attribute value shapes. Mirrored by hand into a
|
|
15
|
+
# generated Rust enum (bin/project_kernel_capabilities) — adding a shape
|
|
16
|
+
# here without a matching Rust file leaves the kernel unaware of it.
|
|
35
17
|
SHAPES = %i[scalar list optional composite].freeze
|
|
36
18
|
|
|
37
19
|
# Coerces `value` for one of `aggregate`'s declared attributes, by name.
|
|
38
20
|
#
|
|
39
|
-
# @param aggregate [Bluebook::Aggregate, Bluebook::Entity]
|
|
40
|
-
# `name`
|
|
21
|
+
# @param aggregate [Bluebook::Aggregate, Bluebook::Entity] construct to look
|
|
22
|
+
# up `name` on
|
|
41
23
|
# @param name [String, Symbol] the declared attribute name
|
|
42
24
|
# @param value [Object] the raw value to coerce
|
|
43
|
-
# @return [Runtime::Value, Object, nil]
|
|
44
|
-
# declares no such attribute
|
|
45
|
-
# @raise [Runtime::TypeMismatch] if `value` cannot be coerced to the
|
|
46
|
-
#
|
|
47
|
-
# @raise [Runtime::
|
|
48
|
-
# attribute's own value-object type does not declare
|
|
49
|
-
# @raise [Runtime::InvariantViolation] if a coerced value object breaks one
|
|
50
|
-
# of its own invariants
|
|
25
|
+
# @return [Runtime::Value, Object, nil] the coerced value, or `value`
|
|
26
|
+
# unchanged if `aggregate` declares no such attribute
|
|
27
|
+
# @raise [Runtime::TypeMismatch] if `value` cannot be coerced to the declared type
|
|
28
|
+
# @raise [Runtime::UnknownArgument] if `value` is a Hash naming an undeclared field
|
|
29
|
+
# @raise [Runtime::InvariantViolation] if a coerced value object breaks an invariant
|
|
51
30
|
def for(aggregate, name, value)
|
|
52
31
|
attribute = aggregate.attribute(name)
|
|
53
32
|
return value unless attribute
|
|
@@ -55,60 +34,18 @@ module Hecks
|
|
|
55
34
|
for_attribute(aggregate, attribute, value)
|
|
56
35
|
end
|
|
57
36
|
|
|
58
|
-
#
|
|
59
|
-
#
|
|
60
|
-
# has no shape left to branch on), `list` second (a list of
|
|
61
|
-
# elements, hydrated as entities), then — inside `coerced =` —
|
|
62
|
-
# `composite` (the type names a declared value object, rebuilt
|
|
63
|
-
# recursively via `build`) with `scalar` as what's left once
|
|
64
|
-
# neither of those applies (the raw value, passed through
|
|
65
|
-
# unchanged).
|
|
66
|
-
# `boundary: false` is the query door (`QueryInterpreter#normalize_args`):
|
|
67
|
-
# a query attribute's declared type names the argument for callers and
|
|
68
|
-
# generators, never a runtime shape — comparison unwraps both sides
|
|
69
|
-
# itself, so a `reference: {value: ...}` offered against a `String`
|
|
70
|
-
# query field is the documented allowance (see banking's own
|
|
71
|
-
# `Account.OpenForCustomer`), not a C3.8 mismatch.
|
|
72
|
-
# `argument: true` is the command/entity/port argument door only
|
|
73
|
-
# (`Interpreting#normalize_args`, every command/entity/port
|
|
74
|
-
# dispatch): the one place a nil for a non-optional attribute is
|
|
75
|
-
# the caller leaving a required argument empty (C3.7), absorbed
|
|
76
|
-
# via the type's own field defaults when every field has one
|
|
77
|
-
# (`nil_argument` below). Every other caller — `sets` copying an
|
|
78
|
-
# optional argument into state, hydration, entity elements,
|
|
79
|
-
# identity, defaults — is state assembly, where nil is a
|
|
80
|
-
# legitimate "absent is not empty" value the aggregate's own
|
|
81
|
-
# attribute may hold. `QueryInterpreter#normalize_args` never
|
|
82
|
-
# passes `argument: true` — a null required value-object-typed
|
|
83
|
-
# query argument is checked, and refused, entirely on its own
|
|
84
|
-
# side (`null_vo_argument!`, query_interpreter.rb) precisely so
|
|
85
|
-
# it does not reach this default-absorbing fallback (QualityControl
|
|
86
|
-
# BUG#36 — a query's own null VO argument must refuse regardless
|
|
87
|
-
# of any default, unlike a command's).
|
|
37
|
+
# Coerces `value` for a single, already-resolved `attribute`, branching on
|
|
38
|
+
# its declared shape (list, reference, composite, or bare scalar).
|
|
88
39
|
#
|
|
89
|
-
#
|
|
90
|
-
#
|
|
91
|
-
#
|
|
92
|
-
#
|
|
93
|
-
#
|
|
94
|
-
# @
|
|
95
|
-
#
|
|
96
|
-
#
|
|
97
|
-
# @
|
|
98
|
-
# door, where a nil for a non-optional attribute is a left-empty argument
|
|
99
|
-
# (C3.7) rather than ordinary state-assembly nil
|
|
100
|
-
# @return [Runtime::Value, Object, nil] the coerced value: a built `Value`
|
|
101
|
-
# for a composite-typed attribute, a frozen Array for a `has_many`
|
|
102
|
-
# reference or hydrated entity list, a joined identity String for a
|
|
103
|
-
# scalar reference, or `value` passed through unchanged for a bare
|
|
104
|
-
# primitive or an unknown attribute; nil for an absent optional attribute
|
|
105
|
-
# @raise [Runtime::TypeMismatch] if `value` cannot be coerced to the
|
|
106
|
-
# attribute's declared type, or a required reference/attribute is offered
|
|
107
|
-
# as a wrong-shaped value
|
|
108
|
-
# @raise [Runtime::UnknownArgument] if `value` is a Hash naming a field the
|
|
109
|
-
# attribute's own value-object type does not declare
|
|
110
|
-
# @raise [Runtime::InvariantViolation] if a coerced value object breaks one
|
|
111
|
-
# of its own invariants
|
|
40
|
+
# `boundary: false` is the query door, where a declared type documents the
|
|
41
|
+
# argument for callers/generators rather than naming a shape to enforce.
|
|
42
|
+
# `argument: true` is the command/entity/port dispatch door, where a nil for
|
|
43
|
+
# a required attribute is a left-empty argument (C3.7), not ordinary state nil.
|
|
44
|
+
#
|
|
45
|
+
# @raise [Runtime::TypeMismatch] if `value` cannot be coerced, or a required
|
|
46
|
+
# reference/attribute is a wrong-shaped value
|
|
47
|
+
# @raise [Runtime::UnknownArgument] if `value` is a Hash naming an undeclared field
|
|
48
|
+
# @raise [Runtime::InvariantViolation] if a coerced value object breaks an invariant
|
|
112
49
|
def for_attribute(aggregate, attribute, value, boundary: true, argument: false)
|
|
113
50
|
return nil_or_missing(aggregate, attribute, value, argument) if attribute.nil? || value.nil?
|
|
114
51
|
return reference_list(attribute, value) if attribute.list? && attribute.reference?
|
|
@@ -116,14 +53,9 @@ module Hecks
|
|
|
116
53
|
return hydrate_entity_list(aggregate, attribute, value) if attribute.list? # :list
|
|
117
54
|
return value unless aggregate.respond_to?(:value_object)
|
|
118
55
|
|
|
119
|
-
#
|
|
120
|
-
#
|
|
121
|
-
#
|
|
122
|
-
# is the door every argument and every head field comes through.
|
|
123
|
-
#
|
|
124
|
-
# After coercion, not before: a scalar arrives wrapped in whatever holder
|
|
125
|
-
# its type names (`{value: "append"}` for an OpName), and checking the
|
|
126
|
-
# raw payload would be checking the envelope.
|
|
56
|
+
# `admits:` is checked here, where the attribute is known — `build` only
|
|
57
|
+
# sees the value object. Checked after coercion: a scalar arrives wrapped
|
|
58
|
+
# in its type's own holder, and checking the raw payload would be wrong.
|
|
127
59
|
value_object = value_object_for(aggregate, attribute.type)
|
|
128
60
|
return bare_primitive(aggregate, attribute, value, boundary) if value_object.nil?
|
|
129
61
|
|
|
@@ -137,22 +69,9 @@ module Hecks
|
|
|
137
69
|
coerced
|
|
138
70
|
end
|
|
139
71
|
|
|
140
|
-
#
|
|
141
|
-
#
|
|
142
|
-
#
|
|
143
|
-
# required attribute is
|
|
144
|
-
# refused as the field the caller left empty — a value object is
|
|
145
|
-
# built from no fields, so its first required field refuses with
|
|
146
|
-
# exactly the wording the Rust side's `from_json` gives it
|
|
147
|
-
# ("Money.cents expects Integer, got nil"), and a bare scalar
|
|
148
|
-
# refuses through `check_bare_primitive`'s own wording. Lists and
|
|
149
|
-
# references keep their nil passthrough (their absence is an empty
|
|
150
|
-
# relationship, `validate_relationship_cardinality`'s business).
|
|
151
|
-
# The `attribute.nil?`/`value.nil?` branch of `for_attribute`,
|
|
152
|
-
# pulled out on its own — an unknown attribute has no shape left
|
|
153
|
-
# to branch on, and a nil value is either an ordinary absence
|
|
154
|
-
# (state assembly, hydration, a query ask) or, at the argument
|
|
155
|
-
# door only, `nil_argument`'s own C3.7 refusal.
|
|
72
|
+
# A required argument's nil is refused (C3.7); state assembly, hydration
|
|
73
|
+
# and query nils pass through unchanged. Lists/references keep their own
|
|
74
|
+
# nil passthrough regardless of `argument`.
|
|
156
75
|
private def nil_or_missing(aggregate, attribute, value, argument)
|
|
157
76
|
return value if attribute.nil? || !value.nil?
|
|
158
77
|
|
|
@@ -172,31 +91,21 @@ module Hecks
|
|
|
172
91
|
expected: attribute.type, offered: "nil")
|
|
173
92
|
end
|
|
174
93
|
|
|
175
|
-
# A bare primitive is
|
|
176
|
-
#
|
|
177
|
-
#
|
|
178
|
-
# TypeMismatch refusal here, never an evaluation fault later. Its
|
|
179
|
-
# `admits:` set is still checked, exactly as a value object's is.
|
|
94
|
+
# A bare primitive is boundary-checked too (C3.8) — wrong-typed input
|
|
95
|
+
# refuses here as a TypeMismatch, never later as a broken predicate.
|
|
96
|
+
# Its `admits:` set is still checked, exactly as a value object's is.
|
|
180
97
|
private def bare_primitive(aggregate, attribute, value, boundary)
|
|
181
98
|
check_bare_primitive(aggregate, attribute, value) if boundary
|
|
182
99
|
admit_declared_set(aggregate, attribute, value)
|
|
183
100
|
value
|
|
184
101
|
end
|
|
185
102
|
|
|
186
|
-
# Resolves the value-object class `type` names
|
|
187
|
-
# declarations first, then its chapter's other aggregates.
|
|
188
|
-
#
|
|
189
|
-
# Aggregate-local value objects remain authoritative, which permits
|
|
190
|
-
# intentional duplication. An ordinary fact may also name an identity
|
|
191
|
-
# value object declared on another aggregate; that shape is borrowed
|
|
192
|
-
# only when every chapter declaration with the name agrees.
|
|
103
|
+
# Resolves the value-object class `type` names: `aggregate`'s own
|
|
104
|
+
# declarations first, then its chapter's other aggregates if they agree.
|
|
193
105
|
#
|
|
194
|
-
# @param aggregate [Bluebook::Aggregate, Bluebook::Entity]
|
|
195
|
-
# search first
|
|
106
|
+
# @param aggregate [Bluebook::Aggregate, Bluebook::Entity] construct to search first
|
|
196
107
|
# @param type [String, Symbol, #to_s] the declared type name to resolve
|
|
197
|
-
# @return [Class, nil] the `Bluebook::ValueObject` subclass `type` names
|
|
198
|
-
# if `aggregate` declares none, and its chapter's other aggregates disagree
|
|
199
|
-
# on the shape of every same-named one (or declare none either)
|
|
108
|
+
# @return [Class, nil] the `Bluebook::ValueObject` subclass `type` names, or nil
|
|
200
109
|
def value_object_for(aggregate, type)
|
|
201
110
|
local = aggregate.value_object(type)
|
|
202
111
|
return local if local
|
|
@@ -214,18 +123,11 @@ module Hecks
|
|
|
214
123
|
# Coerces a reference-typed attribute's offered value into the target's own
|
|
215
124
|
# canonical identity string.
|
|
216
125
|
#
|
|
217
|
-
# Retained relationships store canonical target identities, not Ruby
|
|
218
|
-
# Value wrappers. Raw scalar IDs remain a compatibility input. A named
|
|
219
|
-
# identity VO omits its minted aggregate field at the command boundary;
|
|
220
|
-
# a bespoke compound VO may instead name the target heads directly.
|
|
221
|
-
# Neither form requires reverse-splitting a canonical ID.
|
|
222
|
-
#
|
|
223
126
|
# @param attribute [Bluebook::Attribute] the reference-typed attribute
|
|
224
|
-
# @param value [Object]
|
|
225
|
-
#
|
|
226
|
-
# @return [String, Object] the joined
|
|
227
|
-
#
|
|
228
|
-
# unresolvable target, or a shape `sole_scalar_identity` cannot unwrap)
|
|
127
|
+
# @param value [Object] a bare scalar identity, a `Runtime::Value`, or a Hash
|
|
128
|
+
# naming the target's own identity fields
|
|
129
|
+
# @return [String, Object] the joined identity String, or `value` unchanged
|
|
130
|
+
# when it cannot be resolved
|
|
229
131
|
def reference_identity(attribute, value)
|
|
230
132
|
return value unless value.is_a?(self) || value.is_a?(Hash)
|
|
231
133
|
|
|
@@ -245,42 +147,13 @@ module Hecks
|
|
|
245
147
|
Naming.identity(parts)
|
|
246
148
|
end
|
|
247
149
|
|
|
248
|
-
#
|
|
249
|
-
#
|
|
250
|
-
#
|
|
251
|
-
# QualityControl BUG#121, `qa/stress_domains/generated_revalued_
|
|
252
|
-
# shape`'s own `Hangar.Repoint`) instead of naming the target's
|
|
253
|
-
# own identity field(s) directly. `identity_part`'s path walk
|
|
254
|
-
# above only ever matches an incoming shape that already uses the
|
|
255
|
-
# target's own field names (or is the target's own identity value
|
|
256
|
-
# object, `direct_identity_head`) — an ad hoc wrapper around a
|
|
257
|
-
# bare scalar under some other field name (`VenueHandle`'s own
|
|
258
|
-
# `value`, not `Venue`'s own `code`) fails every path lookup, and
|
|
259
|
-
# without this would fall through to the unresolved `return value`
|
|
260
|
-
# above, storing the wrapped Value. That leaves one reference field on
|
|
261
|
-
# one aggregate holding two different shapes depending on which
|
|
262
|
-
# command last wrote it — `Open`'s own bare `reference_to Venue`
|
|
263
|
-
# argument was never wrapped in the first place (the top guard
|
|
264
|
-
# clause passes a bare scalar straight through), so it always
|
|
265
|
-
# stored the canonical bare identity this class's own header
|
|
266
|
-
# comment promises ("canonical target identities, not Ruby Value
|
|
267
|
-
# wrappers") — while `Repoint` silently kept the wrapper instead.
|
|
268
|
-
#
|
|
269
|
-
# Unambiguous only when both sides admit exactly one scalar: the
|
|
270
|
-
# target names exactly one identity path (`paths.one?` — a
|
|
271
|
-
# compound identity has no single field either side could stand
|
|
272
|
-
# in for) and the offered value is itself a single-attribute
|
|
273
|
-
# value object (`sole_attribute` — a multi-field VO has no one
|
|
274
|
-
# scalar to unwrap either). `materialize_unwrapped` recurses
|
|
275
|
-
# through any further single-field wrapping the same way it
|
|
276
|
-
# already does for `Value.materialize_unwrapped`'s other callers,
|
|
277
|
-
# landing on the bare scalar `Open`'s own path already produces
|
|
278
|
-
# for the identical target field.
|
|
150
|
+
# Unwraps `value` to a bare scalar when `paths` names exactly one identity
|
|
151
|
+
# field and `value` is itself a single-attribute value object — the only
|
|
152
|
+
# case where unwrapping cannot be ambiguous.
|
|
279
153
|
#
|
|
280
154
|
# @param value [Object] the offered reference value to unwrap
|
|
281
155
|
# @param paths [Array<String>] the target's own declared identity paths
|
|
282
|
-
# @return [Object, nil] the unwrapped scalar
|
|
283
|
-
# value object and `paths` names exactly one path; nil otherwise
|
|
156
|
+
# @return [Object, nil] the unwrapped scalar, or nil if it does not apply
|
|
284
157
|
def sole_scalar_identity(value, paths)
|
|
285
158
|
return nil unless value.is_a?(self) && paths.one?
|
|
286
159
|
return nil unless value.value_object.sole_attribute
|
|
@@ -288,14 +161,11 @@ module Hecks
|
|
|
288
161
|
materialize_unwrapped(value)
|
|
289
162
|
end
|
|
290
163
|
|
|
291
|
-
# Whether `value` is itself the target's own
|
|
292
|
-
# value object — pure, self-contained: reads only `value` and
|
|
293
|
-
# `target`, decides nothing about any particular path.
|
|
164
|
+
# Whether `value` is itself the target's own single identity value object.
|
|
294
165
|
#
|
|
295
166
|
# @param value [Object] the offered reference value to check
|
|
296
167
|
# @param target [Bluebook::Aggregate] the reference's own resolved target
|
|
297
|
-
# @return [String, nil] the target's
|
|
298
|
-
# when `value` is that head's own declared value-object type; nil otherwise
|
|
168
|
+
# @return [String, nil] the target's identity head name, or nil
|
|
299
169
|
def direct_identity_head(value, target)
|
|
300
170
|
return nil unless value.is_a?(self) && target.identity_heads.one?
|
|
301
171
|
|
|
@@ -303,29 +173,21 @@ module Hecks
|
|
|
303
173
|
target.attribute(head)&.type.to_s == value.type_name ? head.to_s : nil
|
|
304
174
|
end
|
|
305
175
|
|
|
306
|
-
# One identity path's own value out of the materialized hash
|
|
307
|
-
#
|
|
308
|
-
# `direct_identity_head`, then walking the rest. Pure given its
|
|
309
|
-
# three inputs; extracted from `reference_identity` alongside
|
|
310
|
-
# `direct_identity_head` above purely to keep that method to its
|
|
311
|
-
# own guard-clause shape.
|
|
176
|
+
# One identity path's own value out of the materialized hash, stripping a
|
|
177
|
+
# leading segment already covered by `direct_identity_head`.
|
|
312
178
|
#
|
|
313
|
-
# @param materialized [Hash, Object] the offered reference value
|
|
314
|
-
# materialized to a plain Hash (or scalar, for a path that finds nothing)
|
|
179
|
+
# @param materialized [Hash, Object] the offered reference value as a Hash
|
|
315
180
|
# @param path [String, Symbol] one dotted identity path to dig
|
|
316
|
-
# @param direct_head [String, nil]
|
|
317
|
-
#
|
|
318
|
-
# @return [Object, nil] the value found by walking `path`'s segments; nil if
|
|
319
|
-
# any segment is missing or a non-Hash is dug into before the path ends
|
|
181
|
+
# @param direct_head [String, nil] leading segment to strip, if present
|
|
182
|
+
# @return [Object, nil] the value found by walking `path`, or nil if missing
|
|
320
183
|
def identity_part(materialized, path, direct_head)
|
|
321
184
|
segments = path.to_s.split(".")
|
|
322
185
|
segments.shift if direct_head && segments.first == direct_head
|
|
323
186
|
segments.reduce(materialized) do |held, segment|
|
|
324
187
|
next nil unless held.is_a?(Hash)
|
|
325
188
|
|
|
326
|
-
# `key?` decides which spelling answers
|
|
327
|
-
#
|
|
328
|
-
# other spelling (usually absent) and read as `nil`.
|
|
189
|
+
# `key?` decides which spelling answers — a genuinely-held `false`
|
|
190
|
+
# must not fall through to the other spelling and read as `nil`.
|
|
329
191
|
sym = segment.to_sym
|
|
330
192
|
held.key?(sym) ? held[sym] : held[segment]
|
|
331
193
|
end
|
|
@@ -350,40 +212,21 @@ module Hecks
|
|
|
350
212
|
# defaults, nested normalization and validation run.
|
|
351
213
|
#
|
|
352
214
|
# @param value_object [Class] the target `Bluebook::ValueObject` subclass
|
|
353
|
-
# @param name [String, Symbol] the attribute or argument name, quoted in a
|
|
354
|
-
#
|
|
355
|
-
#
|
|
356
|
-
# fields, an already-built `Value` of a differently-named but same-shaped
|
|
357
|
-
# type, or a bare scalar for a single-field value object
|
|
215
|
+
# @param name [String, Symbol] the attribute or argument name, quoted in a refusal
|
|
216
|
+
# @param value [Hash, Runtime::Value, Object] a Hash of fields, an already-built
|
|
217
|
+
# `Value`, or a bare scalar for a single-field value object
|
|
358
218
|
# @return [Hash{Symbol => Object}] the offered fields, keyed by attribute name
|
|
359
219
|
# @raise [Runtime::TypeMismatch] if `value` is a bare scalar and `value_object`
|
|
360
220
|
# declares more than one field
|
|
361
221
|
def fields_for(value_object, name, value)
|
|
362
222
|
return value.transform_keys(&:to_sym) if value.is_a?(Hash)
|
|
363
|
-
#
|
|
364
|
-
#
|
|
365
|
-
# PositiveMoney into an Account's Money balance). Rebuild the target
|
|
366
|
-
# type from its state; callers at the public boundary still have to
|
|
367
|
-
# supply an object rather than a scalar.
|
|
223
|
+
# A same-shaped value object may fill a differently-named slot (e.g.
|
|
224
|
+
# PositiveMoney into an Account's Money balance) — rebuild from its state.
|
|
368
225
|
return value.to_h if value.is_a?(self)
|
|
369
226
|
|
|
370
|
-
#
|
|
371
|
-
#
|
|
372
|
-
# value
|
|
373
|
-
# #from_identifier already establishes for identity coercion
|
|
374
|
-
# (`build(value_object, { fields.first.name => identifier }) if
|
|
375
|
-
# fields.size == 1`), made consistent here for mutation
|
|
376
|
-
# coercion too. Real, corpus-wide gap: a synthesised single-
|
|
377
|
-
# field wrapper (Part 3a's bare-primitive auto-synthesis, the
|
|
378
|
-
# norm for a VO-typed aggregate field) is exactly the shape
|
|
379
|
-
# #rewrap_arithmetic_result hands back a raw scalar result to
|
|
380
|
-
# -- without this, every phantom-field increment/multiply on a
|
|
381
|
-
# single-field-wrapped attribute refused with "pass its fields
|
|
382
|
-
# as an object, not <scalar>" the instant it tried to re-wrap
|
|
383
|
-
# its own correctly-computed result. Multi-field VOs still
|
|
384
|
-
# refuse below, unchanged -- only the genuinely unambiguous
|
|
385
|
-
# single-field case auto-wraps, matching from_identifier's own
|
|
386
|
-
# precedent exactly.
|
|
227
|
+
# A bare scalar auto-wraps into a single-field value object's sole
|
|
228
|
+
# attribute, matching `from_identifier`'s own precedent. Multi-field
|
|
229
|
+
# value objects still refuse below.
|
|
387
230
|
return { value_object.attributes.first.name => value } if value_object.attributes.size == 1
|
|
388
231
|
|
|
389
232
|
raise TypeMismatch,
|
|
@@ -392,76 +235,27 @@ module Hecks
|
|
|
392
235
|
offered: Rendering.describe(value))
|
|
393
236
|
end
|
|
394
237
|
|
|
395
|
-
# `build`'s own recursive twin of `for_attribute
|
|
396
|
-
#
|
|
397
|
-
# (`Pizza.price_cents`, a `Price`) never otherwise pass back
|
|
398
|
-
# through `fields_for`, so a bare scalar or partial Hash for one
|
|
399
|
-
# of those sails past the outer VO's own shape check (`Pizza`
|
|
400
|
-
# itself has two fields, so nothing unwraps there) and lands
|
|
401
|
-
# stored one field down exactly as handed in — found live: once
|
|
402
|
-
# the fuzzer actually generated the bare-scalar shape
|
|
403
|
-
# `fields_for` has accepted at the top level since 86727afd, a
|
|
404
|
-
# nested `Price` stored as a raw Integer broke every later
|
|
405
|
-
# dotted-path read (`pizza.price_cents.cents`) expecting one
|
|
406
|
-
# more level of Hash.
|
|
238
|
+
# `build`'s own recursive twin of `for_attribute`, normalizing a value
|
|
239
|
+
# object's own composite-typed fields into their declared shape.
|
|
407
240
|
#
|
|
408
|
-
# Stays a plain Hash, never a nested `Value` — `Value#with`
|
|
409
|
-
# header and `materialize_unwrapped`'s comment already depend on
|
|
410
|
-
# a value-object-typed field of another value object staying a
|
|
411
|
-
# plain Hash once stored, and this does not change that; it only
|
|
412
|
-
# makes sure that Hash has the shape its own type declares.
|
|
413
|
-
# `aggregate` is the one thing this needs beyond `fields_for`'s own
|
|
414
|
-
# scope — a nested type can only be resolved through `aggregate.
|
|
415
|
-
# value_object(name)`, so callers with no aggregate in reach
|
|
416
|
-
# (`Value#with`, always re-setting an already-scalar arithmetic
|
|
417
|
-
# field) simply skip this and keep their own behavior unchanged.
|
|
418
|
-
# Recurses into each nested field's own validation too, not only its
|
|
419
|
-
# shape — found live alongside the shape bug this method's header
|
|
420
|
-
# already describes: a nested `Price`/`Size` (a value-object-typed
|
|
421
|
-
# field of another value object, e.g. `Pizza.price_cents`,
|
|
422
|
-
# `Pizza.size`) had its Hash shape normalized here but never ran
|
|
423
|
-
# `validate!` — `build`, below, only ever validated the outer value
|
|
424
|
-
# object's own direct fields, so a negative `price_cents.cents` or an
|
|
425
|
-
# out-of-`one_of` `size.value` sailed through a `Pizza`-typed command
|
|
426
|
-
# argument untouched, while the exact same nested type declared as a
|
|
427
|
-
# direct, top-level command attribute (`SafeDepositBox.Rent`'s own
|
|
428
|
-
# `attribute :size, Size`) was already checked correctly. `apply_
|
|
429
|
-
# defaults` runs first, same as the outer value object gets in
|
|
430
|
-
# `build`, so a nested field's own default is filled in before its
|
|
431
|
-
# own invariants read it.
|
|
241
|
+
# Stays a plain Hash, never a nested `Value` — `Value#with` depends on that.
|
|
432
242
|
#
|
|
433
|
-
# @param aggregate [Bluebook::Aggregate,
|
|
434
|
-
#
|
|
435
|
-
#
|
|
436
|
-
# @
|
|
437
|
-
#
|
|
438
|
-
# @
|
|
439
|
-
#
|
|
440
|
-
# @return [Hash{Symbol => Object}] `fields`, with every composite-typed field
|
|
441
|
-
# normalized (and, for a non-list one, validated) into its own declared shape
|
|
442
|
-
# @raise [Runtime::TypeMismatch] if a nested field cannot be coerced to its
|
|
443
|
-
# declared type
|
|
444
|
-
# @raise [Runtime::UnknownArgument] if a nested field's own Hash names a
|
|
445
|
-
# field its declared type does not declare
|
|
446
|
-
# @raise [Runtime::InvariantViolation] if a nested field breaks one of its
|
|
447
|
-
# own invariants
|
|
243
|
+
# @param aggregate [Bluebook::Aggregate, Entity, nil] nested-type lookup scope
|
|
244
|
+
# @param value_object [Class] the value object `fields` belongs to
|
|
245
|
+
# @param fields [Hash{Symbol => Object}] the outer fields, already defaulted
|
|
246
|
+
# @return [Hash{Symbol => Object}] `fields` with composite fields normalized
|
|
247
|
+
# @raise [Runtime::TypeMismatch] if a nested field cannot be coerced
|
|
248
|
+
# @raise [Runtime::UnknownArgument] if a nested field names an undeclared key
|
|
249
|
+
# @raise [Runtime::InvariantViolation] if a nested field breaks its own invariant
|
|
448
250
|
def normalize_composite_fields(aggregate, value_object, fields)
|
|
449
251
|
return fields unless aggregate.respond_to?(:value_object)
|
|
450
252
|
|
|
451
253
|
value_object.attributes.each do |attribute|
|
|
452
254
|
next unless fields.key?(attribute.name)
|
|
453
255
|
|
|
454
|
-
# A list member
|
|
455
|
-
#
|
|
456
|
-
#
|
|
457
|
-
# holding a list loads into the same shape the live dispatch that
|
|
458
|
-
# wrote it held. Found by PR A3 (every adapter through the state
|
|
459
|
-
# codec): chess-style `sets :positions, append: { pieces:
|
|
460
|
-
# state(:pieces) }` snapshots read back from Heki/Sqlite/Postgres
|
|
461
|
-
# (and now Memory's codec copy) with raw element hashes, so a
|
|
462
|
-
# `given` comparing a snapshot piece's `id` Value to a live one
|
|
463
|
-
# never matched after a restart. Load door only: an input list
|
|
464
|
-
# member is left exactly as before, refusals unchanged.
|
|
256
|
+
# A list member hydrates the same as a top-level list, so a value
|
|
257
|
+
# read back from the store matches the shape a live dispatch wrote.
|
|
258
|
+
# Load door only — an input list member is left as offered.
|
|
465
259
|
if attribute.list?
|
|
466
260
|
fields[attribute.name] = for_attribute(aggregate, attribute, fields[attribute.name]) if trusting_stored_state?
|
|
467
261
|
next
|
|
@@ -482,13 +276,12 @@ module Hecks
|
|
|
482
276
|
fields
|
|
483
277
|
end
|
|
484
278
|
|
|
485
|
-
# Fills every declared attribute `fields`
|
|
486
|
-
# own declared `default:`, when it has one.
|
|
279
|
+
# Fills every declared attribute `fields` lacks with its own `default:`.
|
|
487
280
|
#
|
|
488
281
|
# @param value_object [Class] the `Bluebook::ValueObject` subclass whose
|
|
489
282
|
# declared defaults are read
|
|
490
283
|
# @param fields [Hash{Symbol => Object}] the offered fields; written in place
|
|
491
|
-
# @return [Hash{Symbol => Object}] `fields`, with
|
|
284
|
+
# @return [Hash{Symbol => Object}] `fields`, with defaults filled in
|
|
492
285
|
def apply_defaults(value_object, fields)
|
|
493
286
|
value_object.attributes.each_with_object(fields) do |attribute, completed|
|
|
494
287
|
completed[attribute.name] = attribute.default unless completed.key?(attribute.name) || attribute.default.nil?
|
|
@@ -496,30 +289,20 @@ module Hecks
|
|
|
496
289
|
end
|
|
497
290
|
|
|
498
291
|
# The full door a value object's own fields pass through — shared by
|
|
499
|
-
# `build`
|
|
500
|
-
#
|
|
501
|
-
# the same way, with exactly the same wording, as the identical type
|
|
502
|
-
# declared directly on a command.
|
|
292
|
+
# `build` and `normalize_composite_fields`, so a nested field refuses
|
|
293
|
+
# exactly like the same type declared directly on a command.
|
|
503
294
|
#
|
|
504
295
|
# @param value_object [Class] the `Bluebook::ValueObject` subclass to
|
|
505
296
|
# validate `fields` against
|
|
506
|
-
# @param fields [Hash{Symbol => Object}]
|
|
507
|
-
# nested-normalized fields to check
|
|
297
|
+
# @param fields [Hash{Symbol => Object}] already-defaulted, nested-normalized fields
|
|
508
298
|
# @return [void]
|
|
509
|
-
# @raise [Runtime::UnknownArgument] if `fields` names
|
|
510
|
-
#
|
|
511
|
-
# @raise [Runtime::
|
|
512
|
-
# or pattern-constrained field is the wrong shape, or a scalar field
|
|
513
|
-
# arrives as a composite
|
|
514
|
-
# @raise [Runtime::InvariantViolation] if `fields` breaks one of
|
|
515
|
-
# `value_object`'s own declared invariants
|
|
299
|
+
# @raise [Runtime::UnknownArgument] if `fields` names an undeclared key
|
|
300
|
+
# @raise [Runtime::TypeMismatch] if a required field is missing or wrong-shaped
|
|
301
|
+
# @raise [Runtime::InvariantViolation] if `fields` breaks a declared invariant
|
|
516
302
|
def validate!(value_object, fields)
|
|
517
|
-
# C6.3
|
|
518
|
-
#
|
|
519
|
-
#
|
|
520
|
-
# invariant never makes an old record unreadable (migration is
|
|
521
|
-
# the era system's job). `hydrate` — the one load door — sets
|
|
522
|
-
# the flag; every input door leaves it unset.
|
|
303
|
+
# C6.3 — a value object validates on construction from input only; state
|
|
304
|
+
# read back from the store is trusted as written, so tightening an
|
|
305
|
+
# invariant never makes an old record unreadable. `hydrate` sets this flag.
|
|
523
306
|
return if trusting_stored_state?
|
|
524
307
|
|
|
525
308
|
check_unknown_fields(value_object, fields)
|
|
@@ -545,16 +328,12 @@ module Hecks
|
|
|
545
328
|
# @param value_object [Class] the `Bluebook::ValueObject` subclass to build
|
|
546
329
|
# @param fields [Hash{Symbol, String => Object}] the offered field values,
|
|
547
330
|
# either key spelling
|
|
548
|
-
# @param aggregate [Bluebook::Aggregate, Bluebook::Entity, nil]
|
|
549
|
-
#
|
|
550
|
-
# normalization entirely
|
|
331
|
+
# @param aggregate [Bluebook::Aggregate, Bluebook::Entity, nil] construct a
|
|
332
|
+
# nested composite field resolves against; nil skips nested normalization
|
|
551
333
|
# @return [Runtime::Value] the built, validated value object
|
|
552
|
-
# @raise [Runtime::UnknownArgument] if
|
|
553
|
-
#
|
|
554
|
-
# @raise [Runtime::
|
|
555
|
-
# coerced to its declared type
|
|
556
|
-
# @raise [Runtime::InvariantViolation] if the built value object (or a
|
|
557
|
-
# nested one) breaks one of its own invariants
|
|
334
|
+
# @raise [Runtime::UnknownArgument] if a field names an undeclared key
|
|
335
|
+
# @raise [Runtime::TypeMismatch] if a field cannot be coerced to its declared type
|
|
336
|
+
# @raise [Runtime::InvariantViolation] if the built value object breaks an invariant
|
|
558
337
|
def build(value_object, fields, aggregate = nil)
|
|
559
338
|
fields = apply_defaults(value_object, fields.transform_keys(&:to_sym))
|
|
560
339
|
fields = normalize_composite_fields(aggregate, value_object, fields)
|
|
@@ -562,22 +341,15 @@ module Hecks
|
|
|
562
341
|
new(value_object, fields)
|
|
563
342
|
end
|
|
564
343
|
|
|
565
|
-
# State arrives decoded or not at all
|
|
566
|
-
#
|
|
567
|
-
#
|
|
568
|
-
# the runtime's own callers (entity elements, the remote dispatcher's
|
|
569
|
-
# `symbolize_names:` parse, Era's audit) build symbol-keyed state
|
|
570
|
-
# themselves. A String key here is an adapter or caller that skipped
|
|
571
|
-
# the codec, so it is refused by name rather than respelled: a silent
|
|
572
|
-
# `to_sym` here would hide exactly this kind of bypass instead. Always
|
|
573
|
-
# on, because it costs one `is_a?` per key, no more than a `to_sym` would.
|
|
344
|
+
# State arrives decoded or not at all: every persistence adapter symbolizes
|
|
345
|
+
# keys before this point, so a String key here means a caller skipped that
|
|
346
|
+
# step — refused by name rather than silently respelled.
|
|
574
347
|
#
|
|
575
|
-
# @param aggregate [Bluebook::Aggregate, Bluebook::Entity]
|
|
576
|
-
#
|
|
348
|
+
# @param aggregate [Bluebook::Aggregate, Bluebook::Entity] construct whose
|
|
349
|
+
# declared attributes coerce `state`'s own values
|
|
577
350
|
# @param state [Hash{Symbol => Object}] the stored state to hydrate; every
|
|
578
351
|
# key must already be a Symbol
|
|
579
|
-
# @return [Hash{Symbol => Object}] `state`, coerced through
|
|
580
|
-
# attribute it names
|
|
352
|
+
# @return [Hash{Symbol => Object}] `state`, coerced through each declared attribute
|
|
581
353
|
# @raise [Runtime::WiringError] if `state` holds any non-Symbol key
|
|
582
354
|
def hydrate(aggregate, state)
|
|
583
355
|
undecoded = state.keys.grep_v(Symbol)
|
|
@@ -600,9 +372,8 @@ module Hecks
|
|
|
600
372
|
# Marks the block as loading trusted, already-validated stored state, so
|
|
601
373
|
# `validate!` skips its own checks for the block's duration.
|
|
602
374
|
#
|
|
603
|
-
#
|
|
604
|
-
#
|
|
605
|
-
# each other's flag.
|
|
375
|
+
# Thread-local, not a plain ivar, so two threads hydrating concurrently
|
|
376
|
+
# never see or clear each other's flag.
|
|
606
377
|
#
|
|
607
378
|
# @yield the code that should see `trusting_stored_state?` true
|
|
608
379
|
# @return [Object] the block's result
|
|
@@ -614,59 +385,27 @@ module Hecks
|
|
|
614
385
|
Thread.current[TRUSTED_LOAD_KEY] = previous
|
|
615
386
|
end
|
|
616
387
|
|
|
617
|
-
#
|
|
388
|
+
# Whether the current thread is inside a `trusting_stored_state` block.
|
|
618
389
|
#
|
|
619
|
-
# @return [Boolean] true if
|
|
620
|
-
# thread's own call stack
|
|
390
|
+
# @return [Boolean] true if on this thread's own call stack
|
|
621
391
|
def trusting_stored_state? = Thread.current[TRUSTED_LOAD_KEY] == true
|
|
622
392
|
|
|
623
|
-
#
|
|
624
|
-
#
|
|
625
|
-
#
|
|
626
|
-
#
|
|
627
|
-
#
|
|
628
|
-
#
|
|
629
|
-
#
|
|
630
|
-
#
|
|
631
|
-
# the convention every other append list actually uses it for
|
|
632
|
-
# (ValueObject::Member, ProcessManager::Handler, ... — all really
|
|
633
|
-
# `Position`/Integer-typed); `Normalise`'s own `NormalisationRule`
|
|
634
|
-
# happens to also name its own domain field "position"
|
|
635
|
-
# (bluebook.bluebook), but declares it `RuleText` (String) — so the
|
|
636
|
-
# same walk-index substitution (`Judge#v(index)`) hands it a raw
|
|
637
|
-
# Integer too, on every domain's very first boot (the language
|
|
638
|
-
# self-judges its own grammar via `MetaValidator.fresh_runtime`'s
|
|
639
|
-
# fixpoint). Confirmed (QualityControl BUG#125 investigation): the
|
|
640
|
-
# resulting value is never read back — `normalisations` is an
|
|
641
|
-
# `ELSEWHERE`/`derived` field spliced straight from
|
|
642
|
-
# `Expression::CanonicalForm.table` (assembly/contracts.rb), so the
|
|
643
|
-
# judged record holding the Integer is discarded whole — this is a
|
|
644
|
-
# walk-index/domain-field name collision inside `Judge#appends`, not
|
|
645
|
-
# a genuine semantic need for `position` to arrive numeric. Fixing
|
|
646
|
-
# that collision at its own root is a separate, larger change to
|
|
647
|
-
# self-hosted bootstrap mechanics that every domain's boot depends
|
|
648
|
-
# on; this flag only ever loosens scalar-shape checking for the
|
|
649
|
-
# meta-grammar's own value objects (RuleText, BluebookName, Position,
|
|
650
|
-
# …) that Judge itself constructs while walking a bluebook's
|
|
651
|
-
# declarations — never for a real domain's own declared value
|
|
652
|
-
# objects (PieceId, Money, …), which Judge never dispatches commands
|
|
653
|
-
# against. `offer` (judge.rb) already converts a `TypeMismatch` here
|
|
654
|
-
# into a recorded refusal rather than letting it propagate, but
|
|
655
|
-
# `MetaValidator.call` raises the instant `refusals` is non-empty
|
|
656
|
-
# (meta_validator.rb) — so, unexempted, this would fail every
|
|
657
|
-
# domain's boot, not just the language's own bootstrap. Composite
|
|
658
|
-
# shapes (Array/Hash) stay refused unconditionally, bootstrap or not
|
|
659
|
-
# — nothing Judge does ever legitimately needs those for a scalar
|
|
660
|
-
# field.
|
|
393
|
+
# `MetaValidator::Judge#send_to` walks a bluebook's own declarations through
|
|
394
|
+
# the self-hosted "Bluebook" meta-domain, and its generic append handling
|
|
395
|
+
# keys off a field's name ("position") rather than its declared type — so
|
|
396
|
+
# the language's own grammar can hand this a raw Integer for a String-typed
|
|
397
|
+
# meta field on every domain's first boot. This flag loosens
|
|
398
|
+
# `check_scalar_shapes`'s String check only for that one caller; composite
|
|
399
|
+
# shapes (Array/Hash) stay refused unconditionally, bootstrap or not, and no
|
|
400
|
+
# real domain's own declared value objects are affected.
|
|
661
401
|
BOOTSTRAP_KEY = :hecks_judge_bootstrapping
|
|
662
402
|
|
|
663
|
-
# Marks the block as `MetaValidator::Judge#send_to`'s
|
|
664
|
-
#
|
|
665
|
-
#
|
|
403
|
+
# Marks the block as `MetaValidator::Judge#send_to`'s self-hosted bootstrap
|
|
404
|
+
# dispatch, so `check_scalar_shapes` loosens its `String` check for the
|
|
405
|
+
# block's duration.
|
|
666
406
|
#
|
|
667
|
-
#
|
|
668
|
-
#
|
|
669
|
-
# each other's flag.
|
|
407
|
+
# Thread-local, not a plain ivar, so two threads bootstrapping concurrently
|
|
408
|
+
# never see or clear each other's flag.
|
|
670
409
|
#
|
|
671
410
|
# @yield the code that should see `judge_bootstrapping?` true
|
|
672
411
|
# @return [Object] the block's result
|
|
@@ -678,83 +417,22 @@ module Hecks
|
|
|
678
417
|
Thread.current[BOOTSTRAP_KEY] = previous
|
|
679
418
|
end
|
|
680
419
|
|
|
681
|
-
#
|
|
420
|
+
# Whether the current thread is inside a `judge_bootstrapping` block.
|
|
682
421
|
#
|
|
683
|
-
# @return [Boolean] true if
|
|
684
|
-
# thread's own call stack
|
|
422
|
+
# @return [Boolean] true if on this thread's own call stack
|
|
685
423
|
def judge_bootstrapping? = Thread.current[BOOTSTRAP_KEY] == true
|
|
686
424
|
|
|
687
|
-
#
|
|
688
|
-
#
|
|
689
|
-
#
|
|
690
|
-
# field is refused when the bluebook loads, so nothing has to be
|
|
691
|
-
# unwrapped later.
|
|
692
|
-
#
|
|
693
|
-
# `scalar` below is a different job and stays: rendering a value object
|
|
694
|
-
# into a column or a message, where there is no path to consult.
|
|
695
|
-
|
|
696
|
-
# A reference is the id itself — refused at the payload gate if it
|
|
697
|
-
# arrives as anything else — so there is nothing to open inside it.
|
|
698
|
-
|
|
699
|
-
# A reference is an ID, so anything else is not one.
|
|
700
|
-
#
|
|
701
|
-
# Nothing coerces a reference — `for_attribute` misses on
|
|
702
|
-
# "Reference<Account>", which is no value object's name, and hands the
|
|
703
|
-
# argument straight through. That is why the wrapped form went in
|
|
704
|
-
# unnoticed for as long as it did: there was no place it could be
|
|
705
|
-
# refused, so whatever the first caller wrote became the shape.
|
|
706
|
-
#
|
|
707
|
-
# This is that place. It sits at the payload gate rather than inside
|
|
708
|
-
# coercion because the sentence names the command, and `for_attribute`
|
|
709
|
-
# never learns which command it is serving.
|
|
425
|
+
# A reference is an ID; refused at the payload gate if it arrives as a
|
|
426
|
+
# Hash or `Runtime::Value` instead. `nil` stays legitimate for an optional
|
|
427
|
+
# reference — a required reference's own `nil` is refused elsewhere (C3.7).
|
|
710
428
|
#
|
|
711
|
-
#
|
|
712
|
-
#
|
|
713
|
-
#
|
|
714
|
-
# `null` would sail through here untouched — nothing but Hash/Value
|
|
715
|
-
# ever refused — then get `.to_s`'d into a lookup key by `CommandRules::
|
|
716
|
-
# References#reference_key` ("true", "false", "[8, 8]") and answer
|
|
717
|
-
# NotFound, or, for `null`, skip the lookup outright
|
|
718
|
-
# (`next if held.nil?`, command_rules/references.rb) and let the
|
|
719
|
-
# command run on to whatever its own `given` happened to say — a
|
|
720
|
-
# shape error misreading as a missing record, or as an unrelated
|
|
721
|
-
# domain refusal. Rust's generated `from_json` requires a JSON
|
|
722
|
-
# string for a required reference field before anything else runs
|
|
723
|
-
# (`JoinArgs.sponsor: expected String`); this closes the same gate
|
|
724
|
-
# at the same DISPATCH_ORDER step Ruby already runs it at
|
|
725
|
-
# (`normalize_args`, `Vocabulary::AggregateDispatchOrder`/
|
|
726
|
-
# `EntityDispatchOrder`), strictly before `resolve_references` ever
|
|
727
|
-
# receives a value to look up — so the two engines now agree on
|
|
728
|
-
# both kind and order, not just kind.
|
|
729
|
-
#
|
|
730
|
-
# `nil` stays legitimate for a `reference_to ..., optional: true`
|
|
731
|
-
# argument (`Improvement.Open`'s own `reference_to Angle, optional:
|
|
732
|
-
# true` — `qa/bluebook/quality_control.bluebook`): the caller
|
|
733
|
-
# genuinely may have nothing to name yet, and `nil_argument`
|
|
734
|
-
# (interpreting.rb) already passes an optional reference's `nil`
|
|
735
|
-
# through untouched. A required reference offered as `null` is a
|
|
736
|
-
# caller leaving a required argument empty in every other sense
|
|
737
|
-
# this runtime already refuses (C3.7) — refusing it here, rather
|
|
738
|
-
# than falling through to `resolve_references`' own nil-skip and
|
|
739
|
-
# then whatever the command's `given` happens to say, is what
|
|
740
|
-
# actually names the empty argument instead of something else.
|
|
741
|
-
#
|
|
742
|
-
# A `has_many` reference's own Array shape is still never refused
|
|
743
|
-
# by its wrapper (`Array(value).find { ... }` only inspects the
|
|
744
|
-
# list's elements) — a reference is never a scalar list-of-lists
|
|
745
|
-
# today, and inventing a rule for a shape the language cannot
|
|
746
|
-
# declare is how decoration gets written. `reference_list` (below)
|
|
747
|
-
# already owns "not an Array at all" for that case.
|
|
748
|
-
#
|
|
749
|
-
# @param command [Class] the command class (`Bluebook::Command` subclass)
|
|
750
|
-
# `attribute` is declared on, named in a refusal
|
|
751
|
-
# @param attribute [Bluebook::Attribute] the attribute to check; a no-op
|
|
752
|
-
# unless it is reference-typed
|
|
429
|
+
# @param command [Class] the command `attribute` is declared on, named in a refusal
|
|
430
|
+
# @param attribute [Bluebook::Attribute] the attribute to check; a no-op unless
|
|
431
|
+
# reference-typed
|
|
753
432
|
# @param value [Object] the offered value
|
|
754
433
|
# @return [void]
|
|
755
|
-
# @raise [Runtime::TypeMismatch] if `value` (or, for
|
|
756
|
-
#
|
|
757
|
-
# identity, or a required scalar reference is anything but a String
|
|
434
|
+
# @raise [Runtime::TypeMismatch] if `value` (or an element, for `has_many`) is a
|
|
435
|
+
# Hash or `Runtime::Value` rather than a plain identity
|
|
758
436
|
def refuse_object_reference(command, attribute, value)
|
|
759
437
|
return unless attribute.reference?
|
|
760
438
|
|
|
@@ -775,13 +453,8 @@ module Hecks
|
|
|
775
453
|
known_by: known_by(attribute))
|
|
776
454
|
end
|
|
777
455
|
|
|
778
|
-
# "an object" for the Hash/Value shape
|
|
779
|
-
#
|
|
780
|
-
# `spec/runtime/reference_shape_spec.rb`, kept unchanged by BUG#27's
|
|
781
|
-
# widening. `Rendering.describe` for everything else: `true`,
|
|
782
|
-
# `false`, `nil`, `[8, 8]` — the same rendering every other
|
|
783
|
-
# TypeMismatch in this file already uses for "here is what you
|
|
784
|
-
# actually sent."
|
|
456
|
+
# "an object" for the Hash/Value shape; `Rendering.describe` otherwise —
|
|
457
|
+
# the same rendering every other TypeMismatch in this file uses.
|
|
785
458
|
#
|
|
786
459
|
# @param value [Object] the wrongly-shaped offered value to describe
|
|
787
460
|
# @return [String] `"an object"` for a Hash or `Runtime::Value`; otherwise
|
|
@@ -792,22 +465,13 @@ module Hecks
|
|
|
792
465
|
Rendering.describe(value)
|
|
793
466
|
end
|
|
794
467
|
|
|
795
|
-
# "(Account is known by number)" — what to send instead. No article,
|
|
796
|
-
#
|
|
797
|
-
# letter, and a refusal pinned byte-for-byte should not hinge on an
|
|
798
|
-
# article-choosing rule. Silent when the target is another chapter's,
|
|
799
|
-
# where this runtime cannot see what it is known by.
|
|
800
|
-
#
|
|
801
|
-
# Every head, because a caller has to pass every one. This read
|
|
802
|
-
# `identified_by`, which is the single head and is nil the moment an
|
|
803
|
-
# identity has two parts — so a composite target fell through the guard
|
|
804
|
-
# and the refusal went silent exactly where it had the most to say. A
|
|
805
|
-
# single-path target reads as it always did.
|
|
468
|
+
# "(Account is known by number)" — what to send instead. No article, since
|
|
469
|
+
# "an Account" vs "a Customer" would make a pinned refusal hinge on spelling.
|
|
806
470
|
#
|
|
807
471
|
# @param attribute [Bluebook::Attribute] the reference-typed attribute to
|
|
808
|
-
# describe the target's
|
|
809
|
-
# @return [String] `" (Target is known by head1, head2)"`, or `""`
|
|
810
|
-
# target
|
|
472
|
+
# describe the target's identity heads for
|
|
473
|
+
# @return [String] `" (Target is known by head1, head2)"`, or `""` if the
|
|
474
|
+
# target or its identity heads cannot be resolved
|
|
811
475
|
def known_by(attribute)
|
|
812
476
|
heads = Array(attribute.type.resolve&.identity_heads)
|
|
813
477
|
return "" if heads.empty?
|
|
@@ -818,12 +482,9 @@ module Hecks
|
|
|
818
482
|
# Renders a value object into the bare scalar its one field holds — for a
|
|
819
483
|
# column or a message, where there is no path to consult.
|
|
820
484
|
#
|
|
821
|
-
# @param value [Object] the value to render; passed through unless a
|
|
822
|
-
#
|
|
823
|
-
# @
|
|
824
|
-
# otherwise its one field's own value
|
|
825
|
-
# @raise [Runtime::TypeMismatch] if `value` is a `Runtime::Value` with more
|
|
826
|
-
# than one field
|
|
485
|
+
# @param value [Object] the value to render; passed through unless a `Runtime::Value`
|
|
486
|
+
# @return [Object] `value` unchanged, or its one field's own value
|
|
487
|
+
# @raise [Runtime::TypeMismatch] if `value` has more than one field
|
|
827
488
|
def scalar(value)
|
|
828
489
|
return value unless value.is_a?(self)
|
|
829
490
|
|
|
@@ -835,17 +496,13 @@ module Hecks
|
|
|
835
496
|
|
|
836
497
|
# Coerces a derived identity string back into `attribute`'s own declared type.
|
|
837
498
|
#
|
|
838
|
-
# @param aggregate [Bluebook::Aggregate, Bluebook::Entity]
|
|
499
|
+
# @param aggregate [Bluebook::Aggregate, Bluebook::Entity] construct
|
|
839
500
|
# `attribute` is declared on
|
|
840
|
-
# @param attribute [Bluebook::Attribute] the identity attribute to coerce
|
|
841
|
-
#
|
|
842
|
-
# @
|
|
843
|
-
#
|
|
844
|
-
# @
|
|
845
|
-
# `attribute`'s type names a single-field value object; `identifier`
|
|
846
|
-
# unchanged otherwise
|
|
847
|
-
# @raise [Runtime::TypeMismatch] if `attribute`'s type names a value object
|
|
848
|
-
# with more than one field
|
|
501
|
+
# @param attribute [Bluebook::Attribute] the identity attribute to coerce for
|
|
502
|
+
# @param identifier [String, Object] the derived identity
|
|
503
|
+
# @return [Runtime::Value, String, Object] a built value object for a
|
|
504
|
+
# single-field value-object type; `identifier` unchanged otherwise
|
|
505
|
+
# @raise [Runtime::TypeMismatch] if the type names a multi-field value object
|
|
849
506
|
def from_identifier(aggregate, attribute, identifier)
|
|
850
507
|
value_object = value_object_for(aggregate, attribute.type)
|
|
851
508
|
return identifier unless value_object
|
|
@@ -859,31 +516,7 @@ module Hecks
|
|
|
859
516
|
raise TypeMismatch, RefusalWording.render_site("TypeMismatch", "composite_identity", type: value_object.hecks_name)
|
|
860
517
|
end
|
|
861
518
|
|
|
862
|
-
#
|
|
863
|
-
# task 9): `identifier` here is always the derived identity
|
|
864
|
-
# string -- `Identity.of`/`Identity.from` intentionally return
|
|
865
|
-
# one (correct for naming a repository key), and
|
|
866
|
-
# `Runtime::Instance#materialize_identity!` calls `from_identifier`
|
|
867
|
-
# with exactly that string on every fresh hydration -- but when
|
|
868
|
-
# the identity field's own declared type is Integer/Float (not
|
|
869
|
-
# the overwhelmingly common String), seeding it straight from
|
|
870
|
-
# that string round-trips a correctly-derived identity back in
|
|
871
|
-
# as the wrong Ruby type -- and #build's own
|
|
872
|
-
# `check_numeric_fields` (added specifically to catch a genuine
|
|
873
|
-
# caller mismatch) then refused the runtime's own internal
|
|
874
|
-
# identity seed instead, on every dispatch, valid input or not.
|
|
875
|
-
#
|
|
876
|
-
# Reuses this same file's own `NUMERIC` table (declared-type ->
|
|
877
|
-
# expected-Ruby-class, already read by `check_numeric_fields`)
|
|
878
|
-
# to decide which declared types need converting, and
|
|
879
|
-
# Kernel#Integer/#Float to do the converting. A genuinely
|
|
880
|
-
# malformed identifier (should never happen, since an identity
|
|
881
|
-
# is always derived from a correctly-typed field in the first
|
|
882
|
-
# place, but this stays defensive rather than assume it) passes
|
|
883
|
-
# back unconverted, and `check_numeric_fields` refuses it
|
|
884
|
-
# exactly as it always has -- preserving its real job of
|
|
885
|
-
# catching a genuine caller mismatch, not just this migration's
|
|
886
|
-
# own runtime-internal one.
|
|
519
|
+
# Converts a derived numeric identity string back before `check_numeric_fields` runs.
|
|
887
520
|
private def coerce_identifier(field, identifier)
|
|
888
521
|
return identifier unless identifier.is_a?(String) && NUMERIC.key?(field.type.to_s)
|
|
889
522
|
|
|
@@ -905,27 +538,7 @@ module Hecks
|
|
|
905
538
|
JSON.generate(fields.sort_by { |name, _| name.to_s }.to_h)
|
|
906
539
|
end
|
|
907
540
|
|
|
908
|
-
#
|
|
909
|
-
#
|
|
910
|
-
# Without this a String sails into a numeric field and the failure surfaces
|
|
911
|
-
# later, inside a predicate, as `positive? expects a number, got "three"` —
|
|
912
|
-
# an EvaluationError, which is not a domain refusal. So the runtime broke
|
|
913
|
-
# where the domain should have said no, and the run contract recorded the
|
|
914
|
-
# crash beside genuine refusals as though the domain had judged it.
|
|
915
|
-
#
|
|
916
|
-
# C3.8 — the boundary check for an attribute whose type is a bare
|
|
917
|
-
# primitive rather than a value object: `Integer`/`Float` by exact
|
|
918
|
-
# numeric class (`NUMERIC`), `String`/booleans by rejecting only a
|
|
919
|
-
# composite shape (`COMPOSITE_SHAPES`) — a looser test than
|
|
920
|
-
# `check_scalar_shapes` holds a value object's own `String` field to
|
|
921
|
-
# (QualityControl BUG#125 has that one also refuse
|
|
922
|
-
# a non-string scalar; a bare `String` argument here still admits
|
|
923
|
-
# any other scalar — a bare-primitive
|
|
924
|
-
# attribute was never part of BUG#125's own investigation or fix,
|
|
925
|
-
# and whether it needs the same tightening, and against what real
|
|
926
|
-
# Judge dependency if any, is still open); worded by the same
|
|
927
|
-
# template with the owning construct as `type`. The Rust boundary is
|
|
928
|
-
# stricter there too, recorded in the clause.
|
|
541
|
+
# C3.8 boundary check — refuses a mistyped argument before it breaks a predicate.
|
|
929
542
|
private def check_bare_primitive(owner, attribute, value)
|
|
930
543
|
type = attribute.type.to_s
|
|
931
544
|
expected = NUMERIC[type]
|
|
@@ -946,10 +559,8 @@ module Hecks
|
|
|
946
559
|
check_numeric_bounds(owner.hecks_name, attribute.name, value)
|
|
947
560
|
end
|
|
948
561
|
|
|
949
|
-
# C3.3/C3.4 —
|
|
950
|
-
#
|
|
951
|
-
# One check for value-object fields and bare-primitive arguments
|
|
952
|
-
# alike (`check_numeric_fields` and `check_bare_primitive`).
|
|
562
|
+
# C3.3/C3.4 — value bounds enforced at every boundary: an Integer must fit
|
|
563
|
+
# signed 64 bits, a Float must be finite.
|
|
953
564
|
INT64_RANGE = (-(2**63))..((2**63) - 1)
|
|
954
565
|
|
|
955
566
|
private def check_numeric_bounds(type_name, field_name, given)
|
|
@@ -965,42 +576,8 @@ module Hecks
|
|
|
965
576
|
type: type_name, field: field_name, offered: Rendering.describe(given))
|
|
966
577
|
end
|
|
967
578
|
|
|
968
|
-
#
|
|
969
|
-
#
|
|
970
|
-
# (`CommandInterpreter::ArgumentGate#refuse_unknown_arguments`,
|
|
971
|
-
# argument_gate.rb) — reusing that method's exact refusal wording
|
|
972
|
-
# (`UnknownArgument unknown_args`, refusal_wording.rb:
|
|
973
|
-
# "{command} does not declare {unknown} — it takes {declared}")
|
|
974
|
-
# rather than inventing a new template, because every generated
|
|
975
|
-
# Rust value-object `from_json` already renders this refusal
|
|
976
|
-
# through that identical site: `rust/project/json_codec.rb`'s
|
|
977
|
-
# `emit_unknown_argument_check` (mirrored byte-for-byte in
|
|
978
|
-
# `rust/codegen/src/json_codec.rs`) emits `v.unknown_keys(&[...])`
|
|
979
|
-
# and the same "{name} does not declare {unknown} — it takes
|
|
980
|
-
# {declared}" format string for every value object's own
|
|
981
|
-
# `from_json` — `GameLabel::from_json`
|
|
982
|
-
# (rust/src/generated/chess/game.rs) is simply the first case
|
|
983
|
-
# this gap was reproduced against.
|
|
984
|
-
#
|
|
985
|
-
# Ruby never had an equivalent check anywhere in this `validate!`
|
|
986
|
-
# door before now — `fields[attribute.name]` reads only the
|
|
987
|
-
# declared attributes, so any other key a caller's Hash carried
|
|
988
|
-
# was silently ignored. `fields` here only ever holds what a
|
|
989
|
-
# caller (or `for_attribute`'s own recursive coercion) offered
|
|
990
|
-
# for this value object — built by `fields_for`'s plain
|
|
991
|
-
# key-symbolizing (never a Hash the runtime pads with bookkeeping
|
|
992
|
-
# keys of its own; confirmed by reading every call site that
|
|
993
|
-
# reaches `validate!`) — so there is nothing legitimate here to
|
|
994
|
-
# exempt.
|
|
995
|
-
#
|
|
996
|
-
# Checked first, before `check_required_fields` and everything
|
|
997
|
-
# after it — matching Rust's own `from_json`, which checks
|
|
998
|
-
# `unknown_keys` before reading a single declared field. So a
|
|
999
|
-
# Hash offering both an unrecognized key and a missing required
|
|
1000
|
-
# one (BUG#41's own second demonstration case: `label: {extra:
|
|
1001
|
-
# "bogus"}` — unknown and missing `value`) refuses the same
|
|
1002
|
-
# UnknownArgument on both engines, not two different refusal
|
|
1003
|
-
# kinds for one malformed call.
|
|
579
|
+
# A value object refuses an undeclared key the same way a command's own
|
|
580
|
+
# payload does, checked before any other field-content check.
|
|
1004
581
|
private def check_unknown_fields(value_object, fields)
|
|
1005
582
|
known = value_object.attributes.map { |attribute| attribute.name.to_sym }
|
|
1006
583
|
unknown = (fields.keys.map(&:to_sym) - known).sort
|
|
@@ -1013,19 +590,8 @@ module Hecks
|
|
|
1013
590
|
declared: declared)
|
|
1014
591
|
end
|
|
1015
592
|
|
|
1016
|
-
# C3.7 —
|
|
1017
|
-
#
|
|
1018
|
-
# one are the same absence (`fields[name]` reads nil for both), worded
|
|
1019
|
-
# as the type mismatch it is — "{type}.{field} expects {expected}, got
|
|
1020
|
-
# nil" — the identical string the Rust side's generated `from_json`
|
|
1021
|
-
# gives the same input, so the corpus can pin it on both. Checked
|
|
1022
|
-
# first among the field-content checks (after `check_unknown_fields`'s
|
|
1023
|
-
# own structural gate above, BUG#41): without this, an invariant
|
|
1024
|
-
# reading a field that never arrived would be exactly the thing that
|
|
1025
|
-
# answers "invariant violated" (or nothing at all — `ToppingName`'s
|
|
1026
|
-
# `{value: null}` would be accepted and stored). A `default:` has
|
|
1027
|
-
# already been filled in by `apply_defaults`; a list field's absence
|
|
1028
|
-
# is an empty list, never a refusal.
|
|
593
|
+
# C3.7 — every non-optional, non-list field must arrive (or construction
|
|
594
|
+
# refuses); a `default:` has already been filled in by `apply_defaults`.
|
|
1029
595
|
private def check_required_fields(value_object, fields)
|
|
1030
596
|
value_object.attributes.each do |attribute|
|
|
1031
597
|
next if attribute.optional? || attribute.list?
|
|
@@ -1056,60 +622,15 @@ module Hecks
|
|
|
1056
622
|
expected: attribute.type, offered: Rendering.describe(given))
|
|
1057
623
|
end
|
|
1058
624
|
|
|
1059
|
-
#
|
|
1060
|
-
#
|
|
1061
|
-
# real `Float`s, so `is_a?(Numeric)`/`is_a?(Float)` is true for
|
|
1062
|
-
# either. Never exercised before this, because
|
|
1063
|
-
# `ValueGenerator::FLOAT_EDGE_CASES` had no non-finite value in
|
|
1064
|
-
# it — the fuzzer could not have found this on its own until
|
|
1065
|
-
# the table was widened alongside this fix. Left unchecked, a
|
|
1066
|
-
# non-finite Float reaches `CommandRules::Arithmetic#clamp`
|
|
1067
|
-
# (`current.clamp(min, max)` — `ArgumentError: comparison of
|
|
1068
|
-
# Float with X failed`, a genuine Ruby-level crash, not a
|
|
1069
|
-
# domain refusal, exactly the same failure mode this method's
|
|
1070
|
-
# own header describes for a mistyped field) or all the way to
|
|
1071
|
-
# storage, where `JSON.generate`/`#to_json` raises
|
|
1072
|
-
# `JSON::GeneratorError: NaN/Infinity not allowed in JSON` the
|
|
1073
|
-
# moment anything tries to persist or replay it — again a raw
|
|
1074
|
-
# crash, not a refusal. `-0.0` is deliberately not refused
|
|
1075
|
-
# here: it is finite, round-trips through JSON as `-0.0`
|
|
1076
|
-
# cleanly (confirmed empirically), and is a legitimate,
|
|
1077
|
-
# meaningful float value (a signed zero), not a corruption
|
|
1078
|
-
# risk — only NaN and +/-Infinity are.
|
|
625
|
+
# `is_a?(expected)` alone waves NaN/Infinity through — both are real
|
|
626
|
+
# Floats. `-0.0` is deliberately left unchecked: finite and legitimate.
|
|
1079
627
|
check_numeric_bounds(value_object.hecks_name, attribute.name, given)
|
|
1080
628
|
end
|
|
1081
629
|
end
|
|
1082
630
|
|
|
1083
|
-
# A field
|
|
1084
|
-
#
|
|
1085
|
-
#
|
|
1086
|
-
# arrive as any other non-composite scalar either (Integer, Float,
|
|
1087
|
-
# true/false) — QualityControl BUG#125, matching Rust's generated
|
|
1088
|
-
# `from_json`, which requires a JSON string node for a String-typed
|
|
1089
|
-
# field unconditionally and refuses anything else, including a JSON
|
|
1090
|
-
# number or boolean. Without this, Ruby would tolerate exactly that
|
|
1091
|
-
# (found live: `Chess::Piece.Capture`'s `PieceId`, String-typed,
|
|
1092
|
-
# offered a bignum `id` — Ruby let it pass and failed later on an
|
|
1093
|
-
# unrelated field, Rust refused on `id` itself, immediately) — except
|
|
1094
|
-
# inside `judge_bootstrapping?` (above), the one caller genuinely
|
|
1095
|
-
# relying on that leniency; see that flag's own comment for why.
|
|
1096
|
-
#
|
|
1097
|
-
# `TrueClass`/`FalseClass` stay laxer than `check_numeric_fields`
|
|
1098
|
-
# above: for those two, this still only enforces that the shape
|
|
1099
|
-
# isn't a collection, not the exact Ruby class — narrower than the
|
|
1100
|
-
# `String` case above because BUG#125 investigated and fixed String
|
|
1101
|
-
# specifically; a boolean field's own scalar-shape tolerance is a
|
|
1102
|
-
# separate, uninvestigated question left exactly as it was. No
|
|
1103
|
-
# scalar field, of any declared type, can ever legitimately be
|
|
1104
|
-
# handed an Array or a Hash — that shape is always wrong, and always
|
|
1105
|
-
# was: `InvalidValueGenerator#array_for_scalar`'s own corruption is
|
|
1106
|
-
# deliberately built to be refused (see that file's header), and
|
|
1107
|
-
# until this check existed it sailed straight through for a
|
|
1108
|
-
# String/boolean field the way it never could for an Integer/Float
|
|
1109
|
-
# one (`check_numeric_fields` above already catches an Array offered
|
|
1110
|
-
# for those). Found live via bin/fuzz, seed 17 on the fixtures
|
|
1111
|
-
# domain : an Array standing in for a single-field identity's
|
|
1112
|
-
# declared `String`, `.to_s`'d into a record id downstream.
|
|
631
|
+
# A scalar field (String, or a boolean) must not arrive as a composite
|
|
632
|
+
# (Array/Hash) standing in for a leaf value. A String field additionally
|
|
633
|
+
# must not arrive as any other scalar, except inside `judge_bootstrapping?`.
|
|
1113
634
|
COMPOSITE_SHAPES = [Array, ::Hash].freeze
|
|
1114
635
|
NON_NUMERIC_SCALARS = %w[String TrueClass FalseClass].freeze
|
|
1115
636
|
private def check_scalar_shapes(value_object, fields)
|
|
@@ -1131,16 +652,9 @@ module Hecks
|
|
|
1131
652
|
end
|
|
1132
653
|
end
|
|
1133
654
|
|
|
1134
|
-
# A field declared with a pattern must match it
|
|
1135
|
-
#
|
|
1136
|
-
#
|
|
1137
|
-
# not look like what it claims to be is the domain saying no, and it should
|
|
1138
|
-
# say so here rather than let the wrong shape travel on and surface as a
|
|
1139
|
-
# broken predicate later.
|
|
1140
|
-
#
|
|
1141
|
-
# Which regexes may be written at all is PatternSubset's job, enforced when
|
|
1142
|
-
# the bluebook is declared — so by the time a value arrives here the pattern
|
|
1143
|
-
# is already a vetted, unambiguous one, and this is a plain match.
|
|
655
|
+
# A field declared with a pattern must match it, refused as a TypeMismatch
|
|
656
|
+
# rather than surfacing later as a broken predicate. The pattern itself is
|
|
657
|
+
# already vetted by PatternSubset when the bluebook is declared.
|
|
1144
658
|
private def check_patterns(value_object, fields)
|
|
1145
659
|
value_object.attributes.each do |attribute|
|
|
1146
660
|
pattern = attribute.pattern
|