textus 0.10.5 → 0.12.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.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +104 -3
  3. data/README.md +39 -26
  4. data/SPEC.md +222 -144
  5. data/lib/textus/application/reads/freshness.rb +2 -2
  6. data/lib/textus/application/reads/get.rb +1 -1
  7. data/lib/textus/application/reads/policy_explain.rb +2 -2
  8. data/lib/textus/application/refresh/orchestrator.rb +1 -1
  9. data/lib/textus/application/refresh/worker.rb +5 -5
  10. data/lib/textus/application/writes/accept.rb +19 -1
  11. data/lib/textus/application/writes/build.rb +5 -5
  12. data/lib/textus/application/writes/delete.rb +1 -1
  13. data/lib/textus/application/writes/publish.rb +1 -1
  14. data/lib/textus/application/writes/put.rb +1 -1
  15. data/lib/textus/builder/pipeline.rb +1 -1
  16. data/lib/textus/builder/renderer/json.rb +1 -1
  17. data/lib/textus/builder/renderer/yaml.rb +1 -1
  18. data/lib/textus/cli/group/key.rb +1 -1
  19. data/lib/textus/cli/group/refresh.rb +21 -0
  20. data/lib/textus/cli/group/rule.rb +11 -0
  21. data/lib/textus/cli/verb/build.rb +1 -1
  22. data/lib/textus/cli/verb/hook_run.rb +3 -2
  23. data/lib/textus/cli/verb/hooks.rb +1 -1
  24. data/lib/textus/cli/verb/{migrate_keys.rb → key_normalize.rb} +1 -1
  25. data/lib/textus/cli/verb/put.rb +1 -1
  26. data/lib/textus/cli/verb/{policy_explain.rb → rule_explain.rb} +1 -1
  27. data/lib/textus/cli/verb/{policy_list.rb → rule_list.rb} +3 -3
  28. data/lib/textus/cli/verb.rb +3 -2
  29. data/lib/textus/cli.rb +6 -6
  30. data/lib/textus/doctor/check/handler_allowlist.rb +1 -1
  31. data/lib/textus/doctor/check/illegal_keys.rb +39 -16
  32. data/lib/textus/doctor/check/intake_registration.rb +4 -4
  33. data/lib/textus/doctor/check/protocol_version.rb +47 -0
  34. data/lib/textus/doctor/check/{policy_ambiguity.rb → rule_ambiguity.rb} +6 -6
  35. data/lib/textus/doctor.rb +5 -4
  36. data/lib/textus/domain/permission.rb +4 -4
  37. data/lib/textus/domain/policy/predicates/human_accept.rb +31 -0
  38. data/lib/textus/domain/policy/predicates/schema_valid.rb +50 -0
  39. data/lib/textus/domain/policy/promotion.rb +45 -0
  40. data/lib/textus/errors.rb +24 -5
  41. data/lib/textus/hooks/builtin.rb +5 -5
  42. data/lib/textus/hooks/dispatcher.rb +1 -1
  43. data/lib/textus/hooks/dsl.rb +3 -10
  44. data/lib/textus/hooks/loader.rb +1 -2
  45. data/lib/textus/hooks/registry.rb +22 -21
  46. data/lib/textus/infra/refresh/detached.rb +1 -1
  47. data/lib/textus/init.rb +25 -34
  48. data/lib/textus/intro.rb +9 -9
  49. data/lib/textus/manifest/entry.rb +33 -6
  50. data/lib/textus/manifest/{policies.rb → rules.rb} +12 -10
  51. data/lib/textus/manifest/schema.rb +49 -0
  52. data/lib/textus/manifest.rb +45 -9
  53. data/lib/textus/migrate_keys.rb +1 -1
  54. data/lib/textus/projection.rb +4 -4
  55. data/lib/textus/refresh.rb +1 -1
  56. data/lib/textus/store/mover.rb +1 -1
  57. data/lib/textus/store/staleness/intake_check.rb +1 -1
  58. data/lib/textus/store/writer.rb +1 -1
  59. data/lib/textus/store.rb +1 -1
  60. data/lib/textus/version.rb +2 -2
  61. data/lib/textus.rb +1 -0
  62. metadata +13 -7
  63. data/lib/textus/cli/group/policy.rb +0 -11
@@ -0,0 +1,49 @@
1
+ module Textus
2
+ class Manifest
3
+ module Schema
4
+ ROOT_KEYS = %w[version zones entries rules].freeze
5
+ ZONE_KEYS = %w[name write_policy read_policy].freeze
6
+ ENTRY_KEYS = %w[
7
+ key path zone schema owner nested format
8
+ compute template publish_to publish_each
9
+ intake events inject_intro index_filename
10
+ ].freeze
11
+ COMPUTE_KEYS = %w[kind select pluck sort_by limit transform command sources].freeze
12
+ INTAKE_KEYS = %w[handler config].freeze
13
+ RULE_KEYS = %w[match refresh intake_handler_allowlist promotion retention].freeze
14
+ REFRESH_KEYS = %w[ttl on_stale sync_budget_ms].freeze
15
+ PROMOTION_KEYS = %w[requires].freeze
16
+
17
+ def self.validate!(raw)
18
+ raise BadManifest.new("manifest must be a hash") unless raw.is_a?(Hash)
19
+
20
+ walk(raw, ROOT_KEYS, "$")
21
+ Array(raw["zones"]).each_with_index do |z, i|
22
+ walk(z, ZONE_KEYS, "$.zones[#{i}]")
23
+ end
24
+ Array(raw["entries"]).each_with_index do |e, i|
25
+ path = "$.entries[#{i}]"
26
+ walk(e, ENTRY_KEYS, path)
27
+ walk(e["compute"], COMPUTE_KEYS, "#{path}.compute") if e["compute"].is_a?(Hash)
28
+ walk(e["intake"], INTAKE_KEYS, "#{path}.intake") if e["intake"].is_a?(Hash)
29
+ end
30
+ Array(raw["rules"]).each_with_index do |r, i|
31
+ path = "$.rules[#{i}]"
32
+ walk(r, RULE_KEYS, path)
33
+ walk(r["refresh"], REFRESH_KEYS, "#{path}.refresh") if r["refresh"].is_a?(Hash)
34
+ walk(r["promotion"], PROMOTION_KEYS, "#{path}.promotion") if r["promotion"].is_a?(Hash)
35
+ end
36
+ end
37
+
38
+ def self.walk(hash, allowed, path)
39
+ return unless hash.is_a?(Hash)
40
+
41
+ hash.each_key do |k|
42
+ next if allowed.include?(k)
43
+
44
+ raise BadManifest.new("unknown key '#{k}' at '#{path}'")
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -1,4 +1,5 @@
1
1
  require "yaml"
2
+ require_relative "manifest/schema"
2
3
 
3
4
  module Textus
4
5
  class Manifest
@@ -10,10 +11,26 @@ module Textus
10
11
  ".txt" => "text",
11
12
  }.freeze
12
13
 
14
+ TEXTUS_2_HINT = "Install textus 0.11.x to run the migrator, then upgrade to this version. " \
15
+ "See https://github.com/patrick204nqh/textus/blob/main/CHANGELOG.md#0110".freeze
16
+
17
+ def self.version_hint_for(version)
18
+ version == "textus/2" ? TEXTUS_2_HINT : nil
19
+ end
20
+
21
+ private_class_method :version_hint_for
22
+
13
23
  attr_reader :root, :entries, :raw
14
24
 
15
25
  def zones
16
- @zones ||= Array(@raw["zones"]).to_h { |z| [z["name"], Array(z["writable_by"])] }
26
+ @zones ||= Array(@raw["zones"]).to_h { |z| [z["name"], Array(z["write_policy"])] }
27
+ end
28
+
29
+ def zone_readers
30
+ @zone_readers ||= Array(@raw["zones"]).to_h do |z|
31
+ rp = z["read_policy"]
32
+ [z["name"], rp.nil? ? :all : Array(rp)]
33
+ end
17
34
  end
18
35
 
19
36
  def zone_writers(zone_name)
@@ -23,18 +40,35 @@ module Textus
23
40
  def permission_for(zone_name)
24
41
  Textus::Domain::Permission.new(
25
42
  zone: zone_name,
26
- writable_by: zone_writers(zone_name),
27
- readable_by: :all,
43
+ write_policy: zone_writers(zone_name),
44
+ read_policy: zone_readers[zone_name] || :all,
28
45
  )
29
46
  end
30
47
 
48
+ def self.parse(yaml_text, root: ".")
49
+ raw = YAML.safe_load(yaml_text, aliases: false)
50
+ unless raw["version"] == PROTOCOL
51
+ raise BadFrontmatter.new(
52
+ "<string>",
53
+ "unsupported manifest version #{raw["version"].inspect}; expected #{PROTOCOL.inspect}",
54
+ hint: version_hint_for(raw["version"]),
55
+ )
56
+ end
57
+
58
+ new(root, raw)
59
+ end
60
+
31
61
  def self.load(root)
32
62
  manifest_path = File.join(root, "manifest.yaml")
33
63
  raise IoError.new("manifest not found: #{manifest_path}") unless File.exist?(manifest_path)
34
64
 
35
65
  raw = YAML.safe_load_file(manifest_path, aliases: false)
36
66
  unless raw["version"] == PROTOCOL
37
- raise BadFrontmatter.new(manifest_path, "unsupported manifest version #{raw["version"].inspect}; expected #{PROTOCOL.inspect}")
67
+ raise BadFrontmatter.new(
68
+ manifest_path,
69
+ "unsupported manifest version #{raw["version"].inspect}; expected #{PROTOCOL.inspect}",
70
+ hint: version_hint_for(raw["version"]),
71
+ )
38
72
  end
39
73
 
40
74
  new(root, raw)
@@ -45,16 +79,18 @@ module Textus
45
79
  @raw = raw
46
80
  raise BadFrontmatter.new(File.join(root, "manifest.yaml"), "manifest must declare zones:") if Array(raw["zones"]).empty?
47
81
 
82
+ Schema.validate!(raw)
83
+
48
84
  @entries = Array(raw["entries"]).map { |e| Manifest::Entry.new(self, e) }
49
85
  validate_declared_keys!
50
86
  end
51
87
 
52
- def policies
53
- @policies ||= Textus::Manifest::Policies.parse(@raw["policies"] || [])
88
+ def rules
89
+ @rules ||= Textus::Manifest::Rules.parse(@raw["rules"] || [])
54
90
  end
55
91
 
56
- def policies_for(key)
57
- policies.for(key)
92
+ def rules_for(key)
93
+ rules.for(key)
58
94
  end
59
95
 
60
96
  # Returns [Manifest::Entry, resolved_path, remaining_segments]
@@ -135,7 +171,7 @@ module Textus
135
171
 
136
172
  illegal = segs.find { |s| !valid_segment?(s) }
137
173
  if illegal
138
- warn("textus: skipping illegal key segment '#{illegal}' at #{path} — run 'textus key migrate --dry-run'")
174
+ warn("textus: skipping illegal key segment '#{illegal}' at #{path} — run 'textus key normalize --dry-run'")
139
175
  return nil
140
176
  end
141
177
 
@@ -122,7 +122,7 @@ module Textus
122
122
  File.rename(from, to)
123
123
  new_key = compute_new_key(r, renames)
124
124
  audit.append(
125
- role: "script",
125
+ role: "runner",
126
126
  verb: "migrate-keys",
127
127
  key: new_key,
128
128
  etag_before: nil,
@@ -38,14 +38,14 @@ module Textus
38
38
  private
39
39
 
40
40
  def apply_reducer(rows)
41
- name = @spec["reduce"] or return rows
42
- callable = @store.registry.rpc_callable(:reduce, name)
41
+ name = @spec["transform"] or return rows
42
+ callable = @store.registry.rpc_callable(:transform_rows, name)
43
43
  view = Application::Context.new(store: @store, role: "human")
44
44
  Timeout.timeout(REDUCER_TIMEOUT_SECONDS) do
45
- callable.call(store: view, rows: rows, config: @spec["reduce_config"] || {})
45
+ callable.call(store: view, rows: rows, config: @spec["transform_config"] || {})
46
46
  end
47
47
  rescue Timeout::Error
48
- raise UsageError.new("reduce '#{name}' exceeded #{REDUCER_TIMEOUT_SECONDS}s timeout")
48
+ raise UsageError.new("transform_rows '#{name}' exceeded #{REDUCER_TIMEOUT_SECONDS}s timeout")
49
49
  end
50
50
 
51
51
  def collect_keys
@@ -5,7 +5,7 @@ module Textus
5
5
  Textus::Composition.refresh_worker(ctx).run(key)
6
6
  end
7
7
 
8
- def self.refresh_stale(store, prefix: nil, zone: nil, as: "script")
8
+ def self.refresh_stale(store, prefix: nil, zone: nil, as: "runner")
9
9
  ctx = Textus::Composition.context(store, role: as)
10
10
  Textus::Application::Refresh::All.call(ctx, prefix: prefix, zone: zone)
11
11
  end
@@ -112,7 +112,7 @@ module Textus
112
112
  )
113
113
  new_envelope = @reader.get(plan.new_key)
114
114
  @store.fire_event(
115
- :mv,
115
+ :entry_renamed,
116
116
  key: plan.new_key, from_key: plan.old_key, to_key: plan.new_key,
117
117
  envelope: new_envelope
118
118
  )
@@ -13,7 +13,7 @@ module Textus
13
13
  def rows_for(mentry)
14
14
  return [] unless mentry.intake_handler
15
15
 
16
- ttl = @manifest.policies_for(mentry.key).refresh&.ttl_seconds
16
+ ttl = @manifest.rules_for(mentry.key).refresh&.ttl_seconds
17
17
  return [] unless ttl
18
18
 
19
19
  path = Textus::Key::Path.resolve(@manifest, mentry)
@@ -163,7 +163,7 @@ module Textus
163
163
  target_key = proposal["target_key"] or raise ProposalError.new("proposal missing target_key")
164
164
 
165
165
  delete(pending_key, as: as)
166
- @store.fire_event(:reject, key: pending_key, target_key: target_key)
166
+ @store.fire_event(:proposal_rejected, key: pending_key, target_key: target_key)
167
167
  { "protocol" => PROTOCOL, "rejected" => pending_key, "target_key" => target_key }
168
168
  end
169
169
  end
data/lib/textus/store.rb CHANGED
@@ -45,7 +45,7 @@ module Textus
45
45
  load_hooks
46
46
  @reader = Reader.new(self)
47
47
  @writer = Writer.new(self)
48
- fire_event(:loaded)
48
+ fire_event(:store_loaded)
49
49
  end
50
50
 
51
51
  def load_hooks
@@ -1,4 +1,4 @@
1
1
  module Textus
2
- VERSION = "0.10.5"
3
- PROTOCOL = "textus/2"
2
+ VERSION = "0.12.1"
3
+ PROTOCOL = "textus/3"
4
4
  end
data/lib/textus.rb CHANGED
@@ -7,6 +7,7 @@ loader.inflector.inflect(
7
7
  "cli" => "CLI",
8
8
  "json" => "Json",
9
9
  "yaml" => "Yaml",
10
+ "hook_dsl_scanner" => "HookDSLScanner",
10
11
  )
11
12
  loader.ignore(File.expand_path("textus/errors.rb", __dir__))
12
13
  loader.setup
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.10.5
4
+ version: 0.12.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Patrick
@@ -133,7 +133,8 @@ files:
133
133
  - lib/textus/cli/group.rb
134
134
  - lib/textus/cli/group/hook.rb
135
135
  - lib/textus/cli/group/key.rb
136
- - lib/textus/cli/group/policy.rb
136
+ - lib/textus/cli/group/refresh.rb
137
+ - lib/textus/cli/group/rule.rb
137
138
  - lib/textus/cli/group/schema.rb
138
139
  - lib/textus/cli/verb.rb
139
140
  - lib/textus/cli/verb/accept.rb
@@ -149,17 +150,17 @@ files:
149
150
  - lib/textus/cli/verb/hooks.rb
150
151
  - lib/textus/cli/verb/init.rb
151
152
  - lib/textus/cli/verb/intro.rb
153
+ - lib/textus/cli/verb/key_normalize.rb
152
154
  - lib/textus/cli/verb/list.rb
153
- - lib/textus/cli/verb/migrate_keys.rb
154
155
  - lib/textus/cli/verb/mv.rb
155
- - lib/textus/cli/verb/policy_explain.rb
156
- - lib/textus/cli/verb/policy_list.rb
157
156
  - lib/textus/cli/verb/published.rb
158
157
  - lib/textus/cli/verb/put.rb
159
158
  - lib/textus/cli/verb/rdeps.rb
160
159
  - lib/textus/cli/verb/refresh.rb
161
160
  - lib/textus/cli/verb/refresh_stale.rb
162
161
  - lib/textus/cli/verb/reject.rb
162
+ - lib/textus/cli/verb/rule_explain.rb
163
+ - lib/textus/cli/verb/rule_list.rb
163
164
  - lib/textus/cli/verb/schema.rb
164
165
  - lib/textus/cli/verb/schema_diff.rb
165
166
  - lib/textus/cli/verb/schema_init.rb
@@ -176,7 +177,8 @@ files:
176
177
  - lib/textus/doctor/check/illegal_keys.rb
177
178
  - lib/textus/doctor/check/intake_registration.rb
178
179
  - lib/textus/doctor/check/manifest_files.rb
179
- - lib/textus/doctor/check/policy_ambiguity.rb
180
+ - lib/textus/doctor/check/protocol_version.rb
181
+ - lib/textus/doctor/check/rule_ambiguity.rb
180
182
  - lib/textus/doctor/check/schema_parse_error.rb
181
183
  - lib/textus/doctor/check/schema_violations.rb
182
184
  - lib/textus/doctor/check/schemas.rb
@@ -192,7 +194,10 @@ files:
192
194
  - lib/textus/domain/policy.rb
193
195
  - lib/textus/domain/policy/handler_allowlist.rb
194
196
  - lib/textus/domain/policy/matcher.rb
197
+ - lib/textus/domain/policy/predicates/human_accept.rb
198
+ - lib/textus/domain/policy/predicates/schema_valid.rb
195
199
  - lib/textus/domain/policy/promote.rb
200
+ - lib/textus/domain/policy/promotion.rb
196
201
  - lib/textus/domain/policy/refresh.rb
197
202
  - lib/textus/entry.rb
198
203
  - lib/textus/entry/base.rb
@@ -220,7 +225,8 @@ files:
220
225
  - lib/textus/key/path.rb
221
226
  - lib/textus/manifest.rb
222
227
  - lib/textus/manifest/entry.rb
223
- - lib/textus/manifest/policies.rb
228
+ - lib/textus/manifest/rules.rb
229
+ - lib/textus/manifest/schema.rb
224
230
  - lib/textus/migrate_keys.rb
225
231
  - lib/textus/mustache.rb
226
232
  - lib/textus/projection.rb
@@ -1,11 +0,0 @@
1
- module Textus
2
- class CLI
3
- class Group
4
- class Policy < Group
5
- self.cli_name = "policy"
6
- subcommands["list"] = Verb::PolicyList
7
- subcommands["explain"] = Verb::PolicyExplain
8
- end
9
- end
10
- end
11
- end