hecks 1.5.1 → 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/adapters/driven/identity_registry.rb +7 -1
- data/lib/hecks/bluebook/capabilities.rb +48 -1
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +24 -2
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +26 -1
- data/lib/hecks/bluebook/hexagon.rb +18 -3
- data/lib/hecks/corpus.rb +120 -9
- data/lib/hecks/framework/bluebook/identity.bluebook +11 -0
- data/lib/hecks/fuzzing/concurrent_dispatch.rb +9 -2
- data/lib/hecks/fuzzing/isolated_boot.rb +12 -2
- data/lib/hecks/language/bluebook/bluebook.bluebook +10 -1
- data/lib/hecks/language/hecksagon/hecksagon.bluebook +6 -0
- data/lib/hecks/ports/persistence/plugins/era/lineage.rb +32 -2
- data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +41 -0
- data/lib/hecks/projections/deploy/fargate.rb +273 -26
- data/lib/hecks/projections/deploy/lambda.rb +21 -4
- data/lib/hecks/projector/exporter.rb +76 -0
- data/lib/hecks/router/namespace_installer.rb +10 -0
- data/lib/hecks/runtime/registry/verification.rb +116 -2
- data/lib/hecks/runtime/registry.rb +119 -3
- 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
|
|
@@ -24,8 +24,14 @@ module Hecks
|
|
|
24
24
|
# @param subject [String] the OIDC subject the issuer vouches for, compared as a String
|
|
25
25
|
# @return [String, nil] the linked identity's id, or nil if nothing has linked this pair
|
|
26
26
|
def resolve(registry, issuer:, subject:)
|
|
27
|
+
# The resolve verb this registry's declared identity provider names
|
|
28
|
+
# (`provides "identity", resolve:`), never a hard-coded chapter —
|
|
29
|
+
# same declared-not-named shape GovernanceAuthorization already holds.
|
|
30
|
+
provider = registry.bluebooks.values.find { |chapter| chapter.provides?(::Hecks::Bluebook::Capabilities::IDENTITY) }
|
|
31
|
+
verb = provider&.provided_verb(::Hecks::Bluebook::Capabilities::IDENTITY, :resolve) ||
|
|
32
|
+
"Identity::ExternalIdentifier.ResolvedBy"
|
|
27
33
|
rows = Runtime::Dispatcher.new(registry).query(
|
|
28
|
-
|
|
34
|
+
verb,
|
|
29
35
|
issuer: { value: issuer.to_s }, subject: { value: subject.to_s }
|
|
30
36
|
)
|
|
31
37
|
|
|
@@ -8,9 +8,30 @@ 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
|
|
19
|
+
# Who may sign in, and with what role — rust/host's Google-OAuth
|
|
20
|
+
# provision/member_rows resolve this instead of an env var naming
|
|
21
|
+
# the aggregate (HECKS_MEMBERSHIP_AGGREGATE). Same declared-
|
|
22
|
+
# not-named shape AUTHORIZATION already is for Governance.
|
|
23
|
+
MEMBERSHIP = "membership".freeze
|
|
24
|
+
# A stable organizational identity, independent of how it was
|
|
25
|
+
# authenticated — recognised by declaration, not the literal name
|
|
26
|
+
# "Identity". Same declared-not-named shape AUTHORIZATION already
|
|
27
|
+
# is. rust/host does not build this chapter's payloads; any field
|
|
28
|
+
# mapping lives on the consuming hecksagon's `translates` ACL.
|
|
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
|
|
14
35
|
|
|
15
36
|
CONTRACTS = {
|
|
16
37
|
AUTHORIZATION => {
|
|
@@ -20,6 +41,32 @@ module Hecks
|
|
|
20
41
|
grant: :command,
|
|
21
42
|
# every grant of one role acting as another
|
|
22
43
|
transitions: :query
|
|
44
|
+
}.freeze,
|
|
45
|
+
MEMBERSHIP => {
|
|
46
|
+
# recognize a person who may eventually sign in
|
|
47
|
+
admit: :command,
|
|
48
|
+
# grant an admitted person a role (the access-grant half)
|
|
49
|
+
grant: :command,
|
|
50
|
+
# every admitted person, for the admin listing
|
|
51
|
+
people: :query
|
|
52
|
+
}.freeze,
|
|
53
|
+
IDENTITY => {
|
|
54
|
+
# mint a stable identity, independent of how it authenticated
|
|
55
|
+
register: :command,
|
|
56
|
+
# associate an (issuer, subject) pair with that identity
|
|
57
|
+
link: :command,
|
|
58
|
+
# look up the identity an authenticated pair resolves to
|
|
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
|
|
23
70
|
}.freeze
|
|
24
71
|
}.freeze
|
|
25
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
|
|
@@ -27,6 +27,20 @@ module Hecks
|
|
|
27
27
|
@subscriptions = []
|
|
28
28
|
@framework_members = []
|
|
29
29
|
@vendored_bluebooks = []
|
|
30
|
+
@bounded = false
|
|
31
|
+
@translates = []
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Marks THIS chapter as a bounded context — a consumer-owned chapter
|
|
35
|
+
# that `uses_framework` / `uses_embryonaut_bluebook` will not load.
|
|
36
|
+
# Framework and vendored packages get the mark automatically from
|
|
37
|
+
# those words; they never write `bounded` in their own bluebooks.
|
|
38
|
+
# A bounded chapter wraps in its own module (no Object shortcut)
|
|
39
|
+
# and must declare at least one `translates` ACL or boot refuses.
|
|
40
|
+
#
|
|
41
|
+
# @return [Boolean] true
|
|
42
|
+
def bounded
|
|
43
|
+
@bounded = true
|
|
30
44
|
end
|
|
31
45
|
|
|
32
46
|
# Subscribes this hecksagon to an event it takes from outside the domain's own bluebook.
|
|
@@ -54,6 +68,11 @@ module Hecks
|
|
|
54
68
|
def uses_framework(name)
|
|
55
69
|
@framework_members << name.to_s
|
|
56
70
|
Hecks::Framework.load!(name)
|
|
71
|
+
# Automatic bounded mark — the framework member is a bounded
|
|
72
|
+
# context; the consumer's sibling `Hecks.hecksagon "Name"` is
|
|
73
|
+
# the anti-corruption layer. Not written in the framework
|
|
74
|
+
# bluebook itself.
|
|
75
|
+
Hecks.current_registry&.mark_bounded(name.to_s)
|
|
57
76
|
end
|
|
58
77
|
|
|
59
78
|
# Attaches a vendored embryonaut bluebook to this domain and loads its files into the
|
|
@@ -83,6 +102,10 @@ module Hecks
|
|
|
83
102
|
def uses_embryonaut_bluebook(name)
|
|
84
103
|
@vendored_bluebooks << name.to_s
|
|
85
104
|
Hecks::EmbryonautBluebook.load!(name)
|
|
105
|
+
# Automatic bounded mark — same as `uses_framework`. The
|
|
106
|
+
# package's directory name Pascal-cases to the chapter
|
|
107
|
+
# (`"membership"` → `Membership`).
|
|
108
|
+
Hecks.current_registry&.mark_bounded(Hecks::Naming.pascal(name.to_s))
|
|
86
109
|
end
|
|
87
110
|
|
|
88
111
|
# Declares a port at the hecksagon's root and attaches it to the registered bluebook.
|
|
@@ -173,6 +196,7 @@ module Hecks
|
|
|
173
196
|
resolver = ->(const) { ConstShim::ScopedConstant.for(const) }
|
|
174
197
|
built = ConstShim.with(resolver) { PolicyBuilder.build(name, &block) }
|
|
175
198
|
|
|
199
|
+
@translates << name.to_s
|
|
176
200
|
bluebook_ir.add_policy(built)
|
|
177
201
|
end
|
|
178
202
|
|
|
@@ -197,7 +221,8 @@ module Hecks
|
|
|
197
221
|
# with any other block declared for the same domain
|
|
198
222
|
def build
|
|
199
223
|
Hecksagon.new(domain: @domain, binds: @binds, subscriptions: @subscriptions,
|
|
200
|
-
framework_members: @framework_members, vendored_bluebooks: @vendored_bluebooks
|
|
224
|
+
framework_members: @framework_members, vendored_bluebooks: @vendored_bluebooks,
|
|
225
|
+
bounded: @bounded, translates: @translates)
|
|
201
226
|
end
|
|
202
227
|
|
|
203
228
|
# Records any verb the grammar does not own as a domain-wide bind to the named adapter.
|
|
@@ -51,10 +51,13 @@ module Hecks
|
|
|
51
51
|
binds: many(:binds),
|
|
52
52
|
subscriptions: -> { subscriptions.map(&:to_s) },
|
|
53
53
|
framework_members: -> { framework_members.map(&:to_s) },
|
|
54
|
-
vendored_bluebooks: -> { vendored_bluebooks.map(&:to_s) }
|
|
54
|
+
vendored_bluebooks: -> { vendored_bluebooks.map(&:to_s) },
|
|
55
|
+
bounded: :bounded,
|
|
56
|
+
translates: -> { translates.map(&:to_s) }
|
|
55
57
|
)
|
|
56
58
|
|
|
57
|
-
attr_reader :domain, :binds, :subscriptions, :framework_members, :vendored_bluebooks
|
|
59
|
+
attr_reader :domain, :binds, :subscriptions, :framework_members, :vendored_bluebooks,
|
|
60
|
+
:translates
|
|
58
61
|
|
|
59
62
|
# @param domain [String, Symbol] the domain this hecksagon wires
|
|
60
63
|
# @param binds [Array<Bluebook::Bind>] the declared adapter binds
|
|
@@ -64,13 +67,25 @@ module Hecks
|
|
|
64
67
|
# (`Governance`, `Identity`, ...) this domain attaches
|
|
65
68
|
# @param vendored_bluebooks [Array<String, Symbol>] the vendored embryonaut
|
|
66
69
|
# bluebook package names this domain attaches
|
|
67
|
-
|
|
70
|
+
# @param bounded [Boolean] whether this chapter is an explicit bounded context
|
|
71
|
+
# (consumer-owned; `uses_framework` / `uses_embryonaut_bluebook` mark
|
|
72
|
+
# attached chapters bounded on the registry instead)
|
|
73
|
+
# @param translates [Array<String>] names of `translates` ACL blocks declared here
|
|
74
|
+
def initialize(domain:, binds: [], subscriptions: [], framework_members: [],
|
|
75
|
+
vendored_bluebooks: [], bounded: false, translates: [])
|
|
68
76
|
@domain = domain.to_s
|
|
69
77
|
@binds = binds
|
|
70
78
|
@subscriptions = subscriptions
|
|
71
79
|
@framework_members = framework_members
|
|
72
80
|
@vendored_bluebooks = vendored_bluebooks
|
|
81
|
+
@bounded = bounded ? true : false
|
|
82
|
+
@translates = Array(translates).map(&:to_s)
|
|
73
83
|
end
|
|
84
|
+
|
|
85
|
+
# Says whether this hecksagon marked its own chapter `bounded`.
|
|
86
|
+
#
|
|
87
|
+
# @return [Boolean] whether `bounded` was declared on this block
|
|
88
|
+
def bounded? = @bounded
|
|
74
89
|
end
|
|
75
90
|
|
|
76
91
|
# The built form of a `.world` file, produced by `DSL::WorldBuilder` —
|
data/lib/hecks/corpus.rb
CHANGED
|
@@ -265,20 +265,112 @@ module Hecks
|
|
|
265
265
|
Elsewhere = Struct.new(:check, :destination, :names, :why)
|
|
266
266
|
|
|
267
267
|
RUST_ELSEWHERE = {
|
|
268
|
-
"meta"
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
"embryonaut"
|
|
273
|
-
|
|
274
|
-
|
|
268
|
+
"meta" => Elsewhere.new(:named_in, "spec/codegen_parity_spec.rb", "bluebook_language",
|
|
269
|
+
"the self-hosted grammar (lib/hecks/language), not a domain directory — every " \
|
|
270
|
+
"bin/project_rust run rewrites it (so the drift check diffs it), codegen parity " \
|
|
271
|
+
"checks it as bluebook_language, and there is no directory to fuzz"),
|
|
272
|
+
"embryonaut" => Elsewhere.new(:external, "~/Projects/embryonautfoundersapp", "embryonaut",
|
|
273
|
+
"an external product's domain — its bluebook, regeneration and parity are owed " \
|
|
274
|
+
"by its own repo; here bin/rust_coverage checks only the committed snapshot"),
|
|
275
|
+
# Lifeadelics — same external-product shape as "embryonaut" above,
|
|
276
|
+
# first generated 2026-09-19 fixing a live era-shape-drift outage.
|
|
277
|
+
# Its directory used to be named "domain" (a generic, collision-
|
|
278
|
+
# prone Cargo feature/module name — the chapter name is, and
|
|
279
|
+
# always was, "Lifeadelics"), so the generated module and Cargo
|
|
280
|
+
# feature were "domain" too, read off `metadata.rs`'s own stamp
|
|
281
|
+
# the same way `generated_source` reads any other module's. Fixed
|
|
282
|
+
# 2026-09-20 by renaming the directory itself
|
|
283
|
+
# (~/Projects/lifeadelics/domain -> ~/Projects/lifeadelics/
|
|
284
|
+
# lifeadelics) — the generated module and Cargo feature are
|
|
285
|
+
# "lifeadelics" now, matching every other domain's own convention.
|
|
286
|
+
#
|
|
287
|
+
# Lifeadelics also attaches `newsletter`/`payments`/`membership`
|
|
288
|
+
# (its own vendored embryonaut_bluebooks packages, see
|
|
289
|
+
# RUST_EXTERNAL_VENDORED_CHAPTERS below) and `Privacy` (an in-repo
|
|
290
|
+
# framework chapter, lib/hecks/framework/bluebook/privacy.bluebook,
|
|
291
|
+
# generated into Rust for the first time by any domain here).
|
|
292
|
+
# `rust_side_chapters` only ever looks for the attaching domain
|
|
293
|
+
# among `rust_domains` — an in-repo directory — so it can attribute
|
|
294
|
+
# neither module to lifeadelics on its own; `rust_attachment_
|
|
295
|
+
# hecksagon_text`, below, reads every `:external` domain's own
|
|
296
|
+
# hecksagon files too, the same way it reads an in-repo domain's,
|
|
297
|
+
# so `Privacy`'s own bucket membership (already correct — it is a
|
|
298
|
+
# real in-repo framework member with no merged.rs) can still be
|
|
299
|
+
# proven attached.
|
|
300
|
+
"lifeadelics" => Elsewhere.new(:external, "~/Projects/lifeadelics", "lifeadelics",
|
|
301
|
+
"an external product's domain — its bluebook, regeneration and parity are owed " \
|
|
302
|
+
"by its own repo; here bin/rust_coverage checks only the committed snapshot")
|
|
275
303
|
}.freeze
|
|
276
304
|
|
|
305
|
+
# Vendored embryonaut_bluebooks packages attached only by an external
|
|
306
|
+
# RUST_ELSEWHERE domain — `members(:vendored)` can't find them
|
|
307
|
+
# itself; that glob only reaches `examples/*/vendor/
|
|
308
|
+
# embryonaut_bluebooks/*`, never an external checkout. Declared once,
|
|
309
|
+
# by hand, the same manual-commit contract "embryonaut" itself
|
|
310
|
+
# already carries: stem => the RUST_ELSEWHERE feature that attaches
|
|
311
|
+
# it. spec/corpus_rust_spec.rb checks each is really attached, the
|
|
312
|
+
# same as an in-repo vendored chapter (see
|
|
313
|
+
# `rust_external_vendored_domain_dir`, below).
|
|
314
|
+
RUST_EXTERNAL_VENDORED_CHAPTERS = {
|
|
315
|
+
"newsletter" => "lifeadelics",
|
|
316
|
+
"payments" => "lifeadelics",
|
|
317
|
+
"membership" => "lifeadelics"
|
|
318
|
+
}.freeze
|
|
319
|
+
|
|
320
|
+
# Every external vendored chapter's own stem.
|
|
321
|
+
#
|
|
322
|
+
# @return [Array<String>] stems declared in RUST_EXTERNAL_VENDORED_CHAPTERS
|
|
323
|
+
def rust_external_vendored_chapters
|
|
324
|
+
RUST_EXTERNAL_VENDORED_CHAPTERS.keys
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
# Where an external vendored chapter's own bluebook lives — somewhere
|
|
328
|
+
# under `<destination>/**/vendor/embryonaut_bluebooks/<stem>`, the
|
|
329
|
+
# same layout `EmbryonautBluebook.load!` resolves for an in-repo
|
|
330
|
+
# vendored member, one level further out. Globbed rather than joined
|
|
331
|
+
# directly: `RUST_ELSEWHERE`'s own `destination` names the external
|
|
332
|
+
# product's checkout root, not necessarily the exact directory its
|
|
333
|
+
# own bluebook lives under (confirmed live against lifeadelics's own
|
|
334
|
+
# checkout: `~/Projects/lifeadelics/lifeadelics/vendor/
|
|
335
|
+
# embryonaut_bluebooks/accounts/bluebook`, one level below
|
|
336
|
+
# `~/Projects/lifeadelics` itself).
|
|
337
|
+
#
|
|
338
|
+
# @param stem [String] an external vendored chapter's stem
|
|
339
|
+
# @return [String, nil] the vendored member's own directory, or nil when none is found
|
|
340
|
+
def rust_external_vendored_domain_dir(stem)
|
|
341
|
+
feature = RUST_EXTERNAL_VENDORED_CHAPTERS.fetch(stem)
|
|
342
|
+
destination = File.expand_path(RUST_ELSEWHERE.fetch(feature).destination)
|
|
343
|
+
Dir.glob(File.join(destination, "**", "vendor", "embryonaut_bluebooks", stem)).first
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
# Every place a Rust-facing domain's own `uses_framework`/
|
|
347
|
+
# `uses_embryonaut_bluebook` attachment could be declared — every
|
|
348
|
+
# in-repo Rust domain's own hecksagon files, plus each `:external`
|
|
349
|
+
# RUST_ELSEWHERE domain's own (its `destination`, expanded, is a
|
|
350
|
+
# real checkout on this machine, read the same way an in-repo
|
|
351
|
+
# domain's own hecksagon files already are).
|
|
352
|
+
#
|
|
353
|
+
# @param root [String] repository root to search under
|
|
354
|
+
# @return [String] every reachable hecksagon file's own text, joined by newlines
|
|
355
|
+
def rust_attachment_hecksagon_text(root: ROOT)
|
|
356
|
+
in_repo = rust_domains(root: root).flat_map { |domain| Dir.glob(File.join(domain.dir, "**", "*.hecksagon")) }
|
|
357
|
+
external = RUST_ELSEWHERE.values.select { |route| route.check == :external }
|
|
358
|
+
.flat_map { |route| Dir.glob(File.join(File.expand_path(route.destination), "**", "*.hecksagon")) }
|
|
359
|
+
(in_repo + external).map { |path| File.read(path) }.join("\n")
|
|
360
|
+
end
|
|
361
|
+
|
|
277
362
|
# **Shrink-only**. A generated module `bin/rust_coverage` still reports a
|
|
278
363
|
# gap for. `bin/corpus --rust-coverage` requires each of these to
|
|
279
364
|
# still fail, so an entry that starts passing breaks the build until
|
|
280
365
|
# it is deleted here.
|
|
281
|
-
RUST_COVERAGE_PENDING = {
|
|
366
|
+
RUST_COVERAGE_PENDING = {
|
|
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"
|
|
373
|
+
}.freeze
|
|
282
374
|
|
|
283
375
|
# The Cargo `[features]` table's raw text.
|
|
284
376
|
#
|
|
@@ -396,7 +488,26 @@ module Hecks
|
|
|
396
488
|
def rust_side_chapters(kind, root: ROOT)
|
|
397
489
|
modules = generated_modules(root: root)
|
|
398
490
|
members(kind, root: root).map(&:stem)
|
|
399
|
-
.select
|
|
491
|
+
.select do |stem|
|
|
492
|
+
module_name = Naming.pascal(stem).downcase
|
|
493
|
+
modules.include?(module_name) && !generated?(module_name, root: root)
|
|
494
|
+
end
|
|
495
|
+
end
|
|
496
|
+
|
|
497
|
+
# The generated module name a `:framework`/`:vendored` stem writes
|
|
498
|
+
# under — `Naming.pascal(stem).downcase` (`bin/project_rust`'s own
|
|
499
|
+
# convention, see `rust_side_chapters`'s header); a chapter whose
|
|
500
|
+
# stem holds an underscore ("console_settings") writes to a
|
|
501
|
+
# different, underscore-free module name ("consolesettings"), so a
|
|
502
|
+
# bucket-membership check against `generated_modules` (a directory
|
|
503
|
+
# name) needs this mapping rather than the raw stem
|
|
504
|
+
# `rust_framework_chapters`/`rust_vendored_chapters` still return
|
|
505
|
+
# (every other caller wants the stem, to build a bluebook file path).
|
|
506
|
+
#
|
|
507
|
+
# @param stem [String] a `:framework`/`:vendored` corpus member's stem
|
|
508
|
+
# @return [String] the generated module's own directory name
|
|
509
|
+
def rust_side_module_name(stem)
|
|
510
|
+
Naming.pascal(stem).downcase
|
|
400
511
|
end
|
|
401
512
|
|
|
402
513
|
def rust_framework_chapters(root: ROOT)
|
|
@@ -9,6 +9,17 @@ Hecks.bluebook "Identity" do
|
|
|
9
9
|
# identity provider is.
|
|
10
10
|
generic
|
|
11
11
|
|
|
12
|
+
# WHAT THIS CHAPTER ANSWERS FOR EVERY DOMAIN THAT ATTACHES IT. Same
|
|
13
|
+
# declared-not-named shape Governance's own `provides "authorization"`
|
|
14
|
+
# already is. rust/host does not build this chapter's payloads — any
|
|
15
|
+
# field mapping lives on the consuming hecksagon's `translates` ACL
|
|
16
|
+
# (any field; this chapter does not list which). Link's reference is
|
|
17
|
+
# `identity`, not `identity_id` and not `to:`.
|
|
18
|
+
provides "identity",
|
|
19
|
+
register: "Identity.Register",
|
|
20
|
+
link: "ExternalIdentifier.Link",
|
|
21
|
+
resolve: "ExternalIdentifier.ResolvedBy"
|
|
22
|
+
|
|
12
23
|
aggregate "Identity" do
|
|
13
24
|
description "A stable organizational identity, independent of how it was authenticated."
|
|
14
25
|
|
|
@@ -435,11 +435,18 @@ module Hecks
|
|
|
435
435
|
# @param schema [String] the PostgresEra schema name to write into the `.world` file
|
|
436
436
|
# @return [void]
|
|
437
437
|
def write_postgres_era_world!(copy, database:, schema:)
|
|
438
|
+
# Same merge IsolatedBoot.rebind_to_postgres_era! now does — one
|
|
439
|
+
# world file per directory, every hecksagon name in that directory.
|
|
440
|
+
worlds_by_dir = Hash.new { |h, k| h[k] = [] }
|
|
438
441
|
Dir.glob(File.join(copy, "**", "*.hecksagon")).each do |hecksagon_path|
|
|
439
|
-
names = File.read(hecksagon_path).scan(/Hecks\.hecksagon\s+"([^"]+)"/).flatten
|
|
442
|
+
names = File.read(hecksagon_path).scan(/Hecks\.hecksagon\s+"([^"]+)"/).flatten
|
|
443
|
+
worlds_by_dir[File.dirname(hecksagon_path)].concat(names)
|
|
444
|
+
end
|
|
445
|
+
worlds_by_dir.each do |dir, names|
|
|
446
|
+
names = names.uniq
|
|
440
447
|
next if names.empty?
|
|
441
448
|
|
|
442
|
-
world_path = File.join(
|
|
449
|
+
world_path = File.join(dir, "hecks_fuzz_postgres_era.world")
|
|
443
450
|
File.write(world_path, names.map do |name|
|
|
444
451
|
<<~WORLD
|
|
445
452
|
Hecks.world "#{name}" do
|
|
@@ -404,11 +404,21 @@ module Hecks
|
|
|
404
404
|
# database it is about to throw away; the one-line warning
|
|
405
405
|
# PostgresEra prints per boot under the opt-in is the honest
|
|
406
406
|
# price. Inert on a machine whose ambient user is ordinary.
|
|
407
|
+
# One world file per directory, every hecksagon name in that
|
|
408
|
+
# directory — not one write per *.hecksagon file. context_map.hecksagon
|
|
409
|
+
# sits beside the domain file; a second File.write to the same
|
|
410
|
+
# hecks_fuzz_postgres_era.world would drop the first file's names
|
|
411
|
+
# (found live: ConcurrentDispatchUnboundFixture + Governance).
|
|
412
|
+
worlds_by_dir = Hash.new { |h, k| h[k] = [] }
|
|
407
413
|
Dir.glob(File.join(copy, "**", "*.hecksagon")).each do |hecksagon_path|
|
|
408
|
-
names = File.read(hecksagon_path).scan(/Hecks\.hecksagon\s+"([^"]+)"/).flatten
|
|
414
|
+
names = File.read(hecksagon_path).scan(/Hecks\.hecksagon\s+"([^"]+)"/).flatten
|
|
415
|
+
worlds_by_dir[File.dirname(hecksagon_path)].concat(names)
|
|
416
|
+
end
|
|
417
|
+
worlds_by_dir.each do |dir, names|
|
|
418
|
+
names = names.uniq
|
|
409
419
|
next if names.empty?
|
|
410
420
|
|
|
411
|
-
world_path = File.join(
|
|
421
|
+
world_path = File.join(dir, "hecks_fuzz_postgres_era.world")
|
|
412
422
|
File.write(world_path, names.map do |name|
|
|
413
423
|
<<~WORLD
|
|
414
424
|
Hecks.world "#{name}" do
|
|
@@ -181,7 +181,11 @@ Hecks.bluebook "Bluebook", version: "1" do
|
|
|
181
181
|
|
|
182
182
|
# ONE ROW OF ONE CAPABILITY — `provides "authorization", assignments:
|
|
183
183
|
# ..., grant: ..., transitions: ...` offers three, one per key, in the
|
|
184
|
-
# order they were written.
|
|
184
|
+
# order they were written. Membership (`admit`/`grant`/`people`) and
|
|
185
|
+
# identity (`register`/`link`/`resolve`) use the same word with their
|
|
186
|
+
# own named keys; the argument table admits every key any capability
|
|
187
|
+
# declares, and `Capabilities::CONTRACTS` holds which keys a given
|
|
188
|
+
# capability must name.
|
|
185
189
|
command "Provide" do
|
|
186
190
|
role "Language"
|
|
187
191
|
goal "Declare one verb this chapter answers a capability with"
|
|
@@ -287,6 +291,11 @@ Hecks.bluebook "Bluebook", version: "1" do
|
|
|
287
291
|
member keyword: "provides", context: "Bluebook", at: "", named: "assignments", kind: "text", required: "false", fills: "provides"
|
|
288
292
|
member keyword: "provides", context: "Bluebook", at: "", named: "grant", kind: "text", required: "false", fills: "provides"
|
|
289
293
|
member keyword: "provides", context: "Bluebook", at: "", named: "transitions", kind: "text", required: "false", fills: "provides"
|
|
294
|
+
member keyword: "provides", context: "Bluebook", at: "", named: "admit", kind: "text", required: "false", fills: "provides"
|
|
295
|
+
member keyword: "provides", context: "Bluebook", at: "", named: "people", kind: "text", required: "false", fills: "provides"
|
|
296
|
+
member keyword: "provides", context: "Bluebook", at: "", named: "register", kind: "text", required: "false", fills: "provides"
|
|
297
|
+
member keyword: "provides", context: "Bluebook", at: "", named: "link", kind: "text", required: "false", fills: "provides"
|
|
298
|
+
member keyword: "provides", context: "Bluebook", at: "", named: "resolve", kind: "text", required: "false", fills: "provides"
|
|
290
299
|
member keyword: "aggregate", context: "Bluebook", at: "1", named: "", kind: "text", required: "true", fills: "name"
|
|
291
300
|
member keyword: "read_model", context: "Bluebook", at: "1", named: "", kind: "text", required: "true", fills: "name"
|
|
292
301
|
member keyword: "policy", context: "Bluebook", at: "1", named: "", kind: "text", required: "true", fills: "name"
|
|
@@ -110,6 +110,12 @@ Hecks.bluebook "Hecksagon" do
|
|
|
110
110
|
# the same shape `subscribe`/`uses_framework` already are, reached
|
|
111
111
|
# by Ruby's own method lookup before `word_gate` ever runs.
|
|
112
112
|
member word: "translates", context: "Hecksagon", body: "keywords", inner: "Policy", opens: "Policy", fills: ""
|
|
113
|
+
# A CONSUMER-OWNED bounded context — framework/vendored packages
|
|
114
|
+
# get this mark automatically from uses_framework /
|
|
115
|
+
# uses_embryonaut_bluebook and never write it in their own files.
|
|
116
|
+
# No `calls:` — `HecksagonBuilder#bounded` is an ordinary method,
|
|
117
|
+
# same shape `subscribe` already is.
|
|
118
|
+
member word: "bounded", context: "Hecksagon", body: "none", inner: "", opens: "", fills: ""
|
|
113
119
|
|
|
114
120
|
end
|
|
115
121
|
|
|
@@ -155,8 +155,13 @@ module Hecks
|
|
|
155
155
|
@drops.each { |name| apply_drop(state, name) }
|
|
156
156
|
# Last, and only where nothing already answered — a backfill
|
|
157
157
|
# fills the gap a rename/move/convert left untouched, never
|
|
158
|
-
# overwrites a value that already made it across.
|
|
159
|
-
|
|
158
|
+
# overwrites a value that already made it across. Dotted-path
|
|
159
|
+
# aware, same as `apply_drop` above (`apply_backfill`'s own
|
|
160
|
+
# header) — a bare name was the only shape this ever needed
|
|
161
|
+
# until a value object gained new required members with zero
|
|
162
|
+
# source data of their own (found live: lifeadelics' Attendee
|
|
163
|
+
# redesign, commit 4326dcd).
|
|
164
|
+
@backfills.each { |backfill| apply_backfill(state, backfill) }
|
|
160
165
|
Entry.new(operation: entry.operation, id: entry.id, state: state, mirrors: entry.mirrors)
|
|
161
166
|
end
|
|
162
167
|
|
|
@@ -312,6 +317,31 @@ module Hecks
|
|
|
312
317
|
end
|
|
313
318
|
end
|
|
314
319
|
|
|
320
|
+
# Applies one backfill, dotted-path aware — `apply_drop`'s own
|
|
321
|
+
# mirror on the addition side. A bare name sets a plain top-level
|
|
322
|
+
# key, unchanged from before; a dotted name (`"attendee.
|
|
323
|
+
# first_name"`) reaches into an existing value-object member the
|
|
324
|
+
# same way `apply_drop`/`hecks_tr_insert` (rule_compiler.rb's own
|
|
325
|
+
# SQL-compiled twin, kept in step with this) already do — creating
|
|
326
|
+
# the container Hash if an old record somehow lacks it entirely,
|
|
327
|
+
# never overwriting a value already there at either level.
|
|
328
|
+
#
|
|
329
|
+
# @param state [Hash] the entry's own state Hash, mutated in place
|
|
330
|
+
# @param backfill [Bluebook::TranslationBackfill] the backfill rule to apply
|
|
331
|
+
# @return [void]
|
|
332
|
+
def apply_backfill(state, backfill)
|
|
333
|
+
name = backfill.name.to_s
|
|
334
|
+
top, member = name.split(".", 2)
|
|
335
|
+
top = top.to_sym
|
|
336
|
+
|
|
337
|
+
if member
|
|
338
|
+
nested = (state[top] ||= {})
|
|
339
|
+
nested[member] = backfill.default unless nested.key?(member)
|
|
340
|
+
else
|
|
341
|
+
state[top] = backfill.default unless state.key?(top)
|
|
342
|
+
end
|
|
343
|
+
end
|
|
344
|
+
|
|
315
345
|
# A dotted path's first segment is a top-level (symbol) key; a
|
|
316
346
|
# second segment reaches into a value-object member by its string
|
|
317
347
|
# key — the spelling a raw stored row carries. Translation runs on
|
|
@@ -54,6 +54,21 @@ module Hecks
|
|
|
54
54
|
declared.computes.each do |compute|
|
|
55
55
|
expression = compile_compute(expression, compute)
|
|
56
56
|
end
|
|
57
|
+
# Backfills last, same order `Lineage#translate` already applies
|
|
58
|
+
# in-process (that method's own comment: "only where nothing
|
|
59
|
+
# already answered") — now compiled here too, closing the gap
|
|
60
|
+
# this rule kind otherwise leaves: `rust/host`'s boot-time mint
|
|
61
|
+
# audit reads this exact compiled expression, never
|
|
62
|
+
# `Lineage#translate`, so it cannot see a backfilled value unless
|
|
63
|
+
# backfills compile to SQL like every other rule kind here. A
|
|
64
|
+
# real, live gap for any new required value-object member with no
|
|
65
|
+
# source data at all — `compute` cannot fill it either, since its
|
|
66
|
+
# own guard requires a real, already-present field to consume
|
|
67
|
+
# (found live: lifeadelics' Attendee redesign, commit 4326dcd,
|
|
68
|
+
# needed exactly this and had nothing that worked).
|
|
69
|
+
declared.backfills.each do |backfill|
|
|
70
|
+
expression = compile_backfill(expression, backfill)
|
|
71
|
+
end
|
|
57
72
|
expression
|
|
58
73
|
end
|
|
59
74
|
|
|
@@ -124,6 +139,32 @@ module Hecks
|
|
|
124
139
|
"LATERAL (SELECT (__s ->> #{text_literal(from)}) AS #{quote(from)}) __fields)"
|
|
125
140
|
end
|
|
126
141
|
|
|
142
|
+
# A newly added, required attribute with no source at all — the
|
|
143
|
+
# addition-side sibling of `compile_compute`'s own header, but with
|
|
144
|
+
# no field to consume: `hecks_tr_extract`'s own `.present` flag
|
|
145
|
+
# (already installed for `hecks_tr_drop`/`hecks_tr_move`/
|
|
146
|
+
# `hecks_tr_convert` — no new database function needed) answers
|
|
147
|
+
# "is this path — bare or a dotted value-object member alike —
|
|
148
|
+
# already there," and `hecks_tr_insert` (already merge-safe: it
|
|
149
|
+
# creates a missing intermediate container without disturbing any
|
|
150
|
+
# sibling member already in it) fills it only when it is not, the
|
|
151
|
+
# exact "never overwrites a value already there" rule `Lineage#
|
|
152
|
+
# translate`'s own in-process backfill already holds itself to.
|
|
153
|
+
#
|
|
154
|
+
# @param expression [String] the SQL expression built so far by `compile_rules`, read as
|
|
155
|
+
# `__s` inside this backfill's own check
|
|
156
|
+
# @param backfill [Bluebook::TranslationBackfill] the declared backfill rule
|
|
157
|
+
# @return [String] `expression` wrapped so the backfill's default lands at its declared
|
|
158
|
+
# path when nothing is there yet, unchanged otherwise
|
|
159
|
+
def compile_backfill(expression, backfill)
|
|
160
|
+
name = backfill.name.to_s
|
|
161
|
+
default_json = JSON.generate(backfill.default)
|
|
162
|
+
"(SELECT CASE WHEN (hecks_tr_extract(__s, #{path_literal(name)})).present THEN __s " \
|
|
163
|
+
"ELSE hecks_tr_insert(__s, #{path_literal(name)}, #{text_literal(default_json)}::jsonb, " \
|
|
164
|
+
"#{text_literal("backfill #{name}")}) END " \
|
|
165
|
+
"FROM (SELECT (#{expression}) AS __s) __outer)"
|
|
166
|
+
end
|
|
167
|
+
|
|
127
168
|
# `PG::Connection.quote_ident` needs the `pg` gem loaded, not
|
|
128
169
|
# connected — required here, lazily, the same "a domain that
|
|
129
170
|
# never wires PostgresEra should never need the gem" reasoning
|