textus 0.22.0 → 0.26.0

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 (160) hide show
  1. checksums.yaml +4 -4
  2. data/ARCHITECTURE.md +148 -45
  3. data/CHANGELOG.md +102 -0
  4. data/README.md +1 -1
  5. data/SPEC.md +12 -12
  6. data/docs/conventions.md +10 -0
  7. data/lib/textus/application/caps.rb +49 -0
  8. data/lib/textus/application/context.rb +2 -2
  9. data/lib/textus/application/envelope/reader.rb +44 -0
  10. data/lib/textus/application/{writes/envelope_io.rb → envelope/writer.rb} +24 -50
  11. data/lib/textus/application/maintenance/key_delete_prefix.rb +44 -0
  12. data/lib/textus/application/maintenance/key_mv_prefix.rb +57 -0
  13. data/lib/textus/application/maintenance/migrate.rb +59 -0
  14. data/lib/textus/application/maintenance/rule_lint.rb +65 -0
  15. data/lib/textus/application/maintenance/zone_mv.rb +60 -0
  16. data/lib/textus/application/maintenance.rb +17 -0
  17. data/lib/textus/application/projection.rb +12 -10
  18. data/lib/textus/application/read/audit.rb +106 -0
  19. data/lib/textus/application/read/blame.rb +91 -0
  20. data/lib/textus/application/read/deps.rb +34 -0
  21. data/lib/textus/application/read/freshness.rb +110 -0
  22. data/lib/textus/application/read/get.rb +75 -0
  23. data/lib/textus/application/read/get_or_refresh.rb +63 -0
  24. data/lib/textus/application/read/list.rb +25 -0
  25. data/lib/textus/application/read/policy_explain.rb +47 -0
  26. data/lib/textus/application/read/published.rb +25 -0
  27. data/lib/textus/application/read/pulse.rb +101 -0
  28. data/lib/textus/application/read/rdeps.rb +35 -0
  29. data/lib/textus/application/read/schema_envelope.rb +26 -0
  30. data/lib/textus/application/read/stale.rb +23 -0
  31. data/lib/textus/application/read/uid.rb +30 -0
  32. data/lib/textus/application/read/validate_all.rb +32 -0
  33. data/lib/textus/application/{reads → read}/validator.rb +2 -2
  34. data/lib/textus/application/read/where.rb +26 -0
  35. data/lib/textus/application/use_case.rb +22 -0
  36. data/lib/textus/application/write/accept.rb +102 -0
  37. data/lib/textus/application/{writes → write}/authority_gate.rb +3 -3
  38. data/lib/textus/application/write/delete.rb +45 -0
  39. data/lib/textus/application/{writes → write}/materializer.rb +14 -15
  40. data/lib/textus/application/write/mv.rb +118 -0
  41. data/lib/textus/application/write/publish.rb +96 -0
  42. data/lib/textus/application/write/put.rb +49 -0
  43. data/lib/textus/application/write/refresh_all.rb +63 -0
  44. data/lib/textus/application/{refresh/orchestrator.rb → write/refresh_orchestrator.rb} +32 -8
  45. data/lib/textus/application/write/refresh_worker.rb +134 -0
  46. data/lib/textus/application/write/reject.rb +62 -0
  47. data/lib/textus/boot.rb +27 -29
  48. data/lib/textus/builder/pipeline.rb +3 -3
  49. data/lib/textus/cli/group/mcp.rb +9 -0
  50. data/lib/textus/cli/group/zone.rb +9 -0
  51. data/lib/textus/cli/verb/accept.rb +1 -1
  52. data/lib/textus/cli/verb/audit.rb +2 -2
  53. data/lib/textus/cli/verb/blame.rb +1 -1
  54. data/lib/textus/cli/verb/boot.rb +1 -1
  55. data/lib/textus/cli/verb/build.rb +2 -2
  56. data/lib/textus/cli/verb/delete.rb +1 -1
  57. data/lib/textus/cli/verb/deps.rb +1 -1
  58. data/lib/textus/cli/verb/doctor.rb +1 -1
  59. data/lib/textus/cli/verb/freshness.rb +1 -1
  60. data/lib/textus/cli/verb/get.rb +1 -1
  61. data/lib/textus/cli/verb/hook_run.rb +3 -4
  62. data/lib/textus/cli/verb/hooks.rb +11 -14
  63. data/lib/textus/cli/verb/key_delete.rb +24 -0
  64. data/lib/textus/cli/verb/list.rb +1 -1
  65. data/lib/textus/cli/verb/mcp_serve.rb +17 -0
  66. data/lib/textus/cli/verb/migrate.rb +18 -0
  67. data/lib/textus/cli/verb/mv.rb +11 -3
  68. data/lib/textus/cli/verb/published.rb +1 -1
  69. data/lib/textus/cli/verb/pulse.rb +1 -1
  70. data/lib/textus/cli/verb/put.rb +8 -6
  71. data/lib/textus/cli/verb/rdeps.rb +1 -1
  72. data/lib/textus/cli/verb/refresh.rb +1 -1
  73. data/lib/textus/cli/verb/refresh_stale.rb +1 -1
  74. data/lib/textus/cli/verb/reject.rb +1 -1
  75. data/lib/textus/cli/verb/rule_explain.rb +1 -1
  76. data/lib/textus/cli/verb/rule_lint.rb +18 -0
  77. data/lib/textus/cli/verb/schema.rb +1 -1
  78. data/lib/textus/cli/verb/uid.rb +1 -1
  79. data/lib/textus/cli/verb/where.rb +1 -1
  80. data/lib/textus/cli/verb/zone_mv.rb +19 -0
  81. data/lib/textus/cli/verb.rb +4 -4
  82. data/lib/textus/doctor/check/audit_log.rb +2 -2
  83. data/lib/textus/doctor/check/handler_allowlist.rb +2 -2
  84. data/lib/textus/doctor/check/hooks.rb +4 -3
  85. data/lib/textus/doctor/check/illegal_keys.rb +2 -2
  86. data/lib/textus/doctor/check/intake_registration.rb +2 -2
  87. data/lib/textus/doctor/check/manifest_files.rb +2 -2
  88. data/lib/textus/doctor/check/protocol_version.rb +2 -2
  89. data/lib/textus/doctor/check/refresh_locks.rb +2 -2
  90. data/lib/textus/doctor/check/rule_ambiguity.rb +2 -2
  91. data/lib/textus/doctor/check/schema_parse_error.rb +1 -1
  92. data/lib/textus/doctor/check/schema_violations.rb +1 -1
  93. data/lib/textus/doctor/check/schemas.rb +2 -2
  94. data/lib/textus/doctor/check/sentinels.rb +2 -2
  95. data/lib/textus/doctor/check/templates.rb +2 -2
  96. data/lib/textus/doctor/check/unowned_schema_fields.rb +1 -1
  97. data/lib/textus/doctor/check.rb +5 -3
  98. data/lib/textus/doctor.rb +24 -27
  99. data/lib/textus/domain/authorizer.rb +4 -4
  100. data/lib/textus/{application → domain}/policy/predicates/accept_authority_signed.rb +2 -2
  101. data/lib/textus/{application → domain}/policy/predicates/schema_valid.rb +1 -1
  102. data/lib/textus/{application → domain}/policy/promotion.rb +1 -1
  103. data/lib/textus/domain/staleness/generator_check.rb +2 -2
  104. data/lib/textus/domain/staleness/intake_check.rb +2 -2
  105. data/lib/textus/domain/staleness.rb +1 -1
  106. data/lib/textus/hooks/builtin.rb +14 -14
  107. data/lib/textus/hooks/context.rb +13 -13
  108. data/lib/textus/hooks/error_log.rb +32 -0
  109. data/lib/textus/hooks/{bus.rb → event_bus.rb} +41 -53
  110. data/lib/textus/hooks/loader.rb +29 -3
  111. data/lib/textus/hooks/rpc_registry.rb +77 -0
  112. data/lib/textus/infra/audit_subscriber.rb +6 -7
  113. data/lib/textus/infra/refresh/detached.rb +1 -1
  114. data/lib/textus/key/path.rb +7 -3
  115. data/lib/textus/manifest/data.rb +78 -0
  116. data/lib/textus/manifest/entry/base.rb +4 -4
  117. data/lib/textus/manifest/entry/derived.rb +4 -5
  118. data/lib/textus/manifest/entry/validators/events.rb +1 -1
  119. data/lib/textus/manifest/policy.rb +48 -0
  120. data/lib/textus/manifest/resolver.rb +14 -14
  121. data/lib/textus/manifest/rules.rb +1 -1
  122. data/lib/textus/manifest.rb +53 -111
  123. data/lib/textus/mcp/errors.rb +32 -0
  124. data/lib/textus/mcp/server.rb +127 -0
  125. data/lib/textus/mcp/session.rb +31 -0
  126. data/lib/textus/mcp/tool_schemas.rb +71 -0
  127. data/lib/textus/mcp/tools.rb +129 -0
  128. data/lib/textus/mcp.rb +6 -0
  129. data/lib/textus/schema/tools.rb +14 -10
  130. data/lib/textus/session.rb +84 -0
  131. data/lib/textus/store.rb +14 -9
  132. data/lib/textus/version.rb +1 -1
  133. data/lib/textus.rb +8 -1
  134. metadata +61 -36
  135. data/lib/textus/application/reads/audit.rb +0 -94
  136. data/lib/textus/application/reads/blame.rb +0 -82
  137. data/lib/textus/application/reads/deps.rb +0 -26
  138. data/lib/textus/application/reads/freshness.rb +0 -88
  139. data/lib/textus/application/reads/get.rb +0 -67
  140. data/lib/textus/application/reads/get_or_refresh.rb +0 -51
  141. data/lib/textus/application/reads/list.rb +0 -17
  142. data/lib/textus/application/reads/policy_explain.rb +0 -39
  143. data/lib/textus/application/reads/published.rb +0 -17
  144. data/lib/textus/application/reads/pulse.rb +0 -63
  145. data/lib/textus/application/reads/rdeps.rb +0 -27
  146. data/lib/textus/application/reads/schema_envelope.rb +0 -18
  147. data/lib/textus/application/reads/stale.rb +0 -15
  148. data/lib/textus/application/reads/uid.rb +0 -23
  149. data/lib/textus/application/reads/validate_all.rb +0 -24
  150. data/lib/textus/application/reads/where.rb +0 -18
  151. data/lib/textus/application/refresh/all.rb +0 -52
  152. data/lib/textus/application/refresh/worker.rb +0 -116
  153. data/lib/textus/application/writes/accept.rb +0 -89
  154. data/lib/textus/application/writes/delete.rb +0 -33
  155. data/lib/textus/application/writes/mv.rb +0 -105
  156. data/lib/textus/application/writes/publish.rb +0 -81
  157. data/lib/textus/application/writes/put.rb +0 -37
  158. data/lib/textus/application/writes/reject.rb +0 -50
  159. data/lib/textus/infra/event_bus.rb +0 -27
  160. data/lib/textus/operations.rb +0 -176
@@ -8,7 +8,7 @@ module Textus
8
8
  option :use_stdin, "--stdin"
9
9
  option :fetch_name, "--fetch=NAME"
10
10
 
11
- def call(store) # rubocop:disable Metrics/AbcSize
11
+ def call(store)
12
12
  key = positional.shift or raise UsageError.new("put requires a key")
13
13
  raise UsageError.new("put requires --stdin in v1") unless use_stdin
14
14
 
@@ -17,15 +17,17 @@ module Textus
17
17
  raw = @stdin.read
18
18
  payload =
19
19
  if fetch_name
20
- callable = store.bus.rpc_callable(:resolve_intake, fetch_name)
21
20
  result =
22
21
  begin
23
- Timeout.timeout(Textus::Application::Refresh::Worker::FETCH_TIMEOUT_SECONDS) do
24
- callable.call(config: { "bytes" => raw }, store: store, args: {})
22
+ Timeout.timeout(Textus::Application::Write::RefreshWorker::FETCH_TIMEOUT_SECONDS) do
23
+ store.rpc.invoke(:resolve_intake, fetch_name,
24
+ caps: nil,
25
+ config: { "bytes" => raw },
26
+ args: {})
25
27
  end
26
28
  rescue Timeout::Error
27
29
  raise UsageError.new(
28
- "fetch '#{fetch_name}' exceeded #{Textus::Application::Refresh::Worker::FETCH_TIMEOUT_SECONDS}s timeout",
30
+ "fetch '#{fetch_name}' exceeded #{Textus::Application::Write::RefreshWorker::FETCH_TIMEOUT_SECONDS}s timeout",
29
31
  )
30
32
  end
31
33
  basename = key.split(".").last
@@ -44,7 +46,7 @@ module Textus
44
46
  meta = payload["_meta"] || {}
45
47
  body = payload["body"] || ""
46
48
  if_etag = payload["if_etag"]
47
- result = Textus::Operations.for(store, role: role).put(key, meta: meta, body: body, if_etag: if_etag)
49
+ result = store.session(role: role).put(key, meta: meta, body: body, if_etag: if_etag)
48
50
  emit(result.to_h_for_wire)
49
51
  end
50
52
  end
@@ -6,7 +6,7 @@ module Textus
6
6
 
7
7
  def call(store)
8
8
  key = positional.shift or raise UsageError.new("rdeps requires a key")
9
- emit({ "key" => key, "rdeps" => operations_for(store).rdeps(key) })
9
+ emit({ "key" => key, "rdeps" => session_for(store).rdeps(key) })
10
10
  end
11
11
  end
12
12
  end
@@ -6,7 +6,7 @@ module Textus
6
6
 
7
7
  def call(store)
8
8
  key = positional.shift or raise UsageError.new("refresh requires a key")
9
- emit(operations_for(store).refresh(key).to_h_for_wire)
9
+ emit(session_for(store).refresh(key).to_h_for_wire)
10
10
  end
11
11
  end
12
12
  end
@@ -10,7 +10,7 @@ module Textus
10
10
  option :as_flag, "--as=ROLE"
11
11
 
12
12
  def call(store)
13
- result = operations_for(store).refresh_all(prefix: prefix, zone: zone)
13
+ result = session_for(store).refresh_all(prefix: prefix, zone: zone)
14
14
  emit(result)
15
15
  result["ok"] ? 0 : 1
16
16
  end
@@ -8,7 +8,7 @@ module Textus
8
8
 
9
9
  def call(store)
10
10
  key = positional.shift or raise UsageError.new("reject requires a key")
11
- emit(operations_for(store).reject(key))
11
+ emit(session_for(store).reject(key))
12
12
  end
13
13
  end
14
14
  end
@@ -7,7 +7,7 @@ module Textus
7
7
 
8
8
  def call(store)
9
9
  key = positional.shift or raise UsageError.new("policy explain requires a KEY")
10
- result = operations_for(store).policy_explain(key: key)
10
+ result = session_for(store).policy_explain(key: key)
11
11
  emit({ "verb" => "policy_explain" }.merge(result.transform_keys(&:to_s)))
12
12
  end
13
13
  end
@@ -0,0 +1,18 @@
1
+ module Textus
2
+ class CLI
3
+ class Verb
4
+ class RuleLint < Verb
5
+ command_name "lint"
6
+ parent_group Group::Rule
7
+
8
+ option :against, "--against=FILE"
9
+
10
+ def call(store)
11
+ path = against or raise UsageError.new("rule lint --against=FILE required")
12
+ yaml = File.read(path)
13
+ emit(session_for(store).rule_lint(candidate_yaml: yaml).to_h)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -7,7 +7,7 @@ module Textus
7
7
 
8
8
  def call(store)
9
9
  key = positional.shift or raise UsageError.new("schema requires a key")
10
- emit(operations_for(store).schema_envelope(key))
10
+ emit(session_for(store).schema_envelope(key))
11
11
  end
12
12
  end
13
13
  end
@@ -7,7 +7,7 @@ module Textus
7
7
 
8
8
  def call(store)
9
9
  key = positional.shift or raise UsageError.new("uid requires a key")
10
- emit({ "key" => key, "uid" => operations_for(store).uid(key) })
10
+ emit({ "key" => key, "uid" => session_for(store).uid(key) })
11
11
  end
12
12
  end
13
13
  end
@@ -6,7 +6,7 @@ module Textus
6
6
 
7
7
  def call(store)
8
8
  key = positional.shift or raise UsageError.new("where requires a key")
9
- emit(operations_for(store).where(key))
9
+ emit(session_for(store).where(key))
10
10
  end
11
11
  end
12
12
  end
@@ -0,0 +1,19 @@
1
+ module Textus
2
+ class CLI
3
+ class Verb
4
+ class ZoneMv < Verb
5
+ command_name "mv"
6
+ parent_group Group::Zone
7
+
8
+ option :as_flag, "--as=ROLE"
9
+ option :dry_run, "--dry-run"
10
+
11
+ def call(store)
12
+ from = positional.shift or raise UsageError.new("zone mv requires <from> <to>")
13
+ to = positional.shift or raise UsageError.new("zone mv requires <from> <to>")
14
+ emit(session_for(store).zone_mv(from: from, to: to, dry_run: dry_run || false).to_h)
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -100,12 +100,12 @@ module Textus
100
100
  # Convenience for verbs whose only pre-call boilerplate is
101
101
  # resolving the role and wrapping it in a context.
102
102
  def context_for(store)
103
- Textus::Operations.for(store, role: resolved_role(store)).ctx
103
+ store.session(role: resolved_role(store)).ctx
104
104
  end
105
105
 
106
- # Returns an Operations instance bound to the resolved role.
107
- def operations_for(store)
108
- Textus::Operations.for(store, role: resolved_role(store))
106
+ # Returns a Session instance bound to the resolved role.
107
+ def session_for(store)
108
+ store.session(role: resolved_role(store))
109
109
  end
110
110
  end
111
111
  end
@@ -3,8 +3,8 @@ module Textus
3
3
  class Check
4
4
  class AuditLog < Check
5
5
  def call
6
- path = File.join(store.root, "audit.log")
7
- Textus::Infra::AuditLog.new(store.root).verify_integrity.map do |v|
6
+ path = File.join(root, "audit.log")
7
+ Textus::Infra::AuditLog.new(root).verify_integrity.map do |v|
8
8
  {
9
9
  "code" => "audit.parse_error",
10
10
  "level" => "warning",
@@ -7,12 +7,12 @@ module Textus
7
7
  class HandlerAllowlist < Check
8
8
  def call
9
9
  out = []
10
- store.manifest.entries.each do |mentry|
10
+ manifest.data.entries.each do |mentry|
11
11
  next unless mentry.is_a?(Textus::Manifest::Entry::Intake)
12
12
 
13
13
  handler = mentry.handler
14
14
 
15
- allow = store.manifest.rules_for(mentry.key).handler_allowlist
15
+ allow = manifest.rules.for(mentry.key).handler_allowlist
16
16
  next if allow.nil?
17
17
  next if allow.allows?(handler)
18
18
 
@@ -4,15 +4,16 @@ module Textus
4
4
  class Hooks < Check
5
5
  def call
6
6
  out = []
7
- dir = File.join(store.root, "hooks")
7
+ dir = File.join(root, "hooks")
8
8
  return out unless File.directory?(dir)
9
9
 
10
10
  Dir.glob(File.join(dir, "*.rb")).sort.each do |f| # rubocop:disable Lint/RedundantDirGlobSort
11
- bus = Textus::Hooks::Bus.new
11
+ events = Textus::Hooks::EventBus.new
12
+ rpc = Textus::Hooks::RpcRegistry.new
12
13
  Textus.drain_hook_blocks
13
14
  begin
14
15
  load(f)
15
- Textus.drain_hook_blocks.each { |b| b.call(bus) }
16
+ Textus.drain_hook_blocks.each { |b| b.call(Textus::Hooks::Loader::Dsl.new(events: events, rpc: rpc)) }
16
17
  end
17
18
  rescue StandardError, ScriptError => e
18
19
  out << {
@@ -4,10 +4,10 @@ module Textus
4
4
  class IllegalKeys < Check
5
5
  def call
6
6
  out = []
7
- store.manifest.entries.each do |entry|
7
+ manifest.data.entries.each do |entry|
8
8
  next unless entry.nested?
9
9
 
10
- base = File.join(store.root, "zones", entry.path)
10
+ base = File.join(root, "zones", entry.path)
11
11
  next unless File.directory?(base)
12
12
 
13
13
  index_fn = entry.respond_to?(:index_filename) ? entry.index_filename : nil
@@ -6,7 +6,7 @@ module Textus
6
6
 
7
7
  def call
8
8
  declared = collect_declared_handlers
9
- registered = store.bus.rpc_names(:resolve_intake).to_set
9
+ registered = rpc.names(:resolve_intake).to_set
10
10
 
11
11
  out = (declared - registered).map do |name|
12
12
  {
@@ -35,7 +35,7 @@ module Textus
35
35
 
36
36
  def collect_declared_handlers
37
37
  set = Set.new
38
- store.manifest.entries.each do |mentry|
38
+ manifest.data.entries.each do |mentry|
39
39
  set << mentry.handler.to_sym if mentry.is_a?(Textus::Manifest::Entry::Intake)
40
40
  end
41
41
  set
@@ -3,10 +3,10 @@ module Textus
3
3
  class Check
4
4
  class ManifestFiles < Check
5
5
  def call
6
- store.manifest.entries.each_with_object([]) do |entry, out|
6
+ manifest.data.entries.each_with_object([]) do |entry, out|
7
7
  next if entry.nested?
8
8
 
9
- path = Textus::Key::Path.resolve(store.manifest, entry)
9
+ path = Textus::Key::Path.resolve(manifest.data, entry)
10
10
  next if File.exist?(path)
11
11
 
12
12
  out << {
@@ -23,10 +23,10 @@ module Textus
23
23
  }]
24
24
  end
25
25
 
26
- # Doctor check interface: store.root is the .textus/ directory itself,
26
+ # Doctor check interface: root is the .textus/ directory itself,
27
27
  # so manifest.yaml lives directly inside it.
28
28
  def call
29
- path = File.join(store.root, "manifest.yaml")
29
+ path = File.join(root, "manifest.yaml")
30
30
  return [] unless File.exist?(path)
31
31
 
32
32
  doc = YAML.safe_load_file(path, aliases: false) || {}
@@ -1,7 +1,7 @@
1
1
  module Textus
2
2
  module Doctor
3
3
  class Check
4
- # Lists per-key refresh lock files under <store.root>/.locks/ whose
4
+ # Lists per-key refresh lock files under <root>/.locks/ whose
5
5
  # recorded PID is no longer running. These are forensic artifacts only:
6
6
  # Refresh::Lock uses flock(2), which the kernel releases on process
7
7
  # death, so stale files do not block subsequent acquires. The check
@@ -9,7 +9,7 @@ module Textus
9
9
  # (e.g. a refresh path that crashes repeatedly).
10
10
  class RefreshLocks < Check
11
11
  def call
12
- dir = File.join(store.root, ".locks")
12
+ dir = File.join(root, ".locks")
13
13
  return [] unless File.directory?(dir)
14
14
 
15
15
  Dir.glob(File.join(dir, "*.lock")).filter_map { |path| inspect_lock(path) }
@@ -10,8 +10,8 @@ module Textus
10
10
 
11
11
  def call
12
12
  out = []
13
- rules = store.manifest.rules
14
- store.manifest.entries.each do |mentry|
13
+ rules = manifest.rules
14
+ manifest.data.entries.each do |mentry|
15
15
  matches = rules.explain(mentry.key)
16
16
  next if matches.length < 2
17
17
 
@@ -7,7 +7,7 @@ module Textus
7
7
  # leaving the operator with no signal that a schema is broken.
8
8
  class SchemaParseError < Check
9
9
  def call
10
- dir = File.join(store.root, "schemas")
10
+ dir = File.join(root, "schemas")
11
11
  return [] unless File.directory?(dir)
12
12
 
13
13
  Dir.glob(File.join(dir, "*.yaml")).each_with_object([]) do |path, out|
@@ -3,7 +3,7 @@ module Textus
3
3
  class Check
4
4
  class SchemaViolations < Check
5
5
  def call
6
- res = Textus::Operations.for(store).validate_all
6
+ res = @session.validate_all
7
7
  res["violations"].map do |v|
8
8
  fix = v["expected"] &&
9
9
  "field '#{v["field"]}' should be written by '#{v["expected"]}' (last writer: #{v["last_writer"]})"
@@ -4,10 +4,10 @@ module Textus
4
4
  class Schemas < Check
5
5
  def call
6
6
  out = []
7
- store.manifest.entries.each do |entry|
7
+ manifest.data.entries.each do |entry|
8
8
  next if entry.schema.nil?
9
9
 
10
- sp = File.join(store.root, "schemas", "#{entry.schema}.yaml")
10
+ sp = File.join(root, "schemas", "#{entry.schema}.yaml")
11
11
  next if File.exist?(sp)
12
12
 
13
13
  out << {
@@ -3,10 +3,10 @@ module Textus
3
3
  class Check
4
4
  class Sentinels < Check
5
5
  def call
6
- dir = File.join(store.root, "sentinels")
6
+ dir = File.join(root, "sentinels")
7
7
  return [] unless File.directory?(dir)
8
8
 
9
- repo_root = File.dirname(store.root)
9
+ repo_root = File.dirname(root)
10
10
  Dir.glob(File.join(dir, "**", "*#{Textus::Domain::Sentinel::SUFFIX}")).flat_map do |sentinel_path|
11
11
  inspect_sentinel(sentinel_path, repo_root)
12
12
  end
@@ -4,11 +4,11 @@ module Textus
4
4
  class Templates < Check
5
5
  def call
6
6
  out = []
7
- store.manifest.entries.each do |entry|
7
+ manifest.data.entries.each do |entry|
8
8
  template = entry.respond_to?(:template) ? entry.template : nil
9
9
  next if template.nil?
10
10
 
11
- tp = File.join(store.root, "templates", template)
11
+ tp = File.join(root, "templates", template)
12
12
  next if File.exist?(tp)
13
13
 
14
14
  out << {
@@ -3,7 +3,7 @@ module Textus
3
3
  class Check
4
4
  class UnownedSchemaFields < Check
5
5
  def call
6
- dir = File.join(store.root, "schemas")
6
+ dir = File.join(root, "schemas")
7
7
  return [] unless File.directory?(dir)
8
8
 
9
9
  Dir.glob(File.join(dir, "*.yaml")).flat_map do |path|
@@ -14,8 +14,8 @@ module Textus
14
14
  .downcase
15
15
  end
16
16
 
17
- def initialize(store)
18
- @store = store
17
+ def initialize(session)
18
+ @session = session
19
19
  end
20
20
 
21
21
  def call
@@ -24,7 +24,9 @@ module Textus
24
24
 
25
25
  protected
26
26
 
27
- attr_reader :store
27
+ def root = @session.read_caps.root
28
+ def manifest = @session.read_caps.manifest
29
+ def rpc = @session.rpc
28
30
  end
29
31
  end
30
32
  end
data/lib/textus/doctor.rb CHANGED
@@ -30,7 +30,7 @@ module Textus
30
30
 
31
31
  module_function
32
32
 
33
- def run(store, checks: nil)
33
+ def run(session, checks: nil)
34
34
  selected_keys = checks ? Array(checks).map(&:to_s) : ALL_CHECKS
35
35
  unknown = selected_keys - ALL_CHECKS
36
36
  unless unknown.empty?
@@ -40,8 +40,8 @@ module Textus
40
40
  end
41
41
 
42
42
  selected = CHECKS.select { |c| selected_keys.include?(c.name_key) }
43
- issues = selected.flat_map { |c| c.new(store).call }
44
- issues.concat(run_registered_checks(store))
43
+ issues = selected.flat_map { |c| c.new(session).call }
44
+ issues.concat(run_registered_checks(session))
45
45
 
46
46
  summary = LEVELS.to_h { |l| [l, issues.count { |i| i["level"] == l }] }
47
47
  {
@@ -52,30 +52,27 @@ module Textus
52
52
  }
53
53
  end
54
54
 
55
- def run_registered_checks(store)
56
- out = []
57
- store.bus.rpc_names(:validate).each do |name|
58
- callable = store.bus.rpc_callable(:validate, name)
59
- begin
60
- result = Timeout.timeout(DOCTOR_CHECK_TIMEOUT_SECONDS) { callable.call(store: store) }
61
- if result.is_a?(Array)
62
- out.concat(result.map { |h| h.transform_keys(&:to_s) })
63
- else
64
- out << fail_issue(name, code: "doctor_check.bad_return",
65
- message: "doctor_check '#{name}' returned #{result.class} (expected Array)",
66
- fix: "return an array of issue hashes from the doctor_check block")
67
- end
68
- rescue Timeout::Error
69
- out << fail_issue(name, code: "doctor_check.timeout",
70
- message: "doctor_check '#{name}' exceeded #{DOCTOR_CHECK_TIMEOUT_SECONDS}s",
71
- fix: "shorten the check or split it into smaller checks")
72
- rescue StandardError => e
73
- out << fail_issue(name, code: "doctor_check.failed",
74
- message: "#{e.class}: #{e.message}",
75
- fix: "fix the :validate hook in .textus/hooks/")
76
- end
55
+ def run_registered_checks(session)
56
+ session.rpc.names(:validate).flat_map { |name| invoke_registered_check(session, name) }
57
+ end
58
+
59
+ def invoke_registered_check(session, name)
60
+ result = Timeout.timeout(DOCTOR_CHECK_TIMEOUT_SECONDS) do
61
+ session.rpc.invoke(:validate, name, caps: session.write_caps)
77
62
  end
78
- out
63
+ return result.map { |h| h.transform_keys(&:to_s) } if result.is_a?(Array)
64
+
65
+ [fail_issue(name, code: "doctor_check.bad_return",
66
+ message: "doctor_check '#{name}' returned #{result.class} (expected Array)",
67
+ fix: "return an array of issue hashes from the doctor_check block")]
68
+ rescue Timeout::Error
69
+ [fail_issue(name, code: "doctor_check.timeout",
70
+ message: "doctor_check '#{name}' exceeded #{DOCTOR_CHECK_TIMEOUT_SECONDS}s",
71
+ fix: "shorten the check or split it into smaller checks")]
72
+ rescue StandardError => e
73
+ [fail_issue(name, code: "doctor_check.failed",
74
+ message: "#{e.class}: #{e.message}",
75
+ fix: "fix the :validate hook in .textus/hooks/")]
79
76
  end
80
77
 
81
78
  def fail_issue(name, code:, message:, fix:)
@@ -88,6 +85,6 @@ module Textus
88
85
  }
89
86
  end
90
87
 
91
- private_class_method :run_registered_checks, :fail_issue
88
+ private_class_method :run_registered_checks, :invoke_registered_check, :fail_issue
92
89
  end
93
90
  end
@@ -11,24 +11,24 @@ module Textus
11
11
  end
12
12
 
13
13
  def can_write?(zone, role:)
14
- @manifest.permission_for(zone.to_s).allows_write?(role)
14
+ @manifest.policy.permission_for(zone.to_s).allows_write?(role)
15
15
  end
16
16
 
17
17
  def can_read?(zone, role:)
18
- @manifest.permission_for(zone.to_s).allows_read?(role)
18
+ @manifest.policy.permission_for(zone.to_s).allows_read?(role)
19
19
  end
20
20
 
21
21
  def authorize_write!(mentry, role:)
22
22
  return if can_write?(mentry.zone, role: role)
23
23
 
24
- writers = @manifest.zone_writers(mentry.zone)
24
+ writers = @manifest.policy.zone_writers(mentry.zone)
25
25
  raise WriteForbidden.new(mentry.key, mentry.zone, writers: writers)
26
26
  end
27
27
 
28
28
  def authorize_read!(mentry, role:)
29
29
  return if can_read?(mentry.zone, role: role)
30
30
 
31
- readers = @manifest.zone_readers[mentry.zone]
31
+ readers = @manifest.policy.zone_readers[mentry.zone]
32
32
  readers = nil if readers == :all
33
33
  raise ReadForbidden.new(mentry.key, mentry.zone, readers: readers)
34
34
  end
@@ -1,5 +1,5 @@
1
1
  module Textus
2
- module Application
2
+ module Domain
3
3
  module Policy
4
4
  module Predicates
5
5
  # Promotion predicate: the role driving the promotion must have
@@ -20,7 +20,7 @@ module Textus
20
20
  role_str = role&.to_s
21
21
  return true if role_str.nil? || role_str.empty?
22
22
 
23
- kind = manifest.role_kind(role_str)
23
+ kind = manifest.policy.role_kind(role_str)
24
24
  return true if kind == :accept_authority
25
25
 
26
26
  @reason = "role '#{role_str}' has kind '#{kind.inspect}', expected ':accept_authority'"
@@ -1,5 +1,5 @@
1
1
  module Textus
2
- module Application
2
+ module Domain
3
3
  module Policy
4
4
  module Predicates
5
5
  class SchemaValid
@@ -2,7 +2,7 @@ require_relative "predicates/schema_valid"
2
2
  require_relative "predicates/accept_authority_signed"
3
3
 
4
4
  module Textus
5
- module Application
5
+ module Domain
6
6
  module Policy
7
7
  class Promotion
8
8
  Result = Struct.new(:ok?, :reasons, keyword_init: true)
@@ -19,7 +19,7 @@ module Textus
19
19
  src = mentry.source
20
20
  return [] unless src.is_a?(Textus::Manifest::Entry::Derived::External)
21
21
 
22
- path = Textus::Key::Path.resolve(@manifest, mentry)
22
+ path = Textus::Key::Path.resolve(@manifest.data, mentry)
23
23
  return [stale_row(mentry, path, "derived entry has never been generated")] unless File.exist?(path)
24
24
 
25
25
  parsed = Entry.for_format(mentry.format).parse(File.binread(path), path: path)
@@ -63,7 +63,7 @@ module Textus
63
63
  end
64
64
 
65
65
  def check_filesystem_source(src, gen_time)
66
- abs = File.absolute_path?(src) ? src : File.join(File.dirname(@manifest.root), src)
66
+ abs = File.absolute_path?(src) ? src : File.join(File.dirname(@manifest.data.root), src)
67
67
  if File.directory?(abs)
68
68
  Dir.glob(File.join(abs, "**", "*")).each do |fp|
69
69
  next unless File.file?(fp)
@@ -13,10 +13,10 @@ module Textus
13
13
  def rows_for(mentry)
14
14
  return [] unless mentry.is_a?(Textus::Manifest::Entry::Intake)
15
15
 
16
- ttl = @manifest.rules_for(mentry.key).refresh&.ttl_seconds
16
+ ttl = @manifest.rules.for(mentry.key).refresh&.ttl_seconds
17
17
  return [] unless ttl
18
18
 
19
- path = Textus::Key::Path.resolve(@manifest, mentry)
19
+ path = Textus::Key::Path.resolve(@manifest.data, mentry)
20
20
  return [row(mentry, path, "never refreshed")] unless File.exist?(path)
21
21
 
22
22
  meta = Entry.for_format(mentry.format).parse(File.binread(path), path: path)["_meta"]
@@ -8,7 +8,7 @@ module Textus
8
8
  end
9
9
 
10
10
  def call(prefix: nil, zone: nil)
11
- @manifest.entries
11
+ @manifest.data.entries
12
12
  .select { |m| entry_matches?(m, prefix: prefix, zone: zone) }
13
13
  .flat_map { |m| @generator_check.rows_for(m) + @intake_check.rows_for(m) }
14
14
  end