hecks 3.1.3 → 3.2.1
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/dsl/hecksagon_builder.rb +3 -3
- data/lib/hecks/cli/check_era.rb +3 -39
- data/lib/hecks/deploy/bluebook/deploy.bluebook +105 -0
- data/lib/hecks/deploy/bluebook/deploy.hecksagon +8 -0
- data/lib/hecks/deploy/oidc.json +15 -0
- data/lib/hecks/doors/mcp_door.rb +16 -2
- data/lib/hecks/doors/mcp_door_scope.rb +43 -4
- data/lib/hecks/doors/mcp_guide_cache.rb +75 -0
- data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +17 -1
- data/lib/hecks/hecks/adapters/codebase/test_runner.rb +10 -2
- data/lib/hecks/hecks/adapters/cost_explorer.adapter +7 -0
- data/lib/hecks/hecks/adapters/cost_explorer.rb +102 -0
- data/lib/hecks/hecks/adapters/rust_workspace.rb +1 -1
- data/lib/hecks/hecks/adapters/site_toolchain.rb +21 -11
- data/lib/hecks/hecks/hecks.hecksagon +8 -0
- data/lib/hecks/language/bluebook/vocabulary.bluebook +7 -7
- data/lib/hecks/language/hecksagon/hecksagon.bluebook +1 -1
- data/lib/hecks/ports/persistence/plugins/era/expected_era.rb +3 -3
- data/lib/hecks/projections/deploy/box/settings.rb +23 -2
- data/lib/hecks/projections/deploy/box/templates/deploy-box.sh.tmpl +3 -1
- data/lib/hecks/projections/deploy/box/templates/render-compose-taskdef.sh.tmpl +17 -0
- data/lib/hecks/projections/deploy/box.rb +13 -2
- 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/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/rust_build/kernel_input.rb +18 -6
- data/lib/hecks/rust_build/project_rust.rb +6 -132
- data/lib/hecks/site/bluebook/site.bluebook +27 -13
- data/lib/hecks/storehouse.rb +35 -1
- data/lib/hecks/tools/comment_style.rb +4 -2
- data/lib/hecks/tools/deploy_recipe.rb +1 -0
- data/lib/hecks/tools/rust_comment_style.rb +1 -1
- data/lib/hecks/tools/site_routes.rb +69 -28
- data/lib/hecks/version.rb +1 -1
- data/lib/hecks/vocabulary.rb +1 -1
- data/lib/hecks.rb +1 -0
- data/rust/build/src/lineage_pass.rs +1 -1
- data/rust/build/src/pipeline.rs +1 -1
- data/rust/codegen/src/attr.rs +1 -1
- data/rust/codegen/src/bridging.rs +1 -1
- 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_codec.rs +1 -1
- data/rust/codegen/src/main.rs +3 -4
- data/rust/codegen/src/mutations.rs +1 -1
- data/rust/codegen/src/naming.rs +1 -1
- 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/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/auth.rs +25 -1
- data/rust/host/src/needs.rs +94 -27
- data/rust/host/src/web.rs +9 -5
- 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 +2 -2
- data/rust/src/kernel/read_model.rs +1 -1
- data/rust/src/lib.rs +1 -1
- metadata +5 -23
- 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
|
@@ -252,6 +252,7 @@ module Hecks
|
|
|
252
252
|
# @return [void]
|
|
253
253
|
def write(artifact, out_dir)
|
|
254
254
|
written = Hecks::Projector.write(artifact, out_dir, as: :files)
|
|
255
|
+
written.each { |path| File.chmod(0o755, path) if path.end_with?(".sh") }
|
|
255
256
|
|
|
256
257
|
written.each { |path| puts "wrote #{path}" }
|
|
257
258
|
puts
|
|
@@ -30,7 +30,7 @@ module Hecks
|
|
|
30
30
|
DOC_LINE = %r{\A\s*(?:///(?!/)|//!)}
|
|
31
31
|
|
|
32
32
|
# `// TMPL:name BEGIN`/`END` sentinels are parsed verbatim by
|
|
33
|
-
# `rust/
|
|
33
|
+
# `rust/codegen/src/exemplar.rs`; every prose rule skips them outright.
|
|
34
34
|
TMPL_MARKER = /\bTMPL:\S+\s+(?:BEGIN|END)\b/i
|
|
35
35
|
MOD_DOC_LINE = %r{\A\s*//!}
|
|
36
36
|
ATTR_LINE = /\A\s*#\[/
|
|
@@ -14,41 +14,47 @@ module Hecks
|
|
|
14
14
|
# it declares an edge, into the behaviours and listener rules of its template
|
|
15
15
|
# (`Projections::Site::SiteCdn`).
|
|
16
16
|
#
|
|
17
|
-
# hecks site site_projection.project_site [<project>] [--out=<dir>] [--
|
|
17
|
+
# hecks site site_projection.project_site [<project>] [--out=<dir>] [--template=<file>]
|
|
18
|
+
# [--extension=ts|mts] [--check]
|
|
18
19
|
#
|
|
19
|
-
# `<project>`
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
# names each file that differs from the table.
|
|
20
|
+
# `<project>` (default: the working directory) holds `bluebook/` and the `vendor/` it attaches
|
|
21
|
+
# from. `routes.ts` goes to `<project>/generated` or to `--out`, anywhere. The template is the
|
|
22
|
+
# `Edge` row's, relative to the project, or `--template`, anywhere; it is rewritten in place
|
|
23
|
+
# between its `BEGIN`/`END GENERATED site_cdn` markers. With `--out` alone, `--out` receives a
|
|
24
|
+
# copy of it instead. `--extension=mts` names the module `routes.mts`; `--check` writes nothing.
|
|
25
25
|
module SiteRoutes
|
|
26
26
|
# Where the files go, relative to the project, when `--out` names no directory.
|
|
27
27
|
DEFAULT_OUT = "generated"
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
# The extensions the module may take; the first is the default.
|
|
30
|
+
EXTENSIONS = %w[ts mts].freeze
|
|
31
|
+
|
|
32
|
+
USAGE = "usage: hecks site site_projection.project_site [<project>] [--out=<dir>] " \
|
|
33
|
+
"[--template=<file>] [--extension=#{EXTENSIONS.join('|')}] [--check]".freeze
|
|
30
34
|
|
|
31
35
|
module_function
|
|
32
36
|
|
|
33
37
|
# Projects the route table, writes what differs and prints a line for each file written.
|
|
34
38
|
#
|
|
35
39
|
# @param argv [Array<String>] the project directory, then the flags
|
|
36
|
-
# @param root [String] the
|
|
40
|
+
# @param root [String] the project used when `argv` names none; the working directory
|
|
37
41
|
# @return [Integer] 0, or 1 when `--check` finds a file out of date
|
|
38
|
-
# @raise [SystemExit] with the reason on stderr when the project declares no route table
|
|
39
|
-
#
|
|
40
|
-
def main(argv, root:
|
|
42
|
+
# @raise [SystemExit] with the reason on stderr when the project declares no route table, the
|
|
43
|
+
# table is refused, or a flag is refused
|
|
44
|
+
def main(argv, root: Dir.pwd)
|
|
41
45
|
argv = argv.dup
|
|
42
|
-
out = nil
|
|
46
|
+
out = template = extension = nil
|
|
43
47
|
check = false
|
|
44
48
|
OptionParser.new do |parser|
|
|
45
49
|
parser.on("--out=DIR") { |value| out = value }
|
|
50
|
+
parser.on("--template=FILE") { |value| template = value }
|
|
51
|
+
parser.on("--extension=EXT") { |value| extension = value }
|
|
46
52
|
parser.on("--check") { check = true }
|
|
47
53
|
end.parse!(argv)
|
|
48
54
|
project = argv.empty? ? root : File.expand_path(argv.shift)
|
|
49
55
|
abort USAGE unless argv.empty?
|
|
50
56
|
|
|
51
|
-
files = projection(project, out: out)
|
|
57
|
+
files = projection(project, out: out, template: template, extension: extension)
|
|
52
58
|
stale = files.reject { |path, text| File.file?(path) && File.read(path) == text }.keys
|
|
53
59
|
return report(stale, project, check) if check
|
|
54
60
|
|
|
@@ -63,16 +69,26 @@ module Hecks
|
|
|
63
69
|
|
|
64
70
|
# @param root [String] the project directory
|
|
65
71
|
# @param out [String, nil] the directory to write to; `generated/` of the project when nil
|
|
72
|
+
# @param template [String, nil] the template to rewrite in place, wherever it lies; the
|
|
73
|
+
# `Edge` row's template, relative to the project, when nil
|
|
74
|
+
# @param extension [String, nil] `ts` (the default) or `mts`, the module's file extension
|
|
66
75
|
# @return [Hash{String => String}] each file's absolute path to the text it should hold
|
|
67
|
-
# @raise [SystemExit] when the project declares no route table
|
|
68
|
-
|
|
76
|
+
# @raise [SystemExit] when the project declares no route table, the table is refused, or a
|
|
77
|
+
# path or the extension is refused
|
|
78
|
+
def projection(root, out: nil, template: nil, extension: nil)
|
|
79
|
+
extension ||= EXTENSIONS.first
|
|
80
|
+
unless EXTENSIONS.include?(extension)
|
|
81
|
+
abort "project_site: --extension is one of #{EXTENSIONS.join(', ')}, not #{extension.inspect}"
|
|
82
|
+
end
|
|
83
|
+
dir = out ? File.expand_path(out) : File.join(root, DEFAULT_OUT)
|
|
84
|
+
abort "project_site: --out #{dir} is a file; it names the directory routes.#{extension} goes in" if File.file?(dir)
|
|
85
|
+
|
|
69
86
|
registry = registry_for(root)
|
|
70
87
|
chapter = Projections::Site::Table.chapter(registry)
|
|
71
88
|
table = Projections::Site::Table.read(chapter, registry: registry)
|
|
72
89
|
files = Projector.call(:site_routes_ts, bluebook: chapter, options: { table: table })
|
|
73
|
-
|
|
74
|
-
written
|
|
75
|
-
written.merge(template_files(root, out, chapter, table, registry))
|
|
90
|
+
written = files.to_h { |name, text| [File.join(dir, name.sub(/\.ts\z/, ".#{extension}")), text] }
|
|
91
|
+
written.merge(template_files(root, out, chapter, table, registry, template: template))
|
|
76
92
|
rescue Projections::Site::Table::Invalid => e
|
|
77
93
|
abort "project_site: #{e.message}"
|
|
78
94
|
end
|
|
@@ -84,25 +100,50 @@ module Hecks
|
|
|
84
100
|
# @param chapter [Bluebook::Chapter] the chapter that declares the route table
|
|
85
101
|
# @param table [Projections::Site::Table] the checked table
|
|
86
102
|
# @param registry [Hecks::Runtime::Registry] the registry the project booted into
|
|
103
|
+
# @param template [String, nil] a template named on the command line, rewritten in place
|
|
87
104
|
# @return [Hash{String => String}] the template's path to its text; empty with no edge
|
|
88
|
-
# @raise [SystemExit] when
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
105
|
+
# @raise [SystemExit] when a template is named and the project declares no edge, the
|
|
106
|
+
# template is missing or lacks a region it needs, or holds a region an edge without a load
|
|
107
|
+
# balancer does not use
|
|
108
|
+
def template_files(root, out, chapter, table, registry, template: nil)
|
|
109
|
+
edge = Projections::Site::Edge.read(chapter, table: table, template: template,
|
|
110
|
+
vocabulary: Projections::Site::Table.vocabulary(registry))
|
|
111
|
+
unless edge
|
|
112
|
+
abort "project_site: --template names #{template}, but the project declares no Edge rows" if template
|
|
113
|
+
return {}
|
|
114
|
+
end
|
|
92
115
|
|
|
93
116
|
regions = Projector.call(:site_cdn, bluebook: chapter, options: { table: table, edge: edge })
|
|
94
|
-
relative = edge.setting.template
|
|
95
|
-
source = File.join(root, relative)
|
|
96
|
-
|
|
117
|
+
relative = template || edge.setting.template
|
|
118
|
+
source = template ? File.expand_path(template) : File.join(root, relative)
|
|
119
|
+
unless File.file?(source)
|
|
120
|
+
abort "project_site: the template #{relative} does not exist#{" in #{root}" unless template}"
|
|
121
|
+
end
|
|
97
122
|
|
|
98
|
-
text =
|
|
123
|
+
text = rewritten(File.read(source), regions, relative, edge)
|
|
124
|
+
in_place = template || out.nil?
|
|
125
|
+
{ (in_place ? source : File.join(File.expand_path(out), relative)) => text }
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# @param text [String] the template
|
|
129
|
+
# @param regions [Hash{String => String}] each region's name to the block that goes in it
|
|
130
|
+
# @param relative [String] the template's name, for a message
|
|
131
|
+
# @param edge [Projections::Site::Edge] the checked edge
|
|
132
|
+
# @return [String] the template with each region rewritten
|
|
133
|
+
# @raise [SystemExit] when a region is missing, or a listener_rules region is left behind
|
|
134
|
+
# by an edge with no load balancer
|
|
135
|
+
def rewritten(text, regions, relative, edge)
|
|
136
|
+
if !edge.alb? && Projections::Site::Regions.region?(text, "listener_rules")
|
|
137
|
+
abort "project_site: #{relative} has a BEGIN/END GENERATED site_cdn listener_rules region, " \
|
|
138
|
+
"and the Edge row says alb: false; remove the region"
|
|
139
|
+
end
|
|
140
|
+
regions.reduce(text) do |current, (name, block)|
|
|
99
141
|
unless Projections::Site::Regions.region?(current, name)
|
|
100
142
|
abort "project_site: #{relative} has no BEGIN/END GENERATED site_cdn #{name} region"
|
|
101
143
|
end
|
|
102
144
|
|
|
103
145
|
Projections::Site::Regions.replace(current, name, block)
|
|
104
146
|
end
|
|
105
|
-
{ (out ? File.join(File.expand_path(out), relative) : source) => text }
|
|
106
147
|
end
|
|
107
148
|
|
|
108
149
|
# Loads the project's chapters the way a deploy does: the bluebooks, then the hecksagons that
|
data/lib/hecks/version.rb
CHANGED
data/lib/hecks/vocabulary.rb
CHANGED
|
@@ -51,7 +51,7 @@ module Hecks
|
|
|
51
51
|
].freeze,
|
|
52
52
|
"CiGate" => [
|
|
53
53
|
{"name"=>"runtime_changed", "workflow"=>"ci.yml", "mode"=>"touches", "pattern"=>"^lib/hecks/runtime/", "push"=>"skip", "label"=>"does this change touch lib/hecks/runtime/**?"}.freeze,
|
|
54
|
-
{"name"=>"postgres_io_relevant_changed", "workflow"=>"ci-postgres-io-parallel.yml", "mode"=>"skips_unless", "pattern"=>"^(docs/|editors/|release/|deploy/|\\.claude/|\\.githooks/|rust/(parser|codegen|host|build|lsp|web|tests
|
|
54
|
+
{"name"=>"postgres_io_relevant_changed", "workflow"=>"ci-postgres-io-parallel.yml", "mode"=>"skips_unless", "pattern"=>"^(docs/|editors/|release/|deploy/|\\.claude/|\\.githooks/|rust/(parser|codegen|host|build|lsp|web|tests)/|\\.rubocop\\.yml$|\\.rubocop_todo\\.yml$|\\.mcp\\.json$|\\.rspec-local\\.example$|README\\.md$|CHANGELOG\\.md$|CONTRIBUTING\\.md$|SECURITY\\.md$|LICENSE$|\\.gitignore$)", "push"=>"before_sha", "label"=>"does this change touch anything rspec_postgres_io_parallel covers?"}.freeze
|
|
55
55
|
].freeze,
|
|
56
56
|
"Comparison" => [
|
|
57
57
|
{"symbol"=>">=", "compares_less_than"=>"true", "compares_equal"=>"false", "negated"=>"true"}.freeze,
|
data/lib/hecks.rb
CHANGED
|
@@ -34,6 +34,7 @@ require_relative "hecks/doors/cli_runner"
|
|
|
34
34
|
require_relative "hecks/storehouse"
|
|
35
35
|
require_relative "hecks/mcp_stdio_guard"
|
|
36
36
|
require_relative "hecks/doors/mcp_door_scope"
|
|
37
|
+
require_relative "hecks/doors/mcp_guide_cache"
|
|
37
38
|
require_relative "hecks/framework"
|
|
38
39
|
require_relative "hecks/vendoring"
|
|
39
40
|
require_relative "hecks/embryonaut_bluebook"
|
data/rust/build/src/pipeline.rs
CHANGED
data/rust/codegen/src/attr.rs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//! Port of `
|
|
1
|
+
//! Port of the retired Ruby generator's `dependency_planning.rb`, an independent re-derivation of the
|
|
2
2
|
//! Analyzer's `complete_state? && state_independent?` predicate. Mirrors the Ruby file.
|
|
3
3
|
|
|
4
4
|
use crate::json::Json;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//! Port of `
|
|
1
|
+
//! Port of the retired Ruby generator's `domain_generator.rb`'s `DomainGenerator.call`: the per-aggregate
|
|
2
2
|
//! `.rs` files plus `registry.rs`. `metadata.rs` and `ir.json` are not emitted here.
|
|
3
3
|
|
|
4
4
|
use crate::exemplar::Exemplar;
|
|
@@ -1181,7 +1181,7 @@ pub fn generate(
|
|
|
1181
1181
|
None,
|
|
1182
1182
|
None,
|
|
1183
1183
|
Some(format!(
|
|
1184
|
-
"read via rust/host's journal::read_lineage_head_all/_by_id, written via journal::append_lineage_mutation — both generic over storage_name (\"{}\"), dispatched OUTSIDE the WASM kernel/InMemoryRepository path entirely, matching Ruby's own CommandInterpreter routing for a Postgres-bound aggregate (rust/
|
|
1184
|
+
"read via rust/host's journal::read_lineage_head_all/_by_id, written via journal::append_lineage_mutation — both generic over storage_name (\"{}\"), dispatched OUTSIDE the WASM kernel/InMemoryRepository path entirely, matching Ruby's own CommandInterpreter routing for a Postgres-bound aggregate (rust/codegen/src/domain_generator.rs)",
|
|
1185
1185
|
lineage_aggregate.get("storage_name").map(Json::to_s).unwrap_or_default()
|
|
1186
1186
|
)),
|
|
1187
1187
|
);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//! Port of `
|
|
1
|
+
//! Port of the retired Ruby generator's `exemplar.rb`: loads the template shapes in `rust/src/exemplar/*.rs`.
|
|
2
2
|
//! Both codegens read those files at runtime, so the shapes cannot drift.
|
|
3
3
|
use std::collections::HashMap;
|
|
4
4
|
use std::path::PathBuf;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//! Port of `
|
|
1
|
+
//! Port of the retired Ruby generator's `expr_emitter.rb`: turns each IR rule's `ast` tree into `Expr` source.
|
|
2
2
|
//! A literal-array `include?` never reaches here; `AstJson` rewrites it to an or of equalities.
|
|
3
3
|
|
|
4
4
|
use crate::json::Json;
|
data/rust/codegen/src/fielded.rs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//! Port of `
|
|
1
|
+
//! Port of the retired Ruby generator's `json_codec.rb`, the JSON boundary generator: `to_json`/`from_json`
|
|
2
2
|
//! codecs, closed-set codecs, and `extract_id`/`extract_wants`/`self_identity`.
|
|
3
3
|
|
|
4
4
|
use crate::exemplar::Exemplar;
|
data/rust/codegen/src/main.rs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//! `hecks-codegen` —
|
|
1
|
+
//! `hecks-codegen` — the IR-to-Rust-source generator, the only one (ADR 0086).
|
|
2
2
|
//! Usage: `hecks-codegen <prelude|domain|full> ...`, one subcommand per function below.
|
|
3
3
|
// Some ported `naming.rs` helpers are not called yet.
|
|
4
4
|
#![allow(dead_code)]
|
|
@@ -110,7 +110,7 @@ fn write_domain(
|
|
|
110
110
|
mod_name: &str,
|
|
111
111
|
out_dir: &str,
|
|
112
112
|
) -> Result<domain_generator::GeneratedDomain, String> {
|
|
113
|
-
// Refuse before writing anything
|
|
113
|
+
// Refuse before writing anything, so a name collision is reported before any file exists.
|
|
114
114
|
let aggregate_names: Vec<&str> = ir
|
|
115
115
|
.get("aggregates")
|
|
116
116
|
.map(Json::each)
|
|
@@ -257,8 +257,7 @@ fn run_full(args: &[String]) -> Result<(), String> {
|
|
|
257
257
|
puts_blank(&mut merged_rs);
|
|
258
258
|
puts_str(&mut merged_rs, &reactions::emit_identity_head_table(&ex, &merged_aggregates));
|
|
259
259
|
puts_blank(&mut merged_rs);
|
|
260
|
-
// Section order
|
|
261
|
-
// this file byte for byte.
|
|
260
|
+
// Section order is part of the output: `hecks regenerate_corpus --check` diffs this file byte for byte.
|
|
262
261
|
puts_str(&mut merged_rs, &reactions::emit_entity_identity_head_table(&ex, &merged_aggregates));
|
|
263
262
|
puts_blank(&mut merged_rs);
|
|
264
263
|
puts_str(&mut merged_rs, &reactions::emit_command_attributes_table(&ex, &merged_aggregates));
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//! Port of `
|
|
1
|
+
//! Port of the retired Ruby generator's `mutations.rb`, function for function.
|
|
2
2
|
//! `mark_append_optional_fields!` is not ported: `Json` has no mutation API and the corpus
|
|
3
3
|
//! already declares `optional: true` on every field that pass would touch.
|
|
4
4
|
|
data/rust/codegen/src/naming.rs
CHANGED
data/rust/codegen/src/ports.rs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//! Port of `
|
|
1
|
+
//! Port of the retired Ruby generator's `ports.rb` — read that file's own header comments
|
|
2
2
|
//! in full; this mirrors its algorithm directly, function for function.
|
|
3
3
|
|
|
4
4
|
use crate::exemplar::Exemplar;
|
data/rust/codegen/src/queries.rs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//! Port of `
|
|
1
|
+
//! Port of the retired Ruby generator's `queries.rb`, mirrored function for function.
|
|
2
2
|
|
|
3
3
|
use crate::exemplar::Exemplar;
|
|
4
4
|
use crate::json::Json;
|
|
@@ -302,7 +302,7 @@ pub fn query_skip_reason(query: &Json, aggregate: &Json, value_objects_by_name:
|
|
|
302
302
|
let extra_keys = ["cursor", "consistency", "freshness", "inspection"];
|
|
303
303
|
let extras: Vec<&str> = extra_keys.iter().filter(|k| query.get(k).is_some()).copied().collect();
|
|
304
304
|
if !extras.is_empty() {
|
|
305
|
-
return Some(skip(extras[0], format!("declares {} — out of scope for this generator (rust/
|
|
305
|
+
return Some(skip(extras[0], format!("declares {} — out of scope for this generator (rust/codegen/src/queries.rs's own header has the full argument)", extras.join(", "))));
|
|
306
306
|
}
|
|
307
307
|
if query.get("index_hints").map(Json::each).unwrap_or(&[]).iter().any(|_| true) {
|
|
308
308
|
return Some(skip("index_hints", "declares use_index, out of scope for the same reason the extras above are"));
|
|
@@ -780,7 +780,7 @@ pub fn emit_query_arg_check_table(query_defs: &[QueryDef]) -> String {
|
|
|
780
780
|
})
|
|
781
781
|
.collect();
|
|
782
782
|
format!(
|
|
783
|
-
"/// C3.7 for a named query's own arguments — `query_arg_checks`\n/// (rust/
|
|
783
|
+
"/// C3.7 for a named query's own arguments — `query_arg_checks`\n/// (rust/codegen/src/queries.rs) has the full story.\npub fn check_query_args(verb: &str, args: &crate::kernel::Json) -> Result<(), crate::kernel::Refusal> {{\n match verb {{\n{}\n _ => Ok(()),\n }}\n}}\n",
|
|
784
784
|
arms.join("\n")
|
|
785
785
|
)
|
|
786
786
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//! Port of `
|
|
1
|
+
//! Port of the retired Ruby generator's `read_models.rb`, mirrored function for function —
|
|
2
2
|
//! read that file's own header for the full algorithm.
|
|
3
3
|
|
|
4
4
|
use crate::exemplar::Exemplar;
|
|
@@ -267,7 +267,7 @@ fn aggregation_skip_reason(read_model: &Json, aggregates_by_name: &HashMap<Strin
|
|
|
267
267
|
None
|
|
268
268
|
}
|
|
269
269
|
|
|
270
|
-
// Mirrors `
|
|
270
|
+
// Mirrors the retired Ruby generator's `read_models.rb`'s `group_by_skip_reason`: the one
|
|
271
271
|
// shape the corpus declares — a single rootless head, group_by alone.
|
|
272
272
|
fn group_by_skip_reason(read_model: &Json, aggregates_by_name: &HashMap<String, &Json>, unsupported_names: &[String]) -> Option<SkipReason> {
|
|
273
273
|
let heads = read_model.get("aggregate_heads").map(Json::each).unwrap_or(&[]);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//! Command router emitter, ported from `
|
|
1
|
+
//! Command router emitter, ported from the retired Ruby generator's `registry.rb`.
|
|
2
2
|
//! The entry structs mirror the Hashes `domain_generator.rb` accumulates while walking the IR.
|
|
3
3
|
|
|
4
4
|
use crate::exemplar::Exemplar;
|
|
@@ -144,7 +144,7 @@ pub fn emit_role_check(
|
|
|
144
144
|
))
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
-
/// Port of `
|
|
147
|
+
/// Port of the retired Ruby generator's `registry.rb#emit_tenant_boundary_check`.
|
|
148
148
|
/// Hand-built: the target accessor differs for a single-attribute value object and a bare scalar.
|
|
149
149
|
pub fn emit_tenant_boundary_check(check: &TenantBoundaryCheck) -> String {
|
|
150
150
|
let ref_ident = naming::rust_ident_field(&check.reference_field);
|
|
@@ -740,7 +740,7 @@ pub fn emit_registry(exemplar: &Exemplar, aggregates: &[AggregateEntry]) -> Stri
|
|
|
740
740
|
format!("{header}{body}")
|
|
741
741
|
}
|
|
742
742
|
|
|
743
|
-
/// Port of `
|
|
743
|
+
/// Port of the retired Ruby generator's `registry.rb#emit_reference_table`.
|
|
744
744
|
pub fn emit_reference_table(aggregates: &[AggregateEntry]) -> String {
|
|
745
745
|
let rows: Vec<String> = aggregates
|
|
746
746
|
.iter()
|
|
@@ -760,7 +760,7 @@ pub fn emit_reference_table(aggregates: &[AggregateEntry]) -> String {
|
|
|
760
760
|
)
|
|
761
761
|
}
|
|
762
762
|
|
|
763
|
-
/// Port of `
|
|
763
|
+
/// Port of the retired Ruby generator's `registry.rb#emit_reference_lookup`.
|
|
764
764
|
pub fn emit_reference_lookup(aggregates: &[AggregateEntry]) -> String {
|
|
765
765
|
let arms: Vec<String> = aggregates
|
|
766
766
|
.iter()
|
data/rust/codegen/src/shared.rs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//! Predicates ported from `
|
|
1
|
+
//! Predicates ported from the retired Ruby generator's `mutations.rb` that `types.rs` and `json_codec.rs` share.
|
|
2
2
|
|
|
3
3
|
use crate::json::Json;
|
|
4
4
|
use std::collections::HashMap;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//! A skip decision plus the construct family that forced it (`
|
|
1
|
+
//! A skip decision plus the construct family that forced it (the retired Ruby generator's `skip_reason.rb`).
|
|
2
2
|
//! `construct` goes to `manifest.json` beside the reason so tools read the family, not the prose.
|
|
3
3
|
|
|
4
4
|
use std::fmt;
|
data/rust/codegen/src/types.rs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//! Type emitters ported from `
|
|
1
|
+
//! Type emitters ported from the retired Ruby generator's `types.rb`: records, entities, value objects and
|
|
2
2
|
//! closed-set tables.
|
|
3
3
|
|
|
4
4
|
use crate::exemplar::Exemplar;
|
|
@@ -286,7 +286,7 @@ pub fn emit_record(exemplar: &Exemplar, aggregate: &Json, value_objects_by_name:
|
|
|
286
286
|
format!("{struct_part}\n\n{fielded_part}")
|
|
287
287
|
}
|
|
288
288
|
|
|
289
|
-
/// Port of `
|
|
289
|
+
/// Port of the retired Ruby generator's `types.rb#projected_field_pseudo_attributes` (ADR 0025).
|
|
290
290
|
pub fn projected_field_pseudo_attributes(aggregate: &Json) -> Vec<Json> {
|
|
291
291
|
aggregate
|
|
292
292
|
.get("projected_fields")
|
|
@@ -318,7 +318,7 @@ pub fn with_projected_field_pseudo_attributes(aggregate: &Json) -> Json {
|
|
|
318
318
|
Json::Object(pairs)
|
|
319
319
|
}
|
|
320
320
|
|
|
321
|
-
/// Port of `
|
|
321
|
+
/// Port of the retired Ruby generator's `types.rb#emit_set_projected_field`.
|
|
322
322
|
pub fn emit_set_projected_field(aggregate: &Json) -> String {
|
|
323
323
|
let name = naming::rust_ident(aggregate.get("name").and_then(Json::as_str).unwrap_or(""));
|
|
324
324
|
let arms: Vec<String> = aggregate
|
|
@@ -338,7 +338,7 @@ pub fn emit_set_projected_field(aggregate: &Json) -> String {
|
|
|
338
338
|
)
|
|
339
339
|
}
|
|
340
340
|
|
|
341
|
-
/// Port of `
|
|
341
|
+
/// Port of the retired Ruby generator's `types.rb#emit_projected_field_table`.
|
|
342
342
|
pub fn emit_projected_field_table(aggregate: &Json) -> String {
|
|
343
343
|
let name = naming::screaming_snake(aggregate.get("name").and_then(Json::as_str).unwrap_or(""));
|
|
344
344
|
let rows: Vec<String> = aggregate
|
data/rust/host/HECKS_RELEASE
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
3.1
|
|
1
|
+
3.2.1
|
data/rust/host/src/auth.rs
CHANGED
|
@@ -592,8 +592,25 @@ pub async fn active_role(client: &Mutex<Client>, domain_ir: &Value, email: &str)
|
|
|
592
592
|
.and_then(|(_, state)| role_of(state).map(String::from)))
|
|
593
593
|
}
|
|
594
594
|
|
|
595
|
+
/// The Governance identity id linked to the member with `email`, read from
|
|
596
|
+
/// the live membership head. `None` when the member has not signed in yet.
|
|
597
|
+
pub async fn member_identity_id(client: &Mutex<Client>, domain_ir: &Value, email: &str) -> anyhow::Result<Option<String>> {
|
|
598
|
+
let email = email.to_lowercase();
|
|
599
|
+
Ok(member_rows(client, domain_ir)
|
|
600
|
+
.await?
|
|
601
|
+
.iter()
|
|
602
|
+
.find(|(id, state)| id.to_lowercase() == email && !is_deleted(state))
|
|
603
|
+
.and_then(|(_, state)| linked_identity_id(state)))
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
// The id the sign-in flow wrote onto a Member row, or `None` before then.
|
|
607
|
+
fn linked_identity_id(state: &Value) -> Option<String> {
|
|
608
|
+
state.get("identity_id").and_then(|v| v.get("value")).and_then(|v| v.as_str()).map(str::to_string)
|
|
609
|
+
}
|
|
610
|
+
|
|
595
611
|
/// Every admitted person as a JSON row: name, email, role, `linked`
|
|
596
|
-
/// (signed in), `granted` (has access), and `
|
|
612
|
+
/// (signed in), `granted` (has access), `disabled`, and `identity_id`
|
|
613
|
+
/// (the Governance identity id, `null` until the person has signed in).
|
|
597
614
|
pub async fn all_people(client: &Mutex<Client>, domain_ir: &Value) -> anyhow::Result<Vec<Value>> {
|
|
598
615
|
Ok(member_rows(client, domain_ir)
|
|
599
616
|
.await?
|
|
@@ -610,6 +627,7 @@ pub async fn all_people(client: &Mutex<Client>, domain_ir: &Value) -> anyhow::Re
|
|
|
610
627
|
"linked": linked,
|
|
611
628
|
"granted": role.is_some() && !disabled,
|
|
612
629
|
"disabled": disabled,
|
|
630
|
+
"identity_id": linked_identity_id(&state),
|
|
613
631
|
})
|
|
614
632
|
})
|
|
615
633
|
.collect())
|
|
@@ -1206,9 +1224,15 @@ mod tests {
|
|
|
1206
1224
|
let chris = people.iter().find(|p| p["email"] == "chris@example.com").unwrap();
|
|
1207
1225
|
assert_eq!(chris["linked"], true);
|
|
1208
1226
|
assert_eq!(chris["granted"], true);
|
|
1227
|
+
assert_eq!(chris["identity_id"], "id-1");
|
|
1209
1228
|
let angie = people.iter().find(|p| p["email"] == "angie@example.com").unwrap();
|
|
1210
1229
|
assert_eq!(angie["linked"], false);
|
|
1211
1230
|
assert_eq!(angie["granted"], false);
|
|
1231
|
+
assert!(angie["identity_id"].is_null());
|
|
1232
|
+
|
|
1233
|
+
assert_eq!(member_identity_id(&db, &domain_ir, "Chris@Example.com").await.unwrap().as_deref(), Some("id-1"));
|
|
1234
|
+
assert_eq!(member_identity_id(&db, &domain_ir, "angie@example.com").await.unwrap(), None);
|
|
1235
|
+
assert_eq!(member_identity_id(&db, &domain_ir, "nobody@example.com").await.unwrap(), None);
|
|
1212
1236
|
}
|
|
1213
1237
|
|
|
1214
1238
|
#[tokio::test]
|