hecks 2.9.0 → 3.0.1
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 +13 -4
- 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 +15 -8
- 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 +15 -5
- 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,27 @@
|
|
|
1
|
+
module RustProjection
|
|
2
|
+
module Projector
|
|
3
|
+
module_function
|
|
4
|
+
|
|
5
|
+
# Per-attribute reference specs for one declaring node, computed at codegen time.
|
|
6
|
+
# Mirrors the walk in `CommandRules::References#dereference`.
|
|
7
|
+
def reference_specs(domain_name, attributes)
|
|
8
|
+
attributes.filter_map do |attr|
|
|
9
|
+
target = reference_target(attr[:type])
|
|
10
|
+
next unless target
|
|
11
|
+
|
|
12
|
+
{ field: attr[:name].to_s, as_name: attr[:name].to_s.sub(/_id\z/, ""), target: "#{domain_name}::#{target}" }
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def emit_reference_spec(spec)
|
|
17
|
+
"crate::kernel::ReferenceSpec { field: #{spec[:field].inspect}, as_name: #{spec[:as_name].inspect}, target: #{spec[:target].inspect} }"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# `&[]` when empty: call sites need a `&'static [ReferenceSpec]` expression.
|
|
21
|
+
def emit_reference_specs_literal(specs)
|
|
22
|
+
return "&[]" if specs.empty?
|
|
23
|
+
|
|
24
|
+
"&[#{specs.map { |s| emit_reference_spec(s) }.join(', ')}]"
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
# Emits the generated Rust registry and JSON command router for a projected domain.
|
|
2
|
+
module RustProjection
|
|
3
|
+
module Projector
|
|
4
|
+
module_function
|
|
5
|
+
|
|
6
|
+
# Statement refusing a caller whose role differs from the command's declared `role:`.
|
|
7
|
+
# Emitted at router level, where the caller's role arrives as `caller_role`.
|
|
8
|
+
# `command_name` is the short name ("Open"), matching Ruby's refusal wording.
|
|
9
|
+
def emit_role_check(role, command_name)
|
|
10
|
+
return nil unless role
|
|
11
|
+
|
|
12
|
+
Exemplar.render("role_check", '"TmplRole"' => role.inspect, '"TmplCommandName"' => command_name.to_s.inspect)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# The `kernel::ArgumentGates` literal for one command, consumed by
|
|
16
|
+
# `decode_aggregate_arguments`/`decode_entity_arguments`, which own the step order.
|
|
17
|
+
# Role and reference checks are closures because they need `store`.
|
|
18
|
+
def emit_argument_gates_literal(args_path, invariant_check_lines, role_line, reference_lines)
|
|
19
|
+
normalize = ["let args = #{args_path}::from_json(v)?;", *Array(invariant_check_lines).map { |line| squeeze(line) }, "Ok(args)"].join(" ")
|
|
20
|
+
role = role_line ? "&|| { #{squeeze(role_line)} Ok(()) }" : "&|| Ok(())"
|
|
21
|
+
references =
|
|
22
|
+
if reference_lines.empty?
|
|
23
|
+
"&|_args: &#{args_path}| Ok(())"
|
|
24
|
+
else
|
|
25
|
+
"&|args: &#{args_path}| { #{reference_lines.map { |line| squeeze(line) }.join(' ')} Ok(()) }"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
"crate::kernel::ArgumentGates { " \
|
|
29
|
+
"decode_arguments: &#{args_path}::decode_arguments, " \
|
|
30
|
+
"refuse_unknown_arguments: &#{args_path}::refuse_unknown_arguments, " \
|
|
31
|
+
"refuse_absent_arguments: &#{args_path}::refuse_absent_arguments, " \
|
|
32
|
+
"normalize_args: &|v: &crate::kernel::Json| { #{normalize} }, " \
|
|
33
|
+
"refuse_role_mismatch: #{role}, " \
|
|
34
|
+
"resolve_references: #{references} }"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Collapses a multi-line emitted statement to one line so it can sit in a closure.
|
|
38
|
+
def squeeze(text) = text.to_s.split("\n").map(&:strip).reject(&:empty?).join(" ")
|
|
39
|
+
|
|
40
|
+
def emit_reference_check(check)
|
|
41
|
+
ident = rust_ident_field(check[:field])
|
|
42
|
+
target_subs = { '"TmplTarget"' => check[:target_name].inspect, '"tmpl_heads"' => check[:heads].inspect }
|
|
43
|
+
|
|
44
|
+
if check[:list_item]
|
|
45
|
+
list_subs = { "tmpl_target_mod" => check[:target_mod], "tmpl_list_field" => ident,
|
|
46
|
+
"&item.tmpl_element_field" => check[:list_item] }
|
|
47
|
+
Exemplar.render("reference_check_list", target_subs.merge(list_subs))
|
|
48
|
+
elsif check[:optional]
|
|
49
|
+
Exemplar.render("reference_check_optional", target_subs.merge("tmpl_target_mod" => check[:target_mod], "tmpl_optional_field" => ident))
|
|
50
|
+
else
|
|
51
|
+
Exemplar.render("reference_check_required", target_subs.merge("tmpl_target_mod" => check[:target_mod], "tmpl_field" => ident))
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Rust statement refusing a cross-tenant reference (`check` is built by
|
|
56
|
+
# `tenant_boundary_checks` in domain_generator.rb). Hand-built, not an Exemplar: the target
|
|
57
|
+
# accessor is either a single-attribute value object or a bare scalar, which the fixed
|
|
58
|
+
# reference_check_* templates have no slot for. `{:?}` matches Ruby's `#inspect` for both.
|
|
59
|
+
def emit_tenant_boundary_check(check)
|
|
60
|
+
ref_ident = rust_ident_field(check[:reference_field])
|
|
61
|
+
own_expr = "args.#{rust_ident_field(check[:own_accessor])}.clone()"
|
|
62
|
+
|
|
63
|
+
target_accessor = check[:target_accessor]
|
|
64
|
+
target_expr =
|
|
65
|
+
if target_accessor.include?(".")
|
|
66
|
+
head, inner = target_accessor.split(".", 2)
|
|
67
|
+
"record.#{rust_ident_field(head)}.as_ref().map(|v| v.#{rust_ident_field(inner)}.clone())"
|
|
68
|
+
else
|
|
69
|
+
"record.#{rust_ident_field(target_accessor)}.clone()"
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
"if let Some(record) = store.#{check[:target_mod]}.find(&args.#{ref_ident}) { " \
|
|
73
|
+
"if let Some(target_tenant) = #{target_expr} { " \
|
|
74
|
+
"let own_tenant = #{own_expr}; " \
|
|
75
|
+
"if target_tenant != own_tenant { " \
|
|
76
|
+
"return Err(crate::kernel::Refusal::Unauthorized(crate::kernel::refusal_wording::UnauthorizedCrossTenantReferenceArgs { " \
|
|
77
|
+
"aggregate: #{check[:aggregate_name].inspect}, " \
|
|
78
|
+
"field: #{check[:own_tenant_field].to_s.inspect}, " \
|
|
79
|
+
"tenant: &format!(\"{:?}\", own_tenant), " \
|
|
80
|
+
"attribute: #{check[:reference_field].to_s.inspect}, " \
|
|
81
|
+
"target: #{check[:target_name].inspect}, " \
|
|
82
|
+
"target_field: #{check[:target_tenant_field].to_s.inspect}, " \
|
|
83
|
+
"other: &format!(\"{:?}\", target_tenant)" \
|
|
84
|
+
" }.render_args())); " \
|
|
85
|
+
"} } }"
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Builds registry.rs: `Store`, `instances()`, `from_seed`, the query lookup and the JSON
|
|
89
|
+
# command router. `aggregates` is accumulated by domain_generator.rb. Paths are absolute
|
|
90
|
+
# (`crate::generated::<chapter>::<mod>`) so single- and multi-chapter callers resolve alike;
|
|
91
|
+
# `domain_name` is read per aggregate so a merged registry labels each dump correctly.
|
|
92
|
+
def emit_registry(aggregates)
|
|
93
|
+
chapter_path = ->(a) { "crate::generated::#{a[:chapter_mod]}::#{a[:mod]}" }
|
|
94
|
+
store_fields = aggregates.map { |a| " pub #{a[:mod]}: crate::kernel::InMemoryRepository<#{chapter_path.call(a)}::#{a[:record]}>," }
|
|
95
|
+
store_inits = aggregates.map { |a| " #{a[:mod]}: crate::kernel::InMemoryRepository::new()," }
|
|
96
|
+
|
|
97
|
+
dump_arms = aggregates.map do |a|
|
|
98
|
+
prefix = "#{a[:domain_name]}::#{a[:name]}#"
|
|
99
|
+
<<~RUST.rstrip
|
|
100
|
+
for (id, record) in self.#{a[:mod]}.entries() {
|
|
101
|
+
instances.push((format!("{}{}", #{prefix.inspect}, id), record.to_json()));
|
|
102
|
+
}
|
|
103
|
+
RUST
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Inverse of `instances()`: one `if let` per aggregate on its "Domain::Aggregate#" prefix.
|
|
107
|
+
# Unrecognized seed keys are skipped, not refused, so a host can seed loosely.
|
|
108
|
+
seed_arms = aggregates.map do |a|
|
|
109
|
+
mod_path = chapter_path.call(a)
|
|
110
|
+
prefix = "#{a[:domain_name]}::#{a[:name]}#"
|
|
111
|
+
<<~RUST.rstrip
|
|
112
|
+
if let Some(id) = key.strip_prefix(#{prefix.inspect}) {
|
|
113
|
+
store.#{a[:mod]}.save(id, #{mod_path}::#{a[:record]}::from_json(value)?);
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
RUST
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Query lookup: one `if` per aggregate on the bare "Domain::Aggregate" prefix (no "#";
|
|
120
|
+
# the whole string names one aggregate). Called by the "query" step in kernel/cli.rs.
|
|
121
|
+
query_arms = aggregates.map do |a|
|
|
122
|
+
prefix = "#{a[:domain_name]}::#{a[:name]}"
|
|
123
|
+
<<~RUST.rstrip
|
|
124
|
+
if aggregate == #{prefix.inspect} {
|
|
125
|
+
return Some(self.#{a[:mod]}.json_entries(|record| record.to_json()).map(|(id, json)| (id.clone(), json.clone())).collect());
|
|
126
|
+
}
|
|
127
|
+
RUST
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Borrowed twin of `query_arms`: the same prefixes, visiting each cached row in place.
|
|
131
|
+
scan_each_arms = aggregates.map do |a|
|
|
132
|
+
prefix = "#{a[:domain_name]}::#{a[:name]}"
|
|
133
|
+
<<~RUST.rstrip
|
|
134
|
+
if aggregate == #{prefix.inspect} {
|
|
135
|
+
for (id, json) in self.#{a[:mod]}.json_entries(|record| record.to_json()) {
|
|
136
|
+
visit(id, json);
|
|
137
|
+
}
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
140
|
+
RUST
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
aggregate_arms = aggregates.flat_map do |a|
|
|
144
|
+
mod_path = chapter_path.call(a)
|
|
145
|
+
a[:commands].map do |c|
|
|
146
|
+
# Identity extras (bare `identified_by` heads that are not attributes) are read from
|
|
147
|
+
# `facts_json`: no
|
|
148
|
+
# typed struct carries — then passed positionally into
|
|
149
|
+
# `dispatch_call` in commands.rb's `fn_signature` order. Absent is `NotFound`.
|
|
150
|
+
extra_names = Array(c[:identity_extra_params])
|
|
151
|
+
extra_idents = extra_names.map { |name| rust_ident_field(name) }
|
|
152
|
+
extra_lines = extra_names.zip(extra_idents).map do |name, ident|
|
|
153
|
+
"let #{ident} = facts_json.dig(#{name.to_s.inspect}).ok_or_else(|| crate::kernel::Refusal::NotFound(#{"#{c[:verb]} creates a #{a[:record]} — pass #{name}".inspect}.to_string()))?.to_id_component()?;"
|
|
154
|
+
end
|
|
155
|
+
extra_pass = extra_idents.map { |ident| "&#{ident}, " }.join
|
|
156
|
+
|
|
157
|
+
# A creating command's `dispatch_*` takes `route` right after `repo`.
|
|
158
|
+
# `tenant_boundary_check` is computed eagerly here and applied deferred in `dispatch()`.
|
|
159
|
+
dispatch_call = "#{mod_path}::dispatch_#{c[:fn]}(&mut store.#{a[:mod]}, #{c[:creates] ? "route, #{extra_pass}" : '&id, '}args, mutations, owner_deref, command_deref, tenant_boundary_check)"
|
|
160
|
+
# The route supplies an acting command's `id`; otherwise `extract_id` reads `facts_json`.
|
|
161
|
+
# A wholly missing identity is `NotFound` (acting_no_identity), as in `hydrate_existing`;
|
|
162
|
+
# `render_args` keeps the wording declared in one place.
|
|
163
|
+
acting_no_identity_args =
|
|
164
|
+
"crate::kernel::refusal_wording::NotFoundActingNoIdentityArgs { " \
|
|
165
|
+
"command: #{c[:name].to_s.inspect}, aggregate: #{a[:record].to_s.inspect}, " \
|
|
166
|
+
"identity: #{Array(a[:identified_by]).join(', ').inspect} }.render_args()"
|
|
167
|
+
# Route depth is checked before the argument gates for a flat-facts call and after them
|
|
168
|
+
# for an explicit `with:` call, as in Ruby's `Invocation.from_call`. A fixed order
|
|
169
|
+
# either way regressed, so `explicit_with()` picks the order per call.
|
|
170
|
+
route_precheck_line = "if let Some(route) = route { route.require_depth(0)?; }"
|
|
171
|
+
not_found_expr = "crate::kernel::Refusal::NotFound(#{acting_no_identity_args})"
|
|
172
|
+
# Identity resolution runs after every argument gate, as Ruby's `step_hydrate` does, so a
|
|
173
|
+
# malformed identity never masks an unknown, absent or ill-typed argument.
|
|
174
|
+
id_line =
|
|
175
|
+
if c[:creates]
|
|
176
|
+
nil
|
|
177
|
+
else
|
|
178
|
+
"let id = match route { Some(route) => route.aggregate().to_string(), None => #{mod_path}::#{a[:record]}::extract_id(facts_json).map_err(|_| #{not_found_expr})?, };"
|
|
179
|
+
end
|
|
180
|
+
# The kernel runs the argument gates in `AggregateStep::ORDER`; this arm does not.
|
|
181
|
+
# Role and reference checks are closures because they need `store`, and they finish
|
|
182
|
+
# borrowing it before `dispatch_call` takes its `&mut`.
|
|
183
|
+
gates_expr = "crate::kernel::decode_aggregate_arguments(facts_json, &#{emit_argument_gates_literal("#{mod_path}::#{c[:args_struct]}", c[:invariant_check_lines], emit_role_check(c[:role], c[:name]), c[:reference_checks].map { |check| emit_reference_check(check) })})?"
|
|
184
|
+
# One if/else expression, so exactly one route-check order runs per call.
|
|
185
|
+
args_line =
|
|
186
|
+
"let args = if invocation.explicit_with() { let args = #{gates_expr}; #{route_precheck_line} args } " \
|
|
187
|
+
"else { #{route_precheck_line} #{gates_expr} };"
|
|
188
|
+
# Computed here because it needs `store`; applied deferred by `kernel::dispatch()` where
|
|
189
|
+
# Ruby's `resolve_state_references` runs, after every earlier dispatch step. Each check's
|
|
190
|
+
# `return Err(...)` sits in a closure so the first violation is captured as a value.
|
|
191
|
+
tenant_boundary_check_bodies = Array(c[:tenant_boundary_checks]).map { |check| emit_tenant_boundary_check(check) }
|
|
192
|
+
tenant_boundary_check_line =
|
|
193
|
+
if tenant_boundary_check_bodies.empty?
|
|
194
|
+
"let tenant_boundary_check: Result<(), crate::kernel::Refusal> = Ok(());"
|
|
195
|
+
else
|
|
196
|
+
"let tenant_boundary_check: Result<(), crate::kernel::Refusal> = " \
|
|
197
|
+
"(|| -> Result<(), crate::kernel::Refusal> { #{tenant_boundary_check_bodies.join(' ')} Ok(()) })();"
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# `given`/`ensures` cross-aggregate dereference is resolved here because it needs `store`
|
|
201
|
+
# and must end its borrow before the `&mut store.<mod>` in the dispatch call. A creating
|
|
202
|
+
# command has no record yet, so `owner_deref` is empty; `command_deref` covers it.
|
|
203
|
+
owner_deref_expr = c[:creates] ? "Vec::new()" : "crate::kernel::owner_deref(&*store, REFERENCE_TABLE, #{"#{a[:domain_name]}::#{a[:name]}".inspect}, &id)"
|
|
204
|
+
deref_lines = [
|
|
205
|
+
"let owner_deref = #{owner_deref_expr};",
|
|
206
|
+
"let command_deref = crate::kernel::command_deref(&*store, REFERENCE_TABLE, #{emit_reference_specs_literal(c[:reference_specs])}, &args);",
|
|
207
|
+
]
|
|
208
|
+
|
|
209
|
+
body = ["let invocation = crate::kernel::CommandInvocation::from_json(args_json)?;",
|
|
210
|
+
"let route = invocation.route();",
|
|
211
|
+
"let facts_json = invocation.facts();",
|
|
212
|
+
args_line,
|
|
213
|
+
# Identity resolves after the gates; identity extras are read with it.
|
|
214
|
+
id_line, *extra_lines, tenant_boundary_check_line,
|
|
215
|
+
*deref_lines,
|
|
216
|
+
"let payload = crate::kernel::Json::overlay(facts_json, &args.to_json());",
|
|
217
|
+
"#{dispatch_call}.map(|(_, events)| stamp_payload(events, &payload))"].compact.reject(&:empty?)
|
|
218
|
+
|
|
219
|
+
" #{c[:verb].inspect} => {\n#{body.map { |line| " #{line}" }.join("\n")}\n }"
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
# Entity commands never create: parent and element identity are both read from the raw
|
|
224
|
+
# JSON. The generated function is `dispatch_entity_<fn>`.
|
|
225
|
+
entity_arms = aggregates.flat_map do |a|
|
|
226
|
+
mod_path = chapter_path.call(a)
|
|
227
|
+
a[:entity_commands].map do |c|
|
|
228
|
+
# Argument gates: see `aggregate_arms`; `decode_entity_arguments` walks
|
|
229
|
+
# `EntityStep::ORDER`.
|
|
230
|
+
gates_line = "let args = crate::kernel::decode_entity_arguments(facts_json, &#{emit_argument_gates_literal("#{mod_path}::#{c[:args_struct]}", c[:invariant_check_lines], emit_role_check(c[:role], c[:name]), c[:reference_checks].map { |check| emit_reference_check(check) })})?;"
|
|
231
|
+
dispatch_call = "#{mod_path}::dispatch_entity_#{c[:fn]}(&mut store.#{a[:mod]}, &parent_id, &element_id, &element_wants, args, mutations, owner_deref, command_deref).map(|(_, events)| stamp_payload(events, &payload))"
|
|
232
|
+
|
|
233
|
+
# `element_wants` is the caller-offered scalar identity values (`emit_extract_wants`),
|
|
234
|
+
# computed unconditionally alongside `element_id`.
|
|
235
|
+
# `owner_deref` is taken off `parent_id`, like an aggregate `Act`, so re-seeded parent
|
|
236
|
+
# `projects` fields resolve; `command_deref` also gets a "parent" node for `parent.X`
|
|
237
|
+
# givens.
|
|
238
|
+
# An entity's own reference attributes are not dereferenced (none in the corpus has one).
|
|
239
|
+
# An explicit route addresses one entity (`require_depth(1)`) and is checked ahead of the
|
|
240
|
+
# argument gates, as Ruby resolves `to:` independently of facts; unrouted calls fall back
|
|
241
|
+
# to `extract_id`/`extract_wants`. A failed `extract_id` becomes Ruby's NotFound wording
|
|
242
|
+
# (entity_parent_no_identity, entity_element_no_identity), not `TypeMismatch`.
|
|
243
|
+
entity_parent_no_identity_message = "#{c[:name]} acts on a #{a[:record]}'s #{c[:entity_name]} — pass #{Array(a[:identified_by]).join(', ')}:"
|
|
244
|
+
entity_element_no_identity_message = "#{c[:name]} acts on one #{c[:entity_name]} — pass #{c[:entity_identity_reading]}:"
|
|
245
|
+
body = ["let invocation = crate::kernel::CommandInvocation::from_json(args_json)?;",
|
|
246
|
+
"let route = invocation.route();",
|
|
247
|
+
"let facts_json = invocation.facts();",
|
|
248
|
+
# `element_id` uses `extract_id_lenient`: only an absent identity key is refused;
|
|
249
|
+
# a blank one flows through as a non-matching id. `parent_id` stays strict.
|
|
250
|
+
"if let Some(route) = route { route.require_depth(1)?; }",
|
|
251
|
+
gates_line,
|
|
252
|
+
"let (parent_id, element_id, element_wants) = match route { Some(route) => { let element_id = route.entities()[0].clone(); (route.aggregate().to_string(), element_id.clone(), element_id) }, None => { let parent_id = #{mod_path}::#{a[:record]}::extract_id(facts_json).map_err(|_| crate::kernel::Refusal::NotFound(#{entity_parent_no_identity_message.inspect}.to_string()))?; let element_id = #{mod_path}::#{c[:entity_record]}::extract_id_lenient(facts_json).map_err(|_| crate::kernel::Refusal::NotFound(#{entity_element_no_identity_message.inspect}.to_string()))?; let element_wants = #{mod_path}::#{c[:entity_record]}::extract_wants(facts_json); (parent_id, element_id, element_wants) }, };",
|
|
253
|
+
"let owner_deref = crate::kernel::owner_deref(&*store, REFERENCE_TABLE, #{"#{a[:domain_name]}::#{a[:name]}".inspect}, &parent_id);",
|
|
254
|
+
"let mut command_deref = crate::kernel::command_deref(&*store, REFERENCE_TABLE, #{emit_reference_specs_literal(c[:reference_specs])}, &args);",
|
|
255
|
+
"if let Some(parent_node) = crate::kernel::parent_deref(&*store, REFERENCE_TABLE, #{"#{a[:domain_name]}::#{a[:name]}".inspect}, &parent_id) { command_deref.push((\"parent\", parent_node)); }",
|
|
256
|
+
"let payload = crate::kernel::Json::overlay(facts_json, &args.to_json());",
|
|
257
|
+
dispatch_call].compact.reject(&:empty?)
|
|
258
|
+
|
|
259
|
+
" #{c[:verb].inspect} => {\n#{body.map { |line| " #{line}" }.join("\n")}\n }"
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
# Commands owned by an entity nested two levels deep
|
|
264
|
+
# (`dispatch_entity_<entity>_<nested>_<fn>`). With `c[:unrouted_supported]` the arm mirrors
|
|
265
|
+
# `entity_arms` one hop deeper; otherwise it requires an explicit route (`require_depth(2)`).
|
|
266
|
+
nested_entity_arms = aggregates.flat_map do |a|
|
|
267
|
+
mod_path = chapter_path.call(a)
|
|
268
|
+
Array(a[:nested_entity_commands]).map do |c|
|
|
269
|
+
# Argument gates: see `aggregate_arms`; `decode_entity_arguments` walks
|
|
270
|
+
# `EntityStep::ORDER`.
|
|
271
|
+
gates_line = "let args = crate::kernel::decode_entity_arguments(facts_json, &#{emit_argument_gates_literal("#{mod_path}::#{c[:args_struct]}", c[:invariant_check_lines], emit_role_check(c[:role], c[:name]), c[:reference_checks].map { |check| emit_reference_check(check) })})?;"
|
|
272
|
+
dispatch_call = "#{mod_path}::dispatch_entity_#{c[:fn]}(&mut store.#{a[:mod]}, &parent_id, &hop1_id, &hop1_wants, &hop2_id, &hop2_wants, args, mutations, owner_deref, command_deref).map(|(_, events)| stamp_payload(events, &payload))"
|
|
273
|
+
|
|
274
|
+
# Gates run before identity and the depth check leads, as in `entity_arms`. A failed
|
|
275
|
+
# `extract_id` per hop becomes that hop's own NotFound wording, as in `locate_chain`;
|
|
276
|
+
# the parent uses the joined entity path.
|
|
277
|
+
entity_parent_no_identity_message = "#{c[:name]} acts on a #{a[:record]}'s #{c[:entity_name]}.#{c[:nested_name]} — pass #{Array(a[:identified_by]).join(', ')}:"
|
|
278
|
+
hop1_no_identity_message = "#{c[:name]} acts on one #{c[:entity_name]} — pass #{c[:entity_identity_reading]}:"
|
|
279
|
+
hop2_no_identity_message = "#{c[:name]} acts on one #{c[:nested_name]} — pass #{c[:nested_identity_reading]}:"
|
|
280
|
+
# Every hop uses `extract_id_lenient` (see `entity_arms`); `parent_id` stays strict.
|
|
281
|
+
route_binding =
|
|
282
|
+
if c[:unrouted_supported]
|
|
283
|
+
"let (parent_id, hop1_id, hop1_wants, hop2_id, hop2_wants) = match route { Some(route) => { let hop1_id = route.entities()[0].clone(); let hop2_id = route.entities()[1].clone(); (route.aggregate().to_string(), hop1_id.clone(), hop1_id, hop2_id.clone(), hop2_id) }, None => { let parent_id = #{mod_path}::#{a[:record]}::extract_id(facts_json).map_err(|_| crate::kernel::Refusal::NotFound(#{entity_parent_no_identity_message.inspect}.to_string()))?; let hop1_id = #{mod_path}::#{c[:entity_record]}::extract_id_lenient(facts_json).map_err(|_| crate::kernel::Refusal::NotFound(#{hop1_no_identity_message.inspect}.to_string()))?; let hop1_wants = #{mod_path}::#{c[:entity_record]}::extract_wants(facts_json); let hop2_id = #{mod_path}::#{c[:nested_record]}::extract_id_lenient(facts_json).map_err(|_| crate::kernel::Refusal::NotFound(#{hop2_no_identity_message.inspect}.to_string()))?; let hop2_wants = #{mod_path}::#{c[:nested_record]}::extract_wants(facts_json); (parent_id, hop1_id, hop1_wants, hop2_id, hop2_wants) }, };"
|
|
284
|
+
else
|
|
285
|
+
"let route = route.ok_or_else(|| crate::kernel::Refusal::TypeMismatch(#{"#{c[:verb]} addresses an entity nested two levels deep — requires an explicit to: { aggregate:, entities: [...] } route".inspect}.to_string()))?; let parent_id = route.aggregate().to_string(); let hop1_id = route.entities()[0].clone(); let hop2_id = route.entities()[1].clone(); let hop1_wants = hop1_id.clone(); let hop2_wants = hop2_id.clone();"
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
body = ["let invocation = crate::kernel::CommandInvocation::from_json(args_json)?;",
|
|
289
|
+
"let route = invocation.route();",
|
|
290
|
+
"let facts_json = invocation.facts();",
|
|
291
|
+
"if let Some(route) = route { route.require_depth(2)?; }",
|
|
292
|
+
gates_line,
|
|
293
|
+
route_binding,
|
|
294
|
+
# `owner_deref` uses the top-level aggregate's reference fields.
|
|
295
|
+
"let owner_deref = crate::kernel::owner_deref(&*store, REFERENCE_TABLE, #{"#{a[:domain_name]}::#{a[:name]}".inspect}, &parent_id);",
|
|
296
|
+
"let command_deref = crate::kernel::command_deref(&*store, REFERENCE_TABLE, #{emit_reference_specs_literal(c[:reference_specs])}, &args);",
|
|
297
|
+
"let payload = crate::kernel::Json::overlay(facts_json, &args.to_json());",
|
|
298
|
+
dispatch_call].compact.reject(&:empty?)
|
|
299
|
+
|
|
300
|
+
" #{c[:verb].inspect} => {\n#{body.map { |line| " #{line}" }.join("\n")}\n }"
|
|
301
|
+
end
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
# Port operations: a command arm without `creates` or a role check (a port has no caller
|
|
305
|
+
# role). Reference checks are emitted here because they need `store`. The receiver comes from
|
|
306
|
+
# the route or `legacy_receiver_field` and is stripped from the facts `from_json` sees; its
|
|
307
|
+
# existence is checked once here.
|
|
308
|
+
port_arms = aggregates.flat_map do |a|
|
|
309
|
+
mod_path = chapter_path.call(a)
|
|
310
|
+
a[:ports].map do |p|
|
|
311
|
+
reference_lines = p[:reference_checks].map { |check| emit_reference_check(check) }
|
|
312
|
+
legacy_receiver = p[:legacy_receiver_field] ? "Some(#{p[:legacy_receiver_field].to_s.inspect})" : "None"
|
|
313
|
+
to_receiver = p[:to_receiver_field] ? "Some(#{p[:to_receiver_field].to_s.inspect})" : "None"
|
|
314
|
+
dispatch_call = "#{mod_path}::dispatch_operation_#{p[:fn]}(&id, args).map(|events| stamp_payload(events, &payload))"
|
|
315
|
+
|
|
316
|
+
body = ["let invocation = crate::kernel::CommandInvocation::from_json(args_json)?;",
|
|
317
|
+
"let (id, port_facts) = invocation.split_aggregate_receiver(#{legacy_receiver}, #{to_receiver})?;",
|
|
318
|
+
"let facts_json = &port_facts;",
|
|
319
|
+
"let _instance = store.#{a[:mod]}.find(&id).ok_or_else(|| crate::kernel::Refusal::NotFound(format!(\"#{a[:name]} {:?} does not exist\", id)))?;",
|
|
320
|
+
"let args = #{mod_path}::#{p[:args_struct]}::from_json(facts_json)?;", *reference_lines,
|
|
321
|
+
"let payload = crate::kernel::Json::overlay(facts_json, &args.to_json());",
|
|
322
|
+
dispatch_call].compact
|
|
323
|
+
|
|
324
|
+
" #{p[:verb].inspect} => {\n#{body.map { |line| " #{line}" }.join("\n")}\n }"
|
|
325
|
+
end
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
dispatch_arms = aggregate_arms + entity_arms + nested_entity_arms + port_arms
|
|
329
|
+
|
|
330
|
+
header = <<~RUST
|
|
331
|
+
// GENERATED by hecks project_rust — the JSON command router
|
|
332
|
+
// `kernel::cli` dispatches every step through. Do not hand-edit —
|
|
333
|
+
// re-run hecks project_rust instead.
|
|
334
|
+
#![allow(dead_code, unused_variables)]
|
|
335
|
+
|
|
336
|
+
// `Repository::save` (from_seed, below) is a TRAIT method —
|
|
337
|
+
// `InMemoryRepository`'s own inherent methods (entries(), used
|
|
338
|
+
// by instances()) need no import, but save() does.
|
|
339
|
+
use crate::kernel::Repository;
|
|
340
|
+
|
|
341
|
+
RUST
|
|
342
|
+
|
|
343
|
+
body = Exemplar.render(
|
|
344
|
+
"registry_file",
|
|
345
|
+
"TmplStore2" => "Store",
|
|
346
|
+
" pub tmpl_field: crate::kernel::InMemoryRepository<i64>," => store_fields.join("\n"),
|
|
347
|
+
" tmpl_field: tmpl_store_fields_placeholder()," => store_inits.join("\n"),
|
|
348
|
+
"tmpl_dump_arm_placeholder();" => dump_arms.join("\n"),
|
|
349
|
+
"tmpl_seed_arm_placeholder();" => seed_arms.join("\n"),
|
|
350
|
+
"tmpl_query_arm_placeholder();" => query_arms.join("\n"),
|
|
351
|
+
"tmpl_scan_each_arm_placeholder();" => scan_each_arms.join("\n"),
|
|
352
|
+
'"tmpl_verb" => { tmpl_dispatch_arm_placeholder() }' => dispatch_arms.join("\n")
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
"#{header}#{body}"
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
# The domain-wide `REFERENCE_TABLE` plus `Store`'s `ReferenceLookup` impl, which needs
|
|
359
|
+
# `store` to fetch targets. One row per aggregate, from `a[:reference_specs]`.
|
|
360
|
+
def emit_reference_table(aggregates)
|
|
361
|
+
rows = aggregates.map do |a|
|
|
362
|
+
qualified = "#{a[:domain_name]}::#{a[:name]}"
|
|
363
|
+
" (#{qualified.inspect}, #{emit_reference_specs_literal(a[:reference_specs])}),"
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
<<~RUST
|
|
367
|
+
pub static REFERENCE_TABLE: crate::kernel::ReferenceTable = &[
|
|
368
|
+
#{rows.join("\n")}
|
|
369
|
+
];
|
|
370
|
+
RUST
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
# `find_fielded`: one `if` per aggregate, boxing the found record as a type-erased
|
|
374
|
+
# `Fielded`. An undeclared or ungenerated target (e.g. cross-domain) yields `None`.
|
|
375
|
+
def emit_reference_lookup(aggregates)
|
|
376
|
+
arms = aggregates.map do |a|
|
|
377
|
+
prefix = "#{a[:domain_name]}::#{a[:name]}"
|
|
378
|
+
<<~RUST.rstrip
|
|
379
|
+
if target == #{prefix.inspect} {
|
|
380
|
+
return self.#{a[:mod]}.find(id).map(|r| Box::new(r) as Box<dyn crate::kernel::Fielded>);
|
|
381
|
+
}
|
|
382
|
+
RUST
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
<<~RUST
|
|
386
|
+
#{emit_reference_table(aggregates)}
|
|
387
|
+
impl crate::kernel::ReferenceLookup for Store {
|
|
388
|
+
fn find_fielded(&self, target: &str, id: &str) -> Option<Box<dyn crate::kernel::Fielded>> {
|
|
389
|
+
#{arms.join("\n")}
|
|
390
|
+
None
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
RUST
|
|
394
|
+
end
|
|
395
|
+
end
|
|
396
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
module RustProjection
|
|
2
|
+
module Projector
|
|
3
|
+
module_function
|
|
4
|
+
|
|
5
|
+
# A skip reason: the message text (a String) plus the machine-readable
|
|
6
|
+
# `construct` family (`rootless`, `optional_source`, ...) that forced the skip.
|
|
7
|
+
#
|
|
8
|
+
# Consumers read `construct` from manifest.json, so the prose is free to change.
|
|
9
|
+
# Re-wording an inner reason must go through `reskip`; interpolation drops `construct`.
|
|
10
|
+
class SkipReason < String
|
|
11
|
+
attr_reader :construct
|
|
12
|
+
|
|
13
|
+
def initialize(construct, text)
|
|
14
|
+
raise ArgumentError, "a skip reason needs a construct family" if construct.to_s.empty?
|
|
15
|
+
|
|
16
|
+
super(text)
|
|
17
|
+
@construct = construct.to_s.freeze
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def skip(construct, text) = SkipReason.new(construct, text)
|
|
22
|
+
|
|
23
|
+
# Re-words `inner` while keeping its construct; `fallback` applies only to a plain String.
|
|
24
|
+
def reskip(inner, text, fallback: nil)
|
|
25
|
+
construct = inner.respond_to?(:construct) ? inner.construct : fallback
|
|
26
|
+
SkipReason.new(construct, text)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|