textus 0.52.0 → 0.53.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +25 -0
- data/README.md +62 -54
- data/SPEC.md +62 -187
- data/docs/architecture/README.md +88 -77
- data/exe/textus +1 -1
- data/lib/textus/action/accept.rb +53 -0
- data/lib/textus/action/audit.rb +133 -0
- data/lib/textus/action/base.rb +42 -0
- data/lib/textus/{read → action}/blame.rb +30 -22
- data/lib/textus/action/boot.rb +26 -0
- data/lib/textus/action/data_mv.rb +71 -0
- data/lib/textus/action/deps.rb +48 -0
- data/lib/textus/action/doctor.rb +26 -0
- data/lib/textus/action/drain.rb +41 -0
- data/lib/textus/action/enqueue.rb +55 -0
- data/lib/textus/action/get.rb +80 -0
- data/lib/textus/action/jobs.rb +38 -0
- data/lib/textus/action/key_delete.rb +46 -0
- data/lib/textus/action/key_delete_prefix.rb +46 -0
- data/lib/textus/action/key_mv.rb +143 -0
- data/lib/textus/action/key_mv_prefix.rb +59 -0
- data/lib/textus/action/list.rb +44 -0
- data/lib/textus/action/propose.rb +54 -0
- data/lib/textus/action/published.rb +26 -0
- data/lib/textus/action/pulse/scanner.rb +118 -0
- data/lib/textus/action/pulse.rb +87 -0
- data/lib/textus/action/put.rb +63 -0
- data/lib/textus/action/rdeps.rb +49 -0
- data/lib/textus/action/reject.rb +49 -0
- data/lib/textus/action/rule_explain.rb +95 -0
- data/lib/textus/action/rule_lint.rb +70 -0
- data/lib/textus/action/rule_list.rb +46 -0
- data/lib/textus/action/schema_envelope.rb +31 -0
- data/lib/textus/action/uid.rb +35 -0
- data/lib/textus/action/where.rb +38 -0
- data/lib/textus/action/write_verb.rb +58 -0
- data/lib/textus/background/job/base.rb +27 -0
- data/lib/textus/background/job/materialize.rb +31 -0
- data/lib/textus/background/job/refresh.rb +22 -0
- data/lib/textus/background/job/sweep.rb +31 -0
- data/lib/textus/background/job.rb +19 -0
- data/lib/textus/background/plan.rb +9 -0
- data/lib/textus/background/planner/plan.rb +113 -0
- data/lib/textus/{maintenance → background}/retention/apply.rb +7 -9
- data/lib/textus/background/worker.rb +67 -0
- data/lib/textus/boot.rb +53 -45
- data/lib/textus/command.rb +36 -0
- data/lib/textus/container.rb +1 -1
- data/lib/textus/{domain → core}/duration.rb +1 -1
- data/lib/textus/{domain → core}/freshness/evaluator.rb +11 -11
- data/lib/textus/{domain → core}/freshness/verdict.rb +2 -2
- data/lib/textus/{domain → core}/freshness.rb +2 -2
- data/lib/textus/{domain → core}/retention/sweep.rb +7 -7
- data/lib/textus/{domain → core}/retention.rb +2 -2
- data/lib/textus/{domain → core}/sentinel.rb +1 -1
- data/lib/textus/doctor/check/generator_drift.rb +1 -1
- data/lib/textus/doctor/check/handler_permit.rb +34 -0
- data/lib/textus/doctor/check/hooks.rb +11 -18
- data/lib/textus/doctor/check/illegal_keys.rb +1 -1
- data/lib/textus/doctor/check/intake_registration.rb +5 -5
- data/lib/textus/doctor/check/proposal_targets.rb +3 -3
- data/lib/textus/doctor/check/rule_ambiguity.rb +2 -2
- data/lib/textus/doctor/check/schema_violations.rb +8 -2
- data/lib/textus/doctor/check.rb +12 -9
- data/lib/textus/{read → doctor}/validator.rb +22 -13
- data/lib/textus/doctor.rb +6 -6
- data/lib/textus/envelope/io/writer.rb +65 -36
- data/lib/textus/envelope.rb +5 -3
- data/lib/textus/errors.rb +17 -9
- data/lib/textus/events.rb +21 -0
- data/lib/textus/gate/auth.rb +181 -0
- data/lib/textus/gate.rb +114 -0
- data/lib/textus/init/templates/machine_intake.rb +39 -35
- data/lib/textus/init/templates/orientation_reducer.rb +15 -11
- data/lib/textus/init.rb +90 -73
- data/lib/textus/key/path.rb +9 -2
- data/lib/textus/layout.rb +13 -0
- data/lib/textus/manifest/data.rb +14 -14
- data/lib/textus/manifest/entry/base.rb +15 -11
- data/lib/textus/manifest/entry/parser.rb +6 -6
- data/lib/textus/manifest/entry/produced.rb +3 -2
- data/lib/textus/manifest/entry/publish/mode.rb +1 -1
- data/lib/textus/manifest/entry/publish/to_paths.rb +2 -1
- data/lib/textus/manifest/entry/validators/events.rb +1 -1
- data/lib/textus/{domain/policy/handler_allowlist.rb → manifest/policy/handler_permit.rb} +4 -4
- data/lib/textus/{domain → manifest}/policy/matcher.rb +2 -2
- data/lib/textus/{domain → manifest}/policy/publish_target.rb +2 -2
- data/lib/textus/manifest/policy/react.rb +30 -0
- data/lib/textus/{domain → manifest}/policy/retention.rb +3 -3
- data/lib/textus/{domain → manifest}/policy/source.rb +24 -19
- data/lib/textus/manifest/policy.rb +36 -48
- data/lib/textus/manifest/resolver.rb +3 -2
- data/lib/textus/manifest/rules.rb +4 -4
- data/lib/textus/manifest/schema/keys.rb +17 -11
- data/lib/textus/manifest/schema/validator.rb +24 -22
- data/lib/textus/manifest/schema/vocabulary.rb +1 -1
- data/lib/textus/manifest/schema.rb +2 -2
- data/lib/textus/manifest.rb +2 -2
- data/lib/textus/{produce → pipeline}/acquire/handler.rb +2 -2
- data/lib/textus/{produce → pipeline}/acquire/intake.rb +22 -20
- data/lib/textus/{produce → pipeline}/acquire/projection.rb +13 -11
- data/lib/textus/{produce → pipeline}/acquire/serializer/json.rb +2 -2
- data/lib/textus/{produce → pipeline}/acquire/serializer/text.rb +1 -1
- data/lib/textus/{produce → pipeline}/acquire/serializer/yaml.rb +2 -2
- data/lib/textus/{produce → pipeline}/acquire/serializer.rb +1 -1
- data/lib/textus/{produce → pipeline}/engine.rb +7 -5
- data/lib/textus/{produce → pipeline}/render.rb +3 -1
- data/lib/textus/ports/audit_log.rb +31 -5
- data/lib/textus/ports/audit_subscriber.rb +4 -4
- data/lib/textus/{domain/jobs → ports/queue}/job.rb +19 -12
- data/lib/textus/ports/queue.rb +1 -1
- data/lib/textus/ports/sentinel_store.rb +2 -2
- data/lib/textus/ports/watcher_lock.rb +48 -0
- data/lib/textus/projection.rb +8 -8
- data/lib/textus/schema/tools.rb +4 -3
- data/lib/textus/session.rb +6 -3
- data/lib/textus/step/base.rb +35 -0
- data/lib/textus/step/builtin/csv_fetch.rb +19 -0
- data/lib/textus/step/builtin/ical_events_fetch.rb +30 -0
- data/lib/textus/step/builtin/json_fetch.rb +18 -0
- data/lib/textus/step/builtin/markdown_links_fetch.rb +20 -0
- data/lib/textus/step/builtin/rss_fetch.rb +26 -0
- data/lib/textus/step/builtin.rb +22 -0
- data/lib/textus/{hooks → step}/catalog.rb +3 -3
- data/lib/textus/{hooks → step}/context.rb +15 -13
- data/lib/textus/step/discovery.rb +24 -0
- data/lib/textus/{hooks → step}/error_log.rb +1 -1
- data/lib/textus/{hooks → step}/event_bus.rb +15 -16
- data/lib/textus/step/fetch.rb +13 -0
- data/lib/textus/{hooks → step}/fire_report.rb +1 -1
- data/lib/textus/step/loader.rb +108 -0
- data/lib/textus/step/observe.rb +31 -0
- data/lib/textus/step/registry_store.rb +66 -0
- data/lib/textus/{hooks → step}/signature.rb +1 -1
- data/lib/textus/step/transform.rb +12 -0
- data/lib/textus/step/validate.rb +11 -0
- data/lib/textus/step.rb +10 -0
- data/lib/textus/store.rb +17 -15
- data/lib/textus/surfaces/cli/group/data.rb +11 -0
- data/lib/textus/surfaces/cli/group/key.rb +11 -0
- data/lib/textus/surfaces/cli/group/mcp.rb +11 -0
- data/lib/textus/surfaces/cli/group/rule.rb +11 -0
- data/lib/textus/surfaces/cli/group/schema.rb +11 -0
- data/lib/textus/surfaces/cli/group.rb +50 -0
- data/lib/textus/surfaces/cli/runner.rb +236 -0
- data/lib/textus/surfaces/cli/verb/doctor.rb +21 -0
- data/lib/textus/surfaces/cli/verb/get.rb +21 -0
- data/lib/textus/surfaces/cli/verb/init.rb +20 -0
- data/lib/textus/surfaces/cli/verb/mcp_serve.rb +24 -0
- data/lib/textus/surfaces/cli/verb/put.rb +30 -0
- data/lib/textus/surfaces/cli/verb/schema_diff.rb +17 -0
- data/lib/textus/surfaces/cli/verb/schema_init.rb +21 -0
- data/lib/textus/surfaces/cli/verb/schema_migrate.rb +21 -0
- data/lib/textus/surfaces/cli/verb/watch.rb +19 -0
- data/lib/textus/surfaces/cli/verb.rb +111 -0
- data/lib/textus/surfaces/cli.rb +148 -0
- data/lib/textus/surfaces/mcp/catalog.rb +99 -0
- data/lib/textus/surfaces/mcp/errors.rb +34 -0
- data/lib/textus/surfaces/mcp/server.rb +145 -0
- data/lib/textus/surfaces/mcp/session.rb +9 -0
- data/lib/textus/surfaces/mcp/tool_schemas.rb +17 -0
- data/lib/textus/surfaces/mcp.rb +8 -0
- data/lib/textus/surfaces/role_scope.rb +38 -0
- data/lib/textus/surfaces/watcher.rb +38 -0
- data/lib/textus/version.rb +1 -1
- data/lib/textus.rb +64 -22
- metadata +132 -118
- data/lib/textus/cli/group/hook.rb +0 -9
- data/lib/textus/cli/group/key.rb +0 -9
- data/lib/textus/cli/group/mcp.rb +0 -9
- data/lib/textus/cli/group/rule.rb +0 -9
- data/lib/textus/cli/group/schema.rb +0 -9
- data/lib/textus/cli/group/zone.rb +0 -9
- data/lib/textus/cli/group.rb +0 -48
- data/lib/textus/cli/runner.rb +0 -193
- data/lib/textus/cli/verb/doctor.rb +0 -17
- data/lib/textus/cli/verb/get.rb +0 -18
- data/lib/textus/cli/verb/hook_run.rb +0 -48
- data/lib/textus/cli/verb/hooks.rb +0 -50
- data/lib/textus/cli/verb/init.rb +0 -18
- data/lib/textus/cli/verb/mcp_serve.rb +0 -22
- data/lib/textus/cli/verb/put.rb +0 -30
- data/lib/textus/cli/verb/schema_diff.rb +0 -15
- data/lib/textus/cli/verb/schema_init.rb +0 -19
- data/lib/textus/cli/verb/schema_migrate.rb +0 -19
- data/lib/textus/cli/verb/serve.rb +0 -19
- data/lib/textus/cli/verb.rb +0 -116
- data/lib/textus/cli.rb +0 -138
- data/lib/textus/dispatcher.rb +0 -54
- data/lib/textus/doctor/check/handler_allowlist.rb +0 -34
- data/lib/textus/domain/action.rb +0 -9
- data/lib/textus/domain/jobs/registry.rb +0 -37
- data/lib/textus/domain/permission.rb +0 -7
- data/lib/textus/domain/policy/base_guards.rb +0 -25
- data/lib/textus/domain/policy/evaluation.rb +0 -15
- data/lib/textus/domain/policy/guard.rb +0 -35
- data/lib/textus/domain/policy/guard_factory.rb +0 -40
- data/lib/textus/domain/policy/predicates/author_held.rb +0 -33
- data/lib/textus/domain/policy/predicates/etag_match.rb +0 -32
- data/lib/textus/domain/policy/predicates/fresh_within.rb +0 -59
- data/lib/textus/domain/policy/predicates/registry.rb +0 -39
- data/lib/textus/domain/policy/predicates/schema_valid.rb +0 -61
- data/lib/textus/domain/policy/predicates/target_is_canon.rb +0 -33
- data/lib/textus/domain/policy/predicates/zone_writable_by.rb +0 -39
- data/lib/textus/hooks/builtin.rb +0 -70
- data/lib/textus/hooks/loader.rb +0 -54
- data/lib/textus/hooks/rpc_registry.rb +0 -43
- data/lib/textus/jobs/handlers.rb +0 -62
- data/lib/textus/jobs/scheduler.rb +0 -36
- data/lib/textus/jobs/seeder.rb +0 -57
- data/lib/textus/maintenance/drain.rb +0 -42
- data/lib/textus/maintenance/key_delete_prefix.rb +0 -48
- data/lib/textus/maintenance/key_mv_prefix.rb +0 -68
- data/lib/textus/maintenance/rule_lint.rb +0 -66
- data/lib/textus/maintenance/serve.rb +0 -30
- data/lib/textus/maintenance/worker.rb +0 -74
- data/lib/textus/maintenance/zone_mv.rb +0 -64
- data/lib/textus/maintenance.rb +0 -15
- data/lib/textus/mcp/catalog.rb +0 -70
- data/lib/textus/mcp/errors.rb +0 -32
- data/lib/textus/mcp/server.rb +0 -138
- data/lib/textus/mcp/session.rb +0 -7
- data/lib/textus/mcp/tool_schemas.rb +0 -15
- data/lib/textus/mcp.rb +0 -6
- data/lib/textus/mustache.rb +0 -117
- data/lib/textus/ports/produce_on_write_subscriber.rb +0 -73
- data/lib/textus/produce/events.rb +0 -36
- data/lib/textus/read/audit.rb +0 -130
- data/lib/textus/read/boot.rb +0 -26
- data/lib/textus/read/capabilities.rb +0 -70
- data/lib/textus/read/deps.rb +0 -38
- data/lib/textus/read/doctor.rb +0 -27
- data/lib/textus/read/freshness.rb +0 -152
- data/lib/textus/read/get.rb +0 -73
- data/lib/textus/read/jobs.rb +0 -31
- data/lib/textus/read/list.rb +0 -24
- data/lib/textus/read/published.rb +0 -22
- data/lib/textus/read/pulse.rb +0 -98
- data/lib/textus/read/rdeps.rb +0 -39
- data/lib/textus/read/rule_explain.rb +0 -96
- data/lib/textus/read/rule_list.rb +0 -54
- data/lib/textus/read/schema_envelope.rb +0 -25
- data/lib/textus/read/uid.rb +0 -29
- data/lib/textus/read/validate_all.rb +0 -36
- data/lib/textus/read/where.rb +0 -24
- data/lib/textus/role_scope.rb +0 -78
- data/lib/textus/write/accept.rb +0 -58
- data/lib/textus/write/enqueue.rb +0 -50
- data/lib/textus/write/key_delete.rb +0 -65
- data/lib/textus/write/key_mv.rb +0 -141
- data/lib/textus/write/propose.rb +0 -54
- data/lib/textus/write/put.rb +0 -74
- data/lib/textus/write/reject.rb +0 -68
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Textus
|
|
4
|
-
module Domain
|
|
5
|
-
module Policy
|
|
6
|
-
module Predicates
|
|
7
|
-
# Predicate #0 of every write guard. Wraps the post-0.31.0 capability
|
|
8
|
-
# topology gate (role.can ⊇ verb_for(zone.kind)). On failure, #error
|
|
9
|
-
# raises the capability-shaped WriteForbidden so the topology refusal
|
|
10
|
-
# — textus's signature product feature — is unchanged.
|
|
11
|
-
class ZoneWritableBy
|
|
12
|
-
attr_reader :reason
|
|
13
|
-
|
|
14
|
-
def name = "zone_writable_by"
|
|
15
|
-
|
|
16
|
-
def call(eval)
|
|
17
|
-
manifest = eval.manifest
|
|
18
|
-
@mentry = manifest.resolver.resolve(eval.target).entry
|
|
19
|
-
return true if manifest.policy.permission_for(@mentry.zone.to_s).allows_write?(eval.actor)
|
|
20
|
-
|
|
21
|
-
@verb = manifest.policy.verb_for_zone(@mentry.zone) # capability the kind requires
|
|
22
|
-
@holders = manifest.policy.roles_with_capability(@verb)
|
|
23
|
-
@reason = "zone '#{@mentry.zone}' needs capability '#{@verb}'; '#{eval.actor}' lacks it"
|
|
24
|
-
false
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
# Matches the capability-shaped WriteForbidden landed by ADR 0030
|
|
28
|
-
# Task 3:
|
|
29
|
-
# WriteForbidden.new(key, zone, verb:, holders:)
|
|
30
|
-
# → "writing '<k>' (zone '<z>') needs capability '<verb>'",
|
|
31
|
-
# hint: "held by: <holders>; pass --as=<role>".
|
|
32
|
-
def error(_eval)
|
|
33
|
-
Textus::WriteForbidden.new(@mentry.key, @mentry.zone, verb: @verb, holders: @holders)
|
|
34
|
-
end
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
end
|
|
38
|
-
end
|
|
39
|
-
end
|
data/lib/textus/hooks/builtin.rb
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
require "json"
|
|
2
|
-
require "csv"
|
|
3
|
-
require "yaml"
|
|
4
|
-
require "rexml/document"
|
|
5
|
-
|
|
6
|
-
module Textus
|
|
7
|
-
module Hooks
|
|
8
|
-
module Builtin
|
|
9
|
-
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
|
10
|
-
def self.register_all(events:, rpc:) # rubocop:disable Lint/UnusedMethodArgument
|
|
11
|
-
rpc.register(:resolve_handler, :json) do |caps:, config:, args:|
|
|
12
|
-
_ = caps
|
|
13
|
-
_ = args
|
|
14
|
-
data = JSON.parse(config["bytes"].to_s)
|
|
15
|
-
{ _meta: {}, body: YAML.dump(data) }
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
rpc.register(:resolve_handler, :csv) do |caps:, config:, args:|
|
|
19
|
-
_ = caps
|
|
20
|
-
_ = args
|
|
21
|
-
rows = CSV.parse(config["bytes"].to_s, headers: true).map(&:to_h)
|
|
22
|
-
{ _meta: {}, body: YAML.dump(rows) }
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
rpc.register(:resolve_handler, :"markdown-links") do |caps:, config:, args:|
|
|
26
|
-
_ = caps
|
|
27
|
-
_ = args
|
|
28
|
-
links = config["bytes"].to_s.scan(%r{\[([^\]]+)\]\((https?://[^)\s]+)\)}).map do |text, href|
|
|
29
|
-
{ "text" => text, "href" => href }
|
|
30
|
-
end
|
|
31
|
-
{ _meta: {}, body: YAML.dump(links) }
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
rpc.register(:resolve_handler, :"ical-events") do |caps:, config:, args:|
|
|
35
|
-
_ = caps
|
|
36
|
-
_ = args
|
|
37
|
-
events_list = []
|
|
38
|
-
current = nil
|
|
39
|
-
config["bytes"].to_s.each_line do |line|
|
|
40
|
-
line = line.strip
|
|
41
|
-
case line
|
|
42
|
-
when "BEGIN:VEVENT" then current = {}
|
|
43
|
-
when "END:VEVENT"
|
|
44
|
-
events_list << current if current
|
|
45
|
-
current = nil
|
|
46
|
-
when /\A(SUMMARY|DTSTART|DTEND|UID|LOCATION|DESCRIPTION):(.*)\z/
|
|
47
|
-
current[Regexp.last_match(1).downcase] = Regexp.last_match(2) if current
|
|
48
|
-
end
|
|
49
|
-
end
|
|
50
|
-
{ _meta: {}, body: YAML.dump(events_list) }
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
rpc.register(:resolve_handler, :rss) do |caps:, config:, args:|
|
|
54
|
-
_ = caps
|
|
55
|
-
_ = args
|
|
56
|
-
doc = REXML::Document.new(config["bytes"].to_s)
|
|
57
|
-
items = doc.elements.to_a("//item").map do |item|
|
|
58
|
-
{
|
|
59
|
-
"title" => item.elements["title"]&.text,
|
|
60
|
-
"link" => item.elements["link"]&.text,
|
|
61
|
-
"pubDate" => item.elements["pubDate"]&.text,
|
|
62
|
-
}
|
|
63
|
-
end
|
|
64
|
-
{ _meta: {}, body: YAML.dump(items) }
|
|
65
|
-
end
|
|
66
|
-
end
|
|
67
|
-
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
|
|
68
|
-
end
|
|
69
|
-
end
|
|
70
|
-
end
|
data/lib/textus/hooks/loader.rb
DELETED
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
module Hooks
|
|
3
|
-
class Loader
|
|
4
|
-
# A small DSL object passed to user hook blocks. Routes `.on(...)` to the
|
|
5
|
-
# EventBus and `.rpc(...)` / `.register(...)` to the RpcRegistry.
|
|
6
|
-
class Dsl
|
|
7
|
-
def initialize(events:, rpc:)
|
|
8
|
-
@events = events
|
|
9
|
-
@rpc = rpc
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
# Pubsub registration — delegates to EventBus.
|
|
13
|
-
# Also handles RPC event names by delegating to RpcRegistry.
|
|
14
|
-
def on(event, name, keys: nil, &)
|
|
15
|
-
if Hooks::Catalog::RPC.key?(event.to_sym)
|
|
16
|
-
@rpc.register(event, name, &)
|
|
17
|
-
else
|
|
18
|
-
@events.register(event, name, keys: keys, &)
|
|
19
|
-
end
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
# Explicit RPC registration.
|
|
23
|
-
def register(event, name, &)
|
|
24
|
-
@rpc.register(event, name, &)
|
|
25
|
-
end
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
def initialize(events:, rpc:)
|
|
29
|
-
@events = events
|
|
30
|
-
@rpc = rpc
|
|
31
|
-
@dsl = Dsl.new(events: @events, rpc: @rpc)
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
def load_dir(dir)
|
|
35
|
-
return unless File.directory?(dir)
|
|
36
|
-
|
|
37
|
-
# Discard any leftover blocks from a prior partial load.
|
|
38
|
-
Textus.drain_hook_blocks
|
|
39
|
-
|
|
40
|
-
Dir.glob(File.join(dir, "**/*.rb")).sort.each do |f| # rubocop:disable Lint/RedundantDirGlobSort
|
|
41
|
-
load(f)
|
|
42
|
-
rescue StandardError, ScriptError => e
|
|
43
|
-
raise UsageError.new("failed loading hook #{File.basename(f)}: #{e.class}: #{e.message}")
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
Textus.drain_hook_blocks.each do |blk|
|
|
47
|
-
blk.call(@dsl)
|
|
48
|
-
rescue StandardError, ScriptError => e
|
|
49
|
-
raise UsageError.new("failed registering hook: #{e.class}: #{e.message}")
|
|
50
|
-
end
|
|
51
|
-
end
|
|
52
|
-
end
|
|
53
|
-
end
|
|
54
|
-
end
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Textus
|
|
4
|
-
module Hooks
|
|
5
|
-
class RpcRegistry
|
|
6
|
-
def initialize
|
|
7
|
-
@table = Hash.new { |h, k| h[k] = {} }
|
|
8
|
-
end
|
|
9
|
-
|
|
10
|
-
def register(event, name, &blk)
|
|
11
|
-
event_sym = event.to_sym
|
|
12
|
-
raise UsageError.new("#{event_sym} is a pubsub event; register on EventBus") if Catalog::PUBSUB.key?(event_sym)
|
|
13
|
-
|
|
14
|
-
required = Catalog::RPC[event_sym] or raise UsageError.new("unknown RPC event: #{event}")
|
|
15
|
-
sig = Signature.new(blk)
|
|
16
|
-
missing = sig.missing(required)
|
|
17
|
-
raise UsageError.new("#{event_sym} RPC must accept kwargs: #{required.join(", ")} (missing: #{missing.join(", ")})") if missing.any?
|
|
18
|
-
|
|
19
|
-
name = name.to_sym
|
|
20
|
-
raise UsageError.new("#{event_sym} '#{name}' already registered") if @table[event_sym].key?(name)
|
|
21
|
-
|
|
22
|
-
@table[event_sym][name] = blk
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
def names(event) = @table[event.to_sym].keys
|
|
26
|
-
|
|
27
|
-
def callable(event, name)
|
|
28
|
-
@table[event.to_sym][name.to_sym] or raise UsageError.new("unknown #{event}: #{name}")
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
# Invoke a registered callable, injecting `caps:` only if the callable
|
|
32
|
-
# declares it (or accepts keyrest). Mis-named kwargs (e.g. the legacy
|
|
33
|
-
# `store:`) are rejected at registration time, not here.
|
|
34
|
-
def invoke(event, name, caps:, **other)
|
|
35
|
-
blk = callable(event, name)
|
|
36
|
-
sig = Signature.new(blk)
|
|
37
|
-
kwargs = other.dup
|
|
38
|
-
kwargs[:caps] = caps if sig.accepts_keyrest? || sig.declared_keys.include?(:caps)
|
|
39
|
-
blk.call(**kwargs)
|
|
40
|
-
end
|
|
41
|
-
end
|
|
42
|
-
end
|
|
43
|
-
end
|
data/lib/textus/jobs/handlers.rb
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
module Jobs
|
|
3
|
-
# Wires the closed allow-list of convergence job types to the existing
|
|
4
|
-
# convergence code. Authority is read from the job's frozen `enqueued_by`
|
|
5
|
-
# and turned into the Call the handler runs under: produce self-elevates
|
|
6
|
-
# inside Produce::Engine regardless; destructive sweep runs AS the caller.
|
|
7
|
-
module Handlers
|
|
8
|
-
module_function
|
|
9
|
-
|
|
10
|
-
def registry
|
|
11
|
-
reg = Textus::Domain::Jobs::Registry.new
|
|
12
|
-
# produce is pure (self-elevates) — any caller may request a rematerialize.
|
|
13
|
-
reg.register("materialize", handler: method(:produce))
|
|
14
|
-
reg.register("re-pull", handler: method(:produce))
|
|
15
|
-
# sweep is destructive — gate ad-hoc enqueue to the automation authority.
|
|
16
|
-
reg.register("sweep", handler: method(:sweep), required_role: Textus::Role::AUTOMATION)
|
|
17
|
-
reg
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
# produce: render derived / re-pull intake for a single key. Engine
|
|
21
|
-
# self-elevates to the build actor internally; the passed call carries
|
|
22
|
-
# only correlation/dry_run plus the stamped role for audit. Engine#call
|
|
23
|
-
# isolates per-key produce errors into its result hash rather than raising,
|
|
24
|
-
# so surface them as :produce_failed events (the converge result hash used
|
|
25
|
-
# to carry them; the worker drops the return, so re-publish here).
|
|
26
|
-
def produce(job:, container:)
|
|
27
|
-
call = call_for(job)
|
|
28
|
-
result = Textus::Produce::Engine.converge(container: container, call: call, keys: [job.args["key"]])
|
|
29
|
-
return unless result.is_a?(Hash)
|
|
30
|
-
|
|
31
|
-
Array(result[:failed]).each do |failure|
|
|
32
|
-
container.events.publish(
|
|
33
|
-
:produce_failed,
|
|
34
|
-
ctx: Textus::Hooks::Context.for(container: container, call: call),
|
|
35
|
-
keys: [failure["key"]], error: failure["error"]
|
|
36
|
-
)
|
|
37
|
-
end
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
# sweep: compute retention rows for the scope, then apply destructively AS
|
|
41
|
-
# the job's role (no self-elevation).
|
|
42
|
-
def sweep(job:, container:)
|
|
43
|
-
call = call_for(job)
|
|
44
|
-
scope = job.args["scope"]
|
|
45
|
-
rows = Textus::Domain::Retention::Sweep.new(
|
|
46
|
-
manifest: container.manifest,
|
|
47
|
-
file_stat: Textus::Ports::Storage::FileStat.new,
|
|
48
|
-
clock: Textus::Ports::Clock.new,
|
|
49
|
-
).call(prefix: scope_prefix(scope), zone: scope_zone(scope))
|
|
50
|
-
Textus::Maintenance::Retention::Apply.new(container: container, call: call).call(rows)
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
def call_for(job)
|
|
54
|
-
Textus::Call.build(role: job.enqueued_by || Textus::Role::AUTOMATION)
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
# A scope is `{ "prefix" => ..., "zone" => ... }` or nil (whole store).
|
|
58
|
-
def scope_prefix(scope) = scope.is_a?(Hash) ? scope["prefix"] : nil
|
|
59
|
-
def scope_zone(scope) = scope.is_a?(Hash) ? scope["zone"] : nil
|
|
60
|
-
end
|
|
61
|
-
end
|
|
62
|
-
end
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
module Jobs
|
|
3
|
-
# Time-based seeding for the daemon: at each tick, enqueue a re-pull job for
|
|
4
|
-
# every intake key past its source.ttl and a sweep job to GC entries past
|
|
5
|
-
# retention.ttl. Dedup means a job already queued from a prior tick is a
|
|
6
|
-
# no-op. Both are stamped automation (the daemon's own authority); the sweep
|
|
7
|
-
# handler runs retention as that role.
|
|
8
|
-
class Scheduler
|
|
9
|
-
def initialize(container:, queue:)
|
|
10
|
-
@container = container
|
|
11
|
-
@queue = queue
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
def run_once
|
|
15
|
-
stale_intake.each do |key|
|
|
16
|
-
@queue.enqueue(job("re-pull", { "key" => key }))
|
|
17
|
-
end
|
|
18
|
-
@queue.enqueue(job("sweep", { "scope" => { "prefix" => nil, "zone" => nil } }))
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
private
|
|
22
|
-
|
|
23
|
-
def stale_intake
|
|
24
|
-
Textus::Domain::Freshness::Evaluator.new(
|
|
25
|
-
manifest: @container.manifest,
|
|
26
|
-
file_stat: Textus::Ports::Storage::FileStat.new,
|
|
27
|
-
clock: Textus::Ports::Clock.new,
|
|
28
|
-
).stale_intake_keys(prefix: nil, zone: nil)
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
def job(type, args)
|
|
32
|
-
Textus::Domain::Jobs::Job.new(type: type, args: args, enqueued_by: Textus::Role::AUTOMATION)
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
end
|
|
36
|
-
end
|
data/lib/textus/jobs/seeder.rb
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
module Jobs
|
|
3
|
-
# Enqueues the full convergence set for a scope: a produce job per derived /
|
|
4
|
-
# publish_tree / publish.to entry, a re-pull job per stale intake key, and a
|
|
5
|
-
# single sweep job for the scope. The scope logic mirrors
|
|
6
|
-
# the converge scope (Produce::Engine) so `drain` and `serve` converge identically.
|
|
7
|
-
# Produce jobs self-elevate (stamped automation); the sweep job carries the
|
|
8
|
-
# caller's role (destructive runs as caller).
|
|
9
|
-
class Seeder
|
|
10
|
-
def initialize(container:, queue:, call:)
|
|
11
|
-
@container = container
|
|
12
|
-
@queue = queue
|
|
13
|
-
@call = call
|
|
14
|
-
@manifest = container.manifest
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
def seed(prefix:, zone:)
|
|
18
|
-
file_stat = Textus::Ports::Storage::FileStat.new
|
|
19
|
-
|
|
20
|
-
producible_keys(prefix, zone).each do |key|
|
|
21
|
-
@queue.enqueue(job("materialize", { "key" => key }, Textus::Role::AUTOMATION))
|
|
22
|
-
end
|
|
23
|
-
stale_intake_keys(prefix, zone, file_stat).each do |key|
|
|
24
|
-
@queue.enqueue(job("re-pull", { "key" => key }, Textus::Role::AUTOMATION))
|
|
25
|
-
end
|
|
26
|
-
@queue.enqueue(job("sweep", { "scope" => { "prefix" => prefix, "zone" => zone } }, @call.role))
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
private
|
|
30
|
-
|
|
31
|
-
def job(type, args, role)
|
|
32
|
-
Textus::Domain::Jobs::Job.new(type: type, args: args, enqueued_by: role)
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
# Mirrors the converge scope (the publishable arm).
|
|
36
|
-
def producible_keys(prefix, zone)
|
|
37
|
-
@manifest.data.entries
|
|
38
|
-
.select { |e| e.derived? || !e.publish_tree.nil? || !e.publish_to.empty? }
|
|
39
|
-
.select { |e| in_scope?(e, prefix, zone) }
|
|
40
|
-
.map(&:key)
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
def stale_intake_keys(prefix, zone, file_stat)
|
|
44
|
-
Textus::Domain::Freshness::Evaluator.new(
|
|
45
|
-
manifest: @manifest, file_stat: file_stat, clock: Textus::Ports::Clock.new,
|
|
46
|
-
).stale_intake_keys(prefix: prefix, zone: zone)
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
def in_scope?(entry, prefix, zone)
|
|
50
|
-
return false if zone && entry.zone != zone
|
|
51
|
-
return false if prefix && !entry.key.start_with?(prefix)
|
|
52
|
-
|
|
53
|
-
true
|
|
54
|
-
end
|
|
55
|
-
end
|
|
56
|
-
end
|
|
57
|
-
end
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
module Maintenance
|
|
3
|
-
# Converge-and-exit: seed the full convergence set for the scope, run the
|
|
4
|
-
# worker until the queue is empty, return a health summary. Exits not-ok if
|
|
5
|
-
# any job dead-lettered. This is the converge entry point and what CI
|
|
6
|
-
# runs. Single-pass (serial) on purpose: each produce job self-locks via
|
|
7
|
-
# Produce::Engine.converge, so running them in turn keeps the build lock
|
|
8
|
-
# uncontended; a concurrent pool would make all-but-one produce job hit
|
|
9
|
-
# BuildInProgress and skip.
|
|
10
|
-
class Drain
|
|
11
|
-
extend Textus::Contract::DSL
|
|
12
|
-
|
|
13
|
-
verb :drain
|
|
14
|
-
summary "Converge everything now: seed produce + retention jobs and drain the queue to empty."
|
|
15
|
-
surfaces :cli, :mcp
|
|
16
|
-
cli "drain"
|
|
17
|
-
arg :prefix, String, description: "restrict convergence to keys under this dotted prefix"
|
|
18
|
-
arg :zone, String, description: "restrict convergence to entries in this zone"
|
|
19
|
-
|
|
20
|
-
def initialize(container:, call:)
|
|
21
|
-
@container = container
|
|
22
|
-
@call = call
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
def call(prefix: nil, zone: nil)
|
|
26
|
-
queue = Textus::Ports::Queue.new(root: @container.root)
|
|
27
|
-
Textus::Jobs::Seeder.new(container: @container, queue: queue, call: @call).seed(prefix: prefix, zone: zone)
|
|
28
|
-
|
|
29
|
-
summary = Worker.for(container: @container, queue: queue).drain
|
|
30
|
-
health = Read::Doctor.new(container: @container, call: @call).call
|
|
31
|
-
|
|
32
|
-
{
|
|
33
|
-
"protocol" => Textus::PROTOCOL,
|
|
34
|
-
"ok" => summary.failed.zero?,
|
|
35
|
-
"completed" => summary.completed,
|
|
36
|
-
"failed" => summary.failed,
|
|
37
|
-
"health" => health,
|
|
38
|
-
}
|
|
39
|
-
end
|
|
40
|
-
end
|
|
41
|
-
end
|
|
42
|
-
end
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
module Maintenance
|
|
3
|
-
# Bulk-delete every leaf key under `prefix`.
|
|
4
|
-
class KeyDeletePrefix
|
|
5
|
-
extend Textus::Contract::DSL
|
|
6
|
-
|
|
7
|
-
verb :key_delete_prefix
|
|
8
|
-
summary "Bulk-delete every leaf key under prefix."
|
|
9
|
-
surfaces :cli, :mcp
|
|
10
|
-
cli "key delete-prefix"
|
|
11
|
-
arg :prefix, String, required: true, positional: true, description: "every leaf key under this dotted prefix is deleted"
|
|
12
|
-
arg :dry_run, :boolean, default: false,
|
|
13
|
-
description: "when true, returns the keys that would be deleted without deleting them; " \
|
|
14
|
-
"defaults to false, so omitting it deletes immediately"
|
|
15
|
-
view { |v, _i| v.to_h }
|
|
16
|
-
|
|
17
|
-
def initialize(container:, call:)
|
|
18
|
-
@container = container
|
|
19
|
-
@call = call
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
def call(prefix, dry_run: false)
|
|
23
|
-
raise UsageError.new("prefix required") if prefix.nil? || prefix.empty?
|
|
24
|
-
|
|
25
|
-
leaves = Read::List.new(container: @container)
|
|
26
|
-
.call(prefix: prefix)
|
|
27
|
-
.map { |r| r.is_a?(Hash) ? (r["key"] || r[:key]) : r }
|
|
28
|
-
|
|
29
|
-
warnings = leaves.empty? ? ["no keys under #{prefix}"] : []
|
|
30
|
-
steps = leaves.map { |k| { "op" => "delete", "key" => k } }
|
|
31
|
-
|
|
32
|
-
plan = Plan.new(steps: steps, warnings: warnings)
|
|
33
|
-
return plan if dry_run
|
|
34
|
-
|
|
35
|
-
steps.each do |s|
|
|
36
|
-
delete.call(s["key"])
|
|
37
|
-
end
|
|
38
|
-
plan
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
private
|
|
42
|
-
|
|
43
|
-
def delete
|
|
44
|
-
Write::KeyDelete.new(container: @container, call: @call)
|
|
45
|
-
end
|
|
46
|
-
end
|
|
47
|
-
end
|
|
48
|
-
end
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
module Maintenance
|
|
3
|
-
# Bulk-rename every leaf key under `from_prefix` to `to_prefix`.
|
|
4
|
-
# Calls Write::KeyMv directly for each entry — emits one audit row per file moved.
|
|
5
|
-
class KeyMvPrefix
|
|
6
|
-
extend Textus::Contract::DSL
|
|
7
|
-
|
|
8
|
-
verb :key_mv_prefix
|
|
9
|
-
summary "Bulk-rename every leaf key under from_prefix to to_prefix. Dry-run returns a Plan; apply with dry_run: false."
|
|
10
|
-
surfaces :cli, :mcp
|
|
11
|
-
cli "key mv-prefix"
|
|
12
|
-
arg :from_prefix, String, required: true, positional: true, description: "dotted prefix whose leaf keys are renamed"
|
|
13
|
-
arg :to_prefix, String, required: true, positional: true, description: "dotted prefix the keys are renamed to"
|
|
14
|
-
arg :dry_run, :boolean, default: false,
|
|
15
|
-
description: "when true, returns the planned moves without applying them; " \
|
|
16
|
-
"defaults to false, so omitting it applies the rename immediately"
|
|
17
|
-
view { |v, _i| v.to_h }
|
|
18
|
-
|
|
19
|
-
def initialize(container:, call:)
|
|
20
|
-
@container = container
|
|
21
|
-
@call = call
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
def call(from_prefix, to_prefix, dry_run: false)
|
|
25
|
-
raise UsageError.new("from_prefix and to_prefix required") if from_prefix.nil? || to_prefix.nil?
|
|
26
|
-
|
|
27
|
-
leaves = list_leaves_under(from_prefix)
|
|
28
|
-
|
|
29
|
-
# When from_prefix is itself a leaf, `delete_prefix("#{from_prefix}.")`
|
|
30
|
-
# finds no trailing dot to strip, so the tail keeps the whole key and the
|
|
31
|
-
# move silently targets "to_prefix.<full-from_prefix>". Refuse it — a
|
|
32
|
-
# single-key rename is `mv`'s job, not the bulk prefix verb's.
|
|
33
|
-
if leaves.include?(from_prefix)
|
|
34
|
-
raise UsageError.new("from_prefix '#{from_prefix}' is itself a leaf — use `mv` to rename a single key")
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
warnings = []
|
|
38
|
-
warnings << "no keys under #{from_prefix}" if leaves.empty?
|
|
39
|
-
|
|
40
|
-
steps = leaves.map do |old_key|
|
|
41
|
-
tail = old_key.delete_prefix("#{from_prefix}.")
|
|
42
|
-
new_key = "#{to_prefix}.#{tail}"
|
|
43
|
-
{ "op" => "mv", "from" => old_key, "to" => new_key }
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
plan = Plan.new(steps: steps, warnings: warnings)
|
|
47
|
-
return plan if dry_run
|
|
48
|
-
|
|
49
|
-
steps.each do |s|
|
|
50
|
-
mv.call(s["from"], s["to"], dry_run: false)
|
|
51
|
-
end
|
|
52
|
-
plan
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
private
|
|
56
|
-
|
|
57
|
-
def list_leaves_under(prefix)
|
|
58
|
-
Read::List.new(container: @container)
|
|
59
|
-
.call(prefix: prefix)
|
|
60
|
-
.map { |row| row.is_a?(Hash) ? (row["key"] || row[:key]) : row }
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
def mv
|
|
64
|
-
Write::KeyMv.new(container: @container, call: @call)
|
|
65
|
-
end
|
|
66
|
-
end
|
|
67
|
-
end
|
|
68
|
-
end
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
require "yaml"
|
|
2
|
-
|
|
3
|
-
module Textus
|
|
4
|
-
module Maintenance
|
|
5
|
-
# Compare the live manifest's `rules:` block against a candidate
|
|
6
|
-
# YAML string. Returns a Plan describing rule additions/removals/
|
|
7
|
-
# changes. Does NOT write anything.
|
|
8
|
-
class RuleLint
|
|
9
|
-
extend Textus::Contract::DSL
|
|
10
|
-
|
|
11
|
-
verb :rule_lint
|
|
12
|
-
summary "Diff candidate manifest YAML's rules against the live manifest. No writes."
|
|
13
|
-
surfaces :cli, :mcp
|
|
14
|
-
cli "rule lint"
|
|
15
|
-
arg :candidate_yaml, String, required: true, wire_name: :against, source: :file,
|
|
16
|
-
description: "path to candidate manifest YAML; its `rules:` block is diffed against the live manifest"
|
|
17
|
-
view { |v, _i| v.to_h }
|
|
18
|
-
|
|
19
|
-
def initialize(container:, call:)
|
|
20
|
-
@container = container
|
|
21
|
-
@call = call
|
|
22
|
-
@root = container.root
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
def call(candidate_yaml:)
|
|
26
|
-
live_rules = current_rules
|
|
27
|
-
candidate_rules = parse_candidate(candidate_yaml)
|
|
28
|
-
|
|
29
|
-
live_by_match = live_rules.to_h { |r| [r["match"], r] }
|
|
30
|
-
candidate_by_match = candidate_rules.to_h { |r| [r["match"], r] }
|
|
31
|
-
|
|
32
|
-
steps = (candidate_by_match.keys - live_by_match.keys).map do |m|
|
|
33
|
-
{ "op" => "add_rule", "match" => m, "rule" => candidate_by_match[m] }
|
|
34
|
-
end
|
|
35
|
-
(live_by_match.keys - candidate_by_match.keys).each do |m|
|
|
36
|
-
steps << { "op" => "remove_rule", "match" => m }
|
|
37
|
-
end
|
|
38
|
-
(live_by_match.keys & candidate_by_match.keys).each do |m|
|
|
39
|
-
next if live_by_match[m] == candidate_by_match[m]
|
|
40
|
-
|
|
41
|
-
steps << { "op" => "change_rule", "match" => m,
|
|
42
|
-
"from" => live_by_match[m], "to" => candidate_by_match[m] }
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
Plan.new(steps: steps, warnings: [])
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
private
|
|
49
|
-
|
|
50
|
-
def current_rules
|
|
51
|
-
raw = YAML.safe_load_file(File.join(@root, "manifest.yaml"),
|
|
52
|
-
permitted_classes: [Symbol], aliases: false)
|
|
53
|
-
Array(raw["rules"])
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
def parse_candidate(yaml_text)
|
|
57
|
-
raw = YAML.safe_load(yaml_text, permitted_classes: [Symbol], aliases: false)
|
|
58
|
-
raise UsageError.new("candidate is not a YAML mapping") unless raw.is_a?(Hash)
|
|
59
|
-
|
|
60
|
-
Array(raw["rules"])
|
|
61
|
-
rescue Psych::Exception => e
|
|
62
|
-
raise UsageError.new("candidate YAML parse error: #{e.message}")
|
|
63
|
-
end
|
|
64
|
-
end
|
|
65
|
-
end
|
|
66
|
-
end
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
module Maintenance
|
|
3
|
-
# The convergence daemon loop: seed scheduled work (TTL re-pull + sweep),
|
|
4
|
-
# reclaim crashed leases, drain the queue, sleep, repeat. `tick` is one
|
|
5
|
-
# iteration (unit-testable); `run` loops forever. Drains serially for the
|
|
6
|
-
# same reason as Drain — each produce job self-locks, so running them in turn
|
|
7
|
-
# keeps the build lock uncontended.
|
|
8
|
-
class Serve
|
|
9
|
-
def initialize(container:, call:)
|
|
10
|
-
@container = container
|
|
11
|
-
@call = call
|
|
12
|
-
@queue = Textus::Ports::Queue.new(root: container.root)
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
def tick
|
|
16
|
-
Textus::Jobs::Scheduler.new(container: @container, queue: @queue).run_once
|
|
17
|
-
@queue.reclaim(now: Textus::Ports::Clock.new.now)
|
|
18
|
-
Worker.for(container: @container, queue: @queue).drain
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
def run(poll: nil)
|
|
22
|
-
interval = poll || @container.manifest.data.worker_config[:poll]
|
|
23
|
-
loop do
|
|
24
|
-
tick
|
|
25
|
-
sleep(interval)
|
|
26
|
-
end
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
end
|