hecks 3.0.5 → 3.1.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/exe/hecks +24 -12
- data/lib/hecks/adapters/driven/lambda/client.rb +1 -1
- data/lib/hecks/adapters/driven/lambda.rb +1 -1
- data/lib/hecks/adapters/driven/tenant_provisioner.rb +2 -2
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +59 -32
- data/lib/hecks/bluebook/hexagon.rb +45 -25
- data/lib/hecks/bluebook/model_check.rb +7 -7
- data/lib/hecks/chapters.rb +31 -3
- data/lib/hecks/cli/check_era.rb +1 -1
- data/lib/hecks/cli/deploy_template_diff.rb +1 -1
- data/lib/hecks/cli/doc_coverage.rb +1 -1
- data/lib/hecks/cli/domain_stub.rb +128 -0
- data/lib/hecks/cli/interview_draft.rb +246 -0
- data/lib/hecks/cli/model_check.rb +1 -1
- data/lib/hecks/cli/project_cli.rb +51 -29
- data/lib/hecks/cli/reference.rb +2 -1
- data/lib/hecks/cli/refresh_rspec_runtime_baseline.rb +1 -1
- data/lib/hecks/cli/seed_semantics_corpus.rb +3 -3
- data/lib/hecks/cli/spec_example.rb +1 -1
- data/lib/hecks/cli/stress_concurrency_specs.rb +14 -5
- data/lib/hecks/corpus.rb +11 -8
- data/lib/hecks/deploy/bluebook/deploy.bluebook +85 -12
- data/lib/hecks/deploy/oidc.json +5 -0
- data/lib/hecks/doc/reference.rb +4 -3
- data/lib/hecks/doors/cli_door.rb +13 -13
- data/lib/hecks/doors/cli_runner.rb +52 -32
- data/lib/hecks/doors/launcher_options.rb +5 -5
- data/lib/hecks/doors/mcp_door.rb +40 -18
- data/lib/hecks/doors/mcp_door_scope.rb +244 -24
- data/lib/hecks/doors.rb +1 -1
- data/lib/hecks/embryonaut_bluebook.rb +2 -2
- data/lib/hecks/forms/banking_presentation.rb +1 -1
- data/lib/hecks/framework/bluebook/compliance.bluebook +1 -1
- data/lib/hecks/framework/bluebook/console_settings.bluebook +1 -1
- data/lib/hecks/framework/bluebook/framework.hecksagon +4 -4
- data/lib/hecks/framework.rb +1 -1
- data/lib/hecks/fuzzing/combination_miner.rb +1 -1
- data/lib/hecks/fuzzing/concurrent_dispatch.rb +1 -1
- data/lib/hecks/fuzzing/differential.rb +4 -2
- data/lib/hecks/fuzzing/era_boundary.rb +1 -1
- data/lib/hecks/fuzzing/form_census.rb +2 -2
- data/lib/hecks/fuzzing/generated_domain_check.rb +1 -1
- data/lib/hecks/fuzzing/isolated_boot.rb +10 -3
- data/lib/hecks/fuzzing/persistence_parity.rb +2 -2
- data/lib/hecks/fuzzing/target_capabilities.rb +3 -3
- data/lib/hecks/gate/stages.yml +5 -5
- data/lib/hecks/grammar/grammar.hecksagon +2 -2
- data/lib/hecks/hecks/adapters/deploy_toolchain.rb +4 -4
- data/lib/hecks/hecks/adapters/host_http.rb +4 -3
- data/lib/hecks/hecks/adapters/local_files.rb +59 -0
- data/lib/hecks/hecks/adapters/site_toolchain.adapter +7 -0
- data/lib/hecks/hecks/adapters/site_toolchain.rb +54 -0
- data/lib/hecks/hecks/codebase.bluebook +4 -3
- data/lib/hecks/hecks/context_map.hecksagon +2 -2
- data/lib/hecks/hecks/custodian.bluebook +45 -3
- data/lib/hecks/hecks/hecks.hecksagon +10 -4
- data/lib/hecks/hecks/hecks.world +6 -6
- data/lib/hecks/hecks/site.behaviors +17 -0
- data/lib/hecks/hecks/site.world +10 -0
- data/lib/hecks/language/bluebook/bluebook.hecksagon +1 -1
- data/lib/hecks/language/hecksagon/hecksagon.bluebook +24 -47
- data/lib/hecks/ports/persistence/plugins/era/era_check.rb +4 -4
- data/lib/hecks/projections/deploy/box/settings.rb +248 -0
- data/lib/hecks/projections/deploy/box/templates/box.yaml.tmpl +267 -0
- data/lib/hecks/projections/deploy/box/templates/deploy-box.sh.tmpl +70 -0
- data/lib/hecks/projections/deploy/box/templates/fetch-secrets.sh +21 -0
- data/lib/hecks/projections/deploy/box/templates/rds.yaml.tmpl +165 -0
- data/lib/hecks/projections/deploy/box/templates/render-compose.sh.tmpl +43 -0
- data/lib/hecks/projections/deploy/box.rb +387 -0
- data/lib/hecks/projections/deploy/fargate/cdn.rb +13 -1
- data/lib/hecks/projections/deploy/fargate.rb +1 -1
- data/lib/hecks/projections/deploy/lambda.rb +11 -10
- data/lib/hecks/projections/deploy/scripts/hosting.mk.tmpl +1 -1
- data/lib/hecks/projections/diagrams.rb +2 -2
- data/lib/hecks/projections/site/edge/behaviours.rb +107 -0
- data/lib/hecks/projections/site/edge/checks.rb +137 -0
- data/lib/hecks/projections/site/edge/pattern.rb +50 -0
- data/lib/hecks/projections/site/edge/rules.rb +128 -0
- data/lib/hecks/projections/site/edge.rb +136 -0
- data/lib/hecks/projections/site/regions.rb +44 -0
- data/lib/hecks/projections/site/routes_ts.rb +284 -0
- data/lib/hecks/projections/site/site_cdn.rb +78 -0
- data/lib/hecks/projections/site/table/checks.rb +124 -0
- data/lib/hecks/projections/site/table/row_builder.rb +155 -0
- data/lib/hecks/projections/site/table.rb +98 -0
- data/lib/hecks/projections.rb +2 -0
- data/lib/hecks/projector/cli_projector.rb +122 -104
- data/lib/hecks/quality_control/adapters/agent.adapter +1 -1
- data/lib/hecks/quality_control/adapters/agent.rb +2 -2
- data/lib/hecks/quality_control/cli/qa_concurrency_racer.rb +3 -3
- data/lib/hecks/quality_control/cli/qa_discover_external_domains.rb +8 -8
- data/lib/hecks/quality_control/cli/qa_domain_novelty.rb +3 -3
- data/lib/hecks/quality_control/cli/qa_generated_domains.rb +4 -4
- data/lib/hecks/quality_control/cli/qa_log_bug.rb +2 -2
- data/lib/hecks/quality_control/cli/qa_mine_combinations.rb +6 -5
- data/lib/hecks/quality_control/cli/qa_postgres_migrate.rb +2 -2
- data/lib/hecks/quality_control/cli/qa_postgres_role.rb +5 -5
- data/lib/hecks/quality_control/cli/qa_pr_check.rb +3 -3
- data/lib/hecks/quality_control/cli/qa_sweep/all_mode.rb +3 -3
- data/lib/hecks/quality_control/cli/qa_sweep/checks.rb +2 -2
- data/lib/hecks/quality_control/cli/qa_sweep.rb +10 -9
- data/lib/hecks/quality_control/cli/qa_tick.rb +3 -3
- data/lib/hecks/quality_control/quality_control.bluebook +18 -18
- data/lib/hecks/quality_control/quality_control.hecksagon +1 -1
- data/lib/hecks/runtime/registry/verification.rb +15 -29
- data/lib/hecks/runtime/registry.rb +14 -20
- data/lib/hecks/rust_build/append_optionals.rb +79 -0
- data/lib/hecks/rust_build/conformance_fuzz.rb +2 -1
- data/lib/hecks/rust_build/domain_name.rb +26 -0
- data/lib/hecks/rust_build/kernel_input.rb +58 -0
- data/lib/hecks/rust_build/project_rust/codegen_run.rb +143 -0
- data/lib/hecks/rust_build/project_rust/root_mod.rb +1 -1
- data/lib/hecks/rust_build/project_rust.rb +57 -24
- data/lib/hecks/rust_build/rust_literal.rb +33 -0
- data/lib/hecks/rust_build/write_if_changed.rb +86 -0
- data/lib/hecks/site/bluebook/site.bluebook +205 -0
- data/lib/hecks/site/bluebook/site.hecksagon +5 -0
- data/lib/hecks/site/bluebook/site.ports.hecksagon +15 -0
- data/lib/hecks/sme/bluebook/sme.bluebook +336 -0
- data/lib/hecks/sme/bluebook/sme.hecksagon +5 -0
- data/lib/hecks/storehouse.rb +37 -7
- data/lib/hecks/tenancy/bluebook/tenancy.bluebook +1 -1
- data/lib/hecks/three_zero/forms.yml +88 -88
- data/lib/hecks/tools/ci_gates.rb +1 -1
- data/lib/hecks/tools/deploy_recipe.rb +14 -6
- data/lib/hecks/tools/oidc_manifests.rb +2 -1
- data/lib/hecks/tools/sequence_script.rb +2 -2
- data/lib/hecks/tools/site_routes.rb +152 -0
- data/lib/hecks/tools/tenant_provisioning.rb +1 -1
- data/lib/hecks/tools.rb +1 -0
- data/lib/hecks/version.rb +1 -1
- data/rust/build/src/cargo_sync.rs +1 -1
- data/rust/build/src/pipeline.rs +23 -16
- data/rust/build/src/resolve.rs +39 -6
- data/rust/host/HECKS_RELEASE +1 -1
- data/rust/host/src/api.rs +1 -1
- data/rust/host/src/dispatch.rs +87 -7
- data/rust/host/src/needs.rs +111 -0
- data/rust/parser/src/keywords.rs +7 -6
- data/rust/parser/src/main.rs +4 -3
- data/rust/parser/src/parse/chapter.rs +1 -1
- data/rust/parser/src/parse/hecksagon.rs +25 -10
- data/rust/project/mutations.rb +4 -30
- data/rust/project/naming.rb +8 -28
- data/rust/project/write_if_changed.rb +4 -80
- data/rust/project_rust_pipeline.rb +16 -12
- data/rust/src/kernel/cli.rs +2 -0
- data/rust/src/kernel/needs.rs +74 -1
- metadata +37 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# The Deploy chapter: what `deployed_to(...)` may declare, and the commands that generate, check and
|
|
2
2
|
# compare a deploy recipe, project the OIDC manifests and provision a tenant.
|
|
3
3
|
#
|
|
4
|
-
# `hecks deploy project` and `hecks deploy provision` boot it directly; the Hecks domain attaches it, so
|
|
4
|
+
# `hecks deploy recipe.project` and `hecks deploy tenant.provision` boot it directly; the Hecks domain attaches it, so
|
|
5
5
|
# each command below is also `hecks deploy <verb>`. The targets validate the `deployed_to` shape
|
|
6
6
|
# that `world.bluebook`'s untyped `Setting` map leaves open. Each run below records that it was
|
|
7
7
|
# requested before its DeployToolchain port is asked, and what came of it after. No command
|
|
@@ -10,7 +10,7 @@ Hecks.bluebook "Deploy" do
|
|
|
10
10
|
vision "Generate, check and compare deploy recipes, project OIDC manifests and provision tenants."
|
|
11
11
|
|
|
12
12
|
aggregate "LambdaTarget" do
|
|
13
|
-
description "One domain's validated AWS Lambda deploy target — what deployed_to(\"AwsLambda\") must resolve to before hecks deploy project generates anything from it."
|
|
13
|
+
description "One domain's validated AWS Lambda deploy target — what deployed_to(\"AwsLambda\") must resolve to before hecks deploy recipe.project generates anything from it."
|
|
14
14
|
|
|
15
15
|
identified_by :domain
|
|
16
16
|
|
|
@@ -38,14 +38,14 @@ Hecks.bluebook "Deploy" do
|
|
|
38
38
|
# (Serverless v2);
|
|
39
39
|
# only the emitted CloudFormation resource differs, never the runtime adapter. "Shared" borrows
|
|
40
40
|
# another domain's instance under its own Postgres schema and needs an `owner` setting, which
|
|
41
|
-
# `hecks deploy project` checks, since the owning domain is a deploy-time fact.
|
|
41
|
+
# `hecks deploy recipe.project` checks, since the owning domain is a deploy-time fact.
|
|
42
42
|
value_object "Database" do
|
|
43
43
|
attribute :value, String, one_of: ["Postgres", "Aurora", "Shared"]
|
|
44
44
|
end
|
|
45
45
|
|
|
46
46
|
# "None" (default) or "Rust": the domain's own Lambda (rust/host) also serves a public web UI
|
|
47
47
|
# in-process (rust/host/src/web.rs). "Ruby" is not a member: a Ruby WebFunction comes from a
|
|
48
|
-
# `lambda_handler.rb` file, and `hecks deploy project` refuses `web "Rust"` alongside one.
|
|
48
|
+
# `lambda_handler.rb` file, and `hecks deploy recipe.project` refuses `web "Rust"` alongside one.
|
|
49
49
|
value_object "Web" do
|
|
50
50
|
attribute :value, String, one_of: ["None", "Rust"]
|
|
51
51
|
end
|
|
@@ -87,7 +87,7 @@ Hecks.bluebook "Deploy" do
|
|
|
87
87
|
|
|
88
88
|
# All required, no `default:`: command-level defaults apply only at the JSON/Rust-codegen
|
|
89
89
|
# boundary, not Ruby's direct dispatch (interpreting.rb `normalize_args`). Defaults are
|
|
90
|
-
# resolved by `hecks deploy project` before it dispatches.
|
|
90
|
+
# resolved by `hecks deploy recipe.project` before it dispatches.
|
|
91
91
|
command "Declare" do
|
|
92
92
|
goal "Validate one domain's AwsLambda deploy target before anything is generated from it"
|
|
93
93
|
|
|
@@ -122,7 +122,7 @@ Hecks.bluebook "Deploy" do
|
|
|
122
122
|
# A sibling of `LambdaTarget` with its own value objects, since value objects are not
|
|
123
123
|
# shared across aggregates.
|
|
124
124
|
aggregate "FargateTarget" do
|
|
125
|
-
description "One domain's validated AWS Fargate deploy target — what deployed_to(\"AwsFargate\") must resolve to before hecks deploy project generates anything from it."
|
|
125
|
+
description "One domain's validated AWS Fargate deploy target — what deployed_to(\"AwsFargate\") must resolve to before hecks deploy recipe.project generates anything from it."
|
|
126
126
|
|
|
127
127
|
identified_by :domain
|
|
128
128
|
|
|
@@ -177,7 +177,7 @@ Hecks.bluebook "Deploy" do
|
|
|
177
177
|
end
|
|
178
178
|
|
|
179
179
|
# All seven required for the same reason as `LambdaTarget.Declare`; this validates a
|
|
180
|
-
# target already resolved by `hecks deploy project`'s defaults.
|
|
180
|
+
# target already resolved by `hecks deploy recipe.project`'s defaults.
|
|
181
181
|
command "Declare" do
|
|
182
182
|
goal "Validate one domain's AwsFargate deploy target before anything is generated from it"
|
|
183
183
|
|
|
@@ -201,10 +201,83 @@ Hecks.bluebook "Deploy" do
|
|
|
201
201
|
end
|
|
202
202
|
end
|
|
203
203
|
|
|
204
|
-
|
|
204
|
+
aggregate "BoxTarget" do
|
|
205
|
+
description "One domain's validated single-box deploy target — what deployed_to(\"AwsBox\") must resolve to before hecks deploy project generates a box and an RDS instance from it"
|
|
206
|
+
|
|
207
|
+
identified_by :domain
|
|
208
|
+
|
|
209
|
+
attribute :domain, DomainName
|
|
210
|
+
attribute :region, Region
|
|
211
|
+
attribute :instance_type, InstanceType
|
|
212
|
+
attribute :volume_gb, VolumeGb
|
|
213
|
+
attribute :database_class, DatabaseClass
|
|
214
|
+
attribute :storage_gb, StorageGb
|
|
215
|
+
|
|
216
|
+
value_object "DomainName" do
|
|
217
|
+
attribute :value, String
|
|
218
|
+
invariant("a domain is named") { !value.to_s.empty? }
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
value_object "Region" do
|
|
222
|
+
attribute :value, String
|
|
223
|
+
invariant("a region is named") { !value.to_s.empty? }
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
# An EC2 instance type such as t4g.medium. The family and size are checked for shape only;
|
|
227
|
+
# EC2 refuses an unknown type at stack creation.
|
|
228
|
+
value_object "InstanceType" do
|
|
229
|
+
attribute :value, String
|
|
230
|
+
invariant("an instance type is family.size, such as t4g.medium") { value.to_s.match?(/\A[a-z][a-z0-9-]*\.[a-z0-9]+\z/) }
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
# The box's root disk, which holds images, the compose files and container logs; the
|
|
234
|
+
# database is on RDS.
|
|
235
|
+
value_object "VolumeGb" do
|
|
236
|
+
attribute :value, Integer
|
|
237
|
+
invariant("a volume is at least 8 GB") { value >= 8 }
|
|
238
|
+
invariant("a volume is at most 16384 GB") { value <= 16384 }
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
# An RDS instance class such as db.t4g.small.
|
|
242
|
+
value_object "DatabaseClass" do
|
|
243
|
+
attribute :value, String
|
|
244
|
+
invariant("a database class is db.family.size, such as db.t4g.small") { value.to_s.match?(/\Adb\.[a-z][a-z0-9-]*\.[a-z0-9]+\z/) }
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
# RDS storage grows with the journal; 20 GB is RDS's gp3 floor.
|
|
248
|
+
value_object "StorageGb" do
|
|
249
|
+
attribute :value, Integer
|
|
250
|
+
invariant("storage is at least 20 GB") { value >= 20 }
|
|
251
|
+
invariant("storage is at most 65536 GB") { value <= 65536 }
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
# All six required for the same reason as `FargateTarget.Declare`; this validates a target
|
|
255
|
+
# already resolved by `hecks deploy project`'s defaults.
|
|
256
|
+
command "Declare" do
|
|
257
|
+
goal "Validate one domain's AwsBox deploy target before anything is generated from it"
|
|
258
|
+
|
|
259
|
+
attribute :domain, DomainName
|
|
260
|
+
attribute :region, Region
|
|
261
|
+
attribute :instance_type, InstanceType
|
|
262
|
+
attribute :volume_gb, VolumeGb
|
|
263
|
+
attribute :database_class, DatabaseClass
|
|
264
|
+
attribute :storage_gb, StorageGb
|
|
265
|
+
|
|
266
|
+
sets :domain
|
|
267
|
+
sets :region
|
|
268
|
+
sets :instance_type
|
|
269
|
+
sets :volume_gb
|
|
270
|
+
sets :database_class
|
|
271
|
+
sets :storage_gb
|
|
272
|
+
|
|
273
|
+
emits "BoxTargetDeclared"
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
# The validated shape of `hecks deploy tenant.provision`, which does the generating and provisioning.
|
|
205
278
|
# Not a saga: each of its steps is idempotent and local, so there is nothing to resume.
|
|
206
279
|
aggregate "Tenant" do
|
|
207
|
-
description "One tenant's validated identity — which realm and Postgres schema `hecks deploy provision` provisions before booting a domain directory under Runtime.boot's own `environment:` overlay for it."
|
|
280
|
+
description "One tenant's validated identity — which realm and Postgres schema `hecks deploy tenant.provision` provisions before booting a domain directory under Runtime.boot's own `environment:` overlay for it."
|
|
208
281
|
|
|
209
282
|
identified_by :slug
|
|
210
283
|
|
|
@@ -268,7 +341,7 @@ Hecks.bluebook "Deploy" do
|
|
|
268
341
|
transition "Abandon" => "refused", from: "requested"
|
|
269
342
|
end
|
|
270
343
|
|
|
271
|
-
# `hecks deploy provision <directory> slug=s domain= realm= schema= database= [adapter=]`.
|
|
344
|
+
# `hecks deploy tenant.provision <directory> slug=s domain= realm= schema= database= [adapter=]`.
|
|
272
345
|
# The only command that creates a tenant: its slug, schema and realm are validated by the value
|
|
273
346
|
# objects' patterns and invariants, then the overlay is written through the TenantProvisioning
|
|
274
347
|
# port; the adapter answers PostgresEra when `adapter` is left out. `dry_run?` on this verb
|
|
@@ -424,7 +497,7 @@ Hecks.bluebook "Deploy" do
|
|
|
424
497
|
transition "Fault" => "faulted", from: "requested"
|
|
425
498
|
end
|
|
426
499
|
|
|
427
|
-
# `hecks deploy project <domain> [tenant=] [schema=] [out=] [environment=]`. A schema override
|
|
500
|
+
# `hecks deploy recipe.project <domain> [tenant=] [schema=] [out=] [environment=]`. A schema override
|
|
428
501
|
# needs its tenant; the generator says so, and a domain with no `deployed_to` target, or one
|
|
429
502
|
# whose settings conflict, is faulted with the generator's own sentence.
|
|
430
503
|
command "Project" do
|
|
@@ -630,7 +703,7 @@ Hecks.bluebook "Deploy" do
|
|
|
630
703
|
transition "Refuse" => "refused", from: "requested"
|
|
631
704
|
end
|
|
632
705
|
|
|
633
|
-
# `hecks deploy diff <before> after=<template> [--json] [--strict]`. `--strict` also counts
|
|
706
|
+
# `hecks deploy template_comparison.diff <before> after=<template> [--json] [--strict]`. `--strict` also counts
|
|
634
707
|
# cosmetic differences (comments, key order); `--json` writes the report as JSON.
|
|
635
708
|
command "Diff" do
|
|
636
709
|
goal "Compare two CloudFormation templates and report what differs by logical id"
|
data/lib/hecks/deploy/oidc.json
CHANGED
data/lib/hecks/doc/reference.rb
CHANGED
|
@@ -4,7 +4,8 @@ require_relative "../naming"
|
|
|
4
4
|
module Hecks
|
|
5
5
|
module Doc
|
|
6
6
|
# The DSL reference, projected from the language's own Syntax chapter so
|
|
7
|
-
# its tables can never drift from the live builders. Regenerate with
|
|
7
|
+
# its tables can never drift from the live builders. Regenerate with
|
|
8
|
+
# `hecks language_run.project_reference`.
|
|
8
9
|
module Reference
|
|
9
10
|
GENERATED_END = "<!-- generated:end -->".freeze
|
|
10
11
|
TODO_SENTINEL = "<!-- TODO: document this word -->".freeze
|
|
@@ -84,7 +85,7 @@ module Hecks
|
|
|
84
85
|
|
|
85
86
|
*The tables on this page are generated from the language's own
|
|
86
87
|
aggregate-local syntax tables (`lib/hecks/language/**/*.bluebook`)
|
|
87
|
-
by `hecks project_reference` — do not edit inside the markers. The prose
|
|
88
|
+
by `hecks language_run.project_reference` — do not edit inside the markers. The prose
|
|
88
89
|
between them is hand-written and survives regeneration.*
|
|
89
90
|
#{GENERATED_END}
|
|
90
91
|
#{"\n#{preamble}\n" unless preamble.empty?}
|
|
@@ -172,7 +173,7 @@ module Hecks
|
|
|
172
173
|
# The DSL reference
|
|
173
174
|
|
|
174
175
|
One page per context — the place in a file where a word may be
|
|
175
|
-
typed. Generated by `hecks project_reference` from the Syntax chapter;
|
|
176
|
+
typed. Generated by `hecks language_run.project_reference` from the Syntax chapter;
|
|
176
177
|
the prose between the generated markers is hand-written and
|
|
177
178
|
survives regeneration.
|
|
178
179
|
|
data/lib/hecks/doors/cli_door.rb
CHANGED
|
@@ -11,17 +11,17 @@ module Hecks
|
|
|
11
11
|
|
|
12
12
|
module_function
|
|
13
13
|
|
|
14
|
-
# Builds a
|
|
14
|
+
# Builds a command's nested, typed argument Hash from its `name=value` words.
|
|
15
15
|
#
|
|
16
16
|
# arguments(spec, ["reference.value=A-1", "sequence.value=99"])
|
|
17
17
|
# # => { reference: { value: "A-1" }, sequence: { value: 99 } }
|
|
18
18
|
#
|
|
19
|
-
# @param spec [Hash{Symbol => Object}] one
|
|
20
|
-
# @param pairs [Array<String>] the words after the
|
|
21
|
-
# be the short form of a single-field value object (`reference` for `reference.value`);
|
|
22
|
-
# `--name` for a Boolean; and at most one bare word, which fills the
|
|
19
|
+
# @param spec [Hash{Symbol => Object}] one command's entry from `Projector::CliProjector`
|
|
20
|
+
# @param pairs [Array<String>] the words after the command: `name=value` pairs, where a path
|
|
21
|
+
# may be the short form of a single-field value object (`reference` for `reference.value`);
|
|
22
|
+
# `--name` for a Boolean; and at most one bare word, which fills the first argument
|
|
23
23
|
# @return [Hash{Symbol => Object}] the arguments nested by path, leaves cast to type
|
|
24
|
-
# @raise [Runtime::NotFound] if a path, flag or bare word does not fit the
|
|
24
|
+
# @raise [Runtime::NotFound] if a path, flag or bare word does not fit the command
|
|
25
25
|
# @raise [Runtime::TypeMismatch] if a value does not parse as its Integer or Float
|
|
26
26
|
def arguments(spec, pairs)
|
|
27
27
|
# Extra accepted arguments stay out of help, which teaches only to=....
|
|
@@ -44,7 +44,7 @@ module Hecks
|
|
|
44
44
|
|
|
45
45
|
# Rewrites the short forms into `name=value`: `--name` for a Boolean (a following `yes`,
|
|
46
46
|
# `no`, `true`, `false`, `on`, `off`, `1` or `0` is that flag's value, not an argument),
|
|
47
|
-
# `--name=value` as `name=value`, and one bare word as the
|
|
47
|
+
# `--name=value` as `name=value`, and one bare word as the command's first argument (`to` for
|
|
48
48
|
# a command on an existing aggregate, the first attribute for one that creates).
|
|
49
49
|
def normalize(spec, words, options)
|
|
50
50
|
queue = words.dup
|
|
@@ -84,20 +84,20 @@ module Hecks
|
|
|
84
84
|
path = options.key?(name) ? name : expand(name, options)
|
|
85
85
|
return path if options.dig(path, :type) == "Boolean"
|
|
86
86
|
|
|
87
|
-
raise Runtime::NotFound, "--#{name} is a flag, but this
|
|
87
|
+
raise Runtime::NotFound, "--#{name} is a flag, but this command has no Boolean argument #{name.inspect}"
|
|
88
88
|
end
|
|
89
89
|
|
|
90
|
-
# The one bare word, as a pair for the
|
|
90
|
+
# The one bare word, as a pair for the command's first argument the launcher does not mint.
|
|
91
91
|
def positional(word, spec)
|
|
92
92
|
first = spec[:arguments].find { |argument| !argument[:minted] }
|
|
93
93
|
return "#{first[:path]}=#{word}" if first
|
|
94
94
|
|
|
95
95
|
if spec[:arguments].any?
|
|
96
|
-
raise Runtime::NotFound, "#{word.inspect} is not name=value; this
|
|
96
|
+
raise Runtime::NotFound, "#{word.inspect} is not name=value; this command's only argument is its run key, " \
|
|
97
97
|
"which is minted when omitted (run=<key> names one)"
|
|
98
98
|
end
|
|
99
99
|
|
|
100
|
-
raise Runtime::NotFound, "#{word.inspect} is not name=value, and this
|
|
100
|
+
raise Runtime::NotFound, "#{word.inspect} is not name=value, and this command takes no arguments"
|
|
101
101
|
end
|
|
102
102
|
|
|
103
103
|
# Words the refusal for more than one unnamed argument.
|
|
@@ -187,9 +187,9 @@ module Hecks
|
|
|
187
187
|
hash
|
|
188
188
|
end
|
|
189
189
|
|
|
190
|
-
# Words the refusal for an argument the
|
|
190
|
+
# Words the refusal for an argument the command does not take, listing what it does.
|
|
191
191
|
def unknown(path, known)
|
|
192
|
-
"no argument #{path.inspect} — this
|
|
192
|
+
"no argument #{path.inspect} — this command takes #{known.sort.join(', ')}"
|
|
193
193
|
end
|
|
194
194
|
end
|
|
195
195
|
end
|
|
@@ -13,6 +13,8 @@ module Hecks
|
|
|
13
13
|
module CliRunner
|
|
14
14
|
# The class an adapter raises when the tool it wraps refuses.
|
|
15
15
|
TOOL_REFUSAL = "Hecks::Adapters::ConsoleCapture::Failure".freeze
|
|
16
|
+
# The words that open the query namespace; `ask` is the older spelling.
|
|
17
|
+
QUERY_WORDS = %w[query ask].freeze
|
|
16
18
|
|
|
17
19
|
module_function
|
|
18
20
|
|
|
@@ -20,7 +22,7 @@ module Hecks
|
|
|
20
22
|
# The booted domain's own chapter is projected, or an attached one its first word names.
|
|
21
23
|
#
|
|
22
24
|
# @param runtime [Runtime::Dispatcher, Runtime::RemoteDispatcher] the booted domain
|
|
23
|
-
# @param argv [Array<String>] an optional `ask
|
|
25
|
+
# @param argv [Array<String>] an optional `query` (or `ask`), the command or query name, then
|
|
24
26
|
# `path=value` pairs; `--help`, `-h`, `help` or nothing answers usage
|
|
25
27
|
# @param program [String] how the caller was invoked, echoed in usage and hints
|
|
26
28
|
# @return [Array(String, Integer)] the text and the status: 0 answered, 1 refused or misused
|
|
@@ -71,7 +73,7 @@ module Hecks
|
|
|
71
73
|
cursor = nil
|
|
72
74
|
loop do
|
|
73
75
|
ask = cursor ? args.except(:from_now).merge(since: { value: cursor }) : args
|
|
74
|
-
rows = runtime.query(spec[:
|
|
76
|
+
rows = runtime.query(spec[:command], **ask)
|
|
75
77
|
row = rows.first || {}
|
|
76
78
|
Array(row[:events]).each { |event| out.puts(JSON.generate(entry_line(JsonDoor.materialize(event)))) }
|
|
77
79
|
out.flush
|
|
@@ -93,23 +95,23 @@ module Hecks
|
|
|
93
95
|
# Answers a command line that asks only for usage, from the projection alone.
|
|
94
96
|
#
|
|
95
97
|
# Needs a registry and no bound adapter, so a `Runtime::Loader::Described` serves: the
|
|
96
|
-
# help text, the no-argument usage, a
|
|
98
|
+
# help text, the no-argument usage, a command's `--help` and an unknown one's hint all come
|
|
97
99
|
# out byte-identical to `call`'s.
|
|
98
100
|
#
|
|
99
101
|
# @param runtime [#registry] a booted domain, or what `Hecks.describe` answers
|
|
100
102
|
# @param argv [Array<String>] as for `call`
|
|
101
103
|
# @param program [String] how the caller was invoked
|
|
102
104
|
# @return [Array(String, Integer), nil] the text and status, or nil when the line would
|
|
103
|
-
# run a
|
|
105
|
+
# run a command or question and so needs a booted domain
|
|
104
106
|
def usage(runtime:, argv:, program: "hecks run")
|
|
105
107
|
resolve(runtime, argv, program)[:answer]
|
|
106
108
|
end
|
|
107
109
|
|
|
108
110
|
# Parses a command line against the projection: either the answer it gives without
|
|
109
|
-
# running anything, or the
|
|
111
|
+
# running anything, or the command it would run.
|
|
110
112
|
#
|
|
111
113
|
# @return [Hash] `{answer: [text, status]}`, or `{spec:, name:, rest:, asking:, program:,
|
|
112
|
-
# bluebook:, launcher:}` for a
|
|
114
|
+
# bluebook:, launcher:}` for a command to dispatch
|
|
113
115
|
def resolve(runtime, argv, program)
|
|
114
116
|
bluebook, argv, program = chapter_for(runtime, argv, program)
|
|
115
117
|
launcher = LauncherOptions.settings(runtime, bluebook.name)
|
|
@@ -119,18 +121,11 @@ module Hecks
|
|
|
119
121
|
name = argv.first
|
|
120
122
|
return { answer: [cli[:usage], 0] } if name.nil? || %w[--help -h help].include?(name)
|
|
121
123
|
|
|
122
|
-
|
|
123
|
-
# of one name.
|
|
124
|
-
asking = name == "ask"
|
|
125
|
-
argv = argv[1..] if asking
|
|
126
|
-
name = argv.first
|
|
124
|
+
asking, name, argv = entry_word(cli, argv)
|
|
127
125
|
return { answer: [cli[:usage], 1] } if name.nil?
|
|
128
126
|
|
|
129
|
-
#
|
|
130
|
-
asking
|
|
131
|
-
|
|
132
|
-
# The alias map lets `create_pizza` and `order.create_pizza` reach the same verb.
|
|
133
|
-
pool = asking ? cli[:questions] : cli[:verbs]
|
|
127
|
+
# The alias map lets `create_pizza` and `order.create_pizza` reach the same command.
|
|
128
|
+
pool = asking ? cli[:questions] : cli[:commands]
|
|
134
129
|
key = cli[:names][asking ? :question : :command][name]
|
|
135
130
|
spec = pool[key]
|
|
136
131
|
return { answer: [unknown(cli, name, asking, program), 1] } unless spec
|
|
@@ -138,7 +133,7 @@ module Hecks
|
|
|
138
133
|
rest = argv[1..]
|
|
139
134
|
if rest.include?("--help")
|
|
140
135
|
help = Projector.call(:cli, bluebook: bluebook,
|
|
141
|
-
options: options.merge(
|
|
136
|
+
options: options.merge(command: name, ask: asking))[:usage]
|
|
142
137
|
return { answer: [help, 0] }
|
|
143
138
|
end
|
|
144
139
|
|
|
@@ -146,6 +141,27 @@ module Hecks
|
|
|
146
141
|
launcher: launcher }
|
|
147
142
|
end
|
|
148
143
|
|
|
144
|
+
# Reads the entry words of a line: whether it asks a query, the bare name, and the words
|
|
145
|
+
# from the name on.
|
|
146
|
+
#
|
|
147
|
+
# `query` gives queries their own namespace (`ask` is its older spelling); a chapter may
|
|
148
|
+
# declare a command and a query of one name. A trailing `!` says the word is a command and
|
|
149
|
+
# is not part of the name. A query answers to its bare name too; `query` is needed only when
|
|
150
|
+
# a command shares it.
|
|
151
|
+
#
|
|
152
|
+
# @return [Array(Boolean, String, Array<String>)] `asking`, `name` (nil when absent), `argv`
|
|
153
|
+
def entry_word(cli, argv)
|
|
154
|
+
asking = QUERY_WORDS.include?(argv.first)
|
|
155
|
+
argv = argv[1..] if asking
|
|
156
|
+
name = argv.first
|
|
157
|
+
return [asking, nil, argv] if name.nil?
|
|
158
|
+
|
|
159
|
+
bang = name.end_with?("!")
|
|
160
|
+
name = name.chomp("!") if bang
|
|
161
|
+
asking ||= !bang && !cli[:names][:command].key?(name) && cli[:names][:question].key?(name)
|
|
162
|
+
[asking, name, argv]
|
|
163
|
+
end
|
|
164
|
+
|
|
149
165
|
# The chapter a command line speaks to: the booted domain's own, or, when the first word
|
|
150
166
|
# names a chapter its hecksagon attaches or uses as a framework member (`deploy`,
|
|
151
167
|
# `governance`), that chapter, with the word dropped and added to the program name.
|
|
@@ -158,11 +174,11 @@ module Hecks
|
|
|
158
174
|
[runtime.registry.bluebook(target), argv[1..], "#{program} #{argv.first}"]
|
|
159
175
|
end
|
|
160
176
|
|
|
161
|
-
# Parses one resolved
|
|
177
|
+
# Parses one resolved command's arguments, runs it as a query or command, and turns the
|
|
162
178
|
# outcome, or the domain's refusal, into `[json_or_message, status]`. With `--wait`, a
|
|
163
179
|
# question whose report names a gap (`LauncherOptions.gap_reported?`) answers status 1.
|
|
164
180
|
#
|
|
165
|
-
# @param bluebook [Bluebook::Chapter] the chapter the
|
|
181
|
+
# @param bluebook [Bluebook::Chapter] the chapter the command belongs to
|
|
166
182
|
# @param launcher [Hash, nil] the chapter's `launcher` world setting; nil when not opted in
|
|
167
183
|
def dispatch(runtime, spec, name, rest, program, asking, bluebook: nil, launcher: nil)
|
|
168
184
|
rest, wait = LauncherOptions.take_wait(spec, rest) if launcher
|
|
@@ -171,10 +187,10 @@ module Hecks
|
|
|
171
187
|
return answer_query(runtime, spec, args, wait) if spec[:kind] == :query
|
|
172
188
|
|
|
173
189
|
args, minted = LauncherOptions.run_key(runtime, spec, args, launcher)
|
|
174
|
-
# Answers with only this
|
|
190
|
+
# Answers with only this command's outcome; a full store dump is every record there is.
|
|
175
191
|
request = CommandRequest.normalize(args, receiver: spec[:receiver],
|
|
176
192
|
legacy_receiver: spec[:legacy_receiver])
|
|
177
|
-
handle = runtime.dispatch_flat(spec[:
|
|
193
|
+
handle = runtime.dispatch_flat(spec[:command], request)
|
|
178
194
|
extra = refused_answer(handle)
|
|
179
195
|
extra = { run: minted }.merge(extra) if minted
|
|
180
196
|
return settled(runtime, spec, handle, bluebook, launcher, extra) if wait
|
|
@@ -185,7 +201,7 @@ module Hecks
|
|
|
185
201
|
events: handle.events.map(&:name) }.merge(extra)), 0]
|
|
186
202
|
rescue Runtime::NotFound, Runtime::TypeMismatch => e
|
|
187
203
|
# A bad argument and a missing record both need the same next step: read the help.
|
|
188
|
-
["#{e.message}\n\n #{program} #{'
|
|
204
|
+
["#{e.message}\n\n #{program} #{'query ' if asking}#{name} --help", 1]
|
|
189
205
|
rescue *Runtime::DOMAIN_REFUSALS => e
|
|
190
206
|
# The refusal is the chapter's own sentence, verbatim.
|
|
191
207
|
[e.message, 1]
|
|
@@ -202,7 +218,7 @@ module Hecks
|
|
|
202
218
|
# @param wait [Boolean, nil] whether `--wait` was given: a report naming a gap then fails
|
|
203
219
|
# @return [Array(String, Integer)] the answer and the status
|
|
204
220
|
def answer_query(runtime, spec, args, wait)
|
|
205
|
-
rows = runtime.query(spec[:
|
|
221
|
+
rows = runtime.query(spec[:command], **args)
|
|
206
222
|
text = text_answer(spec, rows)
|
|
207
223
|
[text || JSON.pretty_generate(rows.map { |row| JsonDoor.materialize(row) }),
|
|
208
224
|
wait && LauncherOptions.gap_reported?(text) ? 1 : 0]
|
|
@@ -256,7 +272,7 @@ module Hecks
|
|
|
256
272
|
|
|
257
273
|
# The aggregate a top-level command belongs to; nil for an entity command or a port.
|
|
258
274
|
def aggregate_of(bluebook, spec)
|
|
259
|
-
head = spec[:
|
|
275
|
+
head = spec[:command].split("::", 2).last
|
|
260
276
|
return if head.count(".") != 1
|
|
261
277
|
|
|
262
278
|
bluebook.aggregates.find { |aggregate| aggregate.hecks_name == head.split(".").first }
|
|
@@ -317,19 +333,23 @@ module Hecks
|
|
|
317
333
|
end }
|
|
318
334
|
end
|
|
319
335
|
|
|
320
|
-
# Words the answer for an unknown
|
|
336
|
+
# Words the answer for an unknown command or question, suggesting up to five near names.
|
|
321
337
|
# Ranked by shared prefix, which survives a dropped letter where a substring match would not.
|
|
322
338
|
def unknown(cli, name, asking, program)
|
|
323
339
|
# Both spellings are candidates: `order.create_piza` shares no prefix with `create_pizza`.
|
|
324
340
|
pool = cli[:names][asking ? :question : :command].keys
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
341
|
+
# A bare name is the commonest slip now that the aggregate is required: its homes first.
|
|
342
|
+
homes = (pool + cli[:names][asking ? :command : :question].keys)
|
|
343
|
+
.select { |candidate| candidate.split(".").last == name }.uniq
|
|
344
|
+
similar = pool.map { |candidate| [shared_prefix(candidate, name), candidate] }
|
|
345
|
+
.select { |shared, _| shared >= [name.length / 2, 3].max }
|
|
346
|
+
.sort_by { |shared, candidate| [-shared, candidate] }
|
|
347
|
+
.map(&:last)
|
|
348
|
+
near = homes + (similar - homes)
|
|
349
|
+
|
|
350
|
+
lines = ["no such #{asking ? 'query' : 'command'}: #{name}"]
|
|
331
351
|
lines += ["", "did you mean:", *near.first(5).map { |candidate| " #{candidate}" }] unless near.empty?
|
|
332
|
-
lines += ["", " #{program}#{'
|
|
352
|
+
lines += ["", " #{program}#{' query' if asking} for the full list"]
|
|
333
353
|
lines.join("\n")
|
|
334
354
|
end
|
|
335
355
|
|
|
@@ -64,13 +64,13 @@ module Hecks
|
|
|
64
64
|
$stderr = shown if hold
|
|
65
65
|
end
|
|
66
66
|
|
|
67
|
-
# Takes `--wait` out of a
|
|
67
|
+
# Takes `--wait` out of a command's words, unless it declares a `wait` argument of its own.
|
|
68
68
|
#
|
|
69
69
|
# `--wait=false` (or `no`, `0`, `off`) is a `--wait` that was switched off; a bare `--wait`
|
|
70
70
|
# may be followed by its Boolean word.
|
|
71
71
|
#
|
|
72
|
-
# @param spec [Hash] the
|
|
73
|
-
# @param words [Array<String>] the words after the
|
|
72
|
+
# @param spec [Hash] the command's projected spec
|
|
73
|
+
# @param words [Array<String>] the words after the command
|
|
74
74
|
# @return [Array(Array<String>, Boolean)] the remaining words, and whether `--wait` was given
|
|
75
75
|
# @raise [Runtime::TypeMismatch] if `--wait=` carries something that is not a Boolean word
|
|
76
76
|
def take_wait(spec, words)
|
|
@@ -96,7 +96,7 @@ module Hecks
|
|
|
96
96
|
def streams?(launcher, spec)
|
|
97
97
|
return false unless launcher && spec[:kind] == :query
|
|
98
98
|
|
|
99
|
-
Array(launcher[:streams]).map(&:to_s).include?(spec[:
|
|
99
|
+
Array(launcher[:streams]).map(&:to_s).include?(spec[:command].to_s.split(/[.:]+/).last)
|
|
100
100
|
end
|
|
101
101
|
|
|
102
102
|
# Takes `--stream` out of a question's words.
|
|
@@ -137,7 +137,7 @@ module Hecks
|
|
|
137
137
|
# rather than sent on without one.
|
|
138
138
|
#
|
|
139
139
|
# @param runtime [Runtime::Dispatcher, Runtime::RemoteDispatcher] the booted domain
|
|
140
|
-
# @param spec [Hash] the
|
|
140
|
+
# @param spec [Hash] the command's projected spec
|
|
141
141
|
# @param args [Hash] the parsed arguments
|
|
142
142
|
# @param settings [Hash, nil] the chapter's `launcher` setting
|
|
143
143
|
# @return [Array(Hash, String)] the arguments, and the key minted (nil when none was)
|
data/lib/hecks/doors/mcp_door.rb
CHANGED
|
@@ -266,30 +266,28 @@ module Hecks
|
|
|
266
266
|
end
|
|
267
267
|
end
|
|
268
268
|
|
|
269
|
-
# `domain:` is confined to `Storehouse::BOOT_ROOT`, and further to a
|
|
270
|
-
# door's `HECKS_DOOR_DOMAINS`, before anything boots.
|
|
269
|
+
# `domain:` is confined to `Storehouse::BOOT_ROOT`, and further to a restricted
|
|
270
|
+
# door's `HECKS_DOOR_DOMAINS`, before anything boots. An unrestricted door boots the
|
|
271
|
+
# domain afresh on every call; a restricted door lives for one spawner's session and
|
|
272
|
+
# keeps each named domain booted until the files of its directory change.
|
|
271
273
|
def boot(domain)
|
|
272
|
-
|
|
274
|
+
path = Storehouse.confine!(scope.admit_domain!(domain), "domain")
|
|
275
|
+
return Hecks.boot(path, install_doors: false) unless scope.restricted?
|
|
276
|
+
|
|
277
|
+
resident = (@resident ||= {})
|
|
278
|
+
fingerprint = Storehouse.fingerprint(path)
|
|
279
|
+
return resident[path][:runtime] if resident[path] && resident[path][:fingerprint] == fingerprint
|
|
280
|
+
|
|
281
|
+
resident[path] = { fingerprint: fingerprint, runtime: Hecks.boot(path, install_doors: false) }
|
|
282
|
+
resident[path][:runtime]
|
|
273
283
|
end
|
|
274
284
|
|
|
275
285
|
# One `when` per tool, so the shared `rescue` catches every branch's escape
|
|
276
286
|
# once; a domain refusal is already `{ok: false, ...}` from `Storehouse`.
|
|
277
287
|
def call_tool(name, args) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
|
|
278
288
|
case name
|
|
279
|
-
when "dispatch"
|
|
280
|
-
|
|
281
|
-
if args["steps"]
|
|
282
|
-
Storehouse.dispatch_batch(runtime: runtime, steps: args["steps"], summary: args["summary"],
|
|
283
|
-
source: args["source"], role: args["role"], actor_id: args["actor_id"])
|
|
284
|
-
else
|
|
285
|
-
Storehouse.dispatch(runtime: runtime, command: args["command"], summary: args["summary"],
|
|
286
|
-
args: args["args"] || {}, source: args["source"],
|
|
287
|
-
dry_run: args["dry_run"] == true, role: args["role"], actor_id: args["actor_id"])
|
|
288
|
-
end
|
|
289
|
-
when "query"
|
|
290
|
-
Storehouse.query(runtime: boot(args["domain"]), question: args["question"],
|
|
291
|
-
summary: args["summary"], args: args["args"] || {}, source: args["source"],
|
|
292
|
-
role: args["role"], actor_id: args["actor_id"])
|
|
289
|
+
when "dispatch" then dispatch_tool(args)
|
|
290
|
+
when "query" then query_tool(args)
|
|
293
291
|
when "events"
|
|
294
292
|
Storehouse.events(runtime: boot(args["domain"]), aggregate: args["aggregate"], id: args["id"],
|
|
295
293
|
limit: args["limit"])
|
|
@@ -319,6 +317,29 @@ module Hecks
|
|
|
319
317
|
{ ok: false, error: "#{e.class}: #{e.message}" }
|
|
320
318
|
end
|
|
321
319
|
|
|
320
|
+
# A question, once the scope has admitted its arguments.
|
|
321
|
+
def query_tool(args)
|
|
322
|
+
scope.admit_arguments!(args["args"])
|
|
323
|
+
Storehouse.query(runtime: boot(args["domain"]), question: args["question"],
|
|
324
|
+
summary: args["summary"], args: args["args"] || {}, source: args["source"],
|
|
325
|
+
role: args["role"], actor_id: args["actor_id"])
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
# A single command or a batch, once the scope has admitted every command in it.
|
|
329
|
+
def dispatch_tool(args)
|
|
330
|
+
runtime = boot(args["domain"])
|
|
331
|
+
if args["steps"]
|
|
332
|
+
scope.admit_steps!(runtime, args["steps"])
|
|
333
|
+
Storehouse.dispatch_batch(runtime: runtime, steps: args["steps"], summary: args["summary"],
|
|
334
|
+
source: args["source"], role: args["role"], actor_id: args["actor_id"])
|
|
335
|
+
else
|
|
336
|
+
scope.admit_command!(runtime, args["command"], args["args"])
|
|
337
|
+
Storehouse.dispatch(runtime: runtime, command: args["command"], summary: args["summary"],
|
|
338
|
+
args: args["args"] || {}, source: args["source"],
|
|
339
|
+
dry_run: args["dry_run"] == true, role: args["role"], actor_id: args["actor_id"])
|
|
340
|
+
end
|
|
341
|
+
end
|
|
342
|
+
|
|
322
343
|
def answer_tool(name, args)
|
|
323
344
|
return scope.refusal(name) unless scope.permits_tool?(name)
|
|
324
345
|
|
|
@@ -352,7 +373,8 @@ module Hecks
|
|
|
352
373
|
when "notifications/initialized"
|
|
353
374
|
nil # a notification — no id, no response
|
|
354
375
|
when "tools/list"
|
|
355
|
-
|
|
376
|
+
served = TOOLS.select { |tool| scope.permits_tool?(tool[:name]) }
|
|
377
|
+
send_response(id, { tools: served.map { |tool| scope.present(tool) } })
|
|
356
378
|
when "tools/call"
|
|
357
379
|
send_response(id, tool_result(answer_tool(params["name"], params["arguments"] || {})))
|
|
358
380
|
when "ping"
|