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
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
module Tools
|
|
5
|
+
module Lanes
|
|
6
|
+
# The workflow that builds and publishes one `PrebuiltBinary` row (ADR 0110): after a push to
|
|
7
|
+
# `main` that touches the row's crate, it builds the binary on the row's runner and, unless
|
|
8
|
+
# a release for that key already exists, publishes it with its sha256. What the key is and
|
|
9
|
+
# where a client looks is `Hecks::RustBuild::PrebuiltBinary`, not anything written here.
|
|
10
|
+
module PrebuiltWorkflow
|
|
11
|
+
module_function
|
|
12
|
+
|
|
13
|
+
# @param row [Hash{String => String}] a `PrebuiltBinary` row
|
|
14
|
+
# @return [String] the path of the workflow, relative to the checkout
|
|
15
|
+
def path(row) = ".github/workflows/publish-#{row["name"]}.yml"
|
|
16
|
+
|
|
17
|
+
# @param row [Hash{String => String}] a `PrebuiltBinary` row
|
|
18
|
+
# @return [String] the workflow
|
|
19
|
+
def text(row)
|
|
20
|
+
[*header(row), *trigger(row), *job(row)].join("\n") << "\n"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def header(row)
|
|
24
|
+
name = row["name"]
|
|
25
|
+
["name: Publish #{name}", "",
|
|
26
|
+
"# Generated by hecks project_lanes from the PrebuiltBinary rows of the Vocabulary chapter. Do not hand-edit.",
|
|
27
|
+
"#",
|
|
28
|
+
"# Builds #{row["executable"]} from #{row["crate"]} for #{row["target"]} and publishes it as the release",
|
|
29
|
+
"# #{name}-<key>, where the key is a hash of the crate's build inputs. A client that needs the same",
|
|
30
|
+
"# bytes downloads it instead of compiling (Hecks::RustBuild::PrebuiltBinary); a key that already has a",
|
|
31
|
+
"# release is left alone, so a push that changed nothing the build reads publishes nothing."]
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def trigger(row)
|
|
35
|
+
["on:", " push:", " branches: [main]", " paths:", " - \"#{row["crate"]}/**\"",
|
|
36
|
+
" - \".github/workflows/publish-#{row["name"]}.yml\"",
|
|
37
|
+
" - \"lib/hecks/rust_build/prebuilt_binary.rb\"",
|
|
38
|
+
" workflow_dispatch: {}", "",
|
|
39
|
+
"concurrency:", " group: publish-#{row["name"]}", " cancel-in-progress: false", "",
|
|
40
|
+
"permissions:", " contents: write", ""]
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def job(row)
|
|
44
|
+
["jobs:", " publish:", " runs-on: #{row["runner"]}", " timeout-minutes: 40", " steps:",
|
|
45
|
+
" - uses: actions/checkout@v4", " - uses: ./.github/actions/setup-ruby", *key_step(row),
|
|
46
|
+
*build_steps(row), *release_step(row)]
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def key_step(row)
|
|
50
|
+
[" - name: Name the release", " id: key", " run: |",
|
|
51
|
+
" key=$(bundle exec ruby -Ilib -rhecks/rust_build/prebuilt_binary -e \\",
|
|
52
|
+
" 'puts Hecks::RustBuild::PrebuiltBinary.key(ARGV.fetch(0))' #{row["crate"]})",
|
|
53
|
+
" tag=#{row["name"]}-$key",
|
|
54
|
+
" echo \"tag=$tag\" >> \"$GITHUB_OUTPUT\"",
|
|
55
|
+
" if gh release view \"$tag\" > /dev/null 2>&1; then echo \"exists=true\" >> \"$GITHUB_OUTPUT\"; fi",
|
|
56
|
+
" env:", " GH_TOKEN: ${{ github.token }}"]
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def build_steps(row)
|
|
60
|
+
[" - name: Build #{row["executable"]}", " if: steps.key.outputs.exists != 'true'",
|
|
61
|
+
" working-directory: #{row["crate"]}", " run: |",
|
|
62
|
+
" rustup toolchain install stable --profile minimal",
|
|
63
|
+
" rustup run stable cargo build --release --target #{row["target"]} --bin #{row["executable"]}"]
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def release_step(row)
|
|
67
|
+
asset = "#{row["executable"]}-#{row["target"]}"
|
|
68
|
+
[" - name: Publish the release", " if: steps.key.outputs.exists != 'true'",
|
|
69
|
+
" env:", " GH_TOKEN: ${{ github.token }}", " TAG: ${{ steps.key.outputs.tag }}",
|
|
70
|
+
" run: |",
|
|
71
|
+
" mkdir out",
|
|
72
|
+
" cp #{row["crate"]}/target/#{row["target"]}/release/#{row["executable"]} out/#{asset}",
|
|
73
|
+
" (cd out && sha256sum #{asset} > #{asset}.sha256)",
|
|
74
|
+
" gh release create \"$TAG\" out/#{asset} out/#{asset}.sha256 --prerelease --target \"$GITHUB_SHA\" \\",
|
|
75
|
+
" --title \"$TAG\" \\",
|
|
76
|
+
" --notes \"#{row["executable"]} for #{row["target"]}, built from #{row["crate"]} at $GITHUB_SHA.\""]
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
data/lib/hecks/tools/lanes.rb
CHANGED
|
@@ -6,7 +6,8 @@ require_relative "../tools"
|
|
|
6
6
|
module Hecks
|
|
7
7
|
module Tools
|
|
8
8
|
# Projects each domain's OIDC client and scope manifest (`Hecks::Projections::OIDC`) into an
|
|
9
|
-
# `oidc.json` beside the domain.
|
|
9
|
+
# `oidc.json` beside the domain, or into one `<chapter>.oidc.json` for each chapter of a
|
|
10
|
+
# directory that holds several.
|
|
10
11
|
#
|
|
11
12
|
# A domain that has no bluebook is skipped and one that cannot be projected is reported, so a
|
|
12
13
|
# sweep over the whole checkout carries on past either. `hecks deploy
|
|
@@ -45,20 +46,57 @@ module Hecks
|
|
|
45
46
|
.uniq.sort
|
|
46
47
|
end
|
|
47
48
|
|
|
49
|
+
# Projects one manifest for each chapter the directory holds.
|
|
50
|
+
#
|
|
48
51
|
# @param root [String] the checkout
|
|
49
52
|
# @param path [String] a domain directory, relative to `root`
|
|
50
53
|
# @return [void]
|
|
51
54
|
def project(root, path)
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
return warn(" #{path}: no bluebook found — skipped") unless name
|
|
55
|
+
registry = Hecks.boot(File.join(root, path), install_driving: false).registry
|
|
56
|
+
return warn(" #{path}: no bluebook found — skipped") if registry.bluebooks.empty?
|
|
55
57
|
|
|
56
|
-
|
|
57
|
-
puts " #{path}/oidc.json <- #{name}"
|
|
58
|
+
project_chapters(registry, root, path)
|
|
58
59
|
rescue StandardError => e
|
|
59
60
|
warn " #{path}: cannot project — #{e.message.lines.first.strip}"
|
|
60
61
|
end
|
|
61
62
|
|
|
63
|
+
# The chapters a directory declares: those with a `.bluebook` file under it. A chapter it
|
|
64
|
+
# only attaches (Governance, for one) is loaded from elsewhere and has its own directory's
|
|
65
|
+
# manifest.
|
|
66
|
+
#
|
|
67
|
+
# @param registry [Hecks::Runtime::Registry] the booted registry of the directory
|
|
68
|
+
# @param directory [String] the directory that was booted
|
|
69
|
+
# @return [Array<String>] the chapter names, in load order; the first loaded when none has a
|
|
70
|
+
# recorded source
|
|
71
|
+
def chapters_of(registry, directory)
|
|
72
|
+
under = "#{File.expand_path(directory)}/"
|
|
73
|
+
own = registry.bluebooks.keys.select do |name|
|
|
74
|
+
Array(registry.bluebook_sources[name]).any? { |source| File.expand_path(source).start_with?(under) }
|
|
75
|
+
end
|
|
76
|
+
own.empty? ? registry.bluebooks.keys.first(1) : own
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# @param registry [Hecks::Runtime::Registry] the booted registry of the directory
|
|
80
|
+
# @param root [String] the checkout
|
|
81
|
+
# @param path [String] the domain directory, relative to `root`
|
|
82
|
+
# @return [void]
|
|
83
|
+
def project_chapters(registry, root, path)
|
|
84
|
+
names = chapters_of(registry, File.join(root, path))
|
|
85
|
+
names.each do |name|
|
|
86
|
+
file = file_for(names, name)
|
|
87
|
+
write_manifest(registry, name, File.join(root, path, file))
|
|
88
|
+
puts " #{path}/#{file} <- #{name}"
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# A domain of one chapter keeps `oidc.json`; a directory of several chapters, such as the
|
|
93
|
+
# framework members, gets one manifest each, so none of them stands for the others.
|
|
94
|
+
#
|
|
95
|
+
# @param names [Array<String>] every chapter the directory holds
|
|
96
|
+
# @param name [String] the chapter the manifest is for
|
|
97
|
+
# @return [String] the manifest's file name: `oidc.json`, or `<chapter>.oidc.json`
|
|
98
|
+
def file_for(names, name) = names.one? ? "oidc.json" : "#{Naming.snake(name)}.oidc.json"
|
|
99
|
+
|
|
62
100
|
# @param registry [Hecks::Runtime::Registry] the booted registry
|
|
63
101
|
# @param name [String] the bluebook to project
|
|
64
102
|
# @param out [String] where the manifest goes
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "open3"
|
|
5
|
+
require "hecks/vocabulary"
|
|
6
|
+
require_relative "../tools"
|
|
7
|
+
|
|
8
|
+
module Hecks
|
|
9
|
+
module Tools
|
|
10
|
+
# Decides whether a commit is only a release bump on a base that is fully certified, so CI may
|
|
11
|
+
# inherit the base's certification instead of running the suite again.
|
|
12
|
+
#
|
|
13
|
+
# The files a bump edits, and which of their lines it may change, are the `ReleaseFile` rows of
|
|
14
|
+
# the Vocabulary chapter. A change is a release bump when it touches at least one file, every
|
|
15
|
+
# file it touches is a row, and every line it adds or removes in a file matches that row's
|
|
16
|
+
# `lines` pattern (an empty pattern admits any line). Whether the base is certified is read
|
|
17
|
+
# from the base commit's own check runs: every `RequiredCheck` row that is not marked
|
|
18
|
+
# `runs: always` must have its latest run on the base succeed or be skipped.
|
|
19
|
+
#
|
|
20
|
+
# Every case that cannot be confirmed answers "not a release bump", so the suite runs.
|
|
21
|
+
module ReleaseOnly
|
|
22
|
+
GREEN = %w[success skipped].freeze
|
|
23
|
+
|
|
24
|
+
module_function
|
|
25
|
+
|
|
26
|
+
# @param changes [Hash{String => Array<String>}] each changed path to the lines it adds or
|
|
27
|
+
# removes
|
|
28
|
+
# @param files [Array<Hash{String => String}>] the `ReleaseFile` rows
|
|
29
|
+
# @return [Boolean] whether the change touches only release files, and only their release
|
|
30
|
+
# lines
|
|
31
|
+
def release_only?(changes, files: Hecks::Vocabulary.rows("ReleaseFile"))
|
|
32
|
+
return false if changes.empty?
|
|
33
|
+
|
|
34
|
+
by_path = files.to_h { |row| [row.fetch("path"), row.fetch("lines", "")] }
|
|
35
|
+
changes.all? { |path, lines| by_path.key?(path) && lines_allowed?(lines, by_path.fetch(path)) }
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# @param lines [Array<String>] the changed lines of one file
|
|
39
|
+
# @param pattern [String] the row's `lines` regex, empty for any line
|
|
40
|
+
# @return [Boolean] whether every changed line matches
|
|
41
|
+
def lines_allowed?(lines, pattern)
|
|
42
|
+
return true if pattern.to_s.empty?
|
|
43
|
+
|
|
44
|
+
regex = Regexp.new(pattern)
|
|
45
|
+
lines.all? { |line| line.match?(regex) }
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# @param runs [Array<Hash{String => String}>] the base commit's check runs: `name`,
|
|
49
|
+
# `conclusion` and `started_at`
|
|
50
|
+
# @param required [Array<Hash{String => String}>] the `RequiredCheck` rows
|
|
51
|
+
# @return [Boolean] whether every check that must be inherited has a green latest run
|
|
52
|
+
def certified?(runs, required: Hecks::Vocabulary.rows("RequiredCheck"))
|
|
53
|
+
latest = latest_runs(runs)
|
|
54
|
+
inherited = required.reject { |row| row["runs"] == "always" }.map { |row| row.fetch("name") }
|
|
55
|
+
inherited.all? { |name| GREEN.include?(latest[name].to_h["conclusion"]) }
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# @return [Hash{String => Hash}] each check's most recently started run
|
|
59
|
+
def latest_runs(runs)
|
|
60
|
+
runs.group_by { |run| run["name"] }.transform_values { |group| group.max_by { |run| run["started_at"].to_s } }
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# @param base [String] the commit the change is measured against
|
|
64
|
+
# @param head [String] the commit under test
|
|
65
|
+
# @param root [String] the checkout
|
|
66
|
+
# @return [Hash{String => Array<String>}, nil] each changed path to its changed lines, or nil
|
|
67
|
+
# when git could not say
|
|
68
|
+
def changes(base, head, root:)
|
|
69
|
+
out, _err, status = Open3.capture3("git", "-C", root, "diff", "-U0", "--no-color", base, head)
|
|
70
|
+
status.success? ? parse(out) : nil
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# @param diff [String] `git diff -U0` output
|
|
74
|
+
# @return [Hash{String => Array<String>}] each changed path to the lines it adds or removes
|
|
75
|
+
def parse(diff)
|
|
76
|
+
found = Hash.new { |hash, key| hash[key] = [] }
|
|
77
|
+
path = nil
|
|
78
|
+
diff.each_line do |line|
|
|
79
|
+
path = file_of(line.chomp) || path
|
|
80
|
+
found[path] << line[1..].chomp if path && content_line?(line)
|
|
81
|
+
end
|
|
82
|
+
found.to_h
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# @return [String, nil] the path a `+++` or `---` header names, nil for any other line
|
|
86
|
+
def file_of(line)
|
|
87
|
+
return line.delete_prefix("+++ b/") if line.start_with?("+++ b/")
|
|
88
|
+
|
|
89
|
+
line.delete_prefix("--- a/") if line.start_with?("--- a/")
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# @return [Boolean] whether the line is an added or removed line, not a header
|
|
93
|
+
def content_line?(line)
|
|
94
|
+
line.match?(/\A[+-]/) && !line.start_with?("+++ ", "--- ")
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# @param repository [String] `owner/name`
|
|
98
|
+
# @param sha [String] the commit whose check runs are read
|
|
99
|
+
# @param env [Hash{String => String}] carries `GH_TOKEN` for the API call
|
|
100
|
+
# @return [Array<Hash{String => String}>, nil] the check runs, or nil when the API failed
|
|
101
|
+
def check_runs(repository, sha, env: ENV)
|
|
102
|
+
path = "repos/#{repository}/commits/#{sha}/check-runs?per_page=100"
|
|
103
|
+
jq = ".check_runs[] | {name, conclusion, started_at}"
|
|
104
|
+
out, _err, status = Open3.capture3(env.to_h.slice("GH_TOKEN"), "gh", "api", path, "--paginate", "--jq", jq)
|
|
105
|
+
status.success? ? out.lines.map { |line| JSON.parse(line) } : nil
|
|
106
|
+
rescue JSON::ParserError
|
|
107
|
+
nil
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
@@ -27,22 +27,19 @@ module Hecks
|
|
|
27
27
|
regions = Projector.call(:site_cdn, bluebook: site.chapter, options: { table: site.table, edge: edge })
|
|
28
28
|
relative = template || edge.setting.template
|
|
29
29
|
source = template_source(root, template, relative)
|
|
30
|
-
text = rewritten(File.read(source), regions, relative
|
|
30
|
+
text = rewritten(File.read(source), regions, relative)
|
|
31
31
|
{ destination(source, out, template, relative) => text }
|
|
32
32
|
end
|
|
33
33
|
|
|
34
34
|
# @param text [String] the template
|
|
35
35
|
# @param regions [Hash{String => String}] each region's name to the block that goes in it
|
|
36
36
|
# @param relative [String] the template's name, for a message
|
|
37
|
-
# @param edge [Projections::Site::Edge] the checked edge
|
|
38
37
|
# @return [String] the template with each region rewritten
|
|
39
|
-
# @raise [SystemExit] when a region is missing, or a listener_rules
|
|
40
|
-
# by an edge
|
|
41
|
-
def rewritten(text, regions, relative
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
"and the Edge row says alb: false; remove the region"
|
|
45
|
-
end
|
|
38
|
+
# @raise [SystemExit] when a region is missing, or a listener_rules or edge_functions region
|
|
39
|
+
# is left behind by an edge that has no load balancer or no EdgeFunction row
|
|
40
|
+
def rewritten(text, regions, relative)
|
|
41
|
+
refuse_leftover(text, regions, relative, "listener_rules", "the Edge row says alb: false")
|
|
42
|
+
refuse_leftover(text, regions, relative, "edge_functions", "the project declares no EdgeFunction rows")
|
|
46
43
|
regions.reduce(text) do |current, (name, block)|
|
|
47
44
|
unless Projections::Site::Regions.region?(current, name)
|
|
48
45
|
abort "project_site: #{relative} has no BEGIN/END GENERATED site_cdn #{name} region"
|
|
@@ -52,6 +49,13 @@ module Hecks
|
|
|
52
49
|
end
|
|
53
50
|
end
|
|
54
51
|
|
|
52
|
+
# Refuses a region the edge does not use, so the stale text in it is removed by hand.
|
|
53
|
+
def refuse_leftover(text, regions, relative, name, reason)
|
|
54
|
+
return if regions.key?(name) || !Projections::Site::Regions.region?(text, name)
|
|
55
|
+
|
|
56
|
+
abort "project_site: #{relative} has a BEGIN/END GENERATED site_cdn #{name} region, and #{reason}; remove the region"
|
|
57
|
+
end
|
|
58
|
+
|
|
55
59
|
# @return [Hash] nothing, after refusing a template named for a project with no edge
|
|
56
60
|
def missing_edge(template)
|
|
57
61
|
abort "project_site: --template names #{template}, but the project declares no Edge rows" if template
|
data/lib/hecks/version.rb
CHANGED
data/lib/hecks/vocabulary.rb
CHANGED
|
@@ -51,6 +51,7 @@ module Hecks
|
|
|
51
51
|
].freeze,
|
|
52
52
|
"CiGate" => [
|
|
53
53
|
{"name"=>"runtime_changed", "workflow"=>"ci.yml", "mode"=>"touches", "pattern"=>"^lib/hecks/runtime/", "push"=>"before_sha", "label"=>"does this change touch lib/hecks/runtime/**?"}.freeze,
|
|
54
|
+
{"name"=>"release_only", "workflow"=>"ci.yml", "mode"=>"release_only", "pattern"=>"", "push"=>"before_sha", "label"=>"is this change only a release bump on a certified base?"}.freeze,
|
|
54
55
|
{"name"=>"postgres_io_relevant_changed", "workflow"=>"ci-postgres-io-parallel.yml", "mode"=>"skips_unless", "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$)", "push"=>"skip", "label"=>"does this change touch anything rspec_postgres_io_parallel covers?"}.freeze
|
|
55
56
|
].freeze,
|
|
56
57
|
"Comparison" => [
|
|
@@ -134,6 +135,9 @@ module Hecks
|
|
|
134
135
|
{"name"=>"replace"}.freeze,
|
|
135
136
|
{"name"=>"scale_call"}.freeze
|
|
136
137
|
].freeze,
|
|
138
|
+
"PrebuiltBinary" => [
|
|
139
|
+
{"name"=>"host", "crate"=>"rust/host", "executable"=>"bootstrap", "target"=>"x86_64-unknown-linux-gnu", "runner"=>"ubuntu-24.04"}.freeze
|
|
140
|
+
].freeze,
|
|
137
141
|
"Primitive" => [
|
|
138
142
|
{"name"=>"String"}.freeze,
|
|
139
143
|
{"name"=>"Integer"}.freeze,
|
|
@@ -363,6 +367,15 @@ module Hecks
|
|
|
363
367
|
{"refusal"=>"AttributeAbsent", "site"=>"absent_read", "template"=>"{aggregate} {field} is absent on this record — declared, not optional, and added since it was written. Backfill it in a translation (backfill :{field}, default: ...), or declare it optional: true"}.freeze,
|
|
364
368
|
{"refusal"=>"ProjectionAbsent", "site"=>"absent_read", "template"=>"{aggregate} {field} is not yet projected on this record — declared via projects :{field}, but no rebuild sweep has populated it. Run the sweep, or read {reference}.{remote_field} directly if this rule cannot wait"}.freeze
|
|
365
369
|
].freeze,
|
|
370
|
+
"ReleaseFile" => [
|
|
371
|
+
{"path"=>"lib/hecks/version.rb", "lines"=>"^\\s*VERSION = \"[0-9]+\\.[0-9]+\\.[0-9]+\"\\.freeze$"}.freeze,
|
|
372
|
+
{"path"=>"CHANGELOG.md", "lines"=>""}.freeze,
|
|
373
|
+
{"path"=>"README.md", "lines"=>"^(\\*\\*Status:\\*\\* )?Current release: `[0-9]+\\.[0-9]+\\.[0-9]+`"}.freeze,
|
|
374
|
+
{"path"=>"Gemfile.lock", "lines"=>"^ hecks \\([0-9]+\\.[0-9]+\\.[0-9]+\\)$"}.freeze,
|
|
375
|
+
{"path"=>"rust/host/HECKS_RELEASE", "lines"=>"^[0-9]+\\.[0-9]+\\.[0-9]+$"}.freeze,
|
|
376
|
+
{"path"=>"packages/hecks-client/package.json", "lines"=>"^\\s*\"version\": \"[0-9]+\\.[0-9]+\\.[0-9]+\",?$"}.freeze,
|
|
377
|
+
{"path"=>"packages/hecks-client/package-lock.json", "lines"=>"^\\s*\"version\": \"[0-9]+\\.[0-9]+\\.[0-9]+\",?$"}.freeze
|
|
378
|
+
].freeze,
|
|
366
379
|
"RequiredCheck" => [
|
|
367
380
|
{"name"=>"rspec"}.freeze,
|
|
368
381
|
{"name"=>"checks"}.freeze,
|
|
@@ -372,7 +385,8 @@ module Hecks
|
|
|
372
385
|
{"name"=>"rspec_rust_parser"}.freeze,
|
|
373
386
|
{"name"=>"rspec_rust_codegen"}.freeze,
|
|
374
387
|
{"name"=>"rspec_rust_host"}.freeze,
|
|
375
|
-
{"name"=>"rspec_fuzzing"}.freeze
|
|
388
|
+
{"name"=>"rspec_fuzzing"}.freeze,
|
|
389
|
+
{"name"=>"release_consistency", "runs"=>"always"}.freeze
|
|
376
390
|
].freeze,
|
|
377
391
|
"RetiredScript" => [
|
|
378
392
|
{"script"=>"ir", "section"=>"Custodian", "aggregate"=>"Introspection", "verb"=>"ir"}.freeze,
|
|
Binary file
|