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
|
@@ -1,146 +1,39 @@
|
|
|
1
1
|
module Hecks
|
|
2
2
|
module Fuzzing
|
|
3
|
-
#
|
|
3
|
+
# What each query verb the differential comparison skipped was hiding.
|
|
4
4
|
#
|
|
5
|
-
# `
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
# much of the domain was never compared at all. Worse, the drop keys
|
|
14
|
-
# on Rust's wording alone, so a codegen regression that started
|
|
15
|
-
# refusing a plain wheres-only query the same way would ALSO vanish
|
|
16
|
-
# into "agreed" — the exact quiet divergence the practice hunts.
|
|
5
|
+
# `Differential.manifest_partition` drops a named query or read model
|
|
6
|
+
# from both sides of the Ruby/Rust comparison only when the binary's own
|
|
7
|
+
# manifest.json declares it `generated: false`. The drop is correct, but
|
|
8
|
+
# a sweep that dropped every `offset`/`cursor` ask would still read as
|
|
9
|
+
# "agreed across all steps". `bin/qa_sweep` therefore logs one Check per
|
|
10
|
+
# sweep naming every skipped verb and the construct family that caused
|
|
11
|
+
# it, and marks it Surprised when a family is outside
|
|
12
|
+
# `QualityControlDials::STRUCTURAL_REFUSAL_BOUNDARY`.
|
|
17
13
|
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
# WHY — and mark it Surprised whenever a skipped verb declares nothing
|
|
23
|
-
# the dial `QualityControlDials::STRUCTURAL_REFUSAL_BOUNDARY` admits as
|
|
24
|
-
# a reason to skip (a wheres-only query on plain fields, a rooted
|
|
25
|
-
# read model with nothing but heads, a verb Ruby doesn't even declare).
|
|
26
|
-
#
|
|
27
|
-
# COARSER THAN CODEGEN'S OWN PREDICATE, ON PURPOSE. `query_where_skip_
|
|
28
|
-
# reason` decides per where clause from the field's resolved KIND
|
|
29
|
-
# (number/string/multi-member value object) — reproducing that here
|
|
30
|
-
# would be a second copy of codegen's typing rules, drifting on its
|
|
31
|
-
# own. Instead a literal-valued where is reported as `where_literal`,
|
|
32
|
-
# a family the dial admits, and only shapes with NO admitted family at
|
|
33
|
-
# all surprise. That trades some sensitivity for zero false surprises
|
|
34
|
-
# on today's corpus; the Check's observation still names every skipped
|
|
35
|
-
# verb with its constructs, so a human reading the sweep sees what was
|
|
36
|
-
# accepted and can narrow the dial when codegen grows.
|
|
14
|
+
# The construct comes straight from the manifest entry, written by the
|
|
15
|
+
# generator branch that made the skip (`Projector::SkipReason`). This
|
|
16
|
+
# module used to guess it from the Ruby declaration's `to_h` keys, which
|
|
17
|
+
# was coarser than codegen's own decision and could drift from it.
|
|
37
18
|
module StructuralSkips
|
|
38
19
|
module_function
|
|
39
20
|
|
|
40
|
-
# `
|
|
41
|
-
#
|
|
42
|
-
#
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
# constructs sorted so the printed observation is stable across
|
|
50
|
-
# seeds and runs.
|
|
51
|
-
def attribute(bluebooks, verbs)
|
|
52
|
-
verbs.sort.map { |verb| { verb: verb, constructs: constructs_of(bluebooks, verb) } }
|
|
21
|
+
# One entry per skipped verb: `{ verb:, constructs: [...] }`, sorted by
|
|
22
|
+
# verb so the printed observation is stable across seeds and runs. A
|
|
23
|
+
# verb the manifest doesn't declare answers `unknown`, which no
|
|
24
|
+
# boundary admits.
|
|
25
|
+
def attribute(gaps, verbs)
|
|
26
|
+
verbs.sort.map do |verb|
|
|
27
|
+
entry = gaps.not_generated(verb)
|
|
28
|
+
{ verb: verb, constructs: entry ? [entry.fetch("construct")] : %w[unknown] }
|
|
29
|
+
end
|
|
53
30
|
end
|
|
54
31
|
|
|
55
|
-
# Every skipped verb whose constructs are
|
|
56
|
-
# — including a verb with no constructs at all (nothing explains the
|
|
57
|
-
# skip) and a verb Ruby never declared (`unknown`).
|
|
32
|
+
# Every skipped verb whose constructs are not all inside `boundary`.
|
|
58
33
|
def outside_boundary(attributed, boundary)
|
|
59
34
|
admitted = boundary.map(&:to_s)
|
|
60
35
|
attributed.select { |entry| entry[:constructs].empty? || (entry[:constructs] - admitted).any? }
|
|
61
36
|
end
|
|
62
|
-
|
|
63
|
-
def constructs_of(bluebooks, verb)
|
|
64
|
-
if verb.include?("::")
|
|
65
|
-
query = find_query(bluebooks, verb)
|
|
66
|
-
return %w[unknown] unless query
|
|
67
|
-
|
|
68
|
-
query_constructs(query)
|
|
69
|
-
else
|
|
70
|
-
model = find_read_model(bluebooks, verb)
|
|
71
|
-
return %w[unknown] unless model
|
|
72
|
-
|
|
73
|
-
read_model_constructs(model)
|
|
74
|
-
end
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
# "Domain::Aggregate.Query" or "Domain::Aggregate.Entity.Query" — the
|
|
78
|
-
# two named-query spellings `Fuzzing::SequenceGenerator`'s catalog
|
|
79
|
-
# generates (entity queries stay one hop deep there).
|
|
80
|
-
def find_query(bluebooks, verb)
|
|
81
|
-
domain, rest = verb.split("::", 2)
|
|
82
|
-
aggregate_name, *path = rest.to_s.split(".")
|
|
83
|
-
aggregate = bluebooks[domain]&.aggregate(aggregate_name)
|
|
84
|
-
return nil unless aggregate && path.any?
|
|
85
|
-
|
|
86
|
-
owner = aggregate
|
|
87
|
-
path[0...-1].each do |entity_name|
|
|
88
|
-
owner = owner.entities.find { |entity| entity.hecks_name == entity_name }
|
|
89
|
-
return nil unless owner
|
|
90
|
-
end
|
|
91
|
-
owner.query(path.last)
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
# "Domain.report_name" — the bare read-model form `Dispatcher#query`
|
|
95
|
-
# routes by the absence of "::".
|
|
96
|
-
def find_read_model(bluebooks, verb)
|
|
97
|
-
domain, name = verb.split(".", 2)
|
|
98
|
-
bluebooks[domain]&.read_models&.find { |model| model.query_name.to_s == name.to_s }
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
def query_constructs(query)
|
|
102
|
-
spec = query.to_h
|
|
103
|
-
constructs = QUERY_OPTION_KEYS.select { |key| present?(spec[key]) }.map(&:to_s)
|
|
104
|
-
constructs << "null_semantics" if spec[:null_semantics] && spec[:null_semantics] != { mode: "native" }
|
|
105
|
-
constructs << "no_wheres" if Array(spec[:wheres]).empty?
|
|
106
|
-
constructs.concat(where_constructs(Array(spec[:wheres])))
|
|
107
|
-
constructs.uniq.sort
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
def read_model_constructs(model)
|
|
111
|
-
spec = model.to_h
|
|
112
|
-
constructs = READ_MODEL_OPTION_KEYS.select { |key| present?(spec[key]) }.map(&:to_s)
|
|
113
|
-
constructs << "null_semantics" if spec[:null_semantics] && spec[:null_semantics] != { mode: "native" }
|
|
114
|
-
constructs << "rootless" if model.reference_target.nil?
|
|
115
|
-
constructs.concat(where_constructs(Array(spec[:wheres])))
|
|
116
|
-
constructs.uniq.sort
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
# The where-clause families `query_where_skip_reason` refuses by:
|
|
120
|
-
# a `/` hop through a reference, a `.` walk into a nested field
|
|
121
|
-
# (an entity-scoped or value-object path codegen may not resolve),
|
|
122
|
-
# `none_in_state` (admitted by the vocabulary, deliberately not
|
|
123
|
-
# generated), and any literal (non-Symbol) value, whose true wire
|
|
124
|
-
# type codegen may not recover from the IR.
|
|
125
|
-
def where_constructs(wheres)
|
|
126
|
-
wheres.flat_map do |where|
|
|
127
|
-
field = where[:field].to_s
|
|
128
|
-
value = where[:value].to_s
|
|
129
|
-
families = []
|
|
130
|
-
families << "reference_hop_where" if field.include?("/")
|
|
131
|
-
families << "where_nested_field" if field.include?(".")
|
|
132
|
-
families << "where_none_in_state" if where[:op].to_s == "none_in_state"
|
|
133
|
-
families << "where_literal" unless value.start_with?(":")
|
|
134
|
-
families
|
|
135
|
-
end
|
|
136
|
-
end
|
|
137
|
-
|
|
138
|
-
def present?(value)
|
|
139
|
-
return false if value.nil? || value == false
|
|
140
|
-
return value.any? if value.respond_to?(:empty?)
|
|
141
|
-
|
|
142
|
-
true
|
|
143
|
-
end
|
|
144
37
|
end
|
|
145
38
|
end
|
|
146
39
|
end
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
module Hecks
|
|
2
2
|
module Fuzzing
|
|
3
|
-
#
|
|
3
|
+
# How hard one sweep fuzzes, as a function of `Target.clean_streak`
|
|
4
4
|
# (qa/bluebook/quality_control.bluebook — that attribute's own comment
|
|
5
5
|
# says what the streak means). The sibling of `RotationPriority`: the
|
|
6
6
|
# ledger holds the number, `QualityControlDials::WIDENING_TIERS` holds
|
|
7
7
|
# the policy, and this module is where the two meet — a pure function
|
|
8
8
|
# of both, nothing else read.
|
|
9
9
|
#
|
|
10
|
-
#
|
|
10
|
+
# Why this is not in `bin/qa_sweep` any more. It was — `WIDENING_TIERS`
|
|
11
11
|
# and `widen_for_streak` lived at the top of that script, which meant
|
|
12
12
|
# policy data lived in a script (unlike every other dial, which lives
|
|
13
13
|
# in the bluebook a human edits and reviews) and duplicated itself as
|
|
@@ -15,7 +15,7 @@ module Hecks
|
|
|
15
15
|
# and `bin/qa_sweep` calls it the way it already calls
|
|
16
16
|
# `RotationPriority.pick`.
|
|
17
17
|
#
|
|
18
|
-
#
|
|
18
|
+
# **Pure, deliberately** — the same discipline `RotationPriority` keeps:
|
|
19
19
|
# same streak in, same `[seeds, steps]` out, every time, which is what
|
|
20
20
|
# lets a human predict what a given sweep is about to do before it
|
|
21
21
|
# runs one. `tiers:` defaults to the dial but is a plain argument, so
|
|
@@ -26,7 +26,7 @@ module Hecks
|
|
|
26
26
|
module SweepDepth
|
|
27
27
|
module_function
|
|
28
28
|
|
|
29
|
-
#
|
|
29
|
+
# The fallback table, for a boot with no `QualityControlDials` at all
|
|
30
30
|
# — the same shape and the same three rows the dial ships with, so a
|
|
31
31
|
# dial-less ledger fuzzes exactly as a dialled one does by default.
|
|
32
32
|
# Never read when the dial exists; `bin/qa_sweep` passes the dial in.
|
|
@@ -1,30 +1,30 @@
|
|
|
1
1
|
module Hecks
|
|
2
2
|
module Fuzzing
|
|
3
|
-
#
|
|
3
|
+
# What a sweep target can actually be checked for, read off the
|
|
4
4
|
# filesystem — never off a stored list.
|
|
5
5
|
#
|
|
6
|
-
# `bin/qa_sweep` used to decide its
|
|
6
|
+
# `bin/qa_sweep` used to decide its one comparison mode inline: "is
|
|
7
7
|
# there a Cargo feature named after this directory? then
|
|
8
8
|
# `:differential`, else `:ruby_only`", and a separate hand-typed abort
|
|
9
9
|
# for `--persistence-parity` ("does any .hecksagon bind PostgresEra?").
|
|
10
10
|
# Every further mode the practice adds (era boundary, concurrency, a
|
|
11
11
|
# WASM front) would have grown one more inline `if`, each one a
|
|
12
12
|
# policy decision hiding in a script. This module is those decisions
|
|
13
|
-
# as
|
|
13
|
+
# as data: `MODE_REQUIREMENTS` says which capabilities each mode
|
|
14
14
|
# needs, `infer` says which capabilities a target's own directory
|
|
15
15
|
# actually has, and `resolve` is the one rule that joins them —
|
|
16
16
|
# `modes_to_run = enabled ∩ eligible`.
|
|
17
17
|
#
|
|
18
|
-
#
|
|
18
|
+
# Inference decides; a stored list only records. `Target.capabilities`
|
|
19
19
|
# (qa/bluebook/quality_control.bluebook, once PR-1's era lands) is
|
|
20
20
|
# written by the runner from exactly this inference at release time
|
|
21
|
-
# so `Target.EligibleFor(mode)` can
|
|
21
|
+
# so `Target.EligibleFor(mode)` can audit the rotation from the
|
|
22
22
|
# ledger alone — but the runner re-infers every sweep, because a
|
|
23
23
|
# stored list that lags yesterday's Cargo feature is precisely the
|
|
24
24
|
# "quiet divergence" (the chapter's own opening comment) this whole
|
|
25
25
|
# practice exists to hunt. Nothing here ever reads the ledger.
|
|
26
26
|
#
|
|
27
|
-
#
|
|
27
|
+
# Every regex is one the harness already owned, moved here rather than
|
|
28
28
|
# re-derived, and each one's provenance is named beside it so a
|
|
29
29
|
# future edit to the original site is a visible drift, not a silent
|
|
30
30
|
# one: the Cargo feature line (`RustConformanceHelpers#build_rust_for`),
|
|
@@ -45,10 +45,13 @@ module Hecks
|
|
|
45
45
|
# domain-level default (`persisted_by "PostgresEra"`).
|
|
46
46
|
POSTGRES_ERA_BINDING = /persisted_by\s*\(?\s*"PostgresEra"/
|
|
47
47
|
|
|
48
|
-
# `HecksagonBuilder#uses_framework` —
|
|
49
|
-
#
|
|
50
|
-
# (`
|
|
51
|
-
|
|
48
|
+
# `HecksagonBuilder#uses_framework "X"` — captures which member a
|
|
49
|
+
# hecksagon attaches, whatever its name. Whether that member answers
|
|
50
|
+
# a role check is then read off its own declaration (`provides
|
|
51
|
+
# "authorization"`, via `Framework.providers_of`), the same rule
|
|
52
|
+
# `Registry#authorization_provider_for` applies at runtime — never a
|
|
53
|
+
# match on the literal "Governance".
|
|
54
|
+
FRAMEWORK_ATTACHED = /uses_framework\s*\(?\s*"([^"]+)"/
|
|
52
55
|
|
|
53
56
|
# A command-level `role "..."` — the only construct
|
|
54
57
|
# `refuse_role_mismatch` ever has anything to check a caller against.
|
|
@@ -61,21 +64,21 @@ module Hecks
|
|
|
61
64
|
|
|
62
65
|
PROCESS_MANAGER = /^\s*process_manager\s+"/
|
|
63
66
|
|
|
64
|
-
#
|
|
65
|
-
#
|
|
67
|
+
# Which capabilities each mode needs before it can say anything true
|
|
68
|
+
# about a target. An empty list means "any target at all" — every
|
|
66
69
|
# domain boots under Memory, so Ruby-only properties and the
|
|
67
70
|
# self-consistency pass are always answerable. `ruby_only` is listed
|
|
68
|
-
# requirement-free on purpose and then
|
|
71
|
+
# requirement-free on purpose and then excluded by `resolve` whenever
|
|
69
72
|
# `differential` resolved too: they are the same seat, and a compiled
|
|
70
73
|
# Rust binary is strictly the better occupant (item 1 of the
|
|
71
|
-
# detection plan folded the Ruby-only property battery
|
|
74
|
+
# detection plan folded the Ruby-only property battery into the
|
|
72
75
|
# differential seat, so nothing is lost by the exclusion).
|
|
73
76
|
#
|
|
74
77
|
# The four `false`-by-default modes in `QualityControlDials::MODES`
|
|
75
78
|
# (`adapter_parity_postgres`, `era_boundary`, `concurrency`,
|
|
76
79
|
# `wasm_front`) are named here with their requirements even though
|
|
77
80
|
# nothing runs them yet — so `resolved modes:` can already say, per
|
|
78
|
-
# target, which of them
|
|
81
|
+
# target, which of them would be eligible the day a human flips the
|
|
79
82
|
# dial, and so flipping it is a one-line data change rather than a
|
|
80
83
|
# code change plus a data change.
|
|
81
84
|
MODE_REQUIREMENTS = {
|
|
@@ -92,7 +95,24 @@ module Hecks
|
|
|
92
95
|
wasm_front: %w[rust]
|
|
93
96
|
}.freeze
|
|
94
97
|
|
|
95
|
-
#
|
|
98
|
+
# Which of those modes `bin/qa_sweep` can actually run today.
|
|
99
|
+
# `MODE_REQUIREMENTS` above says what a mode needs; this says what
|
|
100
|
+
# exists to do it, and the two are not the same. Conflating them is
|
|
101
|
+
# how `qa/settings.yml` came to enable `wasm_front` and
|
|
102
|
+
# `adapter_parity_postgres` with nothing behind either — no seat, no
|
|
103
|
+
# check folded into the seed loop, no `MODE_EXPECTATIONS` entry — while
|
|
104
|
+
# `resolved modes:` still printed them per target, so the sweep
|
|
105
|
+
# advertised coverage it never performed. A mode named in
|
|
106
|
+
# `MODE_REQUIREMENTS` but absent here is one this practice wants and has
|
|
107
|
+
# not built: `bin/qa_sweep` refuses to start when a dial or `--modes`
|
|
108
|
+
# enables it, rather than resolving it into a line nobody can act on.
|
|
109
|
+
# `spec/qa_sweep_runnable_modes_spec.rb` keeps this list honest from
|
|
110
|
+
# both sides by grepping the runner itself.
|
|
111
|
+
RUNNABLE_MODES = %i[differential ruby_only self_consistency properties_in_differential
|
|
112
|
+
structural_skip_report adapter_parity_sqlite persistence_parity
|
|
113
|
+
era_boundary concurrency].freeze
|
|
114
|
+
|
|
115
|
+
# Modes that name a separate, expensive pass of their own rather than
|
|
96
116
|
# an extra check folded into the ordinary per-seed loop —
|
|
97
117
|
# `bin/qa_sweep` runs these only when asked by name (`--modes
|
|
98
118
|
# persistence_parity`, or its older alias `--persistence-parity`) or
|
|
@@ -110,7 +130,7 @@ module Hecks
|
|
|
110
130
|
capabilities << "rust" if rust_feature?(domain_path, rust_dir)
|
|
111
131
|
capabilities << "postgres_era" if any_file?(domain_path, "*.hecksagon", POSTGRES_ERA_BINDING)
|
|
112
132
|
capabilities << "translations" if Dir.glob(File.join(domain_path, "**", "translations", "*.bluebook")).any?
|
|
113
|
-
capabilities << "governance" if
|
|
133
|
+
capabilities << "governance" if authorization_attached?(domain_path)
|
|
114
134
|
capabilities << "role_gated" if any_file?(domain_path, "*.bluebook", ROLE_GATED)
|
|
115
135
|
capabilities << "tenant" if any_file?(domain_path, "*.bluebook", TENANT_SCOPED)
|
|
116
136
|
capabilities << "sagas" if any_file?(domain_path, "*.bluebook", PROCESS_MANAGER)
|
|
@@ -122,7 +142,7 @@ module Hecks
|
|
|
122
142
|
(required - capabilities).empty?
|
|
123
143
|
end
|
|
124
144
|
|
|
125
|
-
#
|
|
145
|
+
# The one rule. `enabled` is whatever the dial (or `--modes`) turned
|
|
126
146
|
# on, in the dial's own declaration order — that order is preserved
|
|
127
147
|
# so the printed line reads the same way the dial does. Then the
|
|
128
148
|
# single exclusion named on `MODE_REQUIREMENTS`.
|
|
@@ -141,6 +161,17 @@ module Hecks
|
|
|
141
161
|
features.match?(/^#{Regexp.escape(feature)}\s*=\s*\[\]/)
|
|
142
162
|
end
|
|
143
163
|
|
|
164
|
+
# The capability label stays "governance" — it is the value
|
|
165
|
+
# `bin/qa_sweep` writes into the QualityControl ledger's
|
|
166
|
+
# `Target.capabilities`, and renaming it is a ledger change, not
|
|
167
|
+
# part of dropping the name check.
|
|
168
|
+
def authorization_attached?(domain_path)
|
|
169
|
+
attached = Dir.glob(File.join(domain_path, "**", "*.hecksagon"))
|
|
170
|
+
.flat_map { |path| File.read(path).scan(FRAMEWORK_ATTACHED).flatten }.uniq
|
|
171
|
+
providers = Framework.providers_of(Bluebook::Capabilities::AUTHORIZATION)
|
|
172
|
+
attached.intersect?(providers)
|
|
173
|
+
end
|
|
174
|
+
|
|
144
175
|
def any_file?(domain_path, glob, pattern)
|
|
145
176
|
Dir.glob(File.join(domain_path, "**", glob)).any? { |path| File.read(path).match?(pattern) }
|
|
146
177
|
end
|
|
@@ -4,7 +4,7 @@ module Hecks
|
|
|
4
4
|
module Fuzzing
|
|
5
5
|
# One attribute, one value — in the exact JSON shape the hand-written
|
|
6
6
|
# corpus already uses (a value-object-typed attribute is a nested hash keyed
|
|
7
|
-
# by its own field names ; a reference is the
|
|
7
|
+
# by its own field names ; a reference is the bare ID of the head it points
|
|
8
8
|
# at, because that is what a reference is). Everything
|
|
9
9
|
# returned is a plain, JSON-safe Ruby value : String keys throughout, never
|
|
10
10
|
# symbols, so a generated step can be dumped straight to JSON.
|
|
@@ -15,7 +15,7 @@ module Hecks
|
|
|
15
15
|
# codebase," and a fixed corpus of a few dozen examples rarely happens to
|
|
16
16
|
# exercise the boundary that actually breaks. But mostly not : an attribute
|
|
17
17
|
# named `email`/`currency` almost always needs a specific shape just to get
|
|
18
|
-
#
|
|
18
|
+
# past its own invariant (`address.include?("@")`, `currency.size == 3`) —
|
|
19
19
|
# banking's very first fuzz run never got past Customer.Register, because
|
|
20
20
|
# no plain random word ever contains "@". Reaching deep state matters more
|
|
21
21
|
# than edge-casing every single field every single time, so the "normal"
|
|
@@ -31,7 +31,7 @@ module Hecks
|
|
|
31
31
|
# (lib/hecks/runtime/value/coercion.rb) has accepted a bare
|
|
32
32
|
# `"large"` in place of `{"value" => "large"}` for any single-field
|
|
33
33
|
# value object since 86727afd — but until this generator actually
|
|
34
|
-
#
|
|
34
|
+
# produces that shape, nothing exercises it: neither the adapter-
|
|
35
35
|
# agreement gate nor the Rust/WASM `from_json` codegen (rust/project/
|
|
36
36
|
# json_codec.rb) can ever be caught drifting on a shape they're
|
|
37
37
|
# never handed.
|
|
@@ -44,22 +44,22 @@ module Hecks
|
|
|
44
44
|
# PRD 05 (numeric-boundary-coverage) — Bignum (`2**100`, past i64's
|
|
45
45
|
# own ceiling, which Ruby's own `Integer` has no such ceiling for —
|
|
46
46
|
# `rust/src/kernel/json.rs`'s own `integral_i64` doc comment names
|
|
47
|
-
# exactly this: a Rust kernel value
|
|
47
|
+
# exactly this: a Rust kernel value cannot represent it, so this
|
|
48
48
|
# exercises a real cross-runtime capability gap, not a Ruby-only
|
|
49
49
|
# edge) and its negative twin. Both round-trip through Ruby's own
|
|
50
50
|
# arithmetic/JSON cleanly (confirmed directly: `(2**100).clamp(...)`
|
|
51
51
|
# and `JSON.generate(2**100)` both just work — Integer has no
|
|
52
|
-
# ceiling here), so nothing in
|
|
52
|
+
# ceiling here), so nothing in this runtime needed a fix for these;
|
|
53
53
|
# they're included so a real generated sequence occasionally
|
|
54
54
|
# produces the value at all, since nothing had, repo-wide, before.
|
|
55
55
|
INTEGER_EDGE_CASES = [0, -1, 2_147_483_647, -2_147_483_648, 2**100, -(2**100)].freeze
|
|
56
56
|
# BUG#35 (QualityControl QA ledger, `lease-clock-json-precision`) —
|
|
57
57
|
# a Bignum edge case (`2**100`, above) landing on an Integer-typed
|
|
58
|
-
#
|
|
58
|
+
# clock or count reading fires the already-catalogued `Json::Num`/
|
|
59
59
|
# f64 precision-loss class (`rust/src/kernel/json.rs`'s
|
|
60
60
|
# `parse_number` parses every number through `s.parse::<f64>()`
|
|
61
61
|
# before any target-type conversion runs, and `Json::Num` is a
|
|
62
|
-
# plain `f64` end to end — see that file's own header) on a
|
|
62
|
+
# plain `f64` end to end — see that file's own header) on a new
|
|
63
63
|
# site every time a new clock/count-shaped field is authored,
|
|
64
64
|
# without adding any new coverage: both engines already refuse
|
|
65
65
|
# (`TypeMismatch`, out of `i64` range either way) for this shape,
|
|
@@ -83,7 +83,7 @@ module Hecks
|
|
|
83
83
|
# as a clock or a count is capped to a narrower, still-real
|
|
84
84
|
# edge-case pool below (still exercises the ordinary i32
|
|
85
85
|
# boundaries, just never a value past f64's own 2**53 exact-
|
|
86
|
-
# integer ceiling) — every
|
|
86
|
+
# integer ceiling) — every other Integer-typed field (a money
|
|
87
87
|
# amount, an identity sequence, anything not clock/count-shaped)
|
|
88
88
|
# still draws from the full `INTEGER_EDGE_CASES` pool above,
|
|
89
89
|
# unchanged.
|
|
@@ -105,7 +105,7 @@ module Hecks
|
|
|
105
105
|
# (raw `ArgumentError`, not a domain refusal) or `JSON.generate`
|
|
106
106
|
# (`JSON::GeneratorError`, also not a domain refusal) — both fixed
|
|
107
107
|
# at the source now, so these are safe to generate. -0.0 is
|
|
108
|
-
# deliberately included too even though it was
|
|
108
|
+
# deliberately included too even though it was already safe
|
|
109
109
|
# (finite, round-trips through JSON as `-0.0` cleanly) — a signed
|
|
110
110
|
# zero is exactly the kind of boundary a hand-written corpus never
|
|
111
111
|
# happens to type, and the fuzzer existing to cover it is the
|
|
@@ -157,10 +157,10 @@ module Hecks
|
|
|
157
157
|
end
|
|
158
158
|
end
|
|
159
159
|
|
|
160
|
-
# `fields.size == 1` — the
|
|
160
|
+
# `fields.size == 1` — the same test `Behaviour::ValueObject#
|
|
161
161
|
# sole_attribute` names: a genuinely single-field value object,
|
|
162
162
|
# not merely "this particular closed-set member happened to pick
|
|
163
|
-
# one field." Unwrapping
|
|
163
|
+
# one field." Unwrapping only here, not in `invalid_member`
|
|
164
164
|
# above — a deliberately-wrong combination stays a Hash so its
|
|
165
165
|
# own wrongness is what gets exercised, not a second, unrelated
|
|
166
166
|
# shape question.
|
|
@@ -178,10 +178,10 @@ module Hecks
|
|
|
178
178
|
end
|
|
179
179
|
end
|
|
180
180
|
|
|
181
|
-
#
|
|
181
|
+
# The ID itself. This minted `{"value" => id}` back when a reference was
|
|
182
182
|
# stored wrapped ; the payload gate refuses that shape now, so a fuzzer
|
|
183
183
|
# still emitting it would have every generated reference refused and
|
|
184
|
-
# the
|
|
184
|
+
# the silent guard would report the fuzzer broken rather than
|
|
185
185
|
# the runtime.
|
|
186
186
|
def reference_value(attribute, random:, known_ids:)
|
|
187
187
|
pool = known_ids[attribute.type.target_name.to_s] || []
|
|
@@ -236,7 +236,7 @@ module Hecks
|
|
|
236
236
|
random.rand(0.01..1000.0).round(2)
|
|
237
237
|
end
|
|
238
238
|
|
|
239
|
-
# The bare scalar a generated
|
|
239
|
+
# The bare scalar a generated identity value stands for, for recording into
|
|
240
240
|
# `known_ids`. An identity is declared as a value object, so this opens one ;
|
|
241
241
|
# a reference pointing at this record is already that scalar and needs no
|
|
242
242
|
# opening at all. The two used to be the same reading and are not any more.
|
data/lib/hecks/fuzzing.rb
CHANGED
|
@@ -8,11 +8,16 @@ end
|
|
|
8
8
|
require_relative "fuzzing/value_generator"
|
|
9
9
|
require_relative "fuzzing/invalid_value_generator"
|
|
10
10
|
require_relative "fuzzing/sequence_generator"
|
|
11
|
+
require_relative "fuzzing/nondeterministic"
|
|
11
12
|
require_relative "fuzzing/replay"
|
|
13
|
+
require_relative "fuzzing/shrinker"
|
|
12
14
|
require_relative "fuzzing/properties"
|
|
15
|
+
require_relative "fuzzing/qa_settings"
|
|
13
16
|
require_relative "fuzzing/rotation_priority"
|
|
14
17
|
require_relative "fuzzing/sweep_depth"
|
|
15
18
|
require_relative "fuzzing/persistence_parity"
|
|
16
19
|
require_relative "fuzzing/form_census"
|
|
17
20
|
require_relative "fuzzing/target_capabilities"
|
|
21
|
+
require_relative "fuzzing/rust_gap_manifest"
|
|
18
22
|
require_relative "fuzzing/structural_skips"
|
|
23
|
+
require_relative "fuzzing/coverage_campaign"
|
data/lib/hecks/grammar/evolve.rb
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
module Hecks
|
|
2
2
|
module Grammar
|
|
3
3
|
# The file surgery under bin/evolve: reading and rewriting the
|
|
4
|
-
# aggregate-local KeywordSeed/ArgumentSeed rows as
|
|
4
|
+
# aggregate-local KeywordSeed/ArgumentSeed rows as text, so a
|
|
5
5
|
# proposed word enters the table exactly as a hand would write it
|
|
6
6
|
# and an admitted one loses its ceremony (an absent status reads as
|
|
7
7
|
# admitted — the grown-column convention).
|
|
@@ -18,7 +18,7 @@ module Hecks
|
|
|
18
18
|
module_function
|
|
19
19
|
|
|
20
20
|
# bin/evolve's own `--name value` flag reader. Only consumes the
|
|
21
|
-
#
|
|
21
|
+
# next argv element as the value when that element doesn't itself
|
|
22
22
|
# look like a flag — otherwise `--foo --bar` would swallow `--bar`
|
|
23
23
|
# as `--foo`'s value (and `--bar` would then never be seen at
|
|
24
24
|
# all), and a value-less `--foo` at the end of argv would bypass
|
|
@@ -97,7 +97,7 @@ module Hecks
|
|
|
97
97
|
row = %(#{indent}member word: "#{word}", context: "#{context}", body: "#{body}", ) +
|
|
98
98
|
%(inner: "#{inner}", opens: "#{opens}", fills: "#{fills}", status: "proposed"\n)
|
|
99
99
|
|
|
100
|
-
# At the
|
|
100
|
+
# At the end of the one_of — grouping by context is a courtesy of
|
|
101
101
|
# the hand; a proposed row sits at the bottom until admission,
|
|
102
102
|
# when whoever admits it may move it home.
|
|
103
103
|
closing = block.rindex(/^\s*end\s*$/)
|
|
@@ -119,7 +119,7 @@ module Hecks
|
|
|
119
119
|
next line unless member_row?(line, word, context)
|
|
120
120
|
|
|
121
121
|
stripped = line.sub(/,\s*status: "[^"]*"/, "")
|
|
122
|
-
# Admitted is the default and stays
|
|
122
|
+
# Admitted is the default and stays unspelled — only a word
|
|
123
123
|
# entering or leaving the language carries its status.
|
|
124
124
|
to == "admitted" ? stripped : stripped.sub(/\n\z/, %(, status: "#{to}"\n))
|
|
125
125
|
end.join
|
|
@@ -219,10 +219,10 @@ module Hecks
|
|
|
219
219
|
block
|
|
220
220
|
end
|
|
221
221
|
|
|
222
|
-
# From `value_object "KeywordSeed"` to
|
|
222
|
+
# From `value_object "KeywordSeed"` to its own closing `end` — `member`
|
|
223
223
|
# rows sit bare now (S3, ADR 0025 — the `one_of do ... end` wrapper
|
|
224
224
|
# is gone), so the first bare `end` line after the opener already
|
|
225
|
-
#
|
|
225
|
+
# is the value object's own, the same fact the original one_of-
|
|
226
226
|
# nested version of this method leaned on (nothing else nested
|
|
227
227
|
# inside it either, before or after).
|
|
228
228
|
def keyword_blocks(source) = seed_blocks(source, "KeywordSeed")
|
|
@@ -230,7 +230,7 @@ module Hecks
|
|
|
230
230
|
|
|
231
231
|
# ── the Argument rows — a word's own arguments, at last with tooling
|
|
232
232
|
# of their own rather than the rename-only cascade above. A word may
|
|
233
|
-
# carry
|
|
233
|
+
# carry several argument rows (one per position, one per named
|
|
234
234
|
# kwarg), so identity here is the full (keyword, context, at, named)
|
|
235
235
|
# tuple, not the two-field key a Keyword row answers to.
|
|
236
236
|
|
|
@@ -250,7 +250,7 @@ module Hecks
|
|
|
250
250
|
end
|
|
251
251
|
end
|
|
252
252
|
|
|
253
|
-
# `pairs_shape` — for a `pairs` argument that fills
|
|
253
|
+
# `pairs_shape` — for a `pairs` argument that fills one field with a
|
|
254
254
|
# whole key/value list rather than naming a field per pair (the
|
|
255
255
|
# shape `Handler.dispatch`'s own `with:` already carries). Without
|
|
256
256
|
# it, `spec/syntax_conformance_spec.rb` reads a pairs argument
|
|
@@ -315,7 +315,7 @@ module Hecks
|
|
|
315
315
|
|
|
316
316
|
def argument_identity(row) = [row[:keyword], row[:context], row[:at], row[:named]]
|
|
317
317
|
|
|
318
|
-
# The rename cascade,
|
|
318
|
+
# The rename cascade, row-aware — only the rows that actually belong
|
|
319
319
|
# to the renamed word, spelling updated in place, rather than a
|
|
320
320
|
# blind `gsub` on every `keyword: "word",` substring in the file
|
|
321
321
|
# (which a coincidentally-matching row elsewhere could have
|
|
@@ -337,7 +337,7 @@ module Hecks
|
|
|
337
337
|
end
|
|
338
338
|
end
|
|
339
339
|
|
|
340
|
-
# From `value_object "ArgumentSeed"` to
|
|
340
|
+
# From `value_object "ArgumentSeed"` to its own closing `end` — see
|
|
341
341
|
# `keyword_block`'s own comment for why the first bare `end` after
|
|
342
342
|
# the opener is already the right one, now that `member` rows sit
|
|
343
343
|
# bare (S3, ADR 0025).
|
data/lib/hecks/grammar.rb
CHANGED
|
@@ -7,12 +7,12 @@ require_relative "../hecks"
|
|
|
7
7
|
|
|
8
8
|
module Hecks
|
|
9
9
|
# The sublanguage grammar domains (grammar/*.bluebook) and the one boot
|
|
10
|
-
# path for reading them as
|
|
10
|
+
# path for reading them as data — the expression chapter replayed
|
|
11
11
|
# through its own admission ledger, so anything derived from it (the
|
|
12
12
|
# operator projections, the conformance specs) reads the set that
|
|
13
13
|
# actually survived the Admit gates, never a hand-copied list.
|
|
14
14
|
#
|
|
15
|
-
# Booted on
|
|
15
|
+
# Booted on call, never at require: the Prism adapter normalises every
|
|
16
16
|
# predicate through CanonicalForm while a bluebook loads, so the
|
|
17
17
|
# expression machinery cannot boot the chapter that configures it —
|
|
18
18
|
# this module exists precisely so generators and specs boot it in a
|
|
@@ -42,7 +42,7 @@ module Hecks
|
|
|
42
42
|
JSON.parse(File.read(LEDGER)).fetch("steps").each do |step|
|
|
43
43
|
args = symbolize(step.fetch("args"))
|
|
44
44
|
begin
|
|
45
|
-
dispatcher.
|
|
45
|
+
dispatcher.dispatch_flat(step.fetch("verb"), args)
|
|
46
46
|
rescue *Runtime::DOMAIN_REFUSALS => e
|
|
47
47
|
raise Runtime::WiringError,
|
|
48
48
|
"the admission ledger refused at #{step['verb']} #{step['args']} — #{e.message}"
|
|
@@ -77,15 +77,15 @@ module Hecks
|
|
|
77
77
|
registry.repository("Expression", aggregate).all
|
|
78
78
|
end
|
|
79
79
|
|
|
80
|
-
#
|
|
80
|
+
# The operators the language stands on. Every guard and invariant in
|
|
81
81
|
# the language's own chapters — the meta-domain (Bluebook, World) and
|
|
82
82
|
# the grammar chapters beside this file — evaluates through the very
|
|
83
83
|
# operator table the ledger admits. An operator one of those
|
|
84
|
-
# predicates uses is
|
|
84
|
+
# predicates uses is self-bearing: retire it and the language can no
|
|
85
85
|
# longer read its own rules — found the hard way, as a projection
|
|
86
86
|
# missing `!=` that could not boot the chapter to fix itself. This
|
|
87
87
|
# derives the set, with a usage site per operator, so the generator
|
|
88
|
-
# and the conformance spec can refuse the retirement
|
|
88
|
+
# and the conformance spec can refuse the retirement by name instead
|
|
89
89
|
# of wedging.
|
|
90
90
|
def self_bearing_operators
|
|
91
91
|
sites = Hash.new { |h, k| h[k] = [] }
|
|
@@ -146,7 +146,7 @@ module Hecks
|
|
|
146
146
|
walk_operators(node, evaluator).uniq
|
|
147
147
|
end
|
|
148
148
|
|
|
149
|
-
# A recursive descent over a
|
|
149
|
+
# A recursive descent over a closed, declared set of AST node types
|
|
150
150
|
# (Evaluator's boolean/compare/include nodes, Resolver's arithmetic
|
|
151
151
|
# nodes, and the generic Struct fallback) — each branch does the
|
|
152
152
|
# same one thing (name the node's own operator, recurse into its
|