hecks 1.5.0 → 2.0.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 +27 -0
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +26 -1
- data/lib/hecks/bluebook/hexagon.rb +18 -3
- data/lib/hecks/corpus.rb +123 -9
- data/lib/hecks/deploy/bluebook/deploy.bluebook +105 -0
- data/lib/hecks/deploy/oidc.json +5 -0
- data/lib/hecks/framework/bluebook/compliance.bluebook +221 -0
- 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/key_vault.rb +27 -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 +913 -0
- data/lib/hecks/projections/deploy/lambda.rb +2440 -0
- data/lib/hecks/projections/deploy/shared.rb +624 -0
- data/lib/hecks/projections.rb +3 -0
- data/lib/hecks/projector/exporter.rb +48 -0
- data/lib/hecks/projector/target.rb +18 -5
- data/lib/hecks/projector.rb +31 -17
- data/lib/hecks/router/namespace_installer.rb +10 -0
- data/lib/hecks/runtime/registry/verification.rb +84 -2
- data/lib/hecks/runtime/registry.rb +94 -3
- data/lib/hecks/version.rb +1 -1
- metadata +5 -2
- data/lib/hecks/framework/bluebook/compliance.bluebook +0 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8261eb6f8b8b490fda7cc10deed5d1668e677031b03d67c89ca2c706cb90140a
|
|
4
|
+
data.tar.gz: 005ef58bad7618ab6085b22c9483465d2b58af9a9c61db7a365e70db81f4a5d6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 926f57e7d5a1af30ec918269460bc04a20c25dd1feaaf8da910fe0a0ea241f7b4622feec775dfa8390bba16253b6d4b18130b0ea5a0e544259cae70cbd961cbe
|
|
7
|
+
data.tar.gz: aca0677bd624c1a61d9f6d3dbe6f0e74991d690557d4abed0abf4a3e4a240931434a375b633ba7f424f04c3c36a2454811608ca2894a99d12cbdc2460876ca1b
|
|
@@ -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
|
|
|
@@ -11,6 +11,17 @@ module Hecks
|
|
|
11
11
|
# key => :command | :query the kind of verb that key must name
|
|
12
12
|
module Capabilities
|
|
13
13
|
AUTHORIZATION = "authorization".freeze
|
|
14
|
+
# Who may sign in, and with what role — rust/host's Google-OAuth
|
|
15
|
+
# provision/member_rows resolve this instead of an env var naming
|
|
16
|
+
# the aggregate (HECKS_MEMBERSHIP_AGGREGATE). Same declared-
|
|
17
|
+
# not-named shape AUTHORIZATION already is for Governance.
|
|
18
|
+
MEMBERSHIP = "membership".freeze
|
|
19
|
+
# A stable organizational identity, independent of how it was
|
|
20
|
+
# authenticated — recognised by declaration, not the literal name
|
|
21
|
+
# "Identity". Same declared-not-named shape AUTHORIZATION already
|
|
22
|
+
# is. rust/host does not build this chapter's payloads; any field
|
|
23
|
+
# mapping lives on the consuming hecksagon's `translates` ACL.
|
|
24
|
+
IDENTITY = "identity".freeze
|
|
14
25
|
|
|
15
26
|
CONTRACTS = {
|
|
16
27
|
AUTHORIZATION => {
|
|
@@ -20,6 +31,22 @@ module Hecks
|
|
|
20
31
|
grant: :command,
|
|
21
32
|
# every grant of one role acting as another
|
|
22
33
|
transitions: :query
|
|
34
|
+
}.freeze,
|
|
35
|
+
MEMBERSHIP => {
|
|
36
|
+
# recognize a person who may eventually sign in
|
|
37
|
+
admit: :command,
|
|
38
|
+
# grant an admitted person a role (the access-grant half)
|
|
39
|
+
grant: :command,
|
|
40
|
+
# every admitted person, for the admin listing
|
|
41
|
+
people: :query
|
|
42
|
+
}.freeze,
|
|
43
|
+
IDENTITY => {
|
|
44
|
+
# mint a stable identity, independent of how it authenticated
|
|
45
|
+
register: :command,
|
|
46
|
+
# associate an (issuer, subject) pair with that identity
|
|
47
|
+
link: :command,
|
|
48
|
+
# look up the identity an authenticated pair resolves to
|
|
49
|
+
resolve: :query
|
|
23
50
|
}.freeze
|
|
24
51
|
}.freeze
|
|
25
52
|
end
|
|
@@ -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,115 @@ 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 `accounts`/`newsletter`/`payments`
|
|
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
|
+
"accounts" => "lifeadelics",
|
|
316
|
+
"newsletter" => "lifeadelics",
|
|
317
|
+
"payments" => "lifeadelics",
|
|
318
|
+
"membership" => "lifeadelics"
|
|
319
|
+
}.freeze
|
|
320
|
+
|
|
321
|
+
# Every external vendored chapter's own stem.
|
|
322
|
+
#
|
|
323
|
+
# @return [Array<String>] stems declared in RUST_EXTERNAL_VENDORED_CHAPTERS
|
|
324
|
+
def rust_external_vendored_chapters
|
|
325
|
+
RUST_EXTERNAL_VENDORED_CHAPTERS.keys
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
# Where an external vendored chapter's own bluebook lives — somewhere
|
|
329
|
+
# under `<destination>/**/vendor/embryonaut_bluebooks/<stem>`, the
|
|
330
|
+
# same layout `EmbryonautBluebook.load!` resolves for an in-repo
|
|
331
|
+
# vendored member, one level further out. Globbed rather than joined
|
|
332
|
+
# directly: `RUST_ELSEWHERE`'s own `destination` names the external
|
|
333
|
+
# product's checkout root, not necessarily the exact directory its
|
|
334
|
+
# own bluebook lives under (confirmed live against lifeadelics's own
|
|
335
|
+
# checkout: `~/Projects/lifeadelics/lifeadelics/vendor/
|
|
336
|
+
# embryonaut_bluebooks/accounts/bluebook`, one level below
|
|
337
|
+
# `~/Projects/lifeadelics` itself).
|
|
338
|
+
#
|
|
339
|
+
# @param stem [String] an external vendored chapter's stem
|
|
340
|
+
# @return [String, nil] the vendored member's own directory, or nil when none is found
|
|
341
|
+
def rust_external_vendored_domain_dir(stem)
|
|
342
|
+
feature = RUST_EXTERNAL_VENDORED_CHAPTERS.fetch(stem)
|
|
343
|
+
destination = File.expand_path(RUST_ELSEWHERE.fetch(feature).destination)
|
|
344
|
+
Dir.glob(File.join(destination, "**", "vendor", "embryonaut_bluebooks", stem)).first
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
# Every place a Rust-facing domain's own `uses_framework`/
|
|
348
|
+
# `uses_embryonaut_bluebook` attachment could be declared — every
|
|
349
|
+
# in-repo Rust domain's own hecksagon files, plus each `:external`
|
|
350
|
+
# RUST_ELSEWHERE domain's own (its `destination`, expanded, is a
|
|
351
|
+
# real checkout on this machine, read the same way an in-repo
|
|
352
|
+
# domain's own hecksagon files already are).
|
|
353
|
+
#
|
|
354
|
+
# @param root [String] repository root to search under
|
|
355
|
+
# @return [String] every reachable hecksagon file's own text, joined by newlines
|
|
356
|
+
def rust_attachment_hecksagon_text(root: ROOT)
|
|
357
|
+
in_repo = rust_domains(root: root).flat_map { |domain| Dir.glob(File.join(domain.dir, "**", "*.hecksagon")) }
|
|
358
|
+
external = RUST_ELSEWHERE.values.select { |route| route.check == :external }
|
|
359
|
+
.flat_map { |route| Dir.glob(File.join(File.expand_path(route.destination), "**", "*.hecksagon")) }
|
|
360
|
+
(in_repo + external).map { |path| File.read(path) }.join("\n")
|
|
361
|
+
end
|
|
362
|
+
|
|
277
363
|
# **Shrink-only**. A generated module `bin/rust_coverage` still reports a
|
|
278
364
|
# gap for. `bin/corpus --rust-coverage` requires each of these to
|
|
279
365
|
# still fail, so an entry that starts passing breaks the build until
|
|
280
366
|
# it is deleted here.
|
|
281
|
-
RUST_COVERAGE_PENDING = {
|
|
367
|
+
RUST_COVERAGE_PENDING = {
|
|
368
|
+
"accounts" => "query Listing declares no where clause at all (\"every account, alphabetically by " \
|
|
369
|
+
"email\") — rust/project/queries.rb's own no_wheres skip refuses to generate an " \
|
|
370
|
+
"unfiltered per_instance Listing (\"nothing for filter_entries to bake in\"); a " \
|
|
371
|
+
"structural Rust codegen limitation, not a bug in this vendored package",
|
|
372
|
+
"newsletter" => "same no_wheres gap as accounts, on all 3 of its own Listing queries " \
|
|
373
|
+
"(Delivery/Issue/Subscriber) — see accounts' entry above",
|
|
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"
|
|
376
|
+
}.freeze
|
|
282
377
|
|
|
283
378
|
# The Cargo `[features]` table's raw text.
|
|
284
379
|
#
|
|
@@ -396,7 +491,26 @@ module Hecks
|
|
|
396
491
|
def rust_side_chapters(kind, root: ROOT)
|
|
397
492
|
modules = generated_modules(root: root)
|
|
398
493
|
members(kind, root: root).map(&:stem)
|
|
399
|
-
.select
|
|
494
|
+
.select do |stem|
|
|
495
|
+
module_name = Naming.pascal(stem).downcase
|
|
496
|
+
modules.include?(module_name) && !generated?(module_name, root: root)
|
|
497
|
+
end
|
|
498
|
+
end
|
|
499
|
+
|
|
500
|
+
# The generated module name a `:framework`/`:vendored` stem writes
|
|
501
|
+
# under — `Naming.pascal(stem).downcase` (`bin/project_rust`'s own
|
|
502
|
+
# convention, see `rust_side_chapters`'s header); a chapter whose
|
|
503
|
+
# stem holds an underscore ("console_settings") writes to a
|
|
504
|
+
# different, underscore-free module name ("consolesettings"), so a
|
|
505
|
+
# bucket-membership check against `generated_modules` (a directory
|
|
506
|
+
# name) needs this mapping rather than the raw stem
|
|
507
|
+
# `rust_framework_chapters`/`rust_vendored_chapters` still return
|
|
508
|
+
# (every other caller wants the stem, to build a bluebook file path).
|
|
509
|
+
#
|
|
510
|
+
# @param stem [String] a `:framework`/`:vendored` corpus member's stem
|
|
511
|
+
# @return [String] the generated module's own directory name
|
|
512
|
+
def rust_side_module_name(stem)
|
|
513
|
+
Naming.pascal(stem).downcase
|
|
400
514
|
end
|
|
401
515
|
|
|
402
516
|
def rust_framework_chapters(root: ROOT)
|
|
@@ -133,6 +133,111 @@ Hecks.bluebook "Deploy" do
|
|
|
133
133
|
end
|
|
134
134
|
end
|
|
135
135
|
|
|
136
|
+
# A sibling of `LambdaTarget`, not a shared shape with it — value objects
|
|
137
|
+
# are not shared across aggregates in this DSL (`Tenant`'s own
|
|
138
|
+
# `DomainName`/`Realm`/`Schema` below already make the same point), so
|
|
139
|
+
# this aggregate carries its own copies of `DomainName`/`Region`/
|
|
140
|
+
# `Database`/`Web` rather than reaching into `LambdaTarget`'s.
|
|
141
|
+
aggregate "FargateTarget" do
|
|
142
|
+
description "One domain's validated AWS Fargate deploy target — what deployed_to(\"AwsFargate\") must resolve to before bin/project_deploy generates anything from it."
|
|
143
|
+
|
|
144
|
+
identified_by :domain
|
|
145
|
+
|
|
146
|
+
attribute :domain, DomainName
|
|
147
|
+
attribute :region, Region
|
|
148
|
+
attribute :cpu, Cpu
|
|
149
|
+
attribute :memory, Memory
|
|
150
|
+
attribute :database, Database
|
|
151
|
+
attribute :web, Web
|
|
152
|
+
attribute :port, Port
|
|
153
|
+
|
|
154
|
+
value_object "DomainName" do
|
|
155
|
+
attribute :value, String
|
|
156
|
+
invariant("a domain is named") { !value.to_s.empty? }
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
value_object "Region" do
|
|
160
|
+
attribute :value, String
|
|
161
|
+
invariant("a region is named") { !value.to_s.empty? }
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# A positive integer, nothing more — AWS's real cpu/memory
|
|
165
|
+
# combinations (256 cpu units needs 512-2048 MB of `memory`, say)
|
|
166
|
+
# are a pairing `ECS` itself enforces at deploy time; encoding that
|
|
167
|
+
# whole table here would duplicate AWS's own rules for no benefit
|
|
168
|
+
# this generator needs. This only catches a typo (zero, negative)
|
|
169
|
+
# before a single file is generated, the same floor `Memory`/`Port`
|
|
170
|
+
# below hold for their own ranges.
|
|
171
|
+
value_object "Cpu" do
|
|
172
|
+
attribute :value, Integer
|
|
173
|
+
invariant("cpu is positive") { value.positive? }
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# Fargate's own valid range, not Lambda's 128-10240 MB —
|
|
177
|
+
# `LambdaTarget`'s own `Memory` value object (above) is scoped to
|
|
178
|
+
# that aggregate alone, so this is a distinct value object under the
|
|
179
|
+
# same name rather than a shared one. A plain sanity floor and
|
|
180
|
+
# ceiling, not AWS's own cpu-paired table (see `Cpu`'s own comment).
|
|
181
|
+
value_object "Memory" do
|
|
182
|
+
attribute :value, Integer
|
|
183
|
+
invariant("memory is positive") { value.positive? }
|
|
184
|
+
invariant("memory is at most 122880 MB") { value <= 122880 }
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# Same shape and reasoning as `LambdaTarget`'s own `Database` —
|
|
188
|
+
# "Postgres"/"Aurora" provision this domain's own RDS instance;
|
|
189
|
+
# "Shared" borrows another already-deployed domain's instance,
|
|
190
|
+
# isolated by a native Postgres schema. See that value object's own
|
|
191
|
+
# comment for the full reasoning; it applies unchanged here.
|
|
192
|
+
value_object "Database" do
|
|
193
|
+
attribute :value, String, one_of: ["Postgres", "Aurora", "Shared"]
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# Same shape and reasoning as `LambdaTarget`'s own `Web` — "Rust"
|
|
197
|
+
# means the domain's own task also serves its public web UI
|
|
198
|
+
# in-process, over the port this aggregate's own `Port` attribute
|
|
199
|
+
# declares.
|
|
200
|
+
value_object "Web" do
|
|
201
|
+
attribute :value, String, one_of: ["None", "Rust"]
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# The container port the task listens on and the target group
|
|
205
|
+
# routes to — a real TCP port, not an arbitrary integer.
|
|
206
|
+
value_object "Port" do
|
|
207
|
+
attribute :value, Integer
|
|
208
|
+
invariant("a port is at least 1") { value >= 1 }
|
|
209
|
+
invariant("a port is at most 65535") { value <= 65535 }
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# All seven required, for the identical reason `LambdaTarget.Declare`
|
|
213
|
+
# requires all of its own attributes — see that command's own
|
|
214
|
+
# comment on `normalize_args`/`coerce_declared_arguments`. This
|
|
215
|
+
# validates a fully-resolved target, after `bin/project_deploy`'s own
|
|
216
|
+
# defaulting (`cpu`/`memory`/`port` each fall back to a Ruby-side
|
|
217
|
+
# default before this ever dispatches), not instead of it.
|
|
218
|
+
command "Declare" do
|
|
219
|
+
goal "Validate one domain's AwsFargate deploy target before anything is generated from it"
|
|
220
|
+
|
|
221
|
+
attribute :domain, DomainName
|
|
222
|
+
attribute :region, Region
|
|
223
|
+
attribute :cpu, Cpu
|
|
224
|
+
attribute :memory, Memory
|
|
225
|
+
attribute :database, Database
|
|
226
|
+
attribute :web, Web
|
|
227
|
+
attribute :port, Port
|
|
228
|
+
|
|
229
|
+
sets :domain
|
|
230
|
+
sets :region
|
|
231
|
+
sets :cpu
|
|
232
|
+
sets :memory
|
|
233
|
+
sets :database
|
|
234
|
+
sets :web
|
|
235
|
+
sets :port
|
|
236
|
+
|
|
237
|
+
emits "FargateTargetDeclared"
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
|
|
136
241
|
# `bin/project_tenant`'S OWN VALIDATED SHAPE — same split LambdaTarget
|
|
137
242
|
# draws with `bin/project_deploy`: this aggregate validates, that
|
|
138
243
|
# script GENERATES/ACTS (writes `environments/<slug>.world`, ensures
|
data/lib/hecks/deploy/oidc.json
CHANGED