hecks 3.0.5 → 3.1.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/exe/hecks +24 -12
- data/lib/hecks/adapters/driven/lambda/client.rb +1 -1
- data/lib/hecks/adapters/driven/lambda.rb +1 -1
- data/lib/hecks/adapters/driven/tenant_provisioner.rb +2 -2
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +59 -32
- data/lib/hecks/bluebook/hexagon.rb +45 -25
- data/lib/hecks/bluebook/model_check.rb +7 -7
- data/lib/hecks/chapters.rb +32 -3
- data/lib/hecks/cli/check_era.rb +1 -1
- data/lib/hecks/cli/deploy_template_diff.rb +1 -1
- data/lib/hecks/cli/doc_coverage.rb +1 -1
- data/lib/hecks/cli/domain_stub.rb +128 -0
- data/lib/hecks/cli/domain_writer.rb +39 -0
- data/lib/hecks/cli/interview_agent.rb +59 -0
- data/lib/hecks/cli/interview_draft.rb +246 -0
- data/lib/hecks/cli/interview_run.rb +87 -0
- data/lib/hecks/cli/interview_session.rb +299 -0
- data/lib/hecks/cli/model_check.rb +1 -1
- data/lib/hecks/cli/project_cli.rb +51 -29
- data/lib/hecks/cli/reference.rb +2 -1
- data/lib/hecks/cli/refresh_rspec_runtime_baseline.rb +1 -1
- data/lib/hecks/cli/seed_semantics_corpus.rb +3 -3
- data/lib/hecks/cli/spec_example.rb +1 -1
- data/lib/hecks/cli/stress_concurrency_specs.rb +14 -5
- data/lib/hecks/corpus.rb +12 -8
- data/lib/hecks/deploy/bluebook/deploy.bluebook +85 -12
- data/lib/hecks/deploy/oidc.json +5 -0
- data/lib/hecks/doc/reference.rb +4 -3
- data/lib/hecks/doors/cli_door.rb +13 -13
- data/lib/hecks/doors/cli_runner.rb +53 -33
- data/lib/hecks/doors/launcher_options.rb +5 -5
- data/lib/hecks/doors/mcp_door.rb +40 -18
- data/lib/hecks/doors/mcp_door_scope.rb +244 -24
- data/lib/hecks/doors/usage_cache.rb +123 -0
- data/lib/hecks/doors.rb +1 -1
- data/lib/hecks/embryonaut_bluebook.rb +2 -2
- data/lib/hecks/forms/banking_presentation.rb +1 -1
- data/lib/hecks/framework/bluebook/compliance.bluebook +1 -1
- data/lib/hecks/framework/bluebook/console_settings.bluebook +1 -1
- data/lib/hecks/framework/bluebook/framework.hecksagon +4 -4
- data/lib/hecks/framework.rb +1 -1
- data/lib/hecks/fuzzing/combination_miner.rb +1 -1
- data/lib/hecks/fuzzing/concurrent_dispatch.rb +1 -1
- data/lib/hecks/fuzzing/differential.rb +4 -2
- data/lib/hecks/fuzzing/era_boundary.rb +1 -1
- data/lib/hecks/fuzzing/form_census.rb +2 -2
- data/lib/hecks/fuzzing/generated_domain_check.rb +1 -1
- data/lib/hecks/fuzzing/isolated_boot.rb +10 -3
- data/lib/hecks/fuzzing/persistence_parity.rb +2 -2
- data/lib/hecks/fuzzing/target_capabilities.rb +3 -3
- data/lib/hecks/gate/stages.yml +5 -5
- data/lib/hecks/grammar/grammar.hecksagon +2 -2
- data/lib/hecks/hecks/adapters/deploy_toolchain.rb +4 -4
- data/lib/hecks/hecks/adapters/finding_github.adapter +7 -0
- data/lib/hecks/hecks/adapters/finding_github.rb +114 -0
- data/lib/hecks/hecks/adapters/host_http.rb +4 -3
- data/lib/hecks/hecks/adapters/local_files.rb +59 -0
- data/lib/hecks/hecks/adapters/site_toolchain.adapter +7 -0
- data/lib/hecks/hecks/adapters/site_toolchain.rb +54 -0
- data/lib/hecks/hecks/adapters/terminal.rb +15 -0
- data/lib/hecks/hecks/codebase.bluebook +4 -3
- data/lib/hecks/hecks/context_map.hecksagon +2 -2
- data/lib/hecks/hecks/custodian.bluebook +81 -3
- data/lib/hecks/hecks/hecks.bluebook +18 -0
- data/lib/hecks/hecks/hecks.hecksagon +16 -4
- data/lib/hecks/hecks/hecks.world +7 -6
- data/lib/hecks/hecks/site.behaviors +17 -0
- data/lib/hecks/hecks/site.world +10 -0
- data/lib/hecks/language/bluebook/bluebook.hecksagon +1 -1
- data/lib/hecks/language/hecksagon/hecksagon.bluebook +24 -47
- data/lib/hecks/ports/persistence/plugins/era/era_check.rb +4 -4
- data/lib/hecks/projections/deploy/box/settings.rb +324 -0
- data/lib/hecks/projections/deploy/box/templates/box.yaml.tmpl +268 -0
- data/lib/hecks/projections/deploy/box/templates/deploy-box.sh.tmpl +68 -0
- data/lib/hecks/projections/deploy/box/templates/fetch-secrets.sh +21 -0
- data/lib/hecks/projections/deploy/box/templates/rds.yaml.tmpl +181 -0
- data/lib/hecks/projections/deploy/box/templates/render-compose-taskdef.sh.tmpl +49 -0
- data/lib/hecks/projections/deploy/box/templates/render-compose.sh.tmpl +43 -0
- data/lib/hecks/projections/deploy/box/templates/restore-to-rds.sh.tmpl +91 -0
- data/lib/hecks/projections/deploy/box/templates/verify-copy.sh.tmpl +58 -0
- data/lib/hecks/projections/deploy/box.rb +553 -0
- data/lib/hecks/projections/deploy/fargate/cdn.rb +13 -1
- data/lib/hecks/projections/deploy/fargate.rb +1 -1
- data/lib/hecks/projections/deploy/lambda.rb +11 -10
- data/lib/hecks/projections/deploy/scripts/hosting.mk.tmpl +1 -1
- data/lib/hecks/projections/diagrams.rb +2 -2
- data/lib/hecks/projections/site/edge/behaviours.rb +107 -0
- data/lib/hecks/projections/site/edge/checks.rb +137 -0
- data/lib/hecks/projections/site/edge/pattern.rb +50 -0
- data/lib/hecks/projections/site/edge/rules.rb +128 -0
- data/lib/hecks/projections/site/edge.rb +136 -0
- data/lib/hecks/projections/site/regions.rb +44 -0
- data/lib/hecks/projections/site/routes_ts.rb +284 -0
- data/lib/hecks/projections/site/site_cdn.rb +78 -0
- data/lib/hecks/projections/site/table/checks.rb +124 -0
- data/lib/hecks/projections/site/table/row_builder.rb +155 -0
- data/lib/hecks/projections/site/table.rb +98 -0
- data/lib/hecks/projections.rb +2 -0
- data/lib/hecks/projector/cli_projector.rb +153 -111
- data/lib/hecks/quality_control/adapters/agent.adapter +1 -1
- data/lib/hecks/quality_control/adapters/agent.rb +2 -2
- data/lib/hecks/quality_control/cli/qa_concurrency_racer.rb +3 -3
- data/lib/hecks/quality_control/cli/qa_discover_external_domains.rb +8 -8
- data/lib/hecks/quality_control/cli/qa_domain_novelty.rb +3 -3
- data/lib/hecks/quality_control/cli/qa_generated_domains.rb +4 -4
- data/lib/hecks/quality_control/cli/qa_log_bug.rb +2 -2
- data/lib/hecks/quality_control/cli/qa_mine_combinations.rb +6 -5
- data/lib/hecks/quality_control/cli/qa_postgres_migrate.rb +2 -2
- data/lib/hecks/quality_control/cli/qa_postgres_role.rb +5 -5
- data/lib/hecks/quality_control/cli/qa_pr_check.rb +3 -3
- data/lib/hecks/quality_control/cli/qa_sweep/all_mode.rb +3 -3
- data/lib/hecks/quality_control/cli/qa_sweep/checks.rb +2 -2
- data/lib/hecks/quality_control/cli/qa_sweep.rb +10 -9
- data/lib/hecks/quality_control/cli/qa_tick.rb +3 -3
- data/lib/hecks/quality_control/quality_control.bluebook +18 -18
- data/lib/hecks/quality_control/quality_control.hecksagon +1 -1
- data/lib/hecks/runtime/loader.rb +31 -8
- data/lib/hecks/runtime/registry/verification.rb +15 -29
- data/lib/hecks/runtime/registry.rb +14 -20
- data/lib/hecks/rust_build/append_optionals.rb +79 -0
- data/lib/hecks/rust_build/conformance_fuzz.rb +2 -1
- data/lib/hecks/rust_build/domain_name.rb +26 -0
- data/lib/hecks/rust_build/kernel_input.rb +58 -0
- data/lib/hecks/rust_build/project_rust/codegen_run.rb +143 -0
- data/lib/hecks/rust_build/project_rust/root_mod.rb +1 -1
- data/lib/hecks/rust_build/project_rust.rb +57 -24
- data/lib/hecks/rust_build/rust_literal.rb +33 -0
- data/lib/hecks/rust_build/write_if_changed.rb +86 -0
- data/lib/hecks/site/bluebook/site.bluebook +205 -0
- data/lib/hecks/site/bluebook/site.hecksagon +5 -0
- data/lib/hecks/site/bluebook/site.ports.hecksagon +15 -0
- data/lib/hecks/sme/bluebook/sme.bluebook +336 -0
- data/lib/hecks/sme/bluebook/sme.hecksagon +5 -0
- data/lib/hecks/storehouse.rb +37 -7
- data/lib/hecks/tenancy/bluebook/tenancy.bluebook +1 -1
- data/lib/hecks/three_zero/forms.yml +88 -88
- data/lib/hecks/tickets/bluebook/tickets.bluebook +331 -0
- data/lib/hecks/tickets/bluebook/tickets.hecksagon +11 -0
- data/lib/hecks/tickets/bluebook/tickets.ports.hecksagon +26 -0
- data/lib/hecks/tools/ci_gates.rb +1 -1
- data/lib/hecks/tools/deploy_recipe.rb +14 -6
- data/lib/hecks/tools/oidc_manifests.rb +2 -1
- data/lib/hecks/tools/sequence_script.rb +2 -2
- data/lib/hecks/tools/site_routes.rb +152 -0
- data/lib/hecks/tools/tenant_provisioning.rb +1 -1
- data/lib/hecks/tools.rb +1 -0
- data/lib/hecks/version.rb +1 -1
- data/lib/hecks.rb +1 -0
- data/rust/build/src/cargo_sync.rs +1 -1
- data/rust/build/src/pipeline.rs +23 -16
- data/rust/build/src/resolve.rs +39 -6
- data/rust/codegen/src/json_codec.rs +3 -0
- data/rust/codegen/src/naming.rs +4 -0
- data/rust/codegen/src/types.rs +45 -0
- data/rust/host/HECKS_RELEASE +1 -1
- data/rust/host/src/api.rs +1 -1
- data/rust/host/src/dispatch.rs +87 -7
- data/rust/host/src/needs.rs +111 -0
- data/rust/parser/src/keywords.rs +7 -6
- data/rust/parser/src/main.rs +4 -3
- data/rust/parser/src/parse/chapter.rs +1 -1
- data/rust/parser/src/parse/hecksagon.rs +25 -10
- data/rust/project/mutations.rb +4 -30
- data/rust/project/naming.rb +8 -28
- data/rust/project/write_if_changed.rb +4 -80
- data/rust/project_rust_pipeline.rb +16 -12
- data/rust/src/kernel/cli.rs +2 -0
- data/rust/src/kernel/needs.rs +74 -1
- metadata +51 -2
|
@@ -9,7 +9,8 @@ module Hecks
|
|
|
9
9
|
# `oidc.json` beside the domain.
|
|
10
10
|
#
|
|
11
11
|
# A domain that has no bluebook is skipped and one that cannot be projected is reported, so a
|
|
12
|
-
# sweep over the whole checkout carries on past either. `hecks deploy
|
|
12
|
+
# sweep over the whole checkout carries on past either. `hecks deploy
|
|
13
|
+
# oidc_manifest.project_oidc` loads
|
|
13
14
|
# `bundler/setup` first, which pins the `json` gem: newer releases pretty-print an empty array
|
|
14
15
|
# as `[\n\n]`, a diff spec/oidc_manifest_spec.rb would report as drift.
|
|
15
16
|
module OidcManifests
|
|
@@ -10,8 +10,8 @@ module Hecks
|
|
|
10
10
|
#
|
|
11
11
|
# hecks generate_sequence <domain> [--seed N] [--steps N] [--adversarial fraction]
|
|
12
12
|
#
|
|
13
|
-
# `--adversarial` mutates the sequence as `hecks quality_control
|
|
14
|
-
# default.
|
|
13
|
+
# `--adversarial` mutates the sequence as `hecks quality_control query sweep.run` does; 0 (off)
|
|
14
|
+
# by default.
|
|
15
15
|
module SequenceScript
|
|
16
16
|
USAGE = "usage: hecks generate_sequence <domain> [--seed N] [--steps N] [--adversarial FRACTION]"
|
|
17
17
|
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "hecks"
|
|
4
|
+
require "fileutils"
|
|
5
|
+
require "optparse"
|
|
6
|
+
require_relative "../tools"
|
|
7
|
+
require_relative "../projections/site/routes_ts"
|
|
8
|
+
require_relative "../projections/site/site_cdn"
|
|
9
|
+
require_relative "../projections/site/regions"
|
|
10
|
+
|
|
11
|
+
module Hecks
|
|
12
|
+
module Tools
|
|
13
|
+
# Projects a project's route table into `routes.ts` (`Projections::Site::RoutesTs`) and, when
|
|
14
|
+
# it declares an edge, into the behaviours and listener rules of its template
|
|
15
|
+
# (`Projections::Site::SiteCdn`).
|
|
16
|
+
#
|
|
17
|
+
# hecks site site_projection.project_site [<project>] [--out=<dir>] [--check]
|
|
18
|
+
#
|
|
19
|
+
# `<project>` is the directory whose `bluebook/` holds the chapter that declares the rows and a
|
|
20
|
+
# hecksagon that attaches Site; it defaults to the checkout. `routes.ts` is written to
|
|
21
|
+
# `<project>/generated` unless `--out` names another directory. The template is rewritten in
|
|
22
|
+
# place between its `BEGIN`/`END GENERATED site_cdn` markers, or under `--out` a copy of it is
|
|
23
|
+
# written at the same relative path. With `--check` nothing is written: the tool answers 1 and
|
|
24
|
+
# names each file that differs from the table.
|
|
25
|
+
module SiteRoutes
|
|
26
|
+
# Where the files go, relative to the project, when `--out` names no directory.
|
|
27
|
+
DEFAULT_OUT = "generated"
|
|
28
|
+
|
|
29
|
+
USAGE = "usage: hecks site site_projection.project_site [<project>] [--out=<dir>] [--check]"
|
|
30
|
+
|
|
31
|
+
module_function
|
|
32
|
+
|
|
33
|
+
# Projects the route table, writes what differs and prints a line for each file written.
|
|
34
|
+
#
|
|
35
|
+
# @param argv [Array<String>] the project directory, then the flags
|
|
36
|
+
# @param root [String] the checkout, used as the project when `argv` names none
|
|
37
|
+
# @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 or
|
|
39
|
+
# the table is refused
|
|
40
|
+
def main(argv, root: Tools::ROOT)
|
|
41
|
+
argv = argv.dup
|
|
42
|
+
out = nil
|
|
43
|
+
check = false
|
|
44
|
+
OptionParser.new do |parser|
|
|
45
|
+
parser.on("--out=DIR") { |value| out = value }
|
|
46
|
+
parser.on("--check") { check = true }
|
|
47
|
+
end.parse!(argv)
|
|
48
|
+
project = argv.empty? ? root : File.expand_path(argv.shift)
|
|
49
|
+
abort USAGE unless argv.empty?
|
|
50
|
+
|
|
51
|
+
files = projection(project, out: out)
|
|
52
|
+
stale = files.reject { |path, text| File.file?(path) && File.read(path) == text }.keys
|
|
53
|
+
return report(stale, project, check) if check
|
|
54
|
+
|
|
55
|
+
stale.each do |path|
|
|
56
|
+
FileUtils.mkdir_p(File.dirname(path))
|
|
57
|
+
File.write(path, files.fetch(path))
|
|
58
|
+
puts "wrote #{display(path, project)}"
|
|
59
|
+
end
|
|
60
|
+
puts "project_site: #{files.size} #{files.size == 1 ? 'file' : 'files'}, current" if stale.empty?
|
|
61
|
+
0
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# @param root [String] the project directory
|
|
65
|
+
# @param out [String, nil] the directory to write to; `generated/` of the project when nil
|
|
66
|
+
# @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 or the table is refused
|
|
68
|
+
def projection(root, out: nil)
|
|
69
|
+
registry = registry_for(root)
|
|
70
|
+
chapter = Projections::Site::Table.chapter(registry)
|
|
71
|
+
table = Projections::Site::Table.read(chapter, registry: registry)
|
|
72
|
+
files = Projector.call(:site_routes_ts, bluebook: chapter, options: { table: table })
|
|
73
|
+
dir = out ? File.expand_path(out) : File.join(root, DEFAULT_OUT)
|
|
74
|
+
written = files.to_h { |name, text| [File.join(dir, name), text] }
|
|
75
|
+
written.merge(template_files(root, out, chapter, table, registry))
|
|
76
|
+
rescue Projections::Site::Table::Invalid => e
|
|
77
|
+
abort "project_site: #{e.message}"
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# The template with the edge's regions rewritten, when the project declares an edge.
|
|
81
|
+
#
|
|
82
|
+
# @param root [String] the project directory
|
|
83
|
+
# @param out [String, nil] the directory to write to, or nil to rewrite the template in place
|
|
84
|
+
# @param chapter [Bluebook::Chapter] the chapter that declares the route table
|
|
85
|
+
# @param table [Projections::Site::Table] the checked table
|
|
86
|
+
# @param registry [Hecks::Runtime::Registry] the registry the project booted into
|
|
87
|
+
# @return [Hash{String => String}] the template's path to its text; empty with no edge
|
|
88
|
+
# @raise [SystemExit] when the template is missing or lacks a region
|
|
89
|
+
def template_files(root, out, chapter, table, registry)
|
|
90
|
+
edge = Projections::Site::Edge.read(chapter, table: table, vocabulary: Projections::Site::Table.vocabulary(registry))
|
|
91
|
+
return {} unless edge
|
|
92
|
+
|
|
93
|
+
regions = Projector.call(:site_cdn, bluebook: chapter, options: { table: table, edge: edge })
|
|
94
|
+
relative = edge.setting.template
|
|
95
|
+
source = File.join(root, relative)
|
|
96
|
+
abort "project_site: the template #{relative} does not exist in #{root}" unless File.file?(source)
|
|
97
|
+
|
|
98
|
+
text = regions.reduce(File.read(source)) do |current, (name, block)|
|
|
99
|
+
unless Projections::Site::Regions.region?(current, name)
|
|
100
|
+
abort "project_site: #{relative} has no BEGIN/END GENERATED site_cdn #{name} region"
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
Projections::Site::Regions.replace(current, name, block)
|
|
104
|
+
end
|
|
105
|
+
{ (out ? File.join(File.expand_path(out), relative) : source) => text }
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Loads the project's chapters the way a deploy does: the bluebooks, then the hecksagons that
|
|
109
|
+
# attach the chapters they need.
|
|
110
|
+
#
|
|
111
|
+
# @param root [String] the project directory
|
|
112
|
+
# @return [Hecks::Runtime::Registry] the registry
|
|
113
|
+
# @raise [SystemExit] when the project has no `bluebook/` directory
|
|
114
|
+
def registry_for(root)
|
|
115
|
+
dir = File.join(root, "bluebook")
|
|
116
|
+
unless Dir.exist?(dir)
|
|
117
|
+
abort "project_site: #{dir} does not exist; a project declares its route table in a bluebook/ directory"
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
registry = Hecks::Runtime::Registry.new(root: root)
|
|
121
|
+
lib_hecks = File.expand_path("..", __dir__)
|
|
122
|
+
Hecks.with_registry(registry) do
|
|
123
|
+
%w[ports/persistence.port ports/extraction.port adapters/driven/memory.adapter
|
|
124
|
+
adapters/driven/prism.adapter].each { |file| Kernel.load(File.join(lib_hecks, file)) }
|
|
125
|
+
Dir.glob(File.join(dir, "*.bluebook")).each { |file| Kernel.load(file) }
|
|
126
|
+
Dir.glob(File.join(dir, "*.hecksagon")).each { |file| Kernel.load(file) }
|
|
127
|
+
end
|
|
128
|
+
registry
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# @param stale [Array<String>] absolute paths whose text differs from the table
|
|
132
|
+
# @param project [String] the project directory
|
|
133
|
+
# @param check [Boolean] whether this is a `--check` run
|
|
134
|
+
# @return [Integer] 0 when current, else 1 with the stale files on stderr
|
|
135
|
+
def report(stale, project, _check)
|
|
136
|
+
if stale.empty?
|
|
137
|
+
puts "project_site: every file current"
|
|
138
|
+
return 0
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
warn "project_site: out of date: #{stale.map { |path| display(path, project) }.join(', ')} " \
|
|
142
|
+
"(run hecks site site_projection.project_site)"
|
|
143
|
+
1
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# @param path [String] an absolute path
|
|
147
|
+
# @param project [String] the project directory
|
|
148
|
+
# @return [String] the path relative to the project when it lies beneath it
|
|
149
|
+
def display(path, project) = path.delete_prefix("#{project}/")
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
|
@@ -16,7 +16,7 @@ module Hecks
|
|
|
16
16
|
# merge) the base `persisted_by` settings, so `--database` is required. A policy's refusal is
|
|
17
17
|
# swallowed by `PolicyInterpreter#deliver`, so success is confirmed by query.
|
|
18
18
|
#
|
|
19
|
-
# hecks deploy provision <domain-directory> <slug> --domain=name --realm=name \
|
|
19
|
+
# hecks deploy tenant.provision <domain-directory> <slug> --domain=name --realm=name \
|
|
20
20
|
# --schema=name --database=name [--adapter=PostgresEra]
|
|
21
21
|
module TenantProvisioning
|
|
22
22
|
USAGE = "usage: hecks deploy provision <domain-directory> <slug> --domain=NAME --realm=NAME " \
|
data/lib/hecks/tools.rb
CHANGED
|
@@ -24,6 +24,7 @@ module Hecks
|
|
|
24
24
|
"project_ci_gates" => ["tools/ci_gates", "CiGates"],
|
|
25
25
|
"decide_ci_gate" => ["tools/ci_gate_decision", "CiGateDecision"],
|
|
26
26
|
"project_deploy" => ["tools/deploy_recipe", "DeployRecipe"],
|
|
27
|
+
"project_site" => ["tools/site_routes", "SiteRoutes"],
|
|
27
28
|
"lint_deploy_recipes" => ["tools/deploy_recipe_lint", "DeployRecipeLint"],
|
|
28
29
|
"project_oidc" => ["tools/oidc_manifests", "OidcManifests"],
|
|
29
30
|
"project_tenant" => ["tools/tenant_provisioning", "TenantProvisioning"],
|
data/lib/hecks/version.rb
CHANGED
data/lib/hecks.rb
CHANGED
|
@@ -29,6 +29,7 @@ require_relative "hecks/projector"
|
|
|
29
29
|
require_relative "hecks/projections"
|
|
30
30
|
# After `Projector`, which dispatches against the `:cli` projection.
|
|
31
31
|
require_relative "hecks/doors/cli_door"
|
|
32
|
+
require_relative "hecks/doors/usage_cache"
|
|
32
33
|
require_relative "hecks/doors/cli_runner"
|
|
33
34
|
require_relative "hecks/storehouse"
|
|
34
35
|
require_relative "hecks/mcp_stdio_guard"
|
|
@@ -50,7 +50,7 @@ fn write_root_mod(out_root: &Path, all_dirs: &[String], domains: &[String], targ
|
|
|
50
50
|
out.push_str("// chapter (governance/identity — no merged.rs of its own, so absent\n");
|
|
51
51
|
out.push_str("// from `domains`) stays unconditional: it has no single feature of its\n");
|
|
52
52
|
out.push_str("// own to gate behind, and is compiled in by whichever domain(s) attach\n");
|
|
53
|
-
out.push_str("// it via `
|
|
53
|
+
out.push_str("// it via `attaches`.\n");
|
|
54
54
|
for name in all_dirs {
|
|
55
55
|
if domains.iter().any(|d| d == name) {
|
|
56
56
|
out.push_str(&format!("#[cfg(feature = {name:?})]\n"));
|
data/rust/build/src/pipeline.rs
CHANGED
|
@@ -66,10 +66,10 @@ pub fn run(root: &Path, domain: &str, opts: &Options) -> Result<(), String> {
|
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
let (
|
|
69
|
+
let (gem_chapter_names, vendored_package_names) = match &hecksagon_path {
|
|
70
70
|
Some(p) => (
|
|
71
|
-
resolve::
|
|
72
|
-
resolve::
|
|
71
|
+
resolve::resolve_gem_chapters(&parser_bin, &target_chapter_name, p)?,
|
|
72
|
+
resolve::resolve_vendored_packages(&parser_bin, &target_chapter_name, p)?,
|
|
73
73
|
),
|
|
74
74
|
None => (Vec::new(), Vec::new()),
|
|
75
75
|
};
|
|
@@ -85,45 +85,52 @@ pub fn run(root: &Path, domain: &str, opts: &Options) -> Result<(), String> {
|
|
|
85
85
|
lineage_pass::run(&mut target_ir, hecksagon_path.as_deref(), world_path.as_deref(), root)?;
|
|
86
86
|
let target_ir_text = crate::json::write(&target_ir);
|
|
87
87
|
|
|
88
|
-
// Every other chapter
|
|
88
|
+
// Every other chapter the gem supplies by `attaches "Name"`, resolved through the
|
|
89
89
|
// same directory listing (`resolve::framework_members`) the Ruby
|
|
90
|
-
// pipeline itself calls.
|
|
90
|
+
// pipeline itself calls. A gem chapter that is not a framework member (the language,
|
|
91
|
+
// Tenancy, Deploy) is loaded by the Ruby runtime alone, so it is checked and skipped.
|
|
91
92
|
let framework_members = resolve::framework_members(root)?;
|
|
93
|
+
let other_gem_chapters = resolve::other_gem_chapter_names(root)?;
|
|
92
94
|
let mut chapters: Vec<Chapter> = Vec::new();
|
|
93
|
-
for fw_name in &
|
|
95
|
+
for fw_name in &gem_chapter_names {
|
|
96
|
+
if other_gem_chapters.contains(fw_name) && !framework_members.iter().any(|(name, _)| name == fw_name) {
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
94
99
|
let fw_path = framework_members
|
|
95
100
|
.iter()
|
|
96
101
|
.find(|(name, _)| name == fw_name)
|
|
97
102
|
.map(|(_, path)| path.clone())
|
|
98
103
|
.ok_or_else(|| {
|
|
99
|
-
let known: Vec<&str> = framework_members.iter().map(|(n, _)| n.as_str()).collect();
|
|
100
|
-
|
|
104
|
+
let mut known: Vec<&str> = framework_members.iter().map(|(n, _)| n.as_str()).collect();
|
|
105
|
+
known.extend(other_gem_chapters.iter().map(String::as_str));
|
|
106
|
+
known.sort();
|
|
107
|
+
format!("hecks-build: attaches {fw_name:?} names no chapter the gem carries — known: {}", known.join(", "))
|
|
101
108
|
})?;
|
|
102
109
|
let fw_chapter_name = resolve::header_chapter_name(&fw_path)?;
|
|
103
110
|
if &fw_chapter_name != fw_name {
|
|
104
111
|
return Err(format!(
|
|
105
|
-
"hecks-build: {} declares chapter {fw_chapter_name:?}, but
|
|
112
|
+
"hecks-build: {} declares chapter {fw_chapter_name:?}, but attaches named {fw_name:?}",
|
|
106
113
|
fw_path.display()
|
|
107
114
|
));
|
|
108
115
|
}
|
|
109
116
|
let fw_ir_text = parse_chapter_with_optionals(&parser_bin, &fw_chapter_name, std::slice::from_ref(&fw_path))?;
|
|
110
117
|
chapters.push(Chapter {
|
|
111
118
|
mod_name: fw_name.to_lowercase(),
|
|
112
|
-
source_label: format!("{domain} (
|
|
119
|
+
source_label: format!("{domain} (attaches {fw_name:?})"),
|
|
113
120
|
ir_text: fw_ir_text,
|
|
114
121
|
});
|
|
115
122
|
}
|
|
116
123
|
|
|
117
|
-
// Every vendored package's `
|
|
124
|
+
// Every vendored package's `attaches ... from: :vendor` names, resolved the
|
|
118
125
|
// same way `EmbryonautBluebook.load!` (Ruby) resolves them — without this,
|
|
119
126
|
// a vendored chapter would silently drop from the output.
|
|
120
|
-
for pkg_name in &
|
|
127
|
+
for pkg_name in &vendored_package_names {
|
|
121
128
|
let pkg_files = resolve::vendored_bluebook_files(&domain_path, pkg_name)?;
|
|
122
129
|
let pkg_chapter_name = resolve::header_chapter_name(&pkg_files[0])?;
|
|
123
130
|
let expected_chapter_name = resolve::pascal(pkg_name);
|
|
124
131
|
if pkg_chapter_name != expected_chapter_name {
|
|
125
132
|
return Err(format!(
|
|
126
|
-
"hecks-build: {} declares chapter {pkg_chapter_name:?}, but
|
|
133
|
+
"hecks-build: {} declares chapter {pkg_chapter_name:?}, but attaches {pkg_name:?}, from: :vendor \
|
|
127
134
|
expects {expected_chapter_name:?}",
|
|
128
135
|
pkg_files[0].display()
|
|
129
136
|
));
|
|
@@ -140,13 +147,13 @@ pub fn run(root: &Path, domain: &str, opts: &Options) -> Result<(), String> {
|
|
|
140
147
|
// `expected_chapter_name` above), not the raw `pkg_name` — that
|
|
141
148
|
// would diverge the moment a package name has an underscore.
|
|
142
149
|
mod_name: expected_chapter_name.to_lowercase(),
|
|
143
|
-
source_label: format!("{domain} (
|
|
150
|
+
source_label: format!("{domain} (attaches {pkg_name:?}, from: :vendor)"),
|
|
144
151
|
ir_text: pkg_ir_text,
|
|
145
152
|
});
|
|
146
153
|
}
|
|
147
154
|
|
|
148
|
-
// Guards against a
|
|
149
|
-
//
|
|
155
|
+
// Guards against a gem-chapter and a vendored-package
|
|
156
|
+
// entry sharing a `mod_name`, which would silently overwrite one
|
|
150
157
|
// chapter's sidecars with another's.
|
|
151
158
|
{
|
|
152
159
|
let mut seen: Vec<&str> = Vec::new();
|
data/rust/build/src/resolve.rs
CHANGED
|
@@ -77,12 +77,45 @@ pub fn grammar_files(root: &Path) -> Result<Vec<PathBuf>, String> {
|
|
|
77
77
|
bluebook_files(&dir)
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
|
|
81
|
-
|
|
80
|
+
// The names `attaches "Name"` gave, for a chapter the gem carries.
|
|
81
|
+
pub fn resolve_gem_chapters(parser_bin: &Path, chapter_name: &str, hecksagon_path: &Path) -> Result<Vec<String>, String> {
|
|
82
|
+
resolve_json(parser_bin, chapter_name, hecksagon_path, "gem_chapters")
|
|
82
83
|
}
|
|
83
84
|
|
|
84
|
-
|
|
85
|
-
|
|
85
|
+
// The names `attaches "name", from: :vendor` gave, for a vendored package.
|
|
86
|
+
pub fn resolve_vendored_packages(parser_bin: &Path, chapter_name: &str, hecksagon_path: &Path) -> Result<Vec<String>, String> {
|
|
87
|
+
resolve_json(parser_bin, chapter_name, hecksagon_path, "vendored_packages")
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Where the gem keeps the chapters other than framework members, relative to `lib/hecks`; the
|
|
91
|
+
// same places `Hecks::Chapters::GLOBS` lists.
|
|
92
|
+
const OTHER_GEM_CHAPTER_DIRS: &[&str] = &["language", "grammar", "tenancy/bluebook", "deploy/bluebook", "quality_control"];
|
|
93
|
+
|
|
94
|
+
// The names of the chapters the gem carries beside the framework members, found by reading each
|
|
95
|
+
// file's header. Only the Ruby runtime loads them, so the build uses this list to tell a real
|
|
96
|
+
// gem chapter from a misspelt name.
|
|
97
|
+
pub fn other_gem_chapter_names(root: &Path) -> Result<Vec<String>, String> {
|
|
98
|
+
fn walk(directory: &Path, names: &mut Vec<String>) -> Result<(), String> {
|
|
99
|
+
let Ok(entries) = std::fs::read_dir(directory) else { return Ok(()) };
|
|
100
|
+
for entry in entries {
|
|
101
|
+
let path = entry.map_err(|e| format!("reading {}: {e}", directory.display()))?.path();
|
|
102
|
+
if path.is_dir() {
|
|
103
|
+
walk(&path, names)?;
|
|
104
|
+
} else if path.extension().and_then(|e| e.to_str()) == Some("bluebook") {
|
|
105
|
+
if let Ok(name) = header_chapter_name(&path) {
|
|
106
|
+
names.push(name);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
Ok(())
|
|
111
|
+
}
|
|
112
|
+
let mut names = Vec::new();
|
|
113
|
+
for directory in OTHER_GEM_CHAPTER_DIRS {
|
|
114
|
+
walk(&root.join("lib/hecks").join(directory), &mut names)?;
|
|
115
|
+
}
|
|
116
|
+
names.sort();
|
|
117
|
+
names.dedup();
|
|
118
|
+
Ok(names)
|
|
86
119
|
}
|
|
87
120
|
|
|
88
121
|
fn resolve_json(parser_bin: &Path, chapter_name: &str, hecksagon_path: &Path, key: &str) -> Result<Vec<String>, String> {
|
|
@@ -105,13 +138,13 @@ pub fn valid_package_name(name: &str) -> bool {
|
|
|
105
138
|
pub fn vendored_bluebook_files(domain: &Path, pkg_name: &str) -> Result<Vec<PathBuf>, String> {
|
|
106
139
|
if !valid_package_name(pkg_name) {
|
|
107
140
|
return Err(format!(
|
|
108
|
-
"
|
|
141
|
+
"attaches {pkg_name:?}, from: :vendor is not a package name — it must match [a-z][a-z0-9_]* (the name `hecks vendor` accepts)"
|
|
109
142
|
));
|
|
110
143
|
}
|
|
111
144
|
let dir = domain.join("vendor").join("embryonaut_bluebooks").join(pkg_name).join("bluebook");
|
|
112
145
|
if !dir.is_dir() {
|
|
113
146
|
return Err(format!(
|
|
114
|
-
"
|
|
147
|
+
"attaches {pkg_name:?}, from: :vendor names no vendored bluebook at {} — run hecks vendor {pkg_name}",
|
|
115
148
|
dir.display()
|
|
116
149
|
));
|
|
117
150
|
}
|
|
@@ -44,6 +44,7 @@ pub fn scalar_json_accessor(scalar_type: &str) -> &'static str {
|
|
|
44
44
|
"String" => "as_str",
|
|
45
45
|
"Integer" => "as_i64",
|
|
46
46
|
"Float" => "as_f64",
|
|
47
|
+
"TrueClass" | "FalseClass" => "as_bool",
|
|
47
48
|
other => panic!("no JSON accessor for scalar type {other:?}"),
|
|
48
49
|
}
|
|
49
50
|
}
|
|
@@ -77,6 +78,7 @@ pub fn scalar_to_json_expr(scalar_type: &str, rust_expr: &str) -> String {
|
|
|
77
78
|
"String" => format!("crate::kernel::Json::Str({rust_expr}.clone())"),
|
|
78
79
|
"Integer" => format!("crate::kernel::Json::int({rust_expr})"),
|
|
79
80
|
"Float" => format!("crate::kernel::Json::Float({rust_expr})"),
|
|
81
|
+
"TrueClass" | "FalseClass" => format!("crate::kernel::Json::Bool({rust_expr})"),
|
|
80
82
|
other => panic!("no to_json expr for scalar type {other:?}"),
|
|
81
83
|
}
|
|
82
84
|
}
|
|
@@ -565,6 +567,7 @@ pub fn emit_closed_set_table_codec(exemplar: &Exemplar, vo: &Json) -> String {
|
|
|
565
567
|
Some("String") => format!("crate::kernel::Json::Str(self.{ident}.to_string())"),
|
|
566
568
|
Some("Integer") => format!("crate::kernel::Json::int(self.{ident})"),
|
|
567
569
|
Some("Float") => format!("crate::kernel::Json::Float(self.{ident})"),
|
|
570
|
+
Some("TrueClass" | "FalseClass") => format!("crate::kernel::Json::Bool(self.{ident})"),
|
|
568
571
|
_ => String::new(),
|
|
569
572
|
};
|
|
570
573
|
exemplar.render("to_json_field", &[("\"tmpl_field_name\"", naming::ruby_inspect_string(&key)), ("tmpl_json_value_placeholder()", value_expr)])
|
data/rust/codegen/src/naming.rs
CHANGED
|
@@ -7,6 +7,7 @@ pub fn scalar_rust_type(type_name: &str) -> Option<&'static str> {
|
|
|
7
7
|
"String" => Some("String"),
|
|
8
8
|
"Integer" => Some("i64"),
|
|
9
9
|
"Float" => Some("f64"),
|
|
10
|
+
"TrueClass" | "FalseClass" => Some("bool"),
|
|
10
11
|
_ => None,
|
|
11
12
|
}
|
|
12
13
|
}
|
|
@@ -29,6 +30,8 @@ pub fn effective_scalar_type(type_name: &str) -> Option<&'static str> {
|
|
|
29
30
|
"String" => Some("String"),
|
|
30
31
|
"Integer" => Some("Integer"),
|
|
31
32
|
"Float" => Some("Float"),
|
|
33
|
+
"TrueClass" => Some("TrueClass"),
|
|
34
|
+
"FalseClass" => Some("FalseClass"),
|
|
32
35
|
_ => None,
|
|
33
36
|
}
|
|
34
37
|
}
|
|
@@ -250,6 +253,7 @@ pub fn scalar_to_value(type_name: &str, rust_expr: &str) -> Option<String> {
|
|
|
250
253
|
"String" => Some(format!("Value::Str({rust_expr}.clone())")),
|
|
251
254
|
"Integer" => Some(format!("Value::Int({rust_expr})")),
|
|
252
255
|
"Float" => Some(format!("Value::Float({rust_expr})")),
|
|
256
|
+
"TrueClass" | "FalseClass" => Some(format!("Value::Bool({rust_expr})")),
|
|
253
257
|
_ => None,
|
|
254
258
|
}
|
|
255
259
|
}
|
data/rust/codegen/src/types.rs
CHANGED
|
@@ -360,3 +360,48 @@ pub fn emit_projected_field_table(aggregate: &Json) -> String {
|
|
|
360
360
|
.collect();
|
|
361
361
|
format!("pub static {name}_PROJECTED_FIELDS: &[crate::kernel::ProjectedFieldSpec] = &[\n{}\n];\n", rows.join("\n"))
|
|
362
362
|
}
|
|
363
|
+
|
|
364
|
+
#[cfg(test)]
|
|
365
|
+
mod tests {
|
|
366
|
+
use super::*;
|
|
367
|
+
|
|
368
|
+
/// A value object with one attribute per Ruby spelling of a boolean.
|
|
369
|
+
fn flag_value_object() -> Json {
|
|
370
|
+
Json::parse(
|
|
371
|
+
r#"{
|
|
372
|
+
"name":"Entry",
|
|
373
|
+
"attributes":[
|
|
374
|
+
{"name":"on","type":"TrueClass","list":false,"optional":false,"default":false},
|
|
375
|
+
{"name":"off","type":"FalseClass","list":false,"optional":false}
|
|
376
|
+
]
|
|
377
|
+
}"#,
|
|
378
|
+
)
|
|
379
|
+
.unwrap()
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
#[test]
|
|
383
|
+
fn boolean_attributes_are_rust_bool_not_a_type_named_for_the_ruby_class() {
|
|
384
|
+
let vo = flag_value_object();
|
|
385
|
+
let generated = emit_value_object(&Exemplar::load(), &vo, &HashMap::new(), &HashMap::new());
|
|
386
|
+
|
|
387
|
+
assert!(generated.contains("pub on: bool,"));
|
|
388
|
+
assert!(generated.contains("pub off: bool,"));
|
|
389
|
+
assert!(generated.contains("Value::Bool(self.on)"));
|
|
390
|
+
assert!(!generated.contains("TrueClass,"));
|
|
391
|
+
assert!(!generated.contains("FalseClass,"));
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
#[test]
|
|
395
|
+
fn boolean_attributes_round_trip_through_json_as_bool() {
|
|
396
|
+
let vo = flag_value_object();
|
|
397
|
+
let attributes = vo.get("attributes").map(Json::each).unwrap_or(&[]);
|
|
398
|
+
let by_name = HashMap::new();
|
|
399
|
+
let exemplar = Exemplar::load();
|
|
400
|
+
let from_json = crate::json_codec::emit_from_json_flat(&exemplar, "Entry", attributes, &by_name, None, None, false, false, None);
|
|
401
|
+
let to_json = crate::json_codec::emit_to_json_flat(&exemplar, "Entry", attributes, &by_name, false, &[], None);
|
|
402
|
+
|
|
403
|
+
assert!(from_json.contains("x.as_bool()"));
|
|
404
|
+
assert!(!from_json.contains("TrueClass::from_json"));
|
|
405
|
+
assert!(to_json.contains("crate::kernel::Json::Bool(self.on)"));
|
|
406
|
+
}
|
|
407
|
+
}
|
data/rust/host/HECKS_RELEASE
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
3.
|
|
1
|
+
3.1.1
|
data/rust/host/src/api.rs
CHANGED
|
@@ -780,7 +780,7 @@ async fn presentation_save(
|
|
|
780
780
|
const PRESENTATION_WRITE_REFUSAL: &str =
|
|
781
781
|
"this host's kernel carries no ConsoleSettings chapter, so it has no StateStyle/Collection/Overview \
|
|
782
782
|
commands to dispatch, and writing the rows behind its back would skip the invariants those commands \
|
|
783
|
-
enforce. Attach it with `
|
|
783
|
+
enforce. Attach it with `attaches \"ConsoleSettings\"` in this domain's own .hecksagon and \
|
|
784
784
|
rebuild the kernel, or save from the Ruby console engine instead.";
|
|
785
785
|
|
|
786
786
|
// ---- response envelopes ---------------------------------------------
|