textus 0.55.2 → 0.56.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 (338) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +175 -2768
  3. data/README.md +8 -9
  4. data/lib/textus/artifact.rb +8 -0
  5. data/lib/textus/errors.rb +0 -39
  6. data/lib/textus/human.rb +7 -0
  7. data/lib/textus/infra/adapters/concurrency_adapter.rb +15 -0
  8. data/lib/textus/infra/clock.rb +7 -0
  9. data/lib/textus/infra/database.rb +127 -0
  10. data/lib/textus/infra/file_system.rb +54 -0
  11. data/lib/textus/infra/locks.rb +50 -0
  12. data/lib/textus/infra/port/clock.rb +18 -0
  13. data/lib/textus/infra/port/publisher.rb +71 -0
  14. data/lib/textus/infra/port/sentinel.rb +25 -0
  15. data/lib/textus/infra/port/sentinel_store.rb +93 -0
  16. data/lib/textus/infra/store/audit_log.rb +42 -0
  17. data/lib/textus/infra/store/base.rb +25 -0
  18. data/lib/textus/infra/store/cursor_store.rb +27 -0
  19. data/lib/textus/infra/store/entry_index.rb +31 -0
  20. data/lib/textus/infra/store/envelope/meta.rb +63 -0
  21. data/lib/textus/infra/store/file_store.rb +23 -0
  22. data/lib/textus/infra/store/index/builder.rb +28 -0
  23. data/lib/textus/infra/store/index/lookup.rb +23 -0
  24. data/lib/textus/infra/store/link_store.rb +43 -0
  25. data/lib/textus/infra/store/workflow_queue.rb +54 -0
  26. data/lib/textus/infra/store.rb +29 -0
  27. data/lib/textus/ingest.rb +8 -0
  28. data/lib/textus/lanes/artifact/cache.rb +35 -0
  29. data/lib/textus/lanes/artifact/handlers.rb +29 -0
  30. data/lib/textus/lanes/artifact/lifecycle.rb +23 -0
  31. data/lib/textus/lanes/artifact/verbs.rb +18 -0
  32. data/lib/textus/lanes/ingest/dedup.rb +17 -0
  33. data/lib/textus/lanes/ingest/entry_types/asset.rb +45 -0
  34. data/lib/textus/lanes/ingest/entry_types/link.rb +16 -0
  35. data/lib/textus/lanes/ingest/entry_types/text.rb +17 -0
  36. data/lib/textus/lanes/ingest/entry_types.rb +25 -0
  37. data/lib/textus/lanes/ingest/handlers.rb +54 -0
  38. data/lib/textus/lanes/ingest/index_rebuilder.rb +13 -0
  39. data/lib/textus/lanes/ingest/key_builder.rb +33 -0
  40. data/lib/textus/lanes/ingest/resolver.rb +21 -0
  41. data/lib/textus/lanes/knowledge/freeform.rb +15 -0
  42. data/lib/textus/lanes/knowledge/handlers.rb +53 -0
  43. data/lib/textus/lanes/knowledge/runner.rb +45 -0
  44. data/lib/textus/lanes/knowledge/verbs.rb +16 -0
  45. data/lib/textus/lanes/scratchpad/handlers.rb +164 -0
  46. data/lib/textus/lanes/scratchpad/verbs.rb +73 -0
  47. data/lib/textus/{dispatch → protocol}/binder.rb +1 -7
  48. data/lib/textus/protocol/boot/catalog.rb +34 -0
  49. data/lib/textus/protocol/boot/context.rb +124 -0
  50. data/lib/textus/protocol/boot/protocol.rb +33 -0
  51. data/lib/textus/protocol/boot.rb +42 -0
  52. data/lib/textus/protocol/builder/domain.rb +17 -0
  53. data/lib/textus/protocol/builder/infrastructure.rb +17 -0
  54. data/lib/textus/protocol/builder/runtime.rb +46 -0
  55. data/lib/textus/protocol/builder.rb +11 -0
  56. data/lib/textus/protocol/container.rb +22 -0
  57. data/lib/textus/protocol/diff.rb +170 -0
  58. data/lib/textus/protocol/entry_constraint.rb +54 -0
  59. data/lib/textus/protocol/envelope.rb +70 -0
  60. data/lib/textus/protocol/format/base.rb +80 -0
  61. data/lib/textus/protocol/format/bash.rb +10 -0
  62. data/lib/textus/protocol/format/javascript.rb +10 -0
  63. data/lib/textus/protocol/format/json.rb +82 -0
  64. data/lib/textus/protocol/format/markdown.rb +53 -0
  65. data/lib/textus/protocol/format/python.rb +10 -0
  66. data/lib/textus/protocol/format/ruby.rb +10 -0
  67. data/lib/textus/protocol/format/script.rb +55 -0
  68. data/lib/textus/protocol/format/text.rb +48 -0
  69. data/lib/textus/protocol/format/yaml.rb +105 -0
  70. data/lib/textus/protocol/format.rb +73 -0
  71. data/lib/textus/{store → protocol}/freshness/ttl_evaluator.rb +7 -3
  72. data/lib/textus/protocol/gate.rb +61 -0
  73. data/lib/textus/protocol/handlers/command_context.rb +19 -0
  74. data/lib/textus/protocol/handlers/concern.rb +15 -0
  75. data/lib/textus/protocol/handlers/ops/audit_filter.rb +37 -0
  76. data/lib/textus/protocol/handlers/ops/bulk_planner.rb +38 -0
  77. data/lib/textus/protocol/handlers/ops/data_mv.rb +49 -0
  78. data/lib/textus/protocol/handlers/ops/git_blame.rb +65 -0
  79. data/lib/textus/protocol/handlers/ops/pulse_coordinator.rb +37 -0
  80. data/lib/textus/protocol/handlers/ops/source_expander.rb +41 -0
  81. data/lib/textus/protocol/handlers/query_context.rb +41 -0
  82. data/lib/textus/protocol/handlers/read.rb +67 -0
  83. data/lib/textus/protocol/handlers/system.rb +93 -0
  84. data/lib/textus/protocol/handlers/write.rb +57 -0
  85. data/lib/textus/protocol/init.rb +143 -0
  86. data/lib/textus/protocol/key/grammar.rb +35 -0
  87. data/lib/textus/protocol/key/matching.rb +23 -0
  88. data/lib/textus/protocol/key/path.rb +23 -0
  89. data/lib/textus/protocol/key.rb +25 -0
  90. data/lib/textus/protocol/lane.rb +12 -0
  91. data/lib/textus/{store → protocol}/layout.rb +16 -22
  92. data/lib/textus/protocol/links/resolver.rb +33 -0
  93. data/lib/textus/protocol/links/uri_rewriter.rb +30 -0
  94. data/lib/textus/protocol/links.rb +10 -0
  95. data/lib/textus/protocol/manifest/data.rb +90 -0
  96. data/lib/textus/protocol/manifest/entry/base.rb +95 -0
  97. data/lib/textus/protocol/manifest/entry/leaf.rb +19 -0
  98. data/lib/textus/protocol/manifest/entry/nested.rb +19 -0
  99. data/lib/textus/protocol/manifest/entry/parser.rb +71 -0
  100. data/lib/textus/protocol/manifest/entry/publish.rb +209 -0
  101. data/lib/textus/protocol/manifest/entry.rb +10 -0
  102. data/lib/textus/protocol/manifest/policy/publish_target.rb +36 -0
  103. data/lib/textus/protocol/manifest/policy.rb +69 -0
  104. data/lib/textus/protocol/manifest/resolver.rb +156 -0
  105. data/lib/textus/protocol/manifest/schema/contract.rb +53 -0
  106. data/lib/textus/protocol/manifest/schema/validator.rb +52 -0
  107. data/lib/textus/protocol/manifest/schema.rb +40 -0
  108. data/lib/textus/protocol/manifest.rb +62 -0
  109. data/lib/textus/protocol/produce/render/context.rb +43 -0
  110. data/lib/textus/protocol/produce/render.rb +44 -0
  111. data/lib/textus/protocol/schema/registry.rb +44 -0
  112. data/lib/textus/protocol/schema/tools.rb +104 -0
  113. data/lib/textus/protocol/schema.rb +117 -0
  114. data/lib/textus/protocol/session/bridge.rb +11 -0
  115. data/lib/textus/protocol/session/context.rb +5 -0
  116. data/lib/textus/protocol/session.rb +87 -0
  117. data/lib/textus/protocol/store_engine/delete.rb +37 -0
  118. data/lib/textus/protocol/store_engine/move.rb +109 -0
  119. data/lib/textus/protocol/store_engine/pipeline.rb +61 -0
  120. data/lib/textus/protocol/store_engine/put.rb +104 -0
  121. data/lib/textus/protocol/store_engine.rb +86 -0
  122. data/lib/textus/protocol/v4.rb +25 -0
  123. data/lib/textus/protocol/verb_registry/arg_spec.rb +19 -0
  124. data/lib/textus/protocol/verb_registry/core_verbs.rb +164 -0
  125. data/lib/textus/protocol/verb_registry/verb_spec.rb +42 -0
  126. data/lib/textus/protocol/verb_registry.rb +42 -0
  127. data/lib/textus/surface/cli/group/{rule.rb → web.rb} +2 -2
  128. data/lib/textus/surface/cli/runner.rb +22 -21
  129. data/lib/textus/surface/cli/sources.rb +1 -3
  130. data/lib/textus/surface/cli/verb/get.rb +2 -7
  131. data/lib/textus/surface/cli/verb/init.rb +1 -1
  132. data/lib/textus/surface/cli/verb/put.rb +7 -7
  133. data/lib/textus/surface/cli/verb/web_serve.rb +22 -0
  134. data/lib/textus/surface/cli.rb +25 -6
  135. data/lib/textus/surface/dispatch.rb +17 -0
  136. data/lib/textus/surface/mcp/adapters/mcp_adapter.rb +28 -0
  137. data/lib/textus/surface/mcp/catalog.rb +12 -12
  138. data/lib/textus/surface/mcp/cursor_manager.rb +22 -0
  139. data/lib/textus/surface/mcp/drift_annotation.rb +19 -0
  140. data/lib/textus/surface/mcp/server.rb +35 -24
  141. data/lib/textus/surface/watcher.rb +18 -12
  142. data/lib/textus/surface/web.rb +50 -0
  143. data/lib/textus/value/call.rb +1 -1
  144. data/lib/textus/value/etag.rb +7 -7
  145. data/lib/textus/version.rb +1 -1
  146. data/lib/textus/workflow/consumer.rb +39 -0
  147. data/lib/textus/workflow/dsl/lifecycle.rb +32 -0
  148. data/lib/textus/workflow/dsl/pattern_matcher.rb +20 -0
  149. data/lib/textus/workflow/dsl/step_builder.rb +56 -0
  150. data/lib/textus/workflow/dsl.rb +111 -21
  151. data/lib/textus/workflow/event_emitter.rb +21 -0
  152. data/lib/textus/workflow/publisher.rb +59 -0
  153. data/lib/textus/workflow/registry.rb +31 -8
  154. data/lib/textus/workflow/runner.rb +25 -53
  155. data/lib/textus/workflow/scheduler.rb +45 -0
  156. data/lib/textus/workflow/step_executor.rb +78 -0
  157. data/lib/textus/workflow/step_helpers.rb +102 -0
  158. data/lib/textus/workflow.rb +2 -5
  159. data/lib/textus.rb +16 -16
  160. metadata +169 -185
  161. data/SPEC.md +0 -909
  162. data/docs/architecture/README.md +0 -273
  163. data/docs/reference/conventions.md +0 -141
  164. data/lib/textus/boot.rb +0 -181
  165. data/lib/textus/dispatch/contracts.rb +0 -63
  166. data/lib/textus/dispatch/handler_registry.rb +0 -21
  167. data/lib/textus/dispatch/middleware/audit_index.rb +0 -51
  168. data/lib/textus/dispatch/middleware/auth.rb +0 -40
  169. data/lib/textus/dispatch/middleware/base.rb +0 -26
  170. data/lib/textus/dispatch/middleware/binder.rb +0 -20
  171. data/lib/textus/dispatch/middleware/cascade.rb +0 -53
  172. data/lib/textus/dispatch/pipeline.rb +0 -35
  173. data/lib/textus/doctor/check/audit_log.rb +0 -34
  174. data/lib/textus/doctor/check/generator_drift.rb +0 -29
  175. data/lib/textus/doctor/check/illegal_keys.rb +0 -60
  176. data/lib/textus/doctor/check/manifest_files.rb +0 -25
  177. data/lib/textus/doctor/check/orphaned_publish_targets.rb +0 -35
  178. data/lib/textus/doctor/check/proposal_targets.rb +0 -45
  179. data/lib/textus/doctor/check/protocol_version.rb +0 -47
  180. data/lib/textus/doctor/check/publish_tree_index_overlap.rb +0 -48
  181. data/lib/textus/doctor/check/raw_asset_paths.rb +0 -50
  182. data/lib/textus/doctor/check/rule_ambiguity.rb +0 -49
  183. data/lib/textus/doctor/check/schema_parse_error.rb +0 -28
  184. data/lib/textus/doctor/check/schema_violations.rb +0 -28
  185. data/lib/textus/doctor/check/schemas.rb +0 -26
  186. data/lib/textus/doctor/check/scratchpad_sources.rb +0 -55
  187. data/lib/textus/doctor/check/sentinels.rb +0 -60
  188. data/lib/textus/doctor/check/stale_reviewed_stamp.rb +0 -54
  189. data/lib/textus/doctor/check/templates.rb +0 -29
  190. data/lib/textus/doctor/check/unowned_schema_fields.rb +0 -40
  191. data/lib/textus/doctor/check.rb +0 -43
  192. data/lib/textus/doctor/validator.rb +0 -93
  193. data/lib/textus/doctor.rb +0 -54
  194. data/lib/textus/format/base.rb +0 -77
  195. data/lib/textus/format/json.rb +0 -81
  196. data/lib/textus/format/markdown.rb +0 -59
  197. data/lib/textus/format/text.rb +0 -55
  198. data/lib/textus/format/yaml.rb +0 -104
  199. data/lib/textus/format.rb +0 -61
  200. data/lib/textus/handlers/maintenance/boot_store.rb +0 -15
  201. data/lib/textus/handlers/maintenance/doctor_store.rb +0 -15
  202. data/lib/textus/handlers/maintenance/drain_store.rb +0 -21
  203. data/lib/textus/handlers/maintenance/ingest_entry.rb +0 -159
  204. data/lib/textus/handlers/maintenance/jobs_action.rb +0 -21
  205. data/lib/textus/handlers/maintenance/published_entries.rb +0 -17
  206. data/lib/textus/handlers/maintenance/rule_explain.rb +0 -77
  207. data/lib/textus/handlers/maintenance/rule_lint.rb +0 -54
  208. data/lib/textus/handlers/maintenance/rule_list.rb +0 -32
  209. data/lib/textus/handlers/maintenance/schema_envelope.rb +0 -19
  210. data/lib/textus/handlers/read/audit_entries.rb +0 -48
  211. data/lib/textus/handlers/read/blame_entry.rb +0 -71
  212. data/lib/textus/handlers/read/deps_entry.rb +0 -17
  213. data/lib/textus/handlers/read/get_entry.rb +0 -68
  214. data/lib/textus/handlers/read/list_keys.rb +0 -36
  215. data/lib/textus/handlers/read/pulse_entries.rb +0 -66
  216. data/lib/textus/handlers/read/rdeps_entry.rb +0 -21
  217. data/lib/textus/handlers/read/uid_entry.rb +0 -18
  218. data/lib/textus/handlers/read/where_entry.rb +0 -18
  219. data/lib/textus/handlers/write/accept_proposal.rb +0 -39
  220. data/lib/textus/handlers/write/data_mv.rb +0 -55
  221. data/lib/textus/handlers/write/delete_key.rb +0 -17
  222. data/lib/textus/handlers/write/enqueue_job.rb +0 -27
  223. data/lib/textus/handlers/write/key_delete_prefix.rb +0 -32
  224. data/lib/textus/handlers/write/key_mv_prefix.rb +0 -45
  225. data/lib/textus/handlers/write/move_key.rb +0 -80
  226. data/lib/textus/handlers/write/propose_entry.rb +0 -29
  227. data/lib/textus/handlers/write/put_entry.rb +0 -29
  228. data/lib/textus/handlers/write/reject_proposal.rb +0 -29
  229. data/lib/textus/init.rb +0 -157
  230. data/lib/textus/jobs.rb +0 -9
  231. data/lib/textus/key/distance.rb +0 -55
  232. data/lib/textus/key/grammar.rb +0 -33
  233. data/lib/textus/key/matching.rb +0 -24
  234. data/lib/textus/key/path.rb +0 -28
  235. data/lib/textus/manifest/capabilities.rb +0 -29
  236. data/lib/textus/manifest/data.rb +0 -102
  237. data/lib/textus/manifest/entry/base.rb +0 -105
  238. data/lib/textus/manifest/entry/ignore_matcher.rb +0 -46
  239. data/lib/textus/manifest/entry/leaf.rb +0 -17
  240. data/lib/textus/manifest/entry/nested.rb +0 -37
  241. data/lib/textus/manifest/entry/parser.rb +0 -99
  242. data/lib/textus/manifest/entry/produced.rb +0 -36
  243. data/lib/textus/manifest/entry/publish/mode.rb +0 -45
  244. data/lib/textus/manifest/entry/publish/none.rb +0 -14
  245. data/lib/textus/manifest/entry/publish/subtree_mirror.rb +0 -73
  246. data/lib/textus/manifest/entry/publish/template.rb +0 -16
  247. data/lib/textus/manifest/entry/publish/to_paths.rb +0 -76
  248. data/lib/textus/manifest/entry/publish/tree.rb +0 -50
  249. data/lib/textus/manifest/entry/publish.rb +0 -45
  250. data/lib/textus/manifest/entry/validators/format_matrix.rb +0 -21
  251. data/lib/textus/manifest/entry/validators/ignore.rb +0 -28
  252. data/lib/textus/manifest/entry/validators/publish.rb +0 -30
  253. data/lib/textus/manifest/entry/validators.rb +0 -18
  254. data/lib/textus/manifest/entry.rb +0 -8
  255. data/lib/textus/manifest/policy/matcher.rb +0 -51
  256. data/lib/textus/manifest/policy/predicates/author_held.rb +0 -22
  257. data/lib/textus/manifest/policy/predicates/etag_match.rb +0 -18
  258. data/lib/textus/manifest/policy/predicates/fresh_within.rb +0 -13
  259. data/lib/textus/manifest/policy/predicates/lane_deletable_by.rb +0 -31
  260. data/lib/textus/manifest/policy/predicates/lane_writable_by.rb +0 -23
  261. data/lib/textus/manifest/policy/predicates/raw_lane_ingest_only.rb +0 -25
  262. data/lib/textus/manifest/policy/predicates/raw_write_once.rb +0 -24
  263. data/lib/textus/manifest/policy/predicates/schema_valid.rb +0 -41
  264. data/lib/textus/manifest/policy/predicates/target_is_canon.rb +0 -20
  265. data/lib/textus/manifest/policy/predicates.rb +0 -54
  266. data/lib/textus/manifest/policy/publish_target.rb +0 -34
  267. data/lib/textus/manifest/policy/react.rb +0 -30
  268. data/lib/textus/manifest/policy/retention.rb +0 -29
  269. data/lib/textus/manifest/policy/source.rb +0 -30
  270. data/lib/textus/manifest/policy.rb +0 -95
  271. data/lib/textus/manifest/resolver.rb +0 -118
  272. data/lib/textus/manifest/rules.rb +0 -86
  273. data/lib/textus/manifest/schema/contract.rb +0 -61
  274. data/lib/textus/manifest/schema/keys.rb +0 -90
  275. data/lib/textus/manifest/schema/semantics/cross_field.rb +0 -53
  276. data/lib/textus/manifest/schema/semantics/invariants.rb +0 -125
  277. data/lib/textus/manifest/schema/semantics/migration.rb +0 -83
  278. data/lib/textus/manifest/schema/semantics.rb +0 -27
  279. data/lib/textus/manifest/schema/validator.rb +0 -48
  280. data/lib/textus/manifest/schema/vocabulary.rb +0 -25
  281. data/lib/textus/manifest/schema.rb +0 -30
  282. data/lib/textus/manifest.rb +0 -65
  283. data/lib/textus/orchestration.rb +0 -55
  284. data/lib/textus/port/audit_log.rb +0 -268
  285. data/lib/textus/port/build_lock.rb +0 -64
  286. data/lib/textus/port/clock.rb +0 -10
  287. data/lib/textus/port/publisher.rb +0 -60
  288. data/lib/textus/port/sentinel.rb +0 -25
  289. data/lib/textus/port/sentinel_store.rb +0 -83
  290. data/lib/textus/port/storage/file_stat.rb +0 -19
  291. data/lib/textus/port/storage/file_store.rb +0 -49
  292. data/lib/textus/port/storage/interface.rb +0 -17
  293. data/lib/textus/port/store.rb +0 -149
  294. data/lib/textus/port/watcher_lock.rb +0 -48
  295. data/lib/textus/produce/engine.rb +0 -40
  296. data/lib/textus/produce/publisher.rb +0 -21
  297. data/lib/textus/produce/render.rb +0 -20
  298. data/lib/textus/schema/registry.rb +0 -42
  299. data/lib/textus/schema/tools.rb +0 -108
  300. data/lib/textus/schema.rb +0 -100
  301. data/lib/textus/store/container.rb +0 -173
  302. data/lib/textus/store/cursor.rb +0 -26
  303. data/lib/textus/store/entry/reader.rb +0 -47
  304. data/lib/textus/store/entry/writer.rb +0 -219
  305. data/lib/textus/store/envelope/meta.rb +0 -61
  306. data/lib/textus/store/freshness/drift_detector.rb +0 -93
  307. data/lib/textus/store/freshness/evaluator.rb +0 -20
  308. data/lib/textus/store/freshness/verdict.rb +0 -24
  309. data/lib/textus/store/freshness.rb +0 -8
  310. data/lib/textus/store/index/builder.rb +0 -76
  311. data/lib/textus/store/index/lookup.rb +0 -60
  312. data/lib/textus/store/jobs/base.rb +0 -13
  313. data/lib/textus/store/jobs/index.rb +0 -15
  314. data/lib/textus/store/jobs/materialize.rb +0 -15
  315. data/lib/textus/store/jobs/plan.rb +0 -11
  316. data/lib/textus/store/jobs/planner.rb +0 -124
  317. data/lib/textus/store/jobs/queue.rb +0 -162
  318. data/lib/textus/store/jobs/registry.rb +0 -19
  319. data/lib/textus/store/jobs/retention/base.rb +0 -52
  320. data/lib/textus/store/jobs/retention/sweep.rb +0 -55
  321. data/lib/textus/store/jobs/retention.rb +0 -8
  322. data/lib/textus/store/jobs/sweep.rb +0 -21
  323. data/lib/textus/store/jobs/worker.rb +0 -64
  324. data/lib/textus/store.rb +0 -122
  325. data/lib/textus/surface/cli/verb/doctor.rb +0 -20
  326. data/lib/textus/surface/cli/verb/schema_diff.rb +0 -17
  327. data/lib/textus/surface/cli/verb/schema_init.rb +0 -21
  328. data/lib/textus/surface/cli/verb/schema_migrate.rb +0 -21
  329. data/lib/textus/surface/mcp/projector.rb +0 -20
  330. data/lib/textus/surface/mcp.rb +0 -8
  331. data/lib/textus/value/command.rb +0 -16
  332. data/lib/textus/value/envelope.rb +0 -90
  333. data/lib/textus/value/result.rb +0 -46
  334. data/lib/textus/verb_registry.rb +0 -417
  335. data/lib/textus/workflow/collector.rb +0 -27
  336. data/lib/textus/workflow/context.rb +0 -5
  337. data/lib/textus/workflow/loader.rb +0 -17
  338. data/lib/textus/workflow/pattern.rb +0 -18
@@ -1,417 +0,0 @@
1
- module Textus
2
- module VerbRegistry
3
- ArgSpec = Data.define(
4
- :name, :type, :required, :positional, :session_default,
5
- :description, :wire_name, :default, :source, :coerce, :cli_default
6
- ) do
7
- def wire = wire_name || name
8
-
9
- # rubocop:disable Metrics/ParameterLists
10
- def self.arg(name:, type: String, required: false, positional: false,
11
- session_default: nil, description: nil, wire_name: nil,
12
- default: nil, source: nil, coerce: nil, cli_default: nil)
13
- new(name:, type:, required:, positional:, session_default:,
14
- description:, wire_name:, default:, source:, coerce:, cli_default:)
15
- end
16
- # rubocop:enable Metrics/ParameterLists
17
- end
18
-
19
- TYPE_MAP = {
20
- String => "string", Integer => "integer", Hash => "object",
21
- Array => "array", :boolean => "boolean"
22
- }.freeze
23
-
24
- VerbSpec = Data.define(:verb, :summary, :args, :surfaces, :views, :cli, :cli_stdin, :category) do
25
- def mcp? = surfaces.include?(:mcp)
26
- def cli? = surfaces.include?(:cli)
27
- def view(surface = :default) = views[surface] || views.fetch(:default)
28
- def cli_path = cli || verb.to_s
29
- def cli_words = cli_path.split
30
- def cli_group = cli_words.size > 1 ? cli_words.first : nil
31
- def cli_leaf = cli_words.last
32
- def required_args = args.select(&:required)
33
- def read? = category == :read
34
- def write? = category == :write
35
- def maintenance? = category == :maintenance
36
-
37
- def input_schema
38
- props = args.to_h do |a|
39
- json_type = VerbRegistry::TYPE_MAP[a.type] || "string"
40
- h = { "type" => json_type }
41
- h["description"] = a.description if a.description
42
- [a.wire.to_s, h]
43
- end
44
- { type: "object", properties: props, required: required_args.map { |a| a.wire.to_s } }
45
- end
46
- end
47
-
48
- VERBS = {}
49
- POSITIONAL = {}
50
-
51
- def self.register(spec)
52
- VERBS[spec.verb] = spec
53
- POSITIONAL[spec.verb] = spec.args.select(&:positional).map(&:name)
54
- end
55
-
56
- def self.for(verb) = VERBS[verb]
57
- def self.positional_for(verb) = POSITIONAL[verb] || []
58
- def self.summary_for(verb) = VERBS[verb]&.summary
59
- def self.registered = VERBS.values
60
- def self.contract_class_for(verb) = VERB_TO_CONTRACT[verb]
61
-
62
- VERB_TO_CONTRACT = {
63
- get: Dispatch::Contracts::GetEntry,
64
- put: Dispatch::Contracts::PutEntry,
65
- list: Dispatch::Contracts::ListKeys,
66
- key_delete: Dispatch::Contracts::DeleteKey,
67
- key_mv: Dispatch::Contracts::MoveKey,
68
- propose: Dispatch::Contracts::ProposeEntry,
69
- accept: Dispatch::Contracts::AcceptProposal,
70
- reject: Dispatch::Contracts::RejectProposal,
71
- enqueue: Dispatch::Contracts::EnqueueJob,
72
- audit: Dispatch::Contracts::AuditEntries,
73
- pulse: Dispatch::Contracts::PulseEntries,
74
- blame: Dispatch::Contracts::BlameEntry,
75
- where: Dispatch::Contracts::WhereEntry,
76
- uid: Dispatch::Contracts::UidEntry,
77
- deps: Dispatch::Contracts::DepsEntry,
78
- rdeps: Dispatch::Contracts::RdepsEntry,
79
- boot: Dispatch::Contracts::BootStore,
80
- doctor: Dispatch::Contracts::DoctorStore,
81
- published: Dispatch::Contracts::PublishedEntries,
82
- rule_explain: Dispatch::Contracts::RuleExplain,
83
- rule_list: Dispatch::Contracts::RuleList,
84
- schema_show: Dispatch::Contracts::SchemaEnvelope,
85
- drain: Dispatch::Contracts::DrainStore,
86
- ingest: Dispatch::Contracts::IngestEntry,
87
- jobs: Dispatch::Contracts::JobsAction,
88
- rule_lint: Dispatch::Contracts::RuleLint,
89
- data_mv: Dispatch::Contracts::DataMv,
90
- key_mv_prefix: Dispatch::Contracts::KeyMvPrefix,
91
- key_delete_prefix: Dispatch::Contracts::KeyDeletePrefix,
92
- }.freeze
93
-
94
- CONTRACT_TO_VERB = VERB_TO_CONTRACT.invert.freeze
95
-
96
- private_constant :VERB_TO_CONTRACT, :CONTRACT_TO_VERB
97
-
98
- def self.contract_to_verb(klass)
99
- CONTRACT_TO_VERB[klass] || klass.name.split("::").last.gsub(/([a-z])([A-Z])/, '\1_\2').downcase
100
- end
101
-
102
- def self.contract_to_verb!(klass)
103
- CONTRACT_TO_VERB.fetch(klass) { raise "unknown contract class: #{klass}" }
104
- end
105
-
106
- identity = ->(v, _) { v }
107
-
108
- # ── get ──────────────────────────────────────────────
109
- register VerbSpec.new(
110
- :get, "Read one entry — on-disk read with freshness verdict.",
111
- [ArgSpec.arg(name: :key, required: true, positional: true,
112
- description: "dotted entry key to read, e.g. 'knowledge.project'")],
113
- %i[cli mcp], { default: ->(v, _i) { v&.to_h_for_wire } }, nil, nil, :read
114
- )
115
-
116
- # ── put ──────────────────────────────────────────────
117
- register VerbSpec.new(
118
- :put, "Create or update an entry. Schema-validated. Returns {uid, etag}.",
119
- [ArgSpec.arg(name: :key, required: true, positional: true,
120
- description: "dotted entry key, e.g. 'knowledge.project'; must resolve to a zone the role may write"),
121
- ArgSpec.arg(name: :meta, type: Hash, wire_name: :_meta,
122
- description: "frontmatter; reads back as `_meta`. Schema-validated — call `schema KEY` first"),
123
- ArgSpec.arg(name: :body,
124
- description: "markdown/text payload for md entries; use `content` for json/yaml"),
125
- ArgSpec.arg(name: :content, type: Hash,
126
- description: "structured payload for json/yaml-format entries; omit (use `body`) for markdown entries"),
127
- ArgSpec.arg(name: :if_etag,
128
- description: "optimistic-concurrency guard; write rejected if entry changed since")],
129
- %i[cli mcp], { default: ->(env, _) { { "uid" => env.uid, "etag" => env.etag } } }, nil, nil, :write
130
- )
131
-
132
- # ── list ─────────────────────────────────────────────
133
- register VerbSpec.new(
134
- :list, "List keys filtered by lane and/or prefix.",
135
- [ArgSpec.arg(name: :prefix,
136
- description: "restrict to keys starting with this dotted prefix, e.g. 'knowledge.runbooks'"),
137
- ArgSpec.arg(name: :lane,
138
- description: "restrict to one lane by name (see `boot` lanes)"),
139
- ArgSpec.arg(name: :q,
140
- description: "full-text search query over entry content (FTS5)"),
141
- ArgSpec.arg(name: :schema,
142
- description: "filter to entries whose schema matches this name")],
143
- %i[cli mcp], { cli: ->(rows, _) { { "entries" => rows } }, default: identity }, nil, nil, :read
144
- )
145
-
146
- # ── delete ───────────────────────────────────────────
147
- register VerbSpec.new(
148
- :key_delete, "Delete one entry by key. Returns {ok, key, deleted}.",
149
- [ArgSpec.arg(name: :key, required: true, positional: true,
150
- description: "dotted entry key to delete"),
151
- ArgSpec.arg(name: :if_etag,
152
- description: "optimistic-concurrency guard: etag you last read")],
153
- %i[cli mcp], { default: identity }, "key delete", nil, :write
154
- )
155
-
156
- # ── move ─────────────────────────────────────────────
157
- register VerbSpec.new(
158
- :key_mv, "Rename one entry (same zone + format). Refuses if target exists.",
159
- [ArgSpec.arg(name: :old_key, required: true, positional: true, description: "current dotted key"),
160
- ArgSpec.arg(name: :new_key, required: true, positional: true,
161
- description: "new dotted key (same zone and format)"),
162
- ArgSpec.arg(name: :dry_run, type: :boolean,
163
- description: "when true, returns planned move without applying")],
164
- %i[cli mcp], { default: identity }, "key mv", nil, :write
165
- )
166
-
167
- # ── propose ──────────────────────────────────────────
168
- register VerbSpec.new(
169
- :propose, "Write a proposal to the role's propose_lane. Auto-prefixes the key.",
170
- [ArgSpec.arg(name: :key, required: true, positional: true,
171
- description: "key relative to propose_lane, e.g. 'decisions.feature-x'"),
172
- ArgSpec.arg(name: :meta, type: Hash, wire_name: :_meta,
173
- description: "frontmatter. Include a 'proposal:' block naming the target_key"),
174
- ArgSpec.arg(name: :body,
175
- description: "markdown/text payload for markdown-format entries"),
176
- ArgSpec.arg(name: :content, type: Hash,
177
- description: "structured payload for json/yaml-format entries")],
178
- %i[cli mcp], { default: ->(env, _) { env.to_h_for_wire } }, nil, :json, :write
179
- )
180
-
181
- # ── accept ───────────────────────────────────────────
182
- register VerbSpec.new(
183
- :accept, "Apply a queued proposal to its target zone; requires author.",
184
- [ArgSpec.arg(name: :pending_key, required: true, positional: true,
185
- description: "the queued proposal's key")],
186
- %i[cli mcp], { default: identity }, "accept", nil, :write
187
- )
188
-
189
- # ── reject ───────────────────────────────────────────
190
- register VerbSpec.new(
191
- :reject, "Discard a queued proposal without applying it.",
192
- [ArgSpec.arg(name: :pending_key, required: true, positional: true,
193
- description: "the queued proposal's key")],
194
- %i[cli mcp], { default: identity }, "reject", nil, :write
195
- )
196
-
197
- # ── enqueue ──────────────────────────────────────────
198
- register VerbSpec.new(
199
- :enqueue, "Push a registered job type onto the convergence queue.",
200
- [ArgSpec.arg(name: :type, required: true, positional: true,
201
- description: "registered job type (e.g. materialize, re-pull, sweep)"),
202
- ArgSpec.arg(name: :args, type: Hash, default: {},
203
- description: "type-specific arguments (e.g. { key: ... })")],
204
- %i[cli mcp], { default: identity }, "enqueue", nil, :write
205
- )
206
-
207
- # ── ingest ───────────────────────────────────────────
208
- register VerbSpec.new(
209
- :ingest, "Capture external source material into the raw lane. Write-once.",
210
- [ArgSpec.arg(name: :kind, required: true, positional: true,
211
- description: "source kind: url | file | asset"),
212
- ArgSpec.arg(name: :slug, required: true,
213
- description: "human slug for the key suffix (kebab-case)"),
214
- ArgSpec.arg(name: :url, description: "remote URL (required when kind=url)"),
215
- ArgSpec.arg(name: :path,
216
- description: "local file path (required when kind=file or kind=asset)"),
217
- ArgSpec.arg(name: :lane,
218
- description: "asset group subdirectory (required when kind=asset)"),
219
- ArgSpec.arg(name: :label, description: "human label stored in source.label")],
220
- %i[cli mcp], { default: ->(env, _) { { "key" => env.key, "uid" => env.uid, "etag" => env.etag } } }, nil, nil, :write
221
- )
222
-
223
- # ── where ────────────────────────────────────────────
224
- register VerbSpec.new(
225
- :where, "Resolve a key to its zone, owner, and path without reading the body.",
226
- [ArgSpec.arg(name: :key, required: true, positional: true, description: "dotted key to locate")],
227
- %i[cli mcp], { default: identity }, nil, nil, :read
228
- )
229
-
230
- # ── uid ──────────────────────────────────────────────
231
- register VerbSpec.new(
232
- :uid, "Return the stable UID of an entry without reading its body.",
233
- [ArgSpec.arg(name: :key, required: true, positional: true, description: "entry key")],
234
- [:cli], { cli: ->(uid, inputs) { { "key" => inputs[:key], "uid" => uid } }, default: identity }, "key uid", nil, :read
235
- )
236
-
237
- # ── blame ────────────────────────────────────────────
238
- register VerbSpec.new(
239
- :blame, "Annotate audit rows with the git commit that introduced each file state.",
240
- [ArgSpec.arg(name: :key, required: true, positional: true, description: "entry key to blame"),
241
- ArgSpec.arg(name: :limit, type: Integer,
242
- description: "maximum number of audit rows to return")],
243
- [:cli], {
244
- cli: ->(rows, inputs) { { "verb" => "blame", "key" => inputs[:key], "rows" => rows } },
245
- default: identity,
246
- }, "blame", nil, :read
247
- )
248
-
249
- # ── audit ────────────────────────────────────────────
250
- register VerbSpec.new(
251
- :audit, "Query the audit log with optional filters.",
252
- [ArgSpec.arg(name: :key, description: "filter to rows for this key"),
253
- ArgSpec.arg(name: :lane, description: "filter to keys in this lane"),
254
- ArgSpec.arg(name: :role, description: "filter to rows written under this role"),
255
- ArgSpec.arg(name: :verb, description: "filter to rows for this verb"),
256
- ArgSpec.arg(name: :since,
257
- description: "ISO-8601 timestamp or relative offset (e.g. 1h, 30m)"),
258
- ArgSpec.arg(name: :seq_since, type: Integer,
259
- description: "return rows with seq > this cursor value"),
260
- ArgSpec.arg(name: :correlation_id,
261
- description: "filter to rows with this correlation_id"),
262
- ArgSpec.arg(name: :limit, type: Integer,
263
- description: "maximum number of rows to return")],
264
- [:cli], { cli: ->(rows, _) { { "verb" => "audit", "rows" => rows } }, default: identity }, "audit", nil, :read
265
- )
266
-
267
- # ── deps ─────────────────────────────────────────────
268
- register VerbSpec.new(
269
- :deps, "List the keys a derived entry depends on.",
270
- [ArgSpec.arg(name: :key, required: true, positional: true,
271
- description: "dotted key of the derived entry whose source keys you want")],
272
- %i[cli mcp], { default: identity }, nil, nil, :read
273
- )
274
-
275
- # ── rdeps ────────────────────────────────────────────
276
- register VerbSpec.new(
277
- :rdeps, "List the derived entries that depend on a key (reverse deps).",
278
- [ArgSpec.arg(name: :key, required: true, positional: true,
279
- description: "dotted key whose dependents you want")],
280
- %i[cli mcp], { default: identity }, nil, nil, :read
281
- )
282
-
283
- # ── pulse ────────────────────────────────────────────
284
- register VerbSpec.new(
285
- :pulse, "Delta since cursor — changed entries, pending proposals, index freshness.",
286
- [ArgSpec.arg(name: :since, type: Integer, session_default: :cursor,
287
- description: "audit seq to diff from; defaults to the session cursor")],
288
- %i[cli mcp], { default: identity }, nil, nil, :read
289
- )
290
-
291
- # ── rule_explain ─────────────────────────────────────
292
- register VerbSpec.new(
293
- :rule_explain, "Effective rules for a key. Lean by default; detail: true adds matched blocks.",
294
- [ArgSpec.arg(name: :key, required: true, positional: true,
295
- description: "dotted key whose effective rules you want"),
296
- ArgSpec.arg(name: :detail, type: :boolean,
297
- description: "detail: true adds matched blocks + guard predicates")],
298
- %i[cli mcp], {
299
- cli: ->(r, _) { { "verb" => "rule_explain" }.merge(r.transform_keys(&:to_s)) },
300
- default: identity,
301
- }, "rule explain", nil, :read
302
- )
303
-
304
- # ── rule_list ────────────────────────────────────────
305
- register VerbSpec.new(
306
- :rule_list, "List every rule block in the manifest.",
307
- [], [:cli], { cli: ->(p, _) { { "verb" => "rule_list", "policies" => p } }, default: identity }, "rule list", nil, :read
308
- )
309
-
310
- # ── published ────────────────────────────────────────
311
- register VerbSpec.new(
312
- :published, "List all entries that declare a publish target.",
313
- [], [:cli], { default: identity }, "published", nil, :read
314
- )
315
-
316
- # ── schema_show ──────────────────────────────────────
317
- register VerbSpec.new(
318
- :schema_show, "Return the schema (field shape) for an entry's family.",
319
- [ArgSpec.arg(name: :key, required: true, positional: true,
320
- description: "any key in the family whose schema you want")],
321
- %i[cli mcp], { default: identity }, "schema show", nil, :read
322
- )
323
-
324
- # ── doctor ───────────────────────────────────────────
325
- register VerbSpec.new(
326
- :doctor, "Run health checks on the textus store.",
327
- [ArgSpec.arg(name: :checks, type: Array,
328
- description: "subset of check names to run (default: all)")],
329
- [:cli], { default: identity }, "doctor", nil, :read
330
- )
331
-
332
- # ── boot ─────────────────────────────────────────────
333
- register VerbSpec.new(
334
- :boot, "Return the orientation contract: lanes, agent_quickstart, agent_protocol.",
335
- [], %i[cli mcp], { default: identity }, nil, nil, :read
336
- )
337
-
338
- # ── jobs ─────────────────────────────────────────────
339
- register VerbSpec.new(
340
- :jobs, "List queued jobs by state; retry a dead-lettered job or purge.",
341
- [ArgSpec.arg(name: :state, default: "ready",
342
- description: "ready|leased|done|failed"),
343
- ArgSpec.arg(name: :action, description: "retry|purge (optional)"),
344
- ArgSpec.arg(name: :job_id, description: "job id (required for action=retry)")],
345
- %i[cli mcp], { default: identity }, "jobs", nil, :maintenance
346
- )
347
-
348
- # ── data_mv ──────────────────────────────────────────
349
- register VerbSpec.new(
350
- :data_mv, "Rename a data lane — manifest + files. Refuses if destination exists.",
351
- [ArgSpec.arg(name: :from, required: true, positional: true, description: "current data lane name"),
352
- ArgSpec.arg(name: :to, required: true, positional: true, description: "new data lane name"),
353
- ArgSpec.arg(name: :dry_run, type: :boolean, default: false,
354
- description: "when true, returns planned zone move without applying")],
355
- %i[cli mcp], { default: ->(v, _) { v.to_h } }, "data mv", nil, :write
356
- )
357
-
358
- # ── key_mv_prefix ────────────────────────────────────
359
- register VerbSpec.new(
360
- :key_mv_prefix, "Bulk-rename every leaf key under from_prefix to to_prefix.",
361
- [ArgSpec.arg(name: :from_prefix, required: true, positional: true,
362
- description: "dotted prefix whose leaf keys are renamed"),
363
- ArgSpec.arg(name: :to_prefix, required: true, positional: true,
364
- description: "dotted prefix the keys are renamed to"),
365
- ArgSpec.arg(name: :dry_run, type: :boolean, default: false,
366
- description: "when true, returns planned moves without applying")],
367
- %i[cli mcp], { default: ->(v, _) { v.to_h } }, "key mv-prefix", nil, :write
368
- )
369
-
370
- # ── key_delete_prefix ────────────────────────────────
371
- register VerbSpec.new(
372
- :key_delete_prefix, "Bulk-delete every leaf key under prefix.",
373
- [ArgSpec.arg(name: :prefix, required: true, positional: true,
374
- description: "every leaf key under this dotted prefix is deleted"),
375
- ArgSpec.arg(name: :dry_run, type: :boolean, default: false,
376
- description: "when true, returns keys that would be deleted without deleting")],
377
- %i[cli mcp], { default: ->(v, _) { v.to_h } }, "key delete-prefix", nil, :write
378
- )
379
-
380
- # ── drain ────────────────────────────────────────────
381
- register VerbSpec.new(
382
- :drain, "Seed materialize + sweep jobs then drain the queue to empty.",
383
- [ArgSpec.arg(name: :prefix, description: "restrict to keys under this dotted prefix"),
384
- ArgSpec.arg(name: :lane, description: "restrict to entries in this lane")],
385
- %i[cli mcp], { default: identity }, nil, nil, :maintenance
386
- )
387
-
388
- # ── rule_lint ────────────────────────────────────────
389
- register VerbSpec.new(
390
- :rule_lint, "Diff candidate manifest rules against the live manifest.",
391
- [ArgSpec.arg(name: :candidate_yaml, required: true,
392
- wire_name: :against, source: :file,
393
- description: "path to candidate manifest YAML")],
394
- %i[cli mcp], { default: ->(v, _) { v.to_h } }, "rule lint", nil, :maintenance
395
- )
396
- end
397
- end
398
-
399
- # Generate explicit methods on Store for each registered verb so the API
400
- # is statically discoverable by IDEs and documentation tools.
401
- Textus::Store.class_eval do
402
- Textus::VerbRegistry::VERBS.each do |verb, spec|
403
- positional_names = Textus::VerbRegistry::POSITIONAL[verb] || []
404
- define_method(verb) do |*args, **kwargs|
405
- if args.size > positional_names.size
406
- raise ArgumentError.new("#{verb} accepts #{positional_names.size} positional argument(s) (got #{args.size})")
407
- end
408
-
409
- positional_inputs = positional_names.zip(args).to_h.compact
410
- inputs = positional_inputs.merge(kwargs)
411
- pending = Textus::Dispatch::Binder.command(spec, inputs)
412
- call = Textus::Value::Call.build(role: @role, correlation_id: @correlation_id)
413
- result = @container.pipeline.dispatch(pending, call: call)
414
- Textus::Value::Result.extract(result)
415
- end
416
- end
417
- end
@@ -1,27 +0,0 @@
1
- module Textus
2
- module Workflow
3
- class Collector
4
- @current = nil
5
-
6
- class << self
7
- attr_reader :current
8
- end
9
-
10
- def self.with(collector)
11
- prev = @current
12
- @current = collector
13
- yield
14
- ensure
15
- @current = prev
16
- end
17
-
18
- def initialize(registry)
19
- @registry = registry
20
- end
21
-
22
- def register(defn)
23
- @registry.register(defn)
24
- end
25
- end
26
- end
27
- end
@@ -1,5 +0,0 @@
1
- module Textus
2
- module Workflow
3
- Context = Data.define(:key, :entry, :config, :lane, :container, :call)
4
- end
5
- end
@@ -1,17 +0,0 @@
1
- module Textus
2
- module Workflow
3
- class Loader
4
- def self.load_all(root)
5
- geometry = Textus::Store::Layout.new(root)
6
- registry = Registry.new
7
- return registry unless File.directory?(geometry.workflow_dir)
8
-
9
- collector = Collector.new(registry)
10
- Collector.with(collector) do
11
- Dir.glob(File.join(geometry.workflow_dir, "**", "*.rb")).each { |path| load path }
12
- end
13
- registry
14
- end
15
- end
16
- end
17
- end
@@ -1,18 +0,0 @@
1
- module Textus
2
- module Workflow
3
- module Pattern
4
- def self.match?(pattern, key)
5
- if pattern.end_with?(".**")
6
- prefix = pattern.delete_suffix(".**")
7
- key.start_with?("#{prefix}.")
8
- elsif pattern.end_with?(".*")
9
- prefix = pattern.delete_suffix(".*")
10
- suffix = key.delete_prefix("#{prefix}.")
11
- key != suffix && !suffix.include?(".")
12
- else
13
- key == pattern
14
- end
15
- end
16
- end
17
- end
18
- end