hecks 3.10.0 → 3.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/examples/pizzas/bluebook/pizzas.bluebook +29 -0
- data/examples/pizzas/bluebook/pizzas.hecksagon +7 -0
- data/exe/hecks +4 -4
- data/lib/hecks/adapters/driven/d1.rb +2 -10
- data/lib/hecks/adapters/driven/heki.adapter +1 -1
- data/lib/hecks/adapters/driven/heki.rb +2 -12
- data/lib/hecks/adapters/driven/lambda.adapter +1 -1
- data/lib/hecks/adapters/driven/lambda.rb +3 -18
- data/lib/hecks/adapters/driven/postgres.rb +4 -11
- data/lib/hecks/adapters/driven/sqlite.adapter +1 -1
- data/lib/hecks/adapters/driven/sqlite.rb +2 -10
- data/lib/hecks/adapters/driving/admission.rb +46 -0
- data/lib/hecks/adapters/driving/cli/nesting.rb +36 -0
- data/lib/hecks/adapters/driving/cli/shorthand.rb +88 -0
- data/lib/hecks/adapters/driving/cli.rb +127 -0
- data/lib/hecks/adapters/driving/cli_runner/answers.rb +160 -0
- data/lib/hecks/adapters/driving/cli_runner/resolution.rb +158 -0
- data/lib/hecks/adapters/driving/cli_runner/settling.rb +120 -0
- data/lib/hecks/adapters/driving/cli_runner/streaming.rb +93 -0
- data/lib/hecks/adapters/driving/cli_runner/suggestions.rb +47 -0
- data/lib/hecks/adapters/driving/cli_runner.rb +70 -0
- data/lib/hecks/adapters/driving/command_request.rb +121 -0
- data/lib/hecks/adapters/driving/handle.rb +214 -0
- data/lib/hecks/adapters/driving/json.rb +136 -0
- data/lib/hecks/adapters/driving/launcher_options.rb +267 -0
- data/lib/hecks/adapters/driving/mcp/properties.rb +49 -0
- data/lib/hecks/adapters/driving/mcp/tool_calls.rb +116 -0
- data/lib/hecks/adapters/driving/mcp/tool_definitions/auditing.rb +53 -0
- data/lib/hecks/adapters/driving/mcp/tool_definitions/calling.rb +71 -0
- data/lib/hecks/adapters/driving/mcp/tool_definitions/reading.rb +99 -0
- data/lib/hecks/adapters/driving/mcp/tool_definitions.rb +18 -0
- data/lib/hecks/adapters/driving/mcp.rb +159 -0
- data/lib/hecks/adapters/driving/mcp_guide_cache.rb +78 -0
- data/lib/hecks/adapters/driving/mcp_scope/argument_policy.rb +97 -0
- data/lib/hecks/adapters/driving/mcp_scope/env_problems.rb +69 -0
- data/lib/hecks/adapters/driving/mcp_scope/presenter.rb +83 -0
- data/lib/hecks/adapters/driving/mcp_scope.rb +213 -0
- data/lib/hecks/adapters/driving/newcomer.rb +50 -0
- data/lib/hecks/adapters/driving/ruby/aggregate_module.rb +171 -0
- data/lib/hecks/adapters/driving/ruby/chapter.rb +91 -0
- data/lib/hecks/adapters/driving/ruby.rb +83 -0
- data/lib/hecks/adapters/driving/usage_cache.rb +144 -0
- data/lib/hecks/{doors.rb → adapters/driving.rb} +10 -6
- data/lib/hecks/behaviors/expectations/boot.rb +1 -1
- data/lib/hecks/bench/ruby_runner.rb +1 -1
- data/lib/hecks/bluebook/ask_resolution.rb +130 -0
- data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +8 -2
- data/lib/hecks/bluebook/assembly/contracts/read_models.rb +1 -1
- data/lib/hecks/bluebook/assembly/contracts/rules.rb +4 -2
- data/lib/hecks/bluebook/assembly/contracts/structure.rb +3 -1
- data/lib/hecks/bluebook/assembly/marks.rb +4 -15
- data/lib/hecks/bluebook/assembly/scalars.rb +39 -0
- data/lib/hecks/bluebook/attribute.rb +1 -1
- data/lib/hecks/bluebook/behaviour/command.rb +13 -8
- data/lib/hecks/bluebook/behaviour/policy.rb +30 -0
- data/lib/hecks/bluebook/capabilities/attribute_values.rb +72 -0
- data/lib/hecks/bluebook/capabilities.rb +91 -9
- data/lib/hecks/bluebook/domain_port.rb +16 -4
- data/lib/hecks/bluebook/dsl/adapter_builder.rb +18 -6
- data/lib/hecks/bluebook/dsl/binding_proxy.rb +13 -0
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/computed_sources.rb +98 -0
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/fan_out_rows.rb +42 -0
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/projected_fields.rb +14 -2
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/provisions.rb +69 -3
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/with_projections.rb +17 -10
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +5 -0
- data/lib/hecks/bluebook/dsl/bootstrap_table.rb +2 -0
- data/lib/hecks/bluebook/dsl/command_builder/mutations.rb +8 -1
- data/lib/hecks/bluebook/dsl/command_builder/operands.rb +121 -0
- data/lib/hecks/bluebook/dsl/command_builder.rb +2 -0
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +10 -1
- data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +45 -2
- data/lib/hecks/bluebook/dsl/policy_builder.rb +47 -10
- data/lib/hecks/bluebook/dsl/rule_reference.rb +7 -4
- data/lib/hecks/bluebook/dsl/world_builder.rb +1 -1
- data/lib/hecks/bluebook/expression/ast_json/emitters.rb +6 -0
- data/lib/hecks/bluebook/expression/ast_json.rb +9 -6
- data/lib/hecks/bluebook/expression/ast_reader.rb +14 -9
- data/lib/hecks/bluebook/expression/projection.json +36 -0
- data/lib/hecks/bluebook/expression/resolver/operations.rb +31 -0
- data/lib/hecks/bluebook/expression/resolver/parsing.rb +38 -5
- data/lib/hecks/bluebook/expression/resolver/scanning.rb +22 -5
- data/lib/hecks/bluebook/expression/resolver/values.rb +10 -0
- data/lib/hecks/bluebook/expression/resolver.rb +20 -1
- data/lib/hecks/bluebook/hecksagon.rb +23 -3
- data/lib/hecks/bluebook/lifecycle.rb +22 -3
- data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +13 -1
- data/lib/hecks/bluebook/meta_validator/modes.rb +4 -1
- data/lib/hecks/bluebook/meta_validator/readings.rb +24 -1
- data/lib/hecks/bluebook/meta_validator/reconstruction/heads.rb +2 -1
- data/lib/hecks/bluebook/meta_validator/reconstruction.rb +6 -3
- data/lib/hecks/bluebook/meta_validator/shapes.rb +5 -0
- data/lib/hecks/bluebook/meta_validator/verdict_cache.rb +1 -1
- data/lib/hecks/bluebook/meta_validator.rb +12 -12
- data/lib/hecks/bluebook/model_check/ask_checks.rb +71 -0
- data/lib/hecks/bluebook/model_check/policy_checks.rb +2 -1
- data/lib/hecks/bluebook/model_check/reference_checks.rb +70 -0
- data/lib/hecks/bluebook/model_check.rb +8 -2
- data/lib/hecks/bluebook/policy.rb +4 -2
- data/lib/hecks/bluebook/smoke_test.rb +1 -1
- data/lib/hecks/bluebook/synthesizer.rb +2 -0
- data/lib/hecks/bluebook.rb +2 -0
- data/lib/hecks/cli/command_table.rb +1 -1
- data/lib/hecks/cli/console.rb +2 -1
- data/lib/hecks/cli/document.rb +1 -1
- data/lib/hecks/cli/launcher_words.rb +4 -4
- data/lib/hecks/cli/mcp.rb +4 -4
- data/lib/hecks/cli/project_cli/launcher_source.rb +2 -2
- data/lib/hecks/cli/project_cli/templates.rb +5 -5
- data/lib/hecks/cli/project_cli.rb +9 -6
- data/lib/hecks/cli/run.rb +2 -2
- data/lib/hecks/cli/smoke_http/checks.rb +4 -1
- data/lib/hecks/cli/smoke_http/transport.rb +3 -3
- data/lib/hecks/cli/smoke_http.rb +3 -0
- data/lib/hecks/codemod.rb +5 -2
- data/lib/hecks/corpus/routes.rb +7 -0
- data/lib/hecks/corpus/rust.rb +3 -2
- data/lib/hecks/deploy/bluebook/deploy.bluebook +34 -4
- data/lib/hecks/doc/reference/rendering.rb +6 -3
- data/lib/hecks/forms/app/commands.rb +8 -7
- data/lib/hecks/forms/app/queries.rb +2 -1
- data/lib/hecks/forms/app.rb +1 -1
- data/lib/hecks/forms/record_renderer.rb +1 -1
- data/lib/hecks/framework/bluebook/privacy.bluebook +11 -0
- data/lib/hecks/fuzzing/concurrent_dispatch.rb +4 -1
- data/lib/hecks/fuzzing/failure_triage.rb +2 -1
- data/lib/hecks/fuzzing/mutation/run.rb +11 -4
- data/lib/hecks/fuzzing/properties/catalog.rb +6 -4
- data/lib/hecks/fuzzing/properties/guaranteed_by_construction.rb +18 -11
- data/lib/hecks/fuzzing/properties/guards.rb +1 -1
- data/lib/hecks/fuzzing/self_consistency/saga_redelivery.rb +1 -1
- data/lib/hecks/fuzzing/self_consistency.rb +6 -2
- data/lib/hecks/fuzzing/shrinker.rb +3 -1
- data/lib/hecks/fuzzing/value_generator.rb +19 -5
- data/lib/hecks/gate/stages.yml +33 -7
- data/lib/hecks/grammar/evolve.rb +1 -1
- data/lib/hecks/grammar/expression_operators.json +372 -0
- data/lib/hecks/grammar/operators.rb +3 -1
- data/lib/hecks/hecks/adapters/codebase/corpus_tasks.rb +10 -10
- data/lib/hecks/hecks/adapters/codebase/inspection/rubycritic.rb +35 -0
- data/lib/hecks/hecks/adapters/codebase/inspection.rb +104 -0
- data/lib/hecks/hecks/adapters/codebase/publishing.rb +2 -1
- data/lib/hecks/hecks/adapters/codebase/release_facts.rb +12 -2
- data/lib/hecks/hecks/adapters/codebase/source_tree.rb +2 -1
- data/lib/hecks/hecks/adapters/codebase/test_runner.rb +1 -1
- data/lib/hecks/hecks/adapters/cost_explorer.rb +8 -5
- data/lib/hecks/hecks/adapters/deploy_toolchain/statuses.rb +2 -1
- data/lib/hecks/hecks/adapters/finding_github.rb +13 -6
- data/lib/hecks/hecks/adapters/first_admin.rb +2 -2
- data/lib/hecks/hecks/adapters/github_rulesets.rb +6 -3
- data/lib/hecks/hecks/adapters/in_process_boot.rb +1 -1
- data/lib/hecks/hecks/adapters/journal_store/compaction.rb +1 -1
- data/lib/hecks/hecks/adapters/journal_store/edge_audit.rb +10 -20
- data/lib/hecks/hecks/adapters/journal_store/held_domain.rb +28 -5
- data/lib/hecks/hecks/adapters/local_files.rb +2 -2
- data/lib/hecks/hecks/adapters/rehearsal/audit_report.rb +107 -0
- data/lib/hecks/hecks/adapters/rehearsal/census.rb +71 -0
- data/lib/hecks/hecks/adapters/rehearsal/declaration.rb +133 -0
- data/lib/hecks/hecks/adapters/rehearsal/environment.rb +83 -0
- data/lib/hecks/hecks/adapters/rehearsal/inquiry.rb +142 -0
- data/lib/hecks/hecks/adapters/rehearsal/memory_store.rb +96 -0
- data/lib/hecks/hecks/adapters/rehearsal/postgres_census.rb +100 -0
- data/lib/hecks/hecks/adapters/rehearsal/postgres_reading.rb +69 -0
- data/lib/hecks/hecks/adapters/rehearsal/postgres_store.rb +133 -0
- data/lib/hecks/hecks/adapters/rehearsal/rows.rb +41 -0
- data/lib/hecks/hecks/adapters/rehearsal/rust_audit.rb +83 -0
- data/lib/hecks/hecks/adapters/rehearsal/subject.rb +104 -0
- data/lib/hecks/hecks/adapters/rehearsal.rb +54 -0
- data/lib/hecks/hecks/adapters/rehearsal_store.adapter +7 -0
- data/lib/hecks/hecks/adapters/rehearsal_store.rb +81 -0
- data/lib/hecks/hecks/adapters/terminal.rb +13 -11
- data/lib/hecks/hecks/codebase.behaviors +13 -1
- data/lib/hecks/hecks/codebase.bluebook +239 -5
- data/lib/hecks/hecks/custodian.behaviors +24 -6
- data/lib/hecks/hecks/custodian.bluebook +6 -1227
- data/lib/hecks/hecks/deploy.behaviors +1 -1
- data/lib/hecks/hecks/deploy.world +1 -3
- data/lib/hecks/hecks/hecks.behaviors +1 -1
- data/lib/hecks/hecks/hecks.hecksagon +53 -18
- data/lib/hecks/hecks/hecks.world +13 -10
- data/lib/hecks/hecks/rehearsal.bluebook +477 -0
- data/lib/hecks/hecks/site.behaviors +1 -1
- data/lib/hecks/hecks/site.world +0 -1
- data/lib/hecks/hecks/tooling.bluebook +1238 -0
- data/lib/hecks/indifferent_key.rb +20 -0
- data/lib/hecks/language/adapter.bluebook +13 -3
- data/lib/hecks/language/bluebook/aggregate.bluebook +23 -0
- data/lib/hecks/language/bluebook/bluebook.behaviors +23 -0
- data/lib/hecks/language/bluebook/bluebook.bluebook +15 -0
- data/lib/hecks/language/bluebook/entity.bluebook +18 -0
- data/lib/hecks/language/bluebook/policy.behaviors +19 -0
- data/lib/hecks/language/bluebook/policy.bluebook +9 -1
- data/lib/hecks/language/bluebook/query.bluebook +1 -1
- data/lib/hecks/language/bluebook/vocabulary.bluebook +63 -4
- data/lib/hecks/language/hecksagon/domain_port.bluebook +1 -1
- data/lib/hecks/language/hecksagon/hecksagon.bluebook +22 -0
- data/lib/hecks/language/oidc.json +10 -0
- data/lib/hecks/language/port.bluebook +4 -4
- data/lib/hecks/literal.rb +12 -3
- data/lib/hecks/ports/key_vault.rb +7 -2
- data/lib/hecks/ports/persistence/plugins/era/era_check.rb +21 -10
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/boot.rb +4 -2
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +2 -1
- data/lib/hecks/ports/persistence/repository_factory.rb +6 -3
- data/lib/hecks/projection_files/expression_tables.rb +7 -2
- data/lib/hecks/projections/deploy/box/compose.rb +6 -4
- data/lib/hecks/projections/deploy/box/edge.rb +62 -0
- data/lib/hecks/projections/deploy/box/logging.rb +99 -0
- data/lib/hecks/projections/deploy/box/roll_scripts.rb +16 -8
- data/lib/hecks/projections/deploy/box/settings/cdn.rb +40 -0
- data/lib/hecks/projections/deploy/box/settings/logs.rb +40 -0
- data/lib/hecks/projections/deploy/box/settings/patterns.rb +11 -0
- data/lib/hecks/projections/deploy/box/settings/types.rb +16 -1
- data/lib/hecks/projections/deploy/box/settings.rb +6 -2
- data/lib/hecks/projections/deploy/box/stack_yaml.rb +5 -2
- data/lib/hecks/projections/deploy/box/templates/Makefile.tmpl +16 -0
- data/lib/hecks/projections/deploy/box/templates/box.yaml.tmpl +8 -4
- data/lib/hecks/projections/deploy/box/templates/deploy-box.sh.tmpl +32 -0
- data/lib/hecks/projections/deploy/box/templates/deploy-service.sh.tmpl +1 -1
- data/lib/hecks/projections/deploy/box/templates/edge.mk.tmpl +17 -0
- data/lib/hecks/projections/deploy/box/templates/edge.yaml.tmpl +65 -0
- data/lib/hecks/projections/deploy/box/templates/log-group.yaml.tmpl +12 -0
- data/lib/hecks/projections/deploy/box/templates/log-policy.yaml.tmpl +7 -0
- data/lib/hecks/projections/deploy/box/templates/render-compose-taskdef.sh.tmpl +5 -4
- data/lib/hecks/projections/deploy/box/templates/render-compose.sh.tmpl +5 -4
- data/lib/hecks/projections/deploy/box.rb +15 -3
- data/lib/hecks/projections/deploy/preview/preview.sh.erb +1 -1
- data/lib/hecks/projections/deploy/shared.rb +5 -2
- data/lib/hecks/projections/model/deviations.rb +2 -2
- data/lib/hecks/projections/model.rb +2 -2
- data/lib/hecks/projections/oidc.rb +13 -1
- data/lib/hecks/projections/shape.rb +5 -2
- data/lib/hecks/projections/site/cms_editor/attributes.rb +26 -6
- data/lib/hecks/projections/site/cms_editor/blocks.rb +157 -0
- data/lib/hecks/projections/site/cms_editor/checks.rb +14 -1
- data/lib/hecks/projections/site/cms_editor/clearing.rb +70 -0
- data/lib/hecks/projections/site/cms_editor/color.rb +124 -0
- data/lib/hecks/projections/site/cms_editor/composition.rb +42 -0
- data/lib/hecks/projections/site/cms_editor/destructive.rb +61 -0
- data/lib/hecks/projections/site/cms_editor/drafts.rb +102 -0
- data/lib/hecks/projections/site/cms_editor/hints.rb +74 -0
- data/lib/hecks/projections/site/cms_editor/icons.rb +86 -0
- data/lib/hecks/projections/site/cms_editor/key_kinds.rb +31 -0
- data/lib/hecks/projections/site/cms_editor/limits.rb +72 -0
- data/lib/hecks/projections/site/cms_editor/media.rb +1 -1
- data/lib/hecks/projections/site/cms_editor/moment.rb +34 -0
- data/lib/hecks/projections/site/cms_editor/palette.rb +115 -0
- data/lib/hecks/projections/site/cms_editor/pickers.rb +85 -0
- data/lib/hecks/projections/site/cms_editor/preview.rb +91 -0
- data/lib/hecks/projections/site/cms_editor/relations.rb +92 -0
- data/lib/hecks/projections/site/cms_editor/scale_checks.rb +66 -0
- data/lib/hecks/projections/site/cms_editor/scheduling.rb +150 -0
- data/lib/hecks/projections/site/cms_editor/schema.rb +96 -19
- data/lib/hecks/projections/site/cms_editor/setting.rb +52 -9
- data/lib/hecks/projections/site/cms_editor/targets.rb +86 -0
- data/lib/hecks/projections/site/cms_editor/templates/package.json.tmpl +18 -1
- data/lib/hecks/projections/site/cms_editor/templates/src/app.ts.tmpl +251 -57
- data/lib/hecks/projections/site/cms_editor/templates/src/assets.ts.tmpl +98 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/auth/membership.ts.tmpl +32 -24
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/app.css.tmpl +412 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/autosave.js.tmpl +144 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/blocks.js.tmpl +500 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/body_doc.js.tmpl +175 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/body_editor.js.tmpl +418 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/epoch.js.tmpl +61 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/forms.js.tmpl +210 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/lists.js.tmpl +40 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/main.js.tmpl +26 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/media_picker.js.tmpl +232 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/moments.js.tmpl +73 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/pickers.js.tmpl +152 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/preview.js.tmpl +98 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/shell.js.tmpl +140 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/toast.js.tmpl +66 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/choices.ts.tmpl +99 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/commands.ts.tmpl +78 -24
- data/lib/hecks/projections/site/cms_editor/templates/src/config.ts.tmpl +6 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/flash.ts.tmpl +52 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/host.ts.tmpl +19 -2
- data/lib/hecks/projections/site/cms_editor/templates/src/media/handler.ts.tmpl +31 -8
- data/lib/hecks/projections/site/cms_editor/templates/src/names.ts.tmpl +36 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/schema.ts.tmpl +131 -2
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/availability.ts.tmpl +15 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/block_list.js.tmpl +213 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/blocks.ts.tmpl +275 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/body_model.js.tmpl +8 -5
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/detail.ts.tmpl +88 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/dialogs.ts.tmpl +43 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/fields.ts.tmpl +229 -34
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/html.ts.tmpl +40 -81
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/icons.ts.tmpl +15 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/input.ts.tmpl +29 -12
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/links.ts.tmpl +100 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/moments.ts.tmpl +44 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/outline.ts.tmpl +53 -17
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/pages.ts.tmpl +178 -88
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/paging.ts.tmpl +82 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/pickers.ts.tmpl +110 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/preview.ts.tmpl +87 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/related.ts.tmpl +97 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/schedule.ts.tmpl +160 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/shell.ts.tmpl +211 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/table.ts.tmpl +151 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/theme.js.tmpl +16 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/values.ts.tmpl +69 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/words.ts.tmpl +97 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/world.ts.tmpl +35 -0
- data/lib/hecks/projections/site/cms_editor/templates/tsconfig.json.tmpl +2 -1
- data/lib/hecks/projections/site/cms_editor/theme.rb +29 -0
- data/lib/hecks/projections/site/cms_editor/theme_checks.rb +57 -0
- data/lib/hecks/projections/site/cms_editor.rb +49 -23
- data/lib/hecks/projections/site/role_probe.rb +2 -0
- data/lib/hecks/projections/site/site_root/workflow.rb +5 -0
- data/lib/hecks/projector/cli_audience.rb +22 -1
- data/lib/hecks/projector/cli_projector/listing.rb +1 -1
- data/lib/hecks/projector/cli_projector/option_specs.rb +1 -1
- data/lib/hecks/projector/cli_projector/usage.rb +15 -4
- data/lib/hecks/projector/docs_projector/closing.rb +1 -1
- data/lib/hecks/projector/docs_projector.rb +1 -1
- data/lib/hecks/projector/exporter/bindings.rb +40 -10
- data/lib/hecks/projector/exporter/capability_values.rb +55 -0
- data/lib/hecks/projector/narrate_projector/reactions.rb +1 -1
- data/lib/hecks/projector/target.rb +11 -0
- data/lib/hecks/projector.rb +32 -5
- data/lib/hecks/quality_control/adapters/github_checks.rb +12 -8
- data/lib/hecks/quality_control/adapters/github_issues.rb +8 -4
- data/lib/hecks/quality_control/cli/qa_open_pr.rb +2 -2
- data/lib/hecks/quality_control/cli/qa_sweep/checks/shrinking.rb +1 -1
- data/lib/hecks/quality_control/cli/qa_sweep/finding_report.rb +1 -1
- data/lib/hecks/quality_control/cli/qa_sweep.rb +1 -1
- data/lib/hecks/quality_control/quality_control.bluebook +6 -2
- data/lib/hecks/release/approval.rb +64 -0
- data/lib/hecks/router/{door.rb → aggregate_reader.rb} +5 -4
- data/lib/hecks/router/namespace_installer.rb +16 -16
- data/lib/hecks/router.rb +1 -1
- data/lib/hecks/runtime/best_effort.rb +22 -0
- data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +12 -2
- data/lib/hecks/runtime/command_interpreter.rb +3 -2
- data/lib/hecks/runtime/command_rules/emission.rb +1 -1
- data/lib/hecks/runtime/dispatcher.rb +4 -4
- data/lib/hecks/runtime/entity_element/location.rb +1 -1
- data/lib/hecks/runtime/event.rb +5 -0
- data/lib/hecks/runtime/identity.rb +7 -6
- data/lib/hecks/runtime/interpreting.rb +2 -1
- data/lib/hecks/runtime/invocation_builder.rb +1 -1
- data/lib/hecks/runtime/loader/boot_steps.rb +8 -5
- data/lib/hecks/runtime/loader.rb +10 -10
- data/lib/hecks/runtime/policy_interpreter/arguments.rb +7 -4
- data/lib/hecks/runtime/policy_interpreter/fan_out.rb +3 -3
- data/lib/hecks/runtime/policy_interpreter.rb +6 -6
- data/lib/hecks/runtime/port_operation_interpreter.rb +1 -1
- data/lib/hecks/runtime/reaction_invocation.rb +1 -1
- data/lib/hecks/runtime/rebuild_sweep.rb +11 -1
- data/lib/hecks/runtime/registry/ask_verification.rb +35 -0
- data/lib/hecks/runtime/registry/hecksagon_rules.rb +10 -0
- data/lib/hecks/runtime/registry/merging.rb +1 -1
- data/lib/hecks/runtime/registry/providers.rb +16 -0
- data/lib/hecks/runtime/registry/read_repository.rb +6 -4
- data/lib/hecks/runtime/registry/verification.rb +4 -1
- data/lib/hecks/runtime/registry/world_defaults.rb +29 -0
- data/lib/hecks/runtime/registry.rb +1 -1
- data/lib/hecks/runtime/saga_interpreter/compensation.rb +1 -1
- data/lib/hecks/runtime/saga_interpreter/delivery.rb +4 -4
- data/lib/hecks/runtime/saga_interpreter/transitions.rb +1 -1
- data/lib/hecks/runtime/saga_interpreter.rb +3 -3
- data/lib/hecks/runtime/tenant_check.rb +5 -4
- data/lib/hecks/runtime/value/coercion.rb +2 -2
- data/lib/hecks/runtime/value/validation.rb +16 -4
- data/lib/hecks/runtime.rb +9 -9
- data/lib/hecks/rust_build/host.rb +11 -4
- data/lib/hecks/rust_build/prebuilt_binary.rb +101 -0
- data/lib/hecks/rust_build/project_rust/target_ir.rb +1 -1
- data/lib/hecks/rust_build/wasm.rb +1 -1
- data/lib/hecks/rust_build/wasm_browser.rb +1 -1
- data/lib/hecks/rust_build.rb +11 -0
- data/lib/hecks/served_options.rb +53 -0
- data/lib/hecks/site/bluebook/site.bluebook +1 -0
- data/lib/hecks/storehouse/audit.rb +1 -1
- data/lib/hecks/storehouse/dispatching.rb +8 -6
- data/lib/hecks/storehouse/introspection.rb +2 -2
- data/lib/hecks/storehouse/querying.rb +6 -4
- data/lib/hecks/storehouse.rb +4 -4
- data/lib/hecks/tools/ci_gate_decision.rb +35 -0
- data/lib/hecks/tools/ci_gates.rb +20 -1
- data/lib/hecks/tools/comment_style/source_file.rb +4 -1
- data/lib/hecks/tools/deploy_recipe_lint/fixtures.rb +4 -1
- data/lib/hecks/tools/lanes/files.rb +8 -0
- data/lib/hecks/tools/lanes/prebuilt_workflow.rb +81 -0
- data/lib/hecks/tools/lanes/rows.rb +6 -0
- data/lib/hecks/tools/lanes.rb +1 -0
- data/lib/hecks/tools/oidc_manifests.rb +4 -5
- data/lib/hecks/tools/regeneration_run.rb +11 -4
- data/lib/hecks/tools/release_only.rb +111 -0
- data/lib/hecks/tools/site_routes/editor_files.rb +12 -5
- data/lib/hecks/tools/tenant_provisioning.rb +2 -2
- data/lib/hecks/tools/tools_doc.rb +4 -4
- data/lib/hecks/version.rb +1 -1
- data/lib/hecks/vocabulary.rb +19 -5
- data/lib/hecks.rb +15 -15
- data/lib/rubocop/cop/hecks/env_dump.rb +69 -0
- data/lib/rubocop/cop/hecks/monotonic_duration.rb +53 -0
- data/lib/rubocop/cop/hecks/no_wall_clock_in_runtime.rb +34 -0
- data/lib/rubocop/cop/hecks/raw_shell_interpolation.rb +79 -0
- data/lib/rubocop/cop/hecks/sleep_in_spec.rb +34 -0
- data/lib/rubocop/cop/hecks/swallowed_rescue.rb +37 -0
- data/lib/rubocop/cop/hecks/symbol_string_key_mix.rb +50 -0
- data/lib/rubocop/cop/hecks/untyped_raise.rb +37 -0
- data/prebuilt/9ee54e2f0d4d6d3fe046827779b221e02de665fba911a048ae5dd8b8fbfb7d90.marshal +0 -0
- data/prebuilt/d74a00e996222c1d8b676d5959092effc46b4dc59e0555f3bf9554ac58e3094a.marshal +0 -0
- data/prebuilt/verdicts-df19f35868d3284a70f55e42dcef87ef4e65e6a678e5135f72e1f6cff4c1357b.json +1 -0
- data/rust/.gitignore +6 -0
- data/rust/Cargo.toml +6 -0
- data/rust/build/src/binding_pass.rs +103 -0
- data/rust/build/src/cargo_sync.rs +6 -0
- data/rust/build/src/lineage_pass.rs +3 -3
- data/rust/build/src/main.rs +17 -5
- data/rust/build/src/pipeline.rs +48 -5
- data/rust/build/src/seams_pass.rs +123 -0
- data/rust/build/src/translation_pass.rs +167 -0
- data/rust/codegen/src/asks.rs +103 -0
- data/rust/codegen/src/commands.rs +13 -8
- data/rust/codegen/src/constraints.rs +25 -0
- data/rust/codegen/src/domain_generator.rs +2 -0
- data/rust/codegen/src/expr_emitter.rs +13 -0
- data/rust/codegen/src/json_codec.rs +54 -15
- data/rust/codegen/src/main.rs +1 -0
- data/rust/codegen/src/mutations.rs +128 -3
- data/rust/codegen/src/queries.rs +11 -2
- data/rust/codegen/src/reactions.rs +1 -1
- data/rust/codegen/src/types.rs +166 -8
- data/rust/host/HECKS_RELEASE +1 -1
- data/rust/host/src/api.rs +4 -4
- data/rust/host/src/bin/validate_harness.rs +43 -0
- data/rust/host/src/boundary_fuzz/expr_json.rs +2 -2
- data/rust/host/src/boundary_fuzz/ir.rs +1 -0
- data/rust/host/src/boundary_fuzz/server.rs +1 -1
- data/rust/host/src/checkout.rs +35 -19
- data/rust/host/src/commerce_ir.rs +408 -2
- data/rust/host/src/expr_json.rs +118 -0
- data/rust/host/src/extension.rs +13 -1
- data/rust/host/src/lib.rs +1 -0
- data/rust/host/src/mint.rs +1 -1
- data/rust/host/src/payments/tests.rs +27 -1
- data/rust/host/src/payments.rs +12 -4
- data/rust/host/src/platform_signin/attack_tests.rs +1100 -0
- data/rust/host/src/platform_signin/config.rs +512 -0
- data/rust/host/src/platform_signin/cookie.rs +237 -0
- data/rust/host/src/platform_signin/extension.rs +190 -0
- data/rust/host/src/platform_signin/fetcher.rs +322 -0
- data/rust/host/src/platform_signin/handler.rs +251 -0
- data/rust/host/src/platform_signin/handler_tests.rs +554 -0
- data/rust/host/src/platform_signin/jti.rs +196 -0
- data/rust/host/src/platform_signin/jwks.rs +542 -0
- data/rust/host/src/platform_signin/jws.rs +249 -0
- data/rust/host/src/platform_signin/known_answer_tests.rs +237 -0
- data/rust/host/src/platform_signin/mapping_tests.rs +254 -0
- data/rust/host/src/platform_signin/mod.rs +75 -0
- data/rust/host/src/platform_signin/mode_tests.rs +382 -0
- data/rust/host/src/platform_signin/pages.rs +204 -0
- data/rust/host/src/platform_signin/query.rs +233 -0
- data/rust/host/src/platform_signin/session.rs +151 -0
- data/rust/host/src/platform_signin/settings.rs +385 -0
- data/rust/host/src/platform_signin/strict_json.rs +351 -0
- data/rust/host/src/platform_signin/test_support.rs +235 -0
- data/rust/host/src/platform_signin/ttl_tests.rs +94 -0
- data/rust/host/src/platform_signin/verify.rs +604 -0
- data/rust/host/src/platform_signin/wiring_support.rs +319 -0
- data/rust/host/src/query_step.rs +22 -4
- data/rust/host/src/reference_validate.rs +653 -108
- data/rust/host/src/resend.rs +2 -0
- data/rust/host/src/server.rs +19 -6
- data/rust/host/src/ui_schema.rs +132 -5
- data/rust/host/src/web/newsletter.rs +7 -8
- data/rust/host/src/web/newsletter_send.rs +38 -13
- data/rust/host/src/web/registrations.rs +229 -26
- data/rust/host/src/web.rs +53 -9
- data/rust/parser/src/emit.rs +38 -6
- data/rust/parser/src/expr/ast_json.rs +7 -1
- data/rust/parser/src/expr/resolver.rs +171 -6
- data/rust/parser/src/ir.rs +7 -0
- data/rust/parser/src/keywords.rs +24 -0
- data/rust/parser/src/main.rs +3 -0
- data/rust/parser/src/parse/aggregate.rs +2 -1
- data/rust/parser/src/parse/chapter.rs +2 -2
- data/rust/parser/src/parse/command.rs +2 -0
- data/rust/parser/src/parse/hecksagon.rs +5 -2
- data/rust/parser/src/parse/lifecycle.rs +81 -0
- data/rust/parser/src/parse/mod.rs +15 -1
- data/rust/parser/src/parse/policy.rs +13 -2
- data/rust/parser/src/parse/value_object.rs +34 -17
- data/rust/parser/src/ruby_value.rs +1 -1
- data/rust/src/exemplar/commands.rs +4 -4
- data/rust/src/exemplar/constraints.rs +1 -1
- data/rust/src/exemplar/json.rs +43 -1
- data/rust/src/kernel/cli.rs +7 -5
- data/rust/src/kernel/dispatch.rs +2 -2
- data/rust/src/kernel/dispatch_guard_tests.rs +1 -1
- data/rust/src/kernel/expr.rs +66 -3
- data/rust/src/kernel/expression_operators/arithmetic.rs +130 -5
- data/rust/src/kernel/expression_operators/text.rs +26 -2
- data/rust/src/kernel/json.rs +15 -0
- data/rust/src/kernel/mod.rs +1 -1
- data/rust/src/kernel/orchestrate.rs +54 -5
- data/rust/src/kernel/orchestrate_guard_tests.rs +67 -0
- data/rust/src/kernel/pattern.rs +60 -0
- data/rust/src/kernel/reference_lookup.rs +90 -7
- metadata +178 -38
- data/lib/hecks/doors/cli_door/nesting.rb +0 -34
- data/lib/hecks/doors/cli_door/shorthand.rb +0 -85
- data/lib/hecks/doors/cli_door.rb +0 -124
- data/lib/hecks/doors/cli_runner/answers.rb +0 -138
- data/lib/hecks/doors/cli_runner/resolution.rb +0 -146
- data/lib/hecks/doors/cli_runner/settling.rb +0 -115
- data/lib/hecks/doors/cli_runner/streaming.rb +0 -90
- data/lib/hecks/doors/cli_runner/suggestions.rb +0 -43
- data/lib/hecks/doors/cli_runner.rb +0 -66
- data/lib/hecks/doors/command_request.rb +0 -119
- data/lib/hecks/doors/handle.rb +0 -208
- data/lib/hecks/doors/json_door.rb +0 -132
- data/lib/hecks/doors/launcher_options.rb +0 -247
- data/lib/hecks/doors/mcp_door/properties.rb +0 -47
- data/lib/hecks/doors/mcp_door/tool_calls.rb +0 -113
- data/lib/hecks/doors/mcp_door/tool_definitions/auditing.rb +0 -51
- data/lib/hecks/doors/mcp_door/tool_definitions/calling.rb +0 -69
- data/lib/hecks/doors/mcp_door/tool_definitions/reading.rb +0 -97
- data/lib/hecks/doors/mcp_door/tool_definitions.rb +0 -15
- data/lib/hecks/doors/mcp_door.rb +0 -156
- data/lib/hecks/doors/mcp_door_scope/argument_policy.rb +0 -92
- data/lib/hecks/doors/mcp_door_scope/env_problems.rb +0 -55
- data/lib/hecks/doors/mcp_door_scope/presenter.rb +0 -78
- data/lib/hecks/doors/mcp_door_scope.rb +0 -201
- data/lib/hecks/doors/mcp_guide_cache.rb +0 -75
- data/lib/hecks/doors/ruby_door/aggregate_door.rb +0 -162
- data/lib/hecks/doors/ruby_door/chapter.rb +0 -89
- data/lib/hecks/doors/ruby_door.rb +0 -80
- data/lib/hecks/doors/usage_cache.rb +0 -139
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/body_widget.js.tmpl +0 -282
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/media_picker.js.tmpl +0 -109
- data/prebuilt/c28d6d9db14eea3e17161e9d9c262efee30b256cf97693a7cfd552bce24b2f7f.marshal +0 -0
- data/prebuilt/cb375cba61ca0dbe8ddb9739c306510fe05e5f4b7a9b6955caff8224c63ea0e0.marshal +0 -0
- data/prebuilt/verdicts-d6d584ad43b5f3cebf88aafbbdbf024c38574acf18e06adffbc555f7786b1c2f.json +0 -1
|
@@ -34,17 +34,15 @@ fn payment_processor(read: &Value, payments: &PaymentsProvider, reference: &str)
|
|
|
34
34
|
.and_then(|(_, payment)| payment.get("processor").and_then(|p| p.get("value")).and_then(|v| v.as_str()).map(String::from))
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
///
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
fn registration_archived(registration: &Value) -> bool {
|
|
47
|
-
registration.get("status").and_then(|s| s.as_str()) == Some(ARCHIVED_REGISTRATION_STATUS)
|
|
37
|
+
// Payment statuses whose registration still holds a seat come from the
|
|
38
|
+
// payment lifecycle's `holds_seat` mark (`PaymentsProvider::holds_seat`). A
|
|
39
|
+
// registration with no Payment, or one in a state that frees its seat, holds none.
|
|
40
|
+
|
|
41
|
+
/// Whether a registration's own lifecycle state is one the chapter declares gives its seat
|
|
42
|
+
/// back (`RegistrationsProvider::frees_seat`). A missing or non-text `status` is active.
|
|
43
|
+
fn registration_frees_seat(registration: &Value, frees_seat: &[String]) -> bool {
|
|
44
|
+
let status = registration.get("status").and_then(|s| s.as_str());
|
|
45
|
+
status.is_some_and(|status| frees_seat.iter().any(|freed| freed == status))
|
|
48
46
|
}
|
|
49
47
|
|
|
50
48
|
/// A reference to an aggregate reads back as its plain id, or wrapped as
|
|
@@ -55,17 +53,24 @@ fn plain_id(raw: Option<&Value>) -> Option<String> {
|
|
|
55
53
|
value.as_str().filter(|id| !id.is_empty()).map(String::from)
|
|
56
54
|
}
|
|
57
55
|
|
|
58
|
-
/// Seats used per event slug:
|
|
56
|
+
/// Seats used per event slug: Registrations not in a seat-freeing state whose Payment (same
|
|
59
57
|
/// reference) is in a seat-holding status.
|
|
60
58
|
fn seat_counts(read: &Value, domain: &str, payments: &PaymentsProvider) -> std::collections::HashMap<String, usize> {
|
|
59
|
+
let binding = crate::commerce_ir::registrations_binding(domain);
|
|
60
|
+
count_seats(read, &binding, payments)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/// The seat count of `seat_counts` against an explicit registrations binding.
|
|
64
|
+
fn count_seats(read: &Value, binding: &crate::commerce_ir::RegistrationsProvider, payments: &PaymentsProvider) -> std::collections::HashMap<String, usize> {
|
|
61
65
|
let statuses: std::collections::HashMap<String, String> = instances_for(read, &payments.instance_prefix())
|
|
62
66
|
.into_iter()
|
|
63
67
|
.filter_map(|(id, payment)| payment.get("status").and_then(|s| s.as_str()).map(|s| (id, s.to_string())))
|
|
64
68
|
.collect();
|
|
65
69
|
let mut counts = std::collections::HashMap::new();
|
|
66
|
-
for (id, registration) in instances_for(read, &
|
|
67
|
-
let holds = statuses.get(&id).is_some_and(|status|
|
|
68
|
-
|
|
70
|
+
for (id, registration) in instances_for(read, &binding.registration_prefix()) {
|
|
71
|
+
let holds = statuses.get(&id).is_some_and(|status| payments.holds_seat.iter().any(|held| held == status));
|
|
72
|
+
let freed = registration_frees_seat(®istration, &binding.frees_seat);
|
|
73
|
+
if let (true, false, Some(event_slug)) = (holds, freed, plain_id(registration.get("event_slug"))) {
|
|
69
74
|
*counts.entry(event_slug).or_insert(0) += 1;
|
|
70
75
|
}
|
|
71
76
|
}
|
|
@@ -82,6 +87,27 @@ fn event_capacity(event: &Value) -> Option<i64> {
|
|
|
82
87
|
event.get("capacity").and_then(|c| c.get("value")).and_then(|v| v.as_i64())
|
|
83
88
|
}
|
|
84
89
|
|
|
90
|
+
/// A whole-number field read as a bare number or as a single-field value
|
|
91
|
+
/// object's `{"value": N}`; `None` for anything else, so an unreadable value
|
|
92
|
+
/// never blocks a registration.
|
|
93
|
+
fn whole_number(field: Option<&Value>) -> Option<i64> {
|
|
94
|
+
let field = field?;
|
|
95
|
+
field.as_i64().or_else(|| field.get("value").and_then(|v| v.as_i64()))
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/// Whether an event's registration window has shut because its start is
|
|
99
|
+
/// nearer than `registration_cutoff_hours` (Unix seconds, whole hours).
|
|
100
|
+
/// Refused only when both `starts_at` and `registration_cutoff_hours` are
|
|
101
|
+
/// readable and `now_secs + hours * 3600 > starts_at`; exactly at the
|
|
102
|
+
/// cutoff is still open. Either field absent, null or unreadable means no
|
|
103
|
+
/// cutoff, the stance the seat check takes.
|
|
104
|
+
fn registration_cutoff_passed(event: &Value, now_secs: i64) -> bool {
|
|
105
|
+
let (Some(starts_at), Some(hours)) = (whole_number(event.get("starts_at")), whole_number(event.get("registration_cutoff_hours"))) else {
|
|
106
|
+
return false;
|
|
107
|
+
};
|
|
108
|
+
now_secs.saturating_add(hours.saturating_mul(3600)) > starts_at
|
|
109
|
+
}
|
|
110
|
+
|
|
85
111
|
/// Seats still open given `taken`, never below zero.
|
|
86
112
|
fn open_seats(capacity: i64, taken: usize) -> i64 {
|
|
87
113
|
(capacity - taken as i64).max(0)
|
|
@@ -419,6 +445,22 @@ pub(crate) async fn registrations_route(
|
|
|
419
445
|
config: &LineageConfig,
|
|
420
446
|
invoker: &dyn LambdaInvoker,
|
|
421
447
|
payments: &PaymentsProvider,
|
|
448
|
+
) -> Value {
|
|
449
|
+
registrations_route_at(raw_body, platform, client, wasm_path, config, invoker, payments, unix_now()).await
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// The route with the wall clock passed in, so a test can fix the time the
|
|
453
|
+
// registration cutoff is measured against.
|
|
454
|
+
#[allow(clippy::too_many_arguments)]
|
|
455
|
+
pub(crate) async fn registrations_route_at(
|
|
456
|
+
raw_body: &str,
|
|
457
|
+
platform: &payments::PlatformConfig,
|
|
458
|
+
client: &Mutex<Client>,
|
|
459
|
+
wasm_path: &Path,
|
|
460
|
+
config: &LineageConfig,
|
|
461
|
+
invoker: &dyn LambdaInvoker,
|
|
462
|
+
payments: &PaymentsProvider,
|
|
463
|
+
now_secs: i64,
|
|
422
464
|
) -> Value {
|
|
423
465
|
let body: Value = match serde_json::from_str(raw_body) {
|
|
424
466
|
Ok(v) => v,
|
|
@@ -448,6 +490,11 @@ pub(crate) async fn registrations_route(
|
|
|
448
490
|
if event.get("status").and_then(|v| v.as_str()) != Some("open") {
|
|
449
491
|
return respond(422, "application/json", &json!({"error": "registration is closed for this event"}).to_string());
|
|
450
492
|
}
|
|
493
|
+
// An event that opted into a cutoff stops taking registrations that many
|
|
494
|
+
// hours before it starts; a missing or unreadable field is no cutoff.
|
|
495
|
+
if registration_cutoff_passed(event, now_secs) {
|
|
496
|
+
return respond(422, "application/json", &json!({"error": "registration has closed for this event"}).to_string());
|
|
497
|
+
}
|
|
451
498
|
// A full event refuses before any Stripe call or write. An event whose
|
|
452
499
|
// capacity cannot be read is not blocked: capacity is required when an
|
|
453
500
|
// event is scheduled, so its absence means nothing to enforce.
|
|
@@ -477,7 +524,7 @@ pub(crate) async fn registrations_route(
|
|
|
477
524
|
// follows, the unused session simply expires.
|
|
478
525
|
let embedded_checkout = if let payments::CheckoutPlan::Stripe { api_key, publishable_key } = &plan {
|
|
479
526
|
let auth = checkout::StripeAuth { api_key, base_url: &platform.api_base };
|
|
480
|
-
match checkout::create_checkout_session(&auth, price_cents, event_name, &reference, checkout::session_expires_at(
|
|
527
|
+
match checkout::create_checkout_session(&auth, price_cents, event_name, &reference, checkout::session_expires_at(now_secs, crate::commerce_ir::checkout_windows_binding().session_hold)).await {
|
|
481
528
|
// Stripe.js is opened with the publishable key; the answer carries
|
|
482
529
|
// no `checkout_url`.
|
|
483
530
|
Ok(session) => Some(json!({
|
|
@@ -611,9 +658,10 @@ pub(crate) async fn webhook_route(
|
|
|
611
658
|
return mock_secret_refused();
|
|
612
659
|
}
|
|
613
660
|
let candidates = if configured.is_empty() { vec![MOCK_STRIPE_WEBHOOK_SECRET] } else { configured };
|
|
661
|
+
let tolerance = crate::commerce_ir::checkout_windows_binding().webhook_tolerance;
|
|
614
662
|
let mut verification = Ok(());
|
|
615
663
|
for secret in &candidates {
|
|
616
|
-
verification = checkout::verify_signature(raw_body, signature_header, secret, now);
|
|
664
|
+
verification = checkout::verify_signature(raw_body, signature_header, secret, now, tolerance);
|
|
617
665
|
if verification.is_ok() {
|
|
618
666
|
break;
|
|
619
667
|
}
|
|
@@ -671,7 +719,7 @@ pub(crate) async fn webhook_route(
|
|
|
671
719
|
}
|
|
672
720
|
"checkout.session.expired" => Some((payments.failed.as_str(), json!({
|
|
673
721
|
"reference": reference_fact,
|
|
674
|
-
"reason": {"value":
|
|
722
|
+
"reason": {"value": payments.lapse_reason},
|
|
675
723
|
"reported_processor": reported_processor,
|
|
676
724
|
}))),
|
|
677
725
|
_ => None,
|
|
@@ -749,7 +797,7 @@ mod tests {
|
|
|
749
797
|
|
|
750
798
|
#[test]
|
|
751
799
|
fn a_registration_holds_a_seat_while_its_payment_is_open_paid_or_being_settled() {
|
|
752
|
-
let payments =
|
|
800
|
+
let payments = four_state_payments();
|
|
753
801
|
let read = read_with(
|
|
754
802
|
10,
|
|
755
803
|
&[("a", "yoga", "pending"), ("b", "yoga", "succeeded"), ("c", "yoga", "refunding"), ("d", "yoga", "disputed")],
|
|
@@ -788,6 +836,49 @@ mod tests {
|
|
|
788
836
|
assert_eq!(seats_left(&read, "CheckoutFixture", &payments, "yoga"), Some(1));
|
|
789
837
|
}
|
|
790
838
|
|
|
839
|
+
fn binding_freeing(states: &[&str]) -> crate::commerce_ir::RegistrationsProvider {
|
|
840
|
+
let mut binding = crate::commerce_ir::RegistrationsProvider::conventional("CheckoutFixture");
|
|
841
|
+
binding.frees_seat = states.iter().map(|s| s.to_string()).collect();
|
|
842
|
+
binding
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
#[test]
|
|
846
|
+
fn a_declared_state_other_than_archived_frees_its_seat() {
|
|
847
|
+
let payments = crate::commerce_ir::fixture_payments();
|
|
848
|
+
let mut read = read_with(3, &[("a", "yoga", "succeeded"), ("b", "yoga", "succeeded")]);
|
|
849
|
+
set_registration_status(&mut read, "a", "cancelled");
|
|
850
|
+
let counts = count_seats(&read, &binding_freeing(&["cancelled"]), &payments);
|
|
851
|
+
assert_eq!(counts.get("yoga"), Some(&1), "cancelled is declared, so it frees the seat");
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
#[test]
|
|
855
|
+
fn the_fixture_declared_cancelled_state_frees_its_seat_beside_archived() {
|
|
856
|
+
let payments = crate::commerce_ir::fixture_payments();
|
|
857
|
+
let binding = crate::commerce_ir::registrations_provider(&crate::commerce_ir::fixture_ir()).expect("registrations");
|
|
858
|
+
let mut read = read_with(3, &[("a", "yoga", "succeeded"), ("b", "yoga", "succeeded"), ("c", "yoga", "succeeded")]);
|
|
859
|
+
set_registration_status(&mut read, "a", "cancelled");
|
|
860
|
+
set_registration_status(&mut read, "b", "archived");
|
|
861
|
+
assert_eq!(count_seats(&read, &binding, &payments).get("yoga"), Some(&1));
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
#[test]
|
|
865
|
+
fn a_state_the_chapter_does_not_declare_keeps_its_seat_even_when_it_is_archived() {
|
|
866
|
+
let payments = crate::commerce_ir::fixture_payments();
|
|
867
|
+
let mut read = read_with(3, &[("a", "yoga", "succeeded")]);
|
|
868
|
+
set_registration_status(&mut read, "a", "archived");
|
|
869
|
+
let counts = count_seats(&read, &binding_freeing(&["cancelled"]), &payments);
|
|
870
|
+
assert_eq!(counts.get("yoga"), Some(&1), "only the declared states free a seat");
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
#[test]
|
|
874
|
+
fn with_no_declaration_the_conventional_binding_frees_the_archived_seat() {
|
|
875
|
+
let payments = crate::commerce_ir::fixture_payments();
|
|
876
|
+
let mut read = read_with(3, &[("a", "yoga", "succeeded")]);
|
|
877
|
+
set_registration_status(&mut read, "a", "archived");
|
|
878
|
+
let conventional = crate::commerce_ir::RegistrationsProvider::conventional("CheckoutFixture");
|
|
879
|
+
assert_eq!(count_seats(&read, &conventional, &payments).get("yoga"), None);
|
|
880
|
+
}
|
|
881
|
+
|
|
791
882
|
#[test]
|
|
792
883
|
fn a_registration_with_no_status_counts_as_active() {
|
|
793
884
|
let payments = crate::commerce_ir::fixture_payments();
|
|
@@ -847,18 +938,22 @@ mod tests {
|
|
|
847
938
|
read["instances"][format!("CheckoutFixture::Registration#{reference}")]["status"] = json!(status);
|
|
848
939
|
}
|
|
849
940
|
|
|
941
|
+
/// The fixture's payments binding with the production table: the fixture lifecycle has only
|
|
942
|
+
/// pending, succeeded and failed, so refunding and disputed need the table set here.
|
|
943
|
+
fn four_state_payments() -> crate::commerce_ir::PaymentsProvider {
|
|
944
|
+
let mut payments = crate::commerce_ir::fixture_payments();
|
|
945
|
+
payments.holds_seat = HOLDING.iter().map(|s| s.to_string()).collect();
|
|
946
|
+
payments
|
|
947
|
+
}
|
|
948
|
+
|
|
850
949
|
#[test]
|
|
851
|
-
fn
|
|
852
|
-
|
|
853
|
-
table.sort_unstable();
|
|
854
|
-
let mut expected = HOLDING;
|
|
855
|
-
expected.sort_unstable();
|
|
856
|
-
assert_eq!(table, expected);
|
|
950
|
+
fn the_seat_holding_table_comes_from_the_fixture_ir_mark() {
|
|
951
|
+
assert_eq!(crate::commerce_ir::fixture_payments().holds_seat, vec!["pending", "succeeded"]);
|
|
857
952
|
}
|
|
858
953
|
|
|
859
954
|
#[test]
|
|
860
955
|
fn every_holding_status_holds_one_seat_and_every_freeing_status_holds_none() {
|
|
861
|
-
let payments =
|
|
956
|
+
let payments = four_state_payments();
|
|
862
957
|
for status in HOLDING {
|
|
863
958
|
let read = read_with(10, &[("r", "yoga", status)]);
|
|
864
959
|
assert_eq!(seats_taken(&read, "CheckoutFixture", &payments, "yoga"), 1, "{status} should hold a seat");
|
|
@@ -1235,6 +1330,114 @@ mod tests {
|
|
|
1235
1330
|
assert!(response["body"].as_str().unwrap().contains("closed"));
|
|
1236
1331
|
}
|
|
1237
1332
|
|
|
1333
|
+
// Schedules `slug` starting at `starts_at` with the optional cutoff set
|
|
1334
|
+
// (or absent), through the real Event.Schedule command.
|
|
1335
|
+
async fn schedule_timed_event(client: &Mutex<Client>, wasm_path: &Path, config: &LineageConfig, slug: &str, starts_at: i64, cutoff_hours: Option<i64>) {
|
|
1336
|
+
let mut args = json!({
|
|
1337
|
+
"slug": {"value": slug}, "name": {"value": "Sample Studio"},
|
|
1338
|
+
"price": {"cents": 4200}, "capacity": {"value": 20},
|
|
1339
|
+
"starts_at": {"value": starts_at},
|
|
1340
|
+
});
|
|
1341
|
+
if let Some(hours) = cutoff_hours {
|
|
1342
|
+
args["registration_cutoff_hours"] = json!({"value": hours});
|
|
1343
|
+
}
|
|
1344
|
+
let outcome = dispatch::handle(client, wasm_path, "CheckoutFixture::Event.Schedule", args, None, config, &lambda_client::NeverInvoker)
|
|
1345
|
+
.await
|
|
1346
|
+
.unwrap();
|
|
1347
|
+
assert!(outcome.accepted, "scheduling the timed fixture event should succeed: {:?}", outcome.result);
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
#[tokio::test]
|
|
1351
|
+
async fn registrations_route_refuses_a_session_inside_its_registration_cutoff_and_writes_nothing() {
|
|
1352
|
+
let client = scratch_db("hecks_host_web_test_registrations_cutoff_passed").await;
|
|
1353
|
+
provision_lineage(&*client.lock().await, "CheckoutFixture", 1, &["Event", "Registration", "Payment"]).await;
|
|
1354
|
+
let config = checkout_config(1);
|
|
1355
|
+
let wasm_path = checkout_wasm_path();
|
|
1356
|
+
|
|
1357
|
+
// Starts in 5 hours with a 6 hour cutoff: registration closed an hour ago.
|
|
1358
|
+
let now = 1_800_000_000;
|
|
1359
|
+
schedule_timed_event(&client, &wasm_path, &config, "too-late", now + 5 * 3600, Some(6)).await;
|
|
1360
|
+
|
|
1361
|
+
let body = json!({"event_slug": "too-late", "name": "Ada", "email": "ada@example.com"}).to_string();
|
|
1362
|
+
let response = registrations_route_at(&body, &payments::test_platform(), &client, &wasm_path, &config, &lambda_client::NeverInvoker, &crate::commerce_ir::fixture_payments(), now).await;
|
|
1363
|
+
assert_eq!(response["statusCode"], 422, "{response:?}");
|
|
1364
|
+
assert_eq!(
|
|
1365
|
+
serde_json::from_str::<Value>(response["body"].as_str().unwrap()).unwrap(),
|
|
1366
|
+
json!({"error": "registration has closed for this event"})
|
|
1367
|
+
);
|
|
1368
|
+
|
|
1369
|
+
let read = dispatch::read(&client, &wasm_path).await.unwrap();
|
|
1370
|
+
let instances = read["instances"].as_object().unwrap();
|
|
1371
|
+
assert!(
|
|
1372
|
+
instances.keys().all(|k| !k.starts_with("Payments::Payment#") && !k.starts_with("CheckoutFixture::Registration#")),
|
|
1373
|
+
"a refused registration must write no Payment and no Registration: {instances:?}"
|
|
1374
|
+
);
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
#[tokio::test]
|
|
1378
|
+
async fn registrations_route_accepts_a_session_with_no_cutoff_even_when_it_has_a_start() {
|
|
1379
|
+
let client = scratch_db("hecks_host_web_test_registrations_cutoff_absent").await;
|
|
1380
|
+
provision_lineage(&*client.lock().await, "CheckoutFixture", 1, &["Event", "Registration", "Payment"]).await;
|
|
1381
|
+
let config = checkout_config(1);
|
|
1382
|
+
let wasm_path = checkout_wasm_path();
|
|
1383
|
+
|
|
1384
|
+
// A start in the past and no cutoff field: nothing to enforce.
|
|
1385
|
+
let now = 1_800_000_000;
|
|
1386
|
+
schedule_timed_event(&client, &wasm_path, &config, "no-cutoff", now - 3600, None).await;
|
|
1387
|
+
|
|
1388
|
+
let body = json!({"event_slug": "no-cutoff", "name": "Ada", "email": "ada@example.com"}).to_string();
|
|
1389
|
+
let response = registrations_route_at(&body, &payments::test_platform(), &client, &wasm_path, &config, &lambda_client::NeverInvoker, &crate::commerce_ir::fixture_payments(), now).await;
|
|
1390
|
+
assert_eq!(response["statusCode"], 200, "{response:?}");
|
|
1391
|
+
|
|
1392
|
+
let read = dispatch::read(&client, &wasm_path).await.unwrap();
|
|
1393
|
+
let instances = read["instances"].as_object().unwrap();
|
|
1394
|
+
assert!(instances.keys().any(|k| k.starts_with("CheckoutFixture::Registration#")), "{instances:?}");
|
|
1395
|
+
assert!(instances.keys().any(|k| k.starts_with("Payments::Payment#")), "{instances:?}");
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
#[test]
|
|
1399
|
+
fn a_registration_cutoff_needs_both_fields_and_ignores_unreadable_ones() {
|
|
1400
|
+
let now = 1_000_000;
|
|
1401
|
+
let past_start = json!({"starts_at": 0});
|
|
1402
|
+
assert!(!registration_cutoff_passed(&json!({}), now));
|
|
1403
|
+
assert!(!registration_cutoff_passed(&past_start, now), "a start with no cutoff hours is no cutoff");
|
|
1404
|
+
assert!(!registration_cutoff_passed(&json!({"registration_cutoff_hours": 48}), now), "hours with no start is no cutoff");
|
|
1405
|
+
assert!(!registration_cutoff_passed(&json!({"starts_at": null, "registration_cutoff_hours": 48}), now));
|
|
1406
|
+
assert!(!registration_cutoff_passed(&json!({"starts_at": 0, "registration_cutoff_hours": null}), now));
|
|
1407
|
+
assert!(!registration_cutoff_passed(&json!({"starts_at": "soon", "registration_cutoff_hours": 48}), now));
|
|
1408
|
+
assert!(!registration_cutoff_passed(&json!({"starts_at": 0, "registration_cutoff_hours": "two"}), now));
|
|
1409
|
+
assert!(!registration_cutoff_passed(&json!({"starts_at": {"value": "soon"}, "registration_cutoff_hours": {"value": 1.5}}), now));
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
#[test]
|
|
1413
|
+
fn a_registration_cutoff_is_open_exactly_at_the_boundary_and_closed_a_second_later() {
|
|
1414
|
+
// Cutoff 2 hours before a start of 100_000: the window shuts after 92_800.
|
|
1415
|
+
let event = json!({"starts_at": 100_000, "registration_cutoff_hours": 2});
|
|
1416
|
+
assert!(!registration_cutoff_passed(&event, 92_799));
|
|
1417
|
+
assert!(!registration_cutoff_passed(&event, 92_800), "exactly at the cutoff is still open");
|
|
1418
|
+
assert!(registration_cutoff_passed(&event, 92_801));
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
#[test]
|
|
1422
|
+
fn a_registration_cutoff_reads_a_value_object_or_a_bare_number() {
|
|
1423
|
+
let wrapped = json!({"starts_at": {"value": 100_000}, "registration_cutoff_hours": {"value": 2}});
|
|
1424
|
+
let bare = json!({"starts_at": 100_000, "registration_cutoff_hours": 2});
|
|
1425
|
+
let mixed = json!({"starts_at": {"value": 100_000}, "registration_cutoff_hours": 2});
|
|
1426
|
+
for event in [wrapped, bare, mixed] {
|
|
1427
|
+
assert!(!registration_cutoff_passed(&event, 92_800), "{event}");
|
|
1428
|
+
assert!(registration_cutoff_passed(&event, 92_801), "{event}");
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1432
|
+
#[test]
|
|
1433
|
+
fn a_registration_cutoff_refuses_a_start_already_in_the_past() {
|
|
1434
|
+
let event = json!({"starts_at": 1_000, "registration_cutoff_hours": 1});
|
|
1435
|
+
assert!(registration_cutoff_passed(&event, 5_000));
|
|
1436
|
+
let no_hours_of_notice = json!({"starts_at": 1_000, "registration_cutoff_hours": 0});
|
|
1437
|
+
assert!(!registration_cutoff_passed(&no_hours_of_notice, 1_000));
|
|
1438
|
+
assert!(registration_cutoff_passed(&no_hours_of_notice, 1_001));
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1238
1441
|
#[tokio::test]
|
|
1239
1442
|
async fn registrations_route_propagates_a_real_domain_refusal_from_payment_initiate() {
|
|
1240
1443
|
// A zero-price event: PositiveMoney's invariant refuses
|
data/rust/host/src/web.rs
CHANGED
|
@@ -608,20 +608,20 @@ async fn registrations_list_route(
|
|
|
608
608
|
// Built field-by-field from an allowlist so intake answers (health,
|
|
609
609
|
// medications) and payment fields can never appear here.
|
|
610
610
|
fn registration_list_rows(read: &Value, domain: &str) -> Vec<Value> {
|
|
611
|
-
|
|
611
|
+
let binding = crate::commerce_ir::registrations_binding(domain);
|
|
612
612
|
let plain = |value: Option<&Value>| -> Option<Value> {
|
|
613
613
|
let value = value?;
|
|
614
614
|
Some(value.get("value").cloned().unwrap_or_else(|| value.clone()))
|
|
615
615
|
};
|
|
616
616
|
let text = |value: Option<&Value>| plain(value).and_then(|v| v.as_str().map(|s| s.trim().to_string()));
|
|
617
617
|
|
|
618
|
-
let mut rows: Vec<(Option<String>, Value)> = instances_for(read, &
|
|
618
|
+
let mut rows: Vec<(Option<String>, Value)> = instances_for(read, &binding.registration_prefix())
|
|
619
619
|
.into_iter()
|
|
620
620
|
.map(|(id, registration)| {
|
|
621
621
|
let attendee = registration.get("attendee").cloned().unwrap_or_else(|| json!({}));
|
|
622
622
|
let joined = [text(attendee.get("first_name")), text(attendee.get("last_name"))].into_iter().flatten().collect::<Vec<_>>().join(" ");
|
|
623
623
|
let name = if joined.is_empty() { text(attendee.get("name")).unwrap_or_default() } else { joined };
|
|
624
|
-
let stamp =
|
|
624
|
+
let stamp = binding.timestamp_keys.iter().find_map(|key| text(registration.get(key.as_str())));
|
|
625
625
|
let mut row = json!({
|
|
626
626
|
"registration_id": id,
|
|
627
627
|
"email": text(attendee.get("email")),
|
|
@@ -1686,17 +1686,29 @@ fn redirect(location: &str) -> Value {
|
|
|
1686
1686
|
json!({"statusCode": 302, "headers": {"location": location}, "body": "", "isBase64Encoded": false})
|
|
1687
1687
|
}
|
|
1688
1688
|
|
|
1689
|
-
// HttpOnly + SameSite always; Secure
|
|
1690
|
-
//
|
|
1689
|
+
// HttpOnly + SameSite always; Secure unless the host's own address is plain http — a localhost
|
|
1690
|
+
// http:// callback can't set a Secure cookie. See `account_cookie_flags` for which address counts.
|
|
1691
1691
|
fn cookie_flags() -> &'static str {
|
|
1692
|
-
let
|
|
1693
|
-
|
|
1694
|
-
|
|
1692
|
+
let redirect_uri = std::env::var("GOOGLE_REDIRECT_URI").unwrap_or_default();
|
|
1693
|
+
let site_url = std::env::var("SITE_URL").unwrap_or_default();
|
|
1694
|
+
account_cookie_flags(&redirect_uri, &site_url)
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1697
|
+
// The flags for the account cookie given the Google redirect URI and the site's public address.
|
|
1698
|
+
// A host that sets the redirect URI is judged by it, as ever; a host that has stopped setting it
|
|
1699
|
+
// is judged by SITE_URL, and with neither set the cookie is Secure.
|
|
1700
|
+
pub(crate) fn account_cookie_flags(redirect_uri: &str, site_url: &str) -> &'static str {
|
|
1701
|
+
let judged_by = if redirect_uri.is_empty() { site_url } else { redirect_uri };
|
|
1702
|
+
if judged_by.starts_with("http://") {
|
|
1703
|
+
COOKIE_FLAGS_PLAIN
|
|
1695
1704
|
} else {
|
|
1696
|
-
|
|
1705
|
+
COOKIE_FLAGS_SECURE
|
|
1697
1706
|
}
|
|
1698
1707
|
}
|
|
1699
1708
|
|
|
1709
|
+
pub(crate) const COOKIE_FLAGS_SECURE: &str = "; Path=/; HttpOnly; Secure; SameSite=Lax";
|
|
1710
|
+
const COOKIE_FLAGS_PLAIN: &str = "; Path=/; HttpOnly; SameSite=Lax";
|
|
1711
|
+
|
|
1700
1712
|
fn redirect_with_cookie(location: &str, cookie: &str) -> Value {
|
|
1701
1713
|
json!({"statusCode": 302, "headers": {"location": location}, "cookies": [cookie], "body": "", "isBase64Encoded": false})
|
|
1702
1714
|
}
|
|
@@ -2985,6 +2997,38 @@ mod tests {
|
|
|
2985
2997
|
|
|
2986
2998
|
}
|
|
2987
2999
|
|
|
3000
|
+
#[cfg(test)]
|
|
3001
|
+
mod cookie_flag_tests {
|
|
3002
|
+
use super::account_cookie_flags;
|
|
3003
|
+
|
|
3004
|
+
const SECURE: &str = "; Path=/; HttpOnly; Secure; SameSite=Lax";
|
|
3005
|
+
const PLAIN: &str = "; Path=/; HttpOnly; SameSite=Lax";
|
|
3006
|
+
|
|
3007
|
+
#[test]
|
|
3008
|
+
fn a_host_that_sets_the_redirect_uri_is_judged_by_it_as_before() {
|
|
3009
|
+
assert_eq!(account_cookie_flags("https://site.example/auth/google/callback", ""), SECURE);
|
|
3010
|
+
assert_eq!(account_cookie_flags("http://localhost:4567/auth/google/callback", ""), PLAIN);
|
|
3011
|
+
assert_eq!(account_cookie_flags("https://site.example/cb", "http://localhost:4321"), SECURE);
|
|
3012
|
+
assert_eq!(account_cookie_flags("http://localhost:4567/cb", "https://site.example"), PLAIN);
|
|
3013
|
+
}
|
|
3014
|
+
|
|
3015
|
+
#[test]
|
|
3016
|
+
fn a_host_with_no_redirect_uri_and_an_https_site_url_still_sets_secure() {
|
|
3017
|
+
assert_eq!(account_cookie_flags("", "https://site.example"), SECURE);
|
|
3018
|
+
assert_eq!(account_cookie_flags("", "https://site.example/"), SECURE);
|
|
3019
|
+
}
|
|
3020
|
+
|
|
3021
|
+
#[test]
|
|
3022
|
+
fn a_host_with_no_redirect_uri_and_an_http_site_url_does_not_demand_secure() {
|
|
3023
|
+
assert_eq!(account_cookie_flags("", "http://localhost:4321"), PLAIN);
|
|
3024
|
+
}
|
|
3025
|
+
|
|
3026
|
+
#[test]
|
|
3027
|
+
fn a_host_with_neither_setting_stays_secure() {
|
|
3028
|
+
assert_eq!(account_cookie_flags("", ""), SECURE);
|
|
3029
|
+
}
|
|
3030
|
+
}
|
|
3031
|
+
|
|
2988
3032
|
#[cfg(test)]
|
|
2989
3033
|
#[path = "boundary_fuzz/web.rs"]
|
|
2990
3034
|
mod boundary_fuzz;
|
data/rust/parser/src/emit.rs
CHANGED
|
@@ -411,13 +411,18 @@ fn mutation_source_json(source: &Option<ir::MutationSource>) -> JsonValue {
|
|
|
411
411
|
("kind".to_string(), JsonValue::str("state")),
|
|
412
412
|
("name".to_string(), JsonValue::str(name.clone())),
|
|
413
413
|
]),
|
|
414
|
+
Some(ir::MutationSource::Expression(text)) => JsonValue::Object(vec![
|
|
415
|
+
("kind".to_string(), JsonValue::str("expression")),
|
|
416
|
+
("text".to_string(), JsonValue::str(text.clone())),
|
|
417
|
+
("ast".to_string(), crate::expr::ast_json::emit_resolver(&crate::expr::resolver::parse(text))),
|
|
418
|
+
]),
|
|
414
419
|
None => JsonValue::Null,
|
|
415
420
|
}
|
|
416
421
|
}
|
|
417
422
|
|
|
418
423
|
/// `IR::Lifecycle#to_h`; `transitions:` arrives already expanded, one row per `from_state`.
|
|
419
424
|
fn lifecycle_json(l: &ir::Lifecycle) -> JsonValue {
|
|
420
|
-
|
|
425
|
+
let mut fields = vec![
|
|
421
426
|
("field".to_string(), JsonValue::str(l.field.clone())),
|
|
422
427
|
("default".to_string(), JsonValue::str(l.default.clone())),
|
|
423
428
|
(
|
|
@@ -435,7 +440,25 @@ fn lifecycle_json(l: &ir::Lifecycle) -> JsonValue {
|
|
|
435
440
|
.collect(),
|
|
436
441
|
),
|
|
437
442
|
),
|
|
438
|
-
]
|
|
443
|
+
];
|
|
444
|
+
// Omitted when empty, so an unmarked lifecycle's IR is unchanged.
|
|
445
|
+
if !l.marks.is_empty() {
|
|
446
|
+
fields.push((
|
|
447
|
+
"marks".to_string(),
|
|
448
|
+
JsonValue::Object(
|
|
449
|
+
l.marks
|
|
450
|
+
.iter()
|
|
451
|
+
.map(|(name, states)| {
|
|
452
|
+
(
|
|
453
|
+
name.clone(),
|
|
454
|
+
JsonValue::Array(states.iter().map(|s| JsonValue::str(s.clone())).collect()),
|
|
455
|
+
)
|
|
456
|
+
})
|
|
457
|
+
.collect(),
|
|
458
|
+
),
|
|
459
|
+
));
|
|
460
|
+
}
|
|
461
|
+
JsonValue::Object(fields)
|
|
439
462
|
}
|
|
440
463
|
|
|
441
464
|
/// `IR::Query#to_h` merged with `query_options_json`, which appends only the declared
|
|
@@ -783,15 +806,24 @@ fn dispatch_spec_json(d: &ir::DispatchSpec) -> JsonValue {
|
|
|
783
806
|
])
|
|
784
807
|
}
|
|
785
808
|
|
|
809
|
+
/// An ask policy carries `ask` where a trigger policy carries `trigger_command`, as
|
|
810
|
+
/// `Behaviour::Policy#to_h` does; a trigger policy has no `ask` key at all.
|
|
811
|
+
fn trigger_or_ask_entry(p: &ir::Policy) -> (String, JsonValue) {
|
|
812
|
+
match &p.ask {
|
|
813
|
+
Some(name) => ("ask".to_string(), JsonValue::str(name.clone())),
|
|
814
|
+
None => (
|
|
815
|
+
"trigger_command".to_string(),
|
|
816
|
+
JsonValue::opt_str(&p.trigger_command),
|
|
817
|
+
),
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
|
|
786
821
|
/// `Policy#to_h`: attribute declaration order from reaction.bluebook, then `where_ast` last.
|
|
787
822
|
fn policy_json(p: &ir::Policy) -> JsonValue {
|
|
788
823
|
JsonValue::Object(vec![
|
|
789
824
|
("name".to_string(), JsonValue::str(p.name.clone())),
|
|
790
825
|
("on_event".to_string(), JsonValue::opt_str(&p.on_event)),
|
|
791
|
-
(
|
|
792
|
-
"trigger_command".to_string(),
|
|
793
|
-
JsonValue::opt_str(&p.trigger_command),
|
|
794
|
-
),
|
|
826
|
+
trigger_or_ask_entry(p),
|
|
795
827
|
(
|
|
796
828
|
"target_domain".to_string(),
|
|
797
829
|
JsonValue::opt_str(&p.target_domain),
|
|
@@ -66,7 +66,7 @@ fn emit_include(haystack: &Resolver, needle: &Resolver) -> JsonValue {
|
|
|
66
66
|
.expect("non-empty by the guard above")
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
fn emit_resolver(node: &Resolver) -> JsonValue {
|
|
69
|
+
pub fn emit_resolver(node: &Resolver) -> JsonValue {
|
|
70
70
|
match node {
|
|
71
71
|
Resolver::IntegerLiteral(v) => obj(vec![op_tag("int"), ("value", JsonValue::Number(v.to_string()))]),
|
|
72
72
|
Resolver::FloatLiteral(v) => obj(vec![op_tag("float"), ("value", JsonValue::Number(ruby_float(*v)))]),
|
|
@@ -78,6 +78,9 @@ fn emit_resolver(node: &Resolver) -> JsonValue {
|
|
|
78
78
|
("path", JsonValue::Array(path.split('.').map(|s| JsonValue::String(s.to_string())).collect())),
|
|
79
79
|
]),
|
|
80
80
|
Resolver::Addition(left, right) => obj(vec![op_tag("add"), ("left", emit_resolver(left)), ("right", emit_resolver(right))]),
|
|
81
|
+
Resolver::Subtraction(left, right) => obj(vec![op_tag("sub"), ("left", emit_resolver(left)), ("right", emit_resolver(right))]),
|
|
82
|
+
Resolver::Multiplication(left, right) => obj(vec![op_tag("mul"), ("left", emit_resolver(left)), ("right", emit_resolver(right))]),
|
|
83
|
+
Resolver::Division(left, right) => obj(vec![op_tag("div"), ("left", emit_resolver(left)), ("right", emit_resolver(right))]),
|
|
81
84
|
Resolver::SignTest { operator, receiver } => obj(vec![
|
|
82
85
|
op_tag("sign_test"),
|
|
83
86
|
("cmp", emit_comparison(operator)),
|
|
@@ -106,6 +109,9 @@ fn emit_resolver(node: &Resolver) -> JsonValue {
|
|
|
106
109
|
Resolver::Split { receiver, separator } => {
|
|
107
110
|
obj(vec![op_tag("split"), ("receiver", emit_resolver(receiver)), ("separator", JsonValue::String(separator.clone()))])
|
|
108
111
|
}
|
|
112
|
+
Resolver::Strip { receiver, side } => {
|
|
113
|
+
obj(vec![op_tag("strip"), ("receiver", emit_resolver(receiver)), ("side", JsonValue::String((*side).to_string()))])
|
|
114
|
+
}
|
|
109
115
|
Resolver::StartsWith { receiver, substring } => {
|
|
110
116
|
obj(vec![op_tag("starts_with"), ("receiver", emit_resolver(receiver)), ("substring", JsonValue::String(substring.clone()))])
|
|
111
117
|
}
|