hecks 3.2.1 → 3.4.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 +21 -23
- 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/interview_draft.rb +88 -33
- data/lib/hecks/cli/interview_session.rb +77 -26
- data/lib/hecks/cli/interview_shape.rb +198 -0
- data/lib/hecks/corpus.rb +3 -5
- data/lib/hecks/doors/cli_runner.rb +14 -1
- data/lib/hecks/doors/launcher_options.rb +13 -3
- data/lib/hecks/doors/mcp_door_scope.rb +2 -1
- data/lib/hecks/doors.rb +0 -17
- 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/isolated_boot.rb +1 -4
- data/lib/hecks/fuzzing/qa_settings.rb +15 -34
- data/lib/hecks/fuzzing/target_capabilities.rb +1 -1
- data/lib/hecks/gate/stages.yml +113 -8
- data/lib/hecks/hecks/adapters/codebase/regeneration.rb +10 -1
- 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/site_toolchain.rb +67 -0
- 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 +213 -0
- data/lib/hecks/language/hecksagon/hecksagon.bluebook +0 -5
- data/lib/hecks/language/oidc.json +5 -0
- 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 +17 -4
- 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/smoke-after-deploy.sh.tmpl +271 -0
- data/lib/hecks/projections/deploy/box.rb +15 -2
- 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/live_edge.rb +162 -0
- 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/loader.rb +6 -7
- 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/runtime.rb +6 -11
- data/lib/hecks/rust_build/host.rb +147 -0
- data/lib/hecks/rust_build/kernel_input.rb +40 -18
- data/lib/hecks/rust_build/project_rust/codegen_run.rb +0 -18
- data/lib/hecks/rust_build/project_rust.rb +3 -10
- data/lib/hecks/rust_build.rb +1 -0
- data/lib/hecks/site/bluebook/site.bluebook +53 -1
- data/lib/hecks/site/bluebook/site.ports.hecksagon +6 -0
- data/lib/hecks/sme/bluebook/sme.bluebook +128 -1
- data/lib/hecks/tools/gate.rb +1 -1
- data/lib/hecks/tools/rust_comment_style.rb +1 -3
- 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 +151 -0
- data/lib/hecks.rb +6 -12
- data/qa/settings.yml +1 -2
- data/rust/build/src/lineage_pass.rs +30 -15
- data/rust/build/src/main.rs +4 -2
- data/rust/build/src/pipeline.rs +8 -19
- data/rust/codegen/src/bridging.rs +7 -7
- data/rust/codegen/src/json.rs +214 -23
- data/rust/codegen/src/literal.rs +6 -3
- data/rust/codegen/src/main.rs +20 -9
- data/rust/codegen/src/naming.rs +4 -2
- data/rust/codegen/src/optional_pass.rs +226 -0
- data/rust/{build → codegen}/src/sidecars.rs +9 -5
- 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 +99 -2
- 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 -4
- data/rust/parser/src/lex.rs +1 -1
- data/rust/parser/src/main.rs +1 -0
- data/rust/parser/src/parse/command.rs +2 -44
- data/rust/parser/src/parse/hecksagon.rs +15 -24
- 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/kernel/cli.rs +16 -6
- data/rust/src/kernel/needs.rs +104 -9
- metadata +22 -9
- data/lib/hecks/facade.rb +0 -31
- 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
|
@@ -147,7 +147,7 @@ module Hecks
|
|
|
147
147
|
deployed_to("AwsLambda") do
|
|
148
148
|
...
|
|
149
149
|
database "Shared"
|
|
150
|
-
owner "
|
|
150
|
+
owner "Core"
|
|
151
151
|
end
|
|
152
152
|
|
|
153
153
|
naming the already-deployed domain whose Postgres instance this one borrows.
|
|
@@ -273,7 +273,7 @@ module Hecks
|
|
|
273
273
|
# GOOGLE_CLIENT_ID/GOOGLE_CLIENT_SECRET/GOOGLE_REDIRECT_URI
|
|
274
274
|
# deliberately NOT set here — #{domain}'s own .env.local has
|
|
275
275
|
# no real GOOGLE_CLIENT_ID yet, matching Fly's own current
|
|
276
|
-
# reality (
|
|
276
|
+
# reality (its secrets list holds only
|
|
277
277
|
# DATABASE_URL and SESSION_SECRET) that nobody has configured
|
|
278
278
|
# OAuth for this app at all. Drop real credentials into
|
|
279
279
|
# .env.local and regenerate to wire this up.
|
|
@@ -410,7 +410,7 @@ module Hecks
|
|
|
410
410
|
# `stack_name` above exactly — one name, two places it's read
|
|
411
411
|
# from, never out of sync since both derive from `infra_name`.
|
|
412
412
|
FunctionName: #{stack_name}
|
|
413
|
-
# EXPLICIT, not omitted -- `sam build
|
|
413
|
+
# EXPLICIT, not omitted -- `sam build WebFunction` and
|
|
414
414
|
# `sam build --use-container WebFunction` run as TWO SEPARATE
|
|
415
415
|
# single-resource invocations (deploy:'s own comment on why:
|
|
416
416
|
# cargo-lambda cross-compiles on this host directly, pg's
|
|
@@ -434,7 +434,7 @@ module Hecks
|
|
|
434
434
|
# "." NOT "#{logical_id}" -- SAM's Makefile build workflow
|
|
435
435
|
# requires the Makefile to live INSIDE CodeUri itself
|
|
436
436
|
# (confirmed live: "Makefile not found at
|
|
437
|
-
# .../
|
|
437
|
+
# .../WebFunction/Makefile" when CodeUri named a
|
|
438
438
|
# directory of its own). This project's one Makefile lives at
|
|
439
439
|
# the deploy directory's own root, alongside this template --
|
|
440
440
|
# "." is where it actually is.
|
|
@@ -568,7 +568,7 @@ module Hecks
|
|
|
568
568
|
"SubnetIds: [!Ref #{db_id}SubnetA, !Ref #{db_id}SubnetB]\n SecurityGroupIds: [!Ref #{logical_id}SecurityGroup]"
|
|
569
569
|
end}
|
|
570
570
|
# AWS_IAM by default — this dispatches real domain commands
|
|
571
|
-
# (cap-table/governance-shaped data
|
|
571
|
+
# (cap-table/governance-shaped data);
|
|
572
572
|
# an unauthenticated public URL is the wrong default for that,
|
|
573
573
|
# even though it's the simpler one to demo with. NONE only when
|
|
574
574
|
# `web "Rust"` (rust/host/src/web.rs) makes this the public web
|
|
@@ -586,7 +586,7 @@ module Hecks
|
|
|
586
586
|
# THE WEB APP'S OWN LAMBDA — opt-in, see `web_handler_present` above.
|
|
587
587
|
# VPC-attached for the SAME reason #{logical_id} is (Member's own
|
|
588
588
|
# persistence needs the private RDS instance directly, permanently —
|
|
589
|
-
#
|
|
589
|
+
# the hecksagon's own comment on why), sharing
|
|
590
590
|
# #{logical_id}SecurityGroup rather than minting a second one, since
|
|
591
591
|
# both need the identical egress-to-DB rule. That VPC attachment
|
|
592
592
|
# ALSO cuts off the public internet by default (this stack's own "NO
|
|
@@ -674,7 +674,7 @@ module Hecks
|
|
|
674
674
|
# without reworking that whole from-source pg build for a newer
|
|
675
675
|
# image is a real, separate undertaking (untested container image
|
|
676
676
|
# availability, a different vendor/bundle native-extension path)
|
|
677
|
-
# out of scope here. Matches
|
|
677
|
+
# out of scope here. Matches the live, currently-
|
|
678
678
|
# deployed choice exactly, so both consumers of this one shared
|
|
679
679
|
# WebFunction code path stay on the same, PROVEN runtime.
|
|
680
680
|
Properties:
|
|
@@ -763,7 +763,7 @@ module Hecks
|
|
|
763
763
|
# documents. But THIS function's consumer (`lambda_handler.rb`)
|
|
764
764
|
# is not a file this generator writes or this repo carries --
|
|
765
765
|
# it lives in the deploying app's own domain directory (real,
|
|
766
|
-
# live example:
|
|
766
|
+
# live example: a Sinatra app), outside this
|
|
767
767
|
# codebase entirely. Redeploying a domain with an existing
|
|
768
768
|
# `lambda_handler.rb` BEFORE that handler is updated to fetch
|
|
769
769
|
# these itself (`aws-sdk-secretsmanager`, not yet in this
|
|
@@ -48,7 +48,7 @@ module Hecks
|
|
|
48
48
|
# Fields emitted by `to_h`'s own merge rather than `emits_ir`, for constructs
|
|
49
49
|
# whose shape is not fixed.
|
|
50
50
|
DYNAMIC_TAIL = {
|
|
51
|
-
"Query" => %i[options returns],
|
|
51
|
+
"Query" => %i[options returns needs],
|
|
52
52
|
"ReadModel" => %i[options group_by aggregate_heads count median_field sum_field avg_field
|
|
53
53
|
min_field max_field percentile_field percentile_at any_field all_field]
|
|
54
54
|
}.freeze
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require_relative "edge"
|
|
5
|
+
|
|
6
|
+
module Hecks
|
|
7
|
+
module Projections
|
|
8
|
+
module Site
|
|
9
|
+
# Compares the CloudFront behaviours a site's edge generates with the ones a live
|
|
10
|
+
# distribution has, and says every way they differ. It only reads: the live side is a
|
|
11
|
+
# `get-distribution-config` answer, however it was obtained.
|
|
12
|
+
#
|
|
13
|
+
# A behaviour is identified by its path pattern (`(default)` for the default one) and judged
|
|
14
|
+
# on its origin, allowed and cached methods, viewer protocol, compression and three policy
|
|
15
|
+
# ids. The edge may name a policy by a CloudFormation intrinsic (`!Ref PageCachePolicy`) that
|
|
16
|
+
# the live distribution holds as an id, so a reference is compared through the `refs` the
|
|
17
|
+
# caller gives for it, and one with none is reported as unchecked. The behaviours a change is
|
|
18
|
+
# about to add (`expect_new`) are reported as expected additions, not differences.
|
|
19
|
+
class LiveEdge
|
|
20
|
+
# The default behaviour's name, since it has no path pattern.
|
|
21
|
+
DEFAULT = "(default)"
|
|
22
|
+
|
|
23
|
+
# The fields a behaviour is compared on, besides its path.
|
|
24
|
+
FIELDS = %i[origin methods cached protocol compress cache request response].freeze
|
|
25
|
+
|
|
26
|
+
# What a comparison found.
|
|
27
|
+
#
|
|
28
|
+
# @!attribute [r] expected
|
|
29
|
+
# @return [Array<String>] a line per behaviour only the project has, that was expected
|
|
30
|
+
# @!attribute [r] differences
|
|
31
|
+
# @return [Array<String>] a line per difference that was not expected
|
|
32
|
+
# @!attribute [r] unchecked
|
|
33
|
+
# @return [Array<String>] the policy references no `refs` entry resolves
|
|
34
|
+
# @!attribute [r] counts
|
|
35
|
+
# @return [Array<Integer>] the number of generated and of live behaviours, default aside
|
|
36
|
+
Comparison = Struct.new(:expected, :differences, :unchecked, :counts, keyword_init: true) do
|
|
37
|
+
# @return [Boolean] whether live is as generated, expected additions aside
|
|
38
|
+
def clean? = differences.empty? && unchecked.empty?
|
|
39
|
+
|
|
40
|
+
# @return [String] the report, one section per kind of finding
|
|
41
|
+
def to_s
|
|
42
|
+
lines = ["generated: #{counts[0]} behaviours + default; live: #{counts[1]} + default"]
|
|
43
|
+
lines += section("expected additions (only in the project):", expected)
|
|
44
|
+
lines += section("differences:", differences)
|
|
45
|
+
lines += section("policy references with no refs entry (unchecked):", unchecked)
|
|
46
|
+
lines << "the live distribution matches the project" if clean?
|
|
47
|
+
lines.join("\n")
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
def section(title, entries) = entries.empty? ? [] : [title, *entries.map { |entry| " #{entry}" }]
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# @param edge [Edge] the project's checked edge
|
|
56
|
+
# @param live [Hash] a CloudFront distribution configuration, or an answer wrapping one in
|
|
57
|
+
# `DistributionConfig`
|
|
58
|
+
# @param expect_new [Array<String>] path patterns the project may have and the live one not
|
|
59
|
+
# @param refs [Hash{String => String}] a policy intrinsic to the id it stands for live
|
|
60
|
+
def initialize(edge, live, expect_new: [], refs: {})
|
|
61
|
+
@edge = edge
|
|
62
|
+
@live = live.fetch("DistributionConfig", live)
|
|
63
|
+
@expect_new = expect_new
|
|
64
|
+
@refs = refs
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# @return [Comparison] every way the project and the live distribution differ
|
|
68
|
+
def call
|
|
69
|
+
generated = generated_behaviours
|
|
70
|
+
current = live_behaviours
|
|
71
|
+
unchecked = unresolved(generated.values)
|
|
72
|
+
generated = generated.transform_values { |entry| resolve(entry) }
|
|
73
|
+
expected, differences = missing(generated, current)
|
|
74
|
+
differences.concat(changed(generated, current), order(generated, current))
|
|
75
|
+
Comparison.new(expected: expected, differences: differences, unchecked: unchecked,
|
|
76
|
+
counts: [generated.size - 1, current.size - 1])
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Reads a policy reference mapping written as `!Ref Name=id` words.
|
|
80
|
+
#
|
|
81
|
+
# @param text [String, nil] the words, separated by commas
|
|
82
|
+
# @return [Hash{String => String}] each intrinsic to its id
|
|
83
|
+
def self.refs_from(text)
|
|
84
|
+
text.to_s.split(",").to_h { |pair| pair.strip.split("=", 2).map(&:strip) }
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
private
|
|
88
|
+
|
|
89
|
+
def generated_behaviours
|
|
90
|
+
list = [[DEFAULT, @edge.default_behaviour], *@edge.behaviours.map { |entry| [entry[:path], entry] }]
|
|
91
|
+
list.to_h do |path, entry|
|
|
92
|
+
[path, { path: path, origin: entry[:origin], methods: Array(entry[:methods]).sort, cached: %w[GET HEAD],
|
|
93
|
+
protocol: entry[:viewer_protocol], compress: entry[:compress] == true,
|
|
94
|
+
cache: policy_id(entry[:cache_policy]), request: policy_id(entry[:origin_request_policy]),
|
|
95
|
+
response: policy_id(entry[:response_headers_policy]) }]
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def policy_id(policy) = policy&.first
|
|
100
|
+
|
|
101
|
+
def live_behaviours
|
|
102
|
+
list = [[DEFAULT, @live.fetch("DefaultCacheBehavior")],
|
|
103
|
+
*Array(@live.dig("CacheBehaviors", "Items")).map { |entry| [entry.fetch("PathPattern"), entry] }]
|
|
104
|
+
list.to_h do |path, entry|
|
|
105
|
+
[path, { path: path, origin: entry.fetch("TargetOriginId"), methods: methods_of(entry, "Items"),
|
|
106
|
+
cached: cached_of(entry), protocol: entry.fetch("ViewerProtocolPolicy"),
|
|
107
|
+
compress: entry.fetch("Compress", false) == true, cache: entry["CachePolicyId"],
|
|
108
|
+
request: blank(entry["OriginRequestPolicyId"]), response: blank(entry["ResponseHeadersPolicyId"]) }]
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def methods_of(entry, key) = entry.fetch("AllowedMethods").fetch(key).sort
|
|
113
|
+
|
|
114
|
+
def cached_of(entry) = entry.dig("AllowedMethods", "CachedMethods", "Items").sort
|
|
115
|
+
|
|
116
|
+
def blank(value) = value.to_s.empty? ? nil : value
|
|
117
|
+
|
|
118
|
+
def intrinsic?(value) = value.is_a?(String) && value.start_with?("!")
|
|
119
|
+
|
|
120
|
+
def unresolved(entries)
|
|
121
|
+
entries.flat_map { |entry| entry.values_at(:cache, :request, :response) }
|
|
122
|
+
.select { |value| intrinsic?(value) && !@refs.key?(value) }.uniq.sort
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def resolve(entry) = entry.transform_values { |value| intrinsic?(value) ? @refs.fetch(value, value) : value }
|
|
126
|
+
|
|
127
|
+
def missing(generated, current)
|
|
128
|
+
expected = []
|
|
129
|
+
differences = []
|
|
130
|
+
generated.each do |path, entry|
|
|
131
|
+
next if current.key?(path)
|
|
132
|
+
|
|
133
|
+
detail = entry.except(:path).map { |key, value| "#{key}=#{value.inspect}" }.join(" ")
|
|
134
|
+
(@expect_new.include?(path) ? expected : differences) << "only in the project: #{path} #{detail}"
|
|
135
|
+
end
|
|
136
|
+
current.each_key { |path| differences << "only live: #{path}" unless generated.key?(path) }
|
|
137
|
+
[expected, differences]
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def changed(generated, current)
|
|
141
|
+
generated.flat_map do |path, entry|
|
|
142
|
+
next [] unless current.key?(path)
|
|
143
|
+
|
|
144
|
+
FIELDS.filter_map do |field|
|
|
145
|
+
next if entry[field] == current[path][field]
|
|
146
|
+
|
|
147
|
+
"#{path} differs in #{field}: project=#{entry[field].inspect} live=#{current[path][field].inspect}"
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def order(generated, current)
|
|
153
|
+
mine = generated.keys.reject { |path| path == DEFAULT || !current.key?(path) }
|
|
154
|
+
theirs = current.keys.reject { |path| path == DEFAULT || !generated.key?(path) }
|
|
155
|
+
return [] if mine == theirs
|
|
156
|
+
|
|
157
|
+
["order differs among the behaviours both have:\n project: #{mine.join(' ')}\n live: #{theirs.join(' ')}"]
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
|
@@ -64,20 +64,6 @@ module Hecks
|
|
|
64
64
|
raise Refusal, "the working tree at #{@repo_dir} is dirty — commit or discard first:\n#{dirty}"
|
|
65
65
|
end
|
|
66
66
|
|
|
67
|
-
# Refuses one more pull request than the day's cap allows; a cap of zero is no cap.
|
|
68
|
-
#
|
|
69
|
-
# @param opened_today [Integer] how many PRs the ledger recorded since local midnight
|
|
70
|
-
# @param cap [Integer] `PR_CAP_PER_DAY`
|
|
71
|
-
# @return [void]
|
|
72
|
-
# @raise [Refusal] when `opened_today` has reached a positive `cap`
|
|
73
|
-
def assert_under_daily_cap!(opened_today:, cap:)
|
|
74
|
-
return unless cap.positive? && opened_today >= cap
|
|
75
|
-
|
|
76
|
-
raise Refusal, "#{opened_today} PR(s) already opened since local midnight, and " \
|
|
77
|
-
"QualityControlDials::PR_CAP_PER_DAY is #{cap} — leave this as an open Bug/Angle " \
|
|
78
|
-
"and open it tomorrow, or raise the dial in qa/settings.yml"
|
|
79
|
-
end
|
|
80
|
-
|
|
81
67
|
# Refuses a fix commit that is not part of the branch being opened.
|
|
82
68
|
#
|
|
83
69
|
# @param commit [String] the commit the ledger says fixes the bug
|
|
@@ -26,7 +26,7 @@ module Hecks
|
|
|
26
26
|
SKIP_DIR_BASENAMES = %w[.git .hg .svn vendor node_modules .bundle tmp log coverage .yardoc .ruby-lsp
|
|
27
27
|
.parked bower_components .claude dist build].freeze
|
|
28
28
|
|
|
29
|
-
# Word-boundary matched so
|
|
29
|
+
# Word-boundary matched so a near-miss gem name (a separate gem) and "hecks_site" never
|
|
30
30
|
# false-positive as a hecks dependency.
|
|
31
31
|
GEMFILE_PATTERN = /gem\s*\(?\s*["']hecks["']/
|
|
32
32
|
GEMFILE_LOCK_PATTERN = /^\s+hecks\s+\(/
|
|
@@ -15,9 +15,10 @@ module Hecks
|
|
|
15
15
|
# take the record and the `GitPr` adapter agrees. The command's `given`s are the rules, asked
|
|
16
16
|
# as a dry run before anything is opened: the branch starts with the practice's prefix; with
|
|
17
17
|
# `--bug`, the bug is `fixed`; with `--angle`, it is `investigating`. The adapter answers the
|
|
18
|
-
# rest: the tree is clean
|
|
19
|
-
#
|
|
20
|
-
#
|
|
18
|
+
# rest: the tree is clean and the bug's commit is an ancestor of `HEAD`. The per-day cap
|
|
19
|
+
# (`PR_CAP_PER_DAY`, 0 = uncapped) is `DailyQuota.Take`'s own `given`: asked as a dry run with
|
|
20
|
+
# the others, and taken for real once the PR is recorded. It is idempotent. `QA_REPO_DIR`
|
|
21
|
+
# picks the checkout `git` runs in, so a spec can use a throwaway repo and a fake `gh`.
|
|
21
22
|
class QaOpenPr
|
|
22
23
|
EXIT_OK = 0
|
|
23
24
|
|
|
@@ -147,7 +148,10 @@ module Hecks
|
|
|
147
148
|
"reads as picked up before something is built from it)"
|
|
148
149
|
when "the branch is one this practice recognises as its own"
|
|
149
150
|
" (the branch is #{@branch.inspect}; a PR this practice opens is on a branch it can recognise " \
|
|
150
|
-
"as its own
|
|
151
|
+
"as its own)"
|
|
152
|
+
when "the day's cap is not spent"
|
|
153
|
+
" (QualityControlDials::PR_CAP_PER_DAY is #{dial(:PR_CAP_PER_DAY, 0)} — leave this as an open " \
|
|
154
|
+
"Bug/Angle and open it tomorrow, or raise the dial in qa/settings.yml)"
|
|
151
155
|
else ""
|
|
152
156
|
end
|
|
153
157
|
end
|
|
@@ -184,15 +188,23 @@ module Hecks
|
|
|
184
188
|
check_ancestry
|
|
185
189
|
end
|
|
186
190
|
|
|
191
|
+
# The record of the UTC day the clock reads: `DailyQuota.Today` fills the day from the clock.
|
|
192
|
+
def todays_quota = query("DailyQuota.Today").first
|
|
193
|
+
|
|
194
|
+
# A day with no record has spent nothing, so only an opened day can refuse.
|
|
187
195
|
def check_daily_cap
|
|
188
196
|
cap = dial(:PR_CAP_PER_DAY, 0)
|
|
189
|
-
|
|
197
|
+
quota = todays_quota
|
|
198
|
+
return unless cap.positive? && quota
|
|
199
|
+
|
|
200
|
+
refuse_unless_ledger_takes("QualityControl::DailyQuota.Take", today: quota[:today], cap: { value: cap })
|
|
201
|
+
end
|
|
190
202
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
203
|
+
# Counts the PR just recorded against the day, opening the day's record on its first.
|
|
204
|
+
def take_daily_slot
|
|
205
|
+
row = todays_quota
|
|
206
|
+
quota = row ? ::QualityControl::DailyQuota.find(row[:today][:value]) : ::QualityControl::DailyQuota.open!
|
|
207
|
+
quota.take!(cap: { value: dial(:PR_CAP_PER_DAY, 0) })
|
|
196
208
|
end
|
|
197
209
|
|
|
198
210
|
def check_ancestry
|
|
@@ -271,6 +283,7 @@ module Hecks
|
|
|
271
283
|
branch: { value: view[:headRefName] }, commit: { value: sha }, title: { value: view[:title] },
|
|
272
284
|
now: { value: now }
|
|
273
285
|
)
|
|
286
|
+
take_daily_slot
|
|
274
287
|
puts "recorded Patch ##{number} for #{@bug.id} at #{sha[0, 7]}"
|
|
275
288
|
end
|
|
276
289
|
|
|
@@ -282,12 +295,15 @@ module Hecks
|
|
|
282
295
|
end
|
|
283
296
|
|
|
284
297
|
# A run that stopped between `open!` and `land!` leaves the record `opened`: finish it.
|
|
285
|
-
existing
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
298
|
+
unless existing
|
|
299
|
+
::QualityControl::Improvement.open!(
|
|
300
|
+
**(@angle ? { angle: @angle.id } : {}),
|
|
301
|
+
number: { value: number }, url: { value: view[:url] },
|
|
302
|
+
branch: { value: view[:headRefName] }, title: { value: view[:title] },
|
|
303
|
+
now: { value: now }
|
|
304
|
+
)
|
|
305
|
+
take_daily_slot
|
|
306
|
+
end
|
|
291
307
|
::QualityControl::Improvement.find(number).land!(number: { value: number }, commit: { value: sha })
|
|
292
308
|
puts "recorded Improvement ##{number}#{" for #{@angle.id}" if @angle}, landed at #{sha[0, 7]}"
|
|
293
309
|
end
|
|
@@ -54,13 +54,13 @@ module Hecks
|
|
|
54
54
|
"cannot see, because there is no second engine to disagree with. " \
|
|
55
55
|
"spec/rust_conformance_fuzz_spec.rb's own DOMAINS list wires up only pizzas and " \
|
|
56
56
|
"banking for CI-gated differential fuzzing. rust/Cargo.toml already declares " \
|
|
57
|
-
"compiled-binary features for compliance and roster (plus
|
|
57
|
+
"compiled-binary features for compliance and roster (plus meta) that " \
|
|
58
58
|
"sit unused for this purpose; examples/chess and examples/directory have no Cargo " \
|
|
59
59
|
"feature at all — `hecks project_rust` has never regenerated a binary for either. " \
|
|
60
60
|
"Widening either list would let differential fuzzing catch a whole bug class " \
|
|
61
61
|
"Ruby-only fuzzing cannot, on domains the practice already owns.",
|
|
62
62
|
citation: "BUG#1/#3/#4 (engine-divergence bugs); spec/rust_conformance_fuzz_spec.rb DOMAINS " \
|
|
63
|
-
"(pizzas/banking only); rust/Cargo.toml [features] (compliance/roster/
|
|
63
|
+
"(pizzas/banking only); rust/Cargo.toml [features] (compliance/roster/" \
|
|
64
64
|
"meta have binaries and are not wired in; chess/directory have no feature at all)",
|
|
65
65
|
proposer: "Claude QA"
|
|
66
66
|
},
|
|
@@ -12,8 +12,7 @@ module Hecks
|
|
|
12
12
|
# rotation `Target`, printing each one's inferred capabilities. It is idempotent: references
|
|
13
13
|
# already on file are skipped whatever their status (re-`Identify` refuses).
|
|
14
14
|
#
|
|
15
|
-
# It skips `
|
|
16
|
-
# Rust binary) on purpose.
|
|
15
|
+
# It skips `lib/hecks/framework` (no standalone Rust binary) on purpose.
|
|
17
16
|
class QaSeedTargets
|
|
18
17
|
# Identifies the targets the ledger lacks.
|
|
19
18
|
#
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# Quality control: every check writes its expectation down before it looks at the answer.
|
|
2
2
|
# A bug is logged with the failing test that proves it, or it is not logged.
|
|
3
3
|
|
|
4
|
-
# Tunable dials for the QA loop: values in qa/settings.yml,
|
|
4
|
+
# Tunable dials for the QA loop: values in qa/settings.yml, what each is in the `QaDial` rows of the
|
|
5
|
+
# Vocabulary chapter.
|
|
5
6
|
# `require`, not `require_relative`: isolated fuzz boots load a copy of this file from a tmpdir.
|
|
6
7
|
require "hecks/fuzzing/qa_settings"
|
|
7
8
|
|
|
@@ -11,111 +12,15 @@ unless defined?(QualityControlDials)
|
|
|
11
12
|
# boots. HECKS_QA_SETTINGS_PATH lets a subprocess test read a different settings file.
|
|
12
13
|
settings = Hecks::Fuzzing::QaSettings.load(ENV.fetch("HECKS_QA_SETTINGS_PATH", Hecks::Fuzzing::QaSettings::DEFAULT_PATH))
|
|
13
14
|
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
PR_CAP_PER_DAY = settings.pr_cap_per_day
|
|
20
|
-
|
|
21
|
-
# Fuzz depth by `Target.clean_streak`, in three discrete tiers, read by
|
|
22
|
-
# `SweepDepth.for_streak`. The last row (`upto: Float::INFINITY`) is the ceiling.
|
|
23
|
-
WIDENING_TIERS = settings.widening_tiers
|
|
24
|
-
|
|
25
|
-
# How many `hecks quality_control query sweep.run <target>` children `--all` keeps running at once.
|
|
26
|
-
SWEEP_MAX_PARALLEL = settings.sweep_max_parallel
|
|
27
|
-
|
|
28
|
-
# How long the `/loop hecks_qa` caller waits for a tick's report before assuming it stalled.
|
|
29
|
-
LIVENESS_FALLBACK_SECONDS = settings.liveness_fallback_seconds
|
|
15
|
+
# Each dial of qa/settings.yml as a constant named for it: `PR_CAP_PER_DAY` is `pr_cap_per_day`.
|
|
16
|
+
# What each is and takes is declared once, as its `QaDial` row in the Vocabulary chapter.
|
|
17
|
+
Hecks::Vocabulary.rows("QaDial").each do |dial|
|
|
18
|
+
const_set(dial.fetch("name").upcase, settings.public_send(dial.fetch("name")))
|
|
19
|
+
end
|
|
30
20
|
|
|
31
21
|
# The identity the loop runs as. `WaivedBy` repeats this literal (invariants read literals,
|
|
32
22
|
# not constants); `spec/quality_control_spec.rb` pins the two equal.
|
|
33
23
|
AUTOMATED_ENGINEER = "qa_sweep"
|
|
34
|
-
|
|
35
|
-
# How `hecks quality_control patch.open` opens a PR: not a draft, queued for auto-merge, on a branch that starts
|
|
36
|
-
# with BRANCH_PREFIX.
|
|
37
|
-
DRAFT_ONLY = settings.draft_only
|
|
38
|
-
AUTO_MERGE = settings.auto_merge
|
|
39
|
-
BRANCH_PREFIX = settings.branch_prefix
|
|
40
|
-
|
|
41
|
-
# Fraction (0.0..1.0) of generated command steps mutated adversarially; 0 turns the layer off.
|
|
42
|
-
# Higher finds refusal-ordering splits sooner but reaches deep state less often.
|
|
43
|
-
ADVERSARIAL_FRACTION = settings.adversarial_fraction
|
|
44
|
-
|
|
45
|
-
# Coverage-guided generation across one sweep's seeds (`Hecks::Fuzzing::CoverageCampaign`).
|
|
46
|
-
GUIDED_GENERATION = settings.guided_generation
|
|
47
|
-
|
|
48
|
-
# Fraction (0.0..1.0) of seeds that start from a prefix of an earlier seed's new coverage.
|
|
49
|
-
# Off by default: measured, it trades breadth for depth (banking reached 1.41x the deep-state
|
|
50
|
-
# tuples, chess lost 0.12x of all tuples), while favoring rare verbs alone never lost.
|
|
51
|
-
CORPUS_SPLICE_PROBABILITY = settings.corpus_splice_probability
|
|
52
|
-
|
|
53
|
-
# How many of the least-hit verbs the next seed weights up; 0 turns the lever off.
|
|
54
|
-
FAVOR_RARE_VERBS = settings.favor_rare_verbs
|
|
55
|
-
|
|
56
|
-
# Whether `hecks quality_control query sweep.run` also runs the self-consistency pass (cold rehydration, replay
|
|
57
|
-
# idempotency, value-object JSON round trip) on the same generated sequence.
|
|
58
|
-
SELF_CONSISTENCY_CHECKS = settings.self_consistency_checks
|
|
59
|
-
|
|
60
|
-
# Candidate replays `hecks quality_control query sweep.run` may spend shrinking one surprised check; 0 turns shrinking
|
|
61
|
-
# off. `concurrency` is never shrunk: a race does not reproduce on demand.
|
|
62
|
-
SHRINK_BUDGET = settings.shrink_budget
|
|
63
|
-
|
|
64
|
-
# Seconds of staleness that one point of yield score is worth in `RotationPriority.pick`.
|
|
65
|
-
# Zero would make yield invisible, so pick a number that lets it matter.
|
|
66
|
-
YIELD_WEIGHT_SECONDS = settings.yield_weight_seconds
|
|
67
|
-
|
|
68
|
-
# Percent of the prior yield score kept at each release, so recent findings dominate.
|
|
69
|
-
# Applied by the caller: the mutation language cannot express "decay, then add" in `Release`.
|
|
70
|
-
YIELD_DECAY_PERCENT = settings.yield_decay_percent
|
|
71
|
-
|
|
72
|
-
# Once a waiting target is this stale, `RotationPriority.pick` takes it whatever its yield
|
|
73
|
-
# score, so a quiet target cannot starve.
|
|
74
|
-
ROTATION_STALE_FLOOR_SECONDS = settings.rotation_stale_floor_seconds
|
|
75
|
-
|
|
76
|
-
# Ceiling on `--seeds` for `--persistence-parity`, which pays for real Postgres per dispatch.
|
|
77
|
-
# `hecks quality_control query sweep.run` clamps down, never up, and prints a note.
|
|
78
|
-
PERSISTENCE_PARITY_SEED_CAP = settings.persistence_parity_seed_cap
|
|
79
|
-
|
|
80
|
-
# Ceiling on `--seeds` for the concurrency mode, which needs two disposable schemas per seed.
|
|
81
|
-
CONCURRENCY_SEED_CAP = settings.concurrency_seed_cap
|
|
82
|
-
|
|
83
|
-
# The two adapters each adapter-parity mode compares, by mode name; a new pairing is a new
|
|
84
|
-
# entry. `adapter_parity_sqlite` needs no `database:`/`schema:` because sqlite has no
|
|
85
|
-
# disposable database.
|
|
86
|
-
ADAPTER_PARITY_PAIRS = settings.adapter_parity_pairs
|
|
87
|
-
|
|
88
|
-
# Which comparison modes `hecks quality_control query sweep.run` may run: the enabled ones intersected with what
|
|
89
|
-
# `TargetCapabilities.infer(path)` finds in the target. `--modes a,b,c` overrides for one run.
|
|
90
|
-
#
|
|
91
|
-
# `wasm_front` and `adapter_parity_postgres` are off because nothing runs them; the others are
|
|
92
|
-
# built and off because their I/O cost across the rotation is unmeasured. `hecks quality_control query sweep.run`
|
|
93
|
-
# refuses to start when a mode outside `TargetCapabilities::RUNNABLE_MODES` is enabled.
|
|
94
|
-
MODES = settings.modes
|
|
95
|
-
|
|
96
|
-
# How often a generated role-gated command step carries a drawn caller (`role:`/`actor_id:`);
|
|
97
|
-
# 0 turns the draw off.
|
|
98
|
-
ROLE_DRAW_PROBABILITY = settings.role_draw_probability
|
|
99
|
-
|
|
100
|
-
# Fraction of generated command steps offered as a dry run instead of a real dispatch; 0 is
|
|
101
|
-
# off. Kept low: a dry run creates no state for later steps to act on.
|
|
102
|
-
DRY_RUN_FRACTION = settings.dry_run_fraction
|
|
103
|
-
|
|
104
|
-
# How many generated domains one `hecks quality_control sweep.tick` checks after the rotation sweep; 0 skips the
|
|
105
|
-
# step.
|
|
106
|
-
GENERATED_DOMAINS_PER_TICK = settings.generated_domains_per_tick
|
|
107
|
-
|
|
108
|
-
# Whether generated domains are also compared against a compiled Rust binary built in a scratch
|
|
109
|
-
# copy.
|
|
110
|
-
GENERATED_DOMAINS_RUST = settings.generated_domains_rust
|
|
111
|
-
|
|
112
|
-
# Generated sequences run per generated domain.
|
|
113
|
-
GENERATED_DOMAIN_SEEDS = settings.generated_domain_seeds
|
|
114
|
-
|
|
115
|
-
# Construct families a skipped query verb may belong to. A skip inside the list is logged Held,
|
|
116
|
-
# one outside it Surprised. Shrink-only: spec/fuzzing/rust_gap_manifest_spec.rb fails when a
|
|
117
|
-
# family here is declared by no committed manifest.
|
|
118
|
-
STRUCTURAL_REFUSAL_BOUNDARY = settings.structural_refusal_boundary
|
|
119
24
|
end
|
|
120
25
|
end
|
|
121
26
|
|
|
@@ -1726,8 +1631,7 @@ Hecks.bluebook "QualityControl" do
|
|
|
1726
1631
|
attribute :branch, PatchBranch
|
|
1727
1632
|
attribute :commit, CommitRef
|
|
1728
1633
|
attribute :title, PatchTitle
|
|
1729
|
-
#
|
|
1730
|
-
# epoch, which the cap never counts.
|
|
1634
|
+
# When the PR was opened, in epoch seconds. `default: 0` hydrates older rows at the epoch.
|
|
1731
1635
|
attribute :opened_at, Instant
|
|
1732
1636
|
|
|
1733
1637
|
value_object "Instant" do
|
|
@@ -1775,8 +1679,7 @@ Hecks.bluebook "QualityControl" do
|
|
|
1775
1679
|
# the record is written by the process that made it true. `now` is that script's clock read.
|
|
1776
1680
|
# The script asks `dry_run?` of this command before it opens anything, so the two `given`s are
|
|
1777
1681
|
# the rules for opening a PR at all: the branch is one this practice recognises as its own, and
|
|
1778
|
-
# the bug it fixes is fixed. The prefix
|
|
1779
|
-
# repeats `AUTOMATED_ENGINEER`; `spec/quality_control_spec.rb` pins the two equal.
|
|
1682
|
+
# the bug it fixes is fixed. The prefix is declared here, once.
|
|
1780
1683
|
command "Open" do
|
|
1781
1684
|
role "QA engineer"
|
|
1782
1685
|
goal "Record that a fix has been opened as a pull request, the moment its number, branch and commit are already known"
|
|
@@ -1828,21 +1731,78 @@ Hecks.bluebook "QualityControl" do
|
|
|
1828
1731
|
order_by :number
|
|
1829
1732
|
end
|
|
1830
1733
|
|
|
1831
|
-
# `hecks quality_control patch.open` compares this and `Improvement.OpenedSince` counts, from local midnight,
|
|
1832
|
-
# against `PR_CAP_PER_DAY`. `gte` on a bound argument, the `Account.AtOrAbove` shape.
|
|
1833
|
-
query "OpenedSince" do
|
|
1834
|
-
description "Every patch opened at or after one instant, in number order — what today's count against PR_CAP_PER_DAY is read from."
|
|
1835
|
-
attribute :since, Instant
|
|
1836
|
-
where(:"opened_at.value" => { gte: :since })
|
|
1837
|
-
order_by :number
|
|
1838
|
-
end
|
|
1839
|
-
|
|
1840
1734
|
query "All" do
|
|
1841
1735
|
description "Every patch ever opened — what a runner reads back to reconcile against GitHub's own list."
|
|
1842
1736
|
order_by :number
|
|
1843
1737
|
end
|
|
1844
1738
|
end
|
|
1845
1739
|
|
|
1740
|
+
# How many pull requests the practice has opened on one UTC day, and the rule that the day's cap
|
|
1741
|
+
# is not exceeded. A rule across many records belongs to an aggregate that owns it (ADR 0081): the
|
|
1742
|
+
# cap is a `given` on one record, not a count over `Patch` and `Improvement`, and a record saves
|
|
1743
|
+
# atomically, so two runs cannot both take the last slot. Identified by the day, so a new day is
|
|
1744
|
+
# a new record. `hecks quality_control patch.open` asks `Today` for the day's record, opens it
|
|
1745
|
+
# with `Open` when there is none, and takes a slot with `Take` once a PR is open.
|
|
1746
|
+
aggregate "DailyQuota" do
|
|
1747
|
+
description "One UTC day's count of the pull requests the practice has opened, and the rule that the day's cap is not exceeded."
|
|
1748
|
+
|
|
1749
|
+
identified_by :today
|
|
1750
|
+
|
|
1751
|
+
# Whole days since the epoch, the fact the runtime calls `today`.
|
|
1752
|
+
attribute :today, Day
|
|
1753
|
+
attribute :used, SlotsUsed
|
|
1754
|
+
|
|
1755
|
+
value_object "Day" do
|
|
1756
|
+
attribute :value, Integer, default: 0
|
|
1757
|
+
invariant("a day is not before the epoch") { !value.negative? }
|
|
1758
|
+
end
|
|
1759
|
+
|
|
1760
|
+
value_object "SlotsUsed" do
|
|
1761
|
+
attribute :value, Integer, default: 0
|
|
1762
|
+
invariant("a count of pull requests is not negative") { !value.negative? }
|
|
1763
|
+
end
|
|
1764
|
+
|
|
1765
|
+
value_object "SlotCap" do
|
|
1766
|
+
attribute :value, Integer
|
|
1767
|
+
invariant("a cap is not negative") { !value.negative? }
|
|
1768
|
+
end
|
|
1769
|
+
|
|
1770
|
+
command "Open" do
|
|
1771
|
+
role "QA engineer"
|
|
1772
|
+
goal "Open the day the clock reads, with no slot taken yet"
|
|
1773
|
+
|
|
1774
|
+
attribute :today, Day
|
|
1775
|
+
needs :today
|
|
1776
|
+
|
|
1777
|
+
sets :today
|
|
1778
|
+
|
|
1779
|
+
emits "QuotaOpened"
|
|
1780
|
+
end
|
|
1781
|
+
|
|
1782
|
+
# `cap` is the dial's value, `PR_CAP_PER_DAY`; 0 is uncapped. The script asks this as a dry
|
|
1783
|
+
# run before it opens a PR, so a spent day refuses before anything is opened.
|
|
1784
|
+
command "Take" do
|
|
1785
|
+
role "QA engineer"
|
|
1786
|
+
goal "Take one of the day's slots for a pull request, refused once the day's cap is spent"
|
|
1787
|
+
|
|
1788
|
+
reference_to DailyQuota
|
|
1789
|
+
attribute :cap, SlotCap
|
|
1790
|
+
|
|
1791
|
+
given("the day's cap is not spent") { cap.value == 0 || used.value < cap.value }
|
|
1792
|
+
|
|
1793
|
+
sets :used, increment: { value: 1 }
|
|
1794
|
+
|
|
1795
|
+
emits "SlotTaken"
|
|
1796
|
+
end
|
|
1797
|
+
|
|
1798
|
+
query "Today" do
|
|
1799
|
+
description "The quota of the day the clock reads: nothing before the day's first pull request."
|
|
1800
|
+
attribute :today, Day
|
|
1801
|
+
needs :today
|
|
1802
|
+
where(:"today.value" => :today)
|
|
1803
|
+
end
|
|
1804
|
+
end
|
|
1805
|
+
|
|
1846
1806
|
# A PR for deliberate, non-bugfix work: a sibling of `Patch`, because `Bug` means "proven wrong"
|
|
1847
1807
|
# and `Patch.Open` keeps its required `reference_to Bug`.
|
|
1848
1808
|
# Born `opened`, with no investigating state: the work was decided before any code moved.
|
|
@@ -1864,7 +1824,7 @@ Hecks.bluebook "QualityControl" do
|
|
|
1864
1824
|
attribute :branch, ImprovementBranch
|
|
1865
1825
|
attribute :commit, CommitRef
|
|
1866
1826
|
attribute :title, ImprovementTitle
|
|
1867
|
-
# See `Patch.opened_at
|
|
1827
|
+
# See `Patch.opened_at`.
|
|
1868
1828
|
attribute :opened_at, Instant
|
|
1869
1829
|
|
|
1870
1830
|
value_object "Instant" do
|
|
@@ -1997,14 +1957,6 @@ Hecks.bluebook "QualityControl" do
|
|
|
1997
1957
|
order_by :number
|
|
1998
1958
|
end
|
|
1999
1959
|
|
|
2000
|
-
# `Patch.OpenedSince`'s twin: the cap counts both kinds of PR.
|
|
2001
|
-
query "OpenedSince" do
|
|
2002
|
-
description "Every improvement opened at or after one instant, in number order — the other half of today's count against PR_CAP_PER_DAY."
|
|
2003
|
-
attribute :since, Instant
|
|
2004
|
-
where(:"opened_at.value" => { gte: :since })
|
|
2005
|
-
order_by :number
|
|
2006
|
-
end
|
|
2007
|
-
|
|
2008
1960
|
query "All" do
|
|
2009
1961
|
description "Every improvement ever landed — what a runner reads back to reconcile against GitHub's own list."
|
|
2010
1962
|
order_by :number
|