hecks 1.3.0 → 1.4.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 +7 -7
- data/lib/hecks/adapters/driven/d1.rb +187 -23
- data/lib/hecks/adapters/driven/folder.rb +10 -10
- data/lib/hecks/adapters/driven/google_authentication.rb +8 -8
- data/lib/hecks/adapters/driven/governance_authorization.rb +31 -13
- data/lib/hecks/adapters/driven/heki/journal.rb +5 -4
- data/lib/hecks/adapters/driven/heki/saga_store.rb +5 -5
- data/lib/hecks/adapters/driven/heki.rb +13 -7
- data/lib/hecks/adapters/driven/identity_registry.rb +2 -2
- data/lib/hecks/adapters/driven/in_memory_ordering.rb +3 -3
- data/lib/hecks/adapters/driven/lambda/client.rb +34 -9
- data/lib/hecks/adapters/driven/lambda.rb +39 -33
- data/lib/hecks/adapters/driven/local_storage.rb +17 -10
- data/lib/hecks/adapters/driven/memory.rb +205 -9
- data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +1 -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 +4 -4
- 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.rb +4 -4
- data/lib/hecks/adapters/driving/github_webhook.rb +18 -18
- data/lib/hecks/behaviors/dsl.rb +2 -2
- data/lib/hecks/behaviors/expectations.rb +23 -23
- data/lib/hecks/behaviors/ir.rb +1 -1
- data/lib/hecks/behaviors/rspec.rb +1 -1
- data/lib/hecks/behaviors/runner.rb +2 -2
- data/lib/hecks/behaviors.rb +1 -1
- data/lib/hecks/bluebook/aggregate.rb +13 -13
- data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +10 -10
- data/lib/hecks/bluebook/assembly/build.rb +1 -1
- data/lib/hecks/bluebook/assembly/contract.rb +39 -16
- data/lib/hecks/bluebook/assembly/contracts.rb +40 -38
- data/lib/hecks/bluebook/assembly/marks.rb +18 -18
- data/lib/hecks/bluebook/assembly/specializer.rb +19 -21
- data/lib/hecks/bluebook/assembly.rb +9 -9
- data/lib/hecks/bluebook/attribute.rb +9 -9
- data/lib/hecks/bluebook/behaviour/aggregate.rb +11 -11
- data/lib/hecks/bluebook/behaviour/attribute.rb +5 -5
- data/lib/hecks/bluebook/behaviour/chapter.rb +23 -5
- data/lib/hecks/bluebook/behaviour/command.rb +23 -23
- data/lib/hecks/bluebook/behaviour/domain_port.rb +7 -7
- data/lib/hecks/bluebook/behaviour/entity.rb +8 -8
- data/lib/hecks/bluebook/behaviour/hexagon.rb +4 -4
- data/lib/hecks/bluebook/behaviour/lifecycle.rb +5 -5
- data/lib/hecks/bluebook/behaviour/policy.rb +12 -12
- data/lib/hecks/bluebook/behaviour/process_manager.rb +7 -7
- data/lib/hecks/bluebook/behaviour/query.rb +1 -1
- data/lib/hecks/bluebook/behaviour/read_model.rb +8 -8
- data/lib/hecks/bluebook/behaviour/traits.rb +12 -12
- data/lib/hecks/bluebook/behaviour/value_object.rb +6 -6
- data/lib/hecks/bluebook/capabilities.rb +27 -0
- data/lib/hecks/bluebook/chapter.rb +28 -9
- data/lib/hecks/bluebook/command.rb +12 -12
- data/lib/hecks/bluebook/domain_port.rb +9 -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 +96 -96
- data/lib/hecks/bluebook/dsl/attribute_collector.rb +41 -41
- data/lib/hecks/bluebook/dsl/binding_proxy.rb +22 -2
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +111 -74
- data/lib/hecks/bluebook/dsl/bluebook_builder.rb +48 -30
- data/lib/hecks/bluebook/dsl/bootstrap_table.rb +116 -0
- data/lib/hecks/bluebook/dsl/command_builder.rb +103 -103
- 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 +56 -56
- data/lib/hecks/bluebook/dsl/generic_dispatch.rb +148 -132
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +89 -30
- data/lib/hecks/bluebook/dsl/identity_declaration.rb +17 -17
- data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +27 -4
- data/lib/hecks/bluebook/dsl/policy_builder.rb +30 -21
- 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 +35 -35
- data/lib/hecks/bluebook/dsl/query_builder.rb +5 -5
- data/lib/hecks/bluebook/dsl/read_model_builder.rb +34 -34
- data/lib/hecks/bluebook/dsl/rule_reference.rb +41 -39
- data/lib/hecks/bluebook/dsl/translation_builder.rb +9 -9
- data/lib/hecks/bluebook/dsl/value_object_builder.rb +16 -16
- 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 +11 -11
- data/lib/hecks/bluebook/expression/ast_json.rb +20 -20
- data/lib/hecks/bluebook/expression/ast_reader.rb +3 -3
- data/lib/hecks/bluebook/expression/canonical_form.rb +9 -9
- data/lib/hecks/bluebook/expression/evaluator.rb +18 -18
- data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +18 -18
- data/lib/hecks/bluebook/expression/resolver.rb +60 -62
- data/lib/hecks/bluebook/hexagon.rb +1 -1
- data/lib/hecks/bluebook/lifecycle.rb +1 -1
- data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +1 -1
- data/lib/hecks/bluebook/meta_validator/judge.rb +101 -87
- data/lib/hecks/bluebook/meta_validator/plan.rb +39 -39
- data/lib/hecks/bluebook/meta_validator/port_judge.rb +2 -2
- data/lib/hecks/bluebook/meta_validator/readings.rb +43 -43
- data/lib/hecks/bluebook/meta_validator/reconstruction.rb +44 -37
- data/lib/hecks/bluebook/meta_validator/shapes.rb +25 -21
- data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +145 -31
- data/lib/hecks/bluebook/meta_validator/translation_judge.rb +6 -6
- data/lib/hecks/bluebook/meta_validator/world_judge.rb +5 -5
- data/lib/hecks/bluebook/meta_validator.rb +70 -70
- data/lib/hecks/bluebook/model_check.rb +170 -84
- data/lib/hecks/bluebook/pattern_subset.rb +9 -9
- data/lib/hecks/bluebook/policy.rb +15 -13
- data/lib/hecks/bluebook/process_manager.rb +14 -14
- data/lib/hecks/bluebook/project_register.rb +6 -6
- data/lib/hecks/bluebook/query.rb +4 -4
- data/lib/hecks/bluebook/read_model.rb +14 -14
- data/lib/hecks/bluebook/reference.rb +8 -8
- data/lib/hecks/bluebook/smoke_test.rb +19 -19
- data/lib/hecks/bluebook/synthesizer.rb +12 -12
- data/lib/hecks/bluebook/translation.rb +4 -4
- data/lib/hecks/bluebook/value_object.rb +6 -6
- data/lib/hecks/bluebook.rb +2 -2
- data/lib/hecks/codemod/legacy_dispatch_args.rb +299 -0
- data/lib/hecks/codemod/legacy_dispatch_recorder.rb +186 -0
- data/lib/hecks/codemod.rb +36 -35
- data/lib/hecks/construct.rb +6 -6
- data/lib/hecks/corpus.rb +317 -0
- data/lib/hecks/deprecation.rb +95 -0
- data/lib/hecks/doc/reference.rb +19 -19
- data/lib/hecks/embryonaut_bluebook.rb +11 -11
- 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 +79 -32
- data/lib/hecks/facade/json_door.rb +106 -25
- data/lib/hecks/facade/surface/aggregate_door.rb +42 -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 +180 -19
- data/lib/hecks/forms/html.rb +51 -7
- data/lib/hecks/forms/index_renderer.rb +14 -2
- data/lib/hecks/forms/params.rb +120 -23
- data/lib/hecks/forms/port_argument.rb +2 -2
- data/lib/hecks/forms/query_form_renderer.rb +2 -2
- data/lib/hecks/forms/record_renderer.rb +2 -2
- data/lib/hecks/forms/record_table.rb +1 -1
- data/lib/hecks/forms/value_object_shape.rb +3 -3
- data/lib/hecks/forms.rb +24 -4
- data/lib/hecks/fqn.rb +1 -1
- data/lib/hecks/framework/bluebook/governance.bluebook +9 -0
- data/lib/hecks/framework.rb +48 -17
- data/lib/hecks/freezer.rb +11 -11
- data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +90 -90
- data/lib/hecks/fuzzing/combination_miner.rb +119 -0
- data/lib/hecks/fuzzing/concurrent_dispatch.rb +140 -45
- data/lib/hecks/fuzzing/coverage_campaign.rb +118 -0
- data/lib/hecks/fuzzing/differential.rb +158 -0
- data/lib/hecks/fuzzing/domain_generator.rb +694 -0
- data/lib/hecks/fuzzing/era_boundary.rb +37 -18
- data/lib/hecks/fuzzing/form_census.rb +35 -20
- data/lib/hecks/fuzzing/generated_domain_check.rb +95 -0
- data/lib/hecks/fuzzing/invalid_value_generator.rb +6 -6
- data/lib/hecks/fuzzing/isolated_boot.rb +62 -62
- data/lib/hecks/fuzzing/nondeterministic.rb +67 -0
- data/lib/hecks/fuzzing/persistence_parity.rb +19 -21
- data/lib/hecks/fuzzing/properties/corrections.rb +9 -9
- data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +154 -49
- data/lib/hecks/fuzzing/properties/guards.rb +42 -42
- data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +32 -32
- data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +22 -35
- data/lib/hecks/fuzzing/properties/outbox.rb +24 -24
- data/lib/hecks/fuzzing/properties/querying.rb +17 -17
- data/lib/hecks/fuzzing/properties.rb +64 -40
- data/lib/hecks/fuzzing/qa_settings.rb +152 -0
- data/lib/hecks/fuzzing/replay.rb +114 -114
- data/lib/hecks/fuzzing/rotation_priority.rb +10 -10
- data/lib/hecks/fuzzing/rust_gap_manifest.rb +113 -0
- data/lib/hecks/fuzzing/self_consistency.rb +141 -112
- data/lib/hecks/fuzzing/sequence_generator/adversary.rb +35 -35
- 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 +58 -31
- data/lib/hecks/fuzzing/sequence_generator.rb +111 -23
- data/lib/hecks/fuzzing/shrinker.rb +197 -0
- data/lib/hecks/fuzzing/structural_skips.rb +23 -130
- data/lib/hecks/fuzzing/sweep_depth.rb +4 -4
- data/lib/hecks/fuzzing/target_capabilities.rb +49 -18
- data/lib/hecks/fuzzing/value_generator.rb +14 -14
- data/lib/hecks/fuzzing.rb +5 -0
- data/lib/hecks/grammar/evolve.rb +10 -10
- data/lib/hecks/grammar.rb +7 -7
- data/lib/hecks/ir.rb +13 -13
- 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/oidc.json +5 -0
- data/lib/hecks/literal.rb +9 -9
- data/lib/hecks/naming.rb +25 -25
- data/lib/hecks/ports/access_control.rb +58 -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 +53 -11
- data/lib/hecks/ports/clock.rb +42 -23
- data/lib/hecks/ports/extraction.rb +16 -0
- data/lib/hecks/ports/identity_assignment.rb +24 -2
- data/lib/hecks/ports/identity_generation.rb +17 -3
- data/lib/hecks/ports/identity_resolution.rb +18 -1
- 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 +28 -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 +57 -4
- 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 +42 -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 +18 -19
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +5 -5
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +1 -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 +3 -3
- data/lib/hecks/ports/query/ordering.rb +6 -6
- data/lib/hecks/ports/query.rb +35 -0
- data/lib/hecks/projections/bootstrap_table.rb +112 -0
- data/lib/hecks/projections/diagrams.rb +75 -75
- data/lib/hecks/projections/glossary/html.rb +3 -3
- data/lib/hecks/projections/glossary/markdown.rb +3 -3
- data/lib/hecks/projections/glossary/mermaid.rb +1 -1
- data/lib/hecks/projections/glossary/sections.rb +1 -1
- data/lib/hecks/projections/glossary/sentences.rb +5 -5
- data/lib/hecks/projections/glossary.rb +15 -15
- data/lib/hecks/projections/ir.rb +1 -1
- data/lib/hecks/projections/model/deviations.rb +18 -17
- data/lib/hecks/projections/model.rb +25 -21
- data/lib/hecks/projections/oidc.rb +7 -7
- data/lib/hecks/projections/parser_table.rb +5 -5
- data/lib/hecks/projections/reference.rb +3 -3
- data/lib/hecks/projections/rust_vocabulary.rb +443 -0
- data/lib/hecks/projections/shape.rb +2 -2
- data/lib/hecks/projections/statements.rb +11 -11
- data/lib/hecks/projections/vocabulary.rb +9 -9
- data/lib/hecks/projections.rb +5 -3
- data/lib/hecks/projector/cli_projector.rb +29 -29
- data/lib/hecks/projector/docs_projector.rb +13 -13
- data/lib/hecks/projector/exporter.rb +42 -21
- data/lib/hecks/projector/ir_projector.rb +1 -1
- data/lib/hecks/projector/narrate_projector.rb +11 -11
- data/lib/hecks/projector/target.rb +13 -13
- data/lib/hecks/projector.rb +15 -15
- data/lib/hecks/query_ir.rb +47 -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 +57 -13
- 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 +3 -3
- data/lib/hecks/router/namespace_installer.rb +3 -3
- data/lib/hecks/router.rb +1 -1
- data/lib/hecks/runtime/aggregate_lock.rb +11 -11
- data/lib/hecks/runtime/boot_gates.rb +3 -3
- data/lib/hecks/runtime/caller.rb +8 -8
- data/lib/hecks/runtime/capability_graph.rb +2 -2
- data/lib/hecks/runtime/command_interpreter/argument_gate.rb +22 -25
- data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +42 -63
- data/lib/hecks/runtime/command_interpreter.rb +159 -102
- data/lib/hecks/runtime/command_rules/admissibility.rb +69 -69
- data/lib/hecks/runtime/command_rules/arithmetic.rb +137 -91
- 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 +35 -35
- data/lib/hecks/runtime/command_rules.rb +3 -0
- data/lib/hecks/runtime/dependency_planning.rb +11 -11
- data/lib/hecks/runtime/dispatcher.rb +283 -90
- data/lib/hecks/runtime/entity_element.rb +151 -67
- data/lib/hecks/runtime/entity_interpreter.rb +108 -82
- data/lib/hecks/runtime/errors.rb +19 -19
- data/lib/hecks/runtime/event.rb +6 -6
- data/lib/hecks/runtime/identity.rb +22 -22
- data/lib/hecks/runtime/instance.rb +39 -14
- data/lib/hecks/runtime/interpreting.rb +12 -12
- data/lib/hecks/runtime/invocation.rb +276 -0
- data/lib/hecks/runtime/loader.rb +14 -14
- data/lib/hecks/runtime/outbox.rb +23 -23
- data/lib/hecks/runtime/policy_interpreter.rb +54 -54
- data/lib/hecks/runtime/port_operation_interpreter.rb +22 -19
- data/lib/hecks/runtime/query_interpreter.rb +55 -64
- data/lib/hecks/runtime/reaction_invocation.rb +20 -20
- data/lib/hecks/runtime/read_model_interpreter.rb +40 -40
- data/lib/hecks/runtime/rebuild_sweep.rb +4 -4
- data/lib/hecks/runtime/reference_hop.rb +6 -6
- data/lib/hecks/runtime/refusal_wording.rb +92 -115
- data/lib/hecks/runtime/registry/saga_persistence.rb +21 -21
- data/lib/hecks/runtime/registry/verification.rb +36 -26
- data/lib/hecks/runtime/registry.rb +45 -28
- data/lib/hecks/runtime/remote_dispatcher.rb +38 -23
- data/lib/hecks/runtime/routing.rb +10 -153
- data/lib/hecks/runtime/saga_interpreter/correlation.rb +17 -17
- data/lib/hecks/runtime/saga_interpreter.rb +64 -64
- data/lib/hecks/runtime/saga_pending_dispatch.rb +12 -12
- data/lib/hecks/runtime/tenant_check.rb +9 -9
- data/lib/hecks/runtime/tenant_scope.rb +5 -5
- data/lib/hecks/runtime/value/admission.rb +75 -30
- data/lib/hecks/runtime/value/coercion.rb +318 -127
- data/lib/hecks/runtime/value/entity_list_coercion.rb +34 -34
- data/lib/hecks/runtime/value.rb +21 -21
- data/lib/hecks/runtime.rb +7 -7
- data/lib/hecks/storehouse.rb +64 -64
- data/lib/hecks/version.rb +3 -3
- data/lib/hecks/vocabulary.rb +205 -4
- data/lib/hecks.rb +13 -11
- data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +11 -11
- data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +14 -12
- data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +11 -11
- metadata +22 -2
|
@@ -8,7 +8,7 @@ module Hecks
|
|
|
8
8
|
# happened to sample," claim is realistically achievable:
|
|
9
9
|
# `Hecks::Bluebook::Expression::{Evaluator,Resolver}`. It's finite-
|
|
10
10
|
# grammar and side-effect-free (Evaluator::PROJECTION/Resolver's own
|
|
11
|
-
# `interpret` never touch a database, a clock, or randomness), but
|
|
11
|
+
# `interpret` never touch a database, a clock, or randomness), but not
|
|
12
12
|
# small enough for naive enumeration — ~20 node-producing constructs
|
|
13
13
|
# across two mutually-recursive grammar layers, unbounded recursion
|
|
14
14
|
# through dotted `Lookup` paths, `Addition`, and nested block
|
|
@@ -20,8 +20,8 @@ module Hecks
|
|
|
20
20
|
# `canonical_form.rb` aliasing bug — neither would have been caught by
|
|
21
21
|
# sampling alone, which is the whole reason this phase exists).
|
|
22
22
|
#
|
|
23
|
-
#
|
|
24
|
-
# recurses into a sub-expression whose
|
|
23
|
+
# Type-directed, not exhaustive-over-strings: this generator only ever
|
|
24
|
+
# recurses into a sub-expression whose own type the surrounding
|
|
25
25
|
# construct actually accepts (never `"x".modulo(true)`) — mirroring,
|
|
26
26
|
# by hand, the same admitted-receiver-class tables `Resolver`'s own
|
|
27
27
|
# `interpret` enforces at runtime (`SizedType`/`ToStringType`/
|
|
@@ -36,34 +36,34 @@ module Hecks
|
|
|
36
36
|
# 3" claim for exactly the sublanguage that already bit this project
|
|
37
37
|
# twice.
|
|
38
38
|
#
|
|
39
|
-
#
|
|
40
|
-
# produces is well-typed
|
|
39
|
+
# **What "PROVEN" means here, precisely**: every expression this generator
|
|
40
|
+
# produces is well-typed by construction (every sub-expression's type
|
|
41
41
|
# matches what its parent construct actually accepts, per this file's
|
|
42
42
|
# own TYPE_RULES). Interpreting a well-typed expression must never
|
|
43
|
-
# raise anything
|
|
43
|
+
# raise anything other than `Hecks::Bluebook::Expression::
|
|
44
44
|
# EvaluationError` — that class alone is the sublanguage's own real
|
|
45
45
|
# refusal vocabulary (a divisor that happens to be zero, a `Lookup`
|
|
46
46
|
# this run's synthetic state doesn't happen to carry); anything else
|
|
47
47
|
# escaping (a raw `TypeError`/`NoMethodError`/`ZeroDivisionError`) is
|
|
48
48
|
# exactly the crash-signature this whole file's own header comments
|
|
49
49
|
# (resolver.rb, block_predicates.rb) name as the historical bug class
|
|
50
|
-
# this sublanguage keeps almost-but-not-quite avoiding. This does
|
|
51
|
-
# prove the interpreter computes the
|
|
50
|
+
# this sublanguage keeps almost-but-not-quite avoiding. This does not
|
|
51
|
+
# prove the interpreter computes the semantically right answer (that
|
|
52
52
|
# needs a second, independent oracle — out of scope here, exactly the
|
|
53
|
-
# way rust_conformance_fuzz_spec.rb is the cross-
|
|
53
|
+
# way rust_conformance_fuzz_spec.rb is the cross-runtime half of this
|
|
54
54
|
# same idea) — only that it never crashes on well-typed input.
|
|
55
55
|
module BoundedExhaustiveExpressions
|
|
56
56
|
module_function
|
|
57
57
|
|
|
58
|
-
# `:numeric` deliberately covers
|
|
58
|
+
# `:numeric` deliberately covers both Integer and Float — the
|
|
59
59
|
# resolver's own `numeric`/`require_number` never distinguish them
|
|
60
60
|
# (resolver.rb) — so a generator that kept them as separate types
|
|
61
61
|
# would be modeling a distinction the grammar itself doesn't make.
|
|
62
62
|
TYPES = %i[numeric string boolean array nil_type].freeze
|
|
63
63
|
|
|
64
|
-
#
|
|
64
|
+
# **The palette** — deliberately small (this file's own header: "a small
|
|
65
65
|
# representative literal palette," matching the plan's own `{0, 1,
|
|
66
|
-
# -1}` example). Widening it doesn't test a different
|
|
66
|
+
# -1}` example). Widening it doesn't test a different shape of
|
|
67
67
|
# expression, only more values through the same shapes — real
|
|
68
68
|
# boundary-value coverage (Bignum, NaN, empty string, unicode) is
|
|
69
69
|
# PRD 05's job (spec/runtime/numeric_boundary_spec.rb,
|
|
@@ -75,18 +75,18 @@ module Hecks
|
|
|
75
75
|
nil_type: %w[nil].freeze
|
|
76
76
|
}.freeze
|
|
77
77
|
|
|
78
|
-
#
|
|
78
|
+
# Two synthetic attributes per type (the plan's own "2-3 fake
|
|
79
79
|
# attributes per type") — real names are irrelevant to `Resolver
|
|
80
80
|
# .parse` (it never touches state/attrs at all, confirmed directly:
|
|
81
81
|
# only `Lookup#interpret` does) and irrelevant to `interpret` beyond
|
|
82
|
-
# needing to resolve against
|
|
82
|
+
# needing to resolve against whatever synthetic state this file
|
|
83
83
|
# supplies (`synthetic_state`, below) — so any non-colliding,
|
|
84
84
|
# non-suffix-shaped name works. Never named the same as a
|
|
85
85
|
# `BLOCK_PARAM` (below) — `Resolver#fetch`'s own `attrs`-wins-over-
|
|
86
|
-
# `state` precedence means a block parameter
|
|
86
|
+
# `state` precedence means a block parameter shadows a same-named
|
|
87
87
|
# top-level attribute for the span of its own predicate, a real
|
|
88
88
|
# hazard this file sidesteps by construction rather than exercising
|
|
89
|
-
# it here (a real, cited resolver.rb hazard, not a gap in
|
|
89
|
+
# it here (a real, cited resolver.rb hazard, not a gap in this
|
|
90
90
|
# generator's own coverage claim).
|
|
91
91
|
SYNTHETIC_ATTRS = {
|
|
92
92
|
numeric: %w[num_a num_b].freeze,
|
|
@@ -95,7 +95,7 @@ module Hecks
|
|
|
95
95
|
array: %w[arr_num arr_str].freeze
|
|
96
96
|
}.freeze
|
|
97
97
|
|
|
98
|
-
# `arr_num`/`arr_str` —
|
|
98
|
+
# `arr_num`/`arr_str` — element type per array attribute, needed so
|
|
99
99
|
# a block predicate's own bound parameter (`BLOCK_PARAM`, below) is
|
|
100
100
|
# generated against the right leaf/production set for whatever it's
|
|
101
101
|
# actually bound to.
|
|
@@ -105,41 +105,41 @@ module Hecks
|
|
|
105
105
|
|
|
106
106
|
MAX_DEPTH = 3
|
|
107
107
|
|
|
108
|
-
# `state`/`attrs` — a plain Hash satisfies
|
|
108
|
+
# `state`/`attrs` — a plain Hash satisfies both (confirmed directly:
|
|
109
109
|
# `Resolver#known?` degrades to a bare index check when `state`
|
|
110
110
|
# doesn't `respond_to?(:key?)`, and `fetch` only ever needs
|
|
111
|
-
# `attrs.key?`/`attrs[]`.). Split
|
|
111
|
+
# `attrs.key?`/`attrs[]`.). Split across both, matching a real
|
|
112
112
|
# dispatch's own shape (some names come from the record's stored
|
|
113
113
|
# state, some from the command's own args) — not load-bearing for
|
|
114
|
-
#
|
|
114
|
+
# this proof (attrs wins regardless), but keeps the synthetic input
|
|
115
115
|
# closer to what `Admissibility` actually builds, in case anything
|
|
116
116
|
# here is reused for a future deeper check.
|
|
117
117
|
#
|
|
118
|
-
#
|
|
118
|
+
# **The VO-vs-scalar unwrap case, deliberately exercised** — half of
|
|
119
119
|
# `num_a`/`str_a`/`bool_a`/one array attribute's elements are bare
|
|
120
120
|
# scalars, the other half (`num_b`/`str_b`/`bool_b`/the other
|
|
121
121
|
# array's elements) are wrapped `{value: X}` — the single-field
|
|
122
122
|
# Value Object shape `Resolver#unwrap_scalar` auto-collapses.
|
|
123
123
|
# resolver.rb's own most serious historical bug in this file (the
|
|
124
124
|
# "UPDATE 2026-08-18" comment: a dotted walk that landed on an
|
|
125
|
-
# un-unwrapped VO compared `false` against every literal,
|
|
125
|
+
# un-unwrapped VO compared `false` against every literal, silently,
|
|
126
126
|
# never raising) lived exactly at this boundary — a generator that
|
|
127
127
|
# only ever supplied bare scalars would never exercise the code
|
|
128
128
|
# path that bug lived in at all.
|
|
129
|
-
# `SingleFieldVO` —
|
|
129
|
+
# `SingleFieldVO` — not a plain Hash, deliberately: `Resolver
|
|
130
130
|
# #unwrap_scalar`'s own guard is `value.respond_to?(:to_h) &&
|
|
131
131
|
# !value.is_a?(Hash) && !value.is_a?(Array)` — it exists
|
|
132
132
|
# specifically to collapse a real hydrated `Runtime::Value`
|
|
133
133
|
# instance (which responds to `#to_h` but is never itself a bare
|
|
134
134
|
# Hash) down to its lone scalar field, and just as deliberately
|
|
135
|
-
# leaves an
|
|
135
|
+
# leaves an actual Hash alone (a genuinely un-hydrated, multi-
|
|
136
136
|
# field record has no single scalar to collapse to). A first
|
|
137
137
|
# version of this generator's own synthetic state used plain
|
|
138
138
|
# `{value: X}` Hashes to stand in for a single-field VO — which
|
|
139
139
|
# `is_a?(Hash)` is true for, so `unwrap_scalar` correctly left them
|
|
140
|
-
#
|
|
140
|
+
# wrapped, and every VO-typed synthetic attribute then failed
|
|
141
141
|
# `Addition`/`Compare`/every scalar-typed operation with "expects a
|
|
142
|
-
# number, got {\"value\":5}" — a bug in
|
|
142
|
+
# number, got {\"value\":5}" — a bug in this generator's own
|
|
143
143
|
# synthetic state, not in `Resolver`, caught only by noticing that
|
|
144
144
|
# `num_b == 5` (`num_b` a plain `{value: 5}` Hash) evaluated to
|
|
145
145
|
# `false` instead of `true` before this fix.
|
|
@@ -162,21 +162,21 @@ module Hecks
|
|
|
162
162
|
|
|
163
163
|
def synthetic_attrs = {}
|
|
164
164
|
|
|
165
|
-
# `leaves(type, depth)` — every
|
|
165
|
+
# `leaves(type, depth)` — every terminal (non-recursive) expression
|
|
166
166
|
# of `type`: the fixed literal palette plus every synthetic
|
|
167
|
-
# attribute
|
|
168
|
-
# generates
|
|
167
|
+
# attribute name declared for that type (never their values — this
|
|
168
|
+
# generates text, the same source a real `given`/`invariant` author
|
|
169
169
|
# would write; `synthetic_state`, above, is what gives those names
|
|
170
170
|
# meaning at `interpret` time).
|
|
171
171
|
def leaves(type)
|
|
172
172
|
(TYPE_LEAVES[type] || []) + (SYNTHETIC_ATTRS[type] || []) + Array(bound_leaves[type])
|
|
173
173
|
end
|
|
174
174
|
|
|
175
|
-
# A
|
|
175
|
+
# A stack, not a single slot — a nested block predicate (the real
|
|
176
176
|
# corpus already does this two levels deep, roster.bluebook's own
|
|
177
177
|
# `seats.any? { |s| assignments.none? { |a| … } }`) pushes a second
|
|
178
178
|
# bound leaf while the outer one is still active. Both share the
|
|
179
|
-
#
|
|
179
|
+
# same `BLOCK_PARAM` spelling ("el") — real Ruby block-parameter
|
|
180
180
|
# shadowing (the inner `el` simply shadows the outer one within its
|
|
181
181
|
# own predicate text), which this generator treats as legal on
|
|
182
182
|
# purpose: `resolver.rb`'s own `interpret_with_element` binds fresh
|
|
@@ -196,11 +196,11 @@ module Hecks
|
|
|
196
196
|
end
|
|
197
197
|
|
|
198
198
|
# `productions(type, depth)` — every expression of `type` reachable
|
|
199
|
-
# in
|
|
199
|
+
# in at most `depth` recursive steps, memoized (the same sub-
|
|
200
200
|
# expression set is reused at every enclosing recursion, so without
|
|
201
201
|
# memoizing, cost would compound multiplicatively per level instead
|
|
202
202
|
# of additively). `depth` 0 is exactly `leaves(type)`; each
|
|
203
|
-
# increment adds every construct
|
|
203
|
+
# increment adds every construct this file's own TYPE_RULES (below)
|
|
204
204
|
# says can produce `type`, built from `depth - 1` sub-expressions.
|
|
205
205
|
def productions(type, depth)
|
|
206
206
|
cache[[type, depth]] ||= begin
|
|
@@ -218,22 +218,22 @@ module Hecks
|
|
|
218
218
|
when :boolean then boolean_productions(depth)
|
|
219
219
|
when :array then array_productions(depth)
|
|
220
220
|
# no recursive producer of nil in this grammar — Find's "not found" is a
|
|
221
|
-
# runtime
|
|
221
|
+
# runtime outcome, not a distinct construct to render as source text
|
|
222
222
|
when :nil_type then []
|
|
223
223
|
else raise ArgumentError, "no production rule for type #{type.inspect}"
|
|
224
224
|
end
|
|
225
225
|
end
|
|
226
226
|
|
|
227
|
-
#
|
|
227
|
+
# Every internal use of a sub-expression list (as opposed to the
|
|
228
228
|
# single final list `productions(type, depth)` returns to its own
|
|
229
229
|
# caller) goes through this, not `productions` directly — the
|
|
230
230
|
# actual thing that made an early version of this generator explode
|
|
231
231
|
# past a million cases by depth 3 wasn't `pairs`' own cross product
|
|
232
|
-
# (already sampled) but the dozen-plus
|
|
232
|
+
# (already sampled) but the dozen-plus linear `flat_map`/`map`
|
|
233
233
|
# passes `boolean_productions` alone makes over `str`/`num`/`sub` —
|
|
234
234
|
# each individually harmless, but an unbounded few-thousand-item
|
|
235
|
-
# list run through a dozen of them, feeding the
|
|
236
|
-
# dozen passes, compounds fast. Bounding every
|
|
235
|
+
# list run through a dozen of them, feeding the next depth's own
|
|
236
|
+
# dozen passes, compounds fast. Bounding every input list (not the
|
|
237
237
|
# final output) keeps the shape diversity `sample`'s even-spacing
|
|
238
238
|
# already preserves while keeping growth roughly linear in depth
|
|
239
239
|
# instead of combinatorial.
|
|
@@ -241,20 +241,20 @@ module Hecks
|
|
|
241
241
|
|
|
242
242
|
# NUMERIC ← Addition(numeric, numeric) | Modulo(numeric, numeric) |
|
|
243
243
|
# Size(sized) | First/Last(numeric array). `Size` returns an
|
|
244
|
-
# Integer for a String
|
|
244
|
+
# Integer for a String or an Array receiver alike (`SizedType` —
|
|
245
245
|
# `size_of`, resolver.rb) — both sides generated here.
|
|
246
246
|
def numeric_productions(depth)
|
|
247
247
|
sub = bounded(:numeric, depth - 1)
|
|
248
|
-
# `Modulo`'s own
|
|
248
|
+
# `Modulo`'s own receiver (not its argument — that side already
|
|
249
249
|
# goes through `Resolver#matching_paren`'s own fresh, self-
|
|
250
250
|
# contained re-parse, confirmed safe for any numeric shape
|
|
251
251
|
# including another `Addition`/`Modulo`) has the identical
|
|
252
252
|
# "`Addition` mis-parsed as a suffix receiver" hazard
|
|
253
253
|
# `resolver_numeric_leaves`'s own comment documents for `.to_s`/
|
|
254
|
-
# `.positive?` — `Resolver.parse` tries `split_addition`
|
|
254
|
+
# `.positive?` — `Resolver.parse` tries `split_addition` before
|
|
255
255
|
# `match_call`, so `"0 + 0.modulo(1)"` (meant as `(0 + 0)
|
|
256
256
|
# .modulo(1)`) actually parses as `0 + (0.modulo(1))`. Restricted
|
|
257
|
-
# to `resolver_numeric_leaves` on the
|
|
257
|
+
# to `resolver_numeric_leaves` on the receiver side only — the
|
|
258
258
|
# argument stays the full, unrestricted numeric set.
|
|
259
259
|
pairs(sub).map { |a, b| "#{a} + #{b}" } +
|
|
260
260
|
cross(resolver_numeric_leaves(depth - 1), sub).map { |a, b| "#{a}.modulo(#{b})" } +
|
|
@@ -263,32 +263,32 @@ module Hecks
|
|
|
263
263
|
sample(numeric_array_productions(depth - 1)).flat_map { |a| ["#{a}.first", "#{a}.last"] }
|
|
264
264
|
end
|
|
265
265
|
|
|
266
|
-
# A "boolean"
|
|
267
|
-
#
|
|
268
|
-
# anywhere else a boolean-typed
|
|
269
|
-
# typed
|
|
270
|
-
# nested-`.modulo` bug was: `Resolver.parse` has
|
|
271
|
-
# `==`/`<`/`&&`/`||`/leading `!`/`.include?`
|
|
272
|
-
# `Evaluator`'s
|
|
273
|
-
#
|
|
266
|
+
# A "boolean" in resolver's own sense — safe to embed as the
|
|
267
|
+
# receiver of a trailing Resolver-level suffix (`.to_s`, and
|
|
268
|
+
# anywhere else a boolean-typed value, as opposed to a boolean-
|
|
269
|
+
# typed expression, is wanted). Found live, the same way the
|
|
270
|
+
# nested-`.modulo` bug was: `Resolver.parse` has no knowledge of
|
|
271
|
+
# `==`/`<`/`&&`/`||`/leading `!`/`.include?` at all — those are
|
|
272
|
+
# `Evaluator`'s own, entirely separate parsing layer, stripped off
|
|
273
|
+
# before anything reaches `Resolver.parse` at all (confirmed
|
|
274
274
|
# directly: `Resolver.parse("str_b < str_a")` — no `.` anywhere in
|
|
275
275
|
# that text for any suffix regex to anchor on — falls through
|
|
276
276
|
# every leaf regex to the `Lookup` catch-all, exactly like the
|
|
277
|
-
# nested-modulo bug did). `bounded(:boolean, depth)`'s
|
|
277
|
+
# nested-modulo bug did). `bounded(:boolean, depth)`'s full set
|
|
278
278
|
# includes `Compare`/`Include`/`Or`/`And`/`Not` — genuinely boolean-
|
|
279
|
-
#
|
|
279
|
+
# typed at `interpret` time, but `Evaluator`-level syntax, not
|
|
280
280
|
# something `Resolver.parse` can ever recognize as a receiver no
|
|
281
281
|
# matter how it's parenthesized (confirmed directly too: `Resolver
|
|
282
282
|
# .parse` never strips parens at all — `"(3)"` alone already fails
|
|
283
|
-
# to resolve). This is a
|
|
283
|
+
# to resolve). This is a real, permanent boundary of the actual
|
|
284
284
|
# grammar (this whole sublanguage's own two-layer split, not a
|
|
285
285
|
# limitation to work around) — a `given`/`invariant` author simply
|
|
286
286
|
# cannot write `(a < b).to_s` in this language, ever, no matter how
|
|
287
287
|
# they punctuate it. So this generator doesn't either: only
|
|
288
|
-
#
|
|
288
|
+
# resolver-level boolean-producing constructs (bare literals/
|
|
289
289
|
# lookups, `SignTest`, `Empty`, `Presence`, `MatchesRegex`,
|
|
290
290
|
# `StartsWith`/`EndsWith`, `BlockPredicate` — every one of them
|
|
291
|
-
# parsed via a suffix regex
|
|
291
|
+
# parsed via a suffix regex inside `Resolver.parse` itself, per
|
|
292
292
|
# this generator's own design report) are eligible here.
|
|
293
293
|
def resolver_boolean_leaves(depth)
|
|
294
294
|
leaves(:boolean) +
|
|
@@ -302,11 +302,11 @@ module Hecks
|
|
|
302
302
|
sample(block_predicate_productions(depth))
|
|
303
303
|
end
|
|
304
304
|
|
|
305
|
-
# A "numeric"
|
|
305
|
+
# A "numeric" in resolver's own sense — `Addition`'s twin of
|
|
306
306
|
# `resolver_boolean_leaves`'s own restriction, found the identical
|
|
307
307
|
# way: `"0 + 0.to_s"` (meant as `(0 + 0).to_s`) actually parses as
|
|
308
308
|
# `0 + (0.to_s)`, because `Resolver.parse` tries `split_addition`
|
|
309
|
-
#
|
|
309
|
+
# before `.to_s`'s own suffix regex in its dispatch order — the `+`
|
|
310
310
|
# "wins" the split before the suffix ever gets a chance to anchor
|
|
311
311
|
# on its own receiver boundary. Confirmed to have zero real-corpus
|
|
312
312
|
# precedent either (`grep`, no `bluebook` file anywhere chains a
|
|
@@ -315,7 +315,7 @@ module Hecks
|
|
|
315
315
|
# a real permanent grammar boundary (`Resolver.parse` never strips
|
|
316
316
|
# parens, confirmed directly, so no amount of punctuation rescues
|
|
317
317
|
# `(a + b).to_s`), not a bug to fix in the resolver for a shape
|
|
318
|
-
# nothing has ever needed. `Modulo`/`Size`/`First`/`Last` stay
|
|
318
|
+
# nothing has ever needed. `Modulo`/`Size`/`First`/`Last` stay in
|
|
319
319
|
# (each is its own trailing `.method(...)`/`.method` call, so
|
|
320
320
|
# `Resolver.parse`'s greedy `(.+)\.suffix\z` regexes correctly
|
|
321
321
|
# isolate them as a receiver regardless of what precedes them —
|
|
@@ -323,8 +323,8 @@ module Hecks
|
|
|
323
323
|
def resolver_numeric_leaves(depth)
|
|
324
324
|
return leaves(:numeric) if depth <= 0
|
|
325
325
|
|
|
326
|
-
#
|
|
327
|
-
# receiver needs the
|
|
326
|
+
# Self-referential on purpose, one depth down — `Modulo`'s own
|
|
327
|
+
# receiver needs the same restriction `resolver_numeric_leaves`
|
|
328
328
|
# exists to express in the first place (see its own header
|
|
329
329
|
# comment); the argument stays the full, unrestricted set, same
|
|
330
330
|
# as `numeric_productions`' identical split right above.
|
|
@@ -336,8 +336,8 @@ module Hecks
|
|
|
336
336
|
end
|
|
337
337
|
|
|
338
338
|
# STRING ← ToS(numeric | boolean | nil | string) | First/Last(string
|
|
339
|
-
# array). `Split` produces an
|
|
340
|
-
# own `apply_split`) — deliberately
|
|
339
|
+
# array). `Split` produces an array, never a String (resolver.rb's
|
|
340
|
+
# own `apply_split`) — deliberately not listed as a string producer
|
|
341
341
|
# here; that would be exactly the "vocabulary-legal but grammar-
|
|
342
342
|
# can't-actually-produce-it" mistake this sublanguage's own
|
|
343
343
|
# `ArrayLiteral` bug (§1 of this generator's own design report) was
|
|
@@ -353,8 +353,8 @@ module Hecks
|
|
|
353
353
|
# ARRAY ← Split(string, separator) | ArrayLiteral[same-type
|
|
354
354
|
# elements] | the synthetic array attributes (already in `leaves`).
|
|
355
355
|
# Kept deliberately small relative to numeric/string/boolean —
|
|
356
|
-
# `array` is overwhelmingly a
|
|
357
|
-
# /`.any?`/`.include?`/…), rarely a produced
|
|
356
|
+
# `array` is overwhelmingly a receiver type in this grammar (`.size`
|
|
357
|
+
# /`.any?`/`.include?`/…), rarely a produced value; the two real
|
|
358
358
|
# producers are enough to exercise every array-typed consumer
|
|
359
359
|
# elsewhere in this file at least once via a non-leaf path.
|
|
360
360
|
def array_productions(depth)
|
|
@@ -367,11 +367,11 @@ module Hecks
|
|
|
367
367
|
def numeric_array_literal(depth) = "[#{bounded(:numeric, depth).first(2).join(', ')}]"
|
|
368
368
|
def string_array_literal(depth) = "[#{bounded(:string, depth).first(2).join(', ')}]"
|
|
369
369
|
|
|
370
|
-
# Arrays
|
|
370
|
+
# Arrays known (by construction, not merely by type) to hold numeric
|
|
371
371
|
# elements — the two synthetic array attributes (`ARRAY_ELEMENT_TYPE`
|
|
372
|
-
# tags `arr_num`) plus any numeric-array
|
|
372
|
+
# tags `arr_num`) plus any numeric-array literal this same depth
|
|
373
373
|
# budget can build. `.first`/`.last`/block predicates need to know
|
|
374
|
-
# the
|
|
374
|
+
# the element type, which plain `array_productions` doesn't carry —
|
|
375
375
|
# this (and `string_array_productions`, its twin) is how that extra
|
|
376
376
|
# bit of type information flows without inventing a second, richer
|
|
377
377
|
# AST just to carry it.
|
|
@@ -387,8 +387,8 @@ module Hecks
|
|
|
387
387
|
end
|
|
388
388
|
|
|
389
389
|
# BOOLEAN ← every comparison/predicate construct in the grammar.
|
|
390
|
-
# This is where almost all of the sublanguage's
|
|
391
|
-
# lives — a `given`/`invariant`/`ensures` body is
|
|
390
|
+
# This is where almost all of the sublanguage's own real surface
|
|
391
|
+
# lives — a `given`/`invariant`/`ensures` body is always boolean-
|
|
392
392
|
# typed at its own top level (`Evaluator.truthy?`), so this is also
|
|
393
393
|
# the set `all_predicates` (below) draws its top-level cases from
|
|
394
394
|
# directly.
|
|
@@ -433,14 +433,14 @@ module Hecks
|
|
|
433
433
|
include_productions(depth - 1)
|
|
434
434
|
end
|
|
435
435
|
|
|
436
|
-
# `.all?`/`.any?`/`.none?` over
|
|
436
|
+
# `.all?`/`.any?`/`.none?` over each known-element-typed array
|
|
437
437
|
# source, with a predicate body drawn from `boolean_productions` at
|
|
438
|
-
#
|
|
439
|
-
# array's own element type — this is the
|
|
438
|
+
# one less depth, evaluated against `BLOCK_PARAM` bound to the
|
|
439
|
+
# array's own element type — this is the only place `Resolver` and
|
|
440
440
|
# `Evaluator` are truly mutually recursive (a `BlockPredicate`'s own
|
|
441
441
|
# `predicate` field is a full `Evaluator` AST, not a `Resolver`
|
|
442
442
|
# leaf — confirmed directly), and the only construct in this whole
|
|
443
|
-
# generator that can nest into
|
|
443
|
+
# generator that can nest into another block predicate (the real
|
|
444
444
|
# corpus already does this two levels deep —
|
|
445
445
|
# `examples/roster/bluebook/roster.bluebook`'s own `seats.any? { |s|
|
|
446
446
|
# assignments.none? { |a| … } }`).
|
|
@@ -458,14 +458,14 @@ module Hecks
|
|
|
458
458
|
end
|
|
459
459
|
|
|
460
460
|
# The predicate body a block gets — `boolean_productions`, but with
|
|
461
|
-
# `BLOCK_PARAM` (bound to `element_type`)
|
|
461
|
+
# `BLOCK_PARAM` (bound to `element_type`) also admitted as a leaf,
|
|
462
462
|
# since inside the block it is exactly as usable as any other
|
|
463
463
|
# `Lookup` name (resolver.rb's own `interpret_with_element`: the
|
|
464
464
|
# bound element joins `attrs` for the span of one evaluation, no
|
|
465
465
|
# different from a top-level attribute — confirmed directly).
|
|
466
466
|
# `sample`d for the same reason every other internal list is — this
|
|
467
|
-
# feeds
|
|
468
|
-
#
|
|
467
|
+
# feeds three more constructs per body (`all?`/`any?`/`none?`) times
|
|
468
|
+
# four array sources, so an unbounded body list here is exactly the
|
|
469
469
|
# kind of multiplier this file's own `bounded` comment warns about.
|
|
470
470
|
def predicate_bodies(element_type, depth)
|
|
471
471
|
sample(with_element_leaf(element_type) { boolean_productions(depth) })
|
|
@@ -473,7 +473,7 @@ module Hecks
|
|
|
473
473
|
|
|
474
474
|
# `haystack.include?(needle)` — String haystack needs a String
|
|
475
475
|
# needle (raises otherwise, evaluator.rb's own `includes?`); Array
|
|
476
|
-
# haystack admits
|
|
476
|
+
# haystack admits any needle type (compared via `equal?`, itself
|
|
477
477
|
# numeric-coerced-first). Both sides generated here, matching
|
|
478
478
|
# `Vocabulary::IncludeHaystack` exactly.
|
|
479
479
|
def include_productions(depth)
|
|
@@ -487,23 +487,23 @@ module Hecks
|
|
|
487
487
|
end
|
|
488
488
|
end
|
|
489
489
|
|
|
490
|
-
#
|
|
490
|
+
# **Sampled, not a full cross product** — a full `list.product(list)`
|
|
491
491
|
# is what actually explodes this generator (numeric productions
|
|
492
|
-
# alone hit 8000+ by depth 2; squaring
|
|
492
|
+
# alone hit 8000+ by depth 2; squaring that for `&&`/`==` pairs is
|
|
493
493
|
# where "tens of thousands" becomes tens of millions). The
|
|
494
494
|
# combinatorics genuinely don't buy proof coverage: proving `Or`/
|
|
495
|
-
# `And`/`Not` themselves never crash needs
|
|
495
|
+
# `And`/`Not` themselves never crash needs one representative pair
|
|
496
496
|
# per depth (`evaluator.rb`'s own `interpret` does zero type-
|
|
497
497
|
# dependent work for those three — `interpret(left) || interpret
|
|
498
498
|
# (right)`, plain Ruby, no coercion, no receiver-type check at
|
|
499
|
-
# all — so a crash there could only come from
|
|
499
|
+
# all — so a crash there could only come from left or right
|
|
500
500
|
# themselves, already covered by testing every operand on its own
|
|
501
501
|
# elsewhere in this same predicate set). `Compare`/`Include`/
|
|
502
|
-
# `Addition`/`Modulo` genuinely
|
|
502
|
+
# `Addition`/`Modulo` genuinely do real per-pair type coercion
|
|
503
503
|
# (`Evaluator.apply`/`less_than`/`Resolver.add`/`apply_modulo`), so
|
|
504
|
-
#
|
|
504
|
+
# those stay covered across every construct shape at every depth —
|
|
505
505
|
# just sampled evenly across each side's own operand set (leaves
|
|
506
|
-
#
|
|
506
|
+
# and deep productions alike, not just whichever the list happens
|
|
507
507
|
# to enumerate first) rather than every possible pairing of them.
|
|
508
508
|
SAMPLE_CAP = 14
|
|
509
509
|
|
|
@@ -514,13 +514,13 @@ module Hecks
|
|
|
514
514
|
sampled.product(sampled)
|
|
515
515
|
end
|
|
516
516
|
|
|
517
|
-
# `pairs`' own two-different-lists twin — used wherever the
|
|
518
|
-
# and
|
|
517
|
+
# `pairs`' own two-different-lists twin — used wherever the left
|
|
518
|
+
# and right of a construct have genuinely different safety
|
|
519
519
|
# requirements (`Modulo`'s own receiver vs. argument, below) and
|
|
520
|
-
# squaring the
|
|
520
|
+
# squaring the same sampled list wouldn't be correct.
|
|
521
521
|
def cross(left, right) = sample(left).product(sample(right))
|
|
522
522
|
|
|
523
|
-
#
|
|
523
|
+
# The full set — every boolean-typed expression up to `MAX_DEPTH`,
|
|
524
524
|
# deduplicated (many shorter expressions are also produced, re-
|
|
525
525
|
# wrapped, at every deeper level — `.uniq` inside `productions`
|
|
526
526
|
# already collapses most of that; this is the final pass over the
|
|
@@ -531,8 +531,8 @@ module Hecks
|
|
|
531
531
|
|
|
532
532
|
# Interprets one predicate against the shared synthetic
|
|
533
533
|
# state/attrs, returning `{ok: true, result: ...}` on any outcome
|
|
534
|
-
# `Evaluator.call` itself can express (a real true/false answer,
|
|
535
|
-
# a clean `EvaluationError` — both are the sublanguage
|
|
534
|
+
# `Evaluator.call` itself can express (a real true/false answer, or
|
|
535
|
+
# a clean `EvaluationError` — both are the sublanguage working
|
|
536
536
|
# correctly, never a finding) and `{ok: false, error: ...}` only
|
|
537
537
|
# for anything else escaping — the one shape this whole file exists
|
|
538
538
|
# to prove never happens for well-typed input.
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
require "open3"
|
|
3
|
+
require_relative "form_census"
|
|
4
|
+
|
|
5
|
+
module Hecks
|
|
6
|
+
module Fuzzing
|
|
7
|
+
# An agent reads the adversarial corpus and writes the next meeting.
|
|
8
|
+
#
|
|
9
|
+
# `DomainGenerator` forces two `FormCensus::FORMS` onto one aggregate
|
|
10
|
+
# from a seed — cheap, mechanical, and blind to why a combination
|
|
11
|
+
# should break. The bugs the ledger actually logged came from someone
|
|
12
|
+
# reading a stress domain, a bug title, and a runtime file side by
|
|
13
|
+
# side and guessing where the next divergence lives (`corrections`
|
|
14
|
+
# from BUG#30-33, `case_escalation` from ADR 0037 Finding 5 meeting
|
|
15
|
+
# `corrects`). This is that guess, delegated: the mechanical half
|
|
16
|
+
# (what the corpus already puts together, which pairs nothing meets,
|
|
17
|
+
# what the recent bugs were) is computed here; the judgment half is a
|
|
18
|
+
# prompt (`qa/combination_miner/prompt.md`) an agent answers by
|
|
19
|
+
# writing candidate bluebooks, each with a hypothesis. Checking them
|
|
20
|
+
# is not this module's job — `bin/qa_mine_combinations` hands every
|
|
21
|
+
# valid candidate to `bin/qa_generated_domains --source`, the same
|
|
22
|
+
# differential, self-consistency, Rust build and shrinking path a
|
|
23
|
+
# generated domain takes.
|
|
24
|
+
#
|
|
25
|
+
# **Opt-in, never the rotation**. An agent call costs money and minutes
|
|
26
|
+
# and answers differently every time; `bin/qa_tick` never runs it and
|
|
27
|
+
# no `QualityControlDials` entry turns it on. A person runs
|
|
28
|
+
# `bin/qa_mine_combinations` when they want new shapes.
|
|
29
|
+
module CombinationMiner
|
|
30
|
+
PROMPT_TEMPLATE = "qa/combination_miner/prompt.md".freeze
|
|
31
|
+
HYPOTHESIS_FILE = "HYPOTHESIS.md".freeze
|
|
32
|
+
|
|
33
|
+
module_function
|
|
34
|
+
|
|
35
|
+
# Every adversarial domain plus every example — the corpus the
|
|
36
|
+
# census is measured over. Promoted generated domains are included:
|
|
37
|
+
# their shapes are already swept too.
|
|
38
|
+
def corpus_paths(root)
|
|
39
|
+
Hecks::Corpus.members(:stress, :example, root: root).map(&:path)
|
|
40
|
+
.select { |path| Hecks::Corpus.bluebook_files(path) }
|
|
41
|
+
.sort
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# The last `limit` bug commit subjects on this branch — cheap, needs
|
|
45
|
+
# no ledger or Postgres, and names the mechanism in the title by
|
|
46
|
+
# convention (`BUG#n: …`).
|
|
47
|
+
def recent_bug_titles(root, limit: 60)
|
|
48
|
+
out, status = Open3.capture2("git", "log", "--format=%s", "--grep=BUG#", "-n", limit.to_s, chdir: root)
|
|
49
|
+
status.success? ? out.lines.map(&:strip).reject(&:empty?) : []
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# The mechanical half: which form pairs the corpus meets (and by
|
|
53
|
+
# whom), which it never meets, and what could not be measured.
|
|
54
|
+
def brief(paths, root:, bug_titles:)
|
|
55
|
+
covered = Hash.new { |hash, key| hash[key] = [] }
|
|
56
|
+
skipped = []
|
|
57
|
+
paths.each do |path|
|
|
58
|
+
FormCensus.covered_pairs(FormCensus.census(path)).each { |pair, names| covered[pair].concat(names) }
|
|
59
|
+
rescue StandardError, ScriptError => e
|
|
60
|
+
skipped << "#{relative(path, root)} (#{e.class}: #{e.message.lines.first&.strip})"
|
|
61
|
+
end
|
|
62
|
+
{ "forms" => FormCensus::FORMS.keys, "corpus" => paths.map { |path| relative(path, root) },
|
|
63
|
+
"unmet_pairs" => (FormCensus.pairs - covered.keys).sort,
|
|
64
|
+
"single_carrier_pairs" => covered.select { |_, names| names.uniq.size == 1 }
|
|
65
|
+
.map { |pair, names| "#{pair} (only #{names.first})" }.sort,
|
|
66
|
+
"skipped" => skipped, "recent_bugs" => bug_titles, "covered" => covered }
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def prompt(root, brief, count:, out_dir:)
|
|
70
|
+
substitutions = {
|
|
71
|
+
"count" => count.to_s, "out_dir" => out_dir, "forms" => brief["forms"].join(", "),
|
|
72
|
+
"corpus" => bulleted(brief["corpus"]), "unmet_pairs" => bulleted(brief["unmet_pairs"]),
|
|
73
|
+
"single_carrier_pairs" => bulleted(brief["single_carrier_pairs"]),
|
|
74
|
+
"recent_bugs" => bulleted(brief["recent_bugs"]), "skipped" => bulleted(brief["skipped"])
|
|
75
|
+
}
|
|
76
|
+
File.read(File.join(root, PROMPT_TEMPLATE)).gsub(/\{\{(\w+)\}\}/) { substitutions.fetch(Regexp.last_match(1)) }
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# The second, narrower ask: these candidates did not boot, here is
|
|
80
|
+
# why, fix them in place. Nothing else in the directory changes.
|
|
81
|
+
def repair_prompt(failures, out_dir:)
|
|
82
|
+
listed = failures.map { |candidate, error| "- #{candidate[:bluebook]}\n boot error: #{error}" }.join("\n")
|
|
83
|
+
<<~PROMPT
|
|
84
|
+
REPAIR ROUND. You wrote candidate Hecks bluebooks under #{out_dir} for the QA combination miner.
|
|
85
|
+
These did not boot. Edit each file IN PLACE so it boots, keeping the construct combination its
|
|
86
|
+
#{HYPOTHESIS_FILE} names (update the hypothesis if the fix changes the shape). Do not create new
|
|
87
|
+
candidates and do not touch anything outside #{out_dir}.
|
|
88
|
+
|
|
89
|
+
#{listed}
|
|
90
|
+
PROMPT
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# `<out_dir>/<slug>/<anything>.bluebook` plus an optional
|
|
94
|
+
# HYPOTHESIS.md beside it. One bluebook per candidate: the first,
|
|
95
|
+
# alphabetically, if an agent wrote more.
|
|
96
|
+
def candidates(out_dir)
|
|
97
|
+
Dir[File.join(out_dir, "*")].select { |dir| File.directory?(dir) }.sort.filter_map do |dir|
|
|
98
|
+
bluebook = Dir[File.join(dir, "*.bluebook")].min
|
|
99
|
+
next unless bluebook
|
|
100
|
+
|
|
101
|
+
hypothesis = File.join(dir, HYPOTHESIS_FILE)
|
|
102
|
+
{ slug: File.basename(dir), dir: dir, bluebook: bluebook,
|
|
103
|
+
hypothesis: File.exist?(hypothesis) ? File.read(hypothesis).strip : nil }
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Pairs the candidate meets on one aggregate that no corpus domain
|
|
108
|
+
# does — informational, never a gate: an agent may be aiming at a
|
|
109
|
+
# shape the census does not name yet.
|
|
110
|
+
def new_pairs(candidate_dir, covered)
|
|
111
|
+
FormCensus.covered_pairs(FormCensus.census(candidate_dir)).keys.reject { |pair| covered.key?(pair) }.sort
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def relative(path, root) = path.delete_prefix("#{root}/")
|
|
115
|
+
|
|
116
|
+
def bulleted(items) = items.empty? ? "(none)" : items.map { |item| "- #{item}" }.join("\n")
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|