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.
Files changed (150) hide show
  1. checksums.yaml +4 -4
  2. data/exe/hecks +24 -12
  3. data/lib/hecks/adapters/driven/lambda/client.rb +1 -1
  4. data/lib/hecks/adapters/driven/lambda.rb +1 -1
  5. data/lib/hecks/adapters/driven/tenant_provisioner.rb +2 -2
  6. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +59 -32
  7. data/lib/hecks/bluebook/hexagon.rb +45 -25
  8. data/lib/hecks/bluebook/model_check.rb +7 -7
  9. data/lib/hecks/chapters.rb +31 -3
  10. data/lib/hecks/cli/check_era.rb +1 -1
  11. data/lib/hecks/cli/deploy_template_diff.rb +1 -1
  12. data/lib/hecks/cli/doc_coverage.rb +1 -1
  13. data/lib/hecks/cli/domain_stub.rb +128 -0
  14. data/lib/hecks/cli/interview_draft.rb +246 -0
  15. data/lib/hecks/cli/model_check.rb +1 -1
  16. data/lib/hecks/cli/project_cli.rb +51 -29
  17. data/lib/hecks/cli/reference.rb +2 -1
  18. data/lib/hecks/cli/refresh_rspec_runtime_baseline.rb +1 -1
  19. data/lib/hecks/cli/seed_semantics_corpus.rb +3 -3
  20. data/lib/hecks/cli/spec_example.rb +1 -1
  21. data/lib/hecks/cli/stress_concurrency_specs.rb +14 -5
  22. data/lib/hecks/corpus.rb +11 -8
  23. data/lib/hecks/deploy/bluebook/deploy.bluebook +85 -12
  24. data/lib/hecks/deploy/oidc.json +5 -0
  25. data/lib/hecks/doc/reference.rb +4 -3
  26. data/lib/hecks/doors/cli_door.rb +13 -13
  27. data/lib/hecks/doors/cli_runner.rb +52 -32
  28. data/lib/hecks/doors/launcher_options.rb +5 -5
  29. data/lib/hecks/doors/mcp_door.rb +40 -18
  30. data/lib/hecks/doors/mcp_door_scope.rb +244 -24
  31. data/lib/hecks/doors.rb +1 -1
  32. data/lib/hecks/embryonaut_bluebook.rb +2 -2
  33. data/lib/hecks/forms/banking_presentation.rb +1 -1
  34. data/lib/hecks/framework/bluebook/compliance.bluebook +1 -1
  35. data/lib/hecks/framework/bluebook/console_settings.bluebook +1 -1
  36. data/lib/hecks/framework/bluebook/framework.hecksagon +4 -4
  37. data/lib/hecks/framework.rb +1 -1
  38. data/lib/hecks/fuzzing/combination_miner.rb +1 -1
  39. data/lib/hecks/fuzzing/concurrent_dispatch.rb +1 -1
  40. data/lib/hecks/fuzzing/differential.rb +4 -2
  41. data/lib/hecks/fuzzing/era_boundary.rb +1 -1
  42. data/lib/hecks/fuzzing/form_census.rb +2 -2
  43. data/lib/hecks/fuzzing/generated_domain_check.rb +1 -1
  44. data/lib/hecks/fuzzing/isolated_boot.rb +10 -3
  45. data/lib/hecks/fuzzing/persistence_parity.rb +2 -2
  46. data/lib/hecks/fuzzing/target_capabilities.rb +3 -3
  47. data/lib/hecks/gate/stages.yml +5 -5
  48. data/lib/hecks/grammar/grammar.hecksagon +2 -2
  49. data/lib/hecks/hecks/adapters/deploy_toolchain.rb +4 -4
  50. data/lib/hecks/hecks/adapters/host_http.rb +4 -3
  51. data/lib/hecks/hecks/adapters/local_files.rb +59 -0
  52. data/lib/hecks/hecks/adapters/site_toolchain.adapter +7 -0
  53. data/lib/hecks/hecks/adapters/site_toolchain.rb +54 -0
  54. data/lib/hecks/hecks/codebase.bluebook +4 -3
  55. data/lib/hecks/hecks/context_map.hecksagon +2 -2
  56. data/lib/hecks/hecks/custodian.bluebook +45 -3
  57. data/lib/hecks/hecks/hecks.hecksagon +10 -4
  58. data/lib/hecks/hecks/hecks.world +6 -6
  59. data/lib/hecks/hecks/site.behaviors +17 -0
  60. data/lib/hecks/hecks/site.world +10 -0
  61. data/lib/hecks/language/bluebook/bluebook.hecksagon +1 -1
  62. data/lib/hecks/language/hecksagon/hecksagon.bluebook +24 -47
  63. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +4 -4
  64. data/lib/hecks/projections/deploy/box/settings.rb +248 -0
  65. data/lib/hecks/projections/deploy/box/templates/box.yaml.tmpl +267 -0
  66. data/lib/hecks/projections/deploy/box/templates/deploy-box.sh.tmpl +70 -0
  67. data/lib/hecks/projections/deploy/box/templates/fetch-secrets.sh +21 -0
  68. data/lib/hecks/projections/deploy/box/templates/rds.yaml.tmpl +165 -0
  69. data/lib/hecks/projections/deploy/box/templates/render-compose.sh.tmpl +43 -0
  70. data/lib/hecks/projections/deploy/box.rb +387 -0
  71. data/lib/hecks/projections/deploy/fargate/cdn.rb +13 -1
  72. data/lib/hecks/projections/deploy/fargate.rb +1 -1
  73. data/lib/hecks/projections/deploy/lambda.rb +11 -10
  74. data/lib/hecks/projections/deploy/scripts/hosting.mk.tmpl +1 -1
  75. data/lib/hecks/projections/diagrams.rb +2 -2
  76. data/lib/hecks/projections/site/edge/behaviours.rb +107 -0
  77. data/lib/hecks/projections/site/edge/checks.rb +137 -0
  78. data/lib/hecks/projections/site/edge/pattern.rb +50 -0
  79. data/lib/hecks/projections/site/edge/rules.rb +128 -0
  80. data/lib/hecks/projections/site/edge.rb +136 -0
  81. data/lib/hecks/projections/site/regions.rb +44 -0
  82. data/lib/hecks/projections/site/routes_ts.rb +284 -0
  83. data/lib/hecks/projections/site/site_cdn.rb +78 -0
  84. data/lib/hecks/projections/site/table/checks.rb +124 -0
  85. data/lib/hecks/projections/site/table/row_builder.rb +155 -0
  86. data/lib/hecks/projections/site/table.rb +98 -0
  87. data/lib/hecks/projections.rb +2 -0
  88. data/lib/hecks/projector/cli_projector.rb +122 -104
  89. data/lib/hecks/quality_control/adapters/agent.adapter +1 -1
  90. data/lib/hecks/quality_control/adapters/agent.rb +2 -2
  91. data/lib/hecks/quality_control/cli/qa_concurrency_racer.rb +3 -3
  92. data/lib/hecks/quality_control/cli/qa_discover_external_domains.rb +8 -8
  93. data/lib/hecks/quality_control/cli/qa_domain_novelty.rb +3 -3
  94. data/lib/hecks/quality_control/cli/qa_generated_domains.rb +4 -4
  95. data/lib/hecks/quality_control/cli/qa_log_bug.rb +2 -2
  96. data/lib/hecks/quality_control/cli/qa_mine_combinations.rb +6 -5
  97. data/lib/hecks/quality_control/cli/qa_postgres_migrate.rb +2 -2
  98. data/lib/hecks/quality_control/cli/qa_postgres_role.rb +5 -5
  99. data/lib/hecks/quality_control/cli/qa_pr_check.rb +3 -3
  100. data/lib/hecks/quality_control/cli/qa_sweep/all_mode.rb +3 -3
  101. data/lib/hecks/quality_control/cli/qa_sweep/checks.rb +2 -2
  102. data/lib/hecks/quality_control/cli/qa_sweep.rb +10 -9
  103. data/lib/hecks/quality_control/cli/qa_tick.rb +3 -3
  104. data/lib/hecks/quality_control/quality_control.bluebook +18 -18
  105. data/lib/hecks/quality_control/quality_control.hecksagon +1 -1
  106. data/lib/hecks/runtime/registry/verification.rb +15 -29
  107. data/lib/hecks/runtime/registry.rb +14 -20
  108. data/lib/hecks/rust_build/append_optionals.rb +79 -0
  109. data/lib/hecks/rust_build/conformance_fuzz.rb +2 -1
  110. data/lib/hecks/rust_build/domain_name.rb +26 -0
  111. data/lib/hecks/rust_build/kernel_input.rb +58 -0
  112. data/lib/hecks/rust_build/project_rust/codegen_run.rb +143 -0
  113. data/lib/hecks/rust_build/project_rust/root_mod.rb +1 -1
  114. data/lib/hecks/rust_build/project_rust.rb +57 -24
  115. data/lib/hecks/rust_build/rust_literal.rb +33 -0
  116. data/lib/hecks/rust_build/write_if_changed.rb +86 -0
  117. data/lib/hecks/site/bluebook/site.bluebook +205 -0
  118. data/lib/hecks/site/bluebook/site.hecksagon +5 -0
  119. data/lib/hecks/site/bluebook/site.ports.hecksagon +15 -0
  120. data/lib/hecks/sme/bluebook/sme.bluebook +336 -0
  121. data/lib/hecks/sme/bluebook/sme.hecksagon +5 -0
  122. data/lib/hecks/storehouse.rb +37 -7
  123. data/lib/hecks/tenancy/bluebook/tenancy.bluebook +1 -1
  124. data/lib/hecks/three_zero/forms.yml +88 -88
  125. data/lib/hecks/tools/ci_gates.rb +1 -1
  126. data/lib/hecks/tools/deploy_recipe.rb +14 -6
  127. data/lib/hecks/tools/oidc_manifests.rb +2 -1
  128. data/lib/hecks/tools/sequence_script.rb +2 -2
  129. data/lib/hecks/tools/site_routes.rb +152 -0
  130. data/lib/hecks/tools/tenant_provisioning.rb +1 -1
  131. data/lib/hecks/tools.rb +1 -0
  132. data/lib/hecks/version.rb +1 -1
  133. data/rust/build/src/cargo_sync.rs +1 -1
  134. data/rust/build/src/pipeline.rs +23 -16
  135. data/rust/build/src/resolve.rs +39 -6
  136. data/rust/host/HECKS_RELEASE +1 -1
  137. data/rust/host/src/api.rs +1 -1
  138. data/rust/host/src/dispatch.rs +87 -7
  139. data/rust/host/src/needs.rs +111 -0
  140. data/rust/parser/src/keywords.rs +7 -6
  141. data/rust/parser/src/main.rs +4 -3
  142. data/rust/parser/src/parse/chapter.rs +1 -1
  143. data/rust/parser/src/parse/hecksagon.rs +25 -10
  144. data/rust/project/mutations.rb +4 -30
  145. data/rust/project/naming.rb +8 -28
  146. data/rust/project/write_if_changed.rb +4 -80
  147. data/rust/project_rust_pipeline.rb +16 -12
  148. data/rust/src/kernel/cli.rs +2 -0
  149. data/rust/src/kernel/needs.rs +74 -1
  150. metadata +37 -1
@@ -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)
@@ -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; 10 when absent)
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 named
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
- finding = CLI::CheckEra.assess(plain(held[:host]), file, timeout: (plain(held[:timeout]) || 10).to_f)
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
@@ -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, optional: true
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, optional: true
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, optional: true
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) { uses_framework "Governance" }
11
+ %w[Bluebook Hecksagon World Adapter Port Translation Expression Tenancy Deploy Site 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=10]` records the first check of a host under
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, optional: true
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, optional: true
592
+ attribute :timeout, Seconds, default: 10
593
593
 
594
594
  sets :expected
595
595
  sets :timeout
@@ -1277,6 +1277,9 @@ 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
1280
1283
  attribute :output, Note, optional: true
1281
1284
  attribute :refusal, Note, optional: true
1282
1285
  identified_by :run
@@ -1296,6 +1299,21 @@ policy "RecordTheEra" do
1296
1299
  attribute :value, Boolean
1297
1300
  end
1298
1301
 
1302
+ # What a new domain is called, as its bluebook spells it: a capital and then letters and digits.
1303
+ value_object "DomainName" do
1304
+ attribute :value, String, pattern: '[A-Z][A-Za-z0-9]*'
1305
+ end
1306
+
1307
+ # A persistence adapter a stub can be bound to; `Hecks::CLI::DomainStub` names the ones it knows.
1308
+ value_object "AdapterName" do
1309
+ attribute :value, String, pattern: '[A-Za-z]+'
1310
+ end
1311
+
1312
+ # Where the new domain goes; the domain's snake-cased name under the current directory when absent.
1313
+ value_object "TargetDir" do
1314
+ attribute :value, String, pattern: '[^\t\n\r]'
1315
+ end
1316
+
1299
1317
  value_object "Note" do
1300
1318
  attribute :value, String
1301
1319
  end
@@ -1320,6 +1338,25 @@ policy "RecordTheEra" do
1320
1338
  emits LaunchersRequested
1321
1339
  end
1322
1340
 
1341
+ # Writes the stub files of a new domain (ADR 0087). It asks nothing and replaces nothing: a
1342
+ # directory that already holds any file it would write is refused, with that file named.
1343
+ command "Init" do
1344
+ role "Operator"
1345
+ goal "Write the stub files of a new domain"
1346
+
1347
+ attribute :run, DoorKey
1348
+ attribute :name, DomainName
1349
+ attribute :adapter, AdapterName, optional: true
1350
+ attribute :dir, TargetDir, optional: true
1351
+
1352
+ sets :run
1353
+ sets :name
1354
+ sets :adapter
1355
+ sets :dir
1356
+
1357
+ emits StubRequested
1358
+ end
1359
+
1323
1360
  # A stdio server runs until its client closes stdin, so it cannot be asked and answered
1324
1361
  # quickly like the others. The command records that the door was started; the Terminal
1325
1362
  # adapter hands the process over to the door and answers when it closes, as OpenConsole
@@ -1381,6 +1418,11 @@ policy "RecordTheEra" do
1381
1418
  trigger Door::Workspace::Write, with: { run: :run }
1382
1419
  end
1383
1420
 
1421
+ policy "ScaffoldWhenRequested" do
1422
+ on "Door.StubRequested"
1423
+ trigger Door::Workspace::Scaffold, with: { run: :run }
1424
+ end
1425
+
1384
1426
  policy "ServeWhenRequested" do
1385
1427
  on "Door.McpRequested"
1386
1428
  trigger Door::Terminal::Serve, with: { run: :run }
@@ -1,9 +1,9 @@
1
1
  Hecks.hecksagon "Hecks" do
2
- uses_framework "Governance"
2
+ attaches "Governance"
3
3
 
4
4
  # The chapters the gem carries: the language declared in itself (Paging comes with Bluebook),
5
- # Expression, Tenancy, Deploy and QualityControl (which brings its own ports and adapters).
6
- %w[Bluebook Hecksagon World Adapter Port Translation Expression Tenancy Deploy QualityControl]
5
+ # Expression, Tenancy, Deploy, Site and QualityControl (the last two bring their own ports).
6
+ %w[Bluebook Hecksagon World Adapter Port Translation Expression Tenancy Deploy Site QualityControl]
7
7
  .each { |chapter| attaches chapter }
8
8
  # Persistence is not bound here: hecks.world's `default_adapter` binds every aggregate, so
9
9
  # environments/memory.world can swap the whole domain without a double bind.
@@ -95,6 +95,12 @@ Hecks.hecksagon "Hecks" do
95
95
  answers "DoorAnswered"
96
96
  refuses "DoorRefused"
97
97
  end
98
+
99
+ asks "Scaffold", to: Door do
100
+ attribute :run, DoorKey
101
+ answers "DoorAnswered"
102
+ refuses "DoorRefused"
103
+ end
98
104
  end
99
105
 
100
106
  Hecks::Door.port "Terminal" do
@@ -394,7 +400,7 @@ Hecks.hecksagon "Hecks" do
394
400
  end
395
401
 
396
402
  # The one seam where Deploy's "Tenant" (a validated request) becomes Tenancy's "Tenant" (a fact),
397
- # as tenancy.hecksagon declares it for the chapters booted on their own: `hecks deploy provision`
403
+ # as tenancy.hecksagon declares it for the chapters booted on their own: `hecks deploy tenant.provision`
398
404
  # registers the tenant it provisioned. It sits here, after `attaches` has loaded both chapters.
399
405
  Hecks.hecksagon "Tenancy" do
400
406
  translates "RegisterProvisionedTenant" do
@@ -15,7 +15,7 @@
15
15
  # Postgres (a `postgres://` URL, or a bare database name); otherwise a local database named for
16
16
  # the domain. It connects as the ordinary role `hecks`, never a superuser: the era write-fence
17
17
  # (row-level security) is bypassed by one, so boot refuses it. Create the role once with
18
- # `hecks quality_control create_ledger_role hecks` and the database with `createdb -O hecks hecks`.
18
+ # `hecks quality_control sweep.create_ledger_role hecks` and the database with `createdb -O hecks hecks`.
19
19
  #
20
20
  # PostgresEra versions the storage shape. Once a database holds an era, a later change to this
21
21
  # domain's shape needs a translation edge, kept in `translations/` beside the bluebook; an
@@ -26,9 +26,9 @@
26
26
  # `failure_states` are the lifecycle states `--wait` reports as exit 1 (every failure state the
27
27
  # chapter's lifecycles declare), `names` gives a command the launcher name clients type,
28
28
  # `executable` is where `hecks project_cli` writes this chapter's launcher (the gem's
29
- # `exe/hecks`), `streams` lists the questions `--stream` may tail (`Follow`), `memory_verbs` names
30
- # the verbs that launcher runs on the Memory environment unless `HECKS_ENVIRONMENT` is set (so
31
- # `hecks console` needs no database), and `legacy` names the verbs it hands to `Hecks::CLI` in the
29
+ # `exe/hecks`), `streams` lists the questions `--stream` may tail (`Follow`), `memory_commands` names
30
+ # the commands that launcher runs on the Memory environment unless `HECKS_ENVIRONMENT` is set (so
31
+ # `hecks console` needs no database), and `legacy` names the commands it hands to `Hecks::CLI` in the
32
32
  # positional forms ADR 0066 shipped.
33
33
  Hecks.world "Hecks" do
34
34
  realm "Hecks"
@@ -37,10 +37,10 @@ Hecks.world "Hecks" do
37
37
  launcher "Launcher", run_keys: true,
38
38
  failure_states: %w[flagged failed drifted unreachable refused faulted halted stopped
39
39
  abandoned red needs_fix],
40
- names: { "mcp" => "serve_mcp", "console" => "open_console" },
40
+ names: { "mcp" => "door.serve_mcp", "console" => "operation.open_console", "init" => "door.init" },
41
41
  streams: %w[Follow],
42
42
  executable: "exe/hecks",
43
- memory_verbs: %w[console],
43
+ memory_commands: %w[console init],
44
44
  legacy: %w[run docs narrate ir stores model_check smoke_test
45
45
  project_diagrams project_cli mcp]
46
46
  end
@@ -0,0 +1,17 @@
1
+ Hecks.behaviors "Site" do
2
+ vision "A projection of a site's route table is recorded as requested before anything outside is asked."
3
+
4
+ loads "hecks.bluebook", "custodian.bluebook", "codebase.bluebook", "context_map.hecksagon", "hecks.hecksagon", "site.world", "environments/memory.world"
5
+
6
+ test "ProjectSite keeps a project that has no bluebook as a faulted projection" do
7
+ tests "ProjectSite", on: "SiteProjection"
8
+ input run: { value: "routes" }, domain: { value: "/tmp/hecks-behaviors-absent/project" }
9
+ expect emits: %w[SiteProjectionRequested SiteRefused SiteFaulted]
10
+ end
11
+
12
+ test "ProjectSite refuses a run that is not named" do
13
+ tests "ProjectSite", on: "SiteProjection"
14
+ input run: { value: "" }, domain: { value: "/tmp/hecks-behaviors-absent/project" }
15
+ expect refused: "must match"
16
+ end
17
+ end
@@ -0,0 +1,10 @@
1
+ # The Site chapter's launcher when the Hecks domain attaches it: `hecks site <verb>` reads the
2
+ # world named for the chapter, as `deploy.world` does for the Deploy chapter. Its store is not set
3
+ # here: hecks.world's `default_adapter` fills the gap, so the memory overlay swaps it with the rest.
4
+ #
5
+ # `run_keys` mints the `run` key of a projection, and `--wait` reports a faulted projection (a
6
+ # routes.ts out of date under `--check`, or a route table the projector refused) as exit 1.
7
+ Hecks.world "Site" do
8
+ launcher "Launcher", run_keys: true,
9
+ failure_states: %w[faulted refused halted stopped abandoned red needs_fix]
10
+ end
@@ -2,7 +2,7 @@
2
2
  # Memory: this chapter is read, never written to a store.
3
3
  Hecks.hecksagon "Bluebook" do
4
4
  # `role "Language"` is real access control only once Governance can check it (S8).
5
- uses_framework "Governance"
5
+ attaches "Governance"
6
6
  persisted_by "Memory"
7
7
  end
8
8
 
@@ -5,9 +5,7 @@ Hecks.bluebook "Hecksagon" do
5
5
  identified_by HecksagonName, as: :domain
6
6
 
7
7
  attribute :subscriptions, list_of(Subscription)
8
- attribute :framework_members, list_of(FrameworkMember)
9
- attribute :vendored_bluebooks, list_of(VendoredBluebook)
10
- attribute :attached_chapters, list_of(AttachedChapter)
8
+ attribute :attachments, list_of(Attachment)
11
9
 
12
10
  value_object "HecksagonName" do
13
11
  attribute :value, String
@@ -19,22 +17,20 @@ Hecks.bluebook "Hecksagon" do
19
17
  invariant("a subscription names an event") { !value.to_s.empty? }
20
18
  end
21
19
 
22
- # A `framework/bluebook/` member this hecksagon attaches, such as Governance or Identity.
23
- value_object "FrameworkMember" do
24
- attribute :value, String
25
- invariant("a framework member is named") { !value.to_s.empty? }
26
- end
27
-
28
- # A separate bluebook package vendored into the consuming project, not shipped in hecks's lib/.
29
- value_object "VendoredBluebook" do
30
- attribute :value, String
31
- invariant("a vendored bluebook is named") { !value.to_s.empty? }
20
+ # A chapter this hecksagon attaches: `value` is its name, `source` is `gem` for a chapter the
21
+ # gem carries (a framework member such as Governance, or a language chapter such as Deploy)
22
+ # or `vendor` for a package vendored into the consuming project, not shipped in hecks's lib/.
23
+ value_object "Attachment" do
24
+ attribute :value, String
25
+ attribute :source, String, default: "gem"
26
+ invariant("an attachment is named") { !value.to_s.empty? }
27
+ invariant("an attachment comes from the gem or from vendor") { source == "gem" || source == "vendor" }
32
28
  end
33
29
 
34
- # A chapter the gem carries (the language, Expression, Tenancy, Deploy) this hecksagon attaches.
35
- value_object "AttachedChapter" do
30
+ # The name an attachment is asked for by, before its source is known.
31
+ value_object "ChapterName" do
36
32
  attribute :value, String
37
- invariant("an attached chapter is named") { !value.to_s.empty? }
33
+ invariant("a chapter is named") { !value.to_s.empty? }
38
34
  end
39
35
 
40
36
  command "Declare" do
@@ -59,35 +55,14 @@ Hecks.bluebook "Hecksagon" do
59
55
  emits "SubscriptionAdded"
60
56
  end
61
57
 
62
- command "UsesFramework" do
63
- role "Deployment"
64
- goal "Attach a framework/bluebook/ member to this hecksagon"
65
-
66
- attribute :member, FrameworkMember
67
-
68
- sets :framework_members, append: { value: :member }
69
-
70
- emits "FrameworkMemberAttached"
71
- end
72
-
73
- command "UsesEmbryonautBluebook" do
74
- role "Deployment"
75
- goal "Attach a vendored, external bluebook to this hecksagon"
76
-
77
- attribute :package, VendoredBluebook
78
-
79
- sets :vendored_bluebooks, append: { value: :package }
80
-
81
- emits "VendoredBluebookAttached"
82
- end
83
-
84
58
  command "Attaches" do
85
59
  role "Deployment"
86
- goal "Attach a chapter the gem carries to this hecksagon, by name"
60
+ goal "Attach a chapter to this hecksagon, from the gem or from vendor"
87
61
 
88
- attribute :chapter, AttachedChapter
62
+ attribute :chapter, ChapterName
63
+ attribute :source, String, default: "gem"
89
64
 
90
- sets :attached_chapters, append: { value: :chapter }
65
+ sets :attachments, append: { value: :chapter, source: :source }
91
66
 
92
67
  emits "ChapterAttached"
93
68
  end
@@ -107,9 +82,10 @@ Hecks.bluebook "Hecksagon" do
107
82
 
108
83
  member word: "hecksagon", context: "File", body: "keywords", inner: "Hecksagon", opens: "Hecksagon", fills: ""
109
84
  member word: "subscribe", context: "Hecksagon", body: "none", inner: "", opens: "", fills: "subscriptions"
110
- member word: "uses_framework", context: "Hecksagon", body: "none", inner: "", opens: "", fills: "framework_members"
111
- member word: "uses_embryonaut_bluebook", context: "Hecksagon", body: "none", inner: "", opens: "", fills: "vendored_bluebooks"
112
- member word: "attaches", context: "Hecksagon", body: "none", inner: "", opens: "", fills: "attached_chapters"
85
+ member word: "attaches", context: "Hecksagon", body: "none", inner: "", opens: "", fills: "attachments"
86
+ # Deprecated spellings of `attaches`; they warn and are removed in 3.2.0.
87
+ member word: "uses_framework", context: "Hecksagon", body: "none", inner: "", opens: "", fills: "attachments", status: "deprecated"
88
+ member word: "uses_embryonaut_bluebook", context: "Hecksagon", body: "none", inner: "", opens: "", fills: "attachments", status: "deprecated"
113
89
  member word: "port", context: "Hecksagon", body: "keywords", inner: "DomainPort", opens: "DomainPort", fills: "", calls: "port_impl"
114
90
  # A cross-domain reaction wired here rather than in the bluebook; reuses the Policy context.
115
91
  # No `calls:` — HecksagonBuilder#translates is an ordinary method, found before `word_gate`.
@@ -138,9 +114,10 @@ Hecks.bluebook "Hecksagon" do
138
114
 
139
115
  member keyword: "hecksagon", context: "File", at: "1", named: "", kind: "text", required: "true", fills: "domain"
140
116
  member keyword: "subscribe", context: "Hecksagon", at: "1", named: "", kind: "text", required: "true", fills: "subscriptions"
141
- member keyword: "uses_framework", context: "Hecksagon", at: "1", named: "", kind: "text", required: "true", fills: "framework_members"
142
- member keyword: "uses_embryonaut_bluebook", context: "Hecksagon", at: "1", named: "", kind: "text", required: "true", fills: "vendored_bluebooks"
143
- member keyword: "attaches", context: "Hecksagon", at: "1", named: "", kind: "text", required: "true", fills: "attached_chapters"
117
+ member keyword: "attaches", context: "Hecksagon", at: "1", named: "", kind: "text", required: "true", fills: "attachments"
118
+ member keyword: "attaches", context: "Hecksagon", at: "", named: "from", kind: "symbol", required: "false", fills: "source"
119
+ member keyword: "uses_framework", context: "Hecksagon", at: "1", named: "", kind: "text", required: "true", fills: "attachments"
120
+ member keyword: "uses_embryonaut_bluebook", context: "Hecksagon", at: "1", named: "", kind: "text", required: "true", fills: "attachments"
144
121
  member keyword: "port", context: "Hecksagon", at: "1", named: "", kind: "text", required: "true", fills: "name"
145
122
  member keyword: "translates", context: "Hecksagon", at: "1", named: "", kind: "text", required: "true", fills: "name"
146
123
 
@@ -16,7 +16,7 @@ module Hecks
16
16
 
17
17
  # Runs both halves of the era gate over every bluebook in a registry.
18
18
  #
19
- # Looks up each bluebook's own source separately: `uses_framework` can share a boot between
19
+ # Looks up each bluebook's own source separately: `attaches` can share a boot between
20
20
  # the domain's own bluebook and a differently-sourced one, so one file read once and reused
21
21
  # for every bluebook would attribute the wrong text.
22
22
  #
@@ -84,7 +84,7 @@ module Hecks
84
84
  # Reads the source text one bluebook was declared in, wherever that source lives.
85
85
  #
86
86
  # Matched by declared name, not position: a domain directory using
87
- # `uses_framework` may hold more than one file, and a framework
87
+ # `attaches` may hold more than one file, and a framework
88
88
  # member or vendored bluebook is excluded from the single-file
89
89
  # fallback so its own real source is read, not a sibling's.
90
90
  #
@@ -150,11 +150,11 @@ module Hecks
150
150
  #
151
151
  # @param registry [Runtime::Registry] the registry whose hecksagons are searched
152
152
  # @param bluebook_name [String] the bluebook's declared Pascal-case name
153
- # @return [String, nil] the package name as written in `uses_embryonaut_bluebook`; nil
153
+ # @return [String, nil] the package name as written in `attaches ... from: :vendor`; nil
154
154
  # when no hecksagon vendored a package whose name Pascal-cases to `bluebook_name`
155
155
  def vendored_bluebook_name_for(registry, bluebook_name)
156
156
  registry.hecksagons.each_value do |hecksagon|
157
- match = hecksagon.vendored_bluebooks.find { |name| Naming.pascal(name) == bluebook_name }
157
+ match = hecksagon.vendored_packages.find { |name| Naming.pascal(name) == bluebook_name }
158
158
  return match if match
159
159
  end
160
160
  nil