textus 0.55.1 → 0.55.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 (172) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1 -1
  3. data/README.md +9 -9
  4. data/SPEC.md +14 -13
  5. data/docs/architecture/README.md +3 -3
  6. data/docs/reference/conventions.md +5 -2
  7. data/lib/textus/boot.rb +64 -85
  8. data/lib/textus/{gate → dispatch}/binder.rb +8 -10
  9. data/lib/textus/dispatch/contracts.rb +63 -0
  10. data/lib/textus/dispatch/handler_registry.rb +21 -0
  11. data/lib/textus/dispatch/middleware/audit_index.rb +51 -0
  12. data/lib/textus/dispatch/middleware/auth.rb +40 -0
  13. data/lib/textus/dispatch/middleware/base.rb +26 -0
  14. data/lib/textus/dispatch/middleware/binder.rb +20 -0
  15. data/lib/textus/dispatch/middleware/cascade.rb +53 -0
  16. data/lib/textus/dispatch/pipeline.rb +35 -0
  17. data/lib/textus/doctor/check/audit_log.rb +1 -1
  18. data/lib/textus/doctor/check/generator_drift.rb +2 -2
  19. data/lib/textus/doctor/check/orphaned_publish_targets.rb +1 -1
  20. data/lib/textus/doctor/check/schema_violations.rb +1 -1
  21. data/lib/textus/doctor/check/{notebook_sources.rb → scratchpad_sources.rb} +10 -5
  22. data/lib/textus/doctor/check/sentinels.rb +1 -1
  23. data/lib/textus/doctor/check.rb +8 -6
  24. data/lib/textus/doctor.rb +1 -1
  25. data/lib/textus/errors.rb +2 -0
  26. data/lib/textus/format/base.rb +36 -8
  27. data/lib/textus/format/json.rb +0 -21
  28. data/lib/textus/format/markdown.rb +0 -21
  29. data/lib/textus/format/yaml.rb +0 -21
  30. data/lib/textus/format.rb +16 -1
  31. data/lib/textus/handlers/maintenance/boot_store.rb +15 -0
  32. data/lib/textus/handlers/maintenance/doctor_store.rb +15 -0
  33. data/lib/textus/handlers/maintenance/drain_store.rb +21 -0
  34. data/lib/textus/handlers/maintenance/ingest_entry.rb +159 -0
  35. data/lib/textus/handlers/maintenance/jobs_action.rb +21 -0
  36. data/lib/textus/handlers/maintenance/published_entries.rb +17 -0
  37. data/lib/textus/handlers/maintenance/rule_explain.rb +77 -0
  38. data/lib/textus/handlers/maintenance/rule_lint.rb +54 -0
  39. data/lib/textus/handlers/maintenance/rule_list.rb +32 -0
  40. data/lib/textus/handlers/maintenance/schema_envelope.rb +19 -0
  41. data/lib/textus/handlers/read/audit_entries.rb +48 -0
  42. data/lib/textus/handlers/read/blame_entry.rb +71 -0
  43. data/lib/textus/handlers/read/deps_entry.rb +17 -0
  44. data/lib/textus/handlers/read/get_entry.rb +68 -0
  45. data/lib/textus/handlers/read/list_keys.rb +36 -0
  46. data/lib/textus/handlers/read/pulse_entries.rb +66 -0
  47. data/lib/textus/handlers/read/rdeps_entry.rb +21 -0
  48. data/lib/textus/handlers/read/uid_entry.rb +18 -0
  49. data/lib/textus/handlers/read/where_entry.rb +18 -0
  50. data/lib/textus/handlers/write/accept_proposal.rb +39 -0
  51. data/lib/textus/handlers/write/data_mv.rb +55 -0
  52. data/lib/textus/handlers/write/delete_key.rb +17 -0
  53. data/lib/textus/handlers/write/enqueue_job.rb +27 -0
  54. data/lib/textus/handlers/write/key_delete_prefix.rb +32 -0
  55. data/lib/textus/handlers/write/key_mv_prefix.rb +45 -0
  56. data/lib/textus/handlers/write/move_key.rb +80 -0
  57. data/lib/textus/handlers/write/propose_entry.rb +29 -0
  58. data/lib/textus/handlers/write/put_entry.rb +29 -0
  59. data/lib/textus/handlers/write/reject_proposal.rb +29 -0
  60. data/lib/textus/init.rb +5 -5
  61. data/lib/textus/manifest/capabilities.rb +1 -1
  62. data/lib/textus/manifest/entry/base.rb +3 -3
  63. data/lib/textus/manifest/entry/publish/to_paths.rb +1 -1
  64. data/lib/textus/manifest/policy/predicates/author_held.rb +22 -0
  65. data/lib/textus/manifest/policy/predicates/etag_match.rb +18 -0
  66. data/lib/textus/manifest/policy/predicates/fresh_within.rb +13 -0
  67. data/lib/textus/manifest/policy/predicates/lane_deletable_by.rb +31 -0
  68. data/lib/textus/manifest/policy/predicates/lane_writable_by.rb +23 -0
  69. data/lib/textus/manifest/policy/predicates/raw_lane_ingest_only.rb +25 -0
  70. data/lib/textus/manifest/policy/predicates/raw_write_once.rb +24 -0
  71. data/lib/textus/manifest/policy/predicates/schema_valid.rb +41 -0
  72. data/lib/textus/manifest/policy/predicates/target_is_canon.rb +20 -0
  73. data/lib/textus/manifest/policy/predicates.rb +54 -0
  74. data/lib/textus/manifest/policy/retention.rb +1 -1
  75. data/lib/textus/orchestration.rb +55 -0
  76. data/lib/textus/port/audit_log.rb +6 -6
  77. data/lib/textus/port/build_lock.rb +1 -1
  78. data/lib/textus/{core → port}/sentinel.rb +1 -6
  79. data/lib/textus/port/sentinel_store.rb +3 -3
  80. data/lib/textus/port/storage/file_store.rb +23 -0
  81. data/lib/textus/port/storage/interface.rb +17 -0
  82. data/lib/textus/port/store.rb +58 -2
  83. data/lib/textus/port/watcher_lock.rb +2 -2
  84. data/lib/textus/produce/engine.rb +1 -11
  85. data/lib/textus/produce/publisher.rb +21 -0
  86. data/lib/textus/schema/registry.rb +42 -0
  87. data/lib/textus/schema/tools.rb +3 -10
  88. data/lib/textus/store/container.rb +140 -10
  89. data/lib/textus/store/cursor.rb +1 -1
  90. data/lib/textus/store/{envelope → entry}/reader.rb +8 -4
  91. data/lib/textus/store/{envelope → entry}/writer.rb +53 -29
  92. data/lib/textus/store/envelope/meta.rb +61 -0
  93. data/lib/textus/store/freshness/drift_detector.rb +93 -0
  94. data/lib/textus/store/freshness/evaluator.rb +20 -0
  95. data/lib/textus/store/freshness/ttl_evaluator.rb +57 -0
  96. data/lib/textus/{core → store}/freshness/verdict.rb +1 -11
  97. data/lib/textus/store/freshness.rb +8 -0
  98. data/lib/textus/store/index/builder.rb +5 -3
  99. data/lib/textus/store/jobs/planner.rb +27 -7
  100. data/lib/textus/store/jobs/queue.rb +9 -1
  101. data/lib/textus/store/jobs/retention/base.rb +52 -0
  102. data/lib/textus/store/jobs/retention/sweep.rb +55 -0
  103. data/lib/textus/store/jobs/retention.rb +1 -43
  104. data/lib/textus/store/jobs/sweep.rb +2 -2
  105. data/lib/textus/store/{geometry.rb → layout.rb} +19 -3
  106. data/lib/textus/store.rb +53 -30
  107. data/lib/textus/surface/cli/runner.rb +8 -9
  108. data/lib/textus/surface/cli/verb/doctor.rb +3 -2
  109. data/lib/textus/surface/cli/verb/get.rb +5 -3
  110. data/lib/textus/surface/cli/verb/put.rb +5 -3
  111. data/lib/textus/surface/mcp/catalog.rb +26 -62
  112. data/lib/textus/surface/mcp/errors.rb +0 -10
  113. data/lib/textus/surface/mcp/projector.rb +20 -0
  114. data/lib/textus/surface/mcp/server.rb +20 -31
  115. data/lib/textus/{core → value}/duration.rb +1 -4
  116. data/lib/textus/value/envelope.rb +5 -4
  117. data/lib/textus/value/etag.rb +1 -1
  118. data/lib/textus/value/payload.rb +7 -0
  119. data/lib/textus/value/result.rb +36 -16
  120. data/lib/textus/verb_registry.rb +417 -0
  121. data/lib/textus/version.rb +1 -1
  122. data/lib/textus/workflow/loader.rb +1 -1
  123. data/lib/textus/workflow/runner.rb +10 -18
  124. data/lib/textus.rb +0 -64
  125. metadata +70 -70
  126. data/lib/textus/action/accept.rb +0 -46
  127. data/lib/textus/action/audit.rb +0 -94
  128. data/lib/textus/action/base.rb +0 -42
  129. data/lib/textus/action/blame.rb +0 -79
  130. data/lib/textus/action/boot.rb +0 -15
  131. data/lib/textus/action/data_mv.rb +0 -58
  132. data/lib/textus/action/deps.rb +0 -19
  133. data/lib/textus/action/doctor.rb +0 -17
  134. data/lib/textus/action/drain.rb +0 -31
  135. data/lib/textus/action/enqueue.rb +0 -37
  136. data/lib/textus/action/get.rb +0 -34
  137. data/lib/textus/action/ingest.rb +0 -199
  138. data/lib/textus/action/jobs.rb +0 -27
  139. data/lib/textus/action/key_delete.rb +0 -26
  140. data/lib/textus/action/key_delete_prefix.rb +0 -35
  141. data/lib/textus/action/key_mv.rb +0 -122
  142. data/lib/textus/action/key_mv_prefix.rb +0 -48
  143. data/lib/textus/action/list.rb +0 -28
  144. data/lib/textus/action/propose.rb +0 -42
  145. data/lib/textus/action/published.rb +0 -22
  146. data/lib/textus/action/pulse.rb +0 -49
  147. data/lib/textus/action/put.rb +0 -38
  148. data/lib/textus/action/rdeps.rb +0 -24
  149. data/lib/textus/action/reject.rb +0 -28
  150. data/lib/textus/action/rule_explain.rb +0 -81
  151. data/lib/textus/action/rule_lint.rb +0 -62
  152. data/lib/textus/action/rule_list.rb +0 -38
  153. data/lib/textus/action/schema_envelope.rb +0 -22
  154. data/lib/textus/action/uid.rb +0 -19
  155. data/lib/textus/action/where.rb +0 -21
  156. data/lib/textus/contract/arg.rb +0 -10
  157. data/lib/textus/contract/dsl.rb +0 -88
  158. data/lib/textus/contract/spec.rb +0 -25
  159. data/lib/textus/contract.rb +0 -12
  160. data/lib/textus/core/freshness/evaluator.rb +0 -150
  161. data/lib/textus/core/freshness.rb +0 -11
  162. data/lib/textus/core/retention/sweep.rb +0 -57
  163. data/lib/textus/core/retention.rb +0 -11
  164. data/lib/textus/format/shared.rb +0 -17
  165. data/lib/textus/gate/auth.rb +0 -212
  166. data/lib/textus/gate.rb +0 -92
  167. data/lib/textus/meta.rb +0 -54
  168. data/lib/textus/schemas.rb +0 -54
  169. data/lib/textus/store/compositor.rb +0 -34
  170. data/lib/textus/store/session.rb +0 -37
  171. data/lib/textus/surface/projector.rb +0 -27
  172. data/lib/textus/surface/role_scope.rb +0 -34
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.55.1
4
+ version: 0.55.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Patrick
@@ -23,20 +23,6 @@ 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'
40
26
  - !ruby/object:Gem::Dependency
41
27
  name: dry-schema
42
28
  requirement: !ruby/object:Gem::Requirement
@@ -180,55 +166,22 @@ files:
180
166
  - docs/reference/conventions.md
181
167
  - exe/textus
182
168
  - lib/textus.rb
183
- - lib/textus/action/accept.rb
184
- - lib/textus/action/audit.rb
185
- - lib/textus/action/base.rb
186
- - lib/textus/action/blame.rb
187
- - lib/textus/action/boot.rb
188
- - lib/textus/action/data_mv.rb
189
- - lib/textus/action/deps.rb
190
- - lib/textus/action/doctor.rb
191
- - lib/textus/action/drain.rb
192
- - lib/textus/action/enqueue.rb
193
- - lib/textus/action/get.rb
194
- - lib/textus/action/ingest.rb
195
- - lib/textus/action/jobs.rb
196
- - lib/textus/action/key_delete.rb
197
- - lib/textus/action/key_delete_prefix.rb
198
- - lib/textus/action/key_mv.rb
199
- - lib/textus/action/key_mv_prefix.rb
200
- - lib/textus/action/list.rb
201
- - lib/textus/action/propose.rb
202
- - lib/textus/action/published.rb
203
- - lib/textus/action/pulse.rb
204
- - lib/textus/action/put.rb
205
- - lib/textus/action/rdeps.rb
206
- - lib/textus/action/reject.rb
207
- - lib/textus/action/rule_explain.rb
208
- - lib/textus/action/rule_lint.rb
209
- - lib/textus/action/rule_list.rb
210
- - lib/textus/action/schema_envelope.rb
211
- - lib/textus/action/uid.rb
212
- - lib/textus/action/where.rb
213
169
  - lib/textus/boot.rb
214
- - lib/textus/contract.rb
215
- - lib/textus/contract/arg.rb
216
- - lib/textus/contract/dsl.rb
217
- - lib/textus/contract/spec.rb
218
- - lib/textus/core/duration.rb
219
- - lib/textus/core/freshness.rb
220
- - lib/textus/core/freshness/evaluator.rb
221
- - lib/textus/core/freshness/verdict.rb
222
- - lib/textus/core/retention.rb
223
- - lib/textus/core/retention/sweep.rb
224
- - lib/textus/core/sentinel.rb
170
+ - lib/textus/dispatch/binder.rb
171
+ - lib/textus/dispatch/contracts.rb
172
+ - lib/textus/dispatch/handler_registry.rb
173
+ - lib/textus/dispatch/middleware/audit_index.rb
174
+ - lib/textus/dispatch/middleware/auth.rb
175
+ - lib/textus/dispatch/middleware/base.rb
176
+ - lib/textus/dispatch/middleware/binder.rb
177
+ - lib/textus/dispatch/middleware/cascade.rb
178
+ - lib/textus/dispatch/pipeline.rb
225
179
  - lib/textus/doctor.rb
226
180
  - lib/textus/doctor/check.rb
227
181
  - lib/textus/doctor/check/audit_log.rb
228
182
  - lib/textus/doctor/check/generator_drift.rb
229
183
  - lib/textus/doctor/check/illegal_keys.rb
230
184
  - lib/textus/doctor/check/manifest_files.rb
231
- - lib/textus/doctor/check/notebook_sources.rb
232
185
  - lib/textus/doctor/check/orphaned_publish_targets.rb
233
186
  - lib/textus/doctor/check/proposal_targets.rb
234
187
  - lib/textus/doctor/check/protocol_version.rb
@@ -238,6 +191,7 @@ files:
238
191
  - lib/textus/doctor/check/schema_parse_error.rb
239
192
  - lib/textus/doctor/check/schema_violations.rb
240
193
  - lib/textus/doctor/check/schemas.rb
194
+ - lib/textus/doctor/check/scratchpad_sources.rb
241
195
  - lib/textus/doctor/check/sentinels.rb
242
196
  - lib/textus/doctor/check/stale_reviewed_stamp.rb
243
197
  - lib/textus/doctor/check/templates.rb
@@ -248,12 +202,37 @@ files:
248
202
  - lib/textus/format/base.rb
249
203
  - lib/textus/format/json.rb
250
204
  - lib/textus/format/markdown.rb
251
- - lib/textus/format/shared.rb
252
205
  - lib/textus/format/text.rb
253
206
  - lib/textus/format/yaml.rb
254
- - lib/textus/gate.rb
255
- - lib/textus/gate/auth.rb
256
- - lib/textus/gate/binder.rb
207
+ - lib/textus/handlers/maintenance/boot_store.rb
208
+ - lib/textus/handlers/maintenance/doctor_store.rb
209
+ - lib/textus/handlers/maintenance/drain_store.rb
210
+ - lib/textus/handlers/maintenance/ingest_entry.rb
211
+ - lib/textus/handlers/maintenance/jobs_action.rb
212
+ - lib/textus/handlers/maintenance/published_entries.rb
213
+ - lib/textus/handlers/maintenance/rule_explain.rb
214
+ - lib/textus/handlers/maintenance/rule_lint.rb
215
+ - lib/textus/handlers/maintenance/rule_list.rb
216
+ - lib/textus/handlers/maintenance/schema_envelope.rb
217
+ - lib/textus/handlers/read/audit_entries.rb
218
+ - lib/textus/handlers/read/blame_entry.rb
219
+ - lib/textus/handlers/read/deps_entry.rb
220
+ - lib/textus/handlers/read/get_entry.rb
221
+ - lib/textus/handlers/read/list_keys.rb
222
+ - lib/textus/handlers/read/pulse_entries.rb
223
+ - lib/textus/handlers/read/rdeps_entry.rb
224
+ - lib/textus/handlers/read/uid_entry.rb
225
+ - lib/textus/handlers/read/where_entry.rb
226
+ - lib/textus/handlers/write/accept_proposal.rb
227
+ - lib/textus/handlers/write/data_mv.rb
228
+ - lib/textus/handlers/write/delete_key.rb
229
+ - lib/textus/handlers/write/enqueue_job.rb
230
+ - lib/textus/handlers/write/key_delete_prefix.rb
231
+ - lib/textus/handlers/write/key_mv_prefix.rb
232
+ - lib/textus/handlers/write/move_key.rb
233
+ - lib/textus/handlers/write/propose_entry.rb
234
+ - lib/textus/handlers/write/put_entry.rb
235
+ - lib/textus/handlers/write/reject_proposal.rb
257
236
  - lib/textus/init.rb
258
237
  - lib/textus/jobs.rb
259
238
  - lib/textus/key/distance.rb
@@ -283,6 +262,16 @@ files:
283
262
  - lib/textus/manifest/entry/validators/publish.rb
284
263
  - lib/textus/manifest/policy.rb
285
264
  - lib/textus/manifest/policy/matcher.rb
265
+ - lib/textus/manifest/policy/predicates.rb
266
+ - lib/textus/manifest/policy/predicates/author_held.rb
267
+ - lib/textus/manifest/policy/predicates/etag_match.rb
268
+ - lib/textus/manifest/policy/predicates/fresh_within.rb
269
+ - lib/textus/manifest/policy/predicates/lane_deletable_by.rb
270
+ - lib/textus/manifest/policy/predicates/lane_writable_by.rb
271
+ - lib/textus/manifest/policy/predicates/raw_lane_ingest_only.rb
272
+ - lib/textus/manifest/policy/predicates/raw_write_once.rb
273
+ - lib/textus/manifest/policy/predicates/schema_valid.rb
274
+ - lib/textus/manifest/policy/predicates/target_is_canon.rb
286
275
  - lib/textus/manifest/policy/publish_target.rb
287
276
  - lib/textus/manifest/policy/react.rb
288
277
  - lib/textus/manifest/policy/retention.rb
@@ -298,28 +287,35 @@ files:
298
287
  - lib/textus/manifest/schema/semantics/migration.rb
299
288
  - lib/textus/manifest/schema/validator.rb
300
289
  - lib/textus/manifest/schema/vocabulary.rb
301
- - lib/textus/meta.rb
290
+ - lib/textus/orchestration.rb
302
291
  - lib/textus/port/audit_log.rb
303
292
  - lib/textus/port/build_lock.rb
304
293
  - lib/textus/port/clock.rb
305
294
  - lib/textus/port/publisher.rb
295
+ - lib/textus/port/sentinel.rb
306
296
  - lib/textus/port/sentinel_store.rb
307
297
  - lib/textus/port/storage/file_stat.rb
308
298
  - lib/textus/port/storage/file_store.rb
299
+ - lib/textus/port/storage/interface.rb
309
300
  - lib/textus/port/store.rb
310
301
  - lib/textus/port/watcher_lock.rb
311
302
  - lib/textus/produce/engine.rb
303
+ - lib/textus/produce/publisher.rb
312
304
  - lib/textus/produce/render.rb
313
305
  - lib/textus/schema.rb
306
+ - lib/textus/schema/registry.rb
314
307
  - lib/textus/schema/tools.rb
315
- - lib/textus/schemas.rb
316
308
  - lib/textus/store.rb
317
- - lib/textus/store/compositor.rb
318
309
  - lib/textus/store/container.rb
319
310
  - 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
311
+ - lib/textus/store/entry/reader.rb
312
+ - lib/textus/store/entry/writer.rb
313
+ - lib/textus/store/envelope/meta.rb
314
+ - lib/textus/store/freshness.rb
315
+ - lib/textus/store/freshness/drift_detector.rb
316
+ - lib/textus/store/freshness/evaluator.rb
317
+ - lib/textus/store/freshness/ttl_evaluator.rb
318
+ - lib/textus/store/freshness/verdict.rb
323
319
  - lib/textus/store/index/builder.rb
324
320
  - lib/textus/store/index/lookup.rb
325
321
  - lib/textus/store/jobs/base.rb
@@ -330,9 +326,11 @@ files:
330
326
  - lib/textus/store/jobs/queue.rb
331
327
  - lib/textus/store/jobs/registry.rb
332
328
  - lib/textus/store/jobs/retention.rb
329
+ - lib/textus/store/jobs/retention/base.rb
330
+ - lib/textus/store/jobs/retention/sweep.rb
333
331
  - lib/textus/store/jobs/sweep.rb
334
332
  - lib/textus/store/jobs/worker.rb
335
- - lib/textus/store/session.rb
333
+ - lib/textus/store/layout.rb
336
334
  - lib/textus/surface/cli.rb
337
335
  - lib/textus/surface/cli/group.rb
338
336
  - lib/textus/surface/cli/group/data.rb
@@ -355,18 +353,20 @@ files:
355
353
  - lib/textus/surface/mcp.rb
356
354
  - lib/textus/surface/mcp/catalog.rb
357
355
  - lib/textus/surface/mcp/errors.rb
356
+ - lib/textus/surface/mcp/projector.rb
358
357
  - lib/textus/surface/mcp/server.rb
359
- - lib/textus/surface/projector.rb
360
- - lib/textus/surface/role_scope.rb
361
358
  - lib/textus/surface/watcher.rb
362
359
  - lib/textus/value/call.rb
363
360
  - lib/textus/value/command.rb
361
+ - lib/textus/value/duration.rb
364
362
  - lib/textus/value/envelope.rb
365
363
  - lib/textus/value/etag.rb
364
+ - lib/textus/value/payload.rb
366
365
  - lib/textus/value/result.rb
367
366
  - lib/textus/value/role.rb
368
367
  - lib/textus/value/types.rb
369
368
  - lib/textus/value/uid.rb
369
+ - lib/textus/verb_registry.rb
370
370
  - lib/textus/version.rb
371
371
  - lib/textus/workflow.rb
372
372
  - lib/textus/workflow/collector.rb
@@ -1,46 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Textus
4
- module Action
5
- class Accept < Base
6
- verb :accept
7
- summary "apply a queued proposal to its target zone; requires the author capability"
8
- surfaces :cli, :mcp
9
- cli "accept"
10
- arg :pending_key, String, required: true, positional: true, description: "the queued proposal's key"
11
-
12
- def self.call(container:, call:, pending_key:)
13
- env = container.compositor.read(pending_key)
14
- parsed = proposal_from(env, key: pending_key)
15
- return parsed if parsed.is_a?(Dry::Monads::Result::Failure)
16
-
17
- target = parsed[:target_key]
18
- action = parsed[:proposal]["action"] || "put"
19
-
20
- case action
21
- when "put"
22
- mentry = container.manifest.resolver.resolve(target).entry
23
- container.compositor.write(
24
- target,
25
- mentry: mentry,
26
- payload: Textus::Store::Envelope::Writer::Payload.new(
27
- meta: env.meta["_meta"] || {},
28
- body: env.body,
29
- content: nil,
30
- ),
31
- call: call,
32
- )
33
- when "delete"
34
- container.compositor.delete(target, call: call)
35
- else
36
- return Failure(code: :proposal_error, message: "unknown action: #{action}")
37
- end
38
-
39
- container.compositor.delete(pending_key, call: call)
40
-
41
- Success("protocol" => Textus::PROTOCOL, "accepted" => pending_key, "target_key" => target, "action" => action,
42
- "cascade_key" => target)
43
- end
44
- end
45
- end
46
- end
@@ -1,94 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "json"
4
- require "time"
5
-
6
- module Textus
7
- module Action
8
- class Audit < Base
9
- verb :audit
10
- summary "Query the audit log with optional filters."
11
- surfaces :cli
12
- cli "audit"
13
- arg :key, String, required: false, description: "filter to rows for this key"
14
- arg :lane, String, required: false, description: "filter to keys in this lane"
15
- arg :role, String, required: false, description: "filter to rows written under this role"
16
- arg :verb, String, required: false, description: "filter to rows for this verb"
17
- arg :since, String, required: false,
18
- coerce: ->(s) { Textus::Action::Audit.parse_since(s, now: Time.now) },
19
- description: "ISO-8601 timestamp or relative offset (e.g. 1h, 30m)"
20
- arg :seq_since, Integer, required: false, description: "return rows with seq > this cursor value"
21
- arg :correlation_id, String, required: false, description: "filter to rows with this correlation_id"
22
- arg :limit, Integer, required: false, description: "maximum number of rows to return"
23
- view(:cli) { |rows, _i| { "verb" => "audit", "rows" => rows } }
24
-
25
- def self.call(container:, key: nil, lane: nil, role: nil, verb: nil, since: nil, seq_since: nil, correlation_id: nil, limit: nil, **) # rubocop:disable Metrics/ParameterLists
26
- audit_log = container.audit_log
27
- manifest = container.manifest
28
-
29
- cursor_check = check_cursor_expiry(seq_since, audit_log)
30
- return cursor_check if cursor_check.is_a?(Dry::Monads::Result::Failure)
31
-
32
- Success(audit_log.scan(
33
- seq_since: seq_since,
34
- key: key,
35
- role: role,
36
- verb: verb,
37
- correlation_id: correlation_id,
38
- limit: limit,
39
- ).select do |row|
40
- next false if lane && !key_in_lane?(row["key"], lane, manifest)
41
- next false if since && (row["ts"].nil? || Time.parse(row["ts"]) < since)
42
-
43
- true
44
- end)
45
- end
46
-
47
- def self.parse_since(str, now: Time.now.utc)
48
- return nil if str.nil? || str.empty?
49
- return Time.parse(str) if str =~ /\A\d{4}-\d{2}-\d{2}/
50
-
51
- match = str.match(/\A(\d+)([smhd])\z/) or return nil
52
- mult = { "s" => 1, "m" => 60, "h" => 3600, "d" => 86_400 }[match[2]]
53
- now - (match[1].to_i * mult)
54
- end
55
-
56
- Query = Data.define(:key, :lane, :role, :verb, :since, :seq_since, :correlation_id, :limit) do
57
- # rubocop:disable Metrics/ParameterLists
58
- def self.build(key: nil, lane: nil, role: nil, verb: nil,
59
- since: nil, seq_since: nil, correlation_id: nil, limit: nil)
60
- new(key:, lane:, role:, verb:, since:, seq_since:, correlation_id:, limit:)
61
- end
62
- # rubocop:enable Metrics/ParameterLists
63
-
64
- def matches?(row)
65
- return false if key && row["key"] != key
66
- return false if role && row["role"] != role
67
- return false if verb && row["verb"] != verb
68
- return false if since && (row["ts"].nil? || Time.parse(row["ts"]) < since)
69
- return false if seq_since && (row["seq"].nil? || row["seq"] <= seq_since)
70
- return false if correlation_id && row.dig("extras", "correlation_id") != correlation_id
71
-
72
- true
73
- end
74
- end
75
-
76
- def self.check_cursor_expiry(seq_since, audit_log)
77
- return unless seq_since
78
-
79
- min = audit_log.min_available_seq
80
- return unless min && seq_since < min - 1
81
-
82
- Failure(code: :cursor_expired, message: "requested seq #{seq_since} is below minimum available #{min}",
83
- details: { requested: seq_since, min_available: min })
84
- end
85
-
86
- def self.key_in_lane?(key, lane, manifest)
87
- mentry = manifest.resolver.resolve(key).entry
88
- mentry && mentry.lane == lane
89
- rescue Textus::Error
90
- false
91
- end
92
- end
93
- end
94
- end
@@ -1,42 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Textus
4
- module Action
5
- @registry = {}
6
-
7
- def self.registry = @registry
8
-
9
- def self.register(klass)
10
- @registry[klass.name.gsub("::", "/").downcase] = klass
11
- end
12
-
13
- def self.fetch(type)
14
- return @registry[type] if @registry[type]
15
-
16
- match = @registry.values.find { |k| k.const_defined?(:TYPE, false) && type == k::TYPE }
17
- raise Textus::UsageError.new("unknown action type: #{type}") unless match
18
-
19
- @registry[type] = match
20
- end
21
-
22
- class Base
23
- extend Contract::DSL
24
- extend Dry::Monads[:result]
25
-
26
- def self.inherited(subclass)
27
- super
28
- Textus::Action.register(subclass) if subclass.name
29
- end
30
-
31
- def self.call(**)
32
- raise NotImplementedError.new("#{name}.call")
33
- end
34
-
35
- def self.proposal_from(env, key:)
36
- proposal = env.meta&.dig("proposal") or return Failure(code: :proposal_error, message: "entry has no proposal block: #{key}")
37
- target = proposal["target_key"] or return Failure(code: :proposal_error, message: "proposal missing target_key")
38
- { proposal:, target_key: target }
39
- end
40
- end
41
- end
42
- end
@@ -1,79 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "open3"
4
-
5
- module Textus
6
- module Action
7
- class Blame < Base
8
- verb :blame
9
- summary "Annotate audit rows for a key with the git commit that introduced each file state."
10
- surfaces :cli
11
- cli "blame"
12
- arg :key, String, required: true, positional: true, description: "entry key to blame"
13
- arg :limit, Integer, required: false, description: "maximum number of audit rows to return"
14
- view(:cli) { |rows, inputs| { "verb" => "blame", "key" => inputs[:key], "rows" => rows } }
15
-
16
- def self.call(container:, key:, limit: nil, **)
17
- manifest = container.manifest
18
- root = container.root
19
-
20
- audit_result = Textus::Action::Audit.call(container: container, key: key, limit: limit)
21
- audit_rows = Value::Result.unwrap(audit_result)
22
- path = resolve_path(key, manifest: manifest)
23
- return Success(audit_rows.map { |row| row.merge("git" => nil) }) unless git_tracked?(path, root: root)
24
-
25
- Success(audit_rows.map { |row| row.merge("git" => git_commit_at(path, timestamp: row["ts"], root: root)) })
26
- end
27
-
28
- def self.resolve_path(key, manifest:)
29
- res = manifest.resolver.resolve(key)
30
- mentry = res.entry
31
- path = res.path
32
- path || Textus::Key::Path.resolve(manifest.data, mentry)
33
- rescue Textus::Error
34
- nil
35
- end
36
-
37
- def self.git_tracked?(path, root:)
38
- return false if path.nil?
39
- return false unless File.exist?(path)
40
- return false unless git_repo?(root)
41
-
42
- _out, _err, status = Open3.capture3(
43
- "git", "ls-files", "--error-unmatch", path,
44
- chdir: root
45
- )
46
- status.success?
47
- rescue Errno::ENOENT
48
- false
49
- end
50
-
51
- def self.git_repo?(root)
52
- dir = root
53
- loop do
54
- return true if File.directory?(File.join(dir, ".git"))
55
-
56
- parent = File.dirname(dir)
57
- return false if parent == dir
58
-
59
- dir = parent
60
- end
61
- end
62
-
63
- def self.git_commit_at(path, timestamp:, root:)
64
- args = ["git", "log", "-1"]
65
- args << "--before=#{timestamp}" if timestamp
66
- args += ["--format=%H%x09%an%x09%aI%x09%s", "--", path]
67
- out, _err, status = Open3.capture3(*args, chdir: root)
68
- return nil unless status.success?
69
-
70
- sha, author, date, subject = out.strip.split("\t", 4)
71
- return nil if sha.nil? || sha.empty?
72
-
73
- { "sha" => sha, "author" => author, "date" => date, "subject" => subject }
74
- rescue Errno::ENOENT
75
- nil
76
- end
77
- end
78
- end
79
- end
@@ -1,15 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Textus
4
- module Action
5
- class Boot < Base
6
- verb :boot
7
- summary "Return the orientation contract: lanes, agent_quickstart, agent_protocol, and pre-computed artifacts."
8
- surfaces :cli, :mcp
9
-
10
- def self.call(container:, **)
11
- Success(Textus::Boot.build(container: container))
12
- end
13
- end
14
- end
15
- end
@@ -1,58 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "yaml"
4
-
5
- module Textus
6
- module Action
7
- class DataMv < Base
8
- verb :data_mv
9
- summary "Rename a data lane — manifest + files. Refuses if destination exists."
10
- surfaces :cli, :mcp
11
- cli "data mv"
12
- arg :from, String, required: true, positional: true, description: "current data lane name"
13
- arg :to, String, required: true, positional: true,
14
- description: "new data lane name; refused if a lane by this name already exists"
15
- arg :dry_run, :boolean, default: false,
16
- description: "when true, returns the planned zone move without applying it; " \
17
- "defaults to false, so omitting it applies the move immediately"
18
- view { |v, _i| v.to_h }
19
-
20
- def self.call(container:, call:, from:, to:, dry_run: false, **) # rubocop:disable Lint/UnusedMethodArgument
21
- manifest = container.manifest
22
- geom = container.geometry
23
-
24
- return Failure(code: :usage_error, message: "from and to required") if from.nil? || to.nil? || from.empty? || to.empty?
25
- return Failure(code: :usage_error, message: "data lane '#{from}' not declared") unless manifest.data.declared_lane_kinds.key?(from)
26
-
27
- dest_dir = geom.lane_path(to)
28
- return Failure(code: :usage_error, message: "destination 'data/#{to}' already exists") if File.exist?(dest_dir)
29
-
30
- affected_keys = manifest.data.entries.select { |entry| entry.lane == from }.map(&:key)
31
-
32
- steps = [{ "op" => "rename_zone", "from" => from, "to" => to }]
33
- steps += affected_keys.map do |key|
34
- { "op" => "mv", "from" => key, "to" => "#{to}#{key[from.length..]}" }
35
- end
36
-
37
- plan = Textus::Store::Jobs::Plan.new(steps: steps, warnings: [])
38
- return Success(plan) if dry_run
39
-
40
- rewrite_manifest!(geom, from:, to:)
41
- FileUtils.mv(geom.lane_path(from), dest_dir)
42
- Success(plan)
43
- end
44
-
45
- def self.rewrite_manifest!(geom, from:, to:)
46
- path = geom.manifest_path
47
- raw = YAML.safe_load_file(path, permitted_classes: [Symbol], aliases: false)
48
- raw["lanes"].each { |lane| lane["name"] = to if lane["name"] == from }
49
- raw["entries"].each do |entry|
50
- entry["lane"] = to if entry["lane"] == from
51
- entry["key"] = entry["key"].sub(/\A#{Regexp.escape(from)}(\.|\z)/, "#{to}\\1")
52
- entry["path"] = entry["path"].sub(%r{\A(data/)?#{Regexp.escape(from)}(/|\z)}, "\\1#{to}\\2")
53
- end
54
- File.write(path, YAML.dump(raw))
55
- end
56
- end
57
- end
58
- end
@@ -1,19 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Textus
4
- module Action
5
- class Deps < Base
6
- verb :deps
7
- summary "List the keys a derived entry depends on (its projection/external sources)."
8
- surfaces :cli, :mcp
9
- arg :key, String, required: true, positional: true,
10
- description: "dotted key of the derived entry whose source keys you want"
11
-
12
- def self.call(container:, key:, **)
13
- entry = container.manifest.data.entries.find { |e| e.key == key }
14
- deps = entry&.external? ? Array(entry.source&.sources).compact : []
15
- Success({ "key" => key, "deps" => deps.uniq })
16
- end
17
- end
18
- end
19
- end
@@ -1,17 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Textus
4
- module Action
5
- class Doctor < Base
6
- verb :doctor
7
- summary "Run health checks on the textus store and report any issues."
8
- surfaces :cli
9
- cli "doctor"
10
- arg :checks, Array, required: false, description: "subset of check names to run (default: all)"
11
-
12
- def self.call(container:, call:, checks: nil, **)
13
- Success(Textus::Doctor.build(container: container, checks: checks, role: call.role))
14
- end
15
- end
16
- end
17
- end
@@ -1,31 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Textus
4
- module Action
5
- class Drain < Base
6
- verb :drain
7
- summary "Seed materialize + sweep jobs then drain the queue to empty. " \
8
- "Identical to one Watcher tick. Use when no watcher is running."
9
- surfaces :cli, :mcp
10
- arg :prefix, String, description: "restrict to keys under this dotted prefix"
11
- arg :lane, String, description: "restrict to entries in this lane"
12
-
13
- def self.call(container:, call:, prefix: nil, lane: nil) # rubocop:disable Lint/UnusedMethodArgument
14
- queue = Textus::Store::Jobs::Queue.new(store: container.job_store)
15
- Textus::Store::Jobs::Planner.seed(
16
- container: container,
17
- queue: queue,
18
- role: call.role,
19
- )
20
- queue.reclaim(now: Textus::Port::Clock.new.now)
21
- summary = Textus::Store::Jobs::Worker.for(container:, queue:).drain
22
- Success({
23
- "protocol" => Textus::PROTOCOL,
24
- "ok" => summary.failed.zero?,
25
- "completed" => summary.completed,
26
- "failed" => summary.failed,
27
- })
28
- end
29
- end
30
- end
31
- end