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
|
@@ -16,9 +16,9 @@ module Hecks
|
|
|
16
16
|
|
|
17
17
|
# Six operators, reduced to two primitives (less_than, equal) combined
|
|
18
18
|
# with a small boolean algebra: compares_less_than/compares_equal choose
|
|
19
|
-
# which primitive(s)
|
|
19
|
+
# which primitive(s) or together, negated inverts the result.
|
|
20
20
|
#
|
|
21
|
-
#
|
|
21
|
+
# **Read, not restated**. This table is the checked-in projection of the
|
|
22
22
|
# grammar chapter's admitted set (bin/expression_projection), joined with
|
|
23
23
|
# the algebra Vocabulary::Comparison declares. The evaluator cannot
|
|
24
24
|
# boot the chapter that configures it — the Prism adapter normalises
|
|
@@ -53,20 +53,37 @@ module Hecks
|
|
|
53
53
|
|
|
54
54
|
module_function
|
|
55
55
|
|
|
56
|
-
#
|
|
56
|
+
# Memoizes each distinct predicate string's own parsed AST, keyed by
|
|
57
|
+
# the exact string `call` receives. Canonical text is already
|
|
57
58
|
# normalised at DSL-build time, so the same given/invariant's text is
|
|
58
59
|
# byte-identical across every dispatch that evaluates it — parsed once
|
|
59
60
|
# here, interpreted fresh against each call's own state/attrs. Matches
|
|
60
61
|
# MetaValidator.verdicts' unsynchronized `||= {}` idiom : redundant
|
|
61
62
|
# parse work under real parallelism, never corruption.
|
|
63
|
+
#
|
|
64
|
+
# @return [Hash{String => Object}] the process-wide parse cache,
|
|
65
|
+
# keyed by predicate string; each value is one of `Or`, `And`,
|
|
66
|
+
# `Not`, `Compare`, `Include`, or `Resolve`
|
|
62
67
|
def ast_cache = @ast_cache ||= {}
|
|
63
68
|
|
|
69
|
+
# Parses `expr` (cached per distinct string) and interprets it
|
|
70
|
+
# against `state`/`attrs`.
|
|
71
|
+
#
|
|
72
|
+
# @param expr [String] the canonical predicate text to evaluate
|
|
73
|
+
# @param state [Hash{Symbol => Object}] the stored attribute values
|
|
74
|
+
# a `Resolve`/`Compare` leaf may resolve against
|
|
75
|
+
# @param attrs [Hash{Symbol => Object}] the call's own argument
|
|
76
|
+
# values, checked before `state`
|
|
77
|
+
# @return [Boolean] whether `expr` holds
|
|
78
|
+
# @raise [EvaluationError] if `expr` resolves an unknown attribute
|
|
79
|
+
# or argument, or applies an operation to a value of the wrong
|
|
80
|
+
# type
|
|
64
81
|
def call(expr, state, attrs = {})
|
|
65
82
|
interpret(ast_cache[expr] ||= parse(expr), state, attrs)
|
|
66
83
|
end
|
|
67
84
|
|
|
68
|
-
#
|
|
69
|
-
# answering `canonical` and `ast`) by walking its
|
|
85
|
+
# The rule-shaped entry — evaluates a Given/Invariant (anything
|
|
86
|
+
# answering `canonical` and `ast`) by walking its structured form,
|
|
70
87
|
# never re-parsing the text: the one parse happened at DSL-build
|
|
71
88
|
# time behind `AstJson`, and `AstReader` turns that tree back into
|
|
72
89
|
# the same nodes `parse` would have built (the equivalence is
|
|
@@ -77,31 +94,60 @@ module Hecks
|
|
|
77
94
|
#
|
|
78
95
|
# HECKS_EVAL=string reverts to the text path wholesale, kept for
|
|
79
96
|
# one release as the escape hatch while the ast path beds in.
|
|
97
|
+
#
|
|
98
|
+
# @param rule [Bluebook::Given, Bluebook::Invariant] the rule to
|
|
99
|
+
# evaluate
|
|
100
|
+
# @param state [Hash{Symbol => Object}] the stored attribute values
|
|
101
|
+
# a `Resolve`/`Compare` leaf may resolve against
|
|
102
|
+
# @param attrs [Hash{Symbol => Object}] the call's own argument
|
|
103
|
+
# values, checked before `state`
|
|
104
|
+
# @return [Boolean] whether `rule` holds
|
|
105
|
+
# @raise [EvaluationError] if `rule` resolves an unknown attribute
|
|
106
|
+
# or argument, or applies an operation to a value of the wrong
|
|
107
|
+
# type
|
|
80
108
|
def call_rule(rule, state, attrs = {})
|
|
81
109
|
return call(rule.canonical, state, attrs) if ENV["HECKS_EVAL"] == "string"
|
|
82
110
|
|
|
83
111
|
interpret(ast_cache[rule.canonical] ||= nodes_for(rule), state, attrs)
|
|
84
112
|
end
|
|
85
113
|
|
|
114
|
+
# Returns `rule`'s own AST, read back from its `ast` field when
|
|
115
|
+
# present, otherwise parsed fresh from `canonical`.
|
|
116
|
+
#
|
|
117
|
+
# @param rule [Bluebook::Given, Bluebook::Invariant] the rule to
|
|
118
|
+
# read
|
|
119
|
+
# @return [Object] one of `Or`, `And`, `Not`, `Compare`, `Include`,
|
|
120
|
+
# or `Resolve`
|
|
86
121
|
def nodes_for(rule)
|
|
87
122
|
rule.ast ? AstReader.read_predicate(rule.ast) : parse(rule.canonical)
|
|
88
123
|
end
|
|
89
124
|
|
|
90
|
-
#
|
|
91
|
-
#
|
|
92
|
-
#
|
|
93
|
-
#
|
|
94
|
-
#
|
|
95
|
-
#
|
|
96
|
-
#
|
|
97
|
-
#
|
|
98
|
-
#
|
|
99
|
-
#
|
|
100
|
-
#
|
|
101
|
-
#
|
|
102
|
-
#
|
|
125
|
+
# Renders `expr`'s own two operands, for a refusal message to show
|
|
126
|
+
# alongside a failed `given`/`ensures`/`invariant`. A refused rule
|
|
127
|
+
# names its own description ("not already superseded") but, on its
|
|
128
|
+
# own, not what the block actually evaluated to — the difference
|
|
129
|
+
# between "the rule is right and my data is wrong" and "the rule
|
|
130
|
+
# is subtly wrong" is often just seeing the two operands. Scoped
|
|
131
|
+
# to the single shape that has one honest answer: `expr`'s own
|
|
132
|
+
# top-level node is a bare `Compare` — not `Or`/`And`/`Not` (which
|
|
133
|
+
# of several sub-comparisons would even be "the" one at fault is
|
|
134
|
+
# genuinely ambiguous), `Include` (no left/right to show), or
|
|
135
|
+
# `Resolve` (a bare boolean read, nothing to compare against).
|
|
136
|
+
# Values are rendered with `Rendering.describe`, the same house
|
|
137
|
+
# style every other refusal already prints a value through.
|
|
138
|
+
# Returns `nil` — not raised — on anything else, including an
|
|
139
|
+
# operand that itself fails to resolve (`EvaluationError`): a
|
|
103
140
|
# missing diagnostic is a worse debugging experience than none, a
|
|
104
141
|
# crash while building one is worse still.
|
|
142
|
+
#
|
|
143
|
+
# @param expr [String] the canonical predicate text that was just
|
|
144
|
+
# refused
|
|
145
|
+
# @param state [Hash{Symbol => Object}] the stored attribute values
|
|
146
|
+
# the operands may resolve against
|
|
147
|
+
# @param attrs [Hash{Symbol => Object}] the call's own argument
|
|
148
|
+
# values, checked before `state`
|
|
149
|
+
# @return [String, nil] `"left: X, right: Y"` when `expr` is a bare
|
|
150
|
+
# comparison whose operands both resolve; `nil` otherwise
|
|
105
151
|
def comparison_detail(expr, state, attrs = {})
|
|
106
152
|
node = ast_cache[expr] ||= parse(expr)
|
|
107
153
|
return nil unless node.is_a?(Compare)
|
|
@@ -113,6 +159,12 @@ module Hecks
|
|
|
113
159
|
nil
|
|
114
160
|
end
|
|
115
161
|
|
|
162
|
+
# Parses `expr`'s boolean/comparison grammar into an AST, recursing
|
|
163
|
+
# into `Resolver.parse` for each leaf.
|
|
164
|
+
#
|
|
165
|
+
# @param expr [String] the canonical predicate text to parse
|
|
166
|
+
# @return [Object] one of `Or`, `And`, `Not`, `Compare`, `Include`,
|
|
167
|
+
# or `Resolve`, chosen by `expr`'s own shape
|
|
116
168
|
def parse(expr)
|
|
117
169
|
expr = strip_parens(expr.to_s.strip)
|
|
118
170
|
|
|
@@ -122,18 +174,18 @@ module Hecks
|
|
|
122
174
|
left, right = split_top_level(expr, "&&")
|
|
123
175
|
return And.new(left: parse(left), right: parse(right)) if left
|
|
124
176
|
|
|
125
|
-
# Tried
|
|
126
|
-
# the
|
|
177
|
+
# Tried before `.include?`/comparisons, not after — `!` negates
|
|
178
|
+
# the whole boolean expression that follows it (`!names.include?(x)`
|
|
127
179
|
# means `!(names.include?(x))`, never "call .include? on the negated
|
|
128
180
|
# receiver"), so the leading marker has to be stripped and the
|
|
129
181
|
# remainder re-parsed before anything downstream gets a chance to
|
|
130
|
-
# mis-scan across it.
|
|
182
|
+
# mis-scan across it. Tried after `match_include` instead, its
|
|
131
183
|
# naive `rindex(".include?(")` has no concept of a leading `!` —
|
|
132
|
-
# for `!names.include?(x)` it
|
|
133
|
-
# haystack text ("!names"), which `Resolver.parse` cannot
|
|
134
|
-
# so every spelling of negated membership
|
|
135
|
-
# evaluating.
|
|
136
|
-
# (`!names.include?(x)`) and the parenthesized form
|
|
184
|
+
# for `!names.include?(x)` it would swallow the `!` straight into
|
|
185
|
+
# the haystack text ("!names"), which `Resolver.parse` cannot
|
|
186
|
+
# resolve, so every spelling of negated membership would raise
|
|
187
|
+
# instead of evaluating. Checking here first fixes both the bare
|
|
188
|
+
# prefix (`!names.include?(x)`) and the parenthesized form
|
|
137
189
|
# (`!(names.include?(x))`) — the recursive `parse` call sees the
|
|
138
190
|
# clean remainder and correctly finds the `.include?` (or `&&`/`||`)
|
|
139
191
|
# inside it.
|
|
@@ -150,6 +202,19 @@ module Hecks
|
|
|
150
202
|
Resolve.new(expr: Resolver.parse(expr))
|
|
151
203
|
end
|
|
152
204
|
|
|
205
|
+
# Interprets a parsed boolean/comparison node against `state`/
|
|
206
|
+
# `attrs`.
|
|
207
|
+
#
|
|
208
|
+
# @param node [Object] a node `parse` produced (`Or`, `And`, `Not`,
|
|
209
|
+
# `Compare`, `Include`, or `Resolve`)
|
|
210
|
+
# @param state [Hash{Symbol => Object}] the stored attribute values
|
|
211
|
+
# a leaf may resolve against
|
|
212
|
+
# @param attrs [Hash{Symbol => Object}] the call's own argument
|
|
213
|
+
# values, checked before `state`
|
|
214
|
+
# @return [Boolean] whether `node` holds
|
|
215
|
+
# @raise [EvaluationError] if `node` is not one of the handled
|
|
216
|
+
# types, or a leaf it delegates to (`compare`, `includes?`,
|
|
217
|
+
# `Resolver.interpret`) refuses its operand
|
|
153
218
|
def interpret(node, state, attrs)
|
|
154
219
|
case node
|
|
155
220
|
when Or then interpret(node.left, state, attrs) || interpret(node.right, state, attrs)
|
|
@@ -162,16 +227,30 @@ module Hecks
|
|
|
162
227
|
# Every node `parse` can produce has a `when` above — a
|
|
163
228
|
# backstop against the day this grammar grows a new node
|
|
164
229
|
# type and `interpret` doesn't grow to match it. A missing
|
|
165
|
-
# arm here
|
|
166
|
-
# that straight into the boolean algebra as ordinary
|
|
167
|
-
# — reading exactly like "the rule legitimately does
|
|
168
|
-
# hold" rather than "the runtime cannot evaluate this
|
|
169
|
-
# at all", the one silent no-op this language otherwise
|
|
230
|
+
# arm here would instead return bare `nil`, and `Or`/`And`
|
|
231
|
+
# fold that straight into the boolean algebra as ordinary
|
|
232
|
+
# falsy — reading exactly like "the rule legitimately does
|
|
233
|
+
# not hold" rather than "the runtime cannot evaluate this
|
|
234
|
+
# rule at all", the one silent no-op this language otherwise
|
|
170
235
|
# refuses.
|
|
171
236
|
raise EvaluationError, "no interpreter handles #{node.class} — add a case before parse can produce it"
|
|
172
237
|
end
|
|
173
238
|
end
|
|
174
239
|
|
|
240
|
+
# Resolves `left`/`right` and applies `comparator` to the results.
|
|
241
|
+
#
|
|
242
|
+
# @param comparator [Operator] the comparison operator to apply
|
|
243
|
+
# @param left [Object] a `Resolver` leaf node (`Resolver.parse`'s
|
|
244
|
+
# own return) for the left operand
|
|
245
|
+
# @param right [Object] a `Resolver` leaf node (`Resolver.parse`'s
|
|
246
|
+
# own return) for the right operand
|
|
247
|
+
# @param state [Hash{Symbol => Object}] the stored attribute values
|
|
248
|
+
# the operands may resolve against
|
|
249
|
+
# @param attrs [Hash{Symbol => Object}] the call's own argument
|
|
250
|
+
# values, checked before `state`
|
|
251
|
+
# @return [Boolean] the comparison's result
|
|
252
|
+
# @raise [EvaluationError] if an operand does not resolve, or
|
|
253
|
+
# `less_than`/`equal?` cannot compare the resolved values
|
|
175
254
|
def compare(comparator, left, right, state, attrs)
|
|
176
255
|
lhs = Resolver.interpret(left, state, attrs)
|
|
177
256
|
rhs = Resolver.interpret(right, state, attrs)
|
|
@@ -181,14 +260,29 @@ module Hecks
|
|
|
181
260
|
|
|
182
261
|
# The algebra itself, on values already resolved — split out so a sign
|
|
183
262
|
# test (SignTest#compares_via names an Operator symbol) can apply the
|
|
184
|
-
#
|
|
263
|
+
# same primitives compare() uses against the literal 0, rather than
|
|
185
264
|
# re-deriving positive?/negative?/zero? by hand a second time.
|
|
265
|
+
#
|
|
266
|
+
# @param comparator [Operator] the comparison operator to apply
|
|
267
|
+
# @param lhs [Object] the already-resolved left operand
|
|
268
|
+
# @param rhs [Object] the already-resolved right operand
|
|
269
|
+
# @return [Boolean] `comparator`'s result over `lhs`/`rhs`
|
|
270
|
+
# @raise [EvaluationError] if `comparator` tests less-than and
|
|
271
|
+
# `lhs`/`rhs` are not both numeric or both String
|
|
186
272
|
def apply(comparator, lhs, rhs)
|
|
187
273
|
result = (comparator.compares_less_than && less_than(lhs, rhs)) ||
|
|
188
274
|
(comparator.compares_equal && equal?(lhs, rhs))
|
|
189
275
|
comparator.negated ? !result : result
|
|
190
276
|
end
|
|
191
277
|
|
|
278
|
+
# Compares two already-resolved operands for the `<` primitive.
|
|
279
|
+
#
|
|
280
|
+
# @param lhs [Object] the already-resolved left operand
|
|
281
|
+
# @param rhs [Object] the already-resolved right operand
|
|
282
|
+
# @return [Boolean] whether `lhs` is less than `rhs`, comparing
|
|
283
|
+
# numerically if both are numeric, lexically if both are String
|
|
284
|
+
# @raise [EvaluationError] if `lhs`/`rhs` are not both numeric or
|
|
285
|
+
# both String
|
|
192
286
|
def less_than(lhs, rhs)
|
|
193
287
|
left = Resolver.numeric(lhs)
|
|
194
288
|
right = Resolver.numeric(rhs)
|
|
@@ -199,6 +293,12 @@ module Hecks
|
|
|
199
293
|
"comparison of #{class_of(lhs)} with #{Resolver.describe(rhs)} failed"
|
|
200
294
|
end
|
|
201
295
|
|
|
296
|
+
# Compares two already-resolved operands for the `equal` primitive.
|
|
297
|
+
#
|
|
298
|
+
# @param lhs [Object] the already-resolved left operand
|
|
299
|
+
# @param rhs [Object] the already-resolved right operand
|
|
300
|
+
# @return [Boolean] whether `lhs` equals `rhs`, comparing
|
|
301
|
+
# numerically if both are numeric, `==` otherwise
|
|
202
302
|
def equal?(lhs, rhs)
|
|
203
303
|
left = Resolver.numeric(lhs)
|
|
204
304
|
right = Resolver.numeric(rhs)
|
|
@@ -207,26 +307,42 @@ module Hecks
|
|
|
207
307
|
lhs == rhs
|
|
208
308
|
end
|
|
209
309
|
|
|
310
|
+
# Reports whether `value` is truthy, for a bare `Resolve` node.
|
|
311
|
+
#
|
|
312
|
+
# @param value [Object] the value to test
|
|
313
|
+
# @return [Boolean] Ruby's own truthiness: `false` for `nil` and
|
|
314
|
+
# `false`, `true` for everything else
|
|
210
315
|
def truthy?(value)
|
|
211
316
|
!value.nil? && value != false
|
|
212
317
|
end
|
|
213
318
|
|
|
319
|
+
# Names `value`'s class, for a refusal message.
|
|
320
|
+
#
|
|
321
|
+
# @param value [Object] the value to name
|
|
322
|
+
# @return [String] `"nil"` for `nil`, otherwise `value.class.name`
|
|
214
323
|
def class_of(value)
|
|
215
324
|
value.nil? ? "nil" : value.class.name
|
|
216
325
|
end
|
|
217
326
|
|
|
218
|
-
#
|
|
327
|
+
# The same mis-split `Resolver.match_call` had (its own comment
|
|
219
328
|
# has the full story), found here too by the same generator: a
|
|
220
|
-
# `.include?` needle can itself be — or contain —
|
|
329
|
+
# `.include?` needle can itself be — or contain — another
|
|
221
330
|
# `.include?` call (`"".include?(arr.all? { |el| "".include?("")
|
|
222
331
|
# }.to_s)`, a String built via `.to_s` off a block predicate
|
|
223
332
|
# whose own body happens to include one) — `rindex` finds the
|
|
224
|
-
#
|
|
333
|
+
# innermost occurrence, not the outermost this split actually
|
|
225
334
|
# needs. Fixed identically: try each occurrence left to right,
|
|
226
335
|
# keep the first whose own balanced-paren match reaches the
|
|
227
336
|
# string's last character — `Resolver.matching_paren` is reused
|
|
228
337
|
# directly rather than duplicated, the same depth-tracking rule
|
|
229
338
|
# either grammar layer needs here.
|
|
339
|
+
# Splits `expr` at its outermost `.include?(...)` call, if any.
|
|
340
|
+
#
|
|
341
|
+
# @param expr [String] the boolean-position expression text
|
|
342
|
+
# @return [Array(String, String), nil] the `[haystack_text,
|
|
343
|
+
# needle_text]` pair for the outermost `.include?(` occurrence
|
|
344
|
+
# whose matching close paren reaches `expr`'s last character, or
|
|
345
|
+
# `nil` if none does
|
|
230
346
|
def match_include(expr)
|
|
231
347
|
start = 0
|
|
232
348
|
marker = ".include?("
|
|
@@ -246,6 +362,19 @@ module Hecks
|
|
|
246
362
|
# does.
|
|
247
363
|
INCLUDE_HAYSTACKS = Hecks::Vocabulary.fetch("IncludeHaystack")
|
|
248
364
|
|
|
365
|
+
# Resolves and evaluates an `Include` node's own `.include?` test.
|
|
366
|
+
#
|
|
367
|
+
# @param parts [Array(Object, Object)] the `[haystack, needle]`
|
|
368
|
+
# pair of `Resolver` leaf nodes from `Include#haystack`/`#needle`
|
|
369
|
+
# @param state [Hash{Symbol => Object}] the stored attribute values
|
|
370
|
+
# the operands may resolve against
|
|
371
|
+
# @param attrs [Hash{Symbol => Object}] the call's own argument
|
|
372
|
+
# values, checked before `state`
|
|
373
|
+
# @return [Boolean] whether the resolved haystack includes the
|
|
374
|
+
# resolved needle (`equal?`-compared for an Array, `#include?`
|
|
375
|
+
# for a String); `false` for any other resolved haystack type
|
|
376
|
+
# @raise [EvaluationError] if the haystack resolves to a String and
|
|
377
|
+
# the needle does not
|
|
249
378
|
def includes?(parts, state, attrs)
|
|
250
379
|
haystack, needle = parts
|
|
251
380
|
wanted = Resolver.interpret(needle, state, attrs)
|
|
@@ -260,6 +389,11 @@ module Hecks
|
|
|
260
389
|
end
|
|
261
390
|
end
|
|
262
391
|
|
|
392
|
+
# Strips a redundant outer pair of parens, recursively.
|
|
393
|
+
#
|
|
394
|
+
# @param expr [String] the expression text
|
|
395
|
+
# @return [String] `expr` with every redundant outer `(...)` pair
|
|
396
|
+
# removed; `expr` unchanged if it is not wholly parenthesized
|
|
263
397
|
def strip_parens(expr)
|
|
264
398
|
return expr unless expr.start_with?("(") && expr.end_with?(")")
|
|
265
399
|
|
|
@@ -272,6 +406,14 @@ module Hecks
|
|
|
272
406
|
strip_parens(expr[1..-2].strip)
|
|
273
407
|
end
|
|
274
408
|
|
|
409
|
+
# Splits `expr` at its first top-level occurrence of `operator`.
|
|
410
|
+
#
|
|
411
|
+
# @param expr [String] the expression text
|
|
412
|
+
# @param operator [String] the operator text to split on, such as
|
|
413
|
+
# `"||"` or `"&&"`
|
|
414
|
+
# @return [Array(String, String), nil] the `[left, right]` operand
|
|
415
|
+
# text around the split, or `nil` if `expr` has no top-level
|
|
416
|
+
# occurrence
|
|
275
417
|
def split_top_level(expr, operator)
|
|
276
418
|
index = top_level_index(expr, operator)
|
|
277
419
|
return nil unless index
|
|
@@ -279,6 +421,17 @@ module Hecks
|
|
|
279
421
|
[expr[0...index].strip, expr[(index + operator.length)..].strip]
|
|
280
422
|
end
|
|
281
423
|
|
|
424
|
+
# Splits `expr` at its first top-level occurrence of `operator`
|
|
425
|
+
# that is not part of a longer operator's own spelling (`==` is
|
|
426
|
+
# not mistaken for the middle of `===`, `<` is not mistaken for
|
|
427
|
+
# the leading `<` of `<=`).
|
|
428
|
+
#
|
|
429
|
+
# @param expr [String] the expression text
|
|
430
|
+
# @param operator [String] the comparison operator text to split
|
|
431
|
+
# on, such as `"=="` or `"<"`
|
|
432
|
+
# @return [Array(String, String), nil] the `[left, right]` operand
|
|
433
|
+
# text around the split, or `nil` if `expr` has no matching
|
|
434
|
+
# top-level occurrence
|
|
282
435
|
def split_comparison(expr, operator)
|
|
283
436
|
index = top_level_index(expr, operator) { |at| !part_of_longer?(expr, at, operator) }
|
|
284
437
|
return nil unless index
|
|
@@ -286,6 +439,15 @@ module Hecks
|
|
|
286
439
|
[expr[0...index].strip, expr[(index + operator.length)..].strip]
|
|
287
440
|
end
|
|
288
441
|
|
|
442
|
+
# Reports whether the occurrence of `operator` at `index` is
|
|
443
|
+
# actually the middle of a longer operator's own spelling (`==`
|
|
444
|
+
# inside `===`, or `<`/`>`/`!`/`=` immediately before a bare `=`).
|
|
445
|
+
#
|
|
446
|
+
# @param expr [String] the expression text
|
|
447
|
+
# @param index [Integer] the index of the candidate occurrence
|
|
448
|
+
# @param operator [String] the operator text being tried
|
|
449
|
+
# @return [Boolean] whether this occurrence belongs to a longer
|
|
450
|
+
# operator and should be skipped
|
|
289
451
|
def part_of_longer?(expr, index, operator)
|
|
290
452
|
after = expr[index + operator.length]
|
|
291
453
|
before = index.positive? ? expr[index - 1] : nil
|
|
@@ -296,6 +458,21 @@ module Hecks
|
|
|
296
458
|
false
|
|
297
459
|
end
|
|
298
460
|
|
|
461
|
+
# A grammar's own depth-aware scanner, shared by `split_top_level`/
|
|
462
|
+
# `split_comparison` — one pass finds the first top-level
|
|
463
|
+
# occurrence of `operator`, tracking quotes and every bracket
|
|
464
|
+
# kind (`(`/`)`, `{`/`}`, `[`/`]`) so an operator inside a nested
|
|
465
|
+
# call, block predicate, or array literal is never mistaken for a
|
|
466
|
+
# split point at this level.
|
|
467
|
+
#
|
|
468
|
+
# @param expr [String] the expression text
|
|
469
|
+
# @param operator [String] the operator text to search for
|
|
470
|
+
# @yieldparam index [Integer] a candidate top-level occurrence's
|
|
471
|
+
# index, offered so the caller can reject it (`split_comparison`
|
|
472
|
+
# uses this to skip a longer operator's own spelling)
|
|
473
|
+
# @yieldreturn [Boolean] whether to accept this occurrence
|
|
474
|
+
# @return [Integer, nil] the accepted occurrence's index, or `nil`
|
|
475
|
+
# if `operator` has no top-level occurrence the block accepts
|
|
299
476
|
def top_level_index(expr, operator)
|
|
300
477
|
depth = 0
|
|
301
478
|
quote = nil
|
|
@@ -311,13 +488,13 @@ module Hecks
|
|
|
311
488
|
# `{`/`}` depth -- vendored addition, not (yet) upstream
|
|
312
489
|
# hecks (migration plan task 9): this method already
|
|
313
490
|
# treats `(`/`)` as a grouping construct so an operator
|
|
314
|
-
#
|
|
491
|
+
# inside a call's parens is never mistaken for a top-level
|
|
315
492
|
# split point ; `{`/`}` needed the identical treatment the
|
|
316
493
|
# moment `Bluebook::Expression::Resolver` grew block-taking
|
|
317
494
|
# `.all?`/`.any?`/`.none? { |s| PREDICATE }` support (see
|
|
318
495
|
# resolver.rb's own `BlockPredicate` addition) -- without
|
|
319
|
-
# this, an operator
|
|
320
|
-
# `s.length > 0`) reads as a top-level split of the
|
|
496
|
+
# this, an operator inside the block's own predicate (e.g.
|
|
497
|
+
# `s.length > 0`) reads as a top-level split of the whole
|
|
321
498
|
# `value.split("::").all? { |s| s.length > 0 }` expression,
|
|
322
499
|
# confirmed live via `Lexicon::Lexicon.Lookup`/`Query::Query.
|
|
323
500
|
# Run` (the exact `Phrase` invariant this gap was found
|
|
@@ -329,16 +506,17 @@ module Hecks
|
|
|
329
506
|
# a quoted literal either, so this sits beside the existing
|
|
330
507
|
# paren-depth branch, not instead of it.
|
|
331
508
|
#
|
|
332
|
-
# `[`/`]` -- the identical lesson a
|
|
509
|
+
# `[`/`]` -- the identical lesson a third time (found live via
|
|
333
510
|
# the type-directed bounded-exhaustive expression generator,
|
|
334
511
|
# Phase 7 of the equivalence-gap plan): `Resolver::ArrayLiteral`
|
|
335
512
|
# (`[a, b]`) can appear as a general sub-expression, not only
|
|
336
513
|
# as `.include?`'s own haystack, the moment an array-typed
|
|
337
514
|
# attribute or a synthesized literal is embedded anywhere else
|
|
338
|
-
# --
|
|
339
|
-
#
|
|
340
|
-
#
|
|
341
|
-
#
|
|
515
|
+
# -- so without counting `[`/`]` toward depth here too, an
|
|
516
|
+
# element containing a top-level `+`/comparison of its own
|
|
517
|
+
# (`[0, 0 + 0]`) reads as a split point for this expression's
|
|
518
|
+
# own boolean/comparison grammar, exactly the way an
|
|
519
|
+
# un-tracked `{`/`}` does for block predicates.
|
|
342
520
|
elsif ["(", "{", "["].include?(char)
|
|
343
521
|
depth += 1
|
|
344
522
|
elsif [")", "}", "]"].include?(char)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
#
|
|
1
|
+
# **Hand-written** — the block-predicate/find family of the leaf grammar
|
|
2
2
|
# (`Bluebook::Expression::Resolver`'s own `.all?`/`.any?`/`.none?`/
|
|
3
3
|
# `.find` suffixes), split into this sibling file to keep resolver.rb
|
|
4
|
-
# under Metrics/ModuleLength (350) : this reopens the
|
|
4
|
+
# under Metrics/ModuleLength (350) : this reopens the same `Resolver`
|
|
5
5
|
# module resolver.rb defines, the long nested `module A; module B; ...`
|
|
6
6
|
# form (not the compact `A::B` form) so bare constant/method lookups
|
|
7
7
|
# here (`EvaluationError`, `Evaluator`, `describe`, `unwrap_scalar`,
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
module Hecks
|
|
13
13
|
module Bluebook
|
|
14
14
|
module Expression
|
|
15
|
-
# Reopens the
|
|
15
|
+
# Reopens the same `Resolver` module resolver.rb defines — see this
|
|
16
16
|
# file's own header comment above for why the block-predicate/find
|
|
17
17
|
# family lives here rather than in resolver.rb itself.
|
|
18
18
|
module Resolver
|
|
@@ -23,12 +23,12 @@ module Hecks
|
|
|
23
23
|
# (`value.split("::").all? { |s| s.length > 0 }`). Structurally
|
|
24
24
|
# different from every other addition in this file: every prior
|
|
25
25
|
# suffix is a flat receiver -> scalar transform, but a block
|
|
26
|
-
# predicate needs to evaluate its own sub-expression
|
|
27
|
-
#
|
|
26
|
+
# predicate needs to evaluate its own sub-expression once per
|
|
27
|
+
# element with the block parameter bound to that element. Kept
|
|
28
28
|
# minimal per the migration plan's own instruction -- no
|
|
29
29
|
# persistent iteration-variable concept added to Resolver's
|
|
30
30
|
# state model at all ; `predicate` below is a fully-parsed
|
|
31
|
-
#
|
|
31
|
+
# evaluator ast (not a Resolver ast -- the predicate is a
|
|
32
32
|
# boolean/comparison expression like `s.length > 0`, exactly the
|
|
33
33
|
# grammar `Bluebook::Expression::Evaluator` owns, not this
|
|
34
34
|
# module's own leaf grammar), parsed once at `parse`-time same as
|
|
@@ -78,7 +78,7 @@ module Hecks
|
|
|
78
78
|
|
|
79
79
|
# Every suffix that opens a `{ |x| ... }` block, `.find` included
|
|
80
80
|
# -- shared by `parse_block_opener` below, and by nothing else
|
|
81
|
-
# (this is
|
|
81
|
+
# (this is not `BLOCK_PREDICATE_MODES` — `.find` isn't a mode
|
|
82
82
|
# `evaluate_block_predicate` aggregates through, it builds a
|
|
83
83
|
# `Find` node instead, see below).
|
|
84
84
|
BLOCK_OPENER_SUFFIXES = (BLOCK_PREDICATE_MODES.keys + ["find"]).freeze
|
|
@@ -88,27 +88,27 @@ module Hecks
|
|
|
88
88
|
# last among the suffix rules (right before the `Lookup`
|
|
89
89
|
# catch-all) since a block's own predicate text can itself
|
|
90
90
|
# contain almost anything a leaf expression can, including
|
|
91
|
-
#
|
|
91
|
+
# another block-opening suffix -- letting every more specific
|
|
92
92
|
# rule above try first avoids this one accidentally swallowing a
|
|
93
93
|
# receiver another rule was meant to parse.
|
|
94
94
|
#
|
|
95
|
-
#
|
|
95
|
+
# One combined header regex over all four suffixes together,
|
|
96
96
|
# not `.find` and `.all?/any?/none?` scanned separately (that
|
|
97
97
|
# was this file's own first cut, and it broke the moment a
|
|
98
|
-
# block predicate's own predicate text contained a
|
|
98
|
+
# block predicate's own predicate text contained a different
|
|
99
99
|
# kind of block-opener than the one being scanned for --
|
|
100
100
|
# `legs.any? { |leg| ... legs.find { |o| ... } ... }` : scanning
|
|
101
|
-
# for `.find`
|
|
101
|
+
# for `.find` first found the inner `.find`, not the outer
|
|
102
102
|
# `.any?`, because a non-greedy receiver capture only guarantees
|
|
103
|
-
# the
|
|
104
|
-
# occurrence of
|
|
103
|
+
# the first occurrence of its own suffix, not the first
|
|
104
|
+
# occurrence of any block-opening suffix -- confirmed live via
|
|
105
105
|
# the shipping domain's own re-routing rules, the same
|
|
106
106
|
# "no implicit conversion of Symbol into Integer" signature the
|
|
107
107
|
# original nested-`.any?` bug had, not inferred). Scanning for
|
|
108
|
-
# all four
|
|
109
|
-
# match win fixes both directions (`.find` nested in `.any?`
|
|
110
|
-
# `.any?` nested in `.find`) with the
|
|
111
|
-
# true receiver never itself contains
|
|
108
|
+
# all four at once and letting the regex engine's own leftmost
|
|
109
|
+
# match win fixes both directions (`.find` nested in `.any?` or
|
|
110
|
+
# `.any?` nested in `.find`) with the same one rule, since the
|
|
111
|
+
# true receiver never itself contains any of these four words
|
|
112
112
|
# followed by `{`.
|
|
113
113
|
#
|
|
114
114
|
# `matching_brace` walks forward counting `{`/`}` depth from
|
|
@@ -119,6 +119,10 @@ module Hecks
|
|
|
119
119
|
# is captured from whatever follows the closing brace ; every
|
|
120
120
|
# other suffix instead requires nothing follow it at all (the
|
|
121
121
|
# `BlockPredicate` shape, unchanged from before this rewrite).
|
|
122
|
+
#
|
|
123
|
+
# @param expr [String] the leaf expression text to parse
|
|
124
|
+
# @return [Find, BlockPredicate, nil] the parsed node, or nil when `expr` does
|
|
125
|
+
# not open a `.all?`/`.any?`/`.none?`/`.find` block, or its brace never closes
|
|
122
126
|
def parse_block_opener(expr)
|
|
123
127
|
pattern = /\A(.+?)\.(#{BLOCK_OPENER_SUFFIXES.map { |suffix| Regexp.escape(suffix) }.join('|')})\s*\{\s*\|(\w+)\|\s*/m
|
|
124
128
|
header = expr.match(pattern)
|
|
@@ -155,6 +159,11 @@ module Hecks
|
|
|
155
159
|
# quoted substring (`.start_with?("}")`) never miscounts, the
|
|
156
160
|
# same discipline `split_addition`/`array_elements` already
|
|
157
161
|
# apply for their own depth tracking.
|
|
162
|
+
#
|
|
163
|
+
# @param expr [String] the text to scan
|
|
164
|
+
# @param start [Integer] the index just after the opening `{`, where depth is 1
|
|
165
|
+
# @return [Integer, nil] the index of the matching `}`, or nil if `expr` runs
|
|
166
|
+
# out before depth returns to 0
|
|
158
167
|
def matching_brace(expr, start)
|
|
159
168
|
depth = 1
|
|
160
169
|
quote = nil
|
|
@@ -186,6 +195,13 @@ module Hecks
|
|
|
186
195
|
# variable concept added anywhere else in Resolver's state model,
|
|
187
196
|
# just `attrs` extended with the bound name for the span of that
|
|
188
197
|
# one predicate evaluation, discarded immediately after.
|
|
198
|
+
#
|
|
199
|
+
# @param node [BlockPredicate] the parsed `.all?`/`.any?`/`.none?` node
|
|
200
|
+
# @param collection [Object] the interpreted receiver, expected to be an Array
|
|
201
|
+
# @param state [Hash{Symbol => Object}] the record's own current state
|
|
202
|
+
# @param attrs [Hash{Symbol => Object}] the command's own bound arguments
|
|
203
|
+
# @return [Boolean] whether the collection satisfies `node.mode`
|
|
204
|
+
# @raise [EvaluationError] if `collection` is not an Array
|
|
189
205
|
def evaluate_block_predicate(node, collection, state, attrs)
|
|
190
206
|
raise EvaluationError, "#{node.mode}? expects a list, got #{describe(collection)}" unless collection.is_a?(Array)
|
|
191
207
|
|
|
@@ -202,6 +218,18 @@ module Hecks
|
|
|
202
218
|
# evaluation -- `attrs` wins over `state` in `fetch` (see below),
|
|
203
219
|
# so the bound name shadows any same-named state/attrs field for
|
|
204
220
|
# the span of this one call only ; nothing persists past it.
|
|
221
|
+
#
|
|
222
|
+
# @param node [BlockPredicate, Find] the node whose `param`/
|
|
223
|
+
# `predicate` to bind and interpret
|
|
224
|
+
# @param element [Object] the one collection element to bind
|
|
225
|
+
# `node.param` to
|
|
226
|
+
# @param state [Hash{Symbol => Object}] the record's own current
|
|
227
|
+
# state
|
|
228
|
+
# @param attrs [Hash{Symbol => Object}] the command's own bound
|
|
229
|
+
# arguments
|
|
230
|
+
# @return [Object] `node.predicate` interpreted with `node.param`
|
|
231
|
+
# bound to `element`
|
|
232
|
+
# @raise [EvaluationError] if `node.predicate` refuses to evaluate
|
|
205
233
|
def interpret_with_element(node, element, state, attrs)
|
|
206
234
|
Evaluator.interpret(node.predicate, state, attrs.merge(node.param.to_sym => element))
|
|
207
235
|
end
|
|
@@ -210,7 +238,7 @@ module Hecks
|
|
|
210
238
|
# comment above. Reuses `interpret_with_element` unchanged
|
|
211
239
|
# (below, shared with `BlockPredicate` — both bind `node.param`
|
|
212
240
|
# to one element and interpret `node.predicate` against it) to
|
|
213
|
-
# find the
|
|
241
|
+
# find the first element the predicate accepts, then projects
|
|
214
242
|
# `node.path` through it via `walk_path`, the same dotted-
|
|
215
243
|
# segment walk `lookup` uses for a plain attribute path. `nil`
|
|
216
244
|
# (no matching element, or a `path` segment that doesn't
|
|
@@ -219,6 +247,14 @@ module Hecks
|
|
|
219
247
|
# value case in this grammar already has, and the one a re-
|
|
220
248
|
# routing check like "is there a leg after this one" needs :
|
|
221
249
|
# not finding one is a normal outcome, not an error.
|
|
250
|
+
#
|
|
251
|
+
# @param node [Find] the parsed `.find` node
|
|
252
|
+
# @param collection [Object] the interpreted receiver, expected to be an Array
|
|
253
|
+
# @param state [Hash{Symbol => Object}] the record's own current state
|
|
254
|
+
# @param attrs [Hash{Symbol => Object}] the command's own bound arguments
|
|
255
|
+
# @return [Object, nil] the found element (or `node.path` projected through it),
|
|
256
|
+
# or nil when no element matches or a `path` segment does not resolve
|
|
257
|
+
# @raise [EvaluationError] if `collection` is not an Array
|
|
222
258
|
def found_of(node, collection, state, attrs)
|
|
223
259
|
raise EvaluationError, "find expects a list, got #{describe(collection)}" unless collection.is_a?(Array)
|
|
224
260
|
|