hecks 3.0.4 → 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 (159) hide show
  1. checksums.yaml +4 -4
  2. data/exe/hecks +25 -7
  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/console.rb +24 -8
  12. data/lib/hecks/cli/deploy_template_diff.rb +1 -1
  13. data/lib/hecks/cli/doc_coverage.rb +1 -1
  14. data/lib/hecks/cli/domain_stub.rb +128 -0
  15. data/lib/hecks/cli/interview_draft.rb +246 -0
  16. data/lib/hecks/cli/model_check.rb +1 -1
  17. data/lib/hecks/cli/project_cli.rb +69 -18
  18. data/lib/hecks/cli/reference.rb +2 -1
  19. data/lib/hecks/cli/refresh_rspec_runtime_baseline.rb +1 -1
  20. data/lib/hecks/cli/seed_semantics_corpus.rb +38 -9
  21. data/lib/hecks/cli/spec_example.rb +1 -1
  22. data/lib/hecks/cli/stress_concurrency_specs.rb +14 -5
  23. data/lib/hecks/corpus.rb +11 -8
  24. data/lib/hecks/deploy/bluebook/deploy.bluebook +85 -12
  25. data/lib/hecks/deploy/oidc.json +5 -0
  26. data/lib/hecks/doc/reference.rb +4 -3
  27. data/lib/hecks/doors/cli_door.rb +13 -13
  28. data/lib/hecks/doors/cli_runner.rb +52 -32
  29. data/lib/hecks/doors/launcher_options.rb +23 -5
  30. data/lib/hecks/doors/mcp_door.rb +40 -18
  31. data/lib/hecks/doors/mcp_door_scope.rb +244 -24
  32. data/lib/hecks/doors.rb +1 -1
  33. data/lib/hecks/embryonaut_bluebook.rb +2 -2
  34. data/lib/hecks/forms/banking_presentation.rb +1 -1
  35. data/lib/hecks/framework/bluebook/compliance.bluebook +1 -1
  36. data/lib/hecks/framework/bluebook/console_settings.bluebook +1 -1
  37. data/lib/hecks/framework/bluebook/framework.hecksagon +4 -4
  38. data/lib/hecks/framework.rb +1 -1
  39. data/lib/hecks/fuzzing/combination_miner.rb +1 -1
  40. data/lib/hecks/fuzzing/concurrent_dispatch.rb +1 -1
  41. data/lib/hecks/fuzzing/differential.rb +4 -2
  42. data/lib/hecks/fuzzing/era_boundary.rb +1 -1
  43. data/lib/hecks/fuzzing/form_census.rb +2 -2
  44. data/lib/hecks/fuzzing/generated_domain_check.rb +1 -1
  45. data/lib/hecks/fuzzing/isolated_boot.rb +10 -3
  46. data/lib/hecks/fuzzing/persistence_parity.rb +2 -2
  47. data/lib/hecks/fuzzing/target_capabilities.rb +3 -3
  48. data/lib/hecks/gate/stages.yml +8 -4
  49. data/lib/hecks/grammar/grammar.hecksagon +2 -2
  50. data/lib/hecks/hecks/adapters/deploy_toolchain.rb +4 -4
  51. data/lib/hecks/hecks/adapters/host_http.rb +4 -3
  52. data/lib/hecks/hecks/adapters/local_files.rb +59 -0
  53. data/lib/hecks/hecks/adapters/site_toolchain.adapter +7 -0
  54. data/lib/hecks/hecks/adapters/site_toolchain.rb +54 -0
  55. data/lib/hecks/hecks/codebase.bluebook +4 -3
  56. data/lib/hecks/hecks/context_map.hecksagon +2 -2
  57. data/lib/hecks/hecks/custodian.bluebook +45 -3
  58. data/lib/hecks/hecks/hecks.hecksagon +10 -4
  59. data/lib/hecks/hecks/hecks.world +7 -4
  60. data/lib/hecks/hecks/site.behaviors +17 -0
  61. data/lib/hecks/hecks/site.world +10 -0
  62. data/lib/hecks/language/bluebook/bluebook.hecksagon +1 -1
  63. data/lib/hecks/language/hecksagon/hecksagon.bluebook +24 -47
  64. data/lib/hecks/ports/persistence/plugins/era/era_check.rb +4 -4
  65. data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +2 -1
  66. data/lib/hecks/projections/deploy/box/settings.rb +248 -0
  67. data/lib/hecks/projections/deploy/box/templates/box.yaml.tmpl +267 -0
  68. data/lib/hecks/projections/deploy/box/templates/deploy-box.sh.tmpl +70 -0
  69. data/lib/hecks/projections/deploy/box/templates/fetch-secrets.sh +21 -0
  70. data/lib/hecks/projections/deploy/box/templates/rds.yaml.tmpl +165 -0
  71. data/lib/hecks/projections/deploy/box/templates/render-compose.sh.tmpl +43 -0
  72. data/lib/hecks/projections/deploy/box.rb +387 -0
  73. data/lib/hecks/projections/deploy/fargate/cdn.rb +13 -1
  74. data/lib/hecks/projections/deploy/fargate.rb +1 -1
  75. data/lib/hecks/projections/deploy/lambda.rb +11 -10
  76. data/lib/hecks/projections/deploy/scripts/hosting.mk.tmpl +1 -1
  77. data/lib/hecks/projections/diagrams.rb +2 -2
  78. data/lib/hecks/projections/site/edge/behaviours.rb +107 -0
  79. data/lib/hecks/projections/site/edge/checks.rb +137 -0
  80. data/lib/hecks/projections/site/edge/pattern.rb +50 -0
  81. data/lib/hecks/projections/site/edge/rules.rb +128 -0
  82. data/lib/hecks/projections/site/edge.rb +136 -0
  83. data/lib/hecks/projections/site/regions.rb +44 -0
  84. data/lib/hecks/projections/site/routes_ts.rb +284 -0
  85. data/lib/hecks/projections/site/site_cdn.rb +78 -0
  86. data/lib/hecks/projections/site/table/checks.rb +124 -0
  87. data/lib/hecks/projections/site/table/row_builder.rb +155 -0
  88. data/lib/hecks/projections/site/table.rb +98 -0
  89. data/lib/hecks/projections.rb +2 -0
  90. data/lib/hecks/projector/cli_projector.rb +172 -95
  91. data/lib/hecks/quality_control/adapters/agent.adapter +1 -1
  92. data/lib/hecks/quality_control/adapters/agent.rb +2 -2
  93. data/lib/hecks/quality_control/cli/qa_concurrency_racer.rb +3 -3
  94. data/lib/hecks/quality_control/cli/qa_discover_external_domains.rb +8 -8
  95. data/lib/hecks/quality_control/cli/qa_domain_novelty.rb +3 -3
  96. data/lib/hecks/quality_control/cli/qa_generated_domains.rb +4 -4
  97. data/lib/hecks/quality_control/cli/qa_log_bug.rb +2 -2
  98. data/lib/hecks/quality_control/cli/qa_mine_combinations.rb +6 -5
  99. data/lib/hecks/quality_control/cli/qa_postgres_migrate.rb +2 -2
  100. data/lib/hecks/quality_control/cli/qa_postgres_role.rb +5 -5
  101. data/lib/hecks/quality_control/cli/qa_pr_check.rb +3 -3
  102. data/lib/hecks/quality_control/cli/qa_sweep/all_mode.rb +3 -3
  103. data/lib/hecks/quality_control/cli/qa_sweep/checks.rb +2 -2
  104. data/lib/hecks/quality_control/cli/qa_sweep.rb +10 -9
  105. data/lib/hecks/quality_control/cli/qa_tick.rb +3 -3
  106. data/lib/hecks/quality_control/quality_control.bluebook +18 -18
  107. data/lib/hecks/quality_control/quality_control.hecksagon +1 -1
  108. data/lib/hecks/runtime/command_interpreter.rb +2 -0
  109. data/lib/hecks/runtime/interpreting.rb +21 -0
  110. data/lib/hecks/runtime/invocation.rb +5 -3
  111. data/lib/hecks/runtime/registry/verification.rb +15 -29
  112. data/lib/hecks/runtime/registry.rb +14 -20
  113. data/lib/hecks/rust_build/append_optionals.rb +79 -0
  114. data/lib/hecks/rust_build/conformance_fuzz.rb +2 -1
  115. data/lib/hecks/rust_build/domain_name.rb +26 -0
  116. data/lib/hecks/rust_build/kernel_input.rb +58 -0
  117. data/lib/hecks/rust_build/project_rust/codegen_run.rb +143 -0
  118. data/lib/hecks/rust_build/project_rust/root_mod.rb +1 -1
  119. data/lib/hecks/rust_build/project_rust.rb +57 -24
  120. data/lib/hecks/rust_build/rust_literal.rb +33 -0
  121. data/lib/hecks/rust_build/write_if_changed.rb +86 -0
  122. data/lib/hecks/site/bluebook/site.bluebook +205 -0
  123. data/lib/hecks/site/bluebook/site.hecksagon +5 -0
  124. data/lib/hecks/site/bluebook/site.ports.hecksagon +15 -0
  125. data/lib/hecks/sme/bluebook/sme.bluebook +336 -0
  126. data/lib/hecks/sme/bluebook/sme.hecksagon +5 -0
  127. data/lib/hecks/storehouse.rb +37 -7
  128. data/lib/hecks/tenancy/bluebook/tenancy.bluebook +1 -1
  129. data/lib/hecks/three_zero/forms.yml +88 -88
  130. data/lib/hecks/tools/ci_gates.rb +1 -1
  131. data/lib/hecks/tools/deploy_recipe.rb +14 -6
  132. data/lib/hecks/tools/oidc_manifests.rb +2 -1
  133. data/lib/hecks/tools/sequence_script.rb +2 -2
  134. data/lib/hecks/tools/site_routes.rb +152 -0
  135. data/lib/hecks/tools/tenant_provisioning.rb +1 -1
  136. data/lib/hecks/tools.rb +1 -0
  137. data/lib/hecks/version.rb +1 -1
  138. data/rust/build/src/cargo_sync.rs +1 -1
  139. data/rust/build/src/pipeline.rs +23 -16
  140. data/rust/build/src/resolve.rs +39 -6
  141. data/rust/host/HECKS_RELEASE +1 -1
  142. data/rust/host/src/api.rs +1 -1
  143. data/rust/host/src/dispatch.rs +87 -7
  144. data/rust/host/src/needs.rs +111 -0
  145. data/rust/host/src/web/newsletter/template.rs +69 -0
  146. data/rust/host/src/web/newsletter/tests.rs +43 -0
  147. data/rust/host/src/web/newsletter.rs +6 -1
  148. data/rust/host/src/web/newsletter_send.rs +1 -1
  149. data/rust/parser/src/keywords.rs +7 -6
  150. data/rust/parser/src/main.rs +4 -3
  151. data/rust/parser/src/parse/chapter.rs +1 -1
  152. data/rust/parser/src/parse/hecksagon.rs +25 -10
  153. data/rust/project/mutations.rb +4 -30
  154. data/rust/project/naming.rb +8 -28
  155. data/rust/project/write_if_changed.rb +4 -80
  156. data/rust/project_rust_pipeline.rb +16 -12
  157. data/rust/src/kernel/cli.rs +2 -0
  158. data/rust/src/kernel/needs.rs +74 -1
  159. metadata +39 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 90a718a197fae0287e4fe16033540bf625166bc31bfb3482e0752d1039a2a949
4
- data.tar.gz: 15fe27abd9a488dfe4f579118f8e8ffbea1302b956ee36fbd2b9475eb3b88429
3
+ metadata.gz: 29694d3639f1ad69a3d0a81bf4e6494c5a6d60a88ca9897af3a01224944d654e
4
+ data.tar.gz: b9fb1376266fe98b2d09c6127bbbbe3a954c11e9441c3f01976e54f44b63eaf7
5
5
  SHA512:
6
- metadata.gz: d74d07807e22e8899c041adfa96d799b5eb47713a0dec6c05bbb9e61a579a32a35da890b67aaf876b1ba5182e0961a112f370e0e04adcf2719f8ae57a50cfd45
7
- data.tar.gz: 11e85c77dc6a4f5d34ec3acd54e41873be8d03b980e4ef97a8d0d7c9da4b63ecfc2b04e2a1322f8c1536ad1d58f8f85f50df2ca58f3d6fea44126cb871c05b0d
6
+ metadata.gz: 6eea28ed2426948aeca13df88967f06a5d64acf29dbb376a16b53d82367e90383f3646928ee4940ff9d93969ea7721ed29fb4467096eb8a2b88bc14f7f7ede47
7
+ data.tar.gz: 52d5f117ce1b1bae946f939dad3c73567a44c5c466257c37cbaf5bb7bcecb0cb3a3ea92e9c41b3edde78efbdb674669d08f56305287f46313c1cb4ce813d3042
data/exe/hecks CHANGED
@@ -2,12 +2,12 @@
2
2
 
3
3
  # Hecks's command line — GENERATED by hecks project_cli. Do not edit.
4
4
  #
5
- # A launcher, not a CLI: the verbs, their arguments and their refusals are
5
+ # A launcher, not a CLI: the commands, their arguments and their refusals are
6
6
  # projected from the bluebook beside it every time this runs, so a change
7
7
  # to the chapter shows up here without re-minting anything.
8
8
  #
9
- # exe/hecks every verb, and every question
10
- # exe/hecks <verb> --help what it wants, and how it refuses
9
+ # exe/hecks every command, and every question
10
+ # exe/hecks <command> --help what it wants, and how it refuses
11
11
 
12
12
  Encoding.default_external = Encoding::UTF_8
13
13
  Encoding.default_internal = Encoding::UTF_8
@@ -21,10 +21,17 @@ if LEGACY.include?(ARGV.first)
21
21
  exit Hecks::CLI.start(ARGV) unless Hecks::CLI.launcher_form?(ARGV)
22
22
  end
23
23
 
24
+ # These commands keep nothing worth a database, so they run on Memory unless told otherwise.
25
+ # A person is at them: they wait for their result, say why when they are refused, and print
26
+ # no record when they end well.
27
+ MEMORY_COMMANDS = %w[console init].freeze
28
+ ENV["HECKS_ENVIRONMENT"] ||= "memory" if MEMORY_COMMANDS.include?(ARGV.first.to_s.chomp("!"))
29
+ ARGV << "--wait" if MEMORY_COMMANDS.include?(ARGV.first.to_s.chomp("!")) && !ARGV.include?("--wait")
30
+
24
31
  require "hecks"
25
32
 
26
33
  # Usage is answered from the projected chapter alone: no adapter is bound and no
27
- # database is opened. Only a line that runs a verb boots the domain,
34
+ # database is opened. Only a line that runs a command boots the domain,
28
35
  # and it boots from what the usage check already loaded.
29
36
  domain = File.expand_path("../lib/hecks/hecks", __dir__)
30
37
  program = "hecks"
@@ -39,7 +46,7 @@ text, status, reason = Hecks::Doors::CliRunner.usage(
39
46
  )
40
47
  unless text
41
48
  runtime = begin
42
- Hecks.boot_described(described, install_doors: false)
49
+ Hecks::Doors::LauncherOptions.quietly(hold: MEMORY_COMMANDS.include?(ARGV.first.to_s.chomp("!"))) { Hecks.boot_described(described, install_doors: false) }
43
50
  rescue StandardError => e
44
51
  abort "cannot open Hecks: #{e.message.lines.first.strip}"
45
52
  end
@@ -53,8 +60,19 @@ end
53
60
  # A `--wait` run that failed answers its settled record on stdout, so `| jq` reads it
54
61
  # either way, and says why on stderr.
55
62
  if reason
56
- puts text
57
- warn reason
63
+ if MEMORY_COMMANDS.include?(ARGV.first.to_s.chomp("!"))
64
+ why = begin
65
+ require "json"
66
+ JSON.parse(text).dig("state", "refusal", "value")
67
+ rescue JSON::ParserError
68
+ nil
69
+ end
70
+ warn(why ? why.sub(/\A(\w+::)+\w+: /, "") : reason)
71
+ else
72
+ puts text
73
+ warn reason
74
+ end
58
75
  exit status
59
76
  end
77
+ exit 0 if status.zero? && MEMORY_COMMANDS.include?(ARGV.first.to_s.chomp("!"))
60
78
  status.zero? ? puts(text) : abort(text)
@@ -7,7 +7,7 @@ module Hecks
7
7
  # Runtime::RemoteDispatcher's writes: one Lambda invoke, one JSON round trip.
8
8
  #
9
9
  # The function name defaults to `"hecks-#{domain.downcase}"`, matching
10
- # `hecks deploy project`'s `stack_name` computation. A domain whose stack
10
+ # `hecks deploy recipe.project`'s `stack_name` computation. A domain whose stack
11
11
  # name doesn't follow that pattern must pass `function` explicitly, via
12
12
  # a `.world`'s `persisted_by("Lambda")`/`dispatched_by("Lambda")` block.
13
13
  class Client
@@ -38,7 +38,7 @@ module Hecks
38
38
  function = setting(settings, :function, nil)
39
39
  # `domain` only prefixes the instances lookup; the function to call is named by
40
40
  # DOMAIN_NAME (set in a deployed Lambda, where root is always /var/task), else by
41
- # root's basename, which matches hecks deploy project's stack name on a local boot.
41
+ # root's basename, which matches hecks deploy recipe.project's stack name on a local boot.
42
42
  function_domain = ENV["DOMAIN_NAME"] || (root ? File.basename(root) : domain)
43
43
  @client = Client.new(domain: function_domain, region: region, function: function)
44
44
  @prefix = "#{domain}::#{aggregate.hecks_name}#"
@@ -5,8 +5,8 @@ require "fileutils"
5
5
  module Hecks
6
6
  module Adapters
7
7
  # The imperative half of tenant provisioning, behind `Deploy::Tenant.port
8
- # "TenantProvisioning"`, for `Tenant.Provision` and for hecks deploy provision; booting the
9
- # domain and its capability gate stay in hecks deploy provision.
8
+ # "TenantProvisioning"`, for `Tenant.Provision` and for hecks deploy tenant.provision; booting
9
+ # the domain and its capability gate stay in hecks deploy tenant.provision.
10
10
  class TenantProvisioner
11
11
  # Raised when a value is outside its allow-list or the overlay path leaves `environments/`.
12
12
  class Refused < ArgumentError; end
@@ -3,7 +3,7 @@ module Hecks
3
3
  module Bluebook
4
4
  module DSL
5
5
  # Parses a `.hecksagon` file's DSL block into a Hecksagon: a domain's adapter bindings,
6
- # framework/vendored attachments and subscriptions — wiring, not part of the bluebook.
6
+ # attachments and subscriptions — wiring, not part of the bluebook.
7
7
  class HecksagonBuilder
8
8
  GRAMMAR_CONTEXT = "Hecksagon".freeze
9
9
 
@@ -57,20 +57,22 @@ module Hecks
57
57
  end
58
58
  end
59
59
 
60
- attr_reader :binds, :subscriptions, :framework_members, :vendored_bluebooks, :attached_chapters
60
+ # The release that drops the deprecated spellings of `attaches`; kept in step with
61
+ # `Hecks::Doors::REMOVAL`, which this file cannot require.
62
+ REMOVAL = "3.2.0".freeze
63
+
64
+ attr_reader :binds, :subscriptions, :attachments
61
65
 
62
66
  def initialize(domain)
63
67
  @domain = domain
64
68
  @binds = []
65
69
  @subscriptions = []
66
- @framework_members = []
67
- @vendored_bluebooks = []
68
- @attached_chapters = []
70
+ @attachments = []
69
71
  @bounded = false
70
72
  @translates = []
71
73
  end
72
74
 
73
- # Marks this chapter as a bounded context; framework/vendored attachments set it too.
75
+ # Marks this chapter as a bounded context; an attachment marks its chapter bounded too.
74
76
  # Must declare at least one `translates` ACL or boot refuses.
75
77
  def bounded
76
78
  @bounded = true
@@ -79,35 +81,37 @@ module Hecks
79
81
  # Subscribes to an event from outside this domain's own bluebook.
80
82
  def subscribe(event) = @subscriptions << event.to_s
81
83
 
82
- # Attaches a framework member (e.g. Governance) and loads it into the current registry.
84
+ # Attaches a chapter by name and loads it into the current registry, marked bounded.
85
+ #
86
+ # Without `from:` the name is a chapter the gem carries (a framework member such as
87
+ # Governance, or a language chapter such as Deploy) with the ports and adapters it
88
+ # ships. `from: :vendor` loads `vendor/embryonaut_bluebooks/<name>/bluebook` instead,
89
+ # and is never a fallback for a misspelt gem name.
90
+ #
91
+ # @param name [String, Symbol] a gem chapter's name or a vendored package's directory name
92
+ # @param from [Symbol, nil] `:vendor` for a vendored package, nil for a gem chapter
93
+ # @raise [Runtime::WiringError] if the name is unknown, or `from:` names another source
94
+ def attaches(name, from: nil)
95
+ unless [nil, :vendor].include?(from)
96
+ raise Runtime::WiringError,
97
+ "attaches #{name.to_s.inspect}, from: #{from.inspect} — `from:` takes only :vendor"
98
+ end
99
+
100
+ from == :vendor ? attach_vendored(name) : attach_gem(name)
101
+ end
102
+
103
+ # The deprecated spelling of `attaches "Name"` for a framework member.
83
104
  def uses_framework(name)
84
- @framework_members << name.to_s
85
- Hecks::Framework.load!(name)
86
- # Marks the framework member as bounded; the consumer's own hecksagon is the
87
- # anti-corruption layer.
105
+ deprecated_word("uses_framework", "attaches #{name.to_s.inspect}")
106
+ @attachments << Attachment.new(name: name.to_s, source: :gem)
107
+ self.class.with_real_modules { Hecks::Framework.load!(name) }
88
108
  Hecks.current_registry&.mark_bounded(name.to_s)
89
109
  end
90
110
 
91
- # Attaches a vendored embryonaut bluebook to this domain and loads its files into the
92
- # registry. Recorded separately from @framework_members, which is load-bearing for
93
- # governance checks.
111
+ # The deprecated spelling of `attaches "name", from: :vendor`.
94
112
  def uses_embryonaut_bluebook(name)
95
- @vendored_bluebooks << name.to_s
96
- Hecks::EmbryonautBluebook.load!(name)
97
- # Marks the chapter bounded, same as `uses_framework`; the directory name Pascal-cases
98
- # to the chapter name (`"membership"` → `Membership`).
99
- Hecks.current_registry&.mark_bounded(Hecks::Naming.pascal(name.to_s))
100
- end
101
-
102
- # Attaches a chapter the gem carries (the language, Expression, Tenancy, Deploy,
103
- # QualityControl) by name and loads its files into the registry, with the ports and
104
- # adapters it ships. Marked bounded like a framework member, since this hecksagon is its
105
- # anti-corruption layer.
106
- def attaches(name)
107
- require_relative "../../chapters"
108
- @attached_chapters << name.to_s
109
- self.class.with_real_modules { Hecks::Chapters.load!(name) }
110
- Hecks.current_registry&.mark_bounded(name.to_s)
113
+ deprecated_word("uses_embryonaut_bluebook", "attaches #{name.to_s.inspect}, from: :vendor")
114
+ attach_vendored(name)
111
115
  end
112
116
 
113
117
  # Declares a port at the hecksagon's root — the chapter as a whole, not one aggregate.
@@ -145,9 +149,32 @@ module Hecks
145
149
  # No ungoverned-role check here — it runs once on the merged result at verify! time.
146
150
  def build
147
151
  Hecksagon.new(domain: @domain, binds: @binds, subscriptions: @subscriptions,
148
- framework_members: @framework_members, vendored_bluebooks: @vendored_bluebooks,
149
- attached_chapters: @attached_chapters, bounded: @bounded, translates: @translates)
152
+ attachments: @attachments, bounded: @bounded, translates: @translates)
153
+ end
154
+
155
+ # Loads a chapter the gem carries and records it as attached from the gem.
156
+ def attach_gem(name)
157
+ require_relative "../../chapters"
158
+ @attachments << Attachment.new(name: name.to_s, source: :gem)
159
+ self.class.with_real_modules { Hecks::Chapters.attach!(name) }
160
+ Hecks.current_registry&.mark_bounded(name.to_s)
161
+ end
162
+ private :attach_gem
163
+
164
+ # Loads a vendored package and records it as attached from `vendor/`. The directory name
165
+ # Pascal-cases to the chapter name (`"membership"` becomes `Membership`).
166
+ def attach_vendored(name)
167
+ @attachments << Attachment.new(name: name.to_s, source: :vendor)
168
+ Hecks::EmbryonautBluebook.load!(name)
169
+ Hecks.current_registry&.mark_bounded(Hecks::Naming.pascal(name.to_s))
170
+ end
171
+ private :attach_vendored
172
+
173
+ # Tells the author that a word is going away.
174
+ def deprecated_word(word, instead)
175
+ warn "[hecks] `#{word}` is deprecated and is removed in #{REMOVAL}; use `#{instead}`"
150
176
  end
177
+ private :deprecated_word
151
178
 
152
179
  # Records any verb the grammar doesn't own as a domain-wide default bind, e.g. bare
153
180
  # `persisted_by "Heki"` at the top of a block, applied unless an aggregate overrides it.
@@ -20,6 +20,23 @@ module Hecks
20
20
  def aggregate_name = Naming.demodulise(aggregate)
21
21
  end
22
22
 
23
+ # One chapter a hecksagon attaches, with where it was found.
24
+ #
25
+ # `source` is `:gem` for a chapter the gem carries (a framework member such as Governance,
26
+ # or a language chapter such as Deploy) and `:vendor` for a package vendored into the
27
+ # consuming project, whose `name` is the package's directory name (`"membership"`).
28
+ Attachment = Struct.new(:name, :source, keyword_init: true) do
29
+ # @return [Boolean] whether the chapter is a vendored package
30
+ def vendor? = source == :vendor
31
+
32
+ # @return [String] the chapter's name in the registry: a gem chapter's own name, a
33
+ # vendored package's directory name in Pascal case
34
+ def chapter_name = vendor? ? Naming.pascal(name) : name
35
+
36
+ # @return [Hash{Symbol => String}] the attachment as plain data
37
+ def to_h = { name: name.to_s, source: source.to_s }
38
+ end
39
+
23
40
  # The built form of a `.hecksagon` file, produced by `DSL::HecksagonBuilder`.
24
41
  # `Behaviour::Hecksagon` supplies the bind lookups; this class holds the declared data.
25
42
  class Hecksagon
@@ -27,39 +44,31 @@ module Hecks
27
44
  include Behaviour::Hecksagon
28
45
 
29
46
  emits_ir(
30
- domain: :domain,
31
- binds: many(:binds),
32
- subscriptions: -> { subscriptions.map(&:to_s) },
33
- framework_members: -> { framework_members.map(&:to_s) },
34
- vendored_bluebooks: -> { vendored_bluebooks.map(&:to_s) },
35
- attached_chapters: -> { attached_chapters.map(&:to_s) },
36
- bounded: :bounded?,
37
- translates: -> { translates.map(&:to_s) }
47
+ domain: :domain,
48
+ binds: many(:binds),
49
+ subscriptions: -> { subscriptions.map(&:to_s) },
50
+ attachments: -> { attachments.map(&:to_h) },
51
+ bounded: :bounded?,
52
+ translates: -> { translates.map(&:to_s) }
38
53
  )
39
54
 
40
- attr_reader :domain, :binds, :subscriptions, :framework_members, :vendored_bluebooks,
41
- :attached_chapters, :translates
55
+ attr_reader :domain, :binds, :subscriptions, :attachments, :translates
42
56
 
43
57
  # @param domain [String, Symbol] the domain this hecksagon wires
44
58
  # @param binds [Array<Bluebook::Bind>] the declared adapter binds
45
59
  # @param subscriptions [Array<String, Symbol>] the external events this domain
46
60
  # subscribes to
47
- # @param framework_members [Array<String, Symbol>] attached framework members (`Governance`)
48
- # @param vendored_bluebooks [Array<String, Symbol>] the vendored embryonaut
49
- # bluebook package names this domain attaches
50
- # @param attached_chapters [Array<String, Symbol>] chapters the gem carries, attached by name
61
+ # @param attachments [Array<Bluebook::Attachment>] every chapter this hecksagon attaches,
62
+ # each with its source (`:gem` or `:vendor`)
51
63
  # @param bounded [Boolean] whether this chapter is an explicit bounded context
52
- # (consumer-owned; `uses_framework` / `uses_embryonaut_bluebook` mark
53
- # attached chapters bounded on the registry instead)
64
+ # (consumer-owned; `attaches` marks attached chapters bounded on the registry instead)
54
65
  # @param translates [Array<String>] names of `translates` ACL blocks declared here
55
- def initialize(domain:, binds: [], subscriptions: [], framework_members: [],
56
- vendored_bluebooks: [], attached_chapters: [], bounded: false, translates: [])
66
+ def initialize(domain:, binds: [], subscriptions: [], attachments: [], bounded: false,
67
+ translates: [])
57
68
  @domain = domain.to_s
58
69
  @binds = binds
59
70
  @subscriptions = subscriptions
60
- @framework_members = framework_members
61
- @vendored_bluebooks = vendored_bluebooks
62
- @attached_chapters = attached_chapters
71
+ @attachments = attachments
63
72
  @bounded = bounded ? true : false
64
73
  @translates = Array(translates).map(&:to_s)
65
74
  end
@@ -69,11 +78,22 @@ module Hecks
69
78
  # @return [Boolean] whether `bounded` was declared on this block
70
79
  def bounded? = @bounded
71
80
 
72
- # Every chapter this hecksagon brings into its domain's registry: framework members and
73
- # attached chapters alike, which resolve the same way.
81
+ # Every chapter this hecksagon brings into its domain's registry, gem and vendored alike.
82
+ #
83
+ # @return [Array<String>] the chapters' names as the registry holds them, in the order
84
+ # they were attached
85
+ def member_chapters = attachments.map(&:chapter_name)
86
+
87
+ # Says whether this hecksagon attaches a chapter, whatever its source.
88
+ #
89
+ # @param chapter [String, Symbol] a chapter's name as the registry holds it
90
+ # @return [Boolean] whether that chapter is attached here
91
+ def attaches?(chapter) = member_chapters.include?(chapter.to_s)
92
+
93
+ # The vendored packages this hecksagon attaches.
74
94
  #
75
- # @return [Array<String>] the chapters' names, framework members first
76
- def member_chapters = (framework_members + attached_chapters).map(&:to_s)
95
+ # @return [Array<String>] their directory names, in the order they were attached
96
+ def vendored_packages = attachments.select(&:vendor?).map { |attachment| attachment.name.to_s }
77
97
  end
78
98
 
79
99
  # The built form of a `.world` file, produced by `DSL::WorldBuilder`.
@@ -27,7 +27,7 @@ module Hecks
27
27
  # @param hecksagon [Bluebook::Hecksagon, nil] this bluebook's own sibling wiring
28
28
  # file, if the caller loaded one
29
29
  # @param known_domains [Set<String>, nil] every bluebook/hecksagon name booted
30
- # anywhere in this corpus scan, checked to catch a typo'd across/uses_framework target
30
+ # anywhere in this corpus scan, checked to catch a typo'd across/attaches target
31
31
  # @param global_emitted_events [Set<String>, nil] every event emitted anywhere in
32
32
  # the corpus, checked as a fallback so a translates reaction isn't flagged deaf
33
33
  # @param profile [Symbol, nil] :client adds ClientProfile's error findings
@@ -371,7 +371,7 @@ module Hecks
371
371
  findings
372
372
  end
373
373
 
374
- # `uses_framework` is a Shared Kernel relationship (X merges in, no boundary);
374
+ # `attaches` is a Shared Kernel relationship (X merges in, no boundary);
375
375
  # `across` is Customer/Supplier (real cross-Lambda RPC). This checks the choice
376
376
  # between them instead of leaving it as an unenforced comment. (ADR 0025)
377
377
  def cross_domain_policy_findings(policy, hecksagon, known_domains)
@@ -382,13 +382,13 @@ module Hecks
382
382
  target = policy.target_domain
383
383
  findings = []
384
384
 
385
- if hecksagon.framework_members.include?(target)
385
+ if hecksagon.attaches?(target)
386
386
  # Shared Kernel and Customer/Supplier are mutually exclusive claims about
387
387
  # the same target — declaring both means either a pointless RPC to a
388
- # domain already local, or a uses_framework that isn't doing its job.
388
+ # domain already local, or an attaches that isn't doing its job.
389
389
  findings << Finding.new(kind: :contradictory_relationship, severity: :error, subject: policy.name,
390
390
  message: "across #{target.inspect} dispatches over RPC (Customer/Supplier), " \
391
- "but this hecksagon also uses_framework #{target.inspect} (Shared " \
391
+ "but this hecksagon also attaches #{target.inspect} (Shared " \
392
392
  "Kernel) — #{target} is already loaded in-process here, so the two " \
393
393
  "relationship declarations contradict each other for the same " \
394
394
  "target domain")
@@ -399,7 +399,7 @@ module Hecks
399
399
  message: "across #{target.inspect} declares a Customer/Supplier " \
400
400
  "relationship, but nothing in this hecksagon records the " \
401
401
  "expectation — add subscribe \"#{target}.SomeEvent\" for what " \
402
- "you expect back from it, or uses_framework #{target.inspect} to " \
402
+ "you expect back from it, or attaches #{target.inspect} to " \
403
403
  "attach it in-process instead")
404
404
  end
405
405
 
@@ -426,7 +426,7 @@ module Hecks
426
426
  reached = []
427
427
  reached << "#{target} is a domain this corpus boots" if known_domains&.include?(target)
428
428
  if hecksagon
429
- reached << "this hecksagon uses_framework #{target.inspect}" if hecksagon.framework_members.include?(target)
429
+ reached << "this hecksagon attaches #{target.inspect}" if hecksagon.attaches?(target)
430
430
  if hecksagon.subscriptions.any? { |subscribed| Naming.qualifier(subscribed) == target }
431
431
  reached << "this hecksagon subscribes to #{target}"
432
432
  end
@@ -1,11 +1,12 @@
1
1
  require_relative "naming"
2
+ require_relative "framework"
2
3
  require_relative "runtime/registry"
3
4
  require_relative "bluebook/meta_validator"
4
5
 
5
6
  module Hecks
6
7
  # The chapters the gem carries that a hecksagon can attach by name (ADR 0080): the language
7
- # declared in itself, Expression, Tenancy, Deploy and QualityControl. Framework members stay in
8
- # `Framework`.
8
+ # declared in itself, Expression, Tenancy, Deploy, Site and QualityControl. Framework members stay
9
+ # in `Framework`; `table` and `attach!` find a name across both.
9
10
  #
10
11
  # A chapter is named by the `Hecks.bluebook "Name"` header of its files, and may span several.
11
12
  # Beside its bluebook a chapter may carry what every hecksagon attaching it needs, whatever
@@ -14,7 +15,7 @@ module Hecks
14
15
  module Chapters
15
16
  # Where attachable chapters live, relative to `lib/hecks/`.
16
17
  GLOBS = %w[language/**/*.bluebook grammar/expression.bluebook tenancy/bluebook/*.bluebook
17
- deploy/bluebook/*.bluebook quality_control/*.bluebook].freeze
18
+ deploy/bluebook/*.bluebook site/bluebook/*.bluebook quality_control/*.bluebook].freeze
18
19
 
19
20
  # Every attachable chapter, by name, with the files that declare it.
20
21
  #
@@ -26,6 +27,33 @@ module Hecks
26
27
  .freeze
27
28
  end
28
29
 
30
+ # Every chapter the gem carries, whichever module loads it: the one lookup table `attaches`
31
+ # finds a name in.
32
+ #
33
+ # @return [Hash{String => Array<String>}] chapter name to its files' absolute paths, the
34
+ # framework members (`Framework.members`) and the attachable chapters (`index`) together
35
+ def self.table
36
+ Framework.members.transform_values { |path| [path] }.merge(index)
37
+ end
38
+
39
+ # Loads one gem chapter by name, found in `table`: a framework member through
40
+ # `Framework.load!`, any other chapter through `load!`.
41
+ #
42
+ # @param name [String, Symbol] the chapter's name, such as `"Governance"` or `"Deploy"`
43
+ # @return [Boolean, nil] true when this call loaded it, nil when it was already held
44
+ # @raise [Runtime::WiringError] if the gem carries no chapter of that name; the message
45
+ # lists the known names and says how to attach a vendored package
46
+ def self.attach!(name)
47
+ key = name.to_s
48
+ return Framework.load!(key) if Framework.members.key?(key)
49
+ return load!(key) if index.key?(key)
50
+
51
+ raise Runtime::WiringError,
52
+ "attaches #{name.inspect}: the gem carries no chapter of that name — known: " \
53
+ "#{table.keys.sort.join(', ')}. To attach a vendored package, write " \
54
+ "`attaches #{name.to_s.inspect}, from: :vendor`"
55
+ end
56
+
29
57
  # Loads a chapter's files into the current registry, unless it already holds the chapter.
30
58
  #
31
59
  # The files load together inside `MetaValidator.defer`, so a chapter spread over several files
@@ -28,7 +28,7 @@ module Hecks
28
28
  # @raise [Errno::ENOENT, Errno::EACCES] if the file cannot be read
29
29
  # @raise [Runtime::EraCheck::ExpectedEra::Unreachable] if the host cannot be reached
30
30
  # @raise [Runtime::EraCheck::ExpectedEra::BadResponse] if it answers no era
31
- def assess(url, file, timeout: 10)
31
+ def assess(url, file, timeout:)
32
32
  expected = Runtime::EraCheck::ExpectedEra
33
33
  allowed = expected.parse(File.read(file))
34
34
  document = expected.fetch_version(url, timeout: timeout)
@@ -9,7 +9,7 @@ module Hecks
9
9
  # The command behind `hecks console` and Custodian's `Operation.OpenConsole`: boots a domain
10
10
  # (pizzas on Memory by default) and drops into IRB with its door installed, for dispatching a
11
11
  # real command by hand. No argument needs no database (ADR 0073); a domain directory boots as
12
- # it is wired, which may bind PostgresEra.
12
+ # it is wired, which may bind PostgresEra (examples/directory does).
13
13
  module Console
14
14
  PIZZAS = File.expand_path("../../../examples/pizzas", __dir__)
15
15
 
@@ -25,14 +25,27 @@ module Hecks
25
25
  # @param domain [String, nil] a domain directory; the bundled pizzas domain when nil
26
26
  # @param launcher [#call] starts the interactive session; IRB unless a caller stands in
27
27
  # @return [Runtime] the runtime the session ran against
28
- def call(domain = nil, launcher: -> { IRB.start(__FILE__) })
28
+ def call(domain = nil, launcher: -> { irb_session })
29
29
  runtime = domain ? Hecks.boot(domain) : Hecks.boot_files(DEFAULT_FILES)
30
30
  puts overview(runtime)
31
- puts banner
31
+ puts banner(domain)
32
32
  launcher.call
33
33
  runtime
34
34
  end
35
35
 
36
+ # Starts IRB with an empty command line. IRB reads `ARGV` for a script to run, and a
37
+ # launcher's `ARGV` holds its own words (`console`, `subject=<domain>`), which IRB would
38
+ # open as a file and then exit without a prompt.
39
+ #
40
+ # @return [void] when the session ends; `ARGV` is restored
41
+ def irb_session
42
+ words = ARGV.dup
43
+ ARGV.clear
44
+ IRB.start(__FILE__)
45
+ ensure
46
+ ARGV.replace(words)
47
+ end
48
+
36
49
  # @param runtime [Runtime] a booted domain
37
50
  # @return [String] each chapter's vision and its aggregates' commands
38
51
  def overview(runtime)
@@ -46,16 +59,19 @@ module Hecks
46
59
  end.join
47
60
  end
48
61
 
49
- # @return [String] the version and a first session to try against the pizzas domain
50
- def banner
62
+ # @param domain [String, nil] a domain directory, or nil for the bundled pizzas domain
63
+ # @return [String] the version, and for the pizzas domain a first session to try
64
+ def banner(domain = nil)
65
+ return "\nhecks #{Hecks::VERSION}\n\n" if domain
66
+
51
67
  <<~BANNER
52
68
 
53
69
  hecks #{Hecks::VERSION}
54
70
 
55
71
  try:
56
- order = Order.create_pizza!(name: { value: "Margherita" }, pizza: { price_cents: { cents: 1200 }, size: { value: "large" } })
57
- order.add_topping!(topping: { value: "Basil" }, amount: { value: 3 })
58
- order.purchase!(customer_name: { value: "Chris" }, amount: { cents: 1200 })
72
+ order = Order.create_pizza!(name: "Margherita", pizza: { price_cents: { cents: 1200 }, size: "large" })
73
+ order.add_topping!(topping: "Basil", amount: 3)
74
+ order.purchase!(customer_name: "Chris", amount: { cents: 1200 })
59
75
  order.status
60
76
  order.events.last
61
77
 
@@ -5,7 +5,7 @@ require_relative "../projections/deploy/template_diff"
5
5
 
6
6
  module Hecks
7
7
  module CLI
8
- # The command behind `hecks deploy diff` and `hecks deploy diff`: compares two
8
+ # The command behind `hecks deploy template_comparison.diff`: compares two
9
9
  # CloudFormation templates offline and reports differences by logical id; `--strict` also
10
10
  # counts cosmetic differences (comments, key order).
11
11
  module DeployTemplateDiff
@@ -24,7 +24,7 @@ module Hecks
24
24
  return 0 if clean
25
25
 
26
26
  err.puts "docs/implemented/reference/ is behind the language. " \
27
- "Regenerate with hecks project_reference to see the words in place."
27
+ "Regenerate with hecks language_run.project_reference to see the words in place."
28
28
  1
29
29
  end
30
30
  end