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
|
@@ -13,8 +13,10 @@ module Hecks
|
|
|
13
13
|
verify_world_defaults!
|
|
14
14
|
verify_singleton_port_answers!
|
|
15
15
|
refuse_cross_package_bluebook_merge!
|
|
16
|
+
refuse_reserved_chapter_names!
|
|
16
17
|
refuse_membership_without_identity!
|
|
17
18
|
refuse_unresolved_port_operations!
|
|
19
|
+
refuse_unanswerable_queries!
|
|
18
20
|
|
|
19
21
|
@hecksagons.each_value do |hexagon|
|
|
20
22
|
refuse_ungoverned_roles!(hexagon)
|
|
@@ -95,6 +97,13 @@ module Hecks
|
|
|
95
97
|
# each bind above; a singleton port is never bound to an aggregate at all.
|
|
96
98
|
PER_AGGREGATE_PORTS = %w[persistence projection loading].freeze
|
|
97
99
|
|
|
100
|
+
# The directory holding the gem's own Hecks chapter, the only place a chapter named
|
|
101
|
+
# `Hecks` may be declared from.
|
|
102
|
+
GEM_CHAPTER_DIR = File.expand_path("../../hecks", __dir__).freeze
|
|
103
|
+
|
|
104
|
+
# The chapter names only the gem itself may declare (ADR 0080, section 9).
|
|
105
|
+
RESERVED_CHAPTER_NAMES = %w[Hecks].freeze
|
|
106
|
+
|
|
98
107
|
# Checks every singleton port with exactly one wired adapter against its own
|
|
99
108
|
# declared `answers` methods.
|
|
100
109
|
def verify_singleton_port_answers!
|
|
@@ -180,14 +189,14 @@ module Hecks
|
|
|
180
189
|
end
|
|
181
190
|
end
|
|
182
191
|
|
|
183
|
-
# `uses_framework`
|
|
184
|
-
# hecksagon that is its ACL (Governance/Identity/Privacy already do).
|
|
192
|
+
# `uses_framework` and `attaches` load a bounded context; the consumer must declare the
|
|
193
|
+
# sibling hecksagon that is its ACL (Governance/Identity/Privacy already do).
|
|
185
194
|
def refuse_unwired_framework_members!(hexagon)
|
|
186
|
-
|
|
195
|
+
hexagon.member_chapters.each do |member|
|
|
187
196
|
next if hecksagon(member)
|
|
188
197
|
|
|
189
198
|
raise WiringError,
|
|
190
|
-
"#{hexagon.domain} attaches
|
|
199
|
+
"#{hexagon.domain} attaches #{member.inspect} " \
|
|
191
200
|
"(bounded context) but never declared Hecks.hecksagon " \
|
|
192
201
|
"#{member.inspect} — put that sibling (and any `translates` " \
|
|
193
202
|
"ACL) in context_map.hecksagon; same-name blocks merge, " \
|
|
@@ -229,6 +238,130 @@ module Hecks
|
|
|
229
238
|
end
|
|
230
239
|
end
|
|
231
240
|
|
|
241
|
+
# A query has exactly one answer path. It is derivable when the bluebook says which of the
|
|
242
|
+
# aggregate's own records it wants (a where, order_by, limit or offset, or no arguments at
|
|
243
|
+
# all: the plain list) and returns no value object of its own; it is bound when it
|
|
244
|
+
# `returns` a value object and the hecksagon names the port whose adapter answers it. None
|
|
245
|
+
# of the two is "no answer path"; both is "two answer paths". A bound query also needs an
|
|
246
|
+
# adapter behind its port that implements it, found here so it fails at boot rather than
|
|
247
|
+
# on the first ask.
|
|
248
|
+
def refuse_unanswerable_queries!
|
|
249
|
+
@bluebooks.each_value do |chapter|
|
|
250
|
+
chapter.aggregates.each do |aggregate|
|
|
251
|
+
refuse_misbound_queries!(chapter, aggregate)
|
|
252
|
+
|
|
253
|
+
aggregate.queries.each { |query| refuse_answer_paths!(chapter, aggregate, query) }
|
|
254
|
+
aggregate.entities.each do |entity|
|
|
255
|
+
entity.queries.each { |query| refuse_entity_answer_path!(chapter, aggregate, entity, query) }
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
# Counts the query's answer paths (the bindings, plus the derived answer when it exists)
|
|
262
|
+
# and refuses any count but one, then checks a bound query's shape and adapter.
|
|
263
|
+
def refuse_answer_paths!(chapter, aggregate, query)
|
|
264
|
+
where = "#{chapter.name}::#{aggregate.hecks_name}.#{query.hecks_name}"
|
|
265
|
+
ports = aggregate.query_bindings(query.hecks_name)
|
|
266
|
+
paths = ports.size + (derivable?(query) ? 1 : 0)
|
|
267
|
+
|
|
268
|
+
if paths > 1
|
|
269
|
+
raise WiringError, "#{where} has two answer paths: #{answer_path_names(query, ports)} — " \
|
|
270
|
+
"a query is answered from records or from outside, not both"
|
|
271
|
+
end
|
|
272
|
+
return check_bound_query!(chapter, aggregate, query, ports.first) if ports.any?
|
|
273
|
+
return if paths == 1
|
|
274
|
+
|
|
275
|
+
raise WiringError, no_answer_path_message(chapter, aggregate, query, where)
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
# A query that returns nothing and says something about which records it wants, or wants
|
|
279
|
+
# all of them. One that takes arguments but filters, orders and bounds nothing reads none
|
|
280
|
+
# of them, so it derives no answer.
|
|
281
|
+
def derivable?(query)
|
|
282
|
+
return false if query.returns
|
|
283
|
+
|
|
284
|
+
filters_records?(query) || query.attributes.empty?
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
def answer_path_names(query, ports)
|
|
288
|
+
names = ports.map { |port| "the #{port.name} port" }
|
|
289
|
+
names.unshift("its records") if derivable?(query)
|
|
290
|
+
names.join(" and ")
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
def no_answer_path_message(chapter, aggregate, query, where)
|
|
294
|
+
if query.returns
|
|
295
|
+
"#{where} has no answer path: it returns #{query.returns}, which only an adapter can " \
|
|
296
|
+
"answer, and no hecksagon port binds it. Bind it: `#{chapter.name}::#{aggregate.hecks_name}" \
|
|
297
|
+
".port \"Port\" do answers_query \"#{query.hecks_name}\" end`."
|
|
298
|
+
else
|
|
299
|
+
"#{where} has no answer path: it declares no where and returns nothing, and no hecksagon " \
|
|
300
|
+
"binds it — its arguments (#{query.attributes.map(&:name).join(', ')}) select nothing. " \
|
|
301
|
+
"Add a where, or declare what it returns and bind it in the hecksagon."
|
|
302
|
+
end
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
# Checks what a binding needs to be answerable: a returned value object the aggregate
|
|
306
|
+
# declares, and one adapter behind the port that implements the query's method.
|
|
307
|
+
def check_bound_query!(chapter, aggregate, query, port)
|
|
308
|
+
where = "#{chapter.name}::#{aggregate.hecks_name}.#{query.hecks_name}"
|
|
309
|
+
unless query.returns
|
|
310
|
+
raise WiringError, "#{where} is bound to the #{port.name} port but returns nothing — " \
|
|
311
|
+
"declare the value object its answer takes with `returns`"
|
|
312
|
+
end
|
|
313
|
+
if filters_records?(query)
|
|
314
|
+
raise WiringError, "#{where} has two answer paths: it is bound to the #{port.name} port but " \
|
|
315
|
+
"also declares where, order_by or limit over stored records"
|
|
316
|
+
end
|
|
317
|
+
if query.authorization
|
|
318
|
+
raise WiringError, "#{where} is bound to the #{port.name} port but declares authorize — " \
|
|
319
|
+
"an outside answer is never tenant-scoped or authorized, so drop the " \
|
|
320
|
+
"authorize or answer the query from records"
|
|
321
|
+
end
|
|
322
|
+
unless Value.value_object_for(aggregate, query.returns_name)
|
|
323
|
+
raise WiringError, "#{where} returns #{query.returns_name}, but #{aggregate.hecks_name} " \
|
|
324
|
+
"declares no such value object"
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
klass = AdapterLookup.adapter_class(self, port.name, asked: where)
|
|
328
|
+
AdapterLookup.check_answers!(klass, port.name, Naming.snake(query.hecks_name), query, asked: where)
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
# An entity's query reads the elements of its aggregate's list, and nothing outside can
|
|
332
|
+
# be bound to one, so declaring a value object to return leaves it no answer.
|
|
333
|
+
def refuse_entity_answer_path!(chapter, aggregate, entity, query)
|
|
334
|
+
where = "#{chapter.name}::#{aggregate.hecks_name}.#{entity.hecks_name}.#{query.hecks_name}"
|
|
335
|
+
if query.returns
|
|
336
|
+
raise WiringError, "#{where} has no answer path: it returns #{query.returns}, but " \
|
|
337
|
+
"only an aggregate's queries can be bound to a port"
|
|
338
|
+
end
|
|
339
|
+
return if derivable?(query)
|
|
340
|
+
|
|
341
|
+
raise WiringError, "#{where} has no answer path: it declares no where and returns nothing — its " \
|
|
342
|
+
"arguments (#{query.attributes.map(&:name).join(', ')}) select nothing"
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
# Refuses a binding that names a query its aggregate does not declare, or names one twice
|
|
346
|
+
# on a single port (the DSL already refuses that, so this guards a hand-built port).
|
|
347
|
+
def refuse_misbound_queries!(chapter, aggregate)
|
|
348
|
+
aggregate.ports.each do |port|
|
|
349
|
+
names = port.answered_queries.map(&:name)
|
|
350
|
+
names.each do |name|
|
|
351
|
+
where = "#{chapter.name}::#{aggregate.hecks_name}.#{name}"
|
|
352
|
+
unless aggregate.query(name)
|
|
353
|
+
raise WiringError, "the #{port.name} port binds #{where}, which the aggregate does not declare"
|
|
354
|
+
end
|
|
355
|
+
raise WiringError, "#{where} has two answer paths: the #{port.name} port twice" if names.count(name) > 1
|
|
356
|
+
end
|
|
357
|
+
end
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
# A query that says anything about which stored records it wants.
|
|
361
|
+
def filters_records?(query)
|
|
362
|
+
!(query.wheres.empty? && query.order_by.nil? && query.limit.nil? && query.offset.nil?)
|
|
363
|
+
end
|
|
364
|
+
|
|
232
365
|
def port_operation_declared?(chapter, verb)
|
|
233
366
|
aggregate_name, port_name, operation_name = verb.split(".", 3)
|
|
234
367
|
ports = chapter.aggregate(aggregate_name)&.ports || []
|
|
@@ -263,6 +396,32 @@ module Hecks
|
|
|
263
396
|
bluebook_ir.aggregates.flat_map { |aggregate| aggregate.commands + aggregate.entities.flat_map(&:commands) }
|
|
264
397
|
end
|
|
265
398
|
|
|
399
|
+
# Refuses a chapter whose name is reserved unless every file that declared it sits under
|
|
400
|
+
# the gem's own chapter directory. A rename is the only fix, so the message says so.
|
|
401
|
+
#
|
|
402
|
+
# @raise [WiringError] naming the reserved word and the offending file(s)
|
|
403
|
+
def refuse_reserved_chapter_names!
|
|
404
|
+
RESERVED_CHAPTER_NAMES.each do |reserved|
|
|
405
|
+
next unless @bluebooks.key?(reserved)
|
|
406
|
+
|
|
407
|
+
sources = Array(@bluebook_sources[reserved]).compact
|
|
408
|
+
foreign = sources.reject { |path| gem_chapter_source?(path) }
|
|
409
|
+
next if foreign.empty? && !sources.empty?
|
|
410
|
+
|
|
411
|
+
where = foreign.empty? ? "" : " (declared in #{foreign.join(', ')})"
|
|
412
|
+
raise WiringError,
|
|
413
|
+
"a chapter named #{reserved.inspect} is refused: #{reserved.inspect} is a reserved " \
|
|
414
|
+
"word, the name of the gem's own chapter, and only the gem may declare it#{where}. " \
|
|
415
|
+
"Rename the chapter (for example to your app's name) in its " \
|
|
416
|
+
"`Hecks.bluebook #{reserved.inspect}` line and in its hecksagon."
|
|
417
|
+
end
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
# Whether `path` is a file of the gem's own chapter directory.
|
|
421
|
+
def gem_chapter_source?(path)
|
|
422
|
+
File.expand_path(path.to_s).start_with?("#{GEM_CHAPTER_DIR}/")
|
|
423
|
+
end
|
|
424
|
+
|
|
266
425
|
# Two packages can share a chapter name by coincidence (found live: a stale
|
|
267
426
|
# vendor/hecksagain fork of Governance/Identity/Deploy, still on 4 apps' load
|
|
268
427
|
# paths). Intentional accumulation (several files declaring the same chapter on
|
|
@@ -17,6 +17,9 @@ module Hecks
|
|
|
17
17
|
include SagaPersistence
|
|
18
18
|
include WorldDefaults
|
|
19
19
|
|
|
20
|
+
# The thread-local key under which open reaction collections are kept.
|
|
21
|
+
REACTION_SINKS = :hecks_reaction_sinks
|
|
22
|
+
|
|
20
23
|
attr_reader :root, :bluebooks, :hecksagons, :ports, :adapters, :worlds, :event_log,
|
|
21
24
|
:reaction_log, :saga_log, :saga_instances, :translations, :saga_mutex,
|
|
22
25
|
:saga_dispatch_log, :policy_dispatch_log, :bluebook_sources,
|
|
@@ -37,6 +40,7 @@ module Hecks
|
|
|
37
40
|
@translations = []
|
|
38
41
|
@event_log = []
|
|
39
42
|
@reaction_log = []
|
|
43
|
+
@reaction_events = {}.compare_by_identity
|
|
40
44
|
@saga_log = []
|
|
41
45
|
# Recorded at hecksagon-build time (AggregateDoor#mark_sensitive); Loader.boot's
|
|
42
46
|
# post-dispatch step turns each entry into a real Privacy::Marking.Mark, idempotently.
|
|
@@ -102,6 +106,17 @@ module Hecks
|
|
|
102
106
|
# @return [Bluebook::Chapter] `item`, unchanged
|
|
103
107
|
def add_bluebook(item) = @bluebooks[item.name] = item
|
|
104
108
|
|
|
109
|
+
# Drops a chapter and the open builder and source record that accumulated it, so the
|
|
110
|
+
# chapter can be loaded again from nothing.
|
|
111
|
+
#
|
|
112
|
+
# @param name [String, Symbol] the chapter name to forget
|
|
113
|
+
# @return [void]
|
|
114
|
+
def forget_chapter(name)
|
|
115
|
+
@bluebooks.delete(name.to_s)
|
|
116
|
+
@bluebook_builders.delete(name.to_s)
|
|
117
|
+
@bluebook_sources.delete(name.to_s)
|
|
118
|
+
end
|
|
119
|
+
|
|
105
120
|
# Tracks which .bluebook file(s) contributed to a chapter name (a boot-time loading
|
|
106
121
|
# fact, never Rust-mirrored) so refuse_cross_package_bluebook_merge! can catch two
|
|
107
122
|
# unrelated packages accumulating into the same name by coincidence.
|
|
@@ -233,7 +248,7 @@ module Hecks
|
|
|
233
248
|
# @return [Bluebook::Chapter, nil] the chapter that answers `domain`'s role
|
|
234
249
|
# checks, or nil if none does
|
|
235
250
|
def authorization_provider_for(domain)
|
|
236
|
-
names = [domain.to_s, *Array(hecksagon(domain)&.
|
|
251
|
+
names = [domain.to_s, *Array(hecksagon(domain)&.member_chapters)]
|
|
237
252
|
names.filter_map { |name| bluebook(name) }
|
|
238
253
|
.find { |chapter| chapter.provides?(Bluebook::Capabilities::AUTHORIZATION) }
|
|
239
254
|
end
|
|
@@ -254,7 +269,7 @@ module Hecks
|
|
|
254
269
|
# @return [Bluebook::Chapter, nil] the chapter that answers `domain`'s identity
|
|
255
270
|
# questions, or nil if none does
|
|
256
271
|
def identity_provider_for(domain)
|
|
257
|
-
names = [domain.to_s, *Array(hecksagon(domain)&.
|
|
272
|
+
names = [domain.to_s, *Array(hecksagon(domain)&.member_chapters)]
|
|
258
273
|
attached = names.filter_map { |name| bluebook(name) }
|
|
259
274
|
.find { |chapter| chapter.provides?(Bluebook::Capabilities::IDENTITY) }
|
|
260
275
|
return attached if attached
|
|
@@ -350,7 +365,7 @@ module Hecks
|
|
|
350
365
|
def vendored_provider_for(domain, capability)
|
|
351
366
|
hexagon = hecksagon(domain)
|
|
352
367
|
vendored = Array(hexagon&.vendored_bluebooks).map { |name| Naming.pascal(name) }
|
|
353
|
-
names = [domain.to_s, *Array(hexagon&.
|
|
368
|
+
names = [domain.to_s, *Array(hexagon&.member_chapters), *vendored]
|
|
354
369
|
attached = names.filter_map { |name| bluebook(name) }
|
|
355
370
|
.find { |chapter| chapter.provides?(capability) }
|
|
356
371
|
return attached if attached
|
|
@@ -373,6 +388,43 @@ module Hecks
|
|
|
373
388
|
@repositories[[domain.to_s, aggregate.hecks_name]] ||= Ports::Persistence.repository(self, domain, aggregate)
|
|
374
389
|
end
|
|
375
390
|
|
|
391
|
+
# Records one policy reaction on the shared log, and on every collection open on this
|
|
392
|
+
# thread (see `collecting_reactions`).
|
|
393
|
+
#
|
|
394
|
+
# The event that triggered a reaction is kept beside the log, not in the record: the record
|
|
395
|
+
# is the byte-for-byte shape the Rust kernel also writes.
|
|
396
|
+
#
|
|
397
|
+
# @param record [Hash{Symbol => Object}] the reaction's outcome
|
|
398
|
+
# @param event [String, Integer, nil] identity of the event instance the reaction answered
|
|
399
|
+
# @return [void]
|
|
400
|
+
# rubocop:disable-next Hecks/ThreadSharedIvarMutation
|
|
401
|
+
def log_reaction(record, event: nil)
|
|
402
|
+
@reaction_log << record
|
|
403
|
+
@reaction_events[record] = event if event
|
|
404
|
+
Array(Thread.current[REACTION_SINKS]).each { |sink| sink << record }
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
# The identity of the event instance a logged reaction answered.
|
|
408
|
+
#
|
|
409
|
+
# @param record [Hash{Symbol => Object}] a record `log_reaction` was given
|
|
410
|
+
# @return [String, Integer, nil] nil for a record logged without its event
|
|
411
|
+
def reaction_event(record) = @reaction_events[record]
|
|
412
|
+
|
|
413
|
+
# Collects the reactions logged on this thread while the block runs, so one dispatch
|
|
414
|
+
# reads exactly its own even when other threads dispatch on the same registry. Nested
|
|
415
|
+
# dispatches (a reaction that dispatches) each collect, and the outer one sees theirs too.
|
|
416
|
+
#
|
|
417
|
+
# @yield [Array<Hash>] the collection, filled as reactions are logged
|
|
418
|
+
# @return [Object] what the block returns
|
|
419
|
+
def collecting_reactions
|
|
420
|
+
sinks = (Thread.current[REACTION_SINKS] ||= [])
|
|
421
|
+
sink = []
|
|
422
|
+
sinks << sink
|
|
423
|
+
yield sink
|
|
424
|
+
ensure
|
|
425
|
+
sinks&.delete_if { |candidate| candidate.equal?(sink) }
|
|
426
|
+
end
|
|
427
|
+
|
|
376
428
|
# Clears everything a dispatch produced (logs, saga instances, repositories); leaves
|
|
377
429
|
# what booting the files produced (bluebooks, hecksagons, ports, adapters, worlds,
|
|
378
430
|
# resolved eras) untouched.
|
|
@@ -386,6 +438,7 @@ module Hecks
|
|
|
386
438
|
def reset_runtime_state!
|
|
387
439
|
@event_log.clear
|
|
388
440
|
@reaction_log.clear
|
|
441
|
+
@reaction_events.clear
|
|
389
442
|
@saga_log.clear
|
|
390
443
|
@saga_dispatch_log.clear
|
|
391
444
|
@policy_dispatch_log.clear
|
|
@@ -469,6 +522,7 @@ module Hecks
|
|
|
469
522
|
subscriptions: (base.subscriptions + overlay.subscriptions).uniq,
|
|
470
523
|
framework_members: (base.framework_members + overlay.framework_members).uniq,
|
|
471
524
|
vendored_bluebooks: (base.vendored_bluebooks + overlay.vendored_bluebooks).uniq,
|
|
525
|
+
attached_chapters: (base.attached_chapters + overlay.attached_chapters).uniq,
|
|
472
526
|
bounded: base.bounded? || overlay.bounded?,
|
|
473
527
|
translates: (base.translates + overlay.translates).uniq
|
|
474
528
|
)
|
|
@@ -16,7 +16,22 @@ module Hecks
|
|
|
16
16
|
# already Lambda-backed. Writes cannot: givens and constraints must run in Rust,
|
|
17
17
|
# not be pre-checked here against incomplete local state.
|
|
18
18
|
class RemoteDispatcher
|
|
19
|
-
Result = Struct.new(:verb, :instance, :events,
|
|
19
|
+
Result = Struct.new(:verb, :instance, :events, :refused_reactions, :blocking_reactions,
|
|
20
|
+
:reaction_defects, keyword_init: true) do
|
|
21
|
+
# Lists the policy reactions this dispatch caused that the routed runtime refused.
|
|
22
|
+
#
|
|
23
|
+
# @return [Array<Hash{Symbol => Object}>] one `{ policy:, trigger:, reason: }` per refused
|
|
24
|
+
# reaction, oldest first; empty when every reaction was delivered
|
|
25
|
+
def refused_reactions = self[:refused_reactions] || []
|
|
26
|
+
|
|
27
|
+
# Lists the refused reactions that block the run, as opposed to a benign non-match.
|
|
28
|
+
#
|
|
29
|
+
# @return [Array<Hash{Symbol => Object}>] the subset of `refused_reactions` that blocks
|
|
30
|
+
def blocking_reactions = self[:blocking_reactions] || []
|
|
31
|
+
|
|
32
|
+
# @return [Array<Hash{Symbol => Object}>] the reactions the host reported as crashed
|
|
33
|
+
def reaction_defects = self[:reaction_defects] || []
|
|
34
|
+
|
|
20
35
|
# The settled record's identity.
|
|
21
36
|
#
|
|
22
37
|
# @return [String]
|
|
@@ -96,7 +111,10 @@ module Hecks
|
|
|
96
111
|
instance = Instance.new(aggregate: aggregate, id: mutation["id"],
|
|
97
112
|
state: JSON.parse(JSON.generate(mutation["state"]), symbolize_names: true))
|
|
98
113
|
|
|
99
|
-
Result.new(verb: verb, instance: instance, events: step_events(response)
|
|
114
|
+
Result.new(verb: verb, instance: instance, events: step_events(response),
|
|
115
|
+
refused_reactions: refused_reactions_of(response),
|
|
116
|
+
blocking_reactions: ReactionOutcome.blocking(step_reactions(response)),
|
|
117
|
+
reaction_defects: ReactionOutcome.defects(step_reactions(response)))
|
|
100
118
|
end
|
|
101
119
|
|
|
102
120
|
# Delegates to the local `Dispatcher`; see `Dispatcher#query`.
|
|
@@ -116,6 +134,17 @@ module Hecks
|
|
|
116
134
|
|
|
117
135
|
private
|
|
118
136
|
|
|
137
|
+
# The newest step's reactions the remote runtime refused, from its `reactions_per_step`
|
|
138
|
+
# log (the whole-run `reactions` would also carry the replayed history's).
|
|
139
|
+
def refused_reactions_of(response)
|
|
140
|
+
step_reactions(response)
|
|
141
|
+
.select { |entry| entry["delivered"] == false }
|
|
142
|
+
.map { |entry| { policy: entry["policy"], trigger: entry["trigger"], reason: entry["reason"] } }
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Every reaction the newest step caused, delivered or not.
|
|
146
|
+
def step_reactions(response) = Array(response.fetch("reactions_per_step", []).last)
|
|
147
|
+
|
|
119
148
|
def step_events(response)
|
|
120
149
|
response.fetch("events", []).map { |e| build_event(e) }
|
|
121
150
|
end
|
|
@@ -12,7 +12,7 @@ module Hecks
|
|
|
12
12
|
# as `Value.for`, `Value.build`, and so on.
|
|
13
13
|
module Coercion
|
|
14
14
|
# The complete set of attribute value shapes. Mirrored by hand into a
|
|
15
|
-
# generated Rust enum (
|
|
15
|
+
# generated Rust enum (hecks project_kernel_capabilities) — adding a shape
|
|
16
16
|
# here without a matching Rust file leaves the kernel unaware of it.
|
|
17
17
|
SHAPES = %i[scalar list optional composite].freeze
|
|
18
18
|
|
|
@@ -309,6 +309,7 @@ module Hecks
|
|
|
309
309
|
check_required_fields(value_object, fields)
|
|
310
310
|
admit_member(value_object, fields)
|
|
311
311
|
check_admitted(value_object, fields)
|
|
312
|
+
check_list_shapes(value_object, fields)
|
|
312
313
|
check_numeric_fields(value_object, fields)
|
|
313
314
|
check_scalar_shapes(value_object, fields)
|
|
314
315
|
check_patterns(value_object, fields)
|
|
@@ -453,6 +454,26 @@ module Hecks
|
|
|
453
454
|
known_by: known_by(attribute))
|
|
454
455
|
end
|
|
455
456
|
|
|
457
|
+
# A list argument is an Array whatever its element type, so a lone scalar offered for
|
|
458
|
+
# it is refused; nil stays legitimate, as for any optional argument. The single-element
|
|
459
|
+
# form belongs to the `append`/`remove` effects, which take one element and are
|
|
460
|
+
# coerced against the aggregate's own list, never through this gate.
|
|
461
|
+
#
|
|
462
|
+
# @param command [#hecks_name] what `attribute` is declared on, named in a refusal
|
|
463
|
+
# @param attribute [Bluebook::Attribute] the attribute to check; a no-op unless list-typed
|
|
464
|
+
# (a `has_many` reference list has its own refusal)
|
|
465
|
+
# @param value [Object] the offered value
|
|
466
|
+
# @return [void]
|
|
467
|
+
# @raise [Runtime::TypeMismatch] if `value` is neither nil nor an Array
|
|
468
|
+
def refuse_scalar_list(command, attribute, value)
|
|
469
|
+
return unless attribute.list? && !attribute.reference? && !value.nil? && !value.is_a?(Array)
|
|
470
|
+
|
|
471
|
+
raise TypeMismatch, RefusalWording.render_site(
|
|
472
|
+
"TypeMismatch", "numeric_field", type: command.hecks_name, field: attribute.name,
|
|
473
|
+
expected: "list_of(#{attribute.type})", offered: Rendering.describe(value)
|
|
474
|
+
)
|
|
475
|
+
end
|
|
476
|
+
|
|
456
477
|
# "an object" for the Hash/Value shape; `Rendering.describe` otherwise —
|
|
457
478
|
# the same rendering every other TypeMismatch in this file uses.
|
|
458
479
|
#
|
|
@@ -518,7 +539,7 @@ module Hecks
|
|
|
518
539
|
|
|
519
540
|
# Converts a derived numeric identity string back before `check_numeric_fields` runs.
|
|
520
541
|
private def coerce_identifier(field, identifier)
|
|
521
|
-
return identifier unless identifier.is_a?(String) && NUMERIC.key?(field.type.to_s)
|
|
542
|
+
return identifier unless identifier.is_a?(String) && FieldChecks::NUMERIC.key?(field.type.to_s)
|
|
522
543
|
|
|
523
544
|
case field.type.to_s
|
|
524
545
|
when "Integer" then Integer(identifier)
|
|
@@ -537,139 +558,6 @@ module Hecks
|
|
|
537
558
|
def canonical_fields(fields)
|
|
538
559
|
JSON.generate(fields.sort_by { |name, _| name.to_s }.to_h)
|
|
539
560
|
end
|
|
540
|
-
|
|
541
|
-
# C3.8 boundary check — refuses a mistyped argument before it breaks a predicate.
|
|
542
|
-
private def check_bare_primitive(owner, attribute, value)
|
|
543
|
-
type = attribute.type.to_s
|
|
544
|
-
expected = NUMERIC[type]
|
|
545
|
-
mistyped = if expected
|
|
546
|
-
!value.is_a?(expected)
|
|
547
|
-
elsif NON_NUMERIC_SCALARS.include?(type)
|
|
548
|
-
COMPOSITE_SHAPES.any? { |shape| value.is_a?(shape) }
|
|
549
|
-
else
|
|
550
|
-
false
|
|
551
|
-
end
|
|
552
|
-
if mistyped
|
|
553
|
-
raise TypeMismatch,
|
|
554
|
-
RefusalWording.render_site("TypeMismatch", "numeric_field",
|
|
555
|
-
type: owner.hecks_name, field: attribute.name,
|
|
556
|
-
expected: type, offered: Rendering.describe(value))
|
|
557
|
-
end
|
|
558
|
-
|
|
559
|
-
check_numeric_bounds(owner.hecks_name, attribute.name, value)
|
|
560
|
-
end
|
|
561
|
-
|
|
562
|
-
# C3.3/C3.4 — value bounds enforced at every boundary: an Integer must fit
|
|
563
|
-
# signed 64 bits, a Float must be finite.
|
|
564
|
-
INT64_RANGE = (-(2**63))..((2**63) - 1)
|
|
565
|
-
|
|
566
|
-
private def check_numeric_bounds(type_name, field_name, given)
|
|
567
|
-
if given.is_a?(Integer) && !INT64_RANGE.cover?(given)
|
|
568
|
-
raise TypeMismatch,
|
|
569
|
-
RefusalWording.render_site("TypeMismatch", "integer_range",
|
|
570
|
-
type: type_name, field: field_name, offered: Rendering.describe(given))
|
|
571
|
-
end
|
|
572
|
-
return unless given.is_a?(Float) && !given.finite?
|
|
573
|
-
|
|
574
|
-
raise TypeMismatch,
|
|
575
|
-
RefusalWording.render_site("TypeMismatch", "non_finite_field",
|
|
576
|
-
type: type_name, field: field_name, offered: Rendering.describe(given))
|
|
577
|
-
end
|
|
578
|
-
|
|
579
|
-
# A value object refuses an undeclared key the same way a command's own
|
|
580
|
-
# payload does, checked before any other field-content check.
|
|
581
|
-
private def check_unknown_fields(value_object, fields)
|
|
582
|
-
known = value_object.attributes.map { |attribute| attribute.name.to_sym }
|
|
583
|
-
unknown = (fields.keys.map(&:to_sym) - known).sort
|
|
584
|
-
return if unknown.empty?
|
|
585
|
-
|
|
586
|
-
declared = value_object.attributes.map(&:name)
|
|
587
|
-
raise UnknownArgument,
|
|
588
|
-
RefusalWording.render_site("UnknownArgument", "unknown_args",
|
|
589
|
-
command: value_object.hecks_name, unknown: unknown,
|
|
590
|
-
declared: declared)
|
|
591
|
-
end
|
|
592
|
-
|
|
593
|
-
# C3.7 — every non-optional, non-list field must arrive (or construction
|
|
594
|
-
# refuses); a `default:` has already been filled in by `apply_defaults`.
|
|
595
|
-
private def check_required_fields(value_object, fields)
|
|
596
|
-
value_object.attributes.each do |attribute|
|
|
597
|
-
next if attribute.optional? || attribute.list?
|
|
598
|
-
next unless fields[attribute.name].nil?
|
|
599
|
-
|
|
600
|
-
raise TypeMismatch,
|
|
601
|
-
RefusalWording.render_site("TypeMismatch", "numeric_field",
|
|
602
|
-
type: value_object.hecks_name, field: attribute.name,
|
|
603
|
-
expected: attribute.type, offered: "nil")
|
|
604
|
-
end
|
|
605
|
-
end
|
|
606
|
-
|
|
607
|
-
# Checked before invariants, because an invariant reading a mistyped field
|
|
608
|
-
# is exactly the thing that would otherwise explode.
|
|
609
|
-
NUMERIC = { "Integer" => Integer, "Float" => Numeric }.freeze
|
|
610
|
-
private def check_numeric_fields(value_object, fields)
|
|
611
|
-
value_object.attributes.each do |attribute|
|
|
612
|
-
expected = NUMERIC[attribute.type.to_s]
|
|
613
|
-
next unless expected
|
|
614
|
-
|
|
615
|
-
given = fields[attribute.name]
|
|
616
|
-
next if given.nil?
|
|
617
|
-
|
|
618
|
-
unless given.is_a?(expected)
|
|
619
|
-
raise TypeMismatch,
|
|
620
|
-
RefusalWording.render_site("TypeMismatch", "numeric_field",
|
|
621
|
-
type: value_object.hecks_name, field: attribute.name,
|
|
622
|
-
expected: attribute.type, offered: Rendering.describe(given))
|
|
623
|
-
end
|
|
624
|
-
|
|
625
|
-
# `is_a?(expected)` alone waves NaN/Infinity through — both are real
|
|
626
|
-
# Floats. `-0.0` is deliberately left unchecked: finite and legitimate.
|
|
627
|
-
check_numeric_bounds(value_object.hecks_name, attribute.name, given)
|
|
628
|
-
end
|
|
629
|
-
end
|
|
630
|
-
|
|
631
|
-
# A scalar field (String, or a boolean) must not arrive as a composite
|
|
632
|
-
# (Array/Hash) standing in for a leaf value. A String field additionally
|
|
633
|
-
# must not arrive as any other scalar, except inside `judge_bootstrapping?`.
|
|
634
|
-
COMPOSITE_SHAPES = [Array, ::Hash].freeze
|
|
635
|
-
NON_NUMERIC_SCALARS = %w[String TrueClass FalseClass].freeze
|
|
636
|
-
private def check_scalar_shapes(value_object, fields)
|
|
637
|
-
value_object.attributes.each do |attribute|
|
|
638
|
-
type = attribute.type.to_s
|
|
639
|
-
next unless NON_NUMERIC_SCALARS.include?(type)
|
|
640
|
-
|
|
641
|
-
given = fields[attribute.name]
|
|
642
|
-
next if given.nil?
|
|
643
|
-
|
|
644
|
-
composite = COMPOSITE_SHAPES.any? { |shape| given.is_a?(shape) }
|
|
645
|
-
non_string_scalar = type == "String" && !composite && !given.is_a?(String) && !judge_bootstrapping?
|
|
646
|
-
next unless composite || non_string_scalar
|
|
647
|
-
|
|
648
|
-
raise TypeMismatch,
|
|
649
|
-
RefusalWording.render_site("TypeMismatch", "numeric_field",
|
|
650
|
-
type: value_object.hecks_name, field: attribute.name,
|
|
651
|
-
expected: attribute.type, offered: Rendering.describe(given))
|
|
652
|
-
end
|
|
653
|
-
end
|
|
654
|
-
|
|
655
|
-
# A field declared with a pattern must match it, refused as a TypeMismatch
|
|
656
|
-
# rather than surfacing later as a broken predicate. The pattern itself is
|
|
657
|
-
# already vetted by PatternSubset when the bluebook is declared.
|
|
658
|
-
private def check_patterns(value_object, fields)
|
|
659
|
-
value_object.attributes.each do |attribute|
|
|
660
|
-
pattern = attribute.pattern
|
|
661
|
-
next unless pattern
|
|
662
|
-
|
|
663
|
-
given = fields[attribute.name]
|
|
664
|
-
next if given.nil?
|
|
665
|
-
next if given.is_a?(String) && Regexp.new(pattern).match?(given)
|
|
666
|
-
|
|
667
|
-
raise TypeMismatch,
|
|
668
|
-
RefusalWording.render_site("TypeMismatch", "pattern_mismatch",
|
|
669
|
-
type: value_object.hecks_name, field: attribute.name,
|
|
670
|
-
pattern: pattern, offered: Rendering.describe(given))
|
|
671
|
-
end
|
|
672
|
-
end
|
|
673
561
|
end
|
|
674
562
|
end
|
|
675
563
|
end
|