hecks 3.9.1 → 3.11.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 +155 -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 +69 -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/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/rules.rb +4 -2
- data/lib/hecks/bluebook/assembly/contracts/structure.rb +3 -1
- data/lib/hecks/bluebook/assembly/marks.rb +1 -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 +18 -1
- data/lib/hecks/bluebook/meta_validator/reconstruction/heads.rb +2 -1
- 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 +1 -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 +124 -2
- data/lib/hecks/deploy/bluebook/deploy.hecksagon +8 -0
- data/lib/hecks/deploy/oidc.json +16 -3
- 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 +4 -10
- 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/clearing.rb +51 -0
- data/lib/hecks/hecks/adapters/deploy_toolchain/statuses.rb +2 -1
- data/lib/hecks/hecks/adapters/deploy_toolchain.rb +2 -0
- 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 +129 -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/site_toolchain/role_prober.rb +78 -0
- data/lib/hecks/hecks/adapters/site_toolchain.rb +29 -4
- data/lib/hecks/hecks/adapters/terminal.rb +13 -11
- data/lib/hecks/hecks/adapters/world_clearing.rb +65 -0
- 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 +61 -18
- data/lib/hecks/hecks/hecks.world +11 -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.bluebook +15 -0
- data/lib/hecks/language/bluebook/entity.bluebook +18 -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 +11 -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/projections/deploy/box/templates/deploy-box.sh.tmpl +29 -0
- data/lib/hecks/projections/deploy/box/templates/deploy-service.sh.tmpl +1 -1
- data/lib/hecks/projections/deploy/fargate/settings/tuning.rb +15 -0
- data/lib/hecks/projections/deploy/fargate/settings.rb +3 -1
- data/lib/hecks/projections/deploy/fargate/stack/readers.rb +1 -0
- data/lib/hecks/projections/deploy/preview/preview.sh.erb +1 -1
- data/lib/hecks/projections/deploy/shared.rb +5 -2
- data/lib/hecks/projections/deploy/templates/fargate/Makefile.tmpl +7 -4
- 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 +95 -0
- data/lib/hecks/projections/site/cms_editor/blocks.rb +157 -0
- data/lib/hecks/projections/site/cms_editor/checks.rb +77 -0
- 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 +85 -0
- 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/pretty.rb +40 -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 +161 -0
- data/lib/hecks/projections/site/cms_editor/setting.rb +130 -0
- data/lib/hecks/projections/site/cms_editor/targets.rb +86 -0
- data/lib/hecks/projections/site/cms_editor/templates/package.json.tmpl +36 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/app.ts.tmpl +370 -0
- 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 +87 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/auth/session.ts.tmpl +36 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/auth/sso.ts.tmpl +20 -0
- 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 +133 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/config.ts.tmpl +29 -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 +43 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/media/handler.ts.tmpl +177 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/media/multipart.ts.tmpl +53 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/media/sniff.ts.tmpl +94 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/media/storage.ts.tmpl +50 -0
- 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 +214 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/server.ts.tmpl +65 -0
- 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 +123 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/body_parse.js.tmpl +320 -0
- 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 +275 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/html.ts.tmpl +61 -0
- 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 +92 -0
- 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 +63 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/pages.ts.tmpl +213 -0
- 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 +17 -0
- 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 +133 -0
- data/lib/hecks/projections/site/role_probe.rb +74 -0
- data/lib/hecks/projector/cli_audience.rb +2 -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/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/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 +29 -1
- data/lib/hecks/site/bluebook/site.ports.hecksagon +6 -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/comment_style/source_file.rb +4 -1
- data/lib/hecks/tools/deploy_recipe_lint/fixtures.rb +4 -1
- data/lib/hecks/tools/lanes/rows.rb +6 -0
- data/lib/hecks/tools/oidc_manifests.rb +4 -5
- data/lib/hecks/tools/regeneration_run.rb +11 -4
- data/lib/hecks/tools/site_routes/cli.rb +1 -1
- data/lib/hecks/tools/site_routes/editor_files.rb +45 -0
- data/lib/hecks/tools/site_routes.rb +14 -7
- 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 +4 -4
- 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/05c358d38d661a3774f813d49c66f5b68939af94432d66257a56f49ee2598994.marshal +0 -0
- data/prebuilt/26c40c61f09b09b11651c64668eda3ceb180f83cf2a3802d3033d2fd99ac841b.marshal +0 -0
- data/prebuilt/verdicts-230ac18c25abfd3d92ede032bee2661a4c2cd2773ab0cf0e2ae52546ba2f7af9.json +1 -0
- data/rust/.gitignore +6 -0
- data/rust/Cargo.toml +9 -1
- 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/bridging.rs +1 -1
- data/rust/codegen/src/commands.rs +13 -8
- data/rust/codegen/src/constraints.rs +25 -0
- data/rust/codegen/src/domain_generator.rs +11 -9
- data/rust/codegen/src/expr_emitter.rs +13 -0
- data/rust/codegen/src/json_codec.rs +54 -15
- data/rust/codegen/src/main.rs +2 -1
- data/rust/codegen/src/mutations.rs +174 -4
- data/rust/codegen/src/naming.rs +30 -0
- 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/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/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 +3 -3
- 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 +13 -8
- 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 +73 -4
- data/rust/src/kernel/expression_operators/arithmetic.rs +130 -5
- data/rust/src/kernel/expression_operators/presence.rs +41 -3
- 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 +174 -36
- 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/prebuilt/0ee7f83cba59ce7d1c45c65ba166b071779f70143449f584f33ff7989f64eccb.marshal +0 -0
- data/prebuilt/6e4a6bcf0aa880e2e6b056e1846b70e5a768090b9dc998bbadeaf5d6bcfd3db8.marshal +0 -0
- data/prebuilt/verdicts-91aaf93ce8981bd9a754948f075d95650dc2ec6435015376f5c60992b36a24b1.json +0 -1
|
@@ -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 {
|
|
@@ -455,6 +516,16 @@ pub fn lifecycle_transition_for(command: &Json, aggregate: &Json) -> Option<Tran
|
|
|
455
516
|
Some(Transition { field, to_state, from_states, unconstrained })
|
|
456
517
|
}
|
|
457
518
|
|
|
519
|
+
/// Whether a `state(:field)` source reads an `Option` out of the pre-dispatch record: the field
|
|
520
|
+
/// is declared optional, or the record holds every field as an `Option` (`record_optional`).
|
|
521
|
+
/// Such a source already has the target's wrapping, so it must not be wrapped in `Some` again.
|
|
522
|
+
fn state_source_is_option(source: Option<&Json>, aggregate: &Json, record_optional: bool) -> bool {
|
|
523
|
+
let Some(source) = source.filter(|s| s.get("kind").map(Json::to_s).unwrap_or_default() == "state") else { return false };
|
|
524
|
+
let name = source.get("name").map(Json::to_s).unwrap_or_default();
|
|
525
|
+
let attrs = aggregate.get("attributes").map(Json::each).unwrap_or(&[]);
|
|
526
|
+
record_optional || attrs.iter().any(|a| crate::attr::name(a) == name && crate::attr::optional(a))
|
|
527
|
+
}
|
|
528
|
+
|
|
458
529
|
/// Right-hand side for a `set`, coercing the source into the target attribute's declared type.
|
|
459
530
|
///
|
|
460
531
|
/// `target_list` routes list-to-list sets through `list_value_rhs`; `value_rhs` is scalar-only.
|
|
@@ -712,8 +783,12 @@ fn emit_mutation_line_body(
|
|
|
712
783
|
} else {
|
|
713
784
|
let attrs = aggregate.get("attributes").map(Json::each).unwrap_or(&[]);
|
|
714
785
|
let target_attr = attrs.iter().find(|a| crate::attr::name(a) == target_name).expect("set target must be a declared aggregate attribute");
|
|
715
|
-
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));
|
|
716
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
|
+
};
|
|
717
792
|
let source_attr = if source.map(|s| s.get("kind").map(Json::to_s).unwrap_or_default()) == Some("argument".to_string()) {
|
|
718
793
|
let name = source.unwrap().get("name").map(Json::to_s).unwrap_or_default();
|
|
719
794
|
let cmd_attrs = command.get("attributes").map(Json::each).unwrap_or(&[]);
|
|
@@ -731,7 +806,8 @@ fn emit_mutation_line_body(
|
|
|
731
806
|
let (guard, effective_rhs) = entity_list_replace_guard(aggregate, target_attr, &target_field, &rhs, value_objects_by_name);
|
|
732
807
|
format!("{guard}{}", exemplar.render("mutation_set_plain", &[("tmpl_field", target_field.to_string()), ("tmpl_rhs_placeholder2()", effective_rhs)]))
|
|
733
808
|
} else {
|
|
734
|
-
let
|
|
809
|
+
let source_is_option = source_attr.map(crate::attr::optional).unwrap_or(false) || state_source_is_option(source, aggregate, optional);
|
|
810
|
+
let wrap = (optional || crate::attr::optional(target_attr)) && !source_is_option;
|
|
735
811
|
if wrap {
|
|
736
812
|
exemplar.render("mutation_set_wrapped", &[("tmpl_field", target_field.to_string()), ("tmpl_rhs_placeholder2()", rhs)])
|
|
737
813
|
} else {
|
|
@@ -851,3 +927,97 @@ mod transition_tests {
|
|
|
851
927
|
assert_eq!(transition_check_arg(Some(&transition)), "None");
|
|
852
928
|
}
|
|
853
929
|
}
|
|
930
|
+
|
|
931
|
+
#[cfg(test)]
|
|
932
|
+
mod state_source_tests {
|
|
933
|
+
use super::*;
|
|
934
|
+
|
|
935
|
+
fn aggregate() -> Json {
|
|
936
|
+
Json::parse(r#"{"name":"Page","attributes":[{"name":"body","type":"Body"},{"name":"draft_body","type":"Body","optional":true}]}"#).unwrap()
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
fn state(name: &str) -> Json {
|
|
940
|
+
Json::parse(&format!(r#"{{"kind":"state","name":"{name}"}}"#)).unwrap()
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
#[test]
|
|
944
|
+
fn a_state_source_in_an_all_optional_record_is_already_an_option() {
|
|
945
|
+
assert!(state_source_is_option(Some(&state("body")), &aggregate(), true));
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
#[test]
|
|
949
|
+
fn a_state_source_reads_an_option_where_it_is_declared_optional() {
|
|
950
|
+
assert!(state_source_is_option(Some(&state("draft_body")), &aggregate(), false));
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
#[test]
|
|
954
|
+
fn a_required_state_source_in_a_plain_record_is_not_an_option() {
|
|
955
|
+
assert!(!state_source_is_option(Some(&state("body")), &aggregate(), false));
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
#[test]
|
|
959
|
+
fn an_argument_source_is_never_a_state_option() {
|
|
960
|
+
let argument = Json::parse(r#"{"kind":"argument","name":"draft_body"}"#).unwrap();
|
|
961
|
+
assert!(!state_source_is_option(Some(&argument), &aggregate(), true));
|
|
962
|
+
}
|
|
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/naming.rs
CHANGED
|
@@ -70,6 +70,23 @@ pub fn dispatch_fn_name(cmd: &str) -> String {
|
|
|
70
70
|
/// `hecks project_reserved_names` — the same tables `naming.rb` reads.
|
|
71
71
|
pub use crate::reserved_names::{CARGO_RESERVED_DOMAIN_NAMES, RUST_KEYWORDS};
|
|
72
72
|
|
|
73
|
+
/// The modules every generated chapter declares itself, beside its aggregates' modules: the
|
|
74
|
+
/// sidecar `metadata`, the `registry` table, and the `merged` router. An aggregate whose
|
|
75
|
+
/// downcased name is one of these would declare the module twice and overwrite the file.
|
|
76
|
+
pub const CHAPTER_OWNED_MODULES: &[&str] = &["metadata", "registry", "merged"];
|
|
77
|
+
|
|
78
|
+
/// The module (and file stem) one aggregate's generated Rust lives in: its downcased name, or
|
|
79
|
+
/// `<name>_aggregate` when that name is one the chapter declares itself. A downcased name never
|
|
80
|
+
/// holds an underscore, so the suffixed form cannot meet another aggregate's module.
|
|
81
|
+
pub fn aggregate_module(name: &str) -> String {
|
|
82
|
+
let module = name.to_lowercase();
|
|
83
|
+
if CHAPTER_OWNED_MODULES.contains(&module.as_str()) {
|
|
84
|
+
format!("{module}_aggregate")
|
|
85
|
+
} else {
|
|
86
|
+
module
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
73
90
|
/// `/\A[a-z_][a-z0-9_]*\z/` — a plain lowercase Rust identifier.
|
|
74
91
|
fn plain_lower_identifier(name: &str) -> bool {
|
|
75
92
|
let mut chars = name.chars();
|
|
@@ -336,6 +353,19 @@ mod tests {
|
|
|
336
353
|
assert_eq!(scalar_to_value("String", &deref_scalar("String", "v")).as_deref(), Some("Value::Str(v.clone())"));
|
|
337
354
|
}
|
|
338
355
|
|
|
356
|
+
#[test]
|
|
357
|
+
fn an_aggregate_named_for_a_chapter_module_gets_a_suffixed_module() {
|
|
358
|
+
assert_eq!(aggregate_module("Metadata"), "metadata_aggregate");
|
|
359
|
+
assert_eq!(aggregate_module("Registry"), "registry_aggregate");
|
|
360
|
+
assert_eq!(aggregate_module("Merged"), "merged_aggregate");
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
#[test]
|
|
364
|
+
fn any_other_aggregate_keeps_its_downcased_module() {
|
|
365
|
+
assert_eq!(aggregate_module("LineItem"), "lineitem");
|
|
366
|
+
assert_eq!(aggregate_module("MetadataEntry"), "metadataentry");
|
|
367
|
+
}
|
|
368
|
+
|
|
339
369
|
#[test]
|
|
340
370
|
fn refuses_an_aggregate_whose_module_name_is_a_rust_keyword() {
|
|
341
371
|
let refusal = reserved_name_refusal("shop", "shop", &["Pizza", "Match", "Type"]).expect("refused");
|
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());
|
data/rust/codegen/src/types.rs
CHANGED
|
@@ -26,15 +26,15 @@ pub fn emit_check_invariants(exemplar: &Exemplar, vo: &Json, value_objects_by_na
|
|
|
26
26
|
continue;
|
|
27
27
|
}
|
|
28
28
|
let field = naming::rust_ident_field(crate::attr::name(attr));
|
|
29
|
-
|
|
30
|
-
body.push(format!(" for item in &self.{field} {{ item.check_invariants()?; }}"));
|
|
31
|
-
} else {
|
|
32
|
-
body.push(format!(" self.{field}.check_invariants()?;"));
|
|
33
|
-
}
|
|
29
|
+
body.push(nested_invariant_line(&field, crate::attr::list(attr), crate::attr::optional(attr)));
|
|
34
30
|
}
|
|
35
31
|
|
|
36
32
|
for attr in attributes {
|
|
37
33
|
if crate::attr::list(attr) {
|
|
34
|
+
let field = format!("self.{}", naming::rust_ident_field(crate::attr::name(attr)));
|
|
35
|
+
if let Some(line) = crate::constraints::emit_list_pattern_check(exemplar, &field, attr, &name) {
|
|
36
|
+
body.push(format!(" {line}"));
|
|
37
|
+
}
|
|
38
38
|
continue;
|
|
39
39
|
}
|
|
40
40
|
let field = format!("self.{}", naming::rust_ident_field(crate::attr::name(attr)));
|
|
@@ -64,6 +64,18 @@ pub fn emit_check_invariants(exemplar: &Exemplar, vo: &Json, value_objects_by_na
|
|
|
64
64
|
format!("impl {name} {{\n pub fn check_invariants(&self) -> Result<(), crate::kernel::Refusal> {{\n{}\n Ok(())\n }}\n}}\n", body.join("\n"))
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
/// The line that runs a nested value object's own invariants. An optional slot is an `Option` in
|
|
68
|
+
/// the record, so its invariants run only when it is present, as Ruby validates only what it is
|
|
69
|
+
/// given; a list's members each run theirs.
|
|
70
|
+
fn nested_invariant_line(field: &str, list: bool, optional: bool) -> String {
|
|
71
|
+
match (list, optional) {
|
|
72
|
+
(true, true) => format!(" if let Some(items) = &self.{field} {{ for item in items {{ item.check_invariants()?; }} }}"),
|
|
73
|
+
(true, false) => format!(" for item in &self.{field} {{ item.check_invariants()?; }}"),
|
|
74
|
+
(false, true) => format!(" if let Some(value) = &self.{field} {{ value.check_invariants()?; }}"),
|
|
75
|
+
(false, false) => format!(" self.{field}.check_invariants()?;"),
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
67
79
|
pub fn emit_closed_set_table(exemplar: &Exemplar, vo: &Json) -> String {
|
|
68
80
|
let name = naming::rust_ident(vo.get("name").and_then(Json::as_str).unwrap_or(""));
|
|
69
81
|
let attributes = vo.get("attributes").map(Json::each).unwrap_or(&[]);
|
|
@@ -286,6 +298,83 @@ pub fn emit_record(exemplar: &Exemplar, aggregate: &Json, value_objects_by_name:
|
|
|
286
298
|
format!("{struct_part}\n\n{fielded_part}")
|
|
287
299
|
}
|
|
288
300
|
|
|
301
|
+
/// Copy of `ir` whose every `projects` field also carries the `type` of the remote field it reads,
|
|
302
|
+
/// so the projecting aggregate's pseudo-attribute and setter follow it (ADR 0025 addendum). A
|
|
303
|
+
/// single-field value object resolves to its one attribute's type, a reference to `String`, and a
|
|
304
|
+
/// field that resolves to nothing stays `String`.
|
|
305
|
+
pub fn with_resolved_projection_types(ir: &Json) -> Json {
|
|
306
|
+
let mut resolved = ir.clone();
|
|
307
|
+
let aggregates: Vec<Json> = ir.get("aggregates").map(Json::each).unwrap_or(&[]).to_vec();
|
|
308
|
+
let Some(list) = resolved.get_mut("aggregates").and_then(Json::as_array_mut) else {
|
|
309
|
+
return resolved;
|
|
310
|
+
};
|
|
311
|
+
for (aggregate, original) in list.iter_mut().zip(aggregates.iter()) {
|
|
312
|
+
let Some(fields) = aggregate.get_mut("projected_fields").and_then(Json::as_array_mut) else {
|
|
313
|
+
continue;
|
|
314
|
+
};
|
|
315
|
+
for field in fields.iter_mut() {
|
|
316
|
+
let scalar = projected_scalar_type(&aggregates, original, field, 0);
|
|
317
|
+
field.set("type", Json::String(scalar));
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
resolved
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const PROJECTION_CHAIN_LIMIT: usize = 8;
|
|
324
|
+
|
|
325
|
+
// The scalar type a projection of `field` copies, following a chain of projections.
|
|
326
|
+
fn projected_scalar_type(aggregates: &[Json], owner: &Json, field: &Json, depth: usize) -> String {
|
|
327
|
+
let reference = field.get("reference").and_then(Json::as_str).unwrap_or("");
|
|
328
|
+
let remote = field.get("remote_field").and_then(Json::as_str).unwrap_or("");
|
|
329
|
+
let target = owner
|
|
330
|
+
.get("attributes")
|
|
331
|
+
.map(Json::each)
|
|
332
|
+
.unwrap_or(&[])
|
|
333
|
+
.iter()
|
|
334
|
+
.find(|a| crate::attr::name(a) == reference)
|
|
335
|
+
.and_then(|a| naming::reference_target(crate::attr::type_name(a)))
|
|
336
|
+
.and_then(|name| aggregates.iter().find(|a| a.get("name").and_then(Json::as_str) == Some(name)));
|
|
337
|
+
target.map(|t| remote_scalar_type(aggregates, t, remote, depth)).unwrap_or_else(|| "String".to_string())
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
fn remote_scalar_type(aggregates: &[Json], target: &Json, remote: &str, depth: usize) -> String {
|
|
341
|
+
let declared = target.get("attributes").map(Json::each).unwrap_or(&[]).iter().find(|a| crate::attr::name(a) == remote);
|
|
342
|
+
if let Some(attr) = declared {
|
|
343
|
+
return unwrapped_scalar_type(target, crate::attr::type_name(attr));
|
|
344
|
+
}
|
|
345
|
+
let chained = target.get("projected_fields").map(Json::each).unwrap_or(&[]).iter().find(|f| f.get("name").and_then(Json::as_str) == Some(remote));
|
|
346
|
+
match chained {
|
|
347
|
+
Some(next) if depth < PROJECTION_CHAIN_LIMIT => projected_scalar_type(aggregates, target, next, depth + 1),
|
|
348
|
+
_ => "String".to_string(),
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// A single-field value object reads as its one attribute's type; anything else as itself.
|
|
353
|
+
fn unwrapped_scalar_type(aggregate: &Json, type_name: &str) -> String {
|
|
354
|
+
let sole = aggregate
|
|
355
|
+
.get("value_objects")
|
|
356
|
+
.map(Json::each)
|
|
357
|
+
.unwrap_or(&[])
|
|
358
|
+
.iter()
|
|
359
|
+
.find(|vo| vo.get("name").and_then(Json::as_str) == Some(type_name))
|
|
360
|
+
.map(|vo| vo.get("attributes").map(Json::each).unwrap_or(&[]))
|
|
361
|
+
.filter(|attrs| attrs.len() == 1 && !crate::attr::list(&attrs[0]))
|
|
362
|
+
.map(|attrs| crate::attr::type_name(&attrs[0]).to_string());
|
|
363
|
+
let resolved = sole.unwrap_or_else(|| type_name.to_string());
|
|
364
|
+
let resolved = if resolved == "Boolean" { "TrueClass".to_string() } else { resolved };
|
|
365
|
+
naming::effective_scalar_type(&resolved).unwrap_or("String").to_string()
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// The `Value` reader a generated setter narrows with, by scalar type.
|
|
369
|
+
fn projection_reader(scalar: &str) -> &'static str {
|
|
370
|
+
match scalar {
|
|
371
|
+
"Integer" => "into_i64",
|
|
372
|
+
"Float" => "into_f64",
|
|
373
|
+
"TrueClass" | "FalseClass" => "into_bool",
|
|
374
|
+
_ => "into_string",
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
289
378
|
/// Port of the retired Ruby generator's `types.rb#projected_field_pseudo_attributes` (ADR 0025).
|
|
290
379
|
pub fn projected_field_pseudo_attributes(aggregate: &Json) -> Vec<Json> {
|
|
291
380
|
aggregate
|
|
@@ -295,9 +384,10 @@ pub fn projected_field_pseudo_attributes(aggregate: &Json) -> Vec<Json> {
|
|
|
295
384
|
.iter()
|
|
296
385
|
.map(|field| {
|
|
297
386
|
let name = field.get("name").and_then(Json::as_str).unwrap_or("").to_string();
|
|
387
|
+
let scalar = field.get("type").and_then(Json::as_str).unwrap_or("String").to_string();
|
|
298
388
|
Json::Object(vec![
|
|
299
389
|
("name".to_string(), Json::String(name)),
|
|
300
|
-
("type".to_string(), Json::String(
|
|
390
|
+
("type".to_string(), Json::String(scalar)),
|
|
301
391
|
("list".to_string(), Json::Bool(false)),
|
|
302
392
|
("optional".to_string(), Json::Bool(true)),
|
|
303
393
|
])
|
|
@@ -329,11 +419,12 @@ pub fn emit_set_projected_field(aggregate: &Json) -> String {
|
|
|
329
419
|
.map(|field| {
|
|
330
420
|
let field_name = field.get("name").and_then(Json::as_str).unwrap_or("");
|
|
331
421
|
let ident = naming::rust_ident_field(field_name);
|
|
332
|
-
|
|
422
|
+
let reader = projection_reader(field.get("type").and_then(Json::as_str).unwrap_or("String"));
|
|
423
|
+
format!(" {} => self.{} = value.and_then(crate::kernel::Value::{}),", naming::ruby_inspect_string(field_name), ident, reader)
|
|
333
424
|
})
|
|
334
425
|
.collect();
|
|
335
426
|
format!(
|
|
336
|
-
"impl crate::kernel::SetProjectedField for {name} {{\n fn set_projected_field(&mut self, name: &'static str, value: Option<
|
|
427
|
+
"impl crate::kernel::SetProjectedField for {name} {{\n fn set_projected_field(&mut self, name: &'static str, value: Option<crate::kernel::Value>) {{\n match name {{\n{}\n _ => {{}}\n }}\n }}\n}}\n",
|
|
337
428
|
arms.join("\n")
|
|
338
429
|
)
|
|
339
430
|
}
|
|
@@ -404,4 +495,71 @@ mod tests {
|
|
|
404
495
|
assert!(!from_json.contains("TrueClass::from_json"));
|
|
405
496
|
assert!(to_json.contains("crate::kernel::Json::Bool(self.on)"));
|
|
406
497
|
}
|
|
498
|
+
|
|
499
|
+
/// An Event whose `starts_at` is a single-field Integer value object, and a Booking projecting it.
|
|
500
|
+
fn event_and_booking() -> Json {
|
|
501
|
+
Json::parse(
|
|
502
|
+
r#"{"aggregates":[
|
|
503
|
+
{"name":"Event","attributes":[{"name":"starts_at","type":"StartsAt","list":false}],
|
|
504
|
+
"value_objects":[{"name":"StartsAt","attributes":[{"name":"value","type":"Integer","list":false}]}]},
|
|
505
|
+
{"name":"Booking","attributes":[{"name":"event","type":"Reference<Event>","list":false}],
|
|
506
|
+
"projected_fields":[{"name":"starts_at","reference":"event","remote_field":"starts_at"}]}
|
|
507
|
+
]}"#,
|
|
508
|
+
)
|
|
509
|
+
.unwrap()
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
fn resolved_booking() -> Json {
|
|
513
|
+
with_resolved_projection_types(&event_and_booking()).get("aggregates").map(Json::each).unwrap_or(&[])[1].clone()
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
#[test]
|
|
517
|
+
fn a_projection_through_a_single_field_value_object_takes_its_scalar_type() {
|
|
518
|
+
let field = resolved_booking().get("projected_fields").map(Json::each).unwrap_or(&[])[0].clone();
|
|
519
|
+
|
|
520
|
+
assert_eq!(field.get("type").and_then(Json::as_str), Some("Integer"));
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
#[test]
|
|
524
|
+
fn the_projected_pseudo_attribute_follows_the_remote_type() {
|
|
525
|
+
let attrs = projected_field_pseudo_attributes(&resolved_booking());
|
|
526
|
+
|
|
527
|
+
assert_eq!(crate::attr::type_name(&attrs[0]), "Integer");
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
#[test]
|
|
531
|
+
fn the_projected_setter_narrows_with_the_remote_types_reader() {
|
|
532
|
+
let setter = emit_set_projected_field(&resolved_booking());
|
|
533
|
+
|
|
534
|
+
assert!(setter.contains("\"starts_at\" => self.starts_at = value.and_then(crate::kernel::Value::into_i64),"));
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/// A value object holding a required and an optional value object, and a required and an
|
|
538
|
+
/// optional list of them.
|
|
539
|
+
fn holder_value_object() -> Json {
|
|
540
|
+
Json::parse(
|
|
541
|
+
r#"{
|
|
542
|
+
"name":"Holder",
|
|
543
|
+
"attributes":[
|
|
544
|
+
{"name":"required_note","type":"Note","list":false,"optional":false},
|
|
545
|
+
{"name":"note","type":"Note","list":false,"optional":true},
|
|
546
|
+
{"name":"required_notes","type":"Note","list":true,"optional":false},
|
|
547
|
+
{"name":"notes","type":"Note","list":true,"optional":true}
|
|
548
|
+
]
|
|
549
|
+
}"#,
|
|
550
|
+
)
|
|
551
|
+
.unwrap()
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
#[test]
|
|
555
|
+
fn a_nested_value_object_checks_its_invariants_only_when_it_is_present() {
|
|
556
|
+
let note = Json::parse(r#"{"name":"Note","attributes":[{"name":"text","type":"String","list":false,"optional":false}]}"#).unwrap();
|
|
557
|
+
let by_name: HashMap<String, &Json> = HashMap::from([("Note".to_string(), ¬e)]);
|
|
558
|
+
let generated = emit_check_invariants(&Exemplar::load(), &holder_value_object(), &by_name, &HashMap::new());
|
|
559
|
+
|
|
560
|
+
assert!(generated.contains("self.required_note.check_invariants()?;"));
|
|
561
|
+
assert!(generated.contains("if let Some(value) = &self.note { value.check_invariants()?; }"));
|
|
562
|
+
assert!(generated.contains("for item in &self.required_notes { item.check_invariants()?; }"));
|
|
563
|
+
assert!(generated.contains("if let Some(items) = &self.notes { for item in items { item.check_invariants()?; } }"));
|
|
564
|
+
}
|
|
407
565
|
}
|
data/rust/host/HECKS_RELEASE
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
3.
|
|
1
|
+
3.11.0
|
data/rust/host/src/api.rs
CHANGED
|
@@ -1089,8 +1089,8 @@ mod tests {
|
|
|
1089
1089
|
}
|
|
1090
1090
|
|
|
1091
1091
|
#[test]
|
|
1092
|
-
fn
|
|
1093
|
-
// `
|
|
1092
|
+
fn a_record_that_does_not_exist_refuses_with_json_adapters_own_wording() {
|
|
1093
|
+
// `JSON_ADAPTER.find!`'s own message, `id` inspected the way Ruby
|
|
1094
1094
|
// inspects it.
|
|
1095
1095
|
let refusal = not_found(&format!("no {} found for id {:?}", "Client", "acme"));
|
|
1096
1096
|
assert_eq!(body(&refusal), json!({"error": "NotFound", "message": "no Client found for id \"acme\""}));
|
|
@@ -1365,7 +1365,7 @@ mod tests {
|
|
|
1365
1365
|
fn every_state_styled() -> Value {
|
|
1366
1366
|
json!({"states": {
|
|
1367
1367
|
"Event": {"open": {"tone": "good"}, "closed": {"tone": "muted"}},
|
|
1368
|
-
"Registration": {"active": {}, "archived": {"tone": "muted"}},
|
|
1368
|
+
"Registration": {"active": {}, "archived": {"tone": "muted"}, "cancelled": {"tone": "muted"}},
|
|
1369
1369
|
"PaymentConnection": {"connected": {}, "enabled": {}, "disconnected": {}, "paused": {}}
|
|
1370
1370
|
}})
|
|
1371
1371
|
}
|
|
@@ -1590,7 +1590,7 @@ mod tests {
|
|
|
1590
1590
|
assert!(!refused["message"].as_str().expect("a message").is_empty(), "{refused}");
|
|
1591
1591
|
|
|
1592
1592
|
// An unknown command, and an unknown record, both refuse the
|
|
1593
|
-
// way
|
|
1593
|
+
// way Json refuses them.
|
|
1594
1594
|
let unknown_command =
|
|
1595
1595
|
command_route(&ir, "customers", "CUST-9001", "nope", "", &client, &wasm, &lineage, &invoker).await;
|
|
1596
1596
|
assert_eq!(unknown_command["statusCode"], 404);
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
//! Rust side of the stored-value differential: runs the mint audit's structural layer
|
|
2
|
+
//! (`reference_validate::validate`) over each `{aggregate, id, state}` case on stdin against one
|
|
3
|
+
//! domain IR, and prints the violations of each. `spec/rust_host_reference_validate_conformance_spec.rb`
|
|
4
|
+
//! holds those to the answers the Ruby runtime gives the same values.
|
|
5
|
+
|
|
6
|
+
#[allow(dead_code)]
|
|
7
|
+
#[path = "../expr_json.rs"]
|
|
8
|
+
mod expr_json;
|
|
9
|
+
#[allow(dead_code)]
|
|
10
|
+
#[path = "../reference_validate.rs"]
|
|
11
|
+
mod reference_validate;
|
|
12
|
+
|
|
13
|
+
use serde_json::{json, Value};
|
|
14
|
+
use std::io::Read;
|
|
15
|
+
|
|
16
|
+
// Usage: `validate_harness < {"ir": <domain ir.json>, "cases": [{"aggregate": "Name", "id": "x", "state": {..}}, ..]}`.
|
|
17
|
+
// Prints `{"results": [{"violations": [..]} | {"error": ".."}, ..]}`, one per case, in order.
|
|
18
|
+
fn main() -> anyhow::Result<()> {
|
|
19
|
+
let mut input = String::new();
|
|
20
|
+
std::io::stdin().read_to_string(&mut input)?;
|
|
21
|
+
let request: Value = serde_json::from_str(&input)?;
|
|
22
|
+
let ir = request.get("ir").ok_or_else(|| anyhow::anyhow!("no \"ir\" on stdin"))?;
|
|
23
|
+
let cases = request.get("cases").and_then(Value::as_array).ok_or_else(|| anyhow::anyhow!("no \"cases\" array on stdin"))?;
|
|
24
|
+
|
|
25
|
+
let results: Vec<Value> = cases.iter().map(|case| answer(ir, case)).collect();
|
|
26
|
+
println!("{}", json!({ "results": results }));
|
|
27
|
+
Ok(())
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
fn answer(ir: &Value, case: &Value) -> Value {
|
|
31
|
+
let name = case.get("aggregate").and_then(Value::as_str).unwrap_or("");
|
|
32
|
+
let aggregate = ir
|
|
33
|
+
.get("aggregates")
|
|
34
|
+
.and_then(Value::as_array)
|
|
35
|
+
.and_then(|list| list.iter().find(|a| a.get("name").and_then(Value::as_str) == Some(name)));
|
|
36
|
+
match aggregate {
|
|
37
|
+
None => json!({ "error": format!("no aggregate {name:?} in the IR") }),
|
|
38
|
+
Some(aggregate) => {
|
|
39
|
+
let id = case.get("id").and_then(Value::as_str).unwrap_or("");
|
|
40
|
+
json!({ "violations": reference_validate::validate(ir, aggregate, id, &case["state"]) })
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -8,7 +8,7 @@ use crate::fuzz_support::*;
|
|
|
8
8
|
use serde_json::{json, Value};
|
|
9
9
|
|
|
10
10
|
const BINARY: &[&str] = &["or", "and", "compare", "include", "add", "modulo"];
|
|
11
|
-
const UNARY: &[&str] = &["not", "sign_test", "empty", "to_s", "size", "presence", "assignment", "first", "last", "split", "starts_with", "ends_with", "matches_regex"];
|
|
11
|
+
const UNARY: &[&str] = &["not", "sign_test", "empty", "to_s", "size", "presence", "assignment", "first", "last", "split", "strip", "starts_with", "ends_with", "matches_regex"];
|
|
12
12
|
|
|
13
13
|
const PATTERNS: &[&str] = &[
|
|
14
14
|
"", "^$", "a", "(a*)*b", "(a|aa)+$", "[", "]", "[]", "[^]", "[a-", "(", ")", "(?", "(?P<x>", "\\", "\\d+", "\\D\\W\\S\\H", "\\Z", "[\\d]", "[\\Z]", "\\p{L}+", "\\p{Nope}",
|
|
@@ -43,7 +43,7 @@ fn node(rng: &mut Rng, depth: usize) -> Value {
|
|
|
43
43
|
}
|
|
44
44
|
4..=7 => {
|
|
45
45
|
let op = *rng.pick(UNARY);
|
|
46
|
-
let mut value = json!({"op": op, "receiver": node(rng, depth + 1), "expr": node(rng, depth + 1), "negated": rng.chance(2), "separator": random_text(rng), "substring": random_text(rng), "pattern": *rng.pick(PATTERNS), "flags": *rng.pick(&["", "i", "m", "x", "imx", "z"])});
|
|
46
|
+
let mut value = json!({"op": op, "receiver": node(rng, depth + 1), "expr": node(rng, depth + 1), "negated": rng.chance(2), "separator": random_text(rng), "side": *rng.pick(&["both", "left", "right", "sideways"]), "substring": random_text(rng), "pattern": *rng.pick(PATTERNS), "flags": *rng.pick(&["", "i", "m", "x", "imx", "z"])});
|
|
47
47
|
value["cmp"] = json!({"less_than": rng.chance(2), "equal": rng.chance(2), "negated": rng.chance(2)});
|
|
48
48
|
value
|
|
49
49
|
}
|
|
@@ -50,6 +50,7 @@ fn every_provider_reader_answers_any_ir_shape() {
|
|
|
50
50
|
let _ = newsletter_provider(&ir).map(|p| p.instance_prefix());
|
|
51
51
|
let _ = newsletter_issues_provider(&ir).map(|p| p.issue_prefix());
|
|
52
52
|
let _ = payments_provider(&ir).map(|p| p.instance_prefix());
|
|
53
|
+
let _ = checkout_windows(&ir);
|
|
53
54
|
if let Some(p) = registrations_provider(&ir) {
|
|
54
55
|
let _ = (p.event_prefix(), p.registration_prefix(), p.request_target());
|
|
55
56
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//! Seeded fuzz of the HTTP front
|
|
1
|
+
//! Seeded fuzz of the HTTP front entry point (`admit`, `value_to_response`): hostile bodies, methods, URIs
|
|
2
2
|
//! and headers must be answered, never panic, and a refused request must leave no trace.
|
|
3
3
|
//! Everything here is in-process: `admit` is the whole of what runs before dispatch, so none of
|
|
4
4
|
//! it needs Postgres or wasm.
|