hecks 0.2.0 → 0.3.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 +5 -5
- data/lib/hecks/adapters/driven/claude_code.adapter +3 -0
- data/lib/hecks/adapters/driven/claude_code.rb +127 -0
- data/lib/hecks/adapters/driven/d1.adapter +12 -0
- data/lib/hecks/adapters/driven/d1.rb +403 -0
- data/lib/hecks/adapters/driven/folder.adapter +3 -0
- data/lib/hecks/adapters/driven/folder.rb +199 -0
- data/lib/hecks/adapters/driven/google_authentication.adapter +3 -0
- data/lib/hecks/adapters/driven/google_authentication.rb +100 -0
- data/lib/hecks/adapters/driven/governance_authorization.adapter +3 -0
- data/lib/hecks/adapters/driven/governance_authorization.rb +62 -0
- data/lib/hecks/adapters/driven/heki/journal.rb +51 -0
- data/lib/hecks/adapters/driven/heki/saga_store.rb +0 -0
- data/lib/hecks/adapters/driven/heki/snapshot.rb +38 -0
- data/lib/hecks/adapters/driven/heki.adapter +4 -0
- data/lib/hecks/adapters/driven/heki.rb +148 -0
- data/lib/hecks/adapters/driven/identity_registry.adapter +3 -0
- data/lib/hecks/adapters/driven/identity_registry.rb +26 -0
- data/lib/hecks/adapters/driven/in_memory_ordering.rb +51 -0
- data/lib/hecks/adapters/driven/lambda/client.rb +63 -0
- data/lib/hecks/adapters/driven/lambda.adapter +4 -0
- data/lib/hecks/adapters/driven/lambda.rb +131 -0
- data/lib/hecks/adapters/driven/memory.adapter +3 -0
- data/lib/hecks/adapters/driven/memory.rb +103 -0
- data/lib/hecks/adapters/driven/mock_stripe_adapter.adapter +3 -0
- data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +28 -0
- data/lib/hecks/adapters/driven/postgres/codec.rb +80 -0
- data/lib/hecks/adapters/driven/postgres/schema_builder.rb +168 -0
- data/lib/hecks/adapters/driven/postgres.adapter +5 -0
- data/lib/hecks/adapters/driven/postgres.rb +391 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage/era_store.rb +171 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage/field_cache.rb +190 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage/head_compiler.rb +429 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage/mint_transaction.rb +166 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage/provisioning.rb +286 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage/resumable_backfill.rb +168 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage/tail_merge.rb +163 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage/transform_installer.rb +114 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage.rb +137 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage_manager/coverage_check.rb +89 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage_manager/era_resolver.rb +82 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage_manager/merge_coordinator.rb +43 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage_manager/minter.rb +132 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage_manager.rb +74 -0
- data/lib/hecks/adapters/driven/postgres_era.adapter +6 -0
- data/lib/hecks/adapters/driven/postgres_era.rb +671 -0
- data/lib/hecks/adapters/driven/prism.adapter +3 -0
- data/lib/hecks/adapters/driven/prism.rb +80 -0
- data/lib/hecks/adapters/driven/secure_random_identity.adapter +3 -0
- data/lib/hecks/adapters/driven/secure_random_identity.rb +14 -0
- data/lib/hecks/adapters/driven/sql_query_builder.rb +221 -0
- data/lib/hecks/adapters/driven/sqlite/codec.rb +63 -0
- data/lib/hecks/adapters/driven/sqlite/projection.rb +105 -0
- data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +180 -0
- data/lib/hecks/adapters/driven/sqlite.adapter +9 -0
- data/lib/hecks/adapters/driven/sqlite.rb +285 -0
- data/lib/hecks/adapters/driven/system_clock.adapter +3 -0
- data/lib/hecks/adapters/driven/system_clock.rb +14 -0
- data/lib/hecks/adapters/driven.rb +36 -0
- data/lib/hecks/adapters.rb +6 -0
- data/lib/hecks/behaviors/dsl.rb +96 -0
- data/lib/hecks/behaviors/expectations.rb +280 -0
- data/lib/hecks/behaviors/ir.rb +31 -0
- data/lib/hecks/behaviors/rspec.rb +42 -0
- data/lib/hecks/behaviors/runner.rb +96 -0
- data/lib/hecks/behaviors.rb +25 -0
- data/lib/hecks/bluebook/aggregate.rb +108 -0
- data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +134 -0
- data/lib/hecks/bluebook/assembly/build.rb +48 -0
- data/lib/hecks/bluebook/assembly/contract.rb +112 -0
- data/lib/hecks/bluebook/assembly/contracts.rb +409 -0
- data/lib/hecks/bluebook/assembly/marks.rb +227 -0
- data/lib/hecks/bluebook/assembly/specializer.rb +70 -0
- data/lib/hecks/bluebook/assembly.rb +78 -0
- data/lib/hecks/bluebook/attribute.rb +96 -0
- data/lib/hecks/bluebook/behaviour/aggregate.rb +83 -0
- data/lib/hecks/bluebook/behaviour/attribute.rb +27 -0
- data/lib/hecks/bluebook/behaviour/chapter.rb +72 -0
- data/lib/hecks/bluebook/behaviour/command.rb +116 -0
- data/lib/hecks/bluebook/behaviour/domain_port.rb +25 -0
- data/lib/hecks/bluebook/behaviour/entity.rb +59 -0
- data/lib/hecks/bluebook/behaviour/hexagon.rb +50 -0
- data/lib/hecks/bluebook/behaviour/lifecycle.rb +51 -0
- data/lib/hecks/bluebook/behaviour/policy.rb +52 -0
- data/lib/hecks/bluebook/behaviour/process_manager.rb +38 -0
- data/lib/hecks/bluebook/behaviour/query.rb +10 -0
- data/lib/hecks/bluebook/behaviour/read_model.rb +29 -0
- data/lib/hecks/bluebook/behaviour/traits.rb +81 -0
- data/lib/hecks/bluebook/behaviour/value_object.rb +33 -0
- data/lib/hecks/bluebook/chapter.rb +68 -0
- data/lib/hecks/bluebook/command.rb +124 -0
- data/lib/hecks/bluebook/domain_port.rb +102 -0
- data/lib/hecks/bluebook/dsl/adapter_builder.rb +34 -0
- data/lib/hecks/bluebook/dsl/aggregate_builder.rb +906 -0
- data/lib/hecks/bluebook/dsl/attribute_collector.rb +327 -0
- data/lib/hecks/bluebook/dsl/binding_proxy.rb +71 -0
- data/lib/hecks/bluebook/dsl/bluebook_builder.rb +1018 -0
- data/lib/hecks/bluebook/dsl/command_builder.rb +626 -0
- data/lib/hecks/bluebook/dsl/const_shim.rb +81 -0
- data/lib/hecks/bluebook/dsl/domain_port_builder.rb +79 -0
- data/lib/hecks/bluebook/dsl/entity_builder.rb +305 -0
- data/lib/hecks/bluebook/dsl/generic_dispatch.rb +366 -0
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +163 -0
- data/lib/hecks/bluebook/dsl/identity_declaration.rb +191 -0
- data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +44 -0
- data/lib/hecks/bluebook/dsl/malformed.rb +7 -0
- data/lib/hecks/bluebook/dsl/policy_builder.rb +120 -0
- data/lib/hecks/bluebook/dsl/port_builder.rb +30 -0
- data/lib/hecks/bluebook/dsl/port_operation_builder.rb +142 -0
- data/lib/hecks/bluebook/dsl/process_manager_builder.rb +207 -0
- data/lib/hecks/bluebook/dsl/query_builder.rb +113 -0
- data/lib/hecks/bluebook/dsl/read_model_builder.rb +233 -0
- data/lib/hecks/bluebook/dsl/rule_reference.rb +172 -0
- data/lib/hecks/bluebook/dsl/translation_builder.rb +243 -0
- data/lib/hecks/bluebook/dsl/value_object_builder.rb +178 -0
- data/lib/hecks/bluebook/dsl/word_gate.rb +221 -0
- data/lib/hecks/bluebook/dsl/world_builder.rb +77 -0
- data/lib/hecks/bluebook/dsl.rb +45 -0
- data/lib/hecks/bluebook/entity.rb +103 -0
- data/lib/hecks/bluebook/expression/canonical_form.rb +55 -0
- data/lib/hecks/bluebook/expression/evaluator.rb +261 -0
- data/lib/hecks/bluebook/expression/projection.json +170 -0
- data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +233 -0
- data/lib/hecks/bluebook/expression/resolver.rb +630 -0
- data/lib/hecks/bluebook/expression.rb +13 -0
- data/lib/hecks/bluebook/hexagon.rb +60 -0
- data/lib/hecks/bluebook/lifecycle.rb +42 -0
- data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +54 -0
- data/lib/hecks/bluebook/meta_validator/judge.rb +559 -0
- data/lib/hecks/bluebook/meta_validator/plan.rb +332 -0
- data/lib/hecks/bluebook/meta_validator/port_judge.rb +47 -0
- data/lib/hecks/bluebook/meta_validator/readings.rb +350 -0
- data/lib/hecks/bluebook/meta_validator/reconstruction.rb +312 -0
- data/lib/hecks/bluebook/meta_validator/shapes.rb +246 -0
- data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +255 -0
- data/lib/hecks/bluebook/meta_validator/translation_judge.rb +138 -0
- data/lib/hecks/bluebook/meta_validator/world_judge.rb +78 -0
- data/lib/hecks/bluebook/meta_validator.rb +452 -0
- data/lib/hecks/bluebook/model_check.rb +318 -0
- data/lib/hecks/bluebook/pattern_subset.rb +120 -0
- data/lib/hecks/bluebook/policy.rb +41 -0
- data/lib/hecks/bluebook/process_manager.rb +86 -0
- data/lib/hecks/bluebook/project_discovery.rb +30 -0
- data/lib/hecks/bluebook/project_loader.rb +40 -0
- data/lib/hecks/bluebook/project_register.rb +82 -0
- data/lib/hecks/bluebook/query.rb +61 -0
- data/lib/hecks/bluebook/read_model.rb +109 -0
- data/lib/hecks/bluebook/reference.rb +74 -0
- data/lib/hecks/bluebook/smoke_test.rb +166 -0
- data/lib/hecks/bluebook/synthesizer.rb +95 -0
- data/lib/hecks/bluebook/translation.rb +92 -0
- data/lib/hecks/bluebook/value_object.rb +58 -0
- data/lib/hecks/bluebook.rb +74 -0
- data/lib/hecks/codemod.rb +342 -0
- data/lib/hecks/construct.rb +71 -0
- data/lib/hecks/deploy/bluebook/deploy.bluebook +219 -0
- data/lib/hecks/deploy/bluebook/deploy.hecksagon +4 -0
- data/lib/hecks/deploy/oidc.json +18 -0
- data/lib/hecks/doc/reference.rb +389 -0
- data/lib/hecks/embryonaut_bluebook.rb +75 -0
- data/lib/hecks/facade/cli_door.rb +116 -0
- data/lib/hecks/facade/cli_runner.rb +190 -0
- data/lib/hecks/facade/command_request.rb +105 -0
- data/lib/hecks/facade/handle.rb +173 -0
- data/lib/hecks/facade/json_door.rb +154 -0
- data/lib/hecks/facade/surface/aggregate_door.rb +185 -0
- data/lib/hecks/facade/surface/chapter.rb +107 -0
- data/lib/hecks/facade/surface.rb +48 -0
- data/lib/hecks/facade.rb +44 -0
- data/lib/hecks/forms/app.rb +300 -0
- data/lib/hecks/forms/command_form_renderer.rb +113 -0
- data/lib/hecks/forms/examples/banking_console.bluebook +3 -0
- data/lib/hecks/forms/field_renderer.rb +170 -0
- data/lib/hecks/forms/field_shape.rb +232 -0
- data/lib/hecks/forms/html.rb +53 -0
- data/lib/hecks/forms/index_renderer.rb +35 -0
- data/lib/hecks/forms/page.rb +157 -0
- data/lib/hecks/forms/params.rb +132 -0
- data/lib/hecks/forms/query_form_renderer.rb +114 -0
- data/lib/hecks/forms/record_renderer.rb +115 -0
- data/lib/hecks/forms/record_table.rb +63 -0
- data/lib/hecks/forms/reference_options.rb +30 -0
- data/lib/hecks/forms/value_object_shape.rb +46 -0
- data/lib/hecks/forms.rb +54 -0
- data/lib/hecks/fqn.rb +94 -0
- data/lib/hecks/framework/bluebook/compliance.bluebook +1 -0
- data/lib/hecks/framework/bluebook/console_settings.bluebook +489 -0
- data/lib/hecks/framework/bluebook/framework.hecksagon +32 -0
- data/lib/hecks/framework/bluebook/governance.bluebook +130 -0
- data/lib/hecks/framework/bluebook/identity.bluebook +90 -0
- data/lib/hecks/framework/oidc.json +39 -0
- data/lib/hecks/framework.rb +90 -0
- data/lib/hecks/freezer.rb +67 -0
- data/lib/hecks/fuzzing/invalid_value_generator.rb +86 -0
- data/lib/hecks/fuzzing/isolated_boot.rb +92 -0
- data/lib/hecks/fuzzing/properties.rb +1146 -0
- data/lib/hecks/fuzzing/replay.rb +635 -0
- data/lib/hecks/fuzzing/sequence_generator/catalog.rb +93 -0
- data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +54 -0
- data/lib/hecks/fuzzing/sequence_generator/picker.rb +96 -0
- data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +185 -0
- data/lib/hecks/fuzzing/sequence_generator.rb +130 -0
- data/lib/hecks/fuzzing/value_generator.rb +177 -0
- data/lib/hecks/fuzzing.rb +12 -0
- data/lib/hecks/grammar/evolve.rb +291 -0
- data/lib/hecks/grammar/expression.bluebook +417 -0
- data/lib/hecks/grammar/expression_operators.json +1152 -0
- data/lib/hecks/grammar/grammar.hecksagon +20 -0
- data/lib/hecks/grammar/oidc.json +69 -0
- data/lib/hecks/grammar/translation.bluebook +203 -0
- data/lib/hecks/grammar.rb +178 -0
- data/lib/hecks/ir.rb +126 -0
- data/lib/hecks/language/adapter.bluebook +116 -0
- data/lib/hecks/language/bluebook/aggregate.bluebook +581 -0
- data/lib/hecks/language/bluebook/attaches/paging.bluebook +76 -0
- data/lib/hecks/language/bluebook/bluebook.bluebook +256 -0
- data/lib/hecks/language/bluebook/bluebook.hecksagon +24 -0
- data/lib/hecks/language/bluebook/command.bluebook +437 -0
- data/lib/hecks/language/bluebook/entity.bluebook +316 -0
- data/lib/hecks/language/bluebook/policy.bluebook +172 -0
- data/lib/hecks/language/bluebook/process_manager.bluebook +264 -0
- data/lib/hecks/language/bluebook/projection.bluebook +267 -0
- data/lib/hecks/language/bluebook/query.bluebook +245 -0
- data/lib/hecks/language/bluebook/shape.bluebook +292 -0
- data/lib/hecks/language/bluebook/syntax.bluebook +447 -0
- data/lib/hecks/language/bluebook/vocabulary.bluebook +364 -0
- data/lib/hecks/language/hecksagon/adapter_binding.bluebook +51 -0
- data/lib/hecks/language/hecksagon/domain_port.bluebook +76 -0
- data/lib/hecks/language/hecksagon/hecksagon.bluebook +131 -0
- data/lib/hecks/language/hecksagon/port_operation.bluebook +102 -0
- data/lib/hecks/language/oidc.json +313 -0
- data/lib/hecks/language/port.bluebook +92 -0
- data/lib/hecks/language/translation/translation.bluebook +110 -0
- data/lib/hecks/language/translation/translation_aggregate.bluebook +267 -0
- data/lib/hecks/language/world/wiring.bluebook +62 -0
- data/lib/hecks/language/world/world.bluebook +84 -0
- data/lib/hecks/literal.rb +125 -0
- data/lib/hecks/naming.rb +121 -0
- data/lib/hecks/ports/access_control.port +4 -0
- data/lib/hecks/ports/access_control.rb +62 -0
- data/lib/hecks/ports/agent/answers.rb +104 -0
- data/lib/hecks/ports/agent.port +4 -0
- data/lib/hecks/ports/agent.rb +167 -0
- data/lib/hecks/ports/authentication.port +4 -0
- data/lib/hecks/ports/authentication.rb +50 -0
- data/lib/hecks/ports/authorization.port +4 -0
- data/lib/hecks/ports/authorization.rb +59 -0
- data/lib/hecks/ports/clock.port +4 -0
- data/lib/hecks/ports/clock.rb +62 -0
- data/lib/hecks/ports/extraction.port +4 -0
- data/lib/hecks/ports/extraction.rb +37 -0
- data/lib/hecks/ports/identity_assignment.port +4 -0
- data/lib/hecks/ports/identity_assignment.rb +45 -0
- data/lib/hecks/ports/identity_generation.port +4 -0
- data/lib/hecks/ports/identity_generation.rb +49 -0
- data/lib/hecks/ports/identity_resolution.port +4 -0
- data/lib/hecks/ports/identity_resolution.rb +40 -0
- data/lib/hecks/ports/loading.port +4 -0
- data/lib/hecks/ports/loading.rb +13 -0
- data/lib/hecks/ports/persistence/append_only.rb +102 -0
- data/lib/hecks/ports/persistence/binding_policy.rb +56 -0
- data/lib/hecks/ports/persistence/execution.rb +18 -0
- data/lib/hecks/ports/persistence/lineage.rb +277 -0
- data/lib/hecks/ports/persistence/null_saga_store.rb +25 -0
- data/lib/hecks/ports/persistence/remote_runtime.rb +42 -0
- data/lib/hecks/ports/persistence/repository_factory.rb +30 -0
- data/lib/hecks/ports/persistence.port +4 -0
- data/lib/hecks/ports/persistence.rb +39 -0
- data/lib/hecks/ports/projection.port +4 -0
- data/lib/hecks/ports/projection.rb +76 -0
- data/lib/hecks/ports/query/in_memory.rb +60 -0
- data/lib/hecks/ports/query/ordering.rb +41 -0
- data/lib/hecks/ports/query.rb +36 -0
- data/lib/hecks/ports.rb +27 -0
- data/lib/hecks/projections/diagrams.rb +413 -0
- data/lib/hecks/projections/ir.rb +18 -0
- data/lib/hecks/projections/model/deviations.rb +98 -0
- data/lib/hecks/projections/model.rb +145 -0
- data/lib/hecks/projections/oidc.rb +110 -0
- data/lib/hecks/projections/parser_table.rb +159 -0
- data/lib/hecks/projections/reference.rb +38 -0
- data/lib/hecks/projections/shape.rb +31 -0
- data/lib/hecks/projections/statements.rb +110 -0
- data/lib/hecks/projections/vocabulary.rb +100 -0
- data/lib/hecks/projections.rb +32 -0
- data/lib/hecks/projector/cli_projector.rb +437 -0
- data/lib/hecks/projector/docs_projector.rb +321 -0
- data/lib/hecks/projector/exporter.rb +141 -0
- data/lib/hecks/projector/ir_projector.rb +18 -0
- data/lib/hecks/projector/narrate_projector.rb +243 -0
- data/lib/hecks/projector/target.rb +97 -0
- data/lib/hecks/projector.rb +186 -0
- data/lib/hecks/query_ir.rb +392 -0
- data/lib/hecks/query_specification/common/authorization_spec.rb +9 -0
- data/lib/hecks/query_specification/common/comparators.rb +25 -0
- data/lib/hecks/query_specification/common/comparison.rb +174 -0
- data/lib/hecks/query_specification/common/cursor_spec.rb +9 -0
- data/lib/hecks/query_specification/common/dsl.rb +58 -0
- data/lib/hecks/query_specification/common/inspection_spec.rb +9 -0
- data/lib/hecks/query_specification/common/limit_spec.rb +9 -0
- data/lib/hecks/query_specification/common/null_policy.rb +80 -0
- data/lib/hecks/query_specification/common/null_semantics.rb +11 -0
- data/lib/hecks/query_specification/common/offset_spec.rb +9 -0
- data/lib/hecks/query_specification/common/options.rb +32 -0
- data/lib/hecks/query_specification/common/order_by.rb +9 -0
- data/lib/hecks/query_specification/common/specification.rb +10 -0
- data/lib/hecks/query_specification/common/where_clause.rb +9 -0
- data/lib/hecks/query_specification/field_path.rb +89 -0
- data/lib/hecks/query_specification/hop_path.rb +130 -0
- data/lib/hecks/query_specification/read_model/specification.rb +18 -0
- data/lib/hecks/query_specification.rb +14 -0
- data/lib/hecks/rendering.rb +48 -0
- data/lib/hecks/router/namespace_installer.rb +157 -0
- data/lib/hecks/router.rb +70 -0
- data/lib/hecks/runtime/caller.rb +50 -0
- data/lib/hecks/runtime/capability_graph.rb +44 -0
- data/lib/hecks/runtime/command_interpreter/argument_gate.rb +124 -0
- data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +248 -0
- data/lib/hecks/runtime/command_interpreter.rb +413 -0
- data/lib/hecks/runtime/command_rules/admissibility.rb +307 -0
- data/lib/hecks/runtime/command_rules/arithmetic.rb +254 -0
- data/lib/hecks/runtime/command_rules/authorization.rb +64 -0
- data/lib/hecks/runtime/command_rules/emission.rb +34 -0
- data/lib/hecks/runtime/command_rules/references.rb +181 -0
- data/lib/hecks/runtime/command_rules.rb +28 -0
- data/lib/hecks/runtime/dependency_planning.rb +231 -0
- data/lib/hecks/runtime/dispatcher.rb +286 -0
- data/lib/hecks/runtime/entity_element.rb +253 -0
- data/lib/hecks/runtime/entity_interpreter.rb +228 -0
- data/lib/hecks/runtime/era_check.rb +136 -0
- data/lib/hecks/runtime/era_guard/shape_diff.rb +128 -0
- data/lib/hecks/runtime/era_guard.rb +193 -0
- data/lib/hecks/runtime/era_tamper.rb +61 -0
- data/lib/hecks/runtime/errors.rb +100 -0
- data/lib/hecks/runtime/event.rb +51 -0
- data/lib/hecks/runtime/identity.rb +128 -0
- data/lib/hecks/runtime/instance.rb +120 -0
- data/lib/hecks/runtime/interpreting.rb +69 -0
- data/lib/hecks/runtime/loader.rb +143 -0
- data/lib/hecks/runtime/policy_interpreter.rb +366 -0
- data/lib/hecks/runtime/port_operation_interpreter.rb +210 -0
- data/lib/hecks/runtime/query_interpreter.rb +254 -0
- data/lib/hecks/runtime/reaction_invocation.rb +245 -0
- data/lib/hecks/runtime/read_model_interpreter.rb +266 -0
- data/lib/hecks/runtime/rebuild_sweep.rb +74 -0
- data/lib/hecks/runtime/reference_hop.rb +99 -0
- data/lib/hecks/runtime/refusal_wording.rb +124 -0
- data/lib/hecks/runtime/registry/saga_persistence.rb +70 -0
- data/lib/hecks/runtime/registry/verification.rb +158 -0
- data/lib/hecks/runtime/registry.rb +218 -0
- data/lib/hecks/runtime/remote_dispatcher.rb +143 -0
- data/lib/hecks/runtime/routing.rb +96 -0
- data/lib/hecks/runtime/saga_interpreter/correlation.rb +97 -0
- data/lib/hecks/runtime/saga_interpreter.rb +301 -0
- data/lib/hecks/runtime/storage_shape.rb +120 -0
- data/lib/hecks/runtime/tenant_check.rb +84 -0
- data/lib/hecks/runtime/tenant_scope.rb +55 -0
- data/lib/hecks/runtime/value/admission.rb +112 -0
- data/lib/hecks/runtime/value/coercion.rb +500 -0
- data/lib/hecks/runtime/value/invariant_violation.rb +5 -0
- data/lib/hecks/runtime/value.rb +125 -0
- data/lib/hecks/runtime.rb +95 -0
- data/lib/hecks/translation/audit/approval_digest.rb +31 -0
- data/lib/hecks/translation/audit/layer_one.rb +37 -0
- data/lib/hecks/translation/audit/layer_two.rb +77 -0
- data/lib/hecks/translation/audit/unfed_report.rb +42 -0
- data/lib/hecks/translation/audit.rb +70 -0
- data/lib/hecks/translation/reattest.rb +72 -0
- data/lib/hecks/translation/rule_compiler.rb +120 -0
- data/lib/hecks/translation/scaffold/differ.rb +183 -0
- data/lib/hecks/translation/scaffold/renderer.rb +41 -0
- data/lib/hecks/translation/scaffold/writer.rb +28 -0
- data/lib/hecks/translation/scaffold.rb +30 -0
- data/lib/hecks/translation.rb +11 -0
- data/lib/hecks/version.rb +16 -0
- data/lib/hecks/vocabulary.rb +213 -0
- data/lib/hecks.rb +118 -6
- metadata +401 -222
- data/bin/hecks +0 -7
- data/bin/hecks-package +0 -65
- data/bin/hecks_console +0 -12
- data/bin/hecks_serverless +0 -6
- data/lib/cli/build.rb +0 -14
- data/lib/cli/command_runner.rb +0 -28
- data/lib/cli/console.rb +0 -10
- data/lib/cli/generate.rb +0 -37
- data/lib/cli/hecks-cli.rb +0 -27
- data/lib/cli/test.rb +0 -57
- data/lib/console/commands.rb +0 -8
- data/lib/console/hecks-console.rb +0 -1
- data/lib/packager/README.md +0 -0
- data/lib/packager/app_runner.rb +0 -21
- data/lib/packager/args.rb +0 -26
- data/lib/packager/compatibility/fixnum.rb +0 -6
- data/lib/packager/hecks.rb +0 -39
- data/lib/packager/query_runner.rb +0 -21
- data/lib/packager/resources/Dockerfile +0 -11
- data/lib/packager/resources/app_binary +0 -7
- data/lib/packager/resources/bundle_config +0 -3
- data/lib/packager/resources/traveling-ruby-20150715-2.2.2-linux-x86_64.tar.gz +0 -0
- data/lib/packager/resources/traveling-ruby-20150715-2.2.2-osx.tar.gz +0 -0
- data/lib/serverless/Domain +0 -32
- data/lib/serverless/cli.rb +0 -75
- data/lib/serverless/resources/command_name.js +0 -5
- data/lib/serverless/resources/environment.js +0 -7
- data/lib/serverless/resources/handler.js.tt +0 -28
- data/lib/serverless/resources/run_binary.js +0 -22
- data/lib/serverless/resources/serverless.yml +0 -20
|
@@ -0,0 +1,630 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
require_relative "../../rendering"
|
|
3
|
+
require_relative "../../vocabulary"
|
|
4
|
+
require_relative "resolver/block_predicates"
|
|
5
|
+
|
|
6
|
+
module Hecks
|
|
7
|
+
module Bluebook
|
|
8
|
+
module Expression
|
|
9
|
+
class EvaluationError < StandardError; end
|
|
10
|
+
|
|
11
|
+
module Resolver
|
|
12
|
+
SIGN_TESTS = Hecks::Vocabulary.fetch("SignTest")
|
|
13
|
+
|
|
14
|
+
# Which Comparison operator each sign test is sugar for, against the
|
|
15
|
+
# literal 0 — declared the same way in Vocabulary::SignTest's
|
|
16
|
+
# compares_via (language/bluebook/vocabulary.bluebook) ; spec/vocabulary_conformance_spec
|
|
17
|
+
# holds the two tables equal.
|
|
18
|
+
SIGN_TEST_OPERATORS = {
|
|
19
|
+
"positive?" => ">",
|
|
20
|
+
"negative?" => "<",
|
|
21
|
+
"zero?" => "=="
|
|
22
|
+
}.freeze
|
|
23
|
+
|
|
24
|
+
# The leaf grammar an expression's dotted/arithmetic side parses into.
|
|
25
|
+
# Which node a string produces is a pure function of the string, so
|
|
26
|
+
# parse() runs once per distinct leaf (folded into Evaluator's own
|
|
27
|
+
# ast_cache — see evaluator.rb) and interpret() reads state/attrs
|
|
28
|
+
# fresh on every call. Only Lookup — the true leaf — ever touches
|
|
29
|
+
# state/attrs.
|
|
30
|
+
IntegerLiteral = Struct.new(:value, keyword_init: true)
|
|
31
|
+
FloatLiteral = Struct.new(:value, keyword_init: true)
|
|
32
|
+
StringLiteral = Struct.new(:value, keyword_init: true)
|
|
33
|
+
BoolLiteral = Struct.new(:value, keyword_init: true)
|
|
34
|
+
# `["active", "suspended"]` — a literal set, the haystack half of
|
|
35
|
+
# an `.include?`. Vocabulary::IncludeHaystack has always ADMITTED
|
|
36
|
+
# Array (and Evaluator#includes? has always had a `when Array`
|
|
37
|
+
# arm), but nothing could produce one: there was no array-literal
|
|
38
|
+
# node, so `["a", "b"].include?(x)` fell through to Lookup and
|
|
39
|
+
# refused with `cannot resolve "[\"a\", \"b\"]"`. A declared
|
|
40
|
+
# capability with no way to spell it.
|
|
41
|
+
ArrayLiteral = Struct.new(:elements, keyword_init: true)
|
|
42
|
+
# A plain class, not `Struct.new(keyword_init: true)` — every
|
|
43
|
+
# sibling leaf node here carries at least one field, but this
|
|
44
|
+
# one carries none by nature (a nil literal has no data to
|
|
45
|
+
# hold), and `Struct.new` with zero member names ahead of
|
|
46
|
+
# `keyword_init:` is real, live Ruby-version-dependent
|
|
47
|
+
# behavior: works on 3.3, raises "wrong number of arguments
|
|
48
|
+
# (given 0, expected 1+)" on 3.2 (caught deploying to Lambda's
|
|
49
|
+
# own ruby3.2 runtime). `.new`/`case ... when NilLiteral` below
|
|
50
|
+
# are the only two things this type is ever used for, and a
|
|
51
|
+
# bare class answers both identically.
|
|
52
|
+
NilLiteral = Class.new
|
|
53
|
+
Addition = Struct.new(:left, :right, keyword_init: true)
|
|
54
|
+
SignTest = Struct.new(:operator, :test, :receiver, keyword_init: true)
|
|
55
|
+
Empty = Struct.new(:receiver, keyword_init: true)
|
|
56
|
+
ToS = Struct.new(:receiver, keyword_init: true)
|
|
57
|
+
Modulo = Struct.new(:receiver, :divisor, keyword_init: true)
|
|
58
|
+
Size = Struct.new(:receiver, keyword_init: true)
|
|
59
|
+
Lookup = Struct.new(:path, keyword_init: true)
|
|
60
|
+
|
|
61
|
+
# `receiver.match?(/pattern/)` -- vendored addition, not (yet)
|
|
62
|
+
# upstream hecks (migration plan task 8): confirmed the
|
|
63
|
+
# SINGLE most impactful corpus-wide dispatch-time gap of the
|
|
64
|
+
# whole migration -- `.match?(regex)` appears in nearly every
|
|
65
|
+
# value_object's format-validation rule across every corpus
|
|
66
|
+
# this migration touched (email/phone/ISO-8601-timestamp/zip
|
|
67
|
+
# patterns, dozens of files), and had NO parse support at all:
|
|
68
|
+
# it fell all the way through to the `Lookup` catch-all below,
|
|
69
|
+
# which tried to split the ENTIRE ".match?(/\A\d{5}\z/)" text
|
|
70
|
+
# on "." as if it were a dotted attribute path, and crashed with
|
|
71
|
+
# an opaque "no implicit conversion of Symbol into Integer"
|
|
72
|
+
# somewhere downstream -- confirmed live via a real dispatch,
|
|
73
|
+
# not validate (validate never evaluates a predicate body).
|
|
74
|
+
# `receiver` still needs its own parse (it may itself be a
|
|
75
|
+
# dotted lookup, e.g. `some_field.match?(...)`), the pattern
|
|
76
|
+
# text is taken as-is between the slashes (a Ruby Regexp literal,
|
|
77
|
+
# not the evaluator's own mini-grammar -- there is nothing to
|
|
78
|
+
# recurse into).
|
|
79
|
+
MatchesRegex = Struct.new(:receiver, :pattern, :flags, keyword_init: true)
|
|
80
|
+
|
|
81
|
+
# `.present?`/`.blank?` -- vendored addition, not (yet) upstream
|
|
82
|
+
# hecks (migration plan task 8): the second-most pervasive
|
|
83
|
+
# gap this pass found, right behind `.match?` -- confirmed real
|
|
84
|
+
# via the same "no such attribute" Lookup-fallback crash, since
|
|
85
|
+
# neither suffix existed in this leaf grammar at all. Rails-
|
|
86
|
+
# standard semantics (blank = nil, or responds_to?(:empty?) &&
|
|
87
|
+
# empty? ; present = !blank), not just `!nil?` -- matches what
|
|
88
|
+
# every corpus author actually means by `.present?` on a string
|
|
89
|
+
# field that could legitimately be "" rather than absent.
|
|
90
|
+
Presence = Struct.new(:receiver, :negated, keyword_init: true)
|
|
91
|
+
|
|
92
|
+
# `receiver.split("SEP")` -- vendored addition, not (yet) upstream
|
|
93
|
+
# hecks (migration plan task 9): confirmed the single
|
|
94
|
+
# highest-value new finding of the real-dispatch smoke sweep --
|
|
95
|
+
# the bus's own core `Phrase` value object (dispatch/lexicon/
|
|
96
|
+
# query/command_bus.bluebook, all four storehouse-kernel files,
|
|
97
|
+
# byte-identical text) validates its four-segment shape with
|
|
98
|
+
# `value.split("::").length == 4 && value.split("::").all? { |s|
|
|
99
|
+
# s.length > 0 }` -- `.split(` matched none of this grammar's
|
|
100
|
+
# known suffixes, so it fell through to the `Lookup` catch-all,
|
|
101
|
+
# which split the RAW EXPRESSION TEXT on "." (not the runtime
|
|
102
|
+
# value) and crashed with `TypeError: no implicit conversion of
|
|
103
|
+
# Symbol into Integer` the moment `String#[]` was handed a
|
|
104
|
+
# Symbol segment -- meaning every command taking a Phrase was
|
|
105
|
+
# entirely undispatchable, confirmed live via `Lexicon::Lexicon.
|
|
106
|
+
# Lookup`/`Query::Query.Run`, not inferred. Produces a real
|
|
107
|
+
# Array, deliberately not a scalar -- `.length`/`.all?` compose
|
|
108
|
+
# with it below the same way they compose with any other
|
|
109
|
+
# receiver, because `parse` recurses on the receiver text and
|
|
110
|
+
# the existing `Size` node (`.length`/`.size`) already treats
|
|
111
|
+
# "whatever `interpret` returns" as its receiver's value, not a
|
|
112
|
+
# fixed type. Separator is taken literally between the quotes,
|
|
113
|
+
# same "no sub-grammar to recurse into" precedent `MatchesRegex`
|
|
114
|
+
# already set for its own `/pattern/` text above.
|
|
115
|
+
Split = Struct.new(:receiver, :separator, keyword_init: true)
|
|
116
|
+
|
|
117
|
+
# `receiver.last` -- vendored addition, not (yet) upstream
|
|
118
|
+
# hecks (migration plan task 9), same pass as `Split` above
|
|
119
|
+
# and built for the same `Phrase`/`Path` value objects --
|
|
120
|
+
# `Query::Phrase`'s own invariant chains `.split("::").last.
|
|
121
|
+
# match?(...)` to check the fourth segment's casing. Same shape
|
|
122
|
+
# as `.length`/`.size` (Size) and `.empty?` (Empty) -- a plain
|
|
123
|
+
# receiver-in, scalar-out accessor, no sub-grammar of its own.
|
|
124
|
+
Last = Struct.new(:receiver, keyword_init: true)
|
|
125
|
+
|
|
126
|
+
# `receiver.first` -- sibling addition to `Last` immediately
|
|
127
|
+
# above, same shape, same vendored-not-upstream status. Added
|
|
128
|
+
# for the shipping domain's `legs.first` (an itinerary's
|
|
129
|
+
# departure leg), the exact mirror of `legs.last` (its arrival
|
|
130
|
+
# leg) that domain already leaned on -- `Last`'s own duck-typed-
|
|
131
|
+
# on-`respond_to?` reasoning applies unchanged, so `first_of`
|
|
132
|
+
# below is `last_of` with the one method swapped, not a new
|
|
133
|
+
# design.
|
|
134
|
+
First = Struct.new(:receiver, keyword_init: true)
|
|
135
|
+
|
|
136
|
+
# `receiver.start_with?("prefix")` / `receiver.end_with?("suffix")`
|
|
137
|
+
# -- vendored addition, not (yet) upstream hecks (migration
|
|
138
|
+
# plan task 9), the sibling gap the `Split`/`Last`/`BlockPredicate`
|
|
139
|
+
# pass above flagged and deliberately left unfixed --
|
|
140
|
+
# `Query::Query.Run`/`Dispatch::Dispatch.Route`/`CommandBus::
|
|
141
|
+
# CommandBus.Route`'s shared `Params` value object validates its
|
|
142
|
+
# JSON-object shape with `value.start_with?("{") && value.
|
|
143
|
+
# end_with?("}")` (dispatch/query/command_bus.bluebook, all three
|
|
144
|
+
# storehouse-kernel files, byte-identical text) -- `.start_with?(`/
|
|
145
|
+
# `.end_with?(` matched none of this grammar's known suffixes, so
|
|
146
|
+
# both fell through to the `Lookup` catch-all and crashed with the
|
|
147
|
+
# identical `TypeError: no implicit conversion of Symbol into
|
|
148
|
+
# Integer` shape `.split`/`.all?` used to, confirmed live via a
|
|
149
|
+
# real dispatch (not validate), not inferred. Two separate node
|
|
150
|
+
# types rather than one `mode:`-keyed struct (the `BlockPredicate`/
|
|
151
|
+
# `SignTest` precedent) -- `start_with?`/`end_with?` aren't two
|
|
152
|
+
# spellings of the same test the way `all?`/`any?`/`none?` are (one
|
|
153
|
+
# Array-aggregation family) or `positive?`/`negative?`/`zero?` are
|
|
154
|
+
# (one comparison-against-0 family) ; they check different ends of
|
|
155
|
+
# the same string and share nothing but their
|
|
156
|
+
# receiver-plus-literal-argument shape. `substring` is taken
|
|
157
|
+
# literally between the quotes, same "no sub-grammar to recurse
|
|
158
|
+
# into" precedent `Split`'s `separator`/`MatchesRegex`'s `pattern`
|
|
159
|
+
# already set.
|
|
160
|
+
StartsWith = Struct.new(:receiver, :substring, keyword_init: true)
|
|
161
|
+
EndsWith = Struct.new(:receiver, :substring, keyword_init: true)
|
|
162
|
+
|
|
163
|
+
module_function
|
|
164
|
+
|
|
165
|
+
def resolve(expr, state, attrs)
|
|
166
|
+
interpret(parse(expr), state, attrs)
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def parse(expr)
|
|
170
|
+
expr = expr.to_s.strip
|
|
171
|
+
|
|
172
|
+
return Size.new(receiver: parse(Regexp.last_match(1))) if expr =~ /\A(.+)\.length\z/
|
|
173
|
+
|
|
174
|
+
return IntegerLiteral.new(value: Integer(expr, 10)) if expr.match?(/\A-?\d+\z/)
|
|
175
|
+
return FloatLiteral.new(value: Float(expr)) if expr.match?(/\A-?\d*\.\d+\z/)
|
|
176
|
+
return StringLiteral.new(value: expr[1..-2]) if quoted?(expr)
|
|
177
|
+
return BoolLiteral.new(value: true) if expr == "true"
|
|
178
|
+
return BoolLiteral.new(value: false) if expr == "false"
|
|
179
|
+
return NilLiteral.new if expr == "nil"
|
|
180
|
+
|
|
181
|
+
elements = array_elements(expr)
|
|
182
|
+
return ArrayLiteral.new(elements: elements.map { |element| parse(element) }) if elements
|
|
183
|
+
|
|
184
|
+
arithmetic = split_addition(expr)
|
|
185
|
+
return Addition.new(left: parse(arithmetic[0]), right: parse(arithmetic[1])) if arithmetic
|
|
186
|
+
|
|
187
|
+
sign = match_suffix(expr, SIGN_TESTS)
|
|
188
|
+
return sign_test_node(sign) if sign
|
|
189
|
+
|
|
190
|
+
return Empty.new(receiver: parse(Regexp.last_match(1))) if expr =~ /\A(.+)\.empty\?\z/
|
|
191
|
+
return ToS.new(receiver: parse(Regexp.last_match(1))) if expr =~ /\A(.+)\.to_s\z/
|
|
192
|
+
|
|
193
|
+
modulo = match_call(expr, ".modulo(")
|
|
194
|
+
return Modulo.new(receiver: parse(modulo[0]), divisor: parse(modulo[1])) if modulo
|
|
195
|
+
|
|
196
|
+
return Size.new(receiver: parse(Regexp.last_match(1))) if expr =~ /\A(.+)\.size\z/
|
|
197
|
+
|
|
198
|
+
if expr =~ /\A(.+)\.match\?\(\/(.*)\/([a-z]*)\)\z/m
|
|
199
|
+
return MatchesRegex.new(receiver: parse(Regexp.last_match(1)),
|
|
200
|
+
pattern: Regexp.last_match(2),
|
|
201
|
+
flags: Regexp.last_match(3))
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
return Presence.new(receiver: parse(Regexp.last_match(1)), negated: false) if expr =~ /\A(.+)\.present\?\z/
|
|
205
|
+
return Presence.new(receiver: parse(Regexp.last_match(1)), negated: true) if expr =~ /\A(.+)\.blank\?\z/
|
|
206
|
+
|
|
207
|
+
if expr =~ /\A(.+)\.split\("([^"]*)"\)\z/
|
|
208
|
+
return Split.new(receiver: parse(Regexp.last_match(1)), separator: Regexp.last_match(2))
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
return First.new(receiver: parse(Regexp.last_match(1))) if expr =~ /\A(.+)\.first\z/
|
|
212
|
+
return Last.new(receiver: parse(Regexp.last_match(1))) if expr =~ /\A(.+)\.last\z/
|
|
213
|
+
|
|
214
|
+
if expr =~ /\A(.+)\.start_with\?\("([^"]*)"\)\z/
|
|
215
|
+
return StartsWith.new(receiver: parse(Regexp.last_match(1)), substring: Regexp.last_match(2))
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
if expr =~ /\A(.+)\.end_with\?\("([^"]*)"\)\z/
|
|
219
|
+
return EndsWith.new(receiver: parse(Regexp.last_match(1)), substring: Regexp.last_match(2))
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
block_opener = parse_block_opener(expr)
|
|
223
|
+
return block_opener if block_opener
|
|
224
|
+
|
|
225
|
+
Lookup.new(path: expr)
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def sign_test_node(parts)
|
|
229
|
+
receiver, test = parts
|
|
230
|
+
symbol = SIGN_TEST_OPERATORS.fetch(test)
|
|
231
|
+
operator = Evaluator::OPERATORS.find { |candidate| candidate.symbol == symbol }
|
|
232
|
+
SignTest.new(operator: operator, test: test, receiver: parse(receiver))
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def interpret(node, state, attrs)
|
|
236
|
+
case node
|
|
237
|
+
when IntegerLiteral, FloatLiteral, StringLiteral, BoolLiteral then node.value
|
|
238
|
+
when ArrayLiteral then node.elements.map { |element| interpret(element, state, attrs) }
|
|
239
|
+
when NilLiteral then nil
|
|
240
|
+
when Addition
|
|
241
|
+
add(interpret(node.left, state, attrs), interpret(node.right, state, attrs))
|
|
242
|
+
when SignTest
|
|
243
|
+
apply_sign_test(node, interpret(node.receiver, state, attrs))
|
|
244
|
+
when Empty
|
|
245
|
+
emptiness_of(interpret(node.receiver, state, attrs))
|
|
246
|
+
when ToS
|
|
247
|
+
string_of(interpret(node.receiver, state, attrs))
|
|
248
|
+
when Modulo
|
|
249
|
+
apply_modulo(interpret(node.receiver, state, attrs), interpret(node.divisor, state, attrs))
|
|
250
|
+
when Size
|
|
251
|
+
size_of(interpret(node.receiver, state, attrs))
|
|
252
|
+
when MatchesRegex
|
|
253
|
+
matches_regex?(interpret(node.receiver, state, attrs), node.pattern, node.flags)
|
|
254
|
+
when Presence
|
|
255
|
+
present = !blank?(interpret(node.receiver, state, attrs))
|
|
256
|
+
node.negated ? !present : present
|
|
257
|
+
when Split
|
|
258
|
+
split_value(interpret(node.receiver, state, attrs), node.separator)
|
|
259
|
+
when Last
|
|
260
|
+
last_of(interpret(node.receiver, state, attrs))
|
|
261
|
+
when First
|
|
262
|
+
first_of(interpret(node.receiver, state, attrs))
|
|
263
|
+
when Find
|
|
264
|
+
found_of(node, interpret(node.receiver, state, attrs), state, attrs)
|
|
265
|
+
when StartsWith
|
|
266
|
+
starts_with?(interpret(node.receiver, state, attrs), node.substring)
|
|
267
|
+
when EndsWith
|
|
268
|
+
ends_with?(interpret(node.receiver, state, attrs), node.substring)
|
|
269
|
+
when BlockPredicate
|
|
270
|
+
evaluate_block_predicate(node, interpret(node.receiver, state, attrs), state, attrs)
|
|
271
|
+
when Lookup
|
|
272
|
+
lookup(node.path, state, attrs)
|
|
273
|
+
else
|
|
274
|
+
# Every leaf node `parse` can produce has a `when` above —
|
|
275
|
+
# a backstop against the day this grammar grows a new leaf
|
|
276
|
+
# type (this file's own history: MatchesRegex/Presence/
|
|
277
|
+
# Split/Last/First/Find/StartsWith/EndsWith/BlockPredicate
|
|
278
|
+
# were each added exactly this way, and each one — before it had an
|
|
279
|
+
# `interpret` arm — fell all the way through to the
|
|
280
|
+
# `Lookup` catch-all in `parse` and crashed downstream with
|
|
281
|
+
# an opaque type error, never here). A missing arm here
|
|
282
|
+
# would instead return bare `nil` silently, the one wrong-
|
|
283
|
+
# answer shape this leaf grammar has otherwise never
|
|
284
|
+
# allowed.
|
|
285
|
+
raise EvaluationError, "no interpreter handles #{node.class} — add a case before parse can produce it"
|
|
286
|
+
end
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
# `.present?`/`.blank?` -- vendored addition, see the
|
|
290
|
+
# `Presence` struct's own comment above. `nil` and `false` are
|
|
291
|
+
# blank ; a String/Array/Hash is blank when EMPTY, not merely
|
|
292
|
+
# falsy -- a VO-wrapped field that IS assigned (`{value: "x"}`,
|
|
293
|
+
# `Value#to_h`'d first) is present regardless of what its own
|
|
294
|
+
# inner value holds, matching how every VO-typed field in this
|
|
295
|
+
# corpus is actually shaped once set at all.
|
|
296
|
+
def blank?(value)
|
|
297
|
+
return true if value.nil? || value == false
|
|
298
|
+
|
|
299
|
+
# Duck-typed, not `value.is_a?(Runtime::Value)` -- this module
|
|
300
|
+
# is `Bluebook::Expression`, a different namespace tree from
|
|
301
|
+
# `Runtime::Value` entirely, and reaching across for a single
|
|
302
|
+
# class check is the exact cross-module coupling that already
|
|
303
|
+
# broke `Modulo`'s own `match_call` reference elsewhere in
|
|
304
|
+
# this file (found live while building this fix, not assumed).
|
|
305
|
+
value = value.to_h if value.respond_to?(:to_h) && !value.is_a?(Hash)
|
|
306
|
+
case value
|
|
307
|
+
when String, Array, Hash then value.empty?
|
|
308
|
+
else false
|
|
309
|
+
end
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
# `receiver.match?(/pattern/)` -- vendored addition, see the
|
|
313
|
+
# `MatchesRegex` struct's own comment above. `receiver_value` is
|
|
314
|
+
# coerced to a plain String first -- inlined here rather than
|
|
315
|
+
# calling `Evaluator#string_of` (a DIFFERENT module_function
|
|
316
|
+
# module; not actually in scope from inside Resolver despite
|
|
317
|
+
# `Modulo`'s own parse rule above calling a same-named
|
|
318
|
+
# `match_call` that has the identical cross-module problem --
|
|
319
|
+
# found live while building this, not assumed).
|
|
320
|
+
def matches_regex?(receiver_value, pattern, flags)
|
|
321
|
+
text = case receiver_value
|
|
322
|
+
when String, Symbol then receiver_value.to_s
|
|
323
|
+
when Integer, Float then receiver_value.to_s
|
|
324
|
+
when NilClass then ""
|
|
325
|
+
else
|
|
326
|
+
raise EvaluationError, "match? expects a scalar, got #{receiver_value.class}"
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
options = 0
|
|
330
|
+
options |= Regexp::IGNORECASE if flags.include?("i")
|
|
331
|
+
options |= Regexp::MULTILINE if flags.include?("m")
|
|
332
|
+
options |= Regexp::EXTENDED if flags.include?("x")
|
|
333
|
+
|
|
334
|
+
Regexp.new(pattern, options).match?(text)
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
# The elements of a bracketed literal, or nil if this isn't one.
|
|
338
|
+
# Splits on TOP-LEVEL commas only — quote-aware and depth-aware,
|
|
339
|
+
# the same discipline `split_addition` already applies, so a
|
|
340
|
+
# nested array or a comma inside a string element stays whole.
|
|
341
|
+
def array_elements(expr)
|
|
342
|
+
return nil unless expr.start_with?("[") && expr.end_with?("]")
|
|
343
|
+
|
|
344
|
+
inner = expr[1..-2].strip
|
|
345
|
+
return [] if inner.empty?
|
|
346
|
+
|
|
347
|
+
elements = []
|
|
348
|
+
depth = 0
|
|
349
|
+
quote = nil
|
|
350
|
+
current = +""
|
|
351
|
+
inner.each_char do |char|
|
|
352
|
+
if quote
|
|
353
|
+
quote = nil if char == quote
|
|
354
|
+
current << char
|
|
355
|
+
next
|
|
356
|
+
end
|
|
357
|
+
case char
|
|
358
|
+
when '"', "'" then quote = char
|
|
359
|
+
when "[", "(" then depth += 1
|
|
360
|
+
when "]", ")" then depth -= 1
|
|
361
|
+
end
|
|
362
|
+
if char == "," && depth.zero?
|
|
363
|
+
elements << current.strip
|
|
364
|
+
current = +""
|
|
365
|
+
else
|
|
366
|
+
current << char
|
|
367
|
+
end
|
|
368
|
+
end
|
|
369
|
+
elements << current.strip
|
|
370
|
+
elements.reject(&:empty?)
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
# BRACES COUNT TOWARD DEPTH, exactly as parens do — a `+` inside a
|
|
374
|
+
# block predicate's own `{ |x| ... }` body is not this expression's
|
|
375
|
+
# own addition. `parse` tries addition BEFORE `parse_block_opener`,
|
|
376
|
+
# so a paren-only depth count split
|
|
377
|
+
# kings.any? { |k| k.square.file == to.file + 1 && ... }
|
|
378
|
+
# at that inner `+`, turning the whole expression into a nonsense
|
|
379
|
+
# Addition whose left operand then walked "any? { |k| k" into an
|
|
380
|
+
# Array as an attribute path — "TypeError: no implicit conversion
|
|
381
|
+
# of Symbol into Integer", the same signature every unsupported
|
|
382
|
+
# construct raises, which is what let this hide. Found live in a
|
|
383
|
+
# downstream chess domain's castling given; the evaluator's own
|
|
384
|
+
# top_level_index has counted braces since its own version of this
|
|
385
|
+
# exact lesson.
|
|
386
|
+
def split_addition(expr)
|
|
387
|
+
depth = 0
|
|
388
|
+
quote = nil
|
|
389
|
+
|
|
390
|
+
expr.each_char.with_index do |char, index|
|
|
391
|
+
if quote
|
|
392
|
+
quote = nil if char == quote
|
|
393
|
+
elsif ['"', "'"].include?(char)
|
|
394
|
+
quote = char
|
|
395
|
+
elsif ["(", "{"].include?(char)
|
|
396
|
+
depth += 1
|
|
397
|
+
elsif [")", "}"].include?(char)
|
|
398
|
+
depth -= 1
|
|
399
|
+
elsif char == "+" && depth.zero?
|
|
400
|
+
return [expr[0...index].strip, expr[(index + 1)..].strip]
|
|
401
|
+
end
|
|
402
|
+
end
|
|
403
|
+
nil
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
def add(left, right)
|
|
407
|
+
require_number(left, "addition") + require_number(right, "addition")
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
def quoted?(expr)
|
|
411
|
+
return false if expr.length < 2
|
|
412
|
+
|
|
413
|
+
(expr.start_with?('"') && expr.end_with?('"')) ||
|
|
414
|
+
(expr.start_with?("'") && expr.end_with?("'"))
|
|
415
|
+
end
|
|
416
|
+
|
|
417
|
+
# Declared the same way in Vocabulary::SizedType
|
|
418
|
+
# (language/bluebook/vocabulary.bluebook) — spec/vocabulary_conformance_spec
|
|
419
|
+
# holds this equal to the language. Shared by .size and .empty?,
|
|
420
|
+
# which admit the same set for the same reason.
|
|
421
|
+
SIZED_TYPES = Hecks::Vocabulary.fetch("SizedType")
|
|
422
|
+
|
|
423
|
+
def size_of(value)
|
|
424
|
+
return value.size if value.is_a?(Array) || value.is_a?(String) || value.is_a?(Hash)
|
|
425
|
+
|
|
426
|
+
raise EvaluationError, "size expects a list or string, got #{describe(value)}"
|
|
427
|
+
end
|
|
428
|
+
|
|
429
|
+
def emptiness_of(value)
|
|
430
|
+
return value.empty? if value.is_a?(Array) || value.is_a?(String) || value.is_a?(Hash)
|
|
431
|
+
|
|
432
|
+
raise EvaluationError, "empty? expects a list or string, got #{describe(value)}"
|
|
433
|
+
end
|
|
434
|
+
|
|
435
|
+
# `.split("SEP")` -- vendored addition, see the `Split` struct's
|
|
436
|
+
# own comment above. Only a String receiver makes sense to
|
|
437
|
+
# split -- unlike `.length`/`.size`/`.empty?`, which are already
|
|
438
|
+
# meaningful over Array/Hash too, `.split` is a String-only
|
|
439
|
+
# method in the corpus's own usage (every occurrence found this
|
|
440
|
+
# pass splits a Phrase's own string value).
|
|
441
|
+
def split_value(value, separator)
|
|
442
|
+
raise EvaluationError, "split expects a string, got #{describe(value)}" unless value.is_a?(String)
|
|
443
|
+
|
|
444
|
+
value.split(separator)
|
|
445
|
+
end
|
|
446
|
+
|
|
447
|
+
# `.last` -- vendored addition, see the `Last` struct's own
|
|
448
|
+
# comment above. Duck-typed on `respond_to?(:last)` rather than
|
|
449
|
+
# hard-coding Array -- the one corpus usage found this pass
|
|
450
|
+
# (`Query::Phrase`'s `.split("::").last`) always receives a
|
|
451
|
+
# `Split`-produced Array, but nothing about `.last` itself is
|
|
452
|
+
# Array-specific, and this matches `Empty`/`Size`'s own
|
|
453
|
+
# duck-typed-over-a-known-set precedent without inventing a
|
|
454
|
+
# narrower rule than the method needs.
|
|
455
|
+
def last_of(value)
|
|
456
|
+
return value.last if value.respond_to?(:last)
|
|
457
|
+
|
|
458
|
+
raise EvaluationError, "last expects a list, got #{describe(value)}"
|
|
459
|
+
end
|
|
460
|
+
|
|
461
|
+
# `.first` -- see the `First` struct's own comment above.
|
|
462
|
+
# `last_of` with the one method swapped, same duck-typed
|
|
463
|
+
# reasoning.
|
|
464
|
+
def first_of(value)
|
|
465
|
+
return value.first if value.respond_to?(:first)
|
|
466
|
+
|
|
467
|
+
raise EvaluationError, "first expects a list, got #{describe(value)}"
|
|
468
|
+
end
|
|
469
|
+
|
|
470
|
+
# `.start_with?("prefix")` -- vendored addition, see the
|
|
471
|
+
# `StartsWith` struct's own comment above. String-only, same
|
|
472
|
+
# reasoning as `.split` above -- every corpus usage found this
|
|
473
|
+
# pass (`Params`'s own JSON-object-shape invariant) receives a
|
|
474
|
+
# plain String field.
|
|
475
|
+
def starts_with?(value, substring)
|
|
476
|
+
raise EvaluationError, "start_with? expects a string, got #{describe(value)}" unless value.is_a?(String)
|
|
477
|
+
|
|
478
|
+
value.start_with?(substring)
|
|
479
|
+
end
|
|
480
|
+
|
|
481
|
+
# `.end_with?("suffix")` -- vendored addition, see the `EndsWith`
|
|
482
|
+
# struct's own comment above. Same String-only reasoning as
|
|
483
|
+
# `start_with?` immediately above.
|
|
484
|
+
def ends_with?(value, substring)
|
|
485
|
+
raise EvaluationError, "end_with? expects a string, got #{describe(value)}" unless value.is_a?(String)
|
|
486
|
+
|
|
487
|
+
value.end_with?(substring)
|
|
488
|
+
end
|
|
489
|
+
|
|
490
|
+
# Declared the same way in Vocabulary::ToStringType
|
|
491
|
+
# (language/bluebook/vocabulary.bluebook) — spec/vocabulary_conformance_spec
|
|
492
|
+
# holds this equal to the language.
|
|
493
|
+
TO_STRING_TYPES = Hecks::Vocabulary.fetch("ToStringType")
|
|
494
|
+
|
|
495
|
+
def string_of(value)
|
|
496
|
+
case value
|
|
497
|
+
when String then value
|
|
498
|
+
when Integer, Float then value.to_s
|
|
499
|
+
when TrueClass, FalseClass then value.to_s
|
|
500
|
+
when NilClass then ""
|
|
501
|
+
else
|
|
502
|
+
raise EvaluationError, "to_s expects a scalar, got #{describe(value)}"
|
|
503
|
+
end
|
|
504
|
+
end
|
|
505
|
+
|
|
506
|
+
def match_suffix(expr, suffixes)
|
|
507
|
+
suffixes.each do |suffix|
|
|
508
|
+
marker = ".#{suffix}"
|
|
509
|
+
return [expr[0...-marker.length], suffix] if expr.end_with?(marker)
|
|
510
|
+
end
|
|
511
|
+
nil
|
|
512
|
+
end
|
|
513
|
+
|
|
514
|
+
def apply_sign_test(node, value)
|
|
515
|
+
number = numeric(value)
|
|
516
|
+
raise EvaluationError, "#{node.test} expects a number, got #{describe(value)}" unless number
|
|
517
|
+
|
|
518
|
+
Evaluator.apply(node.operator, number, 0)
|
|
519
|
+
end
|
|
520
|
+
|
|
521
|
+
def match_call(expr, marker)
|
|
522
|
+
index = expr.rindex(marker)
|
|
523
|
+
return nil unless index && expr.end_with?(")")
|
|
524
|
+
|
|
525
|
+
[expr[0...index], expr[(index + marker.length)...-1]]
|
|
526
|
+
end
|
|
527
|
+
|
|
528
|
+
def apply_modulo(receiver_value, divisor_value)
|
|
529
|
+
divisor = require_number(divisor_value, "modulo")
|
|
530
|
+
raise EvaluationError, "divided by 0" if divisor.zero?
|
|
531
|
+
|
|
532
|
+
require_number(receiver_value, "modulo").to_i % divisor.to_i
|
|
533
|
+
end
|
|
534
|
+
|
|
535
|
+
def lookup(expr, state, attrs)
|
|
536
|
+
return unwrap_scalar(fetch(expr, state, attrs)) unless expr.include?(".")
|
|
537
|
+
|
|
538
|
+
head, *rest = expr.split(".")
|
|
539
|
+
unwrap_scalar(walk_path(fetch(head, state, attrs), rest))
|
|
540
|
+
end
|
|
541
|
+
|
|
542
|
+
# Walks a list of dotted segments through an already-resolved
|
|
543
|
+
# Hash-like value — extracted from `lookup`'s own reduce so
|
|
544
|
+
# `found_of` (the `Find` node's own path projection) can walk a
|
|
545
|
+
# `.find { ... }`-produced element the identical way `lookup`
|
|
546
|
+
# walks a plain attribute path, rather than duplicating the
|
|
547
|
+
# `value[segment.to_sym] || value[segment]` step twice in this
|
|
548
|
+
# file.
|
|
549
|
+
def walk_path(value, segments)
|
|
550
|
+
segments.reduce(value) do |current, segment|
|
|
551
|
+
break nil unless current.respond_to?(:[])
|
|
552
|
+
|
|
553
|
+
current[segment.to_sym] || current[segment]
|
|
554
|
+
end
|
|
555
|
+
end
|
|
556
|
+
|
|
557
|
+
# `field == "literal"` -- vendored addition, not (yet) upstream
|
|
558
|
+
# hecks (migration plan task 8): the third-most pervasive
|
|
559
|
+
# dispatch-time gap this pass found, same family as `.match?`/
|
|
560
|
+
# `.present?` above -- a lookup of a single-field
|
|
561
|
+
# scalar-convenience value object (the exact shape `Value.
|
|
562
|
+
# from_identifier`/`Value::Coercion#fields_for`'s own single-
|
|
563
|
+
# field auto-unwrap already treats as "this VO IS its scalar"
|
|
564
|
+
# everywhere else in this runtime) came back as the `Value`
|
|
565
|
+
# wrapper itself, never unwrapped for READING -- so `Value#==`
|
|
566
|
+
# (which only ever equals another `Value` instance) silently
|
|
567
|
+
# refused every `guarantees "..." do status == "active" end` /
|
|
568
|
+
# `expects "..." do trash_day.present? end`-shaped bare
|
|
569
|
+
# comparison against a raw literal. Confirmed corpus-wide, not
|
|
570
|
+
# one file's mistake: bin-buddy alone has this exact `field ==
|
|
571
|
+
# "literal"` shape in plan.bluebook, service_task.bluebook,
|
|
572
|
+
# route.bluebook, and subscription.bluebook, all equally silent
|
|
573
|
+
# until a real dispatch (never validate) exercised the
|
|
574
|
+
# predicate. Scoped narrowly to the single-field `{value: X}`
|
|
575
|
+
# shape only.
|
|
576
|
+
#
|
|
577
|
+
# UPDATE 2026-08-18: originally scoped to unwrap ONLY the bare
|
|
578
|
+
# (undotted) case, on the belief that a dotted lookup only ever
|
|
579
|
+
# reaches into a VO's OWN field (`field.value`, `field.sub_
|
|
580
|
+
# field`) and so should keep walking `#[]` untouched. That
|
|
581
|
+
# belief held for the single-hop case but not for the general
|
|
582
|
+
# one: a dotted lookup that NAVIGATES THROUGH an entity/list
|
|
583
|
+
# element to a nested field (`leg.voyage`, where `voyage` is
|
|
584
|
+
# itself a single-field VO) landed on the very same unwrapped-
|
|
585
|
+
# `Value` shape the bare case fixed, and hit the identical
|
|
586
|
+
# silent `Value#==` failure -- comparing it against a raw
|
|
587
|
+
# literal or another unwrapped VO returned false for everything,
|
|
588
|
+
# no error. The terminal value of a dotted walk deserves the
|
|
589
|
+
# same "this VO IS its scalar" treatment as a bare lookup's
|
|
590
|
+
# result; only the INTERMEDIATE hops need raw `#[]` addressing
|
|
591
|
+
# to keep navigating. `unwrap_scalar` is idempotent on an
|
|
592
|
+
# already-raw scalar (a String/Integer doesn't respond to
|
|
593
|
+
# `#to_h`), so this is safe for the existing `field.value`-
|
|
594
|
+
# shaped dotted lookups too -- they already returned a raw
|
|
595
|
+
# scalar and are unaffected.
|
|
596
|
+
def unwrap_scalar(value)
|
|
597
|
+
return value unless value.respond_to?(:to_h) && !value.is_a?(Hash) && !value.is_a?(Array)
|
|
598
|
+
|
|
599
|
+
hash = value.to_h
|
|
600
|
+
hash.size == 1 && hash.key?(:value) ? hash[:value] : value
|
|
601
|
+
end
|
|
602
|
+
|
|
603
|
+
def fetch(name, state, attrs)
|
|
604
|
+
key = name.to_sym
|
|
605
|
+
return attrs[key] if attrs.key?(key)
|
|
606
|
+
return state[key] if known?(state, key)
|
|
607
|
+
|
|
608
|
+
raise EvaluationError, "cannot resolve #{name.inspect} — no such attribute or argument"
|
|
609
|
+
end
|
|
610
|
+
|
|
611
|
+
def known?(state, key)
|
|
612
|
+
return state.key?(key) if state.respond_to?(:key?)
|
|
613
|
+
|
|
614
|
+
!state[key].nil?
|
|
615
|
+
end
|
|
616
|
+
|
|
617
|
+
def numeric(value)
|
|
618
|
+
value if value.is_a?(Integer) || value.is_a?(Float)
|
|
619
|
+
end
|
|
620
|
+
|
|
621
|
+
def require_number(value, operation)
|
|
622
|
+
numeric(value) ||
|
|
623
|
+
raise(EvaluationError, "#{operation} expects a number, got #{describe(value)}")
|
|
624
|
+
end
|
|
625
|
+
|
|
626
|
+
def describe(value) = Rendering.describe(value)
|
|
627
|
+
end
|
|
628
|
+
end
|
|
629
|
+
end
|
|
630
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
module Hecks
|
|
2
|
+
module Bluebook
|
|
3
|
+
# The expression language a bluebook's predicates are written in —
|
|
4
|
+
# canonicalised (canonical_form), resolved against a chapter's names
|
|
5
|
+
# (resolver), and evaluated (evaluator).
|
|
6
|
+
module Expression
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
require_relative "expression/canonical_form"
|
|
12
|
+
require_relative "expression/resolver"
|
|
13
|
+
require_relative "expression/evaluator"
|