textus 0.53.0 → 0.54.1

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 (160) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +52 -0
  3. data/README.md +53 -73
  4. data/SPEC.md +2 -5
  5. data/docs/architecture/README.md +149 -159
  6. data/lib/textus/action/accept.rb +0 -9
  7. data/lib/textus/action/audit.rb +0 -2
  8. data/lib/textus/action/blame.rb +0 -2
  9. data/lib/textus/action/boot.rb +2 -11
  10. data/lib/textus/action/data_mv.rb +1 -3
  11. data/lib/textus/action/deps.rb +1 -15
  12. data/lib/textus/action/doctor.rb +0 -2
  13. data/lib/textus/action/drain.rb +4 -6
  14. data/lib/textus/action/enqueue.rb +3 -5
  15. data/lib/textus/action/get.rb +1 -3
  16. data/lib/textus/action/ingest.rb +135 -0
  17. data/lib/textus/action/jobs.rb +1 -3
  18. data/lib/textus/action/key_delete.rb +0 -10
  19. data/lib/textus/action/key_delete_prefix.rb +1 -3
  20. data/lib/textus/action/key_mv.rb +1 -15
  21. data/lib/textus/action/key_mv_prefix.rb +1 -3
  22. data/lib/textus/action/list.rb +0 -2
  23. data/lib/textus/action/propose.rb +0 -2
  24. data/lib/textus/action/published.rb +0 -2
  25. data/lib/textus/action/pulse.rb +7 -38
  26. data/lib/textus/action/put.rb +1 -12
  27. data/lib/textus/action/rdeps.rb +2 -12
  28. data/lib/textus/action/reject.rb +0 -12
  29. data/lib/textus/action/rule_explain.rb +0 -4
  30. data/lib/textus/action/rule_lint.rb +1 -3
  31. data/lib/textus/action/rule_list.rb +0 -4
  32. data/lib/textus/action/schema_envelope.rb +0 -2
  33. data/lib/textus/action/uid.rb +0 -2
  34. data/lib/textus/action/where.rb +0 -2
  35. data/lib/textus/action/write_verb.rb +4 -18
  36. data/lib/textus/boot.rb +40 -109
  37. data/lib/textus/command.rb +6 -1
  38. data/lib/textus/container.rb +21 -5
  39. data/lib/textus/core/freshness/evaluator.rb +18 -34
  40. data/lib/textus/core/freshness/verdict.rb +1 -1
  41. data/lib/textus/doctor/check/notebook_sources.rb +50 -0
  42. data/lib/textus/doctor/check/protocol_version.rb +6 -6
  43. data/lib/textus/doctor/check/raw_asset_paths.rb +50 -0
  44. data/lib/textus/doctor/check/stale_reviewed_stamp.rb +54 -0
  45. data/lib/textus/doctor/check.rb +0 -1
  46. data/lib/textus/doctor.rb +3 -42
  47. data/lib/textus/envelope/reader.rb +46 -0
  48. data/lib/textus/envelope/writer.rb +209 -0
  49. data/lib/textus/envelope.rb +19 -6
  50. data/lib/textus/errors.rb +9 -5
  51. data/lib/textus/{entry → format}/base.rb +1 -6
  52. data/lib/textus/{entry → format}/json.rb +1 -4
  53. data/lib/textus/{entry → format}/markdown.rb +2 -6
  54. data/lib/textus/{entry → format}/text.rb +2 -6
  55. data/lib/textus/{entry → format}/yaml.rb +1 -4
  56. data/lib/textus/{entry.rb → format.rb} +9 -10
  57. data/lib/textus/gate/auth.rb +58 -12
  58. data/lib/textus/gate.rb +2 -0
  59. data/lib/textus/init.rb +16 -79
  60. data/lib/textus/jobs/base.rb +23 -0
  61. data/lib/textus/jobs/materialize.rb +20 -0
  62. data/lib/textus/{background → jobs}/plan.rb +1 -1
  63. data/lib/textus/jobs/planner.rb +101 -0
  64. data/lib/textus/jobs/retention.rb +48 -0
  65. data/lib/textus/jobs/sweep.rb +27 -0
  66. data/lib/textus/{background → jobs}/worker.rb +2 -2
  67. data/lib/textus/jobs.rb +15 -0
  68. data/lib/textus/key/path.rb +1 -1
  69. data/lib/textus/manifest/entry/base.rb +4 -19
  70. data/lib/textus/manifest/entry/parser.rb +27 -10
  71. data/lib/textus/manifest/entry/produced.rb +13 -34
  72. data/lib/textus/manifest/entry/publish/to_paths.rb +4 -4
  73. data/lib/textus/manifest/entry/publish.rb +0 -12
  74. data/lib/textus/manifest/entry/validators/format_matrix.rb +1 -1
  75. data/lib/textus/manifest/entry/validators.rb +0 -1
  76. data/lib/textus/manifest/policy/source.rb +14 -62
  77. data/lib/textus/manifest/policy.rb +2 -5
  78. data/lib/textus/manifest/resolver.rb +2 -2
  79. data/lib/textus/manifest/schema/contract.rb +61 -0
  80. data/lib/textus/manifest/schema/keys.rb +6 -20
  81. data/lib/textus/manifest/schema/semantics.rb +232 -0
  82. data/lib/textus/manifest/schema/validator.rb +24 -302
  83. data/lib/textus/manifest/schema/vocabulary.rb +1 -0
  84. data/lib/textus/manifest/schema.rb +1 -4
  85. data/lib/textus/manifest.rb +1 -3
  86. data/lib/textus/ports/{queue → job_store}/job.rb +1 -1
  87. data/lib/textus/ports/{queue.rb → job_store.rb} +6 -13
  88. data/lib/textus/produce/engine.rb +50 -0
  89. data/lib/textus/produce/render.rb +20 -0
  90. data/lib/textus/session.rb +15 -8
  91. data/lib/textus/store.rb +5 -14
  92. data/lib/textus/surfaces/mcp/catalog.rb +23 -11
  93. data/lib/textus/surfaces/mcp/errors.rb +0 -9
  94. data/lib/textus/surfaces/mcp/server.rb +87 -110
  95. data/lib/textus/surfaces/role_scope.rb +0 -4
  96. data/lib/textus/surfaces/watcher.rb +3 -3
  97. data/lib/textus/types.rb +15 -0
  98. data/lib/textus/version.rb +2 -2
  99. data/lib/textus/workflow/collector.rb +27 -0
  100. data/lib/textus/workflow/context.rb +5 -0
  101. data/lib/textus/workflow/dsl.rb +44 -0
  102. data/lib/textus/workflow/loader.rb +17 -0
  103. data/lib/textus/workflow/pattern.rb +18 -0
  104. data/lib/textus/workflow/registry.rb +21 -0
  105. data/lib/textus/workflow/runner.rb +99 -0
  106. data/lib/textus/workflow.rb +28 -0
  107. data/lib/textus.rb +12 -1
  108. metadata +67 -66
  109. data/lib/textus/action/pulse/scanner.rb +0 -118
  110. data/lib/textus/background/job/base.rb +0 -27
  111. data/lib/textus/background/job/materialize.rb +0 -31
  112. data/lib/textus/background/job/refresh.rb +0 -22
  113. data/lib/textus/background/job/sweep.rb +0 -31
  114. data/lib/textus/background/job.rb +0 -19
  115. data/lib/textus/background/planner/plan.rb +0 -113
  116. data/lib/textus/background/retention/apply.rb +0 -50
  117. data/lib/textus/doctor/check/handler_permit.rb +0 -34
  118. data/lib/textus/doctor/check/hooks.rb +0 -25
  119. data/lib/textus/doctor/check/intake_registration.rb +0 -46
  120. data/lib/textus/envelope/io/reader.rb +0 -48
  121. data/lib/textus/envelope/io/writer.rb +0 -211
  122. data/lib/textus/events.rb +0 -21
  123. data/lib/textus/init/templates/machine_intake.rb +0 -49
  124. data/lib/textus/init/templates/orientation_reducer.rb +0 -21
  125. data/lib/textus/manifest/entry/validators/events.rb +0 -22
  126. data/lib/textus/manifest/policy/handler_permit.rb +0 -17
  127. data/lib/textus/pipeline/acquire/handler.rb +0 -29
  128. data/lib/textus/pipeline/acquire/intake.rb +0 -132
  129. data/lib/textus/pipeline/acquire/projection.rb +0 -129
  130. data/lib/textus/pipeline/acquire/serializer/json.rb +0 -31
  131. data/lib/textus/pipeline/acquire/serializer/text.rb +0 -16
  132. data/lib/textus/pipeline/acquire/serializer/yaml.rb +0 -31
  133. data/lib/textus/pipeline/acquire/serializer.rb +0 -17
  134. data/lib/textus/pipeline/engine.rb +0 -97
  135. data/lib/textus/pipeline/render.rb +0 -25
  136. data/lib/textus/ports/audit_subscriber.rb +0 -42
  137. data/lib/textus/projection.rb +0 -103
  138. data/lib/textus/step/base.rb +0 -35
  139. data/lib/textus/step/builtin/csv_fetch.rb +0 -19
  140. data/lib/textus/step/builtin/ical_events_fetch.rb +0 -30
  141. data/lib/textus/step/builtin/json_fetch.rb +0 -18
  142. data/lib/textus/step/builtin/markdown_links_fetch.rb +0 -20
  143. data/lib/textus/step/builtin/rss_fetch.rb +0 -26
  144. data/lib/textus/step/builtin.rb +0 -22
  145. data/lib/textus/step/catalog.rb +0 -37
  146. data/lib/textus/step/context.rb +0 -74
  147. data/lib/textus/step/discovery.rb +0 -24
  148. data/lib/textus/step/error_log.rb +0 -32
  149. data/lib/textus/step/event_bus.rb +0 -113
  150. data/lib/textus/step/fetch.rb +0 -13
  151. data/lib/textus/step/fire_report.rb +0 -23
  152. data/lib/textus/step/loader.rb +0 -108
  153. data/lib/textus/step/observe.rb +0 -31
  154. data/lib/textus/step/registry_store.rb +0 -66
  155. data/lib/textus/step/signature.rb +0 -31
  156. data/lib/textus/step/transform.rb +0 -12
  157. data/lib/textus/step/validate.rb +0 -11
  158. data/lib/textus/step.rb +0 -10
  159. data/lib/textus/surfaces/mcp/session.rb +0 -9
  160. data/lib/textus/surfaces/mcp/tool_schemas.rb +0 -17
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.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Patrick
@@ -24,19 +24,47 @@ dependencies:
24
24
  - !ruby/object:Gem::Version
25
25
  version: '3.0'
26
26
  - !ruby/object:Gem::Dependency
27
- name: mustache
27
+ name: dry-schema
28
28
  requirement: !ruby/object:Gem::Requirement
29
29
  requirements:
30
30
  - - "~>"
31
31
  - !ruby/object:Gem::Version
32
- version: '1.1'
32
+ version: '1.13'
33
33
  type: :runtime
34
34
  prerelease: false
35
35
  version_requirements: !ruby/object:Gem::Requirement
36
36
  requirements:
37
37
  - - "~>"
38
38
  - !ruby/object:Gem::Version
39
- version: '1.1'
39
+ version: '1.13'
40
+ - !ruby/object:Gem::Dependency
41
+ name: dry-struct
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '1.6'
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '1.6'
54
+ - !ruby/object:Gem::Dependency
55
+ name: mcp
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '0.20'
61
+ type: :runtime
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '0.20'
40
68
  - !ruby/object:Gem::Dependency
41
69
  name: psych
42
70
  requirement: !ruby/object:Gem::Requirement
@@ -135,6 +163,7 @@ files:
135
163
  - lib/textus/action/drain.rb
136
164
  - lib/textus/action/enqueue.rb
137
165
  - lib/textus/action/get.rb
166
+ - lib/textus/action/ingest.rb
138
167
  - lib/textus/action/jobs.rb
139
168
  - lib/textus/action/key_delete.rb
140
169
  - lib/textus/action/key_delete_prefix.rb
@@ -144,7 +173,6 @@ files:
144
173
  - lib/textus/action/propose.rb
145
174
  - lib/textus/action/published.rb
146
175
  - lib/textus/action/pulse.rb
147
- - lib/textus/action/pulse/scanner.rb
148
176
  - lib/textus/action/put.rb
149
177
  - lib/textus/action/rdeps.rb
150
178
  - lib/textus/action/reject.rb
@@ -155,15 +183,6 @@ files:
155
183
  - lib/textus/action/uid.rb
156
184
  - lib/textus/action/where.rb
157
185
  - 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
186
  - lib/textus/boot.rb
168
187
  - lib/textus/call.rb
169
188
  - lib/textus/command.rb
@@ -187,40 +206,45 @@ files:
187
206
  - lib/textus/doctor/check.rb
188
207
  - lib/textus/doctor/check/audit_log.rb
189
208
  - lib/textus/doctor/check/generator_drift.rb
190
- - lib/textus/doctor/check/handler_permit.rb
191
- - lib/textus/doctor/check/hooks.rb
192
209
  - lib/textus/doctor/check/illegal_keys.rb
193
- - lib/textus/doctor/check/intake_registration.rb
194
210
  - lib/textus/doctor/check/manifest_files.rb
211
+ - lib/textus/doctor/check/notebook_sources.rb
195
212
  - lib/textus/doctor/check/orphaned_publish_targets.rb
196
213
  - lib/textus/doctor/check/proposal_targets.rb
197
214
  - lib/textus/doctor/check/protocol_version.rb
198
215
  - lib/textus/doctor/check/publish_tree_index_overlap.rb
216
+ - lib/textus/doctor/check/raw_asset_paths.rb
199
217
  - lib/textus/doctor/check/rule_ambiguity.rb
200
218
  - lib/textus/doctor/check/schema_parse_error.rb
201
219
  - lib/textus/doctor/check/schema_violations.rb
202
220
  - lib/textus/doctor/check/schemas.rb
203
221
  - lib/textus/doctor/check/sentinels.rb
222
+ - lib/textus/doctor/check/stale_reviewed_stamp.rb
204
223
  - lib/textus/doctor/check/templates.rb
205
224
  - lib/textus/doctor/check/unowned_schema_fields.rb
206
225
  - 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
226
  - lib/textus/envelope.rb
214
- - lib/textus/envelope/io/reader.rb
215
- - lib/textus/envelope/io/writer.rb
227
+ - lib/textus/envelope/reader.rb
228
+ - lib/textus/envelope/writer.rb
216
229
  - lib/textus/errors.rb
217
230
  - lib/textus/etag.rb
218
- - lib/textus/events.rb
231
+ - lib/textus/format.rb
232
+ - lib/textus/format/base.rb
233
+ - lib/textus/format/json.rb
234
+ - lib/textus/format/markdown.rb
235
+ - lib/textus/format/text.rb
236
+ - lib/textus/format/yaml.rb
219
237
  - lib/textus/gate.rb
220
238
  - lib/textus/gate/auth.rb
221
239
  - lib/textus/init.rb
222
- - lib/textus/init/templates/machine_intake.rb
223
- - lib/textus/init/templates/orientation_reducer.rb
240
+ - lib/textus/jobs.rb
241
+ - lib/textus/jobs/base.rb
242
+ - lib/textus/jobs/materialize.rb
243
+ - lib/textus/jobs/plan.rb
244
+ - lib/textus/jobs/planner.rb
245
+ - lib/textus/jobs/retention.rb
246
+ - lib/textus/jobs/sweep.rb
247
+ - lib/textus/jobs/worker.rb
224
248
  - lib/textus/key/distance.rb
225
249
  - lib/textus/key/grammar.rb
226
250
  - lib/textus/key/matching.rb
@@ -244,12 +268,10 @@ files:
244
268
  - lib/textus/manifest/entry/publish/to_paths.rb
245
269
  - lib/textus/manifest/entry/publish/tree.rb
246
270
  - lib/textus/manifest/entry/validators.rb
247
- - lib/textus/manifest/entry/validators/events.rb
248
271
  - lib/textus/manifest/entry/validators/format_matrix.rb
249
272
  - lib/textus/manifest/entry/validators/ignore.rb
250
273
  - lib/textus/manifest/entry/validators/publish.rb
251
274
  - lib/textus/manifest/policy.rb
252
- - lib/textus/manifest/policy/handler_permit.rb
253
275
  - lib/textus/manifest/policy/matcher.rb
254
276
  - lib/textus/manifest/policy/publish_target.rb
255
277
  - lib/textus/manifest/policy/react.rb
@@ -258,56 +280,28 @@ files:
258
280
  - lib/textus/manifest/resolver.rb
259
281
  - lib/textus/manifest/rules.rb
260
282
  - lib/textus/manifest/schema.rb
283
+ - lib/textus/manifest/schema/contract.rb
261
284
  - lib/textus/manifest/schema/keys.rb
285
+ - lib/textus/manifest/schema/semantics.rb
262
286
  - lib/textus/manifest/schema/validator.rb
263
287
  - 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
288
  - lib/textus/ports/audit_log.rb
274
- - lib/textus/ports/audit_subscriber.rb
275
289
  - lib/textus/ports/build_lock.rb
276
290
  - lib/textus/ports/clock.rb
291
+ - lib/textus/ports/job_store.rb
292
+ - lib/textus/ports/job_store/job.rb
277
293
  - lib/textus/ports/publisher.rb
278
- - lib/textus/ports/queue.rb
279
- - lib/textus/ports/queue/job.rb
280
294
  - lib/textus/ports/sentinel_store.rb
281
295
  - lib/textus/ports/storage/file_stat.rb
282
296
  - lib/textus/ports/storage/file_store.rb
283
297
  - lib/textus/ports/watcher_lock.rb
284
- - lib/textus/projection.rb
298
+ - lib/textus/produce/engine.rb
299
+ - lib/textus/produce/render.rb
285
300
  - lib/textus/role.rb
286
301
  - lib/textus/schema.rb
287
302
  - lib/textus/schema/tools.rb
288
303
  - lib/textus/schemas.rb
289
304
  - 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
305
  - lib/textus/store.rb
312
306
  - lib/textus/surfaces/cli.rb
313
307
  - lib/textus/surfaces/cli/group.rb
@@ -331,12 +325,19 @@ files:
331
325
  - lib/textus/surfaces/mcp/catalog.rb
332
326
  - lib/textus/surfaces/mcp/errors.rb
333
327
  - lib/textus/surfaces/mcp/server.rb
334
- - lib/textus/surfaces/mcp/session.rb
335
- - lib/textus/surfaces/mcp/tool_schemas.rb
336
328
  - lib/textus/surfaces/role_scope.rb
337
329
  - lib/textus/surfaces/watcher.rb
330
+ - lib/textus/types.rb
338
331
  - lib/textus/uid.rb
339
332
  - lib/textus/version.rb
333
+ - lib/textus/workflow.rb
334
+ - lib/textus/workflow/collector.rb
335
+ - lib/textus/workflow/context.rb
336
+ - lib/textus/workflow/dsl.rb
337
+ - lib/textus/workflow/loader.rb
338
+ - lib/textus/workflow/pattern.rb
339
+ - lib/textus/workflow/registry.rb
340
+ - lib/textus/workflow/runner.rb
340
341
  homepage: https://github.com/patrick204nqh/textus
341
342
  licenses:
342
343
  - 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
@@ -1,19 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Textus
4
- module Background
5
- module Job
6
- @registry = {}
7
-
8
- def self.registry = @registry
9
-
10
- def self.register(klass)
11
- @registry[klass::TYPE] = klass if klass.const_defined?(:TYPE, false)
12
- end
13
-
14
- def self.fetch(type)
15
- @registry.fetch(type) { raise Textus::UsageError.new("unknown job type: #{type}") }
16
- end
17
- end
18
- end
19
- end
@@ -1,113 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Textus
4
- module Background
5
- module Planner
6
- class Plan
7
- ACTIONS_BY_TRIGGER = {
8
- "convergence" => %w[materialize refresh sweep],
9
- "entry.written" => %w[materialize],
10
- "entry.deleted" => %w[materialize],
11
- "entry.moved" => %w[materialize],
12
- "proposal.accepted" => %w[materialize],
13
- "proposal.rejected" => %w[materialize],
14
- }.freeze
15
-
16
- SCOPE_RESOLVERS = {
17
- "materialize" => :producible_keys,
18
- "refresh" => :stale_intake_keys,
19
- "sweep" => :lane_keys,
20
- }.freeze
21
-
22
- def self.seed(container:, queue:, role:)
23
- jobs = new(container: container).plan(
24
- trigger: { "type" => "convergence" },
25
- role: role,
26
- )
27
- jobs.each { |j| queue.enqueue(j) }
28
- end
29
-
30
- def initialize(container:)
31
- @container = container
32
- @manifest = container.manifest
33
- end
34
-
35
- def plan(trigger:, role:)
36
- type = trigger["type"] || trigger[:type]
37
- trigger["target"] || trigger[:target]
38
- return [] if type.nil?
39
-
40
- blocks_with_react = @manifest.rules.blocks.select(&:react)
41
- if blocks_with_react.any?
42
- plan_from_rules(blocks_with_react, type, role)
43
- else
44
- plan_from_defaults(type, role)
45
- end
46
- end
47
-
48
- private
49
-
50
- def plan_from_rules(blocks, type, role)
51
- jobs = []
52
- blocks
53
- .select { |b| matches_trigger?(b.react, type) }
54
- .each do |block|
55
- do_action = block.react.raw["do"]
56
- Array(do_action).each do |action|
57
- if action == "sweep"
58
- jobs << Textus::Ports::Queue::Job.new(
59
- type: "sweep", args: { "scope" => {} }, enqueued_by: role,
60
- )
61
- else
62
- resolver = SCOPE_RESOLVERS.fetch(action, :producible_keys)
63
- keys = send(resolver, nil)
64
- keys.each { |key| jobs << job(action, key, role) }
65
- end
66
- end
67
- end
68
- jobs
69
- end
70
-
71
- def plan_from_defaults(type, role)
72
- actions = ACTIONS_BY_TRIGGER.fetch(type, [])
73
- jobs = []
74
- producible_keys(nil).each { |k| jobs << job("materialize", k, role) } if actions.include?("materialize")
75
- stale_intake_keys(nil).each { |k| jobs << job("refresh", k, role) } if actions.include?("refresh")
76
- if actions.include?("sweep")
77
- jobs << Textus::Ports::Queue::Job.new(
78
- type: "sweep", args: { "scope" => {} }, enqueued_by: role,
79
- )
80
- end
81
- jobs
82
- end
83
-
84
- def matches_trigger?(react, type)
85
- on = react.raw["on"]
86
- Array(on).include?(type)
87
- end
88
-
89
- def job(type, key, enqueued_by)
90
- Textus::Ports::Queue::Job.new(type: type, args: { "key" => key }, enqueued_by: enqueued_by)
91
- end
92
-
93
- def producible_keys(_target)
94
- @manifest.data.entries
95
- .select { |e| e.derived? || !e.publish_tree.nil? || !e.publish_to.empty? }
96
- .map(&:key)
97
- end
98
-
99
- def stale_intake_keys(_target)
100
- Textus::Core::Freshness::Evaluator.new(
101
- manifest: @manifest,
102
- file_stat: Textus::Ports::Storage::FileStat.new,
103
- clock: Textus::Ports::Clock.new,
104
- ).stale_intake_keys(prefix: nil, lane: nil)
105
- end
106
-
107
- def lane_keys(_target)
108
- @manifest.data.entries.map(&:key)
109
- end
110
- end
111
- end
112
- end
113
- end
@@ -1,50 +0,0 @@
1
- require "fileutils"
2
-
3
- module Textus
4
- module Background
5
- module Retention
6
- class Apply
7
- def initialize(container:, call:)
8
- @container = container
9
- @call = call
10
- end
11
-
12
- def call(rows)
13
- out = { dropped: [], archived: [], failed: [] }
14
- rows.each do |row|
15
- key = row["key"]
16
- begin
17
- case row["action"]
18
- when "drop"
19
- delete(key)
20
- out[:dropped] << key
21
- when "archive"
22
- archive_leaf(row)
23
- delete(key)
24
- out[:archived] << key
25
- end
26
- rescue Textus::Error => e
27
- out[:failed] << { "key" => key, "error" => e.message }
28
- end
29
- end
30
- out
31
- end
32
-
33
- private
34
-
35
- def archive_leaf(row)
36
- src = row["path"]
37
- root = @container.root.to_s
38
- rel = src.delete_prefix("#{root}/")
39
- dest = File.join(root, "archive", rel)
40
- FileUtils.mkdir_p(File.dirname(dest))
41
- FileUtils.cp(src, dest)
42
- end
43
-
44
- def delete(key)
45
- Textus::Action::KeyDelete.new(key: key).call(container: @container, call: @call)
46
- end
47
- end
48
- end
49
- end
50
- end