hecks 1.3.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/hecks/adapters/driven/claude_code.rb +7 -7
- data/lib/hecks/adapters/driven/d1.rb +187 -23
- data/lib/hecks/adapters/driven/folder.rb +10 -10
- data/lib/hecks/adapters/driven/google_authentication.rb +8 -8
- data/lib/hecks/adapters/driven/governance_authorization.rb +31 -13
- data/lib/hecks/adapters/driven/heki/journal.rb +5 -4
- data/lib/hecks/adapters/driven/heki/saga_store.rb +5 -5
- data/lib/hecks/adapters/driven/heki.rb +13 -7
- data/lib/hecks/adapters/driven/identity_registry.rb +2 -2
- data/lib/hecks/adapters/driven/in_memory_ordering.rb +3 -3
- data/lib/hecks/adapters/driven/lambda/client.rb +34 -9
- data/lib/hecks/adapters/driven/lambda.rb +39 -33
- data/lib/hecks/adapters/driven/local_storage.rb +17 -10
- data/lib/hecks/adapters/driven/memory.rb +205 -9
- data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +1 -1
- data/lib/hecks/adapters/driven/postgres/codec.rb +27 -11
- data/lib/hecks/adapters/driven/postgres/outbox.rb +40 -2
- data/lib/hecks/adapters/driven/postgres/reconnect.rb +23 -7
- data/lib/hecks/adapters/driven/postgres/schema_builder.rb +14 -14
- data/lib/hecks/adapters/driven/postgres.rb +175 -28
- data/lib/hecks/adapters/driven/prism.rb +4 -4
- data/lib/hecks/adapters/driven/sql_query_builder.rb +34 -22
- data/lib/hecks/adapters/driven/sqlite/codec.rb +38 -10
- data/lib/hecks/adapters/driven/sqlite/projection.rb +60 -32
- data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +12 -12
- data/lib/hecks/adapters/driven/sqlite.rb +181 -21
- data/lib/hecks/adapters/driven.rb +4 -4
- data/lib/hecks/adapters/driving/github_webhook.rb +18 -18
- data/lib/hecks/behaviors/dsl.rb +2 -2
- data/lib/hecks/behaviors/expectations.rb +23 -23
- data/lib/hecks/behaviors/ir.rb +1 -1
- data/lib/hecks/behaviors/rspec.rb +1 -1
- data/lib/hecks/behaviors/runner.rb +2 -2
- data/lib/hecks/behaviors.rb +1 -1
- data/lib/hecks/bluebook/aggregate.rb +13 -13
- data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +10 -10
- data/lib/hecks/bluebook/assembly/build.rb +1 -1
- data/lib/hecks/bluebook/assembly/contract.rb +39 -16
- data/lib/hecks/bluebook/assembly/contracts.rb +40 -38
- data/lib/hecks/bluebook/assembly/marks.rb +18 -18
- data/lib/hecks/bluebook/assembly/specializer.rb +19 -21
- data/lib/hecks/bluebook/assembly.rb +9 -9
- data/lib/hecks/bluebook/attribute.rb +9 -9
- data/lib/hecks/bluebook/behaviour/aggregate.rb +11 -11
- data/lib/hecks/bluebook/behaviour/attribute.rb +5 -5
- data/lib/hecks/bluebook/behaviour/chapter.rb +23 -5
- data/lib/hecks/bluebook/behaviour/command.rb +23 -23
- data/lib/hecks/bluebook/behaviour/domain_port.rb +7 -7
- data/lib/hecks/bluebook/behaviour/entity.rb +8 -8
- data/lib/hecks/bluebook/behaviour/hexagon.rb +4 -4
- data/lib/hecks/bluebook/behaviour/lifecycle.rb +5 -5
- data/lib/hecks/bluebook/behaviour/policy.rb +12 -12
- data/lib/hecks/bluebook/behaviour/process_manager.rb +7 -7
- data/lib/hecks/bluebook/behaviour/query.rb +1 -1
- data/lib/hecks/bluebook/behaviour/read_model.rb +8 -8
- data/lib/hecks/bluebook/behaviour/traits.rb +12 -12
- data/lib/hecks/bluebook/behaviour/value_object.rb +6 -6
- data/lib/hecks/bluebook/capabilities.rb +27 -0
- data/lib/hecks/bluebook/chapter.rb +28 -9
- data/lib/hecks/bluebook/command.rb +12 -12
- data/lib/hecks/bluebook/domain_port.rb +9 -9
- data/lib/hecks/bluebook/dsl/adapter_builder.rb +24 -0
- data/lib/hecks/bluebook/dsl/aggregate_builder/sealing.rb +49 -49
- data/lib/hecks/bluebook/dsl/aggregate_builder.rb +96 -96
- data/lib/hecks/bluebook/dsl/attribute_collector.rb +41 -41
- data/lib/hecks/bluebook/dsl/binding_proxy.rb +22 -2
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +111 -74
- data/lib/hecks/bluebook/dsl/bluebook_builder.rb +48 -30
- data/lib/hecks/bluebook/dsl/bootstrap_table.rb +116 -0
- data/lib/hecks/bluebook/dsl/command_builder.rb +103 -103
- data/lib/hecks/bluebook/dsl/const_shim.rb +46 -15
- data/lib/hecks/bluebook/dsl/domain_port_builder.rb +90 -25
- data/lib/hecks/bluebook/dsl/entity_builder.rb +56 -56
- data/lib/hecks/bluebook/dsl/generic_dispatch.rb +148 -132
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +89 -30
- data/lib/hecks/bluebook/dsl/identity_declaration.rb +17 -17
- data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +27 -4
- data/lib/hecks/bluebook/dsl/policy_builder.rb +30 -21
- data/lib/hecks/bluebook/dsl/port_builder.rb +38 -7
- data/lib/hecks/bluebook/dsl/port_operation_builder.rb +56 -22
- data/lib/hecks/bluebook/dsl/process_manager_builder.rb +35 -35
- data/lib/hecks/bluebook/dsl/query_builder.rb +5 -5
- data/lib/hecks/bluebook/dsl/read_model_builder.rb +34 -34
- data/lib/hecks/bluebook/dsl/rule_reference.rb +41 -39
- data/lib/hecks/bluebook/dsl/translation_builder.rb +9 -9
- data/lib/hecks/bluebook/dsl/value_object_builder.rb +16 -16
- data/lib/hecks/bluebook/dsl/word_gate.rb +59 -53
- data/lib/hecks/bluebook/dsl/world_builder.rb +51 -8
- data/lib/hecks/bluebook/entity.rb +11 -11
- data/lib/hecks/bluebook/expression/ast_json.rb +20 -20
- data/lib/hecks/bluebook/expression/ast_reader.rb +3 -3
- data/lib/hecks/bluebook/expression/canonical_form.rb +9 -9
- data/lib/hecks/bluebook/expression/evaluator.rb +18 -18
- data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +18 -18
- data/lib/hecks/bluebook/expression/resolver.rb +60 -62
- data/lib/hecks/bluebook/hexagon.rb +1 -1
- data/lib/hecks/bluebook/lifecycle.rb +1 -1
- data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +1 -1
- data/lib/hecks/bluebook/meta_validator/judge.rb +101 -87
- data/lib/hecks/bluebook/meta_validator/plan.rb +39 -39
- data/lib/hecks/bluebook/meta_validator/port_judge.rb +2 -2
- data/lib/hecks/bluebook/meta_validator/readings.rb +43 -43
- data/lib/hecks/bluebook/meta_validator/reconstruction.rb +44 -37
- data/lib/hecks/bluebook/meta_validator/shapes.rb +25 -21
- data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +145 -31
- data/lib/hecks/bluebook/meta_validator/translation_judge.rb +6 -6
- data/lib/hecks/bluebook/meta_validator/world_judge.rb +5 -5
- data/lib/hecks/bluebook/meta_validator.rb +70 -70
- data/lib/hecks/bluebook/model_check.rb +170 -84
- data/lib/hecks/bluebook/pattern_subset.rb +9 -9
- data/lib/hecks/bluebook/policy.rb +15 -13
- data/lib/hecks/bluebook/process_manager.rb +14 -14
- data/lib/hecks/bluebook/project_register.rb +6 -6
- data/lib/hecks/bluebook/query.rb +4 -4
- data/lib/hecks/bluebook/read_model.rb +14 -14
- data/lib/hecks/bluebook/reference.rb +8 -8
- data/lib/hecks/bluebook/smoke_test.rb +19 -19
- data/lib/hecks/bluebook/synthesizer.rb +12 -12
- data/lib/hecks/bluebook/translation.rb +4 -4
- data/lib/hecks/bluebook/value_object.rb +6 -6
- data/lib/hecks/bluebook.rb +2 -2
- data/lib/hecks/codemod/legacy_dispatch_args.rb +299 -0
- data/lib/hecks/codemod/legacy_dispatch_recorder.rb +186 -0
- data/lib/hecks/codemod.rb +36 -35
- data/lib/hecks/construct.rb +6 -6
- data/lib/hecks/corpus.rb +317 -0
- data/lib/hecks/deprecation.rb +95 -0
- data/lib/hecks/doc/reference.rb +19 -19
- data/lib/hecks/embryonaut_bluebook.rb +11 -11
- data/lib/hecks/facade/cli_door.rb +69 -10
- data/lib/hecks/facade/cli_runner.rb +105 -24
- data/lib/hecks/facade/command_request.rb +23 -0
- data/lib/hecks/facade/handle.rb +79 -32
- data/lib/hecks/facade/json_door.rb +106 -25
- data/lib/hecks/facade/surface/aggregate_door.rb +42 -27
- data/lib/hecks/facade/surface/chapter.rb +26 -17
- data/lib/hecks/facade/surface.rb +16 -3
- data/lib/hecks/facade.rb +15 -4
- data/lib/hecks/forms/app.rb +46 -30
- data/lib/hecks/forms/command_form_renderer.rb +70 -9
- data/lib/hecks/forms/field_renderer.rb +142 -6
- data/lib/hecks/forms/field_shape.rb +180 -19
- data/lib/hecks/forms/html.rb +51 -7
- data/lib/hecks/forms/index_renderer.rb +14 -2
- data/lib/hecks/forms/params.rb +120 -23
- data/lib/hecks/forms/port_argument.rb +2 -2
- data/lib/hecks/forms/query_form_renderer.rb +2 -2
- data/lib/hecks/forms/record_renderer.rb +2 -2
- data/lib/hecks/forms/record_table.rb +1 -1
- data/lib/hecks/forms/value_object_shape.rb +3 -3
- data/lib/hecks/forms.rb +24 -4
- data/lib/hecks/fqn.rb +1 -1
- data/lib/hecks/framework/bluebook/governance.bluebook +9 -0
- data/lib/hecks/framework.rb +48 -17
- data/lib/hecks/freezer.rb +11 -11
- data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +90 -90
- data/lib/hecks/fuzzing/combination_miner.rb +119 -0
- data/lib/hecks/fuzzing/concurrent_dispatch.rb +140 -45
- data/lib/hecks/fuzzing/coverage_campaign.rb +118 -0
- data/lib/hecks/fuzzing/differential.rb +158 -0
- data/lib/hecks/fuzzing/domain_generator.rb +694 -0
- data/lib/hecks/fuzzing/era_boundary.rb +37 -18
- data/lib/hecks/fuzzing/form_census.rb +35 -20
- data/lib/hecks/fuzzing/generated_domain_check.rb +95 -0
- data/lib/hecks/fuzzing/invalid_value_generator.rb +6 -6
- data/lib/hecks/fuzzing/isolated_boot.rb +62 -62
- data/lib/hecks/fuzzing/nondeterministic.rb +67 -0
- data/lib/hecks/fuzzing/persistence_parity.rb +19 -21
- data/lib/hecks/fuzzing/properties/corrections.rb +9 -9
- data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +154 -49
- data/lib/hecks/fuzzing/properties/guards.rb +42 -42
- data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +32 -32
- data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +22 -35
- data/lib/hecks/fuzzing/properties/outbox.rb +24 -24
- data/lib/hecks/fuzzing/properties/querying.rb +17 -17
- data/lib/hecks/fuzzing/properties.rb +64 -40
- data/lib/hecks/fuzzing/qa_settings.rb +152 -0
- data/lib/hecks/fuzzing/replay.rb +114 -114
- data/lib/hecks/fuzzing/rotation_priority.rb +10 -10
- data/lib/hecks/fuzzing/rust_gap_manifest.rb +113 -0
- data/lib/hecks/fuzzing/self_consistency.rb +141 -112
- data/lib/hecks/fuzzing/sequence_generator/adversary.rb +35 -35
- data/lib/hecks/fuzzing/sequence_generator/catalog.rb +18 -11
- data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +13 -12
- data/lib/hecks/fuzzing/sequence_generator/picker.rb +21 -12
- data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +58 -31
- data/lib/hecks/fuzzing/sequence_generator.rb +111 -23
- data/lib/hecks/fuzzing/shrinker.rb +197 -0
- data/lib/hecks/fuzzing/structural_skips.rb +23 -130
- data/lib/hecks/fuzzing/sweep_depth.rb +4 -4
- data/lib/hecks/fuzzing/target_capabilities.rb +49 -18
- data/lib/hecks/fuzzing/value_generator.rb +14 -14
- data/lib/hecks/fuzzing.rb +5 -0
- data/lib/hecks/grammar/evolve.rb +10 -10
- data/lib/hecks/grammar.rb +7 -7
- data/lib/hecks/ir.rb +13 -13
- data/lib/hecks/language/bluebook/bluebook.bluebook +41 -0
- data/lib/hecks/language/bluebook/policy.bluebook +11 -1
- data/lib/hecks/language/bluebook/vocabulary.bluebook +348 -13
- data/lib/hecks/language/oidc.json +5 -0
- data/lib/hecks/literal.rb +9 -9
- data/lib/hecks/naming.rb +25 -25
- data/lib/hecks/ports/access_control.rb +58 -2
- data/lib/hecks/ports/agent/answers.rb +83 -6
- data/lib/hecks/ports/agent.rb +119 -35
- data/lib/hecks/ports/authentication.rb +44 -4
- data/lib/hecks/ports/authorization.rb +53 -11
- data/lib/hecks/ports/clock.rb +42 -23
- data/lib/hecks/ports/extraction.rb +16 -0
- data/lib/hecks/ports/identity_assignment.rb +24 -2
- data/lib/hecks/ports/identity_generation.rb +17 -3
- data/lib/hecks/ports/identity_resolution.rb +18 -1
- data/lib/hecks/ports/loading.rb +4 -0
- data/lib/hecks/ports/persistence/append_only.rb +172 -8
- data/lib/hecks/ports/persistence/binding_policy.rb +34 -0
- data/lib/hecks/ports/persistence/codec_boundary.rb +178 -0
- data/lib/hecks/ports/persistence/execution.rb +4 -0
- data/lib/hecks/ports/persistence/null_saga_store.rb +12 -1
- data/lib/hecks/ports/persistence/plugin.rb +42 -4
- data/lib/hecks/ports/persistence/plugins/era/era_check.rb +218 -25
- data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +77 -9
- data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +81 -24
- data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +29 -18
- data/lib/hecks/ports/persistence/plugins/era/lineage.rb +144 -60
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +103 -8
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/field_cache.rb +98 -23
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +282 -109
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/mint_transaction.rb +63 -25
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +118 -66
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/resumable_backfill.rb +51 -28
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +28 -5
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/transform_installer.rb +25 -12
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +129 -34
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/coverage_check.rb +51 -6
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +33 -9
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +16 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +57 -4
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager.rb +25 -2
- data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +314 -90
- data/lib/hecks/ports/persistence/plugins/era/storage_shape.rb +68 -10
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +9 -3
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_one.rb +9 -2
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +42 -8
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +16 -1
- data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +36 -5
- data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +23 -3
- data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +18 -19
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +5 -5
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +1 -1
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +2 -2
- data/lib/hecks/ports/persistence/plugins/era.rb +12 -2
- data/lib/hecks/ports/persistence/remote_runtime.rb +9 -2
- data/lib/hecks/ports/persistence/repository_factory.rb +23 -3
- data/lib/hecks/ports/persistence/state_codec.rb +319 -0
- data/lib/hecks/ports/persistence.rb +36 -1
- data/lib/hecks/ports/projection.rb +61 -7
- data/lib/hecks/ports/query/in_memory.rb +3 -3
- data/lib/hecks/ports/query/ordering.rb +6 -6
- data/lib/hecks/ports/query.rb +35 -0
- data/lib/hecks/projections/bootstrap_table.rb +112 -0
- data/lib/hecks/projections/diagrams.rb +75 -75
- data/lib/hecks/projections/glossary/html.rb +3 -3
- data/lib/hecks/projections/glossary/markdown.rb +3 -3
- data/lib/hecks/projections/glossary/mermaid.rb +1 -1
- data/lib/hecks/projections/glossary/sections.rb +1 -1
- data/lib/hecks/projections/glossary/sentences.rb +5 -5
- data/lib/hecks/projections/glossary.rb +15 -15
- data/lib/hecks/projections/ir.rb +1 -1
- data/lib/hecks/projections/model/deviations.rb +18 -17
- data/lib/hecks/projections/model.rb +25 -21
- data/lib/hecks/projections/oidc.rb +7 -7
- data/lib/hecks/projections/parser_table.rb +5 -5
- data/lib/hecks/projections/reference.rb +3 -3
- data/lib/hecks/projections/rust_vocabulary.rb +443 -0
- data/lib/hecks/projections/shape.rb +2 -2
- data/lib/hecks/projections/statements.rb +11 -11
- data/lib/hecks/projections/vocabulary.rb +9 -9
- data/lib/hecks/projections.rb +5 -3
- data/lib/hecks/projector/cli_projector.rb +29 -29
- data/lib/hecks/projector/docs_projector.rb +13 -13
- data/lib/hecks/projector/exporter.rb +42 -21
- data/lib/hecks/projector/ir_projector.rb +1 -1
- data/lib/hecks/projector/narrate_projector.rb +11 -11
- data/lib/hecks/projector/target.rb +13 -13
- data/lib/hecks/projector.rb +15 -15
- data/lib/hecks/query_ir.rb +47 -47
- data/lib/hecks/query_specification/common/comparators.rb +19 -3
- data/lib/hecks/query_specification/common/comparison.rb +109 -27
- data/lib/hecks/query_specification/common/dsl.rb +65 -9
- data/lib/hecks/query_specification/common/null_policy.rb +57 -13
- data/lib/hecks/query_specification/common/null_semantics.rb +4 -0
- data/lib/hecks/query_specification/common/options.rb +25 -0
- data/lib/hecks/query_specification/field_path.rb +69 -15
- data/lib/hecks/query_specification/hop_path.rb +57 -20
- data/lib/hecks/query_specification/read_model/specification.rb +4 -0
- data/lib/hecks/rendering.rb +3 -3
- data/lib/hecks/router/namespace_installer.rb +3 -3
- data/lib/hecks/router.rb +1 -1
- data/lib/hecks/runtime/aggregate_lock.rb +11 -11
- data/lib/hecks/runtime/boot_gates.rb +3 -3
- data/lib/hecks/runtime/caller.rb +8 -8
- data/lib/hecks/runtime/capability_graph.rb +2 -2
- data/lib/hecks/runtime/command_interpreter/argument_gate.rb +22 -25
- data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +42 -63
- data/lib/hecks/runtime/command_interpreter.rb +159 -102
- data/lib/hecks/runtime/command_rules/admissibility.rb +69 -69
- data/lib/hecks/runtime/command_rules/arithmetic.rb +137 -91
- data/lib/hecks/runtime/command_rules/authorization.rb +38 -17
- data/lib/hecks/runtime/command_rules/emission.rb +18 -1
- data/lib/hecks/runtime/command_rules/references.rb +35 -35
- data/lib/hecks/runtime/command_rules.rb +3 -0
- data/lib/hecks/runtime/dependency_planning.rb +11 -11
- data/lib/hecks/runtime/dispatcher.rb +283 -90
- data/lib/hecks/runtime/entity_element.rb +151 -67
- data/lib/hecks/runtime/entity_interpreter.rb +108 -82
- data/lib/hecks/runtime/errors.rb +19 -19
- data/lib/hecks/runtime/event.rb +6 -6
- data/lib/hecks/runtime/identity.rb +22 -22
- data/lib/hecks/runtime/instance.rb +39 -14
- data/lib/hecks/runtime/interpreting.rb +12 -12
- data/lib/hecks/runtime/invocation.rb +276 -0
- data/lib/hecks/runtime/loader.rb +14 -14
- data/lib/hecks/runtime/outbox.rb +23 -23
- data/lib/hecks/runtime/policy_interpreter.rb +54 -54
- data/lib/hecks/runtime/port_operation_interpreter.rb +22 -19
- data/lib/hecks/runtime/query_interpreter.rb +55 -64
- data/lib/hecks/runtime/reaction_invocation.rb +20 -20
- data/lib/hecks/runtime/read_model_interpreter.rb +40 -40
- data/lib/hecks/runtime/rebuild_sweep.rb +4 -4
- data/lib/hecks/runtime/reference_hop.rb +6 -6
- data/lib/hecks/runtime/refusal_wording.rb +92 -115
- data/lib/hecks/runtime/registry/saga_persistence.rb +21 -21
- data/lib/hecks/runtime/registry/verification.rb +36 -26
- data/lib/hecks/runtime/registry.rb +45 -28
- data/lib/hecks/runtime/remote_dispatcher.rb +38 -23
- data/lib/hecks/runtime/routing.rb +10 -153
- data/lib/hecks/runtime/saga_interpreter/correlation.rb +17 -17
- data/lib/hecks/runtime/saga_interpreter.rb +64 -64
- data/lib/hecks/runtime/saga_pending_dispatch.rb +12 -12
- data/lib/hecks/runtime/tenant_check.rb +9 -9
- data/lib/hecks/runtime/tenant_scope.rb +5 -5
- data/lib/hecks/runtime/value/admission.rb +75 -30
- data/lib/hecks/runtime/value/coercion.rb +318 -127
- data/lib/hecks/runtime/value/entity_list_coercion.rb +34 -34
- data/lib/hecks/runtime/value.rb +21 -21
- data/lib/hecks/runtime.rb +7 -7
- data/lib/hecks/storehouse.rb +64 -64
- data/lib/hecks/version.rb +3 -3
- data/lib/hecks/vocabulary.rb +205 -4
- data/lib/hecks.rb +13 -11
- data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +11 -11
- data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +14 -12
- data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +11 -11
- metadata +22 -2
|
@@ -4,30 +4,30 @@ require "securerandom"
|
|
|
4
4
|
|
|
5
5
|
module Hecks
|
|
6
6
|
module Fuzzing
|
|
7
|
-
# A
|
|
7
|
+
# A fresh, in-process adapter for every ephemeral boot.
|
|
8
8
|
#
|
|
9
9
|
# Fuzzing/replay copies a domain to a tmpdir and boots from there
|
|
10
10
|
# specifically to get zero-history state — `rm_rf`ing the copy's own
|
|
11
11
|
# `data/` achieves that for a file-based adapter (Memory,
|
|
12
|
-
# SqlitePersistence, Heki), because copying the
|
|
12
|
+
# SqlitePersistence, Heki), because copying the directory copies the
|
|
13
13
|
# store. It achieves nothing for an adapter that lives outside the
|
|
14
14
|
# copied directory entirely — Postgres, named by a fixed connection
|
|
15
15
|
# string in `.world` (examples/pizzas/bluebook/pizzas.world, for
|
|
16
16
|
# instance). Copying the directory does not copy or isolate the
|
|
17
|
-
#
|
|
17
|
+
# database, so a "fresh" boot against a Postgres-bound domain would
|
|
18
18
|
# still see every record any other run, ever, wrote to it.
|
|
19
19
|
#
|
|
20
20
|
# So every `.hecksagon` in the copy gets its persistence binding
|
|
21
21
|
# rewritten to Memory before booting, and any `projected_by` bind
|
|
22
22
|
# dropped outright (optional — `Registry#read_repository` already
|
|
23
23
|
# falls back to the authoritative repository when none exists). The
|
|
24
|
-
# domain's own rules and shape are untouched ; only
|
|
24
|
+
# domain's own rules and shape are untouched ; only which adapter this
|
|
25
25
|
# one ephemeral copy answers through changes. What the domain is
|
|
26
26
|
# bound to for real deployment is never touched — only this tmp copy.
|
|
27
27
|
#
|
|
28
28
|
# `adapter:` (PRD 02) — Memory is the default and the only mode every
|
|
29
29
|
# existing caller still gets with no change. `:sqlite` rebinds to the
|
|
30
|
-
#
|
|
30
|
+
# real SQLite adapter instead of the in-memory one, for exactly the
|
|
31
31
|
# same reason PRD 02 exists: 15 declared properties (properties.rb)
|
|
32
32
|
# and every fuzz/replay run has only ever been checked against
|
|
33
33
|
# Memory's own hand-written repository, never against a real,
|
|
@@ -44,8 +44,8 @@ module Hecks
|
|
|
44
44
|
# `root:`, which `Hecks.boot(copy)` passes as this ephemeral copy's
|
|
45
45
|
# own directory — a fresh, empty `data/` per run, exactly like
|
|
46
46
|
# Memory's own zero-history guarantee, just backed by a real SQLite
|
|
47
|
-
# file instead of a Hash.
|
|
48
|
-
#
|
|
47
|
+
# file instead of a Hash. Stale as of this paragraph's original
|
|
48
|
+
# writing — both `Postgres` (PRD 02, docs/prds/02-fuzzer-real-
|
|
49
49
|
# adapters.md) and `PostgresEra` (this mode's own header, below,
|
|
50
50
|
# `rebind_to_postgres_era!`) since gained real `adapter:` modes here.
|
|
51
51
|
# Each writes its own fresh `.world` per `.hecksagon` rather than
|
|
@@ -54,7 +54,7 @@ module Hecks
|
|
|
54
54
|
# connection safely" turned out not to be true: `:postgres` sources
|
|
55
55
|
# one shared, permanent scratch database/schema this module itself
|
|
56
56
|
# owns (see `FUZZ_POSTGRES_DATABASE`'s own header); `:postgres_era`
|
|
57
|
-
# instead requires the
|
|
57
|
+
# instead requires the caller to supply (and own the lifecycle of) its
|
|
58
58
|
# own throwaway `database:`/`schema:`, since its only caller
|
|
59
59
|
# (`bin/qa_sweep --persistence-parity`) already has to manage a
|
|
60
60
|
# disposable database of its own, never a shared one this module could
|
|
@@ -62,7 +62,7 @@ module Hecks
|
|
|
62
62
|
module IsolatedBoot
|
|
63
63
|
module_function
|
|
64
64
|
|
|
65
|
-
# PRD 02 (docs/future-features.md) — `adapter:` picks
|
|
65
|
+
# PRD 02 (docs/future-features.md) — `adapter:` picks which real
|
|
66
66
|
# persistence this one ephemeral boot answers through, not just
|
|
67
67
|
# Memory. `:memory` is the original, zero-config behavior (every
|
|
68
68
|
# other caller in this codebase that doesn't pass `adapter:` gets
|
|
@@ -75,7 +75,7 @@ module Hecks
|
|
|
75
75
|
# so `bin/fuzz --adapter postgres` is meant to run with smaller
|
|
76
76
|
# seed/step counts than the Memory default, not as a like-for-like
|
|
77
77
|
# swap; see that flag's own comment.
|
|
78
|
-
# `database:`/`schema:` are
|
|
78
|
+
# `database:`/`schema:` are only meaningful for `adapter: :postgres_era`
|
|
79
79
|
# — see `rebind_to_postgres_era!`'s own header for why that mode takes
|
|
80
80
|
# caller-supplied connection identity instead of a hardcoded shared
|
|
81
81
|
# constant the way `:postgres` does. Every other adapter ignores both;
|
|
@@ -100,8 +100,8 @@ module Hecks
|
|
|
100
100
|
end
|
|
101
101
|
end
|
|
102
102
|
|
|
103
|
-
#
|
|
104
|
-
# symlink
|
|
103
|
+
# Symlinks are followed, not copied. `FileUtils.cp_r` reproduces a
|
|
104
|
+
# symlink as a symlink, and a relative one then points at nothing
|
|
105
105
|
# from a tmpdir — `lib/hecks/framework/bluebook/compliance
|
|
106
106
|
# .bluebook` is exactly that, a link to
|
|
107
107
|
# `examples/compliance/bluebook/compliance.bluebook`, so the whole
|
|
@@ -109,7 +109,7 @@ module Hecks
|
|
|
109
109
|
# path under /var/folders that had never existed. Nothing about the
|
|
110
110
|
# domain was wrong; the copy was.
|
|
111
111
|
#
|
|
112
|
-
# `FileUtils.cp` follows a symlink and copies its
|
|
112
|
+
# `FileUtils.cp` follows a symlink and copies its content, which is
|
|
113
113
|
# what an isolated boot wants: the copy has to stand alone, since
|
|
114
114
|
# rebind! rewrites files in it and must not reach back through a
|
|
115
115
|
# link into the real tree.
|
|
@@ -140,8 +140,8 @@ module Hecks
|
|
|
140
140
|
rewrite_bindings!(copy, "Memory")
|
|
141
141
|
strip_translations!(copy)
|
|
142
142
|
|
|
143
|
-
#
|
|
144
|
-
# stores a settings block under
|
|
143
|
+
# **The settings, not just the bind** — `WorldBuilder#method_missing`
|
|
144
|
+
# stores a settings block under both "verb:adapter" and the bare
|
|
145
145
|
# "verb" (world_builder.rb:32-33), so a bind rewritten to Memory
|
|
146
146
|
# still falls back to whatever adapter's settings were declared
|
|
147
147
|
# bare — Postgres's `database:`, which Memory does not take and
|
|
@@ -154,7 +154,7 @@ module Hecks
|
|
|
154
154
|
Dir.glob(File.join(copy, "**", "*.world")).each { |path| File.delete(path) }
|
|
155
155
|
end
|
|
156
156
|
|
|
157
|
-
#
|
|
157
|
+
# Same dance as memory, one adapter over — `Adapters::Sqlite#
|
|
158
158
|
# resolve_path` (adapters/driven/sqlite.rb) defaults to
|
|
159
159
|
# `data/<table>.db` under the boot's own root when no `database`
|
|
160
160
|
# setting is declared, which `data/` already being cleared makes a
|
|
@@ -168,24 +168,24 @@ module Hecks
|
|
|
168
168
|
Dir.glob(File.join(copy, "**", "*.world")).each { |path| File.delete(path) }
|
|
169
169
|
end
|
|
170
170
|
|
|
171
|
-
#
|
|
171
|
+
# **The expensive one** — Postgres has no zero-config default the way
|
|
172
172
|
# Sqlite/Memory do (`Adapters::Postgres.connect_for` refuses outright
|
|
173
173
|
# with no `database` setting), so dropping `.world` the way the
|
|
174
174
|
# other two do would just move the WiringError from "wrong adapter"
|
|
175
175
|
# to "no adapter." Every domain name this copy declares gets a
|
|
176
|
-
#
|
|
176
|
+
# fresh `.world` written for it instead — not a rewrite of whatever
|
|
177
177
|
# was there, a replacement, same reasoning `rewrite_bindings!`
|
|
178
178
|
# already applies to `.hecksagon`: this ephemeral boot owns every
|
|
179
179
|
# binding decision, nothing about a real deployment's own settings
|
|
180
180
|
# is relevant or safe to half-preserve here.
|
|
181
181
|
#
|
|
182
|
-
#
|
|
182
|
+
# One shared schema, dropped and recreated before every boot — not a
|
|
183
183
|
# fresh randomly-named one per call. `bin/fuzz` drives every
|
|
184
184
|
# ephemeral boot sequentially (one `IsolatedBoot.call` fully exits
|
|
185
185
|
# before the next begins — see that file's own single-threaded
|
|
186
186
|
# `while` loop), so nothing is ever concurrent here ; a fresh name
|
|
187
187
|
# every time would just leak schemas in `FUZZ_POSTGRES_DATABASE`
|
|
188
|
-
# forever with nothing to ever drop them. If a caller ever
|
|
188
|
+
# forever with nothing to ever drop them. If a caller ever does
|
|
189
189
|
# start running fuzz adapters concurrently, this needs to move to a
|
|
190
190
|
# process-unique schema name (`SecureRandom.hex` is already
|
|
191
191
|
# `require`d here for exactly that day) — flagged, not solved,
|
|
@@ -199,11 +199,11 @@ module Hecks
|
|
|
199
199
|
strip_translations!(copy)
|
|
200
200
|
ensure_fuzz_schema!
|
|
201
201
|
|
|
202
|
-
#
|
|
202
|
+
# One `.world` per directory a `.hecksagon` actually lives in, not
|
|
203
203
|
# one at `copy`'s own root — `Folder#load_domain` resolves a
|
|
204
|
-
#
|
|
204
|
+
# single `bluebook_directory` and globs `*.world` there, non-
|
|
205
205
|
# recursively (`Folder#load_each`); a domain can hold several
|
|
206
|
-
# `Hecks.hecksagon "<Name>" do ... end`
|
|
206
|
+
# `Hecks.hecksagon "<Name>" do ... end` sibling blocks in that one
|
|
207
207
|
# file (banking.hecksagon declares "Banking", "Governance", and
|
|
208
208
|
# "Identity" together), so every name found in one `.hecksagon`
|
|
209
209
|
# file gets bundled into one `.world` written beside it, not
|
|
@@ -225,7 +225,7 @@ module Hecks
|
|
|
225
225
|
end.join("\n"))
|
|
226
226
|
end
|
|
227
227
|
|
|
228
|
-
# Any
|
|
228
|
+
# Any pre-existing `.world` this copy shipped with (a real
|
|
229
229
|
# deployment's own connection string) is now redundant with — and
|
|
230
230
|
# would conflict with, `Registry#add_world`'s own header on
|
|
231
231
|
# loading the same domain name twice — the fresh one just
|
|
@@ -236,20 +236,20 @@ module Hecks
|
|
|
236
236
|
end
|
|
237
237
|
end
|
|
238
238
|
|
|
239
|
-
#
|
|
239
|
+
# Admin connection lives outside the tmp copy entirely — same as
|
|
240
240
|
# every other real-Postgres spec in this repo (`support/
|
|
241
241
|
# postgres_probe.rb`'s own header). Database created once per
|
|
242
242
|
# process and remembered (`@fuzz_database_ready` on this module's
|
|
243
243
|
# own singleton, the same memoization shape `PostgresProbe
|
|
244
244
|
# .available?` already uses) ; the schema inside it is dropped and
|
|
245
|
-
# recreated on
|
|
245
|
+
# recreated on every call, which is what actually isolates one
|
|
246
246
|
# ephemeral boot's data from the next.
|
|
247
247
|
def ensure_fuzz_schema!
|
|
248
|
-
# `Adapters::Postgres#initialize` opens
|
|
249
|
-
#
|
|
248
|
+
# `Adapters::Postgres#initialize` opens one real `PG::Connection`
|
|
249
|
+
# per aggregate and never explicitly closes it — fine for a
|
|
250
250
|
# process that boots once and runs, exactly what every other
|
|
251
251
|
# caller of this adapter is. A fuzz run boots dozens to hundreds
|
|
252
|
-
# of
|
|
252
|
+
# of ephemeral times in one process (every seed does at least a
|
|
253
253
|
# generate + a replay, `replay_is_deterministic` doubles that,
|
|
254
254
|
# shrinking multiplies it further), and `PG::Connection` only
|
|
255
255
|
# actually closes its socket when Ruby's GC finalizes the
|
|
@@ -261,7 +261,7 @@ module Hecks
|
|
|
261
261
|
# ephemeral boots, `PG::ConnectionBad: ... "too many clients
|
|
262
262
|
# already"`. A `GC.start` here — right before the next ephemeral
|
|
263
263
|
# boot's connections open, not on some timer — reclaims every
|
|
264
|
-
# connection the
|
|
264
|
+
# connection the previous boot's now-unreferenced adapters held,
|
|
265
265
|
# keeping the live count bounded regardless of run length. This
|
|
266
266
|
# is a real constraint on running Postgres in a loop, not
|
|
267
267
|
# something to route around by connecting less carefully.
|
|
@@ -278,7 +278,7 @@ module Hecks
|
|
|
278
278
|
end
|
|
279
279
|
|
|
280
280
|
db = PG.connect(dbname: FUZZ_POSTGRES_DATABASE)
|
|
281
|
-
#
|
|
281
|
+
# **Quiet on purpose** — same as `Adapters::Postgres.connect_for`'s
|
|
282
282
|
# own `SET client_min_messages`: a `DROP SCHEMA ... CASCADE` that
|
|
283
283
|
# actually has something to drop (every boot after the first)
|
|
284
284
|
# NOTICEs once per dropped object, which is the ordinary case
|
|
@@ -292,11 +292,11 @@ module Hecks
|
|
|
292
292
|
db.close
|
|
293
293
|
end
|
|
294
294
|
|
|
295
|
-
#
|
|
296
|
-
# are
|
|
295
|
+
# The adapter `:postgres` never touches — `Postgres` and `PostgresEra`
|
|
296
|
+
# are sibling, not interchangeable, adapters (see postgres_era.rb's
|
|
297
297
|
# own header: "the only one that declares the LINEAGE capability").
|
|
298
298
|
# PRD 02 (docs/prds/02-fuzzer-real-adapters.md) shipped `:postgres`
|
|
299
|
-
# and explicitly scoped `PostgresEra`
|
|
299
|
+
# and explicitly scoped `PostgresEra` out: "nothing here touches
|
|
300
300
|
# era/lineage machinery." That gap is real, not cosmetic —
|
|
301
301
|
# `examples/directory` (a `compute`/`rekey` translation edge, the
|
|
302
302
|
# one domain in this corpus that actually exercises PostgresEra-
|
|
@@ -305,20 +305,20 @@ module Hecks
|
|
|
305
305
|
# `:memory` included, structurally cannot reach it. This mode closes
|
|
306
306
|
# that — `bin/qa_sweep --persistence-parity` is its first caller.
|
|
307
307
|
#
|
|
308
|
-
#
|
|
308
|
+
# No shared constant database, unlike `:postgres` above — deliberate.
|
|
309
309
|
# `rebind_to_postgres!`'s own `FUZZ_POSTGRES_DATABASE`/`_SCHEMA` are
|
|
310
310
|
# module-level constants because `bin/fuzz --adapter postgres` is a
|
|
311
311
|
# general-purpose, run-it-anytime tool with no caller-tracked
|
|
312
312
|
# lifecycle of its own. This mode's only caller
|
|
313
313
|
# (`bin/qa_sweep --persistence-parity`) is different: it dispatches
|
|
314
314
|
# through `QualityControl::Target.claim!`'s own cross-process lock
|
|
315
|
-
# first (see that script's own header), so at most
|
|
315
|
+
# first (see that script's own header), so at most one sweep is ever
|
|
316
316
|
# touching a given target's own disposable database at a time — but
|
|
317
|
-
# the caller, not this module, is what
|
|
317
|
+
# the caller, not this module, is what owns that database's name and
|
|
318
318
|
# lifecycle (created, and genuinely dropped, by the caller itself),
|
|
319
|
-
# exactly the discipline `spec/
|
|
319
|
+
# exactly the discipline `spec/support/qa_sweep_all_fixture.rb`'s own header
|
|
320
320
|
# describes and this repository's persistence-parity work is
|
|
321
|
-
# required to follow. Accepting `database:`/`schema:` as
|
|
321
|
+
# required to follow. Accepting `database:`/`schema:` as required
|
|
322
322
|
# keyword arguments (never a fallback constant) is what keeps that
|
|
323
323
|
# ownership from silently drifting back onto this module the way
|
|
324
324
|
# `:postgres`'s own `FUZZ_POSTGRES_DATABASE` already has.
|
|
@@ -334,25 +334,25 @@ module Hecks
|
|
|
334
334
|
rewrite_bindings!(copy, "PostgresEra")
|
|
335
335
|
ensure_postgres_era_schema!(database: database, schema: schema)
|
|
336
336
|
|
|
337
|
-
#
|
|
337
|
+
# Same one-`.world`-per-`.hecksagon`-directory shape `rebind_to_
|
|
338
338
|
# postgres!` already uses, for the identical reason (`Folder#
|
|
339
339
|
# load_domain` globs `*.world` non-recursively) — see that
|
|
340
340
|
# method's own comment on `world_path` above. `PostgresEra`
|
|
341
|
-
# additionally takes `schema:` (postgres_era.rb's own "
|
|
342
|
-
#
|
|
341
|
+
# additionally takes `schema:` (postgres_era.rb's own "shared-
|
|
342
|
+
# instance isolation" comment): the caller-supplied throwaway
|
|
343
343
|
# schema is what actually isolates this one ephemeral boot from
|
|
344
344
|
# the next, the same job `FUZZ_POSTGRES_SCHEMA` does for `:postgres`
|
|
345
345
|
# — `connect_for` itself idempotently `CREATE SCHEMA IF NOT
|
|
346
|
-
# EXISTS`s it, so this method only ever needs to
|
|
346
|
+
# EXISTS`s it, so this method only ever needs to drop it first
|
|
347
347
|
# (in `ensure_postgres_era_schema!`, below) for the zero-history
|
|
348
348
|
# guarantee every other adapter mode already gives.
|
|
349
349
|
#
|
|
350
|
-
# `allow_superuser true` —
|
|
350
|
+
# `allow_superuser true` — on the record, on purpose. A bare
|
|
351
351
|
# `database` connects as the ambient Postgres user, and
|
|
352
352
|
# PostgresEra refuses to boot at all when that user is a
|
|
353
353
|
# superuser (its era write-fence is row-level security, which a
|
|
354
354
|
# superuser walks through — `Lineage#check_fence_applies!`,
|
|
355
|
-
# BUG#24). That refusal protects a
|
|
355
|
+
# BUG#24). That refusal protects a real ledger from an old
|
|
356
356
|
# checkout's stale writes; nothing here is one. This is an
|
|
357
357
|
# ephemeral boot into a throwaway schema the caller itself
|
|
358
358
|
# creates and drops, whose data no second checkout ever shares,
|
|
@@ -386,18 +386,18 @@ module Hecks
|
|
|
386
386
|
end
|
|
387
387
|
end
|
|
388
388
|
|
|
389
|
-
#
|
|
389
|
+
# The zero-history guarantee for this mode — `DROP SCHEMA ... CASCADE`
|
|
390
390
|
# before every ephemeral boot, mirroring `ensure_fuzz_schema!` above
|
|
391
391
|
# (same `GC.start`-before-connecting fix for the identical
|
|
392
392
|
# `max_connections` exhaustion that method's own comment documents —
|
|
393
393
|
# `PostgresEra` opens real `PG::Connection`s exactly like `Postgres`
|
|
394
|
-
# does, same unclosed-until-GC'd lifetime). The
|
|
394
|
+
# does, same unclosed-until-GC'd lifetime). The database itself is
|
|
395
395
|
# created here too, idempotently (`CREATE DATABASE IF NOT EXISTS`
|
|
396
396
|
# has no Postgres spelling, hence the existence check) — but never
|
|
397
|
-
#
|
|
397
|
+
# dropped here: this module creates it once per process because
|
|
398
398
|
# `Hecks.boot` needs it to exist before `PostgresEra.connect_for`'s
|
|
399
399
|
# own `PG.connect(dbname: ...)` can succeed at all, but dropping it
|
|
400
|
-
# again is the
|
|
400
|
+
# again is the caller's own job (its name and lifecycle belong to
|
|
401
401
|
# the caller — see `rebind_to_postgres_era!`'s own header), not
|
|
402
402
|
# something this per-ephemeral-boot helper should ever do mid-sweep.
|
|
403
403
|
def ensure_postgres_era_schema!(database:, schema:)
|
|
@@ -417,7 +417,7 @@ module Hecks
|
|
|
417
417
|
db.close
|
|
418
418
|
end
|
|
419
419
|
|
|
420
|
-
#
|
|
420
|
+
# The shared rewrite — factored out of `rebind_to_memory!` when
|
|
421
421
|
# Sqlite/Postgres modes needed the identical `.hecksagon` surgery
|
|
422
422
|
# with only the target adapter name differing. `persisted_by`/
|
|
423
423
|
# `projected_by` can be spelled two ways: aggregate-scoped
|
|
@@ -431,41 +431,41 @@ module Hecks
|
|
|
431
431
|
# #read_repository` already falls back to the authoritative
|
|
432
432
|
# repository when none exists, so a read model this ephemeral copy
|
|
433
433
|
# never wires is simply unread, not broken.
|
|
434
|
-
# A `compute`/`rekey`
|
|
435
|
-
#
|
|
434
|
+
# A `compute`/`rekey` translation edge refuses to boot at all under
|
|
435
|
+
# any non-lineage-capable adapter — found live, wiring this very
|
|
436
436
|
# mode up against `examples/directory`: `Runtime::EraCheck
|
|
437
|
-
# .check_compute_rules!` (era_check.rb) runs
|
|
437
|
+
# .check_compute_rules!` (era_check.rb) runs unconditionally for
|
|
438
438
|
# every loaded bluebook once the era plugin is loaded at all
|
|
439
439
|
# (`bin/qa_sweep`'s own top-of-file `require "hecks/ports/
|
|
440
440
|
# persistence/plugins/era"`, needed for the ledger's own
|
|
441
441
|
# PostgresEra-bound aggregates), and refuses outright — "compute
|
|
442
|
-
# rules require the Postgres adapter" — for
|
|
443
|
-
# lineage carries a `compute` rule and whose
|
|
442
|
+
# rules require the Postgres adapter" — for any aggregate whose
|
|
443
|
+
# lineage carries a `compute` rule and whose bound adapter is not
|
|
444
444
|
# lineage-capable. `PostgresEra` is the only adapter that answers
|
|
445
445
|
# `lineage_capable? == true` (postgres_era.rb's own `self.
|
|
446
|
-
# lineage_capable? = true`) — plain `Postgres` does
|
|
446
|
+
# lineage_capable? = true`) — plain `Postgres` does not, so this
|
|
447
447
|
# refusal was already real for `:postgres`/`:sqlite`/`:memory`
|
|
448
448
|
# alike, for any domain with a translation edge, before this
|
|
449
449
|
# mode's own `:postgres_era` ever existed. This is very likely the
|
|
450
|
-
#
|
|
450
|
+
# mechanical reason `examples/directory` had to be shelved out of
|
|
451
451
|
# `hecks_qa`'s own rotation in the first place — not merely "less
|
|
452
|
-
# interesting to fuzz on Memory," but "cannot
|
|
452
|
+
# interesting to fuzz on Memory," but "cannot boot on Memory at
|
|
453
453
|
# all" once the era plugin is loaded, which every real
|
|
454
454
|
# `bin/qa_sweep` invocation already does.
|
|
455
455
|
#
|
|
456
|
-
#
|
|
456
|
+
# The fix is to drop the edge, not to chase the refusal — an
|
|
457
457
|
# ephemeral, zero-history replay boot (every mode `IsolatedBoot`
|
|
458
458
|
# offers) never has a pre-existing era-1 row to translate in the
|
|
459
|
-
# first place, so the translation edge is
|
|
459
|
+
# first place, so the translation edge is irrelevant to anything a
|
|
460
460
|
# fuzz/replay run actually exercises (ordinary command dispatch
|
|
461
|
-
# against a fresh boot) — it only ever matters at
|
|
461
|
+
# against a fresh boot) — it only ever matters at mint time,
|
|
462
462
|
# against a real, pre-existing database
|
|
463
463
|
# (`PostgresEra::LineageManager.check!`, a wholly separate,
|
|
464
464
|
# human-approved path this harness was never meant to reach).
|
|
465
465
|
# Dropping it here is exactly the same move `rebind_to_memory!`
|
|
466
466
|
# already makes for `.world` (irrelevant/conflicting settings for
|
|
467
467
|
# an ephemeral boot, deleted outright) — never called for
|
|
468
|
-
# `:postgres_era` itself, where the bound adapter genuinely
|
|
468
|
+
# `:postgres_era` itself, where the bound adapter genuinely is
|
|
469
469
|
# lineage-capable and the edge causes no refusal to begin with.
|
|
470
470
|
def strip_translations!(copy)
|
|
471
471
|
Dir.glob(File.join(copy, "**", "translations", "*.bluebook")).each { |path| File.delete(path) }
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
module Hecks
|
|
2
|
+
module Fuzzing
|
|
3
|
+
# The one declared set of FIELDS that leave a comparison — partition,
|
|
4
|
+
# not filter. Every comparison in lib/hecks/fuzzing that drops a field
|
|
5
|
+
# before comparing two histories names a group here instead of writing
|
|
6
|
+
# its own literal `except(...)` list, so the reason for dropping a field
|
|
7
|
+
# is written exactly once, beside the field.
|
|
8
|
+
#
|
|
9
|
+
# Two specs hold this set honest (spec/fuzzing/nondeterministic_spec.rb):
|
|
10
|
+
# a literal except-list naming any of these fields anywhere under
|
|
11
|
+
# lib/hecks/fuzzing fails the build (the tolerance must be declared
|
|
12
|
+
# here, not re-derived at a call site), and every declared field must
|
|
13
|
+
# actually be produced where its group says — a field nothing emits any
|
|
14
|
+
# more is a stale tolerance and fails too.
|
|
15
|
+
#
|
|
16
|
+
# Groups are keyed by the shape the field rides on, since the same name
|
|
17
|
+
# can be compared on one surface and dropped on another:
|
|
18
|
+
#
|
|
19
|
+
# - `query_row` — one entry of `Replay.call`'s `:queries`.
|
|
20
|
+
# - `outbox_row` — one row of an `:outbox_traces` entry's `:rows`.
|
|
21
|
+
# - `event` — an event hash in full `Event#to_h` form (an outbox
|
|
22
|
+
# row's `:event`, or a Rust binary's `events` entries,
|
|
23
|
+
# string-keyed there).
|
|
24
|
+
# - `history` — the top-level `Replay.call` history hash itself.
|
|
25
|
+
module Nondeterministic
|
|
26
|
+
FIELDS = {
|
|
27
|
+
query_row: {
|
|
28
|
+
instances_at: "a full state snapshot taken for the query oracle's own use " \
|
|
29
|
+
"(Properties::Querying); the same state is already compared as `instances`, " \
|
|
30
|
+
"so keeping it would re-report one instances divergence under every query step"
|
|
31
|
+
}.freeze,
|
|
32
|
+
outbox_row: {
|
|
33
|
+
event_uid: "`Runtime::Outbox::Fanout#rows_for`'s own SecureRandom.uuid, minted fresh per " \
|
|
34
|
+
"enqueue and kept off `Event#to_h` — relay bookkeeping, never something the " \
|
|
35
|
+
"domain produced, so two otherwise-identical replays carry two different uuids",
|
|
36
|
+
delivery_id: "`\"\#{event_uid}/\#{consumer}\"` — inherits event_uid's per-enqueue uuid"
|
|
37
|
+
}.freeze,
|
|
38
|
+
event: {
|
|
39
|
+
occurred_at: "a wall-clock read; never reproducible byte-for-byte between two independent " \
|
|
40
|
+
"runs (Replay's own projected `:events` leave it off for the same reason)"
|
|
41
|
+
}.freeze,
|
|
42
|
+
history: {
|
|
43
|
+
bluebook: "a live IR object (the first-loaded bluebook) handed to properties — an object " \
|
|
44
|
+
"identity, not a value, so two boots never compare equal on it",
|
|
45
|
+
bluebooks: "the full live IR map, keyed by domain — object identities, same as `bluebook`"
|
|
46
|
+
}.freeze
|
|
47
|
+
}.freeze
|
|
48
|
+
|
|
49
|
+
module_function
|
|
50
|
+
|
|
51
|
+
def names(group)
|
|
52
|
+
FIELDS.fetch(group).keys
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Every declared name, across every group.
|
|
56
|
+
def all_names
|
|
57
|
+
FIELDS.values.flat_map(&:keys).uniq
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# `hash` without `group`'s fields — symbol keys, the shape every
|
|
61
|
+
# Ruby-side history carries.
|
|
62
|
+
def strip(hash, group)
|
|
63
|
+
hash.except(*names(group))
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
require "json"
|
|
2
2
|
require_relative "replay"
|
|
3
|
+
require_relative "nondeterministic"
|
|
3
4
|
|
|
4
5
|
module Hecks
|
|
5
6
|
module Fuzzing
|
|
6
|
-
#
|
|
7
|
-
# compares two
|
|
8
|
-
# Rust kernel) against the
|
|
9
|
-
# `SequenceGenerator`'s own header: sequence
|
|
7
|
+
# **The second differential axis** — bin/qa_sweep's own `diff_ruby_vs_rust`
|
|
8
|
+
# compares two different engines (the Ruby interpreter vs the compiled
|
|
9
|
+
# Rust kernel) against the same persistence (Memory, always — see
|
|
10
|
+
# `SequenceGenerator`'s own header: sequence generation stays
|
|
10
11
|
# Memory-only, and `IsolatedBoot`'s own header explains why every
|
|
11
12
|
# existing fuzz/replay path structurally cannot reach a real Postgres-
|
|
12
13
|
# bound domain's own SQL compilation). This module compares the
|
|
13
|
-
# opposite pairing: the
|
|
14
|
+
# opposite pairing: the same one Ruby engine, against two different
|
|
14
15
|
# persistences — Memory (the reference, exactly as fast and as
|
|
15
16
|
# deterministic as every other fuzz check) and a real, disposable
|
|
16
17
|
# `PostgresEra` database (the actual SQL compute/rekey/query-pushdown
|
|
@@ -26,20 +27,20 @@ module Hecks
|
|
|
26
27
|
# Memory, unconditionally, no matter what `directory.world` itself
|
|
27
28
|
# declares.
|
|
28
29
|
#
|
|
29
|
-
#
|
|
30
|
+
# Not a replacement for `bin/qa_sweep`'s own Ruby-vs-Rust differential
|
|
30
31
|
# mode — a genuinely separate axis, opt-in (`--persistence-parity`),
|
|
31
32
|
# because this one pays for a real `PG.connect` and real SQL per
|
|
32
33
|
# dispatch where Memory-vs-Rust pays for neither. See `bin/qa_sweep`'s
|
|
33
34
|
# own `--persistence-parity` handling for the seed-count dial that
|
|
34
35
|
# keeps that cost bounded.
|
|
35
36
|
#
|
|
36
|
-
#
|
|
37
|
+
# Generalized to `left:`/`right:` — originally hardcoded to Memory vs
|
|
37
38
|
# PostgresEra (the only pairing that existed), now any two of
|
|
38
39
|
# `IsolatedBoot`'s own adapter symbols (`:memory`, `:sqlite`,
|
|
39
40
|
# `:postgres`, `:postgres_era`). Defaults preserve the original
|
|
40
41
|
# pairing exactly, so every existing caller (this file's own spec,
|
|
41
42
|
# `bin/qa_sweep`'s `--persistence-parity`) is unchanged. The second
|
|
42
|
-
# pairing this generalization exists
|
|
43
|
+
# pairing this generalization exists for is Memory vs SQLite
|
|
43
44
|
# (`QualityControlDials::ADAPTER_PARITY_PAIRS`, `bin/qa_sweep`'s own
|
|
44
45
|
# `adapter_parity_sqlite` mode) — `:sqlite` is nearly as cheap as
|
|
45
46
|
# Memory itself (`IsolatedBoot#rebind_to_sqlite!`'s own header: an
|
|
@@ -48,7 +49,7 @@ module Hecks
|
|
|
48
49
|
# loop instead of needing a deferred wave of its own the way
|
|
49
50
|
# PostgresEra does.
|
|
50
51
|
#
|
|
51
|
-
# `database:`/`schema:` —
|
|
52
|
+
# `database:`/`schema:` — required only when `:postgres_era` is one of
|
|
52
53
|
# the two adapters (the caller — today, only `bin/qa_sweep` — owns the
|
|
53
54
|
# disposable database's whole lifecycle: created before the sweep,
|
|
54
55
|
# dropped after — see that script's own comment, and the discipline
|
|
@@ -58,21 +59,21 @@ module Hecks
|
|
|
58
59
|
module PersistenceParity
|
|
59
60
|
module_function
|
|
60
61
|
|
|
61
|
-
#
|
|
62
|
+
# The same six fields `bin/qa_sweep`'s own `diff_ruby_vs_rust`
|
|
62
63
|
# compares (its own comment: "instances, events, refusals, queries,
|
|
63
64
|
# sagas, reactions") — deliberately the identical set, so a report
|
|
64
65
|
# this mode produces reads exactly like the sibling mode's own,
|
|
65
66
|
# differing only in which two things were compared, not in what
|
|
66
67
|
# "found something" means.
|
|
67
68
|
#
|
|
68
|
-
#
|
|
69
|
-
# method reduces Rust's
|
|
69
|
+
# Simpler normalization than `diff_ruby_vs_rust`, on purpose — that
|
|
70
|
+
# method reduces Rust's own JSON-over-stdout output to "wire
|
|
70
71
|
# precision" and filters known Ruby/Rust structural gaps, because
|
|
71
72
|
# it is comparing two genuinely different engines that are allowed
|
|
72
73
|
# to differ in already-catalogued, understood ways. Both sides here
|
|
73
|
-
# are the
|
|
74
|
+
# are the same Ruby engine (`Replay.call`, called twice, adapter
|
|
74
75
|
# only) — there is no second engine's own known-gap catalogue to
|
|
75
|
-
# filter against, so any real difference
|
|
76
|
+
# filter against, so any real difference is the finding. Both
|
|
76
77
|
# results still round-trip through `JSON.generate`/`JSON.parse`
|
|
77
78
|
# before comparing, matching `diff_ruby_vs_rust`'s own discipline —
|
|
78
79
|
# not because either side needs a wire-format reduction, but so
|
|
@@ -128,14 +129,11 @@ module Hecks
|
|
|
128
129
|
[{ field: "refusals", left => l, right => r }]
|
|
129
130
|
end
|
|
130
131
|
|
|
131
|
-
# `
|
|
132
|
-
#
|
|
133
|
-
#
|
|
134
|
-
# already covered by `diff_instances` above, and would make every
|
|
135
|
-
# query-step entry re-litigate the SAME instances divergence a
|
|
136
|
-
# second time under a different field name.
|
|
132
|
+
# `Nondeterministic`'s `query_row` group dropped from every entry —
|
|
133
|
+
# the same group `Differential.diff` drops, for the reason declared
|
|
134
|
+
# there (already covered by `diff_instances` above).
|
|
137
135
|
def diff_queries(left_result, right_result, left, right)
|
|
138
|
-
strip = ->(rows) { rows.map { |row|
|
|
136
|
+
strip = ->(rows) { rows.map { |row| Nondeterministic.strip(row, :query_row) } }
|
|
139
137
|
l = as_json(strip.call(left_result[:queries]))
|
|
140
138
|
r = as_json(strip.call(right_result[:queries]))
|
|
141
139
|
return [] if l == r
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
module Hecks
|
|
2
2
|
module Fuzzing
|
|
3
3
|
module Properties
|
|
4
|
-
#
|
|
4
|
+
# Angle-9 — `corrects` (retroactive correction) had exactly one
|
|
5
5
|
# declaration anywhere in the corpus (`examples/banking/bluebook/
|
|
6
6
|
# deposit_accounts.bluebook:353`, aggregate-level) and no property
|
|
7
7
|
# anywhere in this file ever checked it, and no `FEATURE_COVERAGE`
|
|
@@ -9,7 +9,7 @@ module Hecks
|
|
|
9
9
|
# stress_domains/corrections` gives it its first real coverage; see
|
|
10
10
|
# that domain's own NOTES.md for what it found (an entity-level
|
|
11
11
|
# `corrects` crashes Ruby at dispatch outright, and Rust's own
|
|
12
|
-
# generated code has
|
|
12
|
+
# generated code has no admissibility check for it at all — neither
|
|
13
13
|
# engine can be compared on the untested combination this property
|
|
14
14
|
# was written to watch, which is itself the headline finding).
|
|
15
15
|
module Corrections
|
|
@@ -19,30 +19,30 @@ module Hecks
|
|
|
19
19
|
# entities`, below, is the same recursive walk `SequenceGenerator::
|
|
20
20
|
# Catalog#each_entity_chain` already uses, for the identical reason:
|
|
21
21
|
# `Aggregate#entities`/`Entity#entities` nest, ADR 0026, S17). For
|
|
22
|
-
# every such command, every event
|
|
22
|
+
# every such command, every event this history actually recorded
|
|
23
23
|
# under one of the command's own `emits` names must have an event
|
|
24
24
|
# named by the command's own `corrects` target — same aggregate-
|
|
25
|
-
# qualified name, same id — appearing
|
|
25
|
+
# qualified name, same id — appearing strictly earlier in the same
|
|
26
26
|
# history.
|
|
27
27
|
#
|
|
28
28
|
# `history[:events]` is already in occurrence order (`Replay.call`'s
|
|
29
29
|
# own `runtime.events`, appended as each step dispatches) — "earlier"
|
|
30
30
|
# is therefore "earlier in this array," no timestamp comparison
|
|
31
31
|
# needed, and no per-step attribution back to which command produced
|
|
32
|
-
# which event is needed either: an event's own declared
|
|
32
|
+
# which event is needed either: an event's own declared name already
|
|
33
33
|
# identifies the one command in its aggregate that can produce it
|
|
34
34
|
# (`AggregateBuilder::Sealing#seal_correction_targets`'s own
|
|
35
35
|
# `emitted_by` hash reads the identical fact, one level shallower).
|
|
36
36
|
#
|
|
37
|
-
#
|
|
38
|
-
#
|
|
37
|
+
# Why this cannot be `GUARANTEED_BY_CONSTRUCTION` the way the
|
|
38
|
+
# aggregate-level case almost is: `CommandRules::Admissibility#
|
|
39
39
|
# enforce_correction_target` (the dispatch-time check) and
|
|
40
40
|
# `AggregateBuilder::Sealing#seal_correction_targets` (the build-time
|
|
41
|
-
# check) both exist
|
|
41
|
+
# check) both exist only for an aggregate-level `corrects` —
|
|
42
42
|
# `EntityInterpreter#step_enforce_givens` never calls the former at
|
|
43
43
|
# all, and the latter walks only `@commands` (the aggregate's own
|
|
44
44
|
# top-level list), never `@entities`. An entity-level `corrects`
|
|
45
|
-
# mutation is invisible to
|
|
45
|
+
# mutation is invisible to both doors today — this property is the
|
|
46
46
|
# only thing anywhere, on either engine, that would ever catch one
|
|
47
47
|
# going wrong.
|
|
48
48
|
def corrections_reference_an_emitted_event(history)
|