hecks 3.0.3 → 3.0.5
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/exe/hecks +12 -2
- data/lib/hecks/bluebook/assembly/contracts.rb +3 -1
- data/lib/hecks/bluebook/assembly/marks.rb +3 -0
- data/lib/hecks/bluebook/command.rb +7 -5
- data/lib/hecks/bluebook/dsl/bootstrap_table.rb +1 -0
- data/lib/hecks/bluebook/dsl/command_builder.rb +42 -0
- data/lib/hecks/bluebook/expression/canonical_form.rb +12 -0
- data/lib/hecks/bluebook/expression/projection.json +21 -0
- data/lib/hecks/bluebook/meta_validator/shapes.rb +3 -0
- data/lib/hecks/cli/console.rb +24 -8
- data/lib/hecks/cli/project_cli.rb +42 -9
- data/lib/hecks/cli/seed_semantics_corpus.rb +35 -6
- data/lib/hecks/deploy/bluebook/deploy.bluebook +34 -0
- data/lib/hecks/doors/cli_runner.rb +57 -12
- data/lib/hecks/doors/launcher_options.rb +41 -0
- data/lib/hecks/fuzzing/properties.rb +1 -1
- data/lib/hecks/fuzzing/sequence_generator/adversary.rb +11 -3
- data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +9 -7
- data/lib/hecks/gate/stages.yml +55 -0
- data/lib/hecks/grammar/expression.bluebook +6 -1
- data/lib/hecks/grammar/expression_operators.json +135 -0
- data/lib/hecks/hecks/adapters/codebase/gate.rb +37 -0
- data/lib/hecks/hecks/adapters/codebase/regeneration.rb +24 -4
- data/lib/hecks/hecks/adapters/codebase/source_tree.rb +3 -1
- data/lib/hecks/hecks/codebase.behaviors +12 -0
- data/lib/hecks/hecks/codebase.bluebook +194 -0
- data/lib/hecks/hecks/custodian.bluebook +2 -0
- data/lib/hecks/hecks/hecks.hecksagon +14 -0
- data/lib/hecks/hecks/hecks.world +5 -1
- data/lib/hecks/language/bluebook/command.bluebook +26 -0
- data/lib/hecks/language/bluebook/vocabulary.bluebook +29 -1
- data/lib/hecks/language/oidc.json +5 -0
- data/lib/hecks/ports/clock.rb +3 -1
- data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +2 -1
- data/lib/hecks/projections/deploy/lambda.rb +3 -4
- data/lib/hecks/projector/cli_projector.rb +63 -4
- data/lib/hecks/quality_control/quality_control.bluebook +6 -0
- data/lib/hecks/runtime/command_interpreter.rb +8 -4
- data/lib/hecks/runtime/entity_interpreter.rb +5 -2
- data/lib/hecks/runtime/interpreting.rb +50 -0
- data/lib/hecks/runtime/invocation.rb +7 -1
- data/lib/hecks/runtime/loader.rb +16 -13
- data/lib/hecks/runtime.rb +11 -0
- data/lib/hecks/three_zero/forms.yml +1 -1
- data/lib/hecks/tools/ci_gate_decision.rb +109 -0
- data/lib/hecks/tools/ci_gates.rb +124 -0
- data/lib/hecks/tools/gate.rb +117 -0
- data/lib/hecks/tools.rb +3 -0
- data/lib/hecks/version.rb +1 -1
- data/lib/hecks/vocabulary.rb +6 -1
- data/lib/hecks.rb +12 -2
- data/rust/codegen/src/naming.rs +13 -2
- data/rust/host/HECKS_RELEASE +1 -1
- data/rust/host/src/dispatch.rs +207 -1
- data/rust/host/src/main.rs +1 -0
- data/rust/host/src/needs.rs +243 -0
- data/rust/host/src/resend.rs +52 -10
- data/rust/host/src/server.rs +7 -3
- data/rust/host/src/web/newsletter/template.rs +69 -0
- data/rust/host/src/web/newsletter/tests.rs +43 -0
- data/rust/host/src/web/newsletter.rs +6 -1
- data/rust/host/src/web/newsletter_send.rs +1 -1
- data/rust/parser/src/canonical.rs +137 -2
- data/rust/parser/src/emit.rs +25 -19
- 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 +44 -0
- data/rust/project/naming.rb +4 -2
- data/rust/src/kernel/cli.rs +3 -0
- data/rust/src/kernel/mod.rs +1 -0
- data/rust/src/kernel/needs.rs +194 -0
- data/rust/src/kernel/orchestrate.rs +84 -0
- data/rust/src/kernel/repository.rs +43 -2
- metadata +10 -2
|
@@ -960,6 +960,7 @@ Hecks.bluebook "Hecks" do
|
|
|
960
960
|
attribute :operation, Operation, optional: true
|
|
961
961
|
attribute :confirm, Switch, optional: true
|
|
962
962
|
attribute :check, Switch, optional: true
|
|
963
|
+
attribute :gate, GateName, optional: true
|
|
963
964
|
attribute :checkout, Switch, optional: true
|
|
964
965
|
attribute :report, Report, optional: true
|
|
965
966
|
attribute :refusal, Report, optional: true
|
|
@@ -983,6 +984,11 @@ Hecks.bluebook "Hecks" do
|
|
|
983
984
|
attribute :value, String
|
|
984
985
|
end
|
|
985
986
|
|
|
987
|
+
# A `CiGate` row of the Vocabulary chapter, by name.
|
|
988
|
+
value_object "GateName" do
|
|
989
|
+
attribute :value, String, pattern: '[^ \t\n\r]'
|
|
990
|
+
end
|
|
991
|
+
|
|
986
992
|
lifecycle :status, default: "requested" do
|
|
987
993
|
transition "Accept" => "accepted", from: "requested"
|
|
988
994
|
transition "Complete" => "completed", from: "accepted"
|
|
@@ -1005,6 +1011,36 @@ Hecks.bluebook "Hecks" do
|
|
|
1005
1011
|
emits RegenerateCorpusRequested
|
|
1006
1012
|
end
|
|
1007
1013
|
|
|
1014
|
+
command "ProjectCiGates" do
|
|
1015
|
+
role "Maintainer"
|
|
1016
|
+
goal "Project the CiGate rows into the workflows' marked detector jobs, or with check only compare them"
|
|
1017
|
+
|
|
1018
|
+
attribute :check, Switch, optional: true
|
|
1019
|
+
attribute :run, RunKey
|
|
1020
|
+
attribute :confirm, Switch, optional: true
|
|
1021
|
+
|
|
1022
|
+
sets :check
|
|
1023
|
+
sets :run
|
|
1024
|
+
sets :confirm
|
|
1025
|
+
sets :operation, to: "project_ci_gates"
|
|
1026
|
+
|
|
1027
|
+
emits ProjectCiGatesRequested
|
|
1028
|
+
end
|
|
1029
|
+
|
|
1030
|
+
command "DecideCiGate" do
|
|
1031
|
+
role "Maintainer"
|
|
1032
|
+
goal "Say whether this run's change touches what a CiGate row guards, as the gated job's detector"
|
|
1033
|
+
|
|
1034
|
+
attribute :gate, GateName
|
|
1035
|
+
attribute :run, RunKey
|
|
1036
|
+
|
|
1037
|
+
sets :gate
|
|
1038
|
+
sets :run
|
|
1039
|
+
sets :operation, to: "decide_ci_gate"
|
|
1040
|
+
|
|
1041
|
+
emits DecideCiGateRequested
|
|
1042
|
+
end
|
|
1043
|
+
|
|
1008
1044
|
# What the tree reported about itself, held against every request: nothing here runs
|
|
1009
1045
|
# outside a hecks checkout (hecks.gemspec beside lib/).
|
|
1010
1046
|
command "Accept" do
|
|
@@ -1064,6 +1100,16 @@ Hecks.bluebook "Hecks" do
|
|
|
1064
1100
|
trigger RegenerationRun::SourceTree::Examine, with: { run: :run }
|
|
1065
1101
|
end
|
|
1066
1102
|
|
|
1103
|
+
policy "ExamineWhenProjectCiGatesRequested" do
|
|
1104
|
+
on "RegenerationRun.ProjectCiGatesRequested"
|
|
1105
|
+
trigger RegenerationRun::SourceTree::Examine, with: { run: :run }
|
|
1106
|
+
end
|
|
1107
|
+
|
|
1108
|
+
policy "ExamineWhenDecideCiGateRequested" do
|
|
1109
|
+
on "RegenerationRun.DecideCiGateRequested"
|
|
1110
|
+
trigger RegenerationRun::SourceTree::Examine, with: { run: :run }
|
|
1111
|
+
end
|
|
1112
|
+
|
|
1067
1113
|
policy "AcceptRegenerationWhenExamined" do
|
|
1068
1114
|
on "RegenerationRun.RegenerationExamined"
|
|
1069
1115
|
trigger RegenerationRun::Accept, with: { checkout: :checkout }
|
|
@@ -1090,6 +1136,154 @@ Hecks.bluebook "Hecks" do
|
|
|
1090
1136
|
trigger RegenerationRun::Fault, with: { refusal: :refusal }
|
|
1091
1137
|
end
|
|
1092
1138
|
|
|
1139
|
+
# Gate: the checks a stage holds a change against before it is let through. The checks are data
|
|
1140
|
+
# (lib/hecks/gate/stages.yml), run together, and a red one faults the run, so `--wait` fails the
|
|
1141
|
+
# push or the build that asked, and the journal keeps when the gate went red.
|
|
1142
|
+
aggregate "GateRun" do
|
|
1143
|
+
description "One run of a stage's checks: every check of the stage, or the ones named."
|
|
1144
|
+
|
|
1145
|
+
attribute :run, RunKey
|
|
1146
|
+
attribute :operation, Operation, optional: true
|
|
1147
|
+
attribute :stage, Stage
|
|
1148
|
+
attribute :only, CheckList, optional: true
|
|
1149
|
+
attribute :checkout, Switch, optional: true
|
|
1150
|
+
attribute :report, Report, optional: true
|
|
1151
|
+
attribute :refusal, Report, optional: true
|
|
1152
|
+
identified_by :run
|
|
1153
|
+
|
|
1154
|
+
value_object "RunKey" do
|
|
1155
|
+
attribute :value, String, pattern: '[^ \t\n\r]'
|
|
1156
|
+
|
|
1157
|
+
invariant("a run is named") { !value.to_s.empty? }
|
|
1158
|
+
end
|
|
1159
|
+
|
|
1160
|
+
value_object "Operation" do
|
|
1161
|
+
attribute :value, String, pattern: '[^ \t\n\r]'
|
|
1162
|
+
end
|
|
1163
|
+
|
|
1164
|
+
value_object "Stage" do
|
|
1165
|
+
attribute :value, String, pattern: '^[a-z][a-z0-9_]*$'
|
|
1166
|
+
|
|
1167
|
+
invariant("a stage is named") { !value.to_s.empty? }
|
|
1168
|
+
end
|
|
1169
|
+
|
|
1170
|
+
value_object "CheckList" do
|
|
1171
|
+
attribute :value, String, pattern: '^[a-z][a-z0-9_]*(,[a-z][a-z0-9_]*)*$'
|
|
1172
|
+
end
|
|
1173
|
+
|
|
1174
|
+
value_object "Switch" do
|
|
1175
|
+
attribute :value, Boolean
|
|
1176
|
+
end
|
|
1177
|
+
|
|
1178
|
+
value_object "Report" do
|
|
1179
|
+
attribute :value, String
|
|
1180
|
+
end
|
|
1181
|
+
|
|
1182
|
+
lifecycle :status, default: "requested" do
|
|
1183
|
+
transition "Accept" => "accepted", from: "requested"
|
|
1184
|
+
transition "Complete" => "completed", from: "accepted"
|
|
1185
|
+
transition "Fault" => "faulted", from: %w[requested accepted]
|
|
1186
|
+
end
|
|
1187
|
+
|
|
1188
|
+
command "Gate" do
|
|
1189
|
+
role "Maintainer"
|
|
1190
|
+
goal "Run every check of a stage, or the ones named, and keep whether the gate is green"
|
|
1191
|
+
|
|
1192
|
+
attribute :stage, Stage
|
|
1193
|
+
attribute :only, CheckList, optional: true
|
|
1194
|
+
attribute :run, RunKey
|
|
1195
|
+
|
|
1196
|
+
sets :stage
|
|
1197
|
+
sets :only
|
|
1198
|
+
sets :run
|
|
1199
|
+
sets :operation, to: "gate"
|
|
1200
|
+
|
|
1201
|
+
emits GateRequested
|
|
1202
|
+
end
|
|
1203
|
+
|
|
1204
|
+
command "Accept" do
|
|
1205
|
+
role "System"
|
|
1206
|
+
goal "Let a request through only when the working tree is a hecks checkout"
|
|
1207
|
+
|
|
1208
|
+
reference_to GateRun
|
|
1209
|
+
attribute :checkout, Switch
|
|
1210
|
+
|
|
1211
|
+
given("needs a hecks checkout: hecks.gemspec stands beside lib/") { checkout.value }
|
|
1212
|
+
|
|
1213
|
+
sets :checkout
|
|
1214
|
+
|
|
1215
|
+
emits GateAccepted
|
|
1216
|
+
end
|
|
1217
|
+
|
|
1218
|
+
command "Complete" do
|
|
1219
|
+
role "System"
|
|
1220
|
+
goal "Record that every check passed, and which"
|
|
1221
|
+
|
|
1222
|
+
reference_to GateRun
|
|
1223
|
+
attribute :report, Report
|
|
1224
|
+
|
|
1225
|
+
sets :report
|
|
1226
|
+
|
|
1227
|
+
emits GateCompleted
|
|
1228
|
+
end
|
|
1229
|
+
|
|
1230
|
+
command "Fault" do
|
|
1231
|
+
role "System"
|
|
1232
|
+
goal "Record that a check was red or the run could not be made, and why"
|
|
1233
|
+
|
|
1234
|
+
reference_to GateRun
|
|
1235
|
+
attribute :refusal, Report
|
|
1236
|
+
|
|
1237
|
+
sets :refusal
|
|
1238
|
+
|
|
1239
|
+
emits GateFaulted
|
|
1240
|
+
end
|
|
1241
|
+
|
|
1242
|
+
query "GateOutcome" do
|
|
1243
|
+
description "How one run ended: its status, and what it reported or why it failed."
|
|
1244
|
+
attribute :run, RunKey
|
|
1245
|
+
where(run: :run)
|
|
1246
|
+
order_by :run
|
|
1247
|
+
end
|
|
1248
|
+
|
|
1249
|
+
query "GateFaulted" do
|
|
1250
|
+
description "Every run that went red or could not be made."
|
|
1251
|
+
where(status: "faulted")
|
|
1252
|
+
order_by :run
|
|
1253
|
+
end
|
|
1254
|
+
end
|
|
1255
|
+
|
|
1256
|
+
policy "ExamineWhenGateRequested" do
|
|
1257
|
+
on "GateRun.GateRequested"
|
|
1258
|
+
trigger GateRun::SourceTree::Examine, with: { run: :run }
|
|
1259
|
+
end
|
|
1260
|
+
|
|
1261
|
+
policy "AcceptGateWhenExamined" do
|
|
1262
|
+
on "GateRun.GateExamined"
|
|
1263
|
+
trigger GateRun::Accept, with: { checkout: :checkout }
|
|
1264
|
+
end
|
|
1265
|
+
|
|
1266
|
+
policy "FaultGateWhenNotAccepted" do
|
|
1267
|
+
on "GateRun.GateExamined"
|
|
1268
|
+
where { !checkout.value }
|
|
1269
|
+
trigger GateRun::Fault, with: { refusal: "needs a hecks checkout: hecks.gemspec stands beside lib/" }
|
|
1270
|
+
end
|
|
1271
|
+
|
|
1272
|
+
policy "PerformGateWhenAccepted" do
|
|
1273
|
+
on "GateRun.GateAccepted"
|
|
1274
|
+
trigger GateRun::SourceTree::Perform, with: { run: :run }
|
|
1275
|
+
end
|
|
1276
|
+
|
|
1277
|
+
policy "RecordGateOutcome" do
|
|
1278
|
+
on "GateRun.GateAnswered"
|
|
1279
|
+
trigger GateRun::Complete, with: { report: :report }
|
|
1280
|
+
end
|
|
1281
|
+
|
|
1282
|
+
policy "RecordGateFault" do
|
|
1283
|
+
on "GateRun.GateRefused"
|
|
1284
|
+
trigger GateRun::Fault, with: { refusal: :refusal }
|
|
1285
|
+
end
|
|
1286
|
+
|
|
1093
1287
|
# Style: the comment style guides (Ruby and Rust) and the canonical form of a JSON document. A
|
|
1094
1288
|
# check finds every violation; a fix rewrites files, so it lists what it would rewrite unless
|
|
1095
1289
|
# it is confirmed. The baseline of tolerated long blocks is written only when confirmed.
|
|
@@ -460,6 +460,8 @@ Hecks.bluebook "Hecks" do
|
|
|
460
460
|
|
|
461
461
|
# A launcher answers once, so a follow is a bounded poll: it answers the entries past `since`
|
|
462
462
|
# and a `cursor` to pass as the next `since`, waiting up to `wait` seconds for the first one.
|
|
463
|
+
# `hecks follow <domain> --stream` asks again from each cursor and prints every entry as it
|
|
464
|
+
# arrives, until interrupted (the world's `launcher` setting lists the question under `streams`).
|
|
463
465
|
query "Follow" do
|
|
464
466
|
description "A domain's persisted event log past a cursor, for tailing it call by call."
|
|
465
467
|
attribute :domain, Subject
|
|
@@ -229,6 +229,20 @@ Hecks.hecksagon "Hecks" do
|
|
|
229
229
|
end
|
|
230
230
|
end
|
|
231
231
|
|
|
232
|
+
Hecks::GateRun.port "SourceTree" do
|
|
233
|
+
asks "Examine", to: GateRun do
|
|
234
|
+
attribute :run, RunKey
|
|
235
|
+
answers "GateExamined"
|
|
236
|
+
refuses "GateRefused"
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
asks "Perform", to: GateRun do
|
|
240
|
+
attribute :run, RunKey
|
|
241
|
+
answers "GateAnswered"
|
|
242
|
+
refuses "GateRefused"
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
|
|
232
246
|
Hecks::StyleRun.port "SourceTree" do
|
|
233
247
|
asks "Examine", to: StyleRun do
|
|
234
248
|
attribute :run, RunKey
|
data/lib/hecks/hecks/hecks.world
CHANGED
|
@@ -26,7 +26,9 @@
|
|
|
26
26
|
# `failure_states` are the lifecycle states `--wait` reports as exit 1 (every failure state the
|
|
27
27
|
# chapter's lifecycles declare), `names` gives a command the launcher name clients type,
|
|
28
28
|
# `executable` is where `hecks project_cli` writes this chapter's launcher (the gem's
|
|
29
|
-
# `exe/hecks`),
|
|
29
|
+
# `exe/hecks`), `streams` lists the questions `--stream` may tail (`Follow`), `memory_verbs` names
|
|
30
|
+
# the verbs that launcher runs on the Memory environment unless `HECKS_ENVIRONMENT` is set (so
|
|
31
|
+
# `hecks console` needs no database), and `legacy` names the verbs it hands to `Hecks::CLI` in the
|
|
30
32
|
# positional forms ADR 0066 shipped.
|
|
31
33
|
Hecks.world "Hecks" do
|
|
32
34
|
realm "Hecks"
|
|
@@ -36,7 +38,9 @@ Hecks.world "Hecks" do
|
|
|
36
38
|
failure_states: %w[flagged failed drifted unreachable refused faulted halted stopped
|
|
37
39
|
abandoned red needs_fix],
|
|
38
40
|
names: { "mcp" => "serve_mcp", "console" => "open_console" },
|
|
41
|
+
streams: %w[Follow],
|
|
39
42
|
executable: "exe/hecks",
|
|
43
|
+
memory_verbs: %w[console],
|
|
40
44
|
legacy: %w[run docs narrate ir stores model_check smoke_test
|
|
41
45
|
project_diagrams project_cli mcp]
|
|
42
46
|
end
|
|
@@ -21,6 +21,8 @@ Hecks.bluebook "Bluebook" do
|
|
|
21
21
|
attribute :givens, list_of(Rule)
|
|
22
22
|
# Postconditions: same Rule shape as givens, checked after the mutations.
|
|
23
23
|
attribute :ensures, list_of(Rule)
|
|
24
|
+
# Outside facts the runtime supplies before any given runs (ADR 0081): `needs :now`.
|
|
25
|
+
attribute :needs, list_of(Need)
|
|
24
26
|
attribute :mutations, list_of(Change)
|
|
25
27
|
attribute :provenance, CommandText
|
|
26
28
|
# Lifecycle guard as a literal (`from: "open"` or `from: ["open", "frozen"]`); ADR 0025.
|
|
@@ -48,6 +50,10 @@ Hecks.bluebook "Bluebook" do
|
|
|
48
50
|
attribute :name, String
|
|
49
51
|
end
|
|
50
52
|
|
|
53
|
+
value_object "Need" do
|
|
54
|
+
attribute :fact, String
|
|
55
|
+
end
|
|
56
|
+
|
|
51
57
|
value_object "Argument" do
|
|
52
58
|
attribute :name, String
|
|
53
59
|
attribute :type, String
|
|
@@ -254,6 +260,23 @@ Hecks.bluebook "Bluebook" do
|
|
|
254
260
|
emits AnnouncementNamed
|
|
255
261
|
end
|
|
256
262
|
|
|
263
|
+
command "Need" do
|
|
264
|
+
role "Language"
|
|
265
|
+
goal "Name an outside fact the runtime supplies before any given runs"
|
|
266
|
+
|
|
267
|
+
attribute :fact, FactName
|
|
268
|
+
|
|
269
|
+
given("a fact is named") { !fact.value.to_s.empty? }
|
|
270
|
+
|
|
271
|
+
sets :needs, append: { fact: :fact }
|
|
272
|
+
|
|
273
|
+
emits NeedNamed
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
value_object "FactName" do
|
|
277
|
+
attribute :value, String
|
|
278
|
+
end
|
|
279
|
+
|
|
257
280
|
# Keyword rows for this chapter; SyntaxBoot discovers aggregate-local seed tables.
|
|
258
281
|
value_object "KeywordSeed" do
|
|
259
282
|
attribute :word, String
|
|
@@ -283,6 +306,8 @@ Hecks.bluebook "Bluebook" do
|
|
|
283
306
|
member word: "state", context: "Command", body: "none", inner: "", opens: "", fills: "mutations"
|
|
284
307
|
member word: "attribute", context: "Command", body: "none", inner: "", opens: "", fills: "attributes", calls: "attribute_impl"
|
|
285
308
|
member word: "ensures", context: "Command", body: "source", inner: "", opens: "", fills: "ensures"
|
|
309
|
+
# An outside fact the runtime supplies before any given runs (ADR 0081; CommandBuilder#needs_impl).
|
|
310
|
+
member word: "needs", context: "Command", body: "none", inner: "", opens: "", fills: "needs", calls: "needs_impl"
|
|
286
311
|
# Amends a specific past event. Stored as a mutation so it round-trips with the other
|
|
287
312
|
# mutation words (CommandBuilder#corrects_impl).
|
|
288
313
|
member word: "corrects", context: "Command", body: "none", inner: "", opens: "", fills: "mutations", calls: "corrects_impl"
|
|
@@ -314,6 +339,7 @@ Hecks.bluebook "Bluebook" do
|
|
|
314
339
|
member keyword: "reference_to", context: "Command", at: "", named: "optional", kind: "flag", required: "false", fills: "optional"
|
|
315
340
|
member keyword: "given", context: "Command", at: "1", named: "", kind: "text", required: "true", fills: "description"
|
|
316
341
|
member keyword: "ensures", context: "Command", at: "1", named: "", kind: "text", required: "true", fills: "description"
|
|
342
|
+
member keyword: "needs", context: "Command", at: "1", named: "", kind: "symbol", required: "true", fills: "fact"
|
|
317
343
|
member keyword: "sets", context: "Command", at: "1", named: "", kind: "symbol", required: "true", fills: "target"
|
|
318
344
|
member keyword: "sets", context: "Command", at: "", named: "to", kind: "literal", required: "false", fills: "source", selects: "op=set"
|
|
319
345
|
member keyword: "sets", context: "Command", at: "", named: "append", kind: "literal", required: "false", fills: "source", selects: "op=append"
|
|
@@ -73,7 +73,7 @@ Hecks.bluebook "Bluebook" do
|
|
|
73
73
|
|
|
74
74
|
# Expression::CanonicalForm::STRATEGIES
|
|
75
75
|
value_object "NormalisationStrategy" do
|
|
76
|
-
attribute :name, String, one_of: ["collapse_whitespace", "replace"]
|
|
76
|
+
attribute :name, String, one_of: ["collapse_whitespace", "replace", "scale_call"]
|
|
77
77
|
end
|
|
78
78
|
|
|
79
79
|
# Ops Runtime::CommandInterpreter applies. `sign` is the arithmetic delta
|
|
@@ -602,6 +602,34 @@ Hecks.bluebook "Bluebook" do
|
|
|
602
602
|
member name: "textarea", pattern: '\b(text|body|note|notes|description|message|comment)\b', resolves_to: "kind"
|
|
603
603
|
end
|
|
604
604
|
|
|
605
|
+
# The path gates the workflows run before an expensive job: one detector job
|
|
606
|
+
# per row, answering `touched` from the diff of the change against its base.
|
|
607
|
+
# hecks project_ci_gates writes each row into the marked region of its
|
|
608
|
+
# `workflow` (.github/workflows/<workflow>), and the job it writes runs
|
|
609
|
+
# `hecks decide_ci_gate gate=<name>`, which makes the decision from the row.
|
|
610
|
+
#
|
|
611
|
+
# `mode` is how `pattern` (an extended regex over changed paths) decides:
|
|
612
|
+
# `touches` runs the job when any changed path matches, `skips_unless` runs it
|
|
613
|
+
# when any changed path does NOT match (the pattern is the safe-to-skip list).
|
|
614
|
+
# `push` is what a push event does: `skip` leaves the gate out of push runs,
|
|
615
|
+
# `before_sha` diffs against the push's `before` commit.
|
|
616
|
+
value_object "CiGate" do
|
|
617
|
+
attribute :name, String
|
|
618
|
+
attribute :workflow, String
|
|
619
|
+
attribute :mode, String
|
|
620
|
+
attribute :pattern, String
|
|
621
|
+
attribute :push, String
|
|
622
|
+
attribute :label, String
|
|
623
|
+
|
|
624
|
+
member name: "runtime_changed", workflow: "ci.yml", mode: "touches",
|
|
625
|
+
pattern: '^lib/hecks/runtime/', push: "skip",
|
|
626
|
+
label: "does this change touch lib/hecks/runtime/**?"
|
|
627
|
+
member name: "postgres_io_relevant_changed", workflow: "ci-postgres-io-parallel.yml", mode: "skips_unless",
|
|
628
|
+
pattern: '^(docs/|editors/|release/|deploy/|\.claude/|\.githooks/|rust/(parser|codegen|host|build|lsp|web|tests|project)/|rust/project\.rb$|rust/project_rust_pipeline\.rb$|\.rubocop\.yml$|\.rubocop_todo\.yml$|\.mcp\.json$|\.rspec-local\.example$|README\.md$|CHANGELOG\.md$|CONTRIBUTING\.md$|SECURITY\.md$|LICENSE$|\.gitignore$)',
|
|
629
|
+
push: "before_sha",
|
|
630
|
+
label: "does this change touch anything rspec_postgres_io_parallel covers?"
|
|
631
|
+
end
|
|
632
|
+
|
|
605
633
|
# Rust keywords (strict + reserved) a generated identifier must not
|
|
606
634
|
# collide with. RustProjection::Projector::RUST_KEYWORDS reads this;
|
|
607
635
|
# hecks project_reserved_names projects it into
|
|
@@ -111,6 +111,11 @@
|
|
|
111
111
|
"verb": "Bluebook::Command.Ensure",
|
|
112
112
|
"role": "Language"
|
|
113
113
|
},
|
|
114
|
+
{
|
|
115
|
+
"scope": "bluebook:command.need",
|
|
116
|
+
"verb": "Bluebook::Command.Need",
|
|
117
|
+
"role": "Language"
|
|
118
|
+
},
|
|
114
119
|
{
|
|
115
120
|
"scope": "bluebook:command.reference",
|
|
116
121
|
"verb": "Bluebook::Command.Reference",
|
data/lib/hecks/ports/clock.rb
CHANGED
|
@@ -3,7 +3,9 @@ require_relative "../runtime/registry"
|
|
|
3
3
|
module Hecks
|
|
4
4
|
module Ports
|
|
5
5
|
# What time it is, as one registry-wide adapter answers it.
|
|
6
|
-
# Predicates cannot read the clock (replays must be deterministic), so
|
|
6
|
+
# Predicates cannot read the clock (replays must be deterministic), so a command that declares
|
|
7
|
+
# `needs :now` has the runtime read it once, before any given runs, into the command's own
|
|
8
|
+
# `now` argument (ADR 0081). The recorded argument is what a replay sees.
|
|
7
9
|
module Clock
|
|
8
10
|
NAME = "clock".freeze
|
|
9
11
|
|
|
@@ -123,7 +123,8 @@ module Hecks
|
|
|
123
123
|
raise unless defined?(PG::Error) && e.is_a?(PG::Error)
|
|
124
124
|
|
|
125
125
|
raise Runtime::WiringError,
|
|
126
|
-
"cannot bind PostgresEra at #{declared} for #{name}: #{e.message.strip}"
|
|
126
|
+
"cannot bind PostgresEra at #{declared} for #{name}: #{e.message.strip} " \
|
|
127
|
+
"-- to run without a database, set HECKS_ENVIRONMENT=memory (a domain needs an environments/memory overlay)"
|
|
127
128
|
end
|
|
128
129
|
|
|
129
130
|
# Joins the process's shared connection for the declared database and schema, so every
|
|
@@ -85,7 +85,9 @@ module Hecks
|
|
|
85
85
|
memory: { value: deploy_settings.fetch(:memory, 512) },
|
|
86
86
|
timeout: { value: deploy_settings.fetch(:timeout, 10) },
|
|
87
87
|
database: { value: deploy_settings.fetch(:database, "Postgres") },
|
|
88
|
-
web: { value: deploy_settings.fetch(:web, "None") }
|
|
88
|
+
web: { value: deploy_settings.fetch(:web, "None") },
|
|
89
|
+
**%i[dispatch handler_module secret_env].select { |key| deploy_settings.key?(key) }
|
|
90
|
+
.to_h { |key| [key, { value: deploy_settings[key].to_s }] }
|
|
89
91
|
}
|
|
90
92
|
).instance
|
|
91
93
|
rescue *Hecks::Runtime::DOMAIN_REFUSALS => e
|
|
@@ -105,9 +107,6 @@ module Hecks
|
|
|
105
107
|
# The module name `lambda_handler.rb` actually defines; named here
|
|
106
108
|
# since `dispatch "None"` domains may each pick their own.
|
|
107
109
|
webhook_handler_module = deploy_settings.fetch(:handler_module, "WebLambdaHandler")
|
|
108
|
-
if dispatch_none && !deploy_settings.key?(:handler_module)
|
|
109
|
-
raise ArgumentError, "#{world_file}'s deployed_to(\"AwsLambda\") declares dispatch \"None\" but no handler_module — add handler_module \"YourModuleName\" naming the module #{domain}/lambda_handler.rb defines."
|
|
110
|
-
end
|
|
111
110
|
|
|
112
111
|
# Scans every loaded chapter, not just this domain's own — a
|
|
113
112
|
# `uses_framework`-attached chapter can itself declare a cross-domain
|
|
@@ -206,6 +206,7 @@ module Hecks
|
|
|
206
206
|
|
|
207
207
|
{ verb: fqn(bluebook, aggregate, command, entity), kind: :command,
|
|
208
208
|
summary: command.goal, role: command.role, role_gated: !command.role.to_s.empty?,
|
|
209
|
+
group: aggregate.hecks_name, internal: command.role.to_s == "System",
|
|
209
210
|
creates: command.creates?,
|
|
210
211
|
receiver: receiver, legacy_receiver: (receiver == :aggregate ? :id : nil),
|
|
211
212
|
legacy_arguments: legacy_arguments,
|
|
@@ -230,7 +231,7 @@ module Hecks
|
|
|
230
231
|
else
|
|
231
232
|
"#{port.name} telling #{aggregate.hecks_name}"
|
|
232
233
|
end,
|
|
233
|
-
role_gated: false,
|
|
234
|
+
role_gated: false, group: aggregate.hecks_name, internal: true,
|
|
234
235
|
summary: port_summary(port, operation), arguments: arguments }
|
|
235
236
|
end
|
|
236
237
|
|
|
@@ -257,13 +258,30 @@ module Hecks
|
|
|
257
258
|
summary: model.description, arguments: arguments }
|
|
258
259
|
end
|
|
259
260
|
|
|
261
|
+
# A question that only reads the aggregate's own records back: it returns no document and
|
|
262
|
+
# filters on nothing but the record's identity ("how one request ended") or its lifecycle
|
|
263
|
+
# status ("every request that was refused"). Each journaled run has such a pair, which a
|
|
264
|
+
# person reads through `hecks <verb> --wait` rather than asking for by name, so the help
|
|
265
|
+
# sets them apart with the bookkeeping verbs. A query that returns a document, or filters
|
|
266
|
+
# on anything else, is a real question. Only an aggregate that journals its own runs (it has
|
|
267
|
+
# system-role commands, the ones `internal` verbs are made of) has such a pair: a release's
|
|
268
|
+
# "every version that was shipped" is a question worth asking by name.
|
|
269
|
+
def bookkeeping_query?(aggregate, query)
|
|
270
|
+
return false if query.returns || query.wheres.empty?
|
|
271
|
+
return false unless aggregate.commands.any? { |command| command.role.to_s == "System" }
|
|
272
|
+
|
|
273
|
+
own = Array(aggregate.identified_by).map(&:to_s) + [aggregate.lifecycle&.field.to_s]
|
|
274
|
+
query.wheres.all? { |clause| own.include?(clause.field.to_s) }
|
|
275
|
+
end
|
|
276
|
+
|
|
260
277
|
def query_spec(bluebook, aggregate, entity, query)
|
|
261
278
|
arguments = Array(query.to_h[:attributes]).flat_map do |declared|
|
|
262
279
|
attribute = query.attributes.find { |a| a.name.to_s == declared[:name].to_s }
|
|
263
280
|
attribute ? options_for(attribute, entity || aggregate, aggregate) : []
|
|
264
281
|
end
|
|
265
282
|
|
|
266
|
-
{ verb: fqn(bluebook, aggregate, query, entity), kind: :query,
|
|
283
|
+
{ verb: fqn(bluebook, aggregate, query, entity), kind: :query, group: aggregate.hecks_name,
|
|
284
|
+
internal: entity.nil? && bookkeeping_query?(aggregate, query),
|
|
267
285
|
summary: query.description, arguments: arguments, returns: query.returns }
|
|
268
286
|
end
|
|
269
287
|
|
|
@@ -373,16 +391,57 @@ module Hecks
|
|
|
373
391
|
" #{program} ask <question> [name=value …] read something", ""]
|
|
374
392
|
|
|
375
393
|
out << "verbs:"
|
|
376
|
-
verbs
|
|
394
|
+
out.concat(listing(verbs, width) { |spec| spec[:summary] })
|
|
377
395
|
out << ""
|
|
378
396
|
out << "questions (nothing here changes anything):"
|
|
379
|
-
questions
|
|
397
|
+
out.concat(listing(questions, width) { |spec| first_sentence(spec[:summary]) })
|
|
380
398
|
out << ""
|
|
381
399
|
out << " #{program} <verb> --help what one verb wants, and every way it refuses"
|
|
382
400
|
out << " a verb can always be spelled in full — #{example_qualified(verbs)}"
|
|
383
401
|
out.join("\n")
|
|
384
402
|
end
|
|
385
403
|
|
|
404
|
+
# The verb or question lines of the help. A domain with more than one aggregate is listed
|
|
405
|
+
# under a heading per aggregate, so related verbs sit together; the bookkeeping a run records
|
|
406
|
+
# about itself (`internal`: system-role commands and port operations) is set apart as names
|
|
407
|
+
# only, since a person never types them. A single-aggregate domain keeps the plain list.
|
|
408
|
+
def listing(specs, width)
|
|
409
|
+
shown, internal = specs.values.partition { |spec| !spec[:internal] }
|
|
410
|
+
groups = shown.group_by { |spec| spec[:group] }
|
|
411
|
+
lines = []
|
|
412
|
+
if groups.length > 1
|
|
413
|
+
groups.each do |group, members|
|
|
414
|
+
lines << " #{heading(group)}" if group
|
|
415
|
+
members.each { |spec| lines << " #{spec[:short].ljust(width)} #{yield(spec)}" }
|
|
416
|
+
end
|
|
417
|
+
else
|
|
418
|
+
shown.each { |spec| lines << " #{spec[:short].ljust(width)} #{yield(spec)}" }
|
|
419
|
+
end
|
|
420
|
+
lines.concat(internal_lines(internal)) unless internal.empty?
|
|
421
|
+
lines
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
# "LanguageRun" reads "Language": the Run suffix names the journaled-command shape all of
|
|
425
|
+
# these aggregates share, so it adds nothing under a heading ("Test suite", "Model check").
|
|
426
|
+
def heading(group)
|
|
427
|
+
words = Naming.snake(group.sub(/Run\z/, "")).tr("_", " ")
|
|
428
|
+
"#{words.capitalize}:"
|
|
429
|
+
end
|
|
430
|
+
|
|
431
|
+
# The internal verbs as bare names, wrapped, under one line saying what they are.
|
|
432
|
+
def internal_lines(specs)
|
|
433
|
+
lines = [" internal — what a run records about itself, named here only (`--help` still works):"]
|
|
434
|
+
line = " "
|
|
435
|
+
specs.map { |spec| spec[:short] }.each do |name|
|
|
436
|
+
if line.length + name.length + 1 > 98
|
|
437
|
+
lines << line
|
|
438
|
+
line = " "
|
|
439
|
+
end
|
|
440
|
+
line += " #{name}"
|
|
441
|
+
end
|
|
442
|
+
lines << line
|
|
443
|
+
end
|
|
444
|
+
|
|
386
445
|
# One verb whose full spelling differs from its short one, or `""` when none does.
|
|
387
446
|
def example_qualified(verbs)
|
|
388
447
|
name, spec = verbs.find { |key, value| key != value[:short] } || verbs.first
|
|
@@ -238,6 +238,7 @@ Hecks.bluebook "QualityControl" do
|
|
|
238
238
|
reference_to Target
|
|
239
239
|
attribute :held_by, Engineer
|
|
240
240
|
attribute :now, Instant
|
|
241
|
+
needs :now
|
|
241
242
|
|
|
242
243
|
given("a live claim is not taken from the agent holding it") do
|
|
243
244
|
status != "held" || claimed_at.value + window.value <= now.value
|
|
@@ -265,6 +266,7 @@ Hecks.bluebook "QualityControl" do
|
|
|
265
266
|
# carry it. Required, so an omitted streak can neither blank the counter nor guess at it.
|
|
266
267
|
reference_to Target
|
|
267
268
|
attribute :now, Instant
|
|
269
|
+
needs :now
|
|
268
270
|
attribute :next_streak, CleanStreak
|
|
269
271
|
# Required: an in-process dispatch does not fill defaults for absent arguments, so an omitted
|
|
270
272
|
# list refuses rather than blanks. `capabilities.value=""` is an honest value.
|
|
@@ -1115,6 +1117,7 @@ Hecks.bluebook "QualityControl" do
|
|
|
1115
1117
|
reference_to Bug
|
|
1116
1118
|
attribute :held_by, Engineer
|
|
1117
1119
|
attribute :now, Instant
|
|
1120
|
+
needs :now
|
|
1118
1121
|
|
|
1119
1122
|
given("a live claim is not taken from the agent holding it") do
|
|
1120
1123
|
held_by.value == "nobody" || claimed_at.value + window.value <= now.value
|
|
@@ -1388,6 +1391,7 @@ Hecks.bluebook "QualityControl" do
|
|
|
1388
1391
|
attribute :proposer, Proposer
|
|
1389
1392
|
# `now:` is filled from the clock port, as in `Target.Claim`; an explicit value wins.
|
|
1390
1393
|
attribute :now, Instant
|
|
1394
|
+
needs :now
|
|
1391
1395
|
|
|
1392
1396
|
sets :proposed_at, to: :now
|
|
1393
1397
|
|
|
@@ -1784,6 +1788,7 @@ Hecks.bluebook "QualityControl" do
|
|
|
1784
1788
|
attribute :commit, CommitRef
|
|
1785
1789
|
attribute :title, PatchTitle
|
|
1786
1790
|
attribute :now, Instant
|
|
1791
|
+
needs :now
|
|
1787
1792
|
|
|
1788
1793
|
given("the branch is one this practice recognises as its own") { branch.value.start_with?("qa/") }
|
|
1789
1794
|
given("the bug is fixed") { bug.status == "fixed" }
|
|
@@ -1921,6 +1926,7 @@ Hecks.bluebook "QualityControl" do
|
|
|
1921
1926
|
attribute :branch, ImprovementBranch
|
|
1922
1927
|
attribute :title, ImprovementTitle
|
|
1923
1928
|
attribute :now, Instant
|
|
1929
|
+
needs :now
|
|
1924
1930
|
|
|
1925
1931
|
given("the branch is one this practice recognises as its own") { branch.value.start_with?("qa/") }
|
|
1926
1932
|
given("the angle is under investigation") { angle.unset? || angle.status == "investigating" }
|
|
@@ -83,10 +83,12 @@ module Hecks
|
|
|
83
83
|
|
|
84
84
|
private
|
|
85
85
|
|
|
86
|
-
#
|
|
87
|
-
#
|
|
88
|
-
#
|
|
89
|
-
def step_decode_arguments(
|
|
86
|
+
# `Routing` has already handed `call` a decoded argument hash, so the one thing left to do
|
|
87
|
+
# here is answer the outside facts the command `needs`, before any refusal or given reads
|
|
88
|
+
# its arguments. Not traced: the step has always been invisible to a trace observer.
|
|
89
|
+
def step_decode_arguments(ctx)
|
|
90
|
+
ctx.args = enrich_arguments(ctx.command, ctx.args)
|
|
91
|
+
end
|
|
90
92
|
|
|
91
93
|
def step_refuse_unknown_arguments(ctx)
|
|
92
94
|
step(:refuse_unknown_arguments) { refuse_unknown_arguments(ctx.domain, ctx.aggregate, ctx.command, ctx.args) }
|
|
@@ -241,6 +243,8 @@ module Hecks
|
|
|
241
243
|
target_args = ctx.args.merge(
|
|
242
244
|
delegation.source.to_h { |target_key, source_key| [target_key.to_sym, ctx.args[source_key]] }
|
|
243
245
|
)
|
|
246
|
+
# The target is dispatched as if called directly, so its needs and defaults are filled too.
|
|
247
|
+
target_args = enrich_arguments(target_command, target_args)
|
|
244
248
|
|
|
245
249
|
refuse_unknown_arguments(ctx.domain, ctx.aggregate, target_command, target_args,
|
|
246
250
|
extra_identity_heads: entity.identity_heads)
|
|
@@ -133,8 +133,11 @@ module Hecks
|
|
|
133
133
|
|
|
134
134
|
private
|
|
135
135
|
|
|
136
|
-
#
|
|
137
|
-
|
|
136
|
+
# Answers the outside facts the entity command `needs`, as the aggregate interpreter does;
|
|
137
|
+
# the arguments are otherwise already decoded.
|
|
138
|
+
def step_decode_arguments(ctx)
|
|
139
|
+
ctx.args = enrich_arguments(ctx.command, ctx.args)
|
|
140
|
+
end
|
|
138
141
|
|
|
139
142
|
# `extra_identity_heads:` covers every entity in `ctx.chain`, not just
|
|
140
143
|
# the root — each hop is addressed by its own identity fields, which
|