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
data/lib/hecks/doors/mcp_door.rb
DELETED
|
@@ -1,156 +0,0 @@
|
|
|
1
|
-
require "json"
|
|
2
|
-
require_relative "../../hecks"
|
|
3
|
-
require_relative "../cli/mcp"
|
|
4
|
-
require_relative "mcp_door/tool_definitions"
|
|
5
|
-
require_relative "mcp_door/tool_calls"
|
|
6
|
-
|
|
7
|
-
module Hecks
|
|
8
|
-
module Doors
|
|
9
|
-
# The MCP door onto the storehouse bus: hand-rolled JSON-RPC over stdio, one
|
|
10
|
-
# server per booted domain. All logic and the JSON shape answered with live in
|
|
11
|
-
# `Storehouse`; this is plumbing only. A `role:`/`actor_id:` a caller sends is
|
|
12
|
-
# asserted, not verified, unless Governance is attached.
|
|
13
|
-
#
|
|
14
|
-
# There is no authentication: `domain:` boots real Ruby confined to
|
|
15
|
-
# `Storehouse::BOOT_ROOT`, so this is a code execution surface for anyone who
|
|
16
|
-
# can write to stdin (ADR 0062). Spawning with `HECKS_DOOR_TOOLS=readers` and
|
|
17
|
-
# `HECKS_DOOR_DOMAINS=<dir>[:<dir>...]` narrows a door to read-only tools and
|
|
18
|
-
# named domains (`McpDoorScope`); this limits what one agent can reach and is
|
|
19
|
-
# not itself authentication.
|
|
20
|
-
module McpDoor
|
|
21
|
-
PROTOCOL_VERSION = "2024-11-05".freeze
|
|
22
|
-
|
|
23
|
-
TOOLS = ToolDefinitions::TOOLS
|
|
24
|
-
|
|
25
|
-
NOTES = [
|
|
26
|
-
"Identity is self-asserted: role:/actor_id: are claims made by the caller and are not verified.",
|
|
27
|
-
"state, events, history, follow, describe, catalog and query run with no role check at all.",
|
|
28
|
-
"domain: boots real Ruby under #{Storehouse::BOOT_ROOT}; anyone who can write to stdin can run it."
|
|
29
|
-
].freeze
|
|
30
|
-
|
|
31
|
-
# The method that answers each JSON-RPC method that has a response.
|
|
32
|
-
REPLIES = {
|
|
33
|
-
"initialize" => :initialize_result,
|
|
34
|
-
"tools/list" => :tools_list,
|
|
35
|
-
"tools/call" => :tools_call,
|
|
36
|
-
"ping" => :ping_result
|
|
37
|
-
}.freeze
|
|
38
|
-
|
|
39
|
-
module_function
|
|
40
|
-
|
|
41
|
-
# Read once, so a bad setting refuses to start the door before anything is
|
|
42
|
-
# written to stdout.
|
|
43
|
-
def scope
|
|
44
|
-
@scope ||= McpDoorScope.start!(server: CLI::Mcp::SERVER)
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
# The standing notes, then what this door's scope says about `domain:`.
|
|
48
|
-
def warning_notes
|
|
49
|
-
NOTES[0, 2] + (scope.restricted? ? scope.notes : [NOTES[2]])
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
def serve
|
|
53
|
-
McpStdioGuard.warn!(server: CLI::Mcp::SERVER, notes: warning_notes)
|
|
54
|
-
$stdout.sync = true
|
|
55
|
-
|
|
56
|
-
$stdin.each_line { |line| answer_line(line.strip) }
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
# Answers one request line; a line that is not JSON is a parse error, not a crash.
|
|
60
|
-
def answer_line(line)
|
|
61
|
-
return if line.empty?
|
|
62
|
-
|
|
63
|
-
handle_safely(JSON.parse(line))
|
|
64
|
-
rescue JSON::ParserError => e
|
|
65
|
-
send_error(nil, -32_700, "Parse error: #{e.message}")
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
# Handles a request, answering an internal error rather than ending the door.
|
|
69
|
-
def handle_safely(request)
|
|
70
|
-
handle(request)
|
|
71
|
-
rescue StandardError => e
|
|
72
|
-
send_error(request["id"], -32_603, "Internal error: #{e.class}: #{e.message}")
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
# `domain:` is confined to `Storehouse::BOOT_ROOT`, and further to a restricted
|
|
76
|
-
# door's `HECKS_DOOR_DOMAINS`, before anything boots. An unrestricted door boots the
|
|
77
|
-
# domain afresh on every call; a restricted door lives for one spawner's session and
|
|
78
|
-
# keeps each named domain booted until the files of its directory change.
|
|
79
|
-
def boot(domain)
|
|
80
|
-
path = Storehouse.confine!(scope.admit_domain!(domain), "domain")
|
|
81
|
-
return Hecks.boot(path, install_doors: false) unless scope.restricted?
|
|
82
|
-
|
|
83
|
-
resident_boot(path)
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
# The runtime a restricted door keeps for `path`, booted again once its files change.
|
|
87
|
-
def resident_boot(path)
|
|
88
|
-
resident = (@resident ||= {})
|
|
89
|
-
fingerprint = Storehouse.fingerprint(path)
|
|
90
|
-
return resident[path][:runtime] if resident[path] && resident[path][:fingerprint] == fingerprint
|
|
91
|
-
|
|
92
|
-
resident[path] = { fingerprint: fingerprint, runtime: Hecks.boot(path, install_doors: false) }
|
|
93
|
-
resident[path][:runtime]
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
def answer_tool(name, args)
|
|
97
|
-
return scope.refusal(name) unless scope.permits_tool?(name)
|
|
98
|
-
|
|
99
|
-
ToolCalls.call(name, scope.with_default_domain(args))
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
# One line per allowed command: what it takes and the role it declares, read from the booted
|
|
103
|
-
# domain the door serves. Nil when the door serves no single domain or it will not boot, so
|
|
104
|
-
# `tools/list` still answers, with the commands as a bare list.
|
|
105
|
-
def command_guide
|
|
106
|
-
return unless scope.commands_mode? && scope.default_domain
|
|
107
|
-
|
|
108
|
-
McpGuideCache.remember(scope.default_domain, scope.allowed_commands) do
|
|
109
|
-
Storehouse.command_guide(boot(scope.default_domain), scope.allowed_commands)
|
|
110
|
-
end
|
|
111
|
-
rescue StandardError
|
|
112
|
-
nil
|
|
113
|
-
end
|
|
114
|
-
|
|
115
|
-
def tool_result(payload)
|
|
116
|
-
{ content: [{ type: "text", text: JSON.pretty_generate(payload) }], isError: payload[:ok] == false }
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
def send_response(id, result)
|
|
120
|
-
puts JSON.generate({ jsonrpc: "2.0", id: id, result: result })
|
|
121
|
-
end
|
|
122
|
-
|
|
123
|
-
def send_error(id, code, message)
|
|
124
|
-
puts JSON.generate({ jsonrpc: "2.0", id: id, error: { code: code, message: message } })
|
|
125
|
-
end
|
|
126
|
-
|
|
127
|
-
# Answers one JSON-RPC request. A notification has no id and gets no response.
|
|
128
|
-
def handle(request)
|
|
129
|
-
id = request["id"]
|
|
130
|
-
method = request["method"]
|
|
131
|
-
reply = REPLIES[method]
|
|
132
|
-
return send_response(id, public_send(reply, request["params"] || {})) if reply
|
|
133
|
-
|
|
134
|
-
send_error(id, -32_601, "Method not found: #{method}") unless id.nil? || method == "notifications/initialized"
|
|
135
|
-
end
|
|
136
|
-
|
|
137
|
-
def initialize_result(_params)
|
|
138
|
-
{ protocolVersion: PROTOCOL_VERSION, capabilities: { tools: {} },
|
|
139
|
-
serverInfo: { name: CLI::Mcp::SERVER, version: "1.1.0" } }
|
|
140
|
-
end
|
|
141
|
-
|
|
142
|
-
# The tools this door's scope permits, each presented with the command guide.
|
|
143
|
-
def tools_list(_params)
|
|
144
|
-
served = TOOLS.select { |tool| scope.permits_tool?(tool[:name]) }
|
|
145
|
-
guide = command_guide
|
|
146
|
-
{ tools: served.map { |tool| scope.present(tool, guide) } }
|
|
147
|
-
end
|
|
148
|
-
|
|
149
|
-
def tools_call(params)
|
|
150
|
-
tool_result(answer_tool(params["name"], params["arguments"] || {}))
|
|
151
|
-
end
|
|
152
|
-
|
|
153
|
-
def ping_result(_params) = {}
|
|
154
|
-
end
|
|
155
|
-
end
|
|
156
|
-
end
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
require_relative "../../runtime/errors"
|
|
2
|
-
|
|
3
|
-
module Hecks
|
|
4
|
-
module Doors
|
|
5
|
-
class McpDoorScope
|
|
6
|
-
# What a restricted door lets an argument say: a denied name is refused, a path must resolve
|
|
7
|
-
# inside the root with symlinks followed, and a git ref must be a plain name.
|
|
8
|
-
module ArgumentPolicy
|
|
9
|
-
module_function
|
|
10
|
-
|
|
11
|
-
# Checks every argument in `args`, nested or not, and refuses the first one the policy
|
|
12
|
-
# forbids.
|
|
13
|
-
#
|
|
14
|
-
# @param args [Hash, Array, nil] the arguments as the caller gave them
|
|
15
|
-
# @param label [String] how the door describes its mode, for the refusal
|
|
16
|
-
# @raise [Runtime::TypeMismatch] on the first argument refused
|
|
17
|
-
def admit!(args, label)
|
|
18
|
-
each_argument(args) do |name, value|
|
|
19
|
-
if DENIED_ARGUMENTS.include?(name)
|
|
20
|
-
refuse!(name, label, "never passes it on: it names a host, a binary, an output or a change of state")
|
|
21
|
-
end
|
|
22
|
-
admit_path!(name, value, label) if PATH_ARGUMENTS.include?(name)
|
|
23
|
-
admit_ref!(name, value, label) if REF_ARGUMENTS.include?(name)
|
|
24
|
-
end
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
# Yields every argument name with its value, descending into nested objects and lists, so
|
|
28
|
-
# `{"file" => {"value" => "x"}}` is checked the way `{"file" => "x"}` is.
|
|
29
|
-
def each_argument(args, &visit)
|
|
30
|
-
case args
|
|
31
|
-
when Hash
|
|
32
|
-
args.each do |name, value|
|
|
33
|
-
yield(name.to_s, value)
|
|
34
|
-
each_argument(value, &visit)
|
|
35
|
-
end
|
|
36
|
-
when Array
|
|
37
|
-
args.each { |item| each_argument(item, &visit) }
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
# A value written as an object of one field (`{"value" => "x"}`) is that field's value.
|
|
42
|
-
def scalar(value)
|
|
43
|
-
return value unless value.is_a?(Hash)
|
|
44
|
-
|
|
45
|
-
value.fetch("value") { value.fetch(:value, value) }
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
def refuse!(name, label, why)
|
|
49
|
-
raise Runtime::TypeMismatch, "argument: #{name.inspect} is refused: this door runs in #{label} and #{why}"
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
def admit_path!(name, value, label)
|
|
53
|
-
Array(scalar(value)).flat_map { |item| item.to_s.split(",") }.reject(&:empty?).each do |path|
|
|
54
|
-
next if !path.include?(":") && inside_root?(path)
|
|
55
|
-
|
|
56
|
-
refuse!(name, label, "passes a path only when it resolves inside #{Storehouse::BOOT_ROOT} " \
|
|
57
|
-
"and holds no colon: #{path.inspect}")
|
|
58
|
-
end
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
def admit_ref!(name, value, label)
|
|
62
|
-
ref = scalar(value).to_s
|
|
63
|
-
return if ref.match?(PLAIN_REF) && !ref.include?("..")
|
|
64
|
-
|
|
65
|
-
refuse!(name, label, "passes only a plain git ref: #{ref.inspect}")
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
def inside_root?(path)
|
|
69
|
-
root = resolve_real(Storehouse::BOOT_ROOT)
|
|
70
|
-
resolved = resolve_real(File.expand_path(path, Storehouse::BOOT_ROOT))
|
|
71
|
-
resolved == root || resolved.start_with?("#{root}#{File::SEPARATOR}")
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
# The real path of `path`, symlinks followed: of the whole path when it exists, else of its
|
|
75
|
-
# deepest existing parent with the missing names put back, so a link above a file that does
|
|
76
|
-
# not exist yet still shows where the file would land.
|
|
77
|
-
def resolve_real(path)
|
|
78
|
-
missing = []
|
|
79
|
-
current = File.expand_path(path)
|
|
80
|
-
until File.exist?(current)
|
|
81
|
-
parent = File.dirname(current)
|
|
82
|
-
break if parent == current
|
|
83
|
-
|
|
84
|
-
missing.unshift(File.basename(current))
|
|
85
|
-
current = parent
|
|
86
|
-
end
|
|
87
|
-
File.join(File.realpath(current), *missing)
|
|
88
|
-
end
|
|
89
|
-
end
|
|
90
|
-
end
|
|
91
|
-
end
|
|
92
|
-
end
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
module Hecks
|
|
2
|
-
module Doors
|
|
3
|
-
class McpDoorScope
|
|
4
|
-
# What is wrong with the `HECKS_DOOR_*` variables a door was spawned with, as the sentences
|
|
5
|
-
# that refuse to start it.
|
|
6
|
-
module EnvProblems
|
|
7
|
-
module_function
|
|
8
|
-
|
|
9
|
-
# Every problem in `env`: a variable the door does not take, then those of the mode.
|
|
10
|
-
def setting_problems(env)
|
|
11
|
-
known = [TOOLS_VARIABLE, DOMAINS_VARIABLE, COMMANDS_VARIABLE]
|
|
12
|
-
problems = (env.keys.select { |name| name.start_with?(ENV_PREFIX) } - known).map do |name|
|
|
13
|
-
"environment #{name} is not accepted; the door takes only #{known.join(", ")}"
|
|
14
|
-
end
|
|
15
|
-
problems + mode_problems(env[TOOLS_VARIABLE], env[DOMAINS_VARIABLE], env[COMMANDS_VARIABLE])
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
def mode_problems(tools, domains, commands)
|
|
19
|
-
return stray_problems(domains, commands) if tools.nil?
|
|
20
|
-
unless MODES.include?(tools)
|
|
21
|
-
return ["#{TOOLS_VARIABLE}=#{tools.inspect} is not accepted; the values are #{MODES.map(&:inspect).join(" and ")}"]
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
problems = []
|
|
25
|
-
if split_paths(domains.to_s).empty?
|
|
26
|
-
problems << "#{TOOLS_VARIABLE}=#{tools} needs #{DOMAINS_VARIABLE}, the domains this door may read"
|
|
27
|
-
end
|
|
28
|
-
problems + command_problems(tools, commands)
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
def stray_problems(domains, commands)
|
|
32
|
-
stray = { DOMAINS_VARIABLE => domains, COMMANDS_VARIABLE => commands }.compact.keys
|
|
33
|
-
return [] if stray.empty?
|
|
34
|
-
|
|
35
|
-
["#{stray.join(" and ")} set without #{TOOLS_VARIABLE}=#{READERS} or #{TOOLS_VARIABLE}=#{COMMANDS}; " \
|
|
36
|
-
"they only narrow a restricted door"]
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
def command_problems(tools, commands)
|
|
40
|
-
if tools == COMMANDS && split_names(commands).empty?
|
|
41
|
-
["#{TOOLS_VARIABLE}=#{COMMANDS} needs #{COMMANDS_VARIABLE}, the commands this door may dispatch"]
|
|
42
|
-
elsif tools == READERS && !commands.nil?
|
|
43
|
-
["#{COMMANDS_VARIABLE} is set in reader mode; it only narrows a commands door"]
|
|
44
|
-
else
|
|
45
|
-
[]
|
|
46
|
-
end
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
def split_paths(value) = value.to_s.split(File::PATH_SEPARATOR).reject(&:empty?)
|
|
50
|
-
|
|
51
|
-
def split_names(value) = value.to_s.split(",").map(&:strip).reject(&:empty?).uniq
|
|
52
|
-
end
|
|
53
|
-
end
|
|
54
|
-
end
|
|
55
|
-
end
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
module Hecks
|
|
2
|
-
module Doors
|
|
3
|
-
class McpDoorScope
|
|
4
|
-
# A tool definition as `tools/list` shows it to one door, and the notes the door starts with.
|
|
5
|
-
module Presenter
|
|
6
|
-
module_function
|
|
7
|
-
|
|
8
|
-
# The tool as `tools/list` shows it. A door that serves one domain makes `domain:` optional.
|
|
9
|
-
# On a commands door, `dispatch` also names the commands it serves as an enum, so a caller
|
|
10
|
-
# sees them as typed choices instead of finding out by being refused, and says what each
|
|
11
|
-
# takes and which role it declares.
|
|
12
|
-
#
|
|
13
|
-
# @param scope [McpDoorScope] the door's scope
|
|
14
|
-
# @param tool [Hash] a tool definition from `McpDoor::TOOLS`
|
|
15
|
-
# @param guide [Array<String>, nil] one line per allowed command
|
|
16
|
-
# (`Storehouse.command_guide`)
|
|
17
|
-
# @return [Hash] the definition, narrowed for this door
|
|
18
|
-
def present(scope, tool, guide)
|
|
19
|
-
tool = domain_optional(tool, scope.default_domain) if scope.default_domain
|
|
20
|
-
return tool unless scope.commands_mode? && tool[:name] == "dispatch"
|
|
21
|
-
|
|
22
|
-
narrow_dispatch(tool, guide, scope.allowed_commands)
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
# `dispatch`, with the commands the door serves as typed choices.
|
|
26
|
-
def narrow_dispatch(tool, guide, commands)
|
|
27
|
-
properties = tool[:inputSchema][:properties]
|
|
28
|
-
steps = properties[:steps]
|
|
29
|
-
narrowed = properties.merge(command: with_enum(properties[:command], commands),
|
|
30
|
-
steps: steps.merge(items: step_items(steps[:items], commands)))
|
|
31
|
-
tool.merge(description: dispatch_description(tool[:description], guide, commands),
|
|
32
|
-
inputSchema: tool[:inputSchema].merge(properties: narrowed))
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
def domain_optional(tool, default)
|
|
36
|
-
schema = tool[:inputSchema]
|
|
37
|
-
return tool unless schema[:properties].key?(:domain)
|
|
38
|
-
|
|
39
|
-
note = "Optional: this door serves one domain, #{default}, and uses it when you leave this out."
|
|
40
|
-
properties = schema[:properties].merge(domain: schema[:properties][:domain].merge(description: note))
|
|
41
|
-
tool.merge(inputSchema: schema.merge(properties: properties, required: schema[:required] - ["domain"]))
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
def dispatch_description(base, guide, commands)
|
|
45
|
-
intro = "#{base} This door dispatches only the commands below, each with the role it declares: pass " \
|
|
46
|
-
"that role as `role`. A command that takes `run` takes a key you choose, and the call answers " \
|
|
47
|
-
"the record as it stands once its reactions have run; an argument marked * is required."
|
|
48
|
-
entries = Array(guide).empty? ? commands : guide
|
|
49
|
-
([intro] + entries.map { |entry| "- #{entry}" }).join("\n")
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
def with_enum(property, commands)
|
|
53
|
-
property.merge(enum: commands)
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
def step_items(items, commands)
|
|
57
|
-
items.merge(properties: items[:properties].merge(command: with_enum(items[:properties][:command], commands)))
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
# What a reader door says about itself when it starts.
|
|
61
|
-
def reader_notes(domains)
|
|
62
|
-
["Reader mode (#{TOOLS_VARIABLE}=#{READERS}): serves #{READER_TOOLS.join(", ")}; " \
|
|
63
|
-
"refuses dispatch (with dry_run and steps), behaviors and every other tool.",
|
|
64
|
-
"domain: boots only #{DOMAINS_VARIABLE}: #{domains.join(", ")}.",
|
|
65
|
-
"Reader mode limits reach and identifies no one; it is not authentication."]
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
# What a commands door says about itself when it starts.
|
|
69
|
-
def commands_notes(domains, commands)
|
|
70
|
-
["Commands mode (#{TOOLS_VARIABLE}=#{COMMANDS}): serves #{READER_TOOLS.join(", ")} and dispatch of only " \
|
|
71
|
-
"#{COMMANDS_VARIABLE}: #{commands.join(", ")}; refuses behaviors and every other tool.",
|
|
72
|
-
"domain: boots only #{DOMAINS_VARIABLE}: #{domains.join(", ")}.",
|
|
73
|
-
"Commands mode limits reach and identifies no one; role: stays self-asserted and it is not authentication."]
|
|
74
|
-
end
|
|
75
|
-
end
|
|
76
|
-
end
|
|
77
|
-
end
|
|
78
|
-
end
|
|
@@ -1,201 +0,0 @@
|
|
|
1
|
-
require_relative "../runtime/errors"
|
|
2
|
-
|
|
3
|
-
module Hecks
|
|
4
|
-
module Doors
|
|
5
|
-
# What a spawned hecks mcp may do: every tool by default; only reader tools against named
|
|
6
|
-
# domains in reader mode; and in commands mode those plus `dispatch` of a closed list of
|
|
7
|
-
# commands — reach, not identity (ADR 0072, ADR 0089).
|
|
8
|
-
class McpDoorScope
|
|
9
|
-
ENV_PREFIX = "HECKS_DOOR_".freeze
|
|
10
|
-
TOOLS_VARIABLE = "HECKS_DOOR_TOOLS".freeze
|
|
11
|
-
DOMAINS_VARIABLE = "HECKS_DOOR_DOMAINS".freeze
|
|
12
|
-
COMMANDS_VARIABLE = "HECKS_DOOR_COMMANDS".freeze
|
|
13
|
-
READERS = "readers".freeze
|
|
14
|
-
COMMANDS = "commands".freeze
|
|
15
|
-
MODES = [READERS, COMMANDS].freeze
|
|
16
|
-
EXIT_STATUS = 2
|
|
17
|
-
|
|
18
|
-
# The tools a reader door serves — each reads a domain or the door's own
|
|
19
|
-
# audit log and writes no domain state. `validate` still boots its
|
|
20
|
-
# domain, so it stays bound to the allowed set like every other tool.
|
|
21
|
-
READER_TOOLS = %w[query events state catalog describe validate domains history follow].freeze
|
|
22
|
-
|
|
23
|
-
# The tools a commands door serves: the reader tools, and `dispatch` for the allowed commands.
|
|
24
|
-
COMMAND_TOOLS = (READER_TOOLS + %w[dispatch]).freeze
|
|
25
|
-
|
|
26
|
-
# Argument names a restricted door never passes on, whatever command carries them: each one
|
|
27
|
-
# names a host or a URL, a binary to run, a place to write, a port to open, a store to
|
|
28
|
-
# switch to, or flips a command from a preview to a change of state.
|
|
29
|
-
DENIED_ARGUMENTS = %w[
|
|
30
|
-
adapter artifact confirm expected from gem_only header health_path host live no_wait npm_local
|
|
31
|
-
npm_only output path payload payload_file port rust_binary scheme stage_dir state_path stdio url
|
|
32
|
-
write
|
|
33
|
-
].freeze
|
|
34
|
-
|
|
35
|
-
# Argument names whose values are paths (a comma-separated list for some). A restricted door
|
|
36
|
-
# passes them on only when each resolves, symlinks followed, inside `Storehouse::BOOT_ROOT`,
|
|
37
|
-
# and holds no colon: `host:repo` and `https://host/x` would otherwise pass as relative paths.
|
|
38
|
-
PATH_ARGUMENTS = %w[dir domain domains file fixture paths root script].freeze
|
|
39
|
-
|
|
40
|
-
# Argument names whose values are git refs: plain names only, so a value cannot read as an
|
|
41
|
-
# option to git.
|
|
42
|
-
REF_ARGUMENTS = %w[commit ref].freeze
|
|
43
|
-
|
|
44
|
-
# What a plain git ref looks like: no leading dash, no `..`, no spaces.
|
|
45
|
-
PLAIN_REF = %r{\A[A-Za-z0-9][A-Za-z0-9._/~^@-]*\z}
|
|
46
|
-
|
|
47
|
-
# Unrestricted when no HECKS_DOOR_* variable is set.
|
|
48
|
-
def self.from_env(env = ENV)
|
|
49
|
-
problems = EnvProblems.setting_problems(env)
|
|
50
|
-
raise ArgumentError, problems.join("\n") unless problems.empty?
|
|
51
|
-
return new(allowed_domains: nil) unless env[TOOLS_VARIABLE]
|
|
52
|
-
|
|
53
|
-
commands = env[TOOLS_VARIABLE] == COMMANDS ? EnvProblems.split_names(env[COMMANDS_VARIABLE]) : nil
|
|
54
|
-
new(allowed_domains: EnvProblems.split_paths(env[DOMAINS_VARIABLE]), allowed_commands: commands)
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
# Refuses to stderr, not stdout, which carries the MCP protocol.
|
|
58
|
-
def self.start!(server:, env: ENV, stderr: $stderr)
|
|
59
|
-
from_env(env)
|
|
60
|
-
rescue ArgumentError, Runtime::TypeMismatch => e
|
|
61
|
-
e.message.each_line { |line| stderr.puts("#{server}: refusing to start: #{line.chomp}") }
|
|
62
|
-
exit(EXIT_STATUS)
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
# The named domains, resolved against Storehouse::BOOT_ROOT; nil when unrestricted.
|
|
66
|
-
attr_reader :allowed_domains
|
|
67
|
-
|
|
68
|
-
# The command names a commands door dispatches, as the spawner wrote them; nil unless
|
|
69
|
-
# the door runs in commands mode.
|
|
70
|
-
attr_reader :allowed_commands
|
|
71
|
-
|
|
72
|
-
def initialize(allowed_domains:, allowed_commands: nil)
|
|
73
|
-
@allowed_domains = allowed_domains&.map { |path| Storehouse.confine!(path, DOMAINS_VARIABLE) }.freeze
|
|
74
|
-
@allowed_commands = allowed_commands&.dup&.freeze
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
def restricted?
|
|
78
|
-
!@allowed_domains.nil?
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
def commands_mode?
|
|
82
|
-
!@allowed_commands.nil?
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
def served_tools
|
|
86
|
-
commands_mode? ? COMMAND_TOOLS : READER_TOOLS
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
def permits_tool?(name)
|
|
90
|
-
!restricted? || served_tools.include?(name)
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
def refusal(name)
|
|
94
|
-
{ ok: false,
|
|
95
|
-
error: "#{name.inspect} is refused: this door runs in #{mode_label}, " \
|
|
96
|
-
"which serves only #{served_tools.join(", ")}" }
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
# Checked before anything boots, so a path outside the allowed set never reaches Kernel.load.
|
|
100
|
-
def admit_domain!(domain)
|
|
101
|
-
return domain unless restricted?
|
|
102
|
-
return domain if @allowed_domains.include?(Storehouse.confine!(domain, "domain"))
|
|
103
|
-
|
|
104
|
-
raise Runtime::TypeMismatch,
|
|
105
|
-
"domain: #{domain.inspect} is refused: this door runs in #{mode_label} " \
|
|
106
|
-
"and reads only #{DOMAINS_VARIABLE}: #{@allowed_domains.join(", ")}"
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
# A commands door dispatches only the commands it was given. A requested name and each
|
|
110
|
-
# allowed name resolve through the alias map `dispatch` uses, and the resolved verbs are
|
|
111
|
-
# compared, so `complete` cannot reach a command of another aggregate that shares its short
|
|
112
|
-
# name, and an allowed name that resolves to nothing admits nothing.
|
|
113
|
-
#
|
|
114
|
-
# @param runtime [Runtime::Dispatcher] the booted domain
|
|
115
|
-
# @param command [String, nil] the command name the caller asked for
|
|
116
|
-
# @param args [Hash, nil] the arguments the caller gave it; see `admit_arguments!`
|
|
117
|
-
# @raise [Runtime::TypeMismatch] when the command is not on the list or an argument is refused
|
|
118
|
-
def admit_command!(runtime, command, args = nil)
|
|
119
|
-
return unless commands_mode?
|
|
120
|
-
|
|
121
|
-
verbs = Storehouse.verbs_for(runtime, [command, *allowed_commands])
|
|
122
|
-
unless verbs.first && verbs.drop(1).include?(verbs.first)
|
|
123
|
-
raise Runtime::TypeMismatch,
|
|
124
|
-
"command: #{command.to_s.inspect} is refused: this door runs in #{mode_label} " \
|
|
125
|
-
"and dispatches only #{COMMANDS_VARIABLE}: #{allowed_commands.join(", ")}"
|
|
126
|
-
end
|
|
127
|
-
|
|
128
|
-
admit_arguments!(args)
|
|
129
|
-
end
|
|
130
|
-
|
|
131
|
-
# Every step of a batch is admitted before any step runs.
|
|
132
|
-
#
|
|
133
|
-
# @param runtime [Runtime::Dispatcher] the booted domain
|
|
134
|
-
# @param steps [Array<Hash>] the batch, each `{"command" => ..., "args" => ...}`
|
|
135
|
-
# @raise [Runtime::TypeMismatch] when a step names a command off the list or a bad argument
|
|
136
|
-
def admit_steps!(runtime, steps)
|
|
137
|
-
Array(steps).each do |step|
|
|
138
|
-
step = {} unless step.is_a?(Hash)
|
|
139
|
-
admit_command!(runtime, step["command"], step["args"])
|
|
140
|
-
end
|
|
141
|
-
end
|
|
142
|
-
|
|
143
|
-
# The arguments a restricted door is about to pass to a command or a question. A command
|
|
144
|
-
# list admits commands, not values, so each argument is checked by its name: a denied name
|
|
145
|
-
# is refused, a path must resolve inside the root with symlinks followed, and a git ref must
|
|
146
|
-
# be a plain name. Other names pass; a domain with an argument that reaches outside its own
|
|
147
|
-
# files should name it in `DENIED_ARGUMENTS` or `PATH_ARGUMENTS` (ADR 0089).
|
|
148
|
-
#
|
|
149
|
-
# @param args [Hash, Array, nil] the arguments as the caller gave them, nested or not
|
|
150
|
-
# @raise [Runtime::TypeMismatch] on the first argument refused
|
|
151
|
-
def admit_arguments!(args)
|
|
152
|
-
return unless restricted?
|
|
153
|
-
|
|
154
|
-
ArgumentPolicy.admit!(args, mode_label)
|
|
155
|
-
end
|
|
156
|
-
|
|
157
|
-
# The one domain a restricted door serves, when it serves exactly one: a call may leave
|
|
158
|
-
# `domain:` out and get it. Nil for an unrestricted door and for one that names several.
|
|
159
|
-
#
|
|
160
|
-
# @return [String, nil] the domain path as `HECKS_DOOR_DOMAINS` named it, resolved
|
|
161
|
-
def default_domain
|
|
162
|
-
@allowed_domains.first if restricted? && @allowed_domains.size == 1
|
|
163
|
-
end
|
|
164
|
-
|
|
165
|
-
# The arguments with the default domain filled in when the caller left it out.
|
|
166
|
-
#
|
|
167
|
-
# @param args [Hash] a tool call's arguments
|
|
168
|
-
# @return [Hash] `args`, with `"domain"` set when the door has a default and none was given
|
|
169
|
-
def with_default_domain(args)
|
|
170
|
-
return args unless default_domain && args["domain"].to_s.strip.empty?
|
|
171
|
-
|
|
172
|
-
args.merge("domain" => default_domain)
|
|
173
|
-
end
|
|
174
|
-
|
|
175
|
-
# The tool as `tools/list` shows it; see `Presenter.present`.
|
|
176
|
-
#
|
|
177
|
-
# @param tool [Hash] a tool definition from `McpDoor::TOOLS`
|
|
178
|
-
# @param guide [Array<String>, nil] one line per allowed command (`Storehouse.command_guide`)
|
|
179
|
-
# @return [Hash] the definition, narrowed for this door
|
|
180
|
-
def present(tool, guide = nil)
|
|
181
|
-
Presenter.present(self, tool, guide)
|
|
182
|
-
end
|
|
183
|
-
|
|
184
|
-
def notes
|
|
185
|
-
return [] unless restricted?
|
|
186
|
-
|
|
187
|
-
commands_mode? ? Presenter.commands_notes(@allowed_domains, @allowed_commands) : Presenter.reader_notes(@allowed_domains)
|
|
188
|
-
end
|
|
189
|
-
|
|
190
|
-
private
|
|
191
|
-
|
|
192
|
-
def mode_label
|
|
193
|
-
commands_mode? ? "commands mode (#{TOOLS_VARIABLE}=#{COMMANDS})" : "reader mode (#{TOOLS_VARIABLE}=#{READERS})"
|
|
194
|
-
end
|
|
195
|
-
end
|
|
196
|
-
end
|
|
197
|
-
end
|
|
198
|
-
|
|
199
|
-
require_relative "mcp_door_scope/env_problems"
|
|
200
|
-
require_relative "mcp_door_scope/argument_policy"
|
|
201
|
-
require_relative "mcp_door_scope/presenter"
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
require "digest"
|
|
2
|
-
require "fileutils"
|
|
3
|
-
require "json"
|
|
4
|
-
require_relative "../cache_dir"
|
|
5
|
-
require_relative "../storehouse"
|
|
6
|
-
require_relative "../bluebook/meta_validator/verdict_cache"
|
|
7
|
-
|
|
8
|
-
module Hecks
|
|
9
|
-
module Doors
|
|
10
|
-
# Remembers the command guide a commands door builds for `tools/list`, so a door that starts
|
|
11
|
-
# again does not boot its domain to describe its commands. A client waits only a moment for a
|
|
12
|
-
# server's tools, and a boot at that point costs more than the wait.
|
|
13
|
-
#
|
|
14
|
-
# An entry is keyed by the domain directory's files, a digest of the hecks code, and the
|
|
15
|
-
# allowed commands, so a change to any of them builds a new guide. The file is JSON, read only
|
|
16
|
-
# from a directory and a file that only this user can write (the verdict cache's own check),
|
|
17
|
-
# and only when it is a list of strings: a guide is text an agent reads as instructions.
|
|
18
|
-
module McpGuideCache
|
|
19
|
-
module_function
|
|
20
|
-
|
|
21
|
-
# The guide for a domain and its allowed commands, from the cache or else from the block,
|
|
22
|
-
# which is then remembered. A cache that cannot be written is not an error: the next start
|
|
23
|
-
# builds the guide again.
|
|
24
|
-
#
|
|
25
|
-
# @param domain [String] the domain directory the guide describes
|
|
26
|
-
# @param commands [Array<String>] the allowed command names
|
|
27
|
-
# @yieldreturn [Array<String>, nil] the guide, built when none is remembered
|
|
28
|
-
# @return [Array<String>, nil] the guide
|
|
29
|
-
def remember(domain, commands)
|
|
30
|
-
file = File.join(dir, "#{key(domain, commands)}.json")
|
|
31
|
-
cached = read(file)
|
|
32
|
-
return cached if cached
|
|
33
|
-
|
|
34
|
-
guide = yield
|
|
35
|
-
write(file, guide) if guide.is_a?(Array) && guide.any?
|
|
36
|
-
guide
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
# @return [String] the directory entries live in
|
|
40
|
-
def dir = Hecks::CacheDir.path("mcp_door_guides")
|
|
41
|
-
|
|
42
|
-
# @param domain [String] the domain directory
|
|
43
|
-
# @param commands [Array<String>] the allowed command names
|
|
44
|
-
# @return [String] a hex SHA-256 over the domain's files, the hecks code and the commands
|
|
45
|
-
def key(domain, commands)
|
|
46
|
-
parts = [Storehouse.fingerprint(domain), Bluebook::MetaValidator::VerdictCache.code_digest,
|
|
47
|
-
commands.sort.join(",")]
|
|
48
|
-
Digest::SHA256.hexdigest(parts.join("\0"))
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
# @param file [String] an entry's path
|
|
52
|
-
# @return [Array<String>, nil] the guide, or nil when absent, untrusted or malformed
|
|
53
|
-
def read(file)
|
|
54
|
-
return unless File.file?(file) && Bluebook::MetaValidator::VerdictCache.trusted?(file)
|
|
55
|
-
|
|
56
|
-
guide = JSON.parse(File.read(file))
|
|
57
|
-
guide if guide.is_a?(Array) && guide.all?(String)
|
|
58
|
-
rescue StandardError
|
|
59
|
-
nil
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
# @param file [String] an entry's path
|
|
63
|
-
# @param guide [Array<String>] the guide to remember
|
|
64
|
-
# @return [void]
|
|
65
|
-
def write(file, guide)
|
|
66
|
-
FileUtils.mkdir_p(dir, mode: 0o700)
|
|
67
|
-
temp = "#{file}.#{Process.pid}.tmp"
|
|
68
|
-
File.write(temp, JSON.generate(guide), perm: 0o600)
|
|
69
|
-
File.rename(temp, file)
|
|
70
|
-
rescue StandardError
|
|
71
|
-
FileUtils.rm_f(temp) if temp
|
|
72
|
-
end
|
|
73
|
-
end
|
|
74
|
-
end
|
|
75
|
-
end
|