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,20 +1,43 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Textus
2
4
  module Workflow
3
5
  class Registry
4
- def initialize
5
- @definitions = []
6
+ class UnknownWorkflow < KeyError
7
+ end
8
+
9
+ def self.load_all(root)
10
+ geometry = Textus::Protocol::Layout.new(root)
11
+ return [] unless File.directory?(geometry.workflow_dir)
12
+
13
+ Workflow::DSL::Definition.clear_registered
14
+ Dir.glob(File.join(geometry.workflow_dir, "**", "*.rb")).each { |path| load path }
15
+ Workflow::DSL::Definition.definitions
16
+ end
17
+
18
+ attr_reader :definitions
19
+
20
+ def initialize(workflow_definitions)
21
+ @definitions = workflow_definitions
22
+ @by_event = {}
23
+ @by_type = {}
24
+
25
+ workflow_definitions.each do |wf|
26
+ @by_type[wf.type] = wf
27
+ wf.handled_events.each { |ev| (@by_event[ev] ||= []) << wf }
28
+ end
6
29
  end
7
30
 
8
- def register(definition)
9
- @definitions << definition
31
+ def workflows_for(event_type)
32
+ @by_event.fetch(event_type.to_s, [])
10
33
  end
11
34
 
12
- def for(key)
13
- @definitions.find { |d| d.match?(key) }
35
+ def for(workflow_type)
36
+ @by_type[workflow_type.to_s] or raise(UnknownWorkflow.new("Unknown workflow type: #{workflow_type}"))
14
37
  end
15
38
 
16
- def all
17
- @definitions.dup
39
+ def match_for_key(key)
40
+ @definitions.find { |wf| wf.match?(key) }
18
41
  end
19
42
  end
20
43
  end
@@ -1,28 +1,38 @@
1
- require "timeout"
2
-
3
1
  module Textus
4
2
  module Workflow
3
+ class ParallelStepFailed < Textus::Error
4
+ attr_reader :failures
5
+
6
+ def initialize(failures)
7
+ @failures = failures
8
+ msgs = failures.map { |f| "#{f[:step]}: #{f[:error]}" }.join("; ")
9
+ super(:workflow_parallel_step_failed, "parallel step(s) failed: #{msgs}")
10
+ end
11
+ end
12
+
5
13
  class Runner
6
- DEFAULT_TIMEOUT = 30
14
+ Context = Data.define(:key, :entry, :config, :lane, :container, :call) do
15
+ def store_engine = container.store_engine
16
+ def manifest = container.manifest
17
+ def layout = container.layout
18
+ def file_system = container.store.file_system
19
+ def schemas = container.schemas
20
+ def read(key) = store_engine.read(key:)
21
+ def repo_root = File.dirname(container.root)
22
+ def store_root = container.root
23
+ end
7
24
 
8
25
  def initialize(definition, container:, call:)
9
26
  @definition = definition
10
27
  @container = container
11
28
  @call = call
29
+ @publisher = Publisher.new(container: container, call: call)
30
+ @step_executor = StepExecutor.new
12
31
  end
13
32
 
14
33
  def run(key)
15
- ctx = build_context(key)
16
- data = execute_steps(ctx)
17
- publish(key, data, ctx)
18
- data
19
- end
20
-
21
- private
22
-
23
- def build_context(key)
24
34
  res = @container.manifest.resolver.resolve(key)
25
- Context.new(
35
+ ctx = Context.new(
26
36
  key: key,
27
37
  entry: res.entry,
28
38
  config: {}.freeze,
@@ -30,48 +40,10 @@ module Textus
30
40
  container: @container,
31
41
  call: @call,
32
42
  )
33
- end
34
-
35
- def execute_steps(ctx)
36
- data = nil
37
- @definition.steps.each { |step| data = execute_one(step, data, ctx) }
43
+ data = @step_executor.execute(@definition.steps, ctx)
44
+ @publisher.publish(key, data, ctx, @definition) if data
38
45
  data
39
46
  end
40
-
41
- def execute_one(step, data, ctx)
42
- timeout = step.timeout || DEFAULT_TIMEOUT
43
- Timeout.timeout(timeout) { step.callable.call(data, ctx) }
44
- rescue Timeout::Error => e
45
- raise StepFailed.new(step.name, e)
46
- rescue Textus::Error
47
- raise
48
- rescue StandardError => e
49
- raise StepFailed.new(step.name, e)
50
- end
51
-
52
- def publish(key, data, ctx)
53
- blk = @definition.publish_block
54
- return blk.call(data, ctx) if blk && blk != :default
55
-
56
- built_in_publish(key, data, ctx)
57
- end
58
-
59
- def built_in_publish(key, data, ctx)
60
- normalized = Textus::Format.data_to_payload(data, ctx.entry.format)
61
- guard_map = @container.manifest.rules.for(key).guard
62
- rule_preds = guard_map ? Array(guard_map["converge"]) : []
63
- Textus::Manifest::Policy::Predicates.evaluate(
64
- manifest: @container.manifest, schemas: @container.schemas,
65
- action: :converge, actor: @call.role, key: key,
66
- rule_predicates: rule_preds
67
- )
68
- Textus::Store::Entry::Writer.from(container: @container, call: @call).put(
69
- key,
70
- mentry: ctx.entry,
71
- payload: Textus::Value::Payload.new(**normalized),
72
- )
73
- Textus::Produce::Publisher.call(container: @container, call: @call, key: key)
74
- end
75
47
  end
76
48
  end
77
49
  end
@@ -0,0 +1,45 @@
1
+ module Textus
2
+ module Workflow
3
+ class Scheduler
4
+ def initialize(registry:, workflow_queue:, reader:)
5
+ @registry = registry
6
+ @workflow_queue = workflow_queue
7
+ @reader = reader
8
+ end
9
+
10
+ def seed_expired
11
+ seeded = 0
12
+ @registry.definitions.each do |wf|
13
+ next unless wf.ttl && wf.save_to
14
+
15
+ lane = wf.save_to.split(".").first
16
+ next if @workflow_queue.pending?(event_type: "workflow", key: wf.save_to)
17
+
18
+ entry = @reader.call(key: wf.save_to)
19
+ unless entry
20
+ @workflow_queue.push(
21
+ event_type: "workflow", workflow_type: wf.type,
22
+ key: wf.save_to, lane: lane
23
+ )
24
+ seeded += 1
25
+ next
26
+ end
27
+
28
+ meta = entry.respond_to?(:meta) ? entry.meta : (entry["_meta"] || {})
29
+ generated_at = meta["generated_at"]
30
+ next unless generated_at
31
+
32
+ age = Time.now - Time.parse(generated_at)
33
+ next unless age > wf.ttl
34
+
35
+ @workflow_queue.push(
36
+ event_type: "workflow", workflow_type: wf.type,
37
+ key: wf.save_to, lane: lane
38
+ )
39
+ seeded += 1
40
+ end
41
+ seeded
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,78 @@
1
+ require "timeout"
2
+ require "concurrent"
3
+
4
+ module Textus
5
+ module Workflow
6
+ class StepExecutor
7
+ DEFAULT_TIMEOUT = 30
8
+ CONCURRENCY_ADAPTER = Textus::Infra::Adapters::ConcurrencyAdapter.new
9
+
10
+ class StepScope
11
+ include Textus::Workflow::StepHelpers
12
+
13
+ def initialize(data, ctx)
14
+ @data = data
15
+ @ctx = ctx
16
+ end
17
+
18
+ def call(callable)
19
+ instance_exec(@data, @ctx, &callable)
20
+ end
21
+ end
22
+
23
+ def execute(steps, ctx)
24
+ data = nil
25
+ steps.each { |step| data = execute_one(step, data, ctx) }
26
+ data
27
+ end
28
+
29
+ private
30
+
31
+ def execute_one(step, data, ctx)
32
+ case step
33
+ when DSL::Step then execute_single(step, data, ctx)
34
+ when DSL::Parallel then execute_parallel(step, ctx)
35
+ else raise ArgumentError.new("unknown step type: #{step.class}")
36
+ end
37
+ end
38
+
39
+ def execute_single(step, data, ctx)
40
+ scope = StepScope.new(data, ctx)
41
+ timeout = step.timeout || DEFAULT_TIMEOUT
42
+ Timeout.timeout(timeout) { scope.call(step.callable) }
43
+ rescue Timeout::Error => e
44
+ raise StepFailed.new(step.name, e)
45
+ rescue Textus::Error
46
+ raise
47
+ rescue StandardError => e
48
+ raise StepFailed.new(step.name, e)
49
+ end
50
+
51
+ def execute_parallel(parallel, ctx)
52
+ scope = StepScope.new(nil, ctx)
53
+ promises = parallel.steps.map do |step|
54
+ CONCURRENCY_ADAPTER.future do
55
+ timeout = step.timeout || DEFAULT_TIMEOUT
56
+ Timeout.timeout(timeout) { scope.call(step.callable) }
57
+ end
58
+ end
59
+
60
+ results = CONCURRENCY_ADAPTER.zip_futures(*promises).value!
61
+ failures = []
62
+ outputs = {}
63
+ results.each_with_index do |result, i|
64
+ step = parallel.steps[i]
65
+ if result.fulfilled?
66
+ outputs[step.name] = result.value!
67
+ else
68
+ failures << { step: step.name, error: result.reason.message }
69
+ end
70
+ end
71
+
72
+ raise ParallelStepFailed.new(failures) unless failures.empty?
73
+
74
+ outputs
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "net/http"
4
+ require "json"
5
+ require "erb"
6
+
7
+ module Textus
8
+ module Workflow
9
+ module StepHelpers
10
+ def fetch_json(url, headers: {})
11
+ uri = URI(url)
12
+ http = Net::HTTP.new(uri.host, uri.port)
13
+ http.use_ssl = uri.scheme == "https"
14
+ http.open_timeout = 5
15
+ http.read_timeout = 30
16
+
17
+ request = Net::HTTP::Get.new(uri)
18
+ headers.each { |k, v| request[k] = v }
19
+ request["Accept"] ||= "application/json"
20
+
21
+ response = http.request(request)
22
+ raise "HTTP #{response.code} for #{url}" unless response.is_a?(Net::HTTPOK)
23
+
24
+ JSON.parse(response.body)
25
+ end
26
+
27
+ def render_template(name, data)
28
+ path = @container.layout.template_path(name)
29
+ raise "template not found: #{name}" unless File.exist?(path)
30
+
31
+ template = File.read(path)
32
+ ctx = Textus::Protocol::Produce::Render::Context.new(
33
+ container: @container, call: @call,
34
+ key: @ctx&.key, entry: @ctx&.entry
35
+ )
36
+ renderer = Textus::Protocol::Produce::Render.new(template, ctx)
37
+ renderer.bytes_for(data)
38
+ end
39
+
40
+ def merge_hashes(*hashes)
41
+ hashes.reduce({}) { |acc, h| deep_merge(acc, h) }
42
+ end
43
+
44
+ def pluck(source, *keys)
45
+ source.slice(*keys)
46
+ end
47
+
48
+ def check_naming(entries, pattern:, code:, fix:)
49
+ entries.filter_map do |entry|
50
+ key = entry_value(entry, :key)
51
+ path = entry_value(entry, :path)
52
+ name = path && !path.to_s.empty? ? File.basename(path.to_s) : key.to_s.split(".").last
53
+ next if pattern.match?(name)
54
+
55
+ {
56
+ "code" => code,
57
+ "level" => "warning",
58
+ "subject" => key,
59
+ "message" => "#{key} does not match the required naming pattern #{pattern.inspect}",
60
+ "fix" => fix,
61
+ }
62
+ end
63
+ end
64
+
65
+ private
66
+
67
+ def entry_value(entry, key)
68
+ return entry.public_send(key) if !entry.is_a?(Hash) && entry.respond_to?(key)
69
+ return entry[key] if entry.is_a?(Hash) && entry.key?(key)
70
+ return entry[key.to_s] if entry.is_a?(Hash) && entry.key?(key.to_s)
71
+
72
+ nil
73
+ end
74
+
75
+ def deep_merge(left_hash, right_hash)
76
+ left_hash.merge(right_hash) do |_key, old_val, new_val|
77
+ if old_val.is_a?(Hash) && new_val.is_a?(Hash)
78
+ deep_merge(old_val, new_val)
79
+ else
80
+ new_val
81
+ end
82
+ end
83
+ end
84
+
85
+ def build_lanes(ctx)
86
+ data = ctx.manifest.data
87
+ policy = ctx.manifest.policy
88
+ data.declared_lane_kinds.map do |name, kind|
89
+ writers = policy.writers_for(name).map(&:to_s)
90
+ { "name" => name.to_s, "kind" => kind.to_s,
91
+ "purpose" => data.lane_descs[name].to_s, "writers" => writers }
92
+ end
93
+ end
94
+
95
+ def build_roles(ctx)
96
+ ctx.manifest.data.role_caps.map do |role, caps|
97
+ { "name" => role.to_s, "capabilities" => caps.map(&:to_s) }
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
@@ -12,17 +12,14 @@ module Textus
12
12
 
13
13
  class NotFound < Textus::Error
14
14
  def initialize(key)
15
- super(:workflow_not_found, "no workflow matches key '#{key}'; add a .textus/workflows/*.rb file with match: '#{key}'")
15
+ super(:workflow_not_found, "no workflow matches key '#{key}'; add a .textus/workflows/*.rb file with on: '#{key}'")
16
16
  end
17
17
  end
18
18
  end
19
19
 
20
20
  def self.workflow(name, &)
21
- collector = Workflow::Collector.current
22
- raise "Textus.workflow called outside Workflow::Loader.load_all context" unless collector
23
-
24
21
  defn = Workflow::DSL::Definition.new(name)
25
22
  defn.instance_eval(&)
26
- collector.register(defn)
23
+ Workflow::DSL::Definition.register(defn)
27
24
  end
28
25
  end
data/lib/textus.rb CHANGED
@@ -1,9 +1,21 @@
1
1
  require "zeitwerk"
2
2
  require_relative "textus/version"
3
3
  require_relative "textus/errors"
4
- require_relative "textus/surface/mcp"
4
+
5
+ # Declare the MCP namespace before Zeitwerk encounters surface/mcp/errors.rb,
6
+ # which is pre-required below because its filename (errors.rb) does not match
7
+ # its constant name (ToolError) and cannot be managed by Zeitwerk.
8
+ module Textus
9
+ module Surface
10
+ module MCP
11
+ end
12
+ end
13
+ end
14
+
5
15
  require_relative "textus/surface/mcp/errors"
6
16
 
17
+ require "sinatra/base"
18
+
7
19
  loader = Zeitwerk::Loader.for_gem
8
20
  loader.inflector.inflect(
9
21
  "cli" => "CLI",
@@ -14,23 +26,11 @@ loader.inflector.inflect(
14
26
  "dsl" => "DSL",
15
27
  )
16
28
  loader.ignore(File.expand_path("textus/errors.rb", __dir__))
17
- loader.ignore(File.expand_path("textus/surface/mcp.rb", __dir__))
18
29
  loader.ignore(File.expand_path("textus/surface/mcp/errors.rb", __dir__))
19
- loader.ignore(File.expand_path("textus/workflow/errors.rb", __dir__))
20
30
  loader.ignore(File.expand_path("textus/init/templates", __dir__))
21
- loader.ignore(File.expand_path("textus/produce/acquire", __dir__))
31
+ loader.ignore(File.expand_path("textus/protocol/init/templates", __dir__))
32
+ loader.ignore(File.expand_path("textus/protocol/verb_registry/verbs", __dir__))
22
33
  loader.setup
23
34
  loader.eager_load
24
35
 
25
- Textus::Boot::CLI_VERBS = Textus::Boot.build_cli_verbs.freeze
26
-
27
- module Textus
28
- def self.workflow(name, &)
29
- collector = Workflow::Collector.current
30
- raise "Textus.workflow called outside Workflow::Loader.load_all context" unless collector
31
-
32
- defn = Workflow::DSL::Definition.new(name)
33
- defn.instance_eval(&)
34
- collector.register(defn)
35
- end
36
- end
36
+ Textus::Protocol::Boot::CLI_VERBS = Textus::Protocol::Boot::Catalog.build.freeze