hecks 3.11.0 → 3.13.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/script_runner.adapter +3 -0
- data/lib/hecks/adapters/driven/script_runner.rb +93 -0
- data/lib/hecks/adapters/driven.rb +1 -0
- data/lib/hecks/adapters/driving/cli_runner/resolution.rb +6 -3
- data/lib/hecks/adapters/driving/cli_runner.rb +1 -0
- data/lib/hecks/adapters/driving/newcomer.rb +50 -0
- data/lib/hecks/bluebook/assembly/contracts/read_models.rb +1 -1
- data/lib/hecks/bluebook/assembly/marks.rb +3 -15
- data/lib/hecks/bluebook/assembly/scalars.rb +39 -0
- data/lib/hecks/bluebook/meta_validator/readings.rb +6 -0
- data/lib/hecks/bluebook/meta_validator/reconstruction.rb +6 -3
- data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +16 -12
- data/lib/hecks/bluebook/meta_validator.rb +28 -21
- data/lib/hecks/bluebook.rb +1 -0
- data/lib/hecks/deploy/bluebook/deploy.bluebook +17 -3
- data/lib/hecks/framework/bluebook/change.behaviors +30 -0
- data/lib/hecks/framework/bluebook/change.bluebook +162 -0
- data/lib/hecks/framework/bluebook/change.hecksagon +21 -0
- data/lib/hecks/framework/change.oidc.json +34 -0
- data/lib/hecks/framework/console_settings.oidc.json +103 -0
- data/lib/hecks/framework/governance.oidc.json +28 -0
- data/lib/hecks/framework/identity.oidc.json +18 -0
- data/lib/hecks/framework/privacy.oidc.json +30 -0
- data/lib/hecks/gate/stages.yml +35 -0
- data/lib/hecks/grammar/translation.oidc.json +48 -0
- data/lib/hecks/hecks/adapters/codebase/test_runner.rb +12 -1
- data/lib/hecks/hecks/adapters/deploy_toolchain.rb +2 -1
- data/lib/hecks/hecks/adapters/rehearsal/postgres_store.rb +5 -1
- data/lib/hecks/hecks/hecks.world +3 -0
- data/lib/hecks/language/bluebook/bluebook.behaviors +23 -0
- data/lib/hecks/language/bluebook/policy.behaviors +19 -0
- data/lib/hecks/language/bluebook/vocabulary.bluebook +52 -0
- data/lib/hecks/projection_files/expression_tables.rb +7 -2
- data/lib/hecks/projections/deploy/box/compose.rb +6 -4
- data/lib/hecks/projections/deploy/box/edge.rb +62 -0
- data/lib/hecks/projections/deploy/box/logging.rb +99 -0
- data/lib/hecks/projections/deploy/box/roll_scripts.rb +16 -8
- data/lib/hecks/projections/deploy/box/settings/cdn.rb +40 -0
- data/lib/hecks/projections/deploy/box/settings/logs.rb +40 -0
- data/lib/hecks/projections/deploy/box/settings/patterns.rb +11 -0
- data/lib/hecks/projections/deploy/box/settings/types.rb +16 -1
- data/lib/hecks/projections/deploy/box/settings.rb +6 -2
- data/lib/hecks/projections/deploy/box/stack_yaml.rb +5 -2
- data/lib/hecks/projections/deploy/box/templates/Makefile.tmpl +16 -0
- data/lib/hecks/projections/deploy/box/templates/box.yaml.tmpl +8 -4
- data/lib/hecks/projections/deploy/box/templates/deploy-box.sh.tmpl +3 -0
- data/lib/hecks/projections/deploy/box/templates/edge.mk.tmpl +17 -0
- data/lib/hecks/projections/deploy/box/templates/edge.yaml.tmpl +65 -0
- data/lib/hecks/projections/deploy/box/templates/log-group.yaml.tmpl +12 -0
- data/lib/hecks/projections/deploy/box/templates/log-policy.yaml.tmpl +7 -0
- data/lib/hecks/projections/deploy/box/templates/render-compose-taskdef.sh.tmpl +5 -4
- data/lib/hecks/projections/deploy/box/templates/render-compose.sh.tmpl +5 -4
- data/lib/hecks/projections/deploy/box.rb +15 -3
- data/lib/hecks/projections/deploy/fargate/cdn/lines.rb +12 -1
- data/lib/hecks/projections/site/admin_cms/templates/membership.rb +10 -7
- data/lib/hecks/projections/site/edge/behaviours.rb +10 -6
- data/lib/hecks/projections/site/edge/checks.rb +2 -0
- data/lib/hecks/projections/site/edge/function_checks.rb +104 -0
- data/lib/hecks/projections/site/edge/function_code.rb +86 -0
- data/lib/hecks/projections/site/edge/redirect_host.js.tmpl +65 -0
- data/lib/hecks/projections/site/edge.rb +35 -9
- data/lib/hecks/projections/site/site_cdn.rb +21 -7
- data/lib/hecks/projections/site/site_root/workflow.rb +5 -0
- data/lib/hecks/projections/site/table/row_builder.rb +12 -2
- data/lib/hecks/projections/site/table.rb +4 -2
- data/lib/hecks/projector/cli_audience.rb +20 -0
- data/lib/hecks/projector/cli_projector/usage.rb +15 -4
- data/lib/hecks/rust_build/host.rb +11 -4
- data/lib/hecks/rust_build/prebuilt_binary.rb +101 -0
- data/lib/hecks/site/bluebook/site.bluebook +16 -1
- data/lib/hecks/tools/ci_gate_decision.rb +35 -0
- data/lib/hecks/tools/ci_gates.rb +20 -1
- data/lib/hecks/tools/lanes/files.rb +8 -0
- data/lib/hecks/tools/lanes/prebuilt_workflow.rb +81 -0
- data/lib/hecks/tools/lanes.rb +1 -0
- data/lib/hecks/tools/oidc_manifests.rb +44 -6
- data/lib/hecks/tools/release_only.rb +111 -0
- data/lib/hecks/tools/site_routes/templates.rb +13 -9
- data/lib/hecks/version.rb +1 -1
- data/lib/hecks/vocabulary.rb +15 -1
- data/prebuilt/2b59737fbdbd5f44edda0fddce2611f45d76087b3a7c782d1f1a17ea58c77948.marshal +0 -0
- data/prebuilt/58208399bada10b0db99928c48e38789288fc58f7b71a72b21bfa386680e32ed.marshal +0 -0
- data/prebuilt/{verdicts-230ac18c25abfd3d92ede032bee2661a4c2cd2773ab0cf0e2ae52546ba2f7af9.json → verdicts-48318ca172edd21443b1ad02801c697fff100b746e828f09db987647641a5df2.json} +1 -1
- data/rust/host/HECKS_RELEASE +1 -1
- data/rust/host/src/auth.rs +58 -0
- data/rust/host/src/extension.rs +13 -1
- data/rust/host/src/lib.rs +1 -0
- data/rust/host/src/platform_signin/attack_tests.rs +1100 -0
- data/rust/host/src/platform_signin/config.rs +512 -0
- data/rust/host/src/platform_signin/cookie.rs +237 -0
- data/rust/host/src/platform_signin/extension.rs +190 -0
- data/rust/host/src/platform_signin/fetcher.rs +322 -0
- data/rust/host/src/platform_signin/handler.rs +251 -0
- data/rust/host/src/platform_signin/handler_tests.rs +554 -0
- data/rust/host/src/platform_signin/jti.rs +196 -0
- data/rust/host/src/platform_signin/jwks.rs +542 -0
- data/rust/host/src/platform_signin/jws.rs +249 -0
- data/rust/host/src/platform_signin/known_answer_tests.rs +237 -0
- data/rust/host/src/platform_signin/mapping_tests.rs +254 -0
- data/rust/host/src/platform_signin/mod.rs +75 -0
- data/rust/host/src/platform_signin/mode_tests.rs +382 -0
- data/rust/host/src/platform_signin/pages.rs +204 -0
- data/rust/host/src/platform_signin/query.rs +233 -0
- data/rust/host/src/platform_signin/session.rs +151 -0
- data/rust/host/src/platform_signin/settings.rs +385 -0
- data/rust/host/src/platform_signin/strict_json.rs +351 -0
- data/rust/host/src/platform_signin/test_support.rs +235 -0
- data/rust/host/src/platform_signin/ttl_tests.rs +94 -0
- data/rust/host/src/platform_signin/verify.rs +604 -0
- data/rust/host/src/platform_signin/wiring_support.rs +319 -0
- data/rust/host/src/web.rs +50 -6
- data/rust/parser/src/parse/value_object.rs +22 -10
- metadata +59 -7
- data/prebuilt/05c358d38d661a3774f813d49c66f5b68939af94432d66257a56f49ee2598994.marshal +0 -0
- data/prebuilt/26c40c61f09b09b11651c64668eda3ceb180f83cf2a3802d3033d2fd99ac841b.marshal +0 -0
- /data/lib/hecks/framework/{oidc.json → compliance.oidc.json} +0 -0
- /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:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e1a1eacc3bae3f940928e23269c95e0306f291ec5a2b6d62268e254f12f1902e
|
|
4
|
+
data.tar.gz: e4168d9e617dbad8912a65dbe10be8a3a32a6a08b18fd84542a05fa9803aeaef
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a49c8992b247138d92232d00123dd73986be81040bb1356fc912f97905644bbb35a38af431a1d81475b9ce07c644ab8c2aab3370c3fabaf685c0f8a93a16bd70
|
|
7
|
+
data.tar.gz: 48ccea615a818989da2a01babb5c9d344a5a7c94fbe2eea47301a1437f29aa671134011a626f317c128bcaf1aaa912a21f056e30c8b133643123df129aa7a470
|
|
@@ -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
|
|
@@ -82,8 +82,9 @@ module Hecks
|
|
|
82
82
|
# with the first sentence of what it is for.
|
|
83
83
|
#
|
|
84
84
|
# @return [Hash{Symbol => Object}] `:hide` and `:chapters` (pairs of word and summary)
|
|
85
|
-
def audience(runtime, launcher, maintainer: LauncherOptions.maintainer
|
|
86
|
-
|
|
85
|
+
def audience(runtime, launcher, maintainer: LauncherOptions.maintainer?,
|
|
86
|
+
newcomer: !maintainer && Newcomer.applies?)
|
|
87
|
+
shown = (newcomer && Newcomer.audience(launcher)) || LauncherOptions.audience(launcher, maintainer)
|
|
87
88
|
return shown unless shown[:chapters]
|
|
88
89
|
|
|
89
90
|
shown.merge(chapters: chapter_summaries(runtime, shown[:chapters]))
|
|
@@ -102,7 +103,9 @@ module Hecks
|
|
|
102
103
|
|
|
103
104
|
# Names who the help is for, so the help remembered for one is not given to the other.
|
|
104
105
|
def audience_key
|
|
105
|
-
|
|
106
|
+
return "maintainer" if LauncherOptions.maintainer?
|
|
107
|
+
|
|
108
|
+
Newcomer.applies? ? "newcomer" : "project"
|
|
106
109
|
end
|
|
107
110
|
|
|
108
111
|
# Reads the entry words of a line: whether it asks a query, the bare name, and the words
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
require_relative "launcher_options"
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
module Adapters
|
|
5
|
+
module Driving
|
|
6
|
+
# The launcher's help for someone starting out: a folder with no bluebook in it has no use yet
|
|
7
|
+
# for the commands that act on a domain, so the help lists only the `newcomer` commands the
|
|
8
|
+
# chapter's `launcher` setting names (`hecks --all` lists the rest).
|
|
9
|
+
module Newcomer
|
|
10
|
+
# How deep under a directory a bluebook may sit and still make it a project:
|
|
11
|
+
# `Hecks.bluebook` beside it, or `bluebook/pizzas.bluebook` one folder down, as the
|
|
12
|
+
# examples keep theirs.
|
|
13
|
+
PROJECT_GLOBS = ["*.bluebook", "*/*.bluebook", "*/*/*.bluebook"].freeze
|
|
14
|
+
|
|
15
|
+
module_function
|
|
16
|
+
|
|
17
|
+
# Whether the person is in a folder with no bluebook in it, outside a hecks checkout.
|
|
18
|
+
#
|
|
19
|
+
# @param dir [String] where the command line was typed
|
|
20
|
+
# @param env [#[]] the environment
|
|
21
|
+
# @return [Boolean] true when no `.bluebook` stands in `dir` or up to two folders below it
|
|
22
|
+
def applies?(dir = Dir.pwd, env = ENV)
|
|
23
|
+
return false if LauncherOptions.maintainer?(dir, env)
|
|
24
|
+
|
|
25
|
+
PROJECT_GLOBS.none? { |pattern| bluebook_under?(dir, pattern) }
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# The help options a newcomer gets: only the setting's `newcomer` commands, and no chapters.
|
|
29
|
+
#
|
|
30
|
+
# @param settings [Hash, nil] the chapter's `launcher` setting
|
|
31
|
+
# @return [Hash{Symbol => Object}, nil] `:only` and `:chapters`, or nil when the chapter
|
|
32
|
+
# names no newcomer commands
|
|
33
|
+
def audience(settings)
|
|
34
|
+
return unless settings && settings[:newcomer]
|
|
35
|
+
|
|
36
|
+
{ only: Array(settings[:newcomer]).map(&:to_s), chapters: [] }
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Whether `pattern` finds a bluebook under `dir`; a folder the person may not read holds
|
|
40
|
+
# none.
|
|
41
|
+
# @api private
|
|
42
|
+
def bluebook_under?(dir, pattern)
|
|
43
|
+
Dir.glob(File.join(File.expand_path(dir), pattern)).any?
|
|
44
|
+
rescue SystemCallError
|
|
45
|
+
false
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -35,10 +35,10 @@ module Hecks
|
|
|
35
35
|
# One identity part, as the String path `identity_paths` splits.
|
|
36
36
|
def identity_path(part) = part[:value].to_s
|
|
37
37
|
|
|
38
|
-
# A member's fields, an open map, so the pairs arrive as a list.
|
|
39
|
-
#
|
|
38
|
+
# A member's fields, an open map, so the pairs arrive as a list. Each value is already
|
|
39
|
+
# typed: `Reconstruction` reads a stored cell with `Scalars.unmark` before it gets here.
|
|
40
40
|
def member(pairs)
|
|
41
|
-
pairs.to_h { |key, value| [key.to_sym,
|
|
41
|
+
pairs.to_h { |key, value| [key.to_sym, value] }
|
|
42
42
|
end
|
|
43
43
|
|
|
44
44
|
# A read model's gathered head; `as` is a Symbol because `ReadModel#to_h` spells it `to_s`.
|
|
@@ -49,18 +49,6 @@ module Hecks
|
|
|
49
49
|
# Matches the builder's native `{field: :symbol}` shape.
|
|
50
50
|
def group_by_field(row) = { field: row[:field].to_sym }
|
|
51
51
|
|
|
52
|
-
# Reads a member's scalar back from text. Unlike `read`, a bare word stays a
|
|
53
|
-
# String, because a closed set admits words far more often than symbols.
|
|
54
|
-
def unmark_scalar(value)
|
|
55
|
-
text = value.to_s
|
|
56
|
-
return true if text == "true"
|
|
57
|
-
return false if text == "false"
|
|
58
|
-
return text.to_i if text.match?(/\A-?\d+\z/)
|
|
59
|
-
return text.to_f if text.match?(/\A-?\d+\.\d+\z/)
|
|
60
|
-
|
|
61
|
-
text
|
|
62
|
-
end
|
|
63
|
-
|
|
64
52
|
# A saga's argument bindings; Literal marks a Symbol with a leading colon.
|
|
65
53
|
def bindings(with) = Array(with).to_h { |key, value| [key.to_sym, read(value)] }
|
|
66
54
|
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
module Hecks
|
|
2
|
+
module Bluebook
|
|
3
|
+
class Assembly
|
|
4
|
+
# How a `member` cell is spelled as stored text, and read back. The meta-domain holds a cell
|
|
5
|
+
# as text, so a quoted `"true"` and a bare `true` need different spellings: a boolean or
|
|
6
|
+
# number is its own spelling, and a String that would read back as one, or as quoted text,
|
|
7
|
+
# is quoted so it stays a String. The Rust parser reads a cell the same way (`cell_value`
|
|
8
|
+
# in `parse/value_object.rs`).
|
|
9
|
+
module Scalars
|
|
10
|
+
module_function
|
|
11
|
+
|
|
12
|
+
# @param value [Object] a cell as the builder holds it
|
|
13
|
+
# @return [Object] `value` itself unless it is a String that reads back as another type, in
|
|
14
|
+
# which case its quoted spelling
|
|
15
|
+
def mark(value)
|
|
16
|
+
return value unless value.is_a?(String)
|
|
17
|
+
|
|
18
|
+
unmark(value) == value ? value : Literal.quote(value)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Unlike `Literal.read`, a bare word stays a String, because a closed set admits words far
|
|
22
|
+
# more often than symbols; quoted text is a String whatever it spells.
|
|
23
|
+
#
|
|
24
|
+
# @param value [#to_s] a cell's stored text
|
|
25
|
+
# @return [Object] the String, boolean or number the text spells
|
|
26
|
+
def unmark(value)
|
|
27
|
+
text = value.to_s
|
|
28
|
+
return Literal.unquote(text) if Literal.quoted?(text)
|
|
29
|
+
return true if text == "true"
|
|
30
|
+
return false if text == "false"
|
|
31
|
+
return text.to_i if text.match?(/\A-?\d+\z/)
|
|
32
|
+
return text.to_f if text.match?(/\A-?\d+\.\d+\z/)
|
|
33
|
+
|
|
34
|
+
text
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -70,6 +70,12 @@ module Hecks
|
|
|
70
70
|
Array(map&.to_h).map { |key, value| { key: key, value: value } }
|
|
71
71
|
end
|
|
72
72
|
|
|
73
|
+
# A member's cells, each spelled so a quoted `"true"` stays a String and a bare `true` a
|
|
74
|
+
# boolean.
|
|
75
|
+
def member_pair_rows(map)
|
|
76
|
+
Array(map&.to_h).map { |key, value| { key: key, value: Assembly::Scalars.mark(value) } }
|
|
77
|
+
end
|
|
78
|
+
|
|
73
79
|
# Flattens options via `extra_options_to_h` — a new option needs no
|
|
74
80
|
# change here. `filters: true` reads wheres/order_by/limit live off
|
|
75
81
|
# the node, not `to_h`, so wire-format changes there don't affect it.
|
|
@@ -41,11 +41,14 @@ module Hecks
|
|
|
41
41
|
# dispatched state, one member at a time, not behind a `DeclaredIn` query.
|
|
42
42
|
def members_row(row) = members_of(row)
|
|
43
43
|
|
|
44
|
-
# The key is stringified, never the value: a pair's value
|
|
45
|
-
#
|
|
44
|
+
# The key is stringified, never the value: a pair's value is read back from its text by
|
|
45
|
+
# `Assembly::Scalars`, so a quoted cell returns a String and `true`, `false` and numbers
|
|
46
|
+
# return as the builder holds them.
|
|
46
47
|
def members_of(value_object_row)
|
|
47
48
|
Array(value_object_row[:members]).map do |member|
|
|
48
|
-
Array(member[:pairs]).map
|
|
49
|
+
Array(member[:pairs]).map do |pair|
|
|
50
|
+
[text(pair[:key]).to_s, Assembly::Scalars.unmark(text(pair[:value]))]
|
|
51
|
+
end
|
|
49
52
|
end
|
|
50
53
|
end
|
|
51
54
|
|
|
@@ -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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
|
172
|
-
# inside `while_shadow_parsing` that is first to ask would
|
|
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
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
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
|
data/lib/hecks/bluebook.rb
CHANGED
|
@@ -35,6 +35,7 @@ require_relative "bluebook/assembly/contract"
|
|
|
35
35
|
require_relative "bluebook/assembly/contracts"
|
|
36
36
|
require_relative "bluebook/assembly/specializer"
|
|
37
37
|
require_relative "bluebook/assembly/build"
|
|
38
|
+
require_relative "bluebook/assembly/scalars"
|
|
38
39
|
require_relative "bluebook/assembly/marks"
|
|
39
40
|
require_relative "bluebook/assembly/aggregate_assembly"
|
|
40
41
|
require_relative "bluebook/assembly"
|
|
@@ -212,6 +212,7 @@ Hecks.bluebook "Deploy" do
|
|
|
212
212
|
attribute :volume_gb, VolumeGb
|
|
213
213
|
attribute :database_class, DatabaseClass
|
|
214
214
|
attribute :storage_gb, StorageGb
|
|
215
|
+
attribute :cdn_aliases, list_of(CdnAlias), optional: true
|
|
215
216
|
|
|
216
217
|
value_object "DomainName" do
|
|
217
218
|
attribute :value, String
|
|
@@ -251,8 +252,19 @@ Hecks.bluebook "Deploy" do
|
|
|
251
252
|
invariant("storage is at most 65536 GB") { value <= 65536 }
|
|
252
253
|
end
|
|
253
254
|
|
|
254
|
-
#
|
|
255
|
-
#
|
|
255
|
+
# A host name the CloudFront edge answers on, such as app.example.com. Lowercase labels only:
|
|
256
|
+
# CloudFront refuses a name its certificate does not cover at deploy, which this cannot see.
|
|
257
|
+
value_object "CdnAlias" do
|
|
258
|
+
attribute :value, String
|
|
259
|
+
invariant("a CDN alias is a lowercase host name such as app.example.com") do
|
|
260
|
+
value.to_s.match?(/\A([a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z][a-z0-9-]{0,61}[a-z0-9]\z/)
|
|
261
|
+
end
|
|
262
|
+
invariant("a CDN alias is at most 253 characters") { value.to_s.length <= 253 }
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
# The first six are required for the same reason as `FargateTarget.Declare`; this validates a
|
|
266
|
+
# target already resolved by `hecks deploy project`'s defaults. `cdn_aliases` is the one
|
|
267
|
+
# optional setting: naming at least one asks for the CloudFront edge stack.
|
|
256
268
|
command "Declare" do
|
|
257
269
|
goal "Validate one domain's AwsBox deploy target before anything is generated from it"
|
|
258
270
|
|
|
@@ -262,6 +274,7 @@ Hecks.bluebook "Deploy" do
|
|
|
262
274
|
attribute :volume_gb, VolumeGb
|
|
263
275
|
attribute :database_class, DatabaseClass
|
|
264
276
|
attribute :storage_gb, StorageGb
|
|
277
|
+
attribute :cdn_aliases, list_of(CdnAlias), optional: true
|
|
265
278
|
|
|
266
279
|
sets :domain
|
|
267
280
|
sets :region
|
|
@@ -269,6 +282,7 @@ Hecks.bluebook "Deploy" do
|
|
|
269
282
|
sets :volume_gb
|
|
270
283
|
sets :database_class
|
|
271
284
|
sets :storage_gb
|
|
285
|
+
sets :cdn_aliases
|
|
272
286
|
|
|
273
287
|
emits "BoxTargetDeclared"
|
|
274
288
|
end
|
|
@@ -2792,7 +2806,7 @@ Hecks.bluebook "Deploy" do
|
|
|
2792
2806
|
end
|
|
2793
2807
|
|
|
2794
2808
|
command "ProjectOidc" do
|
|
2795
|
-
goal "Write each domain's OIDC client and scope manifest beside it
|
|
2809
|
+
goal "Write each domain's OIDC client and scope manifest beside it: oidc.json, or one <chapter>.oidc.json for each chapter of a directory that holds several"
|
|
2796
2810
|
|
|
2797
2811
|
attribute :domains, DomainList, optional: true
|
|
2798
2812
|
attribute :run, RunKey
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
Hecks.behaviors "Change" do
|
|
2
|
+
vision "A change names its script, faults when that script cannot plan it, and is confirmed only from a plan."
|
|
3
|
+
|
|
4
|
+
loads "change.bluebook", "governance.bluebook", "change.hecksagon", "../../../../spec/fixtures/change/governance.hecksagon"
|
|
5
|
+
|
|
6
|
+
test "Request refuses a change that names no script" do
|
|
7
|
+
tests "Request", on: "Change"
|
|
8
|
+
input run: { value: "c1" }, script: { value: "" }
|
|
9
|
+
expect refused: "must match"
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
test "Request refuses a change that is not named" do
|
|
13
|
+
tests "Request", on: "Change"
|
|
14
|
+
input run: { value: "" }, script: { value: "rename" }
|
|
15
|
+
expect refused: "must match"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
test "Request faults a change whose script nobody registered" do
|
|
19
|
+
tests "Request", on: "Change"
|
|
20
|
+
input run: { value: "c2" }, script: { value: "no_such_script" }
|
|
21
|
+
expect status: "faulted"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
test "Confirm refuses a change that was never planned" do
|
|
25
|
+
tests "Confirm", on: "Change"
|
|
26
|
+
setup "Change::Change.Request", run: { value: "c3" }, script: { value: "no_such_script" }
|
|
27
|
+
input run: { value: "c3" }, digest: { value: "ab12" }
|
|
28
|
+
expect refused: "the digest confirmed is the digest planned"
|
|
29
|
+
end
|
|
30
|
+
end
|