textus 0.54.2 → 0.55.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 (176) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +37 -0
  3. data/README.md +8 -1
  4. data/SPEC.md +27 -0
  5. data/docs/architecture/README.md +20 -8
  6. data/docs/reference/conventions.md +1 -1
  7. data/exe/textus +1 -1
  8. data/lib/textus/action/accept.rb +23 -21
  9. data/lib/textus/action/audit.rb +24 -61
  10. data/lib/textus/action/base.rb +9 -9
  11. data/lib/textus/action/blame.rb +18 -36
  12. data/lib/textus/action/boot.rb +2 -4
  13. data/lib/textus/action/data_mv.rb +20 -31
  14. data/lib/textus/action/deps.rb +3 -18
  15. data/lib/textus/action/doctor.rb +2 -9
  16. data/lib/textus/action/drain.rb +11 -19
  17. data/lib/textus/action/enqueue.rb +14 -30
  18. data/lib/textus/action/get.rb +12 -56
  19. data/lib/textus/action/ingest.rb +74 -78
  20. data/lib/textus/action/jobs.rb +6 -15
  21. data/lib/textus/action/key_delete.rb +6 -16
  22. data/lib/textus/action/key_delete_prefix.rb +8 -17
  23. data/lib/textus/action/key_mv.rb +54 -61
  24. data/lib/textus/action/key_mv_prefix.rb +13 -22
  25. data/lib/textus/action/list.rb +7 -21
  26. data/lib/textus/action/propose.rb +16 -26
  27. data/lib/textus/action/published.rb +3 -5
  28. data/lib/textus/action/pulse.rb +19 -26
  29. data/lib/textus/action/put.rb +15 -29
  30. data/lib/textus/action/rdeps.rb +3 -18
  31. data/lib/textus/action/reject.rb +12 -21
  32. data/lib/textus/action/rule_explain.rb +12 -22
  33. data/lib/textus/action/rule_lint.rb +10 -16
  34. data/lib/textus/action/rule_list.rb +5 -9
  35. data/lib/textus/action/schema_envelope.rb +3 -10
  36. data/lib/textus/action/uid.rb +3 -17
  37. data/lib/textus/action/where.rb +3 -18
  38. data/lib/textus/boot.rb +7 -15
  39. data/lib/textus/contract/arg.rb +10 -0
  40. data/lib/textus/contract/dsl.rb +88 -0
  41. data/lib/textus/contract/spec.rb +25 -0
  42. data/lib/textus/contract.rb +0 -162
  43. data/lib/textus/doctor/check/audit_log.rb +2 -2
  44. data/lib/textus/doctor/check/generator_drift.rb +2 -2
  45. data/lib/textus/doctor/check/orphaned_publish_targets.rb +3 -3
  46. data/lib/textus/doctor/check/protocol_version.rb +2 -2
  47. data/lib/textus/doctor/check/raw_asset_paths.rb +1 -1
  48. data/lib/textus/doctor/check/schema_parse_error.rb +1 -1
  49. data/lib/textus/doctor/check/schema_violations.rb +2 -2
  50. data/lib/textus/doctor/check/schemas.rb +1 -1
  51. data/lib/textus/doctor/check/sentinels.rb +4 -4
  52. data/lib/textus/doctor/check/templates.rb +1 -1
  53. data/lib/textus/doctor/check/unowned_schema_fields.rb +1 -1
  54. data/lib/textus/doctor/check.rb +4 -7
  55. data/lib/textus/doctor.rb +1 -1
  56. data/lib/textus/errors.rb +6 -0
  57. data/lib/textus/format/base.rb +0 -4
  58. data/lib/textus/format/json.rb +5 -6
  59. data/lib/textus/format/markdown.rb +5 -6
  60. data/lib/textus/format/shared.rb +17 -0
  61. data/lib/textus/format/text.rb +5 -4
  62. data/lib/textus/format/yaml.rb +30 -6
  63. data/lib/textus/format.rb +6 -0
  64. data/lib/textus/gate/auth.rb +2 -17
  65. data/lib/textus/gate/binder.rb +50 -0
  66. data/lib/textus/gate.rb +64 -88
  67. data/lib/textus/init.rb +2 -4
  68. data/lib/textus/jobs.rb +3 -9
  69. data/lib/textus/manifest/capabilities.rb +3 -3
  70. data/lib/textus/manifest/entry/base.rb +1 -1
  71. data/lib/textus/manifest/entry/publish/subtree_mirror.rb +2 -2
  72. data/lib/textus/manifest/entry/publish/to_paths.rb +1 -1
  73. data/lib/textus/manifest/schema/semantics/cross_field.rb +53 -0
  74. data/lib/textus/manifest/schema/semantics/invariants.rb +125 -0
  75. data/lib/textus/manifest/schema/semantics/migration.rb +83 -0
  76. data/lib/textus/manifest/schema/semantics.rb +11 -216
  77. data/lib/textus/meta.rb +54 -0
  78. data/lib/textus/{ports → port}/audit_log.rb +44 -4
  79. data/lib/textus/{ports → port}/build_lock.rb +2 -2
  80. data/lib/textus/{ports → port}/clock.rb +1 -1
  81. data/lib/textus/{ports → port}/publisher.rb +5 -5
  82. data/lib/textus/{ports → port}/sentinel_store.rb +3 -3
  83. data/lib/textus/{ports → port}/storage/file_stat.rb +1 -1
  84. data/lib/textus/{ports → port}/storage/file_store.rb +2 -2
  85. data/lib/textus/port/store.rb +93 -0
  86. data/lib/textus/{ports → port}/watcher_lock.rb +3 -3
  87. data/lib/textus/produce/engine.rb +1 -1
  88. data/lib/textus/schema/tools.rb +11 -7
  89. data/lib/textus/store/compositor.rb +34 -0
  90. data/lib/textus/store/container.rb +43 -0
  91. data/lib/textus/store/cursor.rb +26 -0
  92. data/lib/textus/store/envelope/reader.rb +43 -0
  93. data/lib/textus/store/envelope/writer.rb +195 -0
  94. data/lib/textus/store/geometry.rb +81 -0
  95. data/lib/textus/store/index/builder.rb +74 -0
  96. data/lib/textus/store/index/lookup.rb +60 -0
  97. data/lib/textus/store/jobs/base.rb +13 -0
  98. data/lib/textus/store/jobs/index.rb +15 -0
  99. data/lib/textus/store/jobs/materialize.rb +15 -0
  100. data/lib/textus/store/jobs/plan.rb +11 -0
  101. data/lib/textus/store/jobs/planner.rb +104 -0
  102. data/lib/textus/store/jobs/queue.rb +154 -0
  103. data/lib/textus/store/jobs/registry.rb +19 -0
  104. data/lib/textus/store/jobs/retention.rb +50 -0
  105. data/lib/textus/store/jobs/sweep.rb +21 -0
  106. data/lib/textus/store/jobs/worker.rb +64 -0
  107. data/lib/textus/store/session.rb +37 -0
  108. data/lib/textus/store.rb +21 -13
  109. data/lib/textus/{surfaces → surface}/cli/group/data.rb +1 -1
  110. data/lib/textus/{surfaces → surface}/cli/group/key.rb +1 -1
  111. data/lib/textus/{surfaces → surface}/cli/group/mcp.rb +1 -1
  112. data/lib/textus/{surfaces → surface}/cli/group/rule.rb +1 -1
  113. data/lib/textus/{surfaces → surface}/cli/group/schema.rb +1 -1
  114. data/lib/textus/{surfaces → surface}/cli/group.rb +2 -2
  115. data/lib/textus/{surfaces → surface}/cli/runner.rb +10 -63
  116. data/lib/textus/surface/cli/sources.rb +41 -0
  117. data/lib/textus/{surfaces → surface}/cli/verb/doctor.rb +4 -6
  118. data/lib/textus/{surfaces → surface}/cli/verb/get.rb +4 -4
  119. data/lib/textus/{surfaces → surface}/cli/verb/init.rb +1 -1
  120. data/lib/textus/{surfaces → surface}/cli/verb/mcp_serve.rb +3 -3
  121. data/lib/textus/{surfaces → surface}/cli/verb/put.rb +6 -11
  122. data/lib/textus/{surfaces → surface}/cli/verb/schema_diff.rb +1 -1
  123. data/lib/textus/{surfaces → surface}/cli/verb/schema_init.rb +1 -1
  124. data/lib/textus/{surfaces → surface}/cli/verb/schema_migrate.rb +1 -1
  125. data/lib/textus/{surfaces → surface}/cli/verb/watch.rb +2 -2
  126. data/lib/textus/{surfaces → surface}/cli/verb.rb +3 -8
  127. data/lib/textus/{surfaces → surface}/cli.rb +1 -1
  128. data/lib/textus/{surfaces → surface}/mcp/catalog.rb +9 -26
  129. data/lib/textus/{surfaces → surface}/mcp/errors.rb +1 -1
  130. data/lib/textus/{surfaces → surface}/mcp/server.rb +5 -5
  131. data/lib/textus/{surfaces → surface}/mcp.rb +2 -2
  132. data/lib/textus/surface/projector.rb +27 -0
  133. data/lib/textus/{surfaces → surface}/role_scope.rb +1 -1
  134. data/lib/textus/{surfaces → surface}/watcher.rb +8 -8
  135. data/lib/textus/value/call.rb +30 -0
  136. data/lib/textus/value/command.rb +16 -0
  137. data/lib/textus/value/envelope.rb +89 -0
  138. data/lib/textus/value/etag.rb +39 -0
  139. data/lib/textus/value/result.rb +26 -0
  140. data/lib/textus/value/role.rb +38 -0
  141. data/lib/textus/value/types.rb +13 -0
  142. data/lib/textus/{uid.rb → value/uid.rb} +9 -7
  143. data/lib/textus/version.rb +1 -1
  144. data/lib/textus/workflow/loader.rb +4 -4
  145. data/lib/textus/workflow/runner.rb +4 -18
  146. data/lib/textus.rb +9 -10
  147. metadata +100 -63
  148. data/lib/textus/action/write_verb.rb +0 -44
  149. data/lib/textus/call.rb +0 -28
  150. data/lib/textus/command.rb +0 -41
  151. data/lib/textus/container.rb +0 -26
  152. data/lib/textus/contract/around.rb +0 -29
  153. data/lib/textus/contract/binder.rb +0 -88
  154. data/lib/textus/contract/resources/build_lock.rb +0 -17
  155. data/lib/textus/contract/resources/cursor.rb +0 -26
  156. data/lib/textus/contract/sources.rb +0 -39
  157. data/lib/textus/contract/view.rb +0 -15
  158. data/lib/textus/cursor_store.rb +0 -24
  159. data/lib/textus/envelope/reader.rb +0 -46
  160. data/lib/textus/envelope/writer.rb +0 -209
  161. data/lib/textus/envelope.rb +0 -79
  162. data/lib/textus/etag.rb +0 -36
  163. data/lib/textus/jobs/base.rb +0 -23
  164. data/lib/textus/jobs/materialize.rb +0 -20
  165. data/lib/textus/jobs/plan.rb +0 -9
  166. data/lib/textus/jobs/planner.rb +0 -101
  167. data/lib/textus/jobs/retention.rb +0 -48
  168. data/lib/textus/jobs/sweep.rb +0 -27
  169. data/lib/textus/jobs/worker.rb +0 -67
  170. data/lib/textus/layout.rb +0 -91
  171. data/lib/textus/ports/job_store/job.rb +0 -65
  172. data/lib/textus/ports/job_store.rb +0 -123
  173. data/lib/textus/ports/raw_index.rb +0 -61
  174. data/lib/textus/role.rb +0 -36
  175. data/lib/textus/session.rb +0 -35
  176. data/lib/textus/types.rb +0 -15
@@ -57,12 +57,12 @@ module Textus
57
57
  end
58
58
 
59
59
  def built_in_publish(key, data, ctx)
60
- normalized = normalize(data, ctx.entry.format)
60
+ normalized = Textus::Format.data_to_payload(data, ctx.entry.format)
61
61
  Gate::Auth.new(@container).check_action!(action: :converge, actor: @call.role, key: key)
62
- Envelope::Writer.from(container: @container, call: @call).put(
62
+ Textus::Store::Envelope::Writer.from(container: @container, call: @call).put(
63
63
  key,
64
64
  mentry: ctx.entry,
65
- payload: Envelope::Writer::Payload.new(**normalized),
65
+ payload: Textus::Store::Envelope::Writer::Payload.new(**normalized),
66
66
  )
67
67
  publish_external(key, ctx)
68
68
  end
@@ -74,26 +74,12 @@ module Textus
74
74
  entry_path = @container.manifest.resolver.resolve(key).path
75
75
  return unless entry.publish_tree || File.exist?(entry_path)
76
76
 
77
- reader = Textus::Envelope::Reader.from(container: @container)
77
+ reader = Textus::Store::Envelope::Reader.from(container: @container)
78
78
  pctx = Textus::Manifest::Entry::Base::PublishContext.new(
79
79
  container: @container, call: @call, reader: reader.method(:read),
80
80
  )
81
81
  entry.publish_via(pctx)
82
82
  end
83
-
84
- def normalize(data, format)
85
- return { meta: {}, body: "", content: nil } if data.nil?
86
-
87
- data = data.transform_keys(&:to_s) if data.is_a?(Hash)
88
- case format.to_s
89
- when "markdown", "text"
90
- { meta: data["_meta"] || {}, body: (data["body"] || "").to_s, content: nil }
91
- when "json", "yaml"
92
- { meta: data["_meta"] || {}, body: nil, content: data["content"] || data }
93
- else
94
- { meta: {}, body: data.to_s, content: nil }
95
- end
96
- end
97
83
  end
98
84
  end
99
85
  end
data/lib/textus.rb CHANGED
@@ -1,8 +1,9 @@
1
1
  require "zeitwerk"
2
+ require "dry-monads"
2
3
  require_relative "textus/version"
3
4
  require_relative "textus/errors"
4
- require_relative "textus/surfaces/mcp"
5
- require_relative "textus/surfaces/mcp/errors"
5
+ require_relative "textus/surface/mcp"
6
+ require_relative "textus/surface/mcp/errors"
6
7
 
7
8
  loader = Zeitwerk::Loader.for_gem
8
9
  loader.inflector.inflect(
@@ -14,8 +15,8 @@ loader.inflector.inflect(
14
15
  "dsl" => "DSL",
15
16
  )
16
17
  loader.ignore(File.expand_path("textus/errors.rb", __dir__))
17
- loader.ignore(File.expand_path("textus/surfaces/mcp.rb", __dir__))
18
- loader.ignore(File.expand_path("textus/surfaces/mcp/errors.rb", __dir__))
18
+ loader.ignore(File.expand_path("textus/surface/mcp.rb", __dir__))
19
+ loader.ignore(File.expand_path("textus/surface/mcp/errors.rb", __dir__))
19
20
  loader.ignore(File.expand_path("textus/workflow/errors.rb", __dir__))
20
21
  # Scaffold sources copied verbatim into user stores by `textus init`. They are
21
22
  # templates for user-owned step classes, not gem constants — Zeitwerk must not
@@ -64,15 +65,14 @@ Textus::Boot::CLI_VERBS = Textus::Boot.build_cli_verbs.freeze
64
65
 
65
66
  # Dynamic verb methods on Store (deferred after VERBS is defined).
66
67
  Textus::Action::VERBS.each_key do |verb|
67
- Textus::Store.define_method(verb) do |*args, role: Textus::Role::DEFAULT, **kwargs|
68
+ Textus::Store.define_method(verb) do |*args, role: Textus::Value::Role::DEFAULT, **kwargs|
68
69
  as(role).public_send(verb, *args, **kwargs)
69
70
  end
70
71
 
71
- Textus::Surfaces::RoleScope.define_method(verb) do |*args, **kwargs|
72
+ Textus::Surface::RoleScope.define_method(verb) do |*args, **kwargs|
72
73
  klass = Textus::Action::VERBS[verb]
73
- cmd_class = Textus::Gate::VERB_COMMAND[verb]
74
74
  inputs = if klass.respond_to?(:contract?) && klass.contract?
75
- Textus::Contract::Binder.inputs_from_ordered(klass.contract, args, kwargs)
75
+ Textus::Gate::Binder.inputs_from_ordered(klass.contract, args, kwargs)
76
76
  else
77
77
  kwargs.transform_keys(&:to_sym)
78
78
  end
@@ -84,8 +84,7 @@ Textus::Action::VERBS.each_key do |verb|
84
84
  @role
85
85
  end
86
86
 
87
- filled = cmd_class.members.to_h { |m| [m, inputs.merge(role: role_value)[m]] }
88
- @container.gate.dispatch(cmd_class.new(**filled), correlation_id: @correlation_id)
87
+ @container.gate.dispatch(spec: klass.contract, inputs: inputs, role: role_value, correlation_id: @correlation_id)
89
88
  end
90
89
  end
91
90
 
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.54.2
4
+ version: 0.55.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Patrick
@@ -23,6 +23,20 @@ dependencies:
23
23
  - - ">="
24
24
  - !ruby/object:Gem::Version
25
25
  version: '3.0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: dry-monads
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '1.6'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '1.6'
26
40
  - !ruby/object:Gem::Dependency
27
41
  name: dry-schema
28
42
  requirement: !ruby/object:Gem::Requirement
@@ -93,6 +107,20 @@ dependencies:
93
107
  - - ">="
94
108
  - !ruby/object:Gem::Version
95
109
  version: '3.2'
110
+ - !ruby/object:Gem::Dependency
111
+ name: sqlite3
112
+ requirement: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - "~>"
115
+ - !ruby/object:Gem::Version
116
+ version: '2.0'
117
+ type: :runtime
118
+ prerelease: false
119
+ version_requirements: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - "~>"
122
+ - !ruby/object:Gem::Version
123
+ version: '2.0'
96
124
  - !ruby/object:Gem::Dependency
97
125
  name: zeitwerk
98
126
  requirement: !ruby/object:Gem::Requirement
@@ -182,18 +210,11 @@ files:
182
210
  - lib/textus/action/schema_envelope.rb
183
211
  - lib/textus/action/uid.rb
184
212
  - lib/textus/action/where.rb
185
- - lib/textus/action/write_verb.rb
186
213
  - lib/textus/boot.rb
187
- - lib/textus/call.rb
188
- - lib/textus/command.rb
189
- - lib/textus/container.rb
190
214
  - lib/textus/contract.rb
191
- - lib/textus/contract/around.rb
192
- - lib/textus/contract/binder.rb
193
- - lib/textus/contract/resources/build_lock.rb
194
- - lib/textus/contract/resources/cursor.rb
195
- - lib/textus/contract/sources.rb
196
- - lib/textus/contract/view.rb
215
+ - lib/textus/contract/arg.rb
216
+ - lib/textus/contract/dsl.rb
217
+ - lib/textus/contract/spec.rb
197
218
  - lib/textus/core/duration.rb
198
219
  - lib/textus/core/freshness.rb
199
220
  - lib/textus/core/freshness/evaluator.rb
@@ -201,7 +222,6 @@ files:
201
222
  - lib/textus/core/retention.rb
202
223
  - lib/textus/core/retention/sweep.rb
203
224
  - lib/textus/core/sentinel.rb
204
- - lib/textus/cursor_store.rb
205
225
  - lib/textus/doctor.rb
206
226
  - lib/textus/doctor/check.rb
207
227
  - lib/textus/doctor/check/audit_log.rb
@@ -223,33 +243,23 @@ files:
223
243
  - lib/textus/doctor/check/templates.rb
224
244
  - lib/textus/doctor/check/unowned_schema_fields.rb
225
245
  - lib/textus/doctor/validator.rb
226
- - lib/textus/envelope.rb
227
- - lib/textus/envelope/reader.rb
228
- - lib/textus/envelope/writer.rb
229
246
  - lib/textus/errors.rb
230
- - lib/textus/etag.rb
231
247
  - lib/textus/format.rb
232
248
  - lib/textus/format/base.rb
233
249
  - lib/textus/format/json.rb
234
250
  - lib/textus/format/markdown.rb
251
+ - lib/textus/format/shared.rb
235
252
  - lib/textus/format/text.rb
236
253
  - lib/textus/format/yaml.rb
237
254
  - lib/textus/gate.rb
238
255
  - lib/textus/gate/auth.rb
256
+ - lib/textus/gate/binder.rb
239
257
  - lib/textus/init.rb
240
258
  - lib/textus/jobs.rb
241
- - lib/textus/jobs/base.rb
242
- - lib/textus/jobs/materialize.rb
243
- - lib/textus/jobs/plan.rb
244
- - lib/textus/jobs/planner.rb
245
- - lib/textus/jobs/retention.rb
246
- - lib/textus/jobs/sweep.rb
247
- - lib/textus/jobs/worker.rb
248
259
  - lib/textus/key/distance.rb
249
260
  - lib/textus/key/grammar.rb
250
261
  - lib/textus/key/matching.rb
251
262
  - lib/textus/key/path.rb
252
- - lib/textus/layout.rb
253
263
  - lib/textus/manifest.rb
254
264
  - lib/textus/manifest/capabilities.rb
255
265
  - lib/textus/manifest/data.rb
@@ -283,53 +293,80 @@ files:
283
293
  - lib/textus/manifest/schema/contract.rb
284
294
  - lib/textus/manifest/schema/keys.rb
285
295
  - lib/textus/manifest/schema/semantics.rb
296
+ - lib/textus/manifest/schema/semantics/cross_field.rb
297
+ - lib/textus/manifest/schema/semantics/invariants.rb
298
+ - lib/textus/manifest/schema/semantics/migration.rb
286
299
  - lib/textus/manifest/schema/validator.rb
287
300
  - lib/textus/manifest/schema/vocabulary.rb
288
- - lib/textus/ports/audit_log.rb
289
- - lib/textus/ports/build_lock.rb
290
- - lib/textus/ports/clock.rb
291
- - lib/textus/ports/job_store.rb
292
- - lib/textus/ports/job_store/job.rb
293
- - lib/textus/ports/publisher.rb
294
- - lib/textus/ports/raw_index.rb
295
- - lib/textus/ports/sentinel_store.rb
296
- - lib/textus/ports/storage/file_stat.rb
297
- - lib/textus/ports/storage/file_store.rb
298
- - lib/textus/ports/watcher_lock.rb
301
+ - lib/textus/meta.rb
302
+ - lib/textus/port/audit_log.rb
303
+ - lib/textus/port/build_lock.rb
304
+ - lib/textus/port/clock.rb
305
+ - lib/textus/port/publisher.rb
306
+ - lib/textus/port/sentinel_store.rb
307
+ - lib/textus/port/storage/file_stat.rb
308
+ - lib/textus/port/storage/file_store.rb
309
+ - lib/textus/port/store.rb
310
+ - lib/textus/port/watcher_lock.rb
299
311
  - lib/textus/produce/engine.rb
300
312
  - lib/textus/produce/render.rb
301
- - lib/textus/role.rb
302
313
  - lib/textus/schema.rb
303
314
  - lib/textus/schema/tools.rb
304
315
  - lib/textus/schemas.rb
305
- - lib/textus/session.rb
306
316
  - lib/textus/store.rb
307
- - lib/textus/surfaces/cli.rb
308
- - lib/textus/surfaces/cli/group.rb
309
- - lib/textus/surfaces/cli/group/data.rb
310
- - lib/textus/surfaces/cli/group/key.rb
311
- - lib/textus/surfaces/cli/group/mcp.rb
312
- - lib/textus/surfaces/cli/group/rule.rb
313
- - lib/textus/surfaces/cli/group/schema.rb
314
- - lib/textus/surfaces/cli/runner.rb
315
- - lib/textus/surfaces/cli/verb.rb
316
- - lib/textus/surfaces/cli/verb/doctor.rb
317
- - lib/textus/surfaces/cli/verb/get.rb
318
- - lib/textus/surfaces/cli/verb/init.rb
319
- - lib/textus/surfaces/cli/verb/mcp_serve.rb
320
- - lib/textus/surfaces/cli/verb/put.rb
321
- - lib/textus/surfaces/cli/verb/schema_diff.rb
322
- - lib/textus/surfaces/cli/verb/schema_init.rb
323
- - lib/textus/surfaces/cli/verb/schema_migrate.rb
324
- - lib/textus/surfaces/cli/verb/watch.rb
325
- - lib/textus/surfaces/mcp.rb
326
- - lib/textus/surfaces/mcp/catalog.rb
327
- - lib/textus/surfaces/mcp/errors.rb
328
- - lib/textus/surfaces/mcp/server.rb
329
- - lib/textus/surfaces/role_scope.rb
330
- - lib/textus/surfaces/watcher.rb
331
- - lib/textus/types.rb
332
- - lib/textus/uid.rb
317
+ - lib/textus/store/compositor.rb
318
+ - lib/textus/store/container.rb
319
+ - lib/textus/store/cursor.rb
320
+ - lib/textus/store/envelope/reader.rb
321
+ - lib/textus/store/envelope/writer.rb
322
+ - lib/textus/store/geometry.rb
323
+ - lib/textus/store/index/builder.rb
324
+ - lib/textus/store/index/lookup.rb
325
+ - lib/textus/store/jobs/base.rb
326
+ - lib/textus/store/jobs/index.rb
327
+ - lib/textus/store/jobs/materialize.rb
328
+ - lib/textus/store/jobs/plan.rb
329
+ - lib/textus/store/jobs/planner.rb
330
+ - lib/textus/store/jobs/queue.rb
331
+ - lib/textus/store/jobs/registry.rb
332
+ - lib/textus/store/jobs/retention.rb
333
+ - lib/textus/store/jobs/sweep.rb
334
+ - lib/textus/store/jobs/worker.rb
335
+ - lib/textus/store/session.rb
336
+ - lib/textus/surface/cli.rb
337
+ - lib/textus/surface/cli/group.rb
338
+ - lib/textus/surface/cli/group/data.rb
339
+ - lib/textus/surface/cli/group/key.rb
340
+ - lib/textus/surface/cli/group/mcp.rb
341
+ - lib/textus/surface/cli/group/rule.rb
342
+ - lib/textus/surface/cli/group/schema.rb
343
+ - lib/textus/surface/cli/runner.rb
344
+ - lib/textus/surface/cli/sources.rb
345
+ - lib/textus/surface/cli/verb.rb
346
+ - lib/textus/surface/cli/verb/doctor.rb
347
+ - lib/textus/surface/cli/verb/get.rb
348
+ - lib/textus/surface/cli/verb/init.rb
349
+ - lib/textus/surface/cli/verb/mcp_serve.rb
350
+ - lib/textus/surface/cli/verb/put.rb
351
+ - lib/textus/surface/cli/verb/schema_diff.rb
352
+ - lib/textus/surface/cli/verb/schema_init.rb
353
+ - lib/textus/surface/cli/verb/schema_migrate.rb
354
+ - lib/textus/surface/cli/verb/watch.rb
355
+ - lib/textus/surface/mcp.rb
356
+ - lib/textus/surface/mcp/catalog.rb
357
+ - lib/textus/surface/mcp/errors.rb
358
+ - lib/textus/surface/mcp/server.rb
359
+ - lib/textus/surface/projector.rb
360
+ - lib/textus/surface/role_scope.rb
361
+ - lib/textus/surface/watcher.rb
362
+ - lib/textus/value/call.rb
363
+ - lib/textus/value/command.rb
364
+ - lib/textus/value/envelope.rb
365
+ - lib/textus/value/etag.rb
366
+ - lib/textus/value/result.rb
367
+ - lib/textus/value/role.rb
368
+ - lib/textus/value/types.rb
369
+ - lib/textus/value/uid.rb
333
370
  - lib/textus/version.rb
334
371
  - lib/textus/workflow.rb
335
372
  - lib/textus/workflow/collector.rb
@@ -1,44 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Textus
4
- module Action
5
- class WriteVerb < Base
6
- private
7
-
8
- def auth(container)
9
- Textus::Gate::Auth.new(container)
10
- end
11
-
12
- def writer(container, call)
13
- Textus::Envelope::Writer.from(container: container, call: call)
14
- end
15
-
16
- def reader(container)
17
- Textus::Envelope::Reader.from(container: container)
18
- end
19
-
20
- def run_with_cascade(target_key, container:, call:)
21
- result = yield
22
- cascade_to_rdeps(target_key, container, call) if target_key
23
- result
24
- end
25
-
26
- def cascade_to_rdeps(key, container, call)
27
- rdeps = Textus::Action::Rdeps.new(key: key).call(container: container, call: call).fetch("rdeps", [])
28
- producible = rdeps.select { |dep_key| producible?(dep_key, container) }
29
- return if producible.empty?
30
-
31
- producible.each do |dep_key|
32
- Textus::Jobs::Materialize.new(key: dep_key).call(container:, call:)
33
- end
34
- end
35
-
36
- def producible?(key, container)
37
- entry = container.manifest.resolver.resolve(key).entry
38
- !entry.publish_tree.nil?
39
- rescue Textus::Error
40
- false
41
- end
42
- end
43
- end
44
- end
data/lib/textus/call.rb DELETED
@@ -1,28 +0,0 @@
1
- require "securerandom"
2
-
3
- module Textus
4
- # Immutable per-invocation value. Carries who is acting (role), the
5
- # request correlation id, the wall clock, and the dry_run flag — the
6
- # bits Use Cases need that are not part of the Container.
7
- Call = Data.define(:role, :correlation_id, :now, :dry_run) do
8
- def self.build(role:, correlation_id: nil, now: nil, dry_run: false)
9
- new(
10
- role: role.to_s,
11
- correlation_id: correlation_id || SecureRandom.uuid,
12
- now: now || Textus::Ports::Clock.new.now,
13
- dry_run: dry_run,
14
- )
15
- end
16
-
17
- def dry_run? = dry_run
18
-
19
- def with_role(new_role)
20
- self.class.new(
21
- role: new_role.to_s,
22
- correlation_id: correlation_id,
23
- now: now,
24
- dry_run: dry_run,
25
- )
26
- end
27
- end
28
- end
@@ -1,41 +0,0 @@
1
- module Textus
2
- module Command
3
- Get = Data.define(:key, :role)
4
- Put = Data.define(:key, :meta, :body, :content, :if_etag, :role)
5
- Propose = Data.define(:key, :meta, :body, :content, :role, :pending_key) do
6
- def initialize(key:, role:, meta: nil, body: nil, content: nil, pending_key: nil)
7
- super
8
- end
9
- end
10
- KeyDelete = Data.define(:key, :if_etag, :role)
11
- KeyMv = Data.define(:old_key, :new_key, :dry_run, :role)
12
- Accept = Data.define(:pending_key, :role)
13
- Reject = Data.define(:pending_key, :role)
14
- Enqueue = Data.define(:type, :args, :role)
15
- Ingest = Data.define(:kind, :slug, :url, :path, :zone, :label, :role) do
16
- def initialize(kind:, slug:, role:, url: nil, path: nil, zone: nil, label: nil)
17
- super
18
- end
19
- end
20
- List = Data.define(:prefix, :lane, :role)
21
- Where = Data.define(:key, :role)
22
- Uid = Data.define(:key, :role)
23
- Blame = Data.define(:key, :limit, :role)
24
- Audit = Data.define(:key, :lane, :verb, :since, :seq_since, :correlation_id, :limit, :role)
25
- Deps = Data.define(:key, :role)
26
- Rdeps = Data.define(:key, :role)
27
- Pulse = Data.define(:since, :role)
28
- RuleExplain = Data.define(:key, :detail, :role)
29
- RuleList = Data.define(:role)
30
- RuleLint = Data.define(:candidate_yaml, :role)
31
- Published = Data.define(:role)
32
- SchemaShow = Data.define(:key, :role)
33
- Doctor = Data.define(:checks, :role)
34
- Boot = Data.define(:role)
35
- Jobs = Data.define(:state, :action, :job_id, :role)
36
- DataMv = Data.define(:from, :to, :dry_run, :role)
37
- KeyMvPrefix = Data.define(:from_prefix, :to_prefix, :dry_run, :role)
38
- KeyDeletePrefix = Data.define(:prefix, :dry_run, :role)
39
- Drain = Data.define(:prefix, :lane, :role)
40
- end
41
- end
@@ -1,26 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "dry-struct"
4
-
5
- module Textus
6
- # Single capability record handed to every use case. Replaces the
7
- # ReadCaps/WriteCaps/HookCaps trio from 0.26.x. Built once per Store
8
- # (see Store#initialize); Store delegates its readers to this record,
9
- # so this struct is the single source of truth for the field set.
10
- class Container < Dry::Struct
11
- attribute :manifest, Types::Any
12
- attribute :file_store, Types::Any
13
- attribute :schemas, Types::Any
14
- attribute :root, Types::String
15
- attribute :audit_log, Types::Any
16
- attribute :workflows, Types::Any
17
- attribute :gate, Types::Any
18
-
19
- def with(**attrs) = self.class.new(to_h.merge(attrs))
20
-
21
- def initialize(*)
22
- super
23
- freeze
24
- end
25
- end
26
- end
@@ -1,29 +0,0 @@
1
- module Textus
2
- module Contract
3
- # Registry of named, stateful wrappers a verb may declare via `around :name`.
4
- # A resource implements
5
- # `wrap(scope:, inputs:, session:) { |effective_inputs| ... }`:
6
- # it may adjust the inputs before the call and post-process the result after
7
- # — exactly what build's lock and pulse's cursor need, without a hand-authored
8
- # CLI class (ADR 0068). `session:` is the dispatching session (nil for the
9
- # sessionless CLI/Ruby surfaces, present for MCP), so a session-aware resource
10
- # like the cursor can defer to the session's own state instead of its file.
11
- module Around
12
- @registry = {}
13
-
14
- module_function
15
-
16
- def register(name, resource)
17
- @registry[name] = resource
18
- end
19
-
20
- def fetch(name)
21
- @registry.fetch(name) { raise "no around resource registered: #{name.inspect}" }
22
- end
23
-
24
- def with(name, scope:, inputs:, session: nil, &call)
25
- fetch(name).wrap(scope: scope, inputs: inputs, session: session, &call)
26
- end
27
- end
28
- end
29
- end
@@ -1,88 +0,0 @@
1
- module Textus
2
- module Contract
3
- # Raised when a required arg is absent from the bound input. Surface
4
- # adapters translate this to their native error (MCP ToolError, CLI
5
- # UsageError); a direct Ruby call lets it surface as-is.
6
- class MissingArgs < Textus::Error
7
- attr_reader :spec, :missing
8
-
9
- def initialize(spec, missing)
10
- @spec = spec
11
- @missing = missing
12
- super("missing_args", "#{spec.verb}: missing #{missing.map(&:wire).join(", ")}")
13
- end
14
- end
15
-
16
- # The single argument binder for every surface (spike: collapses the three
17
- # historical implementations — MCP::Catalog.map_args, CLI::Runner.call_args,
18
- # and RoleScope's default-injection loop — into one algorithm).
19
- #
20
- # Input is a uniform `inputs` hash keyed by arg NAME (the use-case kwarg
21
- # name, never the wire name): each surface normalizes its own raw transport
22
- # shape (MCP JSON keyed by wire-name, CLI argv+flags, Ruby args+kwargs) into
23
- # this hash. Binder owns the shared algorithm and nothing transport-specific:
24
- #
25
- # 1. validate every required arg is present in `inputs`;
26
- # 2. for absentees, fall back to session_default (when a session is given)
27
- # then to the literal default; otherwise omit the arg entirely;
28
- # 3. split into the (positional, keyword) pair to splat into the use-case,
29
- # routing by `arg.positional`.
30
- #
31
- # Returns `[positional_array, keyword_hash]` — exactly what
32
- # `RoleScope#<verb>(*pos, **kw)` expects.
33
- module Binder
34
- module_function
35
-
36
- # Validation is unconditional: a `required:` arg absent from `inputs` is a
37
- # contract violation on every surface (ADR 0069). `required:` is now an
38
- # honest contract invariant, not a surface policy — args the use-case
39
- # treats as optional (e.g. `meta`, whose real requiredness lives in schema
40
- # validation downstream) are declared `required: false`, so this check
41
- # never fires spuriously and never needs an opt-out.
42
- def bind(spec, inputs, session: nil)
43
- missing = spec.required_args.reject { |a| inputs.key?(a.name) }
44
- raise MissingArgs.new(spec, missing) unless missing.empty?
45
-
46
- pos = []
47
- kw = {}
48
- spec.args.each do |a|
49
- if inputs.key?(a.name)
50
- value = inputs[a.name]
51
- elsif a.session_default && session
52
- value = session.public_send(a.session_default)
53
- elsif !a.default.nil?
54
- value = a.default
55
- else
56
- next
57
- end
58
-
59
- if a.positional
60
- pos << value
61
- else
62
- kw[a.name] = value
63
- end
64
- end
65
- [pos, kw]
66
- end
67
-
68
- # Normalize an ordered positional list + a by-name keyword hash (the shape
69
- # CLI argv+flags and Ruby args+kwargs both arrive in) into the uniform
70
- # by-name `inputs` hash bind expects. Positionals beyond what was supplied
71
- # are dropped so bind's required-check sees them as absent.
72
- def inputs_from_ordered(spec, ordered_positionals, by_name_keywords)
73
- names = spec.args.select(&:positional).map(&:name)
74
- names.zip(ordered_positionals).to_h.compact.merge(by_name_keywords)
75
- end
76
-
77
- # Normalize a raw transport hash keyed by WIRE name (the shape MCP JSON
78
- # arrives in) into the uniform by-name `inputs` hash bind expects. Keys
79
- # not declared on the contract are ignored.
80
- def inputs_from_wire(spec, raw)
81
- raw ||= {}
82
- spec.args.each_with_object({}) do |a, h|
83
- h[a.name] = raw[a.wire.to_s] if raw.key?(a.wire.to_s)
84
- end
85
- end
86
- end
87
- end
88
- end
@@ -1,17 +0,0 @@
1
- module Textus
2
- module Contract
3
- module Resources
4
- # Serializes builds across every surface (CLI, MCP, Ruby). Previously the
5
- # CLI verb wrapped each build in a BuildLock by hand; lifting it into the
6
- # contract means the MCP surface inherits the single-writer guarantee and
7
- # cannot collide with a concurrent CLI or background build.
8
- class BuildLock
9
- def wrap(scope:, inputs:, session: nil) # rubocop:disable Lint/UnusedMethodArgument
10
- Textus::Ports::BuildLock.with(root: scope.container.root) { yield(inputs) }
11
- end
12
- end
13
- end
14
- end
15
- end
16
-
17
- Textus::Contract::Around.register(:build_lock, Textus::Contract::Resources::BuildLock.new)
@@ -1,26 +0,0 @@
1
- module Textus
2
- module Contract
3
- module Resources
4
- # Reads the persisted file cursor as the `since` default when the caller
5
- # did not supply one, runs pulse, then persists the returned cursor.
6
- # Replaces CLI::Verb::Pulse's hand-coded CursorStore read/write (ADR 0068).
7
- #
8
- # A session-bearing surface (MCP) carries its own cursor via the contract's
9
- # `session_default: :cursor`, so this defers entirely when a session is
10
- # present — it is the sessionless CLI/Ruby surfaces that need the file.
11
- class Cursor
12
- def wrap(scope:, inputs:, session:)
13
- return yield(inputs) if session
14
-
15
- store = Textus::CursorStore.new(root: scope.container.root, role: scope.role)
16
- effective = inputs.key?(:since) ? inputs : inputs.merge(since: store.read)
17
- result = yield(effective)
18
- store.write(result["cursor"]) if result.is_a?(Hash) && result["cursor"]
19
- result
20
- end
21
- end
22
- end
23
- end
24
- end
25
-
26
- Textus::Contract::Around.register(:cursor, Textus::Contract::Resources::Cursor.new)