textus 0.18.0 → 0.20.2
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/ARCHITECTURE.md +43 -48
- data/CHANGELOG.md +238 -0
- data/SPEC.md +35 -2
- data/lib/textus/application/context.rb +20 -58
- data/lib/textus/application/policy/predicates/accept_authority_signed.rb +33 -0
- data/lib/textus/{domain → application}/policy/predicates/schema_valid.rb +5 -5
- data/lib/textus/{domain → application}/policy/promotion.rb +18 -6
- data/lib/textus/application/projection.rb +91 -0
- data/lib/textus/application/reads/audit.rb +4 -4
- data/lib/textus/application/reads/blame.rb +9 -8
- data/lib/textus/application/reads/deps.rb +14 -3
- data/lib/textus/application/reads/freshness.rb +10 -8
- data/lib/textus/application/reads/get.rb +10 -8
- data/lib/textus/application/reads/get_or_refresh.rb +3 -3
- data/lib/textus/application/reads/list.rb +3 -3
- data/lib/textus/application/reads/policy_explain.rb +3 -3
- data/lib/textus/application/reads/published.rb +5 -3
- data/lib/textus/application/reads/rdeps.rb +15 -3
- data/lib/textus/application/reads/schema_envelope.rb +5 -4
- data/lib/textus/application/reads/stale.rb +3 -3
- data/lib/textus/application/reads/uid.rb +11 -3
- data/lib/textus/application/reads/validate_all.rb +10 -6
- data/lib/textus/application/reads/validator.rb +5 -3
- data/lib/textus/application/reads/where.rb +3 -3
- data/lib/textus/application/refresh/all.rb +15 -11
- data/lib/textus/application/refresh/orchestrator.rb +9 -8
- data/lib/textus/application/refresh/worker.rb +56 -32
- data/lib/textus/application/writes/accept.rb +43 -16
- data/lib/textus/application/writes/authority_gate.rb +26 -0
- data/lib/textus/application/writes/delete.rb +13 -10
- data/lib/textus/application/writes/envelope_io.rb +64 -4
- data/lib/textus/application/writes/materializer.rb +50 -0
- data/lib/textus/application/writes/mv.rb +57 -94
- data/lib/textus/application/writes/publish.rb +132 -26
- data/lib/textus/application/writes/put.rb +15 -14
- data/lib/textus/application/writes/reject.rb +25 -12
- data/lib/textus/builder/pipeline.rb +21 -15
- data/lib/textus/builder/renderer/json.rb +4 -1
- data/lib/textus/builder/renderer/markdown.rb +7 -1
- data/lib/textus/builder/renderer/yaml.rb +4 -1
- data/lib/textus/cli/verb/build.rb +4 -6
- data/lib/textus/cli/verb/get.rb +1 -1
- data/lib/textus/cli/verb/hook_run.rb +3 -4
- data/lib/textus/cli/verb/hooks.rb +5 -5
- data/lib/textus/cli/verb/put.rb +2 -3
- data/lib/textus/cli/verb/refresh_stale.rb +1 -2
- data/lib/textus/doctor/check/handler_allowlist.rb +3 -2
- data/lib/textus/doctor/check/hooks.rb +2 -2
- data/lib/textus/doctor/check/illegal_keys.rb +7 -7
- data/lib/textus/doctor/check/intake_registration.rb +2 -2
- data/lib/textus/doctor/check/manifest_files.rb +1 -1
- data/lib/textus/doctor/check/protocol_version.rb +2 -2
- data/lib/textus/doctor/check/templates.rb +4 -3
- data/lib/textus/doctor.rb +3 -4
- data/lib/textus/domain/authorizer.rb +37 -0
- data/lib/textus/domain/policy/promote.rb +4 -2
- data/lib/textus/domain/policy/refresh.rb +2 -0
- data/lib/textus/domain/staleness/generator_check.rb +8 -7
- data/lib/textus/domain/staleness/intake_check.rb +2 -2
- data/lib/textus/hooks/builtin.rb +6 -6
- data/lib/textus/hooks/bus.rb +155 -0
- data/lib/textus/hooks/context.rb +38 -0
- data/lib/textus/hooks/fire_report.rb +23 -0
- data/lib/textus/hooks/loader.rb +3 -3
- data/lib/textus/infra/audit_subscriber.rb +4 -4
- data/lib/textus/infra/event_bus.rb +3 -3
- data/lib/textus/infra/refresh/detached.rb +1 -1
- data/lib/textus/init.rb +3 -2
- data/lib/textus/intro.rb +51 -27
- data/lib/textus/manifest/entry/base.rb +38 -0
- data/lib/textus/manifest/entry/derived.rb +25 -0
- data/lib/textus/manifest/entry/intake.rb +19 -0
- data/lib/textus/manifest/entry/leaf.rb +16 -0
- data/lib/textus/manifest/entry/nested.rb +39 -0
- data/lib/textus/manifest/entry/parser.rb +58 -31
- data/lib/textus/manifest/entry/validators/events.rb +3 -2
- data/lib/textus/manifest/entry/validators/format_matrix.rb +5 -3
- data/lib/textus/manifest/entry/validators/index_filename.rb +11 -10
- data/lib/textus/manifest/entry/validators/inject_intro.rb +5 -2
- data/lib/textus/manifest/entry/validators/publish_each.rb +9 -6
- data/lib/textus/manifest/entry.rb +0 -72
- data/lib/textus/manifest/resolver.rb +112 -0
- data/lib/textus/manifest/role_kinds.rb +21 -0
- data/lib/textus/manifest/schema.rb +46 -2
- data/lib/textus/manifest.rb +24 -101
- data/lib/textus/operations.rb +131 -74
- data/lib/textus/schema/tools.rb +10 -3
- data/lib/textus/store.rb +6 -6
- data/lib/textus/version.rb +1 -1
- metadata +18 -14
- data/lib/textus/application/writes/build.rb +0 -78
- data/lib/textus/cli/verb/key_normalize.rb +0 -19
- data/lib/textus/dependencies.rb +0 -23
- data/lib/textus/domain/policy/predicates/human_accept.rb +0 -31
- data/lib/textus/domain/policy.rb +0 -7
- data/lib/textus/hooks/dispatcher.rb +0 -71
- data/lib/textus/hooks/registry.rb +0 -85
- data/lib/textus/manifest/resolution.rb +0 -5
- data/lib/textus/migrate_keys.rb +0 -187
- data/lib/textus/projection.rb +0 -89
- data/lib/textus/refresh.rb +0 -39
data/lib/textus/manifest.rb
CHANGED
|
@@ -1,18 +1,10 @@
|
|
|
1
1
|
require "yaml"
|
|
2
2
|
require_relative "manifest/schema"
|
|
3
|
-
require_relative "manifest/
|
|
3
|
+
require_relative "manifest/resolver"
|
|
4
|
+
require_relative "manifest/role_kinds"
|
|
4
5
|
|
|
5
6
|
module Textus
|
|
6
7
|
class Manifest
|
|
7
|
-
TEXTUS_2_HINT = "Install textus 0.11.x to run the migrator, then upgrade to this version. " \
|
|
8
|
-
"See https://github.com/patrick204nqh/textus/blob/main/CHANGELOG.md#0110".freeze
|
|
9
|
-
|
|
10
|
-
def self.version_hint_for(version)
|
|
11
|
-
version == "textus/2" ? TEXTUS_2_HINT : nil
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
private_class_method :version_hint_for
|
|
15
|
-
|
|
16
8
|
attr_reader :root, :entries, :raw
|
|
17
9
|
|
|
18
10
|
def zones
|
|
@@ -38,6 +30,26 @@ module Textus
|
|
|
38
30
|
)
|
|
39
31
|
end
|
|
40
32
|
|
|
33
|
+
def role_mapping
|
|
34
|
+
@role_mapping ||= RoleKinds.resolve(@raw["roles"])
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def role_kind(name)
|
|
38
|
+
role_mapping[name]
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def roles_with_kind(kind)
|
|
42
|
+
role_mapping.each_with_object([]) { |(name, k), acc| acc << name if k == kind }
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def zone_kinds(zone_name)
|
|
46
|
+
@zone_kinds_cache ||= {}
|
|
47
|
+
@zone_kinds_cache[zone_name] ||= zone_writers(zone_name).each_with_object(Set.new) do |w, acc|
|
|
48
|
+
k = role_kind(w)
|
|
49
|
+
acc << k if k
|
|
50
|
+
end.freeze
|
|
51
|
+
end
|
|
52
|
+
|
|
41
53
|
def self.parse(yaml_text, root: ".")
|
|
42
54
|
raw = YAML.safe_load(yaml_text, aliases: false)
|
|
43
55
|
check_version!(raw, "<string>")
|
|
@@ -59,7 +71,6 @@ module Textus
|
|
|
59
71
|
raise BadFrontmatter.new(
|
|
60
72
|
source,
|
|
61
73
|
"unsupported manifest version #{raw["version"].inspect}; expected #{PROTOCOL.inspect}",
|
|
62
|
-
hint: version_hint_for(raw["version"]),
|
|
63
74
|
)
|
|
64
75
|
end
|
|
65
76
|
private_class_method :check_version!
|
|
@@ -87,53 +98,8 @@ module Textus
|
|
|
87
98
|
rules.for(key)
|
|
88
99
|
end
|
|
89
100
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
validate_key!(key)
|
|
93
|
-
segments = key.split(".")
|
|
94
|
-
# longest-prefix match
|
|
95
|
-
candidates = @entries
|
|
96
|
-
.map { |e| [e, e.key.split(".")] }
|
|
97
|
-
.select { |(_, esegs)| esegs == segments[0, esegs.length] }
|
|
98
|
-
.sort_by { |(_, esegs)| -esegs.length }
|
|
99
|
-
raise UnknownKey.new(key, suggestions: suggestions_for(key)) if candidates.empty?
|
|
100
|
-
|
|
101
|
-
entry, esegs = candidates.first
|
|
102
|
-
remaining = segments[esegs.length..]
|
|
103
|
-
if remaining.empty?
|
|
104
|
-
path = resolve_leaf_path(entry)
|
|
105
|
-
Resolution.new(entry: entry, path: path, remaining: [])
|
|
106
|
-
else
|
|
107
|
-
raise UnknownKey.new(key, suggestions: suggestions_for(key)) unless entry.nested
|
|
108
|
-
|
|
109
|
-
path = if entry.index_filename
|
|
110
|
-
File.join(@root, "zones", entry.path, *remaining, entry.index_filename)
|
|
111
|
-
else
|
|
112
|
-
primary_ext = Textus::Entry.for_format(entry.format).extensions.first
|
|
113
|
-
File.join(@root, "zones", entry.path, *remaining) + primary_ext
|
|
114
|
-
end
|
|
115
|
-
Resolution.new(entry: entry, path: path, remaining: remaining)
|
|
116
|
-
end
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
# Returns up to 5 dotted keys from the manifest that look similar to the
|
|
120
|
-
# requested key, ranked by shared-prefix length then Levenshtein distance.
|
|
121
|
-
def suggestions_for(key)
|
|
122
|
-
candidates = enumerate.map { |r| r[:key] }
|
|
123
|
-
# Include declared (non-nested) entry keys even if file is missing.
|
|
124
|
-
candidates.concat(@entries.reject(&:nested).map(&:key))
|
|
125
|
-
candidates.uniq!
|
|
126
|
-
Key::Distance.suggest(key, candidates, limit: 5)
|
|
127
|
-
rescue StandardError
|
|
128
|
-
[]
|
|
129
|
-
end
|
|
130
|
-
|
|
131
|
-
# Enumerate all entry files reachable through the manifest. Returns
|
|
132
|
-
# [{ key:, path:, manifest_entry: }, ...]
|
|
133
|
-
def enumerate(prefix: nil)
|
|
134
|
-
out = @entries.flat_map { |entry| entry.nested ? enumerate_nested(entry) : enumerate_leaf(entry) }
|
|
135
|
-
out.select! { |row| row[:key] == prefix || row[:key].start_with?("#{prefix}.") } if prefix
|
|
136
|
-
out.sort_by { |row| row[:key] }
|
|
101
|
+
def resolver
|
|
102
|
+
@resolver ||= Resolver.new(self)
|
|
137
103
|
end
|
|
138
104
|
|
|
139
105
|
def validate_key!(key)
|
|
@@ -144,51 +110,8 @@ module Textus
|
|
|
144
110
|
|
|
145
111
|
private
|
|
146
112
|
|
|
147
|
-
def enumerate_leaf(entry)
|
|
148
|
-
fp = resolve_leaf_path(entry)
|
|
149
|
-
File.exist?(fp) ? [{ key: entry.key, path: fp, manifest_entry: entry }] : []
|
|
150
|
-
end
|
|
151
|
-
|
|
152
|
-
def enumerate_nested(entry)
|
|
153
|
-
base = File.join(@root, "zones", entry.path)
|
|
154
|
-
return [] unless File.directory?(base)
|
|
155
|
-
|
|
156
|
-
glob_pattern = entry.index_filename ? "**/#{entry.index_filename}" : nested_glob(entry.format)
|
|
157
|
-
Dir.glob(File.join(base, glob_pattern)).filter_map { |path| nested_row_for(entry, base, path) }
|
|
158
|
-
end
|
|
159
|
-
|
|
160
|
-
def nested_row_for(entry, base, path)
|
|
161
|
-
rel = path.sub(%r{\A#{Regexp.escape(base)}/?}, "")
|
|
162
|
-
stripped = entry.index_filename ? File.dirname(rel) : rel.sub(/#{Regexp.escape(File.extname(rel))}\z/, "")
|
|
163
|
-
segs = stripped.split("/").reject { |s| s.empty? || s == "." }
|
|
164
|
-
return nil if segs.empty?
|
|
165
|
-
|
|
166
|
-
illegal = segs.find { |s| !valid_segment?(s) }
|
|
167
|
-
if illegal
|
|
168
|
-
warn("textus: skipping illegal key segment '#{illegal}' at #{path} — run 'textus key normalize --dry-run'")
|
|
169
|
-
return nil
|
|
170
|
-
end
|
|
171
|
-
|
|
172
|
-
{ key: (entry.key.split(".") + segs).join("."), path: path, manifest_entry: entry }
|
|
173
|
-
end
|
|
174
|
-
|
|
175
|
-
def valid_segment?(seg)
|
|
176
|
-
return false if seg.nil? || seg.empty?
|
|
177
|
-
return false if seg.length > Key::Grammar::MAX_SEGMENT_LEN
|
|
178
|
-
|
|
179
|
-
seg.match?(Key::Grammar::SEGMENT)
|
|
180
|
-
end
|
|
181
|
-
|
|
182
113
|
def validate_declared_keys!
|
|
183
114
|
@entries.each { |e| validate_key!(e.key) }
|
|
184
115
|
end
|
|
185
|
-
|
|
186
|
-
def resolve_leaf_path(entry)
|
|
187
|
-
Textus::Key::Path.resolve(self, entry)
|
|
188
|
-
end
|
|
189
|
-
|
|
190
|
-
def nested_glob(format)
|
|
191
|
-
Textus::Entry.for_format(format).nested_glob
|
|
192
|
-
end
|
|
193
116
|
end
|
|
194
117
|
end
|
data/lib/textus/operations.rb
CHANGED
|
@@ -10,102 +10,159 @@ module Textus
|
|
|
10
10
|
# ops.refresh_all(prefix: ..., zone: ...)
|
|
11
11
|
class Operations
|
|
12
12
|
def self.for(store, role: Role::DEFAULT, correlation_id: nil, dry_run: false)
|
|
13
|
-
|
|
13
|
+
new(
|
|
14
|
+
ctx: Application::Context.build(role: role, correlation_id: correlation_id, dry_run: dry_run),
|
|
15
|
+
manifest: store.manifest,
|
|
16
|
+
file_store: store.file_store,
|
|
17
|
+
schemas: store.schemas,
|
|
18
|
+
audit_log: store.audit_log,
|
|
19
|
+
bus: store.bus,
|
|
20
|
+
root: store.root,
|
|
14
21
|
store: store,
|
|
15
|
-
role: role,
|
|
16
|
-
correlation_id: correlation_id,
|
|
17
|
-
dry_run: dry_run,
|
|
18
22
|
)
|
|
19
|
-
new(ctx)
|
|
20
23
|
end
|
|
21
24
|
|
|
22
|
-
attr_reader :ctx
|
|
25
|
+
attr_reader :ctx, :store
|
|
23
26
|
|
|
24
|
-
|
|
25
|
-
|
|
27
|
+
# rubocop:disable Metrics/ParameterLists
|
|
28
|
+
def initialize(ctx:, manifest:, file_store:, schemas:, audit_log:, bus:, root:, store:)
|
|
29
|
+
@ctx = ctx
|
|
30
|
+
@manifest = manifest
|
|
31
|
+
@file_store = file_store
|
|
32
|
+
@schemas = schemas
|
|
33
|
+
@audit_log = audit_log
|
|
34
|
+
@bus = bus
|
|
35
|
+
@root = root
|
|
36
|
+
@store = store
|
|
37
|
+
@authorizer = Textus::Domain::Authorizer.new(manifest: @manifest)
|
|
26
38
|
end
|
|
39
|
+
# rubocop:enable Metrics/ParameterLists
|
|
27
40
|
|
|
28
|
-
def with_role(role)
|
|
41
|
+
def with_role(role)
|
|
42
|
+
self.class.new(
|
|
43
|
+
ctx: @ctx.with_role(role),
|
|
44
|
+
manifest: @manifest, file_store: @file_store, schemas: @schemas,
|
|
45
|
+
audit_log: @audit_log, bus: @bus,
|
|
46
|
+
root: @root, store: @store
|
|
47
|
+
)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def hook_context
|
|
51
|
+
@hook_context ||= Textus::Hooks::Context.new(ops: self)
|
|
52
|
+
end
|
|
29
53
|
|
|
30
54
|
# writes
|
|
31
|
-
def put(...)
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
55
|
+
def put(...)
|
|
56
|
+
Application::Writes::Put.new(
|
|
57
|
+
ctx: @ctx, manifest: @manifest, envelope_io: envelope_io,
|
|
58
|
+
bus: @bus, authorizer: @authorizer, hook_context: hook_context
|
|
59
|
+
).call(...)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def delete(...)
|
|
63
|
+
Application::Writes::Delete.new(
|
|
64
|
+
ctx: @ctx, manifest: @manifest, envelope_io: envelope_io,
|
|
65
|
+
bus: @bus, authorizer: @authorizer, hook_context: hook_context
|
|
66
|
+
).call(...)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def mv(...)
|
|
70
|
+
Application::Writes::Mv.new(
|
|
71
|
+
ctx: @ctx, manifest: @manifest, envelope_io: envelope_io,
|
|
72
|
+
bus: @bus, authorizer: @authorizer, hook_context: hook_context
|
|
73
|
+
).call(...)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def accept(...)
|
|
77
|
+
Application::Writes::Accept.new(
|
|
78
|
+
ctx: @ctx, manifest: @manifest, file_store: @file_store, schemas: @schemas,
|
|
79
|
+
envelope_io: envelope_io, bus: @bus, authorizer: @authorizer, hook_context: hook_context
|
|
80
|
+
).call(...)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def reject(...)
|
|
84
|
+
Application::Writes::Reject.new(
|
|
85
|
+
ctx: @ctx, manifest: @manifest, file_store: @file_store,
|
|
86
|
+
envelope_io: envelope_io, bus: @bus, authorizer: @authorizer, hook_context: hook_context
|
|
87
|
+
).call(...)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def publish(...)
|
|
91
|
+
Application::Writes::Publish.new(
|
|
92
|
+
ctx: @ctx, manifest: @manifest, file_store: @file_store,
|
|
93
|
+
bus: @bus, root: @root, store: @store, hook_context: hook_context
|
|
94
|
+
).call(...)
|
|
95
|
+
end
|
|
38
96
|
|
|
39
97
|
# reads
|
|
40
|
-
def get(...)
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
def
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
def
|
|
53
|
-
def
|
|
54
|
-
def
|
|
98
|
+
def get(...)
|
|
99
|
+
Application::Reads::Get.new(ctx: @ctx, manifest: @manifest, file_store: @file_store).call(...)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def get_or_refresh(...)
|
|
103
|
+
Application::Reads::GetOrRefresh.new(
|
|
104
|
+
manifest: @manifest,
|
|
105
|
+
get: Application::Reads::Get.new(ctx: @ctx, manifest: @manifest, file_store: @file_store),
|
|
106
|
+
orchestrator: orchestrator,
|
|
107
|
+
).call(...)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def list(...) = Application::Reads::List.new(manifest: @manifest).call(...)
|
|
111
|
+
def where(...) = Application::Reads::Where.new(manifest: @manifest).call(...)
|
|
112
|
+
def uid(...) = Application::Reads::Uid.new(ctx: @ctx, manifest: @manifest, file_store: @file_store).call(...)
|
|
113
|
+
def schema_envelope(...) = Application::Reads::SchemaEnvelope.new(manifest: @manifest, schemas: @schemas).call(...)
|
|
114
|
+
def deps(...) = Application::Reads::Deps.new(manifest: @manifest).call(...)
|
|
115
|
+
def rdeps(...) = Application::Reads::Rdeps.new(manifest: @manifest).call(...)
|
|
116
|
+
def published(...) = Application::Reads::Published.new(manifest: @manifest).call(...)
|
|
117
|
+
def stale(...) = Application::Reads::Stale.new(manifest: @manifest).call(...)
|
|
118
|
+
def audit(...) = Application::Reads::Audit.new(manifest: @manifest, root: @root).call(...)
|
|
119
|
+
def blame(...) = Application::Reads::Blame.new(manifest: @manifest, root: @root).call(...)
|
|
120
|
+
def policy_explain(...) = Application::Reads::PolicyExplain.new(manifest: @manifest).call(...)
|
|
121
|
+
def freshness(...) = Application::Reads::Freshness.new(ctx: @ctx, manifest: @manifest, file_store: @file_store).call(...)
|
|
122
|
+
|
|
123
|
+
def validate_all(...)
|
|
124
|
+
Application::Reads::ValidateAll.new(
|
|
125
|
+
ctx: @ctx, manifest: @manifest, file_store: @file_store, schemas: @schemas, audit_log: @audit_log,
|
|
126
|
+
).call(...)
|
|
127
|
+
end
|
|
55
128
|
|
|
56
129
|
# refresh
|
|
57
|
-
def refresh(key) =
|
|
58
|
-
|
|
130
|
+
def refresh(key) = refresh_worker.run(key)
|
|
131
|
+
|
|
132
|
+
def refresh_all(**)
|
|
133
|
+
Application::Refresh::All.new(
|
|
134
|
+
ctx: @ctx, manifest: @manifest, envelope_io: envelope_io, bus: @bus,
|
|
135
|
+
store: @store, authorizer: @authorizer, hook_context: hook_context
|
|
136
|
+
).call(**)
|
|
137
|
+
end
|
|
59
138
|
|
|
60
139
|
private
|
|
61
140
|
|
|
62
141
|
def envelope_io
|
|
63
142
|
@envelope_io ||= Application::Writes::EnvelopeIO.new(
|
|
64
|
-
file_store: @
|
|
65
|
-
manifest: @
|
|
66
|
-
schemas: @
|
|
67
|
-
audit_log: @
|
|
143
|
+
file_store: @file_store,
|
|
144
|
+
manifest: @manifest,
|
|
145
|
+
schemas: @schemas,
|
|
146
|
+
audit_log: @audit_log,
|
|
68
147
|
ctx: @ctx,
|
|
69
148
|
)
|
|
70
149
|
end
|
|
71
150
|
|
|
72
|
-
def
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
def list_op = @list_op ||= Application::Reads::List.new(ctx: @ctx)
|
|
88
|
-
def where_op = @where_op ||= Application::Reads::Where.new(ctx: @ctx)
|
|
89
|
-
def uid_op = @uid_op ||= Application::Reads::Uid.new(ctx: @ctx)
|
|
90
|
-
def schema_envelope_op = @schema_envelope_op ||= Application::Reads::SchemaEnvelope.new(ctx: @ctx)
|
|
91
|
-
def deps_op = @deps_op ||= Application::Reads::Deps.new(ctx: @ctx)
|
|
92
|
-
def rdeps_op = @rdeps_op ||= Application::Reads::Rdeps.new(ctx: @ctx)
|
|
93
|
-
def published_op = @published_op ||= Application::Reads::Published.new(ctx: @ctx)
|
|
94
|
-
def stale_op = @stale_op ||= Application::Reads::Stale.new(ctx: @ctx)
|
|
95
|
-
def audit_op = @audit_op ||= Application::Reads::Audit.new(ctx: @ctx)
|
|
96
|
-
def blame_op = @blame_op ||= Application::Reads::Blame.new(ctx: @ctx)
|
|
97
|
-
def policy_explain_op = @policy_explain_op ||= Application::Reads::PolicyExplain.new(ctx: @ctx)
|
|
98
|
-
def freshness_op = @freshness_op ||= Application::Reads::Freshness.new(ctx: @ctx)
|
|
99
|
-
def validate_all_op = @validate_all_op ||= Application::Reads::ValidateAll.new(ctx: @ctx)
|
|
100
|
-
|
|
101
|
-
def refresh_worker_op = @refresh_worker_op ||= Application::Refresh::Worker.new(ctx: @ctx, envelope_io: envelope_io)
|
|
102
|
-
|
|
103
|
-
def orchestrator_op
|
|
104
|
-
@orchestrator_op ||= Application::Refresh::Orchestrator.new(
|
|
105
|
-
worker: refresh_worker_op,
|
|
106
|
-
store_root: @ctx.store.root,
|
|
107
|
-
store: @ctx.store,
|
|
108
|
-
role: @ctx.role,
|
|
151
|
+
def refresh_worker
|
|
152
|
+
@refresh_worker ||= Application::Refresh::Worker.new(
|
|
153
|
+
ctx: @ctx, manifest: @manifest, envelope_io: envelope_io, bus: @bus,
|
|
154
|
+
store: @store, authorizer: @authorizer, hook_context: hook_context
|
|
155
|
+
)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def orchestrator
|
|
159
|
+
@orchestrator ||= Application::Refresh::Orchestrator.new(
|
|
160
|
+
worker: refresh_worker,
|
|
161
|
+
store_root: @root,
|
|
162
|
+
bus: @bus,
|
|
163
|
+
store: @store,
|
|
164
|
+
ctx: @ctx,
|
|
165
|
+
hook_context: hook_context,
|
|
109
166
|
)
|
|
110
167
|
end
|
|
111
168
|
end
|
data/lib/textus/schema/tools.rb
CHANGED
|
@@ -24,7 +24,7 @@ module Textus
|
|
|
24
24
|
def self.diff(store, name:)
|
|
25
25
|
schema = load_schema(store, name)
|
|
26
26
|
drift = []
|
|
27
|
-
store.manifest.enumerate.each do |row|
|
|
27
|
+
store.manifest.resolver.enumerate.each do |row|
|
|
28
28
|
env = Textus::Operations.for(store).get(row[:key])
|
|
29
29
|
begin
|
|
30
30
|
schema.validate!(env.meta)
|
|
@@ -49,9 +49,16 @@ module Textus
|
|
|
49
49
|
end
|
|
50
50
|
raise UsageError.new("schema migrate needs --rename=OLD:NEW or schema.evolution.migrate_from") if renames.empty?
|
|
51
51
|
|
|
52
|
-
|
|
52
|
+
authority = store.manifest.roles_with_kind(:accept_authority).first
|
|
53
|
+
if authority.nil?
|
|
54
|
+
raise UsageError.new(
|
|
55
|
+
"schema migrate requires a role with kind :accept_authority in the manifest; " \
|
|
56
|
+
"none declared (add e.g. `- { name: owner, kind: accept_authority }` to roles:)",
|
|
57
|
+
)
|
|
58
|
+
end
|
|
59
|
+
ops = Textus::Operations.for(store, role: authority)
|
|
53
60
|
touched = []
|
|
54
|
-
store.manifest.enumerate.each do |row|
|
|
61
|
+
store.manifest.resolver.enumerate.each do |row|
|
|
55
62
|
env = ops.get(row[:key])
|
|
56
63
|
meta = env.meta.dup
|
|
57
64
|
changed = false
|
data/lib/textus/store.rb
CHANGED
|
@@ -2,7 +2,7 @@ require "fileutils"
|
|
|
2
2
|
|
|
3
3
|
module Textus
|
|
4
4
|
class Store
|
|
5
|
-
attr_reader :root, :manifest, :schemas, :file_store, :audit_log, :bus
|
|
5
|
+
attr_reader :root, :manifest, :schemas, :file_store, :audit_log, :bus
|
|
6
6
|
|
|
7
7
|
def self.discover(start_dir = Dir.pwd, root: nil)
|
|
8
8
|
explicit = root || ENV.fetch("TEXTUS_ROOT", nil)
|
|
@@ -34,12 +34,12 @@ module Textus
|
|
|
34
34
|
@schemas = Schemas.new(File.join(@root, "schemas"))
|
|
35
35
|
@file_store = Infra::Storage::FileStore.new
|
|
36
36
|
@audit_log = Infra::AuditLog.new(@root)
|
|
37
|
-
@bus
|
|
38
|
-
@registry = Hooks::Registry.new(dispatcher: @bus)
|
|
37
|
+
@bus = Hooks::Bus.new
|
|
39
38
|
Infra::AuditSubscriber.new(@audit_log).attach(@bus)
|
|
40
|
-
Hooks::Builtin.register_all(@
|
|
41
|
-
Hooks::Loader.new(
|
|
42
|
-
|
|
39
|
+
Hooks::Builtin.register_all(@bus)
|
|
40
|
+
Hooks::Loader.new(bus: @bus).load_dir(File.join(@root, "hooks"))
|
|
41
|
+
ops = Operations.for(self, role: Role::DEFAULT)
|
|
42
|
+
@bus.publish(:store_loaded, ctx: ops.hook_context)
|
|
43
43
|
end
|
|
44
44
|
end
|
|
45
45
|
end
|
data/lib/textus/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: textus
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.20.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Patrick
|
|
@@ -110,6 +110,10 @@ files:
|
|
|
110
110
|
- exe/textus
|
|
111
111
|
- lib/textus.rb
|
|
112
112
|
- lib/textus/application/context.rb
|
|
113
|
+
- lib/textus/application/policy/predicates/accept_authority_signed.rb
|
|
114
|
+
- lib/textus/application/policy/predicates/schema_valid.rb
|
|
115
|
+
- lib/textus/application/policy/promotion.rb
|
|
116
|
+
- lib/textus/application/projection.rb
|
|
113
117
|
- lib/textus/application/reads/audit.rb
|
|
114
118
|
- lib/textus/application/reads/blame.rb
|
|
115
119
|
- lib/textus/application/reads/deps.rb
|
|
@@ -130,9 +134,10 @@ files:
|
|
|
130
134
|
- lib/textus/application/refresh/orchestrator.rb
|
|
131
135
|
- lib/textus/application/refresh/worker.rb
|
|
132
136
|
- lib/textus/application/writes/accept.rb
|
|
133
|
-
- lib/textus/application/writes/
|
|
137
|
+
- lib/textus/application/writes/authority_gate.rb
|
|
134
138
|
- lib/textus/application/writes/delete.rb
|
|
135
139
|
- lib/textus/application/writes/envelope_io.rb
|
|
140
|
+
- lib/textus/application/writes/materializer.rb
|
|
136
141
|
- lib/textus/application/writes/mv.rb
|
|
137
142
|
- lib/textus/application/writes/publish.rb
|
|
138
143
|
- lib/textus/application/writes/put.rb
|
|
@@ -164,7 +169,6 @@ files:
|
|
|
164
169
|
- lib/textus/cli/verb/hooks.rb
|
|
165
170
|
- lib/textus/cli/verb/init.rb
|
|
166
171
|
- lib/textus/cli/verb/intro.rb
|
|
167
|
-
- lib/textus/cli/verb/key_normalize.rb
|
|
168
172
|
- lib/textus/cli/verb/list.rb
|
|
169
173
|
- lib/textus/cli/verb/mv.rb
|
|
170
174
|
- lib/textus/cli/verb/published.rb
|
|
@@ -181,7 +185,6 @@ files:
|
|
|
181
185
|
- lib/textus/cli/verb/schema_migrate.rb
|
|
182
186
|
- lib/textus/cli/verb/uid.rb
|
|
183
187
|
- lib/textus/cli/verb/where.rb
|
|
184
|
-
- lib/textus/dependencies.rb
|
|
185
188
|
- lib/textus/doctor.rb
|
|
186
189
|
- lib/textus/doctor/check.rb
|
|
187
190
|
- lib/textus/doctor/check/audit_log.rb
|
|
@@ -200,19 +203,16 @@ files:
|
|
|
200
203
|
- lib/textus/doctor/check/templates.rb
|
|
201
204
|
- lib/textus/doctor/check/unowned_schema_fields.rb
|
|
202
205
|
- lib/textus/domain/action.rb
|
|
206
|
+
- lib/textus/domain/authorizer.rb
|
|
203
207
|
- lib/textus/domain/freshness.rb
|
|
204
208
|
- lib/textus/domain/freshness/evaluator.rb
|
|
205
209
|
- lib/textus/domain/freshness/policy.rb
|
|
206
210
|
- lib/textus/domain/freshness/verdict.rb
|
|
207
211
|
- lib/textus/domain/outcome.rb
|
|
208
212
|
- lib/textus/domain/permission.rb
|
|
209
|
-
- lib/textus/domain/policy.rb
|
|
210
213
|
- lib/textus/domain/policy/handler_allowlist.rb
|
|
211
214
|
- lib/textus/domain/policy/matcher.rb
|
|
212
|
-
- lib/textus/domain/policy/predicates/human_accept.rb
|
|
213
|
-
- lib/textus/domain/policy/predicates/schema_valid.rb
|
|
214
215
|
- lib/textus/domain/policy/promote.rb
|
|
215
|
-
- lib/textus/domain/policy/promotion.rb
|
|
216
216
|
- lib/textus/domain/policy/refresh.rb
|
|
217
217
|
- lib/textus/domain/sentinel.rb
|
|
218
218
|
- lib/textus/domain/staleness.rb
|
|
@@ -228,9 +228,10 @@ files:
|
|
|
228
228
|
- lib/textus/errors.rb
|
|
229
229
|
- lib/textus/etag.rb
|
|
230
230
|
- lib/textus/hooks/builtin.rb
|
|
231
|
-
- lib/textus/hooks/
|
|
231
|
+
- lib/textus/hooks/bus.rb
|
|
232
|
+
- lib/textus/hooks/context.rb
|
|
233
|
+
- lib/textus/hooks/fire_report.rb
|
|
232
234
|
- lib/textus/hooks/loader.rb
|
|
233
|
-
- lib/textus/hooks/registry.rb
|
|
234
235
|
- lib/textus/infra/audit_log.rb
|
|
235
236
|
- lib/textus/infra/audit_subscriber.rb
|
|
236
237
|
- lib/textus/infra/build_lock.rb
|
|
@@ -247,6 +248,11 @@ files:
|
|
|
247
248
|
- lib/textus/key/path.rb
|
|
248
249
|
- lib/textus/manifest.rb
|
|
249
250
|
- lib/textus/manifest/entry.rb
|
|
251
|
+
- lib/textus/manifest/entry/base.rb
|
|
252
|
+
- lib/textus/manifest/entry/derived.rb
|
|
253
|
+
- lib/textus/manifest/entry/intake.rb
|
|
254
|
+
- lib/textus/manifest/entry/leaf.rb
|
|
255
|
+
- lib/textus/manifest/entry/nested.rb
|
|
250
256
|
- lib/textus/manifest/entry/parser.rb
|
|
251
257
|
- lib/textus/manifest/entry/validators.rb
|
|
252
258
|
- lib/textus/manifest/entry/validators/events.rb
|
|
@@ -254,14 +260,12 @@ files:
|
|
|
254
260
|
- lib/textus/manifest/entry/validators/index_filename.rb
|
|
255
261
|
- lib/textus/manifest/entry/validators/inject_intro.rb
|
|
256
262
|
- lib/textus/manifest/entry/validators/publish_each.rb
|
|
257
|
-
- lib/textus/manifest/
|
|
263
|
+
- lib/textus/manifest/resolver.rb
|
|
264
|
+
- lib/textus/manifest/role_kinds.rb
|
|
258
265
|
- lib/textus/manifest/rules.rb
|
|
259
266
|
- lib/textus/manifest/schema.rb
|
|
260
|
-
- lib/textus/migrate_keys.rb
|
|
261
267
|
- lib/textus/mustache.rb
|
|
262
268
|
- lib/textus/operations.rb
|
|
263
|
-
- lib/textus/projection.rb
|
|
264
|
-
- lib/textus/refresh.rb
|
|
265
269
|
- lib/textus/role.rb
|
|
266
270
|
- lib/textus/schema.rb
|
|
267
271
|
- lib/textus/schema/tools.rb
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
require "fileutils"
|
|
2
|
-
|
|
3
|
-
module Textus
|
|
4
|
-
module Application
|
|
5
|
-
module Writes
|
|
6
|
-
# Materializes generator-zone entries (template + projection) onto disk
|
|
7
|
-
# and copies the result to any configured `publish_to:` targets. Fires
|
|
8
|
-
# `:build_completed` and `:file_published` events.
|
|
9
|
-
#
|
|
10
|
-
# For `publish_each:` (per-leaf publishing of nested entries), see
|
|
11
|
-
# `Application::Writes::Publish`. The CLI verb `textus build` calls
|
|
12
|
-
# both classes and merges the results.
|
|
13
|
-
class Build
|
|
14
|
-
def initialize(ctx:)
|
|
15
|
-
@ctx = ctx
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
def call(prefix: nil)
|
|
19
|
-
built = manifest.entries.filter_map do |mentry|
|
|
20
|
-
next unless mentry.in_generator_zone?
|
|
21
|
-
next unless mentry.projection || mentry.template
|
|
22
|
-
next if prefix && !mentry.key.start_with?(prefix)
|
|
23
|
-
|
|
24
|
-
materialize(mentry)
|
|
25
|
-
end
|
|
26
|
-
{ "protocol" => Textus::PROTOCOL, "built" => built }
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
private
|
|
30
|
-
|
|
31
|
-
def store = @ctx.store
|
|
32
|
-
def manifest = store.manifest
|
|
33
|
-
def root = store.root
|
|
34
|
-
|
|
35
|
-
def materialize(mentry)
|
|
36
|
-
target_path = Builder::Pipeline.run(
|
|
37
|
-
store: store,
|
|
38
|
-
mentry: mentry,
|
|
39
|
-
template_loader: ->(name) { read_template(name) },
|
|
40
|
-
)
|
|
41
|
-
publish_and_fire(mentry, target_path)
|
|
42
|
-
{ "key" => mentry.key, "path" => target_path, "published_to" => mentry.publish_to }
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
def read_template(name)
|
|
46
|
-
tpl_path = File.join(root, "templates", name)
|
|
47
|
-
raise TemplateError.new("template not found: #{tpl_path}", template_name: name) unless File.exist?(tpl_path)
|
|
48
|
-
|
|
49
|
-
File.read(tpl_path)
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
def publish_and_fire(mentry, target_path)
|
|
53
|
-
envelope = Textus::Application::Reads::Get.new(ctx: @ctx).call(mentry.key)
|
|
54
|
-
repo_root = File.dirname(root)
|
|
55
|
-
|
|
56
|
-
mentry.publish_to.each do |rel|
|
|
57
|
-
target_abs = File.join(repo_root, rel)
|
|
58
|
-
Textus::Infra::Publisher.publish(source: target_path, target: target_abs, store_root: root)
|
|
59
|
-
publish_event(:file_published,
|
|
60
|
-
key: mentry.key,
|
|
61
|
-
envelope: envelope,
|
|
62
|
-
source: target_path,
|
|
63
|
-
target: target_abs)
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
publish_event(:build_completed,
|
|
67
|
-
key: mentry.key,
|
|
68
|
-
envelope: envelope,
|
|
69
|
-
sources: Array(mentry.projection&.fetch("select", nil)).compact)
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
def publish_event(event, **payload)
|
|
73
|
-
@ctx.bus.publish(event, store: @ctx.with_role(@ctx.role), correlation_id: @ctx.correlation_id, **payload)
|
|
74
|
-
end
|
|
75
|
-
end
|
|
76
|
-
end
|
|
77
|
-
end
|
|
78
|
-
end
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
class CLI
|
|
3
|
-
class Verb
|
|
4
|
-
class KeyNormalize < Verb
|
|
5
|
-
command_name "normalize"
|
|
6
|
-
parent_group Group::Key
|
|
7
|
-
|
|
8
|
-
option :write, "--write"
|
|
9
|
-
option :dry_run, "--dry-run"
|
|
10
|
-
|
|
11
|
-
def call(store)
|
|
12
|
-
effective_write = write && !dry_run
|
|
13
|
-
res = Textus::MigrateKeys.run(store, write: effective_write || false)
|
|
14
|
-
emit(res, exit_code: res["ok"] ? 0 : 1)
|
|
15
|
-
end
|
|
16
|
-
end
|
|
17
|
-
end
|
|
18
|
-
end
|
|
19
|
-
end
|