hecks 3.10.0 → 3.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/examples/pizzas/bluebook/pizzas.bluebook +29 -0
- data/examples/pizzas/bluebook/pizzas.hecksagon +7 -0
- data/exe/hecks +4 -4
- data/lib/hecks/adapters/driven/d1.rb +2 -10
- data/lib/hecks/adapters/driven/heki.adapter +1 -1
- data/lib/hecks/adapters/driven/heki.rb +2 -12
- data/lib/hecks/adapters/driven/lambda.adapter +1 -1
- data/lib/hecks/adapters/driven/lambda.rb +3 -18
- data/lib/hecks/adapters/driven/postgres.rb +4 -11
- data/lib/hecks/adapters/driven/sqlite.adapter +1 -1
- data/lib/hecks/adapters/driven/sqlite.rb +2 -10
- data/lib/hecks/adapters/driving/admission.rb +46 -0
- data/lib/hecks/adapters/driving/cli/nesting.rb +36 -0
- data/lib/hecks/adapters/driving/cli/shorthand.rb +88 -0
- data/lib/hecks/adapters/driving/cli.rb +127 -0
- data/lib/hecks/adapters/driving/cli_runner/answers.rb +160 -0
- data/lib/hecks/adapters/driving/cli_runner/resolution.rb +158 -0
- data/lib/hecks/adapters/driving/cli_runner/settling.rb +120 -0
- data/lib/hecks/adapters/driving/cli_runner/streaming.rb +93 -0
- data/lib/hecks/adapters/driving/cli_runner/suggestions.rb +47 -0
- data/lib/hecks/adapters/driving/cli_runner.rb +70 -0
- data/lib/hecks/adapters/driving/command_request.rb +121 -0
- data/lib/hecks/adapters/driving/handle.rb +214 -0
- data/lib/hecks/adapters/driving/json.rb +136 -0
- data/lib/hecks/adapters/driving/launcher_options.rb +267 -0
- data/lib/hecks/adapters/driving/mcp/properties.rb +49 -0
- data/lib/hecks/adapters/driving/mcp/tool_calls.rb +116 -0
- data/lib/hecks/adapters/driving/mcp/tool_definitions/auditing.rb +53 -0
- data/lib/hecks/adapters/driving/mcp/tool_definitions/calling.rb +71 -0
- data/lib/hecks/adapters/driving/mcp/tool_definitions/reading.rb +99 -0
- data/lib/hecks/adapters/driving/mcp/tool_definitions.rb +18 -0
- data/lib/hecks/adapters/driving/mcp.rb +159 -0
- data/lib/hecks/adapters/driving/mcp_guide_cache.rb +78 -0
- data/lib/hecks/adapters/driving/mcp_scope/argument_policy.rb +97 -0
- data/lib/hecks/adapters/driving/mcp_scope/env_problems.rb +69 -0
- data/lib/hecks/adapters/driving/mcp_scope/presenter.rb +83 -0
- data/lib/hecks/adapters/driving/mcp_scope.rb +213 -0
- data/lib/hecks/adapters/driving/newcomer.rb +50 -0
- data/lib/hecks/adapters/driving/ruby/aggregate_module.rb +171 -0
- data/lib/hecks/adapters/driving/ruby/chapter.rb +91 -0
- data/lib/hecks/adapters/driving/ruby.rb +83 -0
- data/lib/hecks/adapters/driving/usage_cache.rb +144 -0
- data/lib/hecks/{doors.rb → adapters/driving.rb} +10 -6
- data/lib/hecks/behaviors/expectations/boot.rb +1 -1
- data/lib/hecks/bench/ruby_runner.rb +1 -1
- data/lib/hecks/bluebook/ask_resolution.rb +130 -0
- data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +8 -2
- data/lib/hecks/bluebook/assembly/contracts/read_models.rb +1 -1
- data/lib/hecks/bluebook/assembly/contracts/rules.rb +4 -2
- data/lib/hecks/bluebook/assembly/contracts/structure.rb +3 -1
- data/lib/hecks/bluebook/assembly/marks.rb +4 -15
- data/lib/hecks/bluebook/assembly/scalars.rb +39 -0
- data/lib/hecks/bluebook/attribute.rb +1 -1
- data/lib/hecks/bluebook/behaviour/command.rb +13 -8
- data/lib/hecks/bluebook/behaviour/policy.rb +30 -0
- data/lib/hecks/bluebook/capabilities/attribute_values.rb +72 -0
- data/lib/hecks/bluebook/capabilities.rb +91 -9
- data/lib/hecks/bluebook/domain_port.rb +16 -4
- data/lib/hecks/bluebook/dsl/adapter_builder.rb +18 -6
- data/lib/hecks/bluebook/dsl/binding_proxy.rb +13 -0
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/computed_sources.rb +98 -0
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/fan_out_rows.rb +42 -0
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/projected_fields.rb +14 -2
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/provisions.rb +69 -3
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/with_projections.rb +17 -10
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +5 -0
- data/lib/hecks/bluebook/dsl/bootstrap_table.rb +2 -0
- data/lib/hecks/bluebook/dsl/command_builder/mutations.rb +8 -1
- data/lib/hecks/bluebook/dsl/command_builder/operands.rb +121 -0
- data/lib/hecks/bluebook/dsl/command_builder.rb +2 -0
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +10 -1
- data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +45 -2
- data/lib/hecks/bluebook/dsl/policy_builder.rb +47 -10
- data/lib/hecks/bluebook/dsl/rule_reference.rb +7 -4
- data/lib/hecks/bluebook/dsl/world_builder.rb +1 -1
- data/lib/hecks/bluebook/expression/ast_json/emitters.rb +6 -0
- data/lib/hecks/bluebook/expression/ast_json.rb +9 -6
- data/lib/hecks/bluebook/expression/ast_reader.rb +14 -9
- data/lib/hecks/bluebook/expression/projection.json +36 -0
- data/lib/hecks/bluebook/expression/resolver/operations.rb +31 -0
- data/lib/hecks/bluebook/expression/resolver/parsing.rb +38 -5
- data/lib/hecks/bluebook/expression/resolver/scanning.rb +22 -5
- data/lib/hecks/bluebook/expression/resolver/values.rb +10 -0
- data/lib/hecks/bluebook/expression/resolver.rb +20 -1
- data/lib/hecks/bluebook/hecksagon.rb +23 -3
- data/lib/hecks/bluebook/lifecycle.rb +22 -3
- data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +13 -1
- data/lib/hecks/bluebook/meta_validator/modes.rb +4 -1
- data/lib/hecks/bluebook/meta_validator/readings.rb +24 -1
- data/lib/hecks/bluebook/meta_validator/reconstruction/heads.rb +2 -1
- data/lib/hecks/bluebook/meta_validator/reconstruction.rb +6 -3
- data/lib/hecks/bluebook/meta_validator/shapes.rb +5 -0
- data/lib/hecks/bluebook/meta_validator/verdict_cache.rb +1 -1
- data/lib/hecks/bluebook/meta_validator.rb +12 -12
- data/lib/hecks/bluebook/model_check/ask_checks.rb +71 -0
- data/lib/hecks/bluebook/model_check/policy_checks.rb +2 -1
- data/lib/hecks/bluebook/model_check/reference_checks.rb +70 -0
- data/lib/hecks/bluebook/model_check.rb +8 -2
- data/lib/hecks/bluebook/policy.rb +4 -2
- data/lib/hecks/bluebook/smoke_test.rb +1 -1
- data/lib/hecks/bluebook/synthesizer.rb +2 -0
- data/lib/hecks/bluebook.rb +2 -0
- data/lib/hecks/cli/command_table.rb +1 -1
- data/lib/hecks/cli/console.rb +2 -1
- data/lib/hecks/cli/document.rb +1 -1
- data/lib/hecks/cli/launcher_words.rb +4 -4
- data/lib/hecks/cli/mcp.rb +4 -4
- data/lib/hecks/cli/project_cli/launcher_source.rb +2 -2
- data/lib/hecks/cli/project_cli/templates.rb +5 -5
- data/lib/hecks/cli/project_cli.rb +9 -6
- data/lib/hecks/cli/run.rb +2 -2
- data/lib/hecks/cli/smoke_http/checks.rb +4 -1
- data/lib/hecks/cli/smoke_http/transport.rb +3 -3
- data/lib/hecks/cli/smoke_http.rb +3 -0
- data/lib/hecks/codemod.rb +5 -2
- data/lib/hecks/corpus/routes.rb +7 -0
- data/lib/hecks/corpus/rust.rb +3 -2
- data/lib/hecks/deploy/bluebook/deploy.bluebook +34 -4
- data/lib/hecks/doc/reference/rendering.rb +6 -3
- data/lib/hecks/forms/app/commands.rb +8 -7
- data/lib/hecks/forms/app/queries.rb +2 -1
- data/lib/hecks/forms/app.rb +1 -1
- data/lib/hecks/forms/record_renderer.rb +1 -1
- data/lib/hecks/framework/bluebook/privacy.bluebook +11 -0
- data/lib/hecks/fuzzing/concurrent_dispatch.rb +4 -1
- data/lib/hecks/fuzzing/failure_triage.rb +2 -1
- data/lib/hecks/fuzzing/mutation/run.rb +11 -4
- data/lib/hecks/fuzzing/properties/catalog.rb +6 -4
- data/lib/hecks/fuzzing/properties/guaranteed_by_construction.rb +18 -11
- data/lib/hecks/fuzzing/properties/guards.rb +1 -1
- data/lib/hecks/fuzzing/self_consistency/saga_redelivery.rb +1 -1
- data/lib/hecks/fuzzing/self_consistency.rb +6 -2
- data/lib/hecks/fuzzing/shrinker.rb +3 -1
- data/lib/hecks/fuzzing/value_generator.rb +19 -5
- data/lib/hecks/gate/stages.yml +33 -7
- data/lib/hecks/grammar/evolve.rb +1 -1
- data/lib/hecks/grammar/expression_operators.json +372 -0
- data/lib/hecks/grammar/operators.rb +3 -1
- data/lib/hecks/hecks/adapters/codebase/corpus_tasks.rb +10 -10
- data/lib/hecks/hecks/adapters/codebase/inspection/rubycritic.rb +35 -0
- data/lib/hecks/hecks/adapters/codebase/inspection.rb +104 -0
- data/lib/hecks/hecks/adapters/codebase/publishing.rb +2 -1
- data/lib/hecks/hecks/adapters/codebase/release_facts.rb +12 -2
- data/lib/hecks/hecks/adapters/codebase/source_tree.rb +2 -1
- data/lib/hecks/hecks/adapters/codebase/test_runner.rb +1 -1
- data/lib/hecks/hecks/adapters/cost_explorer.rb +8 -5
- data/lib/hecks/hecks/adapters/deploy_toolchain/statuses.rb +2 -1
- data/lib/hecks/hecks/adapters/finding_github.rb +13 -6
- data/lib/hecks/hecks/adapters/first_admin.rb +2 -2
- data/lib/hecks/hecks/adapters/github_rulesets.rb +6 -3
- data/lib/hecks/hecks/adapters/in_process_boot.rb +1 -1
- data/lib/hecks/hecks/adapters/journal_store/compaction.rb +1 -1
- data/lib/hecks/hecks/adapters/journal_store/edge_audit.rb +10 -20
- data/lib/hecks/hecks/adapters/journal_store/held_domain.rb +28 -5
- data/lib/hecks/hecks/adapters/local_files.rb +2 -2
- data/lib/hecks/hecks/adapters/rehearsal/audit_report.rb +107 -0
- data/lib/hecks/hecks/adapters/rehearsal/census.rb +71 -0
- data/lib/hecks/hecks/adapters/rehearsal/declaration.rb +133 -0
- data/lib/hecks/hecks/adapters/rehearsal/environment.rb +83 -0
- data/lib/hecks/hecks/adapters/rehearsal/inquiry.rb +142 -0
- data/lib/hecks/hecks/adapters/rehearsal/memory_store.rb +96 -0
- data/lib/hecks/hecks/adapters/rehearsal/postgres_census.rb +100 -0
- data/lib/hecks/hecks/adapters/rehearsal/postgres_reading.rb +69 -0
- data/lib/hecks/hecks/adapters/rehearsal/postgres_store.rb +133 -0
- data/lib/hecks/hecks/adapters/rehearsal/rows.rb +41 -0
- data/lib/hecks/hecks/adapters/rehearsal/rust_audit.rb +83 -0
- data/lib/hecks/hecks/adapters/rehearsal/subject.rb +104 -0
- data/lib/hecks/hecks/adapters/rehearsal.rb +54 -0
- data/lib/hecks/hecks/adapters/rehearsal_store.adapter +7 -0
- data/lib/hecks/hecks/adapters/rehearsal_store.rb +81 -0
- data/lib/hecks/hecks/adapters/terminal.rb +13 -11
- data/lib/hecks/hecks/codebase.behaviors +13 -1
- data/lib/hecks/hecks/codebase.bluebook +239 -5
- data/lib/hecks/hecks/custodian.behaviors +24 -6
- data/lib/hecks/hecks/custodian.bluebook +6 -1227
- data/lib/hecks/hecks/deploy.behaviors +1 -1
- data/lib/hecks/hecks/deploy.world +1 -3
- data/lib/hecks/hecks/hecks.behaviors +1 -1
- data/lib/hecks/hecks/hecks.hecksagon +53 -18
- data/lib/hecks/hecks/hecks.world +13 -10
- data/lib/hecks/hecks/rehearsal.bluebook +477 -0
- data/lib/hecks/hecks/site.behaviors +1 -1
- data/lib/hecks/hecks/site.world +0 -1
- data/lib/hecks/hecks/tooling.bluebook +1238 -0
- data/lib/hecks/indifferent_key.rb +20 -0
- data/lib/hecks/language/adapter.bluebook +13 -3
- data/lib/hecks/language/bluebook/aggregate.bluebook +23 -0
- data/lib/hecks/language/bluebook/bluebook.behaviors +23 -0
- data/lib/hecks/language/bluebook/bluebook.bluebook +15 -0
- data/lib/hecks/language/bluebook/entity.bluebook +18 -0
- data/lib/hecks/language/bluebook/policy.behaviors +19 -0
- data/lib/hecks/language/bluebook/policy.bluebook +9 -1
- data/lib/hecks/language/bluebook/query.bluebook +1 -1
- data/lib/hecks/language/bluebook/vocabulary.bluebook +63 -4
- data/lib/hecks/language/hecksagon/domain_port.bluebook +1 -1
- data/lib/hecks/language/hecksagon/hecksagon.bluebook +22 -0
- data/lib/hecks/language/oidc.json +10 -0
- data/lib/hecks/language/port.bluebook +4 -4
- data/lib/hecks/literal.rb +12 -3
- data/lib/hecks/ports/key_vault.rb +7 -2
- data/lib/hecks/ports/persistence/plugins/era/era_check.rb +21 -10
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/boot.rb +4 -2
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +2 -1
- data/lib/hecks/ports/persistence/repository_factory.rb +6 -3
- data/lib/hecks/projection_files/expression_tables.rb +7 -2
- data/lib/hecks/projections/deploy/box/compose.rb +6 -4
- data/lib/hecks/projections/deploy/box/edge.rb +62 -0
- data/lib/hecks/projections/deploy/box/logging.rb +99 -0
- data/lib/hecks/projections/deploy/box/roll_scripts.rb +16 -8
- data/lib/hecks/projections/deploy/box/settings/cdn.rb +40 -0
- data/lib/hecks/projections/deploy/box/settings/logs.rb +40 -0
- data/lib/hecks/projections/deploy/box/settings/patterns.rb +11 -0
- data/lib/hecks/projections/deploy/box/settings/types.rb +16 -1
- data/lib/hecks/projections/deploy/box/settings.rb +6 -2
- data/lib/hecks/projections/deploy/box/stack_yaml.rb +5 -2
- data/lib/hecks/projections/deploy/box/templates/Makefile.tmpl +16 -0
- data/lib/hecks/projections/deploy/box/templates/box.yaml.tmpl +8 -4
- data/lib/hecks/projections/deploy/box/templates/deploy-box.sh.tmpl +32 -0
- data/lib/hecks/projections/deploy/box/templates/deploy-service.sh.tmpl +1 -1
- data/lib/hecks/projections/deploy/box/templates/edge.mk.tmpl +17 -0
- data/lib/hecks/projections/deploy/box/templates/edge.yaml.tmpl +65 -0
- data/lib/hecks/projections/deploy/box/templates/log-group.yaml.tmpl +12 -0
- data/lib/hecks/projections/deploy/box/templates/log-policy.yaml.tmpl +7 -0
- data/lib/hecks/projections/deploy/box/templates/render-compose-taskdef.sh.tmpl +5 -4
- data/lib/hecks/projections/deploy/box/templates/render-compose.sh.tmpl +5 -4
- data/lib/hecks/projections/deploy/box.rb +15 -3
- data/lib/hecks/projections/deploy/preview/preview.sh.erb +1 -1
- data/lib/hecks/projections/deploy/shared.rb +5 -2
- data/lib/hecks/projections/model/deviations.rb +2 -2
- data/lib/hecks/projections/model.rb +2 -2
- data/lib/hecks/projections/oidc.rb +13 -1
- data/lib/hecks/projections/shape.rb +5 -2
- data/lib/hecks/projections/site/cms_editor/attributes.rb +26 -6
- data/lib/hecks/projections/site/cms_editor/blocks.rb +157 -0
- data/lib/hecks/projections/site/cms_editor/checks.rb +14 -1
- data/lib/hecks/projections/site/cms_editor/clearing.rb +70 -0
- data/lib/hecks/projections/site/cms_editor/color.rb +124 -0
- data/lib/hecks/projections/site/cms_editor/composition.rb +42 -0
- data/lib/hecks/projections/site/cms_editor/destructive.rb +61 -0
- data/lib/hecks/projections/site/cms_editor/drafts.rb +102 -0
- data/lib/hecks/projections/site/cms_editor/hints.rb +74 -0
- data/lib/hecks/projections/site/cms_editor/icons.rb +86 -0
- data/lib/hecks/projections/site/cms_editor/key_kinds.rb +31 -0
- data/lib/hecks/projections/site/cms_editor/limits.rb +72 -0
- data/lib/hecks/projections/site/cms_editor/media.rb +1 -1
- data/lib/hecks/projections/site/cms_editor/moment.rb +34 -0
- data/lib/hecks/projections/site/cms_editor/palette.rb +115 -0
- data/lib/hecks/projections/site/cms_editor/pickers.rb +85 -0
- data/lib/hecks/projections/site/cms_editor/preview.rb +91 -0
- data/lib/hecks/projections/site/cms_editor/relations.rb +92 -0
- data/lib/hecks/projections/site/cms_editor/scale_checks.rb +66 -0
- data/lib/hecks/projections/site/cms_editor/scheduling.rb +150 -0
- data/lib/hecks/projections/site/cms_editor/schema.rb +96 -19
- data/lib/hecks/projections/site/cms_editor/setting.rb +52 -9
- data/lib/hecks/projections/site/cms_editor/targets.rb +86 -0
- data/lib/hecks/projections/site/cms_editor/templates/package.json.tmpl +18 -1
- data/lib/hecks/projections/site/cms_editor/templates/src/app.ts.tmpl +251 -57
- data/lib/hecks/projections/site/cms_editor/templates/src/assets.ts.tmpl +98 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/auth/membership.ts.tmpl +32 -24
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/app.css.tmpl +412 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/autosave.js.tmpl +144 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/blocks.js.tmpl +500 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/body_doc.js.tmpl +175 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/body_editor.js.tmpl +418 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/epoch.js.tmpl +61 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/forms.js.tmpl +210 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/lists.js.tmpl +40 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/main.js.tmpl +26 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/media_picker.js.tmpl +232 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/moments.js.tmpl +73 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/pickers.js.tmpl +152 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/preview.js.tmpl +98 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/shell.js.tmpl +140 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/toast.js.tmpl +66 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/choices.ts.tmpl +99 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/commands.ts.tmpl +78 -24
- data/lib/hecks/projections/site/cms_editor/templates/src/config.ts.tmpl +6 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/flash.ts.tmpl +52 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/host.ts.tmpl +19 -2
- data/lib/hecks/projections/site/cms_editor/templates/src/media/handler.ts.tmpl +31 -8
- data/lib/hecks/projections/site/cms_editor/templates/src/names.ts.tmpl +36 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/schema.ts.tmpl +131 -2
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/availability.ts.tmpl +15 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/block_list.js.tmpl +213 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/blocks.ts.tmpl +275 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/body_model.js.tmpl +8 -5
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/detail.ts.tmpl +88 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/dialogs.ts.tmpl +43 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/fields.ts.tmpl +229 -34
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/html.ts.tmpl +40 -81
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/icons.ts.tmpl +15 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/input.ts.tmpl +29 -12
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/links.ts.tmpl +100 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/moments.ts.tmpl +44 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/outline.ts.tmpl +53 -17
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/pages.ts.tmpl +178 -88
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/paging.ts.tmpl +82 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/pickers.ts.tmpl +110 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/preview.ts.tmpl +87 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/related.ts.tmpl +97 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/schedule.ts.tmpl +160 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/shell.ts.tmpl +211 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/table.ts.tmpl +151 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/theme.js.tmpl +16 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/values.ts.tmpl +69 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/words.ts.tmpl +97 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/world.ts.tmpl +35 -0
- data/lib/hecks/projections/site/cms_editor/templates/tsconfig.json.tmpl +2 -1
- data/lib/hecks/projections/site/cms_editor/theme.rb +29 -0
- data/lib/hecks/projections/site/cms_editor/theme_checks.rb +57 -0
- data/lib/hecks/projections/site/cms_editor.rb +49 -23
- data/lib/hecks/projections/site/role_probe.rb +2 -0
- data/lib/hecks/projections/site/site_root/workflow.rb +5 -0
- data/lib/hecks/projector/cli_audience.rb +22 -1
- data/lib/hecks/projector/cli_projector/listing.rb +1 -1
- data/lib/hecks/projector/cli_projector/option_specs.rb +1 -1
- data/lib/hecks/projector/cli_projector/usage.rb +15 -4
- data/lib/hecks/projector/docs_projector/closing.rb +1 -1
- data/lib/hecks/projector/docs_projector.rb +1 -1
- data/lib/hecks/projector/exporter/bindings.rb +40 -10
- data/lib/hecks/projector/exporter/capability_values.rb +55 -0
- data/lib/hecks/projector/narrate_projector/reactions.rb +1 -1
- data/lib/hecks/projector/target.rb +11 -0
- data/lib/hecks/projector.rb +32 -5
- data/lib/hecks/quality_control/adapters/github_checks.rb +12 -8
- data/lib/hecks/quality_control/adapters/github_issues.rb +8 -4
- data/lib/hecks/quality_control/cli/qa_open_pr.rb +2 -2
- data/lib/hecks/quality_control/cli/qa_sweep/checks/shrinking.rb +1 -1
- data/lib/hecks/quality_control/cli/qa_sweep/finding_report.rb +1 -1
- data/lib/hecks/quality_control/cli/qa_sweep.rb +1 -1
- data/lib/hecks/quality_control/quality_control.bluebook +6 -2
- data/lib/hecks/release/approval.rb +64 -0
- data/lib/hecks/router/{door.rb → aggregate_reader.rb} +5 -4
- data/lib/hecks/router/namespace_installer.rb +16 -16
- data/lib/hecks/router.rb +1 -1
- data/lib/hecks/runtime/best_effort.rb +22 -0
- data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +12 -2
- data/lib/hecks/runtime/command_interpreter.rb +3 -2
- data/lib/hecks/runtime/command_rules/emission.rb +1 -1
- data/lib/hecks/runtime/dispatcher.rb +4 -4
- data/lib/hecks/runtime/entity_element/location.rb +1 -1
- data/lib/hecks/runtime/event.rb +5 -0
- data/lib/hecks/runtime/identity.rb +7 -6
- data/lib/hecks/runtime/interpreting.rb +2 -1
- data/lib/hecks/runtime/invocation_builder.rb +1 -1
- data/lib/hecks/runtime/loader/boot_steps.rb +8 -5
- data/lib/hecks/runtime/loader.rb +10 -10
- data/lib/hecks/runtime/policy_interpreter/arguments.rb +7 -4
- data/lib/hecks/runtime/policy_interpreter/fan_out.rb +3 -3
- data/lib/hecks/runtime/policy_interpreter.rb +6 -6
- data/lib/hecks/runtime/port_operation_interpreter.rb +1 -1
- data/lib/hecks/runtime/reaction_invocation.rb +1 -1
- data/lib/hecks/runtime/rebuild_sweep.rb +11 -1
- data/lib/hecks/runtime/registry/ask_verification.rb +35 -0
- data/lib/hecks/runtime/registry/hecksagon_rules.rb +10 -0
- data/lib/hecks/runtime/registry/merging.rb +1 -1
- data/lib/hecks/runtime/registry/providers.rb +16 -0
- data/lib/hecks/runtime/registry/read_repository.rb +6 -4
- data/lib/hecks/runtime/registry/verification.rb +4 -1
- data/lib/hecks/runtime/registry/world_defaults.rb +29 -0
- data/lib/hecks/runtime/registry.rb +1 -1
- data/lib/hecks/runtime/saga_interpreter/compensation.rb +1 -1
- data/lib/hecks/runtime/saga_interpreter/delivery.rb +4 -4
- data/lib/hecks/runtime/saga_interpreter/transitions.rb +1 -1
- data/lib/hecks/runtime/saga_interpreter.rb +3 -3
- data/lib/hecks/runtime/tenant_check.rb +5 -4
- data/lib/hecks/runtime/value/coercion.rb +2 -2
- data/lib/hecks/runtime/value/validation.rb +16 -4
- data/lib/hecks/runtime.rb +9 -9
- data/lib/hecks/rust_build/host.rb +11 -4
- data/lib/hecks/rust_build/prebuilt_binary.rb +101 -0
- data/lib/hecks/rust_build/project_rust/target_ir.rb +1 -1
- data/lib/hecks/rust_build/wasm.rb +1 -1
- data/lib/hecks/rust_build/wasm_browser.rb +1 -1
- data/lib/hecks/rust_build.rb +11 -0
- data/lib/hecks/served_options.rb +53 -0
- data/lib/hecks/site/bluebook/site.bluebook +1 -0
- data/lib/hecks/storehouse/audit.rb +1 -1
- data/lib/hecks/storehouse/dispatching.rb +8 -6
- data/lib/hecks/storehouse/introspection.rb +2 -2
- data/lib/hecks/storehouse/querying.rb +6 -4
- data/lib/hecks/storehouse.rb +4 -4
- data/lib/hecks/tools/ci_gate_decision.rb +35 -0
- data/lib/hecks/tools/ci_gates.rb +20 -1
- data/lib/hecks/tools/comment_style/source_file.rb +4 -1
- data/lib/hecks/tools/deploy_recipe_lint/fixtures.rb +4 -1
- data/lib/hecks/tools/lanes/files.rb +8 -0
- data/lib/hecks/tools/lanes/prebuilt_workflow.rb +81 -0
- data/lib/hecks/tools/lanes/rows.rb +6 -0
- data/lib/hecks/tools/lanes.rb +1 -0
- data/lib/hecks/tools/oidc_manifests.rb +4 -5
- data/lib/hecks/tools/regeneration_run.rb +11 -4
- data/lib/hecks/tools/release_only.rb +111 -0
- data/lib/hecks/tools/site_routes/editor_files.rb +12 -5
- data/lib/hecks/tools/tenant_provisioning.rb +2 -2
- data/lib/hecks/tools/tools_doc.rb +4 -4
- data/lib/hecks/version.rb +1 -1
- data/lib/hecks/vocabulary.rb +19 -5
- data/lib/hecks.rb +15 -15
- data/lib/rubocop/cop/hecks/env_dump.rb +69 -0
- data/lib/rubocop/cop/hecks/monotonic_duration.rb +53 -0
- data/lib/rubocop/cop/hecks/no_wall_clock_in_runtime.rb +34 -0
- data/lib/rubocop/cop/hecks/raw_shell_interpolation.rb +79 -0
- data/lib/rubocop/cop/hecks/sleep_in_spec.rb +34 -0
- data/lib/rubocop/cop/hecks/swallowed_rescue.rb +37 -0
- data/lib/rubocop/cop/hecks/symbol_string_key_mix.rb +50 -0
- data/lib/rubocop/cop/hecks/untyped_raise.rb +37 -0
- data/prebuilt/9ee54e2f0d4d6d3fe046827779b221e02de665fba911a048ae5dd8b8fbfb7d90.marshal +0 -0
- data/prebuilt/d74a00e996222c1d8b676d5959092effc46b4dc59e0555f3bf9554ac58e3094a.marshal +0 -0
- data/prebuilt/verdicts-df19f35868d3284a70f55e42dcef87ef4e65e6a678e5135f72e1f6cff4c1357b.json +1 -0
- data/rust/.gitignore +6 -0
- data/rust/Cargo.toml +6 -0
- data/rust/build/src/binding_pass.rs +103 -0
- data/rust/build/src/cargo_sync.rs +6 -0
- data/rust/build/src/lineage_pass.rs +3 -3
- data/rust/build/src/main.rs +17 -5
- data/rust/build/src/pipeline.rs +48 -5
- data/rust/build/src/seams_pass.rs +123 -0
- data/rust/build/src/translation_pass.rs +167 -0
- data/rust/codegen/src/asks.rs +103 -0
- data/rust/codegen/src/commands.rs +13 -8
- data/rust/codegen/src/constraints.rs +25 -0
- data/rust/codegen/src/domain_generator.rs +2 -0
- data/rust/codegen/src/expr_emitter.rs +13 -0
- data/rust/codegen/src/json_codec.rs +54 -15
- data/rust/codegen/src/main.rs +1 -0
- data/rust/codegen/src/mutations.rs +128 -3
- data/rust/codegen/src/queries.rs +11 -2
- data/rust/codegen/src/reactions.rs +1 -1
- data/rust/codegen/src/types.rs +166 -8
- data/rust/host/HECKS_RELEASE +1 -1
- data/rust/host/src/api.rs +4 -4
- data/rust/host/src/bin/validate_harness.rs +43 -0
- data/rust/host/src/boundary_fuzz/expr_json.rs +2 -2
- data/rust/host/src/boundary_fuzz/ir.rs +1 -0
- data/rust/host/src/boundary_fuzz/server.rs +1 -1
- data/rust/host/src/checkout.rs +35 -19
- data/rust/host/src/commerce_ir.rs +408 -2
- data/rust/host/src/expr_json.rs +118 -0
- data/rust/host/src/extension.rs +13 -1
- data/rust/host/src/lib.rs +1 -0
- data/rust/host/src/mint.rs +1 -1
- data/rust/host/src/payments/tests.rs +27 -1
- data/rust/host/src/payments.rs +12 -4
- data/rust/host/src/platform_signin/attack_tests.rs +1100 -0
- data/rust/host/src/platform_signin/config.rs +512 -0
- data/rust/host/src/platform_signin/cookie.rs +237 -0
- data/rust/host/src/platform_signin/extension.rs +190 -0
- data/rust/host/src/platform_signin/fetcher.rs +322 -0
- data/rust/host/src/platform_signin/handler.rs +251 -0
- data/rust/host/src/platform_signin/handler_tests.rs +554 -0
- data/rust/host/src/platform_signin/jti.rs +196 -0
- data/rust/host/src/platform_signin/jwks.rs +542 -0
- data/rust/host/src/platform_signin/jws.rs +249 -0
- data/rust/host/src/platform_signin/known_answer_tests.rs +237 -0
- data/rust/host/src/platform_signin/mapping_tests.rs +254 -0
- data/rust/host/src/platform_signin/mod.rs +75 -0
- data/rust/host/src/platform_signin/mode_tests.rs +382 -0
- data/rust/host/src/platform_signin/pages.rs +204 -0
- data/rust/host/src/platform_signin/query.rs +233 -0
- data/rust/host/src/platform_signin/session.rs +151 -0
- data/rust/host/src/platform_signin/settings.rs +385 -0
- data/rust/host/src/platform_signin/strict_json.rs +351 -0
- data/rust/host/src/platform_signin/test_support.rs +235 -0
- data/rust/host/src/platform_signin/ttl_tests.rs +94 -0
- data/rust/host/src/platform_signin/verify.rs +604 -0
- data/rust/host/src/platform_signin/wiring_support.rs +319 -0
- data/rust/host/src/query_step.rs +22 -4
- data/rust/host/src/reference_validate.rs +653 -108
- data/rust/host/src/resend.rs +2 -0
- data/rust/host/src/server.rs +19 -6
- data/rust/host/src/ui_schema.rs +132 -5
- data/rust/host/src/web/newsletter.rs +7 -8
- data/rust/host/src/web/newsletter_send.rs +38 -13
- data/rust/host/src/web/registrations.rs +229 -26
- data/rust/host/src/web.rs +53 -9
- data/rust/parser/src/emit.rs +38 -6
- data/rust/parser/src/expr/ast_json.rs +7 -1
- data/rust/parser/src/expr/resolver.rs +171 -6
- data/rust/parser/src/ir.rs +7 -0
- data/rust/parser/src/keywords.rs +24 -0
- data/rust/parser/src/main.rs +3 -0
- data/rust/parser/src/parse/aggregate.rs +2 -1
- data/rust/parser/src/parse/chapter.rs +2 -2
- data/rust/parser/src/parse/command.rs +2 -0
- data/rust/parser/src/parse/hecksagon.rs +5 -2
- data/rust/parser/src/parse/lifecycle.rs +81 -0
- data/rust/parser/src/parse/mod.rs +15 -1
- data/rust/parser/src/parse/policy.rs +13 -2
- data/rust/parser/src/parse/value_object.rs +34 -17
- data/rust/parser/src/ruby_value.rs +1 -1
- data/rust/src/exemplar/commands.rs +4 -4
- data/rust/src/exemplar/constraints.rs +1 -1
- data/rust/src/exemplar/json.rs +43 -1
- data/rust/src/kernel/cli.rs +7 -5
- data/rust/src/kernel/dispatch.rs +2 -2
- data/rust/src/kernel/dispatch_guard_tests.rs +1 -1
- data/rust/src/kernel/expr.rs +66 -3
- data/rust/src/kernel/expression_operators/arithmetic.rs +130 -5
- data/rust/src/kernel/expression_operators/text.rs +26 -2
- data/rust/src/kernel/json.rs +15 -0
- data/rust/src/kernel/mod.rs +1 -1
- data/rust/src/kernel/orchestrate.rs +54 -5
- data/rust/src/kernel/orchestrate_guard_tests.rs +67 -0
- data/rust/src/kernel/pattern.rs +60 -0
- data/rust/src/kernel/reference_lookup.rs +90 -7
- metadata +178 -38
- data/lib/hecks/doors/cli_door/nesting.rb +0 -34
- data/lib/hecks/doors/cli_door/shorthand.rb +0 -85
- data/lib/hecks/doors/cli_door.rb +0 -124
- data/lib/hecks/doors/cli_runner/answers.rb +0 -138
- data/lib/hecks/doors/cli_runner/resolution.rb +0 -146
- data/lib/hecks/doors/cli_runner/settling.rb +0 -115
- data/lib/hecks/doors/cli_runner/streaming.rb +0 -90
- data/lib/hecks/doors/cli_runner/suggestions.rb +0 -43
- data/lib/hecks/doors/cli_runner.rb +0 -66
- data/lib/hecks/doors/command_request.rb +0 -119
- data/lib/hecks/doors/handle.rb +0 -208
- data/lib/hecks/doors/json_door.rb +0 -132
- data/lib/hecks/doors/launcher_options.rb +0 -247
- data/lib/hecks/doors/mcp_door/properties.rb +0 -47
- data/lib/hecks/doors/mcp_door/tool_calls.rb +0 -113
- data/lib/hecks/doors/mcp_door/tool_definitions/auditing.rb +0 -51
- data/lib/hecks/doors/mcp_door/tool_definitions/calling.rb +0 -69
- data/lib/hecks/doors/mcp_door/tool_definitions/reading.rb +0 -97
- data/lib/hecks/doors/mcp_door/tool_definitions.rb +0 -15
- data/lib/hecks/doors/mcp_door.rb +0 -156
- data/lib/hecks/doors/mcp_door_scope/argument_policy.rb +0 -92
- data/lib/hecks/doors/mcp_door_scope/env_problems.rb +0 -55
- data/lib/hecks/doors/mcp_door_scope/presenter.rb +0 -78
- data/lib/hecks/doors/mcp_door_scope.rb +0 -201
- data/lib/hecks/doors/mcp_guide_cache.rb +0 -75
- data/lib/hecks/doors/ruby_door/aggregate_door.rb +0 -162
- data/lib/hecks/doors/ruby_door/chapter.rb +0 -89
- data/lib/hecks/doors/ruby_door.rb +0 -80
- data/lib/hecks/doors/usage_cache.rb +0 -139
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/body_widget.js.tmpl +0 -282
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/media_picker.js.tmpl +0 -109
- data/prebuilt/c28d6d9db14eea3e17161e9d9c262efee30b256cf97693a7cfd552bce24b2f7f.marshal +0 -0
- data/prebuilt/cb375cba61ca0dbe8ddb9739c306510fe05e5f4b7a9b6955caff8224c63ea0e0.marshal +0 -0
- data/prebuilt/verdicts-d6d584ad43b5f3cebf88aafbbdbf024c38574acf18e06adffbc555f7786b1c2f.json +0 -1
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
module Adapters
|
|
5
|
+
module Rehearsal
|
|
6
|
+
# Which environment overlay (`environments/<name>.hecksagon` and `.world`) a rehearsal loads
|
|
7
|
+
# the subject under, and where that was decided.
|
|
8
|
+
#
|
|
9
|
+
# A rehearsal is about the era the production binds will mint, so it loads `production` when
|
|
10
|
+
# the domain has `environments/production.hecksagon`, the same default the Rust host build
|
|
11
|
+
# takes (`ProjectRust::DomainLoader#load_environment`). Most domains bind their aggregates
|
|
12
|
+
# to a database only in that overlay, so the base files alone have no `persisted_by` bind.
|
|
13
|
+
# The choice is a reported fact (`environment_loaded`), never a silent one.
|
|
14
|
+
class Environment
|
|
15
|
+
# Where the name came from, in the order they are tried.
|
|
16
|
+
SOURCES = %w[world HECKS_PROJECT_ENVIRONMENT default none].freeze
|
|
17
|
+
|
|
18
|
+
NONE_WORDS = "no environment overlay (none named in rehearsed_by or HECKS_PROJECT_ENVIRONMENT, " \
|
|
19
|
+
"and no environments/production.hecksagon)"
|
|
20
|
+
|
|
21
|
+
# @!attribute [r] name
|
|
22
|
+
# @return [String] the overlay's name, or `""` when none was loaded
|
|
23
|
+
# @!attribute [r] source
|
|
24
|
+
# @return [String] one of `SOURCES`
|
|
25
|
+
# @!attribute [r] directory
|
|
26
|
+
# @return [String] the bluebook directory the overlay is looked for under
|
|
27
|
+
attr_reader :name, :source, :directory
|
|
28
|
+
|
|
29
|
+
# @param declared [String, nil] what the world's `rehearsed_by` names (`environment "..."`)
|
|
30
|
+
# @param directory [String] the domain's bluebook directory
|
|
31
|
+
# @return [Environment] the choice: the world's, else `HECKS_PROJECT_ENVIRONMENT`, else
|
|
32
|
+
# `production` when its overlay exists, else none
|
|
33
|
+
def self.resolve(declared, directory)
|
|
34
|
+
env = ENV.fetch("HECKS_PROJECT_ENVIRONMENT", "")
|
|
35
|
+
return new(declared, "world", directory) if declared && !declared.empty?
|
|
36
|
+
return new(env, "HECKS_PROJECT_ENVIRONMENT", directory) unless env.empty?
|
|
37
|
+
return new("production", "default", directory) if File.exist?(File.join(directory, "environments",
|
|
38
|
+
"production.hecksagon"))
|
|
39
|
+
|
|
40
|
+
new("", "none", directory)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# @param name [String] the overlay's name
|
|
44
|
+
# @param source [String] one of `SOURCES`
|
|
45
|
+
# @param directory [String] the bluebook directory
|
|
46
|
+
def initialize(name, source, directory)
|
|
47
|
+
@name = name
|
|
48
|
+
@source = source
|
|
49
|
+
@directory = directory
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# @return [String, nil] the name to load, nil when no overlay is wanted
|
|
53
|
+
def overlay = name.empty? ? nil : name
|
|
54
|
+
|
|
55
|
+
# @return [Boolean] whether the hecksagon file of this overlay exists
|
|
56
|
+
def overlay_file? = File.exist?(file)
|
|
57
|
+
|
|
58
|
+
# @return [String] the hecksagon file the overlay is looked for in
|
|
59
|
+
def file = File.join(directory, "environments", "#{name}.hecksagon")
|
|
60
|
+
|
|
61
|
+
# @return [Hash] the report fact, as the `Environment` value object holds it
|
|
62
|
+
def facts = { name: name, source: source, file: name.empty? ? "" : file }
|
|
63
|
+
|
|
64
|
+
# @return [String] what the overlay choice was, in words
|
|
65
|
+
def to_s
|
|
66
|
+
return NONE_WORDS if name.empty?
|
|
67
|
+
|
|
68
|
+
"environment #{name.inspect} (#{source}), file #{file} #{overlay_file? ? "found" : "NOT FOUND"}"
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# The overlays the directory holds, so a refusal can say what could be chosen.
|
|
72
|
+
#
|
|
73
|
+
# @return [Array<String>] the names of `environments/*.hecksagon`
|
|
74
|
+
def available
|
|
75
|
+
Dir.glob(File.join(directory, "environments", "*.hecksagon")).map { |path| File.basename(path, ".hecksagon") }.sort
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# @return [String] the overlays the directory holds, comma separated, or `none`
|
|
79
|
+
def available_words = available.empty? ? "none" : available.join(", ")
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "subject"
|
|
4
|
+
require_relative "memory_store"
|
|
5
|
+
require_relative "postgres_store"
|
|
6
|
+
require_relative "rust_audit"
|
|
7
|
+
require_relative "audit_report"
|
|
8
|
+
|
|
9
|
+
module Hecks
|
|
10
|
+
module Adapters
|
|
11
|
+
module Rehearsal
|
|
12
|
+
# The two questions a rehearsal asks of a subject and its data copy: where the shape stands
|
|
13
|
+
# (`survey`) and what the mint's audit would refuse (`examine`). Each answer is the facts, as
|
|
14
|
+
# the `RehearsalRun` commands take them.
|
|
15
|
+
class Inquiry
|
|
16
|
+
# What every rehearsal says it did not check.
|
|
17
|
+
ENGINE_LIMIT = { what: "the Rust host's mint audit",
|
|
18
|
+
why: "no validate_harness was built, so the Ruby audit alone ran over the rows" }.freeze
|
|
19
|
+
|
|
20
|
+
ALWAYS = [
|
|
21
|
+
{ what: "the mint itself",
|
|
22
|
+
why: "no era was written: the edge, its approval and the audit were evaluated, nothing minted" },
|
|
23
|
+
{ what: "an edge's coverage of every change in the shape", why: "the mint checks it; the rehearsal does not yet" },
|
|
24
|
+
{ what: "commands, policies and requests", why: "no traffic was run: only the stored data and the shape were read" },
|
|
25
|
+
{ what: "data written after the copy was made", why: "a copy is a moment; the mint sees the data of its own" }
|
|
26
|
+
].freeze
|
|
27
|
+
|
|
28
|
+
# @param subject [Subject] the loaded domain
|
|
29
|
+
# @param store [MemoryStore, PostgresStore] its prepared data copy
|
|
30
|
+
def initialize(subject, store)
|
|
31
|
+
@subject = subject
|
|
32
|
+
@store = store
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# @return [Hash] `booted`, `host_version`, `store`, `mode`, `before`, `after`, `minting`,
|
|
36
|
+
# the schema and environment that were read, and what the copy holds
|
|
37
|
+
def survey
|
|
38
|
+
shape.merge(environment_loaded: @subject.environment.facts, copy_note: plain(copy_note)).merge(copy_facts)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# @return [Hash] the edge facts, every refused row and its total, what was read, the parity
|
|
42
|
+
# of the two engines, and what was not checked
|
|
43
|
+
def examine
|
|
44
|
+
edge = minting == "next" ? @subject.edge_from(before) : nil
|
|
45
|
+
refusals, after = edge ? @store.audit(@subject, edge) : [[], {}]
|
|
46
|
+
rust = RustAudit.call(@subject, after)
|
|
47
|
+
report = AuditReport.new(minting: minting, edge: edge, states: after, ruby: refusals, rust: rust)
|
|
48
|
+
answer(edge, report, (refusals + rust.violations).uniq, limits(edge, rust, report))
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
def plain(value) = { value: value }
|
|
54
|
+
|
|
55
|
+
def shape
|
|
56
|
+
{ booted: flag(true), host_version: plain(Rehearsal.host_version), store: plain(@subject.declaration.kind),
|
|
57
|
+
mode: plain(@subject.declaration.mode), before: plain(before), after: plain(@subject.label),
|
|
58
|
+
minting: plain(minting) }
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def answer(edge, report, refused, unchecked)
|
|
62
|
+
rows, total = Rows.from(refused)
|
|
63
|
+
edge_facts(edge).merge(refused_rows: rows, refusal_total: plain(total), parity: report.parity,
|
|
64
|
+
unchecked: unchecked).merge(report.facts)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def flag(value) = plain(value)
|
|
68
|
+
|
|
69
|
+
def census = @census ||= @store.census(@subject)
|
|
70
|
+
|
|
71
|
+
def copy_note
|
|
72
|
+
domain = @subject.bluebook.name
|
|
73
|
+
copy_empty? ? census.refusal(domain) : census.summary(domain)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def copy_empty?
|
|
77
|
+
census.empty? && @subject.declaration.mode != "empty" && @subject.bluebook.aggregates.any?
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# The copy is empty when a dump or a readonly copy was asked for and it holds no row of a
|
|
81
|
+
# domain that declares aggregates; a mode of `empty` is asked to hold nothing.
|
|
82
|
+
def copy_facts = census.facts.merge(copy_empty: flag(copy_empty?))
|
|
83
|
+
|
|
84
|
+
def edge_facts(edge)
|
|
85
|
+
{ minting: plain(minting), edge_present: flag(!edge.nil?), edge_approved: flag(approved?(edge)) }
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def eras = @eras ||= @store.eras(@subject)
|
|
89
|
+
|
|
90
|
+
def latest = @latest ||= eras.last && @subject.held.named(eras.last)
|
|
91
|
+
|
|
92
|
+
def before = latest ? latest[:label] : "none"
|
|
93
|
+
|
|
94
|
+
def minting
|
|
95
|
+
return "first" unless latest
|
|
96
|
+
|
|
97
|
+
before == @subject.label ? "none" : "next"
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def approved?(edge)
|
|
101
|
+
return false unless edge
|
|
102
|
+
|
|
103
|
+
!Translation::ApprovalFile.needs_rehearsal?(edge) ||
|
|
104
|
+
!Translation::ApprovalFile.applicable(@subject.directory, edge).nil?
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def limits(edge, rust, report)
|
|
108
|
+
engine = rust.ran ? [] : [ENGINE_LIMIT]
|
|
109
|
+
engine + report.limits + ALWAYS + @store.limits(edge) + [journal_limit] + attached_limits +
|
|
110
|
+
(@subject.held.capable? ? [] : [binding_note])
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# The audit reads the journal's latest entry of each record, never the head tables.
|
|
114
|
+
def journal_limit
|
|
115
|
+
{ what: "the head tables",
|
|
116
|
+
why: "the audit reads each record's latest journal entry (hecks_journal_<domain>_era_N), not the head " \
|
|
117
|
+
"tables, so an edit to a head table is not seen. The copy holds #{census.head_rows} head rows " \
|
|
118
|
+
"and #{census.journal_rows} journal records#{divergence}" }
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def divergence
|
|
122
|
+
census.head_rows == census.journal_rows ? "." : ": they differ, and only the journal's are audited."
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# The chapters loaded beside the domain's own (a vendored package): the host holds eras
|
|
126
|
+
# for the domain's own aggregates only, so theirs can neither mint nor be refused.
|
|
127
|
+
def attached_limits
|
|
128
|
+
names = @subject.held.registry.bluebooks.values.map(&:name) - [@subject.bluebook.name]
|
|
129
|
+
return [] if names.empty?
|
|
130
|
+
|
|
131
|
+
[{ what: "the shape of the attached chapters (#{names.sort.join(", ")})",
|
|
132
|
+
why: "their aggregates have no era row of their own, so a change to them is not a mint" }]
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def binding_note
|
|
136
|
+
{ what: "that a boot would mint", why: "the domain is bound to #{@subject.held.adapter_name}, which holds no eras " \
|
|
137
|
+
"in this environment" }
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require_relative "rows"
|
|
5
|
+
require_relative "census"
|
|
6
|
+
|
|
7
|
+
module Hecks
|
|
8
|
+
module Adapters
|
|
9
|
+
module Rehearsal
|
|
10
|
+
# The data copy as a JSON document the world names (`rows`): the eras held and the records
|
|
11
|
+
# stored under the latest, in that era's shape. Nothing is created or dropped, so specs and
|
|
12
|
+
# fixtures rehearse without a database.
|
|
13
|
+
#
|
|
14
|
+
# The document is `{"eras": [{"ordinal": 1, "held_text": "<bluebook text>"}], "records":
|
|
15
|
+
# {"Aggregate": {"id": {"field": ...}}}}`.
|
|
16
|
+
class MemoryStore
|
|
17
|
+
# @param declaration [Declaration] a `memory` declaration
|
|
18
|
+
def initialize(declaration)
|
|
19
|
+
path = declaration.rows
|
|
20
|
+
@document = path ? JSON.parse(File.read(path)) : {}
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# @return [void]
|
|
24
|
+
def prepare! = nil
|
|
25
|
+
|
|
26
|
+
# @return [void] there is nothing to drop
|
|
27
|
+
def drop! = nil
|
|
28
|
+
|
|
29
|
+
# @param _subject [Subject] unused: the document holds its own eras
|
|
30
|
+
# @return [Array<Hash>] the eras, `ordinal` and `held_text`
|
|
31
|
+
def eras(_subject)
|
|
32
|
+
Array(@document["eras"]).map { |era| { ordinal: era["ordinal"], held_text: era["held_text"] } }
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# What the document holds: it keeps the latest state of each record only, so the head and
|
|
36
|
+
# the journal counts are the same number.
|
|
37
|
+
#
|
|
38
|
+
# @param subject [Subject] the loaded domain
|
|
39
|
+
# @return [Census] the counts
|
|
40
|
+
def census(subject)
|
|
41
|
+
counts = subject.bluebook.aggregates.map do |aggregate|
|
|
42
|
+
held = records(aggregate.name).size
|
|
43
|
+
Census::Count.new(aggregate: aggregate.name, head: held, journal: held)
|
|
44
|
+
end
|
|
45
|
+
Census.new(schema: "(memory document)", source: "memory", present: true, eras: eras(subject).size,
|
|
46
|
+
journal_entries: counts.sum(&:journal), counts: counts)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Runs the mint's audit over the stored records, translated through the edge in process.
|
|
50
|
+
#
|
|
51
|
+
# @param subject [Subject] the loaded domain
|
|
52
|
+
# @param edge [Bluebook::Translation] the edge leading to the shape
|
|
53
|
+
# @return [Array(Array<String>, Hash)] the audit's refusals, and the translated records
|
|
54
|
+
# by aggregate name and id
|
|
55
|
+
def audit(subject, edge)
|
|
56
|
+
refusals = []
|
|
57
|
+
after = subject.bluebook.aggregates.to_h do |aggregate|
|
|
58
|
+
declared = edge.for_aggregate(aggregate.name)
|
|
59
|
+
before = records(declared&.was || aggregate.name)
|
|
60
|
+
translated = translate(aggregate, declared, before)
|
|
61
|
+
refusals.concat(Translation::Audit.check(aggregate: aggregate, declared: declared, before: before,
|
|
62
|
+
after: translated).violations)
|
|
63
|
+
[aggregate.name, translated]
|
|
64
|
+
end
|
|
65
|
+
[refusals, after]
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# @param edge [Bluebook::Translation] the edge
|
|
69
|
+
# @return [Array<Hash>] what memory cannot apply, as report limits
|
|
70
|
+
def limits(edge)
|
|
71
|
+
return [] unless edge && Translation::ApprovalFile.needs_rehearsal?(edge)
|
|
72
|
+
|
|
73
|
+
[{ what: "the edge's compute and rekey rules",
|
|
74
|
+
why: "they are SQL, which only a postgres copy runs; memory leaves those fields as stored" }]
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
private
|
|
78
|
+
|
|
79
|
+
def records(name) = @document.dig("records", name.to_s) || {}
|
|
80
|
+
|
|
81
|
+
def translate(aggregate, declared, before)
|
|
82
|
+
lineage = Ports::Persistence::Lineage.from_declared(declared, aggregate.name)
|
|
83
|
+
return before unless lineage
|
|
84
|
+
|
|
85
|
+
before.to_h { |id, state| [id, lineage.translate(entry(id, state)).state] }
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def entry(id, state)
|
|
89
|
+
Ports::Persistence::Entry.new(operation: "save", id: id, state: symbolise(state), mirrors: {})
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def symbolise(state) = JSON.parse(JSON.generate(state), symbolize_names: true)
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "census"
|
|
4
|
+
|
|
5
|
+
module Hecks
|
|
6
|
+
module Adapters
|
|
7
|
+
module Rehearsal
|
|
8
|
+
# Counts what a Postgres copy holds for the domain without creating anything: a plain
|
|
9
|
+
# connection, the schemas looked up before one is chosen, and counts over the relations that
|
|
10
|
+
# exist. A schema that is not in the copy is reported, never created.
|
|
11
|
+
class PostgresCensus
|
|
12
|
+
JOURNAL_LATEST = <<~SQL
|
|
13
|
+
SELECT aggregate, count(*) AS live FROM (
|
|
14
|
+
SELECT DISTINCT ON (aggregate, aggregate_id) aggregate, operation
|
|
15
|
+
FROM %<journal>s ORDER BY aggregate, aggregate_id, ordinal DESC
|
|
16
|
+
) latest WHERE operation = 'save' GROUP BY aggregate
|
|
17
|
+
SQL
|
|
18
|
+
|
|
19
|
+
# @param url [String] the copy's database URL
|
|
20
|
+
# @param subject [Subject] the loaded domain
|
|
21
|
+
# @param candidates [Array<Array(String, String)>] schema and source, in the order tried
|
|
22
|
+
def initialize(url, subject, candidates)
|
|
23
|
+
@url = url
|
|
24
|
+
@subject = subject
|
|
25
|
+
@candidates = candidates
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# @return [Census] the counts, in the first candidate schema that holds a lineage, else the
|
|
29
|
+
# first that exists, else the first named
|
|
30
|
+
def read
|
|
31
|
+
db = PG.connect(@url)
|
|
32
|
+
present = schemas(db)
|
|
33
|
+
lineage = holders(db)
|
|
34
|
+
name, source = choose(present, lineage)
|
|
35
|
+
return absent(name, source, lineage) unless present.include?(name)
|
|
36
|
+
|
|
37
|
+
db.exec("SET search_path TO #{db.quote_ident(name)}")
|
|
38
|
+
counted(db, name, source, lineage - [name])
|
|
39
|
+
ensure
|
|
40
|
+
db&.close
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
def schemas(db) = db.exec("SELECT schema_name FROM information_schema.schemata").map { |row| row["schema_name"] }
|
|
46
|
+
|
|
47
|
+
def holders(db)
|
|
48
|
+
sql = "SELECT table_schema FROM information_schema.tables WHERE table_name = 'hecks_eras'"
|
|
49
|
+
db.exec(sql).map { |row| row["table_schema"] }.sort
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def choose(present, lineage)
|
|
53
|
+
@candidates.find { |name, _| lineage.include?(name) } || @candidates.find { |name, _| present.include?(name) } ||
|
|
54
|
+
@candidates.first
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def absent(name, source, lineage)
|
|
58
|
+
Census.new(schema: name, source: source, present: false, eras: 0, journal_entries: 0,
|
|
59
|
+
counts: empty_counts, elsewhere: lineage)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def empty_counts = @subject.bluebook.aggregates.map { |a| Census::Count.new(aggregate: a.name, head: 0, journal: 0) }
|
|
63
|
+
|
|
64
|
+
def counted(db, name, source, elsewhere)
|
|
65
|
+
lineage = PostgresEra::Lineage.new(db, @subject.bluebook.name)
|
|
66
|
+
Census.new(schema: name, source: source, present: true, eras: eras(db), counts: counts(db, lineage),
|
|
67
|
+
journal_entries: rows(db, lineage.journal), elsewhere: elsewhere)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def counts(db, lineage)
|
|
71
|
+
live = latest(db, lineage)
|
|
72
|
+
@subject.bluebook.aggregates.map do |aggregate|
|
|
73
|
+
Census::Count.new(aggregate: aggregate.name, head: rows(db, lineage.head_view(aggregate.storage_name)),
|
|
74
|
+
journal: live.fetch(aggregate.storage_name, 0))
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def eras(db)
|
|
79
|
+
return 0 unless relation?(db, "hecks_eras")
|
|
80
|
+
|
|
81
|
+
db.exec_params("SELECT count(*) FROM hecks_eras WHERE domain = $1", [@subject.bluebook.name])[0]["count"].to_i
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def latest(db, lineage)
|
|
85
|
+
return {} unless relation?(db, lineage.journal)
|
|
86
|
+
|
|
87
|
+
db.exec(format(JOURNAL_LATEST, journal: db.quote_ident(lineage.journal))).to_h { |r| [r["aggregate"], r["live"].to_i] }
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def rows(db, name)
|
|
91
|
+
relation?(db, name) ? db.exec("SELECT count(*) FROM #{db.quote_ident(name)}")[0]["count"].to_i : 0
|
|
92
|
+
rescue PG::Error
|
|
93
|
+
0
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def relation?(db, name) = !db.exec_params("SELECT to_regclass($1) AS found", [db.quote_ident(name)])[0]["found"].nil?
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "postgres_census"
|
|
4
|
+
|
|
5
|
+
module Hecks
|
|
6
|
+
module Adapters
|
|
7
|
+
module Rehearsal
|
|
8
|
+
# What a `PostgresStore` reads from its database: the eras it holds and the mint's audit over
|
|
9
|
+
# the records under them. No table is provisioned and no era written; a copy somebody else
|
|
10
|
+
# made is read in a read-only session, and the database a run made is its own to touch.
|
|
11
|
+
#
|
|
12
|
+
# The schema is the world's `schema`, else `HECKS_SCHEMA`, else the one the domain's name
|
|
13
|
+
# gives (`Shop` is `shop`), else `public` when that holds the lineage. It is looked up
|
|
14
|
+
# before it is used, so a schema the copy does not have is reported and never created.
|
|
15
|
+
module PostgresReading
|
|
16
|
+
# @param subject [Subject] the loaded domain
|
|
17
|
+
# @return [Census] what the copy holds for the domain, counted once
|
|
18
|
+
def census(subject)
|
|
19
|
+
@census ||= PostgresCensus.new(url, subject, schema_candidates(subject)).read.tap do |found|
|
|
20
|
+
subject.held.read_schema(found.schema)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# @param subject [Subject] the loaded domain
|
|
25
|
+
# @return [Array<Hash>] the eras the database holds, none when it holds no lineage
|
|
26
|
+
def eras(subject)
|
|
27
|
+
return [] unless census(subject).present
|
|
28
|
+
|
|
29
|
+
reading(subject) { |lineage| subject.held.held_eras(lineage) }
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# @param subject [Subject] the loaded domain
|
|
33
|
+
# @param _edge [Bluebook::Translation] the edge leading to the shape
|
|
34
|
+
# @return [Array(Array<String>, Hash)] what the audit's layers refuse over the stored
|
|
35
|
+
# records, and the records as translated, by aggregate name and id
|
|
36
|
+
def audit(subject, _edge)
|
|
37
|
+
return [[], {}] unless census(subject).present
|
|
38
|
+
|
|
39
|
+
reading(subject) do |lineage|
|
|
40
|
+
finding = JournalStore::EdgeAudit.call(subject.held, lineage, subject.held.held_eras(lineage))
|
|
41
|
+
finding ? [finding.refusals, finding.after] : [[], {}]
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# @param _edge [Bluebook::Translation] unused
|
|
46
|
+
# @return [Array<Hash>] none: a postgres copy runs the compiled rules itself
|
|
47
|
+
def limits(_edge) = []
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
def schema_candidates(subject)
|
|
52
|
+
return [[@declaration.schema, "world"]] if @declaration.schema
|
|
53
|
+
return [[ENV.fetch("HECKS_SCHEMA"), "HECKS_SCHEMA"]] if ENV.fetch("HECKS_SCHEMA", "") != ""
|
|
54
|
+
|
|
55
|
+
[[Naming.snake(subject.bluebook.name), "derived"], %w[public default]]
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def reading(subject)
|
|
59
|
+
subject.held.read_from(url)
|
|
60
|
+
db = PostgresEra.connect_for(subject.bluebook.name, subject.held.settings)
|
|
61
|
+
db.exec("SET default_transaction_read_only = on") if @declaration.mode == "readonly_copy"
|
|
62
|
+
yield PostgresEra::Lineage.new(db, subject.bluebook.name)
|
|
63
|
+
ensure
|
|
64
|
+
db&.close
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "digest"
|
|
4
|
+
require "open3"
|
|
5
|
+
require "uri"
|
|
6
|
+
require_relative "declaration"
|
|
7
|
+
require_relative "postgres_reading"
|
|
8
|
+
|
|
9
|
+
module Hecks
|
|
10
|
+
module Adapters
|
|
11
|
+
module Rehearsal
|
|
12
|
+
# The data copy as a throwaway database on the server the world's `connection_env` names.
|
|
13
|
+
#
|
|
14
|
+
# `empty` and `dump` create a database of this run's own (`hecks_rehearsal_<token>`) and
|
|
15
|
+
# restore the dump into it; `readonly_copy` reads a database somebody else made, as it is,
|
|
16
|
+
# in a read-only session, and never creates or drops one. A database is isolated, not a
|
|
17
|
+
# schema, because a dump carries its own schema names. The name derives from the run key, so
|
|
18
|
+
# every ask of one rehearsal reaches the same database and a dropped one is dropped once.
|
|
19
|
+
class PostgresStore
|
|
20
|
+
include PostgresReading
|
|
21
|
+
|
|
22
|
+
PREFIX = "hecks_rehearsal_"
|
|
23
|
+
NAME = /\A#{PREFIX}[0-9a-f]{16}\z/
|
|
24
|
+
|
|
25
|
+
# @param declaration [Declaration] a `postgres` declaration
|
|
26
|
+
# @param run [String] the rehearsal's key
|
|
27
|
+
# @param protected_databases [Array<String>] databases the domain's own world names
|
|
28
|
+
def initialize(declaration, run, protected_databases: [])
|
|
29
|
+
@declaration = declaration
|
|
30
|
+
@run = run
|
|
31
|
+
@protected = protected_databases
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# @return [String] the database this rehearsal reads
|
|
35
|
+
def database_name
|
|
36
|
+
return URI.parse(server_url).path.delete_prefix("/") if @declaration.mode == "readonly_copy"
|
|
37
|
+
|
|
38
|
+
"#{PREFIX}#{Digest::SHA256.hexdigest(@run)[0, 16]}"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Creates the database and restores the dump, or checks the copy is reachable. A failure
|
|
42
|
+
# leaves nothing behind.
|
|
43
|
+
#
|
|
44
|
+
# @return [void]
|
|
45
|
+
# @raise [Refused] if the server refuses, or the restore does
|
|
46
|
+
def prepare!
|
|
47
|
+
return reach_copy! if @declaration.mode == "readonly_copy"
|
|
48
|
+
|
|
49
|
+
discard!
|
|
50
|
+
admin { |db| db.exec("CREATE DATABASE #{db.quote_ident(database_name)}") }
|
|
51
|
+
restore! if @declaration.mode == "dump"
|
|
52
|
+
rescue Refused, PG::Error => e
|
|
53
|
+
discard!
|
|
54
|
+
raise e.is_a?(Refused) ? e : Refused.new(e.message.strip)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Drops the throwaway database. A copy somebody else made is never dropped, and one the
|
|
58
|
+
# world asked to keep is refused here, so the run records why it stands.
|
|
59
|
+
#
|
|
60
|
+
# @return [void]
|
|
61
|
+
# @raise [Refused] if the world's `drop_after` is false and a database stands
|
|
62
|
+
def drop!
|
|
63
|
+
return if @declaration.mode == "readonly_copy" || !NAME.match?(database_name)
|
|
64
|
+
raise Refused, "kept #{database_name}: the world's drop_after is false" unless @declaration.drop_after
|
|
65
|
+
|
|
66
|
+
discard!
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# @return [String] a URL for the database this rehearsal reads
|
|
70
|
+
def url
|
|
71
|
+
uri = URI.parse(server_url)
|
|
72
|
+
uri.path = "/#{database_name}"
|
|
73
|
+
uri.to_s
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
private
|
|
77
|
+
|
|
78
|
+
def discard!
|
|
79
|
+
return unless NAME.match?(database_name)
|
|
80
|
+
|
|
81
|
+
admin { |db| db.exec("DROP DATABASE IF EXISTS #{db.quote_ident(database_name)} WITH (FORCE)") }
|
|
82
|
+
rescue Refused, PG::Error
|
|
83
|
+
nil
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def server_url
|
|
87
|
+
value = @declaration.connection
|
|
88
|
+
return value if value.match?(%r{\Apostgres(ql)?://})
|
|
89
|
+
|
|
90
|
+
raise Refused, "#{@declaration.connection_env} must hold a postgres:// URL"
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def admin
|
|
94
|
+
require "pg"
|
|
95
|
+
db = PG.connect(server_url)
|
|
96
|
+
yield db
|
|
97
|
+
ensure
|
|
98
|
+
db&.close
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def reach_copy!
|
|
102
|
+
raise Refused, "readonly_copy names a database the domain's own world persists to" if @protected.include?(url)
|
|
103
|
+
|
|
104
|
+
admin { |db| db.exec("SELECT 1") }
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# argv arrays and PG* variables: no shell, and no password on a command line.
|
|
108
|
+
def restore!
|
|
109
|
+
file = @declaration.dump
|
|
110
|
+
raise Refused, "no such dump #{file.inspect}" unless File.file?(file)
|
|
111
|
+
|
|
112
|
+
_out, err, status = Open3.capture3(restore_env, *restore_argv(file))
|
|
113
|
+
raise Refused, "restoring the dump failed: #{err.strip.lines.first}" unless status.success?
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def restore_argv(file)
|
|
117
|
+
return ["psql", "--no-psqlrc", "-v", "ON_ERROR_STOP=1", "-d", database_name, "-f", file] if file.end_with?(".sql")
|
|
118
|
+
|
|
119
|
+
["pg_restore", "--no-owner", "--no-privileges", "-d", database_name, file]
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# What the URL names, and nothing it leaves out: `postgres:///postgres` parses with an empty
|
|
123
|
+
# host, which would override a `PGHOST` the environment sets and send the restore to the
|
|
124
|
+
# local socket while the rest of the rehearsal reached the server by TCP.
|
|
125
|
+
def restore_env
|
|
126
|
+
uri = URI.parse(server_url)
|
|
127
|
+
{ "PGHOST" => uri.host, "PGPORT" => uri.port&.to_s, "PGUSER" => uri.user, "PGPASSWORD" => uri.password }
|
|
128
|
+
.reject { |_name, value| value.to_s.empty? }
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
module Adapters
|
|
5
|
+
module Rehearsal
|
|
6
|
+
# The mint audit's refusals as report rows: `Aggregate#id`, the rule that refused, and the
|
|
7
|
+
# audit's own wording, capped, with the total of them all.
|
|
8
|
+
module Rows
|
|
9
|
+
# How many rows a report holds; the total counts the rest.
|
|
10
|
+
CAP = 50
|
|
11
|
+
|
|
12
|
+
ROW = /\A([^#:]+)#([^:]+): (.*)\z/m
|
|
13
|
+
|
|
14
|
+
module_function
|
|
15
|
+
|
|
16
|
+
# @param violations [Array<String>] the audit's refusals, `Aggregate#id: wording`
|
|
17
|
+
# @return [Array(Array<Hash>, Integer)] the first `CAP` rows, and how many there were
|
|
18
|
+
def from(violations)
|
|
19
|
+
[violations.first(CAP).map { |text| row(text) }, violations.size]
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# @param text [String] one refusal
|
|
23
|
+
# @return [Hash] `aggregate`, `id`, `rule`, `wording`
|
|
24
|
+
def row(text)
|
|
25
|
+
match = ROW.match(text)
|
|
26
|
+
aggregate, id, wording = match ? match.captures : ["(edge)", "-", text]
|
|
27
|
+
{ aggregate: aggregate, id: id, rule: rule(wording), wording: wording }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# @param wording [String] a refusal's words
|
|
31
|
+
# @return [String] `lifecycle`, `reference` or `value`
|
|
32
|
+
def rule(wording)
|
|
33
|
+
return "lifecycle" if wording.include?("lifecycle never reaches")
|
|
34
|
+
return "reference" if wording.include?("reference transform") || wording.include?("id set changed")
|
|
35
|
+
|
|
36
|
+
"value"
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|