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
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 288ec2962d96e6d9772c6fd6155d15461f69259cd7f69e107e308a2710fcb668
|
|
4
|
+
data.tar.gz: e71d1ec056df32604dc6ef8de8cab5069ae998faa74d89acd377d79b217820ed
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a2586bb29d327f576976c7573236abfde1f60deba2eec7529b13ea34fc1b4ea917abe996fd44076cb76690835fb7089f4d18f38d0693d0aa14da2a106334a1a7
|
|
7
|
+
data.tar.gz: 1101c11307086a6df1833e6cd7b4fb5fdaa5b0d265b0a4569fa9d4df5d85f27039975f46135dd90619ad69e50d553172eb82d203633177595503f9bbfb67998a
|
|
@@ -101,6 +101,7 @@ module Hecks
|
|
|
101
101
|
order_by: [:order_by, :order_by],
|
|
102
102
|
limit: [:limit, :limit],
|
|
103
103
|
returns: [:returns, :plain],
|
|
104
|
+
needs: [:needs, [:each, :need]],
|
|
104
105
|
# Held by the language as an open map, so every one of these reads the
|
|
105
106
|
# same way and a ninth option needs no new field on either side.
|
|
106
107
|
offset: [:offset, [:option, :offset]],
|
|
@@ -111,7 +112,7 @@ module Hecks
|
|
|
111
112
|
},
|
|
112
113
|
rows: { wheres: :where_rows, options: :option_rows },
|
|
113
114
|
reads: { attributes: [:each, :shape_field], wheres: [:each, :where_clause],
|
|
114
|
-
order_by: [:call, :order_by], limit: [:call, :limit] },
|
|
115
|
+
needs: [:each, :need], order_by: [:call, :order_by], limit: [:call, :limit] },
|
|
115
116
|
derived: {
|
|
116
117
|
position: :walk,
|
|
117
118
|
order_field: [:folded, :order_by, :field],
|
|
@@ -75,6 +75,7 @@ module Hecks
|
|
|
75
75
|
["Query", "where"] => :where_impl,
|
|
76
76
|
["Query", "order_by"] => :order_by_impl,
|
|
77
77
|
["Query", "authorize"] => :authorize_impl,
|
|
78
|
+
["Query", "needs"] => :needs_impl,
|
|
78
79
|
["ValueObject", "attribute"] => :attribute_impl,
|
|
79
80
|
["ValueObject", "one_of"] => :one_of_impl,
|
|
80
81
|
["ValueObject", "invariant"] => :invariant_impl,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
require_relative "word_gate"
|
|
2
|
+
require_relative "need_word"
|
|
2
3
|
module Hecks
|
|
3
4
|
module Bluebook
|
|
4
5
|
module DSL
|
|
@@ -10,6 +11,7 @@ module Hecks
|
|
|
10
11
|
include AttributeCollector
|
|
11
12
|
include RuleReference
|
|
12
13
|
include WordGate
|
|
14
|
+
include NeedWord
|
|
13
15
|
|
|
14
16
|
# Sentinel for "this keyword was never passed" — distinct from Ruby's own
|
|
15
17
|
# nil/false, so `to: false` doesn't get treated as absent.
|
|
@@ -67,31 +69,6 @@ module Hecks
|
|
|
67
69
|
# @return [String] the description as stored
|
|
68
70
|
def goal(value) = @goal = value
|
|
69
71
|
|
|
70
|
-
# The outside facts a command may declare it needs, each the name of the attribute the
|
|
71
|
-
# runtime fills when the caller leaves it out. `now` is the clock port's answer, in epoch
|
|
72
|
-
# seconds.
|
|
73
|
-
NEEDABLE_FACTS = %i[now].freeze
|
|
74
|
-
|
|
75
|
-
# Declares an outside fact the runtime supplies before any given runs (ADR 0081):
|
|
76
|
-
# `needs :now` fills the command's own `now` attribute from the clock port when the caller
|
|
77
|
-
# names none.
|
|
78
|
-
#
|
|
79
|
-
# @param fact [Symbol] one of `NEEDABLE_FACTS`
|
|
80
|
-
# @return [Array<Symbol>] the facts declared so far
|
|
81
|
-
# @raise [Bluebook::DSL::Malformed] if the fact is not one the runtime can supply, or is
|
|
82
|
-
# declared twice
|
|
83
|
-
def needs_impl(fact)
|
|
84
|
-
fact = fact.to_sym
|
|
85
|
-
unless NEEDABLE_FACTS.include?(fact)
|
|
86
|
-
raise Malformed,
|
|
87
|
-
"#{@name} needs :#{fact}, which the runtime cannot supply — it supplies " \
|
|
88
|
-
"#{NEEDABLE_FACTS.map { |known| ":#{known}" }.join(', ')}"
|
|
89
|
-
end
|
|
90
|
-
raise Malformed, "#{@name} declares needs :#{fact} twice" if @needs.include?(fact)
|
|
91
|
-
|
|
92
|
-
@needs << fact
|
|
93
|
-
end
|
|
94
|
-
|
|
95
72
|
# Names where a concept adopted from a canonical source came from.
|
|
96
73
|
#
|
|
97
74
|
# @param from [Object] the canonical source, captured exactly as written
|
|
@@ -344,20 +321,6 @@ module Hecks
|
|
|
344
321
|
)
|
|
345
322
|
end
|
|
346
323
|
|
|
347
|
-
# A fact is filled into the argument of the same name, so the command declares one.
|
|
348
|
-
#
|
|
349
|
-
# @raise [Bluebook::DSL::Malformed] if a needed fact has no attribute to fill
|
|
350
|
-
def refuse_undeclared_needs!
|
|
351
|
-
declared = attributes.map { |attribute| attribute.name.to_s }
|
|
352
|
-
missing = @needs.reject { |fact| declared.include?(fact.to_s) }
|
|
353
|
-
return if missing.empty?
|
|
354
|
-
|
|
355
|
-
raise Malformed,
|
|
356
|
-
"#{@name} needs :#{missing.first} but declares no attribute :#{missing.first} " \
|
|
357
|
-
"for the runtime to fill — add `attribute :#{missing.first}, <type>`"
|
|
358
|
-
end
|
|
359
|
-
private :refuse_undeclared_needs!
|
|
360
|
-
|
|
361
324
|
# Evaluates a `command` block against a fresh builder and returns what it built.
|
|
362
325
|
#
|
|
363
326
|
# @param name [String] the command's name
|
|
@@ -57,9 +57,9 @@ module Hecks
|
|
|
57
57
|
end
|
|
58
58
|
end
|
|
59
59
|
|
|
60
|
-
# The release that drops the deprecated spellings of `attaches`;
|
|
61
|
-
# `Hecks::Doors::REMOVAL`, which
|
|
62
|
-
REMOVAL = "3.
|
|
60
|
+
# The release that drops the deprecated spellings of `attaches`; independent of
|
|
61
|
+
# `Hecks::Doors::REMOVAL`, which covers the older `Facade` aliases.
|
|
62
|
+
REMOVAL = "3.4.0".freeze
|
|
63
63
|
|
|
64
64
|
attr_reader :binds, :subscriptions, :attachments
|
|
65
65
|
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
module Hecks
|
|
2
|
+
module Bluebook
|
|
3
|
+
module DSL
|
|
4
|
+
# The `needs` word, shared by the builders of the two declarations that may use it: a
|
|
5
|
+
# command and a query (ADR 0081). The including builder holds `@name` and `@needs` (an
|
|
6
|
+
# empty list) and answers `attributes`.
|
|
7
|
+
module NeedWord
|
|
8
|
+
# The outside facts a declaration may need, each the name of the attribute the runtime
|
|
9
|
+
# fills when the caller leaves it out. `now` is the clock port's reading in epoch seconds;
|
|
10
|
+
# `today` is the day it falls in, whole days since the epoch in UTC.
|
|
11
|
+
NEEDABLE_FACTS = %i[now today].freeze
|
|
12
|
+
|
|
13
|
+
# Declares an outside fact the runtime supplies before any given or filter runs:
|
|
14
|
+
# `needs :now` fills the declaration's own `now` attribute from the clock port when the
|
|
15
|
+
# caller names none.
|
|
16
|
+
#
|
|
17
|
+
# @param fact [Symbol] one of `NEEDABLE_FACTS`
|
|
18
|
+
# @return [Array<Symbol>] the facts declared so far
|
|
19
|
+
# @raise [Bluebook::DSL::Malformed] if the fact is not one the runtime can supply, or is
|
|
20
|
+
# declared twice
|
|
21
|
+
def needs_impl(fact)
|
|
22
|
+
fact = fact.to_sym
|
|
23
|
+
unless NEEDABLE_FACTS.include?(fact)
|
|
24
|
+
raise Malformed,
|
|
25
|
+
"#{@name} needs :#{fact}, which the runtime cannot supply — it supplies " \
|
|
26
|
+
"#{NEEDABLE_FACTS.map { |known| ":#{known}" }.join(', ')}"
|
|
27
|
+
end
|
|
28
|
+
raise Malformed, "#{@name} declares needs :#{fact} twice" if @needs.include?(fact)
|
|
29
|
+
|
|
30
|
+
@needs << fact
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
# A fact is filled into the argument of the same name, so the declaration has one.
|
|
36
|
+
#
|
|
37
|
+
# @raise [Bluebook::DSL::Malformed] if a needed fact has no attribute to fill
|
|
38
|
+
def refuse_undeclared_needs!
|
|
39
|
+
declared = attributes.map { |attribute| attribute.name.to_s }
|
|
40
|
+
missing = @needs.reject { |fact| declared.include?(fact.to_s) }
|
|
41
|
+
return if missing.empty?
|
|
42
|
+
|
|
43
|
+
raise Malformed,
|
|
44
|
+
"#{@name} needs :#{missing.first} but declares no attribute :#{missing.first} " \
|
|
45
|
+
"for the runtime to fill — add `attribute :#{missing.first}, <type>`"
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
require_relative "word_gate"
|
|
2
|
+
require_relative "need_word"
|
|
2
3
|
module Hecks
|
|
3
4
|
module Bluebook
|
|
4
5
|
module DSL
|
|
@@ -14,11 +15,13 @@ module Hecks
|
|
|
14
15
|
include AttributeCollector
|
|
15
16
|
include QuerySpecification::Common::DSL
|
|
16
17
|
include WordGate
|
|
18
|
+
include NeedWord
|
|
17
19
|
|
|
18
20
|
# @param name [String] the query's name, as written after `query`
|
|
19
21
|
def initialize(name)
|
|
20
22
|
@name = name
|
|
21
23
|
@wheres = []
|
|
24
|
+
@needs = []
|
|
22
25
|
end
|
|
23
26
|
|
|
24
27
|
# Sets the human-readable description shown for this query.
|
|
@@ -75,6 +78,7 @@ module Hecks
|
|
|
75
78
|
# implements
|
|
76
79
|
def build
|
|
77
80
|
seal_cursor
|
|
81
|
+
refuse_undeclared_needs!
|
|
78
82
|
Query.new(
|
|
79
83
|
name: @name,
|
|
80
84
|
description: @description,
|
|
@@ -87,7 +91,8 @@ module Hecks
|
|
|
87
91
|
authorization: @authorization,
|
|
88
92
|
null_semantics: @null_semantics,
|
|
89
93
|
inspection: @inspection,
|
|
90
|
-
returns: @returns
|
|
94
|
+
returns: @returns,
|
|
95
|
+
needs: @needs
|
|
91
96
|
)
|
|
92
97
|
end
|
|
93
98
|
|
data/lib/hecks/bluebook/query.rb
CHANGED
|
@@ -25,14 +25,14 @@ module Hecks
|
|
|
25
25
|
limit: one(:limit)
|
|
26
26
|
)
|
|
27
27
|
|
|
28
|
-
attr_reader :name, :description, :attributes, :returns
|
|
28
|
+
attr_reader :name, :description, :attributes, :returns, :needs
|
|
29
29
|
|
|
30
30
|
# @param null_semantics [QuerySpecification::Common::NullSemantics, nil] defaults to
|
|
31
31
|
# `NullSemantics.default`
|
|
32
32
|
def initialize(name:, description: nil, attributes: [], wheres: [],
|
|
33
33
|
order_by: nil, limit: nil, offset: nil, cursor: nil,
|
|
34
34
|
authorization: nil, null_semantics: nil,
|
|
35
|
-
inspection: nil, returns: nil)
|
|
35
|
+
inspection: nil, returns: nil, needs: [])
|
|
36
36
|
null_semantics ||= QuerySpecification::Common::NullSemantics.default
|
|
37
37
|
super(wheres: wheres, order_by: order_by, limit: limit, offset: offset, cursor: cursor,
|
|
38
38
|
authorization: authorization,
|
|
@@ -42,6 +42,7 @@ module Hecks
|
|
|
42
42
|
@description = description
|
|
43
43
|
@attributes = attributes
|
|
44
44
|
@returns = returns&.to_s
|
|
45
|
+
@needs = needs
|
|
45
46
|
end
|
|
46
47
|
|
|
47
48
|
# The value object a `returns` names, without the `list_of(...)` wrapper.
|
|
@@ -55,13 +56,15 @@ module Hecks
|
|
|
55
56
|
def returns_list? = !@returns.nil? && @returns.start_with?("list_of(")
|
|
56
57
|
|
|
57
58
|
# `extra_options_to_h` (count, median, group_by, scope_to) stays dynamic; `super` covers the
|
|
58
|
-
# declared emission. `returns`
|
|
59
|
-
# returns nothing keeps its wire shape.
|
|
59
|
+
# declared emission. `returns` and `needs` follow, only when the query declares them, so a
|
|
60
|
+
# query that returns nothing and needs nothing keeps its wire shape.
|
|
60
61
|
#
|
|
61
62
|
# @return [Hash] the declared emission merged with `extra_options_to_h`, then `returns`
|
|
63
|
+
# and `needs`
|
|
62
64
|
def to_h
|
|
63
65
|
shape = super.merge(extra_options_to_h)
|
|
64
|
-
|
|
66
|
+
shape = shape.merge(returns: @returns) if @returns
|
|
67
|
+
@needs.empty? ? shape : shape.merge(needs: @needs.map { |fact| { fact: fact.to_s } })
|
|
65
68
|
end
|
|
66
69
|
end
|
|
67
70
|
end
|
data/lib/hecks/cli/check_era.rb
CHANGED
|
@@ -2,11 +2,9 @@ require_relative "../ports/persistence/plugins/era/expected_era"
|
|
|
2
2
|
|
|
3
3
|
module Hecks
|
|
4
4
|
module CLI
|
|
5
|
-
# The
|
|
6
|
-
#
|
|
5
|
+
# The check behind Custodian's `Host.CheckEra`: asks a running host which era it reports and
|
|
6
|
+
# compares it with an allow-list file. Read-only: one GET, no writes.
|
|
7
7
|
module CheckEra
|
|
8
|
-
USAGE = "usage: hecks check_era <url> <expected-era-file> [--timeout=<seconds>]".freeze
|
|
9
|
-
|
|
10
8
|
# What a check found.
|
|
11
9
|
#
|
|
12
10
|
# @!attribute [r] verdict
|
|
@@ -14,7 +12,7 @@ module Hecks
|
|
|
14
12
|
# @!attribute [r] version
|
|
15
13
|
# @return [String] the gem version the host reports, empty when it reports none
|
|
16
14
|
# @!attribute [r] line
|
|
17
|
-
# @return [String] the sentence
|
|
15
|
+
# @return [String] the sentence the check reports for this finding
|
|
18
16
|
Finding = Struct.new(:verdict, :version, :line)
|
|
19
17
|
|
|
20
18
|
module_function
|
|
@@ -36,35 +34,6 @@ module Hecks
|
|
|
36
34
|
Finding.new(verdict, document.fetch("version"), sentence(verdict, file))
|
|
37
35
|
end
|
|
38
36
|
|
|
39
|
-
# Runs `hecks check_era`: prints the finding and answers the exit status.
|
|
40
|
-
#
|
|
41
|
-
# @param argv [Array<String>] the url, the allow-list file and an optional `--timeout=N`
|
|
42
|
-
# @param out [IO] where a finding goes
|
|
43
|
-
# @param err [IO] where a refusal goes
|
|
44
|
-
# @return [Integer] 0 when the era is expected or unlisted, 1 on a mismatch, 2 on bad
|
|
45
|
-
# usage or an unreadable file, 3 when the host cannot be reached or answers no era
|
|
46
|
-
def run(argv, out: $stdout, err: $stderr)
|
|
47
|
-
timeout = 10
|
|
48
|
-
args = argv.reject do |arg|
|
|
49
|
-
next false unless arg.start_with?("--timeout=")
|
|
50
|
-
|
|
51
|
-
timeout = arg.delete_prefix("--timeout=").to_f
|
|
52
|
-
true
|
|
53
|
-
end
|
|
54
|
-
url, file = args
|
|
55
|
-
return usage(err) unless url && file && args.size == 2 && timeout.positive?
|
|
56
|
-
|
|
57
|
-
finding = assess(url, file, timeout: timeout)
|
|
58
|
-
(finding.verdict.ok? ? out : err).puts(finding.line)
|
|
59
|
-
finding.verdict.ok? ? 0 : 1
|
|
60
|
-
rescue Errno::ENOENT, Errno::EACCES => e
|
|
61
|
-
err.puts("cannot read #{file}: #{e.message}")
|
|
62
|
-
2
|
|
63
|
-
rescue Runtime::EraCheck::ExpectedEra::Unreachable, Runtime::EraCheck::ExpectedEra::BadResponse => e
|
|
64
|
-
err.puts("era check failed: #{e.message}")
|
|
65
|
-
3
|
|
66
|
-
end
|
|
67
|
-
|
|
68
37
|
# @param verdict [Runtime::EraCheck::ExpectedEra::Verdict] the comparison
|
|
69
38
|
# @param file [String] the allow-list file, named in the sentence
|
|
70
39
|
# @return [String] the one line that says what the comparison found
|
|
@@ -78,11 +47,6 @@ module Hecks
|
|
|
78
47
|
"host reports era #{verdict.era}, which is not in #{file} (expected: #{verdict.allowed.join(', ')})"
|
|
79
48
|
end
|
|
80
49
|
end
|
|
81
|
-
|
|
82
|
-
def usage(err)
|
|
83
|
-
err.puts(USAGE)
|
|
84
|
-
2
|
|
85
|
-
end
|
|
86
50
|
end
|
|
87
51
|
end
|
|
88
52
|
end
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
require_relative "../naming"
|
|
2
2
|
require_relative "domain_stub"
|
|
3
|
+
require_relative "interview_shape"
|
|
3
4
|
|
|
4
5
|
module Hecks
|
|
5
6
|
module CLI
|
|
@@ -20,7 +21,11 @@ module Hecks
|
|
|
20
21
|
|
|
21
22
|
# What stands in for a creating command nothing in the interview named.
|
|
22
23
|
STUB_NOTE = " # TODO: no action that creates was accepted; replace Create with the real one.".freeze
|
|
23
|
-
|
|
24
|
+
|
|
25
|
+
# One accepted thing: what it is called and identified by, its fields, its transitions, the
|
|
26
|
+
# actions that create it and the others.
|
|
27
|
+
Shape = Struct.new(:name, :identifier, :type, :fields, :steps, :creating, :rest, keyword_init: true)
|
|
28
|
+
private_constant :PATTERN, :STUB_NOTE, :Shape
|
|
24
29
|
|
|
25
30
|
module_function
|
|
26
31
|
|
|
@@ -65,14 +70,21 @@ module Hecks
|
|
|
65
70
|
#
|
|
66
71
|
# @param interview [Object] the booted record
|
|
67
72
|
# @return [Hash] the plain interview this module takes: `reference`, `subject`, `expert`,
|
|
68
|
-
# `exchanges` (`question`, `answer`, `topic`), and `things`, `actions
|
|
69
|
-
# finding with its `number`, `source` and `status`
|
|
73
|
+
# `exchanges` (`question`, `answer`, `topic`), and `things`, `actions`, `rules`,
|
|
74
|
+
# `fields` and `transitions`, each a finding with its `number`, `source` and `status`
|
|
75
|
+
# and the fields of its kind
|
|
70
76
|
def from_record(interview)
|
|
71
77
|
{ reference: plain(interview.reference), subject: plain(interview.subject), expert: plain(interview.expert),
|
|
72
78
|
exchanges: interview.exchanges.map { |ex| slice(ex, :question, :answer, :topic) },
|
|
73
79
|
things: interview.thing_findings.map { |f| slice(f, :number, :name, :identifier, :source, :status) },
|
|
74
|
-
actions: interview.action_findings.map
|
|
75
|
-
|
|
80
|
+
actions: interview.action_findings.map do |f|
|
|
81
|
+
slice(f, :number, :name, :thing, :event, :creates, :takes, :by, :source, :status)
|
|
82
|
+
end,
|
|
83
|
+
rules: interview.rule_findings.map { |f| slice(f, :number, :statement, :source, :status) },
|
|
84
|
+
fields: interview.field_findings.map { |f| slice(f, :number, :thing, :name, :values, :source, :status) },
|
|
85
|
+
transitions: interview.transition_findings.map do |f|
|
|
86
|
+
slice(f, :number, :thing, :action, :to, :from, :source, :status)
|
|
87
|
+
end }
|
|
76
88
|
end
|
|
77
89
|
|
|
78
90
|
# @api private
|
|
@@ -127,24 +139,48 @@ module Hecks
|
|
|
127
139
|
|
|
128
140
|
# @api private
|
|
129
141
|
def aggregate(interview, thing, actions)
|
|
142
|
+
shape = shape_of(interview, thing, actions)
|
|
143
|
+
lines = head_lines(interview, thing, shape)
|
|
144
|
+
lines.concat(command_lines(interview, shape))
|
|
145
|
+
lines.concat(InterviewShape.lifecycle(shape.steps, shape.creating, shape.rest))
|
|
146
|
+
(lines << " end").join("\n")
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# What one accepted thing is made of: its fields, its actions, and its steps.
|
|
150
|
+
# @api private
|
|
151
|
+
def shape_of(interview, thing, actions)
|
|
130
152
|
name = word(thing[:name])
|
|
131
|
-
|
|
132
|
-
field_type = identifier_type(name, field)
|
|
153
|
+
identifier = Naming.snake(word(thing[:identifier]))
|
|
133
154
|
mine = actions.select { |a| word(a[:thing]) == name }
|
|
155
|
+
steps = accepted(interview, :transitions).select { |s| word(s[:thing]) == name }
|
|
156
|
+
found = accepted(interview, :fields).select { |f| word(f[:thing]) == name }
|
|
157
|
+
type = identifier_type(name, identifier)
|
|
158
|
+
fields = InterviewShape.fields(found, name, identifier, type, lifecycle: !steps.empty?)
|
|
134
159
|
creating = mine.select { |a| a[:creates] }
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
160
|
+
Shape.new(name: name, identifier: identifier, type: type, fields: fields, steps: steps,
|
|
161
|
+
creating: creating, rest: mine - creating)
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# The aggregate's opening: its identity, its fields, and the value object of each.
|
|
165
|
+
# @api private
|
|
166
|
+
def head_lines(interview, thing, shape)
|
|
167
|
+
required = shape.creating.flat_map { |action| InterviewShape.takes(action) }
|
|
168
|
+
[" # #{source_note(interview, thing)}".rstrip,
|
|
169
|
+
" aggregate #{shape.name.inspect} do",
|
|
170
|
+
" description #{"TODO: describe what a #{shape.name} is.".inspect}", "",
|
|
171
|
+
" identified_by :#{shape.identifier}", "",
|
|
172
|
+
" attribute :#{shape.identifier}, #{shape.type}",
|
|
173
|
+
*InterviewShape.attributes(shape.fields, required), "",
|
|
174
|
+
" value_object #{shape.type.inspect} do",
|
|
175
|
+
" attribute :value, String, pattern: #{PATTERN}", " end",
|
|
176
|
+
*InterviewShape.value_objects(shape.fields)]
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# @api private
|
|
180
|
+
def command_lines(interview, shape)
|
|
181
|
+
creating = shape.creating.empty? ? [stub_create(shape.name)] : shape.creating
|
|
182
|
+
creating.flat_map { |action| creating_command(interview, action, shape) } +
|
|
183
|
+
shape.rest.flat_map { |action| mutating_command(interview, action, shape) }
|
|
148
184
|
end
|
|
149
185
|
|
|
150
186
|
# The creating command nothing in the interview named: a stub, marked for the developer.
|
|
@@ -152,19 +188,24 @@ module Hecks
|
|
|
152
188
|
def stub_create(name) = { name: "Create", event: "#{name}Created", stub: true }
|
|
153
189
|
|
|
154
190
|
# @api private
|
|
155
|
-
def creating_command(interview, action,
|
|
191
|
+
def creating_command(interview, action, shape)
|
|
156
192
|
verb = word(action[:name])
|
|
157
193
|
note = action[:stub] ? STUB_NOTE : " # #{source_note(interview, action)}"
|
|
158
|
-
|
|
159
|
-
|
|
194
|
+
given, unknown = InterviewShape.inputs(action, shape.fields, shape.identifier)
|
|
195
|
+
["", note, " command #{verb.inspect} do", " goal #{"TODO: say what #{verb} does".inspect}",
|
|
196
|
+
*InterviewShape.who_lines(action), "", " attribute :#{shape.identifier}, #{shape.type}",
|
|
197
|
+
*given.map { |f| " attribute :#{f[:name]}, #{f[:type]}" }, *InterviewShape.unknown_lines(unknown), "",
|
|
198
|
+
" emits #{word(action[:event]).inspect}", " end"]
|
|
160
199
|
end
|
|
161
200
|
|
|
162
201
|
# @api private
|
|
163
|
-
def mutating_command(interview, action,
|
|
202
|
+
def mutating_command(interview, action, shape)
|
|
164
203
|
verb = word(action[:name])
|
|
204
|
+
given, unknown = InterviewShape.inputs(action, shape.fields, shape.identifier)
|
|
165
205
|
["", " # #{source_note(interview, action)}", " command #{verb.inspect} do",
|
|
166
|
-
" goal #{"TODO: say what #{verb} does".inspect}",
|
|
167
|
-
"
|
|
206
|
+
" goal #{"TODO: say what #{verb} does".inspect}", *InterviewShape.who_lines(action), "",
|
|
207
|
+
" reference_to #{shape.name}", *InterviewShape.input_lines(given), *InterviewShape.unknown_lines(unknown),
|
|
208
|
+
*InterviewShape.assignment_lines(given), "", " emits #{word(action[:event]).inspect}", " end"]
|
|
168
209
|
end
|
|
169
210
|
|
|
170
211
|
# An action whose thing nobody accepted has nowhere to go; it is kept as a comment, not lost.
|
|
@@ -196,10 +237,15 @@ module Hecks
|
|
|
196
237
|
lines = interview.fetch(:things).map do |f|
|
|
197
238
|
"- Thing #{f[:number]}, #{f[:status]}: **#{f[:name]}**, identified by `#{f[:identifier]}` (exchange #{f[:source]})"
|
|
198
239
|
end
|
|
199
|
-
lines += interview.fetch(:actions).map
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
240
|
+
lines += interview.fetch(:actions).map { |f| action_line(f) }
|
|
241
|
+
lines += interview.fetch(:fields, []).map do |f|
|
|
242
|
+
values = f[:values].to_s.strip.empty? ? "" : ", one of #{f[:values]}"
|
|
243
|
+
"- Field #{f[:number]}, #{f[:status]}: **#{f[:name]}** on #{f[:thing]}#{values} (exchange #{f[:source]})"
|
|
244
|
+
end
|
|
245
|
+
lines += interview.fetch(:transitions, []).map do |f|
|
|
246
|
+
from = f[:from].to_s.strip.empty? ? "" : " from #{f[:from]}"
|
|
247
|
+
"- Transition #{f[:number]}, #{f[:status]}: **#{f[:action]}** on #{f[:thing]} to #{f[:to]}#{from} " \
|
|
248
|
+
"(exchange #{f[:source]})"
|
|
203
249
|
end
|
|
204
250
|
lines + interview.fetch(:rules).map do |f|
|
|
205
251
|
"- Rule #{f[:number]}, #{f[:status]}: #{one_line(f[:statement])} (exchange #{f[:source]})"
|
|
@@ -207,7 +253,16 @@ module Hecks
|
|
|
207
253
|
end
|
|
208
254
|
|
|
209
255
|
# @api private
|
|
210
|
-
def
|
|
256
|
+
def action_line(finding)
|
|
257
|
+
creates = finding[:creates] ? ", creates it" : ""
|
|
258
|
+
takes = finding[:takes].to_s.strip.empty? ? "" : ", takes #{finding[:takes]}"
|
|
259
|
+
by = finding[:by].to_s.strip.empty? ? "" : ", by #{finding[:by]}"
|
|
260
|
+
"- Action #{finding[:number]}, #{finding[:status]}: **#{finding[:name]}** on #{finding[:thing]}, " \
|
|
261
|
+
"announcing `#{finding[:event]}`#{creates}#{takes}#{by} (exchange #{finding[:source]})"
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
# @api private
|
|
265
|
+
def accepted(interview, kind) = interview.fetch(kind, []).select { |finding| finding[:status] == ACCEPTED }
|
|
211
266
|
|
|
212
267
|
# A free-text name as a PascalCase word safe to write into Ruby: `lend a book` is `LendABook`.
|
|
213
268
|
# @api private
|