hecks 1.3.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/hecks/adapters/driven/claude_code.rb +72 -7
- data/lib/hecks/adapters/driven/d1.rb +187 -23
- data/lib/hecks/adapters/driven/folder.rb +83 -10
- data/lib/hecks/adapters/driven/google_authentication.rb +33 -12
- data/lib/hecks/adapters/driven/governance_authorization.rb +87 -18
- data/lib/hecks/adapters/driven/heki/journal.rb +13 -4
- data/lib/hecks/adapters/driven/heki/saga_store.rb +56 -10
- data/lib/hecks/adapters/driven/heki/snapshot.rb +1 -1
- data/lib/hecks/adapters/driven/heki.rb +106 -9
- data/lib/hecks/adapters/driven/identity_registry.rb +12 -2
- data/lib/hecks/adapters/driven/in_memory_ordering.rb +25 -3
- data/lib/hecks/adapters/driven/in_process_key_vault.adapter +3 -0
- data/lib/hecks/adapters/driven/in_process_key_vault.rb +53 -0
- data/lib/hecks/adapters/driven/lambda/client.rb +67 -14
- data/lib/hecks/adapters/driven/lambda.rb +82 -35
- data/lib/hecks/adapters/driven/local_storage.rb +83 -10
- data/lib/hecks/adapters/driven/memory.rb +205 -9
- data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +21 -1
- data/lib/hecks/adapters/driven/postgres/codec.rb +27 -11
- data/lib/hecks/adapters/driven/postgres/outbox.rb +40 -2
- data/lib/hecks/adapters/driven/postgres/reconnect.rb +23 -7
- data/lib/hecks/adapters/driven/postgres/schema_builder.rb +14 -14
- data/lib/hecks/adapters/driven/postgres.rb +175 -28
- data/lib/hecks/adapters/driven/prism.rb +50 -11
- data/lib/hecks/adapters/driven/secure_random_identity.rb +3 -0
- data/lib/hecks/adapters/driven/sql_query_builder.rb +34 -22
- data/lib/hecks/adapters/driven/sqlite/codec.rb +38 -10
- data/lib/hecks/adapters/driven/sqlite/projection.rb +60 -32
- data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +12 -12
- data/lib/hecks/adapters/driven/sqlite.rb +181 -21
- data/lib/hecks/adapters/driven/system_clock.rb +3 -0
- data/lib/hecks/adapters/driven/tenant_provisioner.adapter +3 -0
- data/lib/hecks/adapters/driven/tenant_provisioner.rb +66 -0
- data/lib/hecks/adapters/driven.rb +6 -4
- data/lib/hecks/adapters/driving/github_webhook.rb +31 -18
- data/lib/hecks/behaviors/dsl.rb +60 -2
- data/lib/hecks/behaviors/expectations.rb +190 -29
- data/lib/hecks/behaviors/ir.rb +12 -1
- data/lib/hecks/behaviors/rspec.rb +9 -1
- data/lib/hecks/behaviors/runner.rb +21 -2
- data/lib/hecks/behaviors.rb +9 -1
- data/lib/hecks/bluebook/aggregate.rb +43 -13
- data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +17 -10
- data/lib/hecks/bluebook/assembly/build.rb +26 -5
- data/lib/hecks/bluebook/assembly/contract.rb +98 -23
- data/lib/hecks/bluebook/assembly/contracts.rb +59 -52
- data/lib/hecks/bluebook/assembly/marks.rb +159 -30
- data/lib/hecks/bluebook/assembly/specializer.rb +38 -21
- data/lib/hecks/bluebook/assembly.rb +32 -14
- data/lib/hecks/bluebook/attribute.rb +26 -12
- data/lib/hecks/bluebook/behaviour/aggregate.rb +43 -11
- data/lib/hecks/bluebook/behaviour/attribute.rb +18 -5
- data/lib/hecks/bluebook/behaviour/chapter.rb +76 -5
- data/lib/hecks/bluebook/behaviour/command.rb +55 -25
- data/lib/hecks/bluebook/behaviour/domain_port.rb +27 -7
- data/lib/hecks/bluebook/behaviour/entity.rb +20 -8
- data/lib/hecks/bluebook/behaviour/hexagon.rb +30 -4
- data/lib/hecks/bluebook/behaviour/lifecycle.rb +27 -6
- data/lib/hecks/bluebook/behaviour/policy.rb +42 -17
- data/lib/hecks/bluebook/behaviour/process_manager.rb +39 -8
- data/lib/hecks/bluebook/behaviour/query.rb +6 -1
- data/lib/hecks/bluebook/behaviour/read_model.rb +29 -8
- data/lib/hecks/bluebook/behaviour/traits.rb +48 -12
- data/lib/hecks/bluebook/behaviour/value_object.rb +21 -9
- data/lib/hecks/bluebook/capabilities.rb +27 -0
- data/lib/hecks/bluebook/chapter.rb +51 -9
- data/lib/hecks/bluebook/command.rb +62 -17
- data/lib/hecks/bluebook/domain_port.rb +34 -9
- data/lib/hecks/bluebook/dsl/adapter_builder.rb +24 -0
- data/lib/hecks/bluebook/dsl/aggregate_builder/sealing.rb +49 -49
- data/lib/hecks/bluebook/dsl/aggregate_builder.rb +282 -123
- data/lib/hecks/bluebook/dsl/attribute_collector.rb +112 -75
- data/lib/hecks/bluebook/dsl/binding_proxy.rb +81 -2
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +486 -117
- data/lib/hecks/bluebook/dsl/bluebook_builder.rb +179 -47
- data/lib/hecks/bluebook/dsl/bootstrap_table.rb +116 -0
- data/lib/hecks/bluebook/dsl/command_builder.rb +284 -122
- data/lib/hecks/bluebook/dsl/const_shim.rb +46 -15
- data/lib/hecks/bluebook/dsl/domain_port_builder.rb +90 -25
- data/lib/hecks/bluebook/dsl/entity_builder.rb +191 -61
- data/lib/hecks/bluebook/dsl/generic_dispatch.rb +148 -132
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +130 -30
- data/lib/hecks/bluebook/dsl/identity_declaration.rb +38 -21
- data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +27 -4
- data/lib/hecks/bluebook/dsl/policy_builder.rb +86 -36
- data/lib/hecks/bluebook/dsl/port_builder.rb +38 -7
- data/lib/hecks/bluebook/dsl/port_operation_builder.rb +56 -22
- data/lib/hecks/bluebook/dsl/process_manager_builder.rb +111 -47
- data/lib/hecks/bluebook/dsl/query_builder.rb +37 -8
- data/lib/hecks/bluebook/dsl/read_model_builder.rb +127 -52
- data/lib/hecks/bluebook/dsl/rule_reference.rb +97 -43
- data/lib/hecks/bluebook/dsl/translation_builder.rb +150 -44
- data/lib/hecks/bluebook/dsl/value_object_builder.rb +68 -20
- data/lib/hecks/bluebook/dsl/word_gate.rb +59 -53
- data/lib/hecks/bluebook/dsl/world_builder.rb +51 -8
- data/lib/hecks/bluebook/entity.rb +40 -11
- data/lib/hecks/bluebook/expression/ast_json.rb +128 -36
- data/lib/hecks/bluebook/expression/ast_reader.rb +32 -3
- data/lib/hecks/bluebook/expression/canonical_form.rb +55 -16
- data/lib/hecks/bluebook/expression/evaluator.rb +221 -43
- data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +54 -18
- data/lib/hecks/bluebook/expression/resolver.rb +369 -128
- data/lib/hecks/bluebook/hexagon.rb +35 -1
- data/lib/hecks/bluebook/lifecycle.rb +12 -1
- data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +2 -1
- data/lib/hecks/bluebook/meta_validator/judge.rb +126 -108
- data/lib/hecks/bluebook/meta_validator/plan.rb +81 -46
- data/lib/hecks/bluebook/meta_validator/port_judge.rb +3 -2
- data/lib/hecks/bluebook/meta_validator/readings.rb +200 -50
- data/lib/hecks/bluebook/meta_validator/reconstruction.rb +68 -41
- data/lib/hecks/bluebook/meta_validator/shapes.rb +166 -21
- data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +286 -46
- data/lib/hecks/bluebook/meta_validator/translation_judge.rb +11 -10
- data/lib/hecks/bluebook/meta_validator/world_judge.rb +6 -5
- data/lib/hecks/bluebook/meta_validator.rb +235 -139
- data/lib/hecks/bluebook/model_check.rb +434 -104
- data/lib/hecks/bluebook/pattern_subset.rb +32 -10
- data/lib/hecks/bluebook/policy.rb +15 -13
- data/lib/hecks/bluebook/process_manager.rb +27 -14
- data/lib/hecks/bluebook/project_discovery.rb +5 -0
- data/lib/hecks/bluebook/project_loader.rb +40 -0
- data/lib/hecks/bluebook/project_register.rb +50 -6
- data/lib/hecks/bluebook/query.rb +31 -4
- data/lib/hecks/bluebook/read_model.rb +35 -15
- data/lib/hecks/bluebook/reference.rb +26 -13
- data/lib/hecks/bluebook/smoke_test.rb +46 -23
- data/lib/hecks/bluebook/synthesizer.rb +46 -12
- data/lib/hecks/bluebook/translation.rb +34 -5
- data/lib/hecks/bluebook/value_object.rb +29 -11
- data/lib/hecks/bluebook.rb +5 -6
- data/lib/hecks/codemod.rb +138 -50
- data/lib/hecks/construct.rb +21 -7
- data/lib/hecks/corpus.rb +438 -0
- data/lib/hecks/deploy/bluebook/deploy.hecksagon +19 -0
- data/lib/hecks/doc/reference.rb +200 -31
- data/lib/hecks/embryonaut_bluebook.rb +38 -15
- data/lib/hecks/facade/cli_door.rb +69 -10
- data/lib/hecks/facade/cli_runner.rb +105 -24
- data/lib/hecks/facade/command_request.rb +23 -0
- data/lib/hecks/facade/handle.rb +155 -35
- data/lib/hecks/facade/json_door.rb +106 -25
- data/lib/hecks/facade/surface/aggregate_door.rb +50 -27
- data/lib/hecks/facade/surface/chapter.rb +26 -17
- data/lib/hecks/facade/surface.rb +16 -3
- data/lib/hecks/facade.rb +15 -4
- data/lib/hecks/forms/app.rb +46 -30
- data/lib/hecks/forms/command_form_renderer.rb +70 -9
- data/lib/hecks/forms/field_renderer.rb +142 -6
- data/lib/hecks/forms/field_shape.rb +183 -19
- data/lib/hecks/forms/html.rb +51 -7
- data/lib/hecks/forms/index_renderer.rb +14 -2
- data/lib/hecks/forms/page.rb +14 -0
- data/lib/hecks/forms/params.rb +120 -23
- data/lib/hecks/forms/port_argument.rb +14 -2
- data/lib/hecks/forms/query_form_renderer.rb +65 -2
- data/lib/hecks/forms/record_renderer.rb +60 -2
- data/lib/hecks/forms/record_table.rb +28 -1
- data/lib/hecks/forms/reference_options.rb +24 -0
- data/lib/hecks/forms/value_object_shape.rb +13 -3
- data/lib/hecks/forms.rb +24 -4
- data/lib/hecks/fqn.rb +59 -1
- data/lib/hecks/framework/bluebook/governance.bluebook +9 -0
- data/lib/hecks/framework/bluebook/privacy.bluebook +155 -0
- data/lib/hecks/framework/oidc.json +15 -0
- data/lib/hecks/framework.rb +79 -25
- data/lib/hecks/freezer.rb +27 -11
- data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +241 -106
- data/lib/hecks/fuzzing/combination_miner.rb +178 -0
- data/lib/hecks/fuzzing/concurrent_dispatch.rb +241 -45
- data/lib/hecks/fuzzing/coverage_campaign.rb +161 -0
- data/lib/hecks/fuzzing/differential.rb +192 -0
- data/lib/hecks/fuzzing/domain_generator.rb +871 -0
- data/lib/hecks/fuzzing/era_boundary.rb +68 -19
- data/lib/hecks/fuzzing/form_census.rb +121 -20
- data/lib/hecks/fuzzing/generated_domain_check.rb +171 -0
- data/lib/hecks/fuzzing/invalid_value_generator.rb +45 -6
- data/lib/hecks/fuzzing/isolated_boot.rb +137 -80
- data/lib/hecks/fuzzing/nondeterministic.rb +79 -0
- data/lib/hecks/fuzzing/persistence_parity.rb +111 -21
- data/lib/hecks/fuzzing/properties/corrections.rb +34 -9
- data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +300 -51
- data/lib/hecks/fuzzing/properties/guards.rb +86 -42
- data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +80 -32
- data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +40 -35
- data/lib/hecks/fuzzing/properties/outbox.rb +70 -32
- data/lib/hecks/fuzzing/properties/querying.rb +82 -28
- data/lib/hecks/fuzzing/properties.rb +84 -51
- data/lib/hecks/fuzzing/qa_settings.rb +164 -0
- data/lib/hecks/fuzzing/replay.rb +241 -133
- data/lib/hecks/fuzzing/rotation_priority.rb +48 -28
- data/lib/hecks/fuzzing/rust_gap_manifest.rb +139 -0
- data/lib/hecks/fuzzing/self_consistency.rb +315 -137
- data/lib/hecks/fuzzing/sequence_generator/adversary.rb +46 -40
- data/lib/hecks/fuzzing/sequence_generator/catalog.rb +18 -11
- data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +13 -12
- data/lib/hecks/fuzzing/sequence_generator/picker.rb +21 -12
- data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +59 -32
- data/lib/hecks/fuzzing/sequence_generator.rb +158 -23
- data/lib/hecks/fuzzing/shrinker.rb +309 -0
- data/lib/hecks/fuzzing/structural_skips.rb +37 -130
- data/lib/hecks/fuzzing/sweep_depth.rb +12 -4
- data/lib/hecks/fuzzing/target_capabilities.rb +107 -29
- data/lib/hecks/fuzzing/value_generator.rb +110 -22
- data/lib/hecks/fuzzing.rb +5 -0
- data/lib/hecks/grammar/evolve.rb +188 -12
- data/lib/hecks/grammar.rb +53 -7
- data/lib/hecks/ir.rb +51 -20
- data/lib/hecks/language/bluebook/bluebook.bluebook +41 -0
- data/lib/hecks/language/bluebook/policy.bluebook +11 -1
- data/lib/hecks/language/bluebook/vocabulary.bluebook +348 -13
- data/lib/hecks/language/hecksagon/hecksagon.bluebook +11 -0
- data/lib/hecks/language/oidc.json +5 -0
- data/lib/hecks/literal.rb +41 -9
- data/lib/hecks/naming.rb +112 -31
- data/lib/hecks/ports/access_control.rb +53 -2
- data/lib/hecks/ports/agent/answers.rb +83 -6
- data/lib/hecks/ports/agent.rb +119 -35
- data/lib/hecks/ports/authentication.rb +44 -4
- data/lib/hecks/ports/authorization.rb +50 -11
- data/lib/hecks/ports/clock.rb +42 -23
- data/lib/hecks/ports/extraction.rb +16 -0
- data/lib/hecks/ports/identity_assignment.rb +23 -2
- data/lib/hecks/ports/identity_generation.rb +17 -3
- data/lib/hecks/ports/identity_resolution.rb +17 -1
- data/lib/hecks/ports/key_vault.port +6 -0
- data/lib/hecks/ports/key_vault.rb +58 -0
- data/lib/hecks/ports/loading.rb +4 -0
- data/lib/hecks/ports/persistence/append_only.rb +172 -8
- data/lib/hecks/ports/persistence/binding_policy.rb +34 -0
- data/lib/hecks/ports/persistence/codec_boundary.rb +178 -0
- data/lib/hecks/ports/persistence/execution.rb +4 -0
- data/lib/hecks/ports/persistence/null_saga_store.rb +12 -1
- data/lib/hecks/ports/persistence/plugin.rb +42 -4
- data/lib/hecks/ports/persistence/plugins/era/era_check.rb +218 -25
- data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +77 -9
- data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +81 -24
- data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +29 -18
- data/lib/hecks/ports/persistence/plugins/era/lineage.rb +144 -60
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +103 -8
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/field_cache.rb +98 -23
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +282 -109
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/mint_transaction.rb +63 -25
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +118 -66
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/resumable_backfill.rb +51 -28
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +34 -5
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/transform_installer.rb +25 -12
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +129 -34
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/coverage_check.rb +51 -6
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +33 -9
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +16 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +95 -6
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager.rb +25 -2
- data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +314 -90
- data/lib/hecks/ports/persistence/plugins/era/storage_shape.rb +68 -10
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +9 -3
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_one.rb +9 -2
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +48 -8
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +16 -1
- data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +36 -5
- data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +23 -3
- data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +58 -19
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +96 -5
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/renderer.rb +15 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +9 -1
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +2 -2
- data/lib/hecks/ports/persistence/plugins/era.rb +12 -2
- data/lib/hecks/ports/persistence/remote_runtime.rb +9 -2
- data/lib/hecks/ports/persistence/repository_factory.rb +23 -3
- data/lib/hecks/ports/persistence/state_codec.rb +319 -0
- data/lib/hecks/ports/persistence.rb +36 -1
- data/lib/hecks/ports/projection.rb +61 -7
- data/lib/hecks/ports/query/in_memory.rb +41 -8
- data/lib/hecks/ports/query/ordering.rb +21 -6
- data/lib/hecks/ports/query.rb +35 -0
- data/lib/hecks/ports.rb +1 -0
- data/lib/hecks/projections/bootstrap_table.rb +147 -0
- data/lib/hecks/projections/diagrams.rb +315 -79
- data/lib/hecks/projections/glossary/html.rb +91 -3
- data/lib/hecks/projections/glossary/markdown.rb +50 -3
- data/lib/hecks/projections/glossary/mermaid.rb +49 -1
- data/lib/hecks/projections/glossary/sections.rb +1 -1
- data/lib/hecks/projections/glossary/sentences.rb +110 -5
- data/lib/hecks/projections/glossary.rb +173 -31
- data/lib/hecks/projections/ir.rb +1 -1
- data/lib/hecks/projections/model/deviations.rb +62 -17
- data/lib/hecks/projections/model.rb +75 -21
- data/lib/hecks/projections/oidc.rb +45 -8
- data/lib/hecks/projections/parser_table.rb +53 -12
- data/lib/hecks/projections/reference.rb +15 -3
- data/lib/hecks/projections/rust_vocabulary.rb +646 -0
- data/lib/hecks/projections/shape.rb +10 -2
- data/lib/hecks/projections/statements.rb +69 -22
- data/lib/hecks/projections/vocabulary.rb +26 -9
- data/lib/hecks/projections.rb +5 -3
- data/lib/hecks/projector/cli_projector.rb +245 -37
- data/lib/hecks/projector/docs_projector.rb +154 -28
- data/lib/hecks/projector/exporter.rb +104 -29
- data/lib/hecks/projector/ir_projector.rb +7 -1
- data/lib/hecks/projector/narrate_projector.rb +144 -23
- data/lib/hecks/projector/target.rb +42 -18
- data/lib/hecks/projector.rb +86 -18
- data/lib/hecks/query_ir.rb +94 -47
- data/lib/hecks/query_specification/common/comparators.rb +19 -3
- data/lib/hecks/query_specification/common/comparison.rb +109 -27
- data/lib/hecks/query_specification/common/dsl.rb +65 -9
- data/lib/hecks/query_specification/common/null_policy.rb +61 -15
- data/lib/hecks/query_specification/common/null_semantics.rb +4 -0
- data/lib/hecks/query_specification/common/options.rb +25 -0
- data/lib/hecks/query_specification/field_path.rb +69 -15
- data/lib/hecks/query_specification/hop_path.rb +57 -20
- data/lib/hecks/query_specification/read_model/specification.rb +4 -0
- data/lib/hecks/rendering.rb +9 -3
- data/lib/hecks/router/namespace_installer.rb +16 -3
- data/lib/hecks/router.rb +56 -1
- data/lib/hecks/runtime/aggregate_lock.rb +20 -11
- data/lib/hecks/runtime/boot_gates.rb +21 -3
- data/lib/hecks/runtime/caller.rb +40 -8
- data/lib/hecks/runtime/capability_graph.rb +13 -2
- data/lib/hecks/runtime/command_interpreter/argument_gate.rb +40 -41
- data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +45 -67
- data/lib/hecks/runtime/command_interpreter.rb +195 -113
- data/lib/hecks/runtime/command_rules/admissibility.rb +231 -80
- data/lib/hecks/runtime/command_rules/arithmetic.rb +154 -96
- data/lib/hecks/runtime/command_rules/authorization.rb +38 -17
- data/lib/hecks/runtime/command_rules/emission.rb +18 -1
- data/lib/hecks/runtime/command_rules/references.rb +145 -55
- data/lib/hecks/runtime/command_rules.rb +3 -0
- data/lib/hecks/runtime/dependency_planning.rb +56 -11
- data/lib/hecks/runtime/dispatcher.rb +263 -92
- data/lib/hecks/runtime/entity_element.rb +304 -67
- data/lib/hecks/runtime/entity_interpreter.rb +149 -88
- data/lib/hecks/runtime/errors.rb +37 -23
- data/lib/hecks/runtime/event.rb +15 -10
- data/lib/hecks/runtime/identity.rb +91 -23
- data/lib/hecks/runtime/instance.rb +103 -18
- data/lib/hecks/runtime/interpreting.rb +24 -16
- data/lib/hecks/runtime/invocation.rb +358 -0
- data/lib/hecks/runtime/loader.rb +105 -19
- data/lib/hecks/runtime/outbox.rb +164 -26
- data/lib/hecks/runtime/policy_interpreter.rb +73 -60
- data/lib/hecks/runtime/port_operation_interpreter.rb +42 -19
- data/lib/hecks/runtime/query_interpreter.rb +93 -74
- data/lib/hecks/runtime/reaction_invocation.rb +73 -28
- data/lib/hecks/runtime/read_model_interpreter.rb +60 -44
- data/lib/hecks/runtime/rebuild_sweep.rb +32 -4
- data/lib/hecks/runtime/reference_hop.rb +48 -6
- data/lib/hecks/runtime/refusal_wording.rb +142 -115
- data/lib/hecks/runtime/registry/saga_persistence.rb +32 -21
- data/lib/hecks/runtime/registry/verification.rb +153 -28
- data/lib/hecks/runtime/registry.rb +202 -32
- data/lib/hecks/runtime/remote_dispatcher.rb +125 -24
- data/lib/hecks/runtime/routing.rb +36 -154
- data/lib/hecks/runtime/saga_interpreter/correlation.rb +25 -27
- data/lib/hecks/runtime/saga_interpreter.rb +90 -76
- data/lib/hecks/runtime/saga_pending_dispatch.rb +12 -12
- data/lib/hecks/runtime/tenant_check.rb +33 -13
- data/lib/hecks/runtime/tenant_scope.rb +23 -5
- data/lib/hecks/runtime/value/admission.rb +75 -30
- data/lib/hecks/runtime/value/coercion.rb +555 -142
- data/lib/hecks/runtime/value/entity_list_coercion.rb +132 -60
- data/lib/hecks/runtime/value.rb +71 -21
- data/lib/hecks/runtime.rb +39 -7
- data/lib/hecks/storehouse.rb +368 -72
- data/lib/hecks/tenancy/bluebook/tenancy.bluebook +130 -0
- data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +32 -0
- data/lib/hecks/version.rb +3 -3
- data/lib/hecks/vocabulary.rb +205 -4
- data/lib/hecks.rb +91 -11
- data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +19 -11
- data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +24 -12
- data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +38 -14
- metadata +28 -2
|
@@ -8,7 +8,7 @@ module Hecks
|
|
|
8
8
|
# happened to sample," claim is realistically achievable:
|
|
9
9
|
# `Hecks::Bluebook::Expression::{Evaluator,Resolver}`. It's finite-
|
|
10
10
|
# grammar and side-effect-free (Evaluator::PROJECTION/Resolver's own
|
|
11
|
-
# `interpret` never touch a database, a clock, or randomness), but
|
|
11
|
+
# `interpret` never touch a database, a clock, or randomness), but not
|
|
12
12
|
# small enough for naive enumeration — ~20 node-producing constructs
|
|
13
13
|
# across two mutually-recursive grammar layers, unbounded recursion
|
|
14
14
|
# through dotted `Lookup` paths, `Addition`, and nested block
|
|
@@ -20,8 +20,10 @@ module Hecks
|
|
|
20
20
|
# `canonical_form.rb` aliasing bug — neither would have been caught by
|
|
21
21
|
# sampling alone, which is the whole reason this phase exists).
|
|
22
22
|
#
|
|
23
|
-
#
|
|
24
|
-
#
|
|
23
|
+
# ## Type-directed generation
|
|
24
|
+
#
|
|
25
|
+
# Type-directed, not exhaustive-over-strings: this generator only ever
|
|
26
|
+
# recurses into a sub-expression whose own type the surrounding
|
|
25
27
|
# construct actually accepts (never `"x".modulo(true)`) — mirroring,
|
|
26
28
|
# by hand, the same admitted-receiver-class tables `Resolver`'s own
|
|
27
29
|
# `interpret` enforces at runtime (`SizedType`/`ToStringType`/
|
|
@@ -36,34 +38,36 @@ module Hecks
|
|
|
36
38
|
# 3" claim for exactly the sublanguage that already bit this project
|
|
37
39
|
# twice.
|
|
38
40
|
#
|
|
39
|
-
#
|
|
40
|
-
#
|
|
41
|
+
# ## What "PROVEN" means here, precisely
|
|
42
|
+
#
|
|
43
|
+
# Every expression this generator
|
|
44
|
+
# produces is well-typed by construction (every sub-expression's type
|
|
41
45
|
# matches what its parent construct actually accepts, per this file's
|
|
42
46
|
# own TYPE_RULES). Interpreting a well-typed expression must never
|
|
43
|
-
# raise anything
|
|
47
|
+
# raise anything other than `Hecks::Bluebook::Expression::
|
|
44
48
|
# EvaluationError` — that class alone is the sublanguage's own real
|
|
45
49
|
# refusal vocabulary (a divisor that happens to be zero, a `Lookup`
|
|
46
50
|
# this run's synthetic state doesn't happen to carry); anything else
|
|
47
51
|
# escaping (a raw `TypeError`/`NoMethodError`/`ZeroDivisionError`) is
|
|
48
52
|
# exactly the crash-signature this whole file's own header comments
|
|
49
53
|
# (resolver.rb, block_predicates.rb) name as the historical bug class
|
|
50
|
-
# this sublanguage keeps almost-but-not-quite avoiding. This does
|
|
51
|
-
# prove the interpreter computes the
|
|
54
|
+
# this sublanguage keeps almost-but-not-quite avoiding. This does not
|
|
55
|
+
# prove the interpreter computes the semantically right answer (that
|
|
52
56
|
# needs a second, independent oracle — out of scope here, exactly the
|
|
53
|
-
# way rust_conformance_fuzz_spec.rb is the cross-
|
|
57
|
+
# way rust_conformance_fuzz_spec.rb is the cross-runtime half of this
|
|
54
58
|
# same idea) — only that it never crashes on well-typed input.
|
|
55
59
|
module BoundedExhaustiveExpressions
|
|
56
60
|
module_function
|
|
57
61
|
|
|
58
|
-
# `:numeric` deliberately covers
|
|
62
|
+
# `:numeric` deliberately covers both Integer and Float — the
|
|
59
63
|
# resolver's own `numeric`/`require_number` never distinguish them
|
|
60
64
|
# (resolver.rb) — so a generator that kept them as separate types
|
|
61
65
|
# would be modeling a distinction the grammar itself doesn't make.
|
|
62
66
|
TYPES = %i[numeric string boolean array nil_type].freeze
|
|
63
67
|
|
|
64
|
-
#
|
|
68
|
+
# **The palette** — deliberately small (this file's own header: "a small
|
|
65
69
|
# representative literal palette," matching the plan's own `{0, 1,
|
|
66
|
-
# -1}` example). Widening it doesn't test a different
|
|
70
|
+
# -1}` example). Widening it doesn't test a different shape of
|
|
67
71
|
# expression, only more values through the same shapes — real
|
|
68
72
|
# boundary-value coverage (Bignum, NaN, empty string, unicode) is
|
|
69
73
|
# PRD 05's job (spec/runtime/numeric_boundary_spec.rb,
|
|
@@ -75,18 +79,18 @@ module Hecks
|
|
|
75
79
|
nil_type: %w[nil].freeze
|
|
76
80
|
}.freeze
|
|
77
81
|
|
|
78
|
-
#
|
|
82
|
+
# Two synthetic attributes per type (the plan's own "2-3 fake
|
|
79
83
|
# attributes per type") — real names are irrelevant to `Resolver
|
|
80
84
|
# .parse` (it never touches state/attrs at all, confirmed directly:
|
|
81
85
|
# only `Lookup#interpret` does) and irrelevant to `interpret` beyond
|
|
82
|
-
# needing to resolve against
|
|
86
|
+
# needing to resolve against whatever synthetic state this file
|
|
83
87
|
# supplies (`synthetic_state`, below) — so any non-colliding,
|
|
84
88
|
# non-suffix-shaped name works. Never named the same as a
|
|
85
89
|
# `BLOCK_PARAM` (below) — `Resolver#fetch`'s own `attrs`-wins-over-
|
|
86
|
-
# `state` precedence means a block parameter
|
|
90
|
+
# `state` precedence means a block parameter shadows a same-named
|
|
87
91
|
# top-level attribute for the span of its own predicate, a real
|
|
88
92
|
# hazard this file sidesteps by construction rather than exercising
|
|
89
|
-
# it here (a real, cited resolver.rb hazard, not a gap in
|
|
93
|
+
# it here (a real, cited resolver.rb hazard, not a gap in this
|
|
90
94
|
# generator's own coverage claim).
|
|
91
95
|
SYNTHETIC_ATTRS = {
|
|
92
96
|
numeric: %w[num_a num_b].freeze,
|
|
@@ -95,7 +99,7 @@ module Hecks
|
|
|
95
99
|
array: %w[arr_num arr_str].freeze
|
|
96
100
|
}.freeze
|
|
97
101
|
|
|
98
|
-
# `arr_num`/`arr_str` —
|
|
102
|
+
# `arr_num`/`arr_str` — element type per array attribute, needed so
|
|
99
103
|
# a block predicate's own bound parameter (`BLOCK_PARAM`, below) is
|
|
100
104
|
# generated against the right leaf/production set for whatever it's
|
|
101
105
|
# actually bound to.
|
|
@@ -105,48 +109,53 @@ module Hecks
|
|
|
105
109
|
|
|
106
110
|
MAX_DEPTH = 3
|
|
107
111
|
|
|
108
|
-
# `state`/`attrs` — a plain Hash satisfies
|
|
112
|
+
# `state`/`attrs` — a plain Hash satisfies both (confirmed directly:
|
|
109
113
|
# `Resolver#known?` degrades to a bare index check when `state`
|
|
110
114
|
# doesn't `respond_to?(:key?)`, and `fetch` only ever needs
|
|
111
|
-
# `attrs.key?`/`attrs[]`.). Split
|
|
115
|
+
# `attrs.key?`/`attrs[]`.). Split across both, matching a real
|
|
112
116
|
# dispatch's own shape (some names come from the record's stored
|
|
113
117
|
# state, some from the command's own args) — not load-bearing for
|
|
114
|
-
#
|
|
118
|
+
# this proof (attrs wins regardless), but keeps the synthetic input
|
|
115
119
|
# closer to what `Admissibility` actually builds, in case anything
|
|
116
120
|
# here is reused for a future deeper check.
|
|
117
121
|
#
|
|
118
|
-
#
|
|
122
|
+
# **The VO-vs-scalar unwrap case, deliberately exercised** — half of
|
|
119
123
|
# `num_a`/`str_a`/`bool_a`/one array attribute's elements are bare
|
|
120
124
|
# scalars, the other half (`num_b`/`str_b`/`bool_b`/the other
|
|
121
125
|
# array's elements) are wrapped `{value: X}` — the single-field
|
|
122
126
|
# Value Object shape `Resolver#unwrap_scalar` auto-collapses.
|
|
123
127
|
# resolver.rb's own most serious historical bug in this file (the
|
|
124
128
|
# "UPDATE 2026-08-18" comment: a dotted walk that landed on an
|
|
125
|
-
# un-unwrapped VO compared `false` against every literal,
|
|
129
|
+
# un-unwrapped VO compared `false` against every literal, silently,
|
|
126
130
|
# never raising) lived exactly at this boundary — a generator that
|
|
127
131
|
# only ever supplied bare scalars would never exercise the code
|
|
128
132
|
# path that bug lived in at all.
|
|
129
|
-
# `SingleFieldVO` —
|
|
133
|
+
# `SingleFieldVO` — not a plain Hash, deliberately: `Resolver
|
|
130
134
|
# #unwrap_scalar`'s own guard is `value.respond_to?(:to_h) &&
|
|
131
135
|
# !value.is_a?(Hash) && !value.is_a?(Array)` — it exists
|
|
132
136
|
# specifically to collapse a real hydrated `Runtime::Value`
|
|
133
137
|
# instance (which responds to `#to_h` but is never itself a bare
|
|
134
138
|
# Hash) down to its lone scalar field, and just as deliberately
|
|
135
|
-
# leaves an
|
|
136
|
-
# field record has no single scalar to collapse to). A
|
|
137
|
-
#
|
|
138
|
-
#
|
|
139
|
-
#
|
|
140
|
-
#
|
|
141
|
-
#
|
|
142
|
-
#
|
|
143
|
-
#
|
|
144
|
-
#
|
|
145
|
-
# `false` instead of `true` before this fix.
|
|
139
|
+
# leaves an actual Hash alone (a genuinely un-hydrated, multi-
|
|
140
|
+
# field record has no single scalar to collapse to). A plain
|
|
141
|
+
# `{value: X}` Hash cannot stand in here: `is_a?(Hash)` is true for
|
|
142
|
+
# it, so `unwrap_scalar` leaves it wrapped, and every VO-typed
|
|
143
|
+
# synthetic attribute then fails `Addition`/`Compare`/every
|
|
144
|
+
# scalar-typed operation with "expects a number, got
|
|
145
|
+
# {\"value\":5}" — a bug in this generator's own synthetic state,
|
|
146
|
+
# not in `Resolver`, that would surface only as `num_b == 5`
|
|
147
|
+
# (`num_b` a plain `{value: 5}` Hash) evaluating to `false` instead
|
|
148
|
+
# of `true`.
|
|
146
149
|
SingleFieldVO = Struct.new(:value) do
|
|
147
150
|
def to_h = { value: value }
|
|
148
151
|
end
|
|
149
152
|
|
|
153
|
+
# Builds the fixed state every generated predicate is interpreted against.
|
|
154
|
+
#
|
|
155
|
+
# @return [Hash{Symbol => Object}] symbol-keyed synthetic state: half bare
|
|
156
|
+
# scalars (`num_a`, `str_a`, `bool_a`, half of `arr_num`'s/`arr_str`'s
|
|
157
|
+
# elements), half wrapped `SingleFieldVO` instances (`num_b`, `str_b`,
|
|
158
|
+
# `bool_b`, the other half)
|
|
150
159
|
def synthetic_state
|
|
151
160
|
{
|
|
152
161
|
num_a: 3,
|
|
@@ -160,23 +169,32 @@ module Hecks
|
|
|
160
169
|
}
|
|
161
170
|
end
|
|
162
171
|
|
|
172
|
+
# The `attrs` half of the interpret call — always empty, since every
|
|
173
|
+
# synthetic name this generator uses lives in `synthetic_state` instead.
|
|
174
|
+
#
|
|
175
|
+
# @return [Hash] always `{}`
|
|
163
176
|
def synthetic_attrs = {}
|
|
164
177
|
|
|
165
|
-
#
|
|
166
|
-
#
|
|
167
|
-
#
|
|
168
|
-
#
|
|
169
|
-
#
|
|
170
|
-
#
|
|
178
|
+
# Lists every terminal (non-recursive) expression of `type`: the fixed
|
|
179
|
+
# literal palette plus every synthetic attribute name declared for that
|
|
180
|
+
# type (never their values — this generates text, the same source a
|
|
181
|
+
# real `given`/`invariant` author would write; `synthetic_state`,
|
|
182
|
+
# above, is what gives those names meaning at `interpret` time).
|
|
183
|
+
#
|
|
184
|
+
# @param type [Symbol] one of `TYPES`
|
|
185
|
+
# @return [Array<String>] `type`'s literal palette, synthetic attribute
|
|
186
|
+
# names, and any currently-bound block-parameter leaf for `type`
|
|
171
187
|
def leaves(type)
|
|
172
188
|
(TYPE_LEAVES[type] || []) + (SYNTHETIC_ATTRS[type] || []) + Array(bound_leaves[type])
|
|
173
189
|
end
|
|
174
190
|
|
|
175
|
-
#
|
|
191
|
+
# The per-type stack of currently-bound block-parameter leaf names.
|
|
192
|
+
#
|
|
193
|
+
# A stack, not a single slot — a nested block predicate (the real
|
|
176
194
|
# corpus already does this two levels deep, roster.bluebook's own
|
|
177
195
|
# `seats.any? { |s| assignments.none? { |a| … } }`) pushes a second
|
|
178
196
|
# bound leaf while the outer one is still active. Both share the
|
|
179
|
-
#
|
|
197
|
+
# same `BLOCK_PARAM` spelling ("el") — real Ruby block-parameter
|
|
180
198
|
# shadowing (the inner `el` simply shadows the outer one within its
|
|
181
199
|
# own predicate text), which this generator treats as legal on
|
|
182
200
|
# purpose: `resolver.rb`'s own `interpret_with_element` binds fresh
|
|
@@ -184,8 +202,20 @@ module Hecks
|
|
|
184
202
|
# outer level already bound, so a shadowed name still interprets
|
|
185
203
|
# correctly — this generator is proving "does it crash," not "is
|
|
186
204
|
# every generated predicate semantically distinct."
|
|
205
|
+
#
|
|
206
|
+
# @return [Hash{Symbol => Array<String>}] each type's own stack of bound
|
|
207
|
+
# leaf names, usually `[]` or `["el"]`
|
|
187
208
|
def bound_leaves = @bound_leaves ||= Hash.new { |h, k| h[k] = [] }
|
|
188
209
|
|
|
210
|
+
# Runs the block with `BLOCK_PARAM` admitted as a leaf of `element_type`,
|
|
211
|
+
# for building a block predicate's own body.
|
|
212
|
+
#
|
|
213
|
+
# @param element_type [Symbol] the type `BLOCK_PARAM` is bound to for the
|
|
214
|
+
# duration of the block
|
|
215
|
+
# @yield runs with the leaf pushed and any cached `element_type` productions
|
|
216
|
+
# invalidated
|
|
217
|
+
# @yieldreturn [Object] the block's own result
|
|
218
|
+
# @return [Object] the block's own result, unchanged
|
|
189
219
|
def with_element_leaf(element_type)
|
|
190
220
|
bound_leaves[element_type] << BLOCK_PARAM
|
|
191
221
|
cache.delete_if { |(type, _depth), _| type == element_type }
|
|
@@ -195,13 +225,18 @@ module Hecks
|
|
|
195
225
|
cache.delete_if { |(type, _depth), _| type == element_type }
|
|
196
226
|
end
|
|
197
227
|
|
|
198
|
-
#
|
|
199
|
-
#
|
|
200
|
-
#
|
|
201
|
-
#
|
|
202
|
-
#
|
|
203
|
-
#
|
|
204
|
-
#
|
|
228
|
+
# Lists every expression of `type` reachable in at most `depth`
|
|
229
|
+
# recursive steps, memoized (the same sub-expression set is reused at
|
|
230
|
+
# every enclosing recursion, so without memoizing, cost would compound
|
|
231
|
+
# multiplicatively per level instead of additively). `depth` 0 is
|
|
232
|
+
# exactly `leaves(type)`; each increment adds every construct this
|
|
233
|
+
# file's own production methods (below) say can produce `type`, built
|
|
234
|
+
# from `depth - 1` sub-expressions.
|
|
235
|
+
#
|
|
236
|
+
# @param type [Symbol] one of `TYPES`
|
|
237
|
+
# @param depth [Integer] maximum recursive steps remaining
|
|
238
|
+
# @return [Array<String>] every expression of `type` reachable in at most
|
|
239
|
+
# `depth` steps, deduplicated
|
|
205
240
|
def productions(type, depth)
|
|
206
241
|
cache[[type, depth]] ||= begin
|
|
207
242
|
base = leaves(type)
|
|
@@ -209,8 +244,18 @@ module Hecks
|
|
|
209
244
|
end
|
|
210
245
|
end
|
|
211
246
|
|
|
247
|
+
# The `productions` memo, keyed by `[type, depth]`.
|
|
248
|
+
#
|
|
249
|
+
# @return [Hash{Array(Symbol, Integer) => Array<String>}] the memoized productions cache
|
|
212
250
|
def cache = @cache ||= {}
|
|
213
251
|
|
|
252
|
+
# Dispatches to the recursive (non-leaf) production rule for `type`.
|
|
253
|
+
#
|
|
254
|
+
# @param type [Symbol] one of `TYPES`
|
|
255
|
+
# @param depth [Integer] maximum recursive steps remaining
|
|
256
|
+
# @return [Array<String>] every non-leaf expression of `type` reachable in at
|
|
257
|
+
# most `depth` steps; `[]` for `:nil_type`, which has no recursive producer
|
|
258
|
+
# @raise [ArgumentError] if `type` names anything outside `TYPES`
|
|
214
259
|
def recursive_productions(type, depth)
|
|
215
260
|
case type
|
|
216
261
|
when :numeric then numeric_productions(depth)
|
|
@@ -218,43 +263,52 @@ module Hecks
|
|
|
218
263
|
when :boolean then boolean_productions(depth)
|
|
219
264
|
when :array then array_productions(depth)
|
|
220
265
|
# no recursive producer of nil in this grammar — Find's "not found" is a
|
|
221
|
-
# runtime
|
|
266
|
+
# runtime outcome, not a distinct construct to render as source text
|
|
222
267
|
when :nil_type then []
|
|
223
268
|
else raise ArgumentError, "no production rule for type #{type.inspect}"
|
|
224
269
|
end
|
|
225
270
|
end
|
|
226
271
|
|
|
227
|
-
#
|
|
272
|
+
# Every internal use of a sub-expression list (as opposed to the
|
|
228
273
|
# single final list `productions(type, depth)` returns to its own
|
|
229
274
|
# caller) goes through this, not `productions` directly — the
|
|
230
275
|
# actual thing that made an early version of this generator explode
|
|
231
276
|
# past a million cases by depth 3 wasn't `pairs`' own cross product
|
|
232
|
-
# (already sampled) but the dozen-plus
|
|
277
|
+
# (already sampled) but the dozen-plus linear `flat_map`/`map`
|
|
233
278
|
# passes `boolean_productions` alone makes over `str`/`num`/`sub` —
|
|
234
279
|
# each individually harmless, but an unbounded few-thousand-item
|
|
235
|
-
# list run through a dozen of them, feeding the
|
|
236
|
-
# dozen passes, compounds fast. Bounding every
|
|
280
|
+
# list run through a dozen of them, feeding the next depth's own
|
|
281
|
+
# dozen passes, compounds fast. Bounding every input list (not the
|
|
237
282
|
# final output) keeps the shape diversity `sample`'s even-spacing
|
|
238
283
|
# already preserves while keeping growth roughly linear in depth
|
|
239
284
|
# instead of combinatorial.
|
|
285
|
+
#
|
|
286
|
+
# @param type [Symbol] one of `TYPES`
|
|
287
|
+
# @param depth [Integer] maximum recursive steps remaining
|
|
288
|
+
# @return [Array<String>] `productions(type, depth)`, sampled down to at most
|
|
289
|
+
# `SAMPLE_CAP` entries
|
|
240
290
|
def bounded(type, depth) = sample(productions(type, depth))
|
|
241
291
|
|
|
242
292
|
# NUMERIC ← Addition(numeric, numeric) | Modulo(numeric, numeric) |
|
|
243
293
|
# Size(sized) | First/Last(numeric array). `Size` returns an
|
|
244
|
-
# Integer for a String
|
|
294
|
+
# Integer for a String or an Array receiver alike (`SizedType` —
|
|
245
295
|
# `size_of`, resolver.rb) — both sides generated here.
|
|
296
|
+
#
|
|
297
|
+
# @param depth [Integer] maximum recursive steps remaining
|
|
298
|
+
# @return [Array<String>] numeric expressions one construct deep: `Addition`,
|
|
299
|
+
# `Modulo`, `Size` (of a string or array), `First`/`Last` (of a numeric array)
|
|
246
300
|
def numeric_productions(depth)
|
|
247
301
|
sub = bounded(:numeric, depth - 1)
|
|
248
|
-
# `Modulo`'s own
|
|
302
|
+
# `Modulo`'s own receiver (not its argument — that side already
|
|
249
303
|
# goes through `Resolver#matching_paren`'s own fresh, self-
|
|
250
304
|
# contained re-parse, confirmed safe for any numeric shape
|
|
251
305
|
# including another `Addition`/`Modulo`) has the identical
|
|
252
306
|
# "`Addition` mis-parsed as a suffix receiver" hazard
|
|
253
307
|
# `resolver_numeric_leaves`'s own comment documents for `.to_s`/
|
|
254
|
-
# `.positive?` — `Resolver.parse` tries `split_addition`
|
|
308
|
+
# `.positive?` — `Resolver.parse` tries `split_addition` before
|
|
255
309
|
# `match_call`, so `"0 + 0.modulo(1)"` (meant as `(0 + 0)
|
|
256
310
|
# .modulo(1)`) actually parses as `0 + (0.modulo(1))`. Restricted
|
|
257
|
-
# to `resolver_numeric_leaves` on the
|
|
311
|
+
# to `resolver_numeric_leaves` on the receiver side only — the
|
|
258
312
|
# argument stays the full, unrestricted numeric set.
|
|
259
313
|
pairs(sub).map { |a, b| "#{a} + #{b}" } +
|
|
260
314
|
cross(resolver_numeric_leaves(depth - 1), sub).map { |a, b| "#{a}.modulo(#{b})" } +
|
|
@@ -263,33 +317,38 @@ module Hecks
|
|
|
263
317
|
sample(numeric_array_productions(depth - 1)).flat_map { |a| ["#{a}.first", "#{a}.last"] }
|
|
264
318
|
end
|
|
265
319
|
|
|
266
|
-
# A "boolean"
|
|
267
|
-
#
|
|
268
|
-
# anywhere else a boolean-typed
|
|
269
|
-
# typed
|
|
270
|
-
# nested-`.modulo` bug was: `Resolver.parse` has
|
|
271
|
-
# `==`/`<`/`&&`/`||`/leading `!`/`.include?`
|
|
272
|
-
# `Evaluator`'s
|
|
273
|
-
#
|
|
320
|
+
# A "boolean" in resolver's own sense — safe to embed as the
|
|
321
|
+
# receiver of a trailing Resolver-level suffix (`.to_s`, and
|
|
322
|
+
# anywhere else a boolean-typed value, as opposed to a boolean-
|
|
323
|
+
# typed expression, is wanted). Found live, the same way the
|
|
324
|
+
# nested-`.modulo` bug was: `Resolver.parse` has no knowledge of
|
|
325
|
+
# `==`/`<`/`&&`/`||`/leading `!`/`.include?` at all — those are
|
|
326
|
+
# `Evaluator`'s own, entirely separate parsing layer, stripped off
|
|
327
|
+
# before anything reaches `Resolver.parse` at all (confirmed
|
|
274
328
|
# directly: `Resolver.parse("str_b < str_a")` — no `.` anywhere in
|
|
275
329
|
# that text for any suffix regex to anchor on — falls through
|
|
276
330
|
# every leaf regex to the `Lookup` catch-all, exactly like the
|
|
277
|
-
# nested-modulo bug did). `bounded(:boolean, depth)`'s
|
|
331
|
+
# nested-modulo bug did). `bounded(:boolean, depth)`'s full set
|
|
278
332
|
# includes `Compare`/`Include`/`Or`/`And`/`Not` — genuinely boolean-
|
|
279
|
-
#
|
|
333
|
+
# typed at `interpret` time, but `Evaluator`-level syntax, not
|
|
280
334
|
# something `Resolver.parse` can ever recognize as a receiver no
|
|
281
335
|
# matter how it's parenthesized (confirmed directly too: `Resolver
|
|
282
336
|
# .parse` never strips parens at all — `"(3)"` alone already fails
|
|
283
|
-
# to resolve). This is a
|
|
337
|
+
# to resolve). This is a real, permanent boundary of the actual
|
|
284
338
|
# grammar (this whole sublanguage's own two-layer split, not a
|
|
285
339
|
# limitation to work around) — a `given`/`invariant` author simply
|
|
286
340
|
# cannot write `(a < b).to_s` in this language, ever, no matter how
|
|
287
341
|
# they punctuate it. So this generator doesn't either: only
|
|
288
|
-
#
|
|
342
|
+
# resolver-level boolean-producing constructs (bare literals/
|
|
289
343
|
# lookups, `SignTest`, `Empty`, `Presence`, `MatchesRegex`,
|
|
290
344
|
# `StartsWith`/`EndsWith`, `BlockPredicate` — every one of them
|
|
291
|
-
# parsed via a suffix regex
|
|
345
|
+
# parsed via a suffix regex inside `Resolver.parse` itself, per
|
|
292
346
|
# this generator's own design report) are eligible here.
|
|
347
|
+
#
|
|
348
|
+
# @param depth [Integer] maximum recursive steps remaining
|
|
349
|
+
# @return [Array<String>] boolean-typed expressions safe to embed as a
|
|
350
|
+
# Resolver-level suffix receiver: literals/lookups, sign tests, string
|
|
351
|
+
# predicates, `.empty?`, and sampled block predicates
|
|
293
352
|
def resolver_boolean_leaves(depth)
|
|
294
353
|
leaves(:boolean) +
|
|
295
354
|
resolver_numeric_leaves(depth).flat_map { |n| ["#{n}.positive?", "#{n}.negative?", "#{n}.zero?"] } +
|
|
@@ -302,11 +361,11 @@ module Hecks
|
|
|
302
361
|
sample(block_predicate_productions(depth))
|
|
303
362
|
end
|
|
304
363
|
|
|
305
|
-
# A "numeric"
|
|
364
|
+
# A "numeric" in resolver's own sense — `Addition`'s twin of
|
|
306
365
|
# `resolver_boolean_leaves`'s own restriction, found the identical
|
|
307
366
|
# way: `"0 + 0.to_s"` (meant as `(0 + 0).to_s`) actually parses as
|
|
308
367
|
# `0 + (0.to_s)`, because `Resolver.parse` tries `split_addition`
|
|
309
|
-
#
|
|
368
|
+
# before `.to_s`'s own suffix regex in its dispatch order — the `+`
|
|
310
369
|
# "wins" the split before the suffix ever gets a chance to anchor
|
|
311
370
|
# on its own receiver boundary. Confirmed to have zero real-corpus
|
|
312
371
|
# precedent either (`grep`, no `bluebook` file anywhere chains a
|
|
@@ -315,16 +374,20 @@ module Hecks
|
|
|
315
374
|
# a real permanent grammar boundary (`Resolver.parse` never strips
|
|
316
375
|
# parens, confirmed directly, so no amount of punctuation rescues
|
|
317
376
|
# `(a + b).to_s`), not a bug to fix in the resolver for a shape
|
|
318
|
-
# nothing has ever needed. `Modulo`/`Size`/`First`/`Last` stay
|
|
377
|
+
# nothing has ever needed. `Modulo`/`Size`/`First`/`Last` stay in
|
|
319
378
|
# (each is its own trailing `.method(...)`/`.method` call, so
|
|
320
379
|
# `Resolver.parse`'s greedy `(.+)\.suffix\z` regexes correctly
|
|
321
380
|
# isolate them as a receiver regardless of what precedes them —
|
|
322
381
|
# only bare top-level `+` has this problem).
|
|
382
|
+
#
|
|
383
|
+
# @param depth [Integer] maximum recursive steps remaining
|
|
384
|
+
# @return [Array<String>] numeric-typed expressions safe to embed as a
|
|
385
|
+
# Resolver-level suffix receiver (never a bare top-level `Addition`)
|
|
323
386
|
def resolver_numeric_leaves(depth)
|
|
324
387
|
return leaves(:numeric) if depth <= 0
|
|
325
388
|
|
|
326
|
-
#
|
|
327
|
-
# receiver needs the
|
|
389
|
+
# Self-referential on purpose, one depth down — `Modulo`'s own
|
|
390
|
+
# receiver needs the same restriction `resolver_numeric_leaves`
|
|
328
391
|
# exists to express in the first place (see its own header
|
|
329
392
|
# comment); the argument stays the full, unrestricted set, same
|
|
330
393
|
# as `numeric_productions`' identical split right above.
|
|
@@ -336,12 +399,16 @@ module Hecks
|
|
|
336
399
|
end
|
|
337
400
|
|
|
338
401
|
# STRING ← ToS(numeric | boolean | nil | string) | First/Last(string
|
|
339
|
-
# array). `Split` produces an
|
|
340
|
-
# own `apply_split`) — deliberately
|
|
402
|
+
# array). `Split` produces an array, never a String (resolver.rb's
|
|
403
|
+
# own `apply_split`) — deliberately not listed as a string producer
|
|
341
404
|
# here; that would be exactly the "vocabulary-legal but grammar-
|
|
342
405
|
# can't-actually-produce-it" mistake this sublanguage's own
|
|
343
406
|
# `ArrayLiteral` bug (§1 of this generator's own design report) was
|
|
344
407
|
# found from, inverted.
|
|
408
|
+
#
|
|
409
|
+
# @param depth [Integer] maximum recursive steps remaining
|
|
410
|
+
# @return [Array<String>] string expressions one construct deep: `ToS` of a
|
|
411
|
+
# numeric, boolean, nil, or string operand, or `First`/`Last` of a string array
|
|
345
412
|
def string_productions(depth)
|
|
346
413
|
sample(resolver_numeric_leaves(depth - 1)).map { |n| "#{n}.to_s" } +
|
|
347
414
|
sample(resolver_boolean_leaves(depth - 1)).map { |b| "#{b}.to_s" } +
|
|
@@ -353,10 +420,14 @@ module Hecks
|
|
|
353
420
|
# ARRAY ← Split(string, separator) | ArrayLiteral[same-type
|
|
354
421
|
# elements] | the synthetic array attributes (already in `leaves`).
|
|
355
422
|
# Kept deliberately small relative to numeric/string/boolean —
|
|
356
|
-
# `array` is overwhelmingly a
|
|
357
|
-
# /`.any?`/`.include?`/…), rarely a produced
|
|
423
|
+
# `array` is overwhelmingly a receiver type in this grammar (`.size`
|
|
424
|
+
# /`.any?`/`.include?`/…), rarely a produced value; the two real
|
|
358
425
|
# producers are enough to exercise every array-typed consumer
|
|
359
426
|
# elsewhere in this file at least once via a non-leaf path.
|
|
427
|
+
#
|
|
428
|
+
# @param depth [Integer] maximum recursive steps remaining
|
|
429
|
+
# @return [Array<String>] array expressions one construct deep: a string
|
|
430
|
+
# `Split`, a numeric array literal, and a string array literal; `[]` at depth 0
|
|
360
431
|
def array_productions(depth)
|
|
361
432
|
return [] if depth <= 0
|
|
362
433
|
|
|
@@ -364,17 +435,30 @@ module Hecks
|
|
|
364
435
|
[numeric_array_literal(depth - 1), string_array_literal(depth - 1)]
|
|
365
436
|
end
|
|
366
437
|
|
|
438
|
+
# Builds a two-element numeric array literal at `depth`.
|
|
439
|
+
#
|
|
440
|
+
# @param depth [Integer] maximum recursive steps remaining for its own elements
|
|
441
|
+
# @return [String] a `"[a, b]"` numeric array literal source text
|
|
367
442
|
def numeric_array_literal(depth) = "[#{bounded(:numeric, depth).first(2).join(', ')}]"
|
|
443
|
+
|
|
444
|
+
# Builds a two-element string array literal at `depth`.
|
|
445
|
+
#
|
|
446
|
+
# @param depth [Integer] maximum recursive steps remaining for its own elements
|
|
447
|
+
# @return [String] a `"[a, b]"` string array literal source text
|
|
368
448
|
def string_array_literal(depth) = "[#{bounded(:string, depth).first(2).join(', ')}]"
|
|
369
449
|
|
|
370
|
-
# Arrays
|
|
450
|
+
# Arrays known (by construction, not merely by type) to hold numeric
|
|
371
451
|
# elements — the two synthetic array attributes (`ARRAY_ELEMENT_TYPE`
|
|
372
|
-
# tags `arr_num`) plus any numeric-array
|
|
452
|
+
# tags `arr_num`) plus any numeric-array literal this same depth
|
|
373
453
|
# budget can build. `.first`/`.last`/block predicates need to know
|
|
374
|
-
# the
|
|
454
|
+
# the element type, which plain `array_productions` doesn't carry —
|
|
375
455
|
# this (and `string_array_productions`, its twin) is how that extra
|
|
376
456
|
# bit of type information flows without inventing a second, richer
|
|
377
457
|
# AST just to carry it.
|
|
458
|
+
#
|
|
459
|
+
# @param depth [Integer] maximum recursive steps remaining
|
|
460
|
+
# @return [Array<String>] array expressions known to hold numeric elements:
|
|
461
|
+
# `"arr_num"`, plus a numeric array literal at `depth`
|
|
378
462
|
def numeric_array_productions(depth)
|
|
379
463
|
["arr_num", numeric_array_literal(depth)]
|
|
380
464
|
# a Split of a string never yields numeric elements — no third
|
|
@@ -382,13 +466,19 @@ module Hecks
|
|
|
382
466
|
# `string_array_productions`, its non-empty twin, right below).
|
|
383
467
|
end
|
|
384
468
|
|
|
469
|
+
# Arrays known (by construction) to hold string elements — `arr_str`'s
|
|
470
|
+
# own twin of `numeric_array_productions`, above.
|
|
471
|
+
#
|
|
472
|
+
# @param depth [Integer] maximum recursive steps remaining
|
|
473
|
+
# @return [Array<String>] `"arr_str"`, a string array literal at `depth`, and
|
|
474
|
+
# every string `Split` expression at `depth`
|
|
385
475
|
def string_array_productions(depth)
|
|
386
476
|
["arr_str", string_array_literal(depth)] + bounded(:string, depth).map { |s| "#{s}.split(\",\")" }
|
|
387
477
|
end
|
|
388
478
|
|
|
389
479
|
# BOOLEAN ← every comparison/predicate construct in the grammar.
|
|
390
|
-
# This is where almost all of the sublanguage's
|
|
391
|
-
# lives — a `given`/`invariant`/`ensures` body is
|
|
480
|
+
# This is where almost all of the sublanguage's own real surface
|
|
481
|
+
# lives — a `given`/`invariant`/`ensures` body is always boolean-
|
|
392
482
|
# typed at its own top level (`Evaluator.truthy?`), so this is also
|
|
393
483
|
# the set `all_predicates` (below) draws its top-level cases from
|
|
394
484
|
# directly.
|
|
@@ -401,6 +491,12 @@ module Hecks
|
|
|
401
491
|
# need `sub`/`num`/`str` threaded into each as parameters and
|
|
402
492
|
# would separate every rule from the comment justifying it.
|
|
403
493
|
# rubocop:disable-next Metrics/AbcSize
|
|
494
|
+
#
|
|
495
|
+
# @param depth [Integer] maximum recursive steps remaining
|
|
496
|
+
# @return [Array<String>] every boolean-typed construct in the grammar, one
|
|
497
|
+
# level deep: comparisons, sign tests, `.empty?`/`.match?`/`.present?`/
|
|
498
|
+
# `.blank?`/`.start_with?`/`.end_with?`, `&&`/`||`/`!`, block predicates,
|
|
499
|
+
# and `include?`
|
|
404
500
|
def boolean_productions(depth)
|
|
405
501
|
sub = bounded(:boolean, depth - 1)
|
|
406
502
|
num = bounded(:numeric, depth - 1)
|
|
@@ -433,17 +529,22 @@ module Hecks
|
|
|
433
529
|
include_productions(depth - 1)
|
|
434
530
|
end
|
|
435
531
|
|
|
436
|
-
# `.all?`/`.any?`/`.none?` over
|
|
532
|
+
# `.all?`/`.any?`/`.none?` over each known-element-typed array
|
|
437
533
|
# source, with a predicate body drawn from `boolean_productions` at
|
|
438
|
-
#
|
|
439
|
-
# array's own element type — this is the
|
|
534
|
+
# one less depth, evaluated against `BLOCK_PARAM` bound to the
|
|
535
|
+
# array's own element type — this is the only place `Resolver` and
|
|
440
536
|
# `Evaluator` are truly mutually recursive (a `BlockPredicate`'s own
|
|
441
537
|
# `predicate` field is a full `Evaluator` AST, not a `Resolver`
|
|
442
538
|
# leaf — confirmed directly), and the only construct in this whole
|
|
443
|
-
# generator that can nest into
|
|
539
|
+
# generator that can nest into another block predicate (the real
|
|
444
540
|
# corpus already does this two levels deep —
|
|
445
541
|
# `examples/roster/bluebook/roster.bluebook`'s own `seats.any? { |s|
|
|
446
542
|
# assignments.none? { |a| … } }`).
|
|
543
|
+
#
|
|
544
|
+
# @param depth [Integer] maximum recursive steps remaining for the predicate body
|
|
545
|
+
# @return [Array<String>] `.all?`/`.any?`/`.none?` calls over each
|
|
546
|
+
# known-element-typed array source, one per predicate body `predicate_bodies`
|
|
547
|
+
# produces; `[]` if `depth` is negative
|
|
447
548
|
def block_predicate_productions(depth)
|
|
448
549
|
return [] if depth.negative?
|
|
449
550
|
|
|
@@ -458,24 +559,34 @@ module Hecks
|
|
|
458
559
|
end
|
|
459
560
|
|
|
460
561
|
# The predicate body a block gets — `boolean_productions`, but with
|
|
461
|
-
# `BLOCK_PARAM` (bound to `element_type`)
|
|
562
|
+
# `BLOCK_PARAM` (bound to `element_type`) also admitted as a leaf,
|
|
462
563
|
# since inside the block it is exactly as usable as any other
|
|
463
564
|
# `Lookup` name (resolver.rb's own `interpret_with_element`: the
|
|
464
565
|
# bound element joins `attrs` for the span of one evaluation, no
|
|
465
566
|
# different from a top-level attribute — confirmed directly).
|
|
466
567
|
# `sample`d for the same reason every other internal list is — this
|
|
467
|
-
# feeds
|
|
468
|
-
#
|
|
568
|
+
# feeds three more constructs per body (`all?`/`any?`/`none?`) times
|
|
569
|
+
# four array sources, so an unbounded body list here is exactly the
|
|
469
570
|
# kind of multiplier this file's own `bounded` comment warns about.
|
|
571
|
+
#
|
|
572
|
+
# @param element_type [Symbol] the type `BLOCK_PARAM` is bound to inside the block
|
|
573
|
+
# @param depth [Integer] maximum recursive steps remaining for the body
|
|
574
|
+
# @return [Array<String>] a sampled set of boolean expressions, with
|
|
575
|
+
# `BLOCK_PARAM` admitted as a leaf of `element_type`
|
|
470
576
|
def predicate_bodies(element_type, depth)
|
|
471
577
|
sample(with_element_leaf(element_type) { boolean_productions(depth) })
|
|
472
578
|
end
|
|
473
579
|
|
|
474
580
|
# `haystack.include?(needle)` — String haystack needs a String
|
|
475
581
|
# needle (raises otherwise, evaluator.rb's own `includes?`); Array
|
|
476
|
-
# haystack admits
|
|
582
|
+
# haystack admits any needle type (compared via `equal?`, itself
|
|
477
583
|
# numeric-coerced-first). Both sides generated here, matching
|
|
478
584
|
# `Vocabulary::IncludeHaystack` exactly.
|
|
585
|
+
#
|
|
586
|
+
# @param depth [Integer] maximum recursive steps remaining
|
|
587
|
+
# @return [Array<String>] `haystack.include?(needle)` expressions: a String
|
|
588
|
+
# haystack with a String needle, or a numeric/string array haystack with a
|
|
589
|
+
# matching-type needle
|
|
479
590
|
def include_productions(depth)
|
|
480
591
|
str = bounded(:string, depth)
|
|
481
592
|
pairs(str).map { |haystack, needle| "#{haystack}.include?(#{needle})" } +
|
|
@@ -487,55 +598,79 @@ module Hecks
|
|
|
487
598
|
end
|
|
488
599
|
end
|
|
489
600
|
|
|
490
|
-
#
|
|
601
|
+
# **Sampled, not a full cross product** — a full `list.product(list)`
|
|
491
602
|
# is what actually explodes this generator (numeric productions
|
|
492
|
-
# alone hit 8000+ by depth 2; squaring
|
|
603
|
+
# alone hit 8000+ by depth 2; squaring that for `&&`/`==` pairs is
|
|
493
604
|
# where "tens of thousands" becomes tens of millions). The
|
|
494
605
|
# combinatorics genuinely don't buy proof coverage: proving `Or`/
|
|
495
|
-
# `And`/`Not` themselves never crash needs
|
|
606
|
+
# `And`/`Not` themselves never crash needs one representative pair
|
|
496
607
|
# per depth (`evaluator.rb`'s own `interpret` does zero type-
|
|
497
608
|
# dependent work for those three — `interpret(left) || interpret
|
|
498
609
|
# (right)`, plain Ruby, no coercion, no receiver-type check at
|
|
499
|
-
# all — so a crash there could only come from
|
|
610
|
+
# all — so a crash there could only come from left or right
|
|
500
611
|
# themselves, already covered by testing every operand on its own
|
|
501
612
|
# elsewhere in this same predicate set). `Compare`/`Include`/
|
|
502
|
-
# `Addition`/`Modulo` genuinely
|
|
613
|
+
# `Addition`/`Modulo` genuinely do real per-pair type coercion
|
|
503
614
|
# (`Evaluator.apply`/`less_than`/`Resolver.add`/`apply_modulo`), so
|
|
504
|
-
#
|
|
615
|
+
# those stay covered across every construct shape at every depth —
|
|
505
616
|
# just sampled evenly across each side's own operand set (leaves
|
|
506
|
-
#
|
|
617
|
+
# and deep productions alike, not just whichever the list happens
|
|
507
618
|
# to enumerate first) rather than every possible pairing of them.
|
|
508
619
|
SAMPLE_CAP = 14
|
|
509
620
|
|
|
621
|
+
# Evenly samples `list` down to at most `SAMPLE_CAP` entries, so a list
|
|
622
|
+
# size never compounds unbounded across recursive passes.
|
|
623
|
+
#
|
|
624
|
+
# @param list [Array<String>] the list to sample
|
|
625
|
+
# @return [Array<String>] `list` unchanged if it is at most `SAMPLE_CAP` entries;
|
|
626
|
+
# otherwise every `list.size.fdiv(SAMPLE_CAP).ceil`-th entry, evenly spaced
|
|
510
627
|
def sample(list) = list.size <= SAMPLE_CAP ? list : list.each_slice(list.size.fdiv(SAMPLE_CAP).ceil).map(&:first)
|
|
511
628
|
|
|
629
|
+
# Builds every pair of one sampled list with itself.
|
|
630
|
+
#
|
|
631
|
+
# @param list [Array<String>] the list to sample and pair with itself
|
|
632
|
+
# @return [Array<Array(String, String)>] the sampled list's own self cross product
|
|
512
633
|
def pairs(list)
|
|
513
634
|
sampled = sample(list)
|
|
514
635
|
sampled.product(sampled)
|
|
515
636
|
end
|
|
516
637
|
|
|
517
|
-
# `pairs`' own two-different-lists twin — used wherever the
|
|
518
|
-
# and
|
|
638
|
+
# `pairs`' own two-different-lists twin — used wherever the left
|
|
639
|
+
# and right of a construct have genuinely different safety
|
|
519
640
|
# requirements (`Modulo`'s own receiver vs. argument, below) and
|
|
520
|
-
# squaring the
|
|
641
|
+
# squaring the same sampled list wouldn't be correct.
|
|
642
|
+
#
|
|
643
|
+
# @param left [Array<String>] the left-hand list to sample
|
|
644
|
+
# @param right [Array<String>] the right-hand list to sample
|
|
645
|
+
# @return [Array<Array(String, String)>] the two sampled lists' own cross product
|
|
521
646
|
def cross(left, right) = sample(left).product(sample(right))
|
|
522
647
|
|
|
523
|
-
#
|
|
648
|
+
# The full set — every boolean-typed expression up to `MAX_DEPTH`,
|
|
524
649
|
# deduplicated (many shorter expressions are also produced, re-
|
|
525
650
|
# wrapped, at every deeper level — `.uniq` inside `productions`
|
|
526
651
|
# already collapses most of that; this is the final pass over the
|
|
527
652
|
# complete depth-`MAX_DEPTH` set specifically).
|
|
653
|
+
#
|
|
654
|
+
# @param max_depth [Integer] maximum recursive depth to generate up to
|
|
655
|
+
# @return [Array<String>] every boolean-typed expression up to `max_depth`,
|
|
656
|
+
# deduplicated
|
|
528
657
|
def all_predicates(max_depth = MAX_DEPTH)
|
|
529
658
|
productions(:boolean, max_depth).uniq
|
|
530
659
|
end
|
|
531
660
|
|
|
532
661
|
# Interprets one predicate against the shared synthetic
|
|
533
662
|
# state/attrs, returning `{ok: true, result: ...}` on any outcome
|
|
534
|
-
# `Evaluator.call` itself can express (a real true/false answer,
|
|
535
|
-
# a clean `EvaluationError` — both are the sublanguage
|
|
663
|
+
# `Evaluator.call` itself can express (a real true/false answer, or
|
|
664
|
+
# a clean `EvaluationError` — both are the sublanguage working
|
|
536
665
|
# correctly, never a finding) and `{ok: false, error: ...}` only
|
|
537
666
|
# for anything else escaping — the one shape this whole file exists
|
|
538
667
|
# to prove never happens for well-typed input.
|
|
668
|
+
#
|
|
669
|
+
# @param expr [String] a generated (or hand-written) expression source string
|
|
670
|
+
# @return [Hash{Symbol => Object}] `{ok: true, result: Object}` for a real
|
|
671
|
+
# answer, `{ok: true, result: :refused, message: String}` for a clean
|
|
672
|
+
# `EvaluationError`, or `{ok: false, error: StandardError}` for anything else
|
|
673
|
+
# escaping
|
|
539
674
|
def check(expr)
|
|
540
675
|
result = Hecks::Bluebook::Expression::Evaluator.call(expr, synthetic_state, synthetic_attrs)
|
|
541
676
|
{ ok: true, result: result }
|