hecks 3.9.1 → 3.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/examples/pizzas/bluebook/pizzas.bluebook +29 -0
- data/examples/pizzas/bluebook/pizzas.hecksagon +7 -0
- data/exe/hecks +4 -4
- data/lib/hecks/adapters/driven/d1.rb +2 -10
- data/lib/hecks/adapters/driven/heki.adapter +1 -1
- data/lib/hecks/adapters/driven/heki.rb +2 -12
- data/lib/hecks/adapters/driven/lambda.adapter +1 -1
- data/lib/hecks/adapters/driven/lambda.rb +3 -18
- data/lib/hecks/adapters/driven/postgres.rb +4 -11
- data/lib/hecks/adapters/driven/sqlite.adapter +1 -1
- data/lib/hecks/adapters/driven/sqlite.rb +2 -10
- data/lib/hecks/adapters/driving/admission.rb +46 -0
- data/lib/hecks/adapters/driving/cli/nesting.rb +36 -0
- data/lib/hecks/adapters/driving/cli/shorthand.rb +88 -0
- data/lib/hecks/adapters/driving/cli.rb +127 -0
- data/lib/hecks/adapters/driving/cli_runner/answers.rb +160 -0
- data/lib/hecks/adapters/driving/cli_runner/resolution.rb +155 -0
- data/lib/hecks/adapters/driving/cli_runner/settling.rb +120 -0
- data/lib/hecks/adapters/driving/cli_runner/streaming.rb +93 -0
- data/lib/hecks/adapters/driving/cli_runner/suggestions.rb +47 -0
- data/lib/hecks/adapters/driving/cli_runner.rb +69 -0
- data/lib/hecks/adapters/driving/command_request.rb +121 -0
- data/lib/hecks/adapters/driving/handle.rb +214 -0
- data/lib/hecks/adapters/driving/json.rb +136 -0
- data/lib/hecks/adapters/driving/launcher_options.rb +267 -0
- data/lib/hecks/adapters/driving/mcp/properties.rb +49 -0
- data/lib/hecks/adapters/driving/mcp/tool_calls.rb +116 -0
- data/lib/hecks/adapters/driving/mcp/tool_definitions/auditing.rb +53 -0
- data/lib/hecks/adapters/driving/mcp/tool_definitions/calling.rb +71 -0
- data/lib/hecks/adapters/driving/mcp/tool_definitions/reading.rb +99 -0
- data/lib/hecks/adapters/driving/mcp/tool_definitions.rb +18 -0
- data/lib/hecks/adapters/driving/mcp.rb +159 -0
- data/lib/hecks/adapters/driving/mcp_guide_cache.rb +78 -0
- data/lib/hecks/adapters/driving/mcp_scope/argument_policy.rb +97 -0
- data/lib/hecks/adapters/driving/mcp_scope/env_problems.rb +69 -0
- data/lib/hecks/adapters/driving/mcp_scope/presenter.rb +83 -0
- data/lib/hecks/adapters/driving/mcp_scope.rb +213 -0
- data/lib/hecks/adapters/driving/ruby/aggregate_module.rb +171 -0
- data/lib/hecks/adapters/driving/ruby/chapter.rb +91 -0
- data/lib/hecks/adapters/driving/ruby.rb +83 -0
- data/lib/hecks/adapters/driving/usage_cache.rb +144 -0
- data/lib/hecks/{doors.rb → adapters/driving.rb} +10 -6
- data/lib/hecks/behaviors/expectations/boot.rb +1 -1
- data/lib/hecks/bench/ruby_runner.rb +1 -1
- data/lib/hecks/bluebook/ask_resolution.rb +130 -0
- data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +8 -2
- data/lib/hecks/bluebook/assembly/contracts/rules.rb +4 -2
- data/lib/hecks/bluebook/assembly/contracts/structure.rb +3 -1
- data/lib/hecks/bluebook/assembly/marks.rb +1 -0
- data/lib/hecks/bluebook/attribute.rb +1 -1
- data/lib/hecks/bluebook/behaviour/command.rb +13 -8
- data/lib/hecks/bluebook/behaviour/policy.rb +30 -0
- data/lib/hecks/bluebook/capabilities/attribute_values.rb +72 -0
- data/lib/hecks/bluebook/capabilities.rb +91 -9
- data/lib/hecks/bluebook/domain_port.rb +16 -4
- data/lib/hecks/bluebook/dsl/adapter_builder.rb +18 -6
- data/lib/hecks/bluebook/dsl/binding_proxy.rb +13 -0
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/computed_sources.rb +98 -0
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/fan_out_rows.rb +42 -0
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/projected_fields.rb +14 -2
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/provisions.rb +69 -3
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/with_projections.rb +17 -10
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +5 -0
- data/lib/hecks/bluebook/dsl/bootstrap_table.rb +2 -0
- data/lib/hecks/bluebook/dsl/command_builder/mutations.rb +8 -1
- data/lib/hecks/bluebook/dsl/command_builder/operands.rb +121 -0
- data/lib/hecks/bluebook/dsl/command_builder.rb +2 -0
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +10 -1
- data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +45 -2
- data/lib/hecks/bluebook/dsl/policy_builder.rb +47 -10
- data/lib/hecks/bluebook/dsl/rule_reference.rb +7 -4
- data/lib/hecks/bluebook/dsl/world_builder.rb +1 -1
- data/lib/hecks/bluebook/expression/ast_json/emitters.rb +6 -0
- data/lib/hecks/bluebook/expression/ast_json.rb +9 -6
- data/lib/hecks/bluebook/expression/ast_reader.rb +14 -9
- data/lib/hecks/bluebook/expression/projection.json +36 -0
- data/lib/hecks/bluebook/expression/resolver/operations.rb +31 -0
- data/lib/hecks/bluebook/expression/resolver/parsing.rb +38 -5
- data/lib/hecks/bluebook/expression/resolver/scanning.rb +22 -5
- data/lib/hecks/bluebook/expression/resolver/values.rb +10 -0
- data/lib/hecks/bluebook/expression/resolver.rb +20 -1
- data/lib/hecks/bluebook/hecksagon.rb +23 -3
- data/lib/hecks/bluebook/lifecycle.rb +22 -3
- data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +13 -1
- data/lib/hecks/bluebook/meta_validator/modes.rb +4 -1
- data/lib/hecks/bluebook/meta_validator/readings.rb +18 -1
- data/lib/hecks/bluebook/meta_validator/reconstruction/heads.rb +2 -1
- data/lib/hecks/bluebook/meta_validator/shapes.rb +5 -0
- data/lib/hecks/bluebook/meta_validator/verdict_cache.rb +1 -1
- data/lib/hecks/bluebook/meta_validator.rb +12 -12
- data/lib/hecks/bluebook/model_check/ask_checks.rb +71 -0
- data/lib/hecks/bluebook/model_check/policy_checks.rb +2 -1
- data/lib/hecks/bluebook/model_check/reference_checks.rb +70 -0
- data/lib/hecks/bluebook/model_check.rb +8 -2
- data/lib/hecks/bluebook/policy.rb +4 -2
- data/lib/hecks/bluebook/smoke_test.rb +1 -1
- data/lib/hecks/bluebook/synthesizer.rb +2 -0
- data/lib/hecks/bluebook.rb +1 -0
- data/lib/hecks/cli/command_table.rb +1 -1
- data/lib/hecks/cli/console.rb +2 -1
- data/lib/hecks/cli/document.rb +1 -1
- data/lib/hecks/cli/launcher_words.rb +4 -4
- data/lib/hecks/cli/mcp.rb +4 -4
- data/lib/hecks/cli/project_cli/launcher_source.rb +2 -2
- data/lib/hecks/cli/project_cli/templates.rb +5 -5
- data/lib/hecks/cli/project_cli.rb +9 -6
- data/lib/hecks/cli/run.rb +2 -2
- data/lib/hecks/cli/smoke_http/checks.rb +4 -1
- data/lib/hecks/cli/smoke_http/transport.rb +3 -3
- data/lib/hecks/cli/smoke_http.rb +3 -0
- data/lib/hecks/codemod.rb +5 -2
- data/lib/hecks/corpus/routes.rb +7 -0
- data/lib/hecks/corpus/rust.rb +3 -2
- data/lib/hecks/deploy/bluebook/deploy.bluebook +124 -2
- data/lib/hecks/deploy/bluebook/deploy.hecksagon +8 -0
- data/lib/hecks/deploy/oidc.json +16 -3
- data/lib/hecks/doc/reference/rendering.rb +6 -3
- data/lib/hecks/forms/app/commands.rb +8 -7
- data/lib/hecks/forms/app/queries.rb +2 -1
- data/lib/hecks/forms/app.rb +1 -1
- data/lib/hecks/forms/record_renderer.rb +1 -1
- data/lib/hecks/framework/bluebook/privacy.bluebook +11 -0
- data/lib/hecks/fuzzing/concurrent_dispatch.rb +4 -1
- data/lib/hecks/fuzzing/failure_triage.rb +2 -1
- data/lib/hecks/fuzzing/mutation/run.rb +11 -4
- data/lib/hecks/fuzzing/properties/catalog.rb +6 -4
- data/lib/hecks/fuzzing/properties/guaranteed_by_construction.rb +18 -11
- data/lib/hecks/fuzzing/properties/guards.rb +1 -1
- data/lib/hecks/fuzzing/self_consistency/saga_redelivery.rb +1 -1
- data/lib/hecks/fuzzing/self_consistency.rb +6 -2
- data/lib/hecks/fuzzing/shrinker.rb +3 -1
- data/lib/hecks/fuzzing/value_generator.rb +19 -5
- data/lib/hecks/gate/stages.yml +4 -10
- data/lib/hecks/grammar/evolve.rb +1 -1
- data/lib/hecks/grammar/expression_operators.json +372 -0
- data/lib/hecks/grammar/operators.rb +3 -1
- data/lib/hecks/hecks/adapters/codebase/corpus_tasks.rb +10 -10
- data/lib/hecks/hecks/adapters/codebase/inspection/rubycritic.rb +35 -0
- data/lib/hecks/hecks/adapters/codebase/inspection.rb +104 -0
- data/lib/hecks/hecks/adapters/codebase/publishing.rb +2 -1
- data/lib/hecks/hecks/adapters/codebase/release_facts.rb +12 -2
- data/lib/hecks/hecks/adapters/codebase/source_tree.rb +2 -1
- data/lib/hecks/hecks/adapters/codebase/test_runner.rb +1 -1
- data/lib/hecks/hecks/adapters/cost_explorer.rb +8 -5
- data/lib/hecks/hecks/adapters/deploy_toolchain/clearing.rb +51 -0
- data/lib/hecks/hecks/adapters/deploy_toolchain/statuses.rb +2 -1
- data/lib/hecks/hecks/adapters/deploy_toolchain.rb +2 -0
- data/lib/hecks/hecks/adapters/finding_github.rb +13 -6
- data/lib/hecks/hecks/adapters/first_admin.rb +2 -2
- data/lib/hecks/hecks/adapters/github_rulesets.rb +6 -3
- data/lib/hecks/hecks/adapters/in_process_boot.rb +1 -1
- data/lib/hecks/hecks/adapters/journal_store/compaction.rb +1 -1
- data/lib/hecks/hecks/adapters/journal_store/edge_audit.rb +10 -20
- data/lib/hecks/hecks/adapters/journal_store/held_domain.rb +28 -5
- data/lib/hecks/hecks/adapters/local_files.rb +2 -2
- data/lib/hecks/hecks/adapters/rehearsal/audit_report.rb +107 -0
- data/lib/hecks/hecks/adapters/rehearsal/census.rb +71 -0
- data/lib/hecks/hecks/adapters/rehearsal/declaration.rb +133 -0
- data/lib/hecks/hecks/adapters/rehearsal/environment.rb +83 -0
- data/lib/hecks/hecks/adapters/rehearsal/inquiry.rb +142 -0
- data/lib/hecks/hecks/adapters/rehearsal/memory_store.rb +96 -0
- data/lib/hecks/hecks/adapters/rehearsal/postgres_census.rb +100 -0
- data/lib/hecks/hecks/adapters/rehearsal/postgres_reading.rb +69 -0
- data/lib/hecks/hecks/adapters/rehearsal/postgres_store.rb +129 -0
- data/lib/hecks/hecks/adapters/rehearsal/rows.rb +41 -0
- data/lib/hecks/hecks/adapters/rehearsal/rust_audit.rb +83 -0
- data/lib/hecks/hecks/adapters/rehearsal/subject.rb +104 -0
- data/lib/hecks/hecks/adapters/rehearsal.rb +54 -0
- data/lib/hecks/hecks/adapters/rehearsal_store.adapter +7 -0
- data/lib/hecks/hecks/adapters/rehearsal_store.rb +81 -0
- data/lib/hecks/hecks/adapters/site_toolchain/role_prober.rb +78 -0
- data/lib/hecks/hecks/adapters/site_toolchain.rb +29 -4
- data/lib/hecks/hecks/adapters/terminal.rb +13 -11
- data/lib/hecks/hecks/adapters/world_clearing.rb +65 -0
- data/lib/hecks/hecks/codebase.behaviors +13 -1
- data/lib/hecks/hecks/codebase.bluebook +239 -5
- data/lib/hecks/hecks/custodian.behaviors +24 -6
- data/lib/hecks/hecks/custodian.bluebook +6 -1227
- data/lib/hecks/hecks/deploy.behaviors +1 -1
- data/lib/hecks/hecks/deploy.world +1 -3
- data/lib/hecks/hecks/hecks.behaviors +1 -1
- data/lib/hecks/hecks/hecks.hecksagon +61 -18
- data/lib/hecks/hecks/hecks.world +11 -10
- data/lib/hecks/hecks/rehearsal.bluebook +477 -0
- data/lib/hecks/hecks/site.behaviors +1 -1
- data/lib/hecks/hecks/site.world +0 -1
- data/lib/hecks/hecks/tooling.bluebook +1238 -0
- data/lib/hecks/indifferent_key.rb +20 -0
- data/lib/hecks/language/adapter.bluebook +13 -3
- data/lib/hecks/language/bluebook/aggregate.bluebook +23 -0
- data/lib/hecks/language/bluebook/bluebook.bluebook +15 -0
- data/lib/hecks/language/bluebook/entity.bluebook +18 -0
- data/lib/hecks/language/bluebook/policy.bluebook +9 -1
- data/lib/hecks/language/bluebook/query.bluebook +1 -1
- data/lib/hecks/language/bluebook/vocabulary.bluebook +11 -4
- data/lib/hecks/language/hecksagon/domain_port.bluebook +1 -1
- data/lib/hecks/language/hecksagon/hecksagon.bluebook +22 -0
- data/lib/hecks/language/oidc.json +10 -0
- data/lib/hecks/language/port.bluebook +4 -4
- data/lib/hecks/literal.rb +12 -3
- data/lib/hecks/ports/key_vault.rb +7 -2
- data/lib/hecks/ports/persistence/plugins/era/era_check.rb +21 -10
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/boot.rb +4 -2
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +2 -1
- data/lib/hecks/ports/persistence/repository_factory.rb +6 -3
- data/lib/hecks/projections/deploy/box/templates/deploy-box.sh.tmpl +29 -0
- data/lib/hecks/projections/deploy/box/templates/deploy-service.sh.tmpl +1 -1
- data/lib/hecks/projections/deploy/fargate/settings/tuning.rb +15 -0
- data/lib/hecks/projections/deploy/fargate/settings.rb +3 -1
- data/lib/hecks/projections/deploy/fargate/stack/readers.rb +1 -0
- data/lib/hecks/projections/deploy/preview/preview.sh.erb +1 -1
- data/lib/hecks/projections/deploy/shared.rb +5 -2
- data/lib/hecks/projections/deploy/templates/fargate/Makefile.tmpl +7 -4
- data/lib/hecks/projections/model/deviations.rb +2 -2
- data/lib/hecks/projections/model.rb +2 -2
- data/lib/hecks/projections/oidc.rb +13 -1
- data/lib/hecks/projections/shape.rb +5 -2
- data/lib/hecks/projections/site/cms_editor/attributes.rb +95 -0
- data/lib/hecks/projections/site/cms_editor/blocks.rb +157 -0
- data/lib/hecks/projections/site/cms_editor/checks.rb +77 -0
- data/lib/hecks/projections/site/cms_editor/clearing.rb +70 -0
- data/lib/hecks/projections/site/cms_editor/color.rb +124 -0
- data/lib/hecks/projections/site/cms_editor/composition.rb +42 -0
- data/lib/hecks/projections/site/cms_editor/destructive.rb +61 -0
- data/lib/hecks/projections/site/cms_editor/drafts.rb +102 -0
- data/lib/hecks/projections/site/cms_editor/hints.rb +74 -0
- data/lib/hecks/projections/site/cms_editor/icons.rb +86 -0
- data/lib/hecks/projections/site/cms_editor/key_kinds.rb +31 -0
- data/lib/hecks/projections/site/cms_editor/limits.rb +72 -0
- data/lib/hecks/projections/site/cms_editor/media.rb +85 -0
- data/lib/hecks/projections/site/cms_editor/moment.rb +34 -0
- data/lib/hecks/projections/site/cms_editor/palette.rb +115 -0
- data/lib/hecks/projections/site/cms_editor/pickers.rb +85 -0
- data/lib/hecks/projections/site/cms_editor/pretty.rb +40 -0
- data/lib/hecks/projections/site/cms_editor/preview.rb +91 -0
- data/lib/hecks/projections/site/cms_editor/relations.rb +92 -0
- data/lib/hecks/projections/site/cms_editor/scale_checks.rb +66 -0
- data/lib/hecks/projections/site/cms_editor/scheduling.rb +150 -0
- data/lib/hecks/projections/site/cms_editor/schema.rb +161 -0
- data/lib/hecks/projections/site/cms_editor/setting.rb +130 -0
- data/lib/hecks/projections/site/cms_editor/targets.rb +86 -0
- data/lib/hecks/projections/site/cms_editor/templates/package.json.tmpl +36 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/app.ts.tmpl +370 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/assets.ts.tmpl +98 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/auth/membership.ts.tmpl +87 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/auth/session.ts.tmpl +36 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/auth/sso.ts.tmpl +20 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/app.css.tmpl +412 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/autosave.js.tmpl +144 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/blocks.js.tmpl +500 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/body_doc.js.tmpl +175 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/body_editor.js.tmpl +418 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/epoch.js.tmpl +61 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/forms.js.tmpl +210 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/lists.js.tmpl +40 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/main.js.tmpl +26 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/media_picker.js.tmpl +232 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/moments.js.tmpl +73 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/pickers.js.tmpl +152 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/preview.js.tmpl +98 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/shell.js.tmpl +140 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/toast.js.tmpl +66 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/choices.ts.tmpl +99 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/commands.ts.tmpl +133 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/config.ts.tmpl +29 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/flash.ts.tmpl +52 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/host.ts.tmpl +43 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/media/handler.ts.tmpl +177 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/media/multipart.ts.tmpl +53 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/media/sniff.ts.tmpl +94 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/media/storage.ts.tmpl +50 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/names.ts.tmpl +36 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/schema.ts.tmpl +214 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/server.ts.tmpl +65 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/availability.ts.tmpl +15 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/block_list.js.tmpl +213 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/blocks.ts.tmpl +275 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/body_model.js.tmpl +123 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/body_parse.js.tmpl +320 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/detail.ts.tmpl +88 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/dialogs.ts.tmpl +43 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/fields.ts.tmpl +275 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/html.ts.tmpl +61 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/icons.ts.tmpl +15 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/input.ts.tmpl +92 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/links.ts.tmpl +100 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/moments.ts.tmpl +44 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/outline.ts.tmpl +63 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/pages.ts.tmpl +213 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/paging.ts.tmpl +82 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/pickers.ts.tmpl +110 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/preview.ts.tmpl +87 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/related.ts.tmpl +97 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/schedule.ts.tmpl +160 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/shell.ts.tmpl +211 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/table.ts.tmpl +151 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/theme.js.tmpl +16 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/values.ts.tmpl +69 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/words.ts.tmpl +97 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/world.ts.tmpl +35 -0
- data/lib/hecks/projections/site/cms_editor/templates/tsconfig.json.tmpl +17 -0
- data/lib/hecks/projections/site/cms_editor/theme.rb +29 -0
- data/lib/hecks/projections/site/cms_editor/theme_checks.rb +57 -0
- data/lib/hecks/projections/site/cms_editor.rb +133 -0
- data/lib/hecks/projections/site/role_probe.rb +74 -0
- data/lib/hecks/projector/cli_audience.rb +2 -1
- data/lib/hecks/projector/cli_projector/listing.rb +1 -1
- data/lib/hecks/projector/cli_projector/option_specs.rb +1 -1
- data/lib/hecks/projector/docs_projector/closing.rb +1 -1
- data/lib/hecks/projector/docs_projector.rb +1 -1
- data/lib/hecks/projector/exporter/bindings.rb +40 -10
- data/lib/hecks/projector/exporter/capability_values.rb +55 -0
- data/lib/hecks/projector/narrate_projector/reactions.rb +1 -1
- data/lib/hecks/projector/target.rb +11 -0
- data/lib/hecks/projector.rb +32 -5
- data/lib/hecks/quality_control/adapters/github_checks.rb +12 -8
- data/lib/hecks/quality_control/adapters/github_issues.rb +8 -4
- data/lib/hecks/quality_control/cli/qa_open_pr.rb +2 -2
- data/lib/hecks/quality_control/cli/qa_sweep/checks/shrinking.rb +1 -1
- data/lib/hecks/quality_control/cli/qa_sweep/finding_report.rb +1 -1
- data/lib/hecks/quality_control/cli/qa_sweep.rb +1 -1
- data/lib/hecks/quality_control/quality_control.bluebook +6 -2
- data/lib/hecks/release/approval.rb +64 -0
- data/lib/hecks/router/{door.rb → aggregate_reader.rb} +5 -4
- data/lib/hecks/router/namespace_installer.rb +16 -16
- data/lib/hecks/router.rb +1 -1
- data/lib/hecks/runtime/best_effort.rb +22 -0
- data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +12 -2
- data/lib/hecks/runtime/command_interpreter.rb +3 -2
- data/lib/hecks/runtime/command_rules/emission.rb +1 -1
- data/lib/hecks/runtime/dispatcher.rb +4 -4
- data/lib/hecks/runtime/entity_element/location.rb +1 -1
- data/lib/hecks/runtime/event.rb +5 -0
- data/lib/hecks/runtime/identity.rb +7 -6
- data/lib/hecks/runtime/interpreting.rb +2 -1
- data/lib/hecks/runtime/invocation_builder.rb +1 -1
- data/lib/hecks/runtime/loader/boot_steps.rb +8 -5
- data/lib/hecks/runtime/loader.rb +10 -10
- data/lib/hecks/runtime/policy_interpreter/arguments.rb +7 -4
- data/lib/hecks/runtime/policy_interpreter/fan_out.rb +3 -3
- data/lib/hecks/runtime/policy_interpreter.rb +6 -6
- data/lib/hecks/runtime/port_operation_interpreter.rb +1 -1
- data/lib/hecks/runtime/reaction_invocation.rb +1 -1
- data/lib/hecks/runtime/rebuild_sweep.rb +11 -1
- data/lib/hecks/runtime/registry/ask_verification.rb +35 -0
- data/lib/hecks/runtime/registry/hecksagon_rules.rb +10 -0
- data/lib/hecks/runtime/registry/merging.rb +1 -1
- data/lib/hecks/runtime/registry/providers.rb +16 -0
- data/lib/hecks/runtime/registry/read_repository.rb +6 -4
- data/lib/hecks/runtime/registry/verification.rb +4 -1
- data/lib/hecks/runtime/registry/world_defaults.rb +29 -0
- data/lib/hecks/runtime/registry.rb +1 -1
- data/lib/hecks/runtime/saga_interpreter/compensation.rb +1 -1
- data/lib/hecks/runtime/saga_interpreter/delivery.rb +4 -4
- data/lib/hecks/runtime/saga_interpreter/transitions.rb +1 -1
- data/lib/hecks/runtime/saga_interpreter.rb +3 -3
- data/lib/hecks/runtime/tenant_check.rb +5 -4
- data/lib/hecks/runtime/value/coercion.rb +2 -2
- data/lib/hecks/runtime/value/validation.rb +16 -4
- data/lib/hecks/runtime.rb +9 -9
- data/lib/hecks/rust_build/project_rust/target_ir.rb +1 -1
- data/lib/hecks/rust_build/wasm.rb +1 -1
- data/lib/hecks/rust_build/wasm_browser.rb +1 -1
- data/lib/hecks/rust_build.rb +11 -0
- data/lib/hecks/served_options.rb +53 -0
- data/lib/hecks/site/bluebook/site.bluebook +29 -1
- data/lib/hecks/site/bluebook/site.ports.hecksagon +6 -0
- data/lib/hecks/storehouse/audit.rb +1 -1
- data/lib/hecks/storehouse/dispatching.rb +8 -6
- data/lib/hecks/storehouse/introspection.rb +2 -2
- data/lib/hecks/storehouse/querying.rb +6 -4
- data/lib/hecks/storehouse.rb +4 -4
- data/lib/hecks/tools/comment_style/source_file.rb +4 -1
- data/lib/hecks/tools/deploy_recipe_lint/fixtures.rb +4 -1
- data/lib/hecks/tools/lanes/rows.rb +6 -0
- data/lib/hecks/tools/oidc_manifests.rb +4 -5
- data/lib/hecks/tools/regeneration_run.rb +11 -4
- data/lib/hecks/tools/site_routes/cli.rb +1 -1
- data/lib/hecks/tools/site_routes/editor_files.rb +45 -0
- data/lib/hecks/tools/site_routes.rb +14 -7
- data/lib/hecks/tools/tenant_provisioning.rb +2 -2
- data/lib/hecks/tools/tools_doc.rb +4 -4
- data/lib/hecks/version.rb +1 -1
- data/lib/hecks/vocabulary.rb +4 -4
- data/lib/hecks.rb +15 -15
- data/lib/rubocop/cop/hecks/env_dump.rb +69 -0
- data/lib/rubocop/cop/hecks/monotonic_duration.rb +53 -0
- data/lib/rubocop/cop/hecks/no_wall_clock_in_runtime.rb +34 -0
- data/lib/rubocop/cop/hecks/raw_shell_interpolation.rb +79 -0
- data/lib/rubocop/cop/hecks/sleep_in_spec.rb +34 -0
- data/lib/rubocop/cop/hecks/swallowed_rescue.rb +37 -0
- data/lib/rubocop/cop/hecks/symbol_string_key_mix.rb +50 -0
- data/lib/rubocop/cop/hecks/untyped_raise.rb +37 -0
- data/prebuilt/05c358d38d661a3774f813d49c66f5b68939af94432d66257a56f49ee2598994.marshal +0 -0
- data/prebuilt/26c40c61f09b09b11651c64668eda3ceb180f83cf2a3802d3033d2fd99ac841b.marshal +0 -0
- data/prebuilt/verdicts-230ac18c25abfd3d92ede032bee2661a4c2cd2773ab0cf0e2ae52546ba2f7af9.json +1 -0
- data/rust/.gitignore +6 -0
- data/rust/Cargo.toml +9 -1
- data/rust/build/src/binding_pass.rs +103 -0
- data/rust/build/src/cargo_sync.rs +6 -0
- data/rust/build/src/lineage_pass.rs +3 -3
- data/rust/build/src/main.rs +17 -5
- data/rust/build/src/pipeline.rs +48 -5
- data/rust/build/src/seams_pass.rs +123 -0
- data/rust/build/src/translation_pass.rs +167 -0
- data/rust/codegen/src/asks.rs +103 -0
- data/rust/codegen/src/bridging.rs +1 -1
- data/rust/codegen/src/commands.rs +13 -8
- data/rust/codegen/src/constraints.rs +25 -0
- data/rust/codegen/src/domain_generator.rs +11 -9
- data/rust/codegen/src/expr_emitter.rs +13 -0
- data/rust/codegen/src/json_codec.rs +54 -15
- data/rust/codegen/src/main.rs +2 -1
- data/rust/codegen/src/mutations.rs +174 -4
- data/rust/codegen/src/naming.rs +30 -0
- data/rust/codegen/src/queries.rs +11 -2
- data/rust/codegen/src/reactions.rs +1 -1
- data/rust/codegen/src/types.rs +166 -8
- data/rust/host/HECKS_RELEASE +1 -1
- data/rust/host/src/api.rs +4 -4
- data/rust/host/src/bin/validate_harness.rs +43 -0
- data/rust/host/src/boundary_fuzz/expr_json.rs +2 -2
- data/rust/host/src/boundary_fuzz/ir.rs +1 -0
- data/rust/host/src/boundary_fuzz/server.rs +1 -1
- data/rust/host/src/checkout.rs +35 -19
- data/rust/host/src/commerce_ir.rs +408 -2
- data/rust/host/src/expr_json.rs +118 -0
- data/rust/host/src/mint.rs +1 -1
- data/rust/host/src/payments/tests.rs +27 -1
- data/rust/host/src/payments.rs +12 -4
- data/rust/host/src/query_step.rs +22 -4
- data/rust/host/src/reference_validate.rs +653 -108
- data/rust/host/src/resend.rs +2 -0
- data/rust/host/src/server.rs +19 -6
- data/rust/host/src/ui_schema.rs +132 -5
- data/rust/host/src/web/newsletter.rs +7 -8
- data/rust/host/src/web/newsletter_send.rs +38 -13
- data/rust/host/src/web/registrations.rs +229 -26
- data/rust/host/src/web.rs +3 -3
- data/rust/parser/src/emit.rs +38 -6
- data/rust/parser/src/expr/ast_json.rs +7 -1
- data/rust/parser/src/expr/resolver.rs +171 -6
- data/rust/parser/src/ir.rs +7 -0
- data/rust/parser/src/keywords.rs +24 -0
- data/rust/parser/src/main.rs +3 -0
- data/rust/parser/src/parse/aggregate.rs +2 -1
- data/rust/parser/src/parse/chapter.rs +2 -2
- data/rust/parser/src/parse/command.rs +2 -0
- data/rust/parser/src/parse/hecksagon.rs +5 -2
- data/rust/parser/src/parse/lifecycle.rs +81 -0
- data/rust/parser/src/parse/mod.rs +15 -1
- data/rust/parser/src/parse/policy.rs +13 -2
- data/rust/parser/src/parse/value_object.rs +13 -8
- data/rust/parser/src/ruby_value.rs +1 -1
- data/rust/src/exemplar/commands.rs +4 -4
- data/rust/src/exemplar/constraints.rs +1 -1
- data/rust/src/exemplar/json.rs +43 -1
- data/rust/src/kernel/cli.rs +7 -5
- data/rust/src/kernel/dispatch.rs +2 -2
- data/rust/src/kernel/dispatch_guard_tests.rs +1 -1
- data/rust/src/kernel/expr.rs +73 -4
- data/rust/src/kernel/expression_operators/arithmetic.rs +130 -5
- data/rust/src/kernel/expression_operators/presence.rs +41 -3
- data/rust/src/kernel/expression_operators/text.rs +26 -2
- data/rust/src/kernel/json.rs +15 -0
- data/rust/src/kernel/mod.rs +1 -1
- data/rust/src/kernel/orchestrate.rs +54 -5
- data/rust/src/kernel/orchestrate_guard_tests.rs +67 -0
- data/rust/src/kernel/pattern.rs +60 -0
- data/rust/src/kernel/reference_lookup.rs +90 -7
- metadata +174 -36
- data/lib/hecks/doors/cli_door/nesting.rb +0 -34
- data/lib/hecks/doors/cli_door/shorthand.rb +0 -85
- data/lib/hecks/doors/cli_door.rb +0 -124
- data/lib/hecks/doors/cli_runner/answers.rb +0 -138
- data/lib/hecks/doors/cli_runner/resolution.rb +0 -146
- data/lib/hecks/doors/cli_runner/settling.rb +0 -115
- data/lib/hecks/doors/cli_runner/streaming.rb +0 -90
- data/lib/hecks/doors/cli_runner/suggestions.rb +0 -43
- data/lib/hecks/doors/cli_runner.rb +0 -66
- data/lib/hecks/doors/command_request.rb +0 -119
- data/lib/hecks/doors/handle.rb +0 -208
- data/lib/hecks/doors/json_door.rb +0 -132
- data/lib/hecks/doors/launcher_options.rb +0 -247
- data/lib/hecks/doors/mcp_door/properties.rb +0 -47
- data/lib/hecks/doors/mcp_door/tool_calls.rb +0 -113
- data/lib/hecks/doors/mcp_door/tool_definitions/auditing.rb +0 -51
- data/lib/hecks/doors/mcp_door/tool_definitions/calling.rb +0 -69
- data/lib/hecks/doors/mcp_door/tool_definitions/reading.rb +0 -97
- data/lib/hecks/doors/mcp_door/tool_definitions.rb +0 -15
- data/lib/hecks/doors/mcp_door.rb +0 -156
- data/lib/hecks/doors/mcp_door_scope/argument_policy.rb +0 -92
- data/lib/hecks/doors/mcp_door_scope/env_problems.rb +0 -55
- data/lib/hecks/doors/mcp_door_scope/presenter.rb +0 -78
- data/lib/hecks/doors/mcp_door_scope.rb +0 -201
- data/lib/hecks/doors/mcp_guide_cache.rb +0 -75
- data/lib/hecks/doors/ruby_door/aggregate_door.rb +0 -162
- data/lib/hecks/doors/ruby_door/chapter.rb +0 -89
- data/lib/hecks/doors/ruby_door.rb +0 -80
- data/lib/hecks/doors/usage_cache.rb +0 -139
- data/prebuilt/0ee7f83cba59ce7d1c45c65ba166b071779f70143449f584f33ff7989f64eccb.marshal +0 -0
- data/prebuilt/6e4a6bcf0aa880e2e6b056e1846b70e5a768090b9dc998bbadeaf5d6bcfd3db8.marshal +0 -0
- data/prebuilt/verdicts-91aaf93ce8981bd9a754948f075d95650dc2ec6435015376f5c60992b36a24b1.json +0 -1
|
@@ -8,6 +8,14 @@
|
|
|
8
8
|
# 40 the box stack has no instance
|
|
9
9
|
# 41 the roll did not succeed on the box
|
|
10
10
|
# 42 the roll succeeded but the box is not healthy after it
|
|
11
|
+
# 43 no restore anchor: the database's backups cannot be read, or are kept fewer days than
|
|
12
|
+
# MIN_BACKUP_RETENTION_DAYS (default 3)
|
|
13
|
+
#
|
|
14
|
+
# Before anything changes, the roll names the point the database can be restored to: the instance, the
|
|
15
|
+
# moment this roll starts, and how long its continuous backups are kept. A roll that mints a new era is
|
|
16
|
+
# a one-way door, so this is the rollback anchor, and it costs nothing (a point-in-time restore uses the
|
|
17
|
+
# backups the instance already keeps, where a manual snapshot would be stored until someone deleted it).
|
|
18
|
+
# SKIP_RESTORE_ANCHOR=1 skips it.
|
|
11
19
|
#
|
|
12
20
|
# Before the roll replaces a container, the box saves that container's log to
|
|
13
21
|
# /var/log/hecks-captures/<container>-<UTC timestamp>.log and keeps the newest 14 per container
|
|
@@ -30,6 +38,27 @@ DB_HOST=$(out "$RDS_STACK" DbEndpoint)
|
|
|
30
38
|
[ -n "$BOX" ] && [ "$BOX" != None ] || { echo "no instance in stack $BOX_STACK" >&2; exit 40; }
|
|
31
39
|
ACCOUNT=$(aws sts get-caller-identity --query Account --output text)
|
|
32
40
|
|
|
41
|
+
if [ "${SKIP_RESTORE_ANCHOR:-}" = 1 ]; then
|
|
42
|
+
echo "==> skipping the restore anchor (SKIP_RESTORE_ANCHOR=1)"
|
|
43
|
+
else
|
|
44
|
+
DB_ID=${DB_HOST%%.*}
|
|
45
|
+
BACKUPS=$(aws rds describe-db-instances --db-instance-identifier "$DB_ID" \
|
|
46
|
+
--query 'DBInstances[0].[BackupRetentionPeriod,LatestRestorableTime]' --output text) || BACKUPS=
|
|
47
|
+
read -r KEPT_DAYS RESTORABLE <<< "$BACKUPS"
|
|
48
|
+
case "${KEPT_DAYS:-}" in ''|*[!0-9]*)
|
|
49
|
+
echo "==> no restore anchor: could not read the backups of $DB_ID (SKIP_RESTORE_ANCHOR=1 rolls without one)" >&2
|
|
50
|
+
exit 43 ;;
|
|
51
|
+
esac
|
|
52
|
+
if [ "$KEPT_DAYS" -lt "${MIN_BACKUP_RETENTION_DAYS:-3}" ]; then
|
|
53
|
+
echo "==> no restore anchor: $DB_ID keeps backups $KEPT_DAYS day(s), fewer than ${MIN_BACKUP_RETENTION_DAYS:-3}" >&2
|
|
54
|
+
exit 43
|
|
55
|
+
fi
|
|
56
|
+
ANCHOR_AT=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
57
|
+
echo "==> restore anchor: $DB_ID restorable to $ANCHOR_AT (latest $RESTORABLE, backups kept $KEPT_DAYS days)"
|
|
58
|
+
echo " to return: aws rds restore-db-instance-to-point-in-time --source-db-instance-identifier $DB_ID \\"
|
|
59
|
+
echo " --target-db-instance-identifier $DB_ID-restore --restore-time $ANCHOR_AT"
|
|
60
|
+
fi
|
|
61
|
+
|
|
33
62
|
WORK=$(mktemp -d); trap 'rm -rf "$WORK"' EXIT
|
|
34
63
|
|
|
35
64
|
# SMOKE_LISTENER=1 (a rehearsal box only) mounts a loopback HTTPS site on 127.0.0.1:8443 that adds the
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
# 32 the box's Compose file is unreadable 33 the box stack has no instance
|
|
25
25
|
# 34 the stack has no parameter for the container 35 the stack update failed or did not settle
|
|
26
26
|
# 36 a parameter other than the container's changed 37 the task definition lacks the pushed image
|
|
27
|
-
# 38 the task definition has no such container 40-
|
|
27
|
+
# 38 the task definition has no such container 40-43 deploy-box.sh's own
|
|
28
28
|
#
|
|
29
29
|
# Environment:
|
|
30
30
|
# LOCAL_IMAGE local image to push, default <ecr repository>:latest
|
|
@@ -38,6 +38,7 @@ module Hecks
|
|
|
38
38
|
plan.domain_env = Check.map!(settings.fetch(:domain_env, {}), "domain_env", nil_ok: true)
|
|
39
39
|
plan.install_dir = install_dir(settings.fetch(:install_dir, DEFAULT_INSTALL_DIR))
|
|
40
40
|
plan.build_context_dir = build_context_dir(settings.fetch(:build_context_dir, ""))
|
|
41
|
+
plan.host_dir = host_dir(settings.fetch(:host_crate, DEFAULT_HOST_DIR))
|
|
41
42
|
end
|
|
42
43
|
|
|
43
44
|
def check_execution_grant!(plan)
|
|
@@ -62,6 +63,20 @@ module Hecks
|
|
|
62
63
|
raise ArgumentError, "install_dir must be an absolute path without a trailing slash, got #{value.inspect}"
|
|
63
64
|
end
|
|
64
65
|
|
|
66
|
+
# The directory of the Cargo package the Makefile builds `bootstrap` from: an absolute
|
|
67
|
+
# path, or one relative to the Makefile's `ROOT`. Only path characters are accepted,
|
|
68
|
+
# since the value is written into a Makefile that shell lines expand.
|
|
69
|
+
def host_dir(value)
|
|
70
|
+
text = value.to_s
|
|
71
|
+
return text if text == DEFAULT_HOST_DIR
|
|
72
|
+
return text if text.match?(%r{\A/[A-Za-z0-9_.-]+(/[A-Za-z0-9_.-]+)*\z})
|
|
73
|
+
return "$(ROOT)/#{text}" if text.match?(%r{\A[A-Za-z0-9_.-]+(/[A-Za-z0-9_.-]+)*\z})
|
|
74
|
+
|
|
75
|
+
raise ArgumentError,
|
|
76
|
+
"host_crate must be a directory path of letters, digits, . _ - and /, absolute or relative to the " \
|
|
77
|
+
"Makefile's ROOT, got #{value.inspect}"
|
|
78
|
+
end
|
|
79
|
+
|
|
65
80
|
def build_context_dir(value)
|
|
66
81
|
text = value.to_s
|
|
67
82
|
return text if text.empty? || text.match?(%r{\A[A-Za-z0-9_.-]+(/[A-Za-z0-9_.-]+)*/\z})
|
|
@@ -19,7 +19,7 @@ module Hecks
|
|
|
19
19
|
Plan = Struct.new(
|
|
20
20
|
:ids, :names, :layout, :cdn, :alerts, :extras, :execute_command, :health_check_grace_period,
|
|
21
21
|
:deregistration_delay, :desired_count_parameter, :domain_env, :install_dir, :build_context_dir,
|
|
22
|
-
:db_name_parameter, :execution_database_grant,
|
|
22
|
+
:db_name_parameter, :execution_database_grant, :host_dir,
|
|
23
23
|
keyword_init: true
|
|
24
24
|
)
|
|
25
25
|
|
|
@@ -37,6 +37,8 @@ module Hecks
|
|
|
37
37
|
}.freeze
|
|
38
38
|
NAME_ROLES = [:cluster, :log_group, :service, :alb, :family, :alb_security_group_description, :db_secret_policy].freeze
|
|
39
39
|
DEFAULT_INSTALL_DIR = "/usr/local/bin".freeze
|
|
40
|
+
# Where the generated Makefile builds the host binary unless `host_crate` names another.
|
|
41
|
+
DEFAULT_HOST_DIR = "$(ROOT)/rust/host".freeze
|
|
40
42
|
|
|
41
43
|
extend Ids
|
|
42
44
|
extend Tuning
|
|
@@ -65,6 +65,7 @@ module Hecks
|
|
|
65
65
|
def vpc_ref = shared ? "!Ref OwningVpcId" : "!Ref #{db_id}Vpc"
|
|
66
66
|
def sidecar_dir = plan.install_dir
|
|
67
67
|
def build_context_dir = plan.build_context_dir
|
|
68
|
+
def host_dir = plan.host_dir
|
|
68
69
|
def cmd_binary = sidecar_dir == Settings::DEFAULT_INSTALL_DIR ? domain_name : "#{sidecar_dir}/#{domain_name}"
|
|
69
70
|
def desired_count_value = plan.desired_count_parameter ? "!Ref #{plan.desired_count_parameter}" : desired_count
|
|
70
71
|
def declared_domain_literal = declared_domain_name.inspect
|
|
@@ -365,7 +365,7 @@ signup_admin() {
|
|
|
365
365
|
# Prints what to paste into the browser console to sign in as the preview's first
|
|
366
366
|
# admin: a session for that email signed with THIS preview's session secret (the
|
|
367
367
|
# secret itself is never printed). Anyone with AWS access to the account could
|
|
368
|
-
# mint the same, which is the point: it is the deployer's
|
|
368
|
+
# mint the same, which is the point: it is the deployer's entry, not a public one.
|
|
369
369
|
cmd_login() {
|
|
370
370
|
[ -n "$(stack_status)" ] || die "no preview for branch '$BRANCH' - run 'preview.sh deploy'"
|
|
371
371
|
admin_identity
|
|
@@ -7,6 +7,9 @@ module Hecks
|
|
|
7
7
|
# VPC, RDS/Aurora, bastion and cross-domain invoke plumbing shared by the
|
|
8
8
|
# `Lambda` and `Fargate` deploy targets. Plain module functions, not a Target.
|
|
9
9
|
module Shared
|
|
10
|
+
# Raised when a bastion parameter names a stack output that is not declared.
|
|
11
|
+
class Error < RuntimeError; end
|
|
12
|
+
|
|
10
13
|
# The names a domain's own VPC and database are rendered by.
|
|
11
14
|
#
|
|
12
15
|
# @!attribute [r] shared [Boolean] whether this domain borrows another domain's RDS instance
|
|
@@ -66,11 +69,11 @@ module Hecks
|
|
|
66
69
|
|
|
67
70
|
# Generation-time check that every bastion parameter maps to a stack output.
|
|
68
71
|
#
|
|
69
|
-
# @raise [
|
|
72
|
+
# @raise [Error] if a parameter names a `from_output` absent from `stack_outputs`
|
|
70
73
|
def check_bastion_parameters!(bastion_parameters, stack_outputs)
|
|
71
74
|
bastion_parameters.each do |param|
|
|
72
75
|
stack_outputs.any? { |o| o[:key] == param[:from_output] } or
|
|
73
|
-
raise "bastion parameter #{param[:name]} references undeclared stack output #{param[:from_output]}"
|
|
76
|
+
raise Error, "bastion parameter #{param[:name]} references undeclared stack output #{param[:from_output]}"
|
|
74
77
|
end
|
|
75
78
|
end
|
|
76
79
|
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
# GENERATED by hecks deploy project @@domain@@ — re-run it to refresh
|
|
2
2
|
# this file rather than hand-editing.
|
|
3
3
|
#
|
|
4
|
-
# `make deploy` — builds
|
|
5
|
-
#
|
|
4
|
+
# `make deploy` — builds the host for this domain (rust/host, or the
|
|
5
|
+
# directory `host_crate` names), pushes it to this stack's own ECR
|
|
6
|
+
# repository, and deploys the CloudFormation stack.
|
|
6
7
|
# No SAM anywhere in this path.
|
|
7
8
|
|
|
8
9
|
ROOT := @@root@@
|
|
@@ -11,6 +12,8 @@ STACK := @@stack_name@@
|
|
|
11
12
|
BASTION_STACK := @@stack_name@@-bastion
|
|
12
13
|
REGION := @@region@@
|
|
13
14
|
IMAGE_TAG := latest
|
|
15
|
+
# The Cargo package `bootstrap` is built from; `make HOST_DIR=<dir>` builds another.
|
|
16
|
+
HOST_DIR ?= @@host_dir@@
|
|
14
17
|
|
|
15
18
|
build:
|
|
16
19
|
# aarch64, not x86_64 — matches template.yaml's own
|
|
@@ -33,8 +36,8 @@ build:
|
|
|
33
36
|
# rust/dist/@@domain_name@@.wasm/.ir.json from this domain's own
|
|
34
37
|
# .bluebook.
|
|
35
38
|
cd $(ROOT) && HECKS_ENVIRONMENT=memory ruby exe/hecks build.build_wasm domain=$(DOMAIN) --wait
|
|
36
|
-
cd $(
|
|
37
|
-
cp $(
|
|
39
|
+
cd $(HOST_DIR) && CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc rustup run stable cargo build --release --target aarch64-unknown-linux-gnu --bin bootstrap
|
|
40
|
+
cp $(HOST_DIR)/target/aarch64-unknown-linux-gnu/release/bootstrap @@domain_name@@-host
|
|
38
41
|
cp $(ROOT)/rust/dist/@@domain_name@@.wasm @@domain_name@@.wasm
|
|
39
42
|
cp $(ROOT)/rust/dist/@@domain_name@@.ir.json @@domain_name@@.ir.json
|
|
40
43
|
|
|
@@ -17,8 +17,8 @@ module Hecks
|
|
|
17
17
|
|
|
18
18
|
# One model field gathered from several declared ones.
|
|
19
19
|
FOLDED = {
|
|
20
|
-
"Aggregate" => { lifecycle: %i[state_field state_start transitions] },
|
|
21
|
-
"Entity" => { lifecycle: %i[state_field state_start transitions] },
|
|
20
|
+
"Aggregate" => { lifecycle: %i[state_field state_start transitions marks] },
|
|
21
|
+
"Entity" => { lifecycle: %i[state_field state_start transitions marks] },
|
|
22
22
|
"Query" => { order_by: %i[order_field order_way] }
|
|
23
23
|
}.freeze
|
|
24
24
|
|
|
@@ -16,10 +16,10 @@ module Hecks
|
|
|
16
16
|
construct: "Policy",
|
|
17
17
|
file: "policy.rb",
|
|
18
18
|
behaviour: "Behaviour::Policy",
|
|
19
|
-
readers: %i[name on_event trigger_command target_domain expect_undelivered where for_each with_spec],
|
|
19
|
+
readers: %i[name on_event trigger_command ask target_domain expect_undelivered where for_each with_spec],
|
|
20
20
|
accessors: %i[aggregate],
|
|
21
21
|
defaults: { name: nil, on_event: "nil", trigger_command: "nil",
|
|
22
|
-
target_domain: "nil", expect_undelivered: "false", where: "nil", for_each: "nil",
|
|
22
|
+
ask: "nil", target_domain: "nil", expect_undelivered: "false", where: "nil", for_each: "nil",
|
|
23
23
|
with_spec: "[]", aggregate: "nil" },
|
|
24
24
|
# The language holds a flag as "true" and the builder as `true`; both become a boolean.
|
|
25
25
|
coerce: { name: ".to_s", aggregate: "&.to_s", expect_undelivered: ".to_s == \"true\"" },
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
require "json"
|
|
1
2
|
require_relative "../naming"
|
|
2
3
|
require_relative "../projector"
|
|
3
4
|
|
|
@@ -35,6 +36,17 @@ module Hecks
|
|
|
35
36
|
}
|
|
36
37
|
end
|
|
37
38
|
|
|
39
|
+
# The manifest as the text `oidc.json` holds: indented JSON ending in a newline, with an empty
|
|
40
|
+
# list as `[]`. `JSON.pretty_generate` spells an empty list `[\n\n]` in some json releases
|
|
41
|
+
# (2.7.2 among them) and `[]` in others, so the file is normalised here and a committed
|
|
42
|
+
# manifest compares equal whichever json a machine has.
|
|
43
|
+
#
|
|
44
|
+
# @param manifest [Hash{String => Object}] what `call` returns
|
|
45
|
+
# @return [String] the text of the manifest file
|
|
46
|
+
def render(manifest)
|
|
47
|
+
"#{JSON.pretty_generate(manifest).gsub(/\[\n\s*\]/, "[]")}\n"
|
|
48
|
+
end
|
|
49
|
+
|
|
38
50
|
# Every command's scope entry, entity commands included (they dispatch as `Entity.Command`).
|
|
39
51
|
# Sorted by scope so two manifest versions diff cleanly.
|
|
40
52
|
#
|
|
@@ -79,7 +91,7 @@ module Hecks
|
|
|
79
91
|
end
|
|
80
92
|
|
|
81
93
|
# One scope entry per command, spelled like `banking:account.open`; snake-cased through
|
|
82
|
-
# `Naming.snake`, as the facade names its
|
|
94
|
+
# `Naming.snake`, as the facade names its module methods.
|
|
83
95
|
#
|
|
84
96
|
# @param commands [Array<Bluebook::Command>] the commands to project
|
|
85
97
|
# @param verb_prefix [String] the owning aggregate or entity's fully-qualified
|
|
@@ -6,6 +6,9 @@ module Hecks
|
|
|
6
6
|
# The storage shape of one bluebook, the form `StorageShape.mint_hash` hashes to name an era.
|
|
7
7
|
# Diffing two of these answers whether a change bumps the era.
|
|
8
8
|
module Shape
|
|
9
|
+
# Raised when the shape projection is asked for without the era plugin loaded.
|
|
10
|
+
class Error < RuntimeError; end
|
|
11
|
+
|
|
9
12
|
extend Projector::Target
|
|
10
13
|
|
|
11
14
|
projects_as :shape
|
|
@@ -15,8 +18,8 @@ module Hecks
|
|
|
15
18
|
# Registered even when the era plugin is unloaded; the call then refuses clearly.
|
|
16
19
|
def call(bluebook:, options: {})
|
|
17
20
|
unless Ports::Persistence.plugin?(:era)
|
|
18
|
-
raise "the :shape projection needs the era persistence plugin loaded " \
|
|
19
|
-
|
|
21
|
+
raise Error, "the :shape projection needs the era persistence plugin loaded " \
|
|
22
|
+
"(require \"hecks/ports/persistence/plugins/era\")"
|
|
20
23
|
end
|
|
21
24
|
|
|
22
25
|
Runtime::StorageShape.project(bluebook)
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "blocks"
|
|
4
|
+
require_relative "hints"
|
|
5
|
+
|
|
6
|
+
module Hecks
|
|
7
|
+
module Projections
|
|
8
|
+
module Site
|
|
9
|
+
module CmsEditor
|
|
10
|
+
# One attribute as the editor reads it: its name, the type as the bluebook spells it, how a
|
|
11
|
+
# value of it is entered (`kind`), and whether it is optional or a list.
|
|
12
|
+
#
|
|
13
|
+
# A type that names one of the aggregate's value objects is an `object`; a reference is a
|
|
14
|
+
# `reference`; the primitives are `integer`, `number`, `boolean` and `text`, and any other
|
|
15
|
+
# is text. The shape decides what more it is, never a name: a body (`widget: "body"`), a
|
|
16
|
+
# block list (see `Blocks`), a moment, a `<kind>:` key (see `Hints`) or a picture.
|
|
17
|
+
class Attributes
|
|
18
|
+
# The parts a value object needs for its list of blocks to be a document body's blocks.
|
|
19
|
+
BLOCK_PARTS = %w[kind spans].freeze
|
|
20
|
+
|
|
21
|
+
# The kind a primitive's name enters as; any other name is text.
|
|
22
|
+
KINDS = { "Integer" => "integer", "Float" => "number", "Boolean" => "boolean", "TrueClass" => "boolean",
|
|
23
|
+
"FalseClass" => "boolean" }.freeze
|
|
24
|
+
|
|
25
|
+
# @param objects [Array<Bluebook::ValueObject>] the aggregate's value objects
|
|
26
|
+
def initialize(objects)
|
|
27
|
+
@by_name = objects.to_h { |object| [object.hecks_name, object] }
|
|
28
|
+
@objects = @by_name.keys
|
|
29
|
+
@hints = Hints.new(@by_name)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# @param attribute [Bluebook::Attribute] a declared attribute
|
|
33
|
+
# @return [Hash{String => Object}] the attribute as the editor reads it
|
|
34
|
+
def of(attribute)
|
|
35
|
+
type = attribute.type
|
|
36
|
+
return reference(attribute, type.target_name) if type.is_a?(Bluebook::Reference)
|
|
37
|
+
|
|
38
|
+
entry = base(attribute, type.to_s, kind(type.to_s))
|
|
39
|
+
return entry.merge("widget" => "body") if body?(attribute)
|
|
40
|
+
|
|
41
|
+
entry.merge(@hints.of(attribute), listing(attribute), pictured(attribute))
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
# @return [Hash{String => Object}] `blockList` when the attribute is a list of blocks
|
|
47
|
+
def listing(attribute)
|
|
48
|
+
found = Blocks.of(attribute, @by_name)
|
|
49
|
+
found ? { "blockList" => found } : {}
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# @return [Hash{String => Object}] `picture` (the parts that name the file, its
|
|
53
|
+
# description and its caption) when the value object is a picture: it has a media key
|
|
54
|
+
# and an `alt` and is not a block of a body or a list of mixed blocks
|
|
55
|
+
def pictured(attribute)
|
|
56
|
+
object = @by_name[attribute.type.to_s]
|
|
57
|
+
return {} unless object && !Blocks.body_block?(object) && !listing(attribute).key?("blockList")
|
|
58
|
+
|
|
59
|
+
found = Blocks.picture(object)
|
|
60
|
+
found&.key?("key") && found.key?("alt") ? { "picture" => found } : {}
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# A single (not listed) attribute whose value object holds a list of document blocks.
|
|
64
|
+
def body?(attribute)
|
|
65
|
+
return false if attribute.list?
|
|
66
|
+
|
|
67
|
+
block = block_object(@by_name[attribute.type.to_s])
|
|
68
|
+
!block.nil? && (BLOCK_PARTS - block.attributes.map { |part| part.name.to_s }).empty?
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# @return [Bluebook::ValueObject, nil] the value object `object`'s `blocks` list holds
|
|
72
|
+
def block_object(object)
|
|
73
|
+
blocks = object&.attributes&.find { |part| part.name.to_s == "blocks" && part.list? }
|
|
74
|
+
blocks && @by_name[blocks.type.to_s]
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def reference(attribute, target)
|
|
78
|
+
base(attribute, target, "reference").merge("target" => target)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def base(attribute, type, kind)
|
|
82
|
+
{ "name" => attribute.name.to_s, "type" => type, "kind" => kind,
|
|
83
|
+
"optional" => attribute.optional?, "list" => attribute.list? }
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def kind(type)
|
|
87
|
+
return "object" if @objects.include?(type)
|
|
88
|
+
|
|
89
|
+
KINDS.fetch(type, "text")
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
module Projections
|
|
5
|
+
module Site
|
|
6
|
+
module CmsEditor
|
|
7
|
+
# A list of mixed blocks, found by shape: a `list_of` value object that has a discriminator,
|
|
8
|
+
# a part that is one of a closed set of kinds. The editor draws it as ordered cards.
|
|
9
|
+
#
|
|
10
|
+
# The discriminator is a part whose type is a closed set (`one_of`) of one plain value, or,
|
|
11
|
+
# failing that, a text part that an invariant restricts to a literal list
|
|
12
|
+
# (`["a", "b"].include?(kind)`). A value object that holds `spans` is a body's block, which
|
|
13
|
+
# the rich-text widget edits, and is never a block list.
|
|
14
|
+
#
|
|
15
|
+
# Which slots each kind uses is read from a declared table: a closed set of rows with the
|
|
16
|
+
# parts `kind`, `requires` and `uses` whose kinds are among the discriminator's. With no
|
|
17
|
+
# such table every slot is shown, and the invariants that name a kind only hint which slots
|
|
18
|
+
# it reads. A limit is read from an invariant or given of the form `<list>.size <= <n>`.
|
|
19
|
+
module Blocks
|
|
20
|
+
# The parts a table of rules must have.
|
|
21
|
+
RULE_PARTS = %w[kind requires uses].freeze
|
|
22
|
+
|
|
23
|
+
# The parts that make a value object's picture, in the vocabulary of a body's image block.
|
|
24
|
+
PICTURE = { "key" => "media_ref", "alt" => "alt", "caption" => "caption" }.freeze
|
|
25
|
+
|
|
26
|
+
# An invariant that restricts a text part to a literal list of kinds.
|
|
27
|
+
LITERALS = /\A\[((?:"[^"]+"(?:, )?)+)\]\.include\?\((\w+)\)\z/
|
|
28
|
+
|
|
29
|
+
# An invariant that holds unless the kind is the named one: what the rest of it reads.
|
|
30
|
+
GUARD = /\A(\w+)(?:\.value)? != "([^"]+)" \|\| (.*)\z/m
|
|
31
|
+
|
|
32
|
+
# A limit written as a size comparison.
|
|
33
|
+
LIMIT = /\A(\w+)\.size <= (\d+)\z/
|
|
34
|
+
|
|
35
|
+
module_function
|
|
36
|
+
|
|
37
|
+
# @param attribute [Bluebook::Attribute] a declared attribute
|
|
38
|
+
# @param objects [Hash{String => Bluebook::ValueObject}] the aggregate's value objects
|
|
39
|
+
# @return [Hash{String => Object}, nil] the block list the attribute is, or nil
|
|
40
|
+
def of(attribute, objects)
|
|
41
|
+
item = objects[attribute.type.to_s]
|
|
42
|
+
return nil unless attribute.list? && item && !body_block?(item)
|
|
43
|
+
|
|
44
|
+
found = discriminator(item, objects)
|
|
45
|
+
found && describe(item, objects, *found)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# @return [Boolean] whether the value object holds `spans`, as a body's block does
|
|
49
|
+
def body_block?(item) = item.attributes.any? { |part| part.name.to_s == "spans" }
|
|
50
|
+
|
|
51
|
+
# @return [Array(String, Array<String>), nil] the part and the kinds it is one of
|
|
52
|
+
def discriminator(item, objects)
|
|
53
|
+
closed(item, objects) || literal(item)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# @return [Array(String, Array<String>), nil] a part typed as a closed set of plain values
|
|
57
|
+
def closed(item, objects)
|
|
58
|
+
item.attributes.each do |part|
|
|
59
|
+
kinds = members_of(objects[part.type.to_s]) unless part.list?
|
|
60
|
+
return [part.name.to_s, kinds] if kinds && !kinds.empty?
|
|
61
|
+
end
|
|
62
|
+
nil
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# @return [Array<String>, nil] the values of a closed set of one plain part
|
|
66
|
+
def members_of(set)
|
|
67
|
+
return nil unless set&.closed_set? && set.attributes.size == 1
|
|
68
|
+
|
|
69
|
+
set.members.filter_map { |member| member[set.attributes.first.name]&.to_s }
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# @return [Array(String, Array<String>), nil] a text part restricted by a literal list
|
|
73
|
+
def literal(item)
|
|
74
|
+
found = literals(item).find { |match| item.attributes.any? { |part| part.name.to_s == match[2] && !part.list? } }
|
|
75
|
+
found && [found[2], found[1].scan(/"([^"]+)"/).flatten]
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# @return [Array<MatchData>] the invariants that restrict a part to a literal list
|
|
79
|
+
def literals(item) = item.invariants.filter_map { |rule| LITERALS.match(rule.canonical.to_s) }
|
|
80
|
+
|
|
81
|
+
def describe(item, objects, name, kinds)
|
|
82
|
+
table = rules(objects, kinds)
|
|
83
|
+
found = { "discriminator" => name, "kinds" => kinds, "limits" => limits(item), "pictures" => pictures(item, objects) }
|
|
84
|
+
found["rules"] = table if table
|
|
85
|
+
found["mentions"] = mentions(item, name, kinds) unless table
|
|
86
|
+
found.reject { |_, value| value.respond_to?(:empty?) && value.empty? }
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# @return [Hash{String => Hash}, nil] each kind's `requires` and `uses`, from the table
|
|
90
|
+
def rules(objects, kinds)
|
|
91
|
+
table = objects.values.find { |object| ruled?(object, kinds) }
|
|
92
|
+
return nil unless table
|
|
93
|
+
|
|
94
|
+
table.members.to_h { |row| [row[:kind].to_s, { "requires" => words(row[:requires]), "uses" => words(row[:uses]) }] }
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def ruled?(object, kinds)
|
|
98
|
+
object.closed_set? && (RULE_PARTS - object.attributes.map { |part| part.name.to_s }).empty? &&
|
|
99
|
+
object.members.all? { |row| kinds.include?(row[:kind].to_s) }
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def words(text) = text.to_s.split(",").map(&:strip).reject(&:empty?)
|
|
103
|
+
|
|
104
|
+
# @return [Hash{String => Array<String>}] the slots each kind's guarded invariants read
|
|
105
|
+
def mentions(item, name, kinds)
|
|
106
|
+
parts = item.attributes.map { |part| part.name.to_s } - [name]
|
|
107
|
+
guarded(item, name).each_with_object({}) do |(kind, rest), found|
|
|
108
|
+
next unless kinds.include?(kind)
|
|
109
|
+
|
|
110
|
+
found[kind] = ((found[kind] || []) | parts.select { |part| rest.match?(/\b#{Regexp.escape(part)}\b/) })
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def guarded(item, name)
|
|
115
|
+
item.invariants.filter_map do |rule|
|
|
116
|
+
match = GUARD.match(rule.canonical.to_s)
|
|
117
|
+
[match[2], match[3]] if match && match[1] == name
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# @return [Hash{String => Integer}] each list part of the item and its declared maximum
|
|
122
|
+
def limits(item)
|
|
123
|
+
item.invariants.filter_map do |rule|
|
|
124
|
+
match = LIMIT.match(rule.canonical.to_s)
|
|
125
|
+
[match[1], match[2].to_i] if match
|
|
126
|
+
end.to_h
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# @param commands [Array<Bluebook::Command>] the aggregate's commands
|
|
130
|
+
# @param name [String] a list attribute's name
|
|
131
|
+
# @return [Integer, nil] the most the list may hold, from a command's `given`
|
|
132
|
+
def maximum(commands, name)
|
|
133
|
+
sizes = commands.flat_map(&:givens).filter_map do |rule|
|
|
134
|
+
match = LIMIT.match(rule.canonical.to_s)
|
|
135
|
+
match[2].to_i if match && match[1] == name
|
|
136
|
+
end
|
|
137
|
+
sizes.min
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# @return [Hash{String => Hash}] the value object and each value object its lists hold,
|
|
141
|
+
# to the parts that name a picture, for those that have the parts
|
|
142
|
+
def pictures(item, objects)
|
|
143
|
+
held = item.attributes.select(&:list?).filter_map { |part| objects[part.type.to_s] }
|
|
144
|
+
[item, *held].filter_map { |object| (found = picture(object)) && [object.hecks_name, found] }.to_h
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def picture(object)
|
|
148
|
+
names = object.attributes.reject(&:list?).map { |part| part.name.to_s }
|
|
149
|
+
return nil unless names.include?(PICTURE["key"]) && names.include?(PICTURE["alt"])
|
|
150
|
+
|
|
151
|
+
PICTURE.select { |_, part| names.include?(part) }
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "preview"
|
|
4
|
+
require_relative "scale_checks"
|
|
5
|
+
require_relative "theme_checks"
|
|
6
|
+
|
|
7
|
+
module Hecks
|
|
8
|
+
module Projections
|
|
9
|
+
module Site
|
|
10
|
+
module CmsEditor
|
|
11
|
+
# What the `Editor` row must satisfy beyond the class of each field: paths that start with
|
|
12
|
+
# a slash, a sign-in path the editor serves itself, a role to admit, and a login page a
|
|
13
|
+
# visitor with no session can reach.
|
|
14
|
+
module Checks
|
|
15
|
+
# A base path: one or more segments, starting with a slash and not ending with one.
|
|
16
|
+
BASE_PATH = %r{\A(/[^/\s]+)+\z}
|
|
17
|
+
|
|
18
|
+
# The largest upload size cap a row may name: 100 MiB.
|
|
19
|
+
MAX_UPLOAD = 104_857_600
|
|
20
|
+
|
|
21
|
+
module_function
|
|
22
|
+
|
|
23
|
+
# @param row [Hash{Symbol => String}] the row, defaults filled
|
|
24
|
+
# @param roles [Array<String>] the roles the row names
|
|
25
|
+
# @param table [Table, nil] the checked route table, to check the login page against
|
|
26
|
+
# @return [Array<String>] every problem, in the order the fields are declared
|
|
27
|
+
def problems(row, roles, table)
|
|
28
|
+
base = row[:base_path]
|
|
29
|
+
[*path_problems(row, base), *("Editor roles name no role" if roles.empty?), *login_problems(row[:login], table),
|
|
30
|
+
*media_problems(row), *media_chapter_problems(row), *ThemeChecks.problems(row), *ScaleChecks.problems(row),
|
|
31
|
+
*Preview.problems(row[:preview].strip)]
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# @return [Array<String>] the problems with the base path and the sign-in path
|
|
35
|
+
def path_problems(row, base)
|
|
36
|
+
found = []
|
|
37
|
+
found << "base_path #{base.inspect} must start with a slash and not end with one" unless BASE_PATH.match?(base)
|
|
38
|
+
found << "sso_path #{row[:sso_path]} must be under #{base}/" unless row[:sso_path].start_with?("#{base}/")
|
|
39
|
+
found
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# @return [Array<String>] the problems with the picture directory and the upload size cap
|
|
43
|
+
def media_problems(row)
|
|
44
|
+
found = []
|
|
45
|
+
found << "media_dir must not be empty" if row[:media_dir].strip.empty?
|
|
46
|
+
cap = row[:media_max_bytes]
|
|
47
|
+
unless cap.match?(/\A[1-9]\d{0,9}\z/) && cap.to_i <= MAX_UPLOAD
|
|
48
|
+
found << "media_max_bytes #{cap.inspect} must be a whole number of bytes from 1 to #{MAX_UPLOAD}"
|
|
49
|
+
end
|
|
50
|
+
found
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# @return [Array<String>] the problem with a `media` chapter that is one the editor edits
|
|
54
|
+
def media_chapter_problems(row)
|
|
55
|
+
named = row[:media].strip
|
|
56
|
+
return [] unless row[:chapter].split(",").map(&:strip).include?(named)
|
|
57
|
+
|
|
58
|
+
["media #{named.inspect} is this editor's own chapter; name another chapter"]
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# The login page is a public row: a visitor with no session must reach it.
|
|
62
|
+
#
|
|
63
|
+
# @return [Array<String>] the problems with the login page
|
|
64
|
+
def login_problems(login, table)
|
|
65
|
+
return ["login #{login} must start with a slash"] unless login.start_with?("/")
|
|
66
|
+
return [] if table.nil?
|
|
67
|
+
|
|
68
|
+
found = table.rows.find { |candidate| candidate.path == login }
|
|
69
|
+
return ["login #{login} is not a route of the table"] if found.nil?
|
|
70
|
+
|
|
71
|
+
found.auth == "public" ? [] : ["login #{login} is #{found.auth}; the login page must be public"]
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
module Projections
|
|
5
|
+
module Site
|
|
6
|
+
module CmsEditor
|
|
7
|
+
# A command argument that only clears: the bluebook's way of setting an optional field back
|
|
8
|
+
# to nothing is an optional argument the command never reads, named as the source of a
|
|
9
|
+
# `sets <field>, to: :<argument>` mutation (`attribute :nothing, Body, optional: true`,
|
|
10
|
+
# `sets :draft_body, to: :nothing`). Nobody types into it, so the editor offers no field
|
|
11
|
+
# for it and the server sends its empty value itself: an empty list, or nothing at all.
|
|
12
|
+
#
|
|
13
|
+
# The shape decides it, never a name. The argument is optional, is no attribute of the
|
|
14
|
+
# aggregate, feeds only plain `sets` mutations of other fields that can be empty (optional
|
|
15
|
+
# or a list), and no `given` or `ensures` of the command reads it.
|
|
16
|
+
module Clearing
|
|
17
|
+
module_function
|
|
18
|
+
|
|
19
|
+
# @param command [Bluebook::Command] the command
|
|
20
|
+
# @param aggregate [Bluebook::Aggregate] the aggregate it belongs to
|
|
21
|
+
# @return [Array<Bluebook::Attribute>] the command's arguments that only clear
|
|
22
|
+
def of(command, aggregate)
|
|
23
|
+
command.attributes.select { |argument| only_clears?(argument, command, aggregate) }
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# @param shaped [Hash{String => Object}] the command as the editor reads it
|
|
27
|
+
# @param only [Array<Bluebook::Attribute>] its arguments that only clear
|
|
28
|
+
# @return [Hash{String => Object}] `shaped` without those arguments, and naming the ones
|
|
29
|
+
# that are lists as `empty`, for the editor to send as empty lists
|
|
30
|
+
def apply(shaped, only)
|
|
31
|
+
return shaped if only.empty?
|
|
32
|
+
|
|
33
|
+
names = only.map { |argument| argument.name.to_s }
|
|
34
|
+
lists = only.select(&:list?).map { |argument| argument.name.to_s }
|
|
35
|
+
kept = shaped["attributes"].reject { |attr| names.include?(attr["name"]) }
|
|
36
|
+
shaped.merge("attributes" => kept, **(lists.empty? ? {} : { "empty" => lists }))
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# @return [Boolean] whether `argument` is a clearing argument of `command`
|
|
40
|
+
def only_clears?(argument, command, aggregate)
|
|
41
|
+
return false if !argument.optional? || named?(aggregate.attributes, argument.name)
|
|
42
|
+
|
|
43
|
+
fed = fed_by(argument, command)
|
|
44
|
+
!fed.empty? && fed.all? { |mutation| clears?(mutation, aggregate) } && !read?(argument, command)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# @return [Array<Bluebook::Mutation>] the mutations that take their value from `argument`
|
|
48
|
+
def fed_by(argument, command)
|
|
49
|
+
command.mutations.select { |mutation| mutation.source.is_a?(Symbol) && named?([argument], mutation.source) }
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# @return [Boolean] whether any of `attributes` is called `name`
|
|
53
|
+
def named?(attributes, name) = attributes.any? { |held| held.name.to_s == name.to_s }
|
|
54
|
+
|
|
55
|
+
# @return [Boolean] whether the mutation is a plain set of a field that can be empty
|
|
56
|
+
def clears?(mutation, aggregate)
|
|
57
|
+
target = aggregate.attributes.find { |held| held.name.to_s == mutation.target.to_s }
|
|
58
|
+
mutation.op == :set && !target.nil? && (target.optional? || target.list?)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# @return [Boolean] whether a `given` or `ensures` of the command names the argument
|
|
62
|
+
def read?(argument, command)
|
|
63
|
+
word = /\b#{Regexp.escape(argument.name.to_s)}\b/
|
|
64
|
+
[*command.givens, *command.ensures].any? { |rule| word.match?(rule.canonical.to_s) }
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|