hecks 3.11.0 → 3.12.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/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.rb +1 -0
- data/lib/hecks/deploy/bluebook/deploy.bluebook +16 -2
- data/lib/hecks/gate/stages.yml +32 -0
- 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/site/site_root/workflow.rb +5 -0
- 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/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/release_only.rb +111 -0
- data/lib/hecks/version.rb +1 -1
- data/lib/hecks/vocabulary.rb +15 -1
- data/prebuilt/9ee54e2f0d4d6d3fe046827779b221e02de665fba911a048ae5dd8b8fbfb7d90.marshal +0 -0
- data/prebuilt/d74a00e996222c1d8b676d5959092effc46b4dc59e0555f3bf9554ac58e3094a.marshal +0 -0
- data/prebuilt/{verdicts-230ac18c25abfd3d92ede032bee2661a4c2cd2773ab0cf0e2ae52546ba2f7af9.json → verdicts-df19f35868d3284a70f55e42dcef87ef4e65e6a678e5135f72e1f6cff4c1357b.json} +1 -1
- data/rust/host/HECKS_RELEASE +1 -1
- 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 +42 -4
- data/prebuilt/05c358d38d661a3774f813d49c66f5b68939af94432d66257a56f49ee2598994.marshal +0 -0
- data/prebuilt/26c40c61f09b09b11651c64668eda3ceb180f83cf2a3802d3033d2fd99ac841b.marshal +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: beba8cd60d4c859286fa1121e3b382cc63e826e010dfd336714befa036dfad94
|
|
4
|
+
data.tar.gz: 16deb6ba2dd98c8d09385e77a9773f92ef12726d097e71601d2e3cfdbb83ae00
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fe730368d2f8942c67a5510c5b8344bdad86f9aa0990515903e147da4fe71fbc6f17880ea2a1b26de4ffa0d24ad20c416455df58ee8a66617e1f832b2637e2b0
|
|
7
|
+
data.tar.gz: a41080c78fc7b1e9d43347fd4b965d6811c246baee7072214c5f7895564eccc94ce699b86bc739de0f3d7d7830fb8057e6bf2e0699496e201ffd057c7e6a39d5
|
|
@@ -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
|
|
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
|
data/lib/hecks/gate/stages.yml
CHANGED
|
@@ -94,6 +94,38 @@ unguarded_push:
|
|
|
94
94
|
- *check_comment_blocks
|
|
95
95
|
- *check_fuzzing
|
|
96
96
|
|
|
97
|
+
# The Rust io specs `ci-rust-io.yml` runs in its `rspec_rust_io` job, for a push that wants their
|
|
98
|
+
# answer first: `hecks gate_run.gate stage=rust_io --wait`. No hook or workflow runs it. It sets CI
|
|
99
|
+
# as the job does, so a skipped example fails here as it fails there. rust/src/generated is not
|
|
100
|
+
# committed, so the check materializes it first, as the job's generated-rust action does. The specs
|
|
101
|
+
# mutate rust/, and a stage's checks run at once, so the job's two legs run one after the other in
|
|
102
|
+
# a single check. spec/gate_rust_io_stage_spec.rb pins the spec lists to the workflow's matrix.
|
|
103
|
+
rust_io:
|
|
104
|
+
env:
|
|
105
|
+
CI: "true"
|
|
106
|
+
HECKS_ENVIRONMENT: memory
|
|
107
|
+
checks:
|
|
108
|
+
- id: rust_io
|
|
109
|
+
title: rust io specs, conformance, coercion, has_many, semantics corpus and feature exclusivity
|
|
110
|
+
run:
|
|
111
|
+
- sh
|
|
112
|
+
- -ec
|
|
113
|
+
- |
|
|
114
|
+
# Building the banking crate makes banking the default feature and rewrites the module
|
|
115
|
+
# list, both tracked. They are put back on the way out, and the check will not start over
|
|
116
|
+
# a person's uncommitted edits to them.
|
|
117
|
+
rewritten="rust/Cargo.toml rust/src/generated/mod.rs"
|
|
118
|
+
if [ -n "$(git status --porcelain -- $rewritten)" ]; then
|
|
119
|
+
echo "uncommitted changes in $rewritten; commit or discard them first" >&2
|
|
120
|
+
exit 1
|
|
121
|
+
fi
|
|
122
|
+
trap 'git checkout -q -- $rewritten' EXIT
|
|
123
|
+
env -u CARGO_TARGET_DIR bundle exec exe/hecks regeneration_run.regenerate_corpus --confirm --wait
|
|
124
|
+
bundle exec exe/hecks build.project_rust domain=examples/banking --wait
|
|
125
|
+
bundle exec rspec --tag io --profile 8 spec/rust_conformance_spec.rb spec/rust_conformance_fuzz_spec.rb spec/rust_numeric_coercion_spec.rb spec/rust_generated/has_many_spec.rb spec/semantics_corpus_spec.rb
|
|
126
|
+
bundle exec rspec --tag io --profile 8 spec/rust_generated/domain_feature_exclusivity_spec.rb
|
|
127
|
+
blocked: a Rust io spec failed, or skipped; the crate it built is under rust/target.
|
|
128
|
+
|
|
97
129
|
# The checks `.github/workflows/ci-checks.yml` runs. It sets no `env`: the job's journal choice
|
|
98
130
|
# (the `hecks-environment` action) reaches every check, as it reached each command before.
|
|
99
131
|
ci:
|
|
@@ -119,9 +119,13 @@ module Hecks
|
|
|
119
119
|
["pg_restore", "--no-owner", "--no-privileges", "-d", database_name, file]
|
|
120
120
|
end
|
|
121
121
|
|
|
122
|
+
# What the URL names, and nothing it leaves out: `postgres:///postgres` parses with an empty
|
|
123
|
+
# host, which would override a `PGHOST` the environment sets and send the restore to the
|
|
124
|
+
# local socket while the rest of the rehearsal reached the server by TCP.
|
|
122
125
|
def restore_env
|
|
123
126
|
uri = URI.parse(server_url)
|
|
124
|
-
{ "PGHOST" => uri.host, "PGPORT" => uri.port&.to_s, "PGUSER" => uri.user, "PGPASSWORD" => uri.password }
|
|
127
|
+
{ "PGHOST" => uri.host, "PGPORT" => uri.port&.to_s, "PGUSER" => uri.user, "PGPASSWORD" => uri.password }
|
|
128
|
+
.reject { |_name, value| value.to_s.empty? }
|
|
125
129
|
end
|
|
126
130
|
end
|
|
127
131
|
end
|
data/lib/hecks/hecks/hecks.world
CHANGED
|
@@ -36,6 +36,8 @@
|
|
|
36
36
|
# checkout runs (Codebase, and releasing hecks itself): the help leaves them out anywhere else and
|
|
37
37
|
# `--maintainer` lists them; `chapters` names the attached chapters the help points at, one line each
|
|
38
38
|
# (`hecks deploy`), and `maintainer_chapters` the language's own, added inside a checkout.
|
|
39
|
+
# `newcomer` names the only commands the help lists in a folder holding no bluebook yet
|
|
40
|
+
# (`hecks --all` lists the rest), so a first run shows how to start and nothing else.
|
|
39
41
|
Hecks.world "Hecks" do
|
|
40
42
|
realm "Hecks"
|
|
41
43
|
default_adapter "PostgresEra"
|
|
@@ -58,6 +60,7 @@ Hecks.world "Hecks" do
|
|
|
58
60
|
maintainer: %w[LanguageRun KernelRun ConformanceRun RegenerationRun GateRun StyleRun InspectionRun
|
|
59
61
|
CodemodRun TestSuiteRun CorpusRun PublishingRun PromotionRun Release
|
|
60
62
|
SyntaxBootCache],
|
|
63
|
+
newcomer: %w[launch.init launch.interview operation.open_console],
|
|
61
64
|
chapters: %w[Deploy Governance Tenancy Site Tickets QualityControl],
|
|
62
65
|
maintainer_chapters: %w[Bluebook Hecksagon World Adapter Port Translation Expression],
|
|
63
66
|
memory_commands: %w[console init interview],
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
Hecks.behaviors "Bluebook" do
|
|
2
|
+
vision "A chapter is opened with a name, a vision and a classification, and refuses a classification the language does not admit."
|
|
3
|
+
|
|
4
|
+
loads "bluebook.bluebook", "bluebook.hecksagon"
|
|
5
|
+
|
|
6
|
+
test "Declare opens a chapter" do
|
|
7
|
+
tests "Declare", on: "Bluebook"
|
|
8
|
+
input name: "Pizzas", vision: "Pizzas, built and sold.", classification: "core"
|
|
9
|
+
expect emits: ["ChapterDeclared"]
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
test "Declare refuses a classification that is not core, supporting or generic" do
|
|
13
|
+
tests "Declare", on: "Bluebook"
|
|
14
|
+
input name: "Pizzas", vision: "Pizzas, built and sold.", classification: "huge"
|
|
15
|
+
expect refused: "a chapter is core, supporting, or generic"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
test "Declare refuses a chapter with no name" do
|
|
19
|
+
tests "Declare", on: "Bluebook"
|
|
20
|
+
input name: "", vision: "Pizzas, built and sold.", classification: "core"
|
|
21
|
+
expect refused: "a chapter is named"
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Hecks.behaviors "Bluebook" do
|
|
2
|
+
vision "A policy hangs off a chapter and names the event it watches; an unnamed policy is refused."
|
|
3
|
+
|
|
4
|
+
loads "bluebook.bluebook", "policy.bluebook", "bluebook.hecksagon"
|
|
5
|
+
|
|
6
|
+
test "Declare puts a policy on a chapter" do
|
|
7
|
+
tests "Declare", on: "Policy"
|
|
8
|
+
setup "Bluebook::Bluebook.Declare", name: "Pizzas", vision: "Pizzas, built and sold.", classification: "core"
|
|
9
|
+
input bluebook: "Pizzas", name: "NotifyKitchen", on_event: "OrderPlaced"
|
|
10
|
+
expect emits: ["ReactionDeclared"]
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
test "Declare refuses a policy with no name" do
|
|
14
|
+
tests "Declare", on: "Policy"
|
|
15
|
+
setup "Bluebook::Bluebook.Declare", name: "Pizzas", vision: "Pizzas, built and sold.", classification: "core"
|
|
16
|
+
input bluebook: "Pizzas", name: "", on_event: "OrderPlaced"
|
|
17
|
+
expect refused: "a policy is named"
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -611,6 +611,10 @@ Hecks.bluebook "Bluebook" do
|
|
|
611
611
|
# `mode` is how `pattern` (an extended regex over changed paths) decides:
|
|
612
612
|
# `touches` runs the job when any changed path matches, `skips_unless` runs it
|
|
613
613
|
# when any changed path does NOT match (the pattern is the safe-to-skip list).
|
|
614
|
+
# `release_only` answers a different question and ignores `pattern`: `touched` is false only
|
|
615
|
+
# when the change edits nothing but the `ReleaseFile` rows' files and lines and the base it is
|
|
616
|
+
# measured against has every inheritable `RequiredCheck` green (ADR 0107), so the run may
|
|
617
|
+
# inherit that certification.
|
|
614
618
|
# `push` is what a push event does: `skip` leaves the gate out of push runs,
|
|
615
619
|
# `before_sha` diffs against the push's `before` commit.
|
|
616
620
|
value_object "CiGate" do
|
|
@@ -624,19 +628,44 @@ Hecks.bluebook "Bluebook" do
|
|
|
624
628
|
member name: "runtime_changed", workflow: "ci.yml", mode: "touches",
|
|
625
629
|
pattern: '^lib/hecks/runtime/', push: "before_sha",
|
|
626
630
|
label: "does this change touch lib/hecks/runtime/**?"
|
|
631
|
+
member name: "release_only", workflow: "ci.yml", mode: "release_only",
|
|
632
|
+
pattern: "", push: "before_sha",
|
|
633
|
+
label: "is this change only a release bump on a certified base?"
|
|
627
634
|
member name: "postgres_io_relevant_changed", workflow: "ci-postgres-io-parallel.yml", mode: "skips_unless",
|
|
628
635
|
pattern: '^(docs/|editors/|release/|deploy/|\.claude/|\.githooks/|rust/(parser|codegen|host|build|lsp|web|tests)/|\.rubocop\.yml$|\.rubocop_todo\.yml$|\.mcp\.json$|\.rspec-local\.example$|README\.md$|CHANGELOG\.md$|CONTRIBUTING\.md$|SECURITY\.md$|LICENSE$|\.gitignore$)',
|
|
629
636
|
push: "skip",
|
|
630
637
|
label: "does this change touch anything rspec_postgres_io_parallel covers?"
|
|
631
638
|
end
|
|
632
639
|
|
|
640
|
+
# The files a release bump edits, and the lines of each it may change. A commit whose whole
|
|
641
|
+
# diff against a certified base falls inside these rows inherits the base's certification
|
|
642
|
+
# (`CiGate` `release_only`, ADR 0107): its checks report success without their jobs running,
|
|
643
|
+
# and the `release_consistency` check still runs on it. `path` is the file, exact; `lines` is
|
|
644
|
+
# an extended regex every added or removed line of that file must match, empty for any line.
|
|
645
|
+
value_object "ReleaseFile" do
|
|
646
|
+
attribute :path, String
|
|
647
|
+
attribute :lines, String
|
|
648
|
+
|
|
649
|
+
member path: "lib/hecks/version.rb", lines: "^\\s*VERSION = \"[0-9]+\\.[0-9]+\\.[0-9]+\"\\.freeze$"
|
|
650
|
+
member path: "CHANGELOG.md", lines: ""
|
|
651
|
+
member path: "README.md", lines: "^(\\*\\*Status:\\*\\* )?Current release: `[0-9]+\\.[0-9]+\\.[0-9]+`"
|
|
652
|
+
member path: "Gemfile.lock", lines: "^ hecks \\([0-9]+\\.[0-9]+\\.[0-9]+\\)$"
|
|
653
|
+
member path: "rust/host/HECKS_RELEASE", lines: "^[0-9]+\\.[0-9]+\\.[0-9]+$"
|
|
654
|
+
member path: "packages/hecks-client/package.json", lines: "^\\s*\"version\": \"[0-9]+\\.[0-9]+\\.[0-9]+\",?$"
|
|
655
|
+
member path: "packages/hecks-client/package-lock.json", lines: "^\\s*\"version\": \"[0-9]+\\.[0-9]+\\.[0-9]+\",?$"
|
|
656
|
+
end
|
|
657
|
+
|
|
633
658
|
# The checks a commit must have passed before a `Promotion` moves it onto a guarded lane. Each
|
|
634
659
|
# is the name of a job in .github/workflows that reports to a commit as a check run; a check
|
|
635
660
|
# that a path gate skipped reports `skipped`, which counts as passed. `hecks promote` reads
|
|
636
661
|
# these rows, and hecks project_lanes writes them into the ruleset of any lane that requires
|
|
637
662
|
# them, so the list lives here and nowhere else.
|
|
663
|
+
#
|
|
664
|
+
# `runs` is `always` for a check a release bump still runs (it is not inherited from the base);
|
|
665
|
+
# empty for a check a bump may inherit.
|
|
638
666
|
value_object "RequiredCheck" do
|
|
639
667
|
attribute :name, String
|
|
668
|
+
attribute :runs, String
|
|
640
669
|
|
|
641
670
|
member name: "rspec"
|
|
642
671
|
member name: "checks"
|
|
@@ -647,6 +676,7 @@ Hecks.bluebook "Bluebook" do
|
|
|
647
676
|
member name: "rspec_rust_codegen"
|
|
648
677
|
member name: "rspec_rust_host"
|
|
649
678
|
member name: "rspec_fuzzing"
|
|
679
|
+
member name: "release_consistency", runs: "always"
|
|
650
680
|
end
|
|
651
681
|
|
|
652
682
|
# The branches a change travels along. `main` takes pushes with no gate; `stable` takes only
|
|
@@ -679,6 +709,28 @@ Hecks.bluebook "Bluebook" do
|
|
|
679
709
|
release_trailer: "Release-Approved-By"
|
|
680
710
|
end
|
|
681
711
|
|
|
712
|
+
# A binary hecks builds once per change to its crate and publishes, so a client that needs the
|
|
713
|
+
# same binary downloads it instead of compiling it (ADR 0110). `hecks regeneration_run.project_lanes`
|
|
714
|
+
# writes .github/workflows/publish-<name>.yml from each row; `Hecks::RustBuild::PrebuiltBinary`
|
|
715
|
+
# reads the same rows to name what to download.
|
|
716
|
+
#
|
|
717
|
+
# `crate` is the directory under the checkout that is built and keyed: the key is a hash of every
|
|
718
|
+
# file in it that goes into the build, so the same bytes in the crate are the same release.
|
|
719
|
+
# `executable` is the Cargo binary and `target` the Rust triple it is built for. `runner` is the
|
|
720
|
+
# GitHub-hosted image it is built on, pinned (never `-latest`) because a binary links the
|
|
721
|
+
# runner's glibc and must run on every host that runs that image or a newer one.
|
|
722
|
+
# The release is tagged `<name>-<key>` and carries `<executable>-<target>` and its `.sha256`.
|
|
723
|
+
value_object "PrebuiltBinary" do
|
|
724
|
+
attribute :name, String
|
|
725
|
+
attribute :crate, String
|
|
726
|
+
attribute :executable, String
|
|
727
|
+
attribute :target, String
|
|
728
|
+
attribute :runner, String
|
|
729
|
+
|
|
730
|
+
member name: "host", crate: "rust/host", executable: "bootstrap", target: "x86_64-unknown-linux-gnu",
|
|
731
|
+
runner: "ubuntu-24.04"
|
|
732
|
+
end
|
|
733
|
+
|
|
682
734
|
# The dials of the hecks_qa loop: what each is called, the type of value it takes and what it
|
|
683
735
|
# does. qa/settings.yml holds the values, which are edited and reviewed as a diff; this table
|
|
684
736
|
# holds the rest, so the loader (`Hecks::Fuzzing::QaSettings`) checks the file against it and
|
|
@@ -47,10 +47,15 @@ module Hecks
|
|
|
47
47
|
declared = algebra.fetch(operator[:symbol]) do
|
|
48
48
|
raise Refused, "#{operator[:symbol]} is admitted but Vocabulary::Comparison declares no algebra for it"
|
|
49
49
|
end
|
|
50
|
-
row.merge(compares_less_than: declared[:compares_less_than],
|
|
51
|
-
compares_equal:
|
|
50
|
+
row.merge(compares_less_than: spelled_true?(declared[:compares_less_than]),
|
|
51
|
+
compares_equal: spelled_true?(declared[:compares_equal]),
|
|
52
|
+
negated: spelled_true?(declared[:negated]))
|
|
52
53
|
end
|
|
53
54
|
|
|
55
|
+
# @param cell [Object] a `Comparison` cell, declared as the text `"true"` or `"false"`
|
|
56
|
+
# @return [Boolean] whether the cell spells true
|
|
57
|
+
def spelled_true?(cell) = cell.to_s == "true"
|
|
58
|
+
|
|
54
59
|
# @param dropped [Hash{String => Array<String>}] operator to the guards evaluating through it
|
|
55
60
|
# @return [String] one line for each operator a projection would strand
|
|
56
61
|
def dropped_message(dropped)
|
|
@@ -13,11 +13,13 @@ module Hecks
|
|
|
13
13
|
# an ECS task definition when the world names one
|
|
14
14
|
def render_compose_sh(plan, region)
|
|
15
15
|
if plan.task_definition
|
|
16
|
-
template("render-compose-taskdef.sh.tmpl",
|
|
17
|
-
|
|
16
|
+
template("render-compose-taskdef.sh.tmpl",
|
|
17
|
+
{ "STACK" => plan.infra_name, "FAMILY" => plan.task_definition, "PROXY_IMAGE" => plan.proxy_image }
|
|
18
|
+
.merge(compose_log_values(plan, region, "$s.name")))
|
|
18
19
|
else
|
|
19
|
-
template("render-compose.sh.tmpl",
|
|
20
|
-
|
|
20
|
+
template("render-compose.sh.tmpl",
|
|
21
|
+
{ "STACK" => plan.infra_name, "REGION" => region, "DB_NAME" => plan.database_name,
|
|
22
|
+
"PROXY_IMAGE" => plan.proxy_image }.merge(compose_log_values(plan, region, ".name")))
|
|
21
23
|
end
|
|
22
24
|
end
|
|
23
25
|
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
module Hecks
|
|
2
|
+
module Projections
|
|
3
|
+
module Deploy
|
|
4
|
+
module Box
|
|
5
|
+
# The CloudFront edge of a box deploy, for a world that declares `cdn`: the `edge.yaml`
|
|
6
|
+
# stack, the `edge.mk` that deploys it, and the two pieces `make stacks` gains so the box
|
|
7
|
+
# admits the CDN's addresses. Mixed into `Box`, which supplies `template`.
|
|
8
|
+
module Edge
|
|
9
|
+
# The AWS-managed prefix list of CloudFront's origin-facing addresses. Its id differs in
|
|
10
|
+
# every region, so it is looked up by this name rather than written down.
|
|
11
|
+
PREFIX_LIST_NAME = "com.amazonaws.global.cloudfront.origin-facing".freeze
|
|
12
|
+
|
|
13
|
+
# @param plan [Settings::Plan] the resolved settings
|
|
14
|
+
# @param region [String] the validated region
|
|
15
|
+
# @return [Hash{String => String}] `edge.yaml` and `edge.mk`, or nothing without a `cdn`
|
|
16
|
+
def edge_files(plan, region)
|
|
17
|
+
return {} unless plan.cdn
|
|
18
|
+
|
|
19
|
+
{ "edge.yaml" => edge_yaml(plan),
|
|
20
|
+
"edge.mk" => template("edge.mk.tmpl", "EDGE_STACK" => plan.edge_stack, "REGION" => region) }
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# @param plan [Settings::Plan] the resolved settings, with a `cdn`
|
|
24
|
+
# @return [String] the CloudFront stack
|
|
25
|
+
def edge_yaml(plan)
|
|
26
|
+
values = { "STACK" => plan.infra_name, "ALIASES" => plan.cdn.aliases.join(","),
|
|
27
|
+
"ORIGIN_HEADER" => plan.origin_header, "ORIGIN_SECRET" => plan.origin_secret }
|
|
28
|
+
template("edge.yaml.tmpl", values)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# @param plan [Settings::Plan] the resolved settings
|
|
32
|
+
# @param region [String] the validated region
|
|
33
|
+
# @return [Hash{String => String}] the Makefile markers `CDN_CHECK` and `CDN_PARAM`:
|
|
34
|
+
# empty without a `cdn`, so a world that does not opt in renders the same Makefile
|
|
35
|
+
def edge_makefile_values(plan, region)
|
|
36
|
+
return { "CDN_CHECK" => "", "CDN_PARAM" => "" } unless plan.cdn
|
|
37
|
+
|
|
38
|
+
{ "CDN_CHECK" => prefix_list_check(region), "CDN_PARAM" => "\t\tCdnPrefixListId=#{prefix_list_lookup(region)} \\" }
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
def prefix_list_query(region)
|
|
44
|
+
"aws ec2 describe-managed-prefix-lists --region #{region} --filters " \
|
|
45
|
+
"Name=prefix-list-name,Values=#{PREFIX_LIST_NAME} --query \"PrefixLists[0].PrefixListId\" --output text"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def prefix_list_lookup(region)
|
|
49
|
+
"$$(#{prefix_list_query(region)})"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Stops `make stacks` before it changes anything when the prefix list cannot be found,
|
|
53
|
+
# because a box deployed without it has no inbound rule and the CDN gets a 504.
|
|
54
|
+
def prefix_list_check(region)
|
|
55
|
+
"\t@#{prefix_list_query(region)} | grep -q '^pl-' || { echo \"no #{PREFIX_LIST_NAME} " \
|
|
56
|
+
"prefix list in #{region}: the box would admit no CDN and answer 504\" >&2; exit 1; }"
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|