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,641 @@
|
|
|
1
|
+
module RustProjection
|
|
2
|
+
module Projector
|
|
3
|
+
module_function
|
|
4
|
+
|
|
5
|
+
# Generates the WASM/CLI JSON boundary (to_json/from_json) alongside
|
|
6
|
+
# each Fielded impl; entities and aggregate records only get to_json.
|
|
7
|
+
|
|
8
|
+
def json_type_error(struct_name, key, expectation)
|
|
9
|
+
"crate::kernel::Refusal::TypeMismatch(#{"#{struct_name}.#{key}: expected #{expectation}".inspect}.to_string())"
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# The refusal for a scalar offered where a list argument goes; worded as Ruby's
|
|
13
|
+
# `Value.refuse_scalar_list`, with the offered value known only at runtime.
|
|
14
|
+
def list_shape_error(struct_name, key, element_type, value_var)
|
|
15
|
+
template = "#{struct_name}.#{key} expects list_of(#{element_type}), got {}"
|
|
16
|
+
"crate::kernel::Refusal::TypeMismatch(format!(#{template.inspect}, #{value_var}.inspect()))"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# A String mismatch also reports TypeMismatch when the value is an
|
|
20
|
+
# Array/Hash/Null, matching Ruby's laxer scalar-shape check; other
|
|
21
|
+
# scalar mismatches fall through to the generic wording.
|
|
22
|
+
def scalar_type_error(struct_name, key, scalar_type, value_var)
|
|
23
|
+
return json_type_error(struct_name, key, scalar_type) unless %w[Integer Float String].include?(scalar_type)
|
|
24
|
+
|
|
25
|
+
template = "#{struct_name}.#{key} expects #{scalar_type}, got {}"
|
|
26
|
+
proper = "crate::kernel::Refusal::TypeMismatch(format!(#{template.inspect}, #{value_var}.inspect()))"
|
|
27
|
+
return proper if scalar_type != "String"
|
|
28
|
+
|
|
29
|
+
generic = json_type_error(struct_name, key, scalar_type)
|
|
30
|
+
"if matches!(#{value_var}, crate::kernel::Json::Array(_) | crate::kernel::Json::Object(_) | crate::kernel::Json::Null) " \
|
|
31
|
+
"{ #{proper} } else { #{generic} }"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# A missing required field refuses with the same wording as an
|
|
35
|
+
# explicit null value (Ruby's check_required_fields).
|
|
36
|
+
def required_field_expr(struct_name, key, expected)
|
|
37
|
+
message = "#{struct_name}.#{key} expects #{expected}, got nil"
|
|
38
|
+
"v.get(#{key.inspect}).ok_or_else(|| crate::kernel::Refusal::TypeMismatch(#{message.inspect}.to_string()))?"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Boolean fields read via as_bool, mirroring naming.rb's SCALAR table.
|
|
42
|
+
SCALAR_JSON_ACCESSOR = { "String" => "as_str", "Integer" => "as_i64", "Float" => "as_f64",
|
|
43
|
+
"TrueClass" => "as_bool", "FalseClass" => "as_bool" }.freeze
|
|
44
|
+
|
|
45
|
+
# A single-attribute value object also accepts a bare JSON scalar in
|
|
46
|
+
# place of the wrapped {"field": ...} shape, matching Ruby's coercion.
|
|
47
|
+
def sole_field_of(type_name, value_objects_by_name)
|
|
48
|
+
vo = value_objects_by_name[type_name]
|
|
49
|
+
return nil unless vo && vo[:attributes]&.size == 1
|
|
50
|
+
|
|
51
|
+
vo[:attributes].first[:name].to_s
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Wraps a single-field value object's value with coerce_single_field
|
|
55
|
+
# before calling its from_json, so the bare-scalar shorthand applies.
|
|
56
|
+
def composite_from_json_expr(attr, value_objects_by_name, value_expr)
|
|
57
|
+
nested_type = rust_ident(attr[:type])
|
|
58
|
+
sole = sole_field_of(attr[:type], value_objects_by_name)
|
|
59
|
+
source =
|
|
60
|
+
if sole
|
|
61
|
+
"&#{value_expr}.coerce_single_field(#{sole.inspect})"
|
|
62
|
+
else
|
|
63
|
+
"#{value_expr}.expect_value_object_shape(#{attr[:name].to_s.inspect}, #{attr[:type].to_s.inspect})?"
|
|
64
|
+
end
|
|
65
|
+
"#{nested_type}::from_json(#{source})?"
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# A scalar-represented list element (e.g. list_of(String), or a
|
|
69
|
+
# has_many reference) reads like any other scalar field rather than
|
|
70
|
+
# calling a from_json that was never generated for it.
|
|
71
|
+
def list_element_from_json_mapper(struct_name, key, attr, value_objects_by_name)
|
|
72
|
+
scalar = effective_scalar_type(attr[:type])
|
|
73
|
+
if scalar
|
|
74
|
+
body = scalar_from_json_value_expr(struct_name, key, scalar, "item")
|
|
75
|
+
return "|item| #{body.sub(/\?\z/, '')}"
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
nested_type = rust_ident(attr[:type])
|
|
79
|
+
sole = sole_field_of(attr[:type], value_objects_by_name)
|
|
80
|
+
return "|item| #{nested_type}::from_json(&item.coerce_single_field(#{sole.inspect}))" if sole
|
|
81
|
+
|
|
82
|
+
"#{nested_type}::from_json"
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# The inverse of list_element_from_json_mapper: a scalar-represented
|
|
86
|
+
# element serializes like any other scalar field.
|
|
87
|
+
def list_element_to_json_expr(attr)
|
|
88
|
+
scalar = effective_scalar_type(attr[:type])
|
|
89
|
+
scalar ? scalar_to_json_expr(scalar, "x") : "x.to_json()"
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# A required value-object argument given a bare JSON null builds from
|
|
93
|
+
# an empty object rather than one literal null field, matching Ruby's
|
|
94
|
+
# leniency for a command-argument door (not a nested field's own null).
|
|
95
|
+
def required_composite_argument_expr(struct_name, key, attr, value_objects_by_name)
|
|
96
|
+
fetch = required_field_expr(struct_name, key, attr[:type])
|
|
97
|
+
nested_type = rust_ident(attr[:type])
|
|
98
|
+
sole = sole_field_of(attr[:type], value_objects_by_name)
|
|
99
|
+
guarded = "match #{fetch} { crate::kernel::Json::Null => crate::kernel::Json::Object(Vec::new()), other => other.clone() }"
|
|
100
|
+
return "#{nested_type}::from_json(&(#{guarded}).coerce_single_field(#{sole.inspect}))?" if sole
|
|
101
|
+
|
|
102
|
+
"#{nested_type}::from_json((#{guarded}).expect_value_object_shape(#{attr[:name].to_s.inspect}, " \
|
|
103
|
+
"#{attr[:type].to_s.inspect})?)?"
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# A scalar field missing from the caller's JSON falls back to the
|
|
107
|
+
# attribute's own default:, matching Value.build's rule.
|
|
108
|
+
def scalar_from_json_expr(struct_name, key, scalar_type, default: nil)
|
|
109
|
+
accessor = SCALAR_JSON_ACCESSOR.fetch(scalar_type)
|
|
110
|
+
wrap = scalar_type == "String" ? ".map(|s| s.to_string())" : ""
|
|
111
|
+
# Matches on presence first so a present-but-wrong-shape value still
|
|
112
|
+
# refuses instead of silently falling back to the default.
|
|
113
|
+
#
|
|
114
|
+
# default.nil?, not `if default` — a boolean attribute's own default
|
|
115
|
+
# can legitimately be false, which plain Ruby truthiness would treat
|
|
116
|
+
# as "no default."
|
|
117
|
+
unless default.nil?
|
|
118
|
+
return %(match v.get(#{key.inspect}) { Some(x) => x.#{accessor}()#{wrap}.ok_or_else(|| #{scalar_type_error(struct_name,
|
|
119
|
+
key, scalar_type, 'x')})?, None => #{literal_rhs(default)} })
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
%({ let x = #{required_field_expr(struct_name, key,
|
|
123
|
+
scalar_type)}; x.#{accessor}()#{wrap}.ok_or_else(|| #{scalar_type_error(struct_name, key,
|
|
124
|
+
scalar_type, 'x')})? })
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# scalar_from_json_expr's scalar-extraction half, applied to a value
|
|
128
|
+
# already looked up (avoids a second key lookup).
|
|
129
|
+
def scalar_from_json_value_expr(struct_name, key, scalar_type, value_expr)
|
|
130
|
+
accessor = SCALAR_JSON_ACCESSOR.fetch(scalar_type)
|
|
131
|
+
wrap = scalar_type == "String" ? ".map(|s| s.to_string())" : ""
|
|
132
|
+
%(#{value_expr}.#{accessor}()#{wrap}.ok_or_else(|| #{scalar_type_error(struct_name, key, scalar_type, value_expr)})?)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def scalar_to_json_expr(scalar_type, rust_expr)
|
|
136
|
+
case scalar_type
|
|
137
|
+
when "String" then "crate::kernel::Json::Str(#{rust_expr}.clone())"
|
|
138
|
+
when "Integer" then "crate::kernel::Json::int(#{rust_expr})"
|
|
139
|
+
when "Float" then "crate::kernel::Json::Float(#{rust_expr})"
|
|
140
|
+
when "TrueClass", "FalseClass" then "crate::kernel::Json::Bool(#{rust_expr})"
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# The allowlist of names a caller may address a command by: declared
|
|
145
|
+
# attributes plus id, the aggregate's identity heads, any reference
|
|
146
|
+
# key, and correlation keys a saga may smuggle through.
|
|
147
|
+
#
|
|
148
|
+
# extra_identity_heads: an entity dispatch also addresses the entity's
|
|
149
|
+
# own identity head, read directly out of args rather than declared.
|
|
150
|
+
def command_argument_allowlist(aggregate, command, process_managers, extra_identity_heads: [])
|
|
151
|
+
reference_key = command[:references].to_s.empty? ? nil : Hecks::Naming.reference_key(command[:references]).to_s
|
|
152
|
+
identity_heads = aggregate[:identified_by].map { |path| path.split(".").first }
|
|
153
|
+
correlation_keys = Array(process_managers).filter_map { |pm| pm[:correlates_by]&.split(".")&.first }
|
|
154
|
+
(["id", reference_key] + identity_heads + extra_identity_heads + correlation_keys).compact.uniq
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# Emits the AbsentArgument check: every required, undeclared-missing
|
|
158
|
+
# name the caller's JSON omits, sorted, refused before any field is
|
|
159
|
+
# built (ADR 0037 finding 3). Only ever emitted for a command's own
|
|
160
|
+
# args struct — a value object's missing field is required_field_expr's
|
|
161
|
+
# job.
|
|
162
|
+
def emit_absent_argument_check(command_name, attributes)
|
|
163
|
+
required = attributes.reject { |a| a[:optional] }.map { |a| rust_field(a[:name]) }.sort
|
|
164
|
+
return "" if required.empty?
|
|
165
|
+
|
|
166
|
+
declared = attributes.map { |a| a[:name].to_s }
|
|
167
|
+
<<~RUST
|
|
168
|
+
let absent: Vec<&str> = [#{required.map(&:inspect).join(', ')}].into_iter().filter(|key| v.get(key).is_none()).collect();
|
|
169
|
+
if !absent.is_empty() {
|
|
170
|
+
return Err(crate::kernel::Refusal::AbsentArgument(crate::kernel::refusal_wording::AbsentArgumentAbsentArgsArgs {
|
|
171
|
+
command: #{command_name.inspect},
|
|
172
|
+
absent: &absent,
|
|
173
|
+
declared: &[#{declared.map(&:inspect).join(', ')}],
|
|
174
|
+
}.render_args()));
|
|
175
|
+
}
|
|
176
|
+
RUST
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# Emits the UnknownArgument check: any key outside known_keys refuses
|
|
180
|
+
# before any field is built, using the shared refusal-wording template.
|
|
181
|
+
def emit_unknown_argument_check(command_name, known_keys, declared_names)
|
|
182
|
+
declared = declared_names.map(&:to_s)
|
|
183
|
+
<<~RUST
|
|
184
|
+
let unknown = v.unknown_keys(&[#{known_keys.map(&:inspect).join(', ')}]);
|
|
185
|
+
if !unknown.is_empty() {
|
|
186
|
+
let unknown: Vec<&str> = unknown.iter().map(|key| key.as_str()).collect();
|
|
187
|
+
return Err(crate::kernel::Refusal::UnknownArgument(crate::kernel::refusal_wording::UnknownArgumentUnknownArgsArgs {
|
|
188
|
+
command: #{command_name.inspect},
|
|
189
|
+
unknown: &unknown,
|
|
190
|
+
declared: &[#{declared.map(&:inspect).join(', ')}],
|
|
191
|
+
}.render_args()));
|
|
192
|
+
}
|
|
193
|
+
RUST
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# Builds one field's RHS. interleave_checks: true (every command/
|
|
197
|
+
# entity-command Args struct) builds each field's shape then runs its
|
|
198
|
+
# own invariant check immediately, before the next field starts, so an
|
|
199
|
+
# earlier argument's invariant failure wins over a later argument's
|
|
200
|
+
# shape failure — matching Ruby's per-argument dispatch order exactly.
|
|
201
|
+
def flat_field_rhs(struct_name, attr, key, value_objects_by_name, absent_argument_check)
|
|
202
|
+
scalar = effective_scalar_type(attr[:type])
|
|
203
|
+
if attr[:list] && attr[:optional]
|
|
204
|
+
# Option<Vec<T>> — absent key means None, unlike a required list
|
|
205
|
+
# argument, which defaults to an empty Vec below.
|
|
206
|
+
mapper = list_element_from_json_mapper(struct_name, key, attr, value_objects_by_name)
|
|
207
|
+
array_error = json_type_error(struct_name, key, "an array")
|
|
208
|
+
"match v.get(#{key.inspect}) { " \
|
|
209
|
+
"Some(x) => Some(x.as_array().ok_or_else(|| #{array_error})?.iter().map(#{mapper}).collect::<Result<Vec<_>, crate::kernel::Refusal>>()?), " \
|
|
210
|
+
"None => None, }".sub("match v.get(#{key.inspect}) { ", "match v.get(#{key.inspect}) { Some(crate::kernel::Json::Null) | None => None, ").sub(", None => None, }", " }")
|
|
211
|
+
elsif attr[:list]
|
|
212
|
+
# A list argument is an array: a lone scalar is refused, as Ruby's
|
|
213
|
+
# `Value.refuse_scalar_list` does; null and an absent key are the empty list.
|
|
214
|
+
mapper = list_element_from_json_mapper(struct_name, key, attr, value_objects_by_name)
|
|
215
|
+
shape_error = list_shape_error(struct_name, key, attr[:type], "x")
|
|
216
|
+
"match v.get(#{key.inspect}) { Some(crate::kernel::Json::Null) | None => Vec::new(), " \
|
|
217
|
+
"Some(x) => x.as_array().ok_or_else(|| #{shape_error})?.iter().map(#{mapper}).collect::<Result<Vec<_>, crate::kernel::Refusal>>()?, }"
|
|
218
|
+
elsif attr[:optional] && scalar
|
|
219
|
+
# A null value is the same absence as an omitted key for an
|
|
220
|
+
# optional argument (Ruby's nil passthrough).
|
|
221
|
+
"match v.get(#{key.inspect}) { Some(crate::kernel::Json::Null) | None => None, Some(x) => Some(#{scalar_from_json_value_expr(
|
|
222
|
+
struct_name, key, scalar, 'x'
|
|
223
|
+
)}) }"
|
|
224
|
+
elsif attr[:optional]
|
|
225
|
+
"match v.get(#{key.inspect}) { Some(crate::kernel::Json::Null) | None => None, Some(x) => Some(#{composite_from_json_expr(
|
|
226
|
+
attr, value_objects_by_name, 'x'
|
|
227
|
+
)}) }"
|
|
228
|
+
elsif scalar
|
|
229
|
+
scalar_from_json_expr(struct_name, key, scalar, default: attr[:default])
|
|
230
|
+
elsif absent_argument_check
|
|
231
|
+
required_composite_argument_expr(struct_name, key, attr, value_objects_by_name)
|
|
232
|
+
else
|
|
233
|
+
composite_from_json_expr(attr, value_objects_by_name, required_field_expr(struct_name, key, attr[:type]))
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
# Builds the unknown-then-absent argument checks shared by
|
|
238
|
+
# emit_argument_gates' two gate functions, matching dispatch order.
|
|
239
|
+
def unknown_and_absent_argument_checks(command_name, attributes, unknown_argument_allowlist, absent_argument_check)
|
|
240
|
+
check =
|
|
241
|
+
if unknown_argument_allowlist
|
|
242
|
+
known_keys = (attributes.map { |a| rust_field(a[:name]) } + unknown_argument_allowlist).uniq
|
|
243
|
+
emit_unknown_argument_check(command_name, known_keys, attributes.map { |a| a[:name] })
|
|
244
|
+
else
|
|
245
|
+
""
|
|
246
|
+
end
|
|
247
|
+
check += emit_absent_argument_check(command_name, attributes) if absent_argument_check
|
|
248
|
+
check
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
# Emits one gate function per declared dispatch step, called in order
|
|
252
|
+
# by kernel::ArgumentGates; reordering the steps in vocabulary.bluebook
|
|
253
|
+
# changes which refusal wins with no generator change.
|
|
254
|
+
def emit_argument_gates(struct_name, command_name, attributes, unknown_argument_allowlist)
|
|
255
|
+
unknown = if unknown_argument_allowlist
|
|
256
|
+
unknown_and_absent_argument_checks(command_name, attributes,
|
|
257
|
+
unknown_argument_allowlist, false)
|
|
258
|
+
else
|
|
259
|
+
""
|
|
260
|
+
end
|
|
261
|
+
absent = emit_absent_argument_check(command_name, attributes)
|
|
262
|
+
|
|
263
|
+
[
|
|
264
|
+
"impl #{struct_name} {",
|
|
265
|
+
argument_gate_fn("decode_arguments", emit_object_shape_check(struct_name)),
|
|
266
|
+
"",
|
|
267
|
+
argument_gate_fn("refuse_unknown_arguments", unknown),
|
|
268
|
+
"",
|
|
269
|
+
argument_gate_fn("refuse_absent_arguments", absent),
|
|
270
|
+
"}\n"
|
|
271
|
+
].join("\n")
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
# An empty body still gets its own function (the kernel calls every
|
|
275
|
+
# declared step for every command); its parameter is named _v so an
|
|
276
|
+
# empty body never warns.
|
|
277
|
+
def argument_gate_fn(name, body)
|
|
278
|
+
parameter = body.empty? ? "_v" : "v"
|
|
279
|
+
" pub fn #{name}(#{parameter}: &crate::kernel::Json) -> Result<(), crate::kernel::Refusal> {\n#{body} Ok(())\n }"
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
def emit_from_json_flat(struct_name, attributes, value_objects_by_name, unknown_argument_allowlist: nil, command_name: struct_name,
|
|
283
|
+
absent_argument_check: false, interleave_checks: false, aggregates_by_name: nil)
|
|
284
|
+
idents = attributes.map { |attr| rust_ident_field(attr[:name]) }
|
|
285
|
+
field_exprs = attributes.zip(idents).map do |attr, ident|
|
|
286
|
+
key = rust_field(attr[:name])
|
|
287
|
+
rhs = flat_field_rhs(struct_name, attr, key, value_objects_by_name, absent_argument_check)
|
|
288
|
+
|
|
289
|
+
if interleave_checks
|
|
290
|
+
checks = argument_check_lines(attr, ident, aggregates_by_name, value_objects_by_name)
|
|
291
|
+
([" let #{ident} = #{rhs};"] + checks).join("\n")
|
|
292
|
+
else
|
|
293
|
+
Exemplar.render("field_assignment", "tmpl_ident" => ident, "tmpl_rhs_placeholder()" => rhs)
|
|
294
|
+
end
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
unknown_check = unknown_and_absent_argument_checks(command_name, attributes, unknown_argument_allowlist,
|
|
298
|
+
absent_argument_check)
|
|
299
|
+
|
|
300
|
+
emit_from_json_skeleton(struct_name, field_exprs, unknown_check, shorthand_fields: interleave_checks ? idents : nil)
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
# Checked before any field is read: a composite value offered as
|
|
304
|
+
# anything but an object (or the single-field auto-wrap) would
|
|
305
|
+
# otherwise default every field silently when each declares a
|
|
306
|
+
# default:, instead of refusing.
|
|
307
|
+
def emit_object_shape_check(struct_name)
|
|
308
|
+
message = "#{struct_name} expects an object"
|
|
309
|
+
<<~RUST
|
|
310
|
+
if !matches!(v, crate::kernel::Json::Object(_)) {
|
|
311
|
+
return Err(crate::kernel::Refusal::TypeMismatch(format!("#{message}, got {}", v.inspect())));
|
|
312
|
+
}
|
|
313
|
+
RUST
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
# shorthand_fields: nil keeps field_exprs as complete `ident: rhs,`
|
|
317
|
+
# struct-literal lines. An Array (interleaved callers) instead folds
|
|
318
|
+
# already-terminated let+check blocks into the preamble, closing the
|
|
319
|
+
# struct literal over plain field-init shorthand.
|
|
320
|
+
def emit_from_json_skeleton(struct_name, field_exprs, unknown_check, shorthand_fields: nil)
|
|
321
|
+
preamble = emit_object_shape_check(struct_name) + unknown_check
|
|
322
|
+
field_block =
|
|
323
|
+
if shorthand_fields
|
|
324
|
+
preamble += field_exprs.map { |f| "#{f}\n" }.join
|
|
325
|
+
shorthand_fields.map { |f| " #{f}," }.join("\n")
|
|
326
|
+
else
|
|
327
|
+
field_exprs.map { |f| " #{f}" }.join("\n")
|
|
328
|
+
end
|
|
329
|
+
# Trailing "\n" — see emit_to_json_flat's own comment on why.
|
|
330
|
+
"#{Exemplar.render(
|
|
331
|
+
'from_json_flat',
|
|
332
|
+
'TmplFlatType2' => struct_name,
|
|
333
|
+
"let _tmpl_unknown_check_placeholder = ();\n Ok(Self {" => "#{preamble} Ok(Self {",
|
|
334
|
+
'tmpl_ident: tmpl_rhs_placeholder(),' => field_block
|
|
335
|
+
)}\n"
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
# optional: true Option-wraps every non-list field (aggregate records);
|
|
339
|
+
# extra_fields appends verbatim pairs (used for the lifecycle field,
|
|
340
|
+
# which is never Option-wrapped). sparse: true drops a
|
|
341
|
+
# (key, Json::Null) pair entirely, matching Ruby's payload: args for
|
|
342
|
+
# command-args serialization; sparse: false keeps every key, correct
|
|
343
|
+
# for a persisted record.
|
|
344
|
+
def emit_to_json_flat(struct_name, attributes, value_objects_by_name, optional: false, extra_fields: [], aggregate: nil,
|
|
345
|
+
sparse: false)
|
|
346
|
+
field_exprs = attributes.map do |attr|
|
|
347
|
+
ident = rust_ident_field(attr[:name])
|
|
348
|
+
key = rust_field(attr[:name])
|
|
349
|
+
scalar = effective_scalar_type(attr[:type])
|
|
350
|
+
# attr[:optional] is data-driven off the declaration, not a
|
|
351
|
+
# runtime emptiness check — an empty-but-required field (e.g. an
|
|
352
|
+
# unmatched ledger) must stay required.
|
|
353
|
+
#
|
|
354
|
+
# aggregate is passed only for a record's own to_json; the
|
|
355
|
+
# aggregate attribute's own optional: flag can differ from the
|
|
356
|
+
# command argument's, so list_attr_creation_optional? is the
|
|
357
|
+
# record-level equivalent check.
|
|
358
|
+
record_optional_list = attr[:list] && aggregate && list_attr_creation_optional?(aggregate, attr[:name],
|
|
359
|
+
value_objects_by_name)
|
|
360
|
+
field_optional = optional || attr[:optional]
|
|
361
|
+
# A record's own list field is Option-wrapped only per
|
|
362
|
+
# list_attr_creation_optional? — using attr[:optional] directly
|
|
363
|
+
# here would call as_ref on a plain Vec<T> field that was never
|
|
364
|
+
# Option-wrapped.
|
|
365
|
+
list_is_optional = aggregate ? record_optional_list : attr[:optional]
|
|
366
|
+
elem_to_json = list_element_to_json_expr(attr) if attr[:list]
|
|
367
|
+
value_expr =
|
|
368
|
+
if attr[:list] && list_is_optional
|
|
369
|
+
"self.#{ident}.as_ref().map(|v| crate::kernel::Json::Array(v.iter().map(|x| #{elem_to_json}).collect())).unwrap_or(crate::kernel::Json::Null)"
|
|
370
|
+
elsif attr[:list]
|
|
371
|
+
"crate::kernel::Json::Array(self.#{ident}.iter().map(|x| #{elem_to_json}).collect())"
|
|
372
|
+
elsif field_optional && scalar
|
|
373
|
+
"self.#{ident}.as_ref().map(|v| #{scalar_to_json_expr(scalar, 'v')}).unwrap_or(crate::kernel::Json::Null)"
|
|
374
|
+
elsif field_optional
|
|
375
|
+
"self.#{ident}.as_ref().map(|v| v.to_json()).unwrap_or(crate::kernel::Json::Null)"
|
|
376
|
+
elsif scalar
|
|
377
|
+
scalar_to_json_expr(scalar, "self.#{ident}")
|
|
378
|
+
else
|
|
379
|
+
"self.#{ident}.to_json()"
|
|
380
|
+
end
|
|
381
|
+
Exemplar.render("to_json_field", '"tmpl_field_name"' => key.inspect, "tmpl_json_value_placeholder()" => value_expr)
|
|
382
|
+
end
|
|
383
|
+
field_exprs += extra_fields.map do |key, expr|
|
|
384
|
+
Exemplar.render("to_json_field", '"tmpl_field_name"' => key.inspect, "tmpl_json_value_placeholder()" => expr)
|
|
385
|
+
end
|
|
386
|
+
field_block = field_exprs.map { |f| " #{f}" }.join("\n")
|
|
387
|
+
|
|
388
|
+
# Trailing "\n" is required because commands.rb's entity-command
|
|
389
|
+
# heredoc interpolates this return value directly.
|
|
390
|
+
rendered = if sparse
|
|
391
|
+
Exemplar.render("to_json_flat_sparse", "TmplFlatType3" => struct_name,
|
|
392
|
+
"tmpl_to_json_field_block_sparse()" => field_block)
|
|
393
|
+
else
|
|
394
|
+
Exemplar.render("to_json_flat", "TmplFlatType2" => struct_name,
|
|
395
|
+
"tmpl_to_json_field_block()" => field_block)
|
|
396
|
+
end
|
|
397
|
+
"#{rendered}\n"
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
# The inverse of emit_to_json_flat's record/entity to_json, not a
|
|
401
|
+
# third case of emit_from_json_flat: here an unset Option field's key
|
|
402
|
+
# is always present with value Json::Null (matching Ruby's JSON
|
|
403
|
+
# round-trip), so Some(&Json::Null) reads as absence everywhere
|
|
404
|
+
# emit_from_json_flat only ever checks for a missing key. Used by
|
|
405
|
+
# Store::from_seed (rust/host) to seed prior state directly instead
|
|
406
|
+
# of replaying command history.
|
|
407
|
+
def emit_from_json_state(struct_name, attributes, value_objects_by_name, optional: false, extra_fields: [], aggregate: nil)
|
|
408
|
+
field_exprs = attributes.map do |attr|
|
|
409
|
+
ident = rust_ident_field(attr[:name])
|
|
410
|
+
key = rust_field(attr[:name])
|
|
411
|
+
scalar = effective_scalar_type(attr[:type])
|
|
412
|
+
record_optional_list = attr[:list] && aggregate && list_attr_creation_optional?(aggregate, attr[:name],
|
|
413
|
+
value_objects_by_name)
|
|
414
|
+
list_is_optional = aggregate ? record_optional_list : attr[:optional]
|
|
415
|
+
field_optional = optional || attr[:optional]
|
|
416
|
+
|
|
417
|
+
rhs =
|
|
418
|
+
if attr[:list] && list_is_optional
|
|
419
|
+
mapper = list_element_from_json_mapper(struct_name, key, attr, value_objects_by_name)
|
|
420
|
+
array_error = json_type_error(struct_name, key, "an array")
|
|
421
|
+
"match v.get(#{key.inspect}) { " \
|
|
422
|
+
"Some(&crate::kernel::Json::Null) | None => None, " \
|
|
423
|
+
"Some(x) => Some(x.as_array().ok_or_else(|| #{array_error})?.iter().map(#{mapper}).collect::<Result<Vec<_>, crate::kernel::Refusal>>()?), }"
|
|
424
|
+
elsif attr[:list]
|
|
425
|
+
mapper = list_element_from_json_mapper(struct_name, key, attr, value_objects_by_name)
|
|
426
|
+
"match v.get(#{key.inspect}).and_then(crate::kernel::Json::as_array) { " \
|
|
427
|
+
"Some(items) => items.iter().map(#{mapper}).collect::<Result<Vec<_>, crate::kernel::Refusal>>()?, " \
|
|
428
|
+
"None => Vec::new(), }"
|
|
429
|
+
elsif field_optional && scalar
|
|
430
|
+
"match v.get(#{key.inspect}) { " \
|
|
431
|
+
"Some(&crate::kernel::Json::Null) | None => None, " \
|
|
432
|
+
"Some(x) => Some(#{scalar_from_json_value_expr(struct_name, key, scalar, 'x')}), }"
|
|
433
|
+
elsif field_optional
|
|
434
|
+
"match v.get(#{key.inspect}) { " \
|
|
435
|
+
"Some(&crate::kernel::Json::Null) | None => None, " \
|
|
436
|
+
"Some(x) => Some(#{composite_from_json_expr(attr, value_objects_by_name, 'x')}), }"
|
|
437
|
+
elsif scalar
|
|
438
|
+
scalar_from_json_expr(struct_name, key, scalar, default: attr[:default])
|
|
439
|
+
else
|
|
440
|
+
composite_from_json_expr(attr, value_objects_by_name, "v.require(#{key.inspect}, #{struct_name.inspect})?")
|
|
441
|
+
end
|
|
442
|
+
Exemplar.render("field_assignment", "tmpl_ident" => ident, "tmpl_rhs_placeholder()" => rhs)
|
|
443
|
+
end
|
|
444
|
+
|
|
445
|
+
# extra_fields carries (key, serialize_expr[, deserialize_rhs])
|
|
446
|
+
# tuples; deserialize_rhs is required for anything that isn't the
|
|
447
|
+
# lifecycle field's always-String shape (e.g. a bool flag). Omitted,
|
|
448
|
+
# the String reader below still applies.
|
|
449
|
+
extra_field_exprs = extra_fields.map do |key, _serialize_expr, deserialize_rhs|
|
|
450
|
+
ident = rust_ident_field(key)
|
|
451
|
+
rhs = deserialize_rhs || "v.require(#{key.inspect}, #{struct_name.inspect})?.as_str()" \
|
|
452
|
+
".ok_or_else(|| #{json_type_error(struct_name, key, 'a string')})?.to_string()"
|
|
453
|
+
Exemplar.render("field_assignment", "tmpl_ident" => ident, "tmpl_rhs_placeholder()" => rhs)
|
|
454
|
+
end
|
|
455
|
+
|
|
456
|
+
emit_from_json_skeleton(struct_name, field_exprs + extra_field_exprs, "")
|
|
457
|
+
end
|
|
458
|
+
|
|
459
|
+
# A single-field closed set only; emit_value_object's multi-attribute
|
|
460
|
+
# branch generates neither Fielded nor invariant checking, so it gets
|
|
461
|
+
# no JSON codec either. The admitted-member wording is resolved here
|
|
462
|
+
# at codegen time since every member is already known statically.
|
|
463
|
+
def emit_closed_set_codec(vo)
|
|
464
|
+
name = rust_ident(vo[:name])
|
|
465
|
+
sole_attribute = vo[:attributes].first
|
|
466
|
+
field_name = rust_field(sole_attribute[:name])
|
|
467
|
+
rows = vo[:members].map { |row| [closed_set_variant(row), row.first.last.to_s] }
|
|
468
|
+
|
|
469
|
+
# Both arm shapes need `TmplMemberA`/`tmpl_member_a` per row, so one
|
|
470
|
+
# subs hash serves both `render_each` calls below.
|
|
471
|
+
row_subs = rows.map { |variant, raw| { "TmplKind" => name, "TmplMemberA" => variant, '"tmpl_member_a"' => raw.inspect } }
|
|
472
|
+
|
|
473
|
+
type_name = vo[:name].to_s
|
|
474
|
+
# The member list goes over raw; the wording itself is
|
|
475
|
+
# InvariantViolationClosedSetMemberArgs::render_args's own
|
|
476
|
+
# RefusalSiteArgument row.
|
|
477
|
+
admitted = "[#{rows.map { |_variant, raw| raw.inspect }.join(', ')}]"
|
|
478
|
+
# Uses the same missing-key wording required_field_expr gives every
|
|
479
|
+
# other composite field, resolved here since the sole field's name
|
|
480
|
+
# and type are already known statically.
|
|
481
|
+
null_message = "#{type_name}.#{sole_attribute[:name]} expects #{sole_attribute[:type]}, got nil"
|
|
482
|
+
|
|
483
|
+
Exemplar.assemble(
|
|
484
|
+
"closed_set_codec",
|
|
485
|
+
{
|
|
486
|
+
"TmplKind" => name,
|
|
487
|
+
'"tmpl_field_name"' => field_name.inspect,
|
|
488
|
+
'"tmpl_closed_set_type"' => type_name.inspect,
|
|
489
|
+
'["tmpl_closed_set_member_a"]' => admitted,
|
|
490
|
+
'"tmpl_null_field_message"' => null_message.inspect
|
|
491
|
+
},
|
|
492
|
+
slots: {
|
|
493
|
+
"closed_set_codec:TO_JSON_ARM" => Exemplar.render_each("closed_set_codec:TO_JSON_ARM", row_subs),
|
|
494
|
+
"closed_set_codec:FROM_JSON_ARM" => Exemplar.render_each("closed_set_codec:FROM_JSON_ARM", row_subs)
|
|
495
|
+
}
|
|
496
|
+
)
|
|
497
|
+
end
|
|
498
|
+
|
|
499
|
+
# A multi-field closed set is a data table, not a tag enum, with
|
|
500
|
+
# &'static str fields — from_json can only select and clone one of
|
|
501
|
+
# the table's fixed rows, since nothing can construct a fresh
|
|
502
|
+
# &'static str from parsed JSON.
|
|
503
|
+
def emit_closed_set_table_codec(vo)
|
|
504
|
+
name = rust_ident(vo[:name])
|
|
505
|
+
const_name = screaming_snake(vo[:name])
|
|
506
|
+
|
|
507
|
+
to_json_fields = vo[:attributes].map do |attr|
|
|
508
|
+
key = rust_field(attr[:name])
|
|
509
|
+
ident = rust_ident_field(attr[:name])
|
|
510
|
+
scalar = effective_scalar_type(attr[:type])
|
|
511
|
+
value_expr =
|
|
512
|
+
case scalar
|
|
513
|
+
when "String" then "crate::kernel::Json::Str(self.#{ident}.to_string())"
|
|
514
|
+
when "Integer" then "crate::kernel::Json::int(self.#{ident})"
|
|
515
|
+
when "Float" then "crate::kernel::Json::Float(self.#{ident})"
|
|
516
|
+
when "TrueClass", "FalseClass" then "crate::kernel::Json::Bool(self.#{ident})"
|
|
517
|
+
end
|
|
518
|
+
Exemplar.render("to_json_field", '"tmpl_field_name"' => key.inspect, "tmpl_json_value_placeholder()" => value_expr)
|
|
519
|
+
end
|
|
520
|
+
to_json_fields_block = to_json_fields.map { |f| " #{f}" }.join("\n")
|
|
521
|
+
|
|
522
|
+
match_conditions = vo[:attributes].map do |attr|
|
|
523
|
+
key = rust_field(attr[:name])
|
|
524
|
+
ident = rust_ident_field(attr[:name])
|
|
525
|
+
accessor = SCALAR_JSON_ACCESSOR.fetch(effective_scalar_type(attr[:type]))
|
|
526
|
+
Exemplar.render(
|
|
527
|
+
"closed_set_table_from_json_condition",
|
|
528
|
+
'"tmpl_field_name"' => key.inspect,
|
|
529
|
+
"tmpl_accessor_fn" => "crate::kernel::Json::#{accessor}",
|
|
530
|
+
"tmpl_field" => ident
|
|
531
|
+
)
|
|
532
|
+
end
|
|
533
|
+
|
|
534
|
+
Exemplar.render(
|
|
535
|
+
"closed_set_table_codec",
|
|
536
|
+
"TmplTableRow" => name,
|
|
537
|
+
"tmpl_to_json_fields_block()" => to_json_fields_block,
|
|
538
|
+
"TMPL_TABLE" => const_name,
|
|
539
|
+
"tmpl_from_json_conditions()" => match_conditions.join(" && ")
|
|
540
|
+
)
|
|
541
|
+
end
|
|
542
|
+
|
|
543
|
+
# Whether every identified_by component resolves to a dotted path or
|
|
544
|
+
# a bare declared attribute; a third addressing-key shape has no JSON
|
|
545
|
+
# source under this CLI's step shape and is skipped by the caller.
|
|
546
|
+
def extract_id_supported?(aggregate)
|
|
547
|
+
aggregate[:identified_by].all? do |path|
|
|
548
|
+
head, *rest = path.split(".")
|
|
549
|
+
rest.any? || head
|
|
550
|
+
end
|
|
551
|
+
end
|
|
552
|
+
|
|
553
|
+
# Mirrors CommandInterpreter#hydrate's three-tier identity chain:
|
|
554
|
+
# identity_of, then identity_from(:id), then identity_from(reference
|
|
555
|
+
# key) — needed because a process manager's with: forwarding can
|
|
556
|
+
# supply only the reference argument, not the identity field itself.
|
|
557
|
+
def emit_extract_id(aggregate)
|
|
558
|
+
emit_extract_id_shaped(aggregate, method_name: "extract_id", coercion: "to_id_component")
|
|
559
|
+
end
|
|
560
|
+
|
|
561
|
+
# The same extract_id shape using to_id_component_lenient instead of
|
|
562
|
+
# the strict coercion; wired only into entity/nested-entity addressing,
|
|
563
|
+
# never a root aggregate's own hydrate.
|
|
564
|
+
def emit_extract_id_lenient(entity)
|
|
565
|
+
emit_extract_id_shaped(entity, method_name: "extract_id_lenient", coercion: "to_id_component_lenient")
|
|
566
|
+
end
|
|
567
|
+
|
|
568
|
+
def emit_extract_id_shaped(aggregate, method_name:, coercion:)
|
|
569
|
+
name = rust_ident(aggregate[:name])
|
|
570
|
+
reference_key = Hecks::Naming.snake(aggregate[:name])
|
|
571
|
+
|
|
572
|
+
# tmpl_id_coercion also goes into each tier1_subs entry, since the
|
|
573
|
+
# nested TIER1_LINE slot renders independently of the outer subs.
|
|
574
|
+
tier1_subs = aggregate[:identified_by].each_with_index.map do |path, i|
|
|
575
|
+
{ '"tmpl_path"' => path.inspect, "c0" => "c#{i}", "tmpl_id_coercion" => coercion }
|
|
576
|
+
end
|
|
577
|
+
tier1_join =
|
|
578
|
+
if aggregate[:identified_by].size == 1
|
|
579
|
+
"c0"
|
|
580
|
+
else
|
|
581
|
+
"vec![#{aggregate[:identified_by].size.times.map { |i| "c#{i}" }.join(', ')}].join(\":\")"
|
|
582
|
+
end
|
|
583
|
+
|
|
584
|
+
tried = (aggregate[:identified_by] + ["id", reference_key]).join(", ")
|
|
585
|
+
|
|
586
|
+
Exemplar.compose(
|
|
587
|
+
"extract_id",
|
|
588
|
+
{
|
|
589
|
+
"TmplExtractIdType" => name,
|
|
590
|
+
"tmpl_extract_id_name" => method_name,
|
|
591
|
+
"tmpl_id_coercion" => coercion,
|
|
592
|
+
'"tmpl_reference_key"' => reference_key.inspect,
|
|
593
|
+
"tmpl_tier1_join_placeholder()" => tier1_join,
|
|
594
|
+
'"tmpl_error_text"' => "#{name}: no identity found (tried #{tried})".inspect
|
|
595
|
+
},
|
|
596
|
+
field_id: "extract_id:TIER1_LINE",
|
|
597
|
+
field_subs_list: tier1_subs
|
|
598
|
+
)
|
|
599
|
+
end
|
|
600
|
+
|
|
601
|
+
# Ruby's own `wants` (element_of) is tier-1-only, unlike extract_id's
|
|
602
|
+
# three-tier chain, since every identity-path head is already known
|
|
603
|
+
# present by the time wants is computed. Joined with ", ", not ":".
|
|
604
|
+
def emit_extract_wants(entity)
|
|
605
|
+
name = rust_ident(entity[:name])
|
|
606
|
+
|
|
607
|
+
tier1_subs = entity[:identified_by].each_with_index.map { |path, i| { '"tmpl_path"' => path.inspect, "c0" => "c#{i}" } }
|
|
608
|
+
wants_join =
|
|
609
|
+
if entity[:identified_by].size == 1
|
|
610
|
+
"c0"
|
|
611
|
+
else
|
|
612
|
+
"vec![#{entity[:identified_by].size.times.map { |i| "c#{i}" }.join(', ')}].join(\", \")"
|
|
613
|
+
end
|
|
614
|
+
|
|
615
|
+
Exemplar.compose(
|
|
616
|
+
"extract_wants",
|
|
617
|
+
{
|
|
618
|
+
"TmplExtractWantsType" => name,
|
|
619
|
+
"tmpl_wants_join_placeholder()" => wants_join
|
|
620
|
+
},
|
|
621
|
+
field_id: "extract_wants:TIER1_LINE",
|
|
622
|
+
field_subs_list: tier1_subs
|
|
623
|
+
)
|
|
624
|
+
end
|
|
625
|
+
|
|
626
|
+
# An entity element's own identity read off an already-constructed
|
|
627
|
+
# Rust value rather than raw JSON, using the same dotted-path,
|
|
628
|
+
# join-with-":" shape extract_id produces, so dispatch_entity's
|
|
629
|
+
# matches closure can compare as plain strings.
|
|
630
|
+
def emit_self_identity(entity)
|
|
631
|
+
name = rust_ident(entity[:name])
|
|
632
|
+
components = entity[:identified_by].map do |path|
|
|
633
|
+
head, *rest = path.split(".")
|
|
634
|
+
(["self.#{rust_ident_field(head)}"] + rest.map { |seg| ".#{rust_ident_field(seg)}" }).join + ".to_string()"
|
|
635
|
+
end
|
|
636
|
+
body = components.size == 1 ? components.first : "vec![#{components.join(', ')}].join(\":\")"
|
|
637
|
+
|
|
638
|
+
Exemplar.render("self_identity", "TmplSelfIdentityType" => name, "tmpl_identity_body_placeholder()" => body)
|
|
639
|
+
end
|
|
640
|
+
end
|
|
641
|
+
end
|