hecks 1.2.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 +60 -2
- 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/postgres_era.adapter +5 -0
- 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 +145 -0
- data/lib/hecks/behaviors/dsl.rb +2 -2
- data/lib/hecks/behaviors/expectations.rb +51 -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 +27 -3
- 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 +122 -86
- 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 +301 -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 +183 -20
- 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 +361 -0
- 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 +124 -0
- data/lib/hecks/fuzzing/form_census.rb +199 -0
- 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 +226 -38
- data/lib/hecks/fuzzing/nondeterministic.rb +67 -0
- data/lib/hecks/fuzzing/persistence_parity.rb +161 -0
- data/lib/hecks/fuzzing/properties/corrections.rb +100 -0
- data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +321 -41
- data/lib/hecks/fuzzing/properties/guards.rb +129 -26
- data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +32 -32
- data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +31 -11
- data/lib/hecks/fuzzing/properties/outbox.rb +142 -0
- data/lib/hecks/fuzzing/properties/querying.rb +17 -17
- data/lib/hecks/fuzzing/properties.rb +84 -39
- data/lib/hecks/fuzzing/qa_settings.rb +152 -0
- data/lib/hecks/fuzzing/replay.rb +224 -114
- data/lib/hecks/fuzzing/rotation_priority.rb +94 -0
- data/lib/hecks/fuzzing/rust_gap_manifest.rb +113 -0
- data/lib/hecks/fuzzing/self_consistency.rb +676 -0
- data/lib/hecks/fuzzing/sequence_generator/adversary.rb +526 -0
- data/lib/hecks/fuzzing/sequence_generator/catalog.rb +101 -30
- data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +55 -8
- data/lib/hecks/fuzzing/sequence_generator/picker.rb +31 -11
- data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +123 -32
- data/lib/hecks/fuzzing/sequence_generator.rb +152 -31
- data/lib/hecks/fuzzing/shrinker.rb +197 -0
- data/lib/hecks/fuzzing/structural_skips.rb +39 -0
- data/lib/hecks/fuzzing/sweep_depth.rb +53 -0
- data/lib/hecks/fuzzing/target_capabilities.rb +180 -0
- data/lib/hecks/fuzzing/value_generator.rb +66 -14
- data/lib/hecks/fuzzing.rb +11 -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 +365 -15
- data/lib/hecks/language/oidc.json +5 -0
- data/lib/hecks/literal.rb +9 -9
- data/lib/hecks/naming.rb +89 -21
- 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 +160 -58
- 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 +44 -6
- 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 +338 -83
- 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 +29 -6
- 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 +250 -0
- data/lib/hecks/projections/glossary/markdown.rb +105 -0
- data/lib/hecks/projections/glossary/mermaid.rb +110 -0
- data/lib/hecks/projections/glossary/page.css +271 -0
- data/lib/hecks/projections/glossary/page.js +72 -0
- data/lib/hecks/projections/glossary/sections.rb +17 -0
- data/lib/hecks/projections/glossary/sentences.rb +205 -0
- data/lib/hecks/projections/glossary.rb +214 -286
- 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 +15 -22
- 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 +132 -24
- 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 +64 -67
- 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 +94 -19
- 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 +282 -46
- data/lib/hecks/runtime/entity_interpreter.rb +160 -69
- 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 +111 -56
- data/lib/hecks/runtime/reaction_invocation.rb +76 -9
- 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 -112
- 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 +56 -27
- data/lib/hecks/runtime/remote_dispatcher.rb +38 -23
- data/lib/hecks/runtime/routing.rb +10 -88
- data/lib/hecks/runtime/saga_interpreter/correlation.rb +17 -17
- data/lib/hecks/runtime/saga_interpreter.rb +93 -56
- 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 +379 -226
- data/lib/hecks/runtime/value/entity_list_coercion.rb +248 -0
- data/lib/hecks/runtime/value.rb +28 -23
- 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 +207 -5
- 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 +43 -2
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
module Hecks
|
|
2
|
+
module Fuzzing
|
|
3
|
+
# What a sweep target can actually be checked for, read off the
|
|
4
|
+
# filesystem — never off a stored list.
|
|
5
|
+
#
|
|
6
|
+
# `bin/qa_sweep` used to decide its one comparison mode inline: "is
|
|
7
|
+
# there a Cargo feature named after this directory? then
|
|
8
|
+
# `:differential`, else `:ruby_only`", and a separate hand-typed abort
|
|
9
|
+
# for `--persistence-parity` ("does any .hecksagon bind PostgresEra?").
|
|
10
|
+
# Every further mode the practice adds (era boundary, concurrency, a
|
|
11
|
+
# WASM front) would have grown one more inline `if`, each one a
|
|
12
|
+
# policy decision hiding in a script. This module is those decisions
|
|
13
|
+
# as data: `MODE_REQUIREMENTS` says which capabilities each mode
|
|
14
|
+
# needs, `infer` says which capabilities a target's own directory
|
|
15
|
+
# actually has, and `resolve` is the one rule that joins them —
|
|
16
|
+
# `modes_to_run = enabled ∩ eligible`.
|
|
17
|
+
#
|
|
18
|
+
# Inference decides; a stored list only records. `Target.capabilities`
|
|
19
|
+
# (qa/bluebook/quality_control.bluebook, once PR-1's era lands) is
|
|
20
|
+
# written by the runner from exactly this inference at release time
|
|
21
|
+
# so `Target.EligibleFor(mode)` can audit the rotation from the
|
|
22
|
+
# ledger alone — but the runner re-infers every sweep, because a
|
|
23
|
+
# stored list that lags yesterday's Cargo feature is precisely the
|
|
24
|
+
# "quiet divergence" (the chapter's own opening comment) this whole
|
|
25
|
+
# practice exists to hunt. Nothing here ever reads the ledger.
|
|
26
|
+
#
|
|
27
|
+
# Every regex is one the harness already owned, moved here rather than
|
|
28
|
+
# re-derived, and each one's provenance is named beside it so a
|
|
29
|
+
# future edit to the original site is a visible drift, not a silent
|
|
30
|
+
# one: the Cargo feature line (`RustConformanceHelpers#build_rust_for`),
|
|
31
|
+
# the PostgresEra binding (`bin/qa_sweep`'s old `POSTGRES_ERA_BINDING`),
|
|
32
|
+
# the translations glob (`IsolatedBoot#strip_translations!`).
|
|
33
|
+
module TargetCapabilities
|
|
34
|
+
module_function
|
|
35
|
+
|
|
36
|
+
# `RustConformanceHelpers#build_rust_for`'s own test, scoped to the
|
|
37
|
+
# `[features]` table the way `bin/project_rust`'s Cargo sync scopes
|
|
38
|
+
# its own lookup (a `[package] name = "rust"` line must never read
|
|
39
|
+
# as a feature named `rust`).
|
|
40
|
+
FEATURES_TABLE = /^\[features\](?:\n(?!\[).*)*$/
|
|
41
|
+
|
|
42
|
+
# `bin/qa_sweep`'s former `POSTGRES_ERA_BINDING` — both spellings
|
|
43
|
+
# `IsolatedBoot#rewrite_bindings!` has to catch: aggregate-scoped
|
|
44
|
+
# (`Directory::Member.persisted_by("PostgresEra")`) and the bare
|
|
45
|
+
# domain-level default (`persisted_by "PostgresEra"`).
|
|
46
|
+
POSTGRES_ERA_BINDING = /persisted_by\s*\(?\s*"PostgresEra"/
|
|
47
|
+
|
|
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*"([^"]+)"/
|
|
55
|
+
|
|
56
|
+
# A command-level `role "..."` — the only construct
|
|
57
|
+
# `refuse_role_mismatch` ever has anything to check a caller against.
|
|
58
|
+
ROLE_GATED = /^\s*role\s+"/
|
|
59
|
+
|
|
60
|
+
# `authorize :vault_access, tenant: :branch_code`
|
|
61
|
+
# (examples/banking/bluebook/safe_deposit_boxes.bluebook) — the one
|
|
62
|
+
# `tenant:` spelling the corpus has.
|
|
63
|
+
TENANT_SCOPED = /\btenant:/
|
|
64
|
+
|
|
65
|
+
PROCESS_MANAGER = /^\s*process_manager\s+"/
|
|
66
|
+
|
|
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
|
|
69
|
+
# domain boots under Memory, so Ruby-only properties and the
|
|
70
|
+
# self-consistency pass are always answerable. `ruby_only` is listed
|
|
71
|
+
# requirement-free on purpose and then excluded by `resolve` whenever
|
|
72
|
+
# `differential` resolved too: they are the same seat, and a compiled
|
|
73
|
+
# Rust binary is strictly the better occupant (item 1 of the
|
|
74
|
+
# detection plan folded the Ruby-only property battery into the
|
|
75
|
+
# differential seat, so nothing is lost by the exclusion).
|
|
76
|
+
#
|
|
77
|
+
# The four `false`-by-default modes in `QualityControlDials::MODES`
|
|
78
|
+
# (`adapter_parity_postgres`, `era_boundary`, `concurrency`,
|
|
79
|
+
# `wasm_front`) are named here with their requirements even though
|
|
80
|
+
# nothing runs them yet — so `resolved modes:` can already say, per
|
|
81
|
+
# target, which of them would be eligible the day a human flips the
|
|
82
|
+
# dial, and so flipping it is a one-line data change rather than a
|
|
83
|
+
# code change plus a data change.
|
|
84
|
+
MODE_REQUIREMENTS = {
|
|
85
|
+
differential: %w[rust],
|
|
86
|
+
ruby_only: [],
|
|
87
|
+
self_consistency: [],
|
|
88
|
+
properties_in_differential: %w[rust],
|
|
89
|
+
structural_skip_report: %w[rust],
|
|
90
|
+
adapter_parity_sqlite: %w[sqlite],
|
|
91
|
+
persistence_parity: %w[postgres_era],
|
|
92
|
+
adapter_parity_postgres: %w[postgres_era],
|
|
93
|
+
era_boundary: %w[translations postgres_era],
|
|
94
|
+
concurrency: %w[postgres_era],
|
|
95
|
+
wasm_front: %w[rust]
|
|
96
|
+
}.freeze
|
|
97
|
+
|
|
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
|
|
116
|
+
# an extra check folded into the ordinary per-seed loop —
|
|
117
|
+
# `bin/qa_sweep` runs these only when asked by name (`--modes
|
|
118
|
+
# persistence_parity`, or its older alias `--persistence-parity`) or
|
|
119
|
+
# as `--all`'s own second wave, never silently inside a plain
|
|
120
|
+
# single-target sweep (the seed cap `PERSISTENCE_PARITY_SEED_CAP`
|
|
121
|
+
# exists because that pass pays for real Postgres I/O per dispatch).
|
|
122
|
+
DEFERRED_MODES = %i[persistence_parity adapter_parity_postgres era_boundary concurrency].freeze
|
|
123
|
+
|
|
124
|
+
# Sorted, plain strings — comma-joined by the runner into the
|
|
125
|
+
# `Target.Release(capabilities:)` value object and printed verbatim
|
|
126
|
+
# on the `resolved modes:` line, so the same spelling is what a
|
|
127
|
+
# human reads, what `--all` parses back, and what the ledger stores.
|
|
128
|
+
def infer(domain_path, rust_dir: File.expand_path("../../../rust", __dir__))
|
|
129
|
+
capabilities = %w[sqlite]
|
|
130
|
+
capabilities << "rust" if rust_feature?(domain_path, rust_dir)
|
|
131
|
+
capabilities << "postgres_era" if any_file?(domain_path, "*.hecksagon", POSTGRES_ERA_BINDING)
|
|
132
|
+
capabilities << "translations" if Dir.glob(File.join(domain_path, "**", "translations", "*.bluebook")).any?
|
|
133
|
+
capabilities << "governance" if authorization_attached?(domain_path)
|
|
134
|
+
capabilities << "role_gated" if any_file?(domain_path, "*.bluebook", ROLE_GATED)
|
|
135
|
+
capabilities << "tenant" if any_file?(domain_path, "*.bluebook", TENANT_SCOPED)
|
|
136
|
+
capabilities << "sagas" if any_file?(domain_path, "*.bluebook", PROCESS_MANAGER)
|
|
137
|
+
capabilities.sort
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def eligible?(mode, capabilities)
|
|
141
|
+
required = MODE_REQUIREMENTS.fetch(mode.to_sym) { raise ArgumentError, "unknown sweep mode #{mode.inspect}" }
|
|
142
|
+
(required - capabilities).empty?
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# The one rule. `enabled` is whatever the dial (or `--modes`) turned
|
|
146
|
+
# on, in the dial's own declaration order — that order is preserved
|
|
147
|
+
# so the printed line reads the same way the dial does. Then the
|
|
148
|
+
# single exclusion named on `MODE_REQUIREMENTS`.
|
|
149
|
+
def resolve(enabled, capabilities)
|
|
150
|
+
resolved = enabled.map(&:to_sym).select { |mode| eligible?(mode, capabilities) }
|
|
151
|
+
resolved.delete(:ruby_only) if resolved.include?(:differential)
|
|
152
|
+
resolved
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def rust_feature?(domain_path, rust_dir)
|
|
156
|
+
cargo_toml = File.join(rust_dir, "Cargo.toml")
|
|
157
|
+
return false unless File.file?(cargo_toml)
|
|
158
|
+
|
|
159
|
+
feature = File.basename(domain_path).downcase
|
|
160
|
+
features = File.read(cargo_toml)[FEATURES_TABLE] || ""
|
|
161
|
+
features.match?(/^#{Regexp.escape(feature)}\s*=\s*\[\]/)
|
|
162
|
+
end
|
|
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
|
+
|
|
175
|
+
def any_file?(domain_path, glob, pattern)
|
|
176
|
+
Dir.glob(File.join(domain_path, "**", glob)).any? { |path| File.read(path).match?(pattern) }
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
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,15 +44,60 @@ 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
|
+
# BUG#35 (QualityControl QA ledger, `lease-clock-json-precision`) —
|
|
57
|
+
# a Bignum edge case (`2**100`, above) landing on an Integer-typed
|
|
58
|
+
# clock or count reading fires the already-catalogued `Json::Num`/
|
|
59
|
+
# f64 precision-loss class (`rust/src/kernel/json.rs`'s
|
|
60
|
+
# `parse_number` parses every number through `s.parse::<f64>()`
|
|
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 new
|
|
63
|
+
# site every time a new clock/count-shaped field is authored,
|
|
64
|
+
# without adding any new coverage: both engines already refuse
|
|
65
|
+
# (`TypeMismatch`, out of `i64` range either way) for this shape,
|
|
66
|
+
# `tenant_ledger`'s own NOTES.md already logged the identical class
|
|
67
|
+
# on a stored money attribute, and `spec/support/
|
|
68
|
+
# rust_conformance_helpers.rb`'s own `reduce_to_wire_precision`
|
|
69
|
+
# already documents and normalizes past the wire format's own loss
|
|
70
|
+
# for a query's echoed args. An exact-integer Rust JSON
|
|
71
|
+
# deserialization path was considered and rejected as this
|
|
72
|
+
# generator's own fix instead: `Json::Num(f64)` is pattern-matched
|
|
73
|
+
# throughout `rust/src/kernel` (query_comparators.rs,
|
|
74
|
+
# query_ordering.rs, read_model.rs, named_query.rs, this kernel's
|
|
75
|
+
# own `Fielded` impl, arithmetic overflow checks) and emitted by
|
|
76
|
+
# codegen (`rust/project/reactions.rb`, `rust/codegen/src/
|
|
77
|
+
# reactions.rs`) — adding a second, exact-integer numeric variant
|
|
78
|
+
# would mean auditing and updating every one of those sites, a
|
|
79
|
+
# refactor of the JSON layer itself, not a one-domain fix (exactly
|
|
80
|
+
# the ledger's own stated concern: "risks affecting every other
|
|
81
|
+
# domain's large-integer handling"). So instead, per the ledger's
|
|
82
|
+
# own sanctioned fallback: an Integer-typed field whose name reads
|
|
83
|
+
# as a clock or a count is capped to a narrower, still-real
|
|
84
|
+
# edge-case pool below (still exercises the ordinary i32
|
|
85
|
+
# boundaries, just never a value past f64's own 2**53 exact-
|
|
86
|
+
# integer ceiling) — every other Integer-typed field (a money
|
|
87
|
+
# amount, an identity sequence, anything not clock/count-shaped)
|
|
88
|
+
# still draws from the full `INTEGER_EDGE_CASES` pool above,
|
|
89
|
+
# unchanged.
|
|
90
|
+
SAFE_INTEGER_EDGE_CASES = [0, -1, 2_147_483_647, -2_147_483_648].freeze
|
|
91
|
+
# Matched against the value object's own declared name plus the
|
|
92
|
+
# attribute's own name (`value_for`'s `"#{context} #{attribute.
|
|
93
|
+
# name}"`, e.g. "LeaseInstant value", "RetryCount value") — a
|
|
94
|
+
# clock reading ("instant"/"clock"/"expir(es/y)"/"ttl"/"now"/
|
|
95
|
+
# "timestamp"/"epoch") or a quantity capped by a policy ("count").
|
|
96
|
+
# Deliberately narrow: matches the ledger's own "clock/count"
|
|
97
|
+
# wording exactly, not every plausibly-large-looking name (an
|
|
98
|
+
# "amount"/"cents"/"sequence" field is not clock/count-shaped and
|
|
99
|
+
# keeps the full Bignum edge-case pool).
|
|
100
|
+
CLOCK_OR_COUNT_NAME_PATTERN = /clock|instant|expir|ttl|\bnow\b|timestamp|epoch|count/i
|
|
56
101
|
# NaN and +/-Infinity — the real find (see `spec/runtime/
|
|
57
102
|
# numeric_boundary_spec.rb`): `Value::Coercion#check_numeric_fields`
|
|
58
103
|
# used to let all three sail through untyped-checked (each really
|
|
@@ -60,7 +105,7 @@ module Hecks
|
|
|
60
105
|
# (raw `ArgumentError`, not a domain refusal) or `JSON.generate`
|
|
61
106
|
# (`JSON::GeneratorError`, also not a domain refusal) — both fixed
|
|
62
107
|
# at the source now, so these are safe to generate. -0.0 is
|
|
63
|
-
# deliberately included too even though it was
|
|
108
|
+
# deliberately included too even though it was already safe
|
|
64
109
|
# (finite, round-trips through JSON as `-0.0` cleanly) — a signed
|
|
65
110
|
# zero is exactly the kind of boundary a hand-written corpus never
|
|
66
111
|
# happens to type, and the fuzzer existing to cover it is the
|
|
@@ -112,10 +157,10 @@ module Hecks
|
|
|
112
157
|
end
|
|
113
158
|
end
|
|
114
159
|
|
|
115
|
-
# `fields.size == 1` — the
|
|
160
|
+
# `fields.size == 1` — the same test `Behaviour::ValueObject#
|
|
116
161
|
# sole_attribute` names: a genuinely single-field value object,
|
|
117
162
|
# not merely "this particular closed-set member happened to pick
|
|
118
|
-
# one field." Unwrapping
|
|
163
|
+
# one field." Unwrapping only here, not in `invalid_member`
|
|
119
164
|
# above — a deliberately-wrong combination stays a Hash so its
|
|
120
165
|
# own wrongness is what gets exercised, not a second, unrelated
|
|
121
166
|
# shape question.
|
|
@@ -133,10 +178,10 @@ module Hecks
|
|
|
133
178
|
end
|
|
134
179
|
end
|
|
135
180
|
|
|
136
|
-
#
|
|
181
|
+
# The ID itself. This minted `{"value" => id}` back when a reference was
|
|
137
182
|
# stored wrapped ; the payload gate refuses that shape now, so a fuzzer
|
|
138
183
|
# still emitting it would have every generated reference refused and
|
|
139
|
-
# the
|
|
184
|
+
# the silent guard would report the fuzzer broken rather than
|
|
140
185
|
# the runtime.
|
|
141
186
|
def reference_value(attribute, random:, known_ids:)
|
|
142
187
|
pool = known_ids[attribute.type.target_name.to_s] || []
|
|
@@ -148,7 +193,7 @@ module Hecks
|
|
|
148
193
|
def primitive(type_name, random:, name: nil)
|
|
149
194
|
case type_name
|
|
150
195
|
when "String" then string_value(random, name: name)
|
|
151
|
-
when "Integer" then integer_value(random)
|
|
196
|
+
when "Integer" then integer_value(random, name: name)
|
|
152
197
|
when "Float" then float_value(random)
|
|
153
198
|
when "TrueClass", "FalseClass" then random.rand(2).zero?
|
|
154
199
|
else raise ArgumentError, "ValueGenerator does not know primitive type #{type_name.inspect}"
|
|
@@ -172,19 +217,26 @@ module Hecks
|
|
|
172
217
|
# sequence that can never get past one never reaches the state a deeper
|
|
173
218
|
# bug would need. Zero and negative are still real, reachable outcomes —
|
|
174
219
|
# via the edge-case pool, deliberately, not by starving them entirely.
|
|
175
|
-
def integer_value(random)
|
|
176
|
-
|
|
220
|
+
def integer_value(random, name: nil)
|
|
221
|
+
if random.rand < EDGE_CASE_PROBABILITY
|
|
222
|
+
pool = clock_or_count_shaped?(name) ? SAFE_INTEGER_EDGE_CASES : INTEGER_EDGE_CASES
|
|
223
|
+
return pool.sample(random: random)
|
|
224
|
+
end
|
|
177
225
|
|
|
178
226
|
random.rand(1..1000)
|
|
179
227
|
end
|
|
180
228
|
|
|
229
|
+
def clock_or_count_shaped?(name)
|
|
230
|
+
name.to_s.match?(CLOCK_OR_COUNT_NAME_PATTERN)
|
|
231
|
+
end
|
|
232
|
+
|
|
181
233
|
def float_value(random)
|
|
182
234
|
return FLOAT_EDGE_CASES.sample(random: random) if random.rand < EDGE_CASE_PROBABILITY
|
|
183
235
|
|
|
184
236
|
random.rand(0.01..1000.0).round(2)
|
|
185
237
|
end
|
|
186
238
|
|
|
187
|
-
# The bare scalar a generated
|
|
239
|
+
# The bare scalar a generated identity value stands for, for recording into
|
|
188
240
|
# `known_ids`. An identity is declared as a value object, so this opens one ;
|
|
189
241
|
# a reference pointing at this record is already that scalar and needs no
|
|
190
242
|
# opening at all. The two used to be the same reading and are not any more.
|
data/lib/hecks/fuzzing.rb
CHANGED
|
@@ -8,5 +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"
|
|
16
|
+
require_relative "fuzzing/rotation_priority"
|
|
17
|
+
require_relative "fuzzing/sweep_depth"
|
|
18
|
+
require_relative "fuzzing/persistence_parity"
|
|
19
|
+
require_relative "fuzzing/form_census"
|
|
20
|
+
require_relative "fuzzing/target_capabilities"
|
|
21
|
+
require_relative "fuzzing/rust_gap_manifest"
|
|
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
|
data/lib/hecks/ir.rb
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
module Hecks
|
|
2
|
-
#
|
|
2
|
+
# What a construct emits, declared rather than written out.
|
|
3
3
|
#
|
|
4
|
-
# `IR` is a thing this framework
|
|
4
|
+
# `IR` is a thing this framework produces, not a thing its model is.
|
|
5
5
|
# The language self-hosts, and its own bluebook declares aggregates
|
|
6
6
|
# named `Bluebook`, `Aggregate`, `Command`, `Entity`, `ValueObject`,
|
|
7
7
|
# `Policy`, `ReadModel` — there is no `IR` aggregate anywhere in the
|
|
8
8
|
# grammar. Where the grammar's own vision line does say IR it means the
|
|
9
|
-
# emission: "the IR it
|
|
10
|
-
#
|
|
9
|
+
# emission: "the IR it stores must equal the IR the DSL builder
|
|
10
|
+
# produces". `IR_VERSION` says the same thing structurally — a version
|
|
11
11
|
# stamped on `to_h`'s output rather than on the object is a version of
|
|
12
|
-
# the
|
|
12
|
+
# the emission, which only makes sense if the two are different things.
|
|
13
13
|
#
|
|
14
|
-
# So emitting IR is a
|
|
14
|
+
# So emitting IR is a capability a construct has, and this is that
|
|
15
15
|
# capability: `include Hecks::IR` and declare the shape once.
|
|
16
|
-
# The model it emits
|
|
16
|
+
# The model it emits from is `Hecks::Bluebook` — a chapter class
|
|
17
17
|
# nesting everything a chapter declares. It is deliberately not named
|
|
18
18
|
# after this, its own output.
|
|
19
19
|
#
|
|
@@ -36,18 +36,18 @@ module Hecks
|
|
|
36
36
|
# canonical_form: -> { CanonicalForm.table } # anything else
|
|
37
37
|
# )
|
|
38
38
|
#
|
|
39
|
-
#
|
|
39
|
+
# Key order is the declaration order, and that is load-bearing rather
|
|
40
40
|
# than cosmetic: `spec/golden/ir/*.json` pins the emitted form exactly,
|
|
41
41
|
# so a reordered declaration is a changed artifact and the golden specs
|
|
42
42
|
# will say so.
|
|
43
43
|
module IR
|
|
44
|
-
#
|
|
44
|
+
# The two shapes a construct comes in, and why this module has two
|
|
45
45
|
# doors instead of hiding the difference.
|
|
46
46
|
#
|
|
47
47
|
# `Bluebook`/`Aggregate`/`Policy`/`ReadModel` are ordinary objects —
|
|
48
48
|
# metadata records, one instance per declaration. `Command`/`Entity`/
|
|
49
|
-
# `ValueObject` are anonymous
|
|
50
|
-
# `Command.declare`), because those three are referenced as
|
|
49
|
+
# `ValueObject` are anonymous classes (`Class.new(self)`, see
|
|
50
|
+
# `Command.declare`), because those three are referenced as types in
|
|
51
51
|
# a bluebook (`attribute :price, Money`) and a type has to be a real
|
|
52
52
|
# Ruby constant to be named.
|
|
53
53
|
#
|
|
@@ -87,7 +87,7 @@ module Hecks
|
|
|
87
87
|
def one(source) = One.new(source)
|
|
88
88
|
|
|
89
89
|
# Walks the superclass chain so a `Class.new(ValueObject)` — which
|
|
90
|
-
# is what every declared value object actually
|
|
90
|
+
# is what every declared value object actually is — inherits the
|
|
91
91
|
# shape its base declared, rather than each anonymous subclass
|
|
92
92
|
# having to redeclare it.
|
|
93
93
|
def ir_spec
|
|
@@ -112,7 +112,7 @@ module Hecks
|
|
|
112
112
|
private
|
|
113
113
|
|
|
114
114
|
# An instance reads its class's declaration; a class-shaped
|
|
115
|
-
# construct
|
|
115
|
+
# construct is the declaration holder.
|
|
116
116
|
def ir_spec_for(construct)
|
|
117
117
|
return construct.ir_spec if construct.respond_to?(:ir_spec)
|
|
118
118
|
|
|
@@ -73,6 +73,26 @@ Hecks.bluebook "Bluebook", version: "1" do
|
|
|
73
73
|
# chapters attach to nothing, and ABSENT IS NOT EMPTY, the same
|
|
74
74
|
# reading `version`/`formerly_known_as` above give.
|
|
75
75
|
attribute :attaches_to, list_of(AttachesToContext)
|
|
76
|
+
# A CAPABILITY THIS CHAPTER ANSWERS FOR OTHER DOMAINS — declared, so
|
|
77
|
+
# nothing downstream has to recognise the chapter BY NAME. One row
|
|
78
|
+
# per (capability, key, verb): `provides "authorization", grant:
|
|
79
|
+
# "RoleAssignment.Assign"` is the row (authorization, grant,
|
|
80
|
+
# RoleAssignment.Assign). The runtime's role check, boot's
|
|
81
|
+
# ungoverned-role refusal, the authorization adapter and the fuzzer
|
|
82
|
+
# all read the rows rather than the literal "Governance". Most
|
|
83
|
+
# chapters provide nothing, and ABSENT IS NOT EMPTY, the same reading
|
|
84
|
+
# `attaches_to` above gives.
|
|
85
|
+
attribute :provides, list_of(Provision)
|
|
86
|
+
|
|
87
|
+
value_object "Provision" do
|
|
88
|
+
attribute :capability, String
|
|
89
|
+
attribute :key, String
|
|
90
|
+
attribute :verb, String
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
value_object "ProvisionText" do
|
|
94
|
+
attribute :value, String
|
|
95
|
+
end
|
|
76
96
|
|
|
77
97
|
value_object "RuleText" do
|
|
78
98
|
# deliberately permissive : source_token and replacement are legitimately
|
|
@@ -159,6 +179,22 @@ Hecks.bluebook "Bluebook", version: "1" do
|
|
|
159
179
|
emits ChapterAttached
|
|
160
180
|
end
|
|
161
181
|
|
|
182
|
+
# ONE ROW OF ONE CAPABILITY — `provides "authorization", assignments:
|
|
183
|
+
# ..., grant: ..., transitions: ...` offers three, one per key, in the
|
|
184
|
+
# order they were written.
|
|
185
|
+
command "Provide" do
|
|
186
|
+
role "Language"
|
|
187
|
+
goal "Declare one verb this chapter answers a capability with"
|
|
188
|
+
|
|
189
|
+
attribute :capability, ProvisionText
|
|
190
|
+
attribute :key, ProvisionText
|
|
191
|
+
attribute :verb, ProvisionText
|
|
192
|
+
|
|
193
|
+
sets :provides, append: { capability: :capability, key: :key, verb: :verb }
|
|
194
|
+
|
|
195
|
+
emits CapabilityProvided
|
|
196
|
+
end
|
|
197
|
+
|
|
162
198
|
# THE WAY BACK.
|
|
163
199
|
#
|
|
164
200
|
# Until these existed the meta-domain was write-only: twelve categories, 34
|
|
@@ -214,6 +250,7 @@ Hecks.bluebook "Bluebook", version: "1" do
|
|
|
214
250
|
member word: "vision", context: "Bluebook", body: "none", inner: "", opens: "", fills: "vision"
|
|
215
251
|
member word: "formerly_known_as", context: "Bluebook", body: "none", inner: "", opens: "", fills: "formerly_known_as"
|
|
216
252
|
member word: "attaches_to", context: "Bluebook", body: "none", inner: "", opens: "", fills: "attaches_to", calls: "attaches_to_impl"
|
|
253
|
+
member word: "provides", context: "Bluebook", body: "none", inner: "", opens: "", fills: "provides", calls: "provides_impl"
|
|
217
254
|
member word: "core", context: "Bluebook", body: "none", inner: "", opens: "", fills: "classification"
|
|
218
255
|
member word: "supporting", context: "Bluebook", body: "none", inner: "", opens: "", fills: "classification"
|
|
219
256
|
member word: "generic", context: "Bluebook", body: "none", inner: "", opens: "", fills: "classification"
|
|
@@ -246,6 +283,10 @@ Hecks.bluebook "Bluebook", version: "1" do
|
|
|
246
283
|
member keyword: "vision", context: "Bluebook", at: "1", named: "", kind: "text", required: "true", fills: "vision", coerce: "false"
|
|
247
284
|
member keyword: "formerly_known_as", context: "Bluebook", at: "1", named: "", kind: "text", required: "true", fills: "formerly_known_as"
|
|
248
285
|
member keyword: "attaches_to", context: "Bluebook", at: "1", named: "", kind: "text", required: "true", fills: "attaches_to", variadic: "true"
|
|
286
|
+
member keyword: "provides", context: "Bluebook", at: "1", named: "", kind: "text", required: "true", fills: "provides"
|
|
287
|
+
member keyword: "provides", context: "Bluebook", at: "", named: "assignments", kind: "text", required: "false", fills: "provides"
|
|
288
|
+
member keyword: "provides", context: "Bluebook", at: "", named: "grant", kind: "text", required: "false", fills: "provides"
|
|
289
|
+
member keyword: "provides", context: "Bluebook", at: "", named: "transitions", kind: "text", required: "false", fills: "provides"
|
|
249
290
|
member keyword: "aggregate", context: "Bluebook", at: "1", named: "", kind: "text", required: "true", fills: "name"
|
|
250
291
|
member keyword: "read_model", context: "Bluebook", at: "1", named: "", kind: "text", required: "true", fills: "name"
|
|
251
292
|
member keyword: "policy", context: "Bluebook", at: "1", named: "", kind: "text", required: "true", fills: "name"
|