hecks 3.2.1 → 3.4.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 (120) hide show
  1. checksums.yaml +4 -4
  2. data/lib/hecks/bluebook/assembly/contracts.rb +2 -1
  3. data/lib/hecks/bluebook/dsl/bootstrap_table.rb +1 -0
  4. data/lib/hecks/bluebook/dsl/command_builder.rb +2 -39
  5. data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +21 -23
  6. data/lib/hecks/bluebook/dsl/need_word.rb +50 -0
  7. data/lib/hecks/bluebook/dsl/query_builder.rb +6 -1
  8. data/lib/hecks/bluebook/query.rb +8 -5
  9. data/lib/hecks/cli/interview_draft.rb +88 -33
  10. data/lib/hecks/cli/interview_session.rb +77 -26
  11. data/lib/hecks/cli/interview_shape.rb +198 -0
  12. data/lib/hecks/corpus.rb +3 -5
  13. data/lib/hecks/doors/cli_runner.rb +14 -1
  14. data/lib/hecks/doors/launcher_options.rb +13 -3
  15. data/lib/hecks/doors/mcp_door_scope.rb +2 -1
  16. data/lib/hecks/doors.rb +0 -17
  17. data/lib/hecks/embryonaut_bluebook/manifest.rb +117 -0
  18. data/lib/hecks/embryonaut_bluebook/registry.rb +209 -0
  19. data/lib/hecks/embryonaut_bluebook/vendor.rb +1 -1
  20. data/lib/hecks/embryonaut_bluebook.rb +2 -0
  21. data/lib/hecks/fuzzing/isolated_boot.rb +1 -4
  22. data/lib/hecks/fuzzing/qa_settings.rb +15 -34
  23. data/lib/hecks/fuzzing/target_capabilities.rb +1 -1
  24. data/lib/hecks/gate/stages.yml +113 -8
  25. data/lib/hecks/hecks/adapters/codebase/regeneration.rb +10 -1
  26. data/lib/hecks/hecks/adapters/first_admin.rb +108 -0
  27. data/lib/hecks/hecks/adapters/git.rb +59 -0
  28. data/lib/hecks/hecks/adapters/in_process_access.rb +25 -0
  29. data/lib/hecks/hecks/adapters/in_process_boot.rb +2 -0
  30. data/lib/hecks/hecks/adapters/rust_toolchain.rb +18 -3
  31. data/lib/hecks/hecks/adapters/site_toolchain.rb +67 -0
  32. data/lib/hecks/hecks/codebase.behaviors +6 -0
  33. data/lib/hecks/hecks/codebase.bluebook +21 -0
  34. data/lib/hecks/hecks/custodian.behaviors +6 -0
  35. data/lib/hecks/hecks/custodian.bluebook +217 -0
  36. data/lib/hecks/hecks/hecks.hecksagon +24 -0
  37. data/lib/hecks/hecks/hecks.world +8 -3
  38. data/lib/hecks/hecks/site.world +5 -2
  39. data/lib/hecks/language/bluebook/query.bluebook +25 -0
  40. data/lib/hecks/language/bluebook/vocabulary.bluebook +213 -0
  41. data/lib/hecks/language/hecksagon/hecksagon.bluebook +0 -5
  42. data/lib/hecks/language/oidc.json +5 -0
  43. data/lib/hecks/projections/deploy/box/hosting.rb +170 -0
  44. data/lib/hecks/projections/deploy/box/hosting_settings.rb +123 -0
  45. data/lib/hecks/projections/deploy/box/settings.rb +17 -4
  46. data/lib/hecks/projections/deploy/box/templates/deploy-service.services-roll.part +33 -0
  47. data/lib/hecks/projections/deploy/box/templates/deploy-service.sh.tmpl +79 -0
  48. data/lib/hecks/projections/deploy/box/templates/deploy-service.taskdef-roll.part +77 -0
  49. data/lib/hecks/projections/deploy/box/templates/deploy-service.taskdef.part +12 -0
  50. data/lib/hecks/projections/deploy/box/templates/hosting.mk.tmpl +24 -0
  51. data/lib/hecks/projections/deploy/box/templates/smoke-after-deploy.sh.tmpl +271 -0
  52. data/lib/hecks/projections/deploy/box.rb +15 -2
  53. data/lib/hecks/projections/deploy/fargate.rb +3 -3
  54. data/lib/hecks/projections/deploy/lambda.rb +8 -8
  55. data/lib/hecks/projections/model/deviations.rb +1 -1
  56. data/lib/hecks/projections/site/live_edge.rb +162 -0
  57. data/lib/hecks/quality_control/adapters/git_pr.rb +0 -14
  58. data/lib/hecks/quality_control/cli/qa_discover_external_domains.rb +1 -1
  59. data/lib/hecks/quality_control/cli/qa_open_pr.rb +32 -16
  60. data/lib/hecks/quality_control/cli/qa_seed_angles.rb +2 -2
  61. data/lib/hecks/quality_control/cli/qa_seed_targets.rb +1 -2
  62. data/lib/hecks/quality_control/quality_control.bluebook +76 -124
  63. data/lib/hecks/runtime/interpreting.rb +3 -22
  64. data/lib/hecks/runtime/loader.rb +6 -7
  65. data/lib/hecks/runtime/needs.rb +48 -0
  66. data/lib/hecks/runtime/query_interpreter.rb +3 -0
  67. data/lib/hecks/runtime/registry/verification.rb +3 -3
  68. data/lib/hecks/runtime.rb +6 -11
  69. data/lib/hecks/rust_build/host.rb +147 -0
  70. data/lib/hecks/rust_build/kernel_input.rb +40 -18
  71. data/lib/hecks/rust_build/project_rust/codegen_run.rb +0 -18
  72. data/lib/hecks/rust_build/project_rust.rb +3 -10
  73. data/lib/hecks/rust_build.rb +1 -0
  74. data/lib/hecks/site/bluebook/site.bluebook +53 -1
  75. data/lib/hecks/site/bluebook/site.ports.hecksagon +6 -0
  76. data/lib/hecks/sme/bluebook/sme.bluebook +128 -1
  77. data/lib/hecks/tools/gate.rb +1 -1
  78. data/lib/hecks/tools/rust_comment_style.rb +1 -3
  79. data/lib/hecks/tools/tools_doc.rb +204 -0
  80. data/lib/hecks/tools.rb +1 -0
  81. data/lib/hecks/version.rb +1 -1
  82. data/lib/hecks/vocabulary.rb +151 -0
  83. data/lib/hecks.rb +6 -12
  84. data/qa/settings.yml +1 -2
  85. data/rust/build/src/lineage_pass.rs +30 -15
  86. data/rust/build/src/main.rs +4 -2
  87. data/rust/build/src/pipeline.rs +8 -19
  88. data/rust/codegen/src/bridging.rs +7 -7
  89. data/rust/codegen/src/json.rs +214 -23
  90. data/rust/codegen/src/literal.rs +6 -3
  91. data/rust/codegen/src/main.rs +20 -9
  92. data/rust/codegen/src/naming.rs +4 -2
  93. data/rust/codegen/src/optional_pass.rs +226 -0
  94. data/rust/{build → codegen}/src/sidecars.rs +9 -5
  95. data/rust/host/HECKS_RELEASE +1 -1
  96. data/rust/host/src/api.rs +1 -1
  97. data/rust/host/src/dispatch.rs +5 -0
  98. data/rust/host/src/needs.rs +99 -2
  99. data/rust/host/src/resend.rs +4 -4
  100. data/rust/host/src/ui_schema.rs +1 -1
  101. data/rust/parser/src/emit.rs +12 -0
  102. data/rust/parser/src/ir.rs +2 -0
  103. data/rust/parser/src/keywords.rs +2 -4
  104. data/rust/parser/src/lex.rs +1 -1
  105. data/rust/parser/src/main.rs +1 -0
  106. data/rust/parser/src/parse/command.rs +2 -44
  107. data/rust/parser/src/parse/hecksagon.rs +15 -24
  108. data/rust/parser/src/parse/mod.rs +1 -0
  109. data/rust/parser/src/parse/needs.rs +60 -0
  110. data/rust/parser/src/parse/query.rs +3 -0
  111. data/rust/src/kernel/cli.rs +16 -6
  112. data/rust/src/kernel/needs.rs +104 -9
  113. metadata +22 -9
  114. data/lib/hecks/facade.rb +0 -31
  115. data/lib/hecks/rust_build/append_optionals.rb +0 -79
  116. data/lib/hecks/rust_build/rust_literal.rb +0 -33
  117. data/lib/hecks/three_zero/forms.yml +0 -127
  118. data/lib/hecks/three_zero.rb +0 -12
  119. data/rust/build/src/json.rs +0 -421
  120. data/rust/build/src/optional_pass.rs +0 -150
@@ -60,6 +60,55 @@ module Hecks
60
60
  { report: { value: out.string } }
61
61
  end
62
62
 
63
+ # Checks a project's vendored packages against their `bluebook.lock` files and describes
64
+ # them as the manifest a client image carries, through `Hecks::EmbryonautBluebook::Manifest`.
65
+ #
66
+ # The manifest records the project's own commit and whether its tree has uncommitted changes,
67
+ # read from the repository the project stands in (`unknown` outside one).
68
+ #
69
+ # @param held [Hash] the `Package` query's arguments: `root` (the project; the current
70
+ # directory when absent)
71
+ # @return [Hash{Symbol => String}] `text:` the manifest as JSON
72
+ # @raise [Runtime::NotFound] naming each package whose files disagree with its lock
73
+ def verify(**held)
74
+ root = File.expand_path(plain(held[:root]) || Dir.pwd)
75
+ raise Runtime::NotFound, "#{root} is not a directory" unless File.directory?(root)
76
+
77
+ { text: EmbryonautBluebook::Manifest.new(root, built_from: built_from(root)).to_json_text }
78
+ rescue EmbryonautBluebook::Manifest::Mismatch => e
79
+ raise Runtime::NotFound, e.message
80
+ end
81
+
82
+ # Judges every package of a bluebook registry against its latest release, through
83
+ # `Hecks::EmbryonautBluebook::Registry#check`.
84
+ #
85
+ # @param held [Hash] the `Registry` query's arguments: `root` (the registry; the current
86
+ # directory when absent)
87
+ # @return [Hash{Symbol => String}] `text:` the notes and `versions ok`
88
+ # @raise [Runtime::NotFound] naming each package at fault, or when `root` is no repository
89
+ def check(**held)
90
+ report = registry(held).check
91
+ raise Runtime::NotFound, report.to_s unless report.ok?
92
+
93
+ { text: report.to_s }
94
+ rescue Vendoring::Error => e
95
+ raise Runtime::NotFound, e.message
96
+ end
97
+
98
+ # Tags a package of a bluebook registry at the version its `bluebook.yml` carries, through
99
+ # `Hecks::EmbryonautBluebook::Registry#release`. The tag is made in the local repository and
100
+ # never pushed.
101
+ #
102
+ # @param held [Hash] the `Registry` record: `package`, and `root` (the current directory when
103
+ # absent)
104
+ # @return [Hash{Symbol => Hash}] `report:` the tag made and the command that publishes it
105
+ # @raise [ConsoleCapture::Failure] when the package breaks a release rule
106
+ def tag(**held)
107
+ { report: { value: registry(held).release(plain(held[:package])).to_s } }
108
+ rescue Vendoring::Error => e
109
+ raise ConsoleCapture::Failure, e.message
110
+ end
111
+
63
112
  # Who the repository at a directory commits as: its configured name and email.
64
113
  #
65
114
  # @param chdir [String, nil] a directory inside the repository; the current one when nil
@@ -87,6 +136,16 @@ module Hecks
87
136
 
88
137
  private
89
138
 
139
+ def registry(held)
140
+ EmbryonautBluebook::Registry.new(File.expand_path(plain(held[:root]) || Dir.pwd))
141
+ end
142
+
143
+ def built_from(root)
144
+ head = capture("rev-parse", "HEAD", chdir: root)
145
+ status = capture("status", "--porcelain", "--", ".", chdir: root)
146
+ { "commit" => head.ok? ? head.out.strip : "unknown", "dirty" => status.ok? && !status.out.strip.empty? }
147
+ end
148
+
90
149
  def plain(argument) = argument.is_a?(Hash) ? argument[:value] : argument
91
150
 
92
151
  # A source that is not a repository is worded here; the vendoring's own refusal for it is a
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "first_admin"
4
+
5
+ module Hecks
6
+ module Adapters
7
+ # What the `DomainRuntime` port's adapter does when an `Operation` asks it to give a domain its
8
+ # first administrator.
9
+ module InProcessAccess
10
+ # Boots the domain and grants the first administrator through its membership chapter.
11
+ #
12
+ # @param held [Hash] the `Operation` record: `subject` (the domain), `email`, and optionally
13
+ # `name` and `role`
14
+ # @return [Hash{Symbol => Hash}] `output:` the sentence saying who now holds which role
15
+ # @raise [Runtime::NotFound] if the domain cannot be found or has no membership chapter, or an
16
+ # administrator already exists
17
+ def bootstrap(**held)
18
+ result = FirstAdmin.new(boot(held[:subject])).call(
19
+ email: plain(held[:email]), name: plain(held[:name]), role: plain(held[:role])
20
+ )
21
+ output(result.to_s)
22
+ end
23
+ end
24
+ end
25
+ end
@@ -9,6 +9,7 @@ require_relative "../../cli/stores"
9
9
  require_relative "../../cli/shape"
10
10
  require_relative "../../cli/history"
11
11
  require_relative "in_process_operations"
12
+ require_relative "in_process_access"
12
13
 
13
14
  module Hecks
14
15
  module Adapters
@@ -25,6 +26,7 @@ module Hecks
25
26
  # a projection refresh, a behaviors run, a smoke test and a bounded follow.
26
27
  class InProcessBoot
27
28
  include InProcessOperations
29
+ include InProcessAccess
28
30
 
29
31
  # Accepts the arguments every driven adapter is built with and keeps none of them.
30
32
  #
@@ -19,9 +19,9 @@ module Hecks
19
19
  # minutes, so it starts as a child through `ProcessPool`.
20
20
  class RustToolchain
21
21
  # The `Hecks::RustBuild` tool each ask runs.
22
- TOOLS = { generate: "project_rust", wasm: "project_wasm", browser_wasm: "project_wasm_browser",
23
- conform: "rust_conformance", replay: "rust_conformance_fuzz",
24
- coverage: "rust_coverage" }.freeze
22
+ TOOLS = { generate: "project_rust", wasm: "project_wasm", host: "project_host",
23
+ browser_wasm: "project_wasm_browser", conform: "rust_conformance",
24
+ replay: "rust_conformance_fuzz", coverage: "rust_coverage" }.freeze
25
25
 
26
26
  # The load path the benchmark child runs with: this checkout's or gem's `lib/`.
27
27
  LIB = File.expand_path("../../..", __dir__)
@@ -73,6 +73,21 @@ module Hecks
73
73
  child(:wasm, [plain(held[:domain])])
74
74
  end
75
75
 
76
+ # Generates the domain, compiles it to a `wasm32-wasip1` module, builds the host binary from
77
+ # the workspace and stages the three files in a directory an image copies from.
78
+ #
79
+ # @param held [Hash] the `Build` record: `domain`, `target` (the machine's own when absent)
80
+ # and `stage_dir` (`.hecks/host/<target>/` when absent)
81
+ # @return [Hash{Symbol => Hash}] `output:` where the files were staged
82
+ # @raise [ConsoleCapture::Failure] when the toolchain or the target is missing, or a step
83
+ # fails
84
+ def host(**held)
85
+ argv = [plain(held[:domain])]
86
+ argv << "--target=#{plain(held[:target])}" if plain(held[:target])
87
+ argv << "--stage=#{plain(held[:stage_dir])}" if plain(held[:stage_dir])
88
+ child(:host, argv)
89
+ end
90
+
76
91
  # Generates the domain, then compiles it to an ES module for a browser through wasm-bindgen.
77
92
  #
78
93
  # @param held [Hash] the `Build` record: `domain`
@@ -1,8 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "json"
3
4
  require_relative "console_capture"
5
+ require_relative "shell"
4
6
  require_relative "codebase/tree"
5
7
  require_relative "codebase/ruby_child"
8
+ require "hecks/tools"
9
+ require "hecks/tools/site_routes"
10
+ require "hecks/projections/site/live_edge"
6
11
 
7
12
  module Hecks
8
13
  module Adapters
@@ -44,8 +49,70 @@ module Hecks
44
49
  { output: { value: result.out } }
45
50
  end
46
51
 
52
+ # Compares the CloudFront behaviours a project's edge generates with a live distribution's.
53
+ # The live configuration is read from a saved `get-distribution-config` answer (`live`) or
54
+ # fetched with that one read-only call (`distribution`, the distribution's id); nothing is
55
+ # changed on either side.
56
+ #
57
+ # @param held [Hash] the `SiteProjection` record: `domain`, and `live` or `distribution`,
58
+ # with `expect_new` (path patterns, comma separated) and `refs` (`!Ref Name=id` words,
59
+ # comma separated) when set
60
+ # @return [Hash{Symbol => Hash}] `output:` the report of a distribution that matches
61
+ # @raise [ConsoleCapture::Failure] when the project has no edge, the live configuration
62
+ # cannot be read, or the two differ; the message is the report
63
+ def compare(**held)
64
+ edge = edge_of(located(:domain, held[:domain]))
65
+ live = live_configuration(held)
66
+ live_edge = Projections::Site::LiveEdge
67
+ comparison = live_edge.new(edge, live, expect_new: words(held[:expect_new]),
68
+ refs: live_edge.refs_from(plain(held[:refs]))).call
69
+ raise ConsoleCapture::Failure, comparison.to_s unless comparison.clean?
70
+
71
+ { output: { value: comparison.to_s } }
72
+ end
73
+
47
74
  private
48
75
 
76
+ # The project's checked edge, read the way `project_site` reads it.
77
+ def edge_of(root)
78
+ registry = nil
79
+ outcome = ConsoleCapture.capture { registry = Tools::SiteRoutes.registry_for(root) }
80
+ raise ConsoleCapture::Failure, outcome.output.strip unless outcome.ok?
81
+
82
+ site = Projections::Site
83
+ chapter = site::Table.chapter(registry)
84
+ table = site::Table.read(chapter, registry: registry)
85
+ site::Edge.read(chapter, table: table, vocabulary: site::Table.vocabulary(registry)) ||
86
+ raise(ConsoleCapture::Failure, "#{root} declares no Edge rows")
87
+ rescue Projections::Site::Table::Invalid => e
88
+ raise ConsoleCapture::Failure, e.message
89
+ end
90
+
91
+ def live_configuration(held)
92
+ file = plain(held[:live])
93
+ id = plain(held[:distribution])
94
+ text = file ? read_saved(file) : fetch(id)
95
+ JSON.parse(text)
96
+ rescue JSON::ParserError => e
97
+ raise ConsoleCapture::Failure, "the live configuration is not JSON: #{e.message.lines.first.strip}"
98
+ end
99
+
100
+ def read_saved(path)
101
+ File.read(File.expand_path(path))
102
+ rescue SystemCallError => e
103
+ raise ConsoleCapture::Failure, "cannot read the live configuration: #{e.message}"
104
+ end
105
+
106
+ # The one AWS call: reading a distribution's configuration.
107
+ def fetch(id)
108
+ result = Shell.new.capture("aws", "cloudfront", "get-distribution-config", "--id", id.to_s)
109
+ return result.out if result.ok?
110
+
111
+ raise ConsoleCapture::Failure, "aws cloudfront get-distribution-config failed: #{message_of(result)}"
112
+ end
113
+
114
+ def words(argument) = plain(argument).to_s.split(",").map(&:strip).reject(&:empty?)
115
+
49
116
  def message_of(result)
50
117
  text = [result.err, result.out].map(&:strip).reject(&:empty?).join("\n")
51
118
  text.empty? ? "the tool ended with status #{result.status.exitstatus}" : text
@@ -85,6 +85,12 @@ Hecks.behaviors "Codebase" do
85
85
  expect refused: "must match"
86
86
  end
87
87
 
88
+ test "ProjectToolsDoc refuses a run that is not named" do
89
+ tests "ProjectToolsDoc", on: "RegenerationRun"
90
+ input run: { value: "" }
91
+ expect refused: "must match"
92
+ end
93
+
88
94
  test "DecideCiGate refuses a run that is not named" do
89
95
  tests "DecideCiGate", on: "RegenerationRun"
90
96
  input run: { value: "" }, gate: { value: "runtime_changed" }
@@ -1027,6 +1027,22 @@ Hecks.bluebook "Hecks" do
1027
1027
  emits ProjectCiGatesRequested
1028
1028
  end
1029
1029
 
1030
+ command "ProjectToolsDoc" do
1031
+ role "Maintainer"
1032
+ goal "Write the launcher forms of docs/tools.md from the RetiredScript rows, or with check only compare them"
1033
+
1034
+ attribute :check, Switch, optional: true
1035
+ attribute :run, RunKey
1036
+ attribute :confirm, Switch, optional: true
1037
+
1038
+ sets :check
1039
+ sets :run
1040
+ sets :confirm
1041
+ sets :operation, to: "project_tools_doc"
1042
+
1043
+ emits ProjectToolsDocRequested
1044
+ end
1045
+
1030
1046
  command "DecideCiGate" do
1031
1047
  role "Maintainer"
1032
1048
  goal "Say whether this run's change touches what a CiGate row guards, as the gated job's detector"
@@ -1105,6 +1121,11 @@ Hecks.bluebook "Hecks" do
1105
1121
  trigger RegenerationRun::SourceTree::Examine, with: { run: :run }
1106
1122
  end
1107
1123
 
1124
+ policy "ExamineWhenProjectToolsDocRequested" do
1125
+ on "RegenerationRun.ProjectToolsDocRequested"
1126
+ trigger RegenerationRun::SourceTree::Examine, with: { run: :run }
1127
+ end
1128
+
1108
1129
  policy "ExamineWhenDecideCiGateRequested" do
1109
1130
  on "RegenerationRun.DecideCiGateRequested"
1110
1131
  trigger RegenerationRun::SourceTree::Examine, with: { run: :run }
@@ -176,4 +176,10 @@ Hecks.behaviors "Custodian" do
176
176
  input domain: { value: "examples/pizzas" }, run: { value: "" }
177
177
  expect refused: "must match"
178
178
  end
179
+
180
+ test "BuildHost refuses a target that is not a triple" do
181
+ tests "BuildHost", on: "Build"
182
+ input domain: { value: "examples/pizzas" }, run: { value: "host" }, target: { value: "arm64" }
183
+ expect refused: "must match"
184
+ end
179
185
  end
@@ -238,6 +238,9 @@ Hecks.bluebook "Hecks" do
238
238
  attribute :payload_file, PayloadFile, optional: true
239
239
  attribute :health_path, RoutePath, optional: true
240
240
  attribute :state_path, RoutePath, optional: true
241
+ attribute :email, Email, optional: true
242
+ attribute :name, PersonName, optional: true
243
+ attribute :role, RoleName, optional: true
241
244
  attribute :output, Output, optional: true
242
245
  attribute :refusal, Output, optional: true
243
246
  identified_by :run
@@ -294,6 +297,19 @@ Hecks.bluebook "Hecks" do
294
297
  attribute :value, String
295
298
  end
296
299
 
300
+ # Who the first administrator is: a person of the domain's membership chapter.
301
+ value_object "Email" do
302
+ attribute :value, String, pattern: '^[^ \t\n\r@]+@[^ \t\n\r@]+$'
303
+ end
304
+
305
+ value_object "PersonName" do
306
+ attribute :value, String, pattern: '[^\t\n\r]'
307
+ end
308
+
309
+ value_object "RoleName" do
310
+ attribute :value, String, pattern: '^[^ \t\n\r]+$'
311
+ end
312
+
297
313
  lifecycle :status, default: "requested" do
298
314
  transition "Succeed" => "succeeded", from: "requested"
299
315
  transition "Fail" => "failed", from: "requested"
@@ -404,6 +420,32 @@ Hecks.bluebook "Hecks" do
404
420
  emits SmokeHttpRequested
405
421
  end
406
422
 
423
+ # `hecks operation.bootstrap_admin <domain> email=<email> [name=<name>] [role=<role>]`. A
424
+ # domain whose chapter `provides "membership"` has nobody who may grant access until someone is
425
+ # granted it. This admits the person and grants them the role the grant command is gated to
426
+ # (or `role`), as a caller that names that role and binds no `actor_id`: Governance checks that
427
+ # caller by the role it states, which is how the first grant gets made (ADR 0025). It is
428
+ # refused once anyone holds the administrator role (`Admin`, or `Owner`), and the domain's
429
+ # persistence must be the one the service runs on (`DATABASE_URL`), not Memory.
430
+ command "BootstrapAdmin" do
431
+ role "Operator"
432
+ goal "Admit the first administrator of a domain's membership chapter and grant them access"
433
+
434
+ attribute :run, OperationKey
435
+ attribute :subject, Subject
436
+ attribute :email, Email
437
+ attribute :name, PersonName, optional: true
438
+ attribute :role, RoleName, optional: true
439
+
440
+ sets :run
441
+ sets :subject
442
+ sets :email
443
+ sets :name
444
+ sets :role
445
+
446
+ emits AdminBootstrapRequested
447
+ end
448
+
407
449
  command "Succeed" do
408
450
  role "System"
409
451
  goal "Record that the operation ended well, and what it reported"
@@ -494,6 +536,11 @@ Hecks.bluebook "Hecks" do
494
536
  trigger Operation::DomainRuntime::Smoke, with: { run: :run }
495
537
  end
496
538
 
539
+ policy "BootstrapWhenRequested" do
540
+ on "Operation.AdminBootstrapRequested"
541
+ trigger Operation::DomainRuntime::Bootstrap, with: { run: :run }
542
+ end
543
+
497
544
  policy "OpenWhenRequested" do
498
545
  on "Operation.ConsoleRequested"
499
546
  trigger Operation::Terminal::Open, with: { run: :run }
@@ -1173,6 +1220,11 @@ policy "RecordTheEra" do
1173
1220
  attribute :value, String
1174
1221
  end
1175
1222
 
1223
+ # An answer that is one document of text, exactly as the tool that produced it wrote it.
1224
+ value_object "Document" do
1225
+ attribute :text, String
1226
+ end
1227
+
1176
1228
  lifecycle :status, default: "requested" do
1177
1229
  transition "Revendor" => "requested", from: %w[vendored refused]
1178
1230
  transition "Complete" => "vendored", from: "requested"
@@ -1248,6 +1300,14 @@ policy "RecordTheEra" do
1248
1300
  where(status: "refused")
1249
1301
  order_by :package
1250
1302
  end
1303
+
1304
+ # `hecks package.verify [root=path]`: a read of the files, so it keeps no record. The manifest
1305
+ # is the answer; a package that disagrees with its lock refuses it, naming each one.
1306
+ query "Verify" do
1307
+ description "Check every vendored package against its bluebook.lock, then describe them as the manifest a client image carries."
1308
+ attribute :root, Location, optional: true
1309
+ returns Document
1310
+ end
1251
1311
  end
1252
1312
 
1253
1313
  policy "PinWhenVendored" do
@@ -1270,6 +1330,125 @@ policy "RecordTheEra" do
1270
1330
  trigger Package::Abandon, with: { refusal: :refusal }
1271
1331
  end
1272
1332
 
1333
+ aggregate "Registry" do
1334
+ description "A bluebook registry repository: packages versioned in it and released as tags."
1335
+
1336
+ attribute :run, RegistryKey
1337
+ attribute :package, PackageName, optional: true
1338
+ attribute :root, Location, optional: true
1339
+ attribute :report, Report, optional: true
1340
+ attribute :refusal, Report, optional: true
1341
+ identified_by :run
1342
+
1343
+ value_object "RegistryKey" do
1344
+ attribute :value, String, pattern: '[^ \t\n\r]'
1345
+
1346
+ invariant("a release is named") { !value.to_s.empty? }
1347
+ end
1348
+
1349
+ value_object "PackageName" do
1350
+ attribute :value, String, pattern: '^[a-z][a-z0-9_]*$'
1351
+ end
1352
+
1353
+ # The registry repository: the current directory when absent.
1354
+ value_object "Location" do
1355
+ attribute :value, String, pattern: '[^\t\n\r]'
1356
+ end
1357
+
1358
+ value_object "Report" do
1359
+ attribute :value, String
1360
+ end
1361
+
1362
+ # An answer that is one document of text, exactly as the tool that produced it wrote it.
1363
+ value_object "Document" do
1364
+ attribute :text, String
1365
+ end
1366
+
1367
+ lifecycle :status, default: "requested" do
1368
+ transition "Complete" => "tagged", from: "requested"
1369
+ transition "Abandon" => "refused", from: "requested"
1370
+ end
1371
+
1372
+ # `hecks package.release <package> [root=path]`. Tags the package's current version as
1373
+ # `<package>-vX.Y.Z` in the local repository and never pushes; the report ends in the command
1374
+ # that does. A refusal says which rule the package broke.
1375
+ command "Release" do
1376
+ role "Operator"
1377
+ goal "Tag a package of this registry at the version its bluebook.yml carries"
1378
+
1379
+ attribute :run, RegistryKey
1380
+ attribute :package, PackageName
1381
+ attribute :root, Location, optional: true
1382
+
1383
+ sets :run
1384
+ sets :package
1385
+ sets :root
1386
+
1387
+ emits ReleaseRequested
1388
+ end
1389
+
1390
+ command "Complete" do
1391
+ role "System"
1392
+ goal "Record that the release was tagged, and the command that publishes it"
1393
+
1394
+ reference_to Registry
1395
+ attribute :report, Report
1396
+
1397
+ sets :report
1398
+
1399
+ emits ReleaseRecorded
1400
+ end
1401
+
1402
+ command "Abandon" do
1403
+ role "System"
1404
+ goal "Record that the release was refused, and why"
1405
+
1406
+ reference_to Registry
1407
+ attribute :refusal, Report
1408
+
1409
+ sets :refusal
1410
+
1411
+ emits ReleaseAbandoned
1412
+ end
1413
+
1414
+ # `hecks package.check [root=path]`: a read of the repository's files and tags, so it keeps no
1415
+ # record. A package that changed since its latest release without a version bump and a changelog
1416
+ # entry refuses it, naming each one.
1417
+ query "Check" do
1418
+ description "Judge every package against its latest release: a changed package needs a newer version and a changelog entry."
1419
+ attribute :root, Location, optional: true
1420
+ returns Document
1421
+ end
1422
+
1423
+ query "Releasing" do
1424
+ description "What releasing one run did: its status, and the report or the refusal."
1425
+ attribute :run, RegistryKey
1426
+ where(run: :run)
1427
+ order_by :run
1428
+ end
1429
+
1430
+ query "Refused" do
1431
+ description "Every release that was refused."
1432
+ where(status: "refused")
1433
+ order_by :run
1434
+ end
1435
+ end
1436
+
1437
+ policy "TagWhenReleased" do
1438
+ on "Registry.ReleaseRequested"
1439
+ trigger Registry::Git::Tag, with: { run: :run }
1440
+ end
1441
+
1442
+ policy "RecordTheTag" do
1443
+ on "Registry.ReleaseTagged"
1444
+ trigger Registry::Complete, with: { report: :report }
1445
+ end
1446
+
1447
+ policy "RecordTheRefusedTag" do
1448
+ on "Registry.ReleaseRefused"
1449
+ trigger Registry::Abandon, with: { refusal: :refusal }
1450
+ end
1451
+
1273
1452
  # A run record for opening a door onto the framework, not a door: the doors live in `Hecks::Doors`.
1274
1453
  aggregate "Door" do
1275
1454
  description "A way into the framework opened for a project: its launchers, or an MCP server."
@@ -1481,6 +1660,8 @@ policy "RecordTheEra" do
1481
1660
  attribute :domain, DomainPath, optional: true
1482
1661
  attribute :script, ScriptPath, optional: true
1483
1662
  attribute :artifact, Artifact, optional: true
1663
+ attribute :target, TargetTriple, optional: true
1664
+ attribute :stage_dir, StagePath, optional: true
1484
1665
  attribute :seeds, Count, optional: true
1485
1666
  attribute :steps, Count, optional: true
1486
1667
  attribute :output, Report, optional: true
@@ -1515,6 +1696,17 @@ policy "RecordTheEra" do
1515
1696
  attribute :value, String, pattern: '[^ \t\n\r]'
1516
1697
  end
1517
1698
 
1699
+ # A Rust target triple: `<arch>-<vendor>-<os>` with an optional `-<env>`. The toolchain
1700
+ # refuses the triples a host cannot be built for (a wasm target) and the ones not installed.
1701
+ value_object "TargetTriple" do
1702
+ attribute :value, String, pattern: '^[a-z0-9_]+(-[a-z0-9_.]+){2,3}$'
1703
+ end
1704
+
1705
+ # The directory a host build stages its files in.
1706
+ value_object "StagePath" do
1707
+ attribute :value, String, pattern: '[^\t\n\r]'
1708
+ end
1709
+
1518
1710
  value_object "Count" do
1519
1711
  attribute :value, Integer
1520
1712
 
@@ -1563,6 +1755,26 @@ policy "RecordTheEra" do
1563
1755
  emits WasmBuildRequested
1564
1756
  end
1565
1757
 
1758
+ # Compiles the host binary from the installed gem's workspace for the target and stages it with
1759
+ # the domain's `.wasm` and `.ir.json` (in `.hecks/host/<target>/` unless `stage_dir` names a
1760
+ # directory), the files a container image copies in.
1761
+ command "BuildHost" do
1762
+ role "Operator"
1763
+ goal "Build the Rust host for a domain, with its wasm and IR, ready to copy into an image"
1764
+
1765
+ attribute :domain, DomainPath
1766
+ attribute :run, BuildKey
1767
+ attribute :target, TargetTriple, optional: true
1768
+ attribute :stage_dir, StagePath, optional: true
1769
+
1770
+ sets :domain
1771
+ sets :run
1772
+ sets :target
1773
+ sets :stage_dir
1774
+
1775
+ emits HostBuildRequested
1776
+ end
1777
+
1566
1778
  command "BuildBrowserWasm" do
1567
1779
  role "Operator"
1568
1780
  goal "Compile a domain to an ES module a browser can import"
@@ -1685,6 +1897,11 @@ policy "RecordTheEra" do
1685
1897
  trigger Build::RustToolchain::Wasm, with: { run: :run }
1686
1898
  end
1687
1899
 
1900
+ policy "BuildHostWhenRequested" do
1901
+ on "Build.HostBuildRequested"
1902
+ trigger Build::RustToolchain::Host, with: { run: :run }
1903
+ end
1904
+
1688
1905
  policy "CompileBrowserWasmWhenRequested" do
1689
1906
  on "Build.BrowserWasmBuildRequested"
1690
1907
  trigger Build::RustToolchain::BrowserWasm, with: { run: :run }
@@ -43,6 +43,12 @@ Hecks.hecksagon "Hecks" do
43
43
  refuses "OperationRefused"
44
44
  end
45
45
 
46
+ asks "Bootstrap", to: Operation do
47
+ attribute :run, OperationKey
48
+ answers "OperationAnswered"
49
+ refuses "OperationRefused"
50
+ end
51
+
46
52
  answers_query "Follow"
47
53
  end
48
54
 
@@ -87,6 +93,18 @@ Hecks.hecksagon "Hecks" do
87
93
  answers "PackagePinned"
88
94
  refuses "PinRefused"
89
95
  end
96
+
97
+ answers_query "Verify"
98
+ end
99
+
100
+ Hecks::Registry.port "Git" do
101
+ asks "Tag", to: Registry do
102
+ attribute :run, RegistryKey
103
+ answers "ReleaseTagged"
104
+ refuses "ReleaseRefused"
105
+ end
106
+
107
+ answers_query "Check"
90
108
  end
91
109
 
92
110
  Hecks::Door.port "Workspace" do
@@ -130,6 +148,12 @@ Hecks.hecksagon "Hecks" do
130
148
  refuses "BuildRefused"
131
149
  end
132
150
 
151
+ asks "Host", to: Build do
152
+ attribute :run, BuildKey
153
+ answers "BuildAnswered"
154
+ refuses "BuildRefused"
155
+ end
156
+
133
157
  asks "BrowserWasm", to: Build do
134
158
  attribute :run, BuildKey
135
159
  answers "BuildAnswered"
@@ -25,8 +25,10 @@
25
25
  # `Doors::LauncherOptions`): `run_keys` mints a `run` key for a creating command given none,
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
- # `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_commands` names
28
+ # `settled` names the commands that always wait for their reactions, as if `--wait` were given, so a
29
+ # refusal is an exit status and a reason on standard error (`package.vendor`, which a script runs
30
+ # for its outcome), `executable` is where `hecks project_cli` writes this chapter's launcher (the
31
+ # gem's `exe/hecks`), `streams` lists the questions `--stream` may tail (`Follow`), `memory_commands` names
30
32
  # the commands that launcher runs on the Memory environment unless `HECKS_ENVIRONMENT` is set (so
31
33
  # `hecks console` needs no database), and `legacy` names the commands it hands to `Hecks::CLI` in the
32
34
  # positional forms ADR 0066 shipped.
@@ -37,8 +39,11 @@ Hecks.world "Hecks" do
37
39
  launcher "Launcher", run_keys: true,
38
40
  failure_states: %w[flagged failed drifted unreachable refused faulted halted stopped
39
41
  abandoned red needs_fix],
42
+ settled: %w[package.vendor package.revendor package.release
43
+ operation.bootstrap_admin],
40
44
  names: { "mcp" => "door.serve_mcp", "console" => "operation.open_console", "init" => "door.init",
41
- "interview" => "door.interview" },
45
+ "interview" => "door.interview", "package.check" => "registry.check",
46
+ "package.release" => "registry.release" },
42
47
  streams: %w[Follow],
43
48
  executable: "exe/hecks",
44
49
  memory_commands: %w[console init interview],
@@ -3,8 +3,11 @@
3
3
  # here: hecks.world's `default_adapter` fills the gap, so the memory overlay swaps it with the rest.
4
4
  #
5
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.
6
+ # routes.ts out of date under `--check`, or a route table the projector refused) as exit 1, and
7
+ # `settled` makes `site_projection.check_live` wait for its reactions without the flag, so a live
8
+ # distribution that differs from the project is an exit status.
7
9
  Hecks.world "Site" do
8
10
  launcher "Launcher", run_keys: true,
9
- failure_states: %w[faulted refused halted stopped abandoned red needs_fix]
11
+ failure_states: %w[faulted refused halted stopped abandoned red needs_fix],
12
+ settled: %w[site_projection.check_live]
10
13
  end