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,129 @@
|
|
|
1
|
+
require_relative "../ports/identity_generation"
|
|
2
|
+
require_relative "../runtime/errors"
|
|
3
|
+
require_relative "cli_door"
|
|
4
|
+
|
|
5
|
+
module Hecks
|
|
6
|
+
module Doors
|
|
7
|
+
# What a domain's world file adds to its generated launcher, all of it opt-in.
|
|
8
|
+
#
|
|
9
|
+
# A `.world` that declares a `launcher` setting switches these on for that chapter; a
|
|
10
|
+
# domain without one keeps the launcher's plain forms exactly:
|
|
11
|
+
#
|
|
12
|
+
# launcher "Launcher", run_keys: true, failure_states: %w[flagged failed],
|
|
13
|
+
# names: { "mcp" => "serve_mcp" }
|
|
14
|
+
#
|
|
15
|
+
# - **run_keys** mints the `run` key of a creating command that was given none.
|
|
16
|
+
# - **failure_states** are the lifecycle states `--wait` reports as a failure (exit 1).
|
|
17
|
+
# - **names** maps a launcher name to the command it stands for (see `CliProjector`).
|
|
18
|
+
module LauncherOptions
|
|
19
|
+
SETTING = "launcher".freeze
|
|
20
|
+
WAIT = "--wait".freeze
|
|
21
|
+
RUN_KEY = "run.value".freeze
|
|
22
|
+
|
|
23
|
+
module_function
|
|
24
|
+
|
|
25
|
+
# Reads a chapter's `launcher` setting.
|
|
26
|
+
#
|
|
27
|
+
# @param runtime [Runtime::Dispatcher, Runtime::RemoteDispatcher] the booted domain
|
|
28
|
+
# @param chapter [String] the chapter the command line speaks to
|
|
29
|
+
# @return [Hash{Symbol => Object}, nil] the setting, or nil when the chapter did not opt in
|
|
30
|
+
def settings(runtime, chapter)
|
|
31
|
+
return unless runtime.respond_to?(:registry)
|
|
32
|
+
|
|
33
|
+
found = runtime.registry.world(chapter)&.for_verb(SETTING)
|
|
34
|
+
found.nil? || found.empty? ? nil : found
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# The options the launcher's projection is asked for: the program's name, the chapter's
|
|
38
|
+
# launcher names, and whether run keys are minted (so help shows them optional).
|
|
39
|
+
#
|
|
40
|
+
# @param settings [Hash, nil] the chapter's `launcher` setting
|
|
41
|
+
# @param program [String] how the caller was invoked
|
|
42
|
+
# @return [Hash{Symbol => Object}] the options `Projector::CliProjector` reads
|
|
43
|
+
def projection(settings, program)
|
|
44
|
+
{ program: program, names: settings && settings[:names], mint_run_keys: settings && settings[:run_keys] }
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Takes `--wait` out of a verb's words, unless the verb declares a `wait` argument of its own.
|
|
48
|
+
#
|
|
49
|
+
# `--wait=false` (or `no`, `0`, `off`) is a `--wait` that was switched off; a bare `--wait`
|
|
50
|
+
# may be followed by its Boolean word.
|
|
51
|
+
#
|
|
52
|
+
# @param spec [Hash] the verb's projected spec
|
|
53
|
+
# @param words [Array<String>] the words after the verb
|
|
54
|
+
# @return [Array(Array<String>, Boolean)] the remaining words, and whether `--wait` was given
|
|
55
|
+
# @raise [Runtime::TypeMismatch] if `--wait=` carries something that is not a Boolean word
|
|
56
|
+
def take_wait(spec, words)
|
|
57
|
+
return [words, false] unless words.any? { |word| wait_word?(word) }
|
|
58
|
+
return [words, false] if spec[:arguments].any? { |argument| argument[:path].split(".").first == "wait" }
|
|
59
|
+
|
|
60
|
+
wait = false
|
|
61
|
+
rest = []
|
|
62
|
+
queue = words.dup
|
|
63
|
+
until queue.empty?
|
|
64
|
+
word = queue.shift
|
|
65
|
+
wait_word?(word) ? wait ||= CliDoor.boolean(wait_value(word, queue)) : rest << word
|
|
66
|
+
end
|
|
67
|
+
[rest, wait]
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# @api private
|
|
71
|
+
def wait_word?(word) = word == WAIT || word.start_with?("#{WAIT}=")
|
|
72
|
+
|
|
73
|
+
# The Boolean word a `--wait` carries: after `=`, or the next word when it is one.
|
|
74
|
+
# @api private
|
|
75
|
+
def wait_value(word, queue)
|
|
76
|
+
return word.split("=", 2).last if word.include?("=")
|
|
77
|
+
|
|
78
|
+
CliDoor::BOOLEAN_WORDS.key?(queue.first.to_s.downcase) ? queue.shift : "true"
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Whether a question's text answer reports a gap, which `--wait` turns into a failing exit.
|
|
82
|
+
#
|
|
83
|
+
# The coverage checks end their report with a `GAP (n)` heading; a non-zero count is a
|
|
84
|
+
# finding, and a CI stage that waits on the question fails on it.
|
|
85
|
+
#
|
|
86
|
+
# @param text [String, nil] the question's text answer
|
|
87
|
+
# @return [Boolean] whether a `GAP (n)` heading with n above zero is in the text
|
|
88
|
+
def gap_reported?(text)
|
|
89
|
+
text.is_a?(String) && text.match?(/^GAP \((?!0\))\d+\)/)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Mints the `run` key a creating command was not given, when the domain opted in.
|
|
93
|
+
#
|
|
94
|
+
# An explicit key always wins. Nothing is minted for a command that acts on an existing
|
|
95
|
+
# record. When no identity adapter is bound the run is refused, saying to name a key,
|
|
96
|
+
# rather than sent on without one.
|
|
97
|
+
#
|
|
98
|
+
# @param runtime [Runtime::Dispatcher, Runtime::RemoteDispatcher] the booted domain
|
|
99
|
+
# @param spec [Hash] the verb's projected spec
|
|
100
|
+
# @param args [Hash] the parsed arguments
|
|
101
|
+
# @param settings [Hash, nil] the chapter's `launcher` setting
|
|
102
|
+
# @return [Array(Hash, String)] the arguments, and the key minted (nil when none was)
|
|
103
|
+
# @raise [Runtime::NotFound] if a key is owed but no identity adapter is bound
|
|
104
|
+
def run_key(runtime, spec, args, settings)
|
|
105
|
+
return [args, nil] unless settings && settings[:run_keys] && spec[:creates]
|
|
106
|
+
return [args, nil] unless spec[:arguments].any? { |argument| argument[:path] == RUN_KEY }
|
|
107
|
+
return [args, nil] if args.key?(:run) || !runtime.respond_to?(:registry)
|
|
108
|
+
|
|
109
|
+
key = Ports::IdentityGeneration.uuid(runtime.registry)
|
|
110
|
+
[args.merge(run: { value: key }), key]
|
|
111
|
+
rescue Runtime::WiringError => e
|
|
112
|
+
raise Runtime::NotFound, "cannot mint a run key (#{e.message.lines.first.strip}); name one with run=<key>"
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Whether a settled record sits in one of the chapter's failure states.
|
|
116
|
+
#
|
|
117
|
+
# @param aggregate [Bluebook::Aggregate, nil] the record's aggregate
|
|
118
|
+
# @param state [Hash, nil] the record's state
|
|
119
|
+
# @param settings [Hash, nil] the chapter's `launcher` setting
|
|
120
|
+
# @return [Boolean] false when the aggregate has no lifecycle
|
|
121
|
+
def failed?(aggregate, state, settings)
|
|
122
|
+
field = aggregate&.lifecycle&.field
|
|
123
|
+
return false unless field && state
|
|
124
|
+
|
|
125
|
+
Array(settings && settings[:failure_states]).map(&:to_s).include?(state[field.to_sym].to_s)
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
require "json"
|
|
2
2
|
require_relative "../../hecks"
|
|
3
|
-
require_relative "mcp"
|
|
3
|
+
require_relative "../cli/mcp"
|
|
4
4
|
|
|
5
5
|
module Hecks
|
|
6
|
-
module
|
|
6
|
+
module Doors
|
|
7
7
|
# The MCP door onto the storehouse bus: hand-rolled JSON-RPC over stdio, one
|
|
8
8
|
# server per booted domain. All logic and the JSON shape answered with live in
|
|
9
9
|
# `Storehouse`; this is plumbing only. A `role:`/`actor_id:` a caller sends is
|
|
@@ -235,7 +235,7 @@ module Hecks
|
|
|
235
235
|
# Read once, so a bad setting refuses to start the door before anything is
|
|
236
236
|
# written to stdout.
|
|
237
237
|
def scope
|
|
238
|
-
@scope ||= McpDoorScope.start!(server: Mcp::SERVER)
|
|
238
|
+
@scope ||= McpDoorScope.start!(server: CLI::Mcp::SERVER)
|
|
239
239
|
end
|
|
240
240
|
|
|
241
241
|
# The standing notes, then what this door's scope says about `domain:`.
|
|
@@ -244,7 +244,7 @@ module Hecks
|
|
|
244
244
|
end
|
|
245
245
|
|
|
246
246
|
def serve
|
|
247
|
-
McpStdioGuard.warn!(server: Mcp::SERVER, notes: warning_notes)
|
|
247
|
+
McpStdioGuard.warn!(server: CLI::Mcp::SERVER, notes: warning_notes)
|
|
248
248
|
$stdout.sync = true
|
|
249
249
|
|
|
250
250
|
$stdin.each_line do |line|
|
|
@@ -269,7 +269,7 @@ module Hecks
|
|
|
269
269
|
# `domain:` is confined to `Storehouse::BOOT_ROOT`, and further to a reader
|
|
270
270
|
# door's `HECKS_DOOR_DOMAINS`, before anything boots.
|
|
271
271
|
def boot(domain)
|
|
272
|
-
Hecks.boot(Storehouse.confine!(scope.admit_domain!(domain), "domain"),
|
|
272
|
+
Hecks.boot(Storehouse.confine!(scope.admit_domain!(domain), "domain"), install_doors: false)
|
|
273
273
|
end
|
|
274
274
|
|
|
275
275
|
# One `when` per tool, so the shared `rescue` catches every branch's escape
|
|
@@ -315,7 +315,7 @@ module Hecks
|
|
|
315
315
|
end
|
|
316
316
|
rescue StandardError => e
|
|
317
317
|
# A defect, not a refusal: reaching here means a bad domain path or an
|
|
318
|
-
# argument shape `
|
|
318
|
+
# argument shape `Doors::JsonDoor` could not symbolize.
|
|
319
319
|
{ ok: false, error: "#{e.class}: #{e.message}" }
|
|
320
320
|
end
|
|
321
321
|
|
|
@@ -347,7 +347,7 @@ module Hecks
|
|
|
347
347
|
send_response(id, {
|
|
348
348
|
protocolVersion: PROTOCOL_VERSION,
|
|
349
349
|
capabilities: { tools: {} },
|
|
350
|
-
serverInfo: { name: Mcp::SERVER, version: "1.1.0" }
|
|
350
|
+
serverInfo: { name: CLI::Mcp::SERVER, version: "1.1.0" }
|
|
351
351
|
})
|
|
352
352
|
when "notifications/initialized"
|
|
353
353
|
nil # a notification — no id, no response
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
require_relative "../runtime/errors"
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
module Doors
|
|
5
|
+
# What a spawned hecks mcp may do: every tool by default, or only
|
|
6
|
+
# reader tools against named domains in reader mode — reach, not identity (ADR 0072).
|
|
7
|
+
class McpDoorScope
|
|
8
|
+
ENV_PREFIX = "HECKS_DOOR_".freeze
|
|
9
|
+
TOOLS_VARIABLE = "HECKS_DOOR_TOOLS".freeze
|
|
10
|
+
DOMAINS_VARIABLE = "HECKS_DOOR_DOMAINS".freeze
|
|
11
|
+
READERS = "readers".freeze
|
|
12
|
+
EXIT_STATUS = 2
|
|
13
|
+
|
|
14
|
+
# The tools a reader door serves — each reads a domain or the door's own
|
|
15
|
+
# audit log and writes no domain state. `validate` still boots its
|
|
16
|
+
# domain, so it stays bound to the allowed set like every other tool.
|
|
17
|
+
READER_TOOLS = %w[query events state catalog describe validate domains history follow].freeze
|
|
18
|
+
|
|
19
|
+
# Unrestricted when no HECKS_DOOR_* variable is set.
|
|
20
|
+
def self.from_env(env = ENV)
|
|
21
|
+
problems = setting_problems(env)
|
|
22
|
+
raise ArgumentError, problems.join("\n") unless problems.empty?
|
|
23
|
+
return new(allowed_domains: nil) unless env[TOOLS_VARIABLE]
|
|
24
|
+
|
|
25
|
+
new(allowed_domains: env[DOMAINS_VARIABLE].split(File::PATH_SEPARATOR).reject(&:empty?))
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Refuses to stderr, not stdout, which carries the MCP protocol.
|
|
29
|
+
def self.start!(server:, env: ENV, stderr: $stderr)
|
|
30
|
+
from_env(env)
|
|
31
|
+
rescue ArgumentError, Runtime::TypeMismatch => e
|
|
32
|
+
e.message.each_line { |line| stderr.puts("#{server}: refusing to start: #{line.chomp}") }
|
|
33
|
+
exit(EXIT_STATUS)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def self.setting_problems(env)
|
|
37
|
+
unknown = env.keys.select { |name| name.start_with?(ENV_PREFIX) } - [TOOLS_VARIABLE, DOMAINS_VARIABLE]
|
|
38
|
+
problems = unknown.map do |name|
|
|
39
|
+
"environment #{name} is not accepted; the door takes only #{TOOLS_VARIABLE} and #{DOMAINS_VARIABLE}"
|
|
40
|
+
end
|
|
41
|
+
problems + mode_problems(env[TOOLS_VARIABLE], env[DOMAINS_VARIABLE])
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def self.mode_problems(tools, domains)
|
|
45
|
+
if tools.nil?
|
|
46
|
+
domains.nil? ? [] : ["#{DOMAINS_VARIABLE} is set without #{TOOLS_VARIABLE}=#{READERS}; it only narrows a reader door"]
|
|
47
|
+
elsif tools != READERS
|
|
48
|
+
["#{TOOLS_VARIABLE}=#{tools.inspect} is not accepted; the only value is #{READERS.inspect}"]
|
|
49
|
+
elsif domains.to_s.split(File::PATH_SEPARATOR).none? { |path| !path.empty? }
|
|
50
|
+
["#{TOOLS_VARIABLE}=#{READERS} needs #{DOMAINS_VARIABLE}, the domains this door may read"]
|
|
51
|
+
else
|
|
52
|
+
[]
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# The named domains, resolved against Storehouse::BOOT_ROOT; nil when unrestricted.
|
|
57
|
+
attr_reader :allowed_domains
|
|
58
|
+
|
|
59
|
+
def initialize(allowed_domains:)
|
|
60
|
+
@allowed_domains = allowed_domains&.map { |path| Storehouse.confine!(path, DOMAINS_VARIABLE) }.freeze
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def restricted?
|
|
64
|
+
!@allowed_domains.nil?
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def permits_tool?(name)
|
|
68
|
+
!restricted? || READER_TOOLS.include?(name)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def refusal(name)
|
|
72
|
+
{ ok: false,
|
|
73
|
+
error: "#{name.inspect} is refused: this door runs in reader mode (#{TOOLS_VARIABLE}=#{READERS}), " \
|
|
74
|
+
"which serves only #{READER_TOOLS.join(', ')}" }
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Checked before anything boots, so a path outside the allowed set never reaches Kernel.load.
|
|
78
|
+
def admit_domain!(domain)
|
|
79
|
+
return domain unless restricted?
|
|
80
|
+
return domain if @allowed_domains.include?(Storehouse.confine!(domain, "domain"))
|
|
81
|
+
|
|
82
|
+
raise Runtime::TypeMismatch,
|
|
83
|
+
"domain: #{domain.inspect} is refused: this door runs in reader mode (#{TOOLS_VARIABLE}=#{READERS}) " \
|
|
84
|
+
"and reads only #{DOMAINS_VARIABLE}: #{@allowed_domains.join(', ')}"
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def notes
|
|
88
|
+
return [] unless restricted?
|
|
89
|
+
|
|
90
|
+
["Reader mode (#{TOOLS_VARIABLE}=#{READERS}): serves #{READER_TOOLS.join(', ')}; " \
|
|
91
|
+
"refuses dispatch (with dry_run and steps), behaviors and every other tool.",
|
|
92
|
+
"domain: boots only #{DOMAINS_VARIABLE}: #{@allowed_domains.join(', ')}.",
|
|
93
|
+
"Reader mode limits reach and identifies no one; it is not authentication."]
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
require_relative "../../bluebook/dsl/hecksagon_builder"
|
|
2
|
+
require_relative "../../bluebook/dsl/world_builder"
|
|
2
3
|
require_relative "../../bluebook/dsl/domain_port_builder"
|
|
3
4
|
require_relative "../../bluebook/dsl/const_shim"
|
|
4
5
|
require_relative "../../bluebook/dsl/binding_proxy"
|
|
@@ -7,8 +8,8 @@ require_relative "../handle"
|
|
|
7
8
|
require_relative "../../naming"
|
|
8
9
|
|
|
9
10
|
module Hecks
|
|
10
|
-
module
|
|
11
|
-
module
|
|
11
|
+
module Doors
|
|
12
|
+
module RubyDoor
|
|
12
13
|
# One aggregate's door: creating verbs and queries as module methods, CRUD
|
|
13
14
|
# delegation, and the `.hecksagon` binding/port hooks it lands on.
|
|
14
15
|
module AggregateDoor
|
|
@@ -123,6 +124,9 @@ module Hecks
|
|
|
123
124
|
return Bluebook::DSL::AttributePath.new(fqn, [verb.to_s]) if args.empty? && kwargs.empty? && !block
|
|
124
125
|
|
|
125
126
|
collector = Bluebook::DSL::HecksagonBuilder.collector
|
|
127
|
+
world = Bluebook::DSL::WorldBuilder.current
|
|
128
|
+
# Inside a `.world` block the same qualified spelling records a world setting.
|
|
129
|
+
return world.record_binding(verb, args, kwargs, block) if world && !collector
|
|
126
130
|
return super(verb, *args, **kwargs, &block) unless collector
|
|
127
131
|
|
|
128
132
|
collector << Bluebook::Bind.new(
|
|
@@ -136,7 +140,8 @@ module Hecks
|
|
|
136
140
|
end
|
|
137
141
|
|
|
138
142
|
door.define_singleton_method(:respond_to_missing?) do |name, include_private = false|
|
|
139
|
-
!Bluebook::DSL::HecksagonBuilder.collector.nil? ||
|
|
143
|
+
!Bluebook::DSL::HecksagonBuilder.collector.nil? || !Bluebook::DSL::WorldBuilder.current.nil? ||
|
|
144
|
+
super(name, include_private)
|
|
140
145
|
end
|
|
141
146
|
|
|
142
147
|
# Uses `aggregate.hecks_name`, not the qualified `fqn`: qualifying it here
|
|
@@ -3,8 +3,8 @@ require_relative "../../bluebook/dsl/const_shim"
|
|
|
3
3
|
require_relative "../../bluebook/dsl/hecksagon_builder"
|
|
4
4
|
|
|
5
5
|
module Hecks
|
|
6
|
-
module
|
|
7
|
-
module
|
|
6
|
+
module Doors
|
|
7
|
+
module RubyDoor
|
|
8
8
|
# One chapter's module: vision and aggregate list on the singleton, one
|
|
9
9
|
# aggregate door per declared head, and a `const_missing` hook for undeclared names.
|
|
10
10
|
module Chapter
|
|
@@ -15,7 +15,7 @@ module Hecks
|
|
|
15
15
|
# @param dispatcher [Runtime::Dispatcher, Runtime::RemoteDispatcher] the booted
|
|
16
16
|
# dispatcher each aggregate door closes over
|
|
17
17
|
# @param bluebook [Bluebook::Chapter] the chapter to project into a module
|
|
18
|
-
# @return [Module] a fresh, unnamed module; `
|
|
18
|
+
# @return [Module] a fresh, unnamed module; `RubyDoor.install` gives it its
|
|
19
19
|
# top-level name
|
|
20
20
|
# @raise [NameError] if an aggregate's name is not a valid constant name
|
|
21
21
|
def chapter_module(dispatcher, bluebook)
|
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
require_relative "
|
|
2
|
-
require_relative "
|
|
1
|
+
require_relative "../chapters"
|
|
2
|
+
require_relative "ruby_door/chapter"
|
|
3
|
+
require_relative "ruby_door/aggregate_door"
|
|
3
4
|
|
|
4
5
|
module Hecks
|
|
5
|
-
module
|
|
6
|
+
module Doors
|
|
6
7
|
# The door without classes: anonymous per-boot modules whose singleton methods
|
|
7
8
|
# dispatch by FQN, installed by `Loader.bind_runtime` and replaced whole on the next boot.
|
|
8
9
|
#
|
|
9
10
|
# `persisted_by("Heki")` in a `.hecksagon` file lands on the module's `method_missing`,
|
|
10
11
|
# which records a `Bind` into the open `HecksagonBuilder.collector`.
|
|
11
|
-
module
|
|
12
|
+
module RubyDoor
|
|
12
13
|
# Names that would shadow the machinery a Handle runs on; a field with one gets no reader.
|
|
13
14
|
RESERVED = %i[id state events reload inspect to_h hash class].freeze
|
|
14
15
|
|
|
@@ -21,6 +22,7 @@ module Hecks
|
|
|
21
22
|
# closing over `dispatcher`, so `Pizzas::Pizza` and the bare `Pizza` both open the
|
|
22
23
|
# door of the boot that ran last.
|
|
23
24
|
#
|
|
25
|
+
# A gem chapter a hecksagon `attaches` installs nothing (`Query`, `Port` stay free).
|
|
24
26
|
# An aggregate sharing its chapter's name gets no second constant. A name that user
|
|
25
27
|
# code or the stdlib already owns is left alone with a warning (see `Namespace.install`).
|
|
26
28
|
#
|
|
@@ -30,8 +32,13 @@ module Hecks
|
|
|
30
32
|
# @raise [NameError] if a chapter or aggregate name is not a valid constant name
|
|
31
33
|
def install(dispatcher)
|
|
32
34
|
dispatcher.registry.bluebooks.each_value do |bluebook|
|
|
35
|
+
next if attached_chapter?(dispatcher.registry, bluebook)
|
|
36
|
+
|
|
33
37
|
chapter = chapter_module(dispatcher, bluebook)
|
|
34
|
-
Namespace.install(
|
|
38
|
+
Namespace.install(*placement(bluebook), chapter)
|
|
39
|
+
# A namespaced chapter keeps its aggregates inside its namespace: top-level
|
|
40
|
+
# shortcuts would bring back the collisions the namespace exists to avoid.
|
|
41
|
+
next if bluebook.namespace
|
|
35
42
|
|
|
36
43
|
bluebook.aggregates.each do |aggregate|
|
|
37
44
|
next if aggregate.hecks_name == bluebook.name
|
|
@@ -41,6 +48,30 @@ module Hecks
|
|
|
41
48
|
end
|
|
42
49
|
dispatcher
|
|
43
50
|
end
|
|
51
|
+
|
|
52
|
+
# Whether `bluebook` is a gem chapter that a hecksagon attached rather than a domain's own.
|
|
53
|
+
#
|
|
54
|
+
# @param registry [Runtime::Registry] the booted registry
|
|
55
|
+
# @param bluebook [Bluebook::Chapter] the chapter under test
|
|
56
|
+
# @return [Boolean]
|
|
57
|
+
def attached_chapter?(registry, bluebook)
|
|
58
|
+
registry.bounded?(bluebook.name) && Hecks::Chapters.index.key?(bluebook.name)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Where a chapter's module installs: `Object::<name>`, or the constant path its
|
|
62
|
+
# `namespace` names, creating any missing parent modules along the way.
|
|
63
|
+
#
|
|
64
|
+
# @param bluebook [Bluebook::Chapter] the chapter being installed
|
|
65
|
+
# @return [Array(Module, String)] the parent module and the constant name within it
|
|
66
|
+
def placement(bluebook)
|
|
67
|
+
return [Object, bluebook.name] unless bluebook.namespace
|
|
68
|
+
|
|
69
|
+
*parents, leaf = bluebook.namespace.split("::")
|
|
70
|
+
parent = parents.reduce(Object) do |outer, name|
|
|
71
|
+
outer.const_defined?(name, false) ? outer.const_get(name, false) : outer.const_set(name, Module.new)
|
|
72
|
+
end
|
|
73
|
+
[parent, leaf]
|
|
74
|
+
end
|
|
44
75
|
end
|
|
45
76
|
end
|
|
46
77
|
end
|
data/lib/hecks/doors.rb
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
module Hecks
|
|
2
|
+
# The class-free public surface, installed per boot by `Runtime::Loader.bind_runtime`.
|
|
3
|
+
module Doors
|
|
4
|
+
# The removal release of the `Facade` and `install_facade:` spellings.
|
|
5
|
+
REMOVAL = "3.1.0".freeze
|
|
6
|
+
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
# Resolves the boot switch from the current keyword and its deprecated spelling.
|
|
10
|
+
#
|
|
11
|
+
# @param install_doors [Boolean] the `install_doors:` keyword
|
|
12
|
+
# @param install_facade [Boolean, nil] the deprecated `install_facade:` keyword; nil when
|
|
13
|
+
# the caller did not pass it
|
|
14
|
+
# @return [Boolean] whether to install the Ruby door constants
|
|
15
|
+
def install?(install_doors, install_facade = nil)
|
|
16
|
+
return install_doors if install_facade.nil?
|
|
17
|
+
|
|
18
|
+
warn "[hecks] `install_facade:` is deprecated and is removed in #{REMOVAL}; use `install_doors:`"
|
|
19
|
+
install_facade
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Installs facade constants at top level, replacing only what it installed itself.
|
|
24
|
+
# A constant owned by user code or the stdlib is left alone with a warning.
|
|
25
|
+
module Namespace
|
|
26
|
+
# A live registry, mutated by `install`, so it cannot be frozen.
|
|
27
|
+
# rubocop:disable-next Style/MutableConstant
|
|
28
|
+
GENERATED = {}
|
|
29
|
+
|
|
30
|
+
module_function
|
|
31
|
+
|
|
32
|
+
# Sets `container::name` to `value`, replacing only a constant this module installed.
|
|
33
|
+
#
|
|
34
|
+
# @param container [Module] the namespace to define the constant in
|
|
35
|
+
# @param name [String, Symbol] the constant name
|
|
36
|
+
# @param value [Module] the facade module to install
|
|
37
|
+
# @return [Object] `value` when installed, else the foreign constant left in place
|
|
38
|
+
# @raise [NameError] if `name` is not a valid constant name
|
|
39
|
+
def install(container, name, value)
|
|
40
|
+
name = name.to_s
|
|
41
|
+
|
|
42
|
+
if container.const_defined?(name, false)
|
|
43
|
+
current = container.const_get(name)
|
|
44
|
+
unless GENERATED[[container, name]].equal?(current)
|
|
45
|
+
warn "[hecks] #{name} is already defined — leaving it alone"
|
|
46
|
+
return current
|
|
47
|
+
end
|
|
48
|
+
container.send(:remove_const, name)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
container.const_set(name, value)
|
|
52
|
+
GENERATED[[container, name]] = value
|
|
53
|
+
value
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
require_relative "doors/handle"
|
|
59
|
+
require_relative "doors/ruby_door"
|
|
60
|
+
require_relative "doors/command_request"
|
|
61
|
+
require_relative "doors/json_door"
|
|
@@ -3,7 +3,7 @@ require_relative "vendor"
|
|
|
3
3
|
|
|
4
4
|
module Hecks
|
|
5
5
|
module EmbryonautBluebook
|
|
6
|
-
# The command line behind `
|
|
6
|
+
# The command line behind `hecks vendor`; lives in the gem so a
|
|
7
7
|
# consuming project reaches it through its own bundle, no script to copy.
|
|
8
8
|
#
|
|
9
9
|
# vendor_bluebook <package>[@<version-or-commit>] [--from path] [--root path]
|
|
@@ -31,7 +31,7 @@ module Hecks
|
|
|
31
31
|
if files.empty?
|
|
32
32
|
raise Runtime::WiringError,
|
|
33
33
|
"no vendored embryonaut bluebook named #{name.inspect} at #{dir} — " \
|
|
34
|
-
"vendor it with
|
|
34
|
+
"vendor it with hecks vendor #{name}"
|
|
35
35
|
end
|
|
36
36
|
|
|
37
37
|
# Loaded in the glob's alphabetical order; a package with several
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
# The grep-based gate that keeps the query engines agreeing on what a comparator means.
|
|
5
|
+
#
|
|
6
|
+
# It fails when an engine re-grows its own comparator dispatch, or when a declared comparator
|
|
7
|
+
# lacks a shared `Comparison` case or a cross-engine agreement spec. Every declared comparator
|
|
8
|
+
# is read from `Hecks::Vocabulary`, as `Common::COMPARATORS` is: one live source, no copied list.
|
|
9
|
+
module EngineAgreement
|
|
10
|
+
# The engine files that must route every comparator through `Comparison.holds?`.
|
|
11
|
+
ENGINE_FILES = {
|
|
12
|
+
"in-memory port (Memory/Heki-backed aggregate queries)" => "lib/hecks/ports/query/in_memory.rb",
|
|
13
|
+
"reference interpreter (entity/sub-list queries, no-native-hook fallback)" => "lib/hecks/runtime/query_interpreter.rb"
|
|
14
|
+
}.freeze
|
|
15
|
+
|
|
16
|
+
# Where the shared comparison logic lives.
|
|
17
|
+
SHARED_COMPARISON_FILE = "lib/hecks/query_specification/common/comparison.rb"
|
|
18
|
+
|
|
19
|
+
# Specs that put each comparator in front of both engines. `none_in_state` needs a second
|
|
20
|
+
# aggregate and a registry, so the two dedicated growth specs cover it instead.
|
|
21
|
+
AGREEMENT_SPEC_FILES = %w[
|
|
22
|
+
spec/adapters/query_agreement_spec.rb
|
|
23
|
+
spec/query_none_in_state_aggregate_level_growth_spec.rb
|
|
24
|
+
spec/query_none_in_state_growth_spec.rb
|
|
25
|
+
].freeze
|
|
26
|
+
|
|
27
|
+
# What a check found.
|
|
28
|
+
#
|
|
29
|
+
# @!attribute [r] declared
|
|
30
|
+
# @return [Array<String>] the comparators the vocabulary declares
|
|
31
|
+
# @!attribute [r] problems
|
|
32
|
+
# @return [Array<String>] each disagreement, in words; empty when the engines agree
|
|
33
|
+
Finding = Struct.new(:declared, :problems)
|
|
34
|
+
|
|
35
|
+
module_function
|
|
36
|
+
|
|
37
|
+
# Checks the engines against the vocabulary.
|
|
38
|
+
#
|
|
39
|
+
# @param root [String] the checkout whose files are read
|
|
40
|
+
# @return [Finding] the declared comparators and every problem found
|
|
41
|
+
def check(root:)
|
|
42
|
+
require "hecks"
|
|
43
|
+
declared = Hecks::Vocabulary.symbols("QueryComparator").map(&:to_s)
|
|
44
|
+
if declared.empty?
|
|
45
|
+
return Finding.new(declared, ["Vocabulary::QueryComparator declared no comparators at all — " \
|
|
46
|
+
"vocabulary.bluebook failed to register, or Hecks::Vocabulary changed " \
|
|
47
|
+
"shape. Refusing to report a clean pass."])
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
problems = vocabulary_problems(declared) + engine_problems(root, declared) + shared_problems(root, declared)
|
|
51
|
+
Finding.new(declared, problems)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# @param declared [Array<String>] the declared comparators
|
|
55
|
+
# @return [Array<String>] a problem when the language and the runtime table disagree
|
|
56
|
+
def vocabulary_problems(declared)
|
|
57
|
+
runtime = Hecks::QuerySpecification::Common::COMPARATORS.map(&:to_s)
|
|
58
|
+
differ = (declared - runtime) | (runtime - declared)
|
|
59
|
+
return [] if differ.empty?
|
|
60
|
+
|
|
61
|
+
["Vocabulary::QueryComparator #{declared.sort.inspect} and " \
|
|
62
|
+
"QuerySpecification::Common::COMPARATORS #{runtime.sort.inspect} disagree " \
|
|
63
|
+
"(only in one: #{differ.sort.inspect}) — the language and the runtime table it drives " \
|
|
64
|
+
"have drifted apart."]
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# @param root [String] the checkout
|
|
68
|
+
# @param declared [Array<String>] the declared comparators
|
|
69
|
+
# @return [Array<String>] a problem for each engine that lost `Comparison.holds?` or grew a case
|
|
70
|
+
def engine_problems(root, declared)
|
|
71
|
+
ENGINE_FILES.flat_map do |label, relative|
|
|
72
|
+
source = File.read(File.join(root, relative))
|
|
73
|
+
found = []
|
|
74
|
+
unless source =~ /Comparison\.holds\?/
|
|
75
|
+
found << "#{relative} (#{label}) no longer calls Comparison.holds? at all — " \
|
|
76
|
+
"has it grown its own comparator dispatch again?"
|
|
77
|
+
end
|
|
78
|
+
declared.each do |comparator|
|
|
79
|
+
escaped = Regexp.escape(comparator)
|
|
80
|
+
next unless source =~ /when\s+"#{escaped}"/ || source =~ /when\s+:#{escaped}\b/
|
|
81
|
+
|
|
82
|
+
found << "#{relative} (#{label}) has its own `when #{comparator.inspect}` — comparator logic " \
|
|
83
|
+
"belongs ONLY in #{SHARED_COMPARISON_FILE}, not re-implemented per engine."
|
|
84
|
+
end
|
|
85
|
+
found
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# @param root [String] the checkout
|
|
90
|
+
# @param declared [Array<String>] the declared comparators
|
|
91
|
+
# @return [Array<String>] a problem for a comparator with no shared case, or no agreement spec
|
|
92
|
+
def shared_problems(root, declared)
|
|
93
|
+
comparison = File.read(File.join(root, SHARED_COMPARISON_FILE))
|
|
94
|
+
specs = AGREEMENT_SPEC_FILES.map { |path| File.read(File.join(root, path)) }
|
|
95
|
+
missing_case = declared.reject { |comparator| comparison =~ /when\s+"#{Regexp.escape(comparator)}"/ }
|
|
96
|
+
missing_spec = declared.reject { |comparator| specs.any? { |text| mentions_operator?(text, comparator) } }
|
|
97
|
+
|
|
98
|
+
[missing_case_problem(missing_case), missing_spec_problem(missing_spec)].compact
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# @param missing [Array<String>] comparators with no `when` case in the shared file
|
|
102
|
+
# @return [String, nil] the problem, or nil when none is missing
|
|
103
|
+
def missing_case_problem(missing)
|
|
104
|
+
return nil if missing.empty?
|
|
105
|
+
|
|
106
|
+
"#{SHARED_COMPARISON_FILE} has no `when` case for: #{missing.sort.inspect} — " \
|
|
107
|
+
"a declared comparator with no shared implementation falls to Comparison.holds?'s own `else`, " \
|
|
108
|
+
"which RAISES (by design), but only the first time anything actually asks for it at runtime. " \
|
|
109
|
+
"Add a `when #{missing.first.inspect} then ...` case before shipping this comparator."
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# @param missing [Array<String>] comparators no agreement spec exercises
|
|
113
|
+
# @return [String, nil] the problem, or nil when none is missing
|
|
114
|
+
def missing_spec_problem(missing)
|
|
115
|
+
return nil if missing.empty?
|
|
116
|
+
|
|
117
|
+
"no example in #{AGREEMENT_SPEC_FILES.inspect} exercises: #{missing.sort.inspect} — " \
|
|
118
|
+
"a comparator with a case but no cross-engine spec has never actually had both engines asked " \
|
|
119
|
+
"the same question. Add a declared query using it, with a hand-computed expected id list, to one " \
|
|
120
|
+
"of those files (see spec/adapters/query_agreement_spec.rb's own header for why a hand-computed " \
|
|
121
|
+
"oracle, not mere pairwise agreement, is the standard)."
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# @param text [String] a spec's source
|
|
125
|
+
# @param comparator [String] the comparator name, such as `"eq"`
|
|
126
|
+
# @return [Boolean] whether the spec puts the comparator in front of an engine
|
|
127
|
+
def mentions_operator?(text, comparator)
|
|
128
|
+
# Explicit form: the comparator as a bare hash key, bounded so `in:` cannot match
|
|
129
|
+
# mid-identifier.
|
|
130
|
+
return true if text =~ /(?<![A-Za-z0-9_])#{Regexp.escape(comparator)}(?![A-Za-z0-9_])\s*:/
|
|
131
|
+
|
|
132
|
+
# `eq` is normally implicit (`where(status: "open")`), so an example description naming it
|
|
133
|
+
# counts; no other comparator gets that leniency.
|
|
134
|
+
return false unless comparator == "eq"
|
|
135
|
+
|
|
136
|
+
text =~ /\b(?:it|describe)\s+"[^"]*\beq\b[^"]*"/i ? true : false
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# @param finding [Finding] what a check found
|
|
140
|
+
# @return [String] the clean-pass line
|
|
141
|
+
def clean_line(finding)
|
|
142
|
+
"check_engine_agreement: #{finding.declared.size} declared comparator(s) " \
|
|
143
|
+
"(#{finding.declared.sort.join(', ')}) — every one has a shared Comparison case, a cross-engine " \
|
|
144
|
+
"agreement spec, and both engine files still route through Comparison.holds? alone. 0 problems."
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# @param finding [Finding] what a check found
|
|
148
|
+
# @return [String] every problem, numbered, with where the mechanism is explained
|
|
149
|
+
def problem_lines(finding)
|
|
150
|
+
numbered = finding.problems.each_with_index.map { |problem, index| " #{index + 1}. #{problem}\n\n" }
|
|
151
|
+
["check_engine_agreement: #{finding.problems.size} problem(s) found:\n\n", *numbered,
|
|
152
|
+
"See lib/hecks/query_specification/common/comparison.rb's own header for what this mechanism " \
|
|
153
|
+
"replaced and why a grep-based gate exists here at all."].join
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|