textus 0.53.0 → 0.54.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 (149) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +34 -0
  3. data/SPEC.md +2 -5
  4. data/docs/architecture/README.md +149 -159
  5. data/lib/textus/action/accept.rb +0 -9
  6. data/lib/textus/action/audit.rb +0 -2
  7. data/lib/textus/action/blame.rb +0 -2
  8. data/lib/textus/action/boot.rb +2 -11
  9. data/lib/textus/action/data_mv.rb +1 -3
  10. data/lib/textus/action/deps.rb +1 -15
  11. data/lib/textus/action/doctor.rb +0 -2
  12. data/lib/textus/action/drain.rb +4 -6
  13. data/lib/textus/action/enqueue.rb +3 -5
  14. data/lib/textus/action/get.rb +1 -3
  15. data/lib/textus/action/ingest.rb +135 -0
  16. data/lib/textus/action/jobs.rb +1 -3
  17. data/lib/textus/action/key_delete.rb +0 -10
  18. data/lib/textus/action/key_delete_prefix.rb +1 -3
  19. data/lib/textus/action/key_mv.rb +1 -15
  20. data/lib/textus/action/key_mv_prefix.rb +1 -3
  21. data/lib/textus/action/list.rb +0 -2
  22. data/lib/textus/action/propose.rb +0 -2
  23. data/lib/textus/action/published.rb +0 -2
  24. data/lib/textus/action/pulse.rb +7 -38
  25. data/lib/textus/action/put.rb +1 -12
  26. data/lib/textus/action/rdeps.rb +2 -12
  27. data/lib/textus/action/reject.rb +0 -12
  28. data/lib/textus/action/rule_explain.rb +0 -4
  29. data/lib/textus/action/rule_lint.rb +1 -3
  30. data/lib/textus/action/rule_list.rb +0 -4
  31. data/lib/textus/action/schema_envelope.rb +0 -2
  32. data/lib/textus/action/uid.rb +0 -2
  33. data/lib/textus/action/where.rb +0 -2
  34. data/lib/textus/action/write_verb.rb +4 -18
  35. data/lib/textus/boot.rb +33 -109
  36. data/lib/textus/command.rb +6 -1
  37. data/lib/textus/container.rb +1 -1
  38. data/lib/textus/core/freshness/evaluator.rb +18 -34
  39. data/lib/textus/doctor/check/notebook_sources.rb +50 -0
  40. data/lib/textus/doctor/check/raw_asset_paths.rb +50 -0
  41. data/lib/textus/doctor/check.rb +0 -1
  42. data/lib/textus/doctor.rb +2 -42
  43. data/lib/textus/envelope/reader.rb +46 -0
  44. data/lib/textus/envelope/writer.rb +209 -0
  45. data/lib/textus/errors.rb +8 -4
  46. data/lib/textus/{entry → format}/base.rb +1 -6
  47. data/lib/textus/{entry → format}/json.rb +1 -4
  48. data/lib/textus/{entry → format}/markdown.rb +2 -6
  49. data/lib/textus/{entry → format}/text.rb +2 -6
  50. data/lib/textus/{entry → format}/yaml.rb +1 -4
  51. data/lib/textus/{entry.rb → format.rb} +9 -10
  52. data/lib/textus/gate/auth.rb +58 -12
  53. data/lib/textus/gate.rb +2 -0
  54. data/lib/textus/init.rb +12 -75
  55. data/lib/textus/jobs/base.rb +23 -0
  56. data/lib/textus/jobs/materialize.rb +20 -0
  57. data/lib/textus/{background → jobs}/plan.rb +1 -1
  58. data/lib/textus/jobs/planner.rb +101 -0
  59. data/lib/textus/jobs/retention.rb +48 -0
  60. data/lib/textus/jobs/sweep.rb +27 -0
  61. data/lib/textus/{background → jobs}/worker.rb +2 -2
  62. data/lib/textus/jobs.rb +15 -0
  63. data/lib/textus/key/path.rb +1 -1
  64. data/lib/textus/manifest/entry/base.rb +4 -19
  65. data/lib/textus/manifest/entry/parser.rb +27 -10
  66. data/lib/textus/manifest/entry/produced.rb +13 -34
  67. data/lib/textus/manifest/entry/publish/to_paths.rb +4 -4
  68. data/lib/textus/manifest/entry/publish.rb +0 -12
  69. data/lib/textus/manifest/entry/validators/format_matrix.rb +1 -1
  70. data/lib/textus/manifest/entry/validators.rb +0 -1
  71. data/lib/textus/manifest/policy/source.rb +14 -62
  72. data/lib/textus/manifest/policy.rb +2 -5
  73. data/lib/textus/manifest/resolver.rb +2 -2
  74. data/lib/textus/manifest/schema/keys.rb +6 -20
  75. data/lib/textus/manifest/schema/validator.rb +3 -11
  76. data/lib/textus/manifest/schema/vocabulary.rb +1 -0
  77. data/lib/textus/ports/{queue → job_store}/job.rb +1 -1
  78. data/lib/textus/ports/{queue.rb → job_store.rb} +6 -13
  79. data/lib/textus/produce/engine.rb +50 -0
  80. data/lib/textus/produce/render.rb +18 -0
  81. data/lib/textus/session.rb +1 -1
  82. data/lib/textus/store.rb +1 -10
  83. data/lib/textus/surfaces/mcp/catalog.rb +1 -1
  84. data/lib/textus/surfaces/mcp/errors.rb +0 -9
  85. data/lib/textus/surfaces/mcp/routing.rb +51 -0
  86. data/lib/textus/surfaces/mcp/server.rb +101 -62
  87. data/lib/textus/surfaces/role_scope.rb +0 -4
  88. data/lib/textus/surfaces/watcher.rb +3 -3
  89. data/lib/textus/version.rb +1 -1
  90. data/lib/textus/workflow/collector.rb +27 -0
  91. data/lib/textus/workflow/context.rb +5 -0
  92. data/lib/textus/workflow/dsl.rb +44 -0
  93. data/lib/textus/workflow/loader.rb +17 -0
  94. data/lib/textus/workflow/pattern.rb +18 -0
  95. data/lib/textus/workflow/registry.rb +21 -0
  96. data/lib/textus/workflow/runner.rb +84 -0
  97. data/lib/textus/workflow.rb +28 -0
  98. data/lib/textus.rb +12 -1
  99. metadata +33 -61
  100. data/lib/textus/action/pulse/scanner.rb +0 -118
  101. data/lib/textus/background/job/base.rb +0 -27
  102. data/lib/textus/background/job/materialize.rb +0 -31
  103. data/lib/textus/background/job/refresh.rb +0 -22
  104. data/lib/textus/background/job/sweep.rb +0 -31
  105. data/lib/textus/background/job.rb +0 -19
  106. data/lib/textus/background/planner/plan.rb +0 -113
  107. data/lib/textus/background/retention/apply.rb +0 -50
  108. data/lib/textus/doctor/check/handler_permit.rb +0 -34
  109. data/lib/textus/doctor/check/hooks.rb +0 -25
  110. data/lib/textus/doctor/check/intake_registration.rb +0 -46
  111. data/lib/textus/envelope/io/reader.rb +0 -48
  112. data/lib/textus/envelope/io/writer.rb +0 -211
  113. data/lib/textus/events.rb +0 -21
  114. data/lib/textus/init/templates/machine_intake.rb +0 -49
  115. data/lib/textus/init/templates/orientation_reducer.rb +0 -21
  116. data/lib/textus/manifest/entry/validators/events.rb +0 -22
  117. data/lib/textus/manifest/policy/handler_permit.rb +0 -17
  118. data/lib/textus/pipeline/acquire/handler.rb +0 -29
  119. data/lib/textus/pipeline/acquire/intake.rb +0 -132
  120. data/lib/textus/pipeline/acquire/projection.rb +0 -129
  121. data/lib/textus/pipeline/acquire/serializer/json.rb +0 -31
  122. data/lib/textus/pipeline/acquire/serializer/text.rb +0 -16
  123. data/lib/textus/pipeline/acquire/serializer/yaml.rb +0 -31
  124. data/lib/textus/pipeline/acquire/serializer.rb +0 -17
  125. data/lib/textus/pipeline/engine.rb +0 -97
  126. data/lib/textus/pipeline/render.rb +0 -25
  127. data/lib/textus/ports/audit_subscriber.rb +0 -42
  128. data/lib/textus/projection.rb +0 -103
  129. data/lib/textus/step/base.rb +0 -35
  130. data/lib/textus/step/builtin/csv_fetch.rb +0 -19
  131. data/lib/textus/step/builtin/ical_events_fetch.rb +0 -30
  132. data/lib/textus/step/builtin/json_fetch.rb +0 -18
  133. data/lib/textus/step/builtin/markdown_links_fetch.rb +0 -20
  134. data/lib/textus/step/builtin/rss_fetch.rb +0 -26
  135. data/lib/textus/step/builtin.rb +0 -22
  136. data/lib/textus/step/catalog.rb +0 -37
  137. data/lib/textus/step/context.rb +0 -74
  138. data/lib/textus/step/discovery.rb +0 -24
  139. data/lib/textus/step/error_log.rb +0 -32
  140. data/lib/textus/step/event_bus.rb +0 -113
  141. data/lib/textus/step/fetch.rb +0 -13
  142. data/lib/textus/step/fire_report.rb +0 -23
  143. data/lib/textus/step/loader.rb +0 -108
  144. data/lib/textus/step/observe.rb +0 -31
  145. data/lib/textus/step/registry_store.rb +0 -66
  146. data/lib/textus/step/signature.rb +0 -31
  147. data/lib/textus/step/transform.rb +0 -12
  148. data/lib/textus/step/validate.rb +0 -11
  149. data/lib/textus/step.rb +0 -10
@@ -0,0 +1,44 @@
1
+ module Textus
2
+ module Workflow
3
+ module DSL
4
+ Step = Data.define(:name, :callable, :timeout)
5
+
6
+ class Definition
7
+ attr_reader :name, :steps, :match_pattern, :publish_block
8
+
9
+ def initialize(name)
10
+ @name = name
11
+ @steps = []
12
+ @match_pattern = nil
13
+ @publish_block = nil
14
+ end
15
+
16
+ def match(pattern)
17
+ @match_pattern = pattern
18
+ end
19
+
20
+ def step(name, callable_or_opt = nil, timeout: nil, &block)
21
+ callable = if callable_or_opt.respond_to?(:call)
22
+ callable_or_opt
23
+ elsif block
24
+ block
25
+ else
26
+ raise ArgumentError.new("step :#{name} requires a block or a callable (got neither)")
27
+ end
28
+ t = callable_or_opt.is_a?(Hash) ? callable_or_opt[:timeout] : timeout
29
+ @steps << Step.new(name: name, callable: callable, timeout: t)
30
+ end
31
+
32
+ def publish(&block)
33
+ @publish_block = block || :default
34
+ end
35
+
36
+ def match?(key)
37
+ return false unless @match_pattern
38
+
39
+ Pattern.match?(@match_pattern, key)
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,17 @@
1
+ module Textus
2
+ module Workflow
3
+ class Loader
4
+ def self.load_all(root)
5
+ registry = Registry.new
6
+ workflows_dir = File.join(root, "workflows")
7
+ return registry unless File.directory?(workflows_dir)
8
+
9
+ collector = Collector.new(registry)
10
+ Collector.with(collector) do
11
+ Dir.glob(File.join(workflows_dir, "**", "*.rb")).each { |path| load path }
12
+ end
13
+ registry
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,18 @@
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
@@ -0,0 +1,21 @@
1
+ module Textus
2
+ module Workflow
3
+ class Registry
4
+ def initialize
5
+ @definitions = []
6
+ end
7
+
8
+ def register(definition)
9
+ @definitions << definition
10
+ end
11
+
12
+ def for(key)
13
+ @definitions.find { |d| d.match?(key) }
14
+ end
15
+
16
+ def all
17
+ @definitions.dup
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,84 @@
1
+ require "timeout"
2
+
3
+ module Textus
4
+ module Workflow
5
+ class Runner
6
+ DEFAULT_TIMEOUT = 30
7
+
8
+ def initialize(definition, container:, call:)
9
+ @definition = definition
10
+ @container = container
11
+ @call = call
12
+ end
13
+
14
+ 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
+ res = @container.manifest.resolver.resolve(key)
25
+ Context.new(
26
+ key: key,
27
+ entry: res.entry,
28
+ config: {}.freeze,
29
+ lane: res.entry.lane.to_s,
30
+ container: @container,
31
+ call: @call,
32
+ )
33
+ end
34
+
35
+ def execute_steps(ctx)
36
+ data = nil
37
+ @definition.steps.each { |step| data = execute_one(step, data, ctx) }
38
+ data
39
+ 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 = normalize(data, ctx.entry.format)
61
+ Gate::Auth.new(@container).check_action!(action: :converge, actor: @call.role, key: key)
62
+ Envelope::Writer.from(container: @container, call: @call).put(
63
+ key,
64
+ mentry: ctx.entry,
65
+ payload: Envelope::Writer::Payload.new(**normalized),
66
+ )
67
+ end
68
+
69
+ def normalize(data, format)
70
+ return { meta: {}, body: "", content: nil } if data.nil?
71
+
72
+ data = data.transform_keys(&:to_s) if data.is_a?(Hash)
73
+ case format.to_s
74
+ when "markdown", "text"
75
+ { meta: data["_meta"] || {}, body: (data["body"] || "").to_s, content: nil }
76
+ when "json", "yaml"
77
+ { meta: data["_meta"] || {}, body: nil, content: data["content"] || data }
78
+ else
79
+ { meta: {}, body: data.to_s, content: nil }
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,28 @@
1
+ module Textus
2
+ module Workflow
3
+ class StepFailed < Textus::Error
4
+ attr_reader :step_name, :cause
5
+
6
+ def initialize(step_name, cause)
7
+ @step_name = step_name
8
+ @cause = cause
9
+ super(:workflow_step_failed, "workflow step '#{step_name}' failed: #{cause.message}")
10
+ end
11
+ end
12
+
13
+ class NotFound < Textus::Error
14
+ def initialize(key)
15
+ super(:workflow_not_found, "no workflow matches key '#{key}'; add a .textus/workflows/*.rb file with match: '#{key}'")
16
+ end
17
+ end
18
+ end
19
+
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
+ defn = Workflow::DSL::Definition.new(name)
25
+ defn.instance_eval(&)
26
+ collector.register(defn)
27
+ end
28
+ end
data/lib/textus.rb CHANGED
@@ -9,17 +9,19 @@ loader.inflector.inflect(
9
9
  "cli" => "CLI",
10
10
  "json" => "Json",
11
11
  "yaml" => "Yaml",
12
- "io" => "IO",
13
12
  "mcp" => "MCP",
14
13
  "mcp_serve" => "MCPServe",
14
+ "dsl" => "DSL",
15
15
  )
16
16
  loader.ignore(File.expand_path("textus/errors.rb", __dir__))
17
17
  loader.ignore(File.expand_path("textus/surfaces/mcp.rb", __dir__))
18
18
  loader.ignore(File.expand_path("textus/surfaces/mcp/errors.rb", __dir__))
19
+ loader.ignore(File.expand_path("textus/workflow/errors.rb", __dir__))
19
20
  # Scaffold sources copied verbatim into user stores by `textus init`. They are
20
21
  # templates for user-owned step classes, not gem constants — Zeitwerk must not
21
22
  # manage or eager-load them.
22
23
  loader.ignore(File.expand_path("textus/init/templates", __dir__))
24
+ loader.ignore(File.expand_path("textus/produce/acquire", __dir__))
23
25
  loader.setup
24
26
  loader.eager_load
25
27
 
@@ -33,6 +35,7 @@ Textus::Action::VERBS = {
33
35
  reject: Textus::Action::Reject,
34
36
  enqueue: Textus::Action::Enqueue,
35
37
  get: Textus::Action::Get,
38
+ ingest: Textus::Action::Ingest,
36
39
  list: Textus::Action::List,
37
40
  where: Textus::Action::Where,
38
41
  uid: Textus::Action::Uid,
@@ -87,4 +90,12 @@ Textus::Action::VERBS.each_key do |verb|
87
90
  end
88
91
 
89
92
  module Textus
93
+ def self.workflow(name, &)
94
+ collector = Workflow::Collector.current
95
+ raise "Textus.workflow called outside Workflow::Loader.load_all context" unless collector
96
+
97
+ defn = Workflow::DSL::Definition.new(name)
98
+ defn.instance_eval(&)
99
+ collector.register(defn)
100
+ end
90
101
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: textus
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.53.0
4
+ version: 0.54.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Patrick
@@ -135,6 +135,7 @@ files:
135
135
  - lib/textus/action/drain.rb
136
136
  - lib/textus/action/enqueue.rb
137
137
  - lib/textus/action/get.rb
138
+ - lib/textus/action/ingest.rb
138
139
  - lib/textus/action/jobs.rb
139
140
  - lib/textus/action/key_delete.rb
140
141
  - lib/textus/action/key_delete_prefix.rb
@@ -144,7 +145,6 @@ files:
144
145
  - lib/textus/action/propose.rb
145
146
  - lib/textus/action/published.rb
146
147
  - lib/textus/action/pulse.rb
147
- - lib/textus/action/pulse/scanner.rb
148
148
  - lib/textus/action/put.rb
149
149
  - lib/textus/action/rdeps.rb
150
150
  - lib/textus/action/reject.rb
@@ -155,15 +155,6 @@ files:
155
155
  - lib/textus/action/uid.rb
156
156
  - lib/textus/action/where.rb
157
157
  - lib/textus/action/write_verb.rb
158
- - lib/textus/background/job.rb
159
- - lib/textus/background/job/base.rb
160
- - lib/textus/background/job/materialize.rb
161
- - lib/textus/background/job/refresh.rb
162
- - lib/textus/background/job/sweep.rb
163
- - lib/textus/background/plan.rb
164
- - lib/textus/background/planner/plan.rb
165
- - lib/textus/background/retention/apply.rb
166
- - lib/textus/background/worker.rb
167
158
  - lib/textus/boot.rb
168
159
  - lib/textus/call.rb
169
160
  - lib/textus/command.rb
@@ -187,15 +178,14 @@ files:
187
178
  - lib/textus/doctor/check.rb
188
179
  - lib/textus/doctor/check/audit_log.rb
189
180
  - lib/textus/doctor/check/generator_drift.rb
190
- - lib/textus/doctor/check/handler_permit.rb
191
- - lib/textus/doctor/check/hooks.rb
192
181
  - lib/textus/doctor/check/illegal_keys.rb
193
- - lib/textus/doctor/check/intake_registration.rb
194
182
  - lib/textus/doctor/check/manifest_files.rb
183
+ - lib/textus/doctor/check/notebook_sources.rb
195
184
  - lib/textus/doctor/check/orphaned_publish_targets.rb
196
185
  - lib/textus/doctor/check/proposal_targets.rb
197
186
  - lib/textus/doctor/check/protocol_version.rb
198
187
  - lib/textus/doctor/check/publish_tree_index_overlap.rb
188
+ - lib/textus/doctor/check/raw_asset_paths.rb
199
189
  - lib/textus/doctor/check/rule_ambiguity.rb
200
190
  - lib/textus/doctor/check/schema_parse_error.rb
201
191
  - lib/textus/doctor/check/schema_violations.rb
@@ -204,23 +194,28 @@ files:
204
194
  - lib/textus/doctor/check/templates.rb
205
195
  - lib/textus/doctor/check/unowned_schema_fields.rb
206
196
  - lib/textus/doctor/validator.rb
207
- - lib/textus/entry.rb
208
- - lib/textus/entry/base.rb
209
- - lib/textus/entry/json.rb
210
- - lib/textus/entry/markdown.rb
211
- - lib/textus/entry/text.rb
212
- - lib/textus/entry/yaml.rb
213
197
  - lib/textus/envelope.rb
214
- - lib/textus/envelope/io/reader.rb
215
- - lib/textus/envelope/io/writer.rb
198
+ - lib/textus/envelope/reader.rb
199
+ - lib/textus/envelope/writer.rb
216
200
  - lib/textus/errors.rb
217
201
  - lib/textus/etag.rb
218
- - lib/textus/events.rb
202
+ - lib/textus/format.rb
203
+ - lib/textus/format/base.rb
204
+ - lib/textus/format/json.rb
205
+ - lib/textus/format/markdown.rb
206
+ - lib/textus/format/text.rb
207
+ - lib/textus/format/yaml.rb
219
208
  - lib/textus/gate.rb
220
209
  - lib/textus/gate/auth.rb
221
210
  - lib/textus/init.rb
222
- - lib/textus/init/templates/machine_intake.rb
223
- - lib/textus/init/templates/orientation_reducer.rb
211
+ - lib/textus/jobs.rb
212
+ - lib/textus/jobs/base.rb
213
+ - lib/textus/jobs/materialize.rb
214
+ - lib/textus/jobs/plan.rb
215
+ - lib/textus/jobs/planner.rb
216
+ - lib/textus/jobs/retention.rb
217
+ - lib/textus/jobs/sweep.rb
218
+ - lib/textus/jobs/worker.rb
224
219
  - lib/textus/key/distance.rb
225
220
  - lib/textus/key/grammar.rb
226
221
  - lib/textus/key/matching.rb
@@ -244,12 +239,10 @@ files:
244
239
  - lib/textus/manifest/entry/publish/to_paths.rb
245
240
  - lib/textus/manifest/entry/publish/tree.rb
246
241
  - lib/textus/manifest/entry/validators.rb
247
- - lib/textus/manifest/entry/validators/events.rb
248
242
  - lib/textus/manifest/entry/validators/format_matrix.rb
249
243
  - lib/textus/manifest/entry/validators/ignore.rb
250
244
  - lib/textus/manifest/entry/validators/publish.rb
251
245
  - lib/textus/manifest/policy.rb
252
- - lib/textus/manifest/policy/handler_permit.rb
253
246
  - lib/textus/manifest/policy/matcher.rb
254
247
  - lib/textus/manifest/policy/publish_target.rb
255
248
  - lib/textus/manifest/policy/react.rb
@@ -261,53 +254,23 @@ files:
261
254
  - lib/textus/manifest/schema/keys.rb
262
255
  - lib/textus/manifest/schema/validator.rb
263
256
  - lib/textus/manifest/schema/vocabulary.rb
264
- - lib/textus/pipeline/acquire/handler.rb
265
- - lib/textus/pipeline/acquire/intake.rb
266
- - lib/textus/pipeline/acquire/projection.rb
267
- - lib/textus/pipeline/acquire/serializer.rb
268
- - lib/textus/pipeline/acquire/serializer/json.rb
269
- - lib/textus/pipeline/acquire/serializer/text.rb
270
- - lib/textus/pipeline/acquire/serializer/yaml.rb
271
- - lib/textus/pipeline/engine.rb
272
- - lib/textus/pipeline/render.rb
273
257
  - lib/textus/ports/audit_log.rb
274
- - lib/textus/ports/audit_subscriber.rb
275
258
  - lib/textus/ports/build_lock.rb
276
259
  - lib/textus/ports/clock.rb
260
+ - lib/textus/ports/job_store.rb
261
+ - lib/textus/ports/job_store/job.rb
277
262
  - lib/textus/ports/publisher.rb
278
- - lib/textus/ports/queue.rb
279
- - lib/textus/ports/queue/job.rb
280
263
  - lib/textus/ports/sentinel_store.rb
281
264
  - lib/textus/ports/storage/file_stat.rb
282
265
  - lib/textus/ports/storage/file_store.rb
283
266
  - lib/textus/ports/watcher_lock.rb
284
- - lib/textus/projection.rb
267
+ - lib/textus/produce/engine.rb
268
+ - lib/textus/produce/render.rb
285
269
  - lib/textus/role.rb
286
270
  - lib/textus/schema.rb
287
271
  - lib/textus/schema/tools.rb
288
272
  - lib/textus/schemas.rb
289
273
  - lib/textus/session.rb
290
- - lib/textus/step.rb
291
- - lib/textus/step/base.rb
292
- - lib/textus/step/builtin.rb
293
- - lib/textus/step/builtin/csv_fetch.rb
294
- - lib/textus/step/builtin/ical_events_fetch.rb
295
- - lib/textus/step/builtin/json_fetch.rb
296
- - lib/textus/step/builtin/markdown_links_fetch.rb
297
- - lib/textus/step/builtin/rss_fetch.rb
298
- - lib/textus/step/catalog.rb
299
- - lib/textus/step/context.rb
300
- - lib/textus/step/discovery.rb
301
- - lib/textus/step/error_log.rb
302
- - lib/textus/step/event_bus.rb
303
- - lib/textus/step/fetch.rb
304
- - lib/textus/step/fire_report.rb
305
- - lib/textus/step/loader.rb
306
- - lib/textus/step/observe.rb
307
- - lib/textus/step/registry_store.rb
308
- - lib/textus/step/signature.rb
309
- - lib/textus/step/transform.rb
310
- - lib/textus/step/validate.rb
311
274
  - lib/textus/store.rb
312
275
  - lib/textus/surfaces/cli.rb
313
276
  - lib/textus/surfaces/cli/group.rb
@@ -330,6 +293,7 @@ files:
330
293
  - lib/textus/surfaces/mcp.rb
331
294
  - lib/textus/surfaces/mcp/catalog.rb
332
295
  - lib/textus/surfaces/mcp/errors.rb
296
+ - lib/textus/surfaces/mcp/routing.rb
333
297
  - lib/textus/surfaces/mcp/server.rb
334
298
  - lib/textus/surfaces/mcp/session.rb
335
299
  - lib/textus/surfaces/mcp/tool_schemas.rb
@@ -337,6 +301,14 @@ files:
337
301
  - lib/textus/surfaces/watcher.rb
338
302
  - lib/textus/uid.rb
339
303
  - lib/textus/version.rb
304
+ - lib/textus/workflow.rb
305
+ - lib/textus/workflow/collector.rb
306
+ - lib/textus/workflow/context.rb
307
+ - lib/textus/workflow/dsl.rb
308
+ - lib/textus/workflow/loader.rb
309
+ - lib/textus/workflow/pattern.rb
310
+ - lib/textus/workflow/registry.rb
311
+ - lib/textus/workflow/runner.rb
340
312
  homepage: https://github.com/patrick204nqh/textus
341
313
  licenses:
342
314
  - MIT
@@ -1,118 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "time"
4
-
5
- module Textus
6
- module Action
7
- class Pulse
8
- class Scanner
9
- def initialize(prefix: nil, lane: nil)
10
- @prefix = prefix
11
- @lane = lane
12
- end
13
-
14
- def call(container:, call:)
15
- @container = container
16
- @call = call
17
- @manifest = container.manifest
18
- @file_store = container.file_store
19
-
20
- rows = []
21
- @manifest.data.entries.each do |mentry|
22
- next if @prefix && !mentry.key.start_with?(@prefix)
23
- next if @lane && mentry.lane != @lane
24
-
25
- rows << row_for(mentry)
26
- end
27
- rows
28
- end
29
-
30
- private
31
-
32
- def row_for(mentry)
33
- envelope = safe_get(mentry.key)
34
- last = envelope&.meta&.dig("last_fetched_at")
35
- ttl, action = policy_for(mentry)
36
- return base_row(mentry, last).merge(status: :no_policy) if ttl.nil?
37
-
38
- basis = basis_for(mentry)
39
- expired = expired?(mentry, basis, ttl)
40
- base_row(mentry, last).merge(
41
- ttl_seconds: ttl,
42
- action: action,
43
- status: expired ? :expired : :fresh,
44
- next_due_at: basis.nil? ? nil : (basis + ttl).utc.iso8601,
45
- )
46
- end
47
-
48
- def policy_for(mentry)
49
- if mentry.intake?
50
- ttl = mentry.source.ttl_seconds
51
- return [ttl, :refresh] unless ttl.nil?
52
- end
53
- ret = @manifest.rules.for(mentry.key).retention
54
- return [ret.ttl_seconds, ret.action] unless ret.nil?
55
-
56
- [nil, nil]
57
- end
58
-
59
- def basis_for(mentry)
60
- return evaluator.intake_basis(mentry) if mentry.intake? && mentry.source.ttl_seconds
61
-
62
- mtime_for(mentry.key)
63
- end
64
-
65
- def expired?(mentry, basis, ttl)
66
- if mentry.intake? && mentry.source.ttl_seconds
67
- evaluator.verdict(mentry).stale
68
- else
69
- basis.nil? || Textus::Core::Retention::Sweep.expired?(ttl_seconds: ttl, mtime: basis, now: @call.now)
70
- end
71
- end
72
-
73
- def evaluator
74
- @evaluator ||= Textus::Core::Freshness::Evaluator.new(
75
- manifest: @manifest,
76
- file_stat: Textus::Ports::Storage::FileStat.new,
77
- clock: @call,
78
- )
79
- end
80
-
81
- def mtime_for(key)
82
- path = @manifest.resolver.resolve(key).path
83
- @file_store.exists?(path) ? Textus::Ports::Storage::FileStat.new.mtime(path) : nil
84
- rescue Textus::Error
85
- nil
86
- end
87
-
88
- def base_row(mentry, last)
89
- {
90
- key: mentry.key,
91
- lane: mentry.lane,
92
- last_fetched_at: last,
93
- age_seconds: last ? (@call.now - Time.parse(last)).to_i : nil,
94
- }
95
- end
96
-
97
- def safe_get(key)
98
- res = @manifest.resolver.resolve(key)
99
- return nil unless @file_store.exists?(res.path)
100
-
101
- raw = @file_store.read(res.path)
102
- parsed = Textus::Entry.for_format(res.entry.format).parse(raw, path: res.path)
103
- Textus::Envelope.build(
104
- key: key,
105
- mentry: res.entry,
106
- path: res.path,
107
- meta: parsed["_meta"],
108
- body: parsed["body"],
109
- etag: Textus::Etag.for_bytes(raw),
110
- content: parsed["content"],
111
- )
112
- rescue Textus::Error
113
- nil
114
- end
115
- end
116
- end
117
- end
118
- end
@@ -1,27 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Textus
4
- module Background
5
- module Job
6
- class Base
7
- def self.inherited(subclass)
8
- super
9
- return unless subclass.name
10
-
11
- TracePoint.new(:end) do |tp|
12
- if tp.self == subclass
13
- Textus::Background::Job.register(subclass)
14
- tp.disable
15
- end
16
- end.enable
17
- end
18
-
19
- def call(**)
20
- raise NotImplementedError.new("#{self.class}#call")
21
- end
22
-
23
- def args = {}
24
- end
25
- end
26
- end
27
- end
@@ -1,31 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Textus
4
- module Background
5
- module Job
6
- class Materialize < Base
7
- TYPE = "materialize"
8
-
9
- def initialize(key:)
10
- super()
11
- @key = key
12
- end
13
-
14
- def args = { key: @key }
15
-
16
- def call(container:, call:)
17
- result = Textus::Pipeline::Engine.converge(container: container, call: call, keys: [@key])
18
- return unless result.is_a?(Hash)
19
-
20
- Array(result[:failed]).each do |failure|
21
- container.steps.publish(
22
- :produce_failed,
23
- ctx: Textus::Step::Context.for(container: container, call: call),
24
- keys: [failure["key"]], error: failure["error"]
25
- )
26
- end
27
- end
28
- end
29
- end
30
- end
31
- end
@@ -1,22 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Textus
4
- module Background
5
- module Job
6
- class Refresh < Base
7
- TYPE = "refresh"
8
-
9
- def initialize(key:)
10
- super()
11
- @key = key
12
- end
13
-
14
- def args = { key: @key }
15
-
16
- def call(container:, call:)
17
- Textus::Pipeline::Engine.converge(container: container, call: call, keys: [@key])
18
- end
19
- end
20
- end
21
- end
22
- end
@@ -1,31 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Textus
4
- module Background
5
- module Job
6
- class Sweep < Base
7
- REQUIRED_ROLE = Textus::Role::AUTOMATION
8
- TYPE = "sweep"
9
-
10
- def initialize(scope: nil, key: nil)
11
- super()
12
- @scope = scope || {}
13
- @key = key
14
- end
15
-
16
- def args = { scope: @scope, key: @key }.compact
17
-
18
- def call(container:, call:)
19
- prefix = @key || (@scope.is_a?(Hash) ? @scope["prefix"] : nil)
20
- lane = @scope.is_a?(Hash) ? @scope["lane"] : nil
21
- rows = Textus::Core::Retention::Sweep.new(
22
- manifest: container.manifest,
23
- file_stat: Textus::Ports::Storage::FileStat.new,
24
- clock: Textus::Ports::Clock.new,
25
- ).call(prefix: prefix, lane: lane)
26
- Textus::Background::Retention::Apply.new(container: container, call: call).call(rows)
27
- end
28
- end
29
- end
30
- end
31
- end