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,111 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "open3"
|
|
5
|
+
require "hecks/vocabulary"
|
|
6
|
+
require_relative "../tools"
|
|
7
|
+
|
|
8
|
+
module Hecks
|
|
9
|
+
module Tools
|
|
10
|
+
# Decides whether a commit is only a release bump on a base that is fully certified, so CI may
|
|
11
|
+
# inherit the base's certification instead of running the suite again.
|
|
12
|
+
#
|
|
13
|
+
# The files a bump edits, and which of their lines it may change, are the `ReleaseFile` rows of
|
|
14
|
+
# the Vocabulary chapter. A change is a release bump when it touches at least one file, every
|
|
15
|
+
# file it touches is a row, and every line it adds or removes in a file matches that row's
|
|
16
|
+
# `lines` pattern (an empty pattern admits any line). Whether the base is certified is read
|
|
17
|
+
# from the base commit's own check runs: every `RequiredCheck` row that is not marked
|
|
18
|
+
# `runs: always` must have its latest run on the base succeed or be skipped.
|
|
19
|
+
#
|
|
20
|
+
# Every case that cannot be confirmed answers "not a release bump", so the suite runs.
|
|
21
|
+
module ReleaseOnly
|
|
22
|
+
GREEN = %w[success skipped].freeze
|
|
23
|
+
|
|
24
|
+
module_function
|
|
25
|
+
|
|
26
|
+
# @param changes [Hash{String => Array<String>}] each changed path to the lines it adds or
|
|
27
|
+
# removes
|
|
28
|
+
# @param files [Array<Hash{String => String}>] the `ReleaseFile` rows
|
|
29
|
+
# @return [Boolean] whether the change touches only release files, and only their release
|
|
30
|
+
# lines
|
|
31
|
+
def release_only?(changes, files: Hecks::Vocabulary.rows("ReleaseFile"))
|
|
32
|
+
return false if changes.empty?
|
|
33
|
+
|
|
34
|
+
by_path = files.to_h { |row| [row.fetch("path"), row.fetch("lines", "")] }
|
|
35
|
+
changes.all? { |path, lines| by_path.key?(path) && lines_allowed?(lines, by_path.fetch(path)) }
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# @param lines [Array<String>] the changed lines of one file
|
|
39
|
+
# @param pattern [String] the row's `lines` regex, empty for any line
|
|
40
|
+
# @return [Boolean] whether every changed line matches
|
|
41
|
+
def lines_allowed?(lines, pattern)
|
|
42
|
+
return true if pattern.to_s.empty?
|
|
43
|
+
|
|
44
|
+
regex = Regexp.new(pattern)
|
|
45
|
+
lines.all? { |line| line.match?(regex) }
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# @param runs [Array<Hash{String => String}>] the base commit's check runs: `name`,
|
|
49
|
+
# `conclusion` and `started_at`
|
|
50
|
+
# @param required [Array<Hash{String => String}>] the `RequiredCheck` rows
|
|
51
|
+
# @return [Boolean] whether every check that must be inherited has a green latest run
|
|
52
|
+
def certified?(runs, required: Hecks::Vocabulary.rows("RequiredCheck"))
|
|
53
|
+
latest = latest_runs(runs)
|
|
54
|
+
inherited = required.reject { |row| row["runs"] == "always" }.map { |row| row.fetch("name") }
|
|
55
|
+
inherited.all? { |name| GREEN.include?(latest[name].to_h["conclusion"]) }
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# @return [Hash{String => Hash}] each check's most recently started run
|
|
59
|
+
def latest_runs(runs)
|
|
60
|
+
runs.group_by { |run| run["name"] }.transform_values { |group| group.max_by { |run| run["started_at"].to_s } }
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# @param base [String] the commit the change is measured against
|
|
64
|
+
# @param head [String] the commit under test
|
|
65
|
+
# @param root [String] the checkout
|
|
66
|
+
# @return [Hash{String => Array<String>}, nil] each changed path to its changed lines, or nil
|
|
67
|
+
# when git could not say
|
|
68
|
+
def changes(base, head, root:)
|
|
69
|
+
out, _err, status = Open3.capture3("git", "-C", root, "diff", "-U0", "--no-color", base, head)
|
|
70
|
+
status.success? ? parse(out) : nil
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# @param diff [String] `git diff -U0` output
|
|
74
|
+
# @return [Hash{String => Array<String>}] each changed path to the lines it adds or removes
|
|
75
|
+
def parse(diff)
|
|
76
|
+
found = Hash.new { |hash, key| hash[key] = [] }
|
|
77
|
+
path = nil
|
|
78
|
+
diff.each_line do |line|
|
|
79
|
+
path = file_of(line.chomp) || path
|
|
80
|
+
found[path] << line[1..].chomp if path && content_line?(line)
|
|
81
|
+
end
|
|
82
|
+
found.to_h
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# @return [String, nil] the path a `+++` or `---` header names, nil for any other line
|
|
86
|
+
def file_of(line)
|
|
87
|
+
return line.delete_prefix("+++ b/") if line.start_with?("+++ b/")
|
|
88
|
+
|
|
89
|
+
line.delete_prefix("--- a/") if line.start_with?("--- a/")
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# @return [Boolean] whether the line is an added or removed line, not a header
|
|
93
|
+
def content_line?(line)
|
|
94
|
+
line.match?(/\A[+-]/) && !line.start_with?("+++ ", "--- ")
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# @param repository [String] `owner/name`
|
|
98
|
+
# @param sha [String] the commit whose check runs are read
|
|
99
|
+
# @param env [Hash{String => String}] carries `GH_TOKEN` for the API call
|
|
100
|
+
# @return [Array<Hash{String => String}>, nil] the check runs, or nil when the API failed
|
|
101
|
+
def check_runs(repository, sha, env: ENV)
|
|
102
|
+
path = "repos/#{repository}/commits/#{sha}/check-runs?per_page=100"
|
|
103
|
+
jq = ".check_runs[] | {name, conclusion, started_at}"
|
|
104
|
+
out, _err, status = Open3.capture3(env.to_h.slice("GH_TOKEN"), "gh", "api", path, "--paginate", "--jq", jq)
|
|
105
|
+
status.success? ? out.lines.map { |line| JSON.parse(line) } : nil
|
|
106
|
+
rescue JSON::ParserError
|
|
107
|
+
nil
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
@@ -18,18 +18,25 @@ module Hecks
|
|
|
18
18
|
setting = Projections::Site::CmsEditor::Setting.read(site.chapter, table: site.table)
|
|
19
19
|
abort "project_site: --editor names #{editor}, but the project declares no Editor row" if setting.nil?
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
files = Projector.call(:site_cms_editor, bluebook: site.chapter, options: options)
|
|
21
|
+
files = Projector.call(:site_cms_editor, bluebook: site.chapter, options: options(project, setting, site))
|
|
23
22
|
files.to_h { |name, text| [File.join(File.expand_path(editor), name), text] }
|
|
24
23
|
rescue ArgumentError, Projections::Site::Table::Invalid => e
|
|
25
24
|
abort "project_site: #{e.message}"
|
|
26
25
|
end
|
|
27
26
|
|
|
27
|
+
# @return [Hash{Symbol => Object}] the chapters the editor reads, and the route table
|
|
28
|
+
def options(project, setting, site)
|
|
29
|
+
chosen = { domain_chapters: setting.chapters.map { |name| domain_chapter(project, setting, name) }, table: site.table }
|
|
30
|
+
chosen[:media_chapter] = domain_chapter(project, setting, setting.media_chapter) if setting.media_chapter
|
|
31
|
+
chosen
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# @param name [String] the chapter to find; the row's first `chapter` unless given
|
|
28
35
|
# @return [Bluebook::Chapter] the chapter the `Editor` row names, from its domain directory
|
|
29
|
-
def domain_chapter(project, setting)
|
|
36
|
+
def domain_chapter(project, setting, name = setting.chapter)
|
|
30
37
|
domain_root = File.join(project, setting.domain)
|
|
31
|
-
chapter = registry_for(domain_root).bluebooks.values.find { |candidate| candidate.name ==
|
|
32
|
-
abort "project_site: #{domain_root} declares no chapter #{
|
|
38
|
+
chapter = registry_for(domain_root).bluebooks.values.find { |candidate| candidate.name == name }
|
|
39
|
+
abort "project_site: #{domain_root} declares no chapter #{name}" unless chapter
|
|
33
40
|
chapter
|
|
34
41
|
end
|
|
35
42
|
end
|
|
@@ -80,7 +80,7 @@ module Hecks
|
|
|
80
80
|
# @raise [SystemExit] when the declared shape is refused, or provisioning is refused
|
|
81
81
|
def provision(slug, options, domain_directory)
|
|
82
82
|
lib_hecks = File.expand_path("..", __dir__)
|
|
83
|
-
dispatcher = Hecks.boot_files(CHAPTER_FILES.map { |file| File.join(lib_hecks, file) },
|
|
83
|
+
dispatcher = Hecks.boot_files(CHAPTER_FILES.map { |file| File.join(lib_hecks, file) }, install_driving: false)
|
|
84
84
|
provision_tenant(dispatcher, slug, tenant_facts(slug, options, domain_directory))
|
|
85
85
|
refuse_if_refused(dispatcher, slug)
|
|
86
86
|
|
|
@@ -126,7 +126,7 @@ module Hecks
|
|
|
126
126
|
# @return [void]
|
|
127
127
|
# @raise [SystemExit] when the tenant was never registered in Tenancy
|
|
128
128
|
def confirm(dispatcher, slug, options, domain_directory)
|
|
129
|
-
target = Hecks.boot(domain_directory, environment: slug,
|
|
129
|
+
target = Hecks.boot(domain_directory, environment: slug, install_driving: false)
|
|
130
130
|
puts "booted #{options[:domain]} for tenant #{slug.inspect} under realm #{options[:realm].inspect}"
|
|
131
131
|
|
|
132
132
|
refuse_unless_tenant_capable(target.registry)
|
|
@@ -177,7 +177,7 @@ module Hecks
|
|
|
177
177
|
class Context
|
|
178
178
|
# @param root [String] the checkout
|
|
179
179
|
def initialize(root)
|
|
180
|
-
@runtime = Hecks.boot(File.join(root, "lib/hecks/hecks"),
|
|
180
|
+
@runtime = Hecks.boot(File.join(root, "lib/hecks/hecks"), install_driving: false)
|
|
181
181
|
@projections = {}
|
|
182
182
|
end
|
|
183
183
|
|
|
@@ -201,8 +201,8 @@ module Hecks
|
|
|
201
201
|
|
|
202
202
|
def projection(chapter)
|
|
203
203
|
@projections[chapter] ||= begin
|
|
204
|
-
launcher =
|
|
205
|
-
options =
|
|
204
|
+
launcher = Adapters::Driving::LauncherOptions.settings(@runtime, chapter)
|
|
205
|
+
options = Adapters::Driving::LauncherOptions.projection(launcher, "hecks")
|
|
206
206
|
Projector.call(:cli, bluebook: @runtime.registry.bluebook(chapter), options: options)
|
|
207
207
|
end
|
|
208
208
|
end
|
|
@@ -210,7 +210,7 @@ module Hecks
|
|
|
210
210
|
# `hecks ir`, `hecks console`, `hecks quality_control sweep.run`, `hecks host.check_era`.
|
|
211
211
|
# The launcher takes a name that is only a query as a query, so no `query` word is needed.
|
|
212
212
|
def words(chapter, name, row)
|
|
213
|
-
route = route_for(
|
|
213
|
+
route = route_for(Adapters::Driving::LauncherOptions.settings(@runtime, chapter) || {}, chapter, name, row)
|
|
214
214
|
return [route] if route
|
|
215
215
|
|
|
216
216
|
[*(Naming.snake(chapter) unless chapter == "Hecks"), name]
|
data/lib/hecks/version.rb
CHANGED
data/lib/hecks/vocabulary.rb
CHANGED
|
@@ -51,6 +51,7 @@ module Hecks
|
|
|
51
51
|
].freeze,
|
|
52
52
|
"CiGate" => [
|
|
53
53
|
{"name"=>"runtime_changed", "workflow"=>"ci.yml", "mode"=>"touches", "pattern"=>"^lib/hecks/runtime/", "push"=>"before_sha", "label"=>"does this change touch lib/hecks/runtime/**?"}.freeze,
|
|
54
|
+
{"name"=>"release_only", "workflow"=>"ci.yml", "mode"=>"release_only", "pattern"=>"", "push"=>"before_sha", "label"=>"is this change only a release bump on a certified base?"}.freeze,
|
|
54
55
|
{"name"=>"postgres_io_relevant_changed", "workflow"=>"ci-postgres-io-parallel.yml", "mode"=>"skips_unless", "pattern"=>"^(docs/|editors/|release/|deploy/|\\.claude/|\\.githooks/|rust/(parser|codegen|host|build|lsp|web|tests)/|\\.rubocop\\.yml$|\\.rubocop_todo\\.yml$|\\.mcp\\.json$|\\.rspec-local\\.example$|README\\.md$|CHANGELOG\\.md$|CONTRIBUTING\\.md$|SECURITY\\.md$|LICENSE$|\\.gitignore$)", "push"=>"skip", "label"=>"does this change touch anything rspec_postgres_io_parallel covers?"}.freeze
|
|
55
56
|
].freeze,
|
|
56
57
|
"Comparison" => [
|
|
@@ -107,8 +108,8 @@ module Hecks
|
|
|
107
108
|
{"type"=>"String", "strategy"=>"substring"}.freeze
|
|
108
109
|
].freeze,
|
|
109
110
|
"Lane" => [
|
|
110
|
-
{"name"=>"main", "guarded"=>"no", "pushers"=>"anyone", "feeds"=>"", "follows"=>"", "alert_after"=>""}.freeze,
|
|
111
|
-
{"name"=>"stable", "guarded"=>"yes", "pushers"=>"green", "feeds"=>"edge", "follows"=>"main", "alert_after"=>"4"}.freeze
|
|
111
|
+
{"name"=>"main", "guarded"=>"no", "pushers"=>"anyone", "feeds"=>"", "follows"=>"", "alert_after"=>"", "release_trailer"=>""}.freeze,
|
|
112
|
+
{"name"=>"stable", "guarded"=>"yes", "pushers"=>"green", "feeds"=>"edge", "follows"=>"main", "alert_after"=>"4", "release_trailer"=>"Release-Approved-By"}.freeze
|
|
112
113
|
].freeze,
|
|
113
114
|
"LoadOrder" => [
|
|
114
115
|
{"glob"=>"*.port"}.freeze,
|
|
@@ -134,6 +135,9 @@ module Hecks
|
|
|
134
135
|
{"name"=>"replace"}.freeze,
|
|
135
136
|
{"name"=>"scale_call"}.freeze
|
|
136
137
|
].freeze,
|
|
138
|
+
"PrebuiltBinary" => [
|
|
139
|
+
{"name"=>"host", "crate"=>"rust/host", "executable"=>"bootstrap", "target"=>"x86_64-unknown-linux-gnu", "runner"=>"ubuntu-24.04"}.freeze
|
|
140
|
+
].freeze,
|
|
137
141
|
"Primitive" => [
|
|
138
142
|
{"name"=>"String"}.freeze,
|
|
139
143
|
{"name"=>"Integer"}.freeze,
|
|
@@ -363,6 +367,15 @@ module Hecks
|
|
|
363
367
|
{"refusal"=>"AttributeAbsent", "site"=>"absent_read", "template"=>"{aggregate} {field} is absent on this record — declared, not optional, and added since it was written. Backfill it in a translation (backfill :{field}, default: ...), or declare it optional: true"}.freeze,
|
|
364
368
|
{"refusal"=>"ProjectionAbsent", "site"=>"absent_read", "template"=>"{aggregate} {field} is not yet projected on this record — declared via projects :{field}, but no rebuild sweep has populated it. Run the sweep, or read {reference}.{remote_field} directly if this rule cannot wait"}.freeze
|
|
365
369
|
].freeze,
|
|
370
|
+
"ReleaseFile" => [
|
|
371
|
+
{"path"=>"lib/hecks/version.rb", "lines"=>"^\\s*VERSION = \"[0-9]+\\.[0-9]+\\.[0-9]+\"\\.freeze$"}.freeze,
|
|
372
|
+
{"path"=>"CHANGELOG.md", "lines"=>""}.freeze,
|
|
373
|
+
{"path"=>"README.md", "lines"=>"^(\\*\\*Status:\\*\\* )?Current release: `[0-9]+\\.[0-9]+\\.[0-9]+`"}.freeze,
|
|
374
|
+
{"path"=>"Gemfile.lock", "lines"=>"^ hecks \\([0-9]+\\.[0-9]+\\.[0-9]+\\)$"}.freeze,
|
|
375
|
+
{"path"=>"rust/host/HECKS_RELEASE", "lines"=>"^[0-9]+\\.[0-9]+\\.[0-9]+$"}.freeze,
|
|
376
|
+
{"path"=>"packages/hecks-client/package.json", "lines"=>"^\\s*\"version\": \"[0-9]+\\.[0-9]+\\.[0-9]+\",?$"}.freeze,
|
|
377
|
+
{"path"=>"packages/hecks-client/package-lock.json", "lines"=>"^\\s*\"version\": \"[0-9]+\\.[0-9]+\\.[0-9]+\",?$"}.freeze
|
|
378
|
+
].freeze,
|
|
366
379
|
"RequiredCheck" => [
|
|
367
380
|
{"name"=>"rspec"}.freeze,
|
|
368
381
|
{"name"=>"checks"}.freeze,
|
|
@@ -372,7 +385,8 @@ module Hecks
|
|
|
372
385
|
{"name"=>"rspec_rust_parser"}.freeze,
|
|
373
386
|
{"name"=>"rspec_rust_codegen"}.freeze,
|
|
374
387
|
{"name"=>"rspec_rust_host"}.freeze,
|
|
375
|
-
{"name"=>"rspec_fuzzing"}.freeze
|
|
388
|
+
{"name"=>"rspec_fuzzing"}.freeze,
|
|
389
|
+
{"name"=>"release_consistency", "runs"=>"always"}.freeze
|
|
376
390
|
].freeze,
|
|
377
391
|
"RetiredScript" => [
|
|
378
392
|
{"script"=>"ir", "section"=>"Custodian", "aggregate"=>"Introspection", "verb"=>"ir"}.freeze,
|
|
@@ -402,8 +416,8 @@ module Hecks
|
|
|
402
416
|
{"script"=>"compact", "section"=>"Custodian", "aggregate"=>"Era", "verb"=>"compact"}.freeze,
|
|
403
417
|
{"script"=>"heki_compact", "section"=>"Custodian", "aggregate"=>"Era", "verb"=>"compact_heki"}.freeze,
|
|
404
418
|
{"script"=>"vendor_bluebook", "section"=>"Custodian", "aggregate"=>"Package", "verb"=>"vendor", "note"=>"waits: a refusal exits 1 with its reason on stderr"}.freeze,
|
|
405
|
-
{"script"=>"project_cli", "section"=>"Custodian", "aggregate"=>"
|
|
406
|
-
{"script"=>"hecks_mcp_door", "section"=>"Custodian", "aggregate"=>"
|
|
419
|
+
{"script"=>"project_cli", "section"=>"Custodian", "aggregate"=>"Launch", "verb"=>"project_cli"}.freeze,
|
|
420
|
+
{"script"=>"hecks_mcp_door", "section"=>"Custodian", "aggregate"=>"Launch", "verb"=>"serve_mcp"}.freeze,
|
|
407
421
|
{"script"=>"project_rust", "section"=>"Custodian", "aggregate"=>"Build", "verb"=>"project_rust"}.freeze,
|
|
408
422
|
{"script"=>"project_wasm", "section"=>"Custodian", "aggregate"=>"Build", "verb"=>"build_wasm"}.freeze,
|
|
409
423
|
{"script"=>"project_host", "section"=>"Custodian", "aggregate"=>"Build", "verb"=>"build_host"}.freeze,
|
data/lib/hecks.rb
CHANGED
|
@@ -12,7 +12,7 @@ require_relative "hecks/construct"
|
|
|
12
12
|
# extends this to declare what it emits.
|
|
13
13
|
require_relative "hecks/ir"
|
|
14
14
|
require_relative "hecks/literal"
|
|
15
|
-
require_relative "hecks/
|
|
15
|
+
require_relative "hecks/adapters/driving"
|
|
16
16
|
require_relative "hecks/query_specification"
|
|
17
17
|
|
|
18
18
|
require_relative "hecks/ports"
|
|
@@ -27,13 +27,13 @@ require_relative "hecks/projector"
|
|
|
27
27
|
# installation of them.
|
|
28
28
|
require_relative "hecks/projections"
|
|
29
29
|
# After `Projector`, which dispatches against the `:cli` projection.
|
|
30
|
-
require_relative "hecks/
|
|
31
|
-
require_relative "hecks/
|
|
32
|
-
require_relative "hecks/
|
|
30
|
+
require_relative "hecks/adapters/driving/cli"
|
|
31
|
+
require_relative "hecks/adapters/driving/usage_cache"
|
|
32
|
+
require_relative "hecks/adapters/driving/cli_runner"
|
|
33
33
|
require_relative "hecks/storehouse"
|
|
34
34
|
require_relative "hecks/mcp_stdio_guard"
|
|
35
|
-
require_relative "hecks/
|
|
36
|
-
require_relative "hecks/
|
|
35
|
+
require_relative "hecks/adapters/driving/mcp_scope"
|
|
36
|
+
require_relative "hecks/adapters/driving/mcp_guide_cache"
|
|
37
37
|
require_relative "hecks/framework"
|
|
38
38
|
require_relative "hecks/vendoring"
|
|
39
39
|
require_relative "hecks/embryonaut_bluebook"
|
|
@@ -56,12 +56,12 @@ module Hecks
|
|
|
56
56
|
# the `HECKS_ENVIRONMENT` variable; pass a name to choose one, or `nil` for none.
|
|
57
57
|
# @param path [String] path to a domain directory, or a file inside one
|
|
58
58
|
# @param shared [String, nil] a shared-root override; see `Runtime::Loader.boot`
|
|
59
|
-
# @param
|
|
59
|
+
# @param install_driving [Boolean] install the `Widget::Item.Add`-style facade
|
|
60
60
|
# @param environment [String, nil] env name; its `.hecksagon`/`.world` overlay,
|
|
61
61
|
# if present, loads after the domain's own. Defaults to `HECKS_ENVIRONMENT`; nil loads none
|
|
62
62
|
# @return [Runtime::Dispatcher, Runtime::RemoteDispatcher] dispatcher bound to the domain
|
|
63
|
-
def boot(path, shared: nil,
|
|
64
|
-
Runtime.boot(path, shared: shared,
|
|
63
|
+
def boot(path, shared: nil, install_driving: true, environment: Runtime::Loader::FROM_ENV)
|
|
64
|
+
Runtime.boot(path, shared: shared, install_driving: install_driving, environment: environment)
|
|
65
65
|
end
|
|
66
66
|
|
|
67
67
|
# Loads a domain's declarations without binding any adapter or opening any store.
|
|
@@ -78,10 +78,10 @@ module Hecks
|
|
|
78
78
|
# once. See `Runtime::Loader.boot_described`.
|
|
79
79
|
#
|
|
80
80
|
# @param described [Runtime::Loader::Described] what `describe` answered
|
|
81
|
-
# @param
|
|
81
|
+
# @param install_driving [Boolean] install the `Widget::Item.Add`-style facade
|
|
82
82
|
# @return [Runtime::Dispatcher, Runtime::RemoteDispatcher] dispatcher bound to the domain
|
|
83
|
-
def boot_described(described,
|
|
84
|
-
Runtime.boot_described(described,
|
|
83
|
+
def boot_described(described, install_driving: true)
|
|
84
|
+
Runtime.boot_described(described, install_driving: install_driving)
|
|
85
85
|
end
|
|
86
86
|
|
|
87
87
|
# Boots a domain from an explicit list of files (a `.bluebook`, its
|
|
@@ -89,11 +89,11 @@ module Hecks
|
|
|
89
89
|
# see `Runtime::Loader.boot_files` for why this exists beside `boot`.
|
|
90
90
|
# @param paths [String, Array<String>] one or more file paths within the domain
|
|
91
91
|
# @param shared [String, nil] a shared-root override; see `Runtime::Loader.boot_files`
|
|
92
|
-
# @param
|
|
92
|
+
# @param install_driving [Boolean] install the `Widget::Item.Add`-style facade
|
|
93
93
|
# @param environment [String, nil] environment name passed to the selected-file loader
|
|
94
94
|
# @return [Runtime::Dispatcher, Runtime::RemoteDispatcher] dispatcher bound to the domain
|
|
95
|
-
def boot_files(paths, shared: nil,
|
|
96
|
-
Runtime.boot_files(paths, shared: shared,
|
|
95
|
+
def boot_files(paths, shared: nil, install_driving: true, environment: Runtime::Loader::FROM_ENV)
|
|
96
|
+
Runtime.boot_files(paths, shared: shared, install_driving: install_driving, environment: environment)
|
|
97
97
|
end
|
|
98
98
|
|
|
99
99
|
# Binds the ambient registry for the duration of the block.
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
module RuboCop
|
|
2
|
+
module Cop
|
|
3
|
+
module Hecks
|
|
4
|
+
# Flags a secret leaving the process in output: the whole environment printed, or a
|
|
5
|
+
# `*_KEY` / `*_SECRET` / `*_TOKEN` / `*_PASSWORD` variable interpolated into a string.
|
|
6
|
+
#
|
|
7
|
+
# An interpolated secret ends up in a log line, a failure message or a command line. Reading
|
|
8
|
+
# one into a local (or an HTTP header) is fine; building a string around it is not. Restoring
|
|
9
|
+
# the environment in a spec (`saved = ENV.to_h`) is not output, so it is left alone.
|
|
10
|
+
#
|
|
11
|
+
# @example
|
|
12
|
+
# puts ENV.to_h # bad
|
|
13
|
+
# warn "using #{ENV.fetch("API_TOKEN")}" # bad
|
|
14
|
+
# headers["Authorization"] = ENV.fetch("API_TOKEN") # good
|
|
15
|
+
class EnvDump < Base
|
|
16
|
+
MSG_DUMP = "This prints the whole environment, which carries every secret the process holds. " \
|
|
17
|
+
"Print only the variable names you need.".freeze
|
|
18
|
+
MSG_SECRET = "`%<read>s` is a secret read into a string, so it will land in a log line or a message. " \
|
|
19
|
+
"Pass it straight to the call that needs it instead of interpolating it.".freeze
|
|
20
|
+
|
|
21
|
+
OUTPUT_CALLS = %i[puts p pp print warn].freeze
|
|
22
|
+
SECRET_NAME = /(KEY|SECRET|TOKEN|PASSWORD|PASSWD|CREDENTIAL)/i
|
|
23
|
+
|
|
24
|
+
# @!method whole_env?(node)
|
|
25
|
+
def_node_matcher :whole_env?, <<~PATTERN
|
|
26
|
+
{(const {nil? cbase} :ENV) (send (const {nil? cbase} :ENV) {:to_h :to_a :to_s :inspect})}
|
|
27
|
+
PATTERN
|
|
28
|
+
|
|
29
|
+
# @!method env_read(node)
|
|
30
|
+
def_node_matcher :env_read, <<~PATTERN
|
|
31
|
+
(send (const {nil? cbase} :ENV) {:[] :fetch} (str $_) ...)
|
|
32
|
+
PATTERN
|
|
33
|
+
|
|
34
|
+
# Flags the whole environment handed to an output call.
|
|
35
|
+
#
|
|
36
|
+
# @param node [RuboCop::AST::SendNode] the call being visited
|
|
37
|
+
# @return [void]
|
|
38
|
+
def on_send(node)
|
|
39
|
+
return unless node.receiver.nil? && OUTPUT_CALLS.include?(node.method_name)
|
|
40
|
+
return unless node.arguments.any? { |argument| whole_env?(argument) }
|
|
41
|
+
|
|
42
|
+
add_offense(node, message: MSG_DUMP)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Flags a secret variable interpolated into a string.
|
|
46
|
+
#
|
|
47
|
+
# @param node [RuboCop::AST::BeginNode] the `#{}` part being visited
|
|
48
|
+
# @return [void]
|
|
49
|
+
def on_begin(node)
|
|
50
|
+
return unless interpolation?(node)
|
|
51
|
+
|
|
52
|
+
read = node.children.filter_map { |child| secret_read(child) }.first
|
|
53
|
+
add_offense(node, message: format(MSG_SECRET, read: read)) if read
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
def interpolation?(node)
|
|
59
|
+
%i[dstr dsym xstr].include?(node.parent&.type)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def secret_read(node)
|
|
63
|
+
name = env_read(node)
|
|
64
|
+
node.source if name&.match?(SECRET_NAME)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
module RuboCop
|
|
2
|
+
module Cop
|
|
3
|
+
module Hecks
|
|
4
|
+
# Flags a duration measured as `Time.now - start`, where `start` was read from the clock.
|
|
5
|
+
#
|
|
6
|
+
# The wall clock steps when it is corrected, so a subtraction can go negative or jump.
|
|
7
|
+
# Elapsed time comes from the monotonic clock. `Time.now - seconds` (a past timestamp)
|
|
8
|
+
# and `Time.now - File.mtime(path)` (an age against a stored time) are left alone.
|
|
9
|
+
#
|
|
10
|
+
# @example
|
|
11
|
+
# started = Time.now
|
|
12
|
+
# elapsed = Time.now - started # bad
|
|
13
|
+
#
|
|
14
|
+
# started = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
15
|
+
# elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - started # good
|
|
16
|
+
class MonotonicDuration < Base
|
|
17
|
+
MSG = "`Time.now - %<start>s` measures elapsed time on the wall clock, which can step. " \
|
|
18
|
+
"Take both ends from `Process.clock_gettime(Process::CLOCK_MONOTONIC)`.".freeze
|
|
19
|
+
|
|
20
|
+
# @!method wall_clock_minus_variable(node)
|
|
21
|
+
def_node_matcher :wall_clock_minus_variable, <<~PATTERN
|
|
22
|
+
(send #clock_read? :- ${lvar ivar})
|
|
23
|
+
PATTERN
|
|
24
|
+
|
|
25
|
+
# @!method clock_read?(node)
|
|
26
|
+
def_node_matcher :clock_read?, <<~PATTERN
|
|
27
|
+
{(send (const {nil? cbase} :Time) :now) (send (send (const {nil? cbase} :Time) :now) :to_f)}
|
|
28
|
+
PATTERN
|
|
29
|
+
|
|
30
|
+
# Flags `Time.now - <variable>` when the variable holds an earlier clock read.
|
|
31
|
+
#
|
|
32
|
+
# @param node [RuboCop::AST::SendNode] the subtraction being visited
|
|
33
|
+
# @return [void]
|
|
34
|
+
def on_send(node)
|
|
35
|
+
start = wall_clock_minus_variable(node)
|
|
36
|
+
return unless start && assigned_from_clock?(node, start)
|
|
37
|
+
|
|
38
|
+
add_offense(node, message: format(MSG, start: start.source))
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
# Whether the variable is assigned a clock read anywhere in the enclosing method or class.
|
|
44
|
+
def assigned_from_clock?(node, variable)
|
|
45
|
+
scope = node.each_ancestor(:def, :defs, :class, :module).first || processed_source.ast
|
|
46
|
+
scope.each_descendant(:lvasgn, :ivasgn).any? do |assignment|
|
|
47
|
+
assignment.name == variable.children.first && clock_read?(assignment.expression)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
module RuboCop
|
|
2
|
+
module Cop
|
|
3
|
+
module Hecks
|
|
4
|
+
# Flags a direct wall-clock read (`Time.now`, `Date.today`, `Time.current`) in the runtime.
|
|
5
|
+
#
|
|
6
|
+
# The runtime must stay replayable (ADR 0081), so time reaches a command through `needs :now`
|
|
7
|
+
# or through the one stamping seam, `Hecks::Runtime::Event.stamp`; a stray read makes a
|
|
8
|
+
# replay differ from the original run and cannot be pinned in a test.
|
|
9
|
+
#
|
|
10
|
+
# @example
|
|
11
|
+
# Event.new(name: n, occurred_at: Time.now.utc.iso8601) # bad
|
|
12
|
+
# Event.new(name: n, occurred_at: Event.stamp) # good
|
|
13
|
+
class NoWallClockInRuntime < Base
|
|
14
|
+
MSG = "`%<call>s` reads the wall clock inside the runtime, so a replay cannot reproduce it. " \
|
|
15
|
+
"Declare `needs :now` on the command, or stamp an event with `Hecks::Runtime::Event.stamp`.".freeze
|
|
16
|
+
|
|
17
|
+
# @!method wall_clock_read?(node)
|
|
18
|
+
def_node_matcher :wall_clock_read?, <<~PATTERN
|
|
19
|
+
(send (const {nil? cbase} {:Time :Date :DateTime}) {:now :today :current})
|
|
20
|
+
PATTERN
|
|
21
|
+
|
|
22
|
+
# Flags a `Time.now`-style call.
|
|
23
|
+
#
|
|
24
|
+
# @param node [RuboCop::AST::SendNode] the call being visited
|
|
25
|
+
# @return [void]
|
|
26
|
+
def on_send(node)
|
|
27
|
+
return unless wall_clock_read?(node)
|
|
28
|
+
|
|
29
|
+
add_offense(node, message: format(MSG, call: node.source))
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
module RuboCop
|
|
2
|
+
module Cop
|
|
3
|
+
module Hecks
|
|
4
|
+
# Flags a shell command built by interpolating a value into one string.
|
|
5
|
+
#
|
|
6
|
+
# `system("rm -rf #{dir}")` hands the shell a string it re-parses, so a value with a space
|
|
7
|
+
# or a metacharacter changes the command. The array form (`system("rm", "-rf", dir)`)
|
|
8
|
+
# passes each argument as-is. An interpolation wrapped in `Shellwords.escape` or
|
|
9
|
+
# `shellescape` is left alone.
|
|
10
|
+
#
|
|
11
|
+
# @example
|
|
12
|
+
# system("git checkout #{branch}") # bad
|
|
13
|
+
# system("git", "checkout", branch) # good
|
|
14
|
+
class RawShellInterpolation < Base
|
|
15
|
+
MSG = "`%<call>s` runs a string with `\#{}` interpolated into it, so the shell re-parses the value. " \
|
|
16
|
+
"Pass the command as separate arguments (`%<call>s(\"cmd\", arg)`), or wrap the value in " \
|
|
17
|
+
"`Shellwords.escape`.".freeze
|
|
18
|
+
|
|
19
|
+
XSTR_MSG = "A backtick or `%x` command with `\#{}` interpolated into it is re-parsed by the shell. " \
|
|
20
|
+
"Use `IO.popen([\"cmd\", arg], &:read)` or `Open3.capture2(\"cmd\", arg)`, or wrap the " \
|
|
21
|
+
"value in `Shellwords.escape`.".freeze
|
|
22
|
+
|
|
23
|
+
SHELL_CALLS = %i[system exec spawn].freeze
|
|
24
|
+
OPEN3_CALLS = %i[capture2 capture2e capture3 popen2 popen2e popen3 pipeline pipeline_r pipeline_w
|
|
25
|
+
pipeline_rw pipeline_start].freeze
|
|
26
|
+
|
|
27
|
+
# @!method open3_receiver?(node)
|
|
28
|
+
def_node_matcher :open3_receiver?, "(const {nil? cbase} :Open3)"
|
|
29
|
+
|
|
30
|
+
# @!method escaped?(node)
|
|
31
|
+
def_node_matcher :escaped?, <<~PATTERN
|
|
32
|
+
{(send (const {nil? cbase} :Shellwords) {:escape :shellescape :join :shelljoin} ...)
|
|
33
|
+
(send _ {:shellescape :shelljoin})}
|
|
34
|
+
PATTERN
|
|
35
|
+
|
|
36
|
+
# Flags a single-string command with a raw interpolation.
|
|
37
|
+
#
|
|
38
|
+
# @param node [RuboCop::AST::SendNode] the call being visited
|
|
39
|
+
# @return [void]
|
|
40
|
+
def on_send(node)
|
|
41
|
+
return unless shell_call?(node)
|
|
42
|
+
|
|
43
|
+
command = command_argument(node)
|
|
44
|
+
return unless command&.dstr_type? && raw_interpolation?(command)
|
|
45
|
+
|
|
46
|
+
add_offense(node, message: format(MSG, call: node.method_name))
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Flags a backtick or `%x` literal with a raw interpolation.
|
|
50
|
+
#
|
|
51
|
+
# @param node [RuboCop::AST::StrNode] the command literal being visited
|
|
52
|
+
# @return [void]
|
|
53
|
+
def on_xstr(node)
|
|
54
|
+
return unless raw_interpolation?(node)
|
|
55
|
+
|
|
56
|
+
add_offense(node, message: XSTR_MSG)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
|
|
61
|
+
def shell_call?(node)
|
|
62
|
+
return SHELL_CALLS.include?(node.method_name) if node.receiver.nil? || node.receiver.cbase_type?
|
|
63
|
+
|
|
64
|
+
open3_receiver?(node.receiver) && OPEN3_CALLS.include?(node.method_name)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# The lone command string, after an optional leading env hash; nil when it passes argv.
|
|
68
|
+
def command_argument(node)
|
|
69
|
+
args = node.arguments.drop_while(&:hash_type?)
|
|
70
|
+
args.first if args.size == 1
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def raw_interpolation?(node)
|
|
74
|
+
node.each_child_node(:begin).any? { |part| !escaped?(part.children.first) }
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
module RuboCop
|
|
2
|
+
module Cop
|
|
3
|
+
module Hecks
|
|
4
|
+
# Flags a fixed `sleep` in a spec.
|
|
5
|
+
#
|
|
6
|
+
# A fixed sleep is a guess about how long another thread needs: too short and the example is
|
|
7
|
+
# flaky, too long and the suite is slow. Wait on the thing itself instead, with a Queue, a
|
|
8
|
+
# latch, or `ThreadParking.wait_until_parked(thread)` when the assertion is "still blocked".
|
|
9
|
+
#
|
|
10
|
+
# @example
|
|
11
|
+
# sleep 0.2 # bad
|
|
12
|
+
# ThreadParking.wait_until_parked(waiter) # good
|
|
13
|
+
class SleepInSpec < Base
|
|
14
|
+
MSG = "A fixed `sleep` guesses how long another thread needs, so the example is either flaky or " \
|
|
15
|
+
"slow. Wait on the thread itself: a Queue, or `ThreadParking.wait_until_parked(thread)`.".freeze
|
|
16
|
+
|
|
17
|
+
RESTRICT_ON_SEND = %i[sleep].freeze
|
|
18
|
+
|
|
19
|
+
# @!method fixed_sleep?(node)
|
|
20
|
+
def_node_matcher :fixed_sleep?, <<~PATTERN
|
|
21
|
+
(send {nil? (const {nil? cbase} :Kernel)} :sleep ...)
|
|
22
|
+
PATTERN
|
|
23
|
+
|
|
24
|
+
# Flags a `sleep` call.
|
|
25
|
+
#
|
|
26
|
+
# @param node [RuboCop::AST::SendNode] the call being visited
|
|
27
|
+
# @return [void]
|
|
28
|
+
def on_send(node)
|
|
29
|
+
add_offense(node, message: MSG) if fixed_sleep?(node)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
module RuboCop
|
|
2
|
+
module Cop
|
|
3
|
+
module Hecks
|
|
4
|
+
# Flags a broad `rescue` whose whole body is `nil`, `false` or `next`.
|
|
5
|
+
#
|
|
6
|
+
# A bare `rescue` or `rescue StandardError` that answers a literal turns any crash into an
|
|
7
|
+
# ordinary "no result". Rescuing a named error is left alone; a deliberate guard goes
|
|
8
|
+
# through `Hecks::Runtime::BestEffort.call(default) { ... }`.
|
|
9
|
+
#
|
|
10
|
+
# @example
|
|
11
|
+
# adapter.capable? rescue false # bad
|
|
12
|
+
# BestEffort.call(false) { adapter.capable? } # good
|
|
13
|
+
class SwallowedRescue < Base
|
|
14
|
+
MSG = "This broad `rescue` answers `%<answer>s` for any failure, so a crash reads as an ordinary " \
|
|
15
|
+
"\"no result\". Rescue the specific error, or use `Runtime::BestEffort.call(default) { ... }` " \
|
|
16
|
+
"for a deliberate guard.".freeze
|
|
17
|
+
|
|
18
|
+
# @!method swallowed_answer(node)
|
|
19
|
+
def_node_matcher :swallowed_answer, <<~PATTERN
|
|
20
|
+
(resbody {nil? (array (const {nil? cbase} {:StandardError :Exception}))} _
|
|
21
|
+
${(nil) (false) (next)})
|
|
22
|
+
PATTERN
|
|
23
|
+
|
|
24
|
+
# Flags a broad rescue clause that answers a bare literal.
|
|
25
|
+
#
|
|
26
|
+
# @param node [RuboCop::AST::ResbodyNode] the rescue clause being visited
|
|
27
|
+
# @return [void]
|
|
28
|
+
def on_resbody(node)
|
|
29
|
+
answer = swallowed_answer(node)
|
|
30
|
+
return unless answer
|
|
31
|
+
|
|
32
|
+
add_offense(node, message: format(MSG, answer: answer.source))
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|