hecks 3.10.0 → 3.12.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/examples/pizzas/bluebook/pizzas.bluebook +29 -0
- data/examples/pizzas/bluebook/pizzas.hecksagon +7 -0
- data/exe/hecks +4 -4
- data/lib/hecks/adapters/driven/d1.rb +2 -10
- data/lib/hecks/adapters/driven/heki.adapter +1 -1
- data/lib/hecks/adapters/driven/heki.rb +2 -12
- data/lib/hecks/adapters/driven/lambda.adapter +1 -1
- data/lib/hecks/adapters/driven/lambda.rb +3 -18
- data/lib/hecks/adapters/driven/postgres.rb +4 -11
- data/lib/hecks/adapters/driven/sqlite.adapter +1 -1
- data/lib/hecks/adapters/driven/sqlite.rb +2 -10
- data/lib/hecks/adapters/driving/admission.rb +46 -0
- data/lib/hecks/adapters/driving/cli/nesting.rb +36 -0
- data/lib/hecks/adapters/driving/cli/shorthand.rb +88 -0
- data/lib/hecks/adapters/driving/cli.rb +127 -0
- data/lib/hecks/adapters/driving/cli_runner/answers.rb +160 -0
- data/lib/hecks/adapters/driving/cli_runner/resolution.rb +158 -0
- data/lib/hecks/adapters/driving/cli_runner/settling.rb +120 -0
- data/lib/hecks/adapters/driving/cli_runner/streaming.rb +93 -0
- data/lib/hecks/adapters/driving/cli_runner/suggestions.rb +47 -0
- data/lib/hecks/adapters/driving/cli_runner.rb +70 -0
- data/lib/hecks/adapters/driving/command_request.rb +121 -0
- data/lib/hecks/adapters/driving/handle.rb +214 -0
- data/lib/hecks/adapters/driving/json.rb +136 -0
- data/lib/hecks/adapters/driving/launcher_options.rb +267 -0
- data/lib/hecks/adapters/driving/mcp/properties.rb +49 -0
- data/lib/hecks/adapters/driving/mcp/tool_calls.rb +116 -0
- data/lib/hecks/adapters/driving/mcp/tool_definitions/auditing.rb +53 -0
- data/lib/hecks/adapters/driving/mcp/tool_definitions/calling.rb +71 -0
- data/lib/hecks/adapters/driving/mcp/tool_definitions/reading.rb +99 -0
- data/lib/hecks/adapters/driving/mcp/tool_definitions.rb +18 -0
- data/lib/hecks/adapters/driving/mcp.rb +159 -0
- data/lib/hecks/adapters/driving/mcp_guide_cache.rb +78 -0
- data/lib/hecks/adapters/driving/mcp_scope/argument_policy.rb +97 -0
- data/lib/hecks/adapters/driving/mcp_scope/env_problems.rb +69 -0
- data/lib/hecks/adapters/driving/mcp_scope/presenter.rb +83 -0
- data/lib/hecks/adapters/driving/mcp_scope.rb +213 -0
- data/lib/hecks/adapters/driving/newcomer.rb +50 -0
- data/lib/hecks/adapters/driving/ruby/aggregate_module.rb +171 -0
- data/lib/hecks/adapters/driving/ruby/chapter.rb +91 -0
- data/lib/hecks/adapters/driving/ruby.rb +83 -0
- data/lib/hecks/adapters/driving/usage_cache.rb +144 -0
- data/lib/hecks/{doors.rb → adapters/driving.rb} +10 -6
- data/lib/hecks/behaviors/expectations/boot.rb +1 -1
- data/lib/hecks/bench/ruby_runner.rb +1 -1
- data/lib/hecks/bluebook/ask_resolution.rb +130 -0
- data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +8 -2
- data/lib/hecks/bluebook/assembly/contracts/read_models.rb +1 -1
- data/lib/hecks/bluebook/assembly/contracts/rules.rb +4 -2
- data/lib/hecks/bluebook/assembly/contracts/structure.rb +3 -1
- data/lib/hecks/bluebook/assembly/marks.rb +4 -15
- data/lib/hecks/bluebook/assembly/scalars.rb +39 -0
- data/lib/hecks/bluebook/attribute.rb +1 -1
- data/lib/hecks/bluebook/behaviour/command.rb +13 -8
- data/lib/hecks/bluebook/behaviour/policy.rb +30 -0
- data/lib/hecks/bluebook/capabilities/attribute_values.rb +72 -0
- data/lib/hecks/bluebook/capabilities.rb +91 -9
- data/lib/hecks/bluebook/domain_port.rb +16 -4
- data/lib/hecks/bluebook/dsl/adapter_builder.rb +18 -6
- data/lib/hecks/bluebook/dsl/binding_proxy.rb +13 -0
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/computed_sources.rb +98 -0
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/fan_out_rows.rb +42 -0
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/projected_fields.rb +14 -2
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/provisions.rb +69 -3
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/with_projections.rb +17 -10
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +5 -0
- data/lib/hecks/bluebook/dsl/bootstrap_table.rb +2 -0
- data/lib/hecks/bluebook/dsl/command_builder/mutations.rb +8 -1
- data/lib/hecks/bluebook/dsl/command_builder/operands.rb +121 -0
- data/lib/hecks/bluebook/dsl/command_builder.rb +2 -0
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +10 -1
- data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +45 -2
- data/lib/hecks/bluebook/dsl/policy_builder.rb +47 -10
- data/lib/hecks/bluebook/dsl/rule_reference.rb +7 -4
- data/lib/hecks/bluebook/dsl/world_builder.rb +1 -1
- data/lib/hecks/bluebook/expression/ast_json/emitters.rb +6 -0
- data/lib/hecks/bluebook/expression/ast_json.rb +9 -6
- data/lib/hecks/bluebook/expression/ast_reader.rb +14 -9
- data/lib/hecks/bluebook/expression/projection.json +36 -0
- data/lib/hecks/bluebook/expression/resolver/operations.rb +31 -0
- data/lib/hecks/bluebook/expression/resolver/parsing.rb +38 -5
- data/lib/hecks/bluebook/expression/resolver/scanning.rb +22 -5
- data/lib/hecks/bluebook/expression/resolver/values.rb +10 -0
- data/lib/hecks/bluebook/expression/resolver.rb +20 -1
- data/lib/hecks/bluebook/hecksagon.rb +23 -3
- data/lib/hecks/bluebook/lifecycle.rb +22 -3
- data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +13 -1
- data/lib/hecks/bluebook/meta_validator/modes.rb +4 -1
- data/lib/hecks/bluebook/meta_validator/readings.rb +24 -1
- data/lib/hecks/bluebook/meta_validator/reconstruction/heads.rb +2 -1
- data/lib/hecks/bluebook/meta_validator/reconstruction.rb +6 -3
- data/lib/hecks/bluebook/meta_validator/shapes.rb +5 -0
- data/lib/hecks/bluebook/meta_validator/verdict_cache.rb +1 -1
- data/lib/hecks/bluebook/meta_validator.rb +12 -12
- data/lib/hecks/bluebook/model_check/ask_checks.rb +71 -0
- data/lib/hecks/bluebook/model_check/policy_checks.rb +2 -1
- data/lib/hecks/bluebook/model_check/reference_checks.rb +70 -0
- data/lib/hecks/bluebook/model_check.rb +8 -2
- data/lib/hecks/bluebook/policy.rb +4 -2
- data/lib/hecks/bluebook/smoke_test.rb +1 -1
- data/lib/hecks/bluebook/synthesizer.rb +2 -0
- data/lib/hecks/bluebook.rb +2 -0
- data/lib/hecks/cli/command_table.rb +1 -1
- data/lib/hecks/cli/console.rb +2 -1
- data/lib/hecks/cli/document.rb +1 -1
- data/lib/hecks/cli/launcher_words.rb +4 -4
- data/lib/hecks/cli/mcp.rb +4 -4
- data/lib/hecks/cli/project_cli/launcher_source.rb +2 -2
- data/lib/hecks/cli/project_cli/templates.rb +5 -5
- data/lib/hecks/cli/project_cli.rb +9 -6
- data/lib/hecks/cli/run.rb +2 -2
- data/lib/hecks/cli/smoke_http/checks.rb +4 -1
- data/lib/hecks/cli/smoke_http/transport.rb +3 -3
- data/lib/hecks/cli/smoke_http.rb +3 -0
- data/lib/hecks/codemod.rb +5 -2
- data/lib/hecks/corpus/routes.rb +7 -0
- data/lib/hecks/corpus/rust.rb +3 -2
- data/lib/hecks/deploy/bluebook/deploy.bluebook +34 -4
- data/lib/hecks/doc/reference/rendering.rb +6 -3
- data/lib/hecks/forms/app/commands.rb +8 -7
- data/lib/hecks/forms/app/queries.rb +2 -1
- data/lib/hecks/forms/app.rb +1 -1
- data/lib/hecks/forms/record_renderer.rb +1 -1
- data/lib/hecks/framework/bluebook/privacy.bluebook +11 -0
- data/lib/hecks/fuzzing/concurrent_dispatch.rb +4 -1
- data/lib/hecks/fuzzing/failure_triage.rb +2 -1
- data/lib/hecks/fuzzing/mutation/run.rb +11 -4
- data/lib/hecks/fuzzing/properties/catalog.rb +6 -4
- data/lib/hecks/fuzzing/properties/guaranteed_by_construction.rb +18 -11
- data/lib/hecks/fuzzing/properties/guards.rb +1 -1
- data/lib/hecks/fuzzing/self_consistency/saga_redelivery.rb +1 -1
- data/lib/hecks/fuzzing/self_consistency.rb +6 -2
- data/lib/hecks/fuzzing/shrinker.rb +3 -1
- data/lib/hecks/fuzzing/value_generator.rb +19 -5
- data/lib/hecks/gate/stages.yml +33 -7
- data/lib/hecks/grammar/evolve.rb +1 -1
- data/lib/hecks/grammar/expression_operators.json +372 -0
- data/lib/hecks/grammar/operators.rb +3 -1
- data/lib/hecks/hecks/adapters/codebase/corpus_tasks.rb +10 -10
- data/lib/hecks/hecks/adapters/codebase/inspection/rubycritic.rb +35 -0
- data/lib/hecks/hecks/adapters/codebase/inspection.rb +104 -0
- data/lib/hecks/hecks/adapters/codebase/publishing.rb +2 -1
- data/lib/hecks/hecks/adapters/codebase/release_facts.rb +12 -2
- data/lib/hecks/hecks/adapters/codebase/source_tree.rb +2 -1
- data/lib/hecks/hecks/adapters/codebase/test_runner.rb +1 -1
- data/lib/hecks/hecks/adapters/cost_explorer.rb +8 -5
- data/lib/hecks/hecks/adapters/deploy_toolchain/statuses.rb +2 -1
- data/lib/hecks/hecks/adapters/finding_github.rb +13 -6
- data/lib/hecks/hecks/adapters/first_admin.rb +2 -2
- data/lib/hecks/hecks/adapters/github_rulesets.rb +6 -3
- data/lib/hecks/hecks/adapters/in_process_boot.rb +1 -1
- data/lib/hecks/hecks/adapters/journal_store/compaction.rb +1 -1
- data/lib/hecks/hecks/adapters/journal_store/edge_audit.rb +10 -20
- data/lib/hecks/hecks/adapters/journal_store/held_domain.rb +28 -5
- data/lib/hecks/hecks/adapters/local_files.rb +2 -2
- data/lib/hecks/hecks/adapters/rehearsal/audit_report.rb +107 -0
- data/lib/hecks/hecks/adapters/rehearsal/census.rb +71 -0
- data/lib/hecks/hecks/adapters/rehearsal/declaration.rb +133 -0
- data/lib/hecks/hecks/adapters/rehearsal/environment.rb +83 -0
- data/lib/hecks/hecks/adapters/rehearsal/inquiry.rb +142 -0
- data/lib/hecks/hecks/adapters/rehearsal/memory_store.rb +96 -0
- data/lib/hecks/hecks/adapters/rehearsal/postgres_census.rb +100 -0
- data/lib/hecks/hecks/adapters/rehearsal/postgres_reading.rb +69 -0
- data/lib/hecks/hecks/adapters/rehearsal/postgres_store.rb +133 -0
- data/lib/hecks/hecks/adapters/rehearsal/rows.rb +41 -0
- data/lib/hecks/hecks/adapters/rehearsal/rust_audit.rb +83 -0
- data/lib/hecks/hecks/adapters/rehearsal/subject.rb +104 -0
- data/lib/hecks/hecks/adapters/rehearsal.rb +54 -0
- data/lib/hecks/hecks/adapters/rehearsal_store.adapter +7 -0
- data/lib/hecks/hecks/adapters/rehearsal_store.rb +81 -0
- data/lib/hecks/hecks/adapters/terminal.rb +13 -11
- data/lib/hecks/hecks/codebase.behaviors +13 -1
- data/lib/hecks/hecks/codebase.bluebook +239 -5
- data/lib/hecks/hecks/custodian.behaviors +24 -6
- data/lib/hecks/hecks/custodian.bluebook +6 -1227
- data/lib/hecks/hecks/deploy.behaviors +1 -1
- data/lib/hecks/hecks/deploy.world +1 -3
- data/lib/hecks/hecks/hecks.behaviors +1 -1
- data/lib/hecks/hecks/hecks.hecksagon +53 -18
- data/lib/hecks/hecks/hecks.world +13 -10
- data/lib/hecks/hecks/rehearsal.bluebook +477 -0
- data/lib/hecks/hecks/site.behaviors +1 -1
- data/lib/hecks/hecks/site.world +0 -1
- data/lib/hecks/hecks/tooling.bluebook +1238 -0
- data/lib/hecks/indifferent_key.rb +20 -0
- data/lib/hecks/language/adapter.bluebook +13 -3
- data/lib/hecks/language/bluebook/aggregate.bluebook +23 -0
- data/lib/hecks/language/bluebook/bluebook.behaviors +23 -0
- data/lib/hecks/language/bluebook/bluebook.bluebook +15 -0
- data/lib/hecks/language/bluebook/entity.bluebook +18 -0
- data/lib/hecks/language/bluebook/policy.behaviors +19 -0
- data/lib/hecks/language/bluebook/policy.bluebook +9 -1
- data/lib/hecks/language/bluebook/query.bluebook +1 -1
- data/lib/hecks/language/bluebook/vocabulary.bluebook +63 -4
- data/lib/hecks/language/hecksagon/domain_port.bluebook +1 -1
- data/lib/hecks/language/hecksagon/hecksagon.bluebook +22 -0
- data/lib/hecks/language/oidc.json +10 -0
- data/lib/hecks/language/port.bluebook +4 -4
- data/lib/hecks/literal.rb +12 -3
- data/lib/hecks/ports/key_vault.rb +7 -2
- data/lib/hecks/ports/persistence/plugins/era/era_check.rb +21 -10
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/boot.rb +4 -2
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +2 -1
- data/lib/hecks/ports/persistence/repository_factory.rb +6 -3
- data/lib/hecks/projection_files/expression_tables.rb +7 -2
- data/lib/hecks/projections/deploy/box/compose.rb +6 -4
- data/lib/hecks/projections/deploy/box/edge.rb +62 -0
- data/lib/hecks/projections/deploy/box/logging.rb +99 -0
- data/lib/hecks/projections/deploy/box/roll_scripts.rb +16 -8
- data/lib/hecks/projections/deploy/box/settings/cdn.rb +40 -0
- data/lib/hecks/projections/deploy/box/settings/logs.rb +40 -0
- data/lib/hecks/projections/deploy/box/settings/patterns.rb +11 -0
- data/lib/hecks/projections/deploy/box/settings/types.rb +16 -1
- data/lib/hecks/projections/deploy/box/settings.rb +6 -2
- data/lib/hecks/projections/deploy/box/stack_yaml.rb +5 -2
- data/lib/hecks/projections/deploy/box/templates/Makefile.tmpl +16 -0
- data/lib/hecks/projections/deploy/box/templates/box.yaml.tmpl +8 -4
- data/lib/hecks/projections/deploy/box/templates/deploy-box.sh.tmpl +32 -0
- data/lib/hecks/projections/deploy/box/templates/deploy-service.sh.tmpl +1 -1
- data/lib/hecks/projections/deploy/box/templates/edge.mk.tmpl +17 -0
- data/lib/hecks/projections/deploy/box/templates/edge.yaml.tmpl +65 -0
- data/lib/hecks/projections/deploy/box/templates/log-group.yaml.tmpl +12 -0
- data/lib/hecks/projections/deploy/box/templates/log-policy.yaml.tmpl +7 -0
- data/lib/hecks/projections/deploy/box/templates/render-compose-taskdef.sh.tmpl +5 -4
- data/lib/hecks/projections/deploy/box/templates/render-compose.sh.tmpl +5 -4
- data/lib/hecks/projections/deploy/box.rb +15 -3
- data/lib/hecks/projections/deploy/preview/preview.sh.erb +1 -1
- data/lib/hecks/projections/deploy/shared.rb +5 -2
- data/lib/hecks/projections/model/deviations.rb +2 -2
- data/lib/hecks/projections/model.rb +2 -2
- data/lib/hecks/projections/oidc.rb +13 -1
- data/lib/hecks/projections/shape.rb +5 -2
- data/lib/hecks/projections/site/cms_editor/attributes.rb +26 -6
- data/lib/hecks/projections/site/cms_editor/blocks.rb +157 -0
- data/lib/hecks/projections/site/cms_editor/checks.rb +14 -1
- data/lib/hecks/projections/site/cms_editor/clearing.rb +70 -0
- data/lib/hecks/projections/site/cms_editor/color.rb +124 -0
- data/lib/hecks/projections/site/cms_editor/composition.rb +42 -0
- data/lib/hecks/projections/site/cms_editor/destructive.rb +61 -0
- data/lib/hecks/projections/site/cms_editor/drafts.rb +102 -0
- data/lib/hecks/projections/site/cms_editor/hints.rb +74 -0
- data/lib/hecks/projections/site/cms_editor/icons.rb +86 -0
- data/lib/hecks/projections/site/cms_editor/key_kinds.rb +31 -0
- data/lib/hecks/projections/site/cms_editor/limits.rb +72 -0
- data/lib/hecks/projections/site/cms_editor/media.rb +1 -1
- data/lib/hecks/projections/site/cms_editor/moment.rb +34 -0
- data/lib/hecks/projections/site/cms_editor/palette.rb +115 -0
- data/lib/hecks/projections/site/cms_editor/pickers.rb +85 -0
- data/lib/hecks/projections/site/cms_editor/preview.rb +91 -0
- data/lib/hecks/projections/site/cms_editor/relations.rb +92 -0
- data/lib/hecks/projections/site/cms_editor/scale_checks.rb +66 -0
- data/lib/hecks/projections/site/cms_editor/scheduling.rb +150 -0
- data/lib/hecks/projections/site/cms_editor/schema.rb +96 -19
- data/lib/hecks/projections/site/cms_editor/setting.rb +52 -9
- data/lib/hecks/projections/site/cms_editor/targets.rb +86 -0
- data/lib/hecks/projections/site/cms_editor/templates/package.json.tmpl +18 -1
- data/lib/hecks/projections/site/cms_editor/templates/src/app.ts.tmpl +251 -57
- data/lib/hecks/projections/site/cms_editor/templates/src/assets.ts.tmpl +98 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/auth/membership.ts.tmpl +32 -24
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/app.css.tmpl +412 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/autosave.js.tmpl +144 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/blocks.js.tmpl +500 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/body_doc.js.tmpl +175 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/body_editor.js.tmpl +418 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/epoch.js.tmpl +61 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/forms.js.tmpl +210 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/lists.js.tmpl +40 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/main.js.tmpl +26 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/media_picker.js.tmpl +232 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/moments.js.tmpl +73 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/pickers.js.tmpl +152 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/preview.js.tmpl +98 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/shell.js.tmpl +140 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/toast.js.tmpl +66 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/choices.ts.tmpl +99 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/commands.ts.tmpl +78 -24
- data/lib/hecks/projections/site/cms_editor/templates/src/config.ts.tmpl +6 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/flash.ts.tmpl +52 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/host.ts.tmpl +19 -2
- data/lib/hecks/projections/site/cms_editor/templates/src/media/handler.ts.tmpl +31 -8
- data/lib/hecks/projections/site/cms_editor/templates/src/names.ts.tmpl +36 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/schema.ts.tmpl +131 -2
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/availability.ts.tmpl +15 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/block_list.js.tmpl +213 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/blocks.ts.tmpl +275 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/body_model.js.tmpl +8 -5
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/detail.ts.tmpl +88 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/dialogs.ts.tmpl +43 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/fields.ts.tmpl +229 -34
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/html.ts.tmpl +40 -81
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/icons.ts.tmpl +15 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/input.ts.tmpl +29 -12
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/links.ts.tmpl +100 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/moments.ts.tmpl +44 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/outline.ts.tmpl +53 -17
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/pages.ts.tmpl +178 -88
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/paging.ts.tmpl +82 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/pickers.ts.tmpl +110 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/preview.ts.tmpl +87 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/related.ts.tmpl +97 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/schedule.ts.tmpl +160 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/shell.ts.tmpl +211 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/table.ts.tmpl +151 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/theme.js.tmpl +16 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/values.ts.tmpl +69 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/words.ts.tmpl +97 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/world.ts.tmpl +35 -0
- data/lib/hecks/projections/site/cms_editor/templates/tsconfig.json.tmpl +2 -1
- data/lib/hecks/projections/site/cms_editor/theme.rb +29 -0
- data/lib/hecks/projections/site/cms_editor/theme_checks.rb +57 -0
- data/lib/hecks/projections/site/cms_editor.rb +49 -23
- data/lib/hecks/projections/site/role_probe.rb +2 -0
- data/lib/hecks/projections/site/site_root/workflow.rb +5 -0
- data/lib/hecks/projector/cli_audience.rb +22 -1
- data/lib/hecks/projector/cli_projector/listing.rb +1 -1
- data/lib/hecks/projector/cli_projector/option_specs.rb +1 -1
- data/lib/hecks/projector/cli_projector/usage.rb +15 -4
- data/lib/hecks/projector/docs_projector/closing.rb +1 -1
- data/lib/hecks/projector/docs_projector.rb +1 -1
- data/lib/hecks/projector/exporter/bindings.rb +40 -10
- data/lib/hecks/projector/exporter/capability_values.rb +55 -0
- data/lib/hecks/projector/narrate_projector/reactions.rb +1 -1
- data/lib/hecks/projector/target.rb +11 -0
- data/lib/hecks/projector.rb +32 -5
- data/lib/hecks/quality_control/adapters/github_checks.rb +12 -8
- data/lib/hecks/quality_control/adapters/github_issues.rb +8 -4
- data/lib/hecks/quality_control/cli/qa_open_pr.rb +2 -2
- data/lib/hecks/quality_control/cli/qa_sweep/checks/shrinking.rb +1 -1
- data/lib/hecks/quality_control/cli/qa_sweep/finding_report.rb +1 -1
- data/lib/hecks/quality_control/cli/qa_sweep.rb +1 -1
- data/lib/hecks/quality_control/quality_control.bluebook +6 -2
- data/lib/hecks/release/approval.rb +64 -0
- data/lib/hecks/router/{door.rb → aggregate_reader.rb} +5 -4
- data/lib/hecks/router/namespace_installer.rb +16 -16
- data/lib/hecks/router.rb +1 -1
- data/lib/hecks/runtime/best_effort.rb +22 -0
- data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +12 -2
- data/lib/hecks/runtime/command_interpreter.rb +3 -2
- data/lib/hecks/runtime/command_rules/emission.rb +1 -1
- data/lib/hecks/runtime/dispatcher.rb +4 -4
- data/lib/hecks/runtime/entity_element/location.rb +1 -1
- data/lib/hecks/runtime/event.rb +5 -0
- data/lib/hecks/runtime/identity.rb +7 -6
- data/lib/hecks/runtime/interpreting.rb +2 -1
- data/lib/hecks/runtime/invocation_builder.rb +1 -1
- data/lib/hecks/runtime/loader/boot_steps.rb +8 -5
- data/lib/hecks/runtime/loader.rb +10 -10
- data/lib/hecks/runtime/policy_interpreter/arguments.rb +7 -4
- data/lib/hecks/runtime/policy_interpreter/fan_out.rb +3 -3
- data/lib/hecks/runtime/policy_interpreter.rb +6 -6
- data/lib/hecks/runtime/port_operation_interpreter.rb +1 -1
- data/lib/hecks/runtime/reaction_invocation.rb +1 -1
- data/lib/hecks/runtime/rebuild_sweep.rb +11 -1
- data/lib/hecks/runtime/registry/ask_verification.rb +35 -0
- data/lib/hecks/runtime/registry/hecksagon_rules.rb +10 -0
- data/lib/hecks/runtime/registry/merging.rb +1 -1
- data/lib/hecks/runtime/registry/providers.rb +16 -0
- data/lib/hecks/runtime/registry/read_repository.rb +6 -4
- data/lib/hecks/runtime/registry/verification.rb +4 -1
- data/lib/hecks/runtime/registry/world_defaults.rb +29 -0
- data/lib/hecks/runtime/registry.rb +1 -1
- data/lib/hecks/runtime/saga_interpreter/compensation.rb +1 -1
- data/lib/hecks/runtime/saga_interpreter/delivery.rb +4 -4
- data/lib/hecks/runtime/saga_interpreter/transitions.rb +1 -1
- data/lib/hecks/runtime/saga_interpreter.rb +3 -3
- data/lib/hecks/runtime/tenant_check.rb +5 -4
- data/lib/hecks/runtime/value/coercion.rb +2 -2
- data/lib/hecks/runtime/value/validation.rb +16 -4
- data/lib/hecks/runtime.rb +9 -9
- data/lib/hecks/rust_build/host.rb +11 -4
- data/lib/hecks/rust_build/prebuilt_binary.rb +101 -0
- data/lib/hecks/rust_build/project_rust/target_ir.rb +1 -1
- data/lib/hecks/rust_build/wasm.rb +1 -1
- data/lib/hecks/rust_build/wasm_browser.rb +1 -1
- data/lib/hecks/rust_build.rb +11 -0
- data/lib/hecks/served_options.rb +53 -0
- data/lib/hecks/site/bluebook/site.bluebook +1 -0
- data/lib/hecks/storehouse/audit.rb +1 -1
- data/lib/hecks/storehouse/dispatching.rb +8 -6
- data/lib/hecks/storehouse/introspection.rb +2 -2
- data/lib/hecks/storehouse/querying.rb +6 -4
- data/lib/hecks/storehouse.rb +4 -4
- data/lib/hecks/tools/ci_gate_decision.rb +35 -0
- data/lib/hecks/tools/ci_gates.rb +20 -1
- data/lib/hecks/tools/comment_style/source_file.rb +4 -1
- data/lib/hecks/tools/deploy_recipe_lint/fixtures.rb +4 -1
- data/lib/hecks/tools/lanes/files.rb +8 -0
- data/lib/hecks/tools/lanes/prebuilt_workflow.rb +81 -0
- data/lib/hecks/tools/lanes/rows.rb +6 -0
- data/lib/hecks/tools/lanes.rb +1 -0
- data/lib/hecks/tools/oidc_manifests.rb +4 -5
- data/lib/hecks/tools/regeneration_run.rb +11 -4
- data/lib/hecks/tools/release_only.rb +111 -0
- data/lib/hecks/tools/site_routes/editor_files.rb +12 -5
- data/lib/hecks/tools/tenant_provisioning.rb +2 -2
- data/lib/hecks/tools/tools_doc.rb +4 -4
- data/lib/hecks/version.rb +1 -1
- data/lib/hecks/vocabulary.rb +19 -5
- data/lib/hecks.rb +15 -15
- data/lib/rubocop/cop/hecks/env_dump.rb +69 -0
- data/lib/rubocop/cop/hecks/monotonic_duration.rb +53 -0
- data/lib/rubocop/cop/hecks/no_wall_clock_in_runtime.rb +34 -0
- data/lib/rubocop/cop/hecks/raw_shell_interpolation.rb +79 -0
- data/lib/rubocop/cop/hecks/sleep_in_spec.rb +34 -0
- data/lib/rubocop/cop/hecks/swallowed_rescue.rb +37 -0
- data/lib/rubocop/cop/hecks/symbol_string_key_mix.rb +50 -0
- data/lib/rubocop/cop/hecks/untyped_raise.rb +37 -0
- data/prebuilt/9ee54e2f0d4d6d3fe046827779b221e02de665fba911a048ae5dd8b8fbfb7d90.marshal +0 -0
- data/prebuilt/d74a00e996222c1d8b676d5959092effc46b4dc59e0555f3bf9554ac58e3094a.marshal +0 -0
- data/prebuilt/verdicts-df19f35868d3284a70f55e42dcef87ef4e65e6a678e5135f72e1f6cff4c1357b.json +1 -0
- data/rust/.gitignore +6 -0
- data/rust/Cargo.toml +6 -0
- data/rust/build/src/binding_pass.rs +103 -0
- data/rust/build/src/cargo_sync.rs +6 -0
- data/rust/build/src/lineage_pass.rs +3 -3
- data/rust/build/src/main.rs +17 -5
- data/rust/build/src/pipeline.rs +48 -5
- data/rust/build/src/seams_pass.rs +123 -0
- data/rust/build/src/translation_pass.rs +167 -0
- data/rust/codegen/src/asks.rs +103 -0
- data/rust/codegen/src/commands.rs +13 -8
- data/rust/codegen/src/constraints.rs +25 -0
- data/rust/codegen/src/domain_generator.rs +2 -0
- data/rust/codegen/src/expr_emitter.rs +13 -0
- data/rust/codegen/src/json_codec.rs +54 -15
- data/rust/codegen/src/main.rs +1 -0
- data/rust/codegen/src/mutations.rs +128 -3
- data/rust/codegen/src/queries.rs +11 -2
- data/rust/codegen/src/reactions.rs +1 -1
- data/rust/codegen/src/types.rs +166 -8
- data/rust/host/HECKS_RELEASE +1 -1
- data/rust/host/src/api.rs +4 -4
- data/rust/host/src/bin/validate_harness.rs +43 -0
- data/rust/host/src/boundary_fuzz/expr_json.rs +2 -2
- data/rust/host/src/boundary_fuzz/ir.rs +1 -0
- data/rust/host/src/boundary_fuzz/server.rs +1 -1
- data/rust/host/src/checkout.rs +35 -19
- data/rust/host/src/commerce_ir.rs +408 -2
- data/rust/host/src/expr_json.rs +118 -0
- data/rust/host/src/extension.rs +13 -1
- data/rust/host/src/lib.rs +1 -0
- data/rust/host/src/mint.rs +1 -1
- data/rust/host/src/payments/tests.rs +27 -1
- data/rust/host/src/payments.rs +12 -4
- data/rust/host/src/platform_signin/attack_tests.rs +1100 -0
- data/rust/host/src/platform_signin/config.rs +512 -0
- data/rust/host/src/platform_signin/cookie.rs +237 -0
- data/rust/host/src/platform_signin/extension.rs +190 -0
- data/rust/host/src/platform_signin/fetcher.rs +322 -0
- data/rust/host/src/platform_signin/handler.rs +251 -0
- data/rust/host/src/platform_signin/handler_tests.rs +554 -0
- data/rust/host/src/platform_signin/jti.rs +196 -0
- data/rust/host/src/platform_signin/jwks.rs +542 -0
- data/rust/host/src/platform_signin/jws.rs +249 -0
- data/rust/host/src/platform_signin/known_answer_tests.rs +237 -0
- data/rust/host/src/platform_signin/mapping_tests.rs +254 -0
- data/rust/host/src/platform_signin/mod.rs +75 -0
- data/rust/host/src/platform_signin/mode_tests.rs +382 -0
- data/rust/host/src/platform_signin/pages.rs +204 -0
- data/rust/host/src/platform_signin/query.rs +233 -0
- data/rust/host/src/platform_signin/session.rs +151 -0
- data/rust/host/src/platform_signin/settings.rs +385 -0
- data/rust/host/src/platform_signin/strict_json.rs +351 -0
- data/rust/host/src/platform_signin/test_support.rs +235 -0
- data/rust/host/src/platform_signin/ttl_tests.rs +94 -0
- data/rust/host/src/platform_signin/verify.rs +604 -0
- data/rust/host/src/platform_signin/wiring_support.rs +319 -0
- data/rust/host/src/query_step.rs +22 -4
- data/rust/host/src/reference_validate.rs +653 -108
- data/rust/host/src/resend.rs +2 -0
- data/rust/host/src/server.rs +19 -6
- data/rust/host/src/ui_schema.rs +132 -5
- data/rust/host/src/web/newsletter.rs +7 -8
- data/rust/host/src/web/newsletter_send.rs +38 -13
- data/rust/host/src/web/registrations.rs +229 -26
- data/rust/host/src/web.rs +53 -9
- data/rust/parser/src/emit.rs +38 -6
- data/rust/parser/src/expr/ast_json.rs +7 -1
- data/rust/parser/src/expr/resolver.rs +171 -6
- data/rust/parser/src/ir.rs +7 -0
- data/rust/parser/src/keywords.rs +24 -0
- data/rust/parser/src/main.rs +3 -0
- data/rust/parser/src/parse/aggregate.rs +2 -1
- data/rust/parser/src/parse/chapter.rs +2 -2
- data/rust/parser/src/parse/command.rs +2 -0
- data/rust/parser/src/parse/hecksagon.rs +5 -2
- data/rust/parser/src/parse/lifecycle.rs +81 -0
- data/rust/parser/src/parse/mod.rs +15 -1
- data/rust/parser/src/parse/policy.rs +13 -2
- data/rust/parser/src/parse/value_object.rs +34 -17
- data/rust/parser/src/ruby_value.rs +1 -1
- data/rust/src/exemplar/commands.rs +4 -4
- data/rust/src/exemplar/constraints.rs +1 -1
- data/rust/src/exemplar/json.rs +43 -1
- data/rust/src/kernel/cli.rs +7 -5
- data/rust/src/kernel/dispatch.rs +2 -2
- data/rust/src/kernel/dispatch_guard_tests.rs +1 -1
- data/rust/src/kernel/expr.rs +66 -3
- data/rust/src/kernel/expression_operators/arithmetic.rs +130 -5
- data/rust/src/kernel/expression_operators/text.rs +26 -2
- data/rust/src/kernel/json.rs +15 -0
- data/rust/src/kernel/mod.rs +1 -1
- data/rust/src/kernel/orchestrate.rs +54 -5
- data/rust/src/kernel/orchestrate_guard_tests.rs +67 -0
- data/rust/src/kernel/pattern.rs +60 -0
- data/rust/src/kernel/reference_lookup.rs +90 -7
- metadata +178 -38
- data/lib/hecks/doors/cli_door/nesting.rb +0 -34
- data/lib/hecks/doors/cli_door/shorthand.rb +0 -85
- data/lib/hecks/doors/cli_door.rb +0 -124
- data/lib/hecks/doors/cli_runner/answers.rb +0 -138
- data/lib/hecks/doors/cli_runner/resolution.rb +0 -146
- data/lib/hecks/doors/cli_runner/settling.rb +0 -115
- data/lib/hecks/doors/cli_runner/streaming.rb +0 -90
- data/lib/hecks/doors/cli_runner/suggestions.rb +0 -43
- data/lib/hecks/doors/cli_runner.rb +0 -66
- data/lib/hecks/doors/command_request.rb +0 -119
- data/lib/hecks/doors/handle.rb +0 -208
- data/lib/hecks/doors/json_door.rb +0 -132
- data/lib/hecks/doors/launcher_options.rb +0 -247
- data/lib/hecks/doors/mcp_door/properties.rb +0 -47
- data/lib/hecks/doors/mcp_door/tool_calls.rb +0 -113
- data/lib/hecks/doors/mcp_door/tool_definitions/auditing.rb +0 -51
- data/lib/hecks/doors/mcp_door/tool_definitions/calling.rb +0 -69
- data/lib/hecks/doors/mcp_door/tool_definitions/reading.rb +0 -97
- data/lib/hecks/doors/mcp_door/tool_definitions.rb +0 -15
- data/lib/hecks/doors/mcp_door.rb +0 -156
- data/lib/hecks/doors/mcp_door_scope/argument_policy.rb +0 -92
- data/lib/hecks/doors/mcp_door_scope/env_problems.rb +0 -55
- data/lib/hecks/doors/mcp_door_scope/presenter.rb +0 -78
- data/lib/hecks/doors/mcp_door_scope.rb +0 -201
- data/lib/hecks/doors/mcp_guide_cache.rb +0 -75
- data/lib/hecks/doors/ruby_door/aggregate_door.rb +0 -162
- data/lib/hecks/doors/ruby_door/chapter.rb +0 -89
- data/lib/hecks/doors/ruby_door.rb +0 -80
- data/lib/hecks/doors/usage_cache.rb +0 -139
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/body_widget.js.tmpl +0 -282
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/media_picker.js.tmpl +0 -109
- data/prebuilt/c28d6d9db14eea3e17161e9d9c262efee30b256cf97693a7cfd552bce24b2f7f.marshal +0 -0
- data/prebuilt/cb375cba61ca0dbe8ddb9739c306510fe05e5f4b7a9b6955caff8224c63ea0e0.marshal +0 -0
- data/prebuilt/verdicts-d6d584ad43b5f3cebf88aafbbdbf024c38574acf18e06adffbc555f7786b1c2f.json +0 -1
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
//! Resolution of a policy's `ask :name` to the port operation the hecksagon declared for it,
|
|
2
|
+
//! mirroring `Hecks::Bluebook::AskResolution`: by the event's aggregate and the ask's name, with
|
|
3
|
+
//! `chosen` (`ask_via`) breaking a tie between ports.
|
|
4
|
+
|
|
5
|
+
use crate::json::Json;
|
|
6
|
+
use crate::naming;
|
|
7
|
+
|
|
8
|
+
/// Policies whose `ask` is replaced by the `Aggregate::Port.Operation` trigger it resolves to,
|
|
9
|
+
/// so every table below reads the verb as it reads a port-naming `trigger`.
|
|
10
|
+
///
|
|
11
|
+
/// Panics when an ask resolves to nothing or to several operations: the Ruby boot refuses both
|
|
12
|
+
/// before the IR is ever exported.
|
|
13
|
+
pub fn resolve_asks(policies: &[Json], aggregates: &[Json]) -> Vec<Json> {
|
|
14
|
+
policies
|
|
15
|
+
.iter()
|
|
16
|
+
.map(|policy| match policy.get("ask").map(Json::to_s) {
|
|
17
|
+
Some(ask) if !ask.is_empty() => with_trigger(policy, resolve(policy, &ask, aggregates)),
|
|
18
|
+
_ => policy.clone(),
|
|
19
|
+
})
|
|
20
|
+
.collect()
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
fn with_trigger(policy: &Json, verb: String) -> Json {
|
|
24
|
+
let mut resolved = policy.clone();
|
|
25
|
+
resolved.set("trigger_command", Json::String(verb));
|
|
26
|
+
resolved
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
fn resolve(policy: &Json, ask: &str, aggregates: &[Json]) -> String {
|
|
30
|
+
let name = policy.get("name").map(Json::to_s).unwrap_or_default();
|
|
31
|
+
let on_event = policy.get("on_event").map(Json::to_s).unwrap_or_default();
|
|
32
|
+
let aggregate = event_aggregate(&on_event, aggregates)
|
|
33
|
+
.unwrap_or_else(|| panic!("policy {name}: ask :{ask} reacts to {on_event:?}, whose aggregate is not declared"));
|
|
34
|
+
let agg_name = aggregate.get("name").map(Json::to_s).unwrap_or_default();
|
|
35
|
+
let mut found = candidates(aggregate, ask);
|
|
36
|
+
if found.len() > 1 && found.iter().any(|(_, op)| op.get("chosen").is_some_and(Json::as_bool)) {
|
|
37
|
+
found.retain(|(_, op)| op.get("chosen").is_some_and(Json::as_bool));
|
|
38
|
+
}
|
|
39
|
+
match found.as_slice() {
|
|
40
|
+
[(port, op)] => format!("{agg_name}::{port}.{}", op.get("name").map(Json::to_s).unwrap_or_default()),
|
|
41
|
+
[] => panic!("policy {name}: ask :{ask} matches no `asks` declared on {agg_name}"),
|
|
42
|
+
_ => panic!("policy {name}: ask :{ask} is asked on several ports of {agg_name}; pick one with ask_via"),
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// The aggregate the event is qualified with, or else the one whose commands emit the bare event.
|
|
47
|
+
fn event_aggregate<'a>(on_event: &str, aggregates: &'a [Json]) -> Option<&'a Json> {
|
|
48
|
+
let named = |wanted: &str| aggregates.iter().find(|a| a.get("name").map(Json::to_s).unwrap_or_default() == wanted);
|
|
49
|
+
match on_event.split_once('.') {
|
|
50
|
+
Some((qualifier, _)) => named(qualifier.rsplit("::").next().unwrap_or(qualifier)),
|
|
51
|
+
None => aggregates.iter().find(|a| {
|
|
52
|
+
a.get("commands")
|
|
53
|
+
.map(Json::each)
|
|
54
|
+
.unwrap_or(&[])
|
|
55
|
+
.iter()
|
|
56
|
+
.any(|c| c.get("emits").map(Json::to_s).unwrap_or_default() == on_event)
|
|
57
|
+
}),
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Every outbound operation of the aggregate's ports whose snake-cased name is the ask.
|
|
62
|
+
fn candidates<'a>(aggregate: &'a Json, ask: &str) -> Vec<(String, &'a Json)> {
|
|
63
|
+
let mut found = Vec::new();
|
|
64
|
+
for port in aggregate.get("ports").map(Json::each).unwrap_or(&[]) {
|
|
65
|
+
let port_name = port.get("name").map(Json::to_s).unwrap_or_default();
|
|
66
|
+
for op in port.get("operations").map(Json::each).unwrap_or(&[]) {
|
|
67
|
+
let outbound = op.get("direction").map(Json::to_s).as_deref() == Some("outbound");
|
|
68
|
+
let spelled = naming::snake(&op.get("name").map(Json::to_s).unwrap_or_default());
|
|
69
|
+
if outbound && spelled == ask {
|
|
70
|
+
found.push((port_name.clone(), op));
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
found
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
#[cfg(test)]
|
|
78
|
+
mod tests {
|
|
79
|
+
use super::*;
|
|
80
|
+
use std::path::PathBuf;
|
|
81
|
+
|
|
82
|
+
// The neutral corpus fixture `spec/corpus/asks/errand.json`: the Ruby spec holds the same
|
|
83
|
+
// `targets` against the Ruby runtime's reaction log, so the two cannot drift.
|
|
84
|
+
fn corpus(name: &str) -> Json {
|
|
85
|
+
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../spec/corpus/asks").join(name);
|
|
86
|
+
Json::parse(&std::fs::read_to_string(path).expect("corpus file")).expect("corpus json")
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
#[test]
|
|
90
|
+
fn every_ask_resolves_to_the_target_the_corpus_freezes() {
|
|
91
|
+
let ir = corpus("errand.ir.json");
|
|
92
|
+
let expect = corpus("errand.json");
|
|
93
|
+
let domain = ir.get("name").map(Json::to_s).unwrap_or_default();
|
|
94
|
+
let resolved = resolve_asks(ir.get("policies").unwrap().each(), ir.get("aggregates").unwrap().each());
|
|
95
|
+
|
|
96
|
+
for policy in &resolved {
|
|
97
|
+
let name = policy.get("name").map(Json::to_s).unwrap_or_default();
|
|
98
|
+
let target = format!("{domain}::{}", policy.get("trigger_command").map(Json::to_s).unwrap_or_default());
|
|
99
|
+
let frozen = expect.get("targets").and_then(|t| t.get(&name)).map(Json::to_s);
|
|
100
|
+
assert_eq!(frozen, Some(target), "{name}");
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -168,6 +168,11 @@ fn command_skip_reason_with(command: &Json, aggregate: &Json, value_objects_by_n
|
|
|
168
168
|
return Some(skip("state_source", format!("sets state source(s): {}", state_problems.join("; "))));
|
|
169
169
|
}
|
|
170
170
|
|
|
171
|
+
let expression_problems = mutations::expression_source_problems(command, aggregate, value_objects_by_name);
|
|
172
|
+
if !expression_problems.is_empty() {
|
|
173
|
+
return Some(skip("expression_source", format!("sets expression source(s): {}", expression_problems.join("; "))));
|
|
174
|
+
}
|
|
175
|
+
|
|
171
176
|
let remove_problems = mutations::remove_field_problems(command, aggregate, value_objects_by_name);
|
|
172
177
|
if !remove_problems.is_empty() {
|
|
173
178
|
return Some(skip("remove_field", format!("sets remove field(s): {}", remove_problems.join("; "))));
|
|
@@ -678,37 +683,37 @@ pub fn delegate_skip_reason(command: &Json, aggregate: &Json, value_objects_by_n
|
|
|
678
683
|
}
|
|
679
684
|
let mapping = delegate_mapping(delegation);
|
|
680
685
|
let source_name_for = |name: &str| mapping.iter().find(|(t, _)| t == name).map(|(_, s)| s.clone()).unwrap_or_else(|| name.to_string());
|
|
681
|
-
let
|
|
686
|
+
let argument_attrs = command.get("attributes").map(Json::each).unwrap_or(&[]);
|
|
682
687
|
for attr in target.get("attributes").map(Json::each).unwrap_or(&[]) {
|
|
683
688
|
let name = crate::attr::name(attr);
|
|
684
689
|
let source_name = source_name_for(name);
|
|
685
|
-
let source =
|
|
690
|
+
let source = argument_attrs.iter().find(|a| crate::attr::name(a) == source_name);
|
|
686
691
|
let Some(source) = source else {
|
|
687
692
|
if crate::attr::optional(attr) {
|
|
688
693
|
continue;
|
|
689
694
|
}
|
|
690
|
-
return Some(format!("{label}: target argument {name} has no source on the
|
|
695
|
+
return Some(format!("{label}: target argument {name} has no source on the entry point"));
|
|
691
696
|
};
|
|
692
697
|
if crate::attr::list(source) != crate::attr::list(attr) {
|
|
693
|
-
return Some(format!("{label}:
|
|
698
|
+
return Some(format!("{label}: entry point argument {source_name} is a list, target wants a scalar (or vice versa) — not generated yet"));
|
|
694
699
|
}
|
|
695
700
|
// Bridgeable, not exact type-name equality: the alias-then-deserialize delegate never
|
|
696
701
|
// compares declared type names (docs/decisions/0045).
|
|
697
702
|
if !crate::bridging::bridgeable_value_types(crate::attr::type_name(source), crate::attr::type_name(attr), value_objects_by_name) {
|
|
698
|
-
return Some(format!("{label}:
|
|
703
|
+
return Some(format!("{label}: entry point argument {source_name} is {}, target wants {} — not generated yet", crate::attr::type_name(source), crate::attr::type_name(attr)));
|
|
699
704
|
}
|
|
700
705
|
if crate::attr::optional(source) && !crate::attr::optional(attr) {
|
|
701
|
-
return Some(format!("{label}: optional
|
|
706
|
+
return Some(format!("{label}: optional entry point argument {source_name} feeds required {name}"));
|
|
702
707
|
}
|
|
703
708
|
}
|
|
704
709
|
for path in entity.get("identified_by").map(Json::each).unwrap_or(&[]) {
|
|
705
710
|
let path = path.to_s();
|
|
706
711
|
let head = path.split('.').next().unwrap_or("").to_string();
|
|
707
712
|
let source_name = source_name_for(&head);
|
|
708
|
-
if
|
|
713
|
+
if argument_attrs.iter().any(|a| crate::attr::name(a) == source_name && !crate::attr::optional(a)) {
|
|
709
714
|
continue;
|
|
710
715
|
}
|
|
711
|
-
return Some(format!("{label}: the element's identity {head} has no source on the
|
|
716
|
+
return Some(format!("{label}: the element's identity {head} has no source on the entry point"));
|
|
712
717
|
}
|
|
713
718
|
None
|
|
714
719
|
}
|
|
@@ -107,3 +107,28 @@ pub fn emit_pattern_check(exemplar: &Exemplar, value_expr: &str, attr: &Json, ow
|
|
|
107
107
|
);
|
|
108
108
|
Some(wrap_if_optional(check, optional_source))
|
|
109
109
|
}
|
|
110
|
+
|
|
111
|
+
/// A `pattern:` on a list of Strings, checked once per member: Ruby's `check_patterns` walks
|
|
112
|
+
/// `offered_items`, so every element of the list must match, and an optional list left unset has
|
|
113
|
+
/// none to check. Lists of anything but plain Strings get no check.
|
|
114
|
+
pub fn emit_list_pattern_check(exemplar: &Exemplar, value_expr: &str, attr: &Json, owner_type_name: &str) -> Option<String> {
|
|
115
|
+
let pattern = crate::attr::pattern(attr)?;
|
|
116
|
+
if !crate::attr::list(attr) || naming::effective_scalar_type(crate::attr::type_name(attr)) != Some("String") {
|
|
117
|
+
return None;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
let check = exemplar.render(
|
|
121
|
+
"pattern_check",
|
|
122
|
+
&[
|
|
123
|
+
("\"tmpl_pattern_text\"", naming::ruby_inspect_string(pattern)),
|
|
124
|
+
("tmpl_scalar", "item".to_string()),
|
|
125
|
+
("\"tmpl_pattern_owner\"", naming::ruby_inspect_string(owner_type_name)),
|
|
126
|
+
("\"tmpl_pattern_field\"", naming::ruby_inspect_string(crate::attr::name(attr))),
|
|
127
|
+
],
|
|
128
|
+
);
|
|
129
|
+
Some(if crate::attr::optional(attr) {
|
|
130
|
+
format!("if let Some(items) = &{value_expr} {{ for item in items {{ {check} }} }}")
|
|
131
|
+
} else {
|
|
132
|
+
format!("for item in &{value_expr} {{ {check} }}")
|
|
133
|
+
})
|
|
134
|
+
}
|
|
@@ -289,6 +289,8 @@ pub fn generate(
|
|
|
289
289
|
source_label: &str,
|
|
290
290
|
mod_name: &str,
|
|
291
291
|
) -> GeneratedDomain {
|
|
292
|
+
let resolved_ir = types::with_resolved_projection_types(ir);
|
|
293
|
+
let ir = &resolved_ir;
|
|
292
294
|
let domain_name = ir.get("name").and_then(Json::as_str).unwrap_or("");
|
|
293
295
|
let all_aggregates = ir.get("aggregates").map(Json::each).unwrap_or(&[]);
|
|
294
296
|
let aggregates_by_name: HashMap<String, &Json> = all_aggregates
|
|
@@ -23,6 +23,9 @@ pub fn emit_ast(node: &Json) -> String {
|
|
|
23
23
|
"nil" => "Expr::Nil".to_string(),
|
|
24
24
|
"lookup" => format!("Expr::Lookup({})", ruby_inspect_string(&path_of(node, op).join("."))),
|
|
25
25
|
"add" => format!("Expr::Add({}, {})", boxed("left"), boxed("right")),
|
|
26
|
+
"sub" => format!("Expr::Sub({}, {})", boxed("left"), boxed("right")),
|
|
27
|
+
"mul" => format!("Expr::Mul({}, {})", boxed("left"), boxed("right")),
|
|
28
|
+
"div" => format!("Expr::Div({}, {})", boxed("left"), boxed("right")),
|
|
26
29
|
"sign_test" => format!("Expr::SignTest {{ op: {}, receiver: {} }}", emit_comparison(field(node, op, "cmp")), boxed("receiver")),
|
|
27
30
|
"empty" => format!("Expr::Empty({})", boxed("receiver")),
|
|
28
31
|
"to_s" => format!("Expr::ToS({})", boxed("receiver")),
|
|
@@ -50,6 +53,7 @@ pub fn emit_ast(node: &Json) -> String {
|
|
|
50
53
|
"presence" => format!("Expr::Presence {{ receiver: {}, negated: {} }}", boxed("receiver"), field(node, op, "negated").to_s()),
|
|
51
54
|
"assignment" => format!("Expr::Assignment {{ receiver: {}, negated: {} }}", boxed("receiver"), field(node, op, "negated").to_s()),
|
|
52
55
|
"split" => format!("Expr::Split {{ receiver: {}, separator: {}.to_string() }}", boxed("receiver"), text("separator")),
|
|
56
|
+
"strip" => format!("Expr::Strip {{ receiver: {}, side: crate::kernel::StripSide::{} }}", boxed("receiver"), strip_side(node, op)),
|
|
53
57
|
"starts_with" => format!("Expr::StartsWith {{ receiver: {}, substring: {}.to_string() }}", boxed("receiver"), text("substring")),
|
|
54
58
|
"ends_with" => format!("Expr::EndsWith {{ receiver: {}, substring: {}.to_string() }}", boxed("receiver"), text("substring")),
|
|
55
59
|
"first" => format!("Expr::First({})", boxed("receiver")),
|
|
@@ -77,6 +81,15 @@ fn path_of(node: &Json, op: &str) -> Vec<String> {
|
|
|
77
81
|
.collect()
|
|
78
82
|
}
|
|
79
83
|
|
|
84
|
+
fn strip_side(node: &Json, op: &str) -> &'static str {
|
|
85
|
+
match field(node, op, "side").as_str() {
|
|
86
|
+
Some("both") => "Both",
|
|
87
|
+
Some("left") => "Left",
|
|
88
|
+
Some("right") => "Right",
|
|
89
|
+
other => panic!("strip's side {other:?} is none of both/left/right: {node:?}"),
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
80
93
|
fn block_mode(node: &Json, op: &str) -> &'static str {
|
|
81
94
|
match field(node, op, "mode").as_str() {
|
|
82
95
|
Some("all") => "All",
|
|
@@ -17,20 +17,22 @@ pub fn list_shape_error(struct_name: &str, key: &str, element_type: &str, value_
|
|
|
17
17
|
format!("crate::kernel::Refusal::TypeMismatch(format!({}, {value_var}.inspect()))", naming::ruby_inspect_string(&template))
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
// A String field refuses
|
|
21
|
-
// `Value::Coercion#check_scalar_shapes`
|
|
20
|
+
// A String field refuses any value that is not a String, and an Integer or Float field any that
|
|
21
|
+
// is not a number, as Ruby's `Value::Coercion#check_scalar_shapes` and `check_numeric_item!`
|
|
22
|
+
// do; the offered shape is only known at runtime, so the message renders it. A whole number
|
|
23
|
+
// beyond signed 64 bits words as Ruby's `integer_range` site.
|
|
22
24
|
pub fn scalar_type_error(struct_name: &str, key: &str, scalar_type: &str, value_var: &str) -> String {
|
|
23
25
|
if scalar_type != "Integer" && scalar_type != "Float" && scalar_type != "String" {
|
|
24
26
|
return json_type_error(struct_name, key, scalar_type);
|
|
25
27
|
}
|
|
26
28
|
let template = format!("{struct_name}.{key} expects {scalar_type}, got {{}}");
|
|
27
29
|
let proper = format!("crate::kernel::Refusal::TypeMismatch(format!({}, {value_var}.inspect()))", naming::ruby_inspect_string(&template));
|
|
28
|
-
if scalar_type != "
|
|
30
|
+
if scalar_type != "Integer" {
|
|
29
31
|
return proper;
|
|
30
32
|
}
|
|
31
|
-
let
|
|
32
|
-
|
|
33
|
-
format!("if matches!({value_var}, crate::kernel::Json::
|
|
33
|
+
let range_template = format!("{struct_name}.{key} must fit in a 64-bit integer, got {{}}");
|
|
34
|
+
let range = format!("crate::kernel::Refusal::TypeMismatch(format!({}, {value_var}.inspect()))", naming::ruby_inspect_string(&range_template));
|
|
35
|
+
format!("if matches!({value_var}, crate::kernel::Json::Num(n, _) if n.is_finite() && n.fract() == 0.0 && n.abs() >= 9.223372036854775808e18) {{ {range} }} else {{ {proper} }}")
|
|
34
36
|
}
|
|
35
37
|
|
|
36
38
|
/// A missing non-optional field refuses as "{type}.{field} expects {expected}, got nil".
|
|
@@ -274,6 +276,9 @@ pub fn emit_from_json_flat(
|
|
|
274
276
|
aggregates_by_name: Option<&HashMap<String, &Json>>,
|
|
275
277
|
) -> String {
|
|
276
278
|
let command_name = command_name.unwrap_or(struct_name);
|
|
279
|
+
// A refusal names the command (`Write.slugs expects ...`), as Ruby's `Value.for_attribute`
|
|
280
|
+
// does, not the generated `WriteArgs` struct that holds its arguments.
|
|
281
|
+
let error_name = if struct_name == format!("{command_name}Args") { command_name } else { struct_name };
|
|
277
282
|
let idents: Vec<String> = attributes.iter().map(|attr| naming::rust_ident_field(crate::attr::name(attr))).collect();
|
|
278
283
|
let field_exprs: Vec<String> = attributes
|
|
279
284
|
.iter()
|
|
@@ -285,33 +290,33 @@ pub fn emit_from_json_flat(
|
|
|
285
290
|
let optional = crate::attr::optional(attr);
|
|
286
291
|
|
|
287
292
|
let rhs = if list && optional {
|
|
288
|
-
let mapper = list_element_from_json_mapper(
|
|
289
|
-
let array_error = json_type_error(
|
|
293
|
+
let mapper = list_element_from_json_mapper(error_name, &key, attr, value_objects_by_name);
|
|
294
|
+
let array_error = json_type_error(error_name, &key, "an array");
|
|
290
295
|
format!(
|
|
291
296
|
"match v.get({}) {{ Some(crate::kernel::Json::Null) | None => None, Some(x) => Some(x.as_array().ok_or_else(|| {array_error})?.iter().map({mapper}).collect::<Result<Vec<_>, crate::kernel::Refusal>>()?) }}",
|
|
292
297
|
naming::ruby_inspect_string(&key)
|
|
293
298
|
)
|
|
294
299
|
} else if list {
|
|
295
|
-
let mapper = list_element_from_json_mapper(
|
|
300
|
+
let mapper = list_element_from_json_mapper(error_name, &key, attr, value_objects_by_name);
|
|
296
301
|
// A list argument is an array: a lone scalar is refused, as Ruby's
|
|
297
302
|
// `Value.refuse_scalar_list` does; null and an absent key are the empty list.
|
|
298
|
-
let shape_error = list_shape_error(
|
|
303
|
+
let shape_error = list_shape_error(error_name, &key, crate::attr::type_name(attr), "x");
|
|
299
304
|
format!(
|
|
300
305
|
"match v.get({}) {{ Some(crate::kernel::Json::Null) | None => Vec::new(), Some(x) => x.as_array().ok_or_else(|| {shape_error})?.iter().map({mapper}).collect::<Result<Vec<_>, crate::kernel::Refusal>>()?, }}",
|
|
301
306
|
naming::ruby_inspect_string(&key)
|
|
302
307
|
)
|
|
303
308
|
} else if optional && scalar.is_some() {
|
|
304
309
|
// An optional argument offered as null is the same absence as an omitted key.
|
|
305
|
-
format!("match v.get({}) {{ Some(crate::kernel::Json::Null) | None => None, Some(x) => Some({}) }}", naming::ruby_inspect_string(&key), scalar_from_json_value_expr(
|
|
310
|
+
format!("match v.get({}) {{ Some(crate::kernel::Json::Null) | None => None, Some(x) => Some({}) }}", naming::ruby_inspect_string(&key), scalar_from_json_value_expr(error_name, &key, scalar.unwrap(), "x"))
|
|
306
311
|
} else if optional {
|
|
307
312
|
format!("match v.get({}) {{ Some(crate::kernel::Json::Null) | None => None, Some(x) => Some({}) }}", naming::ruby_inspect_string(&key), composite_from_json_expr(attr, value_objects_by_name, "x"))
|
|
308
313
|
} else if let Some(scalar) = scalar {
|
|
309
|
-
scalar_from_json_expr(
|
|
314
|
+
scalar_from_json_expr(error_name, &key, scalar, crate::attr::default(attr))
|
|
310
315
|
} else if absent_argument_check {
|
|
311
|
-
// The argument
|
|
312
|
-
required_composite_argument_expr(
|
|
316
|
+
// The argument entry point only; see `required_composite_argument_expr`.
|
|
317
|
+
required_composite_argument_expr(error_name, &key, attr, value_objects_by_name)
|
|
313
318
|
} else {
|
|
314
|
-
composite_from_json_expr(attr, value_objects_by_name, &required_field_expr(
|
|
319
|
+
composite_from_json_expr(attr, value_objects_by_name, &required_field_expr(error_name, &key, crate::attr::type_name(attr)))
|
|
315
320
|
};
|
|
316
321
|
|
|
317
322
|
if interleave_checks {
|
|
@@ -588,6 +593,35 @@ pub fn emit_closed_set_table_codec(exemplar: &Exemplar, vo: &Json) -> String {
|
|
|
588
593
|
})
|
|
589
594
|
.collect();
|
|
590
595
|
|
|
596
|
+
let type_name = vo.get("name").and_then(Json::as_str).unwrap_or("").to_string();
|
|
597
|
+
let field_names: Vec<String> = attributes.iter().map(|attr| naming::ruby_inspect_string(&naming::rust_field(crate::attr::name(attr)))).collect();
|
|
598
|
+
// A field left out (or null) refuses as "{type}.{field} expects {expected}, got nil", before
|
|
599
|
+
// membership is judged.
|
|
600
|
+
let absent_checks: Vec<String> = attributes
|
|
601
|
+
.iter()
|
|
602
|
+
.map(|attr| {
|
|
603
|
+
let key = naming::rust_field(crate::attr::name(attr));
|
|
604
|
+
let message = format!("{type_name}.{} expects {}, got nil", crate::attr::name(attr), crate::attr::type_name(attr));
|
|
605
|
+
format!(
|
|
606
|
+
" if matches!(v.get({}), None | Some(crate::kernel::Json::Null)) {{\n return Err(crate::kernel::Refusal::TypeMismatch({}.to_string()));\n }}",
|
|
607
|
+
naming::ruby_inspect_string(&key),
|
|
608
|
+
naming::ruby_inspect_string(&message)
|
|
609
|
+
)
|
|
610
|
+
})
|
|
611
|
+
.collect();
|
|
612
|
+
// The refusal quotes the first column of every row, as Ruby's `refuse_non_member!` does.
|
|
613
|
+
let admitted: Vec<String> = vo
|
|
614
|
+
.get("members")
|
|
615
|
+
.map(Json::each)
|
|
616
|
+
.unwrap_or(&[])
|
|
617
|
+
.iter()
|
|
618
|
+
.map(|row| {
|
|
619
|
+
let first = row.as_array().unwrap_or(&[]).first().and_then(Json::as_array).unwrap_or(&[]);
|
|
620
|
+
naming::ruby_inspect_string(&first.get(1).map(Json::to_s).unwrap_or_default())
|
|
621
|
+
})
|
|
622
|
+
.collect();
|
|
623
|
+
let first_field = attributes.first().map(|attr| naming::rust_field(crate::attr::name(attr))).unwrap_or_default();
|
|
624
|
+
|
|
591
625
|
exemplar.render(
|
|
592
626
|
"closed_set_table_codec",
|
|
593
627
|
&[
|
|
@@ -595,6 +629,11 @@ pub fn emit_closed_set_table_codec(exemplar: &Exemplar, vo: &Json) -> String {
|
|
|
595
629
|
("tmpl_to_json_fields_block()", to_json_fields_block),
|
|
596
630
|
("TMPL_TABLE", const_name),
|
|
597
631
|
("tmpl_from_json_conditions()", match_conditions.join(" && ")),
|
|
632
|
+
("\"tmpl_table_type\"", naming::ruby_inspect_string(&type_name)),
|
|
633
|
+
("TMPL_DECLARED_NAMES", format!("[{}]", field_names.join(", "))),
|
|
634
|
+
("TMPL_ADMITTED", format!("[{}]", admitted.join(", "))),
|
|
635
|
+
("tmpl_absent_field_checks();", absent_checks.join("\n")),
|
|
636
|
+
("\"tmpl_first_field\"", naming::ruby_inspect_string(&first_field)),
|
|
598
637
|
],
|
|
599
638
|
)
|
|
600
639
|
}
|
data/rust/codegen/src/main.rs
CHANGED
|
@@ -176,10 +176,11 @@ pub fn append_field_source(source: &str) -> Literal {
|
|
|
176
176
|
literal::read(source)
|
|
177
177
|
}
|
|
178
178
|
|
|
179
|
-
/// Whether any effect reads the record's own state
|
|
179
|
+
/// Whether any effect reads the record's own state (a `state(:field)` source, or an expression that
|
|
180
|
+
/// may name a field), so the closure must bind `let pre`.
|
|
180
181
|
pub fn reads_pre_state(mutations: &[Json]) -> bool {
|
|
181
182
|
mutations.iter().any(|m| match m.get("op").map(Json::to_s).unwrap_or_default().as_str() {
|
|
182
|
-
"set" => m.get("source").and_then(|s| s.get("kind")).map(Json::to_s).unwrap_or_default()
|
|
183
|
+
"set" => matches!(m.get("source").and_then(|s| s.get("kind")).map(Json::to_s).unwrap_or_default().as_str(), "state" | "expression"),
|
|
183
184
|
"append" => match m.get("fields") {
|
|
184
185
|
Some(Json::Object(pairs)) => pairs.iter().any(|(_, source)| source.to_s().starts_with("state(:")),
|
|
185
186
|
_ => false,
|
|
@@ -251,6 +252,66 @@ pub fn state_source_problems(command: &Json, aggregate: &Json, value_objects_by_
|
|
|
251
252
|
.collect()
|
|
252
253
|
}
|
|
253
254
|
|
|
255
|
+
/// Problems with `set` mutations sourced from arithmetic: the target must be a scalar
|
|
256
|
+
/// (non-list) attribute typed by a single-field Integer or Float value object, the one shape the
|
|
257
|
+
/// generated code can rebuild a computed number into.
|
|
258
|
+
pub fn expression_source_problems(command: &Json, aggregate: &Json, value_objects_by_name: &HashMap<String, &Json>) -> Vec<String> {
|
|
259
|
+
let attrs = aggregate.get("attributes").map(Json::each).unwrap_or(&[]);
|
|
260
|
+
command
|
|
261
|
+
.get("mutations")
|
|
262
|
+
.map(Json::each)
|
|
263
|
+
.unwrap_or(&[])
|
|
264
|
+
.iter()
|
|
265
|
+
.filter(|m| m.get("op").map(Json::to_s).as_deref() == Some("set"))
|
|
266
|
+
.filter(|m| m.get("source").and_then(|s| s.get("kind")).map(Json::to_s).as_deref() == Some("expression"))
|
|
267
|
+
.filter_map(|m| {
|
|
268
|
+
let target = m.get("target").map(Json::to_s).unwrap_or_default();
|
|
269
|
+
let attr = attrs.iter().find(|a| crate::attr::name(a) == target);
|
|
270
|
+
match attr.and_then(|a| computed_field_type(a, value_objects_by_name)) {
|
|
271
|
+
Some(_) => None,
|
|
272
|
+
None => Some(format!("{target}: a computed value needs a scalar attribute typed by a single-field Integer or Float value object — not generated yet")),
|
|
273
|
+
}
|
|
274
|
+
})
|
|
275
|
+
.collect()
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// The scalar type (`Integer` or `Float`) of the one field of a non-list, single-field value object.
|
|
279
|
+
fn computed_field_type<'a>(attr: &Json, value_objects_by_name: &HashMap<String, &'a Json>) -> Option<(&'a Json, &'static str)> {
|
|
280
|
+
if crate::attr::list(attr) {
|
|
281
|
+
return None;
|
|
282
|
+
}
|
|
283
|
+
let vo = value_objects_by_name.get(crate::attr::type_name(attr)).copied()?;
|
|
284
|
+
let fields = vo.get("attributes").map(Json::each).unwrap_or(&[]);
|
|
285
|
+
if vo.get("closed_set").map(Json::as_bool).unwrap_or(false) || fields.len() != 1 || crate::attr::list(&fields[0]) {
|
|
286
|
+
return None;
|
|
287
|
+
}
|
|
288
|
+
match crate::attr::type_name(&fields[0]) {
|
|
289
|
+
"Integer" => Some((vo, "Integer")),
|
|
290
|
+
"Float" => Some((vo, "Float")),
|
|
291
|
+
_ => None,
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/// Right-hand side for a `set` sourced from arithmetic: evaluates the expression through the
|
|
296
|
+
/// kernel over the command's arguments and then the pre-dispatch record, and rebuilds the number
|
|
297
|
+
/// into the target's single-field value object. A fault returns from the mutation closure.
|
|
298
|
+
pub fn expression_set_rhs(source: &Json, target_attr: &Json, value_objects_by_name: &HashMap<String, &Json>) -> String {
|
|
299
|
+
let (vo, scalar) = computed_field_type(target_attr, value_objects_by_name).expect("expression_source_problems already confirmed the target");
|
|
300
|
+
let ast = source.get("ast").expect("an expression source carries its ast");
|
|
301
|
+
let field = naming::rust_ident_field(crate::attr::name(&vo.get("attributes").map(Json::each).unwrap_or(&[])[0]));
|
|
302
|
+
let vo_type = naming::rust_ident(crate::attr::type_name(target_attr));
|
|
303
|
+
let arms = if scalar == "Integer" {
|
|
304
|
+
"crate::kernel::Value::Int(number) => number,".to_string()
|
|
305
|
+
} else {
|
|
306
|
+
"crate::kernel::Value::Float(number) => number,\n crate::kernel::Value::Int(number) => number as f64,".to_string()
|
|
307
|
+
};
|
|
308
|
+
let target = crate::attr::name(target_attr);
|
|
309
|
+
format!(
|
|
310
|
+
"{{\n let number = match crate::kernel::interpret(&{}, &crate::kernel::EvalContext {{ args: &args, instance: &pre }})? {{\n {arms}\n other => return Err(crate::kernel::Refusal::TypeMismatch(format!(\"{target} expects a {scalar}, computed {{other:?}}\"))),\n }};\n {vo_type} {{ {field}: number }}\n }}",
|
|
311
|
+
crate::expr_emitter::emit_ast(ast)
|
|
312
|
+
)
|
|
313
|
+
}
|
|
314
|
+
|
|
254
315
|
fn state_source_problem(label: &str, state_name: &str, aggregate: &Json, target_attr: Option<&Json>) -> Option<String> {
|
|
255
316
|
let attrs = aggregate.get("attributes").map(Json::each).unwrap_or(&[]);
|
|
256
317
|
let Some(state_attr) = attrs.iter().find(|a| crate::attr::name(a) == state_name) else {
|
|
@@ -722,8 +783,12 @@ fn emit_mutation_line_body(
|
|
|
722
783
|
} else {
|
|
723
784
|
let attrs = aggregate.get("attributes").map(Json::each).unwrap_or(&[]);
|
|
724
785
|
let target_attr = attrs.iter().find(|a| crate::attr::name(a) == target_name).expect("set target must be a declared aggregate attribute");
|
|
725
|
-
let rhs = mutation_set_rhs(mutation.get("source").unwrap_or(&Json::Null), crate::attr::type_name(target_attr), command, value_objects_by_name, crate::attr::list(target_attr));
|
|
726
786
|
let source = mutation.get("source");
|
|
787
|
+
let rhs = if source.and_then(|s| s.get("kind")).map(Json::to_s).as_deref() == Some("expression") {
|
|
788
|
+
expression_set_rhs(source.unwrap(), target_attr, value_objects_by_name)
|
|
789
|
+
} else {
|
|
790
|
+
mutation_set_rhs(mutation.get("source").unwrap_or(&Json::Null), crate::attr::type_name(target_attr), command, value_objects_by_name, crate::attr::list(target_attr))
|
|
791
|
+
};
|
|
727
792
|
let source_attr = if source.map(|s| s.get("kind").map(Json::to_s).unwrap_or_default()) == Some("argument".to_string()) {
|
|
728
793
|
let name = source.unwrap().get("name").map(Json::to_s).unwrap_or_default();
|
|
729
794
|
let cmd_attrs = command.get("attributes").map(Json::each).unwrap_or(&[]);
|
|
@@ -896,3 +961,63 @@ mod state_source_tests {
|
|
|
896
961
|
assert!(!state_source_is_option(Some(&argument), &aggregate(), true));
|
|
897
962
|
}
|
|
898
963
|
}
|
|
964
|
+
|
|
965
|
+
#[cfg(test)]
|
|
966
|
+
mod expression_source_tests {
|
|
967
|
+
use super::*;
|
|
968
|
+
|
|
969
|
+
fn aggregate() -> Json {
|
|
970
|
+
Json::parse(r#"{"name":"Settlement","attributes":[
|
|
971
|
+
{"name":"paid","type":"Cents","list":false},
|
|
972
|
+
{"name":"refund","type":"Cents","list":false},
|
|
973
|
+
{"name":"note","type":"Note","list":false}]}"#)
|
|
974
|
+
.unwrap()
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
fn vos() -> Vec<Json> {
|
|
978
|
+
vec![
|
|
979
|
+
Json::parse(r#"{"name":"Cents","attributes":[{"name":"value","type":"Integer","list":false}]}"#).unwrap(),
|
|
980
|
+
Json::parse(r#"{"name":"Note","attributes":[{"name":"value","type":"String","list":false}]}"#).unwrap(),
|
|
981
|
+
]
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
fn command(target: &str) -> Json {
|
|
985
|
+
let source = r#"{"kind":"expression","text":"paid * 2","ast":{"op":"mul","left":{"op":"lookup","path":["paid"]},"right":{"op":"int","value":2}}}"#;
|
|
986
|
+
Json::parse(&format!(r#"{{"mutations":[{{"target":"{target}","op":"set","source":{source}}}]}}"#)).unwrap()
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
fn by_name(vos: &[Json]) -> HashMap<String, &Json> {
|
|
990
|
+
vos.iter().map(|vo| (vo.get("name").map(Json::to_s).unwrap_or_default(), vo)).collect()
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
#[test]
|
|
994
|
+
fn an_expression_source_binds_the_pre_dispatch_record() {
|
|
995
|
+
assert!(reads_pre_state(command("refund").get("mutations").map(Json::each).unwrap_or(&[])));
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
#[test]
|
|
999
|
+
fn a_target_typed_by_an_integer_value_object_is_generated() {
|
|
1000
|
+
let vos = vos();
|
|
1001
|
+
|
|
1002
|
+
assert!(expression_source_problems(&command("refund"), &aggregate(), &by_name(&vos)).is_empty());
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
#[test]
|
|
1006
|
+
fn a_target_typed_by_a_text_value_object_is_not_generated_yet() {
|
|
1007
|
+
let vos = vos();
|
|
1008
|
+
|
|
1009
|
+
assert_eq!(expression_source_problems(&command("note"), &aggregate(), &by_name(&vos)).len(), 1);
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
#[test]
|
|
1013
|
+
fn the_right_hand_side_evaluates_through_the_kernel_and_rebuilds_the_value_object() {
|
|
1014
|
+
let vos = vos();
|
|
1015
|
+
let aggregate = aggregate();
|
|
1016
|
+
let attrs = aggregate.get("attributes").map(Json::each).unwrap_or(&[]);
|
|
1017
|
+
let source = command("refund").get("mutations").map(Json::each).unwrap_or(&[])[0].get("source").cloned().unwrap();
|
|
1018
|
+
|
|
1019
|
+
let rhs = expression_set_rhs(&source, &attrs[1], &by_name(&vos));
|
|
1020
|
+
|
|
1021
|
+
assert!(rhs.contains("crate::kernel::interpret(&Expr::Mul(") && rhs.contains("Cents { value: number }"));
|
|
1022
|
+
}
|
|
1023
|
+
}
|
data/rust/codegen/src/queries.rs
CHANGED
|
@@ -313,8 +313,17 @@ pub fn query_skip_reason(query: &Json, aggregate: &Json, value_objects_by_name:
|
|
|
313
313
|
// field itself is validated afterward by `declared_authorization_skip_reason`.
|
|
314
314
|
let declared_tenant = query.get("authorization").and_then(|a| a.get("tenant")).is_some();
|
|
315
315
|
let wheres = query.get("wheres").map(Json::each).unwrap_or(&[]);
|
|
316
|
-
|
|
317
|
-
|
|
316
|
+
// A query with no `where` answers every record, ordered and bounded as declared, as Ruby's
|
|
317
|
+
// `QueryInterpreter` does. Ruby refuses at boot one that takes arguments yet filters, orders
|
|
318
|
+
// and bounds nothing (`Registry::QueryVerification#derivable?`: it reads none of them), so no
|
|
319
|
+
// answer exists to generate for it.
|
|
320
|
+
let selects_nothing = query.get("order_by").is_none() && query.get("limit").is_none() && query.get("offset").is_none();
|
|
321
|
+
let takes_arguments = query.get("attributes").map(Json::each).is_some_and(|attributes| !attributes.is_empty());
|
|
322
|
+
if wheres.is_empty() && !declared_tenant && selects_nothing && takes_arguments {
|
|
323
|
+
return Some(skip(
|
|
324
|
+
"no_wheres",
|
|
325
|
+
"takes arguments but declares no where, order_by, limit or offset — it selects nothing, so Ruby refuses it at boot and there is no answer to generate",
|
|
326
|
+
));
|
|
318
327
|
}
|
|
319
328
|
|
|
320
329
|
for where_clause in wheres {
|
|
@@ -60,7 +60,7 @@ fn where_fns(policies: &[Json]) -> Vec<String> {
|
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
fn local_policy_rows(domain_name: &str, policies: &[Json], aggregates: &[Json]) -> Vec<String> {
|
|
63
|
-
policies
|
|
63
|
+
crate::asks::resolve_asks(policies, aggregates)
|
|
64
64
|
.iter()
|
|
65
65
|
.filter_map(|policy| {
|
|
66
66
|
let target_domain = policy.get("target_domain").map(Json::to_s).unwrap_or_else(|| domain_name.to_string());
|