hecks 2.0.0 → 2.1.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.
- checksums.yaml +4 -4
- data/lib/hecks/bluebook/capabilities.rb +21 -1
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +24 -2
- data/lib/hecks/corpus.rb +7 -10
- data/lib/hecks/projector/exporter.rb +28 -0
- data/lib/hecks/runtime/registry/verification.rb +32 -0
- data/lib/hecks/runtime/registry.rb +33 -8
- data/lib/hecks/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b7e80387e679bdc08c6a48b9c1eabefee28d7f9fd0e28a41f203e1fd9b207c34
|
|
4
|
+
data.tar.gz: 455038d69b310e2fc9ae72b30d811623c8c1a85a4a5da118b842b8af7fd082b0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7738a1869d51907dce7b981d7f0d3d1977f73a8baae57d947d2667155830495f9e19163a4c1fe5785e79fa262a7733c6d1d31086b6bc0ed85191bd31d6ad829b
|
|
7
|
+
data.tar.gz: f3506ffdc5133c083030ac87f0056a1373de5802520116956b0e30c00ea3d473e1e55f9fa62e813048e98c063aa05446a44e61ff49540dcc266e74546d6da7fa
|
|
@@ -8,7 +8,12 @@ module Hecks
|
|
|
8
8
|
# complete and point at real verbs. `BluebookBuilder::Validation
|
|
9
9
|
# #validate_provisions!` holds every `provides` to this table.
|
|
10
10
|
#
|
|
11
|
-
# key => :command | :query the kind of verb that key must name
|
|
11
|
+
# key => :command | :query | :port_operation the kind of verb that key must name
|
|
12
|
+
#
|
|
13
|
+
# A `:port_operation` verb is spelled `"Aggregate.Port.Operation"` and names
|
|
14
|
+
# an operation the chapter's hecksagon declares on one of its aggregates.
|
|
15
|
+
# The hecksagon attaches after the chapter is built, so the chapter itself
|
|
16
|
+
# only checks the spelling; `Registry#verify!` checks the operation exists.
|
|
12
17
|
module Capabilities
|
|
13
18
|
AUTHORIZATION = "authorization".freeze
|
|
14
19
|
# Who may sign in, and with what role — rust/host's Google-OAuth
|
|
@@ -22,6 +27,11 @@ module Hecks
|
|
|
22
27
|
# is. rust/host does not build this chapter's payloads; any field
|
|
23
28
|
# mapping lives on the consuming hecksagon's `translates` ACL.
|
|
24
29
|
IDENTITY = "identity".freeze
|
|
30
|
+
# Guest-facing newsletter signup: who subscribed, confirmed, or left —
|
|
31
|
+
# recognised by declaration, not the literal chapter name
|
|
32
|
+
# "Newsletter". rust/host's newsletter routes dispatch these verbs
|
|
33
|
+
# and read the subscribing aggregate's instances off `subscribe`.
|
|
34
|
+
NEWSLETTER = "newsletter".freeze
|
|
25
35
|
|
|
26
36
|
CONTRACTS = {
|
|
27
37
|
AUTHORIZATION => {
|
|
@@ -47,6 +57,16 @@ module Hecks
|
|
|
47
57
|
link: :command,
|
|
48
58
|
# look up the identity an authenticated pair resolves to
|
|
49
59
|
resolve: :query
|
|
60
|
+
}.freeze,
|
|
61
|
+
NEWSLETTER => {
|
|
62
|
+
# a guest signs up; the aggregate it names is the subscriber
|
|
63
|
+
subscribe: :command,
|
|
64
|
+
# attach a display name to an existing subscriber
|
|
65
|
+
add_name: :command,
|
|
66
|
+
# a subscriber confirms their address from the emailed link
|
|
67
|
+
confirm: :command,
|
|
68
|
+
# a subscriber leaves from the emailed link
|
|
69
|
+
unsubscribe: :command
|
|
50
70
|
}.freeze
|
|
51
71
|
}.freeze
|
|
52
72
|
end
|
|
@@ -113,12 +113,14 @@ module Hecks
|
|
|
113
113
|
# @param capability [String] the capability the row belongs to, such as
|
|
114
114
|
# `"authorization"`
|
|
115
115
|
# @param row [Bluebook::Chapter::Provision] the one declared row to check
|
|
116
|
-
# @param kind [Symbol] `:command` or `:
|
|
117
|
-
# must name (from `Capabilities::CONTRACTS`)
|
|
116
|
+
# @param kind [Symbol] `:command`, `:query` or `:port_operation`, the kind of
|
|
117
|
+
# verb this row's key must name (from `Capabilities::CONTRACTS`)
|
|
118
118
|
# @return [void]
|
|
119
119
|
# @raise [Bluebook::DSL::Malformed] if `row.verb` does not split into a real
|
|
120
120
|
# aggregate and a member of the right kind that aggregate declares
|
|
121
121
|
def validate_provided_verb!(bluebook, capability, row, kind)
|
|
122
|
+
return validate_provided_port_operation!(bluebook, capability, row) if kind == :port_operation
|
|
123
|
+
|
|
122
124
|
aggregate_name, member = row.verb.split(".", 2)
|
|
123
125
|
aggregate = bluebook.aggregate(aggregate_name)
|
|
124
126
|
return if aggregate && member && provided_member_names(aggregate, kind).include?(member)
|
|
@@ -127,6 +129,26 @@ module Hecks
|
|
|
127
129
|
"which names no #{kind} this chapter declares (spelled \"Aggregate.#{kind.capitalize}\")"
|
|
128
130
|
end
|
|
129
131
|
|
|
132
|
+
# Checks the spelling of a `provides` verb that names a hecksagon port
|
|
133
|
+
# operation. The port itself is declared in the hecksagon, which attaches
|
|
134
|
+
# after this chapter is built, so all that can be checked here is that the
|
|
135
|
+
# verb has the `"Aggregate.Port.Operation"` shape and its aggregate is
|
|
136
|
+
# this chapter's; `Registry#verify!` checks the operation exists.
|
|
137
|
+
#
|
|
138
|
+
# @param bluebook [Bluebook::Chapter] the chapter the row was declared on
|
|
139
|
+
# @param capability [String] the capability the row belongs to
|
|
140
|
+
# @param row [Bluebook::Chapter::Provision] the one declared row to check
|
|
141
|
+
# @return [void]
|
|
142
|
+
# @raise [Bluebook::DSL::Malformed] if the verb is not `"Aggregate.Port.Operation"`
|
|
143
|
+
# or names an aggregate this chapter does not declare
|
|
144
|
+
def validate_provided_port_operation!(bluebook, capability, row)
|
|
145
|
+
aggregate_name, port, operation = row.verb.split(".", 3)
|
|
146
|
+
return if bluebook.aggregate(aggregate_name) && port && operation && !operation.include?(".")
|
|
147
|
+
|
|
148
|
+
raise Malformed, "#{bluebook.name} provides #{capability.inspect} #{row.key}: #{row.verb.inspect}, " \
|
|
149
|
+
"which is not spelled \"Aggregate.Port.Operation\" over an aggregate this chapter declares"
|
|
150
|
+
end
|
|
151
|
+
|
|
130
152
|
# Names of the members a `provides` verb of the given kind may address.
|
|
131
153
|
#
|
|
132
154
|
# @param aggregate [Bluebook::Aggregate] the aggregate the verb is scoped to
|
data/lib/hecks/corpus.rb
CHANGED
|
@@ -284,7 +284,7 @@ module Hecks
|
|
|
284
284
|
# lifeadelics) — the generated module and Cargo feature are
|
|
285
285
|
# "lifeadelics" now, matching every other domain's own convention.
|
|
286
286
|
#
|
|
287
|
-
# Lifeadelics also attaches `
|
|
287
|
+
# Lifeadelics also attaches `newsletter`/`payments`/`membership`
|
|
288
288
|
# (its own vendored embryonaut_bluebooks packages, see
|
|
289
289
|
# RUST_EXTERNAL_VENDORED_CHAPTERS below) and `Privacy` (an in-repo
|
|
290
290
|
# framework chapter, lib/hecks/framework/bluebook/privacy.bluebook,
|
|
@@ -312,7 +312,6 @@ module Hecks
|
|
|
312
312
|
# same as an in-repo vendored chapter (see
|
|
313
313
|
# `rust_external_vendored_domain_dir`, below).
|
|
314
314
|
RUST_EXTERNAL_VENDORED_CHAPTERS = {
|
|
315
|
-
"accounts" => "lifeadelics",
|
|
316
315
|
"newsletter" => "lifeadelics",
|
|
317
316
|
"payments" => "lifeadelics",
|
|
318
317
|
"membership" => "lifeadelics"
|
|
@@ -365,14 +364,12 @@ module Hecks
|
|
|
365
364
|
# still fail, so an entry that starts passing breaks the build until
|
|
366
365
|
# it is deleted here.
|
|
367
366
|
RUST_COVERAGE_PENDING = {
|
|
368
|
-
"
|
|
369
|
-
"
|
|
370
|
-
"unfiltered per_instance Listing (\"nothing for filter_entries to
|
|
371
|
-
"structural Rust codegen limitation, not a bug in this vendored package",
|
|
372
|
-
"
|
|
373
|
-
|
|
374
|
-
"lifeadelics" => "same no_wheres gap as accounts, on Registration.Listing — see accounts' entry above",
|
|
375
|
-
"membership" => "same no_wheres gap as accounts, on Person.All — see accounts' entry above"
|
|
367
|
+
"newsletter" => "query Listing declares no where clause at all, on all 3 of its own Listing queries " \
|
|
368
|
+
"(Delivery/Issue/Subscriber) — rust/project/queries.rb's own no_wheres skip refuses " \
|
|
369
|
+
"to generate an unfiltered per_instance Listing (\"nothing for filter_entries to " \
|
|
370
|
+
"bake in\"); a structural Rust codegen limitation, not a bug in this vendored package",
|
|
371
|
+
"lifeadelics" => "same no_wheres gap as newsletter, on Registration.Listing — see newsletter's entry above",
|
|
372
|
+
"membership" => "same no_wheres gap as newsletter, on Person.All — see newsletter's entry above"
|
|
376
373
|
}.freeze
|
|
377
374
|
|
|
378
375
|
# The Cargo `[features]` table's raw text.
|
|
@@ -174,6 +174,34 @@ module Hecks
|
|
|
174
174
|
}
|
|
175
175
|
end
|
|
176
176
|
|
|
177
|
+
# Same seam as `membership` — which chapter answers the guest
|
|
178
|
+
# newsletter signup (`Registry#newsletter_provider_for`), with its
|
|
179
|
+
# declared verbs qualified and the subscribing aggregate named off
|
|
180
|
+
# `subscribe`. rust/host reads this instead of naming
|
|
181
|
+
# Newsletter::Subscriber.Subscribe and friends. `{}` when nothing
|
|
182
|
+
# this domain attaches provides newsletter.
|
|
183
|
+
# @param registry [Runtime::Registry] the booted registry `domain_name` is loaded in
|
|
184
|
+
# @param domain_name [String] the domain to export the newsletter binding for
|
|
185
|
+
# @return [Hash{Symbol => String, nil}] `:provider` (name), `:subscribe`, `:add_name`,
|
|
186
|
+
# `:confirm`, `:unsubscribe` (qualified verbs), and `:aggregate` (`:subscribe`'s own
|
|
187
|
+
# leading qualified aggregate name); `{}` if nothing this domain attaches provides
|
|
188
|
+
# newsletter
|
|
189
|
+
def newsletter(registry, domain_name)
|
|
190
|
+
provider = registry.newsletter_provider_for(domain_name)
|
|
191
|
+
return {} unless provider
|
|
192
|
+
|
|
193
|
+
capability = Bluebook::Capabilities::NEWSLETTER
|
|
194
|
+
subscribe = provider.provided_verb(capability, :subscribe)
|
|
195
|
+
{
|
|
196
|
+
provider: provider.name,
|
|
197
|
+
subscribe: subscribe,
|
|
198
|
+
add_name: provider.provided_verb(capability, :add_name),
|
|
199
|
+
confirm: provider.provided_verb(capability, :confirm),
|
|
200
|
+
unsubscribe: provider.provided_verb(capability, :unsubscribe),
|
|
201
|
+
aggregate: subscribe&.split(".")&.first
|
|
202
|
+
}
|
|
203
|
+
end
|
|
204
|
+
|
|
177
205
|
# Translation IR, always as an array, with each aggregate's
|
|
178
206
|
# precompiled SQL attached (`compiled_translation_aggregate`) —
|
|
179
207
|
# this is the export a consumer embeds (`ir.json`'s `translations`
|
|
@@ -24,6 +24,7 @@ module Hecks
|
|
|
24
24
|
verify_singleton_port_answers!
|
|
25
25
|
refuse_cross_package_bluebook_merge!
|
|
26
26
|
refuse_membership_without_identity!
|
|
27
|
+
refuse_unresolved_port_operations!
|
|
27
28
|
|
|
28
29
|
@hecksagons.each_value do |hexagon|
|
|
29
30
|
refuse_ungoverned_roles!(hexagon)
|
|
@@ -330,6 +331,37 @@ module Hecks
|
|
|
330
331
|
"identity verbs are exported onto ir.json, not guessed at deploy."
|
|
331
332
|
end
|
|
332
333
|
|
|
334
|
+
# A `provides` row whose contract kind is `:port_operation` names an
|
|
335
|
+
# operation the chapter's hecksagon declares, and the hecksagon
|
|
336
|
+
# attaches after the chapter is built, so this is the first point the
|
|
337
|
+
# operation can be checked. Without it, a host reading the declared
|
|
338
|
+
# verb would dispatch to an operation nothing declares.
|
|
339
|
+
def refuse_unresolved_port_operations!
|
|
340
|
+
@bluebooks.each_value do |chapter|
|
|
341
|
+
chapter.provides.each do |row|
|
|
342
|
+
next unless Bluebook::Capabilities::CONTRACTS.dig(row.capability, row.key.to_sym) == :port_operation
|
|
343
|
+
next if port_operation_declared?(chapter, row.verb)
|
|
344
|
+
|
|
345
|
+
raise WiringError,
|
|
346
|
+
"#{chapter.name} provides #{row.capability.inspect} #{row.key}: #{row.verb.inspect}, " \
|
|
347
|
+
"but its hecksagon declares no such port operation — declare it with " \
|
|
348
|
+
"`#{chapter.name}::Aggregate.port \"Port\" do operation \"Operation\" ... end`."
|
|
349
|
+
end
|
|
350
|
+
end
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
# Says whether a chapter declares the port operation an
|
|
354
|
+
# `"Aggregate.Port.Operation"` verb names.
|
|
355
|
+
#
|
|
356
|
+
# @param chapter [Bluebook::Chapter] the chapter whose aggregates' ports are searched
|
|
357
|
+
# @param verb [String] the verb, spelled `"Aggregate.Port.Operation"`
|
|
358
|
+
# @return [Boolean] whether that aggregate has that port with that operation
|
|
359
|
+
def port_operation_declared?(chapter, verb)
|
|
360
|
+
aggregate_name, port_name, operation_name = verb.split(".", 3)
|
|
361
|
+
ports = chapter.aggregate(aggregate_name)&.ports || []
|
|
362
|
+
ports.any? { |port| port.name == port_name && port.operations.any? { |op| op.hecks_name == operation_name } }
|
|
363
|
+
end
|
|
364
|
+
|
|
333
365
|
def refuse_bounded_without_acl!(hexagon)
|
|
334
366
|
return unless hexagon.bounded?
|
|
335
367
|
return if hexagon.translates.any?
|
|
@@ -375,20 +375,45 @@ module Hecks
|
|
|
375
375
|
# @return [Bluebook::Chapter, nil] the chapter that answers `domain`'s membership
|
|
376
376
|
# questions, or nil if none does
|
|
377
377
|
def membership_provider_for(domain)
|
|
378
|
+
vendored_provider_for(domain, Bluebook::Capabilities::MEMBERSHIP)
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
# The chapter that answers the guest newsletter signup for `domain` —
|
|
382
|
+
# the domain's own chapter, a framework member, or a vendored
|
|
383
|
+
# embryonaut bluebook it attaches, that declares `provides
|
|
384
|
+
# "newsletter"`. Nil when none does. Newsletter is recognised by
|
|
385
|
+
# what it declares (Subscribe/AddName/Confirm/Unsubscribe), so a
|
|
386
|
+
# chapter that declares the same thing is recognised the same way.
|
|
387
|
+
#
|
|
388
|
+
# @param domain [String, Symbol] the domain whose newsletter chapter is being resolved
|
|
389
|
+
# @return [Bluebook::Chapter, nil] the chapter that answers `domain`'s newsletter
|
|
390
|
+
# signup, or nil if none does
|
|
391
|
+
def newsletter_provider_for(domain)
|
|
392
|
+
vendored_provider_for(domain, Bluebook::Capabilities::NEWSLETTER)
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
# The chapter that provides `capability` for `domain`: the domain's
|
|
396
|
+
# own chapter, any framework member its hecksagon attaches, or any
|
|
397
|
+
# vendored embryonaut bluebook it attaches, that declares it. Falls
|
|
398
|
+
# back to any loaded chapter that does, because a consuming domain
|
|
399
|
+
# often wires a vendored chapter as its own `Hecks.hecksagon "Name"`
|
|
400
|
+
# (so that chapter can attach Governance on that named hexagon)
|
|
401
|
+
# rather than via `uses_embryonaut_bluebook` on itself — the chapter
|
|
402
|
+
# is loaded, it just isn't listed on the consumer's own hexagon.
|
|
403
|
+
#
|
|
404
|
+
# @param domain [String, Symbol] the domain the provider is resolved for
|
|
405
|
+
# @param capability [String] the capability's name, such as
|
|
406
|
+
# `Bluebook::Capabilities::MEMBERSHIP`
|
|
407
|
+
# @return [Bluebook::Chapter, nil] the providing chapter, or nil if none loaded does
|
|
408
|
+
def vendored_provider_for(domain, capability)
|
|
378
409
|
hexagon = hecksagon(domain)
|
|
379
410
|
vendored = Array(hexagon&.vendored_bluebooks).map { |name| Naming.pascal(name) }
|
|
380
411
|
names = [domain.to_s, *Array(hexagon&.framework_members), *vendored]
|
|
381
412
|
attached = names.filter_map { |name| bluebook(name) }
|
|
382
|
-
.find { |chapter| chapter.provides?(
|
|
413
|
+
.find { |chapter| chapter.provides?(capability) }
|
|
383
414
|
return attached if attached
|
|
384
415
|
|
|
385
|
-
|
|
386
|
-
# `Hecks.hecksagon "Membership"` (so Person::Admit can attach
|
|
387
|
-
# Governance on that named hexagon), not via uses_embryonaut_bluebook
|
|
388
|
-
# on the consuming domain. The chapter is loaded; it just isn't
|
|
389
|
-
# listed on Lifeadelics' own hexagon. Any loaded chapter that
|
|
390
|
-
# provides membership is the bounded-context answer.
|
|
391
|
-
@bluebooks.values.find { |chapter| chapter.provides?(Bluebook::Capabilities::MEMBERSHIP) }
|
|
416
|
+
@bluebooks.values.find { |chapter| chapter.provides?(capability) }
|
|
392
417
|
end
|
|
393
418
|
|
|
394
419
|
# Resolves and memoizes `aggregate`'s authoritative repository.
|
data/lib/hecks/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: hecks
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.
|
|
4
|
+
version: 2.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Chris Young
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-09-
|
|
11
|
+
date: 2026-09-25 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: prism
|