hecks 3.10.0 → 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 +18 -2
- 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/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 +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/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 +10 -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/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/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 +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/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/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 +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 +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/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 +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 +140 -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
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.
|
data/rust/host/src/checkout.rs
CHANGED
|
@@ -7,10 +7,6 @@ use sha2::Sha256;
|
|
|
7
7
|
|
|
8
8
|
type HmacSha256 = Hmac<Sha256>;
|
|
9
9
|
|
|
10
|
-
// docs.stripe.com/webhooks#verify-events — 5 minutes, guards against a
|
|
11
|
-
// replayed old payload even against a leaked (not yet rotated) secret.
|
|
12
|
-
const TOLERANCE_SECONDS: i64 = 300;
|
|
13
|
-
|
|
14
10
|
pub struct SignatureError(pub String);
|
|
15
11
|
|
|
16
12
|
impl std::fmt::Display for SignatureError {
|
|
@@ -22,7 +18,10 @@ impl std::fmt::Display for SignatureError {
|
|
|
22
18
|
// Signs `<timestamp>.<raw body>`, the exact bytes Stripe sent -- re-serializing
|
|
23
19
|
// the JSON would disagree on whitespace or key order and break every match.
|
|
24
20
|
// `now` is a parameter, not read internally, so a test can hold time fixed.
|
|
25
|
-
|
|
21
|
+
// `tolerance_seconds` is the freshness window the Checkout bluebook declares
|
|
22
|
+
// (`WebhookReceipt.tolerance`, read from the IR): it guards against a replayed
|
|
23
|
+
// old payload even against a leaked (not yet rotated) secret.
|
|
24
|
+
pub fn verify_signature(payload: &str, header: &str, secret: &str, now: i64, tolerance_seconds: i64) -> Result<(), SignatureError> {
|
|
26
25
|
let mut timestamp: Option<i64> = None;
|
|
27
26
|
let mut signatures: Vec<&str> = Vec::new();
|
|
28
27
|
for part in header.split(',') {
|
|
@@ -39,9 +38,9 @@ pub fn verify_signature(payload: &str, header: &str, secret: &str, now: i64) ->
|
|
|
39
38
|
if signatures.is_empty() {
|
|
40
39
|
return Err(SignatureError("Stripe-Signature header has no v1= signature".to_string()));
|
|
41
40
|
}
|
|
42
|
-
if (now - timestamp).abs() >
|
|
41
|
+
if (now - timestamp).abs() > tolerance_seconds {
|
|
43
42
|
return Err(SignatureError(format!(
|
|
44
|
-
"timestamp {timestamp} is outside the {
|
|
43
|
+
"timestamp {timestamp} is outside the {tolerance_seconds}s tolerance (now: {now})"
|
|
45
44
|
)));
|
|
46
45
|
}
|
|
47
46
|
|
|
@@ -98,11 +97,19 @@ pub struct EmbeddedSession {
|
|
|
98
97
|
pub client_secret: String,
|
|
99
98
|
}
|
|
100
99
|
|
|
101
|
-
// 31, not 30: Stripe requires an expiry at least 30 minutes out.
|
|
102
|
-
|
|
100
|
+
// 31, not 30: Stripe requires an expiry at least 30 minutes out. This floor is a
|
|
101
|
+
// fact about the processor, so it lives here in the adapter; the domain declares
|
|
102
|
+
// only how long a session holds its seat (`CheckoutSession.hold`), and a hold
|
|
103
|
+
// shorter than the floor is raised to it.
|
|
104
|
+
pub const STRIPE_MIN_HOLD_SECONDS: i64 = 31 * 60;
|
|
105
|
+
|
|
106
|
+
/// The hold actually sent to Stripe for a domain hold of `hold_seconds`.
|
|
107
|
+
pub fn effective_hold_seconds(hold_seconds: i64) -> i64 {
|
|
108
|
+
hold_seconds.max(STRIPE_MIN_HOLD_SECONDS)
|
|
109
|
+
}
|
|
103
110
|
|
|
104
|
-
pub fn session_expires_at(now: i64) -> i64 {
|
|
105
|
-
now +
|
|
111
|
+
pub fn session_expires_at(now: i64, hold_seconds: i64) -> i64 {
|
|
112
|
+
now + effective_hold_seconds(hold_seconds)
|
|
106
113
|
}
|
|
107
114
|
|
|
108
115
|
// Errors carry Stripe's message when it refuses, never a secret.
|
|
@@ -158,6 +165,8 @@ pub async fn create_checkout_session(
|
|
|
158
165
|
mod tests {
|
|
159
166
|
use super::*;
|
|
160
167
|
|
|
168
|
+
const TOLERANCE_SECONDS: i64 = 300;
|
|
169
|
+
|
|
161
170
|
fn sign(secret: &str, timestamp: i64, payload: &str) -> String {
|
|
162
171
|
let signed_payload = format!("{timestamp}.{payload}");
|
|
163
172
|
let mut mac = HmacSha256::new_from_slice(secret.as_bytes()).unwrap();
|
|
@@ -195,7 +204,7 @@ mod tests {
|
|
|
195
204
|
let now = 1_700_000_000;
|
|
196
205
|
let header = format!("t={now},v1={}", sign(secret, now, payload));
|
|
197
206
|
|
|
198
|
-
assert!(verify_signature(payload, &header, secret, now).is_ok());
|
|
207
|
+
assert!(verify_signature(payload, &header, secret, now, TOLERANCE_SECONDS).is_ok());
|
|
199
208
|
}
|
|
200
209
|
|
|
201
210
|
#[test]
|
|
@@ -207,7 +216,7 @@ mod tests {
|
|
|
207
216
|
// Same header, different body — exactly what an attacker
|
|
208
217
|
// intercepting and rewriting the request in transit would send.
|
|
209
218
|
let tampered = r#"{"type":"checkout.session.expired"}"#;
|
|
210
|
-
assert!(verify_signature(tampered, &header, secret, now).is_err());
|
|
219
|
+
assert!(verify_signature(tampered, &header, secret, now, TOLERANCE_SECONDS).is_err());
|
|
211
220
|
}
|
|
212
221
|
|
|
213
222
|
#[test]
|
|
@@ -216,7 +225,7 @@ mod tests {
|
|
|
216
225
|
let now = 1_700_000_000;
|
|
217
226
|
let header = format!("t={now},v1={}", sign("whsec_real", now, payload));
|
|
218
227
|
|
|
219
|
-
assert!(verify_signature(payload, &header, "whsec_wrong", now).is_err());
|
|
228
|
+
assert!(verify_signature(payload, &header, "whsec_wrong", now, TOLERANCE_SECONDS).is_err());
|
|
220
229
|
}
|
|
221
230
|
|
|
222
231
|
#[test]
|
|
@@ -231,7 +240,14 @@ mod tests {
|
|
|
231
240
|
// to be a separate, independent gate rather than folded into
|
|
232
241
|
// "does the signature verify at all."
|
|
233
242
|
let much_later = signed_at + TOLERANCE_SECONDS + 1;
|
|
234
|
-
assert!(verify_signature(payload, &header, secret, much_later).is_err());
|
|
243
|
+
assert!(verify_signature(payload, &header, secret, much_later, TOLERANCE_SECONDS).is_err());
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
#[test]
|
|
247
|
+
fn a_hold_shorter_than_stripes_floor_is_raised_to_it() {
|
|
248
|
+
assert_eq!(effective_hold_seconds(1800), STRIPE_MIN_HOLD_SECONDS);
|
|
249
|
+
assert_eq!(effective_hold_seconds(3600), 3600);
|
|
250
|
+
assert_eq!(session_expires_at(1_000, 1800), 1_000 + STRIPE_MIN_HOLD_SECONDS);
|
|
235
251
|
}
|
|
236
252
|
|
|
237
253
|
#[test]
|
|
@@ -246,20 +262,20 @@ mod tests {
|
|
|
246
262
|
sign(new_secret, now, payload)
|
|
247
263
|
);
|
|
248
264
|
|
|
249
|
-
assert!(verify_signature(payload, &header, old_secret, now).is_ok());
|
|
250
|
-
assert!(verify_signature(payload, &header, new_secret, now).is_ok());
|
|
265
|
+
assert!(verify_signature(payload, &header, old_secret, now, TOLERANCE_SECONDS).is_ok());
|
|
266
|
+
assert!(verify_signature(payload, &header, new_secret, now, TOLERANCE_SECONDS).is_ok());
|
|
251
267
|
}
|
|
252
268
|
|
|
253
269
|
#[test]
|
|
254
270
|
fn a_header_with_no_v1_at_all_is_rejected() {
|
|
255
271
|
let now = 1_700_000_000;
|
|
256
272
|
let header = format!("t={now}");
|
|
257
|
-
assert!(verify_signature("{}", &header, "whsec_test", now).is_err());
|
|
273
|
+
assert!(verify_signature("{}", &header, "whsec_test", now, TOLERANCE_SECONDS).is_err());
|
|
258
274
|
}
|
|
259
275
|
|
|
260
276
|
#[test]
|
|
261
277
|
fn a_header_with_no_timestamp_at_all_is_rejected() {
|
|
262
278
|
let header = "v1=deadbeef".to_string();
|
|
263
|
-
assert!(verify_signature("{}", &header, "whsec_test", 1_700_000_000).is_err());
|
|
279
|
+
assert!(verify_signature("{}", &header, "whsec_test", 1_700_000_000, TOLERANCE_SECONDS).is_err());
|
|
264
280
|
}
|
|
265
281
|
}
|