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
|
@@ -4,17 +4,19 @@ require_relative "resolver"
|
|
|
4
4
|
module Hecks
|
|
5
5
|
module Bluebook
|
|
6
6
|
module Expression
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
7
|
+
# Walks the real Evaluator/Resolver AST (the same objects a live
|
|
8
|
+
# dispatch parses `given`/`ensures`/invariant text into — see
|
|
9
|
+
# docs/implemented/guides/running-a-runtime.md's "The expression
|
|
10
|
+
# grammar") and emits plain, JSON-serializable Ruby Hashes, tagged
|
|
11
|
+
# by `"op"`.
|
|
12
12
|
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
13
|
+
# ## Why this exists beside `rust/project/expr_emitter.rb`
|
|
14
|
+
#
|
|
15
|
+
# The same two-method walk `rust/project/expr_emitter.rb`'s own
|
|
16
|
+
# `emit_bool`/`emit_resolver` already do, over the same AST — that
|
|
17
|
+
# file's own methods build Rust source-code strings for `rust/
|
|
16
18
|
# project`'s codegen (`rust/src/kernel::expr::Expr` literals, baked
|
|
17
|
-
# into a generated domain's own compiled binary); this builds
|
|
19
|
+
# into a generated domain's own compiled binary); this builds data
|
|
18
20
|
# instead, for a genuinely different consumer with a genuinely
|
|
19
21
|
# different constraint: `rust/host` can never link the `rust`
|
|
20
22
|
# (kernel) crate at all (a real, load-bearing build constraint —
|
|
@@ -22,41 +24,44 @@ module Hecks
|
|
|
22
24
|
# path dependency would statically bake every domain's generated
|
|
23
25
|
# dispatch code into every Lambda binary), so a value object's own
|
|
24
26
|
# `invariant` predicate has to travel as something `rust/host` can
|
|
25
|
-
# deserialize and interpret itself, at
|
|
27
|
+
# deserialize and interpret itself, at runtime, from `ir.json` — the
|
|
26
28
|
# exact same relationship `rust/project`'s own `Expr` literals
|
|
27
29
|
# already have to the compiled kernel, one layer further out.
|
|
28
30
|
#
|
|
29
|
-
#
|
|
30
|
-
#
|
|
31
|
+
# ## Why it lives in core `lib/hecks`, not `rust/project/`
|
|
32
|
+
#
|
|
33
|
+
# `rust/project.rb` is a separate, downstream toolchain
|
|
31
34
|
# (`lib/hecks/projector.rb`'s own header: "a whole separate Ruby
|
|
32
35
|
# program"), never `require`d by core `lib/hecks/bluebook/*.rb`
|
|
33
36
|
# files (confirmed: no core file does). `value_object.rb`'s own
|
|
34
|
-
# `invariants:` IR emission needs this for
|
|
37
|
+
# `invariants:` IR emission needs this for every domain's ordinary
|
|
35
38
|
# `to_h`/`ir.json` export — golden fixtures, `hecks-parse`'s parity
|
|
36
39
|
# comparisons, and any deploy artifact, not only a `bin/project_rust`
|
|
37
40
|
# run — so it belongs beside `Evaluator`/`Resolver` themselves, not
|
|
38
41
|
# bolted onto a tool that only sometimes runs.
|
|
39
42
|
#
|
|
40
|
-
#
|
|
41
|
-
#
|
|
42
|
-
#
|
|
43
|
-
#
|
|
44
|
-
#
|
|
45
|
-
#
|
|
46
|
-
# `
|
|
47
|
-
#
|
|
48
|
-
# `
|
|
49
|
-
#
|
|
50
|
-
#
|
|
51
|
-
#
|
|
52
|
-
#
|
|
53
|
-
#
|
|
54
|
-
#
|
|
55
|
-
#
|
|
43
|
+
# ## Complete, not corpus-scoped
|
|
44
|
+
#
|
|
45
|
+
# Every node this grammar admits gets a real arm, the identical
|
|
46
|
+
# "raise, don't silently drop" discipline `expr_emitter.rb`'s own
|
|
47
|
+
# `emit_bool`/`emit_resolver` already hold to — even though, as of
|
|
48
|
+
# this writing, no real corpus value object invariant exercises
|
|
49
|
+
# `Include`/`Modulo`/`BlockPredicate`/`Find`/`Array`/`MatchesRegex`/
|
|
50
|
+
# `Presence`/`Assignment`/`Split`/`StartsWith`/`EndsWith`/`First`/
|
|
51
|
+
# `Last` (only `given`/`ensures` clauses do, elsewhere in the corpus
|
|
52
|
+
# — a different consumer of this same grammar). `rust/host/src/
|
|
53
|
+
# expr_json.rs`'s own header names exactly which of these its
|
|
54
|
+
# interpreter evaluates for real today versus refuses cleanly — a
|
|
55
|
+
# narrower, deliberate, documented boundary on the interpreting
|
|
56
|
+
# side, not on this emitting side: an author is free to write any
|
|
57
|
+
# real expression in a value object's own `invariant`, and this
|
|
58
|
+
# always emits it faithfully; whether `rust/host` can yet check it
|
|
59
|
+
# at mint time is that file's own question to answer, not this
|
|
60
|
+
# one's to pre-empt by refusing to even try.
|
|
56
61
|
module AstJson
|
|
57
62
|
module_function
|
|
58
63
|
|
|
59
|
-
#
|
|
64
|
+
# **The closed op roster** — every `"op"` tag the walkers below can
|
|
60
65
|
# emit, pinned so a reader (or a spec) can refuse a tag it does
|
|
61
66
|
# not know instead of guessing. A new node kind is a new entry
|
|
62
67
|
# here, a new arm below, and a new arm in every reader.
|
|
@@ -68,16 +73,26 @@ module Hecks
|
|
|
68
73
|
matches_regex presence assignment split starts_with ends_with
|
|
69
74
|
].freeze
|
|
70
75
|
|
|
71
|
-
#
|
|
76
|
+
# One rule row, the way every rule site emits it — description and
|
|
72
77
|
# canonical text (what every reader has always had) plus the
|
|
73
78
|
# structured form, derived from the same text. `ast` is a pure
|
|
74
79
|
# function of `canonical`: the IR carries both so a reader that
|
|
75
80
|
# only displays keeps the text, and a reader that evaluates never
|
|
76
81
|
# re-parses it.
|
|
82
|
+
#
|
|
83
|
+
# @param rule [Bluebook::Given, Bluebook::Invariant] the rule to
|
|
84
|
+
# render; `rule.ast` is used when already computed, otherwise
|
|
85
|
+
# `rule.canonical` is parsed and emitted fresh
|
|
86
|
+
# @return [Hash{Symbol => Object}] `:description` (`String`),
|
|
87
|
+
# `:canonical` (`String`), and `:ast` (`Hash`, the JSON AST)
|
|
77
88
|
def rule_row(rule)
|
|
78
89
|
{ description: rule.description, canonical: rule.canonical, ast: rule.ast || emit_predicate(rule.canonical) }
|
|
79
90
|
end
|
|
80
91
|
|
|
92
|
+
# Parses `canonical` and emits its JSON AST in one step.
|
|
93
|
+
#
|
|
94
|
+
# @param canonical [String] a canonical predicate source string
|
|
95
|
+
# @return [Hash] the JSON-shaped boolean AST, tagged by `"op"`
|
|
81
96
|
def emit_predicate(canonical)
|
|
82
97
|
emit_bool(Evaluator.parse(canonical))
|
|
83
98
|
end
|
|
@@ -89,6 +104,17 @@ module Hecks
|
|
|
89
104
|
# engine reading it is a defect in the bluebook, refused at build.
|
|
90
105
|
# Walks the emitted AST, so every rule site (givens, ensures,
|
|
91
106
|
# invariants, preconditions, a policy's where) gets the one check.
|
|
107
|
+
#
|
|
108
|
+
# @param ast [Hash] a JSON AST node, as emitted by `emit_predicate`
|
|
109
|
+
# or `emit_bool`
|
|
110
|
+
# @param owner [String] the declaring bluebook's name, for the
|
|
111
|
+
# refusal message
|
|
112
|
+
# @param word [String] the rule's own kind and description
|
|
113
|
+
# (such as `"given \"total is positive\""`), for the refusal
|
|
114
|
+
# message
|
|
115
|
+
# @return [Hash] `ast`, unchanged
|
|
116
|
+
# @raise [Bluebook::DSL::Malformed] if any `matches_regex` node's
|
|
117
|
+
# pattern uses a construct `PatternSubset` does not admit
|
|
92
118
|
def refuse_unshared_patterns!(ast, owner:, word:)
|
|
93
119
|
return ast if Hecks::Bluebook::MetaValidator.shadow_parsing? # frozen era text is history
|
|
94
120
|
|
|
@@ -107,6 +133,11 @@ module Hecks
|
|
|
107
133
|
|
|
108
134
|
# Every name a rule resolves at its root — the first segment of
|
|
109
135
|
# each `lookup` path, unique, in first-seen order.
|
|
136
|
+
#
|
|
137
|
+
# @param ast [Hash] a JSON AST node, as emitted by `emit_predicate`
|
|
138
|
+
# or `emit_bool`
|
|
139
|
+
# @return [Array<String>] each `lookup` node's root name, unique,
|
|
140
|
+
# in first-seen order
|
|
110
141
|
def lookup_heads(ast)
|
|
111
142
|
heads = []
|
|
112
143
|
each_node(ast) do |node|
|
|
@@ -115,6 +146,13 @@ module Hecks
|
|
|
115
146
|
heads.uniq
|
|
116
147
|
end
|
|
117
148
|
|
|
149
|
+
# Visits `node` and every Hash node nested inside it, depth-first.
|
|
150
|
+
#
|
|
151
|
+
# @param node [Hash, Array, Object] a JSON AST node, or an Array or
|
|
152
|
+
# scalar value found while walking one; a scalar is a silent no-op
|
|
153
|
+
# @yieldparam node [Hash] each Hash node reached, including `node`
|
|
154
|
+
# itself
|
|
155
|
+
# @return [void]
|
|
118
156
|
def each_node(node, &block)
|
|
119
157
|
case node
|
|
120
158
|
when ::Hash
|
|
@@ -125,6 +163,17 @@ module Hecks
|
|
|
125
163
|
end
|
|
126
164
|
end
|
|
127
165
|
|
|
166
|
+
# Emits the JSON form of one boolean-position Evaluator node.
|
|
167
|
+
#
|
|
168
|
+
# @param node [Bluebook::Expression::Evaluator::Or,
|
|
169
|
+
# Bluebook::Expression::Evaluator::And,
|
|
170
|
+
# Bluebook::Expression::Evaluator::Not,
|
|
171
|
+
# Bluebook::Expression::Evaluator::Compare,
|
|
172
|
+
# Bluebook::Expression::Evaluator::Include,
|
|
173
|
+
# Bluebook::Expression::Evaluator::Resolve] the boolean-position
|
|
174
|
+
# AST node to emit
|
|
175
|
+
# @return [Hash] the JSON AST for `node`, tagged by `"op"`
|
|
176
|
+
# @raise [RuntimeError] if `node` is not one of the handled classes
|
|
128
177
|
def emit_bool(node)
|
|
129
178
|
case node
|
|
130
179
|
when Evaluator::Or then { "op" => "or", "left" => emit_bool(node.left), "right" => emit_bool(node.right) }
|
|
@@ -143,21 +192,37 @@ module Hecks
|
|
|
143
192
|
end
|
|
144
193
|
end
|
|
145
194
|
|
|
195
|
+
# Emits the JSON form of one comparison operator.
|
|
196
|
+
#
|
|
197
|
+
# @param comparator [Bluebook::Expression::Evaluator::Operator] the
|
|
198
|
+
# operator to emit
|
|
199
|
+
# @return [Hash{String => Boolean}] `"less_than"`, `"equal"`, and
|
|
200
|
+
# `"negated"`, exactly as `comparator` carries them
|
|
146
201
|
def emit_comparison(comparator)
|
|
147
202
|
{ "less_than" => comparator.compares_less_than, "equal" => comparator.compares_equal, "negated" => comparator.negated }
|
|
148
203
|
end
|
|
149
204
|
|
|
150
205
|
# The JSON-target sibling of `expr_emitter.rb`'s own
|
|
151
206
|
# `emit_include` — see that method's own comment for the full
|
|
152
|
-
# reasoning (a
|
|
153
|
-
# representable shape on
|
|
154
|
-
# rewrite it identically into an
|
|
207
|
+
# reasoning (a literal array haystack has no `Expr::Include`-
|
|
208
|
+
# representable shape on either target, kernel or host, so both
|
|
209
|
+
# rewrite it identically into an or-of-equalities at emission
|
|
155
210
|
# time rather than carrying a shape neither interpreter could
|
|
156
211
|
# evaluate). A non-literal haystack still emits `include`
|
|
157
212
|
# unchanged.
|
|
158
213
|
EQ = Evaluator::OPERATORS.find { |op| op.symbol == "==" }
|
|
159
214
|
private_constant :EQ
|
|
160
215
|
|
|
216
|
+
# Emits the JSON form of one `include` node.
|
|
217
|
+
#
|
|
218
|
+
# @param node [Bluebook::Expression::Evaluator::Include] the
|
|
219
|
+
# `include` node to emit
|
|
220
|
+
# @return [Hash] the JSON AST: `{"op" => "include", ...}` for a
|
|
221
|
+
# non-literal haystack, `{"op" => "bool", "value" => false}` for
|
|
222
|
+
# an empty literal array haystack, or an `"or"`-of-`"compare"`
|
|
223
|
+
# tree of equalities for a non-empty literal array haystack
|
|
224
|
+
# @raise [RuntimeError] if `node.haystack`, `node.needle`, or one of
|
|
225
|
+
# the haystack's literal elements is an unhandled resolver node
|
|
161
226
|
def emit_include(node)
|
|
162
227
|
return { "op" => "include", "haystack" => emit_resolver(node.haystack), "needle" => emit_resolver(node.needle) } \
|
|
163
228
|
unless node.haystack.is_a?(Resolver::ArrayLiteral)
|
|
@@ -172,10 +237,37 @@ module Hecks
|
|
|
172
237
|
end
|
|
173
238
|
|
|
174
239
|
# One case arm per Resolver node type — the class header above is
|
|
175
|
-
# explicit that this dispatch must stay
|
|
240
|
+
# explicit that this dispatch must stay complete and in one place
|
|
176
241
|
# ("every node this grammar admits gets a real arm"); splitting it
|
|
177
242
|
# into several methods would hide whether the set is still
|
|
178
243
|
# exhaustive instead of making that visible at a glance.
|
|
244
|
+
#
|
|
245
|
+
# @param node [Bluebook::Expression::Resolver::IntegerLiteral,
|
|
246
|
+
# Bluebook::Expression::Resolver::FloatLiteral,
|
|
247
|
+
# Bluebook::Expression::Resolver::StringLiteral,
|
|
248
|
+
# Bluebook::Expression::Resolver::BoolLiteral,
|
|
249
|
+
# Bluebook::Expression::Resolver::NilLiteral,
|
|
250
|
+
# Bluebook::Expression::Resolver::Lookup,
|
|
251
|
+
# Bluebook::Expression::Resolver::Addition,
|
|
252
|
+
# Bluebook::Expression::Resolver::SignTest,
|
|
253
|
+
# Bluebook::Expression::Resolver::Empty,
|
|
254
|
+
# Bluebook::Expression::Resolver::ToS,
|
|
255
|
+
# Bluebook::Expression::Resolver::Modulo,
|
|
256
|
+
# Bluebook::Expression::Resolver::Size,
|
|
257
|
+
# Bluebook::Expression::Resolver::BlockPredicate,
|
|
258
|
+
# Bluebook::Expression::Resolver::Find,
|
|
259
|
+
# Bluebook::Expression::Resolver::ArrayLiteral,
|
|
260
|
+
# Bluebook::Expression::Resolver::MatchesRegex,
|
|
261
|
+
# Bluebook::Expression::Resolver::Presence,
|
|
262
|
+
# Bluebook::Expression::Resolver::Assignment,
|
|
263
|
+
# Bluebook::Expression::Resolver::Split,
|
|
264
|
+
# Bluebook::Expression::Resolver::StartsWith,
|
|
265
|
+
# Bluebook::Expression::Resolver::EndsWith,
|
|
266
|
+
# Bluebook::Expression::Resolver::First,
|
|
267
|
+
# Bluebook::Expression::Resolver::Last] the value-position AST
|
|
268
|
+
# node to emit
|
|
269
|
+
# @return [Hash] the JSON AST for `node`, tagged by `"op"`
|
|
270
|
+
# @raise [RuntimeError] if `node` is not one of the handled classes
|
|
179
271
|
# rubocop:disable-next Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
|
|
180
272
|
def emit_resolver(node)
|
|
181
273
|
case node
|
|
@@ -184,7 +276,7 @@ module Hecks
|
|
|
184
276
|
when Resolver::StringLiteral then { "op" => "str", "value" => node.value }
|
|
185
277
|
when Resolver::BoolLiteral then { "op" => "bool", "value" => node.value }
|
|
186
278
|
when Resolver::NilLiteral then { "op" => "nil" }
|
|
187
|
-
# `path` is the
|
|
279
|
+
# `path` is the same shape `find.path` already has — segments, not
|
|
188
280
|
# a dotted string a reader would have to split by its own rule.
|
|
189
281
|
when Resolver::Lookup then { "op" => "lookup", "path" => node.path.split(".") }
|
|
190
282
|
when Resolver::Addition then { "op" => "add", "left" => emit_resolver(node.left), "right" => emit_resolver(node.right) }
|
|
@@ -4,8 +4,8 @@ require_relative "resolver"
|
|
|
4
4
|
module Hecks
|
|
5
5
|
module Bluebook
|
|
6
6
|
module Expression
|
|
7
|
-
#
|
|
8
|
-
# carries as `ast` back into the
|
|
7
|
+
# The inverse of `AstJson` — reads the `"op"`-tagged JSON a rule row
|
|
8
|
+
# carries as `ast` back into the same `Evaluator`/`Resolver` node
|
|
9
9
|
# Structs `Evaluator.parse` builds from `canonical`. This is how the
|
|
10
10
|
# runtime evaluates a rule without re-parsing its text: the one
|
|
11
11
|
# parse happened at DSL-build time, behind `AstJson`; dispatch walks
|
|
@@ -19,15 +19,26 @@ module Hecks
|
|
|
19
19
|
# the spec proved the round trip first, then the runtime adopted it.
|
|
20
20
|
#
|
|
21
21
|
# One deliberate asymmetry, inherited: `AstJson` rewrites a
|
|
22
|
-
#
|
|
22
|
+
# literal-array `.include?` into an or of equalities (see
|
|
23
23
|
# `emit_include`), so reading never produces an `Include` over an
|
|
24
24
|
# `ArrayLiteral`. Evaluation is unchanged by that rewrite, which is
|
|
25
25
|
# exactly what the equivalence spec pins.
|
|
26
26
|
module AstReader
|
|
27
27
|
module_function
|
|
28
28
|
|
|
29
|
+
# Reads a whole predicate's own `"op"`-tagged JSON back into its AST.
|
|
30
|
+
#
|
|
31
|
+
# @param json [Hash{String => Object}] the `"op"`-tagged JSON a rule row's own
|
|
32
|
+
# `ast` carries
|
|
33
|
+
# @return [Evaluator::Or, Evaluator::And, Evaluator::Not, Evaluator::Compare,
|
|
34
|
+
# Evaluator::Include, Evaluator::Resolve] the boolean/comparison AST node
|
|
29
35
|
def read_predicate(json) = read_bool(json)
|
|
30
36
|
|
|
37
|
+
# Reads one boolean/comparison node, recursing into its own children.
|
|
38
|
+
#
|
|
39
|
+
# @param json [Hash{String => Object}] the `"op"`-tagged JSON for one node
|
|
40
|
+
# @return [Evaluator::Or, Evaluator::And, Evaluator::Not, Evaluator::Compare,
|
|
41
|
+
# Evaluator::Include, Evaluator::Resolve] the boolean/comparison AST node
|
|
31
42
|
def read_bool(json)
|
|
32
43
|
case json.fetch("op")
|
|
33
44
|
when "or" then Evaluator::Or.new(left: read_bool(json["left"]), right: read_bool(json["right"]))
|
|
@@ -46,6 +57,10 @@ module Hecks
|
|
|
46
57
|
# `Operator` carrying that exact triple is the one `parse` would
|
|
47
58
|
# have chosen, because the roster (`expression/projection.json`)
|
|
48
59
|
# holds one symbol per triple.
|
|
60
|
+
#
|
|
61
|
+
# @param cmp [Hash{String => Boolean}] `{"less_than"=>, "equal"=>, "negated"=>}`
|
|
62
|
+
# @return [Evaluator::Operator] the operator carrying that exact triple
|
|
63
|
+
# @raise [RuntimeError] if no operator in `Evaluator::OPERATORS` has that triple
|
|
49
64
|
def operator(cmp)
|
|
50
65
|
Evaluator::OPERATORS.find do |op|
|
|
51
66
|
op.compares_less_than == cmp.fetch("less_than") &&
|
|
@@ -54,6 +69,15 @@ module Hecks
|
|
|
54
69
|
end or raise "no comparison operator has the triple #{cmp.inspect}"
|
|
55
70
|
end
|
|
56
71
|
|
|
72
|
+
# Reads one dotted/arithmetic leaf node, recursing into its own children.
|
|
73
|
+
#
|
|
74
|
+
# @param json [Hash{String => Object}] the `"op"`-tagged JSON for one leaf node
|
|
75
|
+
# @return [Object] a `Resolver` AST node — one of `IntegerLiteral`, `FloatLiteral`,
|
|
76
|
+
# `StringLiteral`, `BoolLiteral`, `NilLiteral`, `ArrayLiteral`, `Lookup`,
|
|
77
|
+
# `Addition`, `SignTest`, `Empty`, `ToS`, `Modulo`, `Size`, `First`, `Last`,
|
|
78
|
+
# `BlockPredicate`, `Find`, `MatchesRegex`, `Presence`, `Assignment`, `Split`,
|
|
79
|
+
# `StartsWith`, or `EndsWith`
|
|
80
|
+
# @raise [RuntimeError] if `json["op"]` names no known resolver op
|
|
57
81
|
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity -- one arm
|
|
58
82
|
# per AstJson op is the point; splitting the case would hide the roster.
|
|
59
83
|
def read_resolver(json)
|
|
@@ -98,6 +122,11 @@ module Hecks
|
|
|
98
122
|
# triple is what evaluates. Recover the spelling from the
|
|
99
123
|
# vocabulary so a rebuilt node refuses with the same message the
|
|
100
124
|
# parsed one would.
|
|
125
|
+
#
|
|
126
|
+
# @param operator [Evaluator::Operator] the comparison operator a sign test's own
|
|
127
|
+
# triple resolved to
|
|
128
|
+
# @return [String] the sign test's own vocabulary name, or `operator.symbol` when
|
|
129
|
+
# no sign test shares its triple
|
|
101
130
|
def sign_test_name(operator)
|
|
102
131
|
Resolver::SIGN_TEST_OPERATORS.key(operator.symbol) || operator.symbol
|
|
103
132
|
end
|
|
@@ -4,7 +4,7 @@ require_relative "../../vocabulary"
|
|
|
4
4
|
module Hecks
|
|
5
5
|
module Bluebook
|
|
6
6
|
module Expression
|
|
7
|
-
# Rewrites a predicate's
|
|
7
|
+
# Rewrites a predicate's source text into one canonical spelling
|
|
8
8
|
# before it's ever parsed or hashed — collapsing whitespace and
|
|
9
9
|
# folding admitted synonyms (e.g. `.length` → `.size`) via the
|
|
10
10
|
# `RULES` table projected from the grammar chapter, so two byte-
|
|
@@ -16,7 +16,7 @@ module Hecks
|
|
|
16
16
|
|
|
17
17
|
STRATEGIES = Hecks::Vocabulary.fetch("NormalisationStrategy")
|
|
18
18
|
|
|
19
|
-
#
|
|
19
|
+
# **Read, not restated** — the admitted normalisation rules, projected
|
|
20
20
|
# from the grammar chapter by bin/expression_projection exactly as
|
|
21
21
|
# the evaluator's operator table is. See Evaluator::PROJECTION for
|
|
22
22
|
# why a projection rather than a boot.
|
|
@@ -26,6 +26,13 @@ module Hecks
|
|
|
26
26
|
|
|
27
27
|
module_function
|
|
28
28
|
|
|
29
|
+
# Returns the admitted normalisation rules as plain Hashes, for
|
|
30
|
+
# embedding in a bluebook's read-back contract.
|
|
31
|
+
#
|
|
32
|
+
# @return [Array<Hash{Symbol => String}>] one hash per rule, in
|
|
33
|
+
# declared `position` order, with keys `:strategy`,
|
|
34
|
+
# `:source_token`, `:replacement`, `:boundary`, and `:position`
|
|
35
|
+
# (stringified)
|
|
29
36
|
def table
|
|
30
37
|
RULES.sort_by(&:position).map do |rule|
|
|
31
38
|
{
|
|
@@ -38,10 +45,25 @@ module Hecks
|
|
|
38
45
|
end
|
|
39
46
|
end
|
|
40
47
|
|
|
48
|
+
# Rewrites `source` into its canonical spelling by applying every
|
|
49
|
+
# admitted `RULES` entry, in declared `position` order.
|
|
50
|
+
#
|
|
51
|
+
# @param source [String] the raw predicate source to normalise
|
|
52
|
+
# @return [String] the canonicalised text, stripped of leading and
|
|
53
|
+
# trailing whitespace
|
|
41
54
|
def apply(source)
|
|
42
55
|
RULES.sort_by(&:position).reduce(source.to_s) { |text, rule| step(text, rule) }.strip
|
|
43
56
|
end
|
|
44
57
|
|
|
58
|
+
# Applies one normalisation `rule` to `text`, dispatching on the
|
|
59
|
+
# rule's strategy.
|
|
60
|
+
#
|
|
61
|
+
# @param text [String] the source text (or string-literal segment) to
|
|
62
|
+
# transform
|
|
63
|
+
# @param rule [Rule] the normalisation rule to apply
|
|
64
|
+
# @return [String] `text` after the rule is applied
|
|
65
|
+
# @raise [ArgumentError] if `rule.strategy` names a strategy this
|
|
66
|
+
# method does not implement
|
|
45
67
|
def step(text, rule)
|
|
46
68
|
case rule.strategy
|
|
47
69
|
when "collapse_whitespace" then map_outside_strings(text) { |segment| segment.gsub(/\s+/, " ") }
|
|
@@ -51,6 +73,14 @@ module Hecks
|
|
|
51
73
|
end
|
|
52
74
|
end
|
|
53
75
|
|
|
76
|
+
# Substitutes every occurrence of `rule.source_token` in `text` with
|
|
77
|
+
# `rule.replacement`, honoring the rule's word-boundary setting.
|
|
78
|
+
#
|
|
79
|
+
# @param text [String] the source text (or string-literal segment) to
|
|
80
|
+
# transform
|
|
81
|
+
# @param rule [Rule] the `"replace"`-strategy rule supplying the
|
|
82
|
+
# token, replacement, and boundary
|
|
83
|
+
# @return [String] `text` with the substitution applied
|
|
54
84
|
def replace(text, rule)
|
|
55
85
|
map_outside_strings(text) do |segment|
|
|
56
86
|
if rule.boundary == "none"
|
|
@@ -61,21 +91,30 @@ module Hecks
|
|
|
61
91
|
end
|
|
62
92
|
end
|
|
63
93
|
|
|
64
|
-
# Applies
|
|
65
|
-
#
|
|
66
|
-
#
|
|
67
|
-
#
|
|
68
|
-
# `"a.length"`
|
|
69
|
-
# source outside the quotes
|
|
70
|
-
# compares a string attribute against, not merely how the
|
|
71
|
-
#
|
|
72
|
-
#
|
|
94
|
+
# Applies the given block to the text outside quoted string literals
|
|
95
|
+
# only, copying every quoted run through byte-for-byte. Every rule
|
|
96
|
+
# here (collapse_whitespace, the `.length`→`.size` fold) must stay
|
|
97
|
+
# quote-aware: run it quote-blind and `"a b"` collapses to `"a b"`,
|
|
98
|
+
# `"a.length"` folds to `"a.size"`, just as readily as the real
|
|
99
|
+
# source outside the quotes — silently rewriting what a predicate
|
|
100
|
+
# compares a string attribute against, not merely how the predicate
|
|
101
|
+
# itself is spelled. A canonical string literal's contents are data,
|
|
102
|
+
# never syntax to normalise.
|
|
73
103
|
#
|
|
74
104
|
# Handles both `"` and `'` delimiters (this grammar's own
|
|
75
105
|
# `Resolver.quoted?` admits either), quote-aware exactly the way
|
|
76
106
|
# `Evaluator.top_level_index`/`Resolver.array_elements` already are
|
|
77
107
|
# elsewhere in this sublanguage. An unterminated quote (malformed
|
|
78
108
|
# input) is passed through raw rather than risk mangling it further.
|
|
109
|
+
#
|
|
110
|
+
# @param text [String] the source text to scan for quoted string
|
|
111
|
+
# literals
|
|
112
|
+
# @yieldparam segment [String] a run of `text` outside any quoted
|
|
113
|
+
# literal
|
|
114
|
+
# @yieldreturn [String] the transformed replacement for `segment`
|
|
115
|
+
# @return [String] `text` with the block applied to every unquoted
|
|
116
|
+
# run, and every quoted run (`"`- or `'`-delimited) copied through
|
|
117
|
+
# unchanged
|
|
79
118
|
def map_outside_strings(text)
|
|
80
119
|
result = +""
|
|
81
120
|
buffer = +""
|
|
@@ -93,9 +132,9 @@ module Hecks
|
|
|
93
132
|
result << yield(buffer)
|
|
94
133
|
# `char.dup`, not `char.to_s` (a no-op on a String — always
|
|
95
134
|
# returns self, never a copy) and not `+char` either
|
|
96
|
-
# (`String#+@` only dups a
|
|
135
|
+
# (`String#+@` only dups a frozen receiver; `each_char`'s
|
|
97
136
|
# yielded strings aren't frozen, so `+char` is just as
|
|
98
|
-
# much a no-op here). Without a
|
|
137
|
+
# much a no-op here). Without a real copy, `buffer` and
|
|
99
138
|
# `quote` alias the same mutable object: the very next
|
|
100
139
|
# `buffer << char` grows `quote` right along with it, so
|
|
101
140
|
# `char == quote` can only ever compare a single character
|
|
@@ -103,12 +142,12 @@ module Hecks
|
|
|
103
142
|
# literal — everything after a predicate's first quoted
|
|
104
143
|
# string silently skipped normalisation for the rest of
|
|
105
144
|
# the text, undetected because passing text through
|
|
106
|
-
# unnormalised is silent.
|
|
107
|
-
# here, which only checks that quoted
|
|
145
|
+
# unnormalised is silent. Masked by every existing spec
|
|
146
|
+
# here, which only checks that quoted contents survive
|
|
108
147
|
# untouched (the M7 fix this method exists for) — that
|
|
109
148
|
# still holds by accident once the bug makes the "outside"
|
|
110
149
|
# branch unreachable. Found live: a multi-line `given`/
|
|
111
|
-
# `ensures` block whose
|
|
150
|
+
# `ensures` block whose only quoted literal closes before a
|
|
112
151
|
# later line — the newline and that later line's own
|
|
113
152
|
# indentation went uncollapsed, diverging from
|
|
114
153
|
# `hecks-parse`'s own (correct) single-space join.
|