hecks 1.3.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/hecks/adapters/driven/claude_code.rb +72 -7
- data/lib/hecks/adapters/driven/d1.rb +187 -23
- data/lib/hecks/adapters/driven/folder.rb +83 -10
- data/lib/hecks/adapters/driven/google_authentication.rb +33 -12
- data/lib/hecks/adapters/driven/governance_authorization.rb +87 -18
- data/lib/hecks/adapters/driven/heki/journal.rb +13 -4
- data/lib/hecks/adapters/driven/heki/saga_store.rb +56 -10
- data/lib/hecks/adapters/driven/heki/snapshot.rb +1 -1
- data/lib/hecks/adapters/driven/heki.rb +106 -9
- data/lib/hecks/adapters/driven/identity_registry.rb +12 -2
- data/lib/hecks/adapters/driven/in_memory_ordering.rb +25 -3
- data/lib/hecks/adapters/driven/in_process_key_vault.adapter +3 -0
- data/lib/hecks/adapters/driven/in_process_key_vault.rb +53 -0
- data/lib/hecks/adapters/driven/lambda/client.rb +67 -14
- data/lib/hecks/adapters/driven/lambda.rb +82 -35
- data/lib/hecks/adapters/driven/local_storage.rb +83 -10
- data/lib/hecks/adapters/driven/memory.rb +205 -9
- data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +21 -1
- data/lib/hecks/adapters/driven/postgres/codec.rb +27 -11
- data/lib/hecks/adapters/driven/postgres/outbox.rb +40 -2
- data/lib/hecks/adapters/driven/postgres/reconnect.rb +23 -7
- data/lib/hecks/adapters/driven/postgres/schema_builder.rb +14 -14
- data/lib/hecks/adapters/driven/postgres.rb +175 -28
- data/lib/hecks/adapters/driven/prism.rb +50 -11
- data/lib/hecks/adapters/driven/secure_random_identity.rb +3 -0
- data/lib/hecks/adapters/driven/sql_query_builder.rb +34 -22
- data/lib/hecks/adapters/driven/sqlite/codec.rb +38 -10
- data/lib/hecks/adapters/driven/sqlite/projection.rb +60 -32
- data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +12 -12
- data/lib/hecks/adapters/driven/sqlite.rb +181 -21
- data/lib/hecks/adapters/driven/system_clock.rb +3 -0
- data/lib/hecks/adapters/driven/tenant_provisioner.adapter +3 -0
- data/lib/hecks/adapters/driven/tenant_provisioner.rb +66 -0
- data/lib/hecks/adapters/driven.rb +6 -4
- data/lib/hecks/adapters/driving/github_webhook.rb +31 -18
- data/lib/hecks/behaviors/dsl.rb +60 -2
- data/lib/hecks/behaviors/expectations.rb +190 -29
- data/lib/hecks/behaviors/ir.rb +12 -1
- data/lib/hecks/behaviors/rspec.rb +9 -1
- data/lib/hecks/behaviors/runner.rb +21 -2
- data/lib/hecks/behaviors.rb +9 -1
- data/lib/hecks/bluebook/aggregate.rb +43 -13
- data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +17 -10
- data/lib/hecks/bluebook/assembly/build.rb +26 -5
- data/lib/hecks/bluebook/assembly/contract.rb +98 -23
- data/lib/hecks/bluebook/assembly/contracts.rb +59 -52
- data/lib/hecks/bluebook/assembly/marks.rb +159 -30
- data/lib/hecks/bluebook/assembly/specializer.rb +38 -21
- data/lib/hecks/bluebook/assembly.rb +32 -14
- data/lib/hecks/bluebook/attribute.rb +26 -12
- data/lib/hecks/bluebook/behaviour/aggregate.rb +43 -11
- data/lib/hecks/bluebook/behaviour/attribute.rb +18 -5
- data/lib/hecks/bluebook/behaviour/chapter.rb +76 -5
- data/lib/hecks/bluebook/behaviour/command.rb +55 -25
- data/lib/hecks/bluebook/behaviour/domain_port.rb +27 -7
- data/lib/hecks/bluebook/behaviour/entity.rb +20 -8
- data/lib/hecks/bluebook/behaviour/hexagon.rb +30 -4
- data/lib/hecks/bluebook/behaviour/lifecycle.rb +27 -6
- data/lib/hecks/bluebook/behaviour/policy.rb +42 -17
- data/lib/hecks/bluebook/behaviour/process_manager.rb +39 -8
- data/lib/hecks/bluebook/behaviour/query.rb +6 -1
- data/lib/hecks/bluebook/behaviour/read_model.rb +29 -8
- data/lib/hecks/bluebook/behaviour/traits.rb +48 -12
- data/lib/hecks/bluebook/behaviour/value_object.rb +21 -9
- data/lib/hecks/bluebook/capabilities.rb +27 -0
- data/lib/hecks/bluebook/chapter.rb +51 -9
- data/lib/hecks/bluebook/command.rb +62 -17
- data/lib/hecks/bluebook/domain_port.rb +34 -9
- data/lib/hecks/bluebook/dsl/adapter_builder.rb +24 -0
- data/lib/hecks/bluebook/dsl/aggregate_builder/sealing.rb +49 -49
- data/lib/hecks/bluebook/dsl/aggregate_builder.rb +282 -123
- data/lib/hecks/bluebook/dsl/attribute_collector.rb +112 -75
- data/lib/hecks/bluebook/dsl/binding_proxy.rb +81 -2
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +486 -117
- data/lib/hecks/bluebook/dsl/bluebook_builder.rb +179 -47
- data/lib/hecks/bluebook/dsl/bootstrap_table.rb +116 -0
- data/lib/hecks/bluebook/dsl/command_builder.rb +284 -122
- data/lib/hecks/bluebook/dsl/const_shim.rb +46 -15
- data/lib/hecks/bluebook/dsl/domain_port_builder.rb +90 -25
- data/lib/hecks/bluebook/dsl/entity_builder.rb +191 -61
- data/lib/hecks/bluebook/dsl/generic_dispatch.rb +148 -132
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +130 -30
- data/lib/hecks/bluebook/dsl/identity_declaration.rb +38 -21
- data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +27 -4
- data/lib/hecks/bluebook/dsl/policy_builder.rb +86 -36
- data/lib/hecks/bluebook/dsl/port_builder.rb +38 -7
- data/lib/hecks/bluebook/dsl/port_operation_builder.rb +56 -22
- data/lib/hecks/bluebook/dsl/process_manager_builder.rb +111 -47
- data/lib/hecks/bluebook/dsl/query_builder.rb +37 -8
- data/lib/hecks/bluebook/dsl/read_model_builder.rb +127 -52
- data/lib/hecks/bluebook/dsl/rule_reference.rb +97 -43
- data/lib/hecks/bluebook/dsl/translation_builder.rb +150 -44
- data/lib/hecks/bluebook/dsl/value_object_builder.rb +68 -20
- data/lib/hecks/bluebook/dsl/word_gate.rb +59 -53
- data/lib/hecks/bluebook/dsl/world_builder.rb +51 -8
- data/lib/hecks/bluebook/entity.rb +40 -11
- data/lib/hecks/bluebook/expression/ast_json.rb +128 -36
- data/lib/hecks/bluebook/expression/ast_reader.rb +32 -3
- data/lib/hecks/bluebook/expression/canonical_form.rb +55 -16
- data/lib/hecks/bluebook/expression/evaluator.rb +221 -43
- data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +54 -18
- data/lib/hecks/bluebook/expression/resolver.rb +369 -128
- data/lib/hecks/bluebook/hexagon.rb +35 -1
- data/lib/hecks/bluebook/lifecycle.rb +12 -1
- data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +2 -1
- data/lib/hecks/bluebook/meta_validator/judge.rb +126 -108
- data/lib/hecks/bluebook/meta_validator/plan.rb +81 -46
- data/lib/hecks/bluebook/meta_validator/port_judge.rb +3 -2
- data/lib/hecks/bluebook/meta_validator/readings.rb +200 -50
- data/lib/hecks/bluebook/meta_validator/reconstruction.rb +68 -41
- data/lib/hecks/bluebook/meta_validator/shapes.rb +166 -21
- data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +286 -46
- data/lib/hecks/bluebook/meta_validator/translation_judge.rb +11 -10
- data/lib/hecks/bluebook/meta_validator/world_judge.rb +6 -5
- data/lib/hecks/bluebook/meta_validator.rb +235 -139
- data/lib/hecks/bluebook/model_check.rb +434 -104
- data/lib/hecks/bluebook/pattern_subset.rb +32 -10
- data/lib/hecks/bluebook/policy.rb +15 -13
- data/lib/hecks/bluebook/process_manager.rb +27 -14
- data/lib/hecks/bluebook/project_discovery.rb +5 -0
- data/lib/hecks/bluebook/project_loader.rb +40 -0
- data/lib/hecks/bluebook/project_register.rb +50 -6
- data/lib/hecks/bluebook/query.rb +31 -4
- data/lib/hecks/bluebook/read_model.rb +35 -15
- data/lib/hecks/bluebook/reference.rb +26 -13
- data/lib/hecks/bluebook/smoke_test.rb +46 -23
- data/lib/hecks/bluebook/synthesizer.rb +46 -12
- data/lib/hecks/bluebook/translation.rb +34 -5
- data/lib/hecks/bluebook/value_object.rb +29 -11
- data/lib/hecks/bluebook.rb +5 -6
- data/lib/hecks/codemod.rb +138 -50
- data/lib/hecks/construct.rb +21 -7
- data/lib/hecks/corpus.rb +438 -0
- data/lib/hecks/deploy/bluebook/deploy.hecksagon +19 -0
- data/lib/hecks/doc/reference.rb +200 -31
- data/lib/hecks/embryonaut_bluebook.rb +38 -15
- data/lib/hecks/facade/cli_door.rb +69 -10
- data/lib/hecks/facade/cli_runner.rb +105 -24
- data/lib/hecks/facade/command_request.rb +23 -0
- data/lib/hecks/facade/handle.rb +155 -35
- data/lib/hecks/facade/json_door.rb +106 -25
- data/lib/hecks/facade/surface/aggregate_door.rb +50 -27
- data/lib/hecks/facade/surface/chapter.rb +26 -17
- data/lib/hecks/facade/surface.rb +16 -3
- data/lib/hecks/facade.rb +15 -4
- data/lib/hecks/forms/app.rb +46 -30
- data/lib/hecks/forms/command_form_renderer.rb +70 -9
- data/lib/hecks/forms/field_renderer.rb +142 -6
- data/lib/hecks/forms/field_shape.rb +183 -19
- data/lib/hecks/forms/html.rb +51 -7
- data/lib/hecks/forms/index_renderer.rb +14 -2
- data/lib/hecks/forms/page.rb +14 -0
- data/lib/hecks/forms/params.rb +120 -23
- data/lib/hecks/forms/port_argument.rb +14 -2
- data/lib/hecks/forms/query_form_renderer.rb +65 -2
- data/lib/hecks/forms/record_renderer.rb +60 -2
- data/lib/hecks/forms/record_table.rb +28 -1
- data/lib/hecks/forms/reference_options.rb +24 -0
- data/lib/hecks/forms/value_object_shape.rb +13 -3
- data/lib/hecks/forms.rb +24 -4
- data/lib/hecks/fqn.rb +59 -1
- data/lib/hecks/framework/bluebook/governance.bluebook +9 -0
- data/lib/hecks/framework/bluebook/privacy.bluebook +155 -0
- data/lib/hecks/framework/oidc.json +15 -0
- data/lib/hecks/framework.rb +79 -25
- data/lib/hecks/freezer.rb +27 -11
- data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +241 -106
- data/lib/hecks/fuzzing/combination_miner.rb +178 -0
- data/lib/hecks/fuzzing/concurrent_dispatch.rb +241 -45
- data/lib/hecks/fuzzing/coverage_campaign.rb +161 -0
- data/lib/hecks/fuzzing/differential.rb +192 -0
- data/lib/hecks/fuzzing/domain_generator.rb +871 -0
- data/lib/hecks/fuzzing/era_boundary.rb +68 -19
- data/lib/hecks/fuzzing/form_census.rb +121 -20
- data/lib/hecks/fuzzing/generated_domain_check.rb +171 -0
- data/lib/hecks/fuzzing/invalid_value_generator.rb +45 -6
- data/lib/hecks/fuzzing/isolated_boot.rb +137 -80
- data/lib/hecks/fuzzing/nondeterministic.rb +79 -0
- data/lib/hecks/fuzzing/persistence_parity.rb +111 -21
- data/lib/hecks/fuzzing/properties/corrections.rb +34 -9
- data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +300 -51
- data/lib/hecks/fuzzing/properties/guards.rb +86 -42
- data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +80 -32
- data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +40 -35
- data/lib/hecks/fuzzing/properties/outbox.rb +70 -32
- data/lib/hecks/fuzzing/properties/querying.rb +82 -28
- data/lib/hecks/fuzzing/properties.rb +84 -51
- data/lib/hecks/fuzzing/qa_settings.rb +164 -0
- data/lib/hecks/fuzzing/replay.rb +241 -133
- data/lib/hecks/fuzzing/rotation_priority.rb +48 -28
- data/lib/hecks/fuzzing/rust_gap_manifest.rb +139 -0
- data/lib/hecks/fuzzing/self_consistency.rb +315 -137
- data/lib/hecks/fuzzing/sequence_generator/adversary.rb +46 -40
- data/lib/hecks/fuzzing/sequence_generator/catalog.rb +18 -11
- data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +13 -12
- data/lib/hecks/fuzzing/sequence_generator/picker.rb +21 -12
- data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +59 -32
- data/lib/hecks/fuzzing/sequence_generator.rb +158 -23
- data/lib/hecks/fuzzing/shrinker.rb +309 -0
- data/lib/hecks/fuzzing/structural_skips.rb +37 -130
- data/lib/hecks/fuzzing/sweep_depth.rb +12 -4
- data/lib/hecks/fuzzing/target_capabilities.rb +107 -29
- data/lib/hecks/fuzzing/value_generator.rb +110 -22
- data/lib/hecks/fuzzing.rb +5 -0
- data/lib/hecks/grammar/evolve.rb +188 -12
- data/lib/hecks/grammar.rb +53 -7
- data/lib/hecks/ir.rb +51 -20
- data/lib/hecks/language/bluebook/bluebook.bluebook +41 -0
- data/lib/hecks/language/bluebook/policy.bluebook +11 -1
- data/lib/hecks/language/bluebook/vocabulary.bluebook +348 -13
- data/lib/hecks/language/hecksagon/hecksagon.bluebook +11 -0
- data/lib/hecks/language/oidc.json +5 -0
- data/lib/hecks/literal.rb +41 -9
- data/lib/hecks/naming.rb +112 -31
- data/lib/hecks/ports/access_control.rb +53 -2
- data/lib/hecks/ports/agent/answers.rb +83 -6
- data/lib/hecks/ports/agent.rb +119 -35
- data/lib/hecks/ports/authentication.rb +44 -4
- data/lib/hecks/ports/authorization.rb +50 -11
- data/lib/hecks/ports/clock.rb +42 -23
- data/lib/hecks/ports/extraction.rb +16 -0
- data/lib/hecks/ports/identity_assignment.rb +23 -2
- data/lib/hecks/ports/identity_generation.rb +17 -3
- data/lib/hecks/ports/identity_resolution.rb +17 -1
- data/lib/hecks/ports/key_vault.port +6 -0
- data/lib/hecks/ports/key_vault.rb +58 -0
- data/lib/hecks/ports/loading.rb +4 -0
- data/lib/hecks/ports/persistence/append_only.rb +172 -8
- data/lib/hecks/ports/persistence/binding_policy.rb +34 -0
- data/lib/hecks/ports/persistence/codec_boundary.rb +178 -0
- data/lib/hecks/ports/persistence/execution.rb +4 -0
- data/lib/hecks/ports/persistence/null_saga_store.rb +12 -1
- data/lib/hecks/ports/persistence/plugin.rb +42 -4
- data/lib/hecks/ports/persistence/plugins/era/era_check.rb +218 -25
- data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +77 -9
- data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +81 -24
- data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +29 -18
- data/lib/hecks/ports/persistence/plugins/era/lineage.rb +144 -60
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +103 -8
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/field_cache.rb +98 -23
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +282 -109
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/mint_transaction.rb +63 -25
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +118 -66
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/resumable_backfill.rb +51 -28
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +34 -5
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/transform_installer.rb +25 -12
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +129 -34
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/coverage_check.rb +51 -6
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +33 -9
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +16 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +95 -6
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager.rb +25 -2
- data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +314 -90
- data/lib/hecks/ports/persistence/plugins/era/storage_shape.rb +68 -10
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +9 -3
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_one.rb +9 -2
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +48 -8
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +16 -1
- data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +36 -5
- data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +23 -3
- data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +58 -19
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +96 -5
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/renderer.rb +15 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +9 -1
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +2 -2
- data/lib/hecks/ports/persistence/plugins/era.rb +12 -2
- data/lib/hecks/ports/persistence/remote_runtime.rb +9 -2
- data/lib/hecks/ports/persistence/repository_factory.rb +23 -3
- data/lib/hecks/ports/persistence/state_codec.rb +319 -0
- data/lib/hecks/ports/persistence.rb +36 -1
- data/lib/hecks/ports/projection.rb +61 -7
- data/lib/hecks/ports/query/in_memory.rb +41 -8
- data/lib/hecks/ports/query/ordering.rb +21 -6
- data/lib/hecks/ports/query.rb +35 -0
- data/lib/hecks/ports.rb +1 -0
- data/lib/hecks/projections/bootstrap_table.rb +147 -0
- data/lib/hecks/projections/diagrams.rb +315 -79
- data/lib/hecks/projections/glossary/html.rb +91 -3
- data/lib/hecks/projections/glossary/markdown.rb +50 -3
- data/lib/hecks/projections/glossary/mermaid.rb +49 -1
- data/lib/hecks/projections/glossary/sections.rb +1 -1
- data/lib/hecks/projections/glossary/sentences.rb +110 -5
- data/lib/hecks/projections/glossary.rb +173 -31
- data/lib/hecks/projections/ir.rb +1 -1
- data/lib/hecks/projections/model/deviations.rb +62 -17
- data/lib/hecks/projections/model.rb +75 -21
- data/lib/hecks/projections/oidc.rb +45 -8
- data/lib/hecks/projections/parser_table.rb +53 -12
- data/lib/hecks/projections/reference.rb +15 -3
- data/lib/hecks/projections/rust_vocabulary.rb +646 -0
- data/lib/hecks/projections/shape.rb +10 -2
- data/lib/hecks/projections/statements.rb +69 -22
- data/lib/hecks/projections/vocabulary.rb +26 -9
- data/lib/hecks/projections.rb +5 -3
- data/lib/hecks/projector/cli_projector.rb +245 -37
- data/lib/hecks/projector/docs_projector.rb +154 -28
- data/lib/hecks/projector/exporter.rb +104 -29
- data/lib/hecks/projector/ir_projector.rb +7 -1
- data/lib/hecks/projector/narrate_projector.rb +144 -23
- data/lib/hecks/projector/target.rb +42 -18
- data/lib/hecks/projector.rb +86 -18
- data/lib/hecks/query_ir.rb +94 -47
- data/lib/hecks/query_specification/common/comparators.rb +19 -3
- data/lib/hecks/query_specification/common/comparison.rb +109 -27
- data/lib/hecks/query_specification/common/dsl.rb +65 -9
- data/lib/hecks/query_specification/common/null_policy.rb +61 -15
- data/lib/hecks/query_specification/common/null_semantics.rb +4 -0
- data/lib/hecks/query_specification/common/options.rb +25 -0
- data/lib/hecks/query_specification/field_path.rb +69 -15
- data/lib/hecks/query_specification/hop_path.rb +57 -20
- data/lib/hecks/query_specification/read_model/specification.rb +4 -0
- data/lib/hecks/rendering.rb +9 -3
- data/lib/hecks/router/namespace_installer.rb +16 -3
- data/lib/hecks/router.rb +56 -1
- data/lib/hecks/runtime/aggregate_lock.rb +20 -11
- data/lib/hecks/runtime/boot_gates.rb +21 -3
- data/lib/hecks/runtime/caller.rb +40 -8
- data/lib/hecks/runtime/capability_graph.rb +13 -2
- data/lib/hecks/runtime/command_interpreter/argument_gate.rb +40 -41
- data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +45 -67
- data/lib/hecks/runtime/command_interpreter.rb +195 -113
- data/lib/hecks/runtime/command_rules/admissibility.rb +231 -80
- data/lib/hecks/runtime/command_rules/arithmetic.rb +154 -96
- data/lib/hecks/runtime/command_rules/authorization.rb +38 -17
- data/lib/hecks/runtime/command_rules/emission.rb +18 -1
- data/lib/hecks/runtime/command_rules/references.rb +145 -55
- data/lib/hecks/runtime/command_rules.rb +3 -0
- data/lib/hecks/runtime/dependency_planning.rb +56 -11
- data/lib/hecks/runtime/dispatcher.rb +263 -92
- data/lib/hecks/runtime/entity_element.rb +304 -67
- data/lib/hecks/runtime/entity_interpreter.rb +149 -88
- data/lib/hecks/runtime/errors.rb +37 -23
- data/lib/hecks/runtime/event.rb +15 -10
- data/lib/hecks/runtime/identity.rb +91 -23
- data/lib/hecks/runtime/instance.rb +103 -18
- data/lib/hecks/runtime/interpreting.rb +24 -16
- data/lib/hecks/runtime/invocation.rb +358 -0
- data/lib/hecks/runtime/loader.rb +105 -19
- data/lib/hecks/runtime/outbox.rb +164 -26
- data/lib/hecks/runtime/policy_interpreter.rb +73 -60
- data/lib/hecks/runtime/port_operation_interpreter.rb +42 -19
- data/lib/hecks/runtime/query_interpreter.rb +93 -74
- data/lib/hecks/runtime/reaction_invocation.rb +73 -28
- data/lib/hecks/runtime/read_model_interpreter.rb +60 -44
- data/lib/hecks/runtime/rebuild_sweep.rb +32 -4
- data/lib/hecks/runtime/reference_hop.rb +48 -6
- data/lib/hecks/runtime/refusal_wording.rb +142 -115
- data/lib/hecks/runtime/registry/saga_persistence.rb +32 -21
- data/lib/hecks/runtime/registry/verification.rb +153 -28
- data/lib/hecks/runtime/registry.rb +202 -32
- data/lib/hecks/runtime/remote_dispatcher.rb +125 -24
- data/lib/hecks/runtime/routing.rb +36 -154
- data/lib/hecks/runtime/saga_interpreter/correlation.rb +25 -27
- data/lib/hecks/runtime/saga_interpreter.rb +90 -76
- data/lib/hecks/runtime/saga_pending_dispatch.rb +12 -12
- data/lib/hecks/runtime/tenant_check.rb +33 -13
- data/lib/hecks/runtime/tenant_scope.rb +23 -5
- data/lib/hecks/runtime/value/admission.rb +75 -30
- data/lib/hecks/runtime/value/coercion.rb +555 -142
- data/lib/hecks/runtime/value/entity_list_coercion.rb +132 -60
- data/lib/hecks/runtime/value.rb +71 -21
- data/lib/hecks/runtime.rb +39 -7
- data/lib/hecks/storehouse.rb +368 -72
- data/lib/hecks/tenancy/bluebook/tenancy.bluebook +130 -0
- data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +32 -0
- data/lib/hecks/version.rb +3 -3
- data/lib/hecks/vocabulary.rb +205 -4
- data/lib/hecks.rb +91 -11
- data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +19 -11
- data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +24 -12
- data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +38 -14
- metadata +28 -2
|
@@ -1,30 +1,34 @@
|
|
|
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`
|
|
7
|
-
# there a Cargo feature named after this directory?
|
|
8
|
-
# `:differential`, else `:ruby_only`",
|
|
9
|
-
# for `--persistence-parity` ("does any .hecksagon bind PostgresEra?")
|
|
10
|
-
#
|
|
11
|
-
# WASM front)
|
|
6
|
+
# `bin/qa_sweep` does not decide its comparison modes with inline
|
|
7
|
+
# conditionals — "is there a Cargo feature named after this directory?
|
|
8
|
+
# then `:differential`, else `:ruby_only`", a separate hand-typed abort
|
|
9
|
+
# for `--persistence-parity` ("does any .hecksagon bind PostgresEra?"),
|
|
10
|
+
# and so on for every further mode the practice adds (era boundary,
|
|
11
|
+
# concurrency, a WASM front) — because each such inline `if` is 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
|
-
#
|
|
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.
|
|
18
|
+
# ## Inference decides; a stored list only records
|
|
26
19
|
#
|
|
27
|
-
#
|
|
20
|
+
# `Target.capabilities` (qa/bluebook/quality_control.bluebook, once
|
|
21
|
+
# PR-1's era lands) is written by the runner from exactly this
|
|
22
|
+
# inference at release time so `Target.EligibleFor(mode)` can audit
|
|
23
|
+
# the rotation from the ledger alone — but the runner re-infers every
|
|
24
|
+
# sweep, because a stored list that lags yesterday's Cargo feature is
|
|
25
|
+
# precisely the "quiet divergence" (the chapter's own opening comment)
|
|
26
|
+
# this whole practice exists to hunt. Nothing here ever reads the
|
|
27
|
+
# ledger.
|
|
28
|
+
#
|
|
29
|
+
# ## Regex provenance
|
|
30
|
+
#
|
|
31
|
+
# Every regex is one the harness already owned, moved here rather than
|
|
28
32
|
# re-derived, and each one's provenance is named beside it so a
|
|
29
33
|
# future edit to the original site is a visible drift, not a silent
|
|
30
34
|
# one: the Cargo feature line (`RustConformanceHelpers#build_rust_for`),
|
|
@@ -45,10 +49,13 @@ module Hecks
|
|
|
45
49
|
# domain-level default (`persisted_by "PostgresEra"`).
|
|
46
50
|
POSTGRES_ERA_BINDING = /persisted_by\s*\(?\s*"PostgresEra"/
|
|
47
51
|
|
|
48
|
-
# `HecksagonBuilder#uses_framework` —
|
|
49
|
-
#
|
|
50
|
-
# (`
|
|
51
|
-
|
|
52
|
+
# `HecksagonBuilder#uses_framework "X"` — captures which member a
|
|
53
|
+
# hecksagon attaches, whatever its name. Whether that member answers
|
|
54
|
+
# a role check is then read off its own declaration (`provides
|
|
55
|
+
# "authorization"`, via `Framework.providers_of`), the same rule
|
|
56
|
+
# `Registry#authorization_provider_for` applies at runtime — never a
|
|
57
|
+
# match on the literal "Governance".
|
|
58
|
+
FRAMEWORK_ATTACHED = /uses_framework\s*\(?\s*"([^"]+)"/
|
|
52
59
|
|
|
53
60
|
# A command-level `role "..."` — the only construct
|
|
54
61
|
# `refuse_role_mismatch` ever has anything to check a caller against.
|
|
@@ -61,21 +68,21 @@ module Hecks
|
|
|
61
68
|
|
|
62
69
|
PROCESS_MANAGER = /^\s*process_manager\s+"/
|
|
63
70
|
|
|
64
|
-
#
|
|
65
|
-
#
|
|
71
|
+
# Which capabilities each mode needs before it can say anything true
|
|
72
|
+
# about a target. An empty list means "any target at all" — every
|
|
66
73
|
# domain boots under Memory, so Ruby-only properties and the
|
|
67
74
|
# self-consistency pass are always answerable. `ruby_only` is listed
|
|
68
|
-
# requirement-free on purpose and then
|
|
75
|
+
# requirement-free on purpose and then excluded by `resolve` whenever
|
|
69
76
|
# `differential` resolved too: they are the same seat, and a compiled
|
|
70
77
|
# Rust binary is strictly the better occupant (item 1 of the
|
|
71
|
-
# detection plan folded the Ruby-only property battery
|
|
78
|
+
# detection plan folded the Ruby-only property battery into the
|
|
72
79
|
# differential seat, so nothing is lost by the exclusion).
|
|
73
80
|
#
|
|
74
81
|
# The four `false`-by-default modes in `QualityControlDials::MODES`
|
|
75
82
|
# (`adapter_parity_postgres`, `era_boundary`, `concurrency`,
|
|
76
83
|
# `wasm_front`) are named here with their requirements even though
|
|
77
84
|
# nothing runs them yet — so `resolved modes:` can already say, per
|
|
78
|
-
# target, which of them
|
|
85
|
+
# target, which of them would be eligible the day a human flips the
|
|
79
86
|
# dial, and so flipping it is a one-line data change rather than a
|
|
80
87
|
# code change plus a data change.
|
|
81
88
|
MODE_REQUIREMENTS = {
|
|
@@ -92,7 +99,24 @@ module Hecks
|
|
|
92
99
|
wasm_front: %w[rust]
|
|
93
100
|
}.freeze
|
|
94
101
|
|
|
95
|
-
#
|
|
102
|
+
# Which of those modes `bin/qa_sweep` can actually run today.
|
|
103
|
+
# `MODE_REQUIREMENTS` above says what a mode needs; this says what
|
|
104
|
+
# exists to do it, and the two are not the same. Conflating them is
|
|
105
|
+
# how `qa/settings.yml` came to enable `wasm_front` and
|
|
106
|
+
# `adapter_parity_postgres` with nothing behind either — no seat, no
|
|
107
|
+
# check folded into the seed loop, no `MODE_EXPECTATIONS` entry — while
|
|
108
|
+
# `resolved modes:` still printed them per target, so the sweep
|
|
109
|
+
# advertised coverage it never performed. A mode named in
|
|
110
|
+
# `MODE_REQUIREMENTS` but absent here is one this practice wants and has
|
|
111
|
+
# not built: `bin/qa_sweep` refuses to start when a dial or `--modes`
|
|
112
|
+
# enables it, rather than resolving it into a line nobody can act on.
|
|
113
|
+
# `spec/qa_sweep_runnable_modes_spec.rb` keeps this list honest from
|
|
114
|
+
# both sides by grepping the runner itself.
|
|
115
|
+
RUNNABLE_MODES = %i[differential ruby_only self_consistency properties_in_differential
|
|
116
|
+
structural_skip_report adapter_parity_sqlite persistence_parity
|
|
117
|
+
era_boundary concurrency].freeze
|
|
118
|
+
|
|
119
|
+
# Modes that name a separate, expensive pass of their own rather than
|
|
96
120
|
# an extra check folded into the ordinary per-seed loop —
|
|
97
121
|
# `bin/qa_sweep` runs these only when asked by name (`--modes
|
|
98
122
|
# persistence_parity`, or its older alias `--persistence-parity`) or
|
|
@@ -101,37 +125,66 @@ module Hecks
|
|
|
101
125
|
# exists because that pass pays for real Postgres I/O per dispatch).
|
|
102
126
|
DEFERRED_MODES = %i[persistence_parity adapter_parity_postgres era_boundary concurrency].freeze
|
|
103
127
|
|
|
128
|
+
# Reads a target directory to find out which capabilities it actually has.
|
|
129
|
+
#
|
|
104
130
|
# Sorted, plain strings — comma-joined by the runner into the
|
|
105
131
|
# `Target.Release(capabilities:)` value object and printed verbatim
|
|
106
132
|
# on the `resolved modes:` line, so the same spelling is what a
|
|
107
133
|
# human reads, what `--all` parses back, and what the ledger stores.
|
|
134
|
+
#
|
|
135
|
+
# @param domain_path [String] filesystem path to the target domain's directory
|
|
136
|
+
# @param rust_dir [String] path to the Rust project root, checked for a matching
|
|
137
|
+
# Cargo feature; defaults to this repo's own `rust/` directory
|
|
138
|
+
# @return [Array<String>] the target's capabilities, sorted; a subset of
|
|
139
|
+
# `sqlite`, `rust`, `postgres_era`, `translations`, `governance`, `role_gated`,
|
|
140
|
+
# `tenant`, `sagas`
|
|
108
141
|
def infer(domain_path, rust_dir: File.expand_path("../../../rust", __dir__))
|
|
109
142
|
capabilities = %w[sqlite]
|
|
110
143
|
capabilities << "rust" if rust_feature?(domain_path, rust_dir)
|
|
111
144
|
capabilities << "postgres_era" if any_file?(domain_path, "*.hecksagon", POSTGRES_ERA_BINDING)
|
|
112
145
|
capabilities << "translations" if Dir.glob(File.join(domain_path, "**", "translations", "*.bluebook")).any?
|
|
113
|
-
capabilities << "governance" if
|
|
146
|
+
capabilities << "governance" if authorization_attached?(domain_path)
|
|
114
147
|
capabilities << "role_gated" if any_file?(domain_path, "*.bluebook", ROLE_GATED)
|
|
115
148
|
capabilities << "tenant" if any_file?(domain_path, "*.bluebook", TENANT_SCOPED)
|
|
116
149
|
capabilities << "sagas" if any_file?(domain_path, "*.bluebook", PROCESS_MANAGER)
|
|
117
150
|
capabilities.sort
|
|
118
151
|
end
|
|
119
152
|
|
|
153
|
+
# Answers whether `mode` can run against a target with `capabilities`.
|
|
154
|
+
#
|
|
155
|
+
# @param mode [Symbol, String] a key of `MODE_REQUIREMENTS`, such as `:differential`
|
|
156
|
+
# @param capabilities [Array<String>] the target's capabilities, as `infer` returns
|
|
157
|
+
# @return [Boolean] whether `capabilities` covers every capability `mode` requires
|
|
158
|
+
# @raise [ArgumentError] if `mode` names no entry in `MODE_REQUIREMENTS`
|
|
120
159
|
def eligible?(mode, capabilities)
|
|
121
160
|
required = MODE_REQUIREMENTS.fetch(mode.to_sym) { raise ArgumentError, "unknown sweep mode #{mode.inspect}" }
|
|
122
161
|
(required - capabilities).empty?
|
|
123
162
|
end
|
|
124
163
|
|
|
125
|
-
#
|
|
164
|
+
# The one rule. `enabled` is whatever the dial (or `--modes`) turned
|
|
126
165
|
# on, in the dial's own declaration order — that order is preserved
|
|
127
166
|
# so the printed line reads the same way the dial does. Then the
|
|
128
167
|
# single exclusion named on `MODE_REQUIREMENTS`.
|
|
168
|
+
#
|
|
169
|
+
# @param enabled [Array<Symbol, String>] modes turned on, in the dial's own
|
|
170
|
+
# declaration order
|
|
171
|
+
# @param capabilities [Array<String>] the target's capabilities, as `infer` returns
|
|
172
|
+
# @return [Array<Symbol>] `enabled`'s eligible modes, in `enabled`'s order, with
|
|
173
|
+
# `:ruby_only` dropped whenever `:differential` is also eligible
|
|
129
174
|
def resolve(enabled, capabilities)
|
|
130
175
|
resolved = enabled.map(&:to_sym).select { |mode| eligible?(mode, capabilities) }
|
|
131
176
|
resolved.delete(:ruby_only) if resolved.include?(:differential)
|
|
132
177
|
resolved
|
|
133
178
|
end
|
|
134
179
|
|
|
180
|
+
# Answers whether `rust_dir`'s Cargo.toml declares a feature named after
|
|
181
|
+
# `domain_path`'s own directory.
|
|
182
|
+
#
|
|
183
|
+
# @param domain_path [String] filesystem path to the target domain's directory;
|
|
184
|
+
# its basename, lowercased, is the feature name looked up
|
|
185
|
+
# @param rust_dir [String] path to the Rust project root (holds `Cargo.toml`)
|
|
186
|
+
# @return [Boolean] whether a matching Cargo feature is declared; false if
|
|
187
|
+
# `rust_dir` has no `Cargo.toml`
|
|
135
188
|
def rust_feature?(domain_path, rust_dir)
|
|
136
189
|
cargo_toml = File.join(rust_dir, "Cargo.toml")
|
|
137
190
|
return false unless File.file?(cargo_toml)
|
|
@@ -141,6 +194,31 @@ module Hecks
|
|
|
141
194
|
features.match?(/^#{Regexp.escape(feature)}\s*=\s*\[\]/)
|
|
142
195
|
end
|
|
143
196
|
|
|
197
|
+
# Answers whether any `.hecksagon` under `domain_path` attaches a member
|
|
198
|
+
# that provides authorization.
|
|
199
|
+
#
|
|
200
|
+
# The capability label stays "governance" — it is the value
|
|
201
|
+
# `bin/qa_sweep` writes into the QualityControl ledger's
|
|
202
|
+
# `Target.capabilities`, and renaming it is a ledger change, not
|
|
203
|
+
# part of dropping the name check.
|
|
204
|
+
#
|
|
205
|
+
# @param domain_path [String] filesystem path to the target domain's directory
|
|
206
|
+
# @return [Boolean] whether the domain attaches a member that provides the
|
|
207
|
+
# `Bluebook::Capabilities::AUTHORIZATION` capability
|
|
208
|
+
def authorization_attached?(domain_path)
|
|
209
|
+
attached = Dir.glob(File.join(domain_path, "**", "*.hecksagon"))
|
|
210
|
+
.flat_map { |path| File.read(path).scan(FRAMEWORK_ATTACHED).flatten }.uniq
|
|
211
|
+
providers = Framework.providers_of(Bluebook::Capabilities::AUTHORIZATION)
|
|
212
|
+
attached.intersect?(providers)
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# Answers whether any file under `domain_path` matching `glob` contains `pattern`.
|
|
216
|
+
#
|
|
217
|
+
# @param domain_path [String] filesystem path to the target domain's directory
|
|
218
|
+
# @param glob [String] a `Dir.glob` pattern relative to `domain_path`, such as
|
|
219
|
+
# `"*.bluebook"`
|
|
220
|
+
# @param pattern [Regexp] the pattern each matching file's contents is tested against
|
|
221
|
+
# @return [Boolean] whether any matching file's contents match `pattern`
|
|
144
222
|
def any_file?(domain_path, glob, pattern)
|
|
145
223
|
Dir.glob(File.join(domain_path, "**", glob)).any? { |path| File.read(path).match?(pattern) }
|
|
146
224
|
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.
|
|
@@ -100,12 +100,12 @@ module Hecks
|
|
|
100
100
|
CLOCK_OR_COUNT_NAME_PATTERN = /clock|instant|expir|ttl|\bnow\b|timestamp|epoch|count/i
|
|
101
101
|
# NaN and +/-Infinity — the real find (see `spec/runtime/
|
|
102
102
|
# numeric_boundary_spec.rb`): `Value::Coercion#check_numeric_fields`
|
|
103
|
-
#
|
|
104
|
-
#
|
|
105
|
-
#
|
|
103
|
+
# type-checks all three (each really is a Float) before they can
|
|
104
|
+
# reach either `CommandRules::Arithmetic#clamp` (raw
|
|
105
|
+
# `ArgumentError`, not a domain refusal) or `JSON.generate`
|
|
106
106
|
# (`JSON::GeneratorError`, also not a domain refusal) — both fixed
|
|
107
|
-
# at the source
|
|
108
|
-
# deliberately included too even though it was
|
|
107
|
+
# at the source, so these are safe to generate. -0.0 is
|
|
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
|
|
@@ -125,6 +125,18 @@ module Hecks
|
|
|
125
125
|
# just named "address"; the VO's own name is where "email" lives. A
|
|
126
126
|
# combined hint catches both spellings without needing to guess which
|
|
127
127
|
# level a domain happened to name the thing on.
|
|
128
|
+
#
|
|
129
|
+
# @param attribute [Bluebook::Attribute] the attribute to generate a value for
|
|
130
|
+
# @param aggregate [Bluebook::Aggregate] the aggregate `attribute` belongs to;
|
|
131
|
+
# resolves a same-chapter identity value object
|
|
132
|
+
# @param random [Random] the RNG driving every draw this call makes
|
|
133
|
+
# @param known_ids [Hash{String => Array<String>}] known real ids, keyed by
|
|
134
|
+
# aggregate or entity name; a reference draws from this pool
|
|
135
|
+
# @param context [String, nil] the enclosing value object's own declared name,
|
|
136
|
+
# for name-aware primitive generation; nil at the top level
|
|
137
|
+
# @return [String, Integer, Float, Boolean, Hash] a JSON-safe value in the corpus's
|
|
138
|
+
# own shape: a nested Hash for a value-object-typed attribute, a bare id String
|
|
139
|
+
# for a reference, or a plain primitive otherwise
|
|
128
140
|
def value_for(attribute, aggregate, random:, known_ids: {}, context: nil)
|
|
129
141
|
return reference_value(attribute, random: random, known_ids: known_ids) if attribute.reference?
|
|
130
142
|
|
|
@@ -143,6 +155,20 @@ module Hecks
|
|
|
143
155
|
primitive(attribute.type.to_s, random: random, name: "#{context} #{attribute.name}")
|
|
144
156
|
end
|
|
145
157
|
|
|
158
|
+
# Generates a nested value for a whole value object: a random admitted
|
|
159
|
+
# row for a closed set, or a value per declared attribute otherwise —
|
|
160
|
+
# occasionally unwrapped to a bare scalar for a genuinely single-field
|
|
161
|
+
# value object.
|
|
162
|
+
#
|
|
163
|
+
# @param value_object [Bluebook::ValueObject] the value object to generate a value for
|
|
164
|
+
# @param aggregate [Bluebook::Aggregate] the aggregate `value_object` is reached
|
|
165
|
+
# from, forwarded to nested `value_for` calls
|
|
166
|
+
# @param random [Random] the RNG driving every draw this call makes
|
|
167
|
+
# @param known_ids [Hash{String => Array<String>}] known real ids, keyed by
|
|
168
|
+
# aggregate or entity name, forwarded to nested `value_for` calls
|
|
169
|
+
# @return [Hash, Object] `{field name => value, ...}` for each declared attribute
|
|
170
|
+
# or closed-set member; unwrapped to that lone value directly when the value
|
|
171
|
+
# object has exactly one field and the bare-scalar draw hits
|
|
146
172
|
def object_for(value_object, aggregate, random:, known_ids:)
|
|
147
173
|
fields =
|
|
148
174
|
if value_object.closed_set? && !value_object.members.empty?
|
|
@@ -157,10 +183,10 @@ module Hecks
|
|
|
157
183
|
end
|
|
158
184
|
end
|
|
159
185
|
|
|
160
|
-
# `fields.size == 1` — the
|
|
186
|
+
# `fields.size == 1` — the same test `Behaviour::ValueObject#
|
|
161
187
|
# sole_attribute` names: a genuinely single-field value object,
|
|
162
188
|
# not merely "this particular closed-set member happened to pick
|
|
163
|
-
# one field." Unwrapping
|
|
189
|
+
# one field." Unwrapping only here, not in `invalid_member`
|
|
164
190
|
# above — a deliberately-wrong combination stays a Hash so its
|
|
165
191
|
# own wrongness is what gets exercised, not a second, unrelated
|
|
166
192
|
# shape question.
|
|
@@ -172,17 +198,31 @@ module Hecks
|
|
|
172
198
|
# A combination that (almost certainly) isn't one of the closed set's
|
|
173
199
|
# admitted rows — deliberately, to exercise the refusal a `one_of`
|
|
174
200
|
# exists to enforce, not just its happy path.
|
|
201
|
+
#
|
|
202
|
+
# @param value_object [Bluebook::ValueObject] the closed-set value object to
|
|
203
|
+
# generate a non-admitted combination for
|
|
204
|
+
# @param random [Random] the RNG driving every draw this call makes
|
|
205
|
+
# @return [Hash] `{field name => value, ...}` for every declared attribute, each
|
|
206
|
+
# drawn independently rather than sampled from an admitted row
|
|
175
207
|
def invalid_member(value_object, random:)
|
|
176
208
|
value_object.attributes.to_h do |field|
|
|
177
209
|
[field.name.to_s, primitive(field.type.to_s, random: random, name: field.name.to_s)]
|
|
178
210
|
end
|
|
179
211
|
end
|
|
180
212
|
|
|
181
|
-
#
|
|
182
|
-
#
|
|
183
|
-
#
|
|
184
|
-
#
|
|
185
|
-
#
|
|
213
|
+
# The ID itself, unwrapped: the payload gate refuses a reference stored
|
|
214
|
+
# wrapped as `{"value" => id}`, so a fuzzer emitting that shape would
|
|
215
|
+
# have every generated reference refused and the silent guard would
|
|
216
|
+
# report the fuzzer broken rather than the runtime.
|
|
217
|
+
#
|
|
218
|
+
# @param attribute [Bluebook::Attribute] the reference-typed attribute to
|
|
219
|
+
# generate a value for
|
|
220
|
+
# @param random [Random] the RNG driving every draw this call makes
|
|
221
|
+
# @param known_ids [Hash{String => Array<String>}] known real ids, keyed by
|
|
222
|
+
# aggregate or entity name; looked up under `attribute.type.target_name`
|
|
223
|
+
# @return [String] a real id drawn from the matching pool most of the time; a
|
|
224
|
+
# fabricated `"missing-..."` id when the pool is empty or the invalid-reference
|
|
225
|
+
# draw hits
|
|
186
226
|
def reference_value(attribute, random:, known_ids:)
|
|
187
227
|
pool = known_ids[attribute.type.target_name.to_s] || []
|
|
188
228
|
return "missing-#{random.bytes(4).unpack1('H*')}" if pool.empty? || random.rand < INVALID_REFERENCE_PROBABILITY
|
|
@@ -190,6 +230,16 @@ module Hecks
|
|
|
190
230
|
pool.sample(random: random)
|
|
191
231
|
end
|
|
192
232
|
|
|
233
|
+
# Generates a value for one Ruby-primitive-typed attribute.
|
|
234
|
+
#
|
|
235
|
+
# @param type_name [String] the primitive type name: `"String"`, `"Integer"`,
|
|
236
|
+
# `"Float"`, `"TrueClass"`, or `"FalseClass"`
|
|
237
|
+
# @param random [Random] the RNG driving every draw this call makes
|
|
238
|
+
# @param name [String, nil] a name hint (attribute name, optionally
|
|
239
|
+
# context-prefixed) that draws an email-/currency-shaped string or a
|
|
240
|
+
# clock/count-shaped integer
|
|
241
|
+
# @return [String, Integer, Float, Boolean] a value of the type `type_name` names
|
|
242
|
+
# @raise [ArgumentError] if `type_name` names anything else
|
|
193
243
|
def primitive(type_name, random:, name: nil)
|
|
194
244
|
case type_name
|
|
195
245
|
when "String" then string_value(random, name: name)
|
|
@@ -200,6 +250,14 @@ module Hecks
|
|
|
200
250
|
end
|
|
201
251
|
end
|
|
202
252
|
|
|
253
|
+
# Generates a String value, name-aware for the couple of shapes that
|
|
254
|
+
# need to look a specific way to get past their own invariant.
|
|
255
|
+
#
|
|
256
|
+
# @param random [Random] the RNG driving every draw this call makes
|
|
257
|
+
# @param name [String, nil] a name hint; an `"email"` or `"currency"` match
|
|
258
|
+
# draws a shaped value instead of random words
|
|
259
|
+
# @return [String] an edge-case string, an email address, a currency code, or
|
|
260
|
+
# 1-3 random words joined by spaces
|
|
203
261
|
def string_value(random, name: nil)
|
|
204
262
|
return STRING_EDGE_CASES.sample(random: random) if random.rand < EDGE_CASE_PROBABILITY
|
|
205
263
|
return email_value(random) if name&.match?(/email/i)
|
|
@@ -208,15 +266,26 @@ module Hecks
|
|
|
208
266
|
Array.new(random.rand(1..3)) { WORDS.sample(random: random) }.join(" ")
|
|
209
267
|
end
|
|
210
268
|
|
|
269
|
+
# Generates a fabricated, syntactically valid email address.
|
|
270
|
+
#
|
|
271
|
+
# @param random [Random] the RNG driving every draw this call makes
|
|
272
|
+
# @return [String] a `"word@word.example"` address
|
|
211
273
|
def email_value(random)
|
|
212
274
|
"#{WORDS.sample(random: random)}@#{WORDS.sample(random: random)}.example"
|
|
213
275
|
end
|
|
214
276
|
|
|
277
|
+
# Generates an Integer value, skewed positive.
|
|
278
|
+
#
|
|
215
279
|
# Skewed positive : `cents.positive?`/`!cents.negative?`-style
|
|
216
280
|
# invariants are common across this codebase's example domains, and a
|
|
217
281
|
# sequence that can never get past one never reaches the state a deeper
|
|
218
282
|
# bug would need. Zero and negative are still real, reachable outcomes —
|
|
219
283
|
# via the edge-case pool, deliberately, not by starving them entirely.
|
|
284
|
+
#
|
|
285
|
+
# @param random [Random] the RNG driving every draw this call makes
|
|
286
|
+
# @param name [String, nil] a name hint; a clock/count-shaped match narrows
|
|
287
|
+
# the edge-case pool to `SAFE_INTEGER_EDGE_CASES`
|
|
288
|
+
# @return [Integer] an edge-case integer sometimes, else a random count in 1..1000
|
|
220
289
|
def integer_value(random, name: nil)
|
|
221
290
|
if random.rand < EDGE_CASE_PROBABILITY
|
|
222
291
|
pool = clock_or_count_shaped?(name) ? SAFE_INTEGER_EDGE_CASES : INTEGER_EDGE_CASES
|
|
@@ -226,24 +295,43 @@ module Hecks
|
|
|
226
295
|
random.rand(1..1000)
|
|
227
296
|
end
|
|
228
297
|
|
|
298
|
+
# Reports whether a field name reads as a clock reading or a
|
|
299
|
+
# policy-capped count, per `CLOCK_OR_COUNT_NAME_PATTERN`'s own comment.
|
|
300
|
+
#
|
|
301
|
+
# @param name [String, nil] the name to test
|
|
302
|
+
# @return [Boolean] true if `name` matches the clock/count name pattern
|
|
229
303
|
def clock_or_count_shaped?(name)
|
|
230
304
|
name.to_s.match?(CLOCK_OR_COUNT_NAME_PATTERN)
|
|
231
305
|
end
|
|
232
306
|
|
|
307
|
+
# Generates a Float value.
|
|
308
|
+
#
|
|
309
|
+
# @param random [Random] the RNG driving every draw this call makes
|
|
310
|
+
# @return [Float] an edge-case float sometimes, else a random value in
|
|
311
|
+
# 0.01..1000.0, rounded to 2 decimal places
|
|
233
312
|
def float_value(random)
|
|
234
313
|
return FLOAT_EDGE_CASES.sample(random: random) if random.rand < EDGE_CASE_PROBABILITY
|
|
235
314
|
|
|
236
315
|
random.rand(0.01..1000.0).round(2)
|
|
237
316
|
end
|
|
238
317
|
|
|
239
|
-
# The bare scalar a generated
|
|
318
|
+
# The bare scalar a generated identity value stands for, for recording into
|
|
240
319
|
# `known_ids`. An identity is declared as a value object, so this opens one ;
|
|
241
320
|
# a reference pointing at this record is already that scalar and needs no
|
|
242
|
-
# opening at all
|
|
321
|
+
# opening at all — the two are different readings, not interchangeable.
|
|
322
|
+
#
|
|
323
|
+
# @param identity_value [Hash, Object] a generated identity value, as `value_for`
|
|
324
|
+
# produced it — a Hash for a multi-field value object, or the bare scalar already
|
|
325
|
+
# for a single-field one
|
|
326
|
+
# @return [String] the identity's bare scalar, stringified
|
|
243
327
|
def scalar_of(identity_value)
|
|
244
328
|
identity_value.is_a?(Hash) ? identity_value.values.first.to_s : identity_value.to_s
|
|
245
329
|
end
|
|
246
330
|
|
|
331
|
+
# Generates a fabricated id unrelated to anything `known_ids` tracks.
|
|
332
|
+
#
|
|
333
|
+
# @param random [Random] the RNG driving every draw this call makes
|
|
334
|
+
# @return [String] a `"gen-"`-prefixed id with 8 random hex characters
|
|
247
335
|
def random_id(random)
|
|
248
336
|
"gen-#{random.bytes(4).unpack1('H*')}"
|
|
249
337
|
end
|
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"
|