hecks 3.4.1 → 3.5.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 (48) hide show
  1. checksums.yaml +4 -4
  2. data/lib/hecks/adapters/driven/memory/shared_elements.rb +121 -17
  3. data/lib/hecks/deploy/bluebook/deploy.bluebook +1630 -0
  4. data/lib/hecks/deploy/bluebook/deploy.hecksagon +65 -0
  5. data/lib/hecks/deploy/oidc.json +240 -0
  6. data/lib/hecks/doors/cli_runner.rb +13 -4
  7. data/lib/hecks/doors/launcher_options.rb +14 -2
  8. data/lib/hecks/hecks/adapters/bluebook_report.rb +91 -0
  9. data/lib/hecks/hecks/adapters/deploy_plans.rb +49 -0
  10. data/lib/hecks/hecks/adapters/deploy_toolchain.rb +379 -8
  11. data/lib/hecks/hecks/adapters/first_admin.rb +8 -1
  12. data/lib/hecks/hecks/adapters/git.rb +29 -0
  13. data/lib/hecks/hecks/adapters/in_process_operations.rb +6 -2
  14. data/lib/hecks/hecks/adapters/site_toolchain.rb +8 -8
  15. data/lib/hecks/hecks/custodian.bluebook +23 -2
  16. data/lib/hecks/hecks/hecks.hecksagon +66 -0
  17. data/lib/hecks/hecks/hecks.world +6 -1
  18. data/lib/hecks/hecks/site.world +4 -2
  19. data/lib/hecks/projections/deploy/box/hosting.rb +13 -3
  20. data/lib/hecks/projections/deploy/box/roll_recipe.rb +62 -0
  21. data/lib/hecks/projections/deploy/box/templates/deploy-box.sh.tmpl +10 -3
  22. data/lib/hecks/projections/deploy/box/templates/deploy-service.services-roll.part +2 -2
  23. data/lib/hecks/projections/deploy/box/templates/deploy-service.sh.tmpl +16 -4
  24. data/lib/hecks/projections/deploy/box/templates/deploy-service.taskdef-roll.part +6 -6
  25. data/lib/hecks/projections/deploy/box/templates/deploy-service.taskdef.part +1 -1
  26. data/lib/hecks/projections/deploy/box/templates/hosting.mk.tmpl +11 -5
  27. data/lib/hecks/projections/deploy/box/templates/restore-to-rds.sh.tmpl +8 -3
  28. data/lib/hecks/projections/deploy/box/templates/verify-copy.sh.tmpl +2 -2
  29. data/lib/hecks/projections/deploy/box.rb +26 -13
  30. data/lib/hecks/projections/site/admin.rb +140 -0
  31. data/lib/hecks/projections/site/admin_cms/templates.rb +388 -0
  32. data/lib/hecks/projections/site/admin_cms.rb +72 -0
  33. data/lib/hecks/projections/site/payload/fields.rb +234 -0
  34. data/lib/hecks/projections/site/payload/lifecycle.rb +191 -0
  35. data/lib/hecks/projections/site/payload/model.rb +128 -0
  36. data/lib/hecks/projections/site/payload/specs.rb +179 -0
  37. data/lib/hecks/projections/site/payload_driver.rb +93 -0
  38. data/lib/hecks/projections/site/root_rows.rb +59 -0
  39. data/lib/hecks/projections/site/site_admin.rb +292 -0
  40. data/lib/hecks/projections/site/site_host/templates.rb +165 -0
  41. data/lib/hecks/projections/site/site_host.rb +58 -0
  42. data/lib/hecks/projections/site/site_root.rb +179 -0
  43. data/lib/hecks/runtime/value.rb +7 -0
  44. data/lib/hecks/site/bluebook/site.bluebook +13 -2
  45. data/lib/hecks/tools/site_routes.rb +78 -8
  46. data/lib/hecks/version.rb +1 -1
  47. data/rust/host/HECKS_RELEASE +1 -1
  48. metadata +18 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b47e9f61f073e7c8512365b3bb5112f4e214c26cec46e08a323c6f02c509d555
4
- data.tar.gz: d84b642199556cc1b9bbcbc6635c2e248f3f4b63232bb0ca6abee589f85661ca
3
+ metadata.gz: 84b8798a316a3928051f7d6b5aa8baf4ab875e228778303cafeb6e591ada6369
4
+ data.tar.gz: 72bbefbce9a00405f7d1c8e7a3d0f99dcdf1fdf5f6cb8334e96b3f03cb944b4a
5
5
  SHA512:
6
- metadata.gz: e2280fdb747d6d8a299bd58f4ab261e71a0d977ed805e9fcfe86ae108cc248724c96960614b9af6796d4617ef9102be1343107a91c6044123a97f298ba6002f1
7
- data.tar.gz: 6558a4e9698ca6e01a665ab3d9aa435da6990e20a69ef06b0c0cd91bc0edcedc771a976dc664c4501d56883ebca1435adb534d3516a20d8fd55dca3c90ff40ff
6
+ metadata.gz: 82d1b5ea4652ccb5c4ce3788ea20a010e9466022853f5b187a6bb24db2a39d584337e7b6443ea3aa1cb9ed746b79863b41c40ec1ab0ba4312ad6e0dc64fb6cd5
7
+ data.tar.gz: d2d3d2e6b553e1d3d4ffcf98e9007af02d8f80a6bd0ffd022be797adf1571c7328a8e6e79a2b05aa407ec5086fb84e82bd00ce842c4e2dae3b2145510143a8ec
@@ -9,9 +9,12 @@ module Hecks
9
9
  # Lets a Memory save of a growing `list_of` copy and hydrate only the elements it has not
10
10
  # seen. A hydrated composite element is frozen, so its codec copy (the journal form) and
11
11
  # its hydrated form are each computed once, frozen, and shared by every journal entry and
12
- # record that holds it. Shared nodes are immutable, so a caller cannot reach the journal
13
- # through a returned instance. Everything else in the state goes through `StateCodec`
14
- # whole. Both tables are weak and identity-keyed: a cache, never the only holder.
12
+ # record that holds it. A single value-object attribute that holds a list, directly or
13
+ # through a nested value object, shares the same way: the hydrated `Runtime::Value` is
14
+ # copied once, and a later version of it reuses the copies of the list elements it still
15
+ # holds. Shared nodes are immutable, so a caller cannot reach the journal through a
16
+ # returned instance. Everything else in the state goes through `StateCodec` whole. The
17
+ # tables are weak and identity-keyed: a cache, never the only holder.
15
18
  class SharedElements
16
19
  # @param aggregate [Bluebook::Aggregate] the aggregate whose state this shares elements of
17
20
  def initialize(aggregate)
@@ -25,6 +28,7 @@ module Hecks
25
28
  def reset!
26
29
  @journal = ObjectSpace::WeakMap.new # live element => frozen decoded copy
27
30
  @live = ObjectSpace::WeakMap.new # frozen decoded copy => hydrated element
31
+ @copies = ObjectSpace::WeakMap.new # frozen value-object copy this table built => true
28
32
  self
29
33
  end
30
34
 
@@ -36,11 +40,13 @@ module Hecks
36
40
  # are frozen and shared with every other copy that holds them, nothing else is shared
37
41
  # with `state`
38
42
  def journal_state(state)
39
- lists = shareable_lists(state)
40
- return Ports::Persistence::StateCodec.copy(@aggregate, state) if lists.empty?
43
+ lists = shareable_lists(state)
44
+ values = journal_values(state)
45
+ return Ports::Persistence::StateCodec.copy(@aggregate, state) if lists.empty? && values.empty?
41
46
 
42
- copied = Ports::Persistence::StateCodec.copy(@aggregate, placeholders(state, lists))
47
+ copied = Ports::Persistence::StateCodec.copy(@aggregate, placeholders(state, lists, values))
43
48
  lists.each { |name, (attribute, elements)| copied[name] = journal_list(attribute, elements) }
49
+ values.each { |name, (attribute, value)| copied[name] = journal_value(attribute, value) }
44
50
  copied
45
51
  end
46
52
 
@@ -48,12 +54,14 @@ module Hecks
48
54
  #
49
55
  # @param decoded [Hash] a fresh state in the shape `journal_state` returns; its non-shared
50
56
  # values are hydrated in place of copies, so it must not be a journal entry's own state
51
- # @return [Hash{Symbol => Object}] hydrated state, defaults filled; the lists it shares
52
- # are frozen arrays of frozen elements, and nothing mutable is shared with `decoded`
57
+ # @return [Hash{Symbol => Object}] hydrated state, defaults filled; the lists and value
58
+ # objects it shares are frozen, and nothing mutable is shared with `decoded`
53
59
  def live_state(decoded)
54
- lists = shareable_lists(decoded)
55
- hydrated = Runtime::Instance.hydrate_with_defaults(@aggregate, placeholders(decoded, lists))
60
+ lists = shareable_lists(decoded)
61
+ values = live_values(decoded)
62
+ hydrated = Runtime::Instance.hydrate_with_defaults(@aggregate, placeholders(decoded, lists, values))
56
63
  lists.each { |name, (attribute, elements)| hydrated[name] = live_list(attribute, elements) }
64
+ values.each { |name, (attribute, copy)| hydrated[name] = live_value(attribute, copy) }
57
65
  hydrated
58
66
  end
59
67
 
@@ -70,26 +78,122 @@ module Hecks
70
78
  end
71
79
  end
72
80
 
73
- def composite_lists
74
- @composite_lists ||= @aggregate.attributes.each_with_object({}) do |attribute, found|
81
+ def composite_lists = list_attributes(@aggregate)
82
+
83
+ def list_attributes(owner)
84
+ owner.attributes.each_with_object({}) do |attribute, found|
75
85
  next unless attribute.list? && !attribute.reference? && composite?(attribute)
76
86
 
77
87
  found[attribute.name.to_sym] = attribute
78
88
  end
79
89
  end
80
90
 
91
+ # The attributes of the aggregate that hold one value object able to hold a list, by
92
+ # name. A value object with no list in it is small and is left to the codec.
93
+ def growable_values
94
+ @growable_values ||= @aggregate.attributes.each_with_object({}) do |attribute, found|
95
+ next if attribute.list? || attribute.reference?
96
+
97
+ shape = value_object_of(attribute)
98
+ found[attribute.name.to_sym] = attribute if shape && growable?(shape, [])
99
+ end
100
+ end
101
+
102
+ def value_object_of(attribute)
103
+ return nil unless @aggregate.respond_to?(:value_object)
104
+
105
+ Runtime::Value.value_object_for(@aggregate, attribute.type.to_s)
106
+ end
107
+
108
+ def growable?(shape, seen)
109
+ return false if seen.include?(shape)
110
+
111
+ shape.attributes.any? do |attribute|
112
+ next false if attribute.reference?
113
+ next true if attribute.list?
114
+
115
+ nested = value_object_of(attribute)
116
+ nested ? growable?(nested, seen + [shape]) : false
117
+ end
118
+ end
119
+
120
+ # The growable value-object attributes of `state` holding a frozen value of their type.
121
+ def journal_values(state)
122
+ return {} unless state.is_a?(Hash)
123
+
124
+ growable_values.each_with_object({}) do |(name, attribute), found|
125
+ value = state[name]
126
+ found[name] = [attribute, value] if value.is_a?(Runtime::Value) && sharable_value?(attribute, value)
127
+ end
128
+ end
129
+
130
+ def sharable_value?(attribute, value)
131
+ value.frozen? && value.type_name == value_object_of(attribute).hecks_name
132
+ end
133
+
134
+ # The growable value-object attributes of `decoded` holding a frozen Hash: a copy this
135
+ # table made, or one a caller froze.
136
+ def live_values(decoded)
137
+ return {} unless decoded.is_a?(Hash)
138
+
139
+ growable_values.each_with_object({}) do |(name, attribute), found|
140
+ copy = decoded[name]
141
+ found[name] = [attribute, copy] if copy.is_a?(Hash) && copy.frozen?
142
+ end
143
+ end
144
+
81
145
  def composite?(attribute)
82
146
  type = attribute.type.to_s
83
147
  !Runtime::Value.find_entity(@aggregate, type).nil? ||
84
148
  (@aggregate.respond_to?(:value_object) && !Runtime::Value.value_object_for(@aggregate, type).nil?)
85
149
  end
86
150
 
87
- # `state` with each shared list replaced by nil, which the codec leaves alone and
88
- # which holds the list's place in the key order.
89
- def placeholders(state, lists)
90
- return state if lists.empty?
151
+ # `state` with each shared list and value object replaced by nil, which the codec
152
+ # leaves alone and which holds its place in the key order.
153
+ def placeholders(state, lists, values)
154
+ return state if lists.empty? && values.empty?
155
+
156
+ state.merge(lists.merge(values).transform_values { nil })
157
+ end
158
+
159
+ # The frozen codec copy of one value object. A list it holds is rebuilt from the copies
160
+ # of its elements, so a new version of the value costs the new elements, not all of them.
161
+ def journal_value(attribute, value)
162
+ known = @journal[value]
163
+ return known if known
164
+
165
+ fields = value.raw_fields
166
+ lists = value_lists(attribute, fields)
167
+ held = fields.merge(lists.transform_values { nil })
168
+ copy = Ports::Persistence::StateCodec.copy_list_element(@aggregate, attribute, held)
169
+ copy.each_value { |inner| Freezer.deep(inner) }
170
+ lists.each { |name, (list_attribute, elements)| copy[name] = journal_list(list_attribute, elements) }
171
+ @copies[copy.freeze] = true
172
+ @journal[value] = copy
173
+ end
174
+
175
+ def value_lists(attribute, fields)
176
+ list_attributes(value_object_of(attribute)).each_with_object({}) do |(name, list_attribute), found|
177
+ elements = fields[name]
178
+ found[name] = [list_attribute, elements] if elements.is_a?(Array)
179
+ end
180
+ end
181
+
182
+ # The hydrated value for one frozen copy, its lists rebuilt from the hydrated elements
183
+ # already held.
184
+ def live_value(attribute, copy)
185
+ known = @live[copy]
186
+ return known if known
187
+
188
+ lists = value_lists(attribute, copy)
189
+ fields = copy.merge(lists.to_h { |name, (list_attribute, elements)| [name, live_list(list_attribute, elements)] })
190
+ live = Runtime::Value.trusting_stored_state do
191
+ Runtime::Value.for_attribute(@aggregate, attribute, fields)
192
+ end
193
+ return live unless @copies.key?(copy)
91
194
 
92
- state.merge(lists.transform_values { nil })
195
+ @journal[live] = copy
196
+ @live[copy] = live
93
197
  end
94
198
 
95
199
  def journal_list(attribute, elements)