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,32 +0,0 @@
1
- module Textus
2
- module Handlers
3
- module Maintenance
4
- class RuleList
5
- def initialize(manifest:)
6
- @manifest = manifest
7
- end
8
-
9
- def call(_command, _call)
10
- Value::Result.success(@manifest.rules.blocks.map do |block|
11
- row = { "match" => block.match }
12
- LIST_FIELDS.each do |field|
13
- value = block.public_send(field)
14
- row[field.to_s] = serialize(field, value) unless value.nil?
15
- end
16
- row
17
- end)
18
- end
19
-
20
- LIST_FIELDS = Textus::Manifest::Schema::FIELD_REGISTRY.select { |_, m| m[:in_rule_list] }.keys.freeze
21
-
22
- def serialize(field, value)
23
- case field
24
- when :retention then { "ttl_seconds" => value.ttl_seconds, "action" => value.action.to_s }
25
- when :react then value.to_h
26
- else value
27
- end
28
- end
29
- end
30
- end
31
- end
32
- end
@@ -1,19 +0,0 @@
1
- module Textus
2
- module Handlers
3
- module Maintenance
4
- class SchemaEnvelope
5
- def initialize(manifest:, schemas:)
6
- @manifest = manifest
7
- @schemas = schemas
8
- end
9
-
10
- def call(command, _call)
11
- mentry = @manifest.resolver.resolve(command.key).entry
12
- schema = @schemas.fetch_or_nil(mentry.schema)
13
- Value::Result.success("protocol" => Textus::PROTOCOL, "key" => command.key,
14
- "schema_ref" => mentry.schema, "schema" => schema&.to_h)
15
- end
16
- end
17
- end
18
- end
19
- end
@@ -1,48 +0,0 @@
1
- module Textus
2
- module Handlers
3
- module Read
4
- class AuditEntries
5
- def initialize(manifest:, audit_log:)
6
- @manifest = manifest
7
- @audit_log = audit_log
8
- end
9
-
10
- def call(command, _call)
11
- cursor_check = check_cursor_expiry(command.seq_since)
12
- return cursor_check if cursor_check
13
-
14
- rows = @audit_log.scan(
15
- seq_since: command.seq_since,
16
- key: command.key, role: command.role, verb: command.verb,
17
- correlation_id: command.correlation_id, limit: command.limit
18
- ).select do |row|
19
- next false if command.lane && !key_in_lane?(row["key"], command.lane)
20
- next false if command.since && (row["ts"].nil? || Time.parse(row["ts"]) < command.since)
21
-
22
- true
23
- end
24
- Value::Result.success(rows)
25
- end
26
-
27
- private
28
-
29
- def check_cursor_expiry(seq_since)
30
- return unless seq_since
31
-
32
- min = @audit_log.min_available_seq
33
- return unless min && seq_since < min - 1
34
-
35
- Value::Result.failure(:cursor_expired, "requested seq #{seq_since} is below minimum available #{min}",
36
- details: { requested: seq_since, min_available: min })
37
- end
38
-
39
- def key_in_lane?(key, lane)
40
- mentry = @manifest.resolver.resolve(key).entry
41
- mentry && mentry.lane == lane
42
- rescue Textus::Error
43
- false
44
- end
45
- end
46
- end
47
- end
48
- end
@@ -1,71 +0,0 @@
1
- module Textus
2
- module Handlers
3
- module Read
4
- class BlameEntry
5
- def initialize(manifest:, orchestration:)
6
- @manifest = manifest
7
- @orchestration = orchestration
8
- end
9
-
10
- def call(command, call)
11
- root = @manifest.data.root
12
- audit = @orchestration.audit_entries(key: command.key, limit: command.limit, call: call)
13
- return audit if audit.failure?
14
-
15
- audit_rows = audit.value.fetch("rows")
16
-
17
- path = resolve_path(command.key)
18
- return Value::Result.success(audit_rows.map { |row| row.merge("git" => nil) }) unless git_tracked?(path, root: root)
19
-
20
- Value::Result.success(audit_rows.map { |row| row.merge("git" => git_commit_at(path, timestamp: row["ts"], root: root)) })
21
- end
22
-
23
- private
24
-
25
- def resolve_path(key)
26
- res = @manifest.resolver.resolve(key)
27
- path = res.path
28
- path || Textus::Key::Path.resolve(@manifest.data, res.entry)
29
- rescue Textus::Error
30
- nil
31
- end
32
-
33
- def git_tracked?(path, root:)
34
- return false if path.nil? || !File.exist?(path) || !git_repo?(root)
35
-
36
- _out, _err, status = Open3.capture3("git", "ls-files", "--error-unmatch", path, chdir: root)
37
- status.success?
38
- rescue Errno::ENOENT
39
- false
40
- end
41
-
42
- def git_repo?(root)
43
- dir = root
44
- loop do
45
- return true if File.directory?(File.join(dir, ".git"))
46
-
47
- parent = File.dirname(dir)
48
- return false if parent == dir
49
-
50
- dir = parent
51
- end
52
- end
53
-
54
- def git_commit_at(path, timestamp:, root:)
55
- args = ["git", "log", "-1"]
56
- args << "--before=#{timestamp}" if timestamp
57
- args += ["--format=%H%x09%an%x09%aI%x09%s", "--", path]
58
- out, _err, status = Open3.capture3(*args, chdir: root)
59
- return nil unless status.success?
60
-
61
- sha, author, date, subject = out.strip.split("\t", 4)
62
- return nil if sha.nil? || sha.empty?
63
-
64
- { "sha" => sha, "author" => author, "date" => date, "subject" => subject }
65
- rescue Errno::ENOENT
66
- nil
67
- end
68
- end
69
- end
70
- end
71
- end
@@ -1,17 +0,0 @@
1
- module Textus
2
- module Handlers
3
- module Read
4
- class DepsEntry
5
- def initialize(manifest:)
6
- @manifest = manifest
7
- end
8
-
9
- def call(command, _call)
10
- entry = @manifest.data.entries.find { |e| e.key == command.key }
11
- deps = entry&.external? ? Array(entry.source&.sources).compact : []
12
- Value::Result.success("key" => command.key, "deps" => deps.uniq)
13
- end
14
- end
15
- end
16
- end
17
- end
@@ -1,68 +0,0 @@
1
- module Textus
2
- module Handlers
3
- module Read
4
- class GetEntry
5
- def initialize(container:, freshness_evaluator:)
6
- @container = container
7
- @freshness_evaluator = freshness_evaluator
8
- end
9
-
10
- def call(command, _call)
11
- envelope = Store::Entry::Reader.from(container: @container).read(command.key)
12
- return Value::Result.failure(:not_found, "no entry at #{command.key}") unless envelope
13
-
14
- envelope = expand_sources(envelope, depth: 0)
15
- Value::Result.success(envelope.with(freshness: @freshness_evaluator.verdict(resolve_entry(command.key))))
16
- end
17
-
18
- MAX_SOURCE_DEPTH = 5
19
-
20
- private
21
-
22
- def expand_sources(envelope, depth:)
23
- return envelope if depth >= MAX_SOURCE_DEPTH
24
-
25
- raw_sources = Array(envelope.meta["sources"])
26
- return envelope if raw_sources.empty?
27
-
28
- expanded = raw_sources.map { |src| expand_one_source(src, depth: depth) }
29
- envelope.with(sources: expanded)
30
- end
31
-
32
- def expand_one_source(src, depth:)
33
- src = { "key" => src } if src.is_a?(String)
34
- return src unless src.is_a?(Hash) && src["key"].is_a?(String)
35
-
36
- key = src["key"]
37
- stored_etag = src["etag"]
38
- current_etag = resolve_current_etag(key)
39
- suspended = stored_etag && current_etag ? stored_etag != current_etag : false
40
-
41
- result = src.merge("suspended" => suspended)
42
-
43
- child_env = @container.reader.read(key)
44
- if child_env
45
- child_expanded = expand_sources(child_env, depth: depth + 1)
46
- child_sources = Array(child_expanded.sources)
47
- result = result.merge("sources" => child_sources) unless child_sources.empty?
48
- end
49
-
50
- result
51
- end
52
-
53
- def resolve_current_etag(key)
54
- path = @container.manifest.resolver.resolve(key).path
55
- return nil unless @container.file_store.exists?(path)
56
-
57
- @container.file_store.etag(path)
58
- rescue Textus::Error
59
- nil
60
- end
61
-
62
- def resolve_entry(key)
63
- @container.manifest.resolver.resolve(key).entry
64
- end
65
- end
66
- end
67
- end
68
- end
@@ -1,36 +0,0 @@
1
- module Textus
2
- module Handlers
3
- module Read
4
- class ListKeys
5
- def initialize(manifest:, job_store: nil)
6
- @manifest = manifest
7
- @job_store = job_store
8
- end
9
-
10
- def call(command, _call)
11
- q = command.respond_to?(:q) ? command.q : nil
12
- schema = command.respond_to?(:schema) ? command.schema : nil
13
-
14
- return sqlite_list(q: q, schema: schema, lane: command.lane, prefix: command.prefix) if @job_store && (q || schema)
15
-
16
- manifest_list(prefix: command.prefix, lane: command.lane)
17
- end
18
-
19
- private
20
-
21
- def sqlite_list(q:, schema:, lane:, prefix:) # rubocop:disable Naming/MethodParameterName
22
- rows = @job_store.search_entries(q: q, schema: schema, lane: lane, prefix: prefix)
23
- Value::Result.success((rows || []).map { |r| { "key" => r["key"], "lane" => r["lane"] } })
24
- end
25
-
26
- def manifest_list(prefix:, lane:)
27
- rows = @manifest.resolver.enumerate(prefix: prefix)
28
- rows = rows.select { |row| row[:manifest_entry].lane == lane } if lane
29
- Value::Result.success(rows.map do |row|
30
- { "key" => row[:key], "lane" => row[:manifest_entry].lane, "path" => row[:path] }
31
- end)
32
- end
33
- end
34
- end
35
- end
36
- end
@@ -1,66 +0,0 @@
1
- module Textus
2
- module Handlers
3
- module Read
4
- class PulseEntries
5
- def initialize(manifest:, audit_log:, file_store:, orchestration:, job_store: nil)
6
- @manifest = manifest
7
- @audit_log = audit_log
8
- @file_store = file_store
9
- @orchestration = orchestration
10
- @job_store = job_store
11
- end
12
-
13
- def call(command, call)
14
- root = @manifest.data.root
15
- since = command.since || Textus::Store::Cursor.new(root: root, role: call.role).read
16
-
17
- changed = changed_since(since, call)
18
-
19
- result = {
20
- "cursor" => @audit_log.latest_seq,
21
- "changed" => changed,
22
- "pending_review" => review_keys(call),
23
- "contract_etag" => Textus::Value::Etag.for_contract(root),
24
- "index_etag" => index_etag,
25
- }
26
-
27
- Textus::Store::Cursor.new(root: root, role: call.role).write(result["cursor"])
28
- Value::Result.success(result)
29
- end
30
-
31
- private
32
-
33
- def changed_since(since, call)
34
- if @job_store
35
- sqlite_rows = @job_store.audit_events_since(seq: since)
36
- return sqlite_rows.map { |r| { "key" => r["key"], "verb" => r["verb"], "seq" => r["seq"] } } if sqlite_rows.any?
37
- end
38
-
39
- # Fall back to flat-log scan when SQLite index is empty for this window
40
- # (writes that bypassed dispatch, fresh stores, or pre-migration entries).
41
- audit = @orchestration.audit_entries(seq_since: since, call: call)
42
- return [] if audit.failure?
43
-
44
- audit.value.fetch("rows") || []
45
- end
46
-
47
- def review_keys(call)
48
- queue = @manifest.policy.queue_lane
49
- return [] unless queue
50
-
51
- result = @orchestration.list_keys(prefix: nil, lane: queue, call: call)
52
- return [] unless result.success?
53
-
54
- result.value.fetch("rows").map { |r| r["key"] }
55
- end
56
-
57
- def index_etag
58
- path = @manifest.resolver.resolve("artifacts.system.index").path
59
- File.exist?(path) ? @file_store.etag(path) : nil
60
- rescue Textus::Error
61
- nil
62
- end
63
- end
64
- end
65
- end
66
- end
@@ -1,21 +0,0 @@
1
- module Textus
2
- module Handlers
3
- module Read
4
- class RdepsEntry
5
- def initialize(manifest:)
6
- @manifest = manifest
7
- end
8
-
9
- def call(command, _call)
10
- rdeps = @manifest.data.entries.each_with_object([]) do |entry, acc|
11
- next unless entry.external?
12
-
13
- sources = Array(entry.source&.sources).compact
14
- acc << entry.key if sources.any? { |source| source == command.key || command.key.start_with?("#{source}.") }
15
- end
16
- Value::Result.success("key" => command.key, "rdeps" => rdeps)
17
- end
18
- end
19
- end
20
- end
21
- end
@@ -1,18 +0,0 @@
1
- module Textus
2
- module Handlers
3
- module Read
4
- class UidEntry
5
- def initialize(container:)
6
- @container = container
7
- end
8
-
9
- def call(command, _call)
10
- envelope = Store::Entry::Reader.from(container: @container).read(command.key)
11
- return Value::Result.failure(:not_found, "no entry at #{command.key}") unless envelope
12
-
13
- Value::Result.success(envelope.uid)
14
- end
15
- end
16
- end
17
- end
18
- end
@@ -1,18 +0,0 @@
1
- module Textus
2
- module Handlers
3
- module Read
4
- class WhereEntry
5
- def initialize(manifest:)
6
- @manifest = manifest
7
- end
8
-
9
- def call(command, _call)
10
- res = @manifest.resolver.resolve(command.key)
11
- mentry = res.entry
12
- Value::Result.success("protocol" => Textus::PROTOCOL, "key" => command.key,
13
- "lane" => mentry.lane, "owner" => mentry.owner, "path" => res.path)
14
- end
15
- end
16
- end
17
- end
18
- end
@@ -1,39 +0,0 @@
1
- module Textus
2
- module Handlers
3
- module Write
4
- class AcceptProposal
5
- def initialize(container:)
6
- @container = container
7
- end
8
-
9
- def call(command, call)
10
- reader = Store::Entry::Reader.from(container: @container)
11
- env = reader.read(command.pending_key)
12
- proposal = env&.meta&.dig("proposal") or
13
- return Value::Result.failure(:proposal_error, "entry has no proposal block: #{command.pending_key}")
14
- target = proposal["target_key"] or
15
- return Value::Result.failure(:proposal_error, "proposal missing target_key")
16
- action = proposal["action"] || "put"
17
-
18
- writer = Store::Entry::Writer.from(container: @container, call: call)
19
- case action
20
- when "put"
21
- mentry = @container.manifest.resolver.resolve(target).entry
22
- writer.put(
23
- target, mentry: mentry,
24
- payload: Textus::Value::Payload.new(meta: env.meta["_meta"] || {}, body: env.body, content: nil)
25
- )
26
- when "delete"
27
- writer.delete(target)
28
- else
29
- return Value::Result.failure(:proposal_error, "unknown action: #{action}")
30
- end
31
-
32
- writer.delete(command.pending_key)
33
- Value::Result.success("protocol" => Textus::PROTOCOL, "accepted" => command.pending_key,
34
- "target_key" => target, "action" => action, "cascade_key" => target)
35
- end
36
- end
37
- end
38
- end
39
- end
@@ -1,55 +0,0 @@
1
- require "yaml"
2
-
3
- module Textus
4
- module Handlers
5
- module Write
6
- class DataMv
7
- def initialize(container:)
8
- @container = container
9
- end
10
-
11
- def call(command, _call)
12
- manifest = @container.manifest
13
- geom = @container.layout
14
-
15
- return Value::Result.failure(:usage_error, "from and to required") if command.from.nil? || command.to.nil?
16
- unless manifest.data.declared_lane_kinds.key?(command.from)
17
- return Value::Result.failure(:usage_error,
18
- "data lane '#{command.from}' not declared")
19
- end
20
-
21
- dest_dir = geom.lane_path(command.to)
22
- return Value::Result.failure(:usage_error, "destination 'data/#{command.to}' already exists") if File.exist?(dest_dir)
23
-
24
- affected_keys = manifest.data.entries.select { |entry| entry.lane == command.from }.map(&:key)
25
-
26
- steps = [{ "op" => "rename_zone", "from" => command.from, "to" => command.to }]
27
- steps += affected_keys.map do |key|
28
- { "op" => "mv", "from" => key, "to" => "#{command.to}#{key[command.from.length..]}" }
29
- end
30
-
31
- plan = Textus::Store::Jobs::Plan.new(steps: steps, warnings: [])
32
- return Value::Result.success(plan) if command.dry_run
33
-
34
- rewrite_manifest!(geom, from: command.from, to: command.to)
35
- FileUtils.mv(geom.lane_path(command.from), dest_dir)
36
- Value::Result.success(plan)
37
- end
38
-
39
- private
40
-
41
- def rewrite_manifest!(geom, from:, to:)
42
- path = geom.manifest_path
43
- raw = YAML.safe_load_file(path, permitted_classes: [Symbol], aliases: false)
44
- raw["lanes"].each { |lane| lane["name"] = to if lane["name"] == from }
45
- raw["entries"].each do |entry|
46
- entry["lane"] = to if entry["lane"] == from
47
- entry["key"] = entry["key"].sub(/\A#{Regexp.escape(from)}(\.|\z)/, "#{to}\\1")
48
- entry["path"] = entry["path"].sub(%r{\A(data/)?#{Regexp.escape(from)}(/|\z)}, "\\1#{to}\\2")
49
- end
50
- File.write(path, YAML.dump(raw))
51
- end
52
- end
53
- end
54
- end
55
- end
@@ -1,17 +0,0 @@
1
- module Textus
2
- module Handlers
3
- module Write
4
- class DeleteKey
5
- def initialize(container:)
6
- @container = container
7
- end
8
-
9
- def call(command, call)
10
- writer = Store::Entry::Writer.from(container: @container, call: call)
11
- writer.delete(command.key, if_etag: command.if_etag)
12
- Value::Result.success("protocol" => Textus::PROTOCOL, "ok" => true, "key" => command.key, "deleted" => true)
13
- end
14
- end
15
- end
16
- end
17
- end
@@ -1,27 +0,0 @@
1
- module Textus
2
- module Handlers
3
- module Write
4
- class EnqueueJob
5
- def initialize(job_store:)
6
- @job_store = job_store
7
- end
8
-
9
- def call(command, call)
10
- action_class = Textus::Jobs.fetch(command.type.to_s)
11
-
12
- if action_class.const_defined?(:REQUIRED_ROLE) && call.role != action_class::REQUIRED_ROLE
13
- return Value::Result.failure(:forbidden,
14
- "role '#{call.role}' is not authorized to enqueue this job type",
15
- details: { "role" => call.role, "required_role" => action_class::REQUIRED_ROLE })
16
- end
17
-
18
- job = Textus::Store::Jobs::Queue::Job.new(type: command.type, args: command.args, role: call.role, max_attempts: 3)
19
- Textus::Store::Jobs::Queue.new(store: @job_store).enqueue(job)
20
- Value::Result.success("protocol" => Textus::PROTOCOL, "ok" => true, "id" => job.id)
21
- rescue Textus::UsageError
22
- Value::Result.failure(:usage_error, "unregistered job type '#{command.type}'")
23
- end
24
- end
25
- end
26
- end
27
- end
@@ -1,32 +0,0 @@
1
- module Textus
2
- module Handlers
3
- module Write
4
- class KeyDeletePrefix
5
- def initialize(orchestration:)
6
- @orchestration = orchestration
7
- end
8
-
9
- def call(command, call)
10
- return Value::Result.failure(:usage_error, "prefix required") if command.prefix.nil? || command.prefix.empty?
11
-
12
- list = @orchestration.list_keys(prefix: command.prefix, lane: nil, call: call)
13
- return list if list.failure?
14
-
15
- leaves = list.value.fetch("rows")
16
-
17
- warnings = leaves.empty? ? ["no keys under #{command.prefix}"] : []
18
- steps = leaves.map { |row| { "op" => "delete", "key" => row["key"] } }
19
-
20
- plan = Textus::Store::Jobs::Plan.new(steps: steps, warnings: warnings)
21
- return Value::Result.success(plan) if command.dry_run
22
-
23
- steps.each do |step|
24
- delete = @orchestration.delete_key(key: step["key"], call: call)
25
- return delete if delete.failure?
26
- end
27
- Value::Result.success(plan)
28
- end
29
- end
30
- end
31
- end
32
- end
@@ -1,45 +0,0 @@
1
- module Textus
2
- module Handlers
3
- module Write
4
- class KeyMvPrefix
5
- def initialize(orchestration:)
6
- @orchestration = orchestration
7
- end
8
-
9
- def call(command, call)
10
- if command.from_prefix.nil? || command.to_prefix.nil?
11
- return Value::Result.failure(:usage_error,
12
- "from_prefix and to_prefix required")
13
- end
14
-
15
- list = @orchestration.list_keys(prefix: command.from_prefix, lane: nil, call: call)
16
- return list if list.failure?
17
-
18
- leaves = list.value.fetch("rows")
19
-
20
- if leaves.any? { |r| r["key"] == command.from_prefix }
21
- return Value::Result.failure(:usage_error,
22
- "from_prefix '#{command.from_prefix}' is itself a leaf — use `mv` to rename a single key")
23
- end
24
-
25
- warnings = leaves.empty? ? ["no keys under #{command.from_prefix}"] : []
26
- steps = leaves.map do |row|
27
- old_key = row["key"]
28
- tail = old_key.delete_prefix("#{command.from_prefix}.")
29
- new_key = "#{command.to_prefix}.#{tail}"
30
- { "op" => "mv", "from" => old_key, "to" => new_key }
31
- end
32
-
33
- plan = Textus::Store::Jobs::Plan.new(steps: steps, warnings: warnings)
34
- return Value::Result.success(plan) if command.dry_run
35
-
36
- steps.each do |step|
37
- move = @orchestration.move_key(old_key: step["from"], new_key: step["to"], call: call)
38
- return move if move.failure?
39
- end
40
- Value::Result.success(plan)
41
- end
42
- end
43
- end
44
- end
45
- end