hecks 3.10.0 → 3.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/examples/pizzas/bluebook/pizzas.bluebook +29 -0
- data/examples/pizzas/bluebook/pizzas.hecksagon +7 -0
- data/exe/hecks +4 -4
- data/lib/hecks/adapters/driven/d1.rb +2 -10
- data/lib/hecks/adapters/driven/heki.adapter +1 -1
- data/lib/hecks/adapters/driven/heki.rb +2 -12
- data/lib/hecks/adapters/driven/lambda.adapter +1 -1
- data/lib/hecks/adapters/driven/lambda.rb +3 -18
- data/lib/hecks/adapters/driven/postgres.rb +4 -11
- data/lib/hecks/adapters/driven/sqlite.adapter +1 -1
- data/lib/hecks/adapters/driven/sqlite.rb +2 -10
- data/lib/hecks/adapters/driving/admission.rb +46 -0
- data/lib/hecks/adapters/driving/cli/nesting.rb +36 -0
- data/lib/hecks/adapters/driving/cli/shorthand.rb +88 -0
- data/lib/hecks/adapters/driving/cli.rb +127 -0
- data/lib/hecks/adapters/driving/cli_runner/answers.rb +160 -0
- data/lib/hecks/adapters/driving/cli_runner/resolution.rb +155 -0
- data/lib/hecks/adapters/driving/cli_runner/settling.rb +120 -0
- data/lib/hecks/adapters/driving/cli_runner/streaming.rb +93 -0
- data/lib/hecks/adapters/driving/cli_runner/suggestions.rb +47 -0
- data/lib/hecks/adapters/driving/cli_runner.rb +69 -0
- data/lib/hecks/adapters/driving/command_request.rb +121 -0
- data/lib/hecks/adapters/driving/handle.rb +214 -0
- data/lib/hecks/adapters/driving/json.rb +136 -0
- data/lib/hecks/adapters/driving/launcher_options.rb +267 -0
- data/lib/hecks/adapters/driving/mcp/properties.rb +49 -0
- data/lib/hecks/adapters/driving/mcp/tool_calls.rb +116 -0
- data/lib/hecks/adapters/driving/mcp/tool_definitions/auditing.rb +53 -0
- data/lib/hecks/adapters/driving/mcp/tool_definitions/calling.rb +71 -0
- data/lib/hecks/adapters/driving/mcp/tool_definitions/reading.rb +99 -0
- data/lib/hecks/adapters/driving/mcp/tool_definitions.rb +18 -0
- data/lib/hecks/adapters/driving/mcp.rb +159 -0
- data/lib/hecks/adapters/driving/mcp_guide_cache.rb +78 -0
- data/lib/hecks/adapters/driving/mcp_scope/argument_policy.rb +97 -0
- data/lib/hecks/adapters/driving/mcp_scope/env_problems.rb +69 -0
- data/lib/hecks/adapters/driving/mcp_scope/presenter.rb +83 -0
- data/lib/hecks/adapters/driving/mcp_scope.rb +213 -0
- data/lib/hecks/adapters/driving/ruby/aggregate_module.rb +171 -0
- data/lib/hecks/adapters/driving/ruby/chapter.rb +91 -0
- data/lib/hecks/adapters/driving/ruby.rb +83 -0
- data/lib/hecks/adapters/driving/usage_cache.rb +144 -0
- data/lib/hecks/{doors.rb → adapters/driving.rb} +10 -6
- data/lib/hecks/behaviors/expectations/boot.rb +1 -1
- data/lib/hecks/bench/ruby_runner.rb +1 -1
- data/lib/hecks/bluebook/ask_resolution.rb +130 -0
- data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +8 -2
- data/lib/hecks/bluebook/assembly/contracts/rules.rb +4 -2
- data/lib/hecks/bluebook/assembly/contracts/structure.rb +3 -1
- data/lib/hecks/bluebook/assembly/marks.rb +1 -0
- data/lib/hecks/bluebook/attribute.rb +1 -1
- data/lib/hecks/bluebook/behaviour/command.rb +13 -8
- data/lib/hecks/bluebook/behaviour/policy.rb +30 -0
- data/lib/hecks/bluebook/capabilities/attribute_values.rb +72 -0
- data/lib/hecks/bluebook/capabilities.rb +91 -9
- data/lib/hecks/bluebook/domain_port.rb +16 -4
- data/lib/hecks/bluebook/dsl/adapter_builder.rb +18 -6
- data/lib/hecks/bluebook/dsl/binding_proxy.rb +13 -0
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/computed_sources.rb +98 -0
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/fan_out_rows.rb +42 -0
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/projected_fields.rb +14 -2
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/provisions.rb +69 -3
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/with_projections.rb +17 -10
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +5 -0
- data/lib/hecks/bluebook/dsl/bootstrap_table.rb +2 -0
- data/lib/hecks/bluebook/dsl/command_builder/mutations.rb +8 -1
- data/lib/hecks/bluebook/dsl/command_builder/operands.rb +121 -0
- data/lib/hecks/bluebook/dsl/command_builder.rb +2 -0
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +10 -1
- data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +45 -2
- data/lib/hecks/bluebook/dsl/policy_builder.rb +47 -10
- data/lib/hecks/bluebook/dsl/rule_reference.rb +7 -4
- data/lib/hecks/bluebook/dsl/world_builder.rb +1 -1
- data/lib/hecks/bluebook/expression/ast_json/emitters.rb +6 -0
- data/lib/hecks/bluebook/expression/ast_json.rb +9 -6
- data/lib/hecks/bluebook/expression/ast_reader.rb +14 -9
- data/lib/hecks/bluebook/expression/projection.json +36 -0
- data/lib/hecks/bluebook/expression/resolver/operations.rb +31 -0
- data/lib/hecks/bluebook/expression/resolver/parsing.rb +38 -5
- data/lib/hecks/bluebook/expression/resolver/scanning.rb +22 -5
- data/lib/hecks/bluebook/expression/resolver/values.rb +10 -0
- data/lib/hecks/bluebook/expression/resolver.rb +20 -1
- data/lib/hecks/bluebook/hecksagon.rb +23 -3
- data/lib/hecks/bluebook/lifecycle.rb +22 -3
- data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +13 -1
- data/lib/hecks/bluebook/meta_validator/modes.rb +4 -1
- data/lib/hecks/bluebook/meta_validator/readings.rb +18 -1
- data/lib/hecks/bluebook/meta_validator/reconstruction/heads.rb +2 -1
- data/lib/hecks/bluebook/meta_validator/shapes.rb +5 -0
- data/lib/hecks/bluebook/meta_validator/verdict_cache.rb +1 -1
- data/lib/hecks/bluebook/meta_validator.rb +12 -12
- data/lib/hecks/bluebook/model_check/ask_checks.rb +71 -0
- data/lib/hecks/bluebook/model_check/policy_checks.rb +2 -1
- data/lib/hecks/bluebook/model_check/reference_checks.rb +70 -0
- data/lib/hecks/bluebook/model_check.rb +8 -2
- data/lib/hecks/bluebook/policy.rb +4 -2
- data/lib/hecks/bluebook/smoke_test.rb +1 -1
- data/lib/hecks/bluebook/synthesizer.rb +2 -0
- data/lib/hecks/bluebook.rb +1 -0
- data/lib/hecks/cli/command_table.rb +1 -1
- data/lib/hecks/cli/console.rb +2 -1
- data/lib/hecks/cli/document.rb +1 -1
- data/lib/hecks/cli/launcher_words.rb +4 -4
- data/lib/hecks/cli/mcp.rb +4 -4
- data/lib/hecks/cli/project_cli/launcher_source.rb +2 -2
- data/lib/hecks/cli/project_cli/templates.rb +5 -5
- data/lib/hecks/cli/project_cli.rb +9 -6
- data/lib/hecks/cli/run.rb +2 -2
- data/lib/hecks/cli/smoke_http/checks.rb +4 -1
- data/lib/hecks/cli/smoke_http/transport.rb +3 -3
- data/lib/hecks/cli/smoke_http.rb +3 -0
- data/lib/hecks/codemod.rb +5 -2
- data/lib/hecks/corpus/routes.rb +7 -0
- data/lib/hecks/corpus/rust.rb +3 -2
- data/lib/hecks/deploy/bluebook/deploy.bluebook +18 -2
- data/lib/hecks/doc/reference/rendering.rb +6 -3
- data/lib/hecks/forms/app/commands.rb +8 -7
- data/lib/hecks/forms/app/queries.rb +2 -1
- data/lib/hecks/forms/app.rb +1 -1
- data/lib/hecks/forms/record_renderer.rb +1 -1
- data/lib/hecks/framework/bluebook/privacy.bluebook +11 -0
- data/lib/hecks/fuzzing/concurrent_dispatch.rb +4 -1
- data/lib/hecks/fuzzing/failure_triage.rb +2 -1
- data/lib/hecks/fuzzing/mutation/run.rb +11 -4
- data/lib/hecks/fuzzing/properties/catalog.rb +6 -4
- data/lib/hecks/fuzzing/properties/guaranteed_by_construction.rb +18 -11
- data/lib/hecks/fuzzing/properties/guards.rb +1 -1
- data/lib/hecks/fuzzing/self_consistency/saga_redelivery.rb +1 -1
- data/lib/hecks/fuzzing/self_consistency.rb +6 -2
- data/lib/hecks/fuzzing/shrinker.rb +3 -1
- data/lib/hecks/fuzzing/value_generator.rb +19 -5
- data/lib/hecks/gate/stages.yml +4 -10
- data/lib/hecks/grammar/evolve.rb +1 -1
- data/lib/hecks/grammar/expression_operators.json +372 -0
- data/lib/hecks/grammar/operators.rb +3 -1
- data/lib/hecks/hecks/adapters/codebase/corpus_tasks.rb +10 -10
- data/lib/hecks/hecks/adapters/codebase/inspection/rubycritic.rb +35 -0
- data/lib/hecks/hecks/adapters/codebase/inspection.rb +104 -0
- data/lib/hecks/hecks/adapters/codebase/publishing.rb +2 -1
- data/lib/hecks/hecks/adapters/codebase/release_facts.rb +12 -2
- data/lib/hecks/hecks/adapters/codebase/source_tree.rb +2 -1
- data/lib/hecks/hecks/adapters/codebase/test_runner.rb +1 -1
- data/lib/hecks/hecks/adapters/cost_explorer.rb +8 -5
- data/lib/hecks/hecks/adapters/deploy_toolchain/statuses.rb +2 -1
- data/lib/hecks/hecks/adapters/finding_github.rb +13 -6
- data/lib/hecks/hecks/adapters/first_admin.rb +2 -2
- data/lib/hecks/hecks/adapters/github_rulesets.rb +6 -3
- data/lib/hecks/hecks/adapters/in_process_boot.rb +1 -1
- data/lib/hecks/hecks/adapters/journal_store/compaction.rb +1 -1
- data/lib/hecks/hecks/adapters/journal_store/edge_audit.rb +10 -20
- data/lib/hecks/hecks/adapters/journal_store/held_domain.rb +28 -5
- data/lib/hecks/hecks/adapters/local_files.rb +2 -2
- data/lib/hecks/hecks/adapters/rehearsal/audit_report.rb +107 -0
- data/lib/hecks/hecks/adapters/rehearsal/census.rb +71 -0
- data/lib/hecks/hecks/adapters/rehearsal/declaration.rb +133 -0
- data/lib/hecks/hecks/adapters/rehearsal/environment.rb +83 -0
- data/lib/hecks/hecks/adapters/rehearsal/inquiry.rb +142 -0
- data/lib/hecks/hecks/adapters/rehearsal/memory_store.rb +96 -0
- data/lib/hecks/hecks/adapters/rehearsal/postgres_census.rb +100 -0
- data/lib/hecks/hecks/adapters/rehearsal/postgres_reading.rb +69 -0
- data/lib/hecks/hecks/adapters/rehearsal/postgres_store.rb +129 -0
- data/lib/hecks/hecks/adapters/rehearsal/rows.rb +41 -0
- data/lib/hecks/hecks/adapters/rehearsal/rust_audit.rb +83 -0
- data/lib/hecks/hecks/adapters/rehearsal/subject.rb +104 -0
- data/lib/hecks/hecks/adapters/rehearsal.rb +54 -0
- data/lib/hecks/hecks/adapters/rehearsal_store.adapter +7 -0
- data/lib/hecks/hecks/adapters/rehearsal_store.rb +81 -0
- data/lib/hecks/hecks/adapters/terminal.rb +13 -11
- data/lib/hecks/hecks/codebase.behaviors +13 -1
- data/lib/hecks/hecks/codebase.bluebook +239 -5
- data/lib/hecks/hecks/custodian.behaviors +24 -6
- data/lib/hecks/hecks/custodian.bluebook +6 -1227
- data/lib/hecks/hecks/deploy.behaviors +1 -1
- data/lib/hecks/hecks/deploy.world +1 -3
- data/lib/hecks/hecks/hecks.behaviors +1 -1
- data/lib/hecks/hecks/hecks.hecksagon +53 -18
- data/lib/hecks/hecks/hecks.world +10 -10
- data/lib/hecks/hecks/rehearsal.bluebook +477 -0
- data/lib/hecks/hecks/site.behaviors +1 -1
- data/lib/hecks/hecks/site.world +0 -1
- data/lib/hecks/hecks/tooling.bluebook +1238 -0
- data/lib/hecks/indifferent_key.rb +20 -0
- data/lib/hecks/language/adapter.bluebook +13 -3
- data/lib/hecks/language/bluebook/aggregate.bluebook +23 -0
- data/lib/hecks/language/bluebook/bluebook.bluebook +15 -0
- data/lib/hecks/language/bluebook/entity.bluebook +18 -0
- data/lib/hecks/language/bluebook/policy.bluebook +9 -1
- data/lib/hecks/language/bluebook/query.bluebook +1 -1
- data/lib/hecks/language/bluebook/vocabulary.bluebook +11 -4
- data/lib/hecks/language/hecksagon/domain_port.bluebook +1 -1
- data/lib/hecks/language/hecksagon/hecksagon.bluebook +22 -0
- data/lib/hecks/language/oidc.json +10 -0
- data/lib/hecks/language/port.bluebook +4 -4
- data/lib/hecks/literal.rb +12 -3
- data/lib/hecks/ports/key_vault.rb +7 -2
- data/lib/hecks/ports/persistence/plugins/era/era_check.rb +21 -10
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/boot.rb +4 -2
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +2 -1
- data/lib/hecks/ports/persistence/repository_factory.rb +6 -3
- data/lib/hecks/projections/deploy/box/templates/deploy-box.sh.tmpl +29 -0
- data/lib/hecks/projections/deploy/box/templates/deploy-service.sh.tmpl +1 -1
- data/lib/hecks/projections/deploy/preview/preview.sh.erb +1 -1
- data/lib/hecks/projections/deploy/shared.rb +5 -2
- data/lib/hecks/projections/model/deviations.rb +2 -2
- data/lib/hecks/projections/model.rb +2 -2
- data/lib/hecks/projections/oidc.rb +13 -1
- data/lib/hecks/projections/shape.rb +5 -2
- data/lib/hecks/projections/site/cms_editor/attributes.rb +26 -6
- data/lib/hecks/projections/site/cms_editor/blocks.rb +157 -0
- data/lib/hecks/projections/site/cms_editor/checks.rb +14 -1
- data/lib/hecks/projections/site/cms_editor/clearing.rb +70 -0
- data/lib/hecks/projections/site/cms_editor/color.rb +124 -0
- data/lib/hecks/projections/site/cms_editor/composition.rb +42 -0
- data/lib/hecks/projections/site/cms_editor/destructive.rb +61 -0
- data/lib/hecks/projections/site/cms_editor/drafts.rb +102 -0
- data/lib/hecks/projections/site/cms_editor/hints.rb +74 -0
- data/lib/hecks/projections/site/cms_editor/icons.rb +86 -0
- data/lib/hecks/projections/site/cms_editor/key_kinds.rb +31 -0
- data/lib/hecks/projections/site/cms_editor/limits.rb +72 -0
- data/lib/hecks/projections/site/cms_editor/media.rb +1 -1
- data/lib/hecks/projections/site/cms_editor/moment.rb +34 -0
- data/lib/hecks/projections/site/cms_editor/palette.rb +115 -0
- data/lib/hecks/projections/site/cms_editor/pickers.rb +85 -0
- data/lib/hecks/projections/site/cms_editor/preview.rb +91 -0
- data/lib/hecks/projections/site/cms_editor/relations.rb +92 -0
- data/lib/hecks/projections/site/cms_editor/scale_checks.rb +66 -0
- data/lib/hecks/projections/site/cms_editor/scheduling.rb +150 -0
- data/lib/hecks/projections/site/cms_editor/schema.rb +96 -19
- data/lib/hecks/projections/site/cms_editor/setting.rb +52 -9
- data/lib/hecks/projections/site/cms_editor/targets.rb +86 -0
- data/lib/hecks/projections/site/cms_editor/templates/package.json.tmpl +18 -1
- data/lib/hecks/projections/site/cms_editor/templates/src/app.ts.tmpl +251 -57
- data/lib/hecks/projections/site/cms_editor/templates/src/assets.ts.tmpl +98 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/auth/membership.ts.tmpl +32 -24
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/app.css.tmpl +412 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/autosave.js.tmpl +144 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/blocks.js.tmpl +500 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/body_doc.js.tmpl +175 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/body_editor.js.tmpl +418 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/epoch.js.tmpl +61 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/forms.js.tmpl +210 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/lists.js.tmpl +40 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/main.js.tmpl +26 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/media_picker.js.tmpl +232 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/moments.js.tmpl +73 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/pickers.js.tmpl +152 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/preview.js.tmpl +98 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/shell.js.tmpl +140 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/toast.js.tmpl +66 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/choices.ts.tmpl +99 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/commands.ts.tmpl +78 -24
- data/lib/hecks/projections/site/cms_editor/templates/src/config.ts.tmpl +6 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/flash.ts.tmpl +52 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/host.ts.tmpl +19 -2
- data/lib/hecks/projections/site/cms_editor/templates/src/media/handler.ts.tmpl +31 -8
- data/lib/hecks/projections/site/cms_editor/templates/src/names.ts.tmpl +36 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/schema.ts.tmpl +131 -2
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/availability.ts.tmpl +15 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/block_list.js.tmpl +213 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/blocks.ts.tmpl +275 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/body_model.js.tmpl +8 -5
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/detail.ts.tmpl +88 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/dialogs.ts.tmpl +43 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/fields.ts.tmpl +229 -34
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/html.ts.tmpl +40 -81
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/icons.ts.tmpl +15 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/input.ts.tmpl +29 -12
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/links.ts.tmpl +100 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/moments.ts.tmpl +44 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/outline.ts.tmpl +53 -17
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/pages.ts.tmpl +178 -88
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/paging.ts.tmpl +82 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/pickers.ts.tmpl +110 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/preview.ts.tmpl +87 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/related.ts.tmpl +97 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/schedule.ts.tmpl +160 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/shell.ts.tmpl +211 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/table.ts.tmpl +151 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/theme.js.tmpl +16 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/values.ts.tmpl +69 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/words.ts.tmpl +97 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/world.ts.tmpl +35 -0
- data/lib/hecks/projections/site/cms_editor/templates/tsconfig.json.tmpl +2 -1
- data/lib/hecks/projections/site/cms_editor/theme.rb +29 -0
- data/lib/hecks/projections/site/cms_editor/theme_checks.rb +57 -0
- data/lib/hecks/projections/site/cms_editor.rb +49 -23
- data/lib/hecks/projections/site/role_probe.rb +2 -0
- data/lib/hecks/projector/cli_audience.rb +2 -1
- data/lib/hecks/projector/cli_projector/listing.rb +1 -1
- data/lib/hecks/projector/cli_projector/option_specs.rb +1 -1
- data/lib/hecks/projector/docs_projector/closing.rb +1 -1
- data/lib/hecks/projector/docs_projector.rb +1 -1
- data/lib/hecks/projector/exporter/bindings.rb +40 -10
- data/lib/hecks/projector/exporter/capability_values.rb +55 -0
- data/lib/hecks/projector/narrate_projector/reactions.rb +1 -1
- data/lib/hecks/projector/target.rb +11 -0
- data/lib/hecks/projector.rb +32 -5
- data/lib/hecks/quality_control/adapters/github_checks.rb +12 -8
- data/lib/hecks/quality_control/adapters/github_issues.rb +8 -4
- data/lib/hecks/quality_control/cli/qa_open_pr.rb +2 -2
- data/lib/hecks/quality_control/cli/qa_sweep/checks/shrinking.rb +1 -1
- data/lib/hecks/quality_control/cli/qa_sweep/finding_report.rb +1 -1
- data/lib/hecks/quality_control/cli/qa_sweep.rb +1 -1
- data/lib/hecks/quality_control/quality_control.bluebook +6 -2
- data/lib/hecks/release/approval.rb +64 -0
- data/lib/hecks/router/{door.rb → aggregate_reader.rb} +5 -4
- data/lib/hecks/router/namespace_installer.rb +16 -16
- data/lib/hecks/router.rb +1 -1
- data/lib/hecks/runtime/best_effort.rb +22 -0
- data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +12 -2
- data/lib/hecks/runtime/command_interpreter.rb +3 -2
- data/lib/hecks/runtime/command_rules/emission.rb +1 -1
- data/lib/hecks/runtime/dispatcher.rb +4 -4
- data/lib/hecks/runtime/entity_element/location.rb +1 -1
- data/lib/hecks/runtime/event.rb +5 -0
- data/lib/hecks/runtime/identity.rb +7 -6
- data/lib/hecks/runtime/interpreting.rb +2 -1
- data/lib/hecks/runtime/invocation_builder.rb +1 -1
- data/lib/hecks/runtime/loader/boot_steps.rb +8 -5
- data/lib/hecks/runtime/loader.rb +10 -10
- data/lib/hecks/runtime/policy_interpreter/arguments.rb +7 -4
- data/lib/hecks/runtime/policy_interpreter/fan_out.rb +3 -3
- data/lib/hecks/runtime/policy_interpreter.rb +6 -6
- data/lib/hecks/runtime/port_operation_interpreter.rb +1 -1
- data/lib/hecks/runtime/reaction_invocation.rb +1 -1
- data/lib/hecks/runtime/rebuild_sweep.rb +11 -1
- data/lib/hecks/runtime/registry/ask_verification.rb +35 -0
- data/lib/hecks/runtime/registry/hecksagon_rules.rb +10 -0
- data/lib/hecks/runtime/registry/merging.rb +1 -1
- data/lib/hecks/runtime/registry/providers.rb +16 -0
- data/lib/hecks/runtime/registry/read_repository.rb +6 -4
- data/lib/hecks/runtime/registry/verification.rb +4 -1
- data/lib/hecks/runtime/registry/world_defaults.rb +29 -0
- data/lib/hecks/runtime/registry.rb +1 -1
- data/lib/hecks/runtime/saga_interpreter/compensation.rb +1 -1
- data/lib/hecks/runtime/saga_interpreter/delivery.rb +4 -4
- data/lib/hecks/runtime/saga_interpreter/transitions.rb +1 -1
- data/lib/hecks/runtime/saga_interpreter.rb +3 -3
- data/lib/hecks/runtime/tenant_check.rb +5 -4
- data/lib/hecks/runtime/value/coercion.rb +2 -2
- data/lib/hecks/runtime/value/validation.rb +16 -4
- data/lib/hecks/runtime.rb +9 -9
- data/lib/hecks/rust_build/project_rust/target_ir.rb +1 -1
- data/lib/hecks/rust_build/wasm.rb +1 -1
- data/lib/hecks/rust_build/wasm_browser.rb +1 -1
- data/lib/hecks/rust_build.rb +11 -0
- data/lib/hecks/served_options.rb +53 -0
- data/lib/hecks/site/bluebook/site.bluebook +1 -0
- data/lib/hecks/storehouse/audit.rb +1 -1
- data/lib/hecks/storehouse/dispatching.rb +8 -6
- data/lib/hecks/storehouse/introspection.rb +2 -2
- data/lib/hecks/storehouse/querying.rb +6 -4
- data/lib/hecks/storehouse.rb +4 -4
- data/lib/hecks/tools/comment_style/source_file.rb +4 -1
- data/lib/hecks/tools/deploy_recipe_lint/fixtures.rb +4 -1
- data/lib/hecks/tools/lanes/rows.rb +6 -0
- data/lib/hecks/tools/oidc_manifests.rb +4 -5
- data/lib/hecks/tools/regeneration_run.rb +11 -4
- data/lib/hecks/tools/site_routes/editor_files.rb +12 -5
- data/lib/hecks/tools/tenant_provisioning.rb +2 -2
- data/lib/hecks/tools/tools_doc.rb +4 -4
- data/lib/hecks/version.rb +1 -1
- data/lib/hecks/vocabulary.rb +4 -4
- data/lib/hecks.rb +15 -15
- data/lib/rubocop/cop/hecks/env_dump.rb +69 -0
- data/lib/rubocop/cop/hecks/monotonic_duration.rb +53 -0
- data/lib/rubocop/cop/hecks/no_wall_clock_in_runtime.rb +34 -0
- data/lib/rubocop/cop/hecks/raw_shell_interpolation.rb +79 -0
- data/lib/rubocop/cop/hecks/sleep_in_spec.rb +34 -0
- data/lib/rubocop/cop/hecks/swallowed_rescue.rb +37 -0
- data/lib/rubocop/cop/hecks/symbol_string_key_mix.rb +50 -0
- data/lib/rubocop/cop/hecks/untyped_raise.rb +37 -0
- data/prebuilt/05c358d38d661a3774f813d49c66f5b68939af94432d66257a56f49ee2598994.marshal +0 -0
- data/prebuilt/26c40c61f09b09b11651c64668eda3ceb180f83cf2a3802d3033d2fd99ac841b.marshal +0 -0
- data/prebuilt/verdicts-230ac18c25abfd3d92ede032bee2661a4c2cd2773ab0cf0e2ae52546ba2f7af9.json +1 -0
- data/rust/.gitignore +6 -0
- data/rust/Cargo.toml +6 -0
- data/rust/build/src/binding_pass.rs +103 -0
- data/rust/build/src/cargo_sync.rs +6 -0
- data/rust/build/src/lineage_pass.rs +3 -3
- data/rust/build/src/main.rs +17 -5
- data/rust/build/src/pipeline.rs +48 -5
- data/rust/build/src/seams_pass.rs +123 -0
- data/rust/build/src/translation_pass.rs +167 -0
- data/rust/codegen/src/asks.rs +103 -0
- data/rust/codegen/src/commands.rs +13 -8
- data/rust/codegen/src/constraints.rs +25 -0
- data/rust/codegen/src/domain_generator.rs +2 -0
- data/rust/codegen/src/expr_emitter.rs +13 -0
- data/rust/codegen/src/json_codec.rs +54 -15
- data/rust/codegen/src/main.rs +1 -0
- data/rust/codegen/src/mutations.rs +128 -3
- data/rust/codegen/src/queries.rs +11 -2
- data/rust/codegen/src/reactions.rs +1 -1
- data/rust/codegen/src/types.rs +166 -8
- data/rust/host/HECKS_RELEASE +1 -1
- data/rust/host/src/api.rs +4 -4
- data/rust/host/src/bin/validate_harness.rs +43 -0
- data/rust/host/src/boundary_fuzz/expr_json.rs +2 -2
- data/rust/host/src/boundary_fuzz/ir.rs +1 -0
- data/rust/host/src/boundary_fuzz/server.rs +1 -1
- data/rust/host/src/checkout.rs +35 -19
- data/rust/host/src/commerce_ir.rs +408 -2
- data/rust/host/src/expr_json.rs +118 -0
- data/rust/host/src/mint.rs +1 -1
- data/rust/host/src/payments/tests.rs +27 -1
- data/rust/host/src/payments.rs +12 -4
- data/rust/host/src/query_step.rs +22 -4
- data/rust/host/src/reference_validate.rs +653 -108
- data/rust/host/src/resend.rs +2 -0
- data/rust/host/src/server.rs +19 -6
- data/rust/host/src/ui_schema.rs +132 -5
- data/rust/host/src/web/newsletter.rs +7 -8
- data/rust/host/src/web/newsletter_send.rs +38 -13
- data/rust/host/src/web/registrations.rs +229 -26
- data/rust/host/src/web.rs +3 -3
- data/rust/parser/src/emit.rs +38 -6
- data/rust/parser/src/expr/ast_json.rs +7 -1
- data/rust/parser/src/expr/resolver.rs +171 -6
- data/rust/parser/src/ir.rs +7 -0
- data/rust/parser/src/keywords.rs +24 -0
- data/rust/parser/src/main.rs +3 -0
- data/rust/parser/src/parse/aggregate.rs +2 -1
- data/rust/parser/src/parse/chapter.rs +2 -2
- data/rust/parser/src/parse/command.rs +2 -0
- data/rust/parser/src/parse/hecksagon.rs +5 -2
- data/rust/parser/src/parse/lifecycle.rs +81 -0
- data/rust/parser/src/parse/mod.rs +15 -1
- data/rust/parser/src/parse/policy.rs +13 -2
- data/rust/parser/src/parse/value_object.rs +13 -8
- data/rust/parser/src/ruby_value.rs +1 -1
- data/rust/src/exemplar/commands.rs +4 -4
- data/rust/src/exemplar/constraints.rs +1 -1
- data/rust/src/exemplar/json.rs +43 -1
- data/rust/src/kernel/cli.rs +7 -5
- data/rust/src/kernel/dispatch.rs +2 -2
- data/rust/src/kernel/dispatch_guard_tests.rs +1 -1
- data/rust/src/kernel/expr.rs +66 -3
- data/rust/src/kernel/expression_operators/arithmetic.rs +130 -5
- data/rust/src/kernel/expression_operators/text.rs +26 -2
- data/rust/src/kernel/json.rs +15 -0
- data/rust/src/kernel/mod.rs +1 -1
- data/rust/src/kernel/orchestrate.rs +54 -5
- data/rust/src/kernel/orchestrate_guard_tests.rs +67 -0
- data/rust/src/kernel/pattern.rs +60 -0
- data/rust/src/kernel/reference_lookup.rs +90 -7
- metadata +140 -38
- data/lib/hecks/doors/cli_door/nesting.rb +0 -34
- data/lib/hecks/doors/cli_door/shorthand.rb +0 -85
- data/lib/hecks/doors/cli_door.rb +0 -124
- data/lib/hecks/doors/cli_runner/answers.rb +0 -138
- data/lib/hecks/doors/cli_runner/resolution.rb +0 -146
- data/lib/hecks/doors/cli_runner/settling.rb +0 -115
- data/lib/hecks/doors/cli_runner/streaming.rb +0 -90
- data/lib/hecks/doors/cli_runner/suggestions.rb +0 -43
- data/lib/hecks/doors/cli_runner.rb +0 -66
- data/lib/hecks/doors/command_request.rb +0 -119
- data/lib/hecks/doors/handle.rb +0 -208
- data/lib/hecks/doors/json_door.rb +0 -132
- data/lib/hecks/doors/launcher_options.rb +0 -247
- data/lib/hecks/doors/mcp_door/properties.rb +0 -47
- data/lib/hecks/doors/mcp_door/tool_calls.rb +0 -113
- data/lib/hecks/doors/mcp_door/tool_definitions/auditing.rb +0 -51
- data/lib/hecks/doors/mcp_door/tool_definitions/calling.rb +0 -69
- data/lib/hecks/doors/mcp_door/tool_definitions/reading.rb +0 -97
- data/lib/hecks/doors/mcp_door/tool_definitions.rb +0 -15
- data/lib/hecks/doors/mcp_door.rb +0 -156
- data/lib/hecks/doors/mcp_door_scope/argument_policy.rb +0 -92
- data/lib/hecks/doors/mcp_door_scope/env_problems.rb +0 -55
- data/lib/hecks/doors/mcp_door_scope/presenter.rb +0 -78
- data/lib/hecks/doors/mcp_door_scope.rb +0 -201
- data/lib/hecks/doors/mcp_guide_cache.rb +0 -75
- data/lib/hecks/doors/ruby_door/aggregate_door.rb +0 -162
- data/lib/hecks/doors/ruby_door/chapter.rb +0 -89
- data/lib/hecks/doors/ruby_door.rb +0 -80
- data/lib/hecks/doors/usage_cache.rb +0 -139
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/body_widget.js.tmpl +0 -282
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/media_picker.js.tmpl +0 -109
- data/prebuilt/c28d6d9db14eea3e17161e9d9c262efee30b256cf97693a7cfd552bce24b2f7f.marshal +0 -0
- data/prebuilt/cb375cba61ca0dbe8ddb9739c306510fe05e5f4b7a9b6955caff8224c63ea0e0.marshal +0 -0
- data/prebuilt/verdicts-d6d584ad43b5f3cebf88aafbbdbf024c38574acf18e06adffbc555f7786b1c2f.json +0 -1
|
@@ -16,6 +16,9 @@ module Hecks
|
|
|
16
16
|
# it, a span with no complete day, or a failed `aws` call is refused with a reason, which the
|
|
17
17
|
# runtime records on the check.
|
|
18
18
|
class CostExplorer
|
|
19
|
+
# Raised when the budget is exceeded or Cost Explorer cannot be read.
|
|
20
|
+
class Error < RuntimeError; end
|
|
21
|
+
|
|
19
22
|
# Days in an average month, for scaling a daily mean to a monthly rate.
|
|
20
23
|
DAYS_IN_MONTH = 30.4375
|
|
21
24
|
|
|
@@ -35,17 +38,17 @@ module Hecks
|
|
|
35
38
|
# first day to count (`YYYY-MM-DD`), each a value object or a plain value
|
|
36
39
|
# @return [Hash{Symbol => Hash}] `report:` the rate against the budget, the shape
|
|
37
40
|
# `CostCheck.Pass` takes
|
|
38
|
-
# @raise [
|
|
41
|
+
# @raise [Error] when no complete day has passed since `since`, `aws` fails, or the
|
|
39
42
|
# rate is over the budget
|
|
40
43
|
def measure(**held)
|
|
41
44
|
budget = Integer(plain(held[:budget]))
|
|
42
45
|
first = Date.iso8601(plain(held[:since]).to_s)
|
|
43
46
|
last = today
|
|
44
|
-
raise "no complete day since #{first}; today (#{last}) is still being billed" if first >= last
|
|
47
|
+
raise Error, "no complete day since #{first}; today (#{last}) is still being billed" if first >= last
|
|
45
48
|
|
|
46
49
|
days = daily_costs(first, last)
|
|
47
50
|
line = report_line(first, last, days, budget)
|
|
48
|
-
raise "over budget: #{line}" if monthly_rate(days) > budget
|
|
51
|
+
raise Error, "over budget: #{line}" if monthly_rate(days) > budget
|
|
49
52
|
|
|
50
53
|
{ report: { value: line } }
|
|
51
54
|
end
|
|
@@ -62,10 +65,10 @@ module Hecks
|
|
|
62
65
|
"--granularity", "DAILY", "--metrics", "UnblendedCost",
|
|
63
66
|
"--group-by", "Type=DIMENSION,Key=SERVICE", "--output", "json"
|
|
64
67
|
)
|
|
65
|
-
raise "aws ce get-cost-and-usage failed: #{err.strip.empty? ? out.strip : err.strip}" unless status.success?
|
|
68
|
+
raise Error, "aws ce get-cost-and-usage failed: #{err.strip.empty? ? out.strip : err.strip}" unless status.success?
|
|
66
69
|
|
|
67
70
|
rows = JSON.parse(out).fetch("ResultsByTime")
|
|
68
|
-
raise "Cost Explorer returned no days for #{first}..#{last}" if rows.empty?
|
|
71
|
+
raise Error, "Cost Explorer returned no days for #{first}..#{last}" if rows.empty?
|
|
69
72
|
|
|
70
73
|
rows.to_h { |row| [row.dig("TimePeriod", "Start"), by_service(row)] }
|
|
71
74
|
end
|
|
@@ -26,7 +26,8 @@ module Hecks
|
|
|
26
26
|
|
|
27
27
|
# What each status of `deploy-box.sh` means, for the reason a refusal gives.
|
|
28
28
|
BOX_STATUS = { 40 => "the box stack has no instance", 41 => "the roll did not succeed on the box",
|
|
29
|
-
42 => "the box is not healthy after the roll"
|
|
29
|
+
42 => "the box is not healthy after the roll",
|
|
30
|
+
43 => "the database has no usable restore anchor (backups unreadable or kept too few days)" }.freeze
|
|
30
31
|
|
|
31
32
|
# What each status of `deploy-service.sh` means; it ends with the box roll's own statuses.
|
|
32
33
|
SERVICE_STATUS = { 2 => "unknown service", 30 => "the existing tag is not in ECR",
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "open3"
|
|
4
|
+
require_relative "../../indifferent_key"
|
|
4
5
|
|
|
5
6
|
module Hecks
|
|
6
7
|
module Adapters
|
|
@@ -12,6 +13,9 @@ module Hecks
|
|
|
12
13
|
# and the runtime records the refusal on the finding. An ask that needs the issue before one
|
|
13
14
|
# was opened is refused the same way, so a finding never blocks on GitHub.
|
|
14
15
|
class FindingGithub
|
|
16
|
+
# Raised when `gh` cannot file or find the finding's issue.
|
|
17
|
+
class Error < RuntimeError; end
|
|
18
|
+
|
|
15
19
|
# The environment variable that names the repository when no setting does.
|
|
16
20
|
REPOSITORY_VARIABLE = "HECKS_FINDINGS_REPO"
|
|
17
21
|
|
|
@@ -27,12 +31,12 @@ module Hecks
|
|
|
27
31
|
# @param held [Hash] the finding's fields; `title` and `body` are value objects or strings
|
|
28
32
|
# @return [Hash{Symbol => Hash}] `issue_number:` of the new issue, the shape
|
|
29
33
|
# `Finding.RecordIssue` takes
|
|
30
|
-
# @raise [
|
|
34
|
+
# @raise [Error] when no repository is named, `gh` exits non-zero, or prints no
|
|
31
35
|
# issue URL
|
|
32
36
|
def open_issue(**held)
|
|
33
37
|
out = gh("issue", "create", "--title", plain(held[:title]).to_s, "--body", plain(held[:body]).to_s)
|
|
34
38
|
number = out.lines.last.to_s.strip[%r{/issues/(\d+)\z}, 1]
|
|
35
|
-
raise "gh issue create printed no issue URL: #{out.strip.inspect}" unless number
|
|
39
|
+
raise Error, "gh issue create printed no issue URL: #{out.strip.inspect}" unless number
|
|
36
40
|
|
|
37
41
|
{ issue_number: { value: number.to_i } }
|
|
38
42
|
end
|
|
@@ -82,7 +86,10 @@ module Hecks
|
|
|
82
86
|
|
|
83
87
|
def gh(*arguments)
|
|
84
88
|
out, err, status = Open3.capture3("gh", *arguments, "--repo", repository)
|
|
85
|
-
|
|
89
|
+
unless status.success?
|
|
90
|
+
raise Error,
|
|
91
|
+
"gh #{arguments.first(2).join(" ")} failed — #{err.strip.empty? ? out.strip : err.strip}"
|
|
92
|
+
end
|
|
86
93
|
|
|
87
94
|
out
|
|
88
95
|
end
|
|
@@ -91,12 +98,12 @@ module Hecks
|
|
|
91
98
|
name = @repository || ENV.fetch(REPOSITORY_VARIABLE, nil)
|
|
92
99
|
return name unless name.to_s.strip.empty?
|
|
93
100
|
|
|
94
|
-
raise "no repository named: set the `repository` setting or #{REPOSITORY_VARIABLE} to owner/name"
|
|
101
|
+
raise Error, "no repository named: set the `repository` setting or #{REPOSITORY_VARIABLE} to owner/name"
|
|
95
102
|
end
|
|
96
103
|
|
|
97
104
|
def issue(held)
|
|
98
105
|
number = plain(held[:issue_number])
|
|
99
|
-
raise "the finding has no GitHub issue yet" if number.to_s.empty?
|
|
106
|
+
raise Error, "the finding has no GitHub issue yet" if number.to_s.empty?
|
|
100
107
|
|
|
101
108
|
number.to_s
|
|
102
109
|
end
|
|
@@ -107,7 +114,7 @@ module Hecks
|
|
|
107
114
|
def plain(field)
|
|
108
115
|
return field unless field.is_a?(Hash)
|
|
109
116
|
|
|
110
|
-
|
|
117
|
+
IndifferentKey.read(field, :value)
|
|
111
118
|
end
|
|
112
119
|
end
|
|
113
120
|
end
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "../../
|
|
3
|
+
require_relative "../../adapters/driving/json"
|
|
4
4
|
|
|
5
5
|
module Hecks
|
|
6
6
|
module Adapters
|
|
@@ -87,7 +87,7 @@ module Hecks
|
|
|
87
87
|
%i[admit grant people].to_h { |key| [key, provider.provided_verb(Bluebook::Capabilities::MEMBERSHIP, key)] }
|
|
88
88
|
end
|
|
89
89
|
|
|
90
|
-
def rows(verb) = @runtime.query(verb).map { |row|
|
|
90
|
+
def rows(verb) = @runtime.query(verb).map { |row| Adapters::Driving::Json.materialize(row) }
|
|
91
91
|
|
|
92
92
|
# The role a verb's command declares, which the dispatch must assert to be let through.
|
|
93
93
|
def gated_role(provider, verb)
|
|
@@ -12,6 +12,9 @@ module Hecks
|
|
|
12
12
|
# ruleset says; it only carries a ruleset to and from GitHub. `{owner}/{repo}` is a `gh api`
|
|
13
13
|
# template resolved from the git remote.
|
|
14
14
|
class GithubRulesets
|
|
15
|
+
# Raised when `gh` is missing or refuses a ruleset call.
|
|
16
|
+
class Error < RuntimeError; end
|
|
17
|
+
|
|
15
18
|
# The part of a ruleset a lane projects: everything else GitHub fills in (ids, links,
|
|
16
19
|
# timestamps, defaults) is left out of a comparison.
|
|
17
20
|
COMPARED = %w[name target enforcement].freeze
|
|
@@ -23,7 +26,7 @@ module Hecks
|
|
|
23
26
|
end
|
|
24
27
|
|
|
25
28
|
# @return [Array<Hash>] every ruleset of the repository, in full
|
|
26
|
-
# @raise [
|
|
29
|
+
# @raise [Error] when `gh` is missing or refuses
|
|
27
30
|
def all
|
|
28
31
|
listed = json(call("api", "repos/{owner}/{repo}/rulesets?per_page=100"))
|
|
29
32
|
listed.map { |ruleset| json(call("api", "repos/{owner}/{repo}/rulesets/#{ruleset.fetch("id")}")) }
|
|
@@ -37,7 +40,7 @@ module Hecks
|
|
|
37
40
|
#
|
|
38
41
|
# @param projected [Hash] the ruleset as `Tools::Lanes` projects it
|
|
39
42
|
# @return [Symbol] `:created` or `:updated`
|
|
40
|
-
# @raise [
|
|
43
|
+
# @raise [Error] when GitHub refuses it
|
|
41
44
|
def apply(projected)
|
|
42
45
|
live = named(projected.fetch("name"))
|
|
43
46
|
path = "repos/{owner}/{repo}/rulesets"
|
|
@@ -110,7 +113,7 @@ module Hecks
|
|
|
110
113
|
|
|
111
114
|
def call(*args, stdin: nil)
|
|
112
115
|
out, err, ok = @runner.call(args, stdin)
|
|
113
|
-
raise "gh #{args.first(3).join(" ")} failed: #{err.strip.empty? ? out.strip : err.strip}" unless ok
|
|
116
|
+
raise Error, "gh #{args.first(3).join(" ")} failed: #{err.strip.empty? ? out.strip : err.strip}" unless ok
|
|
114
117
|
|
|
115
118
|
out
|
|
116
119
|
end
|
|
@@ -174,7 +174,7 @@ module Hecks
|
|
|
174
174
|
raise Runtime::NotFound, "no domain here, and none named" unless path
|
|
175
175
|
raise Runtime::NotFound, "no such domain #{path.inspect}" unless Dir.exist?(path)
|
|
176
176
|
|
|
177
|
-
Hecks.boot(File.expand_path(path),
|
|
177
|
+
Hecks.boot(File.expand_path(path), install_driving: false)
|
|
178
178
|
end
|
|
179
179
|
|
|
180
180
|
def chapter_of(domain, chapter)
|
|
@@ -23,7 +23,7 @@ module Hecks
|
|
|
23
23
|
def initialize(path, aggregates:, kind:)
|
|
24
24
|
raise Runtime::NotFound, "no such domain #{path.inspect}" unless Dir.exist?(path.to_s)
|
|
25
25
|
|
|
26
|
-
@registry = Hecks.boot(File.expand_path(path),
|
|
26
|
+
@registry = Hecks.boot(File.expand_path(path), install_driving: false).registry
|
|
27
27
|
@wanted = aggregates
|
|
28
28
|
@kind = kind
|
|
29
29
|
end
|
|
@@ -12,17 +12,10 @@ module Hecks
|
|
|
12
12
|
# It answers what the audit's three layers found, as the report `hecks audit_translation`
|
|
13
13
|
# prints, and the edge itself, so approving it audits the very edge that was read.
|
|
14
14
|
class EdgeAudit
|
|
15
|
-
# What one audit found
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
|
|
19
|
-
# @!attribute [r] leaving
|
|
20
|
-
# @return [Integer] how many edges leave the latest held era
|
|
21
|
-
# @!attribute [r] ok
|
|
22
|
-
# @return [Boolean] whether no layer refused
|
|
23
|
-
# @!attribute [r] report
|
|
24
|
-
# @return [String] the report, one block per aggregate
|
|
25
|
-
Finding = Struct.new(:edge, :leaving, :ok, :report, keyword_init: true)
|
|
15
|
+
# What one audit found: the audited `edge`, how many edges are `leaving` the latest held
|
|
16
|
+
# era, whether it is `ok`, the `report` text, every refusal (`Aggregate#id: wording`) and
|
|
17
|
+
# the records as translated (`after`), by aggregate name and id.
|
|
18
|
+
Finding = Struct.new(:edge, :leaving, :ok, :report, :refusals, :after, keyword_init: true)
|
|
26
19
|
|
|
27
20
|
# Audits the edge that leaves the latest held era.
|
|
28
21
|
#
|
|
@@ -89,14 +82,11 @@ module Hecks
|
|
|
89
82
|
end
|
|
90
83
|
|
|
91
84
|
def finding(chain, era, leaving, edge = chain.last[:translation])
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
ok &&= verdict.ok?
|
|
98
|
-
end
|
|
99
|
-
Finding.new(edge: edge, leaving: leaving, ok: ok, report: lines.join("\n"))
|
|
85
|
+
audits = @held.bluebook.aggregates.map { |aggregate| [aggregate, *verdict_for(aggregate, edge, era, chain)] }
|
|
86
|
+
lines = audits.flat_map { |aggregate, verdict, records, _| describe(aggregate, edge, verdict, records) }
|
|
87
|
+
refusals = audits.flat_map { |_, verdict, _, _| verdict.violations }
|
|
88
|
+
Finding.new(edge: edge, leaving: leaving, ok: refusals.empty?, report: lines.join("\n"),
|
|
89
|
+
refusals: refusals, after: audits.to_h { |aggregate, _, _, after| [aggregate.name, after] })
|
|
100
90
|
end
|
|
101
91
|
|
|
102
92
|
def verdict_for(aggregate, edge, era, chain)
|
|
@@ -108,7 +98,7 @@ module Hecks
|
|
|
108
98
|
end
|
|
109
99
|
verdict = Translation::Audit.check(aggregate: aggregate, declared: edge.for_aggregate(aggregate.name),
|
|
110
100
|
before: before, after: after)
|
|
111
|
-
[verdict, after.size]
|
|
101
|
+
[verdict, after.size, after]
|
|
112
102
|
end
|
|
113
103
|
|
|
114
104
|
def translated(aggregate, era, chain)
|
|
@@ -22,16 +22,18 @@ module Hecks
|
|
|
22
22
|
#
|
|
23
23
|
# @param path [String] the domain directory, or the project holding its `bluebook/`
|
|
24
24
|
# @param mode [Symbol] one of `MODES`
|
|
25
|
+
# @param environment [String, nil] the overlay under `environments/` to load last;
|
|
26
|
+
# `HECKS_PROJECT_ENVIRONMENT` when not given
|
|
25
27
|
# @return [HeldDomain] the loaded domain
|
|
26
28
|
# @raise [Runtime::NotFound] if the directory does not exist, declares no bluebook, or
|
|
27
29
|
# does not load
|
|
28
|
-
def self.open(path, mode: :translated)
|
|
30
|
+
def self.open(path, mode: :translated, environment: ENV.fetch("HECKS_PROJECT_ENVIRONMENT", nil))
|
|
29
31
|
raise ArgumentError, "unknown mode #{mode.inspect}" unless MODES.include?(mode)
|
|
30
32
|
|
|
31
33
|
loading = Ports::Loading.bootstrap
|
|
32
34
|
directory = loading.bluebook_directory(path)
|
|
33
35
|
registry = Runtime::Registry.new(root: File.dirname(directory))
|
|
34
|
-
load_into(registry, loading, directory, mode)
|
|
36
|
+
load_into(registry, loading, directory, mode, environment)
|
|
35
37
|
new(registry, directory)
|
|
36
38
|
rescue Errno::ENOENT
|
|
37
39
|
raise Runtime::NotFound, "no such domain #{path.inspect}"
|
|
@@ -40,8 +42,7 @@ module Hecks
|
|
|
40
42
|
end
|
|
41
43
|
|
|
42
44
|
# @api private
|
|
43
|
-
def self.load_into(registry, loading, directory, mode)
|
|
44
|
-
environment = ENV.fetch("HECKS_PROJECT_ENVIRONMENT", nil)
|
|
45
|
+
def self.load_into(registry, loading, directory, mode, environment)
|
|
45
46
|
Hecks.with_registry(registry) do
|
|
46
47
|
loading.load_library
|
|
47
48
|
loading.load_project(loading.shared_root(nil, directory))
|
|
@@ -91,7 +92,29 @@ module Hecks
|
|
|
91
92
|
# @return [Hash] the settings
|
|
92
93
|
def settings
|
|
93
94
|
declared = registry.binding_settings(bluebook.name, Ports::Persistence::VERB, adapter_name)
|
|
94
|
-
|
|
95
|
+
declared = declared.merge(schema: ENV["HECKS_SCHEMA"]) if ENV["HECKS_SCHEMA"]
|
|
96
|
+
declared = declared.merge(schema: @schema) if @schema
|
|
97
|
+
@database ? declared.merge(database: @database) : declared
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Names the schema its journal store is read in, over the world's and `HECKS_SCHEMA`, so a
|
|
101
|
+
# rehearsal reads the schema it reports.
|
|
102
|
+
#
|
|
103
|
+
# @param schema [String] a schema name
|
|
104
|
+
# @return [HeldDomain] self
|
|
105
|
+
def read_schema(schema)
|
|
106
|
+
@schema = schema
|
|
107
|
+
self
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Points the journal store at another database than its world names, so a rehearsal
|
|
111
|
+
# reads a copy and never the world's own. Every read and write after this reaches it.
|
|
112
|
+
#
|
|
113
|
+
# @param database [String] a `postgres://` URL
|
|
114
|
+
# @return [HeldDomain] self
|
|
115
|
+
def read_from(database)
|
|
116
|
+
@database = database
|
|
117
|
+
self
|
|
95
118
|
end
|
|
96
119
|
|
|
97
120
|
# The text the era is frozen from: the bluebook source as it stands on disk.
|
|
@@ -23,7 +23,7 @@ module Hecks
|
|
|
23
23
|
# named ones, through `Hecks::CLI::ProjectCli`. A launcher is a generated file that only
|
|
24
24
|
# points at its domain, so an existing one is replaced and nothing else is touched.
|
|
25
25
|
#
|
|
26
|
-
# @param held [Hash] the `
|
|
26
|
+
# @param held [Hash] the `Launch` record: `domains` (comma separated paths under the current
|
|
27
27
|
# directory; every domain found when absent)
|
|
28
28
|
# @return [Hash{Symbol => Hash}] `output:` one line per launcher written
|
|
29
29
|
# @raise [ConsoleCapture::Failure] when no launcher was written
|
|
@@ -45,7 +45,7 @@ module Hecks
|
|
|
45
45
|
# Nothing is replaced: every file is checked before the first is written, and a directory
|
|
46
46
|
# that already holds a bluebook is refused.
|
|
47
47
|
#
|
|
48
|
-
# @param held [Hash] the `
|
|
48
|
+
# @param held [Hash] the `Launch` record: `name`, and optionally `adapter` and `dir`
|
|
49
49
|
# @return [Hash{Symbol => Hash}] `output:` the report that was printed
|
|
50
50
|
# @raise [ConsoleCapture::Failure] when the name or adapter is refused, or a file exists
|
|
51
51
|
def scaffold(**held)
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
module Adapters
|
|
5
|
+
module Rehearsal
|
|
6
|
+
# What the mint's audit read and what it concluded, as the facts the report holds: how much
|
|
7
|
+
# was examined by each engine, whether anything was, why not when nothing was, and how the
|
|
8
|
+
# two engines stood against each other.
|
|
9
|
+
class AuditReport
|
|
10
|
+
# The limit a clean verdict carries when no stored row was read.
|
|
11
|
+
UNAUDITED = "the stored rows"
|
|
12
|
+
|
|
13
|
+
# The wording that explains a Rust-only refusal: the Ruby audit's first layer does not hold
|
|
14
|
+
# a stored value to a closed set, a required field or an undeclared value-object key.
|
|
15
|
+
RUST_ONLY = "The Rust audit also holds a stored value to its closed set, a required field and an " \
|
|
16
|
+
"undeclared value-object key, which the Ruby audit's first layer does not yet: a row " \
|
|
17
|
+
"only Rust refuses is that known difference between the engines, not a second finding."
|
|
18
|
+
|
|
19
|
+
# @param minting [String] none, first or next
|
|
20
|
+
# @param edge [Bluebook::Translation, nil] the edge the audit ran through
|
|
21
|
+
# @param states [Hash{String => Hash}] the translated records the audit read, by aggregate
|
|
22
|
+
# @param ruby [Array<String>] the Ruby audit's refusals
|
|
23
|
+
# @param rust [RustAudit::Result] the second engine's answer
|
|
24
|
+
def initialize(minting:, edge:, states:, ruby:, rust:)
|
|
25
|
+
@minting = minting
|
|
26
|
+
@edge = edge
|
|
27
|
+
@states = states
|
|
28
|
+
@ruby = ruby
|
|
29
|
+
@rust = rust
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# @return [Boolean] whether any stored row was read by the audit
|
|
33
|
+
def audited? = !@edge.nil? && @minting == "next"
|
|
34
|
+
|
|
35
|
+
# @return [Integer] the rows the Ruby audit read
|
|
36
|
+
def rows = @states.values.sum(&:size)
|
|
37
|
+
|
|
38
|
+
# @return [Hash] `audited`, `rows_examined`, `aggregates_examined`, `rust_rows_examined`
|
|
39
|
+
# (zero when the second engine did not run, which `parity` says) and the sentence the
|
|
40
|
+
# verdict rests on
|
|
41
|
+
def facts
|
|
42
|
+
{ audited: flag(audited?), rows_examined: count(rows), reason: { value: reason },
|
|
43
|
+
aggregates_examined: count(aggregates),
|
|
44
|
+
rust_rows_examined: count(@rust.rows) }
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# @return [Hash] the Rust engine's standing against the Ruby one, with who refused what
|
|
48
|
+
def parity
|
|
49
|
+
return { engine: "rust", status: "not_run", note: @rust.note } unless @rust.ran
|
|
50
|
+
return { engine: "rust", status: "agree", note: agreed } if ruby_records == rust_records
|
|
51
|
+
|
|
52
|
+
{ engine: "rust", status: "differ", note: differed }
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# @return [Array<Hash>] the limit a rehearsal that read nothing carries
|
|
56
|
+
def limits = audited? ? [] : [{ what: UNAUDITED, why: reason }]
|
|
57
|
+
|
|
58
|
+
private
|
|
59
|
+
|
|
60
|
+
def flag(value) = { value: value }
|
|
61
|
+
|
|
62
|
+
def count(value) = { value: value }
|
|
63
|
+
|
|
64
|
+
def reason
|
|
65
|
+
case @minting
|
|
66
|
+
when "none" then "no mint is needed at this shape, so no stored row was examined"
|
|
67
|
+
when "first" then "no era is held, so the first mint holds the shape as it stands and no stored row was examined"
|
|
68
|
+
else next_reason
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def next_reason
|
|
73
|
+
return "no edge leads the held era to this shape, so no stored row was examined" unless @edge
|
|
74
|
+
|
|
75
|
+
"the audit read #{rows} stored row(s) of #{aggregates} aggregate(s) through the edge " \
|
|
76
|
+
"#{@edge.from} -> #{@edge.to}"
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def aggregates = @states.count { |_, held| held.any? }
|
|
80
|
+
|
|
81
|
+
def ruby_records = records(@ruby)
|
|
82
|
+
|
|
83
|
+
def rust_records = records(@rust.violations)
|
|
84
|
+
|
|
85
|
+
def records(refusals) = refusals.map { |text| text[/\A[^:]+/] }.uniq.sort
|
|
86
|
+
|
|
87
|
+
def agreed = "ruby refused #{ruby_records.size} records, rust #{rust_records.size}; rust read #{@rust.rows} rows"
|
|
88
|
+
|
|
89
|
+
def differed
|
|
90
|
+
rust_only = rust_records - ruby_records
|
|
91
|
+
ruby_only = ruby_records - rust_records
|
|
92
|
+
"the engines refused different records: ruby #{ruby_records.size}, rust #{rust_records.size}. " \
|
|
93
|
+
"#{only("rust", rust_only)}#{only("ruby", ruby_only)}#{known_gap(rust_only)}" \
|
|
94
|
+
"Both are held against the report: the union is refused."
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def known_gap(rust_only) = rust_only.empty? ? "" : "#{RUST_ONLY} "
|
|
98
|
+
|
|
99
|
+
def only(engine, held)
|
|
100
|
+
return "" if held.empty?
|
|
101
|
+
|
|
102
|
+
"Only #{engine} refused: #{held.first(5).join(", ")}#{", ..." if held.size > 5}. "
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
module Adapters
|
|
5
|
+
module Rehearsal
|
|
6
|
+
# What a data copy holds for the domain, counted before any audit runs: the eras, the rows
|
|
7
|
+
# each aggregate's head holds, and the entries and live records of the journal.
|
|
8
|
+
#
|
|
9
|
+
# The mint's audit reads the journal (the latest entry of each record), never the head, so
|
|
10
|
+
# the two counts sit side by side and a divergence between them is visible, not refused.
|
|
11
|
+
Census = Struct.new(:schema, :source, :present, :eras, :journal_entries, :counts, :elsewhere,
|
|
12
|
+
keyword_init: true) do
|
|
13
|
+
# One aggregate's rows: what its head holds and what the journal's latest entries hold.
|
|
14
|
+
const_set(:Count, Struct.new(:aggregate, :head, :journal, keyword_init: true))
|
|
15
|
+
|
|
16
|
+
# @return [Array<String>] other schemas of the copy that hold a lineage
|
|
17
|
+
def elsewhere = self[:elsewhere] || []
|
|
18
|
+
|
|
19
|
+
# @return [Integer] the rows the heads hold, over every aggregate
|
|
20
|
+
def head_rows = counts.sum(&:head)
|
|
21
|
+
|
|
22
|
+
# @return [Integer] the live records the journal's latest entries hold
|
|
23
|
+
def journal_rows = counts.sum(&:journal)
|
|
24
|
+
|
|
25
|
+
# @return [Boolean] whether the copy holds no row of the domain at all
|
|
26
|
+
def empty? = head_rows.zero? && journal_rows.zero? && journal_entries.zero?
|
|
27
|
+
|
|
28
|
+
# @param domain [String] the domain's name, for the sentence
|
|
29
|
+
# @return [String] what the copy holds, in words
|
|
30
|
+
def summary(domain)
|
|
31
|
+
"the copy holds #{eras} era(s), #{head_rows} head rows and #{journal_rows} journal records for " \
|
|
32
|
+
"#{domain} in schema #{schema}"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# @param domain [String] the domain's name, for the sentence
|
|
36
|
+
# @return [String] why a copy that holds no row cannot be rehearsed, and what to set
|
|
37
|
+
def refusal(domain)
|
|
38
|
+
held = eras.zero? ? "no era and no rows" : "#{eras} era(s) but no rows"
|
|
39
|
+
where = if source == "memory"
|
|
40
|
+
"in the `rows` document"
|
|
41
|
+
else
|
|
42
|
+
"in schema #{schema}#{" (not in the copy)" unless present}"
|
|
43
|
+
end
|
|
44
|
+
"the copy holds #{held} for domain #{domain} #{where}: #{remedy}#{hint}"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# @return [Hash] the report facts, as the `RehearsalRun` commands take them
|
|
48
|
+
def facts
|
|
49
|
+
{ schema_used: { name: schema, source: source, present: present }, eras_held: { value: eras },
|
|
50
|
+
journal_entries: { value: journal_entries }, head_rows: { value: head_rows }, census: counts.map(&:to_h) }
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
def where
|
|
56
|
+
return "in the `rows` document" if source == "memory"
|
|
57
|
+
|
|
58
|
+
"in schema #{schema}#{" (not in the copy)" unless present}"
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def remedy
|
|
62
|
+
return "name the document that holds the stored records in `rows`" if source == "memory"
|
|
63
|
+
|
|
64
|
+
"set the schema in the world file (`rehearsed_by` { schema \"...\" })"
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def hint = elsewhere.empty? ? "" : "; schemas in this copy that hold a lineage: #{elsewhere.join(", ")}"
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "uri"
|
|
4
|
+
|
|
5
|
+
module Hecks
|
|
6
|
+
module Adapters
|
|
7
|
+
# Everything a rehearsal does on a client's domain: where its data copy comes from, how the
|
|
8
|
+
# mint's audit is run over it, and what is reported. The two port adapters (`RehearsalStore`
|
|
9
|
+
# and `RehearsalSubject`) are thin; the work lives here.
|
|
10
|
+
module Rehearsal
|
|
11
|
+
# A rehearsal that was not admitted or could not be carried on: the port answers it as a
|
|
12
|
+
# refusal, which the run keeps as `refusal`.
|
|
13
|
+
class Refused < StandardError; end
|
|
14
|
+
|
|
15
|
+
# What a subject's own `.world` says about the copy of its data, read from its
|
|
16
|
+
# `rehearsed_by` block and checked before anything is touched.
|
|
17
|
+
#
|
|
18
|
+
# A connection is only ever named by an environment variable (`connection_env`); a literal
|
|
19
|
+
# connection string, user or password in the world is refused, so the file holds no secret.
|
|
20
|
+
class Declaration
|
|
21
|
+
KINDS = %w[memory postgres].freeze
|
|
22
|
+
MODES = %w[empty dump readonly_copy].freeze
|
|
23
|
+
LITERALS = %i[database url connection password user].freeze
|
|
24
|
+
LOCAL_HOSTS = [nil, "", "localhost", "127.0.0.1", "::1", "[::1]"].freeze
|
|
25
|
+
|
|
26
|
+
NAME = /\A[A-Za-z0-9_-]+\z/
|
|
27
|
+
|
|
28
|
+
attr_reader :kind, :mode, :connection_env, :dump, :rows, :drop_after, :allow_remote, :schema, :environment
|
|
29
|
+
|
|
30
|
+
# @param world [#for_verb, nil] the subject's world, as the registry holds it
|
|
31
|
+
# @param directory [String] the domain's bluebook directory, which a relative file is under
|
|
32
|
+
# @return [Declaration] the checked declaration
|
|
33
|
+
# @raise [Refused] if the world declares no `rehearsed_by`, or declares it wrongly
|
|
34
|
+
def self.from_world(world, directory)
|
|
35
|
+
settings = world&.for_verb("rehearsed_by") || {}
|
|
36
|
+
raise Refused, "the domain's world declares no `rehearsed_by`: nothing says where its data copy is" if settings.empty?
|
|
37
|
+
|
|
38
|
+
new(settings, directory).tap(&:check!)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# @param settings [Hash] the block's settings, `adapter:` among them
|
|
42
|
+
# @param directory [String] where a relative `dump` or `rows` is found
|
|
43
|
+
def initialize(settings, directory)
|
|
44
|
+
@kind = settings[:adapter].to_s.downcase
|
|
45
|
+
@connection_env = settings[:connection_env]&.to_s
|
|
46
|
+
@dump = expand(settings[:dump], directory)
|
|
47
|
+
@rows = expand(settings[:rows], directory)
|
|
48
|
+
@mode = settings.fetch(:mode) { default_mode }.to_s
|
|
49
|
+
read_switches(settings)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# @return [void]
|
|
53
|
+
# @raise [Refused] naming what is wrong with the declaration
|
|
54
|
+
def check!
|
|
55
|
+
check_names!
|
|
56
|
+
raise Refused, literal_wording unless @literals.empty?
|
|
57
|
+
|
|
58
|
+
kind == "postgres" ? check_postgres! : check_memory!
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# @return [String] the connection the environment holds
|
|
62
|
+
# @raise [Refused] if the variable the world names is unset or empty, or names a server
|
|
63
|
+
# that is not this machine and the world did not say `allow_remote true`
|
|
64
|
+
def connection
|
|
65
|
+
value = ENV.fetch(connection_env, "")
|
|
66
|
+
raise Refused, "the environment variable #{connection_env} is not set" if value.strip.empty?
|
|
67
|
+
|
|
68
|
+
refuse_remote!(value)
|
|
69
|
+
value
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
private
|
|
73
|
+
|
|
74
|
+
def refuse_remote!(value)
|
|
75
|
+
host = URI.parse(value).host
|
|
76
|
+
return if allow_remote || LOCAL_HOSTS.include?(host)
|
|
77
|
+
|
|
78
|
+
raise Refused, "#{connection_env} names the server #{host.inspect}, which is not this machine: " \
|
|
79
|
+
"a rehearsal reads a local copy; say `allow_remote true` in rehearsed_by to point it elsewhere"
|
|
80
|
+
rescue URI::InvalidURIError
|
|
81
|
+
nil
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def read_switches(settings)
|
|
85
|
+
@drop_after = settings.fetch(:drop_after, true) != false
|
|
86
|
+
@allow_remote = settings.fetch(:allow_remote, false) == true
|
|
87
|
+
@literals = LITERALS & settings.keys.map(&:to_sym)
|
|
88
|
+
@schema = settings[:schema]&.to_s
|
|
89
|
+
@environment = settings[:environment]&.to_s
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def default_mode = dump || rows ? "dump" : "empty"
|
|
93
|
+
|
|
94
|
+
def check_names!
|
|
95
|
+
raise Refused, "rehearsed_by names #{kind.inspect}; the stores are #{KINDS.join(" and ")}" unless KINDS.include?(kind)
|
|
96
|
+
|
|
97
|
+
raise Refused, "rehearsed_by mode #{mode.inspect} is not one of #{MODES.join(", ")}" unless MODES.include?(mode)
|
|
98
|
+
|
|
99
|
+
check_label!("schema", schema)
|
|
100
|
+
check_label!("environment", environment)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def check_label!(word, value)
|
|
104
|
+
return if value.nil? || NAME.match?(value)
|
|
105
|
+
|
|
106
|
+
raise Refused, "rehearsed_by #{word} #{value.inspect} is not a name (letters, digits, `_`, `-`)"
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def expand(path, directory)
|
|
110
|
+
path && File.expand_path(path.to_s, directory)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def literal_wording
|
|
114
|
+
"rehearsed_by holds #{@literals.join(", ")}: name the environment variable in `connection_env`, " \
|
|
115
|
+
"never the value, so the world file holds no secret"
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def check_postgres!
|
|
119
|
+
unless connection_env
|
|
120
|
+
raise Refused,
|
|
121
|
+
"a postgres rehearsal needs `connection_env`, the name of the variable holding the server"
|
|
122
|
+
end
|
|
123
|
+
raise Refused, "mode dump needs `dump`, the file to restore" if mode == "dump" && !dump
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def check_memory!
|
|
127
|
+
raise Refused, "mode readonly_copy is a postgres mode: a memory store has no copy to read" if mode == "readonly_copy"
|
|
128
|
+
raise Refused, "mode dump needs `rows`, the JSON file the memory store holds" if mode == "dump" && !rows
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|