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,6 +1,6 @@
|
|
|
1
1
|
module Hecks
|
|
2
2
|
module Fuzzing
|
|
3
|
-
# A value that is
|
|
3
|
+
# A value that is deliberately the wrong shape for the attribute it is handed
|
|
4
4
|
# to — the sibling of ValueGenerator, which only ever builds values that satisfy
|
|
5
5
|
# a declared type.
|
|
6
6
|
#
|
|
@@ -14,7 +14,7 @@ module Hecks
|
|
|
14
14
|
# Rendering's whole story), and no generated value
|
|
15
15
|
# could ever have produced it.
|
|
16
16
|
#
|
|
17
|
-
# These are expected to be
|
|
17
|
+
# These are expected to be refused, and that is the point : a refusal is an
|
|
18
18
|
# answer, and its wording is pinned byte-for-byte. The bugs live in
|
|
19
19
|
# the sentence, not in the happy path.
|
|
20
20
|
module InvalidValueGenerator
|
|
@@ -32,15 +32,32 @@ module Hecks
|
|
|
32
32
|
null
|
|
33
33
|
].freeze
|
|
34
34
|
|
|
35
|
+
# Builds one value of the wrong shape for `attribute`, chosen from the
|
|
36
|
+
# confusions that are actually meaningful for its declared type.
|
|
37
|
+
#
|
|
38
|
+
# @param attribute [Bluebook::Attribute] the attribute the corrupted value is
|
|
39
|
+
# handed to
|
|
40
|
+
# @param aggregate [Bluebook::Aggregate] the aggregate that declares
|
|
41
|
+
# `attribute` (and any value objects it references)
|
|
42
|
+
# @param random [Random] the seeded RNG to draw the kind and value from
|
|
43
|
+
# @return [Object] a value of the wrong shape for `attribute` — an Integer,
|
|
44
|
+
# Float, String, Array, Hash, or boolean, depending on the kind drawn
|
|
35
45
|
def corrupt(attribute, aggregate, random:)
|
|
36
46
|
kind = kinds_for(attribute, aggregate).sample(random: random)
|
|
37
47
|
build(kind, attribute, aggregate, random: random)
|
|
38
48
|
end
|
|
39
49
|
|
|
40
|
-
# Only the confusions that
|
|
50
|
+
# Only the confusions that mean anything for this attribute. Offering
|
|
41
51
|
# `scalar_for_object` for a plain Integer would just be a second spelling of
|
|
42
52
|
# `numeral_string`, and a kind that cannot be wrong for the attribute it is
|
|
43
53
|
# handed teaches the corpus nothing.
|
|
54
|
+
#
|
|
55
|
+
# @param attribute [Bluebook::Attribute] the attribute to find applicable
|
|
56
|
+
# confusions for
|
|
57
|
+
# @param aggregate [Bluebook::Aggregate] the aggregate that declares
|
|
58
|
+
# `attribute` (and any value objects it references)
|
|
59
|
+
# @return [Array<Symbol>] the subset of `KINDS` that are a real confusion for
|
|
60
|
+
# `attribute`'s declared type
|
|
44
61
|
def kinds_for(attribute, aggregate)
|
|
45
62
|
value_object = aggregate.value_object(attribute.type.to_s)
|
|
46
63
|
return %i[scalar_for_object array_for_scalar null] if value_object
|
|
@@ -49,6 +66,16 @@ module Hecks
|
|
|
49
66
|
%i[object_for_scalar array_for_scalar boolean_for_string null]
|
|
50
67
|
end
|
|
51
68
|
|
|
69
|
+
# Builds the actual corrupted value for one already-chosen `kind`.
|
|
70
|
+
#
|
|
71
|
+
# @param kind [Symbol] one of `KINDS`
|
|
72
|
+
# @param attribute [Bluebook::Attribute] the attribute the value will be
|
|
73
|
+
# handed to
|
|
74
|
+
# @param aggregate [Bluebook::Aggregate] the aggregate that declares
|
|
75
|
+
# `attribute` (and any value objects it references)
|
|
76
|
+
# @param random [Random] the seeded RNG to draw the value from
|
|
77
|
+
# @return [Object, nil] the corrupted value for `kind`; `nil` if `kind` is not
|
|
78
|
+
# one `KINDS` declares
|
|
52
79
|
def build(kind, attribute, aggregate, random:)
|
|
53
80
|
case kind
|
|
54
81
|
when :object_for_scalar then { "cents" => random.rand(1..1000) }
|
|
@@ -60,16 +87,25 @@ module Hecks
|
|
|
60
87
|
end
|
|
61
88
|
end
|
|
62
89
|
|
|
63
|
-
# A bare scalar where a value object is declared. A
|
|
90
|
+
# A bare scalar where a value object is declared. A single-field value object
|
|
64
91
|
# legitimately accepts one (that is the standing-in rule every domain relies
|
|
65
|
-
# on), so the interesting case is a value object with
|
|
92
|
+
# on), so the interesting case is a value object with several fields, where a
|
|
66
93
|
# scalar cannot stand for anything and the refusal has to say so.
|
|
94
|
+
# @param attribute [Bluebook::Attribute] the value-object-typed attribute to
|
|
95
|
+
# build a bare scalar for
|
|
96
|
+
# @param aggregate [Bluebook::Aggregate] the aggregate that declares
|
|
97
|
+
# `attribute`'s value object
|
|
98
|
+
# @param random [Random] accepted for a uniform call signature with `#build`;
|
|
99
|
+
# this method draws nothing from it
|
|
100
|
+
# @return [String, Hash] `"a bare scalar"` when a scalar would legitimately be
|
|
101
|
+
# accepted (a single-field value object); otherwise a Hash corrupting the
|
|
102
|
+
# sole field's own type instead
|
|
67
103
|
def scalar_for(attribute, aggregate, random:)
|
|
68
104
|
value_object = aggregate.value_object(attribute.type.to_s)
|
|
69
105
|
sole = value_object&.sole_attribute
|
|
70
106
|
return "a bare scalar" unless sole
|
|
71
107
|
|
|
72
|
-
# One field, so a scalar is legal — corrupt the
|
|
108
|
+
# One field, so a scalar is legal — corrupt the field's own type instead,
|
|
73
109
|
# which is still a shape the attribute cannot accept.
|
|
74
110
|
{ sole.name.to_s => ["nested", "array"] }
|
|
75
111
|
end
|
|
@@ -77,6 +113,9 @@ module Hecks
|
|
|
77
113
|
# An attribute the command never declared. `refuse_unknown_arguments` is a
|
|
78
114
|
# real dispatch step (Vocabulary::AggregateDispatchOrder), and nothing
|
|
79
115
|
# generated had ever exercised it.
|
|
116
|
+
# @param random [Random] the seeded RNG to draw the name and value from
|
|
117
|
+
# @return [Array(String, String)] a `[name, value]` pair naming an argument
|
|
118
|
+
# no command declares
|
|
80
119
|
def undeclared_argument(random:)
|
|
81
120
|
name = %w[colour flavour rank note].sample(random: random)
|
|
82
121
|
[name, %w[red loud third scribbled].sample(random: random)]
|
|
@@ -4,37 +4,41 @@ 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
|
+
#
|
|
9
|
+
# ## Why rebind at all
|
|
8
10
|
#
|
|
9
11
|
# Fuzzing/replay copies a domain to a tmpdir and boots from there
|
|
10
12
|
# specifically to get zero-history state — `rm_rf`ing the copy's own
|
|
11
13
|
# `data/` achieves that for a file-based adapter (Memory,
|
|
12
|
-
# SqlitePersistence, Heki), because copying the
|
|
14
|
+
# SqlitePersistence, Heki), because copying the directory copies the
|
|
13
15
|
# store. It achieves nothing for an adapter that lives outside the
|
|
14
16
|
# copied directory entirely — Postgres, named by a fixed connection
|
|
15
17
|
# string in `.world` (examples/pizzas/bluebook/pizzas.world, for
|
|
16
18
|
# instance). Copying the directory does not copy or isolate the
|
|
17
|
-
#
|
|
19
|
+
# database, so a "fresh" boot against a Postgres-bound domain would
|
|
18
20
|
# still see every record any other run, ever, wrote to it.
|
|
19
21
|
#
|
|
20
22
|
# So every `.hecksagon` in the copy gets its persistence binding
|
|
21
23
|
# rewritten to Memory before booting, and any `projected_by` bind
|
|
22
24
|
# dropped outright (optional — `Registry#read_repository` already
|
|
23
25
|
# falls back to the authoritative repository when none exists). The
|
|
24
|
-
# domain's own rules and shape are untouched ; only
|
|
26
|
+
# domain's own rules and shape are untouched ; only which adapter this
|
|
25
27
|
# one ephemeral copy answers through changes. What the domain is
|
|
26
28
|
# bound to for real deployment is never touched — only this tmp copy.
|
|
27
29
|
#
|
|
28
|
-
# `adapter:`
|
|
29
|
-
#
|
|
30
|
-
#
|
|
31
|
-
#
|
|
32
|
-
#
|
|
33
|
-
#
|
|
34
|
-
#
|
|
35
|
-
#
|
|
36
|
-
#
|
|
37
|
-
#
|
|
30
|
+
# ## `adapter:` modes
|
|
31
|
+
#
|
|
32
|
+
# `adapter:` (PRD 02) picks which real persistence one ephemeral boot
|
|
33
|
+
# answers through. `:memory` is the default and the only mode every
|
|
34
|
+
# caller that passes no `adapter:` still gets. `:sqlite` rebinds to
|
|
35
|
+
# the real SQLite adapter instead of the in-memory one: 15 declared
|
|
36
|
+
# properties (properties.rb) and every fuzz/replay run only ever
|
|
37
|
+
# exercised Memory's own hand-written repository until this mode
|
|
38
|
+
# existed, and `spec/adapters/query_agreement_spec.rb` already found 4
|
|
39
|
+
# shipped query bugs from exactly that comparison, on a fixed corpus
|
|
40
|
+
# far smaller than what the fuzzer generates. `:sqlite` rewrites to
|
|
41
|
+
# `"SqlitePersistence"` — the name
|
|
38
42
|
# `lib/hecks/adapters/driven/sqlite.adapter` actually registers under
|
|
39
43
|
# (`Sqlite` is the class; `SqlitePersistence` is a thin subclass
|
|
40
44
|
# that's the one real port binding names) — and needs no `.world`
|
|
@@ -44,25 +48,23 @@ module Hecks
|
|
|
44
48
|
# `root:`, which `Hecks.boot(copy)` passes as this ephemeral copy's
|
|
45
49
|
# own directory — a fresh, empty `data/` per run, exactly like
|
|
46
50
|
# Memory's own zero-history guarantee, just backed by a real SQLite
|
|
47
|
-
# file instead of a Hash.
|
|
48
|
-
#
|
|
49
|
-
#
|
|
50
|
-
#
|
|
51
|
-
#
|
|
52
|
-
#
|
|
53
|
-
#
|
|
54
|
-
#
|
|
55
|
-
#
|
|
56
|
-
#
|
|
57
|
-
# instead requires the CALLER to supply (and own the lifecycle of) its
|
|
58
|
-
# own throwaway `database:`/`schema:`, since its only caller
|
|
51
|
+
# file instead of a Hash. `:postgres` (PRD 02, docs/prds/02-fuzzer-
|
|
52
|
+
# real-adapters.md) and `:postgres_era` (`rebind_to_postgres_era!`'s
|
|
53
|
+
# own header, below) are the two real-Postgres modes. Each writes its
|
|
54
|
+
# own fresh `.world` per `.hecksagon` rather than relying on the
|
|
55
|
+
# zero-config default Sqlite/Memory get, and each needs a real,
|
|
56
|
+
# reachable Postgres server: `:postgres` sources one shared,
|
|
57
|
+
# permanent scratch database/schema this module itself owns (see
|
|
58
|
+
# `FUZZ_POSTGRES_DATABASE`'s own header); `:postgres_era` instead
|
|
59
|
+
# requires the caller to supply (and own the lifecycle of) its own
|
|
60
|
+
# throwaway `database:`/`schema:`, since its only caller
|
|
59
61
|
# (`bin/qa_sweep --persistence-parity`) already has to manage a
|
|
60
62
|
# disposable database of its own, never a shared one this module could
|
|
61
63
|
# safely default to.
|
|
62
64
|
module IsolatedBoot
|
|
63
65
|
module_function
|
|
64
66
|
|
|
65
|
-
# PRD 02 (docs/future-features.md) — `adapter:` picks
|
|
67
|
+
# PRD 02 (docs/future-features.md) — `adapter:` picks which real
|
|
66
68
|
# persistence this one ephemeral boot answers through, not just
|
|
67
69
|
# Memory. `:memory` is the original, zero-config behavior (every
|
|
68
70
|
# other caller in this codebase that doesn't pass `adapter:` gets
|
|
@@ -75,7 +77,7 @@ module Hecks
|
|
|
75
77
|
# so `bin/fuzz --adapter postgres` is meant to run with smaller
|
|
76
78
|
# seed/step counts than the Memory default, not as a like-for-like
|
|
77
79
|
# swap; see that flag's own comment.
|
|
78
|
-
# `database:`/`schema:` are
|
|
80
|
+
# `database:`/`schema:` are only meaningful for `adapter: :postgres_era`
|
|
79
81
|
# — see `rebind_to_postgres_era!`'s own header for why that mode takes
|
|
80
82
|
# caller-supplied connection identity instead of a hardcoded shared
|
|
81
83
|
# constant the way `:postgres` does. Every other adapter ignores both;
|
|
@@ -83,6 +85,20 @@ module Hecks
|
|
|
83
85
|
# signature per adapter) keeps `SequenceGenerator`/`Replay`'s own
|
|
84
86
|
# single passthrough (`adapter:`, now joined by these two) uniform
|
|
85
87
|
# across all four modes.
|
|
88
|
+
#
|
|
89
|
+
# @param domain_path [String] path to the real domain directory to copy and isolate
|
|
90
|
+
# @param adapter [Symbol] which persistence the copy answers through: `:memory`
|
|
91
|
+
# (default), `:sqlite`, `:postgres`, or `:postgres_era`
|
|
92
|
+
# @param database [String, nil] the database name, required only for
|
|
93
|
+
# `adapter: :postgres_era`; ignored by every other adapter
|
|
94
|
+
# @param schema [String, nil] the schema name, required only for
|
|
95
|
+
# `adapter: :postgres_era`; ignored by every other adapter
|
|
96
|
+
# @yield [String] the freshly rebound copy's root directory, ready to boot
|
|
97
|
+
# @yieldreturn [Object] anything; becomes this method's own return value
|
|
98
|
+
# @return [Object] whatever the given block returns
|
|
99
|
+
# @raise [ArgumentError] if `adapter` is not `:memory`, `:sqlite`, `:postgres`, or
|
|
100
|
+
# `:postgres_era`, or if `adapter: :postgres_era` is given without both
|
|
101
|
+
# `database:` and `schema:`
|
|
86
102
|
def call(domain_path, adapter: :memory, database: nil, schema: nil)
|
|
87
103
|
Dir.mktmpdir("hecks-fuzz") do |tmp|
|
|
88
104
|
copy = File.join(tmp, File.basename(domain_path))
|
|
@@ -100,8 +116,8 @@ module Hecks
|
|
|
100
116
|
end
|
|
101
117
|
end
|
|
102
118
|
|
|
103
|
-
#
|
|
104
|
-
# symlink
|
|
119
|
+
# Symlinks are followed, not copied. `FileUtils.cp_r` reproduces a
|
|
120
|
+
# symlink as a symlink, and a relative one then points at nothing
|
|
105
121
|
# from a tmpdir — `lib/hecks/framework/bluebook/compliance
|
|
106
122
|
# .bluebook` is exactly that, a link to
|
|
107
123
|
# `examples/compliance/bluebook/compliance.bluebook`, so the whole
|
|
@@ -109,10 +125,14 @@ module Hecks
|
|
|
109
125
|
# path under /var/folders that had never existed. Nothing about the
|
|
110
126
|
# domain was wrong; the copy was.
|
|
111
127
|
#
|
|
112
|
-
# `FileUtils.cp` follows a symlink and copies its
|
|
128
|
+
# `FileUtils.cp` follows a symlink and copies its content, which is
|
|
113
129
|
# what an isolated boot wants: the copy has to stand alone, since
|
|
114
130
|
# rebind! rewrites files in it and must not reach back through a
|
|
115
131
|
# link into the real tree.
|
|
132
|
+
#
|
|
133
|
+
# @param source [String] the real domain directory to copy
|
|
134
|
+
# @param destination [String] the tmpdir path to copy it into; created if missing
|
|
135
|
+
# @return [void]
|
|
116
136
|
def copy_dereferencing(source, destination)
|
|
117
137
|
FileUtils.mkdir_p(destination)
|
|
118
138
|
Dir.glob(File.join(source, "**", "*"), File::FNM_DOTMATCH).each do |path|
|
|
@@ -136,12 +156,17 @@ module Hecks
|
|
|
136
156
|
end
|
|
137
157
|
end
|
|
138
158
|
|
|
159
|
+
# Rewrites every `.hecksagon` in the copy to bind through Memory and drops its
|
|
160
|
+
# `.world` files, so the boot needs no settings at all.
|
|
161
|
+
#
|
|
162
|
+
# @param copy [String] the isolated copy's root directory
|
|
163
|
+
# @return [void]
|
|
139
164
|
def rebind_to_memory!(copy)
|
|
140
165
|
rewrite_bindings!(copy, "Memory")
|
|
141
166
|
strip_translations!(copy)
|
|
142
167
|
|
|
143
|
-
#
|
|
144
|
-
# stores a settings block under
|
|
168
|
+
# **The settings, not just the bind** — `WorldBuilder#method_missing`
|
|
169
|
+
# stores a settings block under both "verb:adapter" and the bare
|
|
145
170
|
# "verb" (world_builder.rb:32-33), so a bind rewritten to Memory
|
|
146
171
|
# still falls back to whatever adapter's settings were declared
|
|
147
172
|
# bare — Postgres's `database:`, which Memory does not take and
|
|
@@ -154,7 +179,7 @@ module Hecks
|
|
|
154
179
|
Dir.glob(File.join(copy, "**", "*.world")).each { |path| File.delete(path) }
|
|
155
180
|
end
|
|
156
181
|
|
|
157
|
-
#
|
|
182
|
+
# Same dance as memory, one adapter over — `Adapters::Sqlite#
|
|
158
183
|
# resolve_path` (adapters/driven/sqlite.rb) defaults to
|
|
159
184
|
# `data/<table>.db` under the boot's own root when no `database`
|
|
160
185
|
# setting is declared, which `data/` already being cleared makes a
|
|
@@ -162,30 +187,33 @@ module Hecks
|
|
|
162
187
|
# needed" property Memory has. `data/` itself is left for Sqlite to
|
|
163
188
|
# recreate on first write, same as it always was for Memory/Heki —
|
|
164
189
|
# nothing here creates it up front.
|
|
190
|
+
#
|
|
191
|
+
# @param copy [String] the isolated copy's root directory
|
|
192
|
+
# @return [void]
|
|
165
193
|
def rebind_to_sqlite!(copy)
|
|
166
194
|
rewrite_bindings!(copy, "SqlitePersistence")
|
|
167
195
|
strip_translations!(copy)
|
|
168
196
|
Dir.glob(File.join(copy, "**", "*.world")).each { |path| File.delete(path) }
|
|
169
197
|
end
|
|
170
198
|
|
|
171
|
-
#
|
|
199
|
+
# **The expensive one** — Postgres has no zero-config default the way
|
|
172
200
|
# Sqlite/Memory do (`Adapters::Postgres.connect_for` refuses outright
|
|
173
201
|
# with no `database` setting), so dropping `.world` the way the
|
|
174
202
|
# other two do would just move the WiringError from "wrong adapter"
|
|
175
203
|
# to "no adapter." Every domain name this copy declares gets a
|
|
176
|
-
#
|
|
204
|
+
# fresh `.world` written for it instead — not a rewrite of whatever
|
|
177
205
|
# was there, a replacement, same reasoning `rewrite_bindings!`
|
|
178
206
|
# already applies to `.hecksagon`: this ephemeral boot owns every
|
|
179
207
|
# binding decision, nothing about a real deployment's own settings
|
|
180
208
|
# is relevant or safe to half-preserve here.
|
|
181
209
|
#
|
|
182
|
-
#
|
|
210
|
+
# One shared schema, dropped and recreated before every boot — not a
|
|
183
211
|
# fresh randomly-named one per call. `bin/fuzz` drives every
|
|
184
212
|
# ephemeral boot sequentially (one `IsolatedBoot.call` fully exits
|
|
185
213
|
# before the next begins — see that file's own single-threaded
|
|
186
214
|
# `while` loop), so nothing is ever concurrent here ; a fresh name
|
|
187
215
|
# every time would just leak schemas in `FUZZ_POSTGRES_DATABASE`
|
|
188
|
-
# forever with nothing to ever drop them. If a caller ever
|
|
216
|
+
# forever with nothing to ever drop them. If a caller ever does
|
|
189
217
|
# start running fuzz adapters concurrently, this needs to move to a
|
|
190
218
|
# process-unique schema name (`SecureRandom.hex` is already
|
|
191
219
|
# `require`d here for exactly that day) — flagged, not solved,
|
|
@@ -193,17 +221,22 @@ module Hecks
|
|
|
193
221
|
FUZZ_POSTGRES_DATABASE = "hecks_fuzz".freeze
|
|
194
222
|
FUZZ_POSTGRES_SCHEMA = "hecks_fuzz".freeze
|
|
195
223
|
|
|
224
|
+
# Rewrites every `.hecksagon` in the copy to bind through Postgres, against the
|
|
225
|
+
# shared `FUZZ_POSTGRES_DATABASE`/`FUZZ_POSTGRES_SCHEMA` scratch schema.
|
|
226
|
+
#
|
|
227
|
+
# @param copy [String] the isolated copy's root directory
|
|
228
|
+
# @return [void]
|
|
196
229
|
def rebind_to_postgres!(copy)
|
|
197
230
|
require "pg"
|
|
198
231
|
rewrite_bindings!(copy, "Postgres")
|
|
199
232
|
strip_translations!(copy)
|
|
200
233
|
ensure_fuzz_schema!
|
|
201
234
|
|
|
202
|
-
#
|
|
235
|
+
# One `.world` per directory a `.hecksagon` actually lives in, not
|
|
203
236
|
# one at `copy`'s own root — `Folder#load_domain` resolves a
|
|
204
|
-
#
|
|
237
|
+
# single `bluebook_directory` and globs `*.world` there, non-
|
|
205
238
|
# recursively (`Folder#load_each`); a domain can hold several
|
|
206
|
-
# `Hecks.hecksagon "<Name>" do ... end`
|
|
239
|
+
# `Hecks.hecksagon "<Name>" do ... end` sibling blocks in that one
|
|
207
240
|
# file (banking.hecksagon declares "Banking", "Governance", and
|
|
208
241
|
# "Identity" together), so every name found in one `.hecksagon`
|
|
209
242
|
# file gets bundled into one `.world` written beside it, not
|
|
@@ -225,7 +258,7 @@ module Hecks
|
|
|
225
258
|
end.join("\n"))
|
|
226
259
|
end
|
|
227
260
|
|
|
228
|
-
# Any
|
|
261
|
+
# Any pre-existing `.world` this copy shipped with (a real
|
|
229
262
|
# deployment's own connection string) is now redundant with — and
|
|
230
263
|
# would conflict with, `Registry#add_world`'s own header on
|
|
231
264
|
# loading the same domain name twice — the fresh one just
|
|
@@ -236,20 +269,22 @@ module Hecks
|
|
|
236
269
|
end
|
|
237
270
|
end
|
|
238
271
|
|
|
239
|
-
#
|
|
272
|
+
# Admin connection lives outside the tmp copy entirely — same as
|
|
240
273
|
# every other real-Postgres spec in this repo (`support/
|
|
241
274
|
# postgres_probe.rb`'s own header). Database created once per
|
|
242
275
|
# process and remembered (`@fuzz_database_ready` on this module's
|
|
243
276
|
# own singleton, the same memoization shape `PostgresProbe
|
|
244
277
|
# .available?` already uses) ; the schema inside it is dropped and
|
|
245
|
-
# recreated on
|
|
278
|
+
# recreated on every call, which is what actually isolates one
|
|
246
279
|
# ephemeral boot's data from the next.
|
|
280
|
+
#
|
|
281
|
+
# @return [void]
|
|
247
282
|
def ensure_fuzz_schema!
|
|
248
|
-
# `Adapters::Postgres#initialize` opens
|
|
249
|
-
#
|
|
283
|
+
# `Adapters::Postgres#initialize` opens one real `PG::Connection`
|
|
284
|
+
# per aggregate and never explicitly closes it — fine for a
|
|
250
285
|
# process that boots once and runs, exactly what every other
|
|
251
286
|
# caller of this adapter is. A fuzz run boots dozens to hundreds
|
|
252
|
-
# of
|
|
287
|
+
# of ephemeral times in one process (every seed does at least a
|
|
253
288
|
# generate + a replay, `replay_is_deterministic` doubles that,
|
|
254
289
|
# shrinking multiplies it further), and `PG::Connection` only
|
|
255
290
|
# actually closes its socket when Ruby's GC finalizes the
|
|
@@ -261,7 +296,7 @@ module Hecks
|
|
|
261
296
|
# ephemeral boots, `PG::ConnectionBad: ... "too many clients
|
|
262
297
|
# already"`. A `GC.start` here — right before the next ephemeral
|
|
263
298
|
# boot's connections open, not on some timer — reclaims every
|
|
264
|
-
# connection the
|
|
299
|
+
# connection the previous boot's now-unreferenced adapters held,
|
|
265
300
|
# keeping the live count bounded regardless of run length. This
|
|
266
301
|
# is a real constraint on running Postgres in a loop, not
|
|
267
302
|
# something to route around by connecting less carefully.
|
|
@@ -278,7 +313,7 @@ module Hecks
|
|
|
278
313
|
end
|
|
279
314
|
|
|
280
315
|
db = PG.connect(dbname: FUZZ_POSTGRES_DATABASE)
|
|
281
|
-
#
|
|
316
|
+
# **Quiet on purpose** — same as `Adapters::Postgres.connect_for`'s
|
|
282
317
|
# own `SET client_min_messages`: a `DROP SCHEMA ... CASCADE` that
|
|
283
318
|
# actually has something to drop (every boot after the first)
|
|
284
319
|
# NOTICEs once per dropped object, which is the ordinary case
|
|
@@ -292,11 +327,11 @@ module Hecks
|
|
|
292
327
|
db.close
|
|
293
328
|
end
|
|
294
329
|
|
|
295
|
-
#
|
|
296
|
-
# are
|
|
330
|
+
# The adapter `:postgres` never touches — `Postgres` and `PostgresEra`
|
|
331
|
+
# are sibling, not interchangeable, adapters (see postgres_era.rb's
|
|
297
332
|
# own header: "the only one that declares the LINEAGE capability").
|
|
298
333
|
# PRD 02 (docs/prds/02-fuzzer-real-adapters.md) shipped `:postgres`
|
|
299
|
-
# and explicitly scoped `PostgresEra`
|
|
334
|
+
# and explicitly scoped `PostgresEra` out: "nothing here touches
|
|
300
335
|
# era/lineage machinery." That gap is real, not cosmetic —
|
|
301
336
|
# `examples/directory` (a `compute`/`rekey` translation edge, the
|
|
302
337
|
# one domain in this corpus that actually exercises PostgresEra-
|
|
@@ -305,23 +340,29 @@ module Hecks
|
|
|
305
340
|
# `:memory` included, structurally cannot reach it. This mode closes
|
|
306
341
|
# that — `bin/qa_sweep --persistence-parity` is its first caller.
|
|
307
342
|
#
|
|
308
|
-
#
|
|
343
|
+
# No shared constant database, unlike `:postgres` above — deliberate.
|
|
309
344
|
# `rebind_to_postgres!`'s own `FUZZ_POSTGRES_DATABASE`/`_SCHEMA` are
|
|
310
345
|
# module-level constants because `bin/fuzz --adapter postgres` is a
|
|
311
346
|
# general-purpose, run-it-anytime tool with no caller-tracked
|
|
312
347
|
# lifecycle of its own. This mode's only caller
|
|
313
348
|
# (`bin/qa_sweep --persistence-parity`) is different: it dispatches
|
|
314
349
|
# through `QualityControl::Target.claim!`'s own cross-process lock
|
|
315
|
-
# first (see that script's own header), so at most
|
|
350
|
+
# first (see that script's own header), so at most one sweep is ever
|
|
316
351
|
# touching a given target's own disposable database at a time — but
|
|
317
|
-
# the caller, not this module, is what
|
|
352
|
+
# the caller, not this module, is what owns that database's name and
|
|
318
353
|
# lifecycle (created, and genuinely dropped, by the caller itself),
|
|
319
|
-
# exactly the discipline `spec/
|
|
354
|
+
# exactly the discipline `spec/support/qa_sweep_all_fixture.rb`'s own header
|
|
320
355
|
# describes and this repository's persistence-parity work is
|
|
321
|
-
# required to follow. Accepting `database:`/`schema:` as
|
|
356
|
+
# required to follow. Accepting `database:`/`schema:` as required
|
|
322
357
|
# keyword arguments (never a fallback constant) is what keeps that
|
|
323
358
|
# ownership from silently drifting back onto this module the way
|
|
324
359
|
# `:postgres`'s own `FUZZ_POSTGRES_DATABASE` already has.
|
|
360
|
+
#
|
|
361
|
+
# @param copy [String] the isolated copy's root directory
|
|
362
|
+
# @param database [String] the caller-owned throwaway database name
|
|
363
|
+
# @param schema [String] the caller-owned throwaway schema name
|
|
364
|
+
# @return [void]
|
|
365
|
+
# @raise [ArgumentError] if `database` or `schema` is empty
|
|
325
366
|
def rebind_to_postgres_era!(copy, database:, schema:)
|
|
326
367
|
require "pg"
|
|
327
368
|
if database.to_s.empty? || schema.to_s.empty?
|
|
@@ -334,25 +375,25 @@ module Hecks
|
|
|
334
375
|
rewrite_bindings!(copy, "PostgresEra")
|
|
335
376
|
ensure_postgres_era_schema!(database: database, schema: schema)
|
|
336
377
|
|
|
337
|
-
#
|
|
378
|
+
# Same one-`.world`-per-`.hecksagon`-directory shape `rebind_to_
|
|
338
379
|
# postgres!` already uses, for the identical reason (`Folder#
|
|
339
380
|
# load_domain` globs `*.world` non-recursively) — see that
|
|
340
381
|
# method's own comment on `world_path` above. `PostgresEra`
|
|
341
|
-
# additionally takes `schema:` (postgres_era.rb's own "
|
|
342
|
-
#
|
|
382
|
+
# additionally takes `schema:` (postgres_era.rb's own "shared-
|
|
383
|
+
# instance isolation" comment): the caller-supplied throwaway
|
|
343
384
|
# schema is what actually isolates this one ephemeral boot from
|
|
344
385
|
# the next, the same job `FUZZ_POSTGRES_SCHEMA` does for `:postgres`
|
|
345
386
|
# — `connect_for` itself idempotently `CREATE SCHEMA IF NOT
|
|
346
|
-
# EXISTS`s it, so this method only ever needs to
|
|
387
|
+
# EXISTS`s it, so this method only ever needs to drop it first
|
|
347
388
|
# (in `ensure_postgres_era_schema!`, below) for the zero-history
|
|
348
389
|
# guarantee every other adapter mode already gives.
|
|
349
390
|
#
|
|
350
|
-
# `allow_superuser true` —
|
|
391
|
+
# `allow_superuser true` — on the record, on purpose. A bare
|
|
351
392
|
# `database` connects as the ambient Postgres user, and
|
|
352
393
|
# PostgresEra refuses to boot at all when that user is a
|
|
353
394
|
# superuser (its era write-fence is row-level security, which a
|
|
354
395
|
# superuser walks through — `Lineage#check_fence_applies!`,
|
|
355
|
-
# BUG#24). That refusal protects a
|
|
396
|
+
# BUG#24). That refusal protects a real ledger from an old
|
|
356
397
|
# checkout's stale writes; nothing here is one. This is an
|
|
357
398
|
# ephemeral boot into a throwaway schema the caller itself
|
|
358
399
|
# creates and drops, whose data no second checkout ever shares,
|
|
@@ -386,20 +427,25 @@ module Hecks
|
|
|
386
427
|
end
|
|
387
428
|
end
|
|
388
429
|
|
|
389
|
-
#
|
|
430
|
+
# The zero-history guarantee for this mode — `DROP SCHEMA ... CASCADE`
|
|
390
431
|
# before every ephemeral boot, mirroring `ensure_fuzz_schema!` above
|
|
391
432
|
# (same `GC.start`-before-connecting fix for the identical
|
|
392
433
|
# `max_connections` exhaustion that method's own comment documents —
|
|
393
434
|
# `PostgresEra` opens real `PG::Connection`s exactly like `Postgres`
|
|
394
|
-
# does, same unclosed-until-GC'd lifetime). The
|
|
435
|
+
# does, same unclosed-until-GC'd lifetime). The database itself is
|
|
395
436
|
# created here too, idempotently (`CREATE DATABASE IF NOT EXISTS`
|
|
396
437
|
# has no Postgres spelling, hence the existence check) — but never
|
|
397
|
-
#
|
|
438
|
+
# dropped here: this module creates it once per process because
|
|
398
439
|
# `Hecks.boot` needs it to exist before `PostgresEra.connect_for`'s
|
|
399
440
|
# own `PG.connect(dbname: ...)` can succeed at all, but dropping it
|
|
400
|
-
# again is the
|
|
441
|
+
# again is the caller's own job (its name and lifecycle belong to
|
|
401
442
|
# the caller — see `rebind_to_postgres_era!`'s own header), not
|
|
402
443
|
# something this per-ephemeral-boot helper should ever do mid-sweep.
|
|
444
|
+
#
|
|
445
|
+
# @param database [String] the caller-owned database name, created if it does not
|
|
446
|
+
# already exist
|
|
447
|
+
# @param schema [String] the caller-owned schema name, dropped and recreated
|
|
448
|
+
# @return [void]
|
|
403
449
|
def ensure_postgres_era_schema!(database:, schema:)
|
|
404
450
|
GC.start
|
|
405
451
|
|
|
@@ -417,7 +463,7 @@ module Hecks
|
|
|
417
463
|
db.close
|
|
418
464
|
end
|
|
419
465
|
|
|
420
|
-
#
|
|
466
|
+
# The shared rewrite — factored out of `rebind_to_memory!` when
|
|
421
467
|
# Sqlite/Postgres modes needed the identical `.hecksagon` surgery
|
|
422
468
|
# with only the target adapter name differing. `persisted_by`/
|
|
423
469
|
# `projected_by` can be spelled two ways: aggregate-scoped
|
|
@@ -431,46 +477,57 @@ module Hecks
|
|
|
431
477
|
# #read_repository` already falls back to the authoritative
|
|
432
478
|
# repository when none exists, so a read model this ephemeral copy
|
|
433
479
|
# never wires is simply unread, not broken.
|
|
434
|
-
# A `compute`/`rekey`
|
|
435
|
-
#
|
|
480
|
+
# A `compute`/`rekey` translation edge refuses to boot at all under
|
|
481
|
+
# any non-lineage-capable adapter — found live, wiring this very
|
|
436
482
|
# mode up against `examples/directory`: `Runtime::EraCheck
|
|
437
|
-
# .check_compute_rules!` (era_check.rb) runs
|
|
483
|
+
# .check_compute_rules!` (era_check.rb) runs unconditionally for
|
|
438
484
|
# every loaded bluebook once the era plugin is loaded at all
|
|
439
485
|
# (`bin/qa_sweep`'s own top-of-file `require "hecks/ports/
|
|
440
486
|
# persistence/plugins/era"`, needed for the ledger's own
|
|
441
487
|
# PostgresEra-bound aggregates), and refuses outright — "compute
|
|
442
|
-
# rules require the Postgres adapter" — for
|
|
443
|
-
# lineage carries a `compute` rule and whose
|
|
488
|
+
# rules require the Postgres adapter" — for any aggregate whose
|
|
489
|
+
# lineage carries a `compute` rule and whose bound adapter is not
|
|
444
490
|
# lineage-capable. `PostgresEra` is the only adapter that answers
|
|
445
491
|
# `lineage_capable? == true` (postgres_era.rb's own `self.
|
|
446
|
-
# lineage_capable? = true`) — plain `Postgres` does
|
|
492
|
+
# lineage_capable? = true`) — plain `Postgres` does not, so this
|
|
447
493
|
# refusal was already real for `:postgres`/`:sqlite`/`:memory`
|
|
448
494
|
# alike, for any domain with a translation edge, before this
|
|
449
495
|
# mode's own `:postgres_era` ever existed. This is very likely the
|
|
450
|
-
#
|
|
496
|
+
# mechanical reason `examples/directory` had to be shelved out of
|
|
451
497
|
# `hecks_qa`'s own rotation in the first place — not merely "less
|
|
452
|
-
# interesting to fuzz on Memory," but "cannot
|
|
498
|
+
# interesting to fuzz on Memory," but "cannot boot on Memory at
|
|
453
499
|
# all" once the era plugin is loaded, which every real
|
|
454
500
|
# `bin/qa_sweep` invocation already does.
|
|
455
501
|
#
|
|
456
|
-
#
|
|
502
|
+
# The fix is to drop the edge, not to chase the refusal — an
|
|
457
503
|
# ephemeral, zero-history replay boot (every mode `IsolatedBoot`
|
|
458
504
|
# offers) never has a pre-existing era-1 row to translate in the
|
|
459
|
-
# first place, so the translation edge is
|
|
505
|
+
# first place, so the translation edge is irrelevant to anything a
|
|
460
506
|
# fuzz/replay run actually exercises (ordinary command dispatch
|
|
461
|
-
# against a fresh boot) — it only ever matters at
|
|
507
|
+
# against a fresh boot) — it only ever matters at mint time,
|
|
462
508
|
# against a real, pre-existing database
|
|
463
509
|
# (`PostgresEra::LineageManager.check!`, a wholly separate,
|
|
464
510
|
# human-approved path this harness was never meant to reach).
|
|
465
511
|
# Dropping it here is exactly the same move `rebind_to_memory!`
|
|
466
512
|
# already makes for `.world` (irrelevant/conflicting settings for
|
|
467
513
|
# an ephemeral boot, deleted outright) — never called for
|
|
468
|
-
# `:postgres_era` itself, where the bound adapter genuinely
|
|
514
|
+
# `:postgres_era` itself, where the bound adapter genuinely is
|
|
469
515
|
# lineage-capable and the edge causes no refusal to begin with.
|
|
516
|
+
#
|
|
517
|
+
# @param copy [String] the isolated copy's root directory
|
|
518
|
+
# @return [void]
|
|
470
519
|
def strip_translations!(copy)
|
|
471
520
|
Dir.glob(File.join(copy, "**", "translations", "*.bluebook")).each { |path| File.delete(path) }
|
|
472
521
|
end
|
|
473
522
|
|
|
523
|
+
# Rewrites every `persisted_by` bind — aggregate-scoped or bare at the
|
|
524
|
+
# hecksagon's own root — in the copy's `.hecksagon` files to name `adapter_name`,
|
|
525
|
+
# and drops every `projected_by` bind outright.
|
|
526
|
+
#
|
|
527
|
+
# @param copy [String] the isolated copy's root directory
|
|
528
|
+
# @param adapter_name [String] the port binding name to rewrite every
|
|
529
|
+
# `persisted_by` to, such as `"Memory"` or `"SqlitePersistence"`
|
|
530
|
+
# @return [void]
|
|
474
531
|
def rewrite_bindings!(copy, adapter_name)
|
|
475
532
|
Dir.glob(File.join(copy, "**", "*.hecksagon")).each do |path|
|
|
476
533
|
lines = File.readlines(path).grep_v(/\bprojected_by\s*\(?\s*"/)
|