hecks 2.9.0 → 3.0.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/exe/hecks +51 -6
- data/lib/hecks/adapters/driven/heki/journal.rb +1 -1
- data/lib/hecks/adapters/driven/lambda/client.rb +1 -1
- data/lib/hecks/adapters/driven/lambda.rb +1 -1
- data/lib/hecks/adapters/driven/memory.rb +16 -3
- data/lib/hecks/adapters/driven/postgres/reconnect.rb +6 -2
- data/lib/hecks/adapters/driven/postgres/shared_connection.rb +180 -0
- data/lib/hecks/adapters/driven/postgres.rb +4 -2
- data/lib/hecks/adapters/driven/prism.rb +41 -8
- data/lib/hecks/adapters/driven/tenant_provisioner.rb +73 -16
- data/lib/hecks/behaviors/expectations.rb +1 -1
- data/lib/hecks/behaviors.rb +1 -1
- data/lib/hecks/bench/cli.rb +62 -0
- data/lib/hecks/bench/environment.rb +71 -0
- data/lib/hecks/bench/postgres_probe.rb +30 -0
- data/lib/hecks/bench/report.rb +76 -0
- data/lib/hecks/bench/ruby_runner.rb +93 -0
- data/lib/hecks/bench/run.rb +55 -0
- data/lib/hecks/bench/rust_runner.rb +101 -0
- data/lib/hecks/bench/stats.rb +46 -0
- data/lib/hecks/bench/suite.rb +98 -0
- data/lib/hecks/bench/workload.rb +89 -0
- data/lib/hecks/bench.rb +17 -0
- data/lib/hecks/bluebook/assembly/contracts.rb +2 -0
- data/lib/hecks/bluebook/attribute.rb +1 -1
- data/lib/hecks/bluebook/behaviour/aggregate.rb +30 -1
- data/lib/hecks/bluebook/behaviour/domain_port.rb +7 -0
- data/lib/hecks/bluebook/chapter.rb +6 -2
- data/lib/hecks/bluebook/domain_port.rb +36 -5
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +10 -0
- data/lib/hecks/bluebook/dsl/bluebook_builder.rb +8 -0
- data/lib/hecks/bluebook/dsl/bootstrap_table.rb +2 -1
- data/lib/hecks/bluebook/dsl/domain_port_builder.rb +31 -3
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +122 -3
- data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +1 -1
- data/lib/hecks/bluebook/dsl/query_builder.rb +27 -1
- data/lib/hecks/bluebook/dsl/rule_reference.rb +1 -1
- data/lib/hecks/bluebook/dsl/world_builder.rb +29 -2
- data/lib/hecks/bluebook/expression/canonical_form.rb +1 -1
- data/lib/hecks/bluebook/expression/resolver.rb +3 -2
- data/lib/hecks/bluebook/hexagon.rb +13 -5
- data/lib/hecks/bluebook/meta_validator/reconstruction.rb +1 -0
- data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +4 -1
- data/lib/hecks/bluebook/meta_validator/verdict_cache.rb +246 -0
- data/lib/hecks/bluebook/meta_validator.rb +9 -4
- data/lib/hecks/bluebook/model_check.rb +23 -0
- data/lib/hecks/bluebook/pattern_subset.rb +38 -0
- data/lib/hecks/bluebook/query.rb +20 -5
- data/lib/hecks/bluebook/smoke_test.rb +1 -1
- data/lib/hecks/canonical_json.rb +30 -0
- data/lib/hecks/chapters.rb +102 -0
- data/lib/hecks/cli/behaviors.rb +61 -0
- data/lib/hecks/cli/canonicalise.rb +25 -0
- data/lib/hecks/cli/check_engine_agreement.rb +32 -0
- data/lib/hecks/cli/check_era.rb +88 -0
- data/lib/hecks/cli/console.rb +66 -0
- data/lib/hecks/cli/deploy_template_diff.rb +40 -0
- data/lib/hecks/cli/doc_coverage.rb +32 -0
- data/lib/hecks/cli/document.rb +2 -2
- data/lib/hecks/cli/expression_projection.rb +41 -0
- data/lib/hecks/cli/follow.rb +88 -0
- data/lib/hecks/cli/history.rb +44 -0
- data/lib/hecks/cli/ir.rb +1 -1
- data/lib/hecks/cli/mcp.rb +3 -3
- data/lib/hecks/cli/model_check.rb +7 -4
- data/lib/hecks/cli/pattern_cases.rb +47 -0
- data/lib/hecks/cli/present.rb +39 -0
- data/lib/hecks/cli/project_cli.rb +224 -32
- data/lib/hecks/cli/project_diagrams.rb +4 -3
- data/lib/hecks/cli/project_glossary.rb +44 -0
- data/lib/hecks/cli/reference.rb +46 -0
- data/lib/hecks/cli/refresh_projections.rb +44 -0
- data/lib/hecks/cli/refresh_rspec_runtime_baseline.rb +150 -0
- data/lib/hecks/cli/release.rb +70 -0
- data/lib/hecks/cli/release_gem.rb +59 -0
- data/lib/hecks/cli/rspec_io_parallel_files.rb +137 -0
- data/lib/hecks/cli/rspec_shard_files.rb +66 -0
- data/lib/hecks/cli/run.rb +3 -3
- data/lib/hecks/cli/seed_semantics_corpus.rb +71 -0
- data/lib/hecks/cli/serve_query_ir_mcp.rb +30 -0
- data/lib/hecks/cli/shape.rb +70 -0
- data/lib/hecks/cli/smoke_http.rb +230 -0
- data/lib/hecks/cli/smoke_test.rb +5 -3
- data/lib/hecks/cli/spec_example.rb +27 -0
- data/lib/hecks/cli/statements.rb +32 -0
- data/lib/hecks/cli/stores.rb +4 -5
- data/lib/hecks/cli/stress_concurrency_specs.rb +177 -0
- data/lib/hecks/cli.rb +201 -52
- data/lib/hecks/codemod.rb +324 -0
- data/lib/hecks/corpus.rb +423 -0
- data/lib/hecks/deploy/bluebook/deploy.bluebook +591 -23
- data/lib/hecks/deploy/bluebook/deploy.hecksagon +41 -4
- data/lib/hecks/deploy/oidc.json +82 -2
- data/lib/hecks/doc/reference.rb +351 -0
- data/lib/hecks/doors/cli_door.rb +196 -0
- data/lib/hecks/doors/cli_runner.rb +297 -0
- data/lib/hecks/{facade → doors}/command_request.rb +1 -1
- data/lib/hecks/{facade → doors}/handle.rb +2 -2
- data/lib/hecks/{facade → doors}/json_door.rb +4 -4
- data/lib/hecks/doors/launcher_options.rb +129 -0
- data/lib/hecks/{cli → doors}/mcp_door.rb +7 -7
- data/lib/hecks/doors/mcp_door_scope.rb +97 -0
- data/lib/hecks/{facade/surface → doors/ruby_door}/aggregate_door.rb +8 -3
- data/lib/hecks/{facade/surface → doors/ruby_door}/chapter.rb +3 -3
- data/lib/hecks/{facade/surface.rb → doors/ruby_door.rb} +36 -5
- data/lib/hecks/doors.rb +61 -0
- data/lib/hecks/embryonaut_bluebook/vendor_cli.rb +1 -1
- data/lib/hecks/embryonaut_bluebook.rb +1 -1
- data/lib/hecks/engine_agreement.rb +156 -0
- data/lib/hecks/facade.rb +24 -37
- data/lib/hecks/forms/app.rb +6 -6
- data/lib/hecks/forms/banking_presentation.rb +50 -0
- data/lib/hecks/forms/field_shape.rb +2 -1
- data/lib/hecks/forms/port_argument.rb +1 -1
- data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +393 -0
- data/lib/hecks/fuzzing/combination_miner.rb +86 -0
- data/lib/hecks/fuzzing/concurrent_dispatch.rb +214 -0
- data/lib/hecks/fuzzing/coverage_campaign.rb +143 -0
- data/lib/hecks/fuzzing/differential.rb +156 -0
- data/lib/hecks/fuzzing/domain_generator.rb +615 -0
- data/lib/hecks/fuzzing/era_boundary.rb +71 -0
- data/lib/hecks/fuzzing/form_census.rb +136 -0
- data/lib/hecks/fuzzing/generated_domain_check.rb +93 -0
- data/lib/hecks/fuzzing/invalid_value_generator.rb +64 -0
- data/lib/hecks/fuzzing/isolated_boot.rb +301 -0
- data/lib/hecks/fuzzing/nondeterministic.rb +51 -0
- data/lib/hecks/fuzzing/outside_world.rb +45 -0
- data/lib/hecks/fuzzing/persistence_parity.rb +102 -0
- data/lib/hecks/fuzzing/properties/corrections.rb +88 -0
- data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +314 -0
- data/lib/hecks/fuzzing/properties/guards.rb +190 -0
- data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +300 -0
- data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +93 -0
- data/lib/hecks/fuzzing/properties/outbox.rb +89 -0
- data/lib/hecks/fuzzing/properties/querying.rb +175 -0
- data/lib/hecks/fuzzing/properties.rb +162 -0
- data/lib/hecks/fuzzing/qa_settings.rb +123 -0
- data/lib/hecks/fuzzing/replay.rb +505 -0
- data/lib/hecks/fuzzing/rotation_priority.rb +48 -0
- data/lib/hecks/fuzzing/rust_gap_manifest.rb +108 -0
- data/lib/hecks/fuzzing/self_consistency.rb +339 -0
- data/lib/hecks/fuzzing/sequence_generator/adversary.rb +385 -0
- data/lib/hecks/fuzzing/sequence_generator/catalog.rb +132 -0
- data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +95 -0
- data/lib/hecks/fuzzing/sequence_generator/picker.rb +77 -0
- data/lib/hecks/fuzzing/sequence_generator/query_binding.rb +109 -0
- data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +237 -0
- data/lib/hecks/fuzzing/sequence_generator.rb +189 -0
- data/lib/hecks/fuzzing/shrinker.rb +166 -0
- data/lib/hecks/fuzzing/structural_skips.rb +33 -0
- data/lib/hecks/fuzzing/sweep_depth.rb +43 -0
- data/lib/hecks/fuzzing/target_capabilities.rb +163 -0
- data/lib/hecks/fuzzing/value_generator.rb +216 -0
- data/lib/hecks/fuzzing.rb +23 -0
- data/lib/hecks/grammar/evolve.rb +336 -0
- data/lib/hecks/hecks/adapters/codebase/codemods.rb +45 -0
- data/lib/hecks/hecks/adapters/codebase/conformance.rb +71 -0
- data/lib/hecks/hecks/adapters/codebase/corpus_tasks.rb +202 -0
- data/lib/hecks/hecks/adapters/codebase/gem_registry.rb +80 -0
- data/lib/hecks/hecks/adapters/codebase/kernel_tables.rb +54 -0
- data/lib/hecks/hecks/adapters/codebase/language.rb +240 -0
- data/lib/hecks/hecks/adapters/codebase/npm_registry.rb +102 -0
- data/lib/hecks/hecks/adapters/codebase/publishing.rb +142 -0
- data/lib/hecks/hecks/adapters/codebase/regeneration.rb +67 -0
- data/lib/hecks/hecks/adapters/codebase/release_facts.rb +90 -0
- data/lib/hecks/hecks/adapters/codebase/ruby_child.rb +83 -0
- data/lib/hecks/hecks/adapters/codebase/secret_vault.rb +36 -0
- data/lib/hecks/hecks/adapters/codebase/source_tree.adapter +7 -0
- data/lib/hecks/hecks/adapters/codebase/source_tree.rb +189 -0
- data/lib/hecks/hecks/adapters/codebase/sqlite_fixture.rb +71 -0
- data/lib/hecks/hecks/adapters/codebase/style.rb +171 -0
- data/lib/hecks/hecks/adapters/codebase/test_runner.rb +55 -0
- data/lib/hecks/hecks/adapters/codebase/test_suite.rb +165 -0
- data/lib/hecks/hecks/adapters/codebase/tree.rb +111 -0
- data/lib/hecks/hecks/adapters/console_capture.rb +79 -0
- data/lib/hecks/hecks/adapters/deploy_toolchain.adapter +7 -0
- data/lib/hecks/hecks/adapters/deploy_toolchain.rb +108 -0
- data/lib/hecks/hecks/adapters/git.adapter +7 -0
- data/lib/hecks/hecks/adapters/git.rb +102 -0
- data/lib/hecks/hecks/adapters/host_http.adapter +7 -0
- data/lib/hecks/hecks/adapters/host_http.rb +65 -0
- data/lib/hecks/hecks/adapters/in_process_boot.adapter +7 -0
- data/lib/hecks/hecks/adapters/in_process_boot.rb +197 -0
- data/lib/hecks/hecks/adapters/in_process_operations.rb +202 -0
- data/lib/hecks/hecks/adapters/journal_store/changes.rb +143 -0
- data/lib/hecks/hecks/adapters/journal_store/compaction.rb +145 -0
- data/lib/hecks/hecks/adapters/journal_store/edge_audit.rb +132 -0
- data/lib/hecks/hecks/adapters/journal_store/examination.rb +135 -0
- data/lib/hecks/hecks/adapters/journal_store/held_domain.rb +163 -0
- data/lib/hecks/hecks/adapters/journal_store/readings.rb +187 -0
- data/lib/hecks/hecks/adapters/journal_store.adapter +7 -0
- data/lib/hecks/hecks/adapters/journal_store.rb +64 -0
- data/lib/hecks/hecks/adapters/local_files.adapter +7 -0
- data/lib/hecks/hecks/adapters/local_files.rb +46 -0
- data/lib/hecks/hecks/adapters/pg_admin.adapter +7 -0
- data/lib/hecks/hecks/adapters/pg_admin.rb +245 -0
- data/lib/hecks/hecks/adapters/process_pool.adapter +7 -0
- data/lib/hecks/hecks/adapters/process_pool.rb +119 -0
- data/lib/hecks/hecks/adapters/rust_toolchain.adapter +7 -0
- data/lib/hecks/hecks/adapters/rust_toolchain.rb +179 -0
- data/lib/hecks/hecks/adapters/rust_workspace.rb +150 -0
- data/lib/hecks/hecks/adapters/shell.adapter +7 -0
- data/lib/hecks/hecks/adapters/shell.rb +49 -0
- data/lib/hecks/hecks/adapters/terminal.adapter +7 -0
- data/lib/hecks/hecks/adapters/terminal.rb +74 -0
- data/lib/hecks/hecks/codebase.behaviors +172 -0
- data/lib/hecks/hecks/codebase.bluebook +2453 -0
- data/lib/hecks/hecks/context_map.hecksagon +13 -0
- data/lib/hecks/hecks/custodian.behaviors +179 -0
- data/lib/hecks/hecks/custodian.bluebook +1847 -0
- data/lib/hecks/hecks/deploy.behaviors +74 -0
- data/lib/hecks/hecks/deploy.world +12 -0
- data/lib/hecks/hecks/environments/memory.world +10 -0
- data/lib/hecks/hecks/governance.world +7 -0
- data/lib/hecks/hecks/hecks.behaviors +62 -0
- data/lib/hecks/hecks/hecks.bluebook +124 -0
- data/lib/hecks/hecks/hecks.hecksagon +390 -0
- data/lib/hecks/hecks/hecks.world +42 -0
- data/lib/hecks/kernel_capabilities.rb +194 -0
- data/lib/hecks/language/bluebook/bluebook.bluebook +11 -0
- data/lib/hecks/language/bluebook/query.bluebook +7 -0
- data/lib/hecks/language/bluebook/vocabulary.bluebook +4 -4
- data/lib/hecks/language/hecksagon/domain_port.bluebook +10 -0
- data/lib/hecks/language/hecksagon/hecksagon.bluebook +20 -0
- data/lib/hecks/mcp_stdio_guard.rb +2 -2
- data/lib/hecks/persistence_legacy_fixture/regenerate.rb +161 -0
- data/lib/hecks/persistence_legacy_fixture.rb +113 -0
- data/lib/hecks/ports/persistence/plugins/era/era_check.rb +8 -5
- data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +4 -1
- data/lib/hecks/ports/persistence/plugins/era/expected_era.rb +15 -2
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +1 -1
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +1 -1
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +1 -1
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +32 -15
- data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +6 -3
- data/lib/hecks/ports/persistence/plugins/era/translation/approval_file.rb +227 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +1 -1
- data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +43 -24
- data/lib/hecks/ports/persistence/plugins/era/translation.rb +1 -0
- data/lib/hecks/ports/persistence/state_codec.rb +24 -5
- data/lib/hecks/projection_files.rb +285 -0
- data/lib/hecks/projections/bootstrap_table.rb +1 -1
- data/lib/hecks/projections/deploy/fargate.rb +5 -5
- data/lib/hecks/projections/deploy/lambda.rb +43 -32
- data/lib/hecks/projections/deploy/preview/preview.sh.erb +1 -1
- data/lib/hecks/projections/deploy/preview/template.rb +1 -1
- data/lib/hecks/projections/deploy/scripts/deploy-service.sh.tmpl +1 -1
- data/lib/hecks/projections/deploy/scripts/expected-era.header +1 -1
- data/lib/hecks/projections/deploy/scripts/hosting.mk.tmpl +3 -3
- data/lib/hecks/projections/deploy/scripts/smoke-after-deploy.sh.tmpl +1 -1
- data/lib/hecks/projections/deploy/shared.rb +1 -1
- data/lib/hecks/projections/diagrams.rb +3 -3
- data/lib/hecks/projections/model/deviations.rb +1 -1
- data/lib/hecks/projections/parser_table.rb +4 -4
- data/lib/hecks/projections/reference.rb +2 -3
- data/lib/hecks/projections/rust_vocabulary.rb +1 -1
- data/lib/hecks/projector/cli_projector.rb +106 -37
- data/lib/hecks/projector.rb +1 -1
- data/lib/hecks/quality_control/adapters/agent.adapter +7 -0
- data/lib/hecks/quality_control/adapters/agent.rb +58 -0
- data/lib/hecks/quality_control/adapters/angle_tools.adapter +7 -0
- data/lib/hecks/quality_control/adapters/angle_tools.rb +15 -0
- data/lib/hecks/quality_control/adapters/clearance_recorder.rb +36 -0
- data/lib/hecks/quality_control/adapters/clearance_tools.adapter +7 -0
- data/lib/hecks/quality_control/adapters/clearance_tools.rb +17 -0
- data/lib/hecks/quality_control/adapters/git_pr.adapter +7 -0
- data/lib/hecks/quality_control/adapters/git_pr.rb +226 -0
- data/lib/hecks/quality_control/adapters/github_checks.adapter +9 -0
- data/lib/hecks/quality_control/adapters/github_checks.rb +83 -0
- data/lib/hecks/quality_control/adapters/github_ci_webhook.rb +56 -0
- data/lib/hecks/quality_control/adapters/github_issues.adapter +7 -0
- data/lib/hecks/quality_control/adapters/github_issues.rb +49 -0
- data/lib/hecks/quality_control/adapters/qa_tool.rb +59 -0
- data/lib/hecks/quality_control/adapters/sweep_tools.adapter +7 -0
- data/lib/hecks/quality_control/adapters/sweep_tools.rb +51 -0
- data/lib/hecks/quality_control/adapters/target_tools.adapter +7 -0
- data/lib/hecks/quality_control/adapters/target_tools.rb +43 -0
- data/lib/hecks/quality_control/cli/child.rb +45 -0
- data/lib/hecks/quality_control/cli/qa_concurrency_racer.rb +45 -0
- data/lib/hecks/quality_control/cli/qa_discover_external_domains.rb +248 -0
- data/lib/hecks/quality_control/cli/qa_domain_novelty.rb +163 -0
- data/lib/hecks/quality_control/cli/qa_generated_domains.rb +450 -0
- data/lib/hecks/quality_control/cli/qa_log_bug.rb +195 -0
- data/lib/hecks/quality_control/cli/qa_mine_combinations.rb +220 -0
- data/lib/hecks/quality_control/cli/qa_open_pr.rb +305 -0
- data/lib/hecks/quality_control/cli/qa_postgres_migrate.rb +154 -0
- data/lib/hecks/quality_control/cli/qa_postgres_role.rb +159 -0
- data/lib/hecks/quality_control/cli/qa_pr_check.rb +232 -0
- data/lib/hecks/quality_control/cli/qa_seed_angles.rb +142 -0
- data/lib/hecks/quality_control/cli/qa_seed_targets.rb +69 -0
- data/lib/hecks/quality_control/cli/qa_sweep/all_mode.rb +280 -0
- data/lib/hecks/quality_control/cli/qa_sweep/checks.rb +329 -0
- data/lib/hecks/quality_control/cli/qa_sweep/dials.rb +92 -0
- data/lib/hecks/quality_control/cli/qa_sweep/finding_report.rb +117 -0
- data/lib/hecks/quality_control/cli/qa_sweep/parsing.rb +175 -0
- data/lib/hecks/quality_control/cli/qa_sweep/release_mode.rb +60 -0
- data/lib/hecks/quality_control/cli/qa_sweep.rb +611 -0
- data/lib/hecks/quality_control/cli/qa_tick.rb +274 -0
- data/lib/hecks/quality_control/quality_control.bluebook +2224 -0
- data/lib/hecks/quality_control/quality_control.hecksagon +13 -0
- data/lib/hecks/quality_control/quality_control.ports.hecksagon +54 -0
- data/lib/hecks/query_ir.rb +346 -0
- data/lib/hecks/query_ir_mcp.rb +198 -0
- data/lib/hecks/release/runner/ci_publisher.rb +2 -2
- data/lib/hecks/release/runner/clean_tree.rb +42 -0
- data/lib/hecks/release/runner/console.rb +6 -0
- data/lib/hecks/release/runner/gem_publisher.rb +14 -7
- data/lib/hecks/release/runner/npm_publisher.rb +6 -36
- data/lib/hecks/release/runner/preflight.rb +26 -2
- data/lib/hecks/release/runner/published.rb +8 -26
- data/lib/hecks/release/runner/tagger.rb +30 -15
- data/lib/hecks/release/runner.rb +57 -6
- data/lib/hecks/router/namespace_installer.rb +3 -3
- data/lib/hecks/runtime/adapter_lookup.rb +124 -0
- data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +4 -0
- data/lib/hecks/runtime/dependency_planning.rb +20 -2
- data/lib/hecks/runtime/dispatcher.rb +53 -3
- data/lib/hecks/runtime/interpreting.rb +3 -0
- data/lib/hecks/runtime/loader.rb +65 -12
- data/lib/hecks/runtime/outbox.rb +1 -1
- data/lib/hecks/runtime/policy_interpreter.rb +5 -2
- data/lib/hecks/runtime/port_operation_interpreter.rb +2 -10
- data/lib/hecks/runtime/query_interpreter.rb +77 -0
- data/lib/hecks/runtime/reaction_outcome.rb +89 -0
- data/lib/hecks/runtime/refusal_wording.rb +23 -1
- data/lib/hecks/runtime/registry/verification.rb +163 -4
- data/lib/hecks/runtime/registry.rb +57 -3
- data/lib/hecks/runtime/remote_dispatcher.rb +31 -2
- data/lib/hecks/runtime/value/coercion.rb +23 -135
- data/lib/hecks/runtime/value/field_checks.rb +172 -0
- data/lib/hecks/runtime/value.rb +4 -1
- data/lib/hecks/runtime.rb +20 -6
- data/lib/hecks/rust_build/conformance.rb +158 -0
- data/lib/hecks/rust_build/conformance_fuzz.rb +71 -0
- data/lib/hecks/rust_build/coverage.rb +218 -0
- data/lib/hecks/rust_build/native_build.rb +124 -0
- data/lib/hecks/rust_build/project_rust/root_mod.rb +120 -0
- data/lib/hecks/rust_build/project_rust.rb +332 -0
- data/lib/hecks/rust_build/wasm.rb +105 -0
- data/lib/hecks/rust_build/wasm_browser.rb +113 -0
- data/lib/hecks/rust_build.rb +144 -0
- data/lib/hecks/storehouse.rb +16 -16
- data/lib/hecks/tenancy/bluebook/tenancy.bluebook +2 -2
- data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +1 -1
- data/lib/hecks/three_zero/forms.yml +127 -0
- data/lib/hecks/three_zero.rb +12 -0
- data/lib/hecks/tools/argument_gate_matrix.rb +316 -0
- data/lib/hecks/tools/comment_style.rb +1070 -0
- data/lib/hecks/tools/corpus_report.rb +87 -0
- data/lib/hecks/tools/deploy_recipe.rb +255 -0
- data/lib/hecks/tools/deploy_recipe_lint.rb +282 -0
- data/lib/hecks/tools/drop_implicit_append_fields.rb +95 -0
- data/lib/hecks/tools/era_projection_backfill.rb +94 -0
- data/lib/hecks/tools/era_reattest.rb +146 -0
- data/lib/hecks/tools/evolve_run.rb +189 -0
- data/lib/hecks/tools/fuzz_sweep.rb +373 -0
- data/lib/hecks/tools/heki_compaction.rb +100 -0
- data/lib/hecks/tools/hoist_local_givens.rb +246 -0
- data/lib/hecks/tools/journal_compaction.rb +89 -0
- data/lib/hecks/tools/oidc_manifests.rb +70 -0
- data/lib/hecks/tools/query_ir_run.rb +81 -0
- data/lib/hecks/tools/regeneration_run.rb +166 -0
- data/lib/hecks/tools/rust_comment_style.rb +642 -0
- data/lib/hecks/tools/sequence_script.rb +62 -0
- data/lib/hecks/tools/tail_merge.rb +117 -0
- data/lib/hecks/tools/tenant_provisioning.rb +142 -0
- data/lib/hecks/tools/translation_audit.rb +208 -0
- data/lib/hecks/tools/translation_scaffold.rb +131 -0
- data/lib/hecks/tools.rb +86 -0
- data/lib/hecks/version.rb +1 -1
- data/lib/hecks.rb +32 -15
- data/qa/settings.yml +66 -0
- data/rust/.cargo/config.toml +6 -0
- data/rust/.gitignore +2 -0
- data/rust/Cargo.lock +54 -0
- data/rust/Cargo.toml +61 -0
- data/rust/build/.cargo/config.toml +3 -0
- data/rust/build/.gitignore +1 -0
- data/rust/build/Cargo.lock +7 -0
- data/rust/build/Cargo.toml +19 -0
- data/rust/build/src/build_artifact.rs +47 -0
- data/rust/build/src/cargo_sync.rs +248 -0
- data/rust/build/src/json.rs +421 -0
- data/rust/build/src/lineage_pass.rs +285 -0
- data/rust/build/src/main.rs +51 -0
- data/rust/build/src/optional_pass.rs +150 -0
- data/rust/build/src/pipeline.rs +264 -0
- data/rust/build/src/reserved_names.rs +76 -0
- data/rust/build/src/resolve.rs +124 -0
- data/rust/build/src/root.rs +25 -0
- data/rust/build/src/sidecars.rs +73 -0
- data/rust/build/src/subprocess.rs +51 -0
- data/rust/build/src/tmp.rs +31 -0
- data/rust/codegen/.cargo/config.toml +3 -0
- data/rust/codegen/.gitignore +1 -0
- data/rust/codegen/Cargo.lock +7 -0
- data/rust/codegen/Cargo.toml +20 -0
- data/rust/codegen/src/attr.rs +32 -0
- data/rust/codegen/src/bridging.rs +481 -0
- data/rust/codegen/src/commands.rs +1226 -0
- data/rust/codegen/src/constraints.rs +109 -0
- data/rust/codegen/src/dependency_planning.rs +209 -0
- data/rust/codegen/src/domain_generator.rs +1288 -0
- data/rust/codegen/src/exemplar.rs +230 -0
- data/rust/codegen/src/expr_emitter.rs +87 -0
- data/rust/codegen/src/fielded.rs +192 -0
- data/rust/codegen/src/hecks_naming.rs +33 -0
- data/rust/codegen/src/json.rs +335 -0
- data/rust/codegen/src/json_codec.rs +697 -0
- data/rust/codegen/src/literal.rs +207 -0
- data/rust/codegen/src/main.rs +290 -0
- data/rust/codegen/src/manifest.rs +227 -0
- data/rust/codegen/src/mutations.rs +784 -0
- data/rust/codegen/src/naming.rs +286 -0
- data/rust/codegen/src/ports.rs +208 -0
- data/rust/codegen/src/prelude.rs +116 -0
- data/rust/codegen/src/queries.rs +786 -0
- data/rust/codegen/src/reactions.rs +532 -0
- data/rust/codegen/src/read_models.rs +697 -0
- data/rust/codegen/src/reference_specs.rs +40 -0
- data/rust/codegen/src/registry.rs +966 -0
- data/rust/codegen/src/reserved_names.rs +76 -0
- data/rust/codegen/src/shared.rs +93 -0
- data/rust/codegen/src/skip_reason.rs +25 -0
- data/rust/codegen/src/types.rs +362 -0
- data/rust/host/.cargo/config.toml +3 -0
- data/rust/host/.gitignore +1 -0
- data/rust/host/Cargo.lock +4297 -0
- data/rust/host/Cargo.toml +61 -0
- data/rust/host/HECKS_RELEASE +1 -0
- data/rust/host/rds-ca-bundle.pem +2736 -0
- data/rust/host/src/api.rs +1596 -0
- data/rust/host/src/approval.rs +652 -0
- data/rust/host/src/auth.rs +1287 -0
- data/rust/host/src/bin/expr_harness.rs +46 -0
- data/rust/host/src/bin/lineage_harness.rs +119 -0
- data/rust/host/src/bin/mint_harness.rs +135 -0
- data/rust/host/src/checkout.rs +265 -0
- data/rust/host/src/dispatch.rs +1443 -0
- data/rust/host/src/expr_json.rs +1117 -0
- data/rust/host/src/field_hints.rs +52 -0
- data/rust/host/src/ir.rs +733 -0
- data/rust/host/src/journal.rs +874 -0
- data/rust/host/src/lambda_client.rs +378 -0
- data/rust/host/src/log.rs +147 -0
- data/rust/host/src/main.rs +474 -0
- data/rust/host/src/mint.rs +1865 -0
- data/rust/host/src/payments/keystore.rs +330 -0
- data/rust/host/src/payments/tests.rs +1400 -0
- data/rust/host/src/payments.rs +767 -0
- data/rust/host/src/presentation.rs +698 -0
- data/rust/host/src/presentation_write.rs +1431 -0
- data/rust/host/src/query_step.rs +149 -0
- data/rust/host/src/rate_limit.rs +904 -0
- data/rust/host/src/reference_transform.rs +241 -0
- data/rust/host/src/reference_validate.rs +338 -0
- data/rust/host/src/resend.rs +296 -0
- data/rust/host/src/secrets.rs +106 -0
- data/rust/host/src/server.rs +663 -0
- data/rust/host/src/storage_shape.rs +175 -0
- data/rust/host/src/test_pg.rs +40 -0
- data/rust/host/src/ui_schema.rs +1351 -0
- data/rust/host/src/wasm_runner.rs +191 -0
- data/rust/host/src/web/newsletter/cooldown.rs +126 -0
- data/rust/host/src/web/newsletter/tests.rs +104 -0
- data/rust/host/src/web/newsletter.rs +310 -0
- data/rust/host/src/web/newsletter_send.rs +357 -0
- data/rust/host/src/web/registrations.rs +1505 -0
- data/rust/host/src/web.rs +2937 -0
- data/rust/lsp/.gitignore +1 -0
- data/rust/lsp/Cargo.lock +7 -0
- data/rust/lsp/Cargo.toml +16 -0
- data/rust/lsp/README.md +133 -0
- data/rust/lsp/src/diagnostics.rs +165 -0
- data/rust/lsp/src/json.rs +385 -0
- data/rust/lsp/src/main.rs +399 -0
- data/rust/lsp/src/outline.rs +177 -0
- data/rust/lsp/src/rpc.rs +67 -0
- data/rust/parser/.cargo/config.toml +5 -0
- data/rust/parser/.gitignore +1 -0
- data/rust/parser/Cargo.lock +7 -0
- data/rust/parser/Cargo.toml +21 -0
- data/rust/parser/src/build/closed_sets.rs +27 -0
- data/rust/parser/src/build/identity.rs +276 -0
- data/rust/parser/src/build/mod.rs +12 -0
- data/rust/parser/src/build/naming.rs +141 -0
- data/rust/parser/src/build/pattern_subset.rs +161 -0
- data/rust/parser/src/build/query_derive.rs +72 -0
- data/rust/parser/src/build/query_inference.rs +269 -0
- data/rust/parser/src/build/query_options.rs +100 -0
- data/rust/parser/src/build/read_model.rs +94 -0
- data/rust/parser/src/build/references.rs +32 -0
- data/rust/parser/src/canonical.rs +114 -0
- data/rust/parser/src/diag.rs +55 -0
- data/rust/parser/src/emit.rs +953 -0
- data/rust/parser/src/expr/ast_json.rs +147 -0
- data/rust/parser/src/expr/evaluator.rs +81 -0
- data/rust/parser/src/expr/mod.rs +84 -0
- data/rust/parser/src/expr/resolver.rs +497 -0
- data/rust/parser/src/ir.rs +387 -0
- data/rust/parser/src/keywords.rs +585 -0
- data/rust/parser/src/lex.rs +662 -0
- data/rust/parser/src/main.rs +252 -0
- data/rust/parser/src/parse/aggregate.rs +612 -0
- data/rust/parser/src/parse/chapter.rs +517 -0
- data/rust/parser/src/parse/command.rs +637 -0
- data/rust/parser/src/parse/domain_port.rs +127 -0
- data/rust/parser/src/parse/entity.rs +527 -0
- data/rust/parser/src/parse/file.rs +67 -0
- data/rust/parser/src/parse/hecksagon.rs +231 -0
- data/rust/parser/src/parse/lifecycle.rs +162 -0
- data/rust/parser/src/parse/mod.rs +1262 -0
- data/rust/parser/src/parse/policy.rs +87 -0
- data/rust/parser/src/parse/process_manager.rs +305 -0
- data/rust/parser/src/parse/query.rs +100 -0
- data/rust/parser/src/parse/read_model.rs +214 -0
- data/rust/parser/src/parse/value_object.rs +250 -0
- data/rust/parser/src/ruby_value.rs +422 -0
- data/rust/project/bridging.rb +257 -0
- data/rust/project/commands.rb +877 -0
- data/rust/project/constraints.rb +86 -0
- data/rust/project/dependency_planning.rb +178 -0
- data/rust/project/domain_generator.rb +980 -0
- data/rust/project/exemplar.rb +148 -0
- data/rust/project/expr_emitter.rb +72 -0
- data/rust/project/fielded.rb +141 -0
- data/rust/project/json_codec.rb +641 -0
- data/rust/project/mutations.rb +721 -0
- data/rust/project/naming.rb +205 -0
- data/rust/project/ports.rb +65 -0
- data/rust/project/queries.rb +493 -0
- data/rust/project/reactions.rb +317 -0
- data/rust/project/read_models.rb +690 -0
- data/rust/project/reference_specs.rb +27 -0
- data/rust/project/registry.rb +396 -0
- data/rust/project/skip_reason.rb +29 -0
- data/rust/project/types.rb +225 -0
- data/rust/project/write_if_changed.rb +83 -0
- data/rust/project.rb +30 -0
- data/rust/project_rust_pipeline.rb +383 -0
- data/rust/src/exemplar/commands.rs +279 -0
- data/rust/src/exemplar/constraints.rs +18 -0
- data/rust/src/exemplar/fielded.rs +280 -0
- data/rust/src/exemplar/json.rs +277 -0
- data/rust/src/exemplar/mod.rs +17 -0
- data/rust/src/exemplar/mutations.rs +80 -0
- data/rust/src/exemplar/queries.rs +33 -0
- data/rust/src/exemplar/reactions.rs +88 -0
- data/rust/src/exemplar/read_models.rs +48 -0
- data/rust/src/exemplar/registry.rs +166 -0
- data/rust/src/exemplar/types.rs +44 -0
- data/rust/src/kernel/attribute_shapes/composite.rs +88 -0
- data/rust/src/kernel/attribute_shapes/list.rs +18 -0
- data/rust/src/kernel/attribute_shapes/mod.rs +44 -0
- data/rust/src/kernel/attribute_shapes/optional.rs +9 -0
- data/rust/src/kernel/attribute_shapes/scalar.rs +39 -0
- data/rust/src/kernel/cli.rs +585 -0
- data/rust/src/kernel/dispatch.rs +1090 -0
- data/rust/src/kernel/expr.rs +458 -0
- data/rust/src/kernel/expression_operators/arithmetic.rs +128 -0
- data/rust/src/kernel/expression_operators/comparison.rs +48 -0
- data/rust/src/kernel/expression_operators/enumeration.rs +232 -0
- data/rust/src/kernel/expression_operators/logical.rs +13 -0
- data/rust/src/kernel/expression_operators/membership.rs +18 -0
- data/rust/src/kernel/expression_operators/mod.rs +68 -0
- data/rust/src/kernel/expression_operators/pattern_match.rs +73 -0
- data/rust/src/kernel/expression_operators/positional.rs +126 -0
- data/rust/src/kernel/expression_operators/presence.rs +101 -0
- data/rust/src/kernel/expression_operators/sign_test.rs +16 -0
- data/rust/src/kernel/expression_operators/sized.rs +35 -0
- data/rust/src/kernel/expression_operators/text.rs +79 -0
- data/rust/src/kernel/expression_operators/to_string.rs +18 -0
- data/rust/src/kernel/json.rs +779 -0
- data/rust/src/kernel/mod.rs +133 -0
- data/rust/src/kernel/named_query.rs +219 -0
- data/rust/src/kernel/orchestrate.rs +1810 -0
- data/rust/src/kernel/pattern.rs +307 -0
- data/rust/src/kernel/query_comparators.rs +206 -0
- data/rust/src/kernel/query_ordering.rs +103 -0
- data/rust/src/kernel/read_model.rs +914 -0
- data/rust/src/kernel/reference_lookup.rs +149 -0
- data/rust/src/kernel/repository.rs +468 -0
- data/rust/src/kernel/routing.rs +612 -0
- data/rust/src/kernel/vocab/aggregate_dispatch_order.rs +118 -0
- data/rust/src/kernel/vocab/entity_dispatch_order.rs +114 -0
- data/rust/src/kernel/vocab/field_hint.rs +70 -0
- data/rust/src/kernel/vocab/mod.rs +20 -0
- data/rust/src/kernel/vocab/query_comparator.rs +65 -0
- data/rust/src/kernel/vocab/refusal_template.rs +1592 -0
- data/rust/src/lib.rs +8 -0
- data/rust/src/main.rs +16 -0
- data/rust/web/.gitignore +1 -0
- data/rust/web/Cargo.lock +119 -0
- data/rust/web/Cargo.toml +18 -0
- data/rust/web/src/lib.rs +8 -0
- metadata +477 -12
- data/lib/hecks/facade/cli_door.rb +0 -96
- data/lib/hecks/facade/cli_runner.rb +0 -127
- data/lib/hecks/mcp_door_scope.rb +0 -95
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
require_relative "../invalid_value_generator"
|
|
2
|
+
require_relative "../value_generator"
|
|
3
|
+
require_relative "../../runtime/value"
|
|
4
|
+
|
|
5
|
+
module Hecks
|
|
6
|
+
module Fuzzing
|
|
7
|
+
class SequenceGenerator
|
|
8
|
+
# Adversarial argument mutations, the shapes Ruby/Rust divergences were found through.
|
|
9
|
+
# Off at `adversarial: 0.0`, which returns before any RNG draw.
|
|
10
|
+
module Adversary
|
|
11
|
+
KINDS = %i[
|
|
12
|
+
routing_key
|
|
13
|
+
blank_identity
|
|
14
|
+
null_value_object
|
|
15
|
+
duplicate_entity_identity
|
|
16
|
+
omit_mapped_argument
|
|
17
|
+
refusal_precedence
|
|
18
|
+
].freeze
|
|
19
|
+
|
|
20
|
+
# `to`/`with` are the dispatcher's routing keywords and `id` the untyped identity fallback;
|
|
21
|
+
# each is offered as bare null, a scalar, or a routing-shaped object.
|
|
22
|
+
ROUTING_KEYS = %w[to with id].freeze
|
|
23
|
+
ROUTING_SHAPES = %w[null scalar route].freeze
|
|
24
|
+
|
|
25
|
+
# A creating command's own identity part, blank three ways.
|
|
26
|
+
BLANK_SHAPES = %w[empty whitespace null].freeze
|
|
27
|
+
|
|
28
|
+
# A single-field value object as bare `null` and as `{}`.
|
|
29
|
+
VALUE_OBJECT_SHAPES = %w[null empty_object].freeze
|
|
30
|
+
|
|
31
|
+
# An unknown key, a type-mismatched key and an absent required key in one step, so both
|
|
32
|
+
# engines must pick which refusal wins. The second row reaches later `DISPATCH_ORDER`
|
|
33
|
+
# stages (lib/hecks/vocabulary.rb): `nonexistent` addresses an id nothing holds,
|
|
34
|
+
# `lifecycle` aims a transition-guarded command at a real record, `role` binds a caller
|
|
35
|
+
# the command does not name.
|
|
36
|
+
PRECEDENCE_SHAPES = %w[
|
|
37
|
+
unknown+mismatch+absent unknown+absent unknown+mismatch mismatch+absent
|
|
38
|
+
nonexistent+mismatch nonexistent+unknown lifecycle+mismatch role+absent role+nonexistent
|
|
39
|
+
].freeze
|
|
40
|
+
|
|
41
|
+
# A drawn caller on a role-gated command, separate from `KINDS` (`role_draw:`) so it
|
|
42
|
+
# composes with any argument mutation and draws nothing when off.
|
|
43
|
+
# matching the command's own role
|
|
44
|
+
# mismatched another declared role, or none the domain knows
|
|
45
|
+
# absent_on_gated no caller at all: the unchecked default, recorded as a control
|
|
46
|
+
# actor_known the role plus an actor this sequence already granted it
|
|
47
|
+
# actor_unknown the role plus an actor nothing granted
|
|
48
|
+
CALLER_SHAPES = %w[matching mismatched absent_on_gated actor_known actor_unknown].freeze
|
|
49
|
+
UNKNOWN_ROLE = "Nobody the domain names".freeze
|
|
50
|
+
|
|
51
|
+
# An entity command two or more hops deep, addressed by flat one-head-per-hop args or the
|
|
52
|
+
# routed `to: { aggregate:, entities: [...] }` envelope. picker.rb weights these up.
|
|
53
|
+
DEEP_ENTITY_DEPTH = 2
|
|
54
|
+
DEEP_ENTITY_WEIGHT = 4
|
|
55
|
+
|
|
56
|
+
# Needs an element this sequence already appended, a rare moment, so it is weighted up
|
|
57
|
+
# when on offer.
|
|
58
|
+
DUPLICATE_IDENTITY_WEIGHT = 3
|
|
59
|
+
|
|
60
|
+
private
|
|
61
|
+
|
|
62
|
+
def adversarial? = @adversarial.positive?
|
|
63
|
+
|
|
64
|
+
# `[]` with no RNG draw when adversarial mode is off; otherwise the deep-entity note, then
|
|
65
|
+
# with probability `@adversarial` one mutation applicable to this step.
|
|
66
|
+
def adversarial_mutations!(args, entry, catalog)
|
|
67
|
+
return [] unless adversarial?
|
|
68
|
+
|
|
69
|
+
mutations = []
|
|
70
|
+
mutations << deep_entity_addressing!(args, entry) if (entry[:chain] || []).size >= DEEP_ENTITY_DEPTH
|
|
71
|
+
return mutations if @random.rand >= @adversarial
|
|
72
|
+
|
|
73
|
+
applicable = KINDS.select { |kind| send(:"#{kind}_applicable?", args, entry, catalog) }
|
|
74
|
+
return mutations if applicable.empty?
|
|
75
|
+
|
|
76
|
+
weighted = applicable.flat_map { |kind| [kind] * (kind == :duplicate_entity_identity ? DUPLICATE_IDENTITY_WEIGHT : 1) }
|
|
77
|
+
mutations << send(:"apply_#{weighted.sample(random: @random)}!", args, entry, catalog)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def deep_entity_addressing!(args, entry)
|
|
81
|
+
depth = entry[:chain].size
|
|
82
|
+
routed = @random.rand(2).zero?
|
|
83
|
+
note = { "mutation" => "deep_entity", "bug" => "BUG#11", "depth" => depth,
|
|
84
|
+
"addressing" => routed ? "routed" : "flat" }
|
|
85
|
+
return note unless routed
|
|
86
|
+
|
|
87
|
+
heads = [entry[:aggregate], *entry[:chain]].map { |construct| (construct.identified_by || :id).to_s }
|
|
88
|
+
scalars = heads.map { |head| ValueGenerator.scalar_of(args[head]) }
|
|
89
|
+
# The heads leave the flat args unless the command declares an attribute of that name
|
|
90
|
+
# (chess's `Piece.Move` declares `id`); dropping that would be a different mutation.
|
|
91
|
+
heads.each { |head| args.delete(head) unless entry[:command].attribute(head) }
|
|
92
|
+
args["to"] = { "aggregate" => scalars.first, "entities" => scalars.drop(1) }
|
|
93
|
+
note
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Not applied over a routed `to:` from `deep_entity_addressing!`; overwriting it would
|
|
97
|
+
# contradict that step's note.
|
|
98
|
+
def routing_key_applicable?(args, _entry, _catalog) = !args.key?("to")
|
|
99
|
+
|
|
100
|
+
def apply_routing_key!(args, entry, _catalog)
|
|
101
|
+
key = ROUTING_KEYS.sample(random: @random)
|
|
102
|
+
shape = ROUTING_SHAPES.sample(random: @random)
|
|
103
|
+
args[key] =
|
|
104
|
+
case shape
|
|
105
|
+
when "null" then nil
|
|
106
|
+
when "scalar" then routing_scalar(args, entry)
|
|
107
|
+
else routing_object(args, entry)
|
|
108
|
+
end
|
|
109
|
+
{ "mutation" => "routing_key", "bug" => "BUG#7/#16/#8", "key" => key, "shape" => shape,
|
|
110
|
+
"declared" => !entry[:command].attribute(key).nil? }
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# This step's own parent id, an out-of-range Integer, or a minted id nothing holds.
|
|
114
|
+
def routing_scalar(args, entry)
|
|
115
|
+
case @random.rand(3)
|
|
116
|
+
when 0 then parent_scalar_of(args, entry)
|
|
117
|
+
when 1 then ValueGenerator::INTEGER_EDGE_CASES.sample(random: @random)
|
|
118
|
+
else ValueGenerator.random_id(@random)
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def routing_object(args, entry)
|
|
123
|
+
entities = (entry[:chain] || []).map { |piece| ValueGenerator.scalar_of(args[(piece.identified_by || :id).to_s]) }
|
|
124
|
+
# Half the time one identity too many, a depth the verb lacks, which both engines must
|
|
125
|
+
# refuse alike.
|
|
126
|
+
entities << ValueGenerator.random_id(@random) if @random.rand(2).zero?
|
|
127
|
+
{ "aggregate" => parent_scalar_of(args, entry), "entities" => entities }
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def parent_scalar_of(args, entry)
|
|
131
|
+
key = (entry[:aggregate].identified_by || :id).to_s
|
|
132
|
+
args.key?(key) ? ValueGenerator.scalar_of(args[key]) : identity_scalar_of(entry[:aggregate], args)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def blank_identity_applicable?(args, entry, catalog) = blank_identity_targets(args, entry, catalog).any?
|
|
136
|
+
|
|
137
|
+
# Identity heads this step supplies: a creating command's own (every composite part) and
|
|
138
|
+
# an append's entity identity arguments.
|
|
139
|
+
def blank_identity_targets(args, entry, catalog)
|
|
140
|
+
targets = []
|
|
141
|
+
if entry[:entity].nil? && entry[:command].creates?
|
|
142
|
+
aggregate = entry[:aggregate]
|
|
143
|
+
heads = composite_identity?(aggregate) ? aggregate.identity_heads : [aggregate.identified_by || :id]
|
|
144
|
+
targets.concat(heads.map(&:to_s))
|
|
145
|
+
end
|
|
146
|
+
populator = populator_for_entry(catalog, entry)
|
|
147
|
+
targets.concat(populator[:identity_arguments].map(&:to_s)) if populator
|
|
148
|
+
targets.uniq.select { |head| args.key?(head) }
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def apply_blank_identity!(args, entry, catalog)
|
|
152
|
+
head = blank_identity_targets(args, entry, catalog).sample(random: @random)
|
|
153
|
+
shape = BLANK_SHAPES.sample(random: @random)
|
|
154
|
+
blank = shape == "empty" ? "" : " "
|
|
155
|
+
args[head] =
|
|
156
|
+
if shape == "null" then nil
|
|
157
|
+
elsif args[head].is_a?(Hash) then args[head].transform_values { blank }
|
|
158
|
+
else blank
|
|
159
|
+
end
|
|
160
|
+
{ "mutation" => "blank_identity", "bug" => "BUG#15", "argument" => head, "shape" => shape }
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def null_value_object_applicable?(args, entry, _catalog) = value_object_targets(args, entry).any?
|
|
164
|
+
|
|
165
|
+
def value_object_targets(args, entry)
|
|
166
|
+
aggregate = entry[:aggregate]
|
|
167
|
+
entry[:command].attributes.reject { |attribute| attribute.list? || attribute.reference? }
|
|
168
|
+
.select { |attribute| args.key?(attribute.name.to_s) }
|
|
169
|
+
.filter_map do |attribute|
|
|
170
|
+
value_object = Runtime::Value.value_object_for(aggregate, attribute.type.to_s)
|
|
171
|
+
[attribute, value_object] if value_object&.sole_attribute
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def apply_null_value_object!(args, entry, _catalog)
|
|
176
|
+
targets = value_object_targets(args, entry)
|
|
177
|
+
closed = targets.select { |_, value_object| value_object.closed_set? }
|
|
178
|
+
attribute, value_object = (closed.empty? ? targets : closed).sample(random: @random)
|
|
179
|
+
shape = VALUE_OBJECT_SHAPES.sample(random: @random)
|
|
180
|
+
args[attribute.name.to_s] = shape == "null" ? nil : {}
|
|
181
|
+
{ "mutation" => "null_value_object", "bug" => "BUG#14", "argument" => attribute.name.to_s,
|
|
182
|
+
"value_object" => value_object.hecks_name, "closed_set" => value_object.closed_set? == true,
|
|
183
|
+
"shape" => shape }
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def duplicate_entity_identity_applicable?(args, entry, catalog) = duplicate_identity_pool(args, entry, catalog).any?
|
|
187
|
+
|
|
188
|
+
def duplicate_identity_pool(args, entry, catalog)
|
|
189
|
+
populator = populator_for_entry(catalog, entry)
|
|
190
|
+
return [] unless populator && populator[:identity_arguments].any?
|
|
191
|
+
|
|
192
|
+
@appended_identities[append_pool_key(populator, args)]
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def apply_duplicate_entity_identity!(args, entry, catalog)
|
|
196
|
+
populator = populator_for_entry(catalog, entry)
|
|
197
|
+
tuple = duplicate_identity_pool(args, entry, catalog).sample(random: @random)
|
|
198
|
+
args.merge!(tuple)
|
|
199
|
+
{ "mutation" => "duplicate_entity_identity", "bug" => "BUG#13", "entity" => populator[:entity].hecks_name,
|
|
200
|
+
"composite" => populator[:identity_arguments].size > 1, "identity" => tuple }
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def omit_mapped_argument_applicable?(args, entry, _catalog) = mapped_argument_targets(args, entry).any?
|
|
204
|
+
|
|
205
|
+
# An append's mapped source arguments, or a plain creating command's non-identity
|
|
206
|
+
# attributes; never an identity head.
|
|
207
|
+
def mapped_argument_targets(args, entry)
|
|
208
|
+
command = entry[:command]
|
|
209
|
+
heads = identity_heads_of(entry)
|
|
210
|
+
mapped = command.mutations.select { |mutation| mutation.op == :append }
|
|
211
|
+
.flat_map { |mutation| mutation.source.values.grep(Symbol).map(&:to_s) }
|
|
212
|
+
if mapped.empty? && !entry.key?(:entity) && command.creates?
|
|
213
|
+
mapped = command.attributes.map { |attribute| attribute.name.to_s }
|
|
214
|
+
end
|
|
215
|
+
command.attributes.select do |attribute|
|
|
216
|
+
name = attribute.name.to_s
|
|
217
|
+
mapped.include?(name) && args.key?(name) && !heads.include?(name)
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def apply_omit_mapped_argument!(args, entry, _catalog)
|
|
222
|
+
attribute = mapped_argument_targets(args, entry).sample(random: @random)
|
|
223
|
+
args.delete(attribute.name.to_s)
|
|
224
|
+
{ "mutation" => "omit_mapped_argument", "bug" => "BUG#12", "argument" => attribute.name.to_s,
|
|
225
|
+
"optional" => attribute.optional? == true }
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def refusal_precedence_applicable?(args, entry, _catalog)
|
|
229
|
+
precedence_shapes_for(args, entry).any?
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
# Every shape whose parts this step can carry. `nonexistent` and `lifecycle` need a
|
|
233
|
+
# record-addressing command with flat addressing; `lifecycle` also a guarded command;
|
|
234
|
+
# `role` a declared role.
|
|
235
|
+
def precedence_shapes_for(args, entry)
|
|
236
|
+
can = {
|
|
237
|
+
"mismatch" => corruptible_attributes(args, entry).any?,
|
|
238
|
+
"absent" => droppable_required_attributes(args, entry).any?,
|
|
239
|
+
"unknown" => true,
|
|
240
|
+
"nonexistent" => acts_on_record?(args, entry),
|
|
241
|
+
"lifecycle" => acts_on_record?(args, entry) && transition_guarded?(entry),
|
|
242
|
+
"role" => !entry[:command].role.to_s.empty?
|
|
243
|
+
}
|
|
244
|
+
PRECEDENCE_SHAPES.select { |shape| shape.split("+").all? { |part| can.fetch(part) } }
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def acts_on_record?(args, entry) = !entry[:command].creates? && !args.key?("to")
|
|
248
|
+
|
|
249
|
+
def transition_guarded?(entry)
|
|
250
|
+
command = entry[:command]
|
|
251
|
+
owner = entry.key?(:entity) ? entry[:entity] : entry[:aggregate]
|
|
252
|
+
return true if command.from
|
|
253
|
+
return false unless owner.respond_to?(:lifecycle)
|
|
254
|
+
|
|
255
|
+
owner.lifecycle&.transitions_for(command.hecks_name)&.any? || false
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
def apply_refusal_precedence!(args, entry, catalog)
|
|
259
|
+
corruptible = corruptible_attributes(args, entry)
|
|
260
|
+
droppable = droppable_required_attributes(args, entry)
|
|
261
|
+
wanted = precedence_shapes_for(args, entry).sample(random: @random).split("+")
|
|
262
|
+
detail = { "mutation" => "refusal_precedence", "bug" => "BUG#7/#8/#14" }
|
|
263
|
+
applied = []
|
|
264
|
+
|
|
265
|
+
if wanted.include?("absent")
|
|
266
|
+
dropped = droppable.sample(random: @random)
|
|
267
|
+
args.delete(dropped.name.to_s)
|
|
268
|
+
detail["absent"] = dropped.name.to_s
|
|
269
|
+
applied << "absent"
|
|
270
|
+
corruptible -= [dropped]
|
|
271
|
+
end
|
|
272
|
+
if wanted.include?("mismatch") && corruptible.any?
|
|
273
|
+
attribute = corruptible.sample(random: @random)
|
|
274
|
+
args[attribute.name.to_s] = InvalidValueGenerator.corrupt(attribute, entry[:aggregate], random: @random)
|
|
275
|
+
detail["mismatched"] = attribute.name.to_s
|
|
276
|
+
applied << "mismatch"
|
|
277
|
+
end
|
|
278
|
+
if wanted.include?("unknown")
|
|
279
|
+
name, value = InvalidValueGenerator.undeclared_argument(random: @random)
|
|
280
|
+
args[name] = value
|
|
281
|
+
detail["unknown"] = name
|
|
282
|
+
applied << "unknown"
|
|
283
|
+
end
|
|
284
|
+
apply_late_stage_parts!(wanted, args, entry, detail, applied, catalog)
|
|
285
|
+
# Reported as what was done: a single-attribute command cannot carry both a drop and a
|
|
286
|
+
# corruption.
|
|
287
|
+
detail.merge("shape" => applied.sort.join("+"))
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
# The parts past the argument gate. `nonexistent` re-addresses the last hop to an id
|
|
291
|
+
# nothing holds; `lifecycle` mutates nothing and is recorded so the pairing is visible;
|
|
292
|
+
# `role` parks a mismatched caller for StepBuilder to bind around the dispatch.
|
|
293
|
+
def apply_late_stage_parts!(wanted, args, entry, detail, applied, catalog)
|
|
294
|
+
if wanted.include?("nonexistent")
|
|
295
|
+
piece = (entry[:chain] || []).last || entry[:aggregate]
|
|
296
|
+
head = (piece.identified_by || :id).to_s
|
|
297
|
+
args[head] = identity_shaped(piece, piece.identified_by, ValueGenerator.random_id(@random), entry[:aggregate])
|
|
298
|
+
detail["nonexistent"] = head
|
|
299
|
+
applied << "nonexistent"
|
|
300
|
+
end
|
|
301
|
+
if wanted.include?("lifecycle")
|
|
302
|
+
detail["lifecycle"] = entry[:command].from || "transition-guarded"
|
|
303
|
+
applied << "lifecycle"
|
|
304
|
+
end
|
|
305
|
+
return unless wanted.include?("role")
|
|
306
|
+
|
|
307
|
+
@precedence_caller = { "role" => other_role(entry[:command].role.to_s, catalog) }
|
|
308
|
+
detail["role"] = @precedence_caller["role"]
|
|
309
|
+
applied << "role"
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
def role_draw? = @role_draw.positive?
|
|
313
|
+
|
|
314
|
+
# `[caller, note]`: the caller StepBuilder binds around the dispatch (nil for the unchecked
|
|
315
|
+
# control) and the note for the step's `"adversarial"` metadata. A caller parked by
|
|
316
|
+
# `refusal_precedence` wins; an ungated command draws nothing.
|
|
317
|
+
def caller_draw!(entry, catalog)
|
|
318
|
+
if @precedence_caller
|
|
319
|
+
caller = @precedence_caller
|
|
320
|
+
@precedence_caller = nil
|
|
321
|
+
return [caller, nil]
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
role = entry[:command].role.to_s
|
|
325
|
+
return [nil, nil] if !role_draw? || role.empty? || @random.rand >= @role_draw
|
|
326
|
+
|
|
327
|
+
shapes = CALLER_SHAPES.dup
|
|
328
|
+
shapes.delete("actor_known") if @granted[role].empty?
|
|
329
|
+
shape = shapes.sample(random: @random)
|
|
330
|
+
caller = caller_for_shape(shape, role, catalog)
|
|
331
|
+
note = { "mutation" => "caller_role", "angle" => "ANGLE-5", "shape" => shape, "gated_role" => role }
|
|
332
|
+
[caller, caller ? note.merge(caller) : note]
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
def caller_for_shape(shape, role, catalog)
|
|
336
|
+
case shape
|
|
337
|
+
when "matching" then { "role" => role }
|
|
338
|
+
when "mismatched" then { "role" => other_role(role, catalog) }
|
|
339
|
+
when "actor_known" then { "role" => role, "actor_id" => @granted[role].sample(random: @random) }
|
|
340
|
+
when "actor_unknown" then { "role" => role, "actor_id" => ValueGenerator.random_id(@random) }
|
|
341
|
+
end
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
# Another declared role when there is one (a real wrong hat), else a role none names.
|
|
345
|
+
def other_role(role, catalog)
|
|
346
|
+
others = catalog[:roles] - [role]
|
|
347
|
+
others.empty? ? UNKNOWN_ROLE : others.sample(random: @random)
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
def corruptible_attributes(args, entry)
|
|
351
|
+
entry[:command].attributes.reject(&:list?).select { |attribute| args.key?(attribute.name.to_s) }
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
def droppable_required_attributes(args, entry)
|
|
355
|
+
heads = identity_heads_of(entry)
|
|
356
|
+
entry[:command].attributes.reject(&:optional?).select do |attribute|
|
|
357
|
+
args.key?(attribute.name.to_s) && !heads.include?(attribute.name.to_s)
|
|
358
|
+
end
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
# Aims a grant at a role some command declares; random role text would make `actor_known`
|
|
362
|
+
# unreachable. Does nothing without the role draw.
|
|
363
|
+
def steer_grant!(args, entry, catalog)
|
|
364
|
+
return unless role_draw? && catalog[:grant_verbs].include?(entry[:verb]) && catalog[:roles].any?
|
|
365
|
+
return unless args.key?("role_name")
|
|
366
|
+
|
|
367
|
+
args["role_name"] = { "value" => catalog[:roles].sample(random: @random) }
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
def populator_for_entry(catalog, entry)
|
|
371
|
+
owner = entry.key?(:entity) ? entry[:entity] : entry[:aggregate]
|
|
372
|
+
catalog[:populators].find { |p| p[:command].equal?(entry[:command]) && p[:owner].equal?(owner) }
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
# Every identity head this step addresses by: the aggregate's own, the untyped `id`, and
|
|
376
|
+
# one per entity hop.
|
|
377
|
+
def identity_heads_of(entry)
|
|
378
|
+
heads = entry[:aggregate].identity_heads.map(&:to_s) + ["id"]
|
|
379
|
+
(entry[:chain] || []).each { |piece| heads.concat(piece.identity_heads.map(&:to_s)) }
|
|
380
|
+
heads.uniq
|
|
381
|
+
end
|
|
382
|
+
end
|
|
383
|
+
end
|
|
384
|
+
end
|
|
385
|
+
end
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
module Hecks
|
|
2
|
+
module Fuzzing
|
|
3
|
+
class SequenceGenerator
|
|
4
|
+
# Everything the booted domain offers, indexed for the picker:
|
|
5
|
+
# creating vs instance commands, entity commands, queries, and the
|
|
6
|
+
# populators that predict where an appended element lands.
|
|
7
|
+
module Catalog
|
|
8
|
+
private
|
|
9
|
+
|
|
10
|
+
def build_catalog(runtime)
|
|
11
|
+
creating = []
|
|
12
|
+
instance = []
|
|
13
|
+
entity_commands = []
|
|
14
|
+
queries = []
|
|
15
|
+
entity_queries = []
|
|
16
|
+
read_models = []
|
|
17
|
+
|
|
18
|
+
runtime.registry.bluebooks.each do |domain_name, bluebook|
|
|
19
|
+
bluebook.aggregates.each do |aggregate|
|
|
20
|
+
aggregate.commands.each do |command|
|
|
21
|
+
entry = { verb: "#{domain_name}::#{aggregate.hecks_name}.#{command.hecks_name}",
|
|
22
|
+
command: command, aggregate: aggregate }
|
|
23
|
+
(command.creates? ? creating : instance) << entry
|
|
24
|
+
end
|
|
25
|
+
aggregate.queries.each do |query|
|
|
26
|
+
queries << { verb: "#{domain_name}::#{aggregate.hecks_name}.#{query.name}",
|
|
27
|
+
query: query, aggregate: aggregate }
|
|
28
|
+
end
|
|
29
|
+
catalog_entities(domain_name, aggregate, entity_commands, entity_queries)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Reports use the bare "Domain.report_name" form, which `Dispatcher#query`
|
|
33
|
+
# routes to the read model. Keyed by `model:`, not `aggregate:`: a
|
|
34
|
+
# rootless report has no aggregate to be eligible against.
|
|
35
|
+
bluebook.read_models.each do |model|
|
|
36
|
+
read_models << { verb: "#{domain_name}.#{model.query_name}", model: model }
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
{ creating: creating, instance: instance, entity_commands: entity_commands,
|
|
41
|
+
queries: queries, entity_queries: entity_queries, read_models: read_models,
|
|
42
|
+
populators: populators(runtime),
|
|
43
|
+
# The aggregates a query filters on, and the fields it compares.
|
|
44
|
+
query_bindings: build_query_bindings(runtime),
|
|
45
|
+
# Every `role "..."` a command declares; the `mismatched` caller shape draws from it.
|
|
46
|
+
roles: (creating + instance + entity_commands).filter_map { |e| e[:command].role }
|
|
47
|
+
.map(&:to_s).reject(&:empty?).uniq.sort,
|
|
48
|
+
# The grant verb each authorization provider declares, read off the
|
|
49
|
+
# declaration rather than a hard-coded Governance name.
|
|
50
|
+
grant_verbs: runtime.registry.authorization_providers
|
|
51
|
+
.filter_map { |chapter| chapter.provided_verb(Bluebook::Capabilities::AUTHORIZATION, :grant) }
|
|
52
|
+
.sort,
|
|
53
|
+
# Aggregates some creating command can make; `satisfiable?` only waits on these.
|
|
54
|
+
creatable: creating.to_set { |entry| entry[:aggregate].hecks_name } }
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Indexes every entity at every depth; `chain:` is the whole hop list
|
|
58
|
+
# (`[Board, Card]`) and `entity:` its last hop.
|
|
59
|
+
#
|
|
60
|
+
# Entity queries stay one hop deep: a nested entity's query has no
|
|
61
|
+
# established wire spelling.
|
|
62
|
+
def catalog_entities(domain_name, aggregate, entity_commands, entity_queries)
|
|
63
|
+
each_entity_chain(aggregate) do |chain|
|
|
64
|
+
entity = chain.last
|
|
65
|
+
path = chain.map(&:hecks_name).join(".")
|
|
66
|
+
entity.commands.each do |command|
|
|
67
|
+
entity_commands << { verb: "#{domain_name}::#{aggregate.hecks_name}.#{path}.#{command.hecks_name}",
|
|
68
|
+
command: command, aggregate: aggregate, entity: entity, chain: chain }
|
|
69
|
+
end
|
|
70
|
+
next unless chain.size == 1
|
|
71
|
+
|
|
72
|
+
entity.queries.each do |query|
|
|
73
|
+
entity_queries << { verb: "#{domain_name}::#{aggregate.hecks_name}.#{path}.#{query.name}",
|
|
74
|
+
query: query, aggregate: aggregate, entity: entity }
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Depth-first, parents before children, in declaration order, which
|
|
80
|
+
# keeps a pinned seed's picker pool stable.
|
|
81
|
+
def each_entity_chain(owner, chain = [], &block)
|
|
82
|
+
owner.entities.each do |entity|
|
|
83
|
+
path = chain + [entity]
|
|
84
|
+
yield path
|
|
85
|
+
each_entity_chain(entity, path, &block)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Which command appends to which entity list, so a successful dispatch
|
|
90
|
+
# can predict the identity of the element it added.
|
|
91
|
+
#
|
|
92
|
+
# `owner_chain:` is `[]` for an aggregate-level append and the entity
|
|
93
|
+
# path for an entity-level one. `identity_arguments:` lists every
|
|
94
|
+
# identity head sourced from a command argument; `identity_argument:`
|
|
95
|
+
# is the single-head reading the auto-mint prediction keys on.
|
|
96
|
+
def populators(runtime)
|
|
97
|
+
runtime.registry.bluebooks.each_value.flat_map do |bluebook|
|
|
98
|
+
bluebook.aggregates.flat_map do |aggregate|
|
|
99
|
+
owners = [[aggregate, []]]
|
|
100
|
+
each_entity_chain(aggregate) { |chain| owners << [chain.last, chain] }
|
|
101
|
+
|
|
102
|
+
owners.flat_map do |owner, chain|
|
|
103
|
+
owner.commands.filter_map { |command| populator_for(aggregate, owner, chain, command) }
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def populator_for(aggregate, owner, chain, command)
|
|
110
|
+
append = command.mutations.find { |mutation| mutation.op == :append }
|
|
111
|
+
return unless append
|
|
112
|
+
|
|
113
|
+
list_attribute = owner.attribute(append.target)
|
|
114
|
+
return unless list_attribute&.list?
|
|
115
|
+
|
|
116
|
+
entity = owner.entities.find { |candidate| candidate.hecks_name == list_attribute.type.to_s }
|
|
117
|
+
return unless entity
|
|
118
|
+
|
|
119
|
+
identity_field = entity.identified_by
|
|
120
|
+
mapped = identity_field && append.source[identity_field]
|
|
121
|
+
identity_arguments = entity.identity_heads.filter_map do |head|
|
|
122
|
+
source = append.source[head]
|
|
123
|
+
source if source.is_a?(Symbol)
|
|
124
|
+
end
|
|
125
|
+
{ command: command, aggregate: aggregate, owner: owner, owner_chain: chain, entity: entity,
|
|
126
|
+
identity_field: identity_field, identity_argument: mapped.is_a?(Symbol) ? mapped : nil,
|
|
127
|
+
identity_arguments: identity_arguments }
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
require_relative "../value_generator"
|
|
2
|
+
require_relative "../../naming"
|
|
3
|
+
|
|
4
|
+
module Hecks
|
|
5
|
+
module Fuzzing
|
|
6
|
+
class SequenceGenerator
|
|
7
|
+
# What a successful step created, and how a later step draws on it —
|
|
8
|
+
# the state tracking that makes multi-step sequences reach further
|
|
9
|
+
# than single calls ever did.
|
|
10
|
+
module OutcomeTracker
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
def record_outcome(catalog, entry, args)
|
|
14
|
+
aggregate = entry[:aggregate]
|
|
15
|
+
parent_scalar = identity_scalar_of(aggregate, args)
|
|
16
|
+
|
|
17
|
+
@known_ids[aggregate.hecks_name] << parent_scalar if entry[:entity].nil? && entry[:command].creates?
|
|
18
|
+
record_grant(args) if catalog[:grant_verbs].include?(entry[:verb])
|
|
19
|
+
|
|
20
|
+
populator = populator_for_entry(catalog, entry)
|
|
21
|
+
return unless populator
|
|
22
|
+
|
|
23
|
+
key = append_pool_key(populator, args)
|
|
24
|
+
|
|
25
|
+
# Auto-minted identities land at count-so-far + 1; an explicit one is
|
|
26
|
+
# read back from the args this generator supplied.
|
|
27
|
+
new_id =
|
|
28
|
+
if populator[:identity_argument]
|
|
29
|
+
ValueGenerator.scalar_of(args[populator[:identity_argument].to_s])
|
|
30
|
+
else
|
|
31
|
+
(@entity_known_ids[key].size + 1).to_s
|
|
32
|
+
end
|
|
33
|
+
@entity_known_ids[key] << new_id
|
|
34
|
+
|
|
35
|
+
# Keep the caller-supplied identity tuple whole so the duplicate-identity
|
|
36
|
+
# mutation can offer it again under the same parent.
|
|
37
|
+
return if populator[:identity_arguments].empty?
|
|
38
|
+
|
|
39
|
+
@appended_identities[key] << populator[:identity_arguments].to_h { |name| [name.to_s, args[name.to_s]] }
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Records a grant that took effect: `actor_id` now holds `role_name`, which
|
|
43
|
+
# lets the `actor_known` caller shape reach the authorized branch.
|
|
44
|
+
def record_grant(args)
|
|
45
|
+
role = ValueGenerator.scalar_of(args["role_name"]).to_s
|
|
46
|
+
actor = ValueGenerator.scalar_of(args["actor_id"]).to_s
|
|
47
|
+
@granted[role] << actor unless role.empty? || actor.empty?
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# The pool an appended element lands in: the aggregate's identity, then
|
|
51
|
+
# one scalar per owning hop (none for an aggregate-level append).
|
|
52
|
+
def append_pool_key(populator, args)
|
|
53
|
+
parent_scalar = identity_scalar_of(populator[:aggregate], args)
|
|
54
|
+
owner_scalars = populator[:owner_chain].map do |piece|
|
|
55
|
+
ValueGenerator.scalar_of(args[(piece.identified_by || :id).to_s])
|
|
56
|
+
end
|
|
57
|
+
entity_pool_key(populator[:aggregate].hecks_name,
|
|
58
|
+
populator[:owner_chain].map(&:hecks_name) + [populator[:entity].hecks_name],
|
|
59
|
+
[parent_scalar] + owner_scalars)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# `"Agg.Board#w1"` for a depth-1 pool, `"Agg.Board.Card#w1/1"` one hop
|
|
63
|
+
# deeper. Depth-1 keys must stay stable for pinned seeds.
|
|
64
|
+
def entity_pool_key(aggregate_name, chain_names, scalars)
|
|
65
|
+
"#{aggregate_name}.#{chain_names.join('.')}##{scalars.join('/')}"
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# The scalar the step's aggregate identity resolves to. A composite identity
|
|
69
|
+
# has no top-level arg, so its parts are joined in the order
|
|
70
|
+
# `Runtime::Identity.of` joins them, yielding a usable bare `id:` later.
|
|
71
|
+
def identity_scalar_of(aggregate, args)
|
|
72
|
+
parent_key = (aggregate.identified_by || :id).to_s
|
|
73
|
+
return ValueGenerator.scalar_of(args[parent_key]) unless composite_identity?(aggregate)
|
|
74
|
+
|
|
75
|
+
parts = aggregate.identity_paths.map { |path| ValueGenerator.scalar_of(args[path.to_s.split(".").first]) }
|
|
76
|
+
Naming.identity(parts)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def pick_known(name)
|
|
80
|
+
pool = @known_ids[name]
|
|
81
|
+
return ValueGenerator.random_id(@random) if pool.empty? || @random.rand < ValueGenerator::INVALID_REFERENCE_PROBABILITY
|
|
82
|
+
|
|
83
|
+
pool.sample(random: @random)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def pick_entity_known(key)
|
|
87
|
+
pool = @entity_known_ids[key]
|
|
88
|
+
return ValueGenerator.random_id(@random) if pool.empty? || @random.rand < ValueGenerator::INVALID_REFERENCE_PROBABILITY
|
|
89
|
+
|
|
90
|
+
pool.sample(random: @random)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|