hecks 3.12.0 → 3.14.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 (72) hide show
  1. checksums.yaml +4 -4
  2. data/examples/pizzas/bluebook/pizzas.bluebook +4 -4
  3. data/lib/hecks/adapters/driven/script_runner.adapter +3 -0
  4. data/lib/hecks/adapters/driven/script_runner.rb +93 -0
  5. data/lib/hecks/adapters/driven.rb +1 -0
  6. data/lib/hecks/bluebook/dsl/attribute_collector.rb +15 -5
  7. data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +16 -12
  8. data/lib/hecks/bluebook/meta_validator.rb +28 -21
  9. data/lib/hecks/bluebook/named_pattern.rb +33 -0
  10. data/lib/hecks/bluebook.rb +1 -0
  11. data/lib/hecks/cli/domain_stub.rb +1 -1
  12. data/lib/hecks/cli/interview_aggregate.rb +4 -4
  13. data/lib/hecks/deploy/bluebook/deploy.bluebook +35 -35
  14. data/lib/hecks/framework/bluebook/change.behaviors +30 -0
  15. data/lib/hecks/framework/bluebook/change.bluebook +162 -0
  16. data/lib/hecks/framework/bluebook/change.hecksagon +21 -0
  17. data/lib/hecks/framework/change.oidc.json +34 -0
  18. data/lib/hecks/framework/console_settings.oidc.json +103 -0
  19. data/lib/hecks/framework/governance.oidc.json +28 -0
  20. data/lib/hecks/framework/identity.oidc.json +18 -0
  21. data/lib/hecks/framework/privacy.oidc.json +30 -0
  22. data/lib/hecks/fuzzing/domain_generator/block_rendering.rb +1 -1
  23. data/lib/hecks/gate/stages.yml +3 -0
  24. data/lib/hecks/grammar/translation.oidc.json +48 -0
  25. data/lib/hecks/hecks/adapters/codebase/test_runner.rb +12 -1
  26. data/lib/hecks/hecks/adapters/deploy_toolchain.rb +2 -1
  27. data/lib/hecks/hecks/codebase.bluebook +37 -37
  28. data/lib/hecks/hecks/custodian.bluebook +14 -14
  29. data/lib/hecks/hecks/hecks.bluebook +2 -2
  30. data/lib/hecks/hecks/rehearsal.bluebook +3 -3
  31. data/lib/hecks/hecks/tooling.bluebook +22 -22
  32. data/lib/hecks/language/bluebook/aggregate.bluebook +1 -0
  33. data/lib/hecks/language/bluebook/command.bluebook +1 -0
  34. data/lib/hecks/language/bluebook/entity.bluebook +1 -0
  35. data/lib/hecks/language/bluebook/query.bluebook +1 -0
  36. data/lib/hecks/language/bluebook/shape.bluebook +8 -7
  37. data/lib/hecks/language/hecksagon/port_operation.bluebook +1 -0
  38. data/lib/hecks/projections/deploy/fargate/cdn/lines.rb +12 -1
  39. data/lib/hecks/projections/site/admin_cms/templates/membership.rb +10 -7
  40. data/lib/hecks/projections/site/edge/behaviours.rb +10 -6
  41. data/lib/hecks/projections/site/edge/checks.rb +2 -0
  42. data/lib/hecks/projections/site/edge/function_checks.rb +104 -0
  43. data/lib/hecks/projections/site/edge/function_code.rb +86 -0
  44. data/lib/hecks/projections/site/edge/redirect_host.js.tmpl +65 -0
  45. data/lib/hecks/projections/site/edge.rb +35 -9
  46. data/lib/hecks/projections/site/site_cdn.rb +21 -7
  47. data/lib/hecks/projections/site/table/row_builder.rb +12 -2
  48. data/lib/hecks/projections/site/table.rb +4 -2
  49. data/lib/hecks/quality_control/quality_control.bluebook +51 -51
  50. data/lib/hecks/site/bluebook/site.bluebook +21 -6
  51. data/lib/hecks/sme/bluebook/sme.bluebook +12 -12
  52. data/lib/hecks/tickets/bluebook/tickets.bluebook +3 -3
  53. data/lib/hecks/tools/oidc_manifests.rb +44 -6
  54. data/lib/hecks/tools/site_routes/templates.rb +13 -9
  55. data/lib/hecks/version.rb +1 -1
  56. data/prebuilt/{d74a00e996222c1d8b676d5959092effc46b4dc59e0555f3bf9554ac58e3094a.marshal → 1a6f596be124228be93eba1d86dbc9d2933190fa389b284331241cb9f27115a0.marshal} +0 -0
  57. data/prebuilt/{9ee54e2f0d4d6d3fe046827779b221e02de665fba911a048ae5dd8b8fbfb7d90.marshal → a883a7e568667aa5ac904c82daff356bdf9e996252a36af6def4a4a43efb967e.marshal} +0 -0
  58. data/prebuilt/verdicts-872993b1aa26d73fe48f28251374b8585fed145d02aab42be0f00de386b6ff80.json +1 -0
  59. data/rust/host/HECKS_RELEASE +1 -1
  60. data/rust/host/src/auth.rs +58 -0
  61. data/rust/host/src/payments/roles.rs +159 -0
  62. data/rust/host/src/payments/tests.rs +25 -0
  63. data/rust/host/src/payments.rs +20 -11
  64. data/rust/host/src/platform_signin/session.rs +1 -1
  65. data/rust/parser/src/build/mod.rs +1 -0
  66. data/rust/parser/src/build/named_pattern.rs +23 -0
  67. data/rust/parser/src/keywords.rs +6 -0
  68. data/rust/parser/src/parse/mod.rs +22 -1
  69. metadata +24 -7
  70. data/prebuilt/verdicts-df19f35868d3284a70f55e42dcef87ef4e65e6a678e5135f72e1f6cff4c1357b.json +0 -1
  71. /data/lib/hecks/framework/{oidc.json → compliance.oidc.json} +0 -0
  72. /data/lib/hecks/grammar/{oidc.json → expression.oidc.json} +0 -0
@@ -12,7 +12,9 @@
12
12
  # The same command projects the CDN: a project that adds Edge, EdgePolicy, EdgeOrigin and EdgeRule rows
13
13
  # to its chapter has the CloudFront behaviours and the listener rules written into the marked regions
14
14
  # of the template it owns (the `:site_cdn` projection). Those rows are the template's facts, so only the
15
- # closed sets they draw on, cache class and origin, are named here.
15
+ # closed sets they draw on, cache class, origin and the kind of an EdgeFunction row, are named here. A
16
+ # route's `edge:` names an EdgeFunction row, a viewer-request function the projection writes into the
17
+ # template's `edge_functions` region and attaches to that route's behaviour.
16
18
  #
17
19
  # Each run records that it was requested before the SiteToolchain port is asked, and what came of
18
20
  # it after. No command declares a role: the chapter boots standing alone.
@@ -33,6 +35,7 @@ Hecks.bluebook "Site" do
33
35
  attribute :source, Source, optional: true
34
36
  attribute :http_method, HttpMethod, optional: true
35
37
  attribute :preview, Preview, optional: true
38
+ attribute :edge, EdgeName, optional: true
36
39
 
37
40
  # `/blog/:slug.html` names a parameter; a trailing `/*` matches everything beneath a prefix. A
38
41
  # row with a `command:` or `query:` source may leave its path out, and the projection derives it
@@ -78,6 +81,18 @@ Hecks.bluebook "Site" do
78
81
  attribute :value, String, one_of: ["public", "draft"]
79
82
  end
80
83
 
84
+ # The logical id of an `EdgeFunction` row: the viewer-request function the route's behaviour runs.
85
+ value_object "EdgeName" do
86
+ attribute :value, String, pattern: '\A[A-Za-z][A-Za-z0-9]*\z'
87
+ end
88
+
89
+ # What an `EdgeFunction` row asks the projection to write: `redirect_host` answers a request for
90
+ # a watched path on an alias host with a redirect to the canonical host and leaves every other
91
+ # request alone.
92
+ value_object "EdgeFunctionKind" do
93
+ attribute :value, String, one_of: ["redirect_host"]
94
+ end
95
+
81
96
  # The HTTP verbs a route answers.
82
97
  value_object "HttpMethod" do
83
98
  attribute :value, String, one_of: ["GET", "POST", "PUT", "PATCH", "DELETE"]
@@ -114,14 +129,14 @@ Hecks.bluebook "Site" do
114
129
  identified_by :run
115
130
 
116
131
  value_object "RunKey" do
117
- attribute :value, String, pattern: '[^ \t\n\r]'
132
+ attribute :value, String, matches: :present
118
133
 
119
134
  invariant("a run is named") { !value.to_s.empty? }
120
135
  end
121
136
 
122
137
  # A project directory, whose `bluebook/` holds the chapter that declares the route table.
123
138
  value_object "DomainPath" do
124
- attribute :value, String, pattern: '[^\t\n\r]'
139
+ attribute :value, String, matches: :not_empty
125
140
 
126
141
  invariant("a project is named by its path") { !value.to_s.empty? }
127
142
  end
@@ -129,7 +144,7 @@ Hecks.bluebook "Site" do
129
144
  # A directory or file anywhere on disk, relative to where the command runs: where routes.ts is
130
145
  # written when not in the project's generated/ directory, or the template to rewrite in place.
131
146
  value_object "Location" do
132
- attribute :value, String, pattern: '[^\t\n\r]'
147
+ attribute :value, String, matches: :not_empty
133
148
  end
134
149
 
135
150
  # The file extension of the generated module: `ts` (routes.ts), or `mts` (routes.mts), which
@@ -149,12 +164,12 @@ Hecks.bluebook "Site" do
149
164
 
150
165
  # Path patterns, comma separated: the behaviours a change is about to add to the live distribution.
151
166
  value_object "PathList" do
152
- attribute :value, String, pattern: '[^\t\n\r]'
167
+ attribute :value, String, matches: :not_empty
153
168
  end
154
169
 
155
170
  # Policy references and the ids they stand for live, as `!Ref Name=id`, comma separated.
156
171
  value_object "PolicyRefs" do
157
- attribute :value, String, pattern: '[^\t\n\r]'
172
+ attribute :value, String, matches: :not_empty
158
173
  end
159
174
 
160
175
  # A running site's address, `https://example.org`: what `check_site` sends its requests to.
@@ -30,37 +30,37 @@ Hecks.bluebook "SME" do
30
30
  attribute :accepted_actions, Tally
31
31
 
32
32
  value_object "Reference" do
33
- attribute :value, String, pattern: '[^ \t\n\r]'
33
+ attribute :value, String, matches: :present
34
34
  end
35
35
 
36
36
  # The domain being discovered, spelled as its bluebook will spell it. Many interviews, with
37
37
  # different experts on different days, name the same subject; the domain is not modelled here.
38
38
  value_object "Subject" do
39
- attribute :value, String, pattern: '[^ \t\n\r]'
39
+ attribute :value, String, matches: :present
40
40
  end
41
41
 
42
42
  # Who is being interviewed.
43
43
  value_object "Expert" do
44
- attribute :value, String, pattern: '[^\t\n\r]'
44
+ attribute :value, String, matches: :not_empty
45
45
  end
46
46
 
47
47
  value_object "Question" do
48
- attribute :value, String, pattern: '[^\t\n\r]'
48
+ attribute :value, String, matches: :not_empty
49
49
  end
50
50
 
51
51
  # The expert's answer in their own words.
52
52
  value_object "Answer" do
53
- attribute :value, String, pattern: '[^\t\n\r]'
53
+ attribute :value, String, matches: :not_empty
54
54
  end
55
55
 
56
56
  # A label for grouping exchanges ("lending rules"); optional on every exchange.
57
57
  value_object "Topic" do
58
- attribute :value, String, pattern: '[^\t\n\r]'
58
+ attribute :value, String, matches: :not_empty
59
59
  end
60
60
 
61
61
  value_object "Exchange" do
62
- attribute :question, String, pattern: '[^\t\n\r]'
63
- attribute :answer, String, pattern: '[^\t\n\r]'
62
+ attribute :question, String, matches: :not_empty
63
+ attribute :answer, String, matches: :not_empty
64
64
  attribute :topic, String, optional: true
65
65
  end
66
66
 
@@ -76,7 +76,7 @@ Hecks.bluebook "SME" do
76
76
  end
77
77
 
78
78
  value_object "Name" do
79
- attribute :value, String, pattern: '[^ \t\n\r]'
79
+ attribute :value, String, matches: :present
80
80
  end
81
81
 
82
82
  # How many findings of a kind were accepted.
@@ -86,7 +86,7 @@ Hecks.bluebook "SME" do
86
86
  end
87
87
 
88
88
  value_object "Statement" do
89
- attribute :value, String, pattern: '[^\t\n\r]'
89
+ attribute :value, String, matches: :not_empty
90
90
  end
91
91
 
92
92
  # Whether an action brings its thing into being, as opposed to happening to one that exists.
@@ -97,12 +97,12 @@ Hecks.bluebook "SME" do
97
97
  # A list the expert gave in a breath, comma separated: the values a field may take, or the
98
98
  # fields an action needs to be told. Absent when the expert gave none.
99
99
  value_object "Listing" do
100
- attribute :value, String, pattern: '[^\t\n\r]'
100
+ attribute :value, String, matches: :not_empty
101
101
  end
102
102
 
103
103
  # Who may do an action, in the expert's words ("a maintainer", "QA engineer").
104
104
  value_object "Actor" do
105
- attribute :value, String, pattern: '[^\t\n\r]'
105
+ attribute :value, String, matches: :not_empty
106
106
  end
107
107
 
108
108
  # A thing the domain keeps track of, and the field that identifies one: an aggregate to be.
@@ -32,7 +32,7 @@ Hecks.bluebook "Tickets" do
32
32
  identified_by :finding
33
33
 
34
34
  value_object "FindingKey" do
35
- attribute :value, String, pattern: '[^ \t\n\r]'
35
+ attribute :value, String, matches: :present
36
36
 
37
37
  invariant("a finding is named") { !value.to_s.empty? }
38
38
  end
@@ -65,7 +65,7 @@ Hecks.bluebook "Tickets" do
65
65
  # The same finding refiled by the QA loop carries the same fingerprint, so a report of it while an
66
66
  # open finding holds it changes nothing.
67
67
  value_object "Fingerprint" do
68
- attribute :value, String, pattern: '[^ \t\n\r]'
68
+ attribute :value, String, matches: :present
69
69
  end
70
70
 
71
71
  value_object "Number" do
@@ -239,7 +239,7 @@ Hecks.bluebook "Tickets" do
239
239
  end
240
240
 
241
241
  value_object "FindingKey" do
242
- attribute :value, String, pattern: '[^ \t\n\r]'
242
+ attribute :value, String, matches: :present
243
243
  end
244
244
 
245
245
  lifecycle :status, default: "draft" do
@@ -6,7 +6,8 @@ require_relative "../tools"
6
6
  module Hecks
7
7
  module Tools
8
8
  # Projects each domain's OIDC client and scope manifest (`Hecks::Projections::OIDC`) into an
9
- # `oidc.json` beside the domain.
9
+ # `oidc.json` beside the domain, or into one `<chapter>.oidc.json` for each chapter of a
10
+ # directory that holds several.
10
11
  #
11
12
  # A domain that has no bluebook is skipped and one that cannot be projected is reported, so a
12
13
  # sweep over the whole checkout carries on past either. `hecks deploy
@@ -45,20 +46,57 @@ module Hecks
45
46
  .uniq.sort
46
47
  end
47
48
 
49
+ # Projects one manifest for each chapter the directory holds.
50
+ #
48
51
  # @param root [String] the checkout
49
52
  # @param path [String] a domain directory, relative to `root`
50
53
  # @return [void]
51
54
  def project(root, path)
52
- runtime = Hecks.boot(File.join(root, path), install_driving: false)
53
- name = runtime.registry.bluebooks.keys.first
54
- return warn(" #{path}: no bluebook found — skipped") unless name
55
+ registry = Hecks.boot(File.join(root, path), install_driving: false).registry
56
+ return warn(" #{path}: no bluebook found — skipped") if registry.bluebooks.empty?
55
57
 
56
- write_manifest(runtime.registry, name, File.join(root, path, "oidc.json"))
57
- puts " #{path}/oidc.json <- #{name}"
58
+ project_chapters(registry, root, path)
58
59
  rescue StandardError => e
59
60
  warn " #{path}: cannot project — #{e.message.lines.first.strip}"
60
61
  end
61
62
 
63
+ # The chapters a directory declares: those with a `.bluebook` file under it. A chapter it
64
+ # only attaches (Governance, for one) is loaded from elsewhere and has its own directory's
65
+ # manifest.
66
+ #
67
+ # @param registry [Hecks::Runtime::Registry] the booted registry of the directory
68
+ # @param directory [String] the directory that was booted
69
+ # @return [Array<String>] the chapter names, in load order; the first loaded when none has a
70
+ # recorded source
71
+ def chapters_of(registry, directory)
72
+ under = "#{File.expand_path(directory)}/"
73
+ own = registry.bluebooks.keys.select do |name|
74
+ Array(registry.bluebook_sources[name]).any? { |source| File.expand_path(source).start_with?(under) }
75
+ end
76
+ own.empty? ? registry.bluebooks.keys.first(1) : own
77
+ end
78
+
79
+ # @param registry [Hecks::Runtime::Registry] the booted registry of the directory
80
+ # @param root [String] the checkout
81
+ # @param path [String] the domain directory, relative to `root`
82
+ # @return [void]
83
+ def project_chapters(registry, root, path)
84
+ names = chapters_of(registry, File.join(root, path))
85
+ names.each do |name|
86
+ file = file_for(names, name)
87
+ write_manifest(registry, name, File.join(root, path, file))
88
+ puts " #{path}/#{file} <- #{name}"
89
+ end
90
+ end
91
+
92
+ # A domain of one chapter keeps `oidc.json`; a directory of several chapters, such as the
93
+ # framework members, gets one manifest each, so none of them stands for the others.
94
+ #
95
+ # @param names [Array<String>] every chapter the directory holds
96
+ # @param name [String] the chapter the manifest is for
97
+ # @return [String] the manifest's file name: `oidc.json`, or `<chapter>.oidc.json`
98
+ def file_for(names, name) = names.one? ? "oidc.json" : "#{Naming.snake(name)}.oidc.json"
99
+
62
100
  # @param registry [Hecks::Runtime::Registry] the booted registry
63
101
  # @param name [String] the bluebook to project
64
102
  # @param out [String] where the manifest goes
@@ -27,22 +27,19 @@ module Hecks
27
27
  regions = Projector.call(:site_cdn, bluebook: site.chapter, options: { table: site.table, edge: edge })
28
28
  relative = template || edge.setting.template
29
29
  source = template_source(root, template, relative)
30
- text = rewritten(File.read(source), regions, relative, edge)
30
+ text = rewritten(File.read(source), regions, relative)
31
31
  { destination(source, out, template, relative) => text }
32
32
  end
33
33
 
34
34
  # @param text [String] the template
35
35
  # @param regions [Hash{String => String}] each region's name to the block that goes in it
36
36
  # @param relative [String] the template's name, for a message
37
- # @param edge [Projections::Site::Edge] the checked edge
38
37
  # @return [String] the template with each region rewritten
39
- # @raise [SystemExit] when a region is missing, or a listener_rules region is left behind
40
- # by an edge with no load balancer
41
- def rewritten(text, regions, relative, edge)
42
- if !edge.alb? && Projections::Site::Regions.region?(text, "listener_rules")
43
- abort "project_site: #{relative} has a BEGIN/END GENERATED site_cdn listener_rules region, " \
44
- "and the Edge row says alb: false; remove the region"
45
- end
38
+ # @raise [SystemExit] when a region is missing, or a listener_rules or edge_functions region
39
+ # is left behind by an edge that has no load balancer or no EdgeFunction row
40
+ def rewritten(text, regions, relative)
41
+ refuse_leftover(text, regions, relative, "listener_rules", "the Edge row says alb: false")
42
+ refuse_leftover(text, regions, relative, "edge_functions", "the project declares no EdgeFunction rows")
46
43
  regions.reduce(text) do |current, (name, block)|
47
44
  unless Projections::Site::Regions.region?(current, name)
48
45
  abort "project_site: #{relative} has no BEGIN/END GENERATED site_cdn #{name} region"
@@ -52,6 +49,13 @@ module Hecks
52
49
  end
53
50
  end
54
51
 
52
+ # Refuses a region the edge does not use, so the stale text in it is removed by hand.
53
+ def refuse_leftover(text, regions, relative, name, reason)
54
+ return if regions.key?(name) || !Projections::Site::Regions.region?(text, name)
55
+
56
+ abort "project_site: #{relative} has a BEGIN/END GENERATED site_cdn #{name} region, and #{reason}; remove the region"
57
+ end
58
+
55
59
  # @return [Hash] nothing, after refusing a template named for a project with no edge
56
60
  def missing_edge(template)
57
61
  abort "project_site: --template names #{template}, but the project declares no Edge rows" if template
data/lib/hecks/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  module Hecks
2
2
  # Read directly by the gemspec, so this file must never require the
3
3
  # rest of the gem (its dependencies aren't resolved yet at that point).
4
- VERSION = "3.12.0".freeze
4
+ VERSION = "3.14.0".freeze # rust/host/HECKS_RELEASE names the same release
5
5
  end