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
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
require_relative "../../runtime/errors"
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
module Adapters
|
|
5
|
+
module Driving
|
|
6
|
+
# What a spawned hecks mcp may do: every tool by default; only reader tools against named
|
|
7
|
+
# domains in reader mode; and in commands mode those plus `dispatch` of a closed list of
|
|
8
|
+
# commands — reach, not identity (ADR 0072, ADR 0089).
|
|
9
|
+
class McpScope
|
|
10
|
+
ENV_PREFIX = "HECKS_SERVER_".freeze
|
|
11
|
+
TOOLS_VARIABLE = "HECKS_SERVER_TOOLS".freeze
|
|
12
|
+
DOMAINS_VARIABLE = "HECKS_SERVER_DOMAINS".freeze
|
|
13
|
+
COMMANDS_VARIABLE = "HECKS_SERVER_COMMANDS".freeze
|
|
14
|
+
READERS = "readers".freeze
|
|
15
|
+
COMMANDS = "commands".freeze
|
|
16
|
+
MODES = [READERS, COMMANDS].freeze
|
|
17
|
+
EXIT_STATUS = 2
|
|
18
|
+
|
|
19
|
+
# The tools a reader server serves — each reads a domain or the server's own
|
|
20
|
+
# audit log and writes no domain state. `validate` still boots its
|
|
21
|
+
# domain, so it stays bound to the allowed set like every other tool.
|
|
22
|
+
READER_TOOLS = %w[query events state catalog describe validate domains history follow].freeze
|
|
23
|
+
|
|
24
|
+
# The tools a commands server serves: the reader tools, and `dispatch` for the allowed
|
|
25
|
+
# commands.
|
|
26
|
+
COMMAND_TOOLS = (READER_TOOLS + %w[dispatch]).freeze
|
|
27
|
+
|
|
28
|
+
# Argument names a restricted server never passes on, whatever command carries them: each
|
|
29
|
+
# one
|
|
30
|
+
# names a host or a URL, a binary to run, a place to write, a port to open, a store to
|
|
31
|
+
# switch to, or flips a command from a preview to a change of state.
|
|
32
|
+
DENIED_ARGUMENTS = %w[
|
|
33
|
+
adapter artifact confirm expected from gem_only header health_path host live no_wait npm_local
|
|
34
|
+
npm_only output path payload payload_file port rust_binary scheme stage_dir state_path stdio url
|
|
35
|
+
write
|
|
36
|
+
].freeze
|
|
37
|
+
|
|
38
|
+
# Argument names whose values are paths (a comma-separated list for some). A restricted
|
|
39
|
+
# server
|
|
40
|
+
# passes them on only when each resolves, symlinks followed, inside `Storehouse::BOOT_ROOT`,
|
|
41
|
+
# and holds no colon: `host:repo` and `https://host/x` would otherwise pass as relative
|
|
42
|
+
# paths.
|
|
43
|
+
PATH_ARGUMENTS = %w[dir domain domains file fixture paths root script].freeze
|
|
44
|
+
|
|
45
|
+
# Argument names whose values are git refs: plain names only, so a value cannot read as an
|
|
46
|
+
# option to git.
|
|
47
|
+
REF_ARGUMENTS = %w[commit ref].freeze
|
|
48
|
+
|
|
49
|
+
# What a plain git ref looks like: no leading dash, no `..`, no spaces.
|
|
50
|
+
PLAIN_REF = %r{\A[A-Za-z0-9][A-Za-z0-9._/~^@-]*\z}
|
|
51
|
+
|
|
52
|
+
# Unrestricted when no HECKS_SERVER_* variable is set.
|
|
53
|
+
def self.from_env(env = ENV)
|
|
54
|
+
problems = EnvProblems.setting_problems(env)
|
|
55
|
+
raise ArgumentError, problems.join("\n") unless problems.empty?
|
|
56
|
+
return new(allowed_domains: nil) unless env[TOOLS_VARIABLE]
|
|
57
|
+
|
|
58
|
+
commands = env[TOOLS_VARIABLE] == COMMANDS ? EnvProblems.split_names(env[COMMANDS_VARIABLE]) : nil
|
|
59
|
+
new(allowed_domains: EnvProblems.split_paths(env[DOMAINS_VARIABLE]), allowed_commands: commands)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Refuses to stderr, not stdout, which carries the MCP protocol.
|
|
63
|
+
def self.start!(server:, env: ENV, stderr: $stderr)
|
|
64
|
+
from_env(env)
|
|
65
|
+
rescue ArgumentError, Runtime::TypeMismatch => e
|
|
66
|
+
e.message.each_line { |line| stderr.puts("#{server}: refusing to start: #{line.chomp}") }
|
|
67
|
+
exit(EXIT_STATUS)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# The named domains, resolved against Storehouse::BOOT_ROOT; nil when unrestricted.
|
|
71
|
+
attr_reader :allowed_domains
|
|
72
|
+
|
|
73
|
+
# The command names a commands server dispatches, as the spawner wrote them; nil unless
|
|
74
|
+
# the server runs in commands mode.
|
|
75
|
+
attr_reader :allowed_commands
|
|
76
|
+
|
|
77
|
+
def initialize(allowed_domains:, allowed_commands: nil)
|
|
78
|
+
@allowed_domains = allowed_domains&.map { |path| Storehouse.confine!(path, DOMAINS_VARIABLE) }.freeze
|
|
79
|
+
@allowed_commands = allowed_commands&.dup&.freeze
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def restricted?
|
|
83
|
+
!@allowed_domains.nil?
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def commands_mode?
|
|
87
|
+
!@allowed_commands.nil?
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def served_tools
|
|
91
|
+
commands_mode? ? COMMAND_TOOLS : READER_TOOLS
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def permits_tool?(name)
|
|
95
|
+
!restricted? || served_tools.include?(name)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def refusal(name)
|
|
99
|
+
{ ok: false,
|
|
100
|
+
error: "#{name.inspect} is refused: this server runs in #{mode_label}, " \
|
|
101
|
+
"which serves only #{served_tools.join(", ")}" }
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Checked before anything boots, so a path outside the allowed set never reaches
|
|
105
|
+
# Kernel.load.
|
|
106
|
+
def admit_domain!(domain)
|
|
107
|
+
return domain unless restricted?
|
|
108
|
+
return domain if @allowed_domains.include?(Storehouse.confine!(domain, "domain"))
|
|
109
|
+
|
|
110
|
+
raise Runtime::TypeMismatch,
|
|
111
|
+
"domain: #{domain.inspect} is refused: this server runs in #{mode_label} " \
|
|
112
|
+
"and reads only #{DOMAINS_VARIABLE}: #{@allowed_domains.join(", ")}"
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# A commands server dispatches only the commands it was given. A requested name and each
|
|
116
|
+
# allowed name resolve through the alias map `dispatch` uses, and the resolved verbs are
|
|
117
|
+
# compared, so `complete` cannot reach a command of another aggregate that shares its short
|
|
118
|
+
# name, and an allowed name that resolves to nothing admits nothing.
|
|
119
|
+
#
|
|
120
|
+
# @param runtime [Runtime::Dispatcher] the booted domain
|
|
121
|
+
# @param command [String, nil] the command name the caller asked for
|
|
122
|
+
# @param args [Hash, nil] the arguments the caller gave it; see `admit_arguments!`
|
|
123
|
+
# @raise [Runtime::TypeMismatch] when the command is not on the list or an argument is
|
|
124
|
+
# refused
|
|
125
|
+
def admit_command!(runtime, command, args = nil)
|
|
126
|
+
return unless commands_mode?
|
|
127
|
+
|
|
128
|
+
verbs = Storehouse.verbs_for(runtime, [command, *allowed_commands])
|
|
129
|
+
unless verbs.first && verbs.drop(1).include?(verbs.first)
|
|
130
|
+
raise Runtime::TypeMismatch,
|
|
131
|
+
"command: #{command.to_s.inspect} is refused: this server runs in #{mode_label} " \
|
|
132
|
+
"and dispatches only #{COMMANDS_VARIABLE}: #{allowed_commands.join(", ")}"
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
admit_arguments!(args)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Every step of a batch is admitted before any step runs.
|
|
139
|
+
#
|
|
140
|
+
# @param runtime [Runtime::Dispatcher] the booted domain
|
|
141
|
+
# @param steps [Array<Hash>] the batch, each `{"command" => ..., "args" => ...}`
|
|
142
|
+
# @raise [Runtime::TypeMismatch] when a step names a command off the list or a bad argument
|
|
143
|
+
def admit_steps!(runtime, steps)
|
|
144
|
+
Array(steps).each do |step|
|
|
145
|
+
step = {} unless step.is_a?(Hash)
|
|
146
|
+
admit_command!(runtime, step["command"], step["args"])
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# The arguments a restricted server is about to pass to a command or a question. A command
|
|
151
|
+
# list admits commands, not values, so each argument is checked by its name: a denied name
|
|
152
|
+
# is refused, a path must resolve inside the root with symlinks followed, and a git ref must
|
|
153
|
+
# be a plain name. Other names pass; a domain with an argument that reaches outside its own
|
|
154
|
+
# files should name it in `DENIED_ARGUMENTS` or `PATH_ARGUMENTS` (ADR 0089).
|
|
155
|
+
#
|
|
156
|
+
# @param args [Hash, Array, nil] the arguments as the caller gave them, nested or not
|
|
157
|
+
# @raise [Runtime::TypeMismatch] on the first argument refused
|
|
158
|
+
def admit_arguments!(args)
|
|
159
|
+
return unless restricted?
|
|
160
|
+
|
|
161
|
+
ArgumentPolicy.admit!(args, mode_label)
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# The one domain a restricted server serves, when it serves exactly one: a call may leave
|
|
165
|
+
# `domain:` out and get it. Nil for an unrestricted server and for one that names several.
|
|
166
|
+
#
|
|
167
|
+
# @return [String, nil] the domain path as `HECKS_SERVER_DOMAINS` named it, resolved
|
|
168
|
+
def default_domain
|
|
169
|
+
@allowed_domains.first if restricted? && @allowed_domains.size == 1
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# The arguments with the default domain filled in when the caller left it out.
|
|
173
|
+
#
|
|
174
|
+
# @param args [Hash] a tool call's arguments
|
|
175
|
+
# @return [Hash] `args`, with `"domain"` set when the server has a default and none was
|
|
176
|
+
# given
|
|
177
|
+
def with_default_domain(args)
|
|
178
|
+
return args unless default_domain && args["domain"].to_s.strip.empty?
|
|
179
|
+
|
|
180
|
+
args.merge("domain" => default_domain)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# The tool as `tools/list` shows it; see `Presenter.present`.
|
|
184
|
+
#
|
|
185
|
+
# @param tool [Hash] a tool definition from `Mcp::TOOLS`
|
|
186
|
+
# @param guide [Array<String>, nil] one line per allowed command
|
|
187
|
+
# (`Storehouse.command_guide`)
|
|
188
|
+
# @return [Hash] the definition, narrowed for this server
|
|
189
|
+
def present(tool, guide = nil)
|
|
190
|
+
Presenter.present(self, tool, guide)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def notes
|
|
194
|
+
return [] unless restricted?
|
|
195
|
+
|
|
196
|
+
return Presenter.reader_notes(@allowed_domains) unless commands_mode?
|
|
197
|
+
|
|
198
|
+
Presenter.commands_notes(@allowed_domains, @allowed_commands)
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
private
|
|
202
|
+
|
|
203
|
+
def mode_label
|
|
204
|
+
commands_mode? ? "commands mode (#{TOOLS_VARIABLE}=#{COMMANDS})" : "reader mode (#{TOOLS_VARIABLE}=#{READERS})"
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
require_relative "mcp_scope/env_problems"
|
|
212
|
+
require_relative "mcp_scope/argument_policy"
|
|
213
|
+
require_relative "mcp_scope/presenter"
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
require_relative "../../../bluebook/dsl/hecksagon_builder"
|
|
2
|
+
require_relative "../../../bluebook/dsl/world_builder"
|
|
3
|
+
require_relative "../../../bluebook/dsl/domain_port_builder"
|
|
4
|
+
require_relative "../../../bluebook/dsl/const_shim"
|
|
5
|
+
require_relative "../../../bluebook/dsl/binding_proxy"
|
|
6
|
+
require_relative "../../../bluebook/hecksagon"
|
|
7
|
+
require_relative "../handle"
|
|
8
|
+
require_relative "../../../naming"
|
|
9
|
+
|
|
10
|
+
module Hecks
|
|
11
|
+
module Adapters
|
|
12
|
+
module Driving
|
|
13
|
+
module Ruby
|
|
14
|
+
# One aggregate module: creating verbs and queries as module methods, CRUD
|
|
15
|
+
# delegation, and the `.hecksagon` binding/port hooks it lands on.
|
|
16
|
+
module AggregateModule
|
|
17
|
+
# Builds one aggregate module: creating-command methods, query methods,
|
|
18
|
+
# find/all/count/events, project/docs/narrate, and the port/binding hooks.
|
|
19
|
+
#
|
|
20
|
+
# Kept as one method: `:port`/`:method_missing`/`:const_missing` below
|
|
21
|
+
# cross-reference each other and would lose that thread if split up.
|
|
22
|
+
# rubocop:disable-next Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
|
23
|
+
def aggregate_module(dispatcher, domain, aggregate)
|
|
24
|
+
fqn = "#{domain}::#{aggregate.hecks_name}"
|
|
25
|
+
mod = Module.new
|
|
26
|
+
|
|
27
|
+
aggregate.attributes.each do |attribute|
|
|
28
|
+
next unless RESERVED.include?(attribute.name.to_sym)
|
|
29
|
+
|
|
30
|
+
warn "[hecks] #{aggregate.hecks_name}##{attribute.name} shadows a built-in — no reader defined"
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# `!` marks a creating command (Ruby's mutate-and-may-refuse convention).
|
|
34
|
+
# Without it, a query sharing the same business name (e.g. Account's
|
|
35
|
+
# "Open") would collide with the creating command's own bare name.
|
|
36
|
+
aggregate.commands.select(&:creates?).each do |command|
|
|
37
|
+
mod.define_singleton_method("#{Naming.snake(command.hecks_name)}!") do |**args|
|
|
38
|
+
Handle.new(dispatcher: dispatcher, domain: domain, aggregate: aggregate,
|
|
39
|
+
instance: dispatcher.dispatch("#{fqn}.#{command.hecks_name}", with: args).instance)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# A query is a bare module method (no `!`): it just reads and returns
|
|
44
|
+
# the same raw row array `dispatcher.query` itself answers.
|
|
45
|
+
aggregate.queries.each do |query|
|
|
46
|
+
mod.define_singleton_method(Naming.snake(query.hecks_name)) do |**args|
|
|
47
|
+
dispatcher.query("#{fqn}.#{query.hecks_name}", **args)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
mod.define_singleton_method(:fqn) { fqn }
|
|
52
|
+
mod.define_singleton_method(:ir) { aggregate }
|
|
53
|
+
|
|
54
|
+
# Every construct emits its own IR, so an aggregate is a legitimate
|
|
55
|
+
# `project` target too. A chapter-scoped target refuses here via
|
|
56
|
+
# `Projector.admits!` rather than answering a confidently empty result.
|
|
57
|
+
mod.define_singleton_method(:project) do |target, out: nil, **options|
|
|
58
|
+
key = Projector.key_for(target)
|
|
59
|
+
artifact = Projector.call(key, bluebook: aggregate, options: options)
|
|
60
|
+
return artifact unless out
|
|
61
|
+
|
|
62
|
+
Projector.write(artifact, out, as: Projector.emits_for(key))
|
|
63
|
+
end
|
|
64
|
+
mod.define_singleton_method(:repository) { dispatcher.registry.repository(domain, aggregate) }
|
|
65
|
+
mod.define_singleton_method(:commands) do
|
|
66
|
+
aggregate.commands.map { |c| "#{Naming.snake(c.hecks_name)}!" }.sort
|
|
67
|
+
end
|
|
68
|
+
mod.define_singleton_method(:queries) { aggregate.queries.map { |q| Naming.snake(q.hecks_name) }.sort }
|
|
69
|
+
# This chapter's own `:docs` projection, narrowed to this aggregate.
|
|
70
|
+
mod.define_singleton_method(:docs) do |**options|
|
|
71
|
+
Projector.call(:docs, bluebook: dispatcher.registry.bluebook(domain),
|
|
72
|
+
options: options.merge(aggregate: aggregate.hecks_name))
|
|
73
|
+
end
|
|
74
|
+
# Same narrowing as `:docs`, aimed at `:narrate`.
|
|
75
|
+
mod.define_singleton_method(:narrate) do |**options|
|
|
76
|
+
Projector.call(:narrate, bluebook: dispatcher.registry.bluebook(domain),
|
|
77
|
+
options: options.merge(aggregate: aggregate.hecks_name))
|
|
78
|
+
end
|
|
79
|
+
mod.define_singleton_method(:count) { dispatcher.registry.repository(domain, aggregate).count }
|
|
80
|
+
mod.define_singleton_method(:events) { dispatcher.events.select { |event| event.aggregate == fqn } }
|
|
81
|
+
|
|
82
|
+
mod.define_singleton_method(:find) do |id|
|
|
83
|
+
found = dispatcher.registry.repository(domain, aggregate).find(id)
|
|
84
|
+
found && Handle.new(dispatcher: dispatcher, domain: domain, aggregate: aggregate, instance: found)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
mod.define_singleton_method(:all) do |**opts|
|
|
88
|
+
dispatcher.registry.repository(domain, aggregate).all(**opts).map do |instance|
|
|
89
|
+
Handle.new(dispatcher: dispatcher, domain: domain, aggregate: aggregate, instance: instance)
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Re-resolves via `Hecks.current_registry` rather than the closed-over
|
|
94
|
+
# `aggregate`: this module can be stale from an earlier boot, and its own
|
|
95
|
+
# `aggregate` would silently port a discarded one instead of the live one.
|
|
96
|
+
mod.define_singleton_method(:port) do |name, &block|
|
|
97
|
+
current = Hecks.current_registry&.bluebook(domain)&.aggregate(aggregate.hecks_name) or
|
|
98
|
+
raise Bluebook::DSL::Malformed, "#{fqn}.port(#{name.inspect}) called outside a boot"
|
|
99
|
+
|
|
100
|
+
built = Bluebook::DSL::ConstShim.with(->(const) { const }) do
|
|
101
|
+
Bluebook::DSL::DomainPortBuilder.build(name, owner: current.hecks_name, &block)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# A `verb`-shaped port is a plain `Port`, the same struct `Hecks.port` registers —
|
|
105
|
+
# it belongs to no aggregate IR the way an operations-shaped `DomainPort` does, so
|
|
106
|
+
# it takes the registry's `add_port` directly. Mirrors `DSL::BindingProxy#port`,
|
|
107
|
+
# which handles the identical shape on a domain's first in-process boot (before
|
|
108
|
+
# this facade constant exists); this method is what a repeat boot reaches instead.
|
|
109
|
+
if built.is_a?(Bluebook::Port)
|
|
110
|
+
Hecks.current_registry.add_port(built)
|
|
111
|
+
return self
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
built.operations.each do |operation|
|
|
115
|
+
operation.attributes.select(&:reference?).each { |attribute| attribute.type.declared_in = current }
|
|
116
|
+
end
|
|
117
|
+
current.add_port(built)
|
|
118
|
+
self
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# `BindingProxy#ask_via` for a repeat boot, when this module stands where the proxy is.
|
|
122
|
+
mod.define_singleton_method(:ask_via) do |name, port:|
|
|
123
|
+
chapter = Hecks.current_registry&.bluebook(domain)
|
|
124
|
+
Bluebook::AskResolution.pick!(chapter, aggregate.hecks_name, name, port)
|
|
125
|
+
self
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
mod.define_singleton_method(:method_missing) do |verb, *args, **kwargs, &block|
|
|
129
|
+
# A bare call starts a Privacy marking chain (see
|
|
130
|
+
# `BindingProxy#method_missing`), reached here when the module is already
|
|
131
|
+
# installed (a second in-process boot) rather than at parse time.
|
|
132
|
+
return Bluebook::DSL::AttributePath.new(fqn, [verb.to_s]) if args.empty? && kwargs.empty? && !block
|
|
133
|
+
|
|
134
|
+
collector = Bluebook::DSL::HecksagonBuilder.collector
|
|
135
|
+
world = Bluebook::DSL::WorldBuilder.current
|
|
136
|
+
# Inside a `.world` block the same qualified spelling records a world setting.
|
|
137
|
+
return world.record_binding(verb, args, kwargs, block) if world && !collector
|
|
138
|
+
return super(verb, *args, **kwargs, &block) unless collector
|
|
139
|
+
|
|
140
|
+
collector << Bluebook::Bind.new(
|
|
141
|
+
aggregate: fqn,
|
|
142
|
+
verb: verb.to_s,
|
|
143
|
+
adapter: args.first.to_s,
|
|
144
|
+
role: kwargs[:role]&.to_s
|
|
145
|
+
)
|
|
146
|
+
block&.call
|
|
147
|
+
self
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
mod.define_singleton_method(:respond_to_missing?) do |name, include_private = false|
|
|
151
|
+
!Bluebook::DSL::HecksagonBuilder.collector.nil? || !Bluebook::DSL::WorldBuilder.current.nil? ||
|
|
152
|
+
super(name, include_private)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Uses `aggregate.hecks_name`, not the qualified `fqn`: qualifying it here
|
|
156
|
+
# would make `Account::Debit`'s meaning depend on whether a stale module
|
|
157
|
+
# from an earlier boot happens to be sitting on this process.
|
|
158
|
+
mod.define_singleton_method(:const_missing) do |name|
|
|
159
|
+
resolver = Bluebook::DSL::ConstShim.resolver
|
|
160
|
+
return resolver.call("#{aggregate.hecks_name}::#{name}") if resolver
|
|
161
|
+
|
|
162
|
+
super(name)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
mod
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
require_relative "../../../bluebook/dsl/binding_proxy"
|
|
2
|
+
require_relative "../../../bluebook/dsl/const_shim"
|
|
3
|
+
require_relative "../../../bluebook/dsl/hecksagon_builder"
|
|
4
|
+
|
|
5
|
+
module Hecks
|
|
6
|
+
module Adapters
|
|
7
|
+
module Driving
|
|
8
|
+
module Ruby
|
|
9
|
+
# One chapter's module: vision and aggregate list on the singleton, one
|
|
10
|
+
# aggregate module per declared head, and a `const_missing` hook for undeclared names.
|
|
11
|
+
module Chapter
|
|
12
|
+
# Builds the anonymous module that stands for one booted chapter: `vision`,
|
|
13
|
+
# `aggregates`, `docs`, `narrate` and `project` as singleton methods, plus one
|
|
14
|
+
# nested constant per aggregate holding that aggregate module.
|
|
15
|
+
#
|
|
16
|
+
# @param dispatcher [Runtime::Dispatcher, Runtime::RemoteDispatcher] the booted
|
|
17
|
+
# dispatcher each aggregate module closes over
|
|
18
|
+
# @param bluebook [Bluebook::Chapter] the chapter to project into a module
|
|
19
|
+
# @return [Module] a fresh, unnamed module; `Ruby.install` gives it its
|
|
20
|
+
# top-level name
|
|
21
|
+
# @raise [NameError] if an aggregate's name is not a valid constant name
|
|
22
|
+
def chapter_module(dispatcher, bluebook)
|
|
23
|
+
chapter = Module.new
|
|
24
|
+
define_readers(chapter, bluebook)
|
|
25
|
+
define_projections(chapter, bluebook)
|
|
26
|
+
|
|
27
|
+
bluebook.aggregates.each do |aggregate|
|
|
28
|
+
chapter.const_set(aggregate.hecks_name, aggregate_module(dispatcher, bluebook.name, aggregate))
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
define_const_missing(chapter, bluebook)
|
|
32
|
+
chapter
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
# The chapter's vision and its aggregate names.
|
|
38
|
+
def define_readers(chapter, bluebook)
|
|
39
|
+
chapter.define_singleton_method(:vision) { bluebook.vision }
|
|
40
|
+
chapter.define_singleton_method(:aggregates) { bluebook.aggregates.map(&:name).sort }
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# The chapter's documents and projections, each projected from its IR on every call so
|
|
44
|
+
# none can go stale.
|
|
45
|
+
def define_projections(chapter, bluebook)
|
|
46
|
+
chapter.define_singleton_method(:docs) do |**options|
|
|
47
|
+
Projector.call(:docs, bluebook: bluebook, options: options)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# The chapter read back in english, via `Projector::NarrateProjector`.
|
|
51
|
+
chapter.define_singleton_method(:narrate) do |**options|
|
|
52
|
+
Projector.call(:narrate, bluebook: bluebook, options: options)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
define_project(chapter, bluebook)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Projects the chapter's IR through a target; `out:` writes the artifact and
|
|
59
|
+
# the remaining options go to the projector (`audience:` for OIDC).
|
|
60
|
+
#
|
|
61
|
+
# Pizzas.project(Projections::OIDC)
|
|
62
|
+
# Pizzas.project(Projections::Shape, out: "shape.json")
|
|
63
|
+
def define_project(chapter, bluebook)
|
|
64
|
+
chapter.define_singleton_method(:project) do |target, out: nil, **options|
|
|
65
|
+
key = Projector.key_for(target)
|
|
66
|
+
artifact = Projector.call(key, bluebook: bluebook, options: options)
|
|
67
|
+
return artifact unless out
|
|
68
|
+
|
|
69
|
+
Projector.write(artifact, out, as: Projector.emits_for(key))
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Inside a hecksagon a name is a declaration: with a collector open it becomes
|
|
74
|
+
# a `BindingProxy`. Otherwise a scoped reference from a bluebook mid-declaration
|
|
75
|
+
# goes to the `ConstShim` resolver, so it resolves whether or not a facade exists.
|
|
76
|
+
def define_const_missing(chapter, bluebook)
|
|
77
|
+
chapter.define_singleton_method(:const_missing) do |name|
|
|
78
|
+
collector = Bluebook::DSL::HecksagonBuilder.collector
|
|
79
|
+
return Bluebook::DSL::BindingProxy.new("#{bluebook.name}::#{name}", collector) if collector
|
|
80
|
+
|
|
81
|
+
resolver = Bluebook::DSL::ConstShim.resolver
|
|
82
|
+
return resolver.call("#{bluebook.name}::#{name}") if resolver
|
|
83
|
+
|
|
84
|
+
super(name)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
require_relative "../../chapters"
|
|
2
|
+
require_relative "ruby/chapter"
|
|
3
|
+
require_relative "ruby/aggregate_module"
|
|
4
|
+
|
|
5
|
+
module Hecks
|
|
6
|
+
module Adapters
|
|
7
|
+
module Driving
|
|
8
|
+
# The Ruby adapter without classes: anonymous per-boot modules whose singleton methods
|
|
9
|
+
# dispatch by FQN, installed by `Loader.bind_runtime` and replaced whole on the next boot.
|
|
10
|
+
#
|
|
11
|
+
# `persisted_by("Heki")` in a `.hecksagon` file lands on the module's `method_missing`,
|
|
12
|
+
# which records a `Bind` into the open `HecksagonBuilder.collector`.
|
|
13
|
+
module Ruby
|
|
14
|
+
# Names that would shadow the machinery a Handle runs on; a field with one gets no reader.
|
|
15
|
+
RESERVED = %i[id state events reload inspect to_h hash class].freeze
|
|
16
|
+
|
|
17
|
+
extend Chapter
|
|
18
|
+
extend AggregateModule
|
|
19
|
+
|
|
20
|
+
module_function
|
|
21
|
+
|
|
22
|
+
# Installs one top-level module per booted chapter, and one per aggregate, each
|
|
23
|
+
# closing over `dispatcher`, so `Pizzas::Pizza` and the bare `Pizza` both open the
|
|
24
|
+
# module of the boot that ran last.
|
|
25
|
+
#
|
|
26
|
+
# A gem chapter a hecksagon `attaches` installs nothing (`Query`, `Port` stay free).
|
|
27
|
+
# An aggregate sharing its chapter's name gets no second constant. A name that user
|
|
28
|
+
# code or the stdlib already owns is left alone with a warning (see `Namespace.install`).
|
|
29
|
+
#
|
|
30
|
+
# @param dispatcher [Runtime::Dispatcher, Runtime::RemoteDispatcher] the booted
|
|
31
|
+
# dispatcher every installed module dispatches and queries through
|
|
32
|
+
# @return [Runtime::Dispatcher, Runtime::RemoteDispatcher] the same `dispatcher`
|
|
33
|
+
# @raise [NameError] if a chapter or aggregate name is not a valid constant name
|
|
34
|
+
def install(dispatcher)
|
|
35
|
+
Admission.admit!(dispatcher.registry, "Ruby")
|
|
36
|
+
dispatcher.registry.bluebooks.each_value do |bluebook|
|
|
37
|
+
install_chapter(dispatcher, bluebook) unless attached_chapter?(dispatcher.registry, bluebook)
|
|
38
|
+
end
|
|
39
|
+
dispatcher
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Installs one chapter's module, and the top-level shortcut of each of its aggregates.
|
|
43
|
+
def install_chapter(dispatcher, bluebook)
|
|
44
|
+
chapter = chapter_module(dispatcher, bluebook)
|
|
45
|
+
Namespace.install(*placement(bluebook), chapter)
|
|
46
|
+
# A namespaced chapter keeps its aggregates inside its namespace: top-level
|
|
47
|
+
# shortcuts would bring back the collisions the namespace exists to avoid.
|
|
48
|
+
return if bluebook.namespace
|
|
49
|
+
|
|
50
|
+
bluebook.aggregates.each do |aggregate|
|
|
51
|
+
next if aggregate.hecks_name == bluebook.name
|
|
52
|
+
|
|
53
|
+
Namespace.install(Object, aggregate.hecks_name, chapter.const_get(aggregate.hecks_name, false))
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Whether `bluebook` is a gem chapter that a hecksagon attached rather than a domain's own.
|
|
58
|
+
#
|
|
59
|
+
# @param registry [Runtime::Registry] the booted registry
|
|
60
|
+
# @param bluebook [Bluebook::Chapter] the chapter under test
|
|
61
|
+
# @return [Boolean]
|
|
62
|
+
def attached_chapter?(registry, bluebook)
|
|
63
|
+
registry.bounded?(bluebook.name) && Hecks::Chapters.index.key?(bluebook.name)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Where a chapter's module installs: `Object::<name>`, or the constant path its
|
|
67
|
+
# `namespace` names, creating any missing parent modules along the way.
|
|
68
|
+
#
|
|
69
|
+
# @param bluebook [Bluebook::Chapter] the chapter being installed
|
|
70
|
+
# @return [Array(Module, String)] the parent module and the constant name within it
|
|
71
|
+
def placement(bluebook)
|
|
72
|
+
return [Object, bluebook.name] unless bluebook.namespace
|
|
73
|
+
|
|
74
|
+
*parents, leaf = bluebook.namespace.split("::")
|
|
75
|
+
parent = parents.reduce(Object) do |outer, name|
|
|
76
|
+
outer.const_defined?(name, false) ? outer.const_get(name, false) : outer.const_set(name, Module.new)
|
|
77
|
+
end
|
|
78
|
+
[parent, leaf]
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|