hecks 3.2.0 → 3.3.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/bluebook/assembly/contracts.rb +2 -1
- data/lib/hecks/bluebook/dsl/bootstrap_table.rb +1 -0
- data/lib/hecks/bluebook/dsl/command_builder.rb +2 -39
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +3 -3
- data/lib/hecks/bluebook/dsl/need_word.rb +50 -0
- data/lib/hecks/bluebook/dsl/query_builder.rb +6 -1
- data/lib/hecks/bluebook/query.rb +8 -5
- data/lib/hecks/cli/check_era.rb +3 -39
- data/lib/hecks/cli/interview_draft.rb +86 -31
- data/lib/hecks/cli/interview_session.rb +77 -26
- data/lib/hecks/cli/interview_shape.rb +179 -0
- data/lib/hecks/doors/cli_runner.rb +14 -1
- data/lib/hecks/doors/launcher_options.rb +13 -3
- data/lib/hecks/doors/mcp_door.rb +16 -2
- data/lib/hecks/doors/mcp_door_scope.rb +45 -5
- data/lib/hecks/doors/mcp_guide_cache.rb +75 -0
- data/lib/hecks/doors.rb +1 -1
- data/lib/hecks/embryonaut_bluebook/manifest.rb +117 -0
- data/lib/hecks/embryonaut_bluebook/registry.rb +209 -0
- data/lib/hecks/embryonaut_bluebook/vendor.rb +1 -1
- data/lib/hecks/embryonaut_bluebook.rb +2 -0
- data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +17 -1
- data/lib/hecks/fuzzing/qa_settings.rb +15 -34
- data/lib/hecks/gate/stages.yml +113 -8
- data/lib/hecks/hecks/adapters/codebase/regeneration.rb +10 -1
- data/lib/hecks/hecks/adapters/codebase/test_runner.rb +10 -2
- data/lib/hecks/hecks/adapters/first_admin.rb +108 -0
- data/lib/hecks/hecks/adapters/git.rb +59 -0
- data/lib/hecks/hecks/adapters/in_process_access.rb +25 -0
- data/lib/hecks/hecks/adapters/in_process_boot.rb +2 -0
- data/lib/hecks/hecks/adapters/rust_toolchain.rb +18 -3
- data/lib/hecks/hecks/adapters/rust_workspace.rb +1 -1
- data/lib/hecks/hecks/adapters/site_toolchain.rb +88 -11
- data/lib/hecks/hecks/codebase.behaviors +6 -0
- data/lib/hecks/hecks/codebase.bluebook +21 -0
- data/lib/hecks/hecks/custodian.behaviors +6 -0
- data/lib/hecks/hecks/custodian.bluebook +217 -0
- data/lib/hecks/hecks/hecks.hecksagon +24 -0
- data/lib/hecks/hecks/hecks.world +8 -3
- data/lib/hecks/hecks/site.world +5 -2
- data/lib/hecks/language/bluebook/query.bluebook +25 -0
- data/lib/hecks/language/bluebook/vocabulary.bluebook +220 -7
- data/lib/hecks/language/hecksagon/hecksagon.bluebook +1 -1
- data/lib/hecks/language/oidc.json +5 -0
- data/lib/hecks/ports/persistence/plugins/era/expected_era.rb +3 -3
- data/lib/hecks/projections/deploy/box/hosting.rb +170 -0
- data/lib/hecks/projections/deploy/box/hosting_settings.rb +123 -0
- data/lib/hecks/projections/deploy/box/settings.rb +40 -6
- data/lib/hecks/projections/deploy/box/templates/deploy-box.sh.tmpl +2 -0
- data/lib/hecks/projections/deploy/box/templates/deploy-service.services-roll.part +33 -0
- data/lib/hecks/projections/deploy/box/templates/deploy-service.sh.tmpl +79 -0
- data/lib/hecks/projections/deploy/box/templates/deploy-service.taskdef-roll.part +77 -0
- data/lib/hecks/projections/deploy/box/templates/deploy-service.taskdef.part +12 -0
- data/lib/hecks/projections/deploy/box/templates/hosting.mk.tmpl +24 -0
- data/lib/hecks/projections/deploy/box/templates/render-compose-taskdef.sh.tmpl +17 -0
- data/lib/hecks/projections/deploy/box/templates/smoke-after-deploy.sh.tmpl +271 -0
- data/lib/hecks/projections/deploy/box.rb +28 -4
- data/lib/hecks/projections/deploy/fargate.rb +3 -3
- data/lib/hecks/projections/deploy/lambda.rb +8 -8
- data/lib/hecks/projections/model/deviations.rb +1 -1
- data/lib/hecks/projections/site/edge/behaviours.rb +5 -3
- data/lib/hecks/projections/site/edge/checks.rb +31 -5
- data/lib/hecks/projections/site/edge.rb +25 -11
- data/lib/hecks/projections/site/live_edge.rb +162 -0
- data/lib/hecks/projections/site/routes_ts.rb +50 -27
- data/lib/hecks/projections/site/site_cdn.rb +7 -4
- data/lib/hecks/projections/site/table/checks.rb +53 -7
- data/lib/hecks/projections/site/table/link_builder.rb +64 -0
- data/lib/hecks/projections/site/table/row_builder.rb +21 -6
- data/lib/hecks/projections/site/table.rb +16 -5
- data/lib/hecks/quality_control/adapters/git_pr.rb +0 -14
- data/lib/hecks/quality_control/cli/qa_discover_external_domains.rb +1 -1
- data/lib/hecks/quality_control/cli/qa_open_pr.rb +32 -16
- data/lib/hecks/quality_control/cli/qa_seed_angles.rb +2 -2
- data/lib/hecks/quality_control/cli/qa_seed_targets.rb +1 -2
- data/lib/hecks/quality_control/quality_control.bluebook +76 -124
- data/lib/hecks/runtime/interpreting.rb +3 -22
- data/lib/hecks/runtime/needs.rb +48 -0
- data/lib/hecks/runtime/query_interpreter.rb +3 -0
- data/lib/hecks/runtime/registry/verification.rb +3 -3
- data/lib/hecks/rust_build/host.rb +147 -0
- data/lib/hecks/rust_build/kernel_input.rb +51 -17
- data/lib/hecks/rust_build/project_rust/codegen_run.rb +0 -18
- data/lib/hecks/rust_build/project_rust.rb +7 -140
- data/lib/hecks/rust_build.rb +1 -0
- data/lib/hecks/site/bluebook/site.bluebook +80 -14
- data/lib/hecks/site/bluebook/site.ports.hecksagon +6 -0
- data/lib/hecks/sme/bluebook/sme.bluebook +128 -1
- data/lib/hecks/storehouse.rb +35 -1
- data/lib/hecks/tools/comment_style.rb +4 -2
- data/lib/hecks/tools/gate.rb +1 -1
- data/lib/hecks/tools/rust_comment_style.rb +2 -4
- data/lib/hecks/tools/site_routes.rb +69 -28
- data/lib/hecks/tools/tools_doc.rb +204 -0
- data/lib/hecks/tools.rb +1 -0
- data/lib/hecks/version.rb +1 -1
- data/lib/hecks/vocabulary.rb +152 -1
- data/lib/hecks.rb +1 -0
- data/qa/settings.yml +1 -2
- data/rust/build/src/lineage_pass.rs +31 -16
- data/rust/build/src/main.rs +4 -2
- data/rust/build/src/pipeline.rs +9 -20
- data/rust/codegen/src/attr.rs +1 -1
- data/rust/codegen/src/bridging.rs +8 -8
- data/rust/codegen/src/commands.rs +1 -1
- data/rust/codegen/src/constraints.rs +1 -1
- data/rust/codegen/src/dependency_planning.rs +1 -1
- data/rust/codegen/src/domain_generator.rs +2 -2
- data/rust/codegen/src/exemplar.rs +1 -1
- data/rust/codegen/src/expr_emitter.rs +1 -1
- data/rust/codegen/src/fielded.rs +1 -1
- data/rust/codegen/src/json.rs +214 -23
- data/rust/codegen/src/json_codec.rs +1 -1
- data/rust/codegen/src/literal.rs +6 -3
- data/rust/codegen/src/main.rs +23 -13
- data/rust/codegen/src/mutations.rs +1 -1
- data/rust/codegen/src/naming.rs +5 -3
- data/rust/codegen/src/optional_pass.rs +226 -0
- data/rust/codegen/src/ports.rs +1 -1
- data/rust/codegen/src/queries.rs +3 -3
- data/rust/codegen/src/reactions.rs +1 -1
- data/rust/codegen/src/read_models.rs +2 -2
- data/rust/codegen/src/reference_specs.rs +1 -1
- data/rust/codegen/src/registry.rs +4 -4
- data/rust/codegen/src/shared.rs +1 -1
- data/rust/{build → codegen}/src/sidecars.rs +9 -5
- data/rust/codegen/src/skip_reason.rs +1 -1
- data/rust/codegen/src/types.rs +4 -4
- data/rust/host/HECKS_RELEASE +1 -1
- data/rust/host/src/api.rs +1 -1
- data/rust/host/src/dispatch.rs +5 -0
- data/rust/host/src/needs.rs +193 -29
- data/rust/host/src/resend.rs +4 -4
- data/rust/host/src/ui_schema.rs +1 -1
- data/rust/parser/src/emit.rs +12 -0
- data/rust/parser/src/ir.rs +2 -0
- data/rust/parser/src/keywords.rs +2 -0
- data/rust/parser/src/main.rs +1 -0
- data/rust/parser/src/parse/command.rs +2 -44
- data/rust/parser/src/parse/mod.rs +1 -0
- data/rust/parser/src/parse/needs.rs +60 -0
- data/rust/parser/src/parse/query.rs +3 -0
- data/rust/src/exemplar/commands.rs +1 -1
- data/rust/src/exemplar/constraints.rs +1 -1
- data/rust/src/exemplar/fielded.rs +1 -1
- data/rust/src/exemplar/json.rs +1 -1
- data/rust/src/exemplar/mod.rs +1 -1
- data/rust/src/exemplar/mutations.rs +1 -1
- data/rust/src/exemplar/queries.rs +1 -1
- data/rust/src/exemplar/reactions.rs +1 -1
- data/rust/src/exemplar/read_models.rs +1 -1
- data/rust/src/exemplar/registry.rs +1 -1
- data/rust/src/exemplar/types.rs +1 -1
- data/rust/src/kernel/cli.rs +18 -8
- data/rust/src/kernel/needs.rs +104 -9
- data/rust/src/kernel/read_model.rs +1 -1
- data/rust/src/lib.rs +1 -1
- metadata +24 -30
- data/lib/hecks/rust_build/append_optionals.rb +0 -79
- data/lib/hecks/rust_build/rust_literal.rb +0 -33
- data/lib/hecks/three_zero/forms.yml +0 -127
- data/lib/hecks/three_zero.rb +0 -12
- data/rust/build/src/json.rs +0 -421
- data/rust/build/src/optional_pass.rs +0 -150
- data/rust/project/bridging.rb +0 -257
- data/rust/project/commands.rb +0 -877
- data/rust/project/constraints.rb +0 -86
- data/rust/project/dependency_planning.rb +0 -178
- data/rust/project/domain_generator.rb +0 -983
- data/rust/project/exemplar.rb +0 -148
- data/rust/project/expr_emitter.rb +0 -72
- data/rust/project/fielded.rb +0 -141
- data/rust/project/json_codec.rb +0 -641
- data/rust/project/mutations.rb +0 -695
- data/rust/project/naming.rb +0 -223
- data/rust/project/ports.rb +0 -65
- data/rust/project/queries.rb +0 -493
- data/rust/project/reactions.rb +0 -317
- data/rust/project/read_models.rb +0 -690
- data/rust/project/reference_specs.rb +0 -27
- data/rust/project/registry.rb +0 -396
- data/rust/project/skip_reason.rb +0 -29
- data/rust/project/types.rb +0 -225
- data/rust/project/write_if_changed.rb +0 -7
- data/rust/project.rb +0 -30
- data/rust/project_rust_pipeline.rb +0 -391
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
require_relative "lock"
|
|
3
|
+
|
|
4
|
+
module Hecks
|
|
5
|
+
module EmbryonautBluebook
|
|
6
|
+
# Checks a project's vendored packages against their `bluebook.lock` files and describes
|
|
7
|
+
# them as data: the manifest a client image carries to say which bluebooks it holds.
|
|
8
|
+
#
|
|
9
|
+
# { "built_from" => { "commit" => "<project commit>", "dirty" => false },
|
|
10
|
+
# "bluebooks" => { "payments" => { "version" => "1.0.0", "tag" => "payments-v1.0.0",
|
|
11
|
+
# "commit" => "<sha>", "digest" => "<sha256>",
|
|
12
|
+
# "shape" => ["Payments d33c23"] } } }
|
|
13
|
+
#
|
|
14
|
+
# There is no timestamp: the same inputs give the same manifest. The digest is `Lock.digest_of`,
|
|
15
|
+
# the one implementation `Vendor` also writes locks with.
|
|
16
|
+
class Manifest
|
|
17
|
+
# The lock fields every package must carry besides its shape.
|
|
18
|
+
FIELDS = %w[package version tag commit digest].freeze
|
|
19
|
+
|
|
20
|
+
# The fields a package's manifest entry repeats from its lock.
|
|
21
|
+
ENTRY = %w[version tag commit digest].freeze
|
|
22
|
+
|
|
23
|
+
# A refusal: every way the vendored packages disagree with their locks, one per line.
|
|
24
|
+
class Mismatch < StandardError
|
|
25
|
+
# @return [Array<String>] one sentence per disagreement
|
|
26
|
+
attr_reader :problems
|
|
27
|
+
|
|
28
|
+
# @param problems [Array<String>] the disagreements found
|
|
29
|
+
def initialize(problems)
|
|
30
|
+
@problems = problems
|
|
31
|
+
super(problems.map { |problem| "FAIL #{problem}" }.join("\n"))
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# @param root [String] the project directory holding `vendor/embryonaut_bluebooks/`
|
|
36
|
+
# @param built_from [Hash{String => Object}, nil] the project's own commit and whether the
|
|
37
|
+
# tree is dirty; left out of the manifest when nil
|
|
38
|
+
def initialize(root, built_from: nil)
|
|
39
|
+
@root = root
|
|
40
|
+
@built_from = built_from
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Checks every vendored package and describes them.
|
|
44
|
+
#
|
|
45
|
+
# @return [Hash{String => Object}] the manifest
|
|
46
|
+
# @raise [Mismatch] when any package disagrees with its lock
|
|
47
|
+
def call
|
|
48
|
+
problems = []
|
|
49
|
+
packages = names.to_h { |name| [name, entry(name, problems)] }.compact
|
|
50
|
+
raise Mismatch, problems unless problems.empty?
|
|
51
|
+
|
|
52
|
+
{ "built_from" => @built_from, "bluebooks" => packages }.compact
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# The manifest as the text a label or file holds: keys sorted, two-space indent.
|
|
56
|
+
#
|
|
57
|
+
# @return [String] JSON ending in a newline
|
|
58
|
+
def to_json_text
|
|
59
|
+
"#{JSON.pretty_generate(sort(call))}\n"
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
private
|
|
63
|
+
|
|
64
|
+
def vendor_dir = File.join(@root, "vendor", "embryonaut_bluebooks")
|
|
65
|
+
|
|
66
|
+
def names
|
|
67
|
+
return [] unless File.directory?(vendor_dir)
|
|
68
|
+
|
|
69
|
+
Dir.children(vendor_dir).sort.select { |name| File.directory?(File.join(vendor_dir, name)) }
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# One package's manifest entry, or nil (its reasons added to `problems`) when it has no lock.
|
|
73
|
+
def entry(name, problems)
|
|
74
|
+
dir = File.join(vendor_dir, name)
|
|
75
|
+
path = File.join(dir, "bluebook.lock")
|
|
76
|
+
unless File.exist?(path)
|
|
77
|
+
problems << "#{name}: no bluebook.lock (hecks package.vendor #{name}@<version>)"
|
|
78
|
+
return nil
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
lock = Lock.read(path)
|
|
82
|
+
judge(name, dir, lock, problems)
|
|
83
|
+
lock.to_h.transform_keys(&:to_s).slice(*ENTRY).merge("shape" => Array(lock.shape))
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def judge(name, dir, lock, problems)
|
|
87
|
+
FIELDS.each { |field| problems << "#{name}: bluebook.lock has no #{field}" if blank?(lock[field.to_sym]) }
|
|
88
|
+
problems << "#{name}: bluebook.lock names package #{lock.package}" if lock.package && lock.package != name
|
|
89
|
+
problems << "#{name}: bluebook.lock has no shape" if Array(lock.shape).empty?
|
|
90
|
+
judge_release(name, lock, problems)
|
|
91
|
+
actual = Lock.digest_of(File.join(dir, "bluebook"))
|
|
92
|
+
return if actual == lock.digest
|
|
93
|
+
|
|
94
|
+
problems << "#{name}: vendored files hash to #{(actual || 'nothing')[0, 12]}, but bluebook.lock says " \
|
|
95
|
+
"#{(lock.digest || '?')[0, 12]} (edited by hand, or re-vendored without its lock?)"
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# The tag of a release is `<package>-v<version>`; a lock saying otherwise was edited.
|
|
99
|
+
def judge_release(name, lock, problems)
|
|
100
|
+
return if blank?(lock.tag) || blank?(lock.version)
|
|
101
|
+
return if lock.tag == "#{name}-v#{lock.version}"
|
|
102
|
+
|
|
103
|
+
problems << "#{name}: bluebook.lock tag #{lock.tag} is not #{name}-v#{lock.version}"
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def blank?(value) = value.nil? || value.to_s.empty?
|
|
107
|
+
|
|
108
|
+
def sort(value)
|
|
109
|
+
case value
|
|
110
|
+
when Hash then value.sort.to_h { |key, inner| [key, sort(inner)] }
|
|
111
|
+
when Array then value.map { |inner| sort(inner) }
|
|
112
|
+
else value
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
require "open3"
|
|
2
|
+
require "tmpdir"
|
|
3
|
+
require "fileutils"
|
|
4
|
+
require_relative "../vendoring"
|
|
5
|
+
require_relative "lock"
|
|
6
|
+
|
|
7
|
+
module Hecks
|
|
8
|
+
module EmbryonautBluebook
|
|
9
|
+
# A bluebook registry repository, judged and released: packages at `<name>/bluebook.yml`, each
|
|
10
|
+
# with `<name>/bluebook/*.bluebook` and a `<name>/CHANGELOG.md`, released as the annotated tag
|
|
11
|
+
# `<name>-vX.Y.Z`.
|
|
12
|
+
#
|
|
13
|
+
# `check` is the rule that a package's bluebook files may not change without a version bump and
|
|
14
|
+
# a changelog entry; `release` is the sequence of refusals before a tag is made. A package's
|
|
15
|
+
# files are compared through `Lock.digest_of`, the digest a consuming project locks.
|
|
16
|
+
class Registry
|
|
17
|
+
# A version a package may carry, `X.Y.Z`.
|
|
18
|
+
VERSION = /\A\d+\.\d+\.\d+\z/
|
|
19
|
+
|
|
20
|
+
# A package name is a directory name.
|
|
21
|
+
NAME = /\A[a-z][a-z0-9_]*\z/
|
|
22
|
+
|
|
23
|
+
# What `check` found, in package order: a note for a package that has no release yet and a
|
|
24
|
+
# sentence for each fault.
|
|
25
|
+
class Report
|
|
26
|
+
# @return [Array<String>] each fault as `<package>: <why>`
|
|
27
|
+
attr_reader :failures
|
|
28
|
+
|
|
29
|
+
def initialize
|
|
30
|
+
@lines = []
|
|
31
|
+
@failures = []
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# @param text [String] a fact about a package that is no fault
|
|
35
|
+
# @return [void]
|
|
36
|
+
def note(text) = @lines << "note #{text}"
|
|
37
|
+
|
|
38
|
+
# @param text [String] a fault, as `<package>: <why>`
|
|
39
|
+
# @return [void]
|
|
40
|
+
def fault(text)
|
|
41
|
+
@failures << text
|
|
42
|
+
@lines << "FAIL #{text}"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# @return [Boolean] whether no package is at fault
|
|
46
|
+
def ok? = @failures.empty?
|
|
47
|
+
|
|
48
|
+
# @return [String] the notes and faults in package order, then `versions ok` if none
|
|
49
|
+
def to_s = (@lines + (ok? ? ["versions ok"] : [])).join("\n")
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# What a release made.
|
|
53
|
+
Tagged = Struct.new(:tag, :commit, keyword_init: true) do
|
|
54
|
+
# @return [String] the report, ending in the command that publishes the tag
|
|
55
|
+
def to_s = "Tagged #{tag} at #{commit}. Publish it with:\n git push origin #{tag}"
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# @param root [String] the registry's working tree
|
|
59
|
+
# @raise [Vendoring::Error] if `root` is not a git repository
|
|
60
|
+
def initialize(root)
|
|
61
|
+
@root = File.expand_path(root.to_s)
|
|
62
|
+
@source = Vendoring::GitSource.new(@root)
|
|
63
|
+
raise Vendoring::Error, "#{@root} is not a git repository" unless git("rev-parse", "--git-dir").last.success?
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Judges every package against its latest release.
|
|
67
|
+
#
|
|
68
|
+
# A package fails when its `bluebook.yml` has no `X.Y.Z` version or names another package,
|
|
69
|
+
# when a release tag points at a commit whose `bluebook.yml` says another version, or when
|
|
70
|
+
# its bluebook files differ from the latest release's without a newer version and a
|
|
71
|
+
# `CHANGELOG.md` entry for it.
|
|
72
|
+
#
|
|
73
|
+
# @return [Report] what was found
|
|
74
|
+
def check
|
|
75
|
+
report = Report.new
|
|
76
|
+
packages.each { |package| check_package(package, report) }
|
|
77
|
+
report
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Tags a package's current version, after the refusals that stand between a version and a
|
|
81
|
+
# release: the tag exists, the version is not newer, the changelog has no entry, the package
|
|
82
|
+
# has uncommitted changes, or its bluebook files are those of the latest release. Nothing is
|
|
83
|
+
# pushed.
|
|
84
|
+
#
|
|
85
|
+
# @param package [String] the package name
|
|
86
|
+
# @return [Tagged] the tag made and the commit it stands on
|
|
87
|
+
# @raise [Vendoring::Error] with the first refusal
|
|
88
|
+
def release(package)
|
|
89
|
+
fault!("#{package.inspect} is not a package name") unless package.to_s.match?(NAME)
|
|
90
|
+
version = manifest_version(package) or fault!("no #{package}/bluebook.yml")
|
|
91
|
+
fault!("#{package}/bluebook.yml: version '#{version}' is not X.Y.Z") unless version.match?(VERSION)
|
|
92
|
+
|
|
93
|
+
tag = "#{package}-v#{version}"
|
|
94
|
+
refuse_release!(package, version, tag)
|
|
95
|
+
notes = changelog_section(package, version)
|
|
96
|
+
_, _, status = git("tag", "-a", tag, "-m", "#{package} #{version}", "-m", notes)
|
|
97
|
+
raise Vendoring::Error, "git could not tag #{tag}" unless status.success?
|
|
98
|
+
|
|
99
|
+
Tagged.new(tag: tag, commit: git("rev-parse", "--short", "HEAD").first.strip)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
private
|
|
103
|
+
|
|
104
|
+
def packages
|
|
105
|
+
Dir.glob(File.join(@root, "*", "bluebook.yml")).map { |file| File.basename(File.dirname(file)) }.sort
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def check_package(package, report)
|
|
109
|
+
version = manifest_version(package).to_s
|
|
110
|
+
unless version.match?(VERSION)
|
|
111
|
+
report.fault "#{package}: version '#{version}' is not X.Y.Z"
|
|
112
|
+
return
|
|
113
|
+
end
|
|
114
|
+
name = File.read(File.join(@root, package, "bluebook.yml"))[/^name: *(.*)$/, 1]
|
|
115
|
+
report.fault "#{package}: bluebook.yml name does not match the directory" unless name == package
|
|
116
|
+
tags(package).each { |tag| check_tag(package, tag, report) }
|
|
117
|
+
latest = latest_version(package)
|
|
118
|
+
return report.note("#{package}: no release tag yet (#{version} unreleased)") unless latest
|
|
119
|
+
|
|
120
|
+
check_changes(package, version, latest, report)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def check_tag(package, tag, report)
|
|
124
|
+
tagged = @source.read(tag, "#{package}/bluebook.yml").to_s[/^version: *(.*)$/, 1].to_s
|
|
125
|
+
return if tagged == tag.delete_prefix("#{package}-v")
|
|
126
|
+
|
|
127
|
+
report.fault "#{package}: #{tag} points at a commit whose bluebook.yml says '#{tagged}'"
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def check_changes(package, version, latest, report)
|
|
131
|
+
return if released_digest(package, latest) == current_digest(package)
|
|
132
|
+
|
|
133
|
+
if version == latest || newest(latest, version) != version
|
|
134
|
+
report.fault "#{package}: bluebook files changed since #{package}-v#{latest} but " \
|
|
135
|
+
"bluebook.yml is still #{version}; bump it"
|
|
136
|
+
end
|
|
137
|
+
return if changelog_entry?(package, version)
|
|
138
|
+
|
|
139
|
+
report.fault "#{package}: CHANGELOG.md has no '## #{version}' entry"
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def refuse_release!(package, version, tag)
|
|
143
|
+
fault!("#{tag} already exists") if git("rev-parse", "-q", "--verify", "refs/tags/#{tag}").last.success?
|
|
144
|
+
|
|
145
|
+
latest = latest_version(package)
|
|
146
|
+
fault!("#{version} is not newer than the latest release #{latest}") if latest && newest(latest, version) != version
|
|
147
|
+
fault!("#{package}/CHANGELOG.md has no '## #{version}' entry") unless changelog_entry?(package, version)
|
|
148
|
+
fault!("#{package} has uncommitted changes; commit them first") unless clean?(package)
|
|
149
|
+
return unless latest && released_digest(package, latest) == current_digest(package)
|
|
150
|
+
|
|
151
|
+
fault!("the bluebook files are identical to #{package}-v#{latest}; nothing to release")
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def fault!(why) = raise(Vendoring::Error, why)
|
|
155
|
+
|
|
156
|
+
def clean?(package) = git("status", "--porcelain", "--", package).first.strip.empty?
|
|
157
|
+
|
|
158
|
+
def manifest_version(package)
|
|
159
|
+
file = File.join(@root, package, "bluebook.yml")
|
|
160
|
+
File.file?(file) ? File.read(file)[/^version: *(.*)$/, 1] : nil
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def tags(package) = @source.tags("#{package}-v*")
|
|
164
|
+
|
|
165
|
+
def latest_version(package)
|
|
166
|
+
tags(package).map { |tag| tag.delete_prefix("#{package}-v") }.max_by { |version| order(version) }
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# The greater of two versions, compared as `sort -V` does: runs of digits as numbers.
|
|
170
|
+
def newest(left, right) = [left, right].max_by { |version| order(version) }
|
|
171
|
+
|
|
172
|
+
def order(version) = version.scan(/\d+|\D+/).map { |part| part.match?(/\A\d/) ? [0, part.to_i, ""] : [1, 0, part] }
|
|
173
|
+
|
|
174
|
+
def changelog_entry?(package, version)
|
|
175
|
+
file = File.join(@root, package, "CHANGELOG.md")
|
|
176
|
+
File.file?(file) && File.read(file).match?(/^## #{Regexp.escape(version)}( |$)/)
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# The lines under a version's heading, up to the next heading, without trailing blank lines.
|
|
180
|
+
def changelog_section(package, version)
|
|
181
|
+
on = false
|
|
182
|
+
lines = File.readlines(File.join(@root, package, "CHANGELOG.md"), chomp: true).select do |line|
|
|
183
|
+
on = line.match?(/^## #{Regexp.escape(version)}( |$)/) if line.start_with?("## ")
|
|
184
|
+
on && !line.start_with?("## ")
|
|
185
|
+
end
|
|
186
|
+
lines.join("\n").sub(/\n+\z/, "")
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def current_digest(package)
|
|
190
|
+
dir = File.join(@root, package, "bluebook")
|
|
191
|
+
File.directory?(dir) ? Lock.digest_of(dir) : nil
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# The digest of a package's bluebook files as a release tag holds them.
|
|
195
|
+
def released_digest(package, version)
|
|
196
|
+
tag = "#{package}-v#{version}"
|
|
197
|
+
files = @source.files(tag, "#{package}/bluebook", "*.bluebook")
|
|
198
|
+
return nil if files.empty?
|
|
199
|
+
|
|
200
|
+
Dir.mktmpdir("hecks-registry") do |scratch|
|
|
201
|
+
@source.export(tag, files, scratch)
|
|
202
|
+
Lock.digest_of(File.join(scratch, package, "bluebook"))
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def git(*) = Open3.capture3(Vendoring::GitEnvironment.clean, "git", "-C", @root, *)
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
end
|
|
@@ -125,7 +125,7 @@ module Hecks
|
|
|
125
125
|
return if @allow_downgrade || Gem::Version.new(version) >= Gem::Version.new(previous_version)
|
|
126
126
|
|
|
127
127
|
raise Vendoring::Error, "#{@name} #{previous_version} is vendored; #{version} is older. " \
|
|
128
|
-
"Pass allow_downgrade to do it anyway."
|
|
128
|
+
"Pass allow_downgrade (ALLOW_DOWNGRADE=1 on the command line) to do it anyway."
|
|
129
129
|
end
|
|
130
130
|
|
|
131
131
|
def minor_or_more?(old, new)
|
|
@@ -3,6 +3,8 @@ module Hecks
|
|
|
3
3
|
# package that ships outside hecks's own lib/ (see .load! and .vendor!).
|
|
4
4
|
module EmbryonautBluebook
|
|
5
5
|
autoload :Lock, File.join(__dir__, "embryonaut_bluebook/lock")
|
|
6
|
+
autoload :Manifest, File.join(__dir__, "embryonaut_bluebook/manifest")
|
|
7
|
+
autoload :Registry, File.join(__dir__, "embryonaut_bluebook/registry")
|
|
6
8
|
autoload :Shape, File.join(__dir__, "embryonaut_bluebook/shape")
|
|
7
9
|
autoload :Vendor, File.join(__dir__, "embryonaut_bluebook/vendor")
|
|
8
10
|
autoload :VendorCli, File.join(__dir__, "embryonaut_bluebook/vendor_cli")
|
|
@@ -118,8 +118,9 @@ module Hecks
|
|
|
118
118
|
|
|
119
119
|
owner = owner_for_verb(bluebooks, entry[:verb]) || aggregate
|
|
120
120
|
|
|
121
|
+
args = with_declared_defaults(command, entry[:args])
|
|
121
122
|
command.mutations.select { |m| RECOMPUTABLE_MUTATION_OPS.include?(m.op) }.filter_map do |mutation|
|
|
122
|
-
expected = recompute_mutation(mutation, entry[:before][mutation.target],
|
|
123
|
+
expected = recompute_mutation(mutation, entry[:before][mutation.target], args, entry[:before],
|
|
123
124
|
aggregate, command, owner)
|
|
124
125
|
next if expected == :unrecomputable
|
|
125
126
|
|
|
@@ -134,6 +135,21 @@ module Hecks
|
|
|
134
135
|
offenders.empty? || offenders.join("; ")
|
|
135
136
|
end
|
|
136
137
|
|
|
138
|
+
# The arguments the command actually ran with: an argument the caller left out takes the
|
|
139
|
+
# default its attribute declares, as dispatch fills it, so the recomputation starts from
|
|
140
|
+
# the same facts.
|
|
141
|
+
#
|
|
142
|
+
# @param command [Object] the command the step dispatched
|
|
143
|
+
# @param args [Hash] the arguments the step offered
|
|
144
|
+
# @return [Hash] `args`, with each omitted defaulted argument filled
|
|
145
|
+
def with_declared_defaults(command, args)
|
|
146
|
+
command.attributes.each_with_object(args.dup) do |attribute, held|
|
|
147
|
+
next if attribute.default.nil? || held.key?(attribute.name.to_sym) || held.key?(attribute.name.to_s)
|
|
148
|
+
|
|
149
|
+
held[attribute.name.to_sym] = attribute.default
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
137
153
|
# Root aggregate for `verb`, derived from the verb alone because hand-built
|
|
138
154
|
# entries may lack `:domain`/`:aggregate`. Value-object types resolve against
|
|
139
155
|
# the root's namespace only.
|
|
@@ -1,44 +1,26 @@
|
|
|
1
1
|
require "yaml"
|
|
2
|
+
require "hecks/vocabulary"
|
|
2
3
|
|
|
3
4
|
module Hecks
|
|
4
5
|
module Fuzzing
|
|
5
6
|
# Loads and validates `qa/settings.yml`, the hecks_qa dials.
|
|
6
7
|
#
|
|
7
8
|
# Fails loud: a missing key, unknown key or wrong-typed value raises at load time.
|
|
8
|
-
#
|
|
9
|
+
# What each dial is and takes is declared as a `QaDial` row in the Vocabulary chapter.
|
|
9
10
|
class QaSettings
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
widening_tiers: Array,
|
|
16
|
-
sweep_max_parallel: Integer,
|
|
17
|
-
liveness_fallback_seconds: Integer,
|
|
18
|
-
draft_only: [TrueClass, FalseClass],
|
|
19
|
-
auto_merge: [TrueClass, FalseClass],
|
|
20
|
-
branch_prefix: String,
|
|
21
|
-
adversarial_fraction: Numeric,
|
|
22
|
-
guided_generation: [TrueClass, FalseClass],
|
|
23
|
-
corpus_splice_probability: Numeric,
|
|
24
|
-
favor_rare_verbs: Integer,
|
|
25
|
-
self_consistency_checks: [TrueClass, FalseClass],
|
|
26
|
-
shrink_budget: Integer,
|
|
27
|
-
yield_weight_seconds: Integer,
|
|
28
|
-
yield_decay_percent: Integer,
|
|
29
|
-
rotation_stale_floor_seconds: Integer,
|
|
30
|
-
persistence_parity_seed_cap: Integer,
|
|
31
|
-
concurrency_seed_cap: Integer,
|
|
32
|
-
adapter_parity_pairs: Hash,
|
|
33
|
-
modes: Hash,
|
|
34
|
-
role_draw_probability: Numeric,
|
|
35
|
-
dry_run_fraction: Numeric,
|
|
36
|
-
generated_domains_per_tick: Integer,
|
|
37
|
-
generated_domains_rust: [TrueClass, FalseClass],
|
|
38
|
-
generated_domain_seeds: Integer,
|
|
39
|
-
structural_refusal_boundary: Array
|
|
11
|
+
# The classes a value of each `type` of a `QaDial` row must be an instance of. Booleans list
|
|
12
|
+
# both TrueClass and FalseClass; Numeric admits `0` as well as `0.0`.
|
|
13
|
+
TYPE_CLASSES = {
|
|
14
|
+
"Integer" => [Integer], "Numeric" => [Numeric], "Boolean" => [TrueClass, FalseClass],
|
|
15
|
+
"String" => [String], "Array" => [Array], "Hash" => [Hash]
|
|
40
16
|
}.freeze
|
|
41
17
|
|
|
18
|
+
# Accessor name to the classes a valid value must be an instance of, one entry per `QaDial`
|
|
19
|
+
# row of the Vocabulary chapter, where each dial is declared with its type and its meaning.
|
|
20
|
+
EXPECTED_TYPES = Hecks::Vocabulary.rows("QaDial").to_h do |dial|
|
|
21
|
+
[dial.fetch("name").to_sym, TYPE_CLASSES.fetch(dial.fetch("type"))]
|
|
22
|
+
end.freeze
|
|
23
|
+
|
|
42
24
|
attr_reader(*EXPECTED_TYPES.keys)
|
|
43
25
|
|
|
44
26
|
# The one real file, resolved from this file's `__dir__` rather than the bluebook's: the
|
|
@@ -68,8 +50,7 @@ module Hecks
|
|
|
68
50
|
end
|
|
69
51
|
end
|
|
70
52
|
|
|
71
|
-
# @param raw [Hash] parsed YAML settings keyed by symbol, one entry per
|
|
72
|
-
# `EXPECTED_TYPES`
|
|
53
|
+
# @param raw [Hash] parsed YAML settings keyed by symbol, one entry per `QaDial` row
|
|
73
54
|
# @param path [String] path to the settings file, used only in error messages
|
|
74
55
|
# @raise [ArgumentError] if `raw` is missing a required key, declares an unknown
|
|
75
56
|
# key, or gives a value the wrong type for its dial
|
|
@@ -81,7 +62,7 @@ module Hecks
|
|
|
81
62
|
if extra.any?
|
|
82
63
|
raise ArgumentError,
|
|
83
64
|
"#{path} declares unknown key(s) #{extra.sort.join(', ')} — " \
|
|
84
|
-
"
|
|
65
|
+
"no QaDial row of the Vocabulary chapter declares them"
|
|
85
66
|
end
|
|
86
67
|
|
|
87
68
|
EXPECTED_TYPES.each do |key, expected|
|
data/lib/hecks/gate/stages.yml
CHANGED
|
@@ -9,6 +9,16 @@
|
|
|
9
9
|
#
|
|
10
10
|
# `env` is set only where the caller has not set it: a push check has no use for the Hecks
|
|
11
11
|
# domain's database, so it runs on Memory, as a pull-request run does in CI.
|
|
12
|
+
#
|
|
13
|
+
# A check a second stage repeats is written once, under an anchor (`&name`), and aliased
|
|
14
|
+
# (`*name`) where the other stage takes it, so the two stages cannot drift apart.
|
|
15
|
+
#
|
|
16
|
+
# The `ci` stage is the checks the CI workflow `ci-checks.yml` holds. Each of its steps runs one
|
|
17
|
+
# job's checks with `hecks gate_run.gate stage=ci only=<ids> --wait`, so the commands live here and
|
|
18
|
+
# the workflow keeps only which job runs which ids and what each step is called;
|
|
19
|
+
# spec/gate_ci_stage_spec.rb pins that every check is run by exactly one step. Select checks
|
|
20
|
+
# with `only=`: several of them write generated files and compare, so the stage is not meant to
|
|
21
|
+
# run whole in one checkout.
|
|
12
22
|
pre_push:
|
|
13
23
|
env:
|
|
14
24
|
HECKS_ENVIRONMENT: memory
|
|
@@ -17,7 +27,8 @@ pre_push:
|
|
|
17
27
|
title: rspec (parallel)
|
|
18
28
|
run: [bundle, exec, parallel_rspec, spec, -n, "{workers}"]
|
|
19
29
|
blocked: the suite is red.
|
|
20
|
-
-
|
|
30
|
+
- &check_fuzzing
|
|
31
|
+
id: fuzzing
|
|
21
32
|
title: spec/fuzzing, which the parallel run above excludes
|
|
22
33
|
run: [bundle, exec, rspec, spec/fuzzing, --tag, fuzzing]
|
|
23
34
|
blocked: spec/fuzzing is red.
|
|
@@ -25,31 +36,125 @@ pre_push:
|
|
|
25
36
|
title: adapter agreement, one declared query and every engine giving the same answer
|
|
26
37
|
run: [bundle, exec, rspec, spec/adapters/query_agreement_spec.rb, --tag, io]
|
|
27
38
|
blocked: the engines disagree about a declared query; one of them is wrong, and a self-consistent adapter spec cannot tell which.
|
|
28
|
-
-
|
|
39
|
+
- &check_engine_agreement
|
|
40
|
+
id: engine_agreement
|
|
29
41
|
title: engine agreement, every declared comparator on both engines
|
|
30
42
|
run: [bundle, exec, exe/hecks, conformance_run.check_engine_agreement, --wait]
|
|
31
43
|
blocked: a comparator is missing a shared case or a cross-engine spec.
|
|
32
|
-
-
|
|
44
|
+
- &check_model_check
|
|
45
|
+
id: model_check
|
|
33
46
|
title: model check, unreachable states, dead transitions and orphaned handlers
|
|
34
47
|
run: [bundle, exec, exe/hecks, model_check, --wait]
|
|
35
48
|
blocked: the model checker found something.
|
|
36
|
-
-
|
|
49
|
+
- &check_doc_coverage
|
|
50
|
+
id: doc_coverage
|
|
37
51
|
title: docs coverage, words with no running example
|
|
38
52
|
run: [bundle, exec, exe/hecks, conformance_run.measure_doc_coverage, --wait]
|
|
39
53
|
blocked: the language ships something undocumented; write the missing sections and examples.
|
|
40
|
-
-
|
|
54
|
+
- &check_rubocop
|
|
55
|
+
id: rubocop
|
|
41
56
|
title: rubocop, the static style and correctness gate
|
|
42
57
|
run: [bundle, exec, rubocop, -c, .rubocop.yml]
|
|
43
58
|
blocked: rubocop found an offense.
|
|
44
|
-
-
|
|
59
|
+
- &check_codegen_drift
|
|
60
|
+
id: codegen_drift
|
|
45
61
|
title: codegen drift, the committed rust/src/generated/ must match a fresh regeneration
|
|
46
62
|
run: [bundle, exec, exe/hecks, regeneration_run.regenerate_corpus, --check, --wait]
|
|
47
63
|
blocked: regenerating hits a diff, so the committed Rust is stale; run `hecks regenerate_corpus --confirm` and commit the result.
|
|
48
|
-
-
|
|
64
|
+
- &check_comment_style
|
|
65
|
+
id: comment_style
|
|
49
66
|
title: comment style, every comment under lib/hecks against docs/COMMENT_STYLE_GUIDE.md
|
|
50
67
|
run: [bundle, exec, exe/hecks, style_run.check_comments, paths=lib/hecks, --wait]
|
|
51
68
|
blocked: a comment breaks the style guide, such as a line past 100 characters; `hecks fix_comments` rewrites the mechanical ones, and the rest are shortened by hand. CI runs this same check.
|
|
52
|
-
-
|
|
69
|
+
- &check_comment_blocks
|
|
70
|
+
id: comment_blocks
|
|
53
71
|
title: comment blocks, no run of comment lines past ADR 0075's cap
|
|
54
72
|
run: [bundle, exec, exe/hecks, style_run.check_comments, "paths=lib,spec,examples", only=long_block, --wait]
|
|
55
73
|
blocked: a comment block ran past the cap; shorten it.
|
|
74
|
+
|
|
75
|
+
# The checks `.githooks/post-commit` runs after each commit. A red one is reported and never
|
|
76
|
+
# blocks the commit; pre_push runs it again as the backstop.
|
|
77
|
+
post_commit:
|
|
78
|
+
env:
|
|
79
|
+
HECKS_ENVIRONMENT: memory
|
|
80
|
+
checks:
|
|
81
|
+
- *check_fuzzing
|
|
82
|
+
|
|
83
|
+
# The checks `.github/workflows/ci-checks.yml` runs. It sets no `env`: the job's journal choice
|
|
84
|
+
# (the `hecks-environment` action) reaches every check, as it reached each command before.
|
|
85
|
+
ci:
|
|
86
|
+
checks:
|
|
87
|
+
- *check_model_check
|
|
88
|
+
- *check_engine_agreement
|
|
89
|
+
- *check_doc_coverage
|
|
90
|
+
- *check_rubocop
|
|
91
|
+
- *check_comment_style
|
|
92
|
+
- *check_comment_blocks
|
|
93
|
+
- *check_codegen_drift
|
|
94
|
+
- id: lint_deploy_recipes
|
|
95
|
+
title: generated deploy recipes, a command with no checked exit status or no echo
|
|
96
|
+
run: [bundle, exec, exe/hecks, deploy, makefile_check.lint, --wait]
|
|
97
|
+
blocked: a generated deploy Makefile has an unchecked command, or real AWS or database work with no echo telling the person running it what is about to happen.
|
|
98
|
+
- id: fuzz
|
|
99
|
+
title: fuzz, every example domain on the Memory adapter
|
|
100
|
+
run: [bundle, exec, exe/hecks, fuzz_run.fuzz, --wait]
|
|
101
|
+
blocked: a property, an interpreter crash or a determinism check failed on a replayed sequence; the seed is in the output.
|
|
102
|
+
- id: fuzz_sqlite
|
|
103
|
+
title: fuzz on a real on-disk SQLite database
|
|
104
|
+
run: [bundle, exec, exe/hecks, fuzz_run.fuzz, adapter=sqlite, seeds=6, --wait]
|
|
105
|
+
blocked: the SQLite adapter broke a property the Memory adapter held; seeds are 1..N, so the run repeats.
|
|
106
|
+
- id: fuzz_postgres
|
|
107
|
+
title: fuzz on real Postgres, one worker (every ephemeral boot recreates the same scratch schema)
|
|
108
|
+
run: [bundle, exec, exe/hecks, fuzz_run.fuzz, adapter=postgres, seeds=3, steps=20, --wait]
|
|
109
|
+
blocked: the Postgres adapter broke a property the Memory adapter held; it needs PGHOST, PGUSER and PGPASSWORD.
|
|
110
|
+
- id: vocabulary_drift
|
|
111
|
+
title: vocabulary, the reserved-name tables regenerated must match the committed ones
|
|
112
|
+
run:
|
|
113
|
+
- sh
|
|
114
|
+
- -ec
|
|
115
|
+
- |
|
|
116
|
+
bundle exec exe/hecks language_run.project_vocabulary --confirm --wait
|
|
117
|
+
bundle exec exe/hecks language_run.project_reserved_names --confirm --wait
|
|
118
|
+
git diff --exit-code -- lib/hecks/vocabulary.rb rust/codegen/src/reserved_names.rs rust/build/src/reserved_names.rs
|
|
119
|
+
blocked: a generated vocabulary file is stale; the check rewrote it, so commit the result.
|
|
120
|
+
- id: ci_gates
|
|
121
|
+
title: ci gates, the CiGate rows must match the marked regions of the workflows
|
|
122
|
+
run: [bundle, exec, exe/hecks, regeneration_run.project_ci_gates, --wait]
|
|
123
|
+
blocked: a CiGate row changed with no projection, or a region was edited by hand; run `hecks regeneration_run.project_ci_gates --confirm`.
|
|
124
|
+
- id: tools_doc
|
|
125
|
+
title: tools doc, docs/tools.md must match the RetiredScript rows
|
|
126
|
+
run: [bundle, exec, exe/hecks, regeneration_run.project_tools_doc, --wait]
|
|
127
|
+
blocked: a command's arguments changed with no regenerated doc; run `hecks regeneration_run.project_tools_doc --confirm`.
|
|
128
|
+
- id: rust_coverage
|
|
129
|
+
title: rust coverage, every generated module
|
|
130
|
+
run: [bundle, exec, exe/hecks, query, corpus_run.corpus_rust_coverage, --wait]
|
|
131
|
+
blocked: a construct a domain declares has no generated Rust, or an entry on RUST_COVERAGE_PENDING is now fixed and must be deleted.
|
|
132
|
+
- id: rust_coverage_codegen
|
|
133
|
+
title: rust coverage against hecks-codegen's own manifest, pizzas governance identity banking meta
|
|
134
|
+
run:
|
|
135
|
+
- sh
|
|
136
|
+
- -ec
|
|
137
|
+
- |
|
|
138
|
+
for module in pizzas governance identity banking meta; do
|
|
139
|
+
bundle exec exe/hecks query build.rust_coverage module_name=$module codegen=rust --wait
|
|
140
|
+
done
|
|
141
|
+
blocked: hecks-codegen generated nothing for a construct a domain declares.
|
|
142
|
+
- id: kernel_coverage
|
|
143
|
+
title: rust kernel coverage, every capability variant has a kernel file
|
|
144
|
+
run: [bundle, exec, exe/hecks, kernel_run.measure_kernel_coverage, --wait]
|
|
145
|
+
blocked: a capability's enum variant has no rust/src/kernel file behind it.
|
|
146
|
+
- id: rust_vocabulary_drift
|
|
147
|
+
title: rust vocabulary, the kernel vocab tables regenerated must match the committed ones
|
|
148
|
+
run:
|
|
149
|
+
- sh
|
|
150
|
+
- -ec
|
|
151
|
+
- |
|
|
152
|
+
bundle exec exe/hecks language_run.project_rust_vocabulary --confirm --wait
|
|
153
|
+
git diff --exit-code -- rust/src/kernel/vocab/
|
|
154
|
+
untracked="$(git status --porcelain --untracked-files=all -- rust/src/kernel/vocab/)"
|
|
155
|
+
if [ -n "$untracked" ]; then
|
|
156
|
+
echo "rust/src/kernel/vocab has uncommitted generated files:" >&2
|
|
157
|
+
echo "$untracked" >&2
|
|
158
|
+
exit 1
|
|
159
|
+
fi
|
|
160
|
+
blocked: the generated kernel vocab tables are stale or untracked; the check rewrote them, so commit the result.
|
|
@@ -16,7 +16,7 @@ module Hecks
|
|
|
16
16
|
# files reports drift and writes only when it is confirmed.
|
|
17
17
|
module Regeneration
|
|
18
18
|
# Every operation this family carries out.
|
|
19
|
-
OPERATIONS = %w[regenerate_corpus project_ci_gates decide_ci_gate].freeze
|
|
19
|
+
OPERATIONS = %w[regenerate_corpus project_ci_gates project_tools_doc decide_ci_gate].freeze
|
|
20
20
|
|
|
21
21
|
# How many lines of a script's output a refusal keeps.
|
|
22
22
|
KEPT_LINES = 60
|
|
@@ -37,6 +37,7 @@ module Hecks
|
|
|
37
37
|
check = flag.call(:check) || !flag.call(:confirm)
|
|
38
38
|
child = RubyChild.new(tree)
|
|
39
39
|
return project_ci_gates(child, check) if operation == "project_ci_gates"
|
|
40
|
+
return project_tools_doc(child, check) if operation == "project_tools_doc"
|
|
40
41
|
return decide_ci_gate(child, held) if operation == "decide_ci_gate"
|
|
41
42
|
|
|
42
43
|
result = child.capture("regen_codegen_domains", *("--check" if check))
|
|
@@ -53,6 +54,14 @@ module Hecks
|
|
|
53
54
|
child.answer("project_ci_gates", *("--check" if check))
|
|
54
55
|
end
|
|
55
56
|
|
|
57
|
+
# @param child [RubyChild] the checkout's tool runner
|
|
58
|
+
# @param check [Boolean] whether to only compare
|
|
59
|
+
# @return [String] what the tool printed
|
|
60
|
+
# @raise [ConsoleCapture::Failure] with the stale sections, when a check finds drift
|
|
61
|
+
def project_tools_doc(child, check)
|
|
62
|
+
child.answer("project_tools_doc", *("--check" if check))
|
|
63
|
+
end
|
|
64
|
+
|
|
56
65
|
# @param child [RubyChild] the checkout's tool runner
|
|
57
66
|
# @param held [Hash] the record's fields: `gate`, a `CiGate` row's name
|
|
58
67
|
# @return [String] `touched=true` or `touched=false`
|