textus 0.8.0 → 0.9.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.
Files changed (82) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +245 -0
  3. data/README.md +54 -26
  4. data/SPEC.md +194 -63
  5. data/docs/architecture.md +22 -4
  6. data/docs/conventions.md +24 -17
  7. data/lib/textus/application/context.rb +68 -0
  8. data/lib/textus/application/reads/audit.rb +69 -0
  9. data/lib/textus/application/reads/blame.rb +79 -0
  10. data/lib/textus/application/reads/freshness.rb +77 -0
  11. data/lib/textus/application/reads/get.rb +62 -0
  12. data/lib/textus/application/reads/policy_explain.rb +39 -0
  13. data/lib/textus/application/refresh/all.rb +41 -0
  14. data/lib/textus/application/refresh/orchestrator.rb +68 -0
  15. data/lib/textus/application/refresh/worker.rb +79 -0
  16. data/lib/textus/application/writes/accept.rb +43 -0
  17. data/lib/textus/application/writes/build.rb +24 -0
  18. data/lib/textus/application/writes/delete.rb +37 -0
  19. data/lib/textus/application/writes/publish.rb +25 -0
  20. data/lib/textus/application/writes/put.rb +44 -0
  21. data/lib/textus/builder.rb +27 -14
  22. data/lib/textus/cli/group/policy.rb +11 -0
  23. data/lib/textus/cli/verb/accept.rb +2 -1
  24. data/lib/textus/cli/verb/audit.rb +31 -0
  25. data/lib/textus/cli/verb/blame.rb +17 -0
  26. data/lib/textus/cli/verb/build.rb +2 -1
  27. data/lib/textus/cli/verb/delete.rb +2 -1
  28. data/lib/textus/cli/verb/freshness.rb +17 -0
  29. data/lib/textus/cli/verb/get.rb +8 -1
  30. data/lib/textus/cli/verb/hook_run.rb +3 -3
  31. data/lib/textus/cli/verb/policy_explain.rb +15 -0
  32. data/lib/textus/cli/verb/policy_list.rb +25 -0
  33. data/lib/textus/cli/verb/put.rb +5 -4
  34. data/lib/textus/cli/verb/refresh.rb +2 -1
  35. data/lib/textus/cli/verb/refresh_stale.rb +19 -0
  36. data/lib/textus/cli/verb/reject.rb +15 -0
  37. data/lib/textus/cli.rb +16 -2
  38. data/lib/textus/composition.rb +71 -0
  39. data/lib/textus/doctor/check/handler_allowlist.rb +33 -0
  40. data/lib/textus/doctor/check/intake_registration.rb +46 -0
  41. data/lib/textus/doctor/check/legacy_intake_fields.rb +57 -0
  42. data/lib/textus/doctor/check/policy_ambiguity.rb +49 -0
  43. data/lib/textus/doctor.rb +5 -1
  44. data/lib/textus/domain/action.rb +9 -0
  45. data/lib/textus/domain/freshness/evaluator.rb +30 -0
  46. data/lib/textus/domain/freshness/policy.rb +18 -0
  47. data/lib/textus/domain/freshness/verdict.rb +12 -0
  48. data/lib/textus/domain/outcome.rb +10 -0
  49. data/lib/textus/domain/permission.rb +15 -0
  50. data/lib/textus/domain/policy/handler_allowlist.rb +17 -0
  51. data/lib/textus/domain/policy/matcher.rb +51 -0
  52. data/lib/textus/domain/policy/promote.rb +24 -0
  53. data/lib/textus/domain/policy/refresh.rb +48 -0
  54. data/lib/textus/domain/policy.rb +7 -0
  55. data/lib/textus/hooks/builtin.rb +5 -5
  56. data/lib/textus/hooks/dispatcher.rb +15 -1
  57. data/lib/textus/hooks/dsl.rb +18 -0
  58. data/lib/textus/hooks/registry.rb +12 -5
  59. data/lib/textus/infra/clock.rb +9 -0
  60. data/lib/textus/infra/event_bus.rb +27 -0
  61. data/lib/textus/infra/publisher.rb +73 -0
  62. data/lib/textus/infra/refresh/detached.rb +38 -0
  63. data/lib/textus/infra/refresh/lock.rb +44 -0
  64. data/lib/textus/init.rb +71 -28
  65. data/lib/textus/intro.rb +22 -14
  66. data/lib/textus/manifest/entry.rb +18 -9
  67. data/lib/textus/manifest/policies.rb +83 -0
  68. data/lib/textus/manifest.rb +30 -0
  69. data/lib/textus/proposal.rb +4 -21
  70. data/lib/textus/publisher.rb +4 -69
  71. data/lib/textus/refresh.rb +9 -44
  72. data/lib/textus/store/mover.rb +14 -9
  73. data/lib/textus/store/reader.rb +10 -8
  74. data/lib/textus/store/staleness.rb +4 -16
  75. data/lib/textus/store/validator.rb +46 -20
  76. data/lib/textus/store/view.rb +8 -19
  77. data/lib/textus/store/writer.rb +51 -14
  78. data/lib/textus/store.rb +32 -12
  79. data/lib/textus/version.rb +1 -1
  80. data/lib/textus.rb +1 -0
  81. metadata +46 -2
  82. data/lib/textus/cli/verb/stale.rb +0 -14
@@ -4,14 +4,15 @@ module Textus
4
4
  class Store
5
5
  # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
6
6
  class Mover
7
- def initialize(reader:, writer:, manifest:, audit_log:)
7
+ def initialize(store:, reader:, writer:, manifest:, audit_log:)
8
+ @store = store
8
9
  @reader = reader
9
10
  @writer = writer
10
11
  @manifest = manifest
11
12
  @audit_log = audit_log
12
13
  end
13
14
 
14
- def call(old_key, new_key, as: Role::DEFAULT, dry_run: false)
15
+ def call(old_key, new_key, as: Role::DEFAULT, dry_run: false, correlation_id: nil)
15
16
  @manifest.validate_key!(old_key)
16
17
  @manifest.validate_key!(new_key)
17
18
  raise UsageError.new("mv: old and new keys are identical") if old_key == new_key
@@ -69,23 +70,27 @@ module Textus
69
70
  rewrite_name_for_mv!(new_mentry, new_path, new_key)
70
71
  etag_after = Etag.for_file(new_path)
71
72
 
73
+ extras = {
74
+ "from_key" => old_key, "to_key" => new_key,
75
+ "from_path" => old_path, "to_path" => new_path,
76
+ "uid" => current_uid
77
+ }
78
+ extras["correlation_id"] = correlation_id if correlation_id
79
+
72
80
  @audit_log.append(
73
81
  role: as, verb: "mv", key: new_key,
74
82
  etag_before: etag_before, etag_after: etag_after,
75
- extras: {
76
- "from_key" => old_key, "to_key" => new_key,
77
- "from_path" => old_path, "to_path" => new_path,
78
- "uid" => current_uid
79
- }
83
+ extras: extras
80
84
  )
81
85
 
82
- env = @reader.get(new_key)
86
+ new_envelope = @reader.get(new_key)
87
+ @store.fire_event(:mv, key: new_key, from_key: old_key, to_key: new_key, envelope: new_envelope)
83
88
  {
84
89
  "protocol" => PROTOCOL, "ok" => true,
85
90
  "from_key" => old_key, "to_key" => new_key,
86
91
  "from_path" => old_path, "to_path" => new_path,
87
92
  "uid" => current_uid,
88
- "envelope" => env
93
+ "envelope" => new_envelope
89
94
  }
90
95
  end
91
96
 
@@ -7,20 +7,22 @@ module Textus
7
7
  end
8
8
 
9
9
  def get(key)
10
+ read_raw_envelope(key) || raise(UnknownKey.new(key, suggestions: @manifest.suggestions_for(key)))
11
+ end
12
+
13
+ # Reads the current on-disk state of key as a bare envelope, skipping
14
+ # freshness annotation to avoid recursion. Used by Freshness.refresh_sync
15
+ # after a sync refresh completes.
16
+ def read_raw_envelope(key)
10
17
  mentry, path, = @manifest.resolve(key)
11
- raise UnknownKey.new(key, suggestions: @manifest.suggestions_for(key)) unless File.exist?(path)
18
+ return nil unless File.exist?(path)
12
19
 
13
20
  raw = File.binread(path)
14
21
  parsed = Entry.for_format(mentry.format).parse(raw, path: path)
15
- meta = parsed["_meta"]
16
- content = parsed["content"]
17
- @store.writer.enforce_name_match!(path, meta, mentry.format)
18
- schema = @store.schema_for(mentry.schema)
19
- Entry.for_format(mentry.format).validate_against(schema, parsed) if schema
20
22
  Envelope.build(
21
23
  key: key, mentry: mentry, path: path,
22
- meta: meta, body: parsed["body"],
23
- etag: Etag.for_bytes(raw), content: content
24
+ meta: parsed["_meta"], body: parsed["body"],
25
+ etag: Etag.for_bytes(raw), content: parsed["content"]
24
26
  )
25
27
  end
26
28
 
@@ -47,11 +47,12 @@ module Textus
47
47
  end
48
48
 
49
49
  @manifest.entries.each do |mentry|
50
- next unless mentry.fetch
50
+ next unless mentry.intake_handler
51
51
  next if zone && mentry.zone != zone
52
52
  next if prefix && !(mentry.key == prefix || mentry.key.start_with?("#{prefix}."))
53
53
 
54
- ttl = parse_ttl(mentry.ttl)
54
+ policy_set = @manifest.policies_for(mentry.key)
55
+ ttl = policy_set.refresh&.ttl_seconds
55
56
  next unless ttl
56
57
 
57
58
  path = Textus::Key::Path.resolve(@manifest, mentry)
@@ -102,21 +103,8 @@ module Textus
102
103
  nil
103
104
  end
104
105
 
105
- def parse_ttl(s)
106
- return nil unless s
107
-
108
- m = s.to_s.match(/\A(\d+)([smhd])\z/) or return nil
109
- n = m[1].to_i
110
- case m[2]
111
- when "s" then n
112
- when "m" then n * 60
113
- when "h" then n * 3600
114
- when "d" then n * 86_400
115
- end
116
- end
117
-
118
106
  def intake_stale_row(mentry, path, reason)
119
- { "key" => mentry.key, "path" => path, "fetch" => mentry.fetch, "reason" => reason }
107
+ { "key" => mentry.key, "path" => path, "handler" => mentry.intake_handler, "reason" => reason }
120
108
  end
121
109
 
122
110
  def stale_row(mentry, path, reason)
@@ -10,14 +10,30 @@ module Textus
10
10
 
11
11
  def call
12
12
  violations = []
13
+ check_content_violations(violations)
14
+ check_role_authority_violations(violations)
15
+ { "protocol" => PROTOCOL, "ok" => violations.empty?, "violations" => violations }
16
+ end
17
+
18
+ private
19
+
20
+ def check_content_violations(violations)
13
21
  @manifest.enumerate.each do |row|
22
+ key = row[:key]
23
+ mentry = row[:manifest_entry]
24
+ env = fetch_envelope(key, violations) or next
25
+ schema = mentry.schema && @schema_for.call(mentry.schema)
26
+ next unless schema
27
+
14
28
  begin
15
- @reader.get(row[:key])
29
+ validate_schema!(schema, env, mentry.format)
16
30
  rescue Textus::Error => e
17
- violations << { "key" => row[:key], "code" => e.code, "message" => e.message }
31
+ violations << { "key" => key, "code" => e.code, "message" => e.message }
18
32
  end
19
33
  end
34
+ end
20
35
 
36
+ def check_role_authority_violations(violations)
21
37
  @manifest.enumerate.each do |row|
22
38
  mentry = row[:manifest_entry]
23
39
  next unless mentry.schema
@@ -30,26 +46,36 @@ module Textus
30
46
  rescue StandardError
31
47
  next
32
48
  end
33
- last_writer = @audit_log.last_writer_for(row[:key])
34
- next if last_writer.nil?
35
-
36
- env["_meta"].each_key do |field|
37
- owner = schema.maintained_by(field)
38
- next if owner.nil?
39
- next if last_writer == owner
40
- next if last_writer == "human"
41
-
42
- violations << {
43
- "key" => row[:key],
44
- "code" => "role_authority",
45
- "field" => field,
46
- "expected" => owner,
47
- "last_writer" => last_writer,
48
- }
49
- end
49
+ append_authority_violations(violations, row[:key], env, schema)
50
50
  end
51
+ end
51
52
 
52
- { "protocol" => PROTOCOL, "ok" => violations.empty?, "violations" => violations }
53
+ def append_authority_violations(violations, key, env, schema)
54
+ last_writer = @audit_log.last_writer_for(key)
55
+ return if last_writer.nil?
56
+
57
+ env["_meta"].each_key do |field|
58
+ owner = schema.maintained_by(field)
59
+ next if owner.nil? || last_writer == owner || last_writer == "human"
60
+
61
+ violations << { "key" => key, "code" => "role_authority",
62
+ "field" => field, "expected" => owner, "last_writer" => last_writer }
63
+ end
64
+ end
65
+
66
+ def fetch_envelope(key, violations)
67
+ @reader.get(key)
68
+ rescue Textus::Error => e
69
+ violations << { "key" => key, "code" => e.code, "message" => e.message }
70
+ nil
71
+ end
72
+
73
+ def validate_schema!(schema, envelope, format)
74
+ payload = case format
75
+ when "json", "yaml" then envelope["content"] || {}
76
+ else envelope["_meta"] || {}
77
+ end
78
+ schema.validate!(payload)
53
79
  end
54
80
  end
55
81
  end
@@ -1,28 +1,17 @@
1
1
  module Textus
2
2
  class Store
3
+ # Deprecated as of 0.9.1: use Textus::Application::Context instead.
4
+ # Removal scheduled for 0.10.0.
3
5
  class View
4
- READ_METHODS = %i[get list where schema_envelope deps rdeps published stale validate_all].freeze
5
- WRITE_METHODS = %i[put delete accept].freeze
6
+ def self.new(store, writable: false, as: nil)
7
+ unless @warned_once
8
+ warn "[textus] Store::View is deprecated; use Application::Context (will be removed in 0.10.0)"
9
+ @warned_once = true
10
+ end
6
11
 
7
- def initialize(store, writable: false, as: nil)
8
12
  raise UsageError.new("writable Store::View requires an as: role") if writable && (as.nil? || as.to_s.empty?)
9
13
 
10
- @store = store
11
- @writable = writable
12
- @as = as
13
- end
14
-
15
- READ_METHODS.each do |m|
16
- define_method(m) { |*args, **kw| @store.reader.public_send(m, *args, **kw) }
17
- end
18
-
19
- WRITE_METHODS.each do |m|
20
- define_method(m) do |*args, **kw|
21
- raise UsageError.new("Store::View is read-only") unless @writable
22
-
23
- kw[:as] = @as unless kw.key?(:as)
24
- @store.public_send(m, *args, **kw)
25
- end
14
+ Textus::Application::Context.new(store: store, role: as || "human")
26
15
  end
27
16
  end
28
17
  end
@@ -10,11 +10,19 @@ module Textus
10
10
  @reader = store.reader
11
11
  end
12
12
 
13
+ # Backward-compat shim — orchestration now lives in Application::Writes::Put.
13
14
  def put(key, meta: nil, body: nil, content: nil, if_etag: nil, as: Role::DEFAULT, suppress_events: false)
14
- @manifest.validate_key!(key)
15
- mentry, path, = @manifest.resolve(key)
16
- writers = @manifest.zone_writers(mentry.zone)
17
- raise WriteForbidden.new(key, mentry.zone, writers: writers) unless writers.include?(as)
15
+ ctx = Textus::Application::Context.new(store: @store, role: as)
16
+ Textus::Application::Writes::Put.new(ctx: ctx, bus: @store.bus).call(
17
+ key, meta: meta, body: body, content: content, if_etag: if_etag, suppress_events: suppress_events
18
+ )
19
+ end
20
+
21
+ # Pure I/O: validate, serialize, etag-check, write to disk, audit. No
22
+ # permission check and no event firing — those are handled by the caller
23
+ # (Application::Writes::Put).
24
+ def write_envelope_to_disk(key, mentry:, meta: nil, body: nil, content: nil, if_etag: nil, as: Role::DEFAULT, correlation_id: nil)
25
+ _, path, = @manifest.resolve(key)
18
26
 
19
27
  meta ||= {}
20
28
  strategy = Entry.for_format(mentry.format)
@@ -43,13 +51,15 @@ module Textus
43
51
  FileUtils.mkdir_p(File.dirname(path))
44
52
  File.binwrite(path, bytes)
45
53
  etag_after = Etag.for_bytes(bytes)
46
- @store.audit_log.append(role: as, verb: "put", key: key, etag_before: etag_before, etag_after: etag_after)
47
- envelope = Envelope.build(
54
+ @store.audit_log.append(
55
+ role: as, verb: "put", key: key,
56
+ etag_before: etag_before, etag_after: etag_after,
57
+ extras: correlation_id ? { "correlation_id" => correlation_id } : nil
58
+ )
59
+ Envelope.build(
48
60
  key: key, mentry: mentry, path: path,
49
61
  meta: eff_meta, body: eff_body, etag: etag_after, content: eff_content
50
62
  )
51
- @store.fire_event(:put, key: key, envelope: envelope) unless suppress_events
52
- envelope
53
63
  end
54
64
 
55
65
  def existing_uid_for(mentry, path)
@@ -108,24 +118,51 @@ module Textus
108
118
  end
109
119
  end
110
120
 
121
+ # Backward-compat shim — orchestration now lives in Application::Writes::Delete.
111
122
  def delete(key, if_etag: nil, as: Role::DEFAULT, suppress_events: false)
112
- mentry, path, = @manifest.resolve(key)
113
- writers = @manifest.zone_writers(mentry.zone)
114
- raise WriteForbidden.new(key, mentry.zone, writers: writers) unless writers.include?(as)
123
+ ctx = Textus::Application::Context.new(store: @store, role: as)
124
+ Textus::Application::Writes::Delete.new(ctx: ctx, bus: @store.bus).call(
125
+ key, if_etag: if_etag, suppress_events: suppress_events
126
+ )
127
+ end
128
+
129
+ # Pure I/O: resolve path, validate etag, delete from disk, audit. No
130
+ # permission check and no event firing — those are handled by the caller
131
+ # (Application::Writes::Delete).
132
+ def delete_envelope_from_disk(key, if_etag: nil, as: Role::DEFAULT, correlation_id: nil)
133
+ _, path, = @manifest.resolve(key)
115
134
  raise UnknownKey.new(key, suggestions: @manifest.suggestions_for(key)) unless File.exist?(path)
116
135
 
117
136
  etag_before = Etag.for_file(path)
118
137
  raise EtagMismatch.new(key, if_etag, etag_before) if if_etag && if_etag != etag_before
119
138
 
120
139
  File.delete(path)
121
- @store.audit_log.append(role: as, verb: "delete", key: key, etag_before: etag_before, etag_after: nil)
122
- @store.fire_event(:delete, key: key) unless suppress_events
123
- { "protocol" => PROTOCOL, "ok" => true, "key" => key, "deleted" => true }
140
+ @store.audit_log.append(
141
+ role: as, verb: "delete", key: key,
142
+ etag_before: etag_before, etag_after: nil,
143
+ extras: correlation_id ? { "correlation_id" => correlation_id } : nil
144
+ )
124
145
  end
125
146
 
126
147
  def accept(key, as:)
127
148
  Proposal.accept(@store, key, as: as)
128
149
  end
150
+
151
+ def reject(pending_key, as: Role::DEFAULT)
152
+ raise ProposalError.new("only human role can reject proposals; got '#{as}'") unless as == "human"
153
+
154
+ mentry, = @store.manifest.resolve(pending_key)
155
+ raise ProposalError.new("reject: '#{pending_key}' is not a pending entry (zone=#{mentry.zone})") unless mentry.zone == "pending"
156
+
157
+ env = @store.get(pending_key)
158
+ proposal = env.dig("_meta", "proposal") or
159
+ raise ProposalError.new("entry has no proposal block: #{pending_key}")
160
+ target_key = proposal["target_key"] or raise ProposalError.new("proposal missing target_key")
161
+
162
+ delete(pending_key, as: as)
163
+ @store.fire_event(:reject, key: pending_key, target_key: target_key)
164
+ { "protocol" => PROTOCOL, "rejected" => pending_key, "target_key" => target_key }
165
+ end
129
166
  end
130
167
  # rubocop:enable Metrics/ParameterLists
131
168
  end
data/lib/textus/store.rb CHANGED
@@ -42,22 +42,23 @@ module Textus
42
42
  @bus = Hooks::Dispatcher.new(audit_log: audit_log)
43
43
  @registry = Hooks::Registry.new(dispatcher: @bus)
44
44
  @schemas = {}
45
- load_extensions
45
+ load_hooks
46
46
  @reader = Reader.new(self)
47
47
  @writer = Writer.new(self)
48
+ fire_event(:loaded)
48
49
  end
49
50
 
50
- def load_extensions
51
+ def load_hooks
51
52
  Textus.with_registry(@registry) do
52
53
  Hooks::Builtin.register_all
53
54
  dir = File.join(@root, "hooks")
54
55
  return unless File.directory?(dir)
55
56
 
56
- Dir.glob(File.join(dir, "*.rb")).sort.each do |f| # rubocop:disable Lint/RedundantDirGlobSort
57
+ Dir.glob(File.join(dir, "**/*.rb")).sort.each do |f| # rubocop:disable Lint/RedundantDirGlobSort
57
58
  begin
58
59
  load(f)
59
60
  rescue StandardError, ScriptError => e
60
- raise UsageError.new("failed loading extension #{File.basename(f)}: #{e.class}: #{e.message}")
61
+ raise UsageError.new("failed loading hook #{File.basename(f)}: #{e.class}: #{e.message}")
61
62
  end
62
63
  end
63
64
  end
@@ -74,24 +75,43 @@ module Textus
74
75
  end
75
76
  end
76
77
 
77
- def get(key)
78
- @reader.get(key)
78
+ def get(key, as: Textus::Role::DEFAULT)
79
+ ctx = Textus::Composition.context(self, role: as)
80
+ result = Textus::Composition.reads_get(ctx).call(key)
81
+ raise UnknownKey.new(key, suggestions: manifest.suggestions_for(key)) if result.nil?
82
+
83
+ result
79
84
  end
80
85
 
81
86
  def where(key) = @reader.where(key)
82
87
  def list(**) = @reader.list(**)
83
88
  def schema_envelope(key) = @reader.schema_envelope(key)
84
89
 
85
- def put(...) = @writer.put(...)
90
+ # rubocop:disable Metrics/ParameterLists
91
+ def put(key, meta: nil, body: nil, content: nil, if_etag: nil, as: Role::DEFAULT, suppress_events: false)
92
+ ctx = Textus::Composition.context(self, role: as)
93
+ Textus::Composition.writes_put(ctx).call(
94
+ key, meta: meta, body: body, content: content, if_etag: if_etag, suppress_events: suppress_events
95
+ )
96
+ end
97
+ # rubocop:enable Metrics/ParameterLists
86
98
 
87
- def delete(...) = @writer.delete(...)
99
+ def delete(key, if_etag: nil, as: Role::DEFAULT, suppress_events: false)
100
+ ctx = Textus::Composition.context(self, role: as)
101
+ Textus::Composition.writes_delete(ctx).call(key, if_etag: if_etag, suppress_events: suppress_events)
102
+ end
88
103
 
89
104
  def fire_event(event, **)
90
105
  view = Store::View.new(self)
91
106
  @bus.publish(event, store: view, **)
92
107
  end
93
108
 
94
- def accept(...) = @writer.accept(...)
109
+ def accept(key, as: Role::DEFAULT)
110
+ ctx = Textus::Composition.context(self, role: as)
111
+ Textus::Composition.writes_accept(ctx).call(key)
112
+ end
113
+
114
+ def reject(...) = @writer.reject(...)
95
115
 
96
116
  def deps(key) = @reader.deps(key)
97
117
  def rdeps(key) = @reader.rdeps(key)
@@ -104,9 +124,9 @@ module Textus
104
124
  # Move an entry from old_key to new_key within the same zone. Preserves
105
125
  # uid (minting one first if absent), validates both keys against the
106
126
  # manifest, refuses to clobber, and writes one mv audit row.
107
- def mv(old_key, new_key, as: Role::DEFAULT, dry_run: false)
108
- Mover.new(reader: @reader, writer: @writer, manifest: @manifest, audit_log: audit_log)
109
- .call(old_key, new_key, as: as, dry_run: dry_run)
127
+ def mv(old_key, new_key, as: Role::DEFAULT, dry_run: false, correlation_id: nil)
128
+ Mover.new(store: self, reader: @reader, writer: @writer, manifest: @manifest, audit_log: audit_log)
129
+ .call(old_key, new_key, as: as, dry_run: dry_run, correlation_id: correlation_id)
110
130
  end
111
131
 
112
132
  def audit_log
@@ -1,4 +1,4 @@
1
1
  module Textus
2
- VERSION = "0.8.0"
2
+ VERSION = "0.9.2"
3
3
  PROTOCOL = "textus/2"
4
4
  end
data/lib/textus.rb CHANGED
@@ -13,4 +13,5 @@ loader.setup
13
13
  loader.eager_load
14
14
 
15
15
  module Textus
16
+ extend Hooks::Dsl
16
17
  end
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.8.0
4
+ version: 0.9.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Patrick
@@ -109,6 +109,20 @@ files:
109
109
  - docs/conventions.md
110
110
  - exe/textus
111
111
  - lib/textus.rb
112
+ - lib/textus/application/context.rb
113
+ - lib/textus/application/reads/audit.rb
114
+ - lib/textus/application/reads/blame.rb
115
+ - lib/textus/application/reads/freshness.rb
116
+ - lib/textus/application/reads/get.rb
117
+ - lib/textus/application/reads/policy_explain.rb
118
+ - lib/textus/application/refresh/all.rb
119
+ - lib/textus/application/refresh/orchestrator.rb
120
+ - lib/textus/application/refresh/worker.rb
121
+ - lib/textus/application/writes/accept.rb
122
+ - lib/textus/application/writes/build.rb
123
+ - lib/textus/application/writes/delete.rb
124
+ - lib/textus/application/writes/publish.rb
125
+ - lib/textus/application/writes/put.rb
112
126
  - lib/textus/builder.rb
113
127
  - lib/textus/builder/pipeline.rb
114
128
  - lib/textus/builder/renderer.rb
@@ -120,13 +134,17 @@ files:
120
134
  - lib/textus/cli/group.rb
121
135
  - lib/textus/cli/group/hook.rb
122
136
  - lib/textus/cli/group/key.rb
137
+ - lib/textus/cli/group/policy.rb
123
138
  - lib/textus/cli/group/schema.rb
124
139
  - lib/textus/cli/verb.rb
125
140
  - lib/textus/cli/verb/accept.rb
141
+ - lib/textus/cli/verb/audit.rb
142
+ - lib/textus/cli/verb/blame.rb
126
143
  - lib/textus/cli/verb/build.rb
127
144
  - lib/textus/cli/verb/delete.rb
128
145
  - lib/textus/cli/verb/deps.rb
129
146
  - lib/textus/cli/verb/doctor.rb
147
+ - lib/textus/cli/verb/freshness.rb
130
148
  - lib/textus/cli/verb/get.rb
131
149
  - lib/textus/cli/verb/hook_run.rb
132
150
  - lib/textus/cli/verb/hooks.rb
@@ -135,29 +153,48 @@ files:
135
153
  - lib/textus/cli/verb/list.rb
136
154
  - lib/textus/cli/verb/migrate_keys.rb
137
155
  - lib/textus/cli/verb/mv.rb
156
+ - lib/textus/cli/verb/policy_explain.rb
157
+ - lib/textus/cli/verb/policy_list.rb
138
158
  - lib/textus/cli/verb/published.rb
139
159
  - lib/textus/cli/verb/put.rb
140
160
  - lib/textus/cli/verb/rdeps.rb
141
161
  - lib/textus/cli/verb/refresh.rb
162
+ - lib/textus/cli/verb/refresh_stale.rb
163
+ - lib/textus/cli/verb/reject.rb
142
164
  - lib/textus/cli/verb/schema.rb
143
165
  - lib/textus/cli/verb/schema_diff.rb
144
166
  - lib/textus/cli/verb/schema_init.rb
145
167
  - lib/textus/cli/verb/schema_migrate.rb
146
- - lib/textus/cli/verb/stale.rb
147
168
  - lib/textus/cli/verb/uid.rb
148
169
  - lib/textus/cli/verb/where.rb
170
+ - lib/textus/composition.rb
149
171
  - lib/textus/dependencies.rb
150
172
  - lib/textus/doctor.rb
151
173
  - lib/textus/doctor/check.rb
152
174
  - lib/textus/doctor/check/audit_log.rb
175
+ - lib/textus/doctor/check/handler_allowlist.rb
153
176
  - lib/textus/doctor/check/hooks.rb
154
177
  - lib/textus/doctor/check/illegal_keys.rb
178
+ - lib/textus/doctor/check/intake_registration.rb
179
+ - lib/textus/doctor/check/legacy_intake_fields.rb
155
180
  - lib/textus/doctor/check/manifest_files.rb
181
+ - lib/textus/doctor/check/policy_ambiguity.rb
156
182
  - lib/textus/doctor/check/schema_violations.rb
157
183
  - lib/textus/doctor/check/schemas.rb
158
184
  - lib/textus/doctor/check/sentinels.rb
159
185
  - lib/textus/doctor/check/templates.rb
160
186
  - lib/textus/doctor/check/unowned_schema_fields.rb
187
+ - lib/textus/domain/action.rb
188
+ - lib/textus/domain/freshness/evaluator.rb
189
+ - lib/textus/domain/freshness/policy.rb
190
+ - lib/textus/domain/freshness/verdict.rb
191
+ - lib/textus/domain/outcome.rb
192
+ - lib/textus/domain/permission.rb
193
+ - lib/textus/domain/policy.rb
194
+ - lib/textus/domain/policy/handler_allowlist.rb
195
+ - lib/textus/domain/policy/matcher.rb
196
+ - lib/textus/domain/policy/promote.rb
197
+ - lib/textus/domain/policy/refresh.rb
161
198
  - lib/textus/entry.rb
162
199
  - lib/textus/entry/base.rb
163
200
  - lib/textus/entry/json.rb
@@ -169,8 +206,14 @@ files:
169
206
  - lib/textus/etag.rb
170
207
  - lib/textus/hooks/builtin.rb
171
208
  - lib/textus/hooks/dispatcher.rb
209
+ - lib/textus/hooks/dsl.rb
172
210
  - lib/textus/hooks/loader.rb
173
211
  - lib/textus/hooks/registry.rb
212
+ - lib/textus/infra/clock.rb
213
+ - lib/textus/infra/event_bus.rb
214
+ - lib/textus/infra/publisher.rb
215
+ - lib/textus/infra/refresh/detached.rb
216
+ - lib/textus/infra/refresh/lock.rb
174
217
  - lib/textus/init.rb
175
218
  - lib/textus/intro.rb
176
219
  - lib/textus/key/distance.rb
@@ -178,6 +221,7 @@ files:
178
221
  - lib/textus/key/path.rb
179
222
  - lib/textus/manifest.rb
180
223
  - lib/textus/manifest/entry.rb
224
+ - lib/textus/manifest/policies.rb
181
225
  - lib/textus/migrate_keys.rb
182
226
  - lib/textus/mustache.rb
183
227
  - lib/textus/projection.rb
@@ -1,14 +0,0 @@
1
- module Textus
2
- class CLI
3
- class Verb
4
- class Stale < Verb
5
- option :prefix, "--prefix=KEY"
6
- option :zone, "--zone=Z"
7
-
8
- def call(store)
9
- emit(store.stale(prefix: prefix, zone: zone))
10
- end
11
- end
12
- end
13
- end
14
- end