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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: beba8cd60d4c859286fa1121e3b382cc63e826e010dfd336714befa036dfad94
4
- data.tar.gz: 16deb6ba2dd98c8d09385e77a9773f92ef12726d097e71601d2e3cfdbb83ae00
3
+ metadata.gz: 52208049da300a9004b163c5e48cfe5587711ad45a3cb4d17a5bd1aa4eca8567
4
+ data.tar.gz: 1788cde0c4a89814395a4be9b3396cc8b5fd823aa408da88534aae617afbd0f1
5
5
  SHA512:
6
- metadata.gz: fe730368d2f8942c67a5510c5b8344bdad86f9aa0990515903e147da4fe71fbc6f17880ea2a1b26de4ffa0d24ad20c416455df58ee8a66617e1f832b2637e2b0
7
- data.tar.gz: a41080c78fc7b1e9d43347fd4b965d6811c246baee7072214c5f7895564eccc94ce699b86bc739de0f3d7d7830fb8057e6bf2e0699496e201ffd057c7e6a39d5
6
+ metadata.gz: 5ad958aea6cbf148bf0164808eae59179ec03743f1591f193f85e196c9aa7c5a25376d9857ad3d42d57303c7af721811cddf11f776c88f48914f46c4522edc87
7
+ data.tar.gz: d1148d6aa2ced591c069816c3f17047d08d0aa902e03340dbd4b7c35c20be8eb0907bda11f00c746fdc8955ae0a9225aafe43e3b92715efb42df0f0d73c3b0a8
@@ -14,7 +14,7 @@ Hecks.bluebook "Pizzas" do
14
14
  identified_by :name
15
15
 
16
16
  value_object "PizzaName" do
17
- attribute :value, String, pattern: '[^ \t\n\r]'
17
+ attribute :value, String, matches: :present
18
18
 
19
19
  invariant("a pizza is named") { !value.to_s.empty? }
20
20
  end
@@ -26,13 +26,13 @@ Hecks.bluebook "Pizzas" do
26
26
  end
27
27
 
28
28
  value_object "CustomerName" do
29
- attribute :value, String, pattern: '[^ \t\n\r]'
29
+ attribute :value, String, matches: :present
30
30
 
31
31
  invariant("a customer is named") { !value.to_s.empty? }
32
32
  end
33
33
 
34
34
  value_object "ToppingName" do
35
- attribute :value, String, pattern: '[^ \t\n\r]'
35
+ attribute :value, String, matches: :present
36
36
 
37
37
  invariant("a topping is named") { !value.to_s.empty? }
38
38
  # The pattern lets a form feed through; `.strip` is what sees past it.
@@ -46,7 +46,7 @@ Hecks.bluebook "Pizzas" do
46
46
  end
47
47
 
48
48
  value_object "Topping" do
49
- attribute :name, String, pattern: '[^ \t\n\r]'
49
+ attribute :name, String, matches: :present
50
50
  attribute :amount, Integer
51
51
  end
52
52
 
@@ -0,0 +1,3 @@
1
+ Hecks.adapter "ScriptRunner" do
2
+ port "Script"
3
+ end
@@ -0,0 +1,93 @@
1
+ require "digest"
2
+ require "json"
3
+
4
+ module Hecks
5
+ module Adapters
6
+ # The `Script` port's adapter for a `Change`: it runs the scripts registered with it, in this
7
+ # process. A script is a name, a `plan` that says what it would do without doing it, and an
8
+ # `apply` that does it.
9
+ #
10
+ # `examine` plans a change and answers the plan with a digest of it. `perform` plans again and
11
+ # carries the change out only when the plan still has the digest that was confirmed, so what
12
+ # runs is what the person saw. A script that cannot be found, or whose plan or apply raises,
13
+ # refuses the change with its message.
14
+ class ScriptRunner
15
+ # A script: what it would do, and doing it. Both take the arguments as a Hash of String keys.
16
+ Script = Struct.new(:plan, :apply, keyword_init: true)
17
+
18
+ # Why a change was refused: an unknown script, a plan that moved, or the script's own failure.
19
+ class Refused < StandardError; end
20
+
21
+ class << self
22
+ # @return [Hash{String => Script}] the scripts this process can run, by name
23
+ def scripts = @scripts ||= {}
24
+
25
+ # @param name [String] the name a change asks for
26
+ # @param plan [#call] takes the arguments, returns the text of what the script would do
27
+ # @param apply [#call] takes the arguments, does it, returns the text of what happened
28
+ # @return [Script] the registered script
29
+ def register(name, plan:, apply:) = scripts[name.to_s] = Script.new(plan: plan, apply: apply)
30
+
31
+ # Forgets every registered script.
32
+ #
33
+ # @return [Hash] the empty registry
34
+ def reset! = @scripts = {}
35
+ end
36
+
37
+ # Accepts the arguments every driven adapter is built with and keeps none of them.
38
+ #
39
+ # @param aggregate [Object, nil] unused
40
+ # @param settings [Hash] unused
41
+ # @param root [String, nil] unused
42
+ def initialize(aggregate: nil, settings: {}, root: nil); end
43
+
44
+ # Plans the change.
45
+ #
46
+ # @param held [Hash] the record: `script` and `arguments`
47
+ # @return [Hash{Symbol => Hash}] `plan:` what the script would do, and `digest:` its
48
+ # fingerprint
49
+ # @raise [Refused] when the script is unknown or its plan raises
50
+ def examine(**held)
51
+ name, args = request(held)
52
+ text = planned(name, args)
53
+ { plan: { value: text }, digest: { value: digest(name, args, text) } }
54
+ end
55
+
56
+ # Carries out a confirmed change if its plan has not moved.
57
+ #
58
+ # @param held [Hash] the record: `script`, `arguments` and `planned_digest`
59
+ # @return [Hash{Symbol => Hash}] `report:` what the script reported
60
+ # @raise [Refused] when the script is unknown, the plan has moved, or the script raises
61
+ def perform(**held)
62
+ name, args = request(held)
63
+ current = digest(name, args, planned(name, args))
64
+ raise Refused, "the plan moved since it was confirmed; plan it again" unless current == plain(held[:planned_digest])
65
+
66
+ { report: { value: run(name, :apply, args).to_s } }
67
+ end
68
+
69
+ private
70
+
71
+ def request(held) = [plain(held[:script]), arguments(held[:arguments])]
72
+
73
+ def planned(name, args) = run(name, :plan, args).to_s
74
+
75
+ def run(name, step, args)
76
+ script = self.class.scripts.fetch(name) { raise Refused, "no script is registered as #{name.inspect}" }
77
+ script.public_send(step).call(args)
78
+ rescue Refused
79
+ raise
80
+ rescue StandardError => e
81
+ raise Refused, "#{name} #{step} failed: #{e.message}"
82
+ end
83
+
84
+ # The digest stands for the script, its arguments and its plan, so none of them can change
85
+ # between confirming and applying.
86
+ def digest(name, args, text) = Digest::SHA256.hexdigest(JSON.generate([name, args.sort.to_h, text]))
87
+
88
+ def arguments(list) = Array(list).to_h { |pair| [plain(pair[:key]), plain(pair[:value])] }
89
+
90
+ def plain(cell) = cell.is_a?(Hash) && cell.key?(:value) ? cell[:value] : cell
91
+ end
92
+ end
93
+ end
@@ -27,3 +27,4 @@ require_relative "driven/governance_authorization"
27
27
  require_relative "driven/identity_registry"
28
28
  require_relative "driven/google_authentication"
29
29
  require_relative "driven/claude_code"
30
+ require_relative "driven/script_runner"
@@ -17,7 +17,7 @@ module Hecks
17
17
  private_constant :UNSET
18
18
 
19
19
  ATTRIBUTE_FIELD_OPTIONS = %i[default optional pattern admits].freeze
20
- ATTRIBUTE_OPTIONS = [*ATTRIBUTE_FIELD_OPTIONS, :one_of].freeze
20
+ ATTRIBUTE_OPTIONS = [*ATTRIBUTE_FIELD_OPTIONS, :one_of, :matches].freeze
21
21
  private_constant :ATTRIBUTE_FIELD_OPTIONS, :ATTRIBUTE_OPTIONS
22
22
 
23
23
  # Returns the attributes declared so far, minting the accumulating list on first use.
@@ -34,12 +34,12 @@ module Hecks
34
34
  # Declares one field on the owning construct; the type is a bare constant (ADR 0025).
35
35
  #
36
36
  # attribute :op, String, admits: "Vocabulary::QueryComparator"
37
- # `admits:` names an already-declared closed set as qualified text, checked on read.
38
- # `one_of:` declares an inline closed set, meaningful only inside a `value_object`.
37
+ # `admits:` names a declared closed set as qualified text. `one_of:` declares an inline one.
39
38
  #
40
39
  # @param name [Symbol] the field's name
41
40
  # @param type [Module, ListOf, OneOf] a bare constant or a wrapper
42
- # @param options [Hash] `default:`, `optional:`, `pattern:`, `admits:` or `one_of:`
41
+ # @param options [Hash] `default:`, `optional:`, `pattern:`, `matches:` (a `NamedPattern`),
42
+ # `admits:` or `one_of:`
43
43
  # @return [void]
44
44
  # @raise [Malformed] on a duplicate name, a missing or quoted type, a disallowed pattern
45
45
  # @raise [ArgumentError] on any other keyword
@@ -86,7 +86,17 @@ module Hecks
86
86
  raise ArgumentError, "unknown keyword#{"s" if unknown.size > 1}: #{unknown.map(&:inspect).join(", ")}"
87
87
  end
88
88
 
89
- { default: nil, optional: false, pattern: nil, admits: nil, one_of: nil }.merge(options)
89
+ given = { default: nil, optional: false, pattern: nil, admits: nil, one_of: nil }.merge(options)
90
+ expand_matches(given)
91
+ end
92
+
93
+ # `matches: :name` stands for the pattern the language gives that name.
94
+ def expand_matches(given)
95
+ matches = given.delete(:matches)
96
+ return given unless matches
97
+ raise ArgumentError, "matches: and pattern: both state the pattern" if given[:pattern]
98
+
99
+ given.merge(pattern: NamedPattern.source(matches))
90
100
  end
91
101
 
92
102
  def typed_attribute(name, type, field_options)
@@ -37,19 +37,23 @@ module Hecks
37
37
  end
38
38
 
39
39
  # Dispatches every seed row into a fresh "Bluebook" instance and
40
- # reads the result back.
40
+ # reads the result back. The table is memoized for the process, so it is built with no
41
+ # ambient caller: an enclosing `Hecks.as_caller` would have the language's own
42
+ # `Language`-role commands refused.
41
43
  def boot
42
- bluebook = MetaValidator.grammar_registry.bluebook("Bluebook")
43
- # `fresh_runtime`, not a new `Runtime::Registry` — the grammar
44
- # registry already has "Bluebook" registered and its adapter
45
- # ports already loaded.
46
- runtime = MetaValidator.fresh_runtime
47
-
48
- declare_syntax(runtime, bluebook)
49
- admit_keywords(runtime, bluebook)
50
- admit_arguments(runtime, bluebook)
51
-
52
- read_back(runtime, bluebook)
44
+ Runtime::Caller.without do
45
+ bluebook = MetaValidator.grammar_registry.bluebook("Bluebook")
46
+ # `fresh_runtime`, not a new `Runtime::Registry` — the grammar
47
+ # registry already has "Bluebook" registered and its adapter
48
+ # ports already loaded.
49
+ runtime = MetaValidator.fresh_runtime
50
+
51
+ declare_syntax(runtime, bluebook)
52
+ admit_keywords(runtime, bluebook)
53
+ admit_arguments(runtime, bluebook)
54
+
55
+ read_back(runtime, bluebook)
56
+ end
53
57
  end
54
58
 
55
59
  # An Integer stays an Integer — `position` is `Position`-typed, so
@@ -168,32 +168,39 @@ module Hecks
168
168
  end
169
169
 
170
170
  # The booted, self-judged language registry every ordinary bluebook is
171
- # judged against — built once per process and memoized, with shadow parsing off: a caller
172
- # inside `while_shadow_parsing` that is first to ask would otherwise memoize a grammar
173
- # parsed in that mode, or fail to load it.
171
+ # judged against — built once per process and memoized, with shadow parsing off and no
172
+ # ambient caller bound. A caller inside `while_shadow_parsing` that is first to ask would
173
+ # otherwise memoize a grammar parsed in that mode, or fail to load it; one inside
174
+ # `Hecks.as_caller` would have the language's own `Language`-role commands refused, and the
175
+ # grammar would be memoized without the chapters those refusals dropped.
174
176
  #
175
177
  # @return [Runtime::Registry] the booted, fixpoint-judged language
176
178
  # registry
177
179
  def self.grammar_registry
178
- @grammar_registry ||= while_not_shadow_parsing do
179
- registry = load_grammar_into(Runtime::Registry.new)
180
- # Assigned before the fixpoint judge below, since judging re-enters
181
- # grammar_registry (via fresh_runtime/Plan.for) and a bare `||=`
182
- # would still be nil mid-evaluation, recursing forever.
183
- @grammar_registry = registry
184
- # The language now judges itself; every bluebook from here on is
185
- # judged by that assembled graph, not the raw bootstrap one. Must
186
- # run after load_grammar_into's ensure clears @bootstrapping.
187
- while_forcing_fixpoint do
188
- LANGUAGE_CHAPTERS.each { |name| registry.add_bluebook(call(registry.bluebook(name))) }
189
- load_attached_grammar_into(registry)
190
- end
191
- # Keyed on this registry's object identity, not a bare boolean, so
192
- # a manual reset can never leave a stale "ready" from the previous
193
- # cycle.
194
- @grammar_ready_for = registry.object_id
195
- registry
180
+ @grammar_registry ||= while_not_shadow_parsing { Runtime::Caller.without { build_grammar_registry } }
181
+ end
182
+
183
+ # The one-time build behind `grammar_registry`.
184
+ #
185
+ # @return [Runtime::Registry] the booted, fixpoint-judged language registry
186
+ def self.build_grammar_registry
187
+ registry = load_grammar_into(Runtime::Registry.new)
188
+ # Assigned before the fixpoint judge below, since judging re-enters
189
+ # grammar_registry (via fresh_runtime/Plan.for) and a bare `||=`
190
+ # would still be nil mid-evaluation, recursing forever.
191
+ @grammar_registry = registry
192
+ # The language now judges itself; every bluebook from here on is
193
+ # judged by that assembled graph, not the raw bootstrap one. Must
194
+ # run after load_grammar_into's ensure clears @bootstrapping.
195
+ while_forcing_fixpoint do
196
+ LANGUAGE_CHAPTERS.each { |name| registry.add_bluebook(call(registry.bluebook(name))) }
197
+ load_attached_grammar_into(registry)
196
198
  end
199
+ # Keyed on this registry's object identity, not a bare boolean, so
200
+ # a manual reset can never leave a stale "ready" from the previous
201
+ # cycle.
202
+ @grammar_ready_for = registry.object_id
203
+ registry
197
204
  end
198
205
 
199
206
  # Whether `grammar_registry`'s memoized singleton has finished its
@@ -0,0 +1,33 @@
1
+ module Hecks
2
+ module Bluebook
3
+ # The patterns a bluebook names instead of spelling: `matches: :snake_name` says what
4
+ # `pattern: '^[a-z][a-z0-9_]*$'` does. A name stands for exactly its regex, so the IR, the
5
+ # generated Rust and the runtime check see the pattern and never the name.
6
+ #
7
+ # `rust/parser/src/build/named_pattern.rs` holds the same table; spec/named_pattern_spec.rb
8
+ # holds the two to each other.
9
+ module NamedPattern
10
+ # Name => the regex source it stands for, each the exact source the corpus spelled by hand.
11
+ TABLE = {
12
+ present: '[^ \t\n\r]',
13
+ not_empty: '[^\t\n\r]',
14
+ no_whitespace: '^[^ \t\n\r]+$',
15
+ snake_name: "^[a-z][a-z0-9_]*$",
16
+ git_sha: "^[0-9a-fA-F]{7,40}$",
17
+ digits: "^[0-9]+$"
18
+ }.freeze
19
+
20
+ module_function
21
+
22
+ # @param name [Symbol, String] a name in `TABLE`
23
+ # @return [String] the regex source it stands for
24
+ # @raise [ArgumentError] when the name is not one the language gives
25
+ def source(name)
26
+ TABLE.fetch(name.to_sym) do
27
+ raise ArgumentError, "matches: #{name.inspect} is not a named pattern — " \
28
+ "the names are #{TABLE.keys.map(&:inspect).join(", ")}"
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -44,6 +44,7 @@ require_relative "bluebook/project_loader"
44
44
 
45
45
  require_relative "bluebook/dsl"
46
46
  require_relative "bluebook/pattern_subset"
47
+ require_relative "bluebook/named_pattern"
47
48
 
48
49
  require_relative "bluebook/meta_validator"
49
50
  require_relative "bluebook/meta_validator/plan"
@@ -96,7 +96,7 @@ module Hecks
96
96
  attribute :name, ExampleName
97
97
 
98
98
  value_object "ExampleName" do
99
- attribute :value, String, pattern: '[^ \\t\\n\\r]'
99
+ attribute :value, String, matches: :present
100
100
  end
101
101
 
102
102
  # TODO: replace Create with the first thing that happens to an Example.
@@ -6,8 +6,8 @@ module Hecks
6
6
  # The bluebook text of one accepted thing: its aggregate, with its value objects, commands and
7
7
  # lifecycle. `InterviewDraft` extends it, and supplies the naming and citing helpers it calls.
8
8
  module InterviewAggregate
9
- # The pattern every generated string field carries: it refuses a blank value.
10
- PATTERN = %q('[^ \t\n\r]').freeze
9
+ # The option every generated string field carries: it refuses a blank value.
10
+ PRESENT = "matches: :present".freeze
11
11
 
12
12
  # What stands in for a creating command nothing in the interview named.
13
13
  STUB_NOTE = " # TODO: no action that creates was accepted; replace Create with the real one.".freeze
@@ -15,7 +15,7 @@ module Hecks
15
15
  # One accepted thing: what it is called and identified by, its fields, its transitions, the
16
16
  # actions that create it and the others.
17
17
  Shape = Struct.new(:name, :identifier, :type, :fields, :steps, :creating, :rest, keyword_init: true)
18
- private_constant :PATTERN, :STUB_NOTE, :Shape
18
+ private_constant :PRESENT, :STUB_NOTE, :Shape
19
19
 
20
20
  # @api private
21
21
  def aggregate(interview, thing, actions)
@@ -66,7 +66,7 @@ module Hecks
66
66
  # @api private
67
67
  def type_lines(shape)
68
68
  [" value_object #{shape.type.inspect} do",
69
- " attribute :value, String, pattern: #{PATTERN}", " end",
69
+ " attribute :value, String, #{PRESENT}", " end",
70
70
  *InterviewShape.value_objects(shape.fields)]
71
71
  end
72
72