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
data/lib/textus/read/pulse.rb
DELETED
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
require "time"
|
|
2
|
-
|
|
3
|
-
module Textus
|
|
4
|
-
module Read
|
|
5
|
-
# Aggregator over audit + freshness + review + doctor. One round-trip
|
|
6
|
-
# for an agent's per-turn heartbeat. All component reads are existing
|
|
7
|
-
# APIs; pulse is sugar with a stable envelope shape and a monotonic
|
|
8
|
-
# cursor (seq).
|
|
9
|
-
class Pulse
|
|
10
|
-
extend Textus::Contract::DSL
|
|
11
|
-
|
|
12
|
-
verb :pulse
|
|
13
|
-
summary "Delta since cursor — changed entries, stale, pending proposals, doctor summary."
|
|
14
|
-
surfaces :cli, :mcp
|
|
15
|
-
around :cursor
|
|
16
|
-
arg :since, Integer, session_default: :cursor, description: "audit seq to diff from; defaults to the session cursor"
|
|
17
|
-
|
|
18
|
-
def initialize(container:, call:)
|
|
19
|
-
@container = container
|
|
20
|
-
@call = call
|
|
21
|
-
@manifest = container.manifest
|
|
22
|
-
@file_store = container.file_store
|
|
23
|
-
@audit_log = container.audit_log
|
|
24
|
-
@root = container.root
|
|
25
|
-
@events = container.events
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
def call(since: 0)
|
|
29
|
-
freshness_rows = freshness.call
|
|
30
|
-
{
|
|
31
|
-
"cursor" => @audit_log.latest_seq,
|
|
32
|
-
"changed" => audit_changes_since(since),
|
|
33
|
-
"stale" => freshness_rows.select { |r| r[:status] == :expired }.map { |r| r[:key] },
|
|
34
|
-
"pending_review" => review_keys,
|
|
35
|
-
"doctor" => doctor_summary,
|
|
36
|
-
"contract_etag" => contract_etag,
|
|
37
|
-
"next_due_at" => soonest_due(freshness_rows),
|
|
38
|
-
"hook_errors" => hook_errors_since(since),
|
|
39
|
-
}
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
private
|
|
43
|
-
|
|
44
|
-
def audit_changes_since(seq)
|
|
45
|
-
Read::Audit.new(container: @container).call(seq_since: seq)
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
def freshness
|
|
49
|
-
@freshness ||= Read::Freshness.new(container: @container, call: @call)
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
def soonest_due(rows)
|
|
53
|
-
times = rows.map { |r| r[:next_due_at] }.compact.map { |t| Time.parse(t) }
|
|
54
|
-
return nil if times.empty?
|
|
55
|
-
|
|
56
|
-
times.min.utc.iso8601
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
def review_keys
|
|
60
|
-
# The single queue zone (kind: queue; schema guarantees ≤1), derived
|
|
61
|
-
# from the manifest rather than a hardcoded zone name (ADR 0034 / D1).
|
|
62
|
-
queue = @manifest.policy.queue_zone
|
|
63
|
-
return [] unless queue
|
|
64
|
-
|
|
65
|
-
rows = Read::List.new(container: @container).call(zone: queue)
|
|
66
|
-
rows.map { |r| r.is_a?(Hash) ? (r["key"] || r[:key]) : r }
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
def doctor_summary
|
|
70
|
-
result = Textus::Doctor.build(container: @container)
|
|
71
|
-
issues = result["issues"] || []
|
|
72
|
-
{
|
|
73
|
-
"ok" => result["ok"],
|
|
74
|
-
"warn" => issues.count { |i| i["level"] == "warning" },
|
|
75
|
-
"fail" => issues.count { |i| i["level"] == "error" },
|
|
76
|
-
}
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
def contract_etag
|
|
80
|
-
Textus::Etag.for_contract(@root)
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
def hook_errors_since(seq)
|
|
84
|
-
@events.error_log.since(seq).map do |r|
|
|
85
|
-
{
|
|
86
|
-
"seq" => r[:seq],
|
|
87
|
-
"event" => r[:event].to_s,
|
|
88
|
-
"hook" => r[:hook].to_s,
|
|
89
|
-
"key" => r[:key],
|
|
90
|
-
"error_class" => r[:error_class],
|
|
91
|
-
"error_message" => r[:error_message],
|
|
92
|
-
"at" => r[:at],
|
|
93
|
-
}
|
|
94
|
-
end
|
|
95
|
-
end
|
|
96
|
-
end
|
|
97
|
-
end
|
|
98
|
-
end
|
data/lib/textus/read/rdeps.rb
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
module Read
|
|
3
|
-
class Rdeps
|
|
4
|
-
extend Textus::Contract::DSL
|
|
5
|
-
|
|
6
|
-
verb :rdeps
|
|
7
|
-
summary "List the derived entries that depend on a key (reverse deps / impact set)."
|
|
8
|
-
surfaces :cli, :mcp
|
|
9
|
-
arg :key, String, required: true, positional: true,
|
|
10
|
-
description: "dotted key whose dependents (what would be stranded if it moved) you want"
|
|
11
|
-
|
|
12
|
-
def initialize(container:, call: nil) # rubocop:disable Lint/UnusedMethodArgument
|
|
13
|
-
@manifest = container.manifest
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
def call(key)
|
|
17
|
-
{ "key" => key, "rdeps" => dependents_of(key) }
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
private
|
|
21
|
-
|
|
22
|
-
def dependents_of(key)
|
|
23
|
-
@manifest.data.entries.each_with_object([]) do |e, acc|
|
|
24
|
-
next unless e.derived?
|
|
25
|
-
|
|
26
|
-
src = e.source
|
|
27
|
-
sources = if src.projection?
|
|
28
|
-
Array(src.select).compact
|
|
29
|
-
elsif src.external?
|
|
30
|
-
Array(src.sources).compact
|
|
31
|
-
else
|
|
32
|
-
[]
|
|
33
|
-
end
|
|
34
|
-
acc << e.key if sources.any? { |s| s == key || key.start_with?("#{s}.") }
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
end
|
|
38
|
-
end
|
|
39
|
-
end
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
module Read
|
|
3
|
-
# Effective rules for a key, at two depths (ADR 0059). Lean by default —
|
|
4
|
-
# `{ lifecycle, guard }`, the agent-cheap read that was the `rules` verb. With
|
|
5
|
-
# `detail: true` it returns the verbose explanation — every matching policy
|
|
6
|
-
# block plus the per-transition guard predicate names — that was
|
|
7
|
-
# `policy_explain`. One verb, one name across CLI/MCP/method; the audience
|
|
8
|
-
# split is a parameter, not two tools.
|
|
9
|
-
class RuleExplain
|
|
10
|
-
extend Textus::Contract::DSL
|
|
11
|
-
|
|
12
|
-
verb :rule_explain
|
|
13
|
-
summary "Effective rules for a key. Lean {lifecycle, guard} by default; detail: true adds matched blocks + guard predicates."
|
|
14
|
-
surfaces :cli, :mcp
|
|
15
|
-
cli "rule explain"
|
|
16
|
-
arg :key, String, required: true, positional: true,
|
|
17
|
-
description: "dotted key whose effective rules you want (lifecycle ttl/action, write guard, ...)"
|
|
18
|
-
arg :detail, :boolean,
|
|
19
|
-
description: "defaults false: lean {lifecycle, guard}. detail: true adds matched blocks + guard predicates per transition."
|
|
20
|
-
view(:cli) { |r| { "verb" => "rule_explain" }.merge(r.transform_keys(&:to_s)) }
|
|
21
|
-
|
|
22
|
-
def initialize(container:, call: nil) # rubocop:disable Lint/UnusedMethodArgument
|
|
23
|
-
@manifest = container.manifest
|
|
24
|
-
@schemas = container.schemas
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
REGISTRY = Textus::Manifest::Schema::FIELD_REGISTRY
|
|
28
|
-
# Field membership is registry-driven (WS3). Lean shows the fields tagged
|
|
29
|
-
# for :lean; detail's matched_blocks flag every :detail field. The
|
|
30
|
-
# `effective` value-block shows the instantiated-policy fields (those with
|
|
31
|
-
# a policy_class) — guard, being a raw deferred field, is surfaced through
|
|
32
|
-
# the dedicated `guards:` predicate section instead.
|
|
33
|
-
LEAN_FIELDS = REGISTRY.select { |_, m| m[:in_rule_explain].include?(:lean) }.keys.freeze
|
|
34
|
-
DETAIL_FIELDS = REGISTRY.select { |_, m| m[:in_rule_explain].include?(:detail) }.keys.freeze
|
|
35
|
-
EFFECTIVE_FIELDS = DETAIL_FIELDS.select { |f| REGISTRY[f][:policy_class] }.freeze
|
|
36
|
-
|
|
37
|
-
def call(key, detail: false)
|
|
38
|
-
detail ? explain(key) : effective(key)
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
private
|
|
42
|
-
|
|
43
|
-
# Lean: the effective winners only (formerly Read::Rules / the `rules` verb).
|
|
44
|
-
def effective(key)
|
|
45
|
-
set = @manifest.rules.for(key)
|
|
46
|
-
LEAN_FIELDS.each_with_object({}) do |field, out|
|
|
47
|
-
value = set.public_send(field)
|
|
48
|
-
out[field.to_s] = lean_value(field, value) unless value.nil?
|
|
49
|
-
end
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
def lean_value(field, value)
|
|
53
|
-
case field
|
|
54
|
-
when :retention then retention_hash(value, string_keys: true)
|
|
55
|
-
else value
|
|
56
|
-
end
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
# Verbose: every matching block, per-slot effective value, and the
|
|
60
|
-
# effective guard predicate names for each write transition (formerly
|
|
61
|
-
# Read::PolicyExplain, ADR 0031).
|
|
62
|
-
def explain(key)
|
|
63
|
-
matching = @manifest.rules.explain(key)
|
|
64
|
-
winners = @manifest.rules.for(key)
|
|
65
|
-
factory = Textus::Domain::Policy::GuardFactory.new(manifest: @manifest, schemas: @schemas)
|
|
66
|
-
|
|
67
|
-
{
|
|
68
|
-
key: key,
|
|
69
|
-
matched_blocks: matching.map do |b|
|
|
70
|
-
{ match: b.match }.merge(DETAIL_FIELDS.to_h { |f| [f, !b.public_send(f).nil?] })
|
|
71
|
-
end,
|
|
72
|
-
effective: EFFECTIVE_FIELDS.to_h { |f| [f, effective_value(f, winners.public_send(f))] },
|
|
73
|
-
guards: Textus::Domain::Policy::BaseGuards::BASE.keys.to_h do |transition|
|
|
74
|
-
[transition, factory.for(transition, key).predicates.map(&:name)]
|
|
75
|
-
end,
|
|
76
|
-
}
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
def effective_value(field, value)
|
|
80
|
-
return nil if value.nil?
|
|
81
|
-
|
|
82
|
-
case field
|
|
83
|
-
when :retention then retention_hash(value, string_keys: false)
|
|
84
|
-
when :handler_allowlist then value.handlers
|
|
85
|
-
else value
|
|
86
|
-
end
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
# ADR 0093: retention is a flat GC policy (ttl + drop/archive action).
|
|
90
|
-
def retention_hash(retention, string_keys:)
|
|
91
|
-
h = { ttl_seconds: retention.ttl_seconds, action: retention.action }
|
|
92
|
-
string_keys ? h.transform_keys(&:to_s) : h
|
|
93
|
-
end
|
|
94
|
-
end
|
|
95
|
-
end
|
|
96
|
-
end
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
module Read
|
|
3
|
-
# Enumerate every declared rule block in the manifest, in order. This is
|
|
4
|
-
# the whole-manifest view; `rule_explain` is the for-key view. Extracted
|
|
5
|
-
# from the CLI verb so the rule family is fully use-case-backed (ADR 0059);
|
|
6
|
-
# CLI-only (no MCP contract) — an agent reasons per-key via rule_explain.
|
|
7
|
-
class RuleList
|
|
8
|
-
extend Textus::Contract::DSL
|
|
9
|
-
|
|
10
|
-
verb :rule_list
|
|
11
|
-
summary "List every rule block in the manifest."
|
|
12
|
-
surfaces :cli
|
|
13
|
-
cli "rule list"
|
|
14
|
-
view(:cli) { |policies| { "verb" => "rule_list", "policies" => policies } }
|
|
15
|
-
|
|
16
|
-
def initialize(container:, call: nil) # rubocop:disable Lint/UnusedMethodArgument
|
|
17
|
-
@manifest = container.manifest
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
# Fields shown here are driven by FIELD_REGISTRY (in_rule_list); only the
|
|
21
|
-
# per-field serialization below is field-specific.
|
|
22
|
-
LIST_FIELDS = Textus::Manifest::Schema::FIELD_REGISTRY.select { |_, m| m[:in_rule_list] }.keys.freeze
|
|
23
|
-
|
|
24
|
-
def call
|
|
25
|
-
@manifest.rules.blocks.map do |b|
|
|
26
|
-
row = { "match" => b.match }
|
|
27
|
-
LIST_FIELDS.each do |field|
|
|
28
|
-
value = b.public_send(field)
|
|
29
|
-
row[field.to_s] = serialize(field, value) unless value.nil?
|
|
30
|
-
end
|
|
31
|
-
row
|
|
32
|
-
end
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
private
|
|
36
|
-
|
|
37
|
-
def serialize(field, value)
|
|
38
|
-
case field
|
|
39
|
-
when :retention
|
|
40
|
-
serialize_retention(value)
|
|
41
|
-
when :handler_allowlist
|
|
42
|
-
value.handlers
|
|
43
|
-
else
|
|
44
|
-
value
|
|
45
|
-
end
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
# ADR 0093: retention is a flat GC policy.
|
|
49
|
-
def serialize_retention(retention)
|
|
50
|
-
{ "ttl_seconds" => retention.ttl_seconds, "action" => retention.action.to_s }
|
|
51
|
-
end
|
|
52
|
-
end
|
|
53
|
-
end
|
|
54
|
-
end
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
module Read
|
|
3
|
-
class SchemaEnvelope
|
|
4
|
-
extend Textus::Contract::DSL
|
|
5
|
-
|
|
6
|
-
verb :schema_show
|
|
7
|
-
summary "Return the schema (field shape) for an entry's family, by key."
|
|
8
|
-
surfaces :cli, :mcp
|
|
9
|
-
cli "schema show"
|
|
10
|
-
arg :key, String, required: true, positional: true,
|
|
11
|
-
description: "any key in the family whose schema you want; returns required/optional fields and their types"
|
|
12
|
-
|
|
13
|
-
def initialize(container:, call: nil) # rubocop:disable Lint/UnusedMethodArgument
|
|
14
|
-
@manifest = container.manifest
|
|
15
|
-
@schemas = container.schemas
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
def call(key)
|
|
19
|
-
mentry = @manifest.resolver.resolve(key).entry
|
|
20
|
-
schema = @schemas.fetch_or_nil(mentry.schema)
|
|
21
|
-
{ "protocol" => PROTOCOL, "key" => key, "schema_ref" => mentry.schema, "schema" => schema&.to_h }
|
|
22
|
-
end
|
|
23
|
-
end
|
|
24
|
-
end
|
|
25
|
-
end
|
data/lib/textus/read/uid.rb
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
module Read
|
|
3
|
-
class Uid
|
|
4
|
-
extend Textus::Contract::DSL
|
|
5
|
-
|
|
6
|
-
verb :uid
|
|
7
|
-
summary "Return the stable UID of an entry without reading its body."
|
|
8
|
-
surfaces :cli
|
|
9
|
-
cli "key uid"
|
|
10
|
-
arg :key, String, required: true, positional: true, description: "entry key"
|
|
11
|
-
view(:cli) { |uid, inputs| { "key" => inputs[:key], "uid" => uid } }
|
|
12
|
-
|
|
13
|
-
def initialize(container:, call:)
|
|
14
|
-
@container = container
|
|
15
|
-
@call = call
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
def call(key)
|
|
19
|
-
get.get(key).uid
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
private
|
|
23
|
-
|
|
24
|
-
def get
|
|
25
|
-
@get ||= Get.new(container: @container, call: @call)
|
|
26
|
-
end
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
end
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
module Read
|
|
3
|
-
# Store-wide schema + role-authority validation: walks every entry and runs
|
|
4
|
-
# the Validator over it. Consumed internally by `doctor`'s schema_violations
|
|
5
|
-
# check and exposed as a Ruby store method (`store.validate_all`).
|
|
6
|
-
#
|
|
7
|
-
# Ruby-only, like `Read::Freshness`: it declares a contract (so it round-trips
|
|
8
|
-
# through the routing<->contract bijection, ADR 0105) but omits `surfaces`, so
|
|
9
|
-
# it gets no CLI or MCP projection. The public `validate-all` CLI verb was
|
|
10
|
-
# removed in v0.5 (`doctor --check=schema_violations` replaces it).
|
|
11
|
-
class ValidateAll
|
|
12
|
-
extend Textus::Contract::DSL
|
|
13
|
-
|
|
14
|
-
verb :validate_all
|
|
15
|
-
summary "Internal store-wide schema + role-authority validation; backs " \
|
|
16
|
-
"doctor's schema_violations check. No public surface (ADR 0105)."
|
|
17
|
-
|
|
18
|
-
def initialize(container:, call:)
|
|
19
|
-
@container = container
|
|
20
|
-
@call = call
|
|
21
|
-
@manifest = container.manifest
|
|
22
|
-
@schemas = container.schemas
|
|
23
|
-
@audit_log = container.audit_log
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
def call
|
|
27
|
-
Validator.new(
|
|
28
|
-
reader: Get.new(container: @container, call: @call),
|
|
29
|
-
manifest: @manifest,
|
|
30
|
-
audit_log: @audit_log,
|
|
31
|
-
schema_for: ->(name) { @schemas.fetch_or_nil(name) },
|
|
32
|
-
).call
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
end
|
|
36
|
-
end
|
data/lib/textus/read/where.rb
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
module Read
|
|
3
|
-
class Where
|
|
4
|
-
extend Textus::Contract::DSL
|
|
5
|
-
|
|
6
|
-
verb :where
|
|
7
|
-
summary "Resolve a key to its zone, owner, and path without reading the body."
|
|
8
|
-
surfaces :cli, :mcp
|
|
9
|
-
arg :key, String, required: true, positional: true,
|
|
10
|
-
description: "dotted key to locate (returns zone, owner, path; does not read content)"
|
|
11
|
-
|
|
12
|
-
def initialize(container:, call: nil) # rubocop:disable Lint/UnusedMethodArgument
|
|
13
|
-
@manifest = container.manifest
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
def call(key)
|
|
17
|
-
res = @manifest.resolver.resolve(key)
|
|
18
|
-
mentry = res.entry
|
|
19
|
-
path = res.path
|
|
20
|
-
{ "protocol" => PROTOCOL, "key" => key, "zone" => mentry.zone, "owner" => mentry.owner, "path" => path }
|
|
21
|
-
end
|
|
22
|
-
end
|
|
23
|
-
end
|
|
24
|
-
end
|
data/lib/textus/role_scope.rb
DELETED
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
# Thin role-scoped facade over a Container. Closes over a role default
|
|
3
|
-
# and a dry_run flag, then forwards every verb in Dispatcher::VERBS to
|
|
4
|
-
# the corresponding use case.
|
|
5
|
-
#
|
|
6
|
-
# Replaces the per-call Session under the 0.27.0 architecture: a Store
|
|
7
|
-
# exposes #as(role) to get a RoleScope, and Store#put / Store#get / etc
|
|
8
|
-
# delegate to RoleScope under the default role.
|
|
9
|
-
class RoleScope
|
|
10
|
-
attr_reader :container, :role, :correlation_id
|
|
11
|
-
|
|
12
|
-
def dry_run?
|
|
13
|
-
@dry_run
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
def initialize(container:, role:, dry_run: false, correlation_id: nil)
|
|
17
|
-
@container = container
|
|
18
|
-
@role = role.to_s
|
|
19
|
-
@dry_run = dry_run
|
|
20
|
-
@correlation_id = correlation_id
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
def with_role(role)
|
|
24
|
-
self.class.new(container: @container, role: role, dry_run: @dry_run, correlation_id: @correlation_id)
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
def with_correlation_id(cid)
|
|
28
|
-
self.class.new(container: @container, role: @role, dry_run: @dry_run, correlation_id: cid)
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
def hook_context
|
|
32
|
-
@hook_context ||= Textus::Hooks::Context.new(scope: self)
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
def with_dry_run
|
|
36
|
-
self.class.new(container: @container, role: @role, dry_run: true, correlation_id: @correlation_id)
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
# Single bind + invoke site for every surface. `inputs` is the uniform
|
|
40
|
-
# by-name hash (the binder's currency). MCP/CLI build it from their raw
|
|
41
|
-
# transport shape and call this directly; the per-verb Ruby methods below
|
|
42
|
-
# normalize positional+keyword Ruby args into `inputs` and delegate here.
|
|
43
|
-
def dispatch_bound(verb, inputs, session: nil)
|
|
44
|
-
klass = Textus::Dispatcher::VERBS[verb]
|
|
45
|
-
spec = (klass.contract if klass.respond_to?(:contract?) && klass.contract?)
|
|
46
|
-
|
|
47
|
-
invoke = lambda do |effective_inputs|
|
|
48
|
-
args, kwargs =
|
|
49
|
-
if spec
|
|
50
|
-
Textus::Contract::Binder.bind(spec, effective_inputs, session: session)
|
|
51
|
-
else
|
|
52
|
-
[[], effective_inputs]
|
|
53
|
-
end
|
|
54
|
-
call_value = Textus::Call.build(role: @role, correlation_id: @correlation_id, dry_run: @dry_run)
|
|
55
|
-
Textus::Dispatcher.invoke(verb, container: @container, call: call_value, args: args, kwargs: kwargs)
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
if spec&.around
|
|
59
|
-
Textus::Contract::Around.with(spec.around, scope: self, inputs: inputs, session: session, &invoke)
|
|
60
|
-
else
|
|
61
|
-
invoke.call(inputs)
|
|
62
|
-
end
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
Textus::Dispatcher::VERBS.each_key do |verb|
|
|
66
|
-
define_method(verb) do |*args, **kwargs|
|
|
67
|
-
klass = Textus::Dispatcher::VERBS[verb]
|
|
68
|
-
inputs =
|
|
69
|
-
if klass.respond_to?(:contract?) && klass.contract?
|
|
70
|
-
Textus::Contract::Binder.inputs_from_ordered(klass.contract, args, kwargs)
|
|
71
|
-
else
|
|
72
|
-
kwargs
|
|
73
|
-
end
|
|
74
|
-
dispatch_bound(verb, inputs)
|
|
75
|
-
end
|
|
76
|
-
end
|
|
77
|
-
end
|
|
78
|
-
end
|
data/lib/textus/write/accept.rb
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
module Write
|
|
3
|
-
class Accept
|
|
4
|
-
extend Textus::Contract::DSL
|
|
5
|
-
|
|
6
|
-
verb :accept
|
|
7
|
-
summary "apply a queued proposal to its target zone; requires the author capability"
|
|
8
|
-
surfaces :cli, :mcp
|
|
9
|
-
cli "accept"
|
|
10
|
-
arg :pending_key, String, required: true, positional: true, description: "the queued proposal's key"
|
|
11
|
-
|
|
12
|
-
def initialize(container:, call:)
|
|
13
|
-
@container = container
|
|
14
|
-
@call = call
|
|
15
|
-
@manifest = container.manifest
|
|
16
|
-
@schemas = container.schemas
|
|
17
|
-
@events = container.events
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
def call(pending_key)
|
|
21
|
-
env = Textus::Read::Get.new(container: @container, call: @call).call(pending_key)
|
|
22
|
-
proposal = env.meta["proposal"] or raise ProposalError.new("entry has no proposal block: #{pending_key}")
|
|
23
|
-
target = proposal["target_key"] or raise ProposalError.new("proposal missing target_key")
|
|
24
|
-
action = proposal["action"] || "put"
|
|
25
|
-
|
|
26
|
-
guard.for(:accept, target).check!(
|
|
27
|
-
Textus::Domain::Policy::Evaluation.new(
|
|
28
|
-
actor: @call.role, transition: :accept, origin: pending_key,
|
|
29
|
-
target: target, envelope: env, manifest: @manifest
|
|
30
|
-
),
|
|
31
|
-
)
|
|
32
|
-
|
|
33
|
-
case action
|
|
34
|
-
when "put"
|
|
35
|
-
put_op.call(target, meta: env.meta["frontmatter"] || {}, body: env.body)
|
|
36
|
-
when "delete"
|
|
37
|
-
delete_op.call(target)
|
|
38
|
-
else
|
|
39
|
-
raise ProposalError.new("unknown action: #{action}")
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
delete_op.call(pending_key)
|
|
43
|
-
@events.publish(:proposal_accepted, ctx: hook_context, key: pending_key, target_key: target)
|
|
44
|
-
{ "protocol" => PROTOCOL, "accepted" => pending_key, "target_key" => target, "action" => action }
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
private
|
|
48
|
-
|
|
49
|
-
def guard
|
|
50
|
-
@guard ||= Textus::Domain::Policy::GuardFactory.new(manifest: @manifest, schemas: @schemas)
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
def hook_context = @hook_context ||= Textus::Hooks::Context.for(container: @container, call: @call)
|
|
54
|
-
def put_op = @put_op ||= Textus::Write::Put.new(container: @container, call: @call)
|
|
55
|
-
def delete_op = @delete_op ||= Textus::Write::KeyDelete.new(container: @container, call: @call)
|
|
56
|
-
end
|
|
57
|
-
end
|
|
58
|
-
end
|
data/lib/textus/write/enqueue.rb
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
module Write
|
|
3
|
-
# Push a job of a REGISTERED type onto the convergence queue, to be run by
|
|
4
|
-
# drain/serve. The closed allow-list (Jobs::Handlers.registry) is the safety
|
|
5
|
-
# boundary: an unregistered type is refused, so the general runner can never
|
|
6
|
-
# execute arbitrary code. Authority is checked here (the caller must hold the
|
|
7
|
-
# type's required_role, if any) and frozen onto the job's `enqueued_by` — the
|
|
8
|
-
# worker runs it as exactly this role, no escalation via the queue.
|
|
9
|
-
class Enqueue
|
|
10
|
-
extend Textus::Contract::DSL
|
|
11
|
-
|
|
12
|
-
verb :enqueue
|
|
13
|
-
summary "Push a registered job type onto the convergence queue, to be run by drain/serve."
|
|
14
|
-
surfaces :cli, :mcp
|
|
15
|
-
cli "enqueue"
|
|
16
|
-
arg :type, String, required: true, positional: true, description: "registered job type (e.g. materialize, re-pull, sweep)"
|
|
17
|
-
arg :args, Hash, positional: true, default: {}, description: "type-specific arguments (e.g. { key: ... } or { scope: ... })"
|
|
18
|
-
|
|
19
|
-
def initialize(container:, call:)
|
|
20
|
-
@container = container
|
|
21
|
-
@call = call
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
def call(type, args = {})
|
|
25
|
-
entry = Textus::Jobs::Handlers.registry.lookup(type) # raises UsageError for unregistered types
|
|
26
|
-
authorize!(entry)
|
|
27
|
-
|
|
28
|
-
job = Textus::Domain::Jobs::Job.new(
|
|
29
|
-
type: type, args: args, enqueued_by: @call.role, max_attempts: entry.max_attempts,
|
|
30
|
-
)
|
|
31
|
-
Textus::Ports::Queue.new(root: @container.root).enqueue(job)
|
|
32
|
-
{ "protocol" => Textus::PROTOCOL, "ok" => true, "id" => job.id }
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
private
|
|
36
|
-
|
|
37
|
-
def authorize!(entry)
|
|
38
|
-
required = entry.required_role
|
|
39
|
-
return if required.nil? || @call.role == required
|
|
40
|
-
|
|
41
|
-
raise Textus::Error.new(
|
|
42
|
-
"forbidden",
|
|
43
|
-
"role '#{@call.role}' is not authorized to enqueue this job type (requires '#{required}')",
|
|
44
|
-
details: { "role" => @call.role, "required_role" => required },
|
|
45
|
-
exit_code: 77,
|
|
46
|
-
)
|
|
47
|
-
end
|
|
48
|
-
end
|
|
49
|
-
end
|
|
50
|
-
end
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
module Write
|
|
3
|
-
class KeyDelete
|
|
4
|
-
extend Textus::Contract::DSL
|
|
5
|
-
|
|
6
|
-
verb :key_delete
|
|
7
|
-
summary "Delete one entry by key. Single-key, lower blast radius than " \
|
|
8
|
-
"key_delete_prefix; guarded by an optional optimistic-concurrency etag. Returns {ok, key, deleted}."
|
|
9
|
-
surfaces :cli, :mcp
|
|
10
|
-
cli "key delete"
|
|
11
|
-
arg :key, String, required: true, positional: true,
|
|
12
|
-
description: "dotted entry key to delete"
|
|
13
|
-
arg :if_etag, String,
|
|
14
|
-
description: "optimistic-concurrency guard: the etag you last read; the delete is rejected if the entry changed since"
|
|
15
|
-
# `call` already returns a wire hash {protocol, ok, key, deleted}; identity response.
|
|
16
|
-
|
|
17
|
-
def initialize(container:, call:)
|
|
18
|
-
@container = container
|
|
19
|
-
@call = call
|
|
20
|
-
@manifest = container.manifest
|
|
21
|
-
@events = container.events
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
def call(key, if_etag: nil, suppress_events: false)
|
|
25
|
-
Textus::Manifest::Data.validate_key!(key)
|
|
26
|
-
mentry = @manifest.resolver.resolve(key).entry
|
|
27
|
-
|
|
28
|
-
guard_for(:key_delete, key, if_etag: if_etag).check!(eval_for(:key_delete, target_key: key))
|
|
29
|
-
|
|
30
|
-
writer.delete(key, mentry: mentry, if_etag: if_etag)
|
|
31
|
-
|
|
32
|
-
unless suppress_events
|
|
33
|
-
@events.publish(:entry_deleted,
|
|
34
|
-
ctx: hook_context,
|
|
35
|
-
key: key)
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
{ "protocol" => Textus::PROTOCOL, "ok" => true, "key" => key, "deleted" => true }
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
private
|
|
42
|
-
|
|
43
|
-
def guard_for(transition, key, if_etag: nil)
|
|
44
|
-
Textus::Domain::Policy::GuardFactory.new(
|
|
45
|
-
manifest: @manifest, schemas: @container.schemas, extra: { if_etag: if_etag },
|
|
46
|
-
).for(transition, key)
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
def eval_for(transition, target_key:, envelope: nil)
|
|
50
|
-
Textus::Domain::Policy::Evaluation.new(
|
|
51
|
-
actor: @call.role, transition: transition, origin: nil,
|
|
52
|
-
target: target_key, envelope: envelope, manifest: @manifest
|
|
53
|
-
)
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
def hook_context
|
|
57
|
-
@hook_context ||= Textus::Hooks::Context.for(container: @container, call: @call)
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
def writer
|
|
61
|
-
@writer ||= Textus::Envelope::IO::Writer.from(container: @container, call: @call)
|
|
62
|
-
end
|
|
63
|
-
end
|
|
64
|
-
end
|
|
65
|
-
end
|