textus 0.22.0 → 0.29.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 (186) hide show
  1. checksums.yaml +4 -4
  2. data/ARCHITECTURE.md +195 -48
  3. data/CHANGELOG.md +178 -0
  4. data/README.md +55 -13
  5. data/SPEC.md +79 -42
  6. data/docs/conventions.md +10 -0
  7. data/lib/textus/boot.rb +31 -29
  8. data/lib/textus/builder/pipeline.rb +13 -12
  9. data/lib/textus/call.rb +28 -0
  10. data/lib/textus/cli/group/mcp.rb +9 -0
  11. data/lib/textus/cli/group/zone.rb +9 -0
  12. data/lib/textus/cli/verb/accept.rb +1 -1
  13. data/lib/textus/cli/verb/audit.rb +2 -2
  14. data/lib/textus/cli/verb/blame.rb +1 -1
  15. data/lib/textus/cli/verb/boot.rb +1 -1
  16. data/lib/textus/cli/verb/build.rb +3 -3
  17. data/lib/textus/cli/verb/delete.rb +1 -1
  18. data/lib/textus/cli/verb/deps.rb +1 -1
  19. data/lib/textus/cli/verb/doctor.rb +1 -1
  20. data/lib/textus/cli/verb/freshness.rb +1 -1
  21. data/lib/textus/cli/verb/get.rb +1 -1
  22. data/lib/textus/cli/verb/hook_run.rb +3 -4
  23. data/lib/textus/cli/verb/hooks.rb +11 -14
  24. data/lib/textus/cli/verb/key_delete.rb +24 -0
  25. data/lib/textus/cli/verb/list.rb +1 -1
  26. data/lib/textus/cli/verb/mcp_serve.rb +17 -0
  27. data/lib/textus/cli/verb/migrate.rb +18 -0
  28. data/lib/textus/cli/verb/mv.rb +11 -3
  29. data/lib/textus/cli/verb/published.rb +1 -1
  30. data/lib/textus/cli/verb/pulse.rb +1 -1
  31. data/lib/textus/cli/verb/put.rb +8 -6
  32. data/lib/textus/cli/verb/rdeps.rb +1 -1
  33. data/lib/textus/cli/verb/refresh.rb +1 -1
  34. data/lib/textus/cli/verb/refresh_stale.rb +1 -1
  35. data/lib/textus/cli/verb/reject.rb +1 -1
  36. data/lib/textus/cli/verb/rule_explain.rb +1 -1
  37. data/lib/textus/cli/verb/rule_lint.rb +18 -0
  38. data/lib/textus/cli/verb/schema.rb +1 -1
  39. data/lib/textus/cli/verb/uid.rb +1 -1
  40. data/lib/textus/cli/verb/where.rb +1 -1
  41. data/lib/textus/cli/verb/zone_mv.rb +19 -0
  42. data/lib/textus/cli/verb.rb +7 -7
  43. data/lib/textus/cli.rb +0 -7
  44. data/lib/textus/container.rb +23 -0
  45. data/lib/textus/dispatcher.rb +49 -0
  46. data/lib/textus/doctor/check/audit_log.rb +2 -2
  47. data/lib/textus/doctor/check/handler_allowlist.rb +2 -2
  48. data/lib/textus/doctor/check/hooks.rb +4 -3
  49. data/lib/textus/doctor/check/illegal_keys.rb +2 -2
  50. data/lib/textus/doctor/check/intake_registration.rb +2 -2
  51. data/lib/textus/doctor/check/manifest_files.rb +2 -2
  52. data/lib/textus/doctor/check/protocol_version.rb +2 -2
  53. data/lib/textus/doctor/check/refresh_locks.rb +2 -2
  54. data/lib/textus/doctor/check/rule_ambiguity.rb +2 -2
  55. data/lib/textus/doctor/check/schema_parse_error.rb +1 -1
  56. data/lib/textus/doctor/check/schema_violations.rb +1 -1
  57. data/lib/textus/doctor/check/schemas.rb +2 -2
  58. data/lib/textus/doctor/check/sentinels.rb +11 -9
  59. data/lib/textus/doctor/check/templates.rb +2 -2
  60. data/lib/textus/doctor/check/unowned_schema_fields.rb +1 -1
  61. data/lib/textus/doctor/check.rb +12 -3
  62. data/lib/textus/doctor.rb +24 -27
  63. data/lib/textus/domain/authorizer.rb +6 -6
  64. data/lib/textus/{application → domain}/policy/predicates/accept_authority_signed.rb +2 -2
  65. data/lib/textus/{application → domain}/policy/predicates/schema_valid.rb +1 -1
  66. data/lib/textus/{application → domain}/policy/promotion.rb +1 -1
  67. data/lib/textus/domain/sentinel.rb +9 -65
  68. data/lib/textus/domain/staleness/generator_check.rb +46 -26
  69. data/lib/textus/domain/staleness/intake_check.rb +20 -12
  70. data/lib/textus/domain/staleness.rb +4 -4
  71. data/lib/textus/envelope/io/reader.rb +44 -0
  72. data/lib/textus/{application/writes/envelope_io.rb → envelope/io/writer.rb} +32 -58
  73. data/lib/textus/hooks/builtin.rb +14 -14
  74. data/lib/textus/hooks/context.rb +30 -13
  75. data/lib/textus/hooks/error_log.rb +32 -0
  76. data/lib/textus/hooks/{bus.rb → event_bus.rb} +41 -53
  77. data/lib/textus/hooks/loader.rb +29 -3
  78. data/lib/textus/hooks/rpc_registry.rb +77 -0
  79. data/lib/textus/key/path.rb +7 -3
  80. data/lib/textus/maintenance/key_delete_prefix.rb +36 -0
  81. data/lib/textus/maintenance/key_mv_prefix.rb +46 -0
  82. data/lib/textus/maintenance/migrate.rb +51 -0
  83. data/lib/textus/maintenance/rule_lint.rb +56 -0
  84. data/lib/textus/maintenance/zone_mv.rb +51 -0
  85. data/lib/textus/maintenance.rb +15 -0
  86. data/lib/textus/manifest/data.rb +79 -0
  87. data/lib/textus/manifest/entry/base.rb +38 -18
  88. data/lib/textus/manifest/entry/derived.rb +8 -9
  89. data/lib/textus/manifest/entry/nested.rb +7 -9
  90. data/lib/textus/manifest/entry/parser.rb +2 -2
  91. data/lib/textus/manifest/entry/validators/events.rb +2 -2
  92. data/lib/textus/manifest/entry/validators/format_matrix.rb +2 -2
  93. data/lib/textus/manifest/entry/validators/index_filename.rb +1 -1
  94. data/lib/textus/manifest/entry/validators/inject_boot.rb +4 -2
  95. data/lib/textus/manifest/entry/validators/publish_each.rb +1 -1
  96. data/lib/textus/manifest/entry/validators.rb +2 -2
  97. data/lib/textus/manifest/entry.rb +0 -5
  98. data/lib/textus/manifest/policy.rb +48 -0
  99. data/lib/textus/manifest/resolver.rb +14 -14
  100. data/lib/textus/manifest/rules.rb +1 -1
  101. data/lib/textus/manifest.rb +47 -110
  102. data/lib/textus/mcp/errors.rb +32 -0
  103. data/lib/textus/mcp/server.rb +126 -0
  104. data/lib/textus/mcp/session.rb +40 -0
  105. data/lib/textus/mcp/tool_schemas.rb +71 -0
  106. data/lib/textus/mcp/tools.rb +129 -0
  107. data/lib/textus/mcp.rb +6 -0
  108. data/lib/textus/{infra → ports}/audit_log.rb +1 -1
  109. data/lib/textus/{infra → ports}/audit_subscriber.rb +7 -8
  110. data/lib/textus/{infra → ports}/build_lock.rb +1 -1
  111. data/lib/textus/{infra → ports}/clock.rb +1 -1
  112. data/lib/textus/{infra → ports}/publisher.rb +6 -6
  113. data/lib/textus/{infra → ports}/refresh/detached.rb +3 -3
  114. data/lib/textus/{infra → ports}/refresh/lock.rb +1 -1
  115. data/lib/textus/ports/sentinel_store.rb +67 -0
  116. data/lib/textus/ports/storage/file_stat.rb +19 -0
  117. data/lib/textus/{infra → ports}/storage/file_store.rb +1 -1
  118. data/lib/textus/projection.rb +91 -0
  119. data/lib/textus/read/audit.rb +111 -0
  120. data/lib/textus/read/blame.rb +81 -0
  121. data/lib/textus/read/boot.rb +18 -0
  122. data/lib/textus/read/deps.rb +24 -0
  123. data/lib/textus/read/doctor.rb +19 -0
  124. data/lib/textus/read/freshness.rb +101 -0
  125. data/lib/textus/read/get.rb +66 -0
  126. data/lib/textus/read/get_or_refresh.rb +69 -0
  127. data/lib/textus/read/list.rb +15 -0
  128. data/lib/textus/read/policy_explain.rb +37 -0
  129. data/lib/textus/read/published.rb +15 -0
  130. data/lib/textus/read/pulse.rb +89 -0
  131. data/lib/textus/read/rdeps.rb +25 -0
  132. data/lib/textus/read/schema_envelope.rb +16 -0
  133. data/lib/textus/read/stale.rb +17 -0
  134. data/lib/textus/read/uid.rb +20 -0
  135. data/lib/textus/read/validate_all.rb +22 -0
  136. data/lib/textus/read/validator.rb +84 -0
  137. data/lib/textus/read/where.rb +16 -0
  138. data/lib/textus/role_scope.rb +49 -0
  139. data/lib/textus/schema/tools.rb +14 -10
  140. data/lib/textus/store.rb +25 -11
  141. data/lib/textus/version.rb +1 -1
  142. data/lib/textus/write/accept.rb +86 -0
  143. data/lib/textus/write/authority_gate.rb +24 -0
  144. data/lib/textus/write/delete.rb +54 -0
  145. data/lib/textus/write/materializer.rb +48 -0
  146. data/lib/textus/write/mv.rb +123 -0
  147. data/lib/textus/write/publish.rb +66 -0
  148. data/lib/textus/write/put.rb +59 -0
  149. data/lib/textus/write/refresh_all.rb +44 -0
  150. data/lib/textus/write/refresh_orchestrator.rb +102 -0
  151. data/lib/textus/write/refresh_worker.rb +138 -0
  152. data/lib/textus/write/reject.rb +54 -0
  153. data/lib/textus.rb +7 -1
  154. metadata +75 -46
  155. data/lib/textus/application/context.rb +0 -34
  156. data/lib/textus/application/projection.rb +0 -91
  157. data/lib/textus/application/reads/audit.rb +0 -94
  158. data/lib/textus/application/reads/blame.rb +0 -82
  159. data/lib/textus/application/reads/deps.rb +0 -26
  160. data/lib/textus/application/reads/freshness.rb +0 -88
  161. data/lib/textus/application/reads/get.rb +0 -67
  162. data/lib/textus/application/reads/get_or_refresh.rb +0 -51
  163. data/lib/textus/application/reads/list.rb +0 -17
  164. data/lib/textus/application/reads/policy_explain.rb +0 -39
  165. data/lib/textus/application/reads/published.rb +0 -17
  166. data/lib/textus/application/reads/pulse.rb +0 -63
  167. data/lib/textus/application/reads/rdeps.rb +0 -27
  168. data/lib/textus/application/reads/schema_envelope.rb +0 -18
  169. data/lib/textus/application/reads/stale.rb +0 -15
  170. data/lib/textus/application/reads/uid.rb +0 -23
  171. data/lib/textus/application/reads/validate_all.rb +0 -24
  172. data/lib/textus/application/reads/validator.rb +0 -86
  173. data/lib/textus/application/reads/where.rb +0 -18
  174. data/lib/textus/application/refresh/all.rb +0 -52
  175. data/lib/textus/application/refresh/orchestrator.rb +0 -78
  176. data/lib/textus/application/refresh/worker.rb +0 -116
  177. data/lib/textus/application/writes/accept.rb +0 -89
  178. data/lib/textus/application/writes/authority_gate.rb +0 -26
  179. data/lib/textus/application/writes/delete.rb +0 -33
  180. data/lib/textus/application/writes/materializer.rb +0 -50
  181. data/lib/textus/application/writes/mv.rb +0 -105
  182. data/lib/textus/application/writes/publish.rb +0 -81
  183. data/lib/textus/application/writes/put.rb +0 -37
  184. data/lib/textus/application/writes/reject.rb +0 -50
  185. data/lib/textus/infra/event_bus.rb +0 -27
  186. data/lib/textus/operations.rb +0 -176
@@ -1,50 +0,0 @@
1
- require_relative "authority_gate"
2
-
3
- module Textus
4
- module Application
5
- module Writes
6
- class Reject
7
- include AuthorityGate
8
-
9
- def initialize(ctx:, manifest:, file_store:, envelope_io:, bus:, authorizer:, hook_context:) # rubocop:disable Metrics/ParameterLists
10
- @ctx = ctx
11
- @manifest = manifest
12
- @file_store = file_store
13
- @envelope_io = envelope_io
14
- @bus = bus
15
- @authorizer = authorizer
16
- @hook_context = hook_context
17
- end
18
-
19
- def call(pending_key)
20
- assert_accept_authority!("reject")
21
-
22
- mentry = @manifest.resolver.resolve(pending_key).entry
23
- unless mentry.in_proposal_zone?
24
- raise ProposalError.new("reject: '#{pending_key}' is not in a proposal zone (zone=#{mentry.zone})")
25
- end
26
-
27
- env = Textus::Application::Reads::Get.new(
28
- ctx: @ctx, manifest: @manifest, file_store: @file_store,
29
- ).call(pending_key)
30
- proposal = env.meta&.dig("proposal") or
31
- raise ProposalError.new("entry has no proposal block: #{pending_key}")
32
- target_key = proposal["target_key"] or
33
- raise ProposalError.new("proposal missing target_key")
34
-
35
- Textus::Application::Writes::Delete.new(
36
- ctx: @ctx, manifest: @manifest, envelope_io: @envelope_io,
37
- bus: @bus, authorizer: @authorizer, hook_context: @hook_context
38
- ).call(pending_key, suppress_events: true)
39
-
40
- @bus.publish(:proposal_rejected,
41
- ctx: @hook_context,
42
- key: pending_key,
43
- target_key: target_key)
44
-
45
- { "protocol" => PROTOCOL, "rejected" => pending_key, "target_key" => target_key }
46
- end
47
- end
48
- end
49
- end
50
- end
@@ -1,27 +0,0 @@
1
- module Textus
2
- module Infra
3
- class EventBus
4
- def initialize(bus:)
5
- @bus = bus
6
- end
7
-
8
- def publish(event, **payload)
9
- @bus.pubsub_handlers(event).each do |entry|
10
- next unless entry[:keys].nil? || matches?(entry[:keys], payload[:key])
11
-
12
- entry[:callable].call(**payload)
13
- rescue StandardError => e
14
- warn "[textus] pub-sub handler #{entry[:name].inspect} for #{event.inspect} failed: #{e.class}: #{e.message}"
15
- end
16
- end
17
-
18
- private
19
-
20
- def matches?(globs, key)
21
- return true if key.nil?
22
-
23
- Array(globs).any? { |g| File.fnmatch?(g, key.to_s, File::FNM_PATHNAME) }
24
- end
25
- end
26
- end
27
- end
@@ -1,176 +0,0 @@
1
- module Textus
2
- # Single canonical entrypoint for invoking application use-cases against a
3
- # store. Public surface is flat — one method per use case:
4
- #
5
- # ops = Textus::Operations.for(store, role: "agent")
6
- # ops.put(key, body: "...")
7
- # ops.get(key) # pure read
8
- # ops.get_or_refresh(key) # read + refresh-on-stale
9
- # ops.refresh(key) # synchronous worker refresh
10
- # ops.refresh_all(prefix: ..., zone: ...)
11
- class Operations
12
- def self.for(store, role: Role::DEFAULT, correlation_id: nil, dry_run: false)
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,
21
- store: store,
22
- )
23
- end
24
-
25
- attr_reader :ctx, :store
26
-
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)
38
- end
39
- # rubocop:enable Metrics/ParameterLists
40
-
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
53
-
54
- # writes
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
96
-
97
- # reads
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, audit_log: @audit_log).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 pulse(...)
124
- Application::Reads::Pulse.new(
125
- ctx: @ctx, manifest: @manifest, file_store: @file_store,
126
- audit_log: @audit_log, root: @root, store: @store
127
- ).call(...)
128
- end
129
-
130
- def validate_all(...)
131
- Application::Reads::ValidateAll.new(
132
- ctx: @ctx, manifest: @manifest, file_store: @file_store, schemas: @schemas, audit_log: @audit_log,
133
- ).call(...)
134
- end
135
-
136
- # refresh
137
- def refresh(key) = refresh_worker.run(key)
138
-
139
- def refresh_all(**)
140
- Application::Refresh::All.new(
141
- ctx: @ctx, manifest: @manifest, envelope_io: envelope_io, bus: @bus,
142
- store: @store, authorizer: @authorizer, hook_context: hook_context
143
- ).call(**)
144
- end
145
-
146
- private
147
-
148
- def envelope_io
149
- @envelope_io ||= Application::Writes::EnvelopeIO.new(
150
- file_store: @file_store,
151
- manifest: @manifest,
152
- schemas: @schemas,
153
- audit_log: @audit_log,
154
- ctx: @ctx,
155
- )
156
- end
157
-
158
- def refresh_worker
159
- @refresh_worker ||= Application::Refresh::Worker.new(
160
- ctx: @ctx, manifest: @manifest, envelope_io: envelope_io, bus: @bus,
161
- store: @store, authorizer: @authorizer, hook_context: hook_context
162
- )
163
- end
164
-
165
- def orchestrator
166
- @orchestrator ||= Application::Refresh::Orchestrator.new(
167
- worker: refresh_worker,
168
- store_root: @root,
169
- bus: @bus,
170
- store: @store,
171
- ctx: @ctx,
172
- hook_context: hook_context,
173
- )
174
- end
175
- end
176
- end