textus 0.43.2 → 0.46.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 +70 -0
- data/README.md +56 -29
- data/SPEC.md +24 -22
- data/docs/architecture/README.md +32 -32
- data/docs/reference/conventions.md +8 -9
- data/lib/textus/boot.rb +4 -4
- data/lib/textus/builder/pipeline.rb +11 -42
- data/lib/textus/builder/renderer/markdown.rb +4 -8
- data/lib/textus/cli/group/fetch.rb +2 -2
- data/lib/textus/cli/group.rb +1 -0
- data/lib/textus/cli/runner.rb +187 -0
- data/lib/textus/cli/verb/build.rb +4 -4
- data/lib/textus/cli/verb/{fetch_stale.rb → fetch_all.rb} +2 -2
- data/lib/textus/cli/verb/get.rb +6 -5
- data/lib/textus/cli/verb/put.rb +3 -3
- data/lib/textus/cli/verb.rb +3 -0
- data/lib/textus/cli.rb +37 -3
- data/lib/textus/container.rb +3 -15
- data/lib/textus/contract/around.rb +29 -0
- data/lib/textus/contract/binder.rb +88 -0
- data/lib/textus/contract/resources/cursor.rb +26 -0
- data/lib/textus/contract/sources.rb +39 -0
- data/lib/textus/contract/view.rb +15 -0
- data/lib/textus/contract.rb +68 -8
- data/lib/textus/dispatcher.rb +6 -6
- data/lib/textus/doctor/check/orphaned_publish_targets.rb +1 -1
- data/lib/textus/doctor/check/sentinels.rb +1 -1
- data/lib/textus/domain/policy/predicates/fresh_within.rb +6 -5
- data/lib/textus/envelope/io/writer.rb +34 -0
- data/lib/textus/hooks/context.rb +24 -2
- data/lib/textus/layout.rb +8 -0
- data/lib/textus/maintenance/key_delete_prefix.rb +8 -5
- data/lib/textus/maintenance/key_mv_prefix.rb +18 -6
- data/lib/textus/maintenance/migrate.rb +14 -10
- data/lib/textus/maintenance/rule_lint.rb +5 -4
- data/lib/textus/maintenance/zone_mv.rb +9 -6
- data/lib/textus/manifest/entry/base.rb +1 -1
- data/lib/textus/mcp/catalog.rb +6 -33
- data/lib/textus/ports/publisher.rb +3 -2
- data/lib/textus/ports/sentinel_store.rb +8 -7
- data/lib/textus/projection.rb +6 -5
- data/lib/textus/read/audit.rb +19 -0
- data/lib/textus/read/blame.rb +11 -1
- data/lib/textus/read/boot.rb +1 -1
- data/lib/textus/read/capabilities.rb +70 -0
- data/lib/textus/read/deps.rb +15 -1
- data/lib/textus/read/doctor.rb +8 -0
- data/lib/textus/read/freshness.rb +10 -0
- data/lib/textus/read/get.rb +87 -22
- data/lib/textus/read/list.rb +2 -1
- data/lib/textus/read/published.rb +7 -0
- data/lib/textus/read/pulse.rb +2 -1
- data/lib/textus/read/rdeps.rb +14 -0
- data/lib/textus/read/rule_explain.rb +84 -0
- data/lib/textus/read/rule_list.rb +39 -0
- data/lib/textus/read/schema_envelope.rb +3 -2
- data/lib/textus/read/uid.rb +9 -0
- data/lib/textus/read/where.rb +8 -0
- data/lib/textus/role_scope.rb +34 -6
- data/lib/textus/schema/tools.rb +12 -3
- data/lib/textus/store.rb +47 -24
- data/lib/textus/version.rb +1 -1
- data/lib/textus/write/accept.rb +8 -0
- data/lib/textus/write/{publish.rb → build.rb} +16 -7
- data/lib/textus/write/delete.rb +13 -0
- data/lib/textus/write/fetch_all.rb +2 -1
- data/lib/textus/write/fetch_orchestrator.rb +1 -1
- data/lib/textus/write/fetch_worker.rb +2 -2
- data/lib/textus/write/mv.rb +16 -0
- data/lib/textus/write/propose.rb +8 -3
- data/lib/textus/write/put.rb +3 -3
- data/lib/textus/write/reject.rb +8 -0
- data/lib/textus/write/retention_sweep.rb +9 -0
- metadata +12 -29
- data/lib/textus/cli/verb/accept.rb +0 -16
- data/lib/textus/cli/verb/audit.rb +0 -34
- data/lib/textus/cli/verb/blame.rb +0 -17
- data/lib/textus/cli/verb/delete.rb +0 -17
- data/lib/textus/cli/verb/deps.rb +0 -14
- data/lib/textus/cli/verb/freshness.rb +0 -17
- data/lib/textus/cli/verb/key_delete.rb +0 -24
- data/lib/textus/cli/verb/list.rb +0 -16
- data/lib/textus/cli/verb/migrate.rb +0 -18
- data/lib/textus/cli/verb/mv.rb +0 -27
- data/lib/textus/cli/verb/propose.rb +0 -28
- data/lib/textus/cli/verb/published.rb +0 -13
- data/lib/textus/cli/verb/pulse.rb +0 -26
- data/lib/textus/cli/verb/rdeps.rb +0 -14
- data/lib/textus/cli/verb/reject.rb +0 -16
- data/lib/textus/cli/verb/retain.rb +0 -19
- data/lib/textus/cli/verb/rule_explain.rb +0 -16
- data/lib/textus/cli/verb/rule_lint.rb +0 -18
- data/lib/textus/cli/verb/rule_list.rb +0 -29
- data/lib/textus/cli/verb/schema.rb +0 -15
- data/lib/textus/cli/verb/uid.rb +0 -15
- data/lib/textus/cli/verb/where.rb +0 -14
- data/lib/textus/cli/verb/zone_mv.rb +0 -19
- data/lib/textus/read/get_or_fetch.rb +0 -69
- data/lib/textus/read/policy_explain.rb +0 -46
- data/lib/textus/read/rules.rb +0 -25
data/lib/textus/cli/verb/list.rb
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
class CLI
|
|
3
|
-
class Verb
|
|
4
|
-
class List < Verb
|
|
5
|
-
command_name "list"
|
|
6
|
-
|
|
7
|
-
option :prefix, "--prefix=KEY"
|
|
8
|
-
option :zone, "--zone=Z"
|
|
9
|
-
|
|
10
|
-
def call(store)
|
|
11
|
-
emit({ "entries" => session_for(store).list(prefix: prefix, zone: zone) })
|
|
12
|
-
end
|
|
13
|
-
end
|
|
14
|
-
end
|
|
15
|
-
end
|
|
16
|
-
end
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
class CLI
|
|
3
|
-
class Verb
|
|
4
|
-
class Migrate < Verb
|
|
5
|
-
command_name "migrate"
|
|
6
|
-
|
|
7
|
-
option :as_flag, "--as=ROLE"
|
|
8
|
-
option :dry_run, "--dry-run"
|
|
9
|
-
|
|
10
|
-
def call(store)
|
|
11
|
-
path = positional.shift or raise UsageError.new("migrate requires <plan.yaml>")
|
|
12
|
-
plan_yaml = File.read(path)
|
|
13
|
-
emit(session_for(store).migrate(plan_yaml: plan_yaml, dry_run: dry_run || false).to_h)
|
|
14
|
-
end
|
|
15
|
-
end
|
|
16
|
-
end
|
|
17
|
-
end
|
|
18
|
-
end
|
data/lib/textus/cli/verb/mv.rb
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
class CLI
|
|
3
|
-
class Verb
|
|
4
|
-
class Mv < Verb
|
|
5
|
-
command_name "mv"
|
|
6
|
-
parent_group Group::Key
|
|
7
|
-
|
|
8
|
-
option :as_flag, "--as=ROLE"
|
|
9
|
-
option :dry_run, "--dry-run"
|
|
10
|
-
option :prefix, "--prefix"
|
|
11
|
-
|
|
12
|
-
def call(store)
|
|
13
|
-
if prefix
|
|
14
|
-
from_p = positional.shift or raise UsageError.new("mv --prefix requires <from-prefix> <to-prefix>")
|
|
15
|
-
to_p = positional.shift or raise UsageError.new("mv --prefix requires <from-prefix> <to-prefix>")
|
|
16
|
-
emit(session_for(store).key_mv_prefix(from_prefix: from_p, to_prefix: to_p,
|
|
17
|
-
dry_run: dry_run || false).to_h)
|
|
18
|
-
else
|
|
19
|
-
old_key = positional.shift or raise UsageError.new("mv requires <old-key> <new-key>")
|
|
20
|
-
new_key = positional.shift or raise UsageError.new("mv requires <old-key> <new-key>")
|
|
21
|
-
emit(session_for(store).mv(old_key, new_key, dry_run: dry_run || false))
|
|
22
|
-
end
|
|
23
|
-
end
|
|
24
|
-
end
|
|
25
|
-
end
|
|
26
|
-
end
|
|
27
|
-
end
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
class CLI
|
|
3
|
-
class Verb
|
|
4
|
-
# Queue a proposal. Mirrors the MCP `propose` tool: resolves the
|
|
5
|
-
# manifest's propose_zone and prefixes the key, so the author does not
|
|
6
|
-
# need to know the queue zone's name. ADR 0036.
|
|
7
|
-
class Propose < Verb
|
|
8
|
-
command_name "propose"
|
|
9
|
-
|
|
10
|
-
option :as_flag, "--as=ROLE"
|
|
11
|
-
option :use_stdin, "--stdin"
|
|
12
|
-
|
|
13
|
-
def call(store)
|
|
14
|
-
rel = positional.shift or raise UsageError.new("propose requires a key")
|
|
15
|
-
raise UsageError.new("propose requires --stdin") unless use_stdin
|
|
16
|
-
|
|
17
|
-
payload = JSON.parse(@stdin.read)
|
|
18
|
-
env = store.as(resolved_role(store)).propose(
|
|
19
|
-
rel,
|
|
20
|
-
meta: payload["_meta"] || {},
|
|
21
|
-
body: payload["body"] || "",
|
|
22
|
-
)
|
|
23
|
-
emit(env.to_h_for_wire)
|
|
24
|
-
end
|
|
25
|
-
end
|
|
26
|
-
end
|
|
27
|
-
end
|
|
28
|
-
end
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
class CLI
|
|
3
|
-
class Verb
|
|
4
|
-
class Pulse < Verb
|
|
5
|
-
command_name "pulse"
|
|
6
|
-
|
|
7
|
-
option :as_flag, "--as=ROLE"
|
|
8
|
-
option :since, "--since=N"
|
|
9
|
-
|
|
10
|
-
def call(store)
|
|
11
|
-
role = resolved_role(store)
|
|
12
|
-
ops = store.as(role)
|
|
13
|
-
|
|
14
|
-
if since
|
|
15
|
-
emit(ops.pulse(since: since.to_i))
|
|
16
|
-
else
|
|
17
|
-
cursors = Textus::CursorStore.new(root: store.root, role: role)
|
|
18
|
-
result = ops.pulse(since: cursors.read)
|
|
19
|
-
cursors.write(result["cursor"])
|
|
20
|
-
emit(result)
|
|
21
|
-
end
|
|
22
|
-
end
|
|
23
|
-
end
|
|
24
|
-
end
|
|
25
|
-
end
|
|
26
|
-
end
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
class CLI
|
|
3
|
-
class Verb
|
|
4
|
-
class Rdeps < Verb
|
|
5
|
-
command_name "rdeps"
|
|
6
|
-
|
|
7
|
-
def call(store)
|
|
8
|
-
key = positional.shift or raise UsageError.new("rdeps requires a key")
|
|
9
|
-
emit({ "key" => key, "rdeps" => session_for(store).rdeps(key) })
|
|
10
|
-
end
|
|
11
|
-
end
|
|
12
|
-
end
|
|
13
|
-
end
|
|
14
|
-
end
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
class CLI
|
|
3
|
-
class Verb
|
|
4
|
-
class Reject < Verb
|
|
5
|
-
command_name "reject"
|
|
6
|
-
|
|
7
|
-
option :as_flag, "--as=ROLE"
|
|
8
|
-
|
|
9
|
-
def call(store)
|
|
10
|
-
key = positional.shift or raise UsageError.new("reject requires a key")
|
|
11
|
-
emit(session_for(store).reject(key))
|
|
12
|
-
end
|
|
13
|
-
end
|
|
14
|
-
end
|
|
15
|
-
end
|
|
16
|
-
end
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
class CLI
|
|
3
|
-
class Verb
|
|
4
|
-
class Retain < Verb
|
|
5
|
-
command_name "retain"
|
|
6
|
-
|
|
7
|
-
option :prefix, "--prefix=KEY"
|
|
8
|
-
option :zone, "--zone=Z"
|
|
9
|
-
option :as_flag, "--as=ROLE"
|
|
10
|
-
|
|
11
|
-
def call(store)
|
|
12
|
-
result = session_for(store).retention_sweep(prefix: prefix, zone: zone)
|
|
13
|
-
emit(result)
|
|
14
|
-
result["ok"] ? 0 : 1
|
|
15
|
-
end
|
|
16
|
-
end
|
|
17
|
-
end
|
|
18
|
-
end
|
|
19
|
-
end
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
class CLI
|
|
3
|
-
class Verb
|
|
4
|
-
class RuleExplain < Verb
|
|
5
|
-
command_name "explain"
|
|
6
|
-
parent_group Group::Rule
|
|
7
|
-
|
|
8
|
-
def call(store)
|
|
9
|
-
key = positional.shift or raise UsageError.new("policy explain requires a KEY")
|
|
10
|
-
result = session_for(store).policy_explain(key: key)
|
|
11
|
-
emit({ "verb" => "policy_explain" }.merge(result.transform_keys(&:to_s)))
|
|
12
|
-
end
|
|
13
|
-
end
|
|
14
|
-
end
|
|
15
|
-
end
|
|
16
|
-
end
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
class CLI
|
|
3
|
-
class Verb
|
|
4
|
-
class RuleLint < Verb
|
|
5
|
-
command_name "lint"
|
|
6
|
-
parent_group Group::Rule
|
|
7
|
-
|
|
8
|
-
option :against, "--against=FILE"
|
|
9
|
-
|
|
10
|
-
def call(store)
|
|
11
|
-
path = against or raise UsageError.new("rule lint --against=FILE required")
|
|
12
|
-
yaml = File.read(path)
|
|
13
|
-
emit(session_for(store).rule_lint(candidate_yaml: yaml).to_h)
|
|
14
|
-
end
|
|
15
|
-
end
|
|
16
|
-
end
|
|
17
|
-
end
|
|
18
|
-
end
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
class CLI
|
|
3
|
-
class Verb
|
|
4
|
-
class RuleList < Verb
|
|
5
|
-
command_name "list"
|
|
6
|
-
parent_group Group::Rule
|
|
7
|
-
|
|
8
|
-
def call(store)
|
|
9
|
-
policies = store.manifest.rules.blocks.map do |b|
|
|
10
|
-
row = { "match" => b.match }
|
|
11
|
-
if b.fetch
|
|
12
|
-
row["fetch"] = {
|
|
13
|
-
"ttl_seconds" => b.fetch.ttl_seconds,
|
|
14
|
-
"on_stale" => b.fetch.on_stale,
|
|
15
|
-
"sync_budget_ms" => b.fetch.sync_budget_ms,
|
|
16
|
-
"fetch_timeout_seconds" => b.fetch.fetch_timeout_seconds,
|
|
17
|
-
}
|
|
18
|
-
end
|
|
19
|
-
row["handler_allowlist"] = b.handler_allowlist.handlers if b.handler_allowlist
|
|
20
|
-
row["guard"] = b.guard if b.guard
|
|
21
|
-
row["retention"] = { "expire_after" => b.retention.expire_after, "archive_after" => b.retention.archive_after } if b.retention
|
|
22
|
-
row
|
|
23
|
-
end
|
|
24
|
-
emit({ "verb" => "policy_list", "policies" => policies })
|
|
25
|
-
end
|
|
26
|
-
end
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
end
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
class CLI
|
|
3
|
-
class Verb
|
|
4
|
-
class Schema < Verb
|
|
5
|
-
command_name "show"
|
|
6
|
-
parent_group Group::Schema
|
|
7
|
-
|
|
8
|
-
def call(store)
|
|
9
|
-
key = positional.shift or raise UsageError.new("schema requires a key")
|
|
10
|
-
emit(session_for(store).schema(key))
|
|
11
|
-
end
|
|
12
|
-
end
|
|
13
|
-
end
|
|
14
|
-
end
|
|
15
|
-
end
|
data/lib/textus/cli/verb/uid.rb
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
class CLI
|
|
3
|
-
class Verb
|
|
4
|
-
class Uid < Verb
|
|
5
|
-
command_name "uid"
|
|
6
|
-
parent_group Group::Key
|
|
7
|
-
|
|
8
|
-
def call(store)
|
|
9
|
-
key = positional.shift or raise UsageError.new("uid requires a key")
|
|
10
|
-
emit({ "key" => key, "uid" => session_for(store).uid(key) })
|
|
11
|
-
end
|
|
12
|
-
end
|
|
13
|
-
end
|
|
14
|
-
end
|
|
15
|
-
end
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
class CLI
|
|
3
|
-
class Verb
|
|
4
|
-
class ZoneMv < Verb
|
|
5
|
-
command_name "mv"
|
|
6
|
-
parent_group Group::Zone
|
|
7
|
-
|
|
8
|
-
option :as_flag, "--as=ROLE"
|
|
9
|
-
option :dry_run, "--dry-run"
|
|
10
|
-
|
|
11
|
-
def call(store)
|
|
12
|
-
from = positional.shift or raise UsageError.new("zone mv requires <from> <to>")
|
|
13
|
-
to = positional.shift or raise UsageError.new("zone mv requires <from> <to>")
|
|
14
|
-
emit(session_for(store).zone_mv(from: from, to: to, dry_run: dry_run || false).to_h)
|
|
15
|
-
end
|
|
16
|
-
end
|
|
17
|
-
end
|
|
18
|
-
end
|
|
19
|
-
end
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
module Read
|
|
3
|
-
# Composes pure `Read::Get` with the fetch orchestrator: runs Get
|
|
4
|
-
# to obtain the envelope and freshness verdict, then if the verdict
|
|
5
|
-
# is stale and the rule's `on_stale` policy demands action, hands
|
|
6
|
-
# off to the orchestrator. Use for interactive reads where the
|
|
7
|
-
# caller wants the freshest obtainable envelope.
|
|
8
|
-
#
|
|
9
|
-
# Pure reads (build, projection, schema tooling) should use
|
|
10
|
-
# `Read::Get` directly; it has no orchestrator dependency.
|
|
11
|
-
class GetOrFetch
|
|
12
|
-
def initialize(container:, call:, get: nil, orchestrator: nil)
|
|
13
|
-
@container = container
|
|
14
|
-
@call = call
|
|
15
|
-
@manifest = container.manifest
|
|
16
|
-
@get = get || Read::Get.new(container: container, call: call)
|
|
17
|
-
@orchestrator = orchestrator || build_orchestrator
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
private
|
|
21
|
-
|
|
22
|
-
def hook_context
|
|
23
|
-
@hook_context ||= Textus::Hooks::Context.for(container: @container, call: @call)
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
def build_orchestrator
|
|
27
|
-
worker = Textus::Write::FetchWorker.new(
|
|
28
|
-
container: @container, call: @call,
|
|
29
|
-
)
|
|
30
|
-
Textus::Write::FetchOrchestrator.new(
|
|
31
|
-
worker: worker, store_root: @container.root, events: @container.events,
|
|
32
|
-
hook_context: hook_context
|
|
33
|
-
)
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
public
|
|
37
|
-
|
|
38
|
-
def call(key)
|
|
39
|
-
envelope = @get.call(key)
|
|
40
|
-
return nil if envelope.nil?
|
|
41
|
-
return envelope unless envelope.freshness&.stale
|
|
42
|
-
|
|
43
|
-
policy_set = @manifest.rules.for(key)
|
|
44
|
-
fetch_policy = policy_set.fetch
|
|
45
|
-
return envelope if fetch_policy.nil?
|
|
46
|
-
|
|
47
|
-
policy = fetch_policy.to_freshness_policy
|
|
48
|
-
verdict = Textus::Domain::Freshness::Verdict.stale(envelope.freshness.reason)
|
|
49
|
-
action = policy.decide(verdict)
|
|
50
|
-
outcome = @orchestrator.execute(action, key: key)
|
|
51
|
-
|
|
52
|
-
case outcome
|
|
53
|
-
when Textus::Domain::Outcome::Skipped
|
|
54
|
-
envelope
|
|
55
|
-
when Textus::Domain::Outcome::Fetched
|
|
56
|
-
outcome.envelope.with(
|
|
57
|
-
freshness: Textus::Domain::Freshness.build(stale: false, reason: nil, fetching: false),
|
|
58
|
-
)
|
|
59
|
-
when Textus::Domain::Outcome::Detached
|
|
60
|
-
envelope.with(freshness: envelope.freshness.with(fetching: true))
|
|
61
|
-
when Textus::Domain::Outcome::Failed
|
|
62
|
-
envelope.with(
|
|
63
|
-
freshness: envelope.freshness.with(fetch_error: outcome.error.message),
|
|
64
|
-
)
|
|
65
|
-
end
|
|
66
|
-
end
|
|
67
|
-
end
|
|
68
|
-
end
|
|
69
|
-
end
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
module Read
|
|
3
|
-
# For one key, surface every matching policy block along with the
|
|
4
|
-
# per-slot effective value (which loses ties win-by-specificity) and the
|
|
5
|
-
# effective guard predicate names for every write transition (ADR 0031).
|
|
6
|
-
class PolicyExplain
|
|
7
|
-
def initialize(container:, call: nil) # rubocop:disable Lint/UnusedMethodArgument
|
|
8
|
-
@manifest = container.manifest
|
|
9
|
-
@schemas = container.schemas
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
def call(key:)
|
|
13
|
-
matching = @manifest.rules.explain(key)
|
|
14
|
-
winners = @manifest.rules.for(key)
|
|
15
|
-
factory = Textus::Domain::Policy::GuardFactory.new(manifest: @manifest, schemas: @schemas)
|
|
16
|
-
|
|
17
|
-
{
|
|
18
|
-
key: key,
|
|
19
|
-
matched_blocks: matching.map do |b|
|
|
20
|
-
{
|
|
21
|
-
match: b.match,
|
|
22
|
-
fetch: !b.fetch.nil?,
|
|
23
|
-
handler_allowlist: !b.handler_allowlist.nil?,
|
|
24
|
-
guard: !b.guard.nil?,
|
|
25
|
-
retention: !b.retention.nil?,
|
|
26
|
-
}
|
|
27
|
-
end,
|
|
28
|
-
effective: {
|
|
29
|
-
fetch: winners.fetch && {
|
|
30
|
-
ttl_seconds: winners.fetch.ttl_seconds,
|
|
31
|
-
on_stale: winners.fetch.on_stale,
|
|
32
|
-
},
|
|
33
|
-
handler_allowlist: winners.handler_allowlist&.handlers,
|
|
34
|
-
retention: winners.retention && {
|
|
35
|
-
expire_after: winners.retention.expire_after,
|
|
36
|
-
archive_after: winners.retention.archive_after,
|
|
37
|
-
},
|
|
38
|
-
},
|
|
39
|
-
guards: Textus::Domain::Policy::BaseGuards::BASE.keys.to_h do |transition|
|
|
40
|
-
[transition, factory.for(transition, key).predicates.map(&:name)]
|
|
41
|
-
end,
|
|
42
|
-
}
|
|
43
|
-
end
|
|
44
|
-
end
|
|
45
|
-
end
|
|
46
|
-
end
|
data/lib/textus/read/rules.rb
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
module Read
|
|
3
|
-
# Effective rule set (fetch + guard) for a key. Was the inlined MCP
|
|
4
|
-
# `rules` tool; promoted to a first-class verb so MCP is a pure projection
|
|
5
|
-
# (ADR 0039).
|
|
6
|
-
class Rules
|
|
7
|
-
extend Textus::Contract::DSL
|
|
8
|
-
|
|
9
|
-
verb :rules
|
|
10
|
-
summary "Return effective rules for a key (fetch, guard, ...)."
|
|
11
|
-
surfaces :ruby, :mcp
|
|
12
|
-
arg :key, String, required: true, positional: true,
|
|
13
|
-
description: "dotted key whose effective rules you want (fetch ttl/action, write guard, ...)"
|
|
14
|
-
|
|
15
|
-
def initialize(container:, call: nil) # rubocop:disable Lint/UnusedMethodArgument
|
|
16
|
-
@manifest = container.manifest
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
def call(key)
|
|
20
|
-
set = @manifest.rules.for(key)
|
|
21
|
-
{ "fetch" => set.fetch&.to_h, "guard" => set.guard }.compact
|
|
22
|
-
end
|
|
23
|
-
end
|
|
24
|
-
end
|
|
25
|
-
end
|