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
@@ -7,6 +7,7 @@ require_relative "edge/row_reader"
7
7
  require_relative "edge/checks"
8
8
  require_relative "edge/behaviours"
9
9
  require_relative "edge/rules"
10
+ require_relative "edge/function_code"
10
11
 
11
12
  module Hecks
12
13
  module Projections
@@ -16,17 +17,33 @@ module Hecks
16
17
  #
17
18
  # The routes are the table's own `Route` rows. What a table cannot know, the names a template
18
19
  # gives its policies, origins, target groups and listener, a project declares as `member`
19
- # rows of four more value objects of the same chapter: `Edge` (the template and listener),
20
- # `EdgePolicy`, `EdgeOrigin` and `EdgeRule`. `docs/site-routes.md` lists their fields. A
21
- # project that declares none of the four has no edge to project.
20
+ # rows of five more value objects of the same chapter: `Edge` (the template and listener),
21
+ # `EdgePolicy`, `EdgeOrigin`, `EdgeRule` and `EdgeFunction` (a viewer-request function a route
22
+ # names with `edge:`). `docs/site-routes.md` lists their fields. A project that declares none
23
+ # of the five has no edge to project.
22
24
  class Edge
23
25
  Setting = Struct.new(:template, :listener, :secret_header, :secret_value, :alb, keyword_init: true)
24
26
  Policy = Struct.new(:cache_class, :origin, :cache, :origin_request, :response_headers, keyword_init: true)
25
27
  Upstream = Struct.new(:origin, :id, :target_group, keyword_init: true)
26
28
  Rule = Struct.new(:rule, :priority, :origin, keyword_init: true)
27
29
 
30
+ # A function the generator writes: `kind` says which, and the rest are its settings, the
31
+ # lists as comma-separated text.
32
+ Function = Struct.new(:name, :kind, :host, :aliases, :paths, keyword_init: true) do
33
+ # @return [Array<String>] the alias hosts that are redirected
34
+ def alias_list = split(aliases)
35
+
36
+ # @return [Array<String>] the exact paths the function watches
37
+ def path_list = split(paths)
38
+
39
+ private
40
+
41
+ def split(text) = text.to_s.split(",").map(&:strip).reject(&:empty?)
42
+ end
43
+
28
44
  # The value objects a project declares its edge rows in, by the kind of record each holds.
29
- OBJECTS = { setting: "Edge", policy: "EdgePolicy", upstream: "EdgeOrigin", rule: "EdgeRule" }.freeze
45
+ OBJECTS = { setting: "Edge", policy: "EdgePolicy", upstream: "EdgeOrigin", rule: "EdgeRule",
46
+ function: "EdgeFunction" }.freeze
30
47
 
31
48
  # The fields each kind of row may carry, with the Ruby class each takes.
32
49
  FIELDS = {
@@ -34,14 +51,15 @@ module Hecks
34
51
  policy: { cache_class: String, origin: String, cache: String, origin_request: String,
35
52
  response_headers: String },
36
53
  upstream: { origin: String, id: String, target_group: String },
37
- rule: { rule: String, priority: Integer, origin: String }
54
+ rule: { rule: String, priority: Integer, origin: String },
55
+ function: { name: String, kind: String, host: String, aliases: String, paths: String }
38
56
  }.freeze
39
57
 
40
58
  # The fields a row of each kind must carry. The template may instead be named to the tool.
41
59
  REQUIRED = { setting: [], policy: %i[cache_class cache], upstream: %i[origin id],
42
- rule: %i[rule priority origin] }.freeze
60
+ rule: %i[rule priority origin], function: %i[name kind host aliases paths] }.freeze
43
61
 
44
- STRUCTS = { setting: Setting, policy: Policy, upstream: Upstream, rule: Rule }.freeze
62
+ STRUCTS = { setting: Setting, policy: Policy, upstream: Upstream, rule: Rule, function: Function }.freeze
45
63
 
46
64
  # @return [Setting] the template and the listener
47
65
  attr_reader :setting
@@ -51,6 +69,8 @@ module Hecks
51
69
  attr_reader :upstreams
52
70
  # @return [Array<Rule>] the listener rules
53
71
  attr_reader :rules
72
+ # @return [Array<Function>] the edge functions the generator writes
73
+ attr_reader :functions
54
74
  # @return [Hash{Symbol => Object}] the default behaviour as `Cdn.behavior_lines` takes it
55
75
  attr_reader :default_behaviour
56
76
  # @return [Array<Hash{Symbol => Object}>] the other behaviours, in matching order
@@ -65,11 +85,12 @@ module Hecks
65
85
  # @param vocabulary [Hash{Symbol => Array<String>}] the Site chapter's closed sets
66
86
  # @param template [String, nil] the template the caller names, which stands in for the
67
87
  # `Edge` row's own `template:`
68
- # @return [Edge, nil] the checked edge, or nil when the chapter declares none
88
+ # @return [Edge, nil] the checked edge, or nil when the chapter declares none and no route
89
+ # names an edge function
69
90
  # @raise [Table::Invalid] when a row is refused or the edge contradicts the routes
70
91
  def self.read(chapter, table:, vocabulary:, template: nil)
71
92
  declared = OBJECTS.transform_values { |name| Table.rows_of(chapter, name) }
72
- return nil if declared.values.all?(&:empty?)
93
+ return nil if declared.values.all?(&:empty?) && table.rows.none?(&:edge)
73
94
 
74
95
  problems = []
75
96
  records = declared.to_h { |kind, members| [kind, build(kind, members, problems)] }
@@ -104,6 +125,7 @@ module Hecks
104
125
  @policies = records.fetch(:policy)
105
126
  @upstreams = records.fetch(:upstream)
106
127
  @rules = records.fetch(:rule)
128
+ @functions = records.fetch(:function)
107
129
  end
108
130
  private :assign
109
131
 
@@ -119,6 +141,10 @@ module Hecks
119
141
  # unless the `Edge` row says `alb: false`
120
142
  def alb? = setting.nil? || setting.alb != false
121
143
 
144
+ # @param name [String] a logical id
145
+ # @return [Function, nil] the function row of that name
146
+ def function(name) = functions.find { |candidate| candidate.name == name }
147
+
122
148
  # @param origin [String] an origin of the Origin set
123
149
  # @return [Upstream, nil] its CloudFront origin and target group
124
150
  def upstream(origin) = upstreams.find { |candidate| candidate.origin == origin }
@@ -7,12 +7,14 @@ require_relative "edge"
7
7
  module Hecks
8
8
  module Projections
9
9
  module Site
10
- # The edge of a site's route table as the two blocks of a CloudFormation template that depend
11
- # on it: the distribution's cache behaviours and the load balancer's listener rules.
10
+ # The edge of a site's route table as the blocks of a CloudFormation template that depend on
11
+ # it: the distribution's cache behaviours, the load balancer's listener rules and the
12
+ # functions the behaviours attach.
12
13
  #
13
- # The blocks are returned by name, `"behaviors"` and `"listener_rules"`, as text from the
14
- # left margin; a project whose `Edge` row says `alb: false` has no load balancer and gets
15
- # `"behaviors"` alone. `Regions` puts each between the markers of the same name in the
14
+ # The blocks are returned by name, `"behaviors"`, `"listener_rules"` and `"edge_functions"`,
15
+ # as text from the left margin; a project whose `Edge` row says `alb: false` has no load
16
+ # balancer and gets no `"listener_rules"`, and one with no `EdgeFunction` row gets no
17
+ # `"edge_functions"`. `Regions` puts each between the markers of the same name in the
16
18
  # project's template, at the markers' indentation. The text is a pure function of the table
17
19
  # and the edge rows.
18
20
  module SiteCdn
@@ -34,9 +36,21 @@ module Hecks
34
36
  edge = options[:edge] || Edge.read(bluebook, table: table, vocabulary: Table.vocabulary(registry))
35
37
  return {} unless edge
36
38
 
37
- return { "behaviors" => behaviors(edge) } unless edge.alb?
39
+ regions(edge)
40
+ end
38
41
 
39
- { "behaviors" => behaviors(edge), "listener_rules" => listener_rules(edge) }
42
+ # @param edge [Edge] a checked edge
43
+ # @return [Hash{String => String}] the regions the edge needs
44
+ def regions(edge)
45
+ { "behaviors" => behaviors(edge) }
46
+ .merge(edge.alb? ? { "listener_rules" => listener_rules(edge) } : {})
47
+ .merge(edge.functions.any? ? { "edge_functions" => edge_functions(edge) } : {})
48
+ end
49
+
50
+ # @param edge [Edge] a checked edge
51
+ # @return [String] one function resource per `EdgeFunction` row, ending in a newline
52
+ def edge_functions(edge)
53
+ edge.functions.map { |function| Edge::FunctionCode.resource(function) }.join("\n")
40
54
  end
41
55
 
42
56
  # @param edge [Edge] a checked edge
@@ -11,7 +11,7 @@ module Hecks
11
11
  :indexable, :switch, :off, :preview, :label, :seo, :redirect_to, :aliases,
12
12
  :nav_group, :nav_order, :mobile_order, :footer_column, :footer_order,
13
13
  :admin_key, :admin_order, :compress, :alb_rule, :cdn,
14
- :seo_title, :edge_verbs, :mobile_heading, :explicit_cache, keyword_init: true)
14
+ :seo_title, :edge_verbs, :mobile_heading, :explicit_cache, :edge, keyword_init: true)
15
15
 
16
16
  # Turns a declared member into a `Row`: checks its fields and their types, fills the
17
17
  # defaults, and checks each closed-set value against the Site chapter's vocabulary.
@@ -23,7 +23,10 @@ module Hecks
23
23
  aliases: String, nav_group: String, nav_order: Integer, mobile_order: Integer,
24
24
  footer_column: String, footer_order: Integer, admin_key: String,
25
25
  admin_order: Integer, compress: :bool, alb_rule: String, cdn: :bool,
26
- seo_title: String, edge_methods: String, mobile_heading: String }.freeze
26
+ seo_title: String, edge_methods: String, mobile_heading: String, edge: String }.freeze
27
+
28
+ # What an `edge:` names: the logical id of an `EdgeFunction` row.
29
+ EDGE_NAME = /\A[A-Za-z][A-Za-z0-9]*\z/
27
30
 
28
31
  # The value objects of the Site chapter's `Route` aggregate whose members are the closed
29
32
  # sets, by the row field each constrains.
@@ -103,6 +106,13 @@ module Hecks
103
106
  problem(label, "has #{field} #{row[field].inspect}; #{field} is one of #{@vocabulary.fetch(field).join(", ")}")
104
107
  end
105
108
  check_verbs(row, label)
109
+ check_edge(row, label)
110
+ end
111
+
112
+ def check_edge(row, label)
113
+ return unless row.edge.is_a?(String) && !EDGE_NAME.match?(row.edge)
114
+
115
+ problem(label, "has edge #{row.edge.inspect}; an edge is the logical id of an EdgeFunction row")
106
116
  end
107
117
 
108
118
  def check_verbs(row, label)
@@ -60,7 +60,8 @@ module Hecks
60
60
 
61
61
  # @param registry [Runtime::Registry] a registry holding the Site chapter
62
62
  # @return [Hash{Symbol => Array<String>}] each constrained field to its allowed values, and
63
- # `:http_method` to the verbs a route may answer
63
+ # `:http_method` to the verbs a route may answer and `:edge_function_kind` to the kinds of
64
+ # `EdgeFunction` row
64
65
  # @raise [Invalid] when the registry does not hold the Site chapter
65
66
  def self.vocabulary(registry)
66
67
  site = registry.bluebook("Site") or
@@ -69,7 +70,8 @@ module Hecks
69
70
  members = lambda do |name|
70
71
  route.value_objects.find { |object| object.hecks_name == name }.members.map { |member| member.fetch(:value) }
71
72
  end
72
- RowBuilder::VOCABULARY.transform_values(&members).merge(http_method: members.call("HttpMethod"))
73
+ closed = RowBuilder::VOCABULARY.transform_values(&members)
74
+ closed.merge(http_method: members.call("HttpMethod"), edge_function_kind: members.call("EdgeFunctionKind"))
73
75
  end
74
76
 
75
77
  # @param chapter [Bluebook::Chapter] a chapter to search
@@ -54,13 +54,13 @@ Hecks.bluebook "QualityControl" do
54
54
  attribute :reason, TargetReason
55
55
 
56
56
  value_object "TargetReference" do
57
- attribute :value, String, pattern: '[^ \t\n\r]'
57
+ attribute :value, String, matches: :present
58
58
  invariant("a target is referenced") { !value.to_s.empty? }
59
59
  end
60
60
 
61
61
  # Required: a name can be disputed, a path can be opened.
62
62
  value_object "TargetPath" do
63
- attribute :value, String, pattern: '[^ \t\n\r]'
63
+ attribute :value, String, matches: :present
64
64
  invariant("a target says where it is") { !value.to_s.empty? }
65
65
  end
66
66
 
@@ -87,7 +87,7 @@ Hecks.bluebook "QualityControl" do
87
87
  end
88
88
 
89
89
  value_object "TargetReason" do
90
- attribute :value, String, pattern: '[^ \t\n\r]'
90
+ attribute :value, String, matches: :present
91
91
  invariant("shelving a target says why") { !value.to_s.empty? }
92
92
  end
93
93
 
@@ -99,7 +99,7 @@ Hecks.bluebook "QualityControl" do
99
99
 
100
100
  # One mode name, as `EligibleFor`'s argument; a query attribute, never stored.
101
101
  value_object "Capability" do
102
- attribute :value, String, pattern: '[^ \t\n\r]'
102
+ attribute :value, String, matches: :present
103
103
  invariant("a mode is named") { !value.to_s.empty? }
104
104
  end
105
105
 
@@ -300,7 +300,7 @@ Hecks.bluebook "QualityControl" do
300
300
  end
301
301
 
302
302
  value_object "DomainPath" do
303
- attribute :value, String, pattern: '[^\t\n\r]'
303
+ attribute :value, String, matches: :not_empty
304
304
 
305
305
  invariant("a domain is named by its path") { !value.to_s.empty? }
306
306
  end
@@ -361,12 +361,12 @@ Hecks.bluebook "QualityControl" do
361
361
  attribute :checks, list_of(Check)
362
362
 
363
363
  value_object "SweepReference" do
364
- attribute :value, String, pattern: '[^ \t\n\r]'
364
+ attribute :value, String, matches: :present
365
365
  invariant("a sweep is referenced") { !value.to_s.empty? }
366
366
  end
367
367
 
368
368
  value_object "Engineer" do
369
- attribute :value, String, pattern: '[^ \t\n\r]'
369
+ attribute :value, String, matches: :present
370
370
  invariant("a sweep says who ran it") { !value.to_s.empty? }
371
371
  end
372
372
 
@@ -381,13 +381,13 @@ Hecks.bluebook "QualityControl" do
381
381
  end
382
382
 
383
383
  value_object "SweepNotes" do
384
- attribute :value, String, pattern: '[^ \t\n\r]'
384
+ attribute :value, String, matches: :present
385
385
  # The number is in the sentence because the sentence is the refusal message.
386
386
  invariant("a sweep says what it learned, in at least 40 characters") { value.to_s.size >= 40 }
387
387
  end
388
388
 
389
389
  value_object "WaiverReason" do
390
- attribute :value, String, pattern: '[^ \t\n\r]'
390
+ attribute :value, String, matches: :present
391
391
  invariant("a waived gate says why") { !value.to_s.empty? }
392
392
  end
393
393
 
@@ -395,7 +395,7 @@ Hecks.bluebook "QualityControl" do
395
395
  # literals, so `spec/quality_control_spec.rb` pins the two equal. "nobody" is refused: unsigned
396
396
  # is anonymous.
397
397
  value_object "WaivedBy" do
398
- attribute :value, String, pattern: '[^ \t\n\r]'
398
+ attribute :value, String, matches: :present
399
399
  invariant("a gate is waived by a person, never by the loop") { value != "qa_sweep" }
400
400
  invariant("a waiver is signed") { value != "nobody" }
401
401
  end
@@ -418,13 +418,13 @@ Hecks.bluebook "QualityControl" do
418
418
  end
419
419
 
420
420
  value_object "Subject" do
421
- attribute :value, String, pattern: '[^ \t\n\r]'
421
+ attribute :value, String, matches: :present
422
422
  invariant("a check names what it put to the system") { !value.to_s.empty? }
423
423
  end
424
424
 
425
425
  # Written before the answer exists: an expectation fitted to the answer cannot be surprised.
426
426
  value_object "Expectation" do
427
- attribute :value, String, pattern: '[^ \t\n\r]'
427
+ attribute :value, String, matches: :present
428
428
  invariant("a check says what it expected") { !value.to_s.empty? }
429
429
  end
430
430
 
@@ -637,37 +637,37 @@ Hecks.bluebook "QualityControl" do
637
637
  end
638
638
 
639
639
  value_object "DomainPath" do
640
- attribute :value, String, pattern: '[^\t\n\r]'
640
+ attribute :value, String, matches: :not_empty
641
641
 
642
642
  invariant("a domain is named by its path") { !value.to_s.empty? }
643
643
  end
644
644
 
645
645
  value_object "DataPath" do
646
- attribute :value, String, pattern: '[^\t\n\r]'
646
+ attribute :value, String, matches: :not_empty
647
647
 
648
648
  invariant("a data directory is named by its path") { !value.to_s.empty? }
649
649
  end
650
650
 
651
651
  value_object "DatabaseName" do
652
- attribute :value, String, pattern: '[^ \t\n\r]'
652
+ attribute :value, String, matches: :present
653
653
 
654
654
  invariant("a database is named") { !value.to_s.empty? }
655
655
  end
656
656
 
657
657
  value_object "RoleName" do
658
- attribute :value, String, pattern: '[^ \t\n\r]'
658
+ attribute :value, String, matches: :present
659
659
 
660
660
  invariant("a role is named") { !value.to_s.empty? }
661
661
  end
662
662
 
663
663
  value_object "SchemaName" do
664
- attribute :value, String, pattern: '[^ \t\n\r]'
664
+ attribute :value, String, matches: :present
665
665
 
666
666
  invariant("a schema is named") { !value.to_s.empty? }
667
667
  end
668
668
 
669
669
  value_object "StepVerb" do
670
- attribute :value, String, pattern: '[^ \t\n\r]'
670
+ attribute :value, String, matches: :present
671
671
 
672
672
  invariant("a step names its verb") { !value.to_s.empty? }
673
673
  end
@@ -770,7 +770,7 @@ Hecks.bluebook "QualityControl" do
770
770
  attribute :window, StaleAfter
771
771
 
772
772
  value_object "BugReference" do
773
- attribute :value, String, pattern: '[^ \t\n\r]'
773
+ attribute :value, String, matches: :present
774
774
  invariant("a bug is referenced") { !value.to_s.empty? }
775
775
  end
776
776
 
@@ -783,56 +783,56 @@ Hecks.bluebook "QualityControl" do
783
783
  # value object sharing a word with any aggregate resolves to that class once the other domain
784
784
  # boots.
785
785
  value_object "BugTitle" do
786
- attribute :value, String, pattern: '[^ \t\n\r]'
786
+ attribute :value, String, matches: :present
787
787
  invariant("a bug is titled") { !value.to_s.empty? }
788
788
  end
789
789
 
790
790
  # Required at `Log`, which is what makes the one absolute rule absolute.
791
791
  value_object "Demonstration" do
792
- attribute :value, String, pattern: '[^ \t\n\r]'
792
+ attribute :value, String, matches: :present
793
793
  invariant("a bug is proven by a test somebody can re-run") { !value.to_s.empty? }
794
794
  end
795
795
 
796
796
  value_object "Symptom" do
797
- attribute :value, String, pattern: '[^ \t\n\r]'
797
+ attribute :value, String, matches: :present
798
798
  invariant("a bug says what actually happened") { !value.to_s.empty? }
799
799
  end
800
800
 
801
801
  value_object "Expectation" do
802
- attribute :value, String, pattern: '[^ \t\n\r]'
802
+ attribute :value, String, matches: :present
803
803
  invariant("a bug says what should have happened") { !value.to_s.empty? }
804
804
  end
805
805
 
806
806
  value_object "BugSite" do
807
- attribute :value, String, pattern: '[^ \t\n\r]'
807
+ attribute :value, String, matches: :present
808
808
  invariant("an investigation names where the bug lives") { !value.to_s.empty? }
809
809
  end
810
810
 
811
811
  value_object "BugCause" do
812
- attribute :value, String, pattern: '[^ \t\n\r]'
812
+ attribute :value, String, matches: :present
813
813
  invariant("an investigation says why the code is wrong") { !value.to_s.empty? }
814
814
  end
815
815
 
816
816
  # 7 to 40 hex: a fix that lives only in a working tree is not a fix.
817
817
  value_object "CommitRef" do
818
- attribute :value, String, pattern: '^[0-9a-fA-F]{7,40}$'
818
+ attribute :value, String, matches: :git_sha
819
819
  end
820
820
 
821
821
  # What was actually run, e.g. "rspec --order random, 1335 examples, 0 failures, seed 12345":
822
822
  # a count and a seed are checkable, "tests pass" is not.
823
823
  value_object "Verification" do
824
- attribute :value, String, pattern: '[^ \t\n\r]'
824
+ attribute :value, String, matches: :present
825
825
  invariant("a verification says what was actually run") { !value.to_s.empty? }
826
826
  end
827
827
 
828
828
  value_object "BugReason" do
829
- attribute :value, String, pattern: '[^ \t\n\r]'
829
+ attribute :value, String, matches: :present
830
830
  invariant("stopping on a bug says why") { !value.to_s.empty? }
831
831
  end
832
832
 
833
833
  # Who decides and what would unblock it, so "deferred" does not become "forgotten".
834
834
  value_object "NextStep" do
835
- attribute :value, String, pattern: '[^ \t\n\r]'
835
+ attribute :value, String, matches: :present
836
836
  invariant("stopping on a bug says what would move it") { !value.to_s.empty? }
837
837
  end
838
838
 
@@ -842,14 +842,14 @@ Hecks.bluebook "QualityControl" do
842
842
  end
843
843
 
844
844
  value_object "WaiverReason" do
845
- attribute :value, String, pattern: '[^ \t\n\r]'
845
+ attribute :value, String, matches: :present
846
846
  invariant("a waived gate says why") { !value.to_s.empty? }
847
847
  end
848
848
 
849
849
  # Redeclared, as elsewhere in this file; the "qa_sweep" literal is pinned to AUTOMATED_ENGINEER
850
850
  # by spec.
851
851
  value_object "WaivedBy" do
852
- attribute :value, String, pattern: '[^ \t\n\r]'
852
+ attribute :value, String, matches: :present
853
853
  invariant("a gate is waived by a person, never by the loop") { value != "qa_sweep" }
854
854
  invariant("a waiver is signed") { value != "nobody" }
855
855
  end
@@ -881,7 +881,7 @@ Hecks.bluebook "QualityControl" do
881
881
  # Not an `Engineer`: that type defaults to "nobody" (an unclaimed bug is held by nobody), which
882
882
  # would let a report omit its submitter. This type has no default and refuses "nobody".
883
883
  value_object "Submitter" do
884
- attribute :value, String, pattern: '[^ \t\n\r]'
884
+ attribute :value, String, matches: :present
885
885
 
886
886
  invariant("a bug is reported by somebody") { value != "nobody" }
887
887
  end
@@ -1237,27 +1237,27 @@ Hecks.bluebook "QualityControl" do
1237
1237
  attribute :reason, AngleReason
1238
1238
 
1239
1239
  value_object "AngleReference" do
1240
- attribute :value, String, pattern: '[^ \t\n\r]'
1240
+ attribute :value, String, matches: :present
1241
1241
  invariant("an angle is referenced") { !value.to_s.empty? }
1242
1242
  end
1243
1243
 
1244
1244
  # The number is in the sentence because the sentence is the refusal; 60 characters trips a
1245
1245
  # stub.
1246
1246
  value_object "AnglePremise" do
1247
- attribute :value, String, pattern: '[^ \t\n\r]'
1247
+ attribute :value, String, matches: :present
1248
1248
  invariant("an angle says enough to be acted on without its proposer in the room, in at least 60 characters") { value.to_s.size >= 60 }
1249
1249
  end
1250
1250
 
1251
1251
  # A Bug reference, ADR number or named gap that a reader can check for themselves.
1252
1252
  value_object "AngleCitation" do
1253
- attribute :value, String, pattern: '[^ \t\n\r]'
1253
+ attribute :value, String, matches: :present
1254
1254
  invariant("an angle cites something concrete, not a mood") { !value.to_s.empty? }
1255
1255
  end
1256
1256
 
1257
1257
  # Not an `Engineer`, for the reason `Submitter` is not: that default would let authorship go
1258
1258
  # unrecorded.
1259
1259
  value_object "Proposer" do
1260
- attribute :value, String, pattern: '[^ \t\n\r]'
1260
+ attribute :value, String, matches: :present
1261
1261
  invariant("an angle is proposed by somebody") { value != "nobody" }
1262
1262
  end
1263
1263
 
@@ -1268,13 +1268,13 @@ Hecks.bluebook "QualityControl" do
1268
1268
 
1269
1269
  # What came of it (a Target, a PR number, a domain path); set once, at `Build`.
1270
1270
  value_object "AngleResolution" do
1271
- attribute :value, String, pattern: '[^ \t\n\r]'
1271
+ attribute :value, String, matches: :present
1272
1272
  invariant("a built angle says what actually got built") { !value.to_s.empty? }
1273
1273
  end
1274
1274
 
1275
1275
  # Redeclared as `Target::Shelve` does: a discard with no reason looks like a lead nobody read.
1276
1276
  value_object "AngleReason" do
1277
- attribute :value, String, pattern: '[^ \t\n\r]'
1277
+ attribute :value, String, matches: :present
1278
1278
  invariant("discarding an angle says why") { !value.to_s.empty? }
1279
1279
  end
1280
1280
 
@@ -1401,7 +1401,7 @@ Hecks.bluebook "QualityControl" do
1401
1401
  attribute :max_attempts, MaxAttempts
1402
1402
 
1403
1403
  value_object "TicketReference" do
1404
- attribute :value, String, pattern: '[^ \t\n\r]'
1404
+ attribute :value, String, matches: :present
1405
1405
  invariant("a ticket is referenced") { !value.to_s.empty? }
1406
1406
  end
1407
1407
 
@@ -1410,7 +1410,7 @@ Hecks.bluebook "QualityControl" do
1410
1410
  end
1411
1411
 
1412
1412
  value_object "TicketTitle" do
1413
- attribute :value, String, pattern: '[^ \t\n\r]'
1413
+ attribute :value, String, matches: :present
1414
1414
  invariant("a ticket is titled") { !value.to_s.empty? }
1415
1415
  end
1416
1416
 
@@ -1421,7 +1421,7 @@ Hecks.bluebook "QualityControl" do
1421
1421
  end
1422
1422
 
1423
1423
  value_object "TicketBody" do
1424
- attribute :value, String, pattern: '[^ \t\n\r]'
1424
+ attribute :value, String, matches: :present
1425
1425
  invariant("a ticket says something") { !value.to_s.empty? }
1426
1426
  end
1427
1427
 
@@ -1647,22 +1647,22 @@ Hecks.bluebook "QualityControl" do
1647
1647
  end
1648
1648
 
1649
1649
  value_object "PatchUrl" do
1650
- attribute :value, String, pattern: '[^ \t\n\r]'
1650
+ attribute :value, String, matches: :present
1651
1651
  invariant("a patch says where it lives") { !value.to_s.empty? }
1652
1652
  end
1653
1653
 
1654
1654
  value_object "PatchBranch" do
1655
- attribute :value, String, pattern: '[^ \t\n\r]'
1655
+ attribute :value, String, matches: :present
1656
1656
  invariant("a patch says which branch it is on") { !value.to_s.empty? }
1657
1657
  end
1658
1658
 
1659
1659
  # Recorded at `Open` so the commit is in hand when the PR needs checking, as `Bug.commit` is.
1660
1660
  value_object "CommitRef" do
1661
- attribute :value, String, pattern: '^[0-9a-fA-F]{7,40}$'
1661
+ attribute :value, String, matches: :git_sha
1662
1662
  end
1663
1663
 
1664
1664
  value_object "PatchTitle" do
1665
- attribute :value, String, pattern: '[^ \t\n\r]'
1665
+ attribute :value, String, matches: :present
1666
1666
  invariant("a patch is titled") { !value.to_s.empty? }
1667
1667
  end
1668
1668
 
@@ -1840,23 +1840,23 @@ Hecks.bluebook "QualityControl" do
1840
1840
  end
1841
1841
 
1842
1842
  value_object "ImprovementUrl" do
1843
- attribute :value, String, pattern: '[^ \t\n\r]'
1843
+ attribute :value, String, matches: :present
1844
1844
  invariant("an improvement says where it lives") { !value.to_s.empty? }
1845
1845
  end
1846
1846
 
1847
1847
  value_object "ImprovementBranch" do
1848
- attribute :value, String, pattern: '[^ \t\n\r]'
1848
+ attribute :value, String, matches: :present
1849
1849
  invariant("an improvement says which branch it is on") { !value.to_s.empty? }
1850
1850
  end
1851
1851
 
1852
1852
  # Redeclared per aggregate. Absent until `Land`: there is nothing to watch until a commit
1853
1853
  # exists.
1854
1854
  value_object "CommitRef" do
1855
- attribute :value, String, pattern: '^[0-9a-fA-F]{7,40}$'
1855
+ attribute :value, String, matches: :git_sha
1856
1856
  end
1857
1857
 
1858
1858
  value_object "ImprovementTitle" do
1859
- attribute :value, String, pattern: '[^ \t\n\r]'
1859
+ attribute :value, String, matches: :present
1860
1860
  invariant("an improvement is titled") { !value.to_s.empty? }
1861
1861
  end
1862
1862
 
@@ -1977,11 +1977,11 @@ Hecks.bluebook "QualityControl" do
1977
1977
  attribute :summary, RunSummary
1978
1978
 
1979
1979
  value_object "CommitRef" do
1980
- attribute :value, String, pattern: '^[0-9a-fA-F]{7,40}$'
1980
+ attribute :value, String, matches: :git_sha
1981
1981
  end
1982
1982
 
1983
1983
  value_object "RunSummary" do
1984
- attribute :value, String, pattern: '[^ \t\n\r]'
1984
+ attribute :value, String, matches: :present
1985
1985
  invariant("a run says what it ran and what came back") { !value.to_s.empty? }
1986
1986
  end
1987
1987