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,218 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
require "json"
|
|
5
|
+
require "open3"
|
|
6
|
+
require "tmpdir"
|
|
7
|
+
require_relative "../rust_build"
|
|
8
|
+
|
|
9
|
+
module Hecks
|
|
10
|
+
module RustBuild
|
|
11
|
+
# Reports whether each construct in a domain's IR has a real, routed implementation under
|
|
12
|
+
# `<workspace>/src/generated/<domain>` (ADR 0054a).
|
|
13
|
+
#
|
|
14
|
+
# Coverage.call([domain, "--codegen=ruby|rust"])
|
|
15
|
+
# Coverage.call(["--check-allowlist"])
|
|
16
|
+
#
|
|
17
|
+
# Ground truth is the generated module's `ir.json` and its `manifest.json`; a construct in the
|
|
18
|
+
# IR that the manifest omits is a synthetic `unaccounted` finding. The exit status is 1 when a
|
|
19
|
+
# gap remains that the allowlist does not excuse.
|
|
20
|
+
module Coverage
|
|
21
|
+
USAGE = "usage: hecks rust_coverage <generated-module-name> [--codegen=ruby|rust] " \
|
|
22
|
+
"(e.g. pizzas, banking, governance, identity)\n hecks rust_coverage --check-allowlist"
|
|
23
|
+
|
|
24
|
+
# Deliberate, documented gaps only, matched by kind, gap class and construct (never a regex
|
|
25
|
+
# over `reason` prose). The list only shrinks: `--check-allowlist` fails once a rule excuses
|
|
26
|
+
# no gap anywhere.
|
|
27
|
+
ALLOWLIST = [].freeze
|
|
28
|
+
|
|
29
|
+
module_function
|
|
30
|
+
|
|
31
|
+
# @param argv [Array<String>] the generated module's name and optional `--codegen=` flag,
|
|
32
|
+
# or `--check-allowlist`
|
|
33
|
+
# @return [Integer] 0 when nothing is a gap, 1 otherwise
|
|
34
|
+
# @raise [Failure] when the module, its `ir.json` or its manifest is missing
|
|
35
|
+
def call(argv)
|
|
36
|
+
flags, positional = argv.partition { |arg| arg.start_with?("--codegen") }
|
|
37
|
+
codegen = flags.empty? ? "ruby" : flags.last.delete_prefix("--codegen=")
|
|
38
|
+
raise Failure, "--codegen must be `ruby` or `rust` (got #{flags.last.inspect})" unless %w[ruby rust].include?(codegen)
|
|
39
|
+
|
|
40
|
+
target = positional.first or raise Failure, USAGE
|
|
41
|
+
return check_allowlist if target == "--check-allowlist"
|
|
42
|
+
|
|
43
|
+
report(target, codegen)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def rust_dir = RustBuild.rust_dir
|
|
47
|
+
|
|
48
|
+
def generated_root = File.join(rust_dir, "src/generated")
|
|
49
|
+
|
|
50
|
+
# Entity-scoped query ids at any depth (`Domain::Aggregate.Entity.Query`); mirrors the
|
|
51
|
+
# generator's own entity query entries.
|
|
52
|
+
def entity_query_ids(owner_id, entities)
|
|
53
|
+
entities.flat_map do |entity|
|
|
54
|
+
entity_id = "#{owner_id}.#{entity.fetch(:name)}"
|
|
55
|
+
entity.fetch(:queries, []).map { |q| "#{entity_id}.#{q.fetch(:name)}" } +
|
|
56
|
+
entity_query_ids(entity_id, entity.fetch(:entities, []))
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# The [kind, id] pairs the IR implies, independent of the manifest.
|
|
61
|
+
def expected_from_ir(payload)
|
|
62
|
+
domain_name = payload.fetch(:name)
|
|
63
|
+
expected = payload.fetch(:aggregates).flat_map { |aggregate| aggregate_constructs(domain_name, aggregate) }
|
|
64
|
+
payload.fetch(:read_models).each { |rm| expected << [:read_model, "#{domain_name}::#{rm.fetch(:name)}"] }
|
|
65
|
+
payload.fetch(:policies).each { |p| expected << [:policy, "#{domain_name}::#{p.fetch(:name)}"] }
|
|
66
|
+
payload.fetch(:process_managers).each { |pm| expected << [:process_manager, "#{domain_name}::#{pm.fetch(:name)}"] }
|
|
67
|
+
# An `ir.json` from before the `lineage` key existed has none: nothing to expect.
|
|
68
|
+
payload.fetch(:lineage, {}).fetch(:capable_aggregates, []).each do |aggregate|
|
|
69
|
+
expected << [:lineage_aggregate, "#{domain_name}::#{aggregate.fetch(:name)}"]
|
|
70
|
+
end
|
|
71
|
+
expected
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def aggregate_constructs(domain_name, aggregate)
|
|
75
|
+
id = "#{domain_name}::#{aggregate.fetch(:name)}"
|
|
76
|
+
found = [[:aggregate, id]]
|
|
77
|
+
aggregate.fetch(:commands).each { |c| found << [:command, "#{id}.#{c.fetch(:name)}"] }
|
|
78
|
+
aggregate.fetch(:queries).each { |q| found << [:query, "#{id}.#{q.fetch(:name)}"] }
|
|
79
|
+
entity_query_ids(id, aggregate.fetch(:entities)).each { |query_id| found << [:query, query_id] }
|
|
80
|
+
aggregate.fetch(:entities).each do |entity|
|
|
81
|
+
entity_id = "#{id}.#{entity.fetch(:name)}"
|
|
82
|
+
found << [:entity, entity_id]
|
|
83
|
+
entity.fetch(:commands).each { |c| found << [:entity_command, "#{entity_id}.#{c.fetch(:name)}"] }
|
|
84
|
+
end
|
|
85
|
+
aggregate.fetch(:ports).each do |port|
|
|
86
|
+
port.fetch(:operations).each { |op| found << [:port_operation, "#{id}.#{port.fetch(:name)}.#{op.fetch(:name)}"] }
|
|
87
|
+
end
|
|
88
|
+
found
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Parses a generated module's `ir.json` and `manifest.json`, adding a synthetic `unaccounted`
|
|
92
|
+
# finding for any IR construct the manifest omits.
|
|
93
|
+
def coverage_findings(mod_dir)
|
|
94
|
+
ir_path = File.join(mod_dir, "ir.json")
|
|
95
|
+
File.exist?(ir_path) or raise Failure, "#{ir_path}: missing — this generated tree predates ir.json " \
|
|
96
|
+
"(hecks project_rust writes it now); re-run hecks project_rust for this domain"
|
|
97
|
+
ir = JSON.parse(File.read(ir_path), symbolize_names: true)
|
|
98
|
+
manifest_path = File.join(mod_dir, "manifest.json")
|
|
99
|
+
File.exist?(manifest_path) or raise Failure, "#{manifest_path}: missing — re-run hecks project_rust for this " \
|
|
100
|
+
"domain to write it"
|
|
101
|
+
manifest = JSON.parse(File.read(manifest_path), symbolize_names: true)
|
|
102
|
+
[ir, manifest + unaccounted_findings(ir, manifest)]
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def allowlist_rule_matches?(rule, finding)
|
|
106
|
+
rule[:kind] == finding[:kind].to_s &&
|
|
107
|
+
rule[:gap_class] == finding[:gap_class].to_s &&
|
|
108
|
+
rule[:construct] == finding[:construct].to_s
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def allowlisted(finding) = ALLOWLIST.find { |rule| allowlist_rule_matches?(rule, finding) }
|
|
112
|
+
|
|
113
|
+
# A synthetic finding for each expected construct with no manifest entry at all: a gap in
|
|
114
|
+
# the manifest writer itself.
|
|
115
|
+
def unaccounted_findings(payload, manifest)
|
|
116
|
+
ids = Hash.new { |hash, kind| hash[kind] = Set.new }
|
|
117
|
+
manifest.each { |entry| ids[entry[:kind].to_sym] << entry[:id] }
|
|
118
|
+
expected_from_ir(payload).reject { |kind, id| ids[kind].include?(id) }.map do |kind, id|
|
|
119
|
+
{ kind: kind.to_s, id: id, generated: false, gap_class: "unaccounted", construct: "unaccounted",
|
|
120
|
+
reason: "expected per ir.json, but manifest.json has NO entry for it at all — not even a " \
|
|
121
|
+
"recorded skip; the generator's own accounting missed this construct entirely" }
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# A real functional gap: not generated, or generated but unrouted.
|
|
126
|
+
def functional_gap?(finding) = finding[:generated] == false || finding[:routed] == false
|
|
127
|
+
|
|
128
|
+
# Every rule must still excuse a real gap somewhere under `src/generated/`.
|
|
129
|
+
def check_allowlist
|
|
130
|
+
gaps = Dir.glob(File.join(generated_root, "*/ir.json")).flat_map do |ir_path|
|
|
131
|
+
coverage_findings(File.dirname(ir_path))[1].select { |finding| functional_gap?(finding) }
|
|
132
|
+
end
|
|
133
|
+
stale = ALLOWLIST.reject { |rule| gaps.any? { |finding| allowlist_rule_matches?(rule, finding) } }
|
|
134
|
+
if stale.empty?
|
|
135
|
+
puts "hecks rust_coverage --check-allowlist: all #{ALLOWLIST.size} rules still excuse a real gap"
|
|
136
|
+
return 0
|
|
137
|
+
end
|
|
138
|
+
stale.each do |rule|
|
|
139
|
+
warn "STALE ALLOWLIST RULE: #{rule.slice(:kind, :gap_class, :construct)} matches no gap in any generated " \
|
|
140
|
+
"module — the gap it excused is gone; delete the rule"
|
|
141
|
+
end
|
|
142
|
+
1
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def report(domain, codegen)
|
|
146
|
+
mod_dir = File.join(generated_root, domain)
|
|
147
|
+
Dir.exist?(mod_dir) or raise Failure, "#{mod_dir}: no such generated module — run hecks project_rust first, " \
|
|
148
|
+
"or check rust/src/generated/ for the actual directory name"
|
|
149
|
+
return print_report(domain, mod_dir) unless codegen == "rust"
|
|
150
|
+
|
|
151
|
+
Dir.mktmpdir("rust-coverage-codegen-") do |scratch|
|
|
152
|
+
print_report(domain, regenerated(mod_dir, domain, scratch))
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# Regenerates the domain's manifest through `hecks-codegen` into a scratch directory. `cargo
|
|
157
|
+
# run` rather than a fixed target path, so `CARGO_TARGET_DIR` and rust-toolchain.toml apply.
|
|
158
|
+
def regenerated(mod_dir, domain, scratch)
|
|
159
|
+
ir_path = File.join(mod_dir, "ir.json")
|
|
160
|
+
File.exist?(File.join(mod_dir, "manifest.json")) or raise Failure, "#{mod_dir}: no committed manifest.json — " \
|
|
161
|
+
"re-run hecks project_rust for this domain to write it"
|
|
162
|
+
out_dir = File.join(scratch, domain)
|
|
163
|
+
output, status = Open3.capture2e("cargo", "run", "--quiet", "--", "domain", ir_path, domain, domain, out_dir,
|
|
164
|
+
chdir: File.join(rust_dir, "codegen"))
|
|
165
|
+
status.success? or raise Failure, "hecks-codegen domain failed for #{domain}:\n#{output}"
|
|
166
|
+
# `hecks-codegen domain` writes no ir.json sidecar; the cross-check reads the committed IR
|
|
167
|
+
# the generator was just handed.
|
|
168
|
+
FileUtils.cp(ir_path, File.join(out_dir, "ir.json"))
|
|
169
|
+
out_dir
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def print_report(domain, mod_dir)
|
|
173
|
+
ir, findings = coverage_findings(mod_dir)
|
|
174
|
+
# A construct is implemented only when generated and routed (where routing applies): a
|
|
175
|
+
# `dispatch_*` that compiles but nothing can call is not working.
|
|
176
|
+
implemented, remainder = findings.partition { |f| f[:generated] && f[:routed] != false }
|
|
177
|
+
deferred, gap = remainder.partition { |f| allowlisted(f) }
|
|
178
|
+
puts "=" * 72
|
|
179
|
+
puts "hecks rust_coverage #{domain} (source: manifest.json)"
|
|
180
|
+
puts "=" * 72
|
|
181
|
+
puts "#{ir.fetch(:name)} — #{findings.size} constructs accounted for " \
|
|
182
|
+
"(#{implemented.size} implemented, #{deferred.size} deferred, #{gap.size} GAP)"
|
|
183
|
+
print_bucket("IMPLEMENTED", implemented) unless implemented.empty?
|
|
184
|
+
print_deferred(deferred)
|
|
185
|
+
print_gaps(gap)
|
|
186
|
+
puts
|
|
187
|
+
gap.empty? ? 0 : 1
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def print_bucket(label, entries)
|
|
191
|
+
puts "\n#{label} (#{entries.size})"
|
|
192
|
+
sorted(entries).each { |entry| print_entry(entry) }
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def print_deferred(deferred)
|
|
196
|
+
puts "\nDEFERRED (#{deferred.size}) — on the allowlist, with its cited reason"
|
|
197
|
+
sorted(deferred).each do |entry|
|
|
198
|
+
puts " [#{entry[:kind]}] #{entry[:id]}"
|
|
199
|
+
puts " allowlist: #{allowlisted(entry)[:doc]}"
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def print_gaps(gap)
|
|
204
|
+
puts "\nGAP (#{gap.size}) — missing, and NOT on the allowlist: a real, actionable finding"
|
|
205
|
+
sorted(gap).each { |entry| print_entry(entry) }
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def print_entry(entry)
|
|
209
|
+
line = " [#{entry[:kind]}] #{entry[:id]}"
|
|
210
|
+
line += " (#{entry[:gap_class]})" if entry[:gap_class]
|
|
211
|
+
puts line
|
|
212
|
+
puts " #{entry[:reason]}" if entry[:reason]
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def sorted(entries) = entries.sort_by { |entry| [entry[:kind].to_s, entry[:id]] }
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
end
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "digest"
|
|
4
|
+
require "fileutils"
|
|
5
|
+
require "open3"
|
|
6
|
+
|
|
7
|
+
module Hecks
|
|
8
|
+
module RustBuild
|
|
9
|
+
# Builds the native binary for one generated domain's Cargo feature, once per workspace.
|
|
10
|
+
#
|
|
11
|
+
# Examples interleave, and alternating `--features` builds forces a full recompile each time,
|
|
12
|
+
# so a built binary is cached per `[rust_dir, feature, sources]`, where `sources` is a digest of
|
|
13
|
+
# the workspace's Rust sources and manifests. Regenerating the domain changes the digest, so the
|
|
14
|
+
# next call rebuilds instead of answering with a stale binary or a stale failure.
|
|
15
|
+
module NativeBuild
|
|
16
|
+
# Raised when a declared feature fails to build; nil is reserved for "feature not declared".
|
|
17
|
+
class BuildFailed < StandardError; end
|
|
18
|
+
|
|
19
|
+
@cache = {}
|
|
20
|
+
|
|
21
|
+
class << self
|
|
22
|
+
# @return [Hash{Array => String, BuildFailed, nil}] each build's answer, keyed by
|
|
23
|
+
# `[rust_dir, feature, sources_digest]`
|
|
24
|
+
attr_reader :cache
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
module_function
|
|
28
|
+
|
|
29
|
+
# Builds the binary for `domain_feature` once per (rust_dir, feature, sources) and
|
|
30
|
+
# memoizes the result.
|
|
31
|
+
#
|
|
32
|
+
# @param domain_feature [String] the Cargo feature, a domain's directory basename
|
|
33
|
+
# @param rust_dir [String] the workspace to build in
|
|
34
|
+
# @return [String, nil] the pinned binary, or nil when Cargo.toml declares no such feature
|
|
35
|
+
# @raise [BuildFailed] when the build fails (memoized too)
|
|
36
|
+
def build_rust_for(domain_feature, rust_dir)
|
|
37
|
+
cache = NativeBuild.cache
|
|
38
|
+
key = [rust_dir, domain_feature, sources_digest(rust_dir)]
|
|
39
|
+
if cache.key?(key)
|
|
40
|
+
raise cache[key] if cache[key].is_a?(BuildFailed)
|
|
41
|
+
|
|
42
|
+
return cache[key]
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
manifest = File.read(File.join(rust_dir, "Cargo.toml"))
|
|
46
|
+
return cache[key] = nil unless manifest =~ /^#{Regexp.escape(domain_feature)}\s*=\s*\[\]/
|
|
47
|
+
|
|
48
|
+
begin
|
|
49
|
+
cache[key] = build_and_pin(domain_feature, rust_dir)
|
|
50
|
+
rescue BuildFailed => e
|
|
51
|
+
cache[key] = e
|
|
52
|
+
raise
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Fingerprints what a build reads: every `.rs`, `.toml` and `Cargo.lock` file under the
|
|
57
|
+
# workspace outside `target/`, by path, size and modification time.
|
|
58
|
+
#
|
|
59
|
+
# @param rust_dir [String] the workspace
|
|
60
|
+
# @return [String] a hex digest that changes when any of those files does
|
|
61
|
+
def sources_digest(rust_dir)
|
|
62
|
+
digest = Digest::SHA256.new
|
|
63
|
+
Dir.glob(File.join(rust_dir, "**", "*.{rs,toml}"), File::FNM_DOTMATCH).sort.each do |path|
|
|
64
|
+
digest_file(digest, path, rust_dir)
|
|
65
|
+
end
|
|
66
|
+
lock = File.join(rust_dir, "Cargo.lock")
|
|
67
|
+
digest_file(digest, lock, rust_dir) if File.file?(lock)
|
|
68
|
+
digest.hexdigest
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def digest_file(digest, path, rust_dir)
|
|
72
|
+
relative = path.delete_prefix("#{rust_dir}/")
|
|
73
|
+
return if relative.start_with?("target/") || !File.file?(path)
|
|
74
|
+
|
|
75
|
+
stat = File.stat(path)
|
|
76
|
+
digest << "#{relative}\0#{stat.size}\0#{stat.mtime.to_i}.#{stat.mtime.nsec}\n"
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Holds an exclusive flock from `cargo build` through the copy-out: parallel sweeps share
|
|
80
|
+
# `target/debug/rust`, and another build could overwrite it before it is pinned. The lock
|
|
81
|
+
# file is left on disk.
|
|
82
|
+
#
|
|
83
|
+
# @param domain_feature [String] the Cargo feature
|
|
84
|
+
# @param rust_dir [String] the workspace to build in
|
|
85
|
+
# @return [String] the binary, pinned to `target/debug/rust-<feature>`
|
|
86
|
+
# @raise [BuildFailed] when the build fails or leaves no binary
|
|
87
|
+
def build_and_pin(domain_feature, rust_dir)
|
|
88
|
+
lock_path = File.join(rust_dir, "target", ".build_rust_for.lock")
|
|
89
|
+
FileUtils.mkdir_p(File.dirname(lock_path))
|
|
90
|
+
|
|
91
|
+
File.open(lock_path, File::CREAT | File::RDWR) do |lock|
|
|
92
|
+
lock.flock(File::LOCK_EX)
|
|
93
|
+
command = ["cargo", "build", "--no-default-features", "--features", domain_feature]
|
|
94
|
+
cargo(command, rust_dir, domain_feature)
|
|
95
|
+
pin(rust_dir, domain_feature, command)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def cargo(command, rust_dir, domain_feature)
|
|
100
|
+
_stdout, stderr, status = Open3.capture3(*command, chdir: rust_dir)
|
|
101
|
+
return if status.success?
|
|
102
|
+
|
|
103
|
+
raise BuildFailed, "`#{command.join(' ')}` failed in #{rust_dir} (exit #{status.exitstatus}) — " \
|
|
104
|
+
"#{domain_feature} is declared in Cargo.toml, so this is a build failure, " \
|
|
105
|
+
"not a missing feature:\n#{stderr}"
|
|
106
|
+
rescue SystemCallError => e
|
|
107
|
+
raise BuildFailed, "`#{command.join(' ')}` could not run in #{rust_dir}: #{e.message}"
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# `cargo build` always writes the same path; it is pinned per feature while the lock is held.
|
|
111
|
+
def pin(rust_dir, domain_feature, command)
|
|
112
|
+
binary = File.join(rust_dir, "target", "debug", "rust")
|
|
113
|
+
unless File.executable?(binary)
|
|
114
|
+
raise BuildFailed, "`#{command.join(' ')}` succeeded in #{rust_dir} but left no executable at #{binary}"
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
pinned = File.join(rust_dir, "target", "debug", "rust-#{domain_feature}")
|
|
118
|
+
FileUtils.cp(binary, pinned)
|
|
119
|
+
File.chmod(0o755, pinned)
|
|
120
|
+
pinned
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
module RustBuild
|
|
5
|
+
class ProjectRust
|
|
6
|
+
# Writes `src/generated/mod.rs`: a `pub mod` per generated directory, the feature-gated
|
|
7
|
+
# `active` re-export of the selected domain, and a `compile_error!` for each pair of
|
|
8
|
+
# non-default domain features enabled together.
|
|
9
|
+
class RootMod
|
|
10
|
+
# @param file [IO] where the module is written
|
|
11
|
+
# @param directories [Array<String>] every directory under `src/generated`
|
|
12
|
+
# @param domains [Array<String>] the directories that carry their own `merged.rs`
|
|
13
|
+
# @param default [String] the domain this run generated, whose feature is the default
|
|
14
|
+
def initialize(file, directories, domains, default)
|
|
15
|
+
@file = file
|
|
16
|
+
@directories = directories
|
|
17
|
+
@domains = domains
|
|
18
|
+
@default = default
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# @return [void]
|
|
22
|
+
def write
|
|
23
|
+
banner
|
|
24
|
+
modules
|
|
25
|
+
active_notes
|
|
26
|
+
reexports
|
|
27
|
+
conflict_guards
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def banner
|
|
33
|
+
@file.puts "// GENERATED by hecks project_rust — re-run it to refresh this list."
|
|
34
|
+
@file.puts "//"
|
|
35
|
+
@file.puts "// BUG#25 — every DOMAIN (an entry in `domains`, below: a directory"
|
|
36
|
+
@file.puts "// carrying its own merged.rs and therefore its own Cargo feature) is"
|
|
37
|
+
@file.puts "// declared behind that SAME feature, so a broken or absent domain's"
|
|
38
|
+
@file.puts "// generated code is never even compiled into a build that selects a"
|
|
39
|
+
@file.puts "// DIFFERENT domain — `cargo build --features waybill` used to pull in"
|
|
40
|
+
@file.puts "// every OTHER domain's `pub mod` unconditionally, so one domain's own"
|
|
41
|
+
@file.puts "// non-compiling generated code (a `has_many` field, before this fix)"
|
|
42
|
+
@file.puts "// broke every feature's build, not just its own. A shared FRAMEWORK"
|
|
43
|
+
@file.puts "// chapter (governance/identity — no merged.rs of its own, so absent"
|
|
44
|
+
@file.puts "// from `domains`) stays unconditional: it has no single feature of its"
|
|
45
|
+
@file.puts "// own to gate behind, and is compiled in by whichever domain(s) attach"
|
|
46
|
+
@file.puts "// it via `uses_framework`."
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def modules
|
|
50
|
+
@directories.each do |name|
|
|
51
|
+
@file.puts "#[cfg(feature = #{name.inspect})]" if @domains.include?(name)
|
|
52
|
+
@file.puts "pub mod #{name};"
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def active_notes
|
|
57
|
+
@file.puts
|
|
58
|
+
@file.puts "// `active` is whichever ONE domain's own merged Store/dispatch table"
|
|
59
|
+
@file.puts "// is selected by Cargo feature (rust/Cargo.toml, kept in sync with this"
|
|
60
|
+
@file.puts "// list by hecks project_rust) — kernel/cli.rs imports"
|
|
61
|
+
@file.puts "// `crate::generated::active::{dispatch_by_name, Store, ...}` unchanged"
|
|
62
|
+
@file.puts "// no matter which domain that resolves to."
|
|
63
|
+
@file.puts "//"
|
|
64
|
+
@file.puts "// DOMAIN FEATURES ARE MUTUALLY EXCLUSIVE (R5) — Cargo has no native"
|
|
65
|
+
@file.puts "// concept of that, and `default = [#{@default.inspect}]` in"
|
|
66
|
+
@file.puts "// Cargo.toml stays enabled unless a build passes"
|
|
67
|
+
@file.puts "// `--no-default-features`, so a plain `cargo build --features banking`"
|
|
68
|
+
@file.puts "// used to enable BOTH the default domain's feature and banking's,"
|
|
69
|
+
@file.puts "// producing two `pub use ... as active;` imports and a hard E0252."
|
|
70
|
+
@file.puts "// Fixed two ways below, entirely mechanically from `domains`/"
|
|
71
|
+
@file.puts "// `target_mod_name` (this run's new default) — never hand-maintained:"
|
|
72
|
+
@file.puts "//"
|
|
73
|
+
@file.puts "// 1. THE DEFAULT DOMAIN'S OWN re-export backs off whenever any OTHER"
|
|
74
|
+
@file.puts "// domain feature is also enabled, so it never contends for `active`"
|
|
75
|
+
@file.puts "// by accident — `cargo build --features banking` (still implicitly"
|
|
76
|
+
@file.puts "// carrying the default feature) now resolves to banking alone,"
|
|
77
|
+
@file.puts "// same as if `--no-default-features` had been passed explicitly."
|
|
78
|
+
@file.puts "// 2. Two NON-default domain features enabled TOGETHER (a genuine,"
|
|
79
|
+
@file.puts "// not-accidental conflict — nothing makes that combination"
|
|
80
|
+
@file.puts "// meaningful) still fail the build, but with an explicit"
|
|
81
|
+
@file.puts "// `compile_error!` naming both features instead of a cryptic"
|
|
82
|
+
@file.puts "// \"defined multiple times\"."
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def reexports
|
|
86
|
+
@domains.each do |name|
|
|
87
|
+
other_domains = @domains - [name]
|
|
88
|
+
if name == @default && !other_domains.empty?
|
|
89
|
+
guard = other_domains.map { |o| "feature = #{o.inspect}" }.join(", ")
|
|
90
|
+
@file.puts "#[cfg(all(feature = #{name.inspect}, not(any(#{guard}))))]"
|
|
91
|
+
else
|
|
92
|
+
@file.puts "#[cfg(feature = #{name.inspect})]"
|
|
93
|
+
end
|
|
94
|
+
@file.puts "pub use #{name}::merged as active;"
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def conflict_guards
|
|
99
|
+
non_default_domains = @domains - [@default]
|
|
100
|
+
if non_default_domains.size > 1
|
|
101
|
+
@file.puts
|
|
102
|
+
@file.puts "// Explicit conflict guard — see point 2 above. Only fires when TWO"
|
|
103
|
+
@file.puts "// non-default domain features are both turned on; the default"
|
|
104
|
+
@file.puts "// domain's own feature never reaches here (point 1 already excludes"
|
|
105
|
+
@file.puts "// it whenever any other domain feature is present)."
|
|
106
|
+
non_default_domains.combination(2).each do |a, b|
|
|
107
|
+
@file.puts "#[cfg(all(feature = #{a.inspect}, feature = #{b.inspect}))]"
|
|
108
|
+
# Plain `a`/`b`/`domains.join` here, never `.inspect` — this whole
|
|
109
|
+
# line already sits inside one Rust string literal
|
|
110
|
+
# (`compile_error!("...")`), and `.inspect`'s own quoted form
|
|
111
|
+
# would nest unescaped double quotes and produce invalid Rust.
|
|
112
|
+
@file.puts "compile_error!(\"domain features are mutually exclusive — enable only one of: #{@domains.join(', ')} " \
|
|
113
|
+
"(both #{a} and #{b} are enabled)\");"
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|