hecks 3.0.5 → 3.1.1
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 +32 -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/domain_writer.rb +39 -0
- data/lib/hecks/cli/interview_agent.rb +59 -0
- data/lib/hecks/cli/interview_draft.rb +246 -0
- data/lib/hecks/cli/interview_run.rb +87 -0
- data/lib/hecks/cli/interview_session.rb +299 -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 +12 -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 +53 -33
- 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/usage_cache.rb +123 -0
- 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/finding_github.adapter +7 -0
- data/lib/hecks/hecks/adapters/finding_github.rb +114 -0
- 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/adapters/terminal.rb +15 -0
- data/lib/hecks/hecks/codebase.bluebook +4 -3
- data/lib/hecks/hecks/context_map.hecksagon +2 -2
- data/lib/hecks/hecks/custodian.bluebook +81 -3
- data/lib/hecks/hecks/hecks.bluebook +18 -0
- data/lib/hecks/hecks/hecks.hecksagon +16 -4
- data/lib/hecks/hecks/hecks.world +7 -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 +324 -0
- data/lib/hecks/projections/deploy/box/templates/box.yaml.tmpl +268 -0
- data/lib/hecks/projections/deploy/box/templates/deploy-box.sh.tmpl +68 -0
- data/lib/hecks/projections/deploy/box/templates/fetch-secrets.sh +21 -0
- data/lib/hecks/projections/deploy/box/templates/rds.yaml.tmpl +181 -0
- data/lib/hecks/projections/deploy/box/templates/render-compose-taskdef.sh.tmpl +49 -0
- data/lib/hecks/projections/deploy/box/templates/render-compose.sh.tmpl +43 -0
- data/lib/hecks/projections/deploy/box/templates/restore-to-rds.sh.tmpl +91 -0
- data/lib/hecks/projections/deploy/box/templates/verify-copy.sh.tmpl +58 -0
- data/lib/hecks/projections/deploy/box.rb +553 -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 +153 -111
- 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/loader.rb +31 -8
- 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/tickets/bluebook/tickets.bluebook +331 -0
- data/lib/hecks/tickets/bluebook/tickets.hecksagon +11 -0
- data/lib/hecks/tickets/bluebook/tickets.ports.hecksagon +26 -0
- 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/lib/hecks.rb +1 -0
- 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/codegen/src/json_codec.rs +3 -0
- data/rust/codegen/src/naming.rs +4 -0
- data/rust/codegen/src/types.rs +45 -0
- 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 +51 -2
|
@@ -9,6 +9,12 @@ module Hecks
|
|
|
9
9
|
# Rewrites a copied domain's persistence bindings before an ephemeral
|
|
10
10
|
# fuzz/replay boot; a directory copy alone does not isolate Postgres state.
|
|
11
11
|
module IsolatedBoot
|
|
12
|
+
# A vendored package named by `attaches "name", from: :vendor`.
|
|
13
|
+
VENDORED_ATTACHED = /attaches\s*\(?\s*"([\w-]+)"\s*\)?\s*,\s*from:\s*:vendor/
|
|
14
|
+
|
|
15
|
+
# A vendored package named by the deprecated `uses_embryonaut_bluebook "name"`.
|
|
16
|
+
VENDORED_DEPRECATED = /uses_embryonaut_bluebook\s*\(?\s*"([\w-]+)"/
|
|
17
|
+
|
|
12
18
|
module_function
|
|
13
19
|
|
|
14
20
|
# Copies `domain_path` to a tmpdir, rebinds its persistence to `adapter`, and
|
|
@@ -67,7 +73,8 @@ module Hecks
|
|
|
67
73
|
# `Hecks.boot` only sees the domain directory itself, not `vendor/`.
|
|
68
74
|
def carry_vendored_bluebooks!(source, destination)
|
|
69
75
|
names = Dir.glob(File.join(destination, "**", "*.hecksagon")).flat_map do |path|
|
|
70
|
-
File.read(path)
|
|
76
|
+
text = File.read(path)
|
|
77
|
+
(text.scan(VENDORED_DEPRECATED) + text.scan(VENDORED_ATTACHED)).flatten
|
|
71
78
|
end
|
|
72
79
|
names.uniq.each do |name|
|
|
73
80
|
package = File.join(File.dirname(source), "vendor", "embryonaut_bluebooks", name)
|
|
@@ -161,8 +168,8 @@ module Hecks
|
|
|
161
168
|
# `:postgres_era` is the only mode that exercises era/lineage-bound SQL; plain
|
|
162
169
|
# `:postgres` never touches that machinery. Unlike `:postgres`, there's no shared
|
|
163
170
|
# scratch constant here — `database:`/`schema:` are required keyword args because
|
|
164
|
-
# the caller (`hecks quality_control
|
|
165
|
-
# lifecycle.
|
|
171
|
+
# the caller (`hecks quality_control query sweep.run --persistence-parity`) owns that
|
|
172
|
+
# database's lifecycle.
|
|
166
173
|
def rebind_to_postgres_era!(copy, database:, schema:)
|
|
167
174
|
require "pg"
|
|
168
175
|
if database.to_s.empty? || schema.to_s.empty?
|
|
@@ -11,8 +11,8 @@ module Hecks
|
|
|
11
11
|
|
|
12
12
|
# Replays `steps` on both adapters and lists where the results differ.
|
|
13
13
|
#
|
|
14
|
-
# Compares the same six fields as `hecks quality_control
|
|
15
|
-
# results are JSON-round-tripped so plain data compares equal on both sides.
|
|
14
|
+
# Compares the same six fields as `hecks quality_control query sweep.run`'s Ruby-vs-Rust mode.
|
|
15
|
+
# Both results are JSON-round-tripped so plain data compares equal on both sides.
|
|
16
16
|
#
|
|
17
17
|
# @param left [Symbol] an `IsolatedBoot` adapter: `:memory`, `:sqlite`, `:postgres`,
|
|
18
18
|
# `:postgres_era`
|
|
@@ -16,7 +16,7 @@ module Hecks
|
|
|
16
16
|
POSTGRES_ERA_VARIABLE = /^\s*([a-z_]\w*)\s*=\s*"PostgresEra"\s*(?:#.*)?$/
|
|
17
17
|
|
|
18
18
|
# Captures the member a hecksagon attaches; authorization is read off its declaration.
|
|
19
|
-
FRAMEWORK_ATTACHED = /uses_framework\s*\(?\s*"([^"]+)"/
|
|
19
|
+
FRAMEWORK_ATTACHED = /(?:attaches|uses_framework)\s*\(?\s*"([^"]+)"/
|
|
20
20
|
|
|
21
21
|
# A command-level `role "..."`, the only construct a role check can compare a caller against.
|
|
22
22
|
ROLE_GATED = /^\s*role\s+"/
|
|
@@ -41,8 +41,8 @@ module Hecks
|
|
|
41
41
|
wasm_front: %w[rust]
|
|
42
42
|
}.freeze
|
|
43
43
|
|
|
44
|
-
# The modes `hecks quality_control
|
|
45
|
-
# resolved.
|
|
44
|
+
# The modes `hecks quality_control query sweep.run` can run; one absent here is refused at
|
|
45
|
+
# start, not resolved.
|
|
46
46
|
RUNNABLE_MODES = %i[differential ruby_only self_consistency properties_in_differential
|
|
47
47
|
structural_skip_report adapter_parity_sqlite persistence_parity
|
|
48
48
|
era_boundary concurrency].freeze
|
data/lib/hecks/gate/stages.yml
CHANGED
|
@@ -27,7 +27,7 @@ pre_push:
|
|
|
27
27
|
blocked: the engines disagree about a declared query; one of them is wrong, and a self-consistent adapter spec cannot tell which.
|
|
28
28
|
- id: engine_agreement
|
|
29
29
|
title: engine agreement, every declared comparator on both engines
|
|
30
|
-
run: [bundle, exec, exe/hecks, check_engine_agreement, --wait]
|
|
30
|
+
run: [bundle, exec, exe/hecks, conformance_run.check_engine_agreement, --wait]
|
|
31
31
|
blocked: a comparator is missing a shared case or a cross-engine spec.
|
|
32
32
|
- id: model_check
|
|
33
33
|
title: model check, unreachable states, dead transitions and orphaned handlers
|
|
@@ -35,7 +35,7 @@ pre_push:
|
|
|
35
35
|
blocked: the model checker found something.
|
|
36
36
|
- id: doc_coverage
|
|
37
37
|
title: docs coverage, words with no running example
|
|
38
|
-
run: [bundle, exec, exe/hecks, measure_doc_coverage, --wait]
|
|
38
|
+
run: [bundle, exec, exe/hecks, conformance_run.measure_doc_coverage, --wait]
|
|
39
39
|
blocked: the language ships something undocumented; write the missing sections and examples.
|
|
40
40
|
- id: rubocop
|
|
41
41
|
title: rubocop, the static style and correctness gate
|
|
@@ -43,13 +43,13 @@ pre_push:
|
|
|
43
43
|
blocked: rubocop found an offense.
|
|
44
44
|
- id: codegen_drift
|
|
45
45
|
title: codegen drift, the committed rust/src/generated/ must match a fresh regeneration
|
|
46
|
-
run: [bundle, exec, exe/hecks, regenerate_corpus, --check, --wait]
|
|
46
|
+
run: [bundle, exec, exe/hecks, regeneration_run.regenerate_corpus, --check, --wait]
|
|
47
47
|
blocked: regenerating hits a diff, so the committed Rust is stale; run `hecks regenerate_corpus --confirm` and commit the result.
|
|
48
48
|
- id: comment_style
|
|
49
49
|
title: comment style, every comment under lib/hecks against docs/COMMENT_STYLE_GUIDE.md
|
|
50
|
-
run: [bundle, exec, exe/hecks, check_comments, paths=lib/hecks, --wait]
|
|
50
|
+
run: [bundle, exec, exe/hecks, style_run.check_comments, paths=lib/hecks, --wait]
|
|
51
51
|
blocked: a comment breaks the style guide, such as a line past 100 characters; `hecks fix_comments` rewrites the mechanical ones, and the rest are shortened by hand. CI runs this same check.
|
|
52
52
|
- id: comment_blocks
|
|
53
53
|
title: comment blocks, no run of comment lines past ADR 0075's cap
|
|
54
|
-
run: [bundle, exec, exe/hecks, check_comments, "paths=lib,spec,examples", only=long_block, --wait]
|
|
54
|
+
run: [bundle, exec, exe/hecks, style_run.check_comments, "paths=lib,spec,examples", only=long_block, --wait]
|
|
55
55
|
blocked: a comment block ran past the cap; shorten it.
|
|
@@ -4,13 +4,13 @@
|
|
|
4
4
|
Hecks.hecksagon "Expression" do
|
|
5
5
|
# Every command here declares `role "Language designer"` — only real
|
|
6
6
|
# access control once Governance can check it against something (S8).
|
|
7
|
-
|
|
7
|
+
attaches "Governance"
|
|
8
8
|
persisted_by "Memory"
|
|
9
9
|
end
|
|
10
10
|
|
|
11
11
|
Hecks.hecksagon "Translation" do
|
|
12
12
|
# Same reason as Expression, above — S8.
|
|
13
|
-
|
|
13
|
+
attaches "Governance"
|
|
14
14
|
persisted_by "Memory"
|
|
15
15
|
end
|
|
16
16
|
|
|
@@ -18,7 +18,7 @@ module Hecks
|
|
|
18
18
|
# The `Hecks::Tools` tool an ask runs, by ask.
|
|
19
19
|
SCRIPTS = { generate: "project_deploy", lint: "lint_deploy_recipes", manifest: "project_oidc" }.freeze
|
|
20
20
|
|
|
21
|
-
# `hecks deploy project`'s flag for each `Recipe` field it takes.
|
|
21
|
+
# `hecks deploy recipe.project`'s flag for each `Recipe` field it takes.
|
|
22
22
|
GENERATE_FLAGS = { "--tenant" => :tenant, "--schema" => :schema, "--out" => :out,
|
|
23
23
|
"--environment" => :environment }.freeze
|
|
24
24
|
|
|
@@ -30,7 +30,7 @@ module Hecks
|
|
|
30
30
|
def initialize(aggregate: nil, settings: {}, root: nil); end
|
|
31
31
|
|
|
32
32
|
# Writes a domain's deploy recipe (the template, scripts and Makefile) from its declared
|
|
33
|
-
# `deployed_to` target, through `hecks deploy project`.
|
|
33
|
+
# `deployed_to` target, through `hecks deploy recipe.project`.
|
|
34
34
|
#
|
|
35
35
|
# @param held [Hash] the `Recipe` record: `domain`, and `tenant`, `schema`, `out` and
|
|
36
36
|
# `environment` when set
|
|
@@ -43,7 +43,7 @@ module Hecks
|
|
|
43
43
|
end
|
|
44
44
|
|
|
45
45
|
# Lints generated deploy Makefiles for prod-touching recipes that hide failure, through
|
|
46
|
-
# `hecks deploy lint`.
|
|
46
|
+
# `hecks deploy makefile_check.lint`.
|
|
47
47
|
#
|
|
48
48
|
# @param held [Hash] the `MakefileCheck` record: `makefiles` (comma separated paths; three
|
|
49
49
|
# generated fixture domains when absent)
|
|
@@ -56,7 +56,7 @@ module Hecks
|
|
|
56
56
|
end
|
|
57
57
|
|
|
58
58
|
# Projects each domain's OIDC client and scope manifest into its `oidc.json`, through
|
|
59
|
-
# `hecks deploy project_oidc`.
|
|
59
|
+
# `hecks deploy oidc_manifest.project_oidc`.
|
|
60
60
|
#
|
|
61
61
|
# @param held [Hash] the `OidcManifest` record: `domains` (comma separated directories; every
|
|
62
62
|
# domain of the checkout when absent)
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Required by name, not relative to this file: a fuzz or model check boots a copy of this directory,
|
|
2
|
+
# and the implementation must load once, from `lib/`, not from the copy.
|
|
3
|
+
require "hecks/hecks/adapters/finding_github"
|
|
4
|
+
|
|
5
|
+
Hecks.adapter "FindingGithub" do
|
|
6
|
+
port "GitHubIssues"
|
|
7
|
+
end
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "open3"
|
|
4
|
+
|
|
5
|
+
module Hecks
|
|
6
|
+
module Adapters
|
|
7
|
+
# The `GitHubIssues` port's adapter: drives one GitHub issue from a `Tickets::Finding` with
|
|
8
|
+
# `gh`.
|
|
9
|
+
#
|
|
10
|
+
# Nothing is opened until a repository is named, by the `repository` setting or the
|
|
11
|
+
# `HECKS_FINDINGS_REPO` environment variable (`owner/name`); without one every ask is refused,
|
|
12
|
+
# and the runtime records the refusal on the finding. An ask that needs the issue before one
|
|
13
|
+
# was opened is refused the same way, so a finding never blocks on GitHub.
|
|
14
|
+
class FindingGithub
|
|
15
|
+
# The environment variable that names the repository when no setting does.
|
|
16
|
+
REPOSITORY_VARIABLE = "HECKS_FINDINGS_REPO"
|
|
17
|
+
|
|
18
|
+
# @param aggregate [Object, nil] unused
|
|
19
|
+
# @param settings [Hash] `repository:` names the `owner/name` to drive
|
|
20
|
+
# @param root [String, nil] unused
|
|
21
|
+
def initialize(aggregate: nil, settings: {}, root: nil)
|
|
22
|
+
@repository = settings[:repository]
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Opens an issue for the finding the runtime hands over (its whole held state).
|
|
26
|
+
#
|
|
27
|
+
# @param held [Hash] the finding's fields; `title` and `body` are value objects or strings
|
|
28
|
+
# @return [Hash{Symbol => Hash}] `issue_number:` of the new issue, the shape
|
|
29
|
+
# `Finding.RecordIssue` takes
|
|
30
|
+
# @raise [RuntimeError] when no repository is named, `gh` exits non-zero, or prints no
|
|
31
|
+
# issue URL
|
|
32
|
+
def open_issue(**held)
|
|
33
|
+
out = gh("issue", "create", "--title", plain(held[:title]).to_s, "--body", plain(held[:body]).to_s)
|
|
34
|
+
number = out.lines.last.to_s.strip[%r{/issues/(\d+)\z}, 1]
|
|
35
|
+
raise "gh issue create printed no issue URL: #{out.strip.inspect}" unless number
|
|
36
|
+
|
|
37
|
+
{ issue_number: { value: number.to_i } }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Puts the finding's kind and severity on its issue as labels.
|
|
41
|
+
#
|
|
42
|
+
# @param held [Hash] the finding's fields
|
|
43
|
+
# @return [Hash{Symbol => Hash}] `output:` the line that says what was done
|
|
44
|
+
def label_issue(**held)
|
|
45
|
+
labels = [["kind", held[:kind]], ["severity", held[:severity]]]
|
|
46
|
+
.map { |name, field| "#{name}:#{plain(field)}" unless plain(field).to_s.empty? }.compact
|
|
47
|
+
return said("no kind or severity to label") if labels.empty?
|
|
48
|
+
|
|
49
|
+
gh("issue", "edit", issue(held), "--add-label", labels.join(","))
|
|
50
|
+
said("labelled #{labels.join(', ')}")
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Comments on the issue with the pull request that fixes the finding.
|
|
54
|
+
#
|
|
55
|
+
# @param held [Hash] the finding's fields
|
|
56
|
+
# @return [Hash{Symbol => Hash}] `output:` the line that says what was done
|
|
57
|
+
def comment_on_issue(**held)
|
|
58
|
+
gh("issue", "comment", issue(held), "--body", "A fix is proposed in ##{plain(held[:fix_pr_number])}")
|
|
59
|
+
said("commented with the fix")
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Closes the issue: as completed for a resolved finding, as not planned for a dismissed one.
|
|
63
|
+
#
|
|
64
|
+
# @param held [Hash] the finding's fields, `status` among them
|
|
65
|
+
# @return [Hash{Symbol => Hash}] `output:` the line that says what was done
|
|
66
|
+
def close_issue(**held)
|
|
67
|
+
reason = plain(held[:status]) == "dismissed" ? "not planned" : "completed"
|
|
68
|
+
gh("issue", "close", issue(held), "--reason", reason)
|
|
69
|
+
said("closed as #{reason}")
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Reopens the issue of a finding taken back to triaged.
|
|
73
|
+
#
|
|
74
|
+
# @param held [Hash] the finding's fields
|
|
75
|
+
# @return [Hash{Symbol => Hash}] `output:` the line that says what was done
|
|
76
|
+
def reopen_issue(**held)
|
|
77
|
+
gh("issue", "reopen", issue(held))
|
|
78
|
+
said("reopened")
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
private
|
|
82
|
+
|
|
83
|
+
def gh(*arguments)
|
|
84
|
+
out, err, status = Open3.capture3("gh", *arguments, "--repo", repository)
|
|
85
|
+
raise "gh #{arguments.first(2).join(' ')} failed — #{err.strip.empty? ? out.strip : err.strip}" unless status.success?
|
|
86
|
+
|
|
87
|
+
out
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def repository
|
|
91
|
+
name = @repository || ENV.fetch(REPOSITORY_VARIABLE, nil)
|
|
92
|
+
return name unless name.to_s.strip.empty?
|
|
93
|
+
|
|
94
|
+
raise "no repository named: set the `repository` setting or #{REPOSITORY_VARIABLE} to owner/name"
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def issue(held)
|
|
98
|
+
number = plain(held[:issue_number])
|
|
99
|
+
raise "the finding has no GitHub issue yet" if number.to_s.empty?
|
|
100
|
+
|
|
101
|
+
number.to_s
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def said(line) = { output: { value: line } }
|
|
105
|
+
|
|
106
|
+
# A materialized value object (`{value: x}`, symbol or string keys) or the value itself.
|
|
107
|
+
def plain(field)
|
|
108
|
+
return field unless field.is_a?(Hash)
|
|
109
|
+
|
|
110
|
+
field.key?(:value) ? field[:value] : field["value"]
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
@@ -42,16 +42,17 @@ module Hecks
|
|
|
42
42
|
# allow-list file. A mismatch is an answer, not a refusal: the record keeps what was found.
|
|
43
43
|
#
|
|
44
44
|
# @param held [Hash] the `Host` record: `host` (the base URL), `expected` (the allow-list
|
|
45
|
-
# file) and `timeout` (seconds;
|
|
45
|
+
# file) and `timeout` (seconds; the command's declared default fills it when none is named)
|
|
46
46
|
# @return [Hash{Symbol => Hash}] `era:`, `version:`, `verdict:` (`match`, `unlisted` or
|
|
47
47
|
# `mismatch`) and `report:` (the sentence `hecks check_era` prints)
|
|
48
|
-
# @raise [ArgumentError] if no allow-list file was
|
|
48
|
+
# @raise [ArgumentError] if no allow-list file or timeout was given
|
|
49
49
|
# @raise [Errno::ENOENT] if the allow-list file cannot be read
|
|
50
50
|
# @raise [Runtime::EraCheck::ExpectedEra::Unreachable] if the host cannot be reached
|
|
51
51
|
# @raise [Runtime::EraCheck::ExpectedEra::BadResponse] if it answers no era
|
|
52
52
|
def fetch(**held)
|
|
53
53
|
file = plain(held[:expected]) or raise ArgumentError, "no allow-list file to compare the era with"
|
|
54
|
-
|
|
54
|
+
timeout = plain(held[:timeout]) or raise ArgumentError, "no timeout to wait for the host with"
|
|
55
|
+
finding = CLI::CheckEra.assess(plain(held[:host]), file, timeout: timeout.to_f)
|
|
55
56
|
|
|
56
57
|
{ era: { value: finding.verdict.era }, version: { value: finding.version },
|
|
57
58
|
verdict: { value: finding.verdict.status.to_s }, report: { value: finding.line } }
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "fileutils"
|
|
3
4
|
require_relative "console_capture"
|
|
4
5
|
require "hecks/cli/project_cli"
|
|
6
|
+
require "hecks/cli/domain_stub"
|
|
5
7
|
|
|
6
8
|
module Hecks
|
|
7
9
|
module Adapters
|
|
@@ -38,9 +40,66 @@ module Hecks
|
|
|
38
40
|
{ output: { value: text } }
|
|
39
41
|
end
|
|
40
42
|
|
|
43
|
+
# Writes the stub files of a new domain (ADR 0087) into the named directory, or into the
|
|
44
|
+
# snake-cased name under the current one, and prints what it wrote and what to type next.
|
|
45
|
+
# Nothing is replaced: every file is checked before the first is written, and a directory
|
|
46
|
+
# that already holds a bluebook is refused.
|
|
47
|
+
#
|
|
48
|
+
# @param held [Hash] the `Door` record: `name`, and optionally `adapter` and `dir`
|
|
49
|
+
# @return [Hash{Symbol => Hash}] `output:` the report that was printed
|
|
50
|
+
# @raise [ConsoleCapture::Failure] when the name or adapter is refused, or a file exists
|
|
51
|
+
def scaffold(**held)
|
|
52
|
+
name = plain(held[:name])
|
|
53
|
+
files = stub_files(name, plain(held[:adapter]))
|
|
54
|
+
target = File.expand_path(plain(held[:dir]) || CLI::DomainStub.directory(name), Dir.pwd)
|
|
55
|
+
refuse_existing!(target, files.keys)
|
|
56
|
+
warn "warning: #{target} is inside the hecks clone; keep a service you deploy outside it." if inside_clone?(target)
|
|
57
|
+
|
|
58
|
+
files.each do |path, text|
|
|
59
|
+
full = File.join(target, path)
|
|
60
|
+
FileUtils.mkdir_p(File.dirname(full))
|
|
61
|
+
File.write(full, text)
|
|
62
|
+
end
|
|
63
|
+
report = scaffold_report(target, files.keys, plain(held[:adapter]))
|
|
64
|
+
puts report
|
|
65
|
+
{ output: { value: report } }
|
|
66
|
+
end
|
|
67
|
+
|
|
41
68
|
private
|
|
42
69
|
|
|
43
70
|
def plain(argument) = argument.is_a?(Hash) ? argument[:value] : argument
|
|
71
|
+
|
|
72
|
+
def stub_files(name, adapter)
|
|
73
|
+
CLI::DomainStub.files(name: name, adapter: adapter)
|
|
74
|
+
rescue ArgumentError => e
|
|
75
|
+
raise ConsoleCapture::Failure, e.message
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def refuse_existing!(target, paths)
|
|
79
|
+
taken = paths.map { |path| File.join(target, path) }.select { |full| File.exist?(full) }
|
|
80
|
+
taken |= Dir.glob(File.join(target, "bluebook", "*.bluebook"))
|
|
81
|
+
return if taken.empty?
|
|
82
|
+
|
|
83
|
+
raise ConsoleCapture::Failure,
|
|
84
|
+
"nothing written; already there: #{taken.map { |full| shown(full) }.join(', ')}"
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def inside_clone?(target)
|
|
88
|
+
root = File.expand_path("../../../..", __dir__)
|
|
89
|
+
File.directory?(File.join(root, ".git")) && target.start_with?("#{root}/")
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def shown(full) = full.delete_prefix("#{Dir.pwd}/")
|
|
93
|
+
|
|
94
|
+
def scaffold_report(target, paths, adapter)
|
|
95
|
+
where = shown(target)
|
|
96
|
+
lines = ["wrote #{paths.length} files in #{where}/:"] + paths.sort.map { |path| " #{path}" }
|
|
97
|
+
lines << "" << "next:" << " hecks docs #{where}/bluebook" << " hecks console subject=#{where}"
|
|
98
|
+
unless %w[Postgres PostgresEra].include?(adapter)
|
|
99
|
+
lines << "" << "a Lambda deployment needs Postgres: run again with --adapter=Postgres."
|
|
100
|
+
end
|
|
101
|
+
lines.join("\n")
|
|
102
|
+
end
|
|
44
103
|
end
|
|
45
104
|
end
|
|
46
105
|
end
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Required by name, not relative to this file: a fuzz or model check boots a copy of this directory,
|
|
2
|
+
# and the implementation must load once, from `lib/`, not from the copy.
|
|
3
|
+
require "hecks/hecks/adapters/site_toolchain"
|
|
4
|
+
|
|
5
|
+
Hecks.adapter "SiteToolchain" do
|
|
6
|
+
port "SiteToolchain"
|
|
7
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "console_capture"
|
|
4
|
+
require_relative "codebase/tree"
|
|
5
|
+
require_relative "codebase/ruby_child"
|
|
6
|
+
|
|
7
|
+
module Hecks
|
|
8
|
+
module Adapters
|
|
9
|
+
# The `SiteToolchain` port's adapter: projects a site's route table into its `routes.ts`, or
|
|
10
|
+
# checks the file on disk is current.
|
|
11
|
+
#
|
|
12
|
+
# The projection runs the `project_site` tool in a child of the checkout, with its printing and
|
|
13
|
+
# exit status captured: a tool that ends non-zero is a refusal whose reason is what it printed.
|
|
14
|
+
# Outside a hecks checkout the ask is refused with "needs a hecks checkout".
|
|
15
|
+
class SiteToolchain
|
|
16
|
+
# The `Hecks::Tools` tool the ask runs, by ask.
|
|
17
|
+
SCRIPTS = { project: "project_site" }.freeze
|
|
18
|
+
|
|
19
|
+
# Accepts the arguments every driven adapter is built with and keeps none of them.
|
|
20
|
+
#
|
|
21
|
+
# @param aggregate [Object, nil] unused
|
|
22
|
+
# @param settings [Hash] unused
|
|
23
|
+
# @param root [String, nil] unused
|
|
24
|
+
def initialize(aggregate: nil, settings: {}, root: nil); end
|
|
25
|
+
|
|
26
|
+
# Writes a project's `routes.ts` from its declared route table, or with `check` only compares.
|
|
27
|
+
#
|
|
28
|
+
# @param held [Hash] the `SiteProjection` record: `domain`, and `out` and `check` when set
|
|
29
|
+
# @return [Hash{Symbol => Hash}] `output:` one line per file written or found current
|
|
30
|
+
# @raise [ConsoleCapture::Failure] when the tree is not a checkout, the route table is
|
|
31
|
+
# refused (an unknown cache class, a duplicate path), or under `check` a file is out of date
|
|
32
|
+
def project(**held)
|
|
33
|
+
flags = []
|
|
34
|
+
flags << "--out=#{plain(held[:out])}" unless plain(held[:out]).nil?
|
|
35
|
+
flags << "--check" if plain(held[:check]) == true
|
|
36
|
+
tree = Codebase::Tree.new
|
|
37
|
+
tree.require_checkout!
|
|
38
|
+
result = Codebase::RubyChild.new(tree).capture(SCRIPTS.fetch(:project), *flags, plain(held[:domain]))
|
|
39
|
+
raise ConsoleCapture::Failure, message_of(result) unless result.ok?
|
|
40
|
+
|
|
41
|
+
{ output: { value: result.out } }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
def message_of(result)
|
|
47
|
+
text = [result.err, result.out].map(&:strip).reject(&:empty?).join("\n")
|
|
48
|
+
text.empty? ? "the tool ended with status #{result.status.exitstatus}" : text
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def plain(argument) = argument.is_a?(Hash) ? argument[:value] : argument
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -66,6 +66,21 @@ module Hecks
|
|
|
66
66
|
raise ConsoleCapture::Failure, "the mcp door refused to start (status #{e.status}); see stderr"
|
|
67
67
|
end
|
|
68
68
|
|
|
69
|
+
# Holds an interview at the terminal and writes the domain it drafts (ADR 0088). Asking is IO,
|
|
70
|
+
# so it happens here and nowhere else; the journal records that one was held, not its words.
|
|
71
|
+
#
|
|
72
|
+
# @param held [Hash] the `Door` record: `name`; optionally `adapter`, `dir`, `expert`, `no_ai`
|
|
73
|
+
# @return [Hash{Symbol => Hash}] `output:` what the interview wrote, or that it wrote nothing
|
|
74
|
+
# @raise [ConsoleCapture::Failure] when the name or adapter is refused, or a file is there
|
|
75
|
+
def converse(**held)
|
|
76
|
+
require "hecks/cli/interview_run"
|
|
77
|
+
report = CLI::InterviewRun.call(name: plain(held[:name]), adapter: plain(held[:adapter]), dir: plain(held[:dir]),
|
|
78
|
+
expert: plain(held[:expert]), use_ai: !plain(held[:no_ai]))
|
|
79
|
+
{ output: { value: report.empty? ? "no interview was written" : report } }
|
|
80
|
+
rescue ArgumentError => e
|
|
81
|
+
raise ConsoleCapture::Failure, e.message
|
|
82
|
+
end
|
|
83
|
+
|
|
69
84
|
private
|
|
70
85
|
|
|
71
86
|
def plain(argument) = argument.is_a?(Hash) ? argument[:value] : argument
|
|
@@ -1864,7 +1864,7 @@ Hecks.bluebook "Hecks" do
|
|
|
1864
1864
|
role "Maintainer"
|
|
1865
1865
|
goal "Regenerate the committed runtime baselines the sharded jobs group from"
|
|
1866
1866
|
|
|
1867
|
-
attribute :workers, Count,
|
|
1867
|
+
attribute :workers, Count, default: 6
|
|
1868
1868
|
attribute :from_run, RunId, optional: true
|
|
1869
1869
|
attribute :run, RunKey
|
|
1870
1870
|
attribute :confirm, Switch, optional: true
|
|
@@ -1898,9 +1898,10 @@ Hecks.bluebook "Hecks" do
|
|
|
1898
1898
|
role "Maintainer"
|
|
1899
1899
|
goal "Rerun the thread-safety specs under many seeds, several processes at a time"
|
|
1900
1900
|
|
|
1901
|
-
attribute :runs, Count,
|
|
1901
|
+
attribute :runs, Count, default: 30
|
|
1902
|
+
# The machine's own core count when none is named, which no fixed default can say.
|
|
1902
1903
|
attribute :parallel, Count, optional: true
|
|
1903
|
-
attribute :seed_start, Count,
|
|
1904
|
+
attribute :seed_start, Count, default: 1
|
|
1904
1905
|
attribute :run, RunKey
|
|
1905
1906
|
|
|
1906
1907
|
sets :runs
|
|
@@ -8,6 +8,6 @@ end
|
|
|
8
8
|
|
|
9
9
|
# The attached chapters (hecks.hecksagon `attaches`): each is a bounded context whose store comes
|
|
10
10
|
# from hecks.world's `default_adapter`, so the memory overlay swaps them with the Hecks chapter.
|
|
11
|
-
%w[Bluebook Hecksagon World Adapter Port Translation Expression Tenancy Deploy QualityControl].each do |chapter|
|
|
12
|
-
Hecks.hecksagon(chapter) {
|
|
11
|
+
%w[Bluebook Hecksagon World Adapter Port Translation Expression Tenancy Deploy Site Tickets QualityControl].each do |chapter|
|
|
12
|
+
Hecks.hecksagon(chapter) { attaches "Governance" }
|
|
13
13
|
end
|
|
@@ -564,7 +564,7 @@ Hecks.bluebook "Hecks" do
|
|
|
564
564
|
transition "Lose" => "unreachable", from: "requested"
|
|
565
565
|
end
|
|
566
566
|
|
|
567
|
-
# `hecks check_era <url> expected=era-file [timeout=
|
|
567
|
+
# `hecks check_era <url> expected=era-file [timeout=]` records the first check of a host under
|
|
568
568
|
# its URL. A host is one record, and a creating command cannot repeat an identity, so a later
|
|
569
569
|
# check of the same host is `hecks recheck <url> expected=era-file`. Both are read with
|
|
570
570
|
# `hecks standing <url>`.
|
|
@@ -574,7 +574,7 @@ command "CheckEra" do
|
|
|
574
574
|
|
|
575
575
|
attribute :host, Address
|
|
576
576
|
attribute :expected, ExpectedFile
|
|
577
|
-
attribute :timeout, Seconds,
|
|
577
|
+
attribute :timeout, Seconds, default: 10
|
|
578
578
|
|
|
579
579
|
sets :host
|
|
580
580
|
sets :expected
|
|
@@ -589,7 +589,7 @@ command "Recheck" do
|
|
|
589
589
|
|
|
590
590
|
reference_to Host
|
|
591
591
|
attribute :expected, ExpectedFile
|
|
592
|
-
attribute :timeout, Seconds,
|
|
592
|
+
attribute :timeout, Seconds, default: 10
|
|
593
593
|
|
|
594
594
|
sets :expected
|
|
595
595
|
sets :timeout
|
|
@@ -1277,6 +1277,11 @@ policy "RecordTheEra" do
|
|
|
1277
1277
|
attribute :run, DoorKey
|
|
1278
1278
|
attribute :domains, DomainList, optional: true
|
|
1279
1279
|
attribute :stdio, Switch, optional: true
|
|
1280
|
+
attribute :name, DomainName, optional: true
|
|
1281
|
+
attribute :adapter, AdapterName, optional: true
|
|
1282
|
+
attribute :dir, TargetDir, optional: true
|
|
1283
|
+
attribute :expert, ExpertName, optional: true
|
|
1284
|
+
attribute :no_ai, Switch, optional: true
|
|
1280
1285
|
attribute :output, Note, optional: true
|
|
1281
1286
|
attribute :refusal, Note, optional: true
|
|
1282
1287
|
identified_by :run
|
|
@@ -1296,6 +1301,26 @@ policy "RecordTheEra" do
|
|
|
1296
1301
|
attribute :value, Boolean
|
|
1297
1302
|
end
|
|
1298
1303
|
|
|
1304
|
+
# What a new domain is called, as its bluebook spells it: a capital and then letters and digits.
|
|
1305
|
+
value_object "DomainName" do
|
|
1306
|
+
attribute :value, String, pattern: '[A-Z][A-Za-z0-9]*'
|
|
1307
|
+
end
|
|
1308
|
+
|
|
1309
|
+
# A persistence adapter a stub can be bound to; `Hecks::CLI::DomainStub` names the ones it knows.
|
|
1310
|
+
value_object "AdapterName" do
|
|
1311
|
+
attribute :value, String, pattern: '[A-Za-z]+'
|
|
1312
|
+
end
|
|
1313
|
+
|
|
1314
|
+
# Where the new domain goes; the domain's snake-cased name under the current directory when absent.
|
|
1315
|
+
value_object "TargetDir" do
|
|
1316
|
+
attribute :value, String, pattern: '[^\t\n\r]'
|
|
1317
|
+
end
|
|
1318
|
+
|
|
1319
|
+
# Who is interviewed; asked for at the terminal when absent.
|
|
1320
|
+
value_object "ExpertName" do
|
|
1321
|
+
attribute :value, String, pattern: '[^\t\n\r]'
|
|
1322
|
+
end
|
|
1323
|
+
|
|
1299
1324
|
value_object "Note" do
|
|
1300
1325
|
attribute :value, String
|
|
1301
1326
|
end
|
|
@@ -1320,6 +1345,49 @@ policy "RecordTheEra" do
|
|
|
1320
1345
|
emits LaunchersRequested
|
|
1321
1346
|
end
|
|
1322
1347
|
|
|
1348
|
+
# Writes the stub files of a new domain (ADR 0087). It asks nothing and replaces nothing: a
|
|
1349
|
+
# directory that already holds any file it would write is refused, with that file named.
|
|
1350
|
+
command "Init" do
|
|
1351
|
+
role "Operator"
|
|
1352
|
+
goal "Write the stub files of a new domain"
|
|
1353
|
+
|
|
1354
|
+
attribute :run, DoorKey
|
|
1355
|
+
attribute :name, DomainName
|
|
1356
|
+
attribute :adapter, AdapterName, optional: true
|
|
1357
|
+
attribute :dir, TargetDir, optional: true
|
|
1358
|
+
|
|
1359
|
+
sets :run
|
|
1360
|
+
sets :name
|
|
1361
|
+
sets :adapter
|
|
1362
|
+
sets :dir
|
|
1363
|
+
|
|
1364
|
+
emits StubRequested
|
|
1365
|
+
end
|
|
1366
|
+
|
|
1367
|
+
# Holds an interview with a subject matter expert and drafts a first domain from it (ADR 0088).
|
|
1368
|
+
# The Terminal adapter runs the conversation, as it runs a console; the journal records that one
|
|
1369
|
+
# was held and how it ended, not what was said. `--no-ai` leaves the model out.
|
|
1370
|
+
command "Interview" do
|
|
1371
|
+
role "Operator"
|
|
1372
|
+
goal "Hold an interview with a subject matter expert and draft a first domain from it"
|
|
1373
|
+
|
|
1374
|
+
attribute :run, DoorKey
|
|
1375
|
+
attribute :name, DomainName
|
|
1376
|
+
attribute :adapter, AdapterName, optional: true
|
|
1377
|
+
attribute :dir, TargetDir, optional: true
|
|
1378
|
+
attribute :expert, ExpertName, optional: true
|
|
1379
|
+
attribute :no_ai, Switch, optional: true
|
|
1380
|
+
|
|
1381
|
+
sets :run
|
|
1382
|
+
sets :name
|
|
1383
|
+
sets :adapter
|
|
1384
|
+
sets :dir
|
|
1385
|
+
sets :expert
|
|
1386
|
+
sets :no_ai
|
|
1387
|
+
|
|
1388
|
+
emits InterviewRequested
|
|
1389
|
+
end
|
|
1390
|
+
|
|
1323
1391
|
# A stdio server runs until its client closes stdin, so it cannot be asked and answered
|
|
1324
1392
|
# quickly like the others. The command records that the door was started; the Terminal
|
|
1325
1393
|
# adapter hands the process over to the door and answers when it closes, as OpenConsole
|
|
@@ -1381,6 +1449,16 @@ policy "RecordTheEra" do
|
|
|
1381
1449
|
trigger Door::Workspace::Write, with: { run: :run }
|
|
1382
1450
|
end
|
|
1383
1451
|
|
|
1452
|
+
policy "ScaffoldWhenRequested" do
|
|
1453
|
+
on "Door.StubRequested"
|
|
1454
|
+
trigger Door::Workspace::Scaffold, with: { run: :run }
|
|
1455
|
+
end
|
|
1456
|
+
|
|
1457
|
+
policy "ConverseWhenRequested" do
|
|
1458
|
+
on "Door.InterviewRequested"
|
|
1459
|
+
trigger Door::Terminal::Converse, with: { run: :run }
|
|
1460
|
+
end
|
|
1461
|
+
|
|
1384
1462
|
policy "ServeWhenRequested" do
|
|
1385
1463
|
on "Door.McpRequested"
|
|
1386
1464
|
trigger Door::Terminal::Serve, with: { run: :run }
|
|
@@ -8,9 +8,15 @@ Hecks.bluebook "Hecks" do
|
|
|
8
8
|
attribute :version, Version
|
|
9
9
|
attribute :ir_version, Version
|
|
10
10
|
attribute :ships_from, Checkout, optional: true
|
|
11
|
+
# The findings the version settled, by the key the Tickets chapter gives them.
|
|
12
|
+
attribute :closes, list_of(FindingKey), optional: true
|
|
11
13
|
|
|
12
14
|
identified_by :version
|
|
13
15
|
|
|
16
|
+
value_object "FindingKey" do
|
|
17
|
+
attribute :value, String, pattern: '[^ \t\n\r]'
|
|
18
|
+
end
|
|
19
|
+
|
|
14
20
|
value_object "Version" do
|
|
15
21
|
attribute :value, String, pattern: '^[0-9]+[.][0-9]+[.][0-9]+'
|
|
16
22
|
|
|
@@ -61,6 +67,18 @@ Hecks.bluebook "Hecks" do
|
|
|
61
67
|
emits Verified
|
|
62
68
|
end
|
|
63
69
|
|
|
70
|
+
command "CloseFindings" do
|
|
71
|
+
role "Maintainer"
|
|
72
|
+
goal "Record the findings this version settled"
|
|
73
|
+
|
|
74
|
+
reference_to Release
|
|
75
|
+
attribute :closes, list_of(FindingKey)
|
|
76
|
+
|
|
77
|
+
sets :closes
|
|
78
|
+
|
|
79
|
+
emits FindingsClosed
|
|
80
|
+
end
|
|
81
|
+
|
|
64
82
|
query "Shipped" do
|
|
65
83
|
description "Every version that was tagged, published and verified."
|
|
66
84
|
where(state: "verified")
|