hecks 3.9.1 → 3.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/examples/pizzas/bluebook/pizzas.bluebook +29 -0
- data/examples/pizzas/bluebook/pizzas.hecksagon +7 -0
- data/exe/hecks +4 -4
- data/lib/hecks/adapters/driven/d1.rb +2 -10
- data/lib/hecks/adapters/driven/heki.adapter +1 -1
- data/lib/hecks/adapters/driven/heki.rb +2 -12
- data/lib/hecks/adapters/driven/lambda.adapter +1 -1
- data/lib/hecks/adapters/driven/lambda.rb +3 -18
- data/lib/hecks/adapters/driven/postgres.rb +4 -11
- data/lib/hecks/adapters/driven/sqlite.adapter +1 -1
- data/lib/hecks/adapters/driven/sqlite.rb +2 -10
- data/lib/hecks/adapters/driving/admission.rb +46 -0
- data/lib/hecks/adapters/driving/cli/nesting.rb +36 -0
- data/lib/hecks/adapters/driving/cli/shorthand.rb +88 -0
- data/lib/hecks/adapters/driving/cli.rb +127 -0
- data/lib/hecks/adapters/driving/cli_runner/answers.rb +160 -0
- data/lib/hecks/adapters/driving/cli_runner/resolution.rb +155 -0
- data/lib/hecks/adapters/driving/cli_runner/settling.rb +120 -0
- data/lib/hecks/adapters/driving/cli_runner/streaming.rb +93 -0
- data/lib/hecks/adapters/driving/cli_runner/suggestions.rb +47 -0
- data/lib/hecks/adapters/driving/cli_runner.rb +69 -0
- data/lib/hecks/adapters/driving/command_request.rb +121 -0
- data/lib/hecks/adapters/driving/handle.rb +214 -0
- data/lib/hecks/adapters/driving/json.rb +136 -0
- data/lib/hecks/adapters/driving/launcher_options.rb +267 -0
- data/lib/hecks/adapters/driving/mcp/properties.rb +49 -0
- data/lib/hecks/adapters/driving/mcp/tool_calls.rb +116 -0
- data/lib/hecks/adapters/driving/mcp/tool_definitions/auditing.rb +53 -0
- data/lib/hecks/adapters/driving/mcp/tool_definitions/calling.rb +71 -0
- data/lib/hecks/adapters/driving/mcp/tool_definitions/reading.rb +99 -0
- data/lib/hecks/adapters/driving/mcp/tool_definitions.rb +18 -0
- data/lib/hecks/adapters/driving/mcp.rb +159 -0
- data/lib/hecks/adapters/driving/mcp_guide_cache.rb +78 -0
- data/lib/hecks/adapters/driving/mcp_scope/argument_policy.rb +97 -0
- data/lib/hecks/adapters/driving/mcp_scope/env_problems.rb +69 -0
- data/lib/hecks/adapters/driving/mcp_scope/presenter.rb +83 -0
- data/lib/hecks/adapters/driving/mcp_scope.rb +213 -0
- data/lib/hecks/adapters/driving/ruby/aggregate_module.rb +171 -0
- data/lib/hecks/adapters/driving/ruby/chapter.rb +91 -0
- data/lib/hecks/adapters/driving/ruby.rb +83 -0
- data/lib/hecks/adapters/driving/usage_cache.rb +144 -0
- data/lib/hecks/{doors.rb → adapters/driving.rb} +10 -6
- data/lib/hecks/behaviors/expectations/boot.rb +1 -1
- data/lib/hecks/bench/ruby_runner.rb +1 -1
- data/lib/hecks/bluebook/ask_resolution.rb +130 -0
- data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +8 -2
- data/lib/hecks/bluebook/assembly/contracts/rules.rb +4 -2
- data/lib/hecks/bluebook/assembly/contracts/structure.rb +3 -1
- data/lib/hecks/bluebook/assembly/marks.rb +1 -0
- data/lib/hecks/bluebook/attribute.rb +1 -1
- data/lib/hecks/bluebook/behaviour/command.rb +13 -8
- data/lib/hecks/bluebook/behaviour/policy.rb +30 -0
- data/lib/hecks/bluebook/capabilities/attribute_values.rb +72 -0
- data/lib/hecks/bluebook/capabilities.rb +91 -9
- data/lib/hecks/bluebook/domain_port.rb +16 -4
- data/lib/hecks/bluebook/dsl/adapter_builder.rb +18 -6
- data/lib/hecks/bluebook/dsl/binding_proxy.rb +13 -0
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/computed_sources.rb +98 -0
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/fan_out_rows.rb +42 -0
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/projected_fields.rb +14 -2
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/provisions.rb +69 -3
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/with_projections.rb +17 -10
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +5 -0
- data/lib/hecks/bluebook/dsl/bootstrap_table.rb +2 -0
- data/lib/hecks/bluebook/dsl/command_builder/mutations.rb +8 -1
- data/lib/hecks/bluebook/dsl/command_builder/operands.rb +121 -0
- data/lib/hecks/bluebook/dsl/command_builder.rb +2 -0
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +10 -1
- data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +45 -2
- data/lib/hecks/bluebook/dsl/policy_builder.rb +47 -10
- data/lib/hecks/bluebook/dsl/rule_reference.rb +7 -4
- data/lib/hecks/bluebook/dsl/world_builder.rb +1 -1
- data/lib/hecks/bluebook/expression/ast_json/emitters.rb +6 -0
- data/lib/hecks/bluebook/expression/ast_json.rb +9 -6
- data/lib/hecks/bluebook/expression/ast_reader.rb +14 -9
- data/lib/hecks/bluebook/expression/projection.json +36 -0
- data/lib/hecks/bluebook/expression/resolver/operations.rb +31 -0
- data/lib/hecks/bluebook/expression/resolver/parsing.rb +38 -5
- data/lib/hecks/bluebook/expression/resolver/scanning.rb +22 -5
- data/lib/hecks/bluebook/expression/resolver/values.rb +10 -0
- data/lib/hecks/bluebook/expression/resolver.rb +20 -1
- data/lib/hecks/bluebook/hecksagon.rb +23 -3
- data/lib/hecks/bluebook/lifecycle.rb +22 -3
- data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +13 -1
- data/lib/hecks/bluebook/meta_validator/modes.rb +4 -1
- data/lib/hecks/bluebook/meta_validator/readings.rb +18 -1
- data/lib/hecks/bluebook/meta_validator/reconstruction/heads.rb +2 -1
- data/lib/hecks/bluebook/meta_validator/shapes.rb +5 -0
- data/lib/hecks/bluebook/meta_validator/verdict_cache.rb +1 -1
- data/lib/hecks/bluebook/meta_validator.rb +12 -12
- data/lib/hecks/bluebook/model_check/ask_checks.rb +71 -0
- data/lib/hecks/bluebook/model_check/policy_checks.rb +2 -1
- data/lib/hecks/bluebook/model_check/reference_checks.rb +70 -0
- data/lib/hecks/bluebook/model_check.rb +8 -2
- data/lib/hecks/bluebook/policy.rb +4 -2
- data/lib/hecks/bluebook/smoke_test.rb +1 -1
- data/lib/hecks/bluebook/synthesizer.rb +2 -0
- data/lib/hecks/bluebook.rb +1 -0
- data/lib/hecks/cli/command_table.rb +1 -1
- data/lib/hecks/cli/console.rb +2 -1
- data/lib/hecks/cli/document.rb +1 -1
- data/lib/hecks/cli/launcher_words.rb +4 -4
- data/lib/hecks/cli/mcp.rb +4 -4
- data/lib/hecks/cli/project_cli/launcher_source.rb +2 -2
- data/lib/hecks/cli/project_cli/templates.rb +5 -5
- data/lib/hecks/cli/project_cli.rb +9 -6
- data/lib/hecks/cli/run.rb +2 -2
- data/lib/hecks/cli/smoke_http/checks.rb +4 -1
- data/lib/hecks/cli/smoke_http/transport.rb +3 -3
- data/lib/hecks/cli/smoke_http.rb +3 -0
- data/lib/hecks/codemod.rb +5 -2
- data/lib/hecks/corpus/routes.rb +7 -0
- data/lib/hecks/corpus/rust.rb +3 -2
- data/lib/hecks/deploy/bluebook/deploy.bluebook +124 -2
- data/lib/hecks/deploy/bluebook/deploy.hecksagon +8 -0
- data/lib/hecks/deploy/oidc.json +16 -3
- data/lib/hecks/doc/reference/rendering.rb +6 -3
- data/lib/hecks/forms/app/commands.rb +8 -7
- data/lib/hecks/forms/app/queries.rb +2 -1
- data/lib/hecks/forms/app.rb +1 -1
- data/lib/hecks/forms/record_renderer.rb +1 -1
- data/lib/hecks/framework/bluebook/privacy.bluebook +11 -0
- data/lib/hecks/fuzzing/concurrent_dispatch.rb +4 -1
- data/lib/hecks/fuzzing/failure_triage.rb +2 -1
- data/lib/hecks/fuzzing/mutation/run.rb +11 -4
- data/lib/hecks/fuzzing/properties/catalog.rb +6 -4
- data/lib/hecks/fuzzing/properties/guaranteed_by_construction.rb +18 -11
- data/lib/hecks/fuzzing/properties/guards.rb +1 -1
- data/lib/hecks/fuzzing/self_consistency/saga_redelivery.rb +1 -1
- data/lib/hecks/fuzzing/self_consistency.rb +6 -2
- data/lib/hecks/fuzzing/shrinker.rb +3 -1
- data/lib/hecks/fuzzing/value_generator.rb +19 -5
- data/lib/hecks/gate/stages.yml +4 -10
- data/lib/hecks/grammar/evolve.rb +1 -1
- data/lib/hecks/grammar/expression_operators.json +372 -0
- data/lib/hecks/grammar/operators.rb +3 -1
- data/lib/hecks/hecks/adapters/codebase/corpus_tasks.rb +10 -10
- data/lib/hecks/hecks/adapters/codebase/inspection/rubycritic.rb +35 -0
- data/lib/hecks/hecks/adapters/codebase/inspection.rb +104 -0
- data/lib/hecks/hecks/adapters/codebase/publishing.rb +2 -1
- data/lib/hecks/hecks/adapters/codebase/release_facts.rb +12 -2
- data/lib/hecks/hecks/adapters/codebase/source_tree.rb +2 -1
- data/lib/hecks/hecks/adapters/codebase/test_runner.rb +1 -1
- data/lib/hecks/hecks/adapters/cost_explorer.rb +8 -5
- data/lib/hecks/hecks/adapters/deploy_toolchain/clearing.rb +51 -0
- data/lib/hecks/hecks/adapters/deploy_toolchain/statuses.rb +2 -1
- data/lib/hecks/hecks/adapters/deploy_toolchain.rb +2 -0
- data/lib/hecks/hecks/adapters/finding_github.rb +13 -6
- data/lib/hecks/hecks/adapters/first_admin.rb +2 -2
- data/lib/hecks/hecks/adapters/github_rulesets.rb +6 -3
- data/lib/hecks/hecks/adapters/in_process_boot.rb +1 -1
- data/lib/hecks/hecks/adapters/journal_store/compaction.rb +1 -1
- data/lib/hecks/hecks/adapters/journal_store/edge_audit.rb +10 -20
- data/lib/hecks/hecks/adapters/journal_store/held_domain.rb +28 -5
- data/lib/hecks/hecks/adapters/local_files.rb +2 -2
- data/lib/hecks/hecks/adapters/rehearsal/audit_report.rb +107 -0
- data/lib/hecks/hecks/adapters/rehearsal/census.rb +71 -0
- data/lib/hecks/hecks/adapters/rehearsal/declaration.rb +133 -0
- data/lib/hecks/hecks/adapters/rehearsal/environment.rb +83 -0
- data/lib/hecks/hecks/adapters/rehearsal/inquiry.rb +142 -0
- data/lib/hecks/hecks/adapters/rehearsal/memory_store.rb +96 -0
- data/lib/hecks/hecks/adapters/rehearsal/postgres_census.rb +100 -0
- data/lib/hecks/hecks/adapters/rehearsal/postgres_reading.rb +69 -0
- data/lib/hecks/hecks/adapters/rehearsal/postgres_store.rb +129 -0
- data/lib/hecks/hecks/adapters/rehearsal/rows.rb +41 -0
- data/lib/hecks/hecks/adapters/rehearsal/rust_audit.rb +83 -0
- data/lib/hecks/hecks/adapters/rehearsal/subject.rb +104 -0
- data/lib/hecks/hecks/adapters/rehearsal.rb +54 -0
- data/lib/hecks/hecks/adapters/rehearsal_store.adapter +7 -0
- data/lib/hecks/hecks/adapters/rehearsal_store.rb +81 -0
- data/lib/hecks/hecks/adapters/site_toolchain/role_prober.rb +78 -0
- data/lib/hecks/hecks/adapters/site_toolchain.rb +29 -4
- data/lib/hecks/hecks/adapters/terminal.rb +13 -11
- data/lib/hecks/hecks/adapters/world_clearing.rb +65 -0
- data/lib/hecks/hecks/codebase.behaviors +13 -1
- data/lib/hecks/hecks/codebase.bluebook +239 -5
- data/lib/hecks/hecks/custodian.behaviors +24 -6
- data/lib/hecks/hecks/custodian.bluebook +6 -1227
- data/lib/hecks/hecks/deploy.behaviors +1 -1
- data/lib/hecks/hecks/deploy.world +1 -3
- data/lib/hecks/hecks/hecks.behaviors +1 -1
- data/lib/hecks/hecks/hecks.hecksagon +61 -18
- data/lib/hecks/hecks/hecks.world +11 -10
- data/lib/hecks/hecks/rehearsal.bluebook +477 -0
- data/lib/hecks/hecks/site.behaviors +1 -1
- data/lib/hecks/hecks/site.world +0 -1
- data/lib/hecks/hecks/tooling.bluebook +1238 -0
- data/lib/hecks/indifferent_key.rb +20 -0
- data/lib/hecks/language/adapter.bluebook +13 -3
- data/lib/hecks/language/bluebook/aggregate.bluebook +23 -0
- data/lib/hecks/language/bluebook/bluebook.bluebook +15 -0
- data/lib/hecks/language/bluebook/entity.bluebook +18 -0
- data/lib/hecks/language/bluebook/policy.bluebook +9 -1
- data/lib/hecks/language/bluebook/query.bluebook +1 -1
- data/lib/hecks/language/bluebook/vocabulary.bluebook +11 -4
- data/lib/hecks/language/hecksagon/domain_port.bluebook +1 -1
- data/lib/hecks/language/hecksagon/hecksagon.bluebook +22 -0
- data/lib/hecks/language/oidc.json +10 -0
- data/lib/hecks/language/port.bluebook +4 -4
- data/lib/hecks/literal.rb +12 -3
- data/lib/hecks/ports/key_vault.rb +7 -2
- data/lib/hecks/ports/persistence/plugins/era/era_check.rb +21 -10
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/boot.rb +4 -2
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +2 -1
- data/lib/hecks/ports/persistence/repository_factory.rb +6 -3
- data/lib/hecks/projections/deploy/box/templates/deploy-box.sh.tmpl +29 -0
- data/lib/hecks/projections/deploy/box/templates/deploy-service.sh.tmpl +1 -1
- data/lib/hecks/projections/deploy/fargate/settings/tuning.rb +15 -0
- data/lib/hecks/projections/deploy/fargate/settings.rb +3 -1
- data/lib/hecks/projections/deploy/fargate/stack/readers.rb +1 -0
- data/lib/hecks/projections/deploy/preview/preview.sh.erb +1 -1
- data/lib/hecks/projections/deploy/shared.rb +5 -2
- data/lib/hecks/projections/deploy/templates/fargate/Makefile.tmpl +7 -4
- data/lib/hecks/projections/model/deviations.rb +2 -2
- data/lib/hecks/projections/model.rb +2 -2
- data/lib/hecks/projections/oidc.rb +13 -1
- data/lib/hecks/projections/shape.rb +5 -2
- data/lib/hecks/projections/site/cms_editor/attributes.rb +95 -0
- data/lib/hecks/projections/site/cms_editor/blocks.rb +157 -0
- data/lib/hecks/projections/site/cms_editor/checks.rb +77 -0
- data/lib/hecks/projections/site/cms_editor/clearing.rb +70 -0
- data/lib/hecks/projections/site/cms_editor/color.rb +124 -0
- data/lib/hecks/projections/site/cms_editor/composition.rb +42 -0
- data/lib/hecks/projections/site/cms_editor/destructive.rb +61 -0
- data/lib/hecks/projections/site/cms_editor/drafts.rb +102 -0
- data/lib/hecks/projections/site/cms_editor/hints.rb +74 -0
- data/lib/hecks/projections/site/cms_editor/icons.rb +86 -0
- data/lib/hecks/projections/site/cms_editor/key_kinds.rb +31 -0
- data/lib/hecks/projections/site/cms_editor/limits.rb +72 -0
- data/lib/hecks/projections/site/cms_editor/media.rb +85 -0
- data/lib/hecks/projections/site/cms_editor/moment.rb +34 -0
- data/lib/hecks/projections/site/cms_editor/palette.rb +115 -0
- data/lib/hecks/projections/site/cms_editor/pickers.rb +85 -0
- data/lib/hecks/projections/site/cms_editor/pretty.rb +40 -0
- data/lib/hecks/projections/site/cms_editor/preview.rb +91 -0
- data/lib/hecks/projections/site/cms_editor/relations.rb +92 -0
- data/lib/hecks/projections/site/cms_editor/scale_checks.rb +66 -0
- data/lib/hecks/projections/site/cms_editor/scheduling.rb +150 -0
- data/lib/hecks/projections/site/cms_editor/schema.rb +161 -0
- data/lib/hecks/projections/site/cms_editor/setting.rb +130 -0
- data/lib/hecks/projections/site/cms_editor/targets.rb +86 -0
- data/lib/hecks/projections/site/cms_editor/templates/package.json.tmpl +36 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/app.ts.tmpl +370 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/assets.ts.tmpl +98 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/auth/membership.ts.tmpl +87 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/auth/session.ts.tmpl +36 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/auth/sso.ts.tmpl +20 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/app.css.tmpl +412 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/autosave.js.tmpl +144 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/blocks.js.tmpl +500 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/body_doc.js.tmpl +175 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/body_editor.js.tmpl +418 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/epoch.js.tmpl +61 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/forms.js.tmpl +210 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/lists.js.tmpl +40 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/main.js.tmpl +26 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/media_picker.js.tmpl +232 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/moments.js.tmpl +73 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/pickers.js.tmpl +152 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/preview.js.tmpl +98 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/shell.js.tmpl +140 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/toast.js.tmpl +66 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/choices.ts.tmpl +99 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/commands.ts.tmpl +133 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/config.ts.tmpl +29 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/flash.ts.tmpl +52 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/host.ts.tmpl +43 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/media/handler.ts.tmpl +177 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/media/multipart.ts.tmpl +53 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/media/sniff.ts.tmpl +94 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/media/storage.ts.tmpl +50 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/names.ts.tmpl +36 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/schema.ts.tmpl +214 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/server.ts.tmpl +65 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/availability.ts.tmpl +15 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/block_list.js.tmpl +213 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/blocks.ts.tmpl +275 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/body_model.js.tmpl +123 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/body_parse.js.tmpl +320 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/detail.ts.tmpl +88 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/dialogs.ts.tmpl +43 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/fields.ts.tmpl +275 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/html.ts.tmpl +61 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/icons.ts.tmpl +15 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/input.ts.tmpl +92 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/links.ts.tmpl +100 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/moments.ts.tmpl +44 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/outline.ts.tmpl +63 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/pages.ts.tmpl +213 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/paging.ts.tmpl +82 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/pickers.ts.tmpl +110 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/preview.ts.tmpl +87 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/related.ts.tmpl +97 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/schedule.ts.tmpl +160 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/shell.ts.tmpl +211 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/table.ts.tmpl +151 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/theme.js.tmpl +16 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/values.ts.tmpl +69 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/words.ts.tmpl +97 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/world.ts.tmpl +35 -0
- data/lib/hecks/projections/site/cms_editor/templates/tsconfig.json.tmpl +17 -0
- data/lib/hecks/projections/site/cms_editor/theme.rb +29 -0
- data/lib/hecks/projections/site/cms_editor/theme_checks.rb +57 -0
- data/lib/hecks/projections/site/cms_editor.rb +133 -0
- data/lib/hecks/projections/site/role_probe.rb +74 -0
- data/lib/hecks/projector/cli_audience.rb +2 -1
- data/lib/hecks/projector/cli_projector/listing.rb +1 -1
- data/lib/hecks/projector/cli_projector/option_specs.rb +1 -1
- data/lib/hecks/projector/docs_projector/closing.rb +1 -1
- data/lib/hecks/projector/docs_projector.rb +1 -1
- data/lib/hecks/projector/exporter/bindings.rb +40 -10
- data/lib/hecks/projector/exporter/capability_values.rb +55 -0
- data/lib/hecks/projector/narrate_projector/reactions.rb +1 -1
- data/lib/hecks/projector/target.rb +11 -0
- data/lib/hecks/projector.rb +32 -5
- data/lib/hecks/quality_control/adapters/github_checks.rb +12 -8
- data/lib/hecks/quality_control/adapters/github_issues.rb +8 -4
- data/lib/hecks/quality_control/cli/qa_open_pr.rb +2 -2
- data/lib/hecks/quality_control/cli/qa_sweep/checks/shrinking.rb +1 -1
- data/lib/hecks/quality_control/cli/qa_sweep/finding_report.rb +1 -1
- data/lib/hecks/quality_control/cli/qa_sweep.rb +1 -1
- data/lib/hecks/quality_control/quality_control.bluebook +6 -2
- data/lib/hecks/release/approval.rb +64 -0
- data/lib/hecks/router/{door.rb → aggregate_reader.rb} +5 -4
- data/lib/hecks/router/namespace_installer.rb +16 -16
- data/lib/hecks/router.rb +1 -1
- data/lib/hecks/runtime/best_effort.rb +22 -0
- data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +12 -2
- data/lib/hecks/runtime/command_interpreter.rb +3 -2
- data/lib/hecks/runtime/command_rules/emission.rb +1 -1
- data/lib/hecks/runtime/dispatcher.rb +4 -4
- data/lib/hecks/runtime/entity_element/location.rb +1 -1
- data/lib/hecks/runtime/event.rb +5 -0
- data/lib/hecks/runtime/identity.rb +7 -6
- data/lib/hecks/runtime/interpreting.rb +2 -1
- data/lib/hecks/runtime/invocation_builder.rb +1 -1
- data/lib/hecks/runtime/loader/boot_steps.rb +8 -5
- data/lib/hecks/runtime/loader.rb +10 -10
- data/lib/hecks/runtime/policy_interpreter/arguments.rb +7 -4
- data/lib/hecks/runtime/policy_interpreter/fan_out.rb +3 -3
- data/lib/hecks/runtime/policy_interpreter.rb +6 -6
- data/lib/hecks/runtime/port_operation_interpreter.rb +1 -1
- data/lib/hecks/runtime/reaction_invocation.rb +1 -1
- data/lib/hecks/runtime/rebuild_sweep.rb +11 -1
- data/lib/hecks/runtime/registry/ask_verification.rb +35 -0
- data/lib/hecks/runtime/registry/hecksagon_rules.rb +10 -0
- data/lib/hecks/runtime/registry/merging.rb +1 -1
- data/lib/hecks/runtime/registry/providers.rb +16 -0
- data/lib/hecks/runtime/registry/read_repository.rb +6 -4
- data/lib/hecks/runtime/registry/verification.rb +4 -1
- data/lib/hecks/runtime/registry/world_defaults.rb +29 -0
- data/lib/hecks/runtime/registry.rb +1 -1
- data/lib/hecks/runtime/saga_interpreter/compensation.rb +1 -1
- data/lib/hecks/runtime/saga_interpreter/delivery.rb +4 -4
- data/lib/hecks/runtime/saga_interpreter/transitions.rb +1 -1
- data/lib/hecks/runtime/saga_interpreter.rb +3 -3
- data/lib/hecks/runtime/tenant_check.rb +5 -4
- data/lib/hecks/runtime/value/coercion.rb +2 -2
- data/lib/hecks/runtime/value/validation.rb +16 -4
- data/lib/hecks/runtime.rb +9 -9
- data/lib/hecks/rust_build/project_rust/target_ir.rb +1 -1
- data/lib/hecks/rust_build/wasm.rb +1 -1
- data/lib/hecks/rust_build/wasm_browser.rb +1 -1
- data/lib/hecks/rust_build.rb +11 -0
- data/lib/hecks/served_options.rb +53 -0
- data/lib/hecks/site/bluebook/site.bluebook +29 -1
- data/lib/hecks/site/bluebook/site.ports.hecksagon +6 -0
- data/lib/hecks/storehouse/audit.rb +1 -1
- data/lib/hecks/storehouse/dispatching.rb +8 -6
- data/lib/hecks/storehouse/introspection.rb +2 -2
- data/lib/hecks/storehouse/querying.rb +6 -4
- data/lib/hecks/storehouse.rb +4 -4
- data/lib/hecks/tools/comment_style/source_file.rb +4 -1
- data/lib/hecks/tools/deploy_recipe_lint/fixtures.rb +4 -1
- data/lib/hecks/tools/lanes/rows.rb +6 -0
- data/lib/hecks/tools/oidc_manifests.rb +4 -5
- data/lib/hecks/tools/regeneration_run.rb +11 -4
- data/lib/hecks/tools/site_routes/cli.rb +1 -1
- data/lib/hecks/tools/site_routes/editor_files.rb +45 -0
- data/lib/hecks/tools/site_routes.rb +14 -7
- data/lib/hecks/tools/tenant_provisioning.rb +2 -2
- data/lib/hecks/tools/tools_doc.rb +4 -4
- data/lib/hecks/version.rb +1 -1
- data/lib/hecks/vocabulary.rb +4 -4
- data/lib/hecks.rb +15 -15
- data/lib/rubocop/cop/hecks/env_dump.rb +69 -0
- data/lib/rubocop/cop/hecks/monotonic_duration.rb +53 -0
- data/lib/rubocop/cop/hecks/no_wall_clock_in_runtime.rb +34 -0
- data/lib/rubocop/cop/hecks/raw_shell_interpolation.rb +79 -0
- data/lib/rubocop/cop/hecks/sleep_in_spec.rb +34 -0
- data/lib/rubocop/cop/hecks/swallowed_rescue.rb +37 -0
- data/lib/rubocop/cop/hecks/symbol_string_key_mix.rb +50 -0
- data/lib/rubocop/cop/hecks/untyped_raise.rb +37 -0
- data/prebuilt/05c358d38d661a3774f813d49c66f5b68939af94432d66257a56f49ee2598994.marshal +0 -0
- data/prebuilt/26c40c61f09b09b11651c64668eda3ceb180f83cf2a3802d3033d2fd99ac841b.marshal +0 -0
- data/prebuilt/verdicts-230ac18c25abfd3d92ede032bee2661a4c2cd2773ab0cf0e2ae52546ba2f7af9.json +1 -0
- data/rust/.gitignore +6 -0
- data/rust/Cargo.toml +9 -1
- data/rust/build/src/binding_pass.rs +103 -0
- data/rust/build/src/cargo_sync.rs +6 -0
- data/rust/build/src/lineage_pass.rs +3 -3
- data/rust/build/src/main.rs +17 -5
- data/rust/build/src/pipeline.rs +48 -5
- data/rust/build/src/seams_pass.rs +123 -0
- data/rust/build/src/translation_pass.rs +167 -0
- data/rust/codegen/src/asks.rs +103 -0
- data/rust/codegen/src/bridging.rs +1 -1
- data/rust/codegen/src/commands.rs +13 -8
- data/rust/codegen/src/constraints.rs +25 -0
- data/rust/codegen/src/domain_generator.rs +11 -9
- data/rust/codegen/src/expr_emitter.rs +13 -0
- data/rust/codegen/src/json_codec.rs +54 -15
- data/rust/codegen/src/main.rs +2 -1
- data/rust/codegen/src/mutations.rs +174 -4
- data/rust/codegen/src/naming.rs +30 -0
- data/rust/codegen/src/queries.rs +11 -2
- data/rust/codegen/src/reactions.rs +1 -1
- data/rust/codegen/src/types.rs +166 -8
- data/rust/host/HECKS_RELEASE +1 -1
- data/rust/host/src/api.rs +4 -4
- data/rust/host/src/bin/validate_harness.rs +43 -0
- data/rust/host/src/boundary_fuzz/expr_json.rs +2 -2
- data/rust/host/src/boundary_fuzz/ir.rs +1 -0
- data/rust/host/src/boundary_fuzz/server.rs +1 -1
- data/rust/host/src/checkout.rs +35 -19
- data/rust/host/src/commerce_ir.rs +408 -2
- data/rust/host/src/expr_json.rs +118 -0
- data/rust/host/src/mint.rs +1 -1
- data/rust/host/src/payments/tests.rs +27 -1
- data/rust/host/src/payments.rs +12 -4
- data/rust/host/src/query_step.rs +22 -4
- data/rust/host/src/reference_validate.rs +653 -108
- data/rust/host/src/resend.rs +2 -0
- data/rust/host/src/server.rs +19 -6
- data/rust/host/src/ui_schema.rs +132 -5
- data/rust/host/src/web/newsletter.rs +7 -8
- data/rust/host/src/web/newsletter_send.rs +38 -13
- data/rust/host/src/web/registrations.rs +229 -26
- data/rust/host/src/web.rs +3 -3
- data/rust/parser/src/emit.rs +38 -6
- data/rust/parser/src/expr/ast_json.rs +7 -1
- data/rust/parser/src/expr/resolver.rs +171 -6
- data/rust/parser/src/ir.rs +7 -0
- data/rust/parser/src/keywords.rs +24 -0
- data/rust/parser/src/main.rs +3 -0
- data/rust/parser/src/parse/aggregate.rs +2 -1
- data/rust/parser/src/parse/chapter.rs +2 -2
- data/rust/parser/src/parse/command.rs +2 -0
- data/rust/parser/src/parse/hecksagon.rs +5 -2
- data/rust/parser/src/parse/lifecycle.rs +81 -0
- data/rust/parser/src/parse/mod.rs +15 -1
- data/rust/parser/src/parse/policy.rs +13 -2
- data/rust/parser/src/parse/value_object.rs +13 -8
- data/rust/parser/src/ruby_value.rs +1 -1
- data/rust/src/exemplar/commands.rs +4 -4
- data/rust/src/exemplar/constraints.rs +1 -1
- data/rust/src/exemplar/json.rs +43 -1
- data/rust/src/kernel/cli.rs +7 -5
- data/rust/src/kernel/dispatch.rs +2 -2
- data/rust/src/kernel/dispatch_guard_tests.rs +1 -1
- data/rust/src/kernel/expr.rs +73 -4
- data/rust/src/kernel/expression_operators/arithmetic.rs +130 -5
- data/rust/src/kernel/expression_operators/presence.rs +41 -3
- data/rust/src/kernel/expression_operators/text.rs +26 -2
- data/rust/src/kernel/json.rs +15 -0
- data/rust/src/kernel/mod.rs +1 -1
- data/rust/src/kernel/orchestrate.rs +54 -5
- data/rust/src/kernel/orchestrate_guard_tests.rs +67 -0
- data/rust/src/kernel/pattern.rs +60 -0
- data/rust/src/kernel/reference_lookup.rs +90 -7
- metadata +174 -36
- data/lib/hecks/doors/cli_door/nesting.rb +0 -34
- data/lib/hecks/doors/cli_door/shorthand.rb +0 -85
- data/lib/hecks/doors/cli_door.rb +0 -124
- data/lib/hecks/doors/cli_runner/answers.rb +0 -138
- data/lib/hecks/doors/cli_runner/resolution.rb +0 -146
- data/lib/hecks/doors/cli_runner/settling.rb +0 -115
- data/lib/hecks/doors/cli_runner/streaming.rb +0 -90
- data/lib/hecks/doors/cli_runner/suggestions.rb +0 -43
- data/lib/hecks/doors/cli_runner.rb +0 -66
- data/lib/hecks/doors/command_request.rb +0 -119
- data/lib/hecks/doors/handle.rb +0 -208
- data/lib/hecks/doors/json_door.rb +0 -132
- data/lib/hecks/doors/launcher_options.rb +0 -247
- data/lib/hecks/doors/mcp_door/properties.rb +0 -47
- data/lib/hecks/doors/mcp_door/tool_calls.rb +0 -113
- data/lib/hecks/doors/mcp_door/tool_definitions/auditing.rb +0 -51
- data/lib/hecks/doors/mcp_door/tool_definitions/calling.rb +0 -69
- data/lib/hecks/doors/mcp_door/tool_definitions/reading.rb +0 -97
- data/lib/hecks/doors/mcp_door/tool_definitions.rb +0 -15
- data/lib/hecks/doors/mcp_door.rb +0 -156
- data/lib/hecks/doors/mcp_door_scope/argument_policy.rb +0 -92
- data/lib/hecks/doors/mcp_door_scope/env_problems.rb +0 -55
- data/lib/hecks/doors/mcp_door_scope/presenter.rb +0 -78
- data/lib/hecks/doors/mcp_door_scope.rb +0 -201
- data/lib/hecks/doors/mcp_guide_cache.rb +0 -75
- data/lib/hecks/doors/ruby_door/aggregate_door.rb +0 -162
- data/lib/hecks/doors/ruby_door/chapter.rb +0 -89
- data/lib/hecks/doors/ruby_door.rb +0 -80
- data/lib/hecks/doors/usage_cache.rb +0 -139
- data/prebuilt/0ee7f83cba59ce7d1c45c65ba166b071779f70143449f584f33ff7989f64eccb.marshal +0 -0
- data/prebuilt/6e4a6bcf0aa880e2e6b056e1846b70e5a768090b9dc998bbadeaf5d6bcfd3db8.marshal +0 -0
- data/prebuilt/verdicts-91aaf93ce8981bd9a754948f075d95650dc2ec6435015376f5c60992b36a24b1.json +0 -1
|
@@ -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
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
require_relative "../console_capture"
|
|
5
|
+
require_relative "../world_clearing"
|
|
6
|
+
|
|
7
|
+
module Hecks
|
|
8
|
+
module Adapters
|
|
9
|
+
class DeployToolchain
|
|
10
|
+
# The `ClearDeployment` ask: empties the `deployed_to` blocks of a domain's `.world` files, in
|
|
11
|
+
# place or into a directory, and says which files and settings it removed.
|
|
12
|
+
module Clearing
|
|
13
|
+
# Empties every `deployed_to` block under `<domain>/bluebook/**/*.world`, through
|
|
14
|
+
# `hecks deploy handover.clear`.
|
|
15
|
+
#
|
|
16
|
+
# With `out`, the cleared files are written there at their paths below `<domain>/bluebook/`
|
|
17
|
+
# and the originals are left alone; without it they are rewritten in place. A world with
|
|
18
|
+
# nothing left to clear is not rewritten and not listed.
|
|
19
|
+
#
|
|
20
|
+
# @param held [Hash] the `Handover` record: `domain`, and `out` when set
|
|
21
|
+
# @return [Hash{Symbol => Hash}] `output:` one line per file cleared with its settings
|
|
22
|
+
# @raise [ConsoleCapture::Failure] when the domain holds no `.world` file
|
|
23
|
+
def clear_deployment(**held)
|
|
24
|
+
bluebook = File.join(File.expand_path(plain(held[:domain])), "bluebook")
|
|
25
|
+
out = plain(held[:out])
|
|
26
|
+
lines = worlds_of(bluebook).filter_map { |path| clear_world(path, bluebook, out && File.expand_path(out)) }
|
|
27
|
+
{ output: { value: lines.empty? ? "no deployed_to settings to clear" : lines.join("\n") } }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def worlds_of(bluebook)
|
|
33
|
+
worlds = Dir.glob(File.join(bluebook, "**", "*.world"))
|
|
34
|
+
raise ConsoleCapture::Failure, "#{bluebook} holds no .world file to clear" if worlds.empty?
|
|
35
|
+
|
|
36
|
+
worlds
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def clear_world(path, bluebook, out)
|
|
40
|
+
cleared = WorldClearing.call(File.read(path))
|
|
41
|
+
return unless cleared.changed?
|
|
42
|
+
|
|
43
|
+
target = out ? File.join(out, path.delete_prefix("#{bluebook}/")) : path
|
|
44
|
+
FileUtils.mkdir_p(File.dirname(target))
|
|
45
|
+
File.write(target, cleared.text)
|
|
46
|
+
"#{target}: cleared #{cleared.settings.join(", ")}"
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
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",
|
|
@@ -10,6 +10,7 @@ require_relative "deploy_plans"
|
|
|
10
10
|
require_relative "deploy_toolchain/statuses"
|
|
11
11
|
require_relative "deploy_toolchain/scripts"
|
|
12
12
|
require_relative "deploy_toolchain/answers"
|
|
13
|
+
require_relative "deploy_toolchain/clearing"
|
|
13
14
|
|
|
14
15
|
module Hecks
|
|
15
16
|
module Adapters
|
|
@@ -26,6 +27,7 @@ module Hecks
|
|
|
26
27
|
include Statuses
|
|
27
28
|
include Scripts
|
|
28
29
|
include Answers
|
|
30
|
+
include Clearing
|
|
29
31
|
|
|
30
32
|
# Accepts the arguments every driven adapter is built with and keeps none of them.
|
|
31
33
|
#
|
|
@@ -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
|