textus 0.20.2 → 0.26.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/ARCHITECTURE.md +148 -45
- data/CHANGELOG.md +194 -0
- data/README.md +8 -5
- data/SPEC.md +54 -15
- data/docs/conventions.md +10 -0
- data/lib/textus/application/caps.rb +49 -0
- data/lib/textus/application/context.rb +2 -2
- data/lib/textus/application/envelope/reader.rb +44 -0
- data/lib/textus/application/{writes/envelope_io.rb → envelope/writer.rb} +24 -50
- data/lib/textus/application/maintenance/key_delete_prefix.rb +44 -0
- data/lib/textus/application/maintenance/key_mv_prefix.rb +57 -0
- data/lib/textus/application/maintenance/migrate.rb +59 -0
- data/lib/textus/application/maintenance/rule_lint.rb +65 -0
- data/lib/textus/application/maintenance/zone_mv.rb +60 -0
- data/lib/textus/application/maintenance.rb +17 -0
- data/lib/textus/application/projection.rb +12 -10
- data/lib/textus/application/read/audit.rb +106 -0
- data/lib/textus/application/read/blame.rb +91 -0
- data/lib/textus/application/read/deps.rb +34 -0
- data/lib/textus/application/read/freshness.rb +110 -0
- data/lib/textus/application/read/get.rb +75 -0
- data/lib/textus/application/read/get_or_refresh.rb +63 -0
- data/lib/textus/application/read/list.rb +25 -0
- data/lib/textus/application/read/policy_explain.rb +47 -0
- data/lib/textus/application/read/published.rb +25 -0
- data/lib/textus/application/read/pulse.rb +101 -0
- data/lib/textus/application/read/rdeps.rb +35 -0
- data/lib/textus/application/read/schema_envelope.rb +26 -0
- data/lib/textus/application/read/stale.rb +23 -0
- data/lib/textus/application/read/uid.rb +30 -0
- data/lib/textus/application/read/validate_all.rb +32 -0
- data/lib/textus/application/{reads → read}/validator.rb +2 -2
- data/lib/textus/application/read/where.rb +26 -0
- data/lib/textus/application/use_case.rb +22 -0
- data/lib/textus/application/write/accept.rb +102 -0
- data/lib/textus/application/{writes → write}/authority_gate.rb +3 -3
- data/lib/textus/application/write/delete.rb +45 -0
- data/lib/textus/application/{writes → write}/materializer.rb +14 -15
- data/lib/textus/application/write/mv.rb +118 -0
- data/lib/textus/application/write/publish.rb +96 -0
- data/lib/textus/application/write/put.rb +49 -0
- data/lib/textus/application/write/refresh_all.rb +63 -0
- data/lib/textus/application/{refresh/orchestrator.rb → write/refresh_orchestrator.rb} +32 -8
- data/lib/textus/application/write/refresh_worker.rb +134 -0
- data/lib/textus/application/write/reject.rb +62 -0
- data/lib/textus/{intro.rb → boot.rb} +49 -29
- data/lib/textus/builder/pipeline.rb +5 -5
- data/lib/textus/cli/group/mcp.rb +9 -0
- data/lib/textus/cli/group/zone.rb +9 -0
- data/lib/textus/cli/verb/accept.rb +1 -1
- data/lib/textus/cli/verb/audit.rb +4 -2
- data/lib/textus/cli/verb/blame.rb +1 -1
- data/lib/textus/cli/verb/boot.rb +13 -0
- data/lib/textus/cli/verb/build.rb +2 -2
- data/lib/textus/cli/verb/delete.rb +1 -1
- data/lib/textus/cli/verb/deps.rb +1 -1
- data/lib/textus/cli/verb/doctor.rb +1 -1
- data/lib/textus/cli/verb/freshness.rb +1 -1
- data/lib/textus/cli/verb/get.rb +1 -1
- data/lib/textus/cli/verb/hook_run.rb +3 -4
- data/lib/textus/cli/verb/hooks.rb +11 -14
- data/lib/textus/cli/verb/key_delete.rb +24 -0
- data/lib/textus/cli/verb/list.rb +1 -1
- data/lib/textus/cli/verb/mcp_serve.rb +17 -0
- data/lib/textus/cli/verb/migrate.rb +18 -0
- data/lib/textus/cli/verb/mv.rb +11 -3
- data/lib/textus/cli/verb/published.rb +1 -1
- data/lib/textus/cli/verb/pulse.rb +17 -0
- data/lib/textus/cli/verb/put.rb +8 -6
- data/lib/textus/cli/verb/rdeps.rb +1 -1
- data/lib/textus/cli/verb/refresh.rb +1 -1
- data/lib/textus/cli/verb/refresh_stale.rb +1 -1
- data/lib/textus/cli/verb/reject.rb +1 -1
- data/lib/textus/cli/verb/rule_explain.rb +1 -1
- data/lib/textus/cli/verb/rule_lint.rb +18 -0
- data/lib/textus/cli/verb/schema.rb +1 -1
- data/lib/textus/cli/verb/uid.rb +1 -1
- data/lib/textus/cli/verb/where.rb +1 -1
- data/lib/textus/cli/verb/zone_mv.rb +19 -0
- data/lib/textus/cli/verb.rb +4 -4
- data/lib/textus/cli.rb +1 -1
- data/lib/textus/doctor/check/audit_log.rb +2 -2
- data/lib/textus/doctor/check/handler_allowlist.rb +2 -2
- data/lib/textus/doctor/check/hooks.rb +4 -3
- data/lib/textus/doctor/check/illegal_keys.rb +2 -2
- data/lib/textus/doctor/check/intake_registration.rb +2 -2
- data/lib/textus/doctor/check/manifest_files.rb +2 -2
- data/lib/textus/doctor/check/protocol_version.rb +2 -2
- data/lib/textus/doctor/check/refresh_locks.rb +2 -2
- data/lib/textus/doctor/check/rule_ambiguity.rb +2 -2
- data/lib/textus/doctor/check/schema_parse_error.rb +1 -1
- data/lib/textus/doctor/check/schema_violations.rb +1 -1
- data/lib/textus/doctor/check/schemas.rb +2 -2
- data/lib/textus/doctor/check/sentinels.rb +2 -2
- data/lib/textus/doctor/check/templates.rb +2 -2
- data/lib/textus/doctor/check/unowned_schema_fields.rb +1 -1
- data/lib/textus/doctor/check.rb +5 -3
- data/lib/textus/doctor.rb +24 -27
- data/lib/textus/domain/authorizer.rb +4 -4
- data/lib/textus/{application → domain}/policy/predicates/accept_authority_signed.rb +2 -2
- data/lib/textus/{application → domain}/policy/predicates/schema_valid.rb +1 -1
- data/lib/textus/{application → domain}/policy/promotion.rb +1 -1
- data/lib/textus/domain/staleness/generator_check.rb +2 -2
- data/lib/textus/domain/staleness/intake_check.rb +2 -2
- data/lib/textus/domain/staleness.rb +1 -1
- data/lib/textus/errors.rb +16 -0
- data/lib/textus/hooks/builtin.rb +14 -14
- data/lib/textus/hooks/context.rb +13 -13
- data/lib/textus/hooks/error_log.rb +32 -0
- data/lib/textus/hooks/{bus.rb → event_bus.rb} +41 -53
- data/lib/textus/hooks/loader.rb +29 -3
- data/lib/textus/hooks/rpc_registry.rb +77 -0
- data/lib/textus/infra/audit_log.rb +126 -16
- data/lib/textus/infra/audit_subscriber.rb +6 -7
- data/lib/textus/infra/refresh/detached.rb +1 -1
- data/lib/textus/key/path.rb +7 -3
- data/lib/textus/manifest/data.rb +78 -0
- data/lib/textus/manifest/entry/base.rb +44 -7
- data/lib/textus/manifest/entry/derived.rb +41 -6
- data/lib/textus/manifest/entry/intake.rb +15 -3
- data/lib/textus/manifest/entry/leaf.rb +6 -5
- data/lib/textus/manifest/entry/nested.rb +42 -3
- data/lib/textus/manifest/entry/parser.rb +8 -44
- data/lib/textus/manifest/entry/validators/events.rb +2 -2
- data/lib/textus/manifest/entry/validators/format_matrix.rb +5 -4
- data/lib/textus/manifest/entry/validators/index_filename.rb +2 -1
- data/lib/textus/manifest/entry/validators/inject_boot.rb +19 -0
- data/lib/textus/manifest/entry/validators/publish_each.rb +4 -3
- data/lib/textus/manifest/entry/validators.rb +1 -1
- data/lib/textus/manifest/entry.rb +3 -0
- data/lib/textus/manifest/policy.rb +48 -0
- data/lib/textus/manifest/resolver.rb +18 -18
- data/lib/textus/manifest/rules.rb +1 -1
- data/lib/textus/manifest/schema.rb +20 -6
- data/lib/textus/manifest.rb +53 -101
- data/lib/textus/mcp/errors.rb +32 -0
- data/lib/textus/mcp/server.rb +127 -0
- data/lib/textus/mcp/session.rb +31 -0
- data/lib/textus/mcp/tool_schemas.rb +71 -0
- data/lib/textus/mcp/tools.rb +129 -0
- data/lib/textus/mcp.rb +6 -0
- data/lib/textus/schema/tools.rb +14 -10
- data/lib/textus/session.rb +84 -0
- data/lib/textus/store.rb +17 -8
- data/lib/textus/version.rb +1 -1
- data/lib/textus.rb +8 -1
- metadata +65 -38
- data/lib/textus/application/reads/audit.rb +0 -69
- data/lib/textus/application/reads/blame.rb +0 -82
- data/lib/textus/application/reads/deps.rb +0 -26
- data/lib/textus/application/reads/freshness.rb +0 -88
- data/lib/textus/application/reads/get.rb +0 -67
- data/lib/textus/application/reads/get_or_refresh.rb +0 -51
- data/lib/textus/application/reads/list.rb +0 -17
- data/lib/textus/application/reads/policy_explain.rb +0 -39
- data/lib/textus/application/reads/published.rb +0 -17
- data/lib/textus/application/reads/rdeps.rb +0 -27
- data/lib/textus/application/reads/schema_envelope.rb +0 -18
- data/lib/textus/application/reads/stale.rb +0 -15
- data/lib/textus/application/reads/uid.rb +0 -23
- data/lib/textus/application/reads/validate_all.rb +0 -24
- data/lib/textus/application/reads/where.rb +0 -18
- data/lib/textus/application/refresh/all.rb +0 -52
- data/lib/textus/application/refresh/worker.rb +0 -116
- data/lib/textus/application/writes/accept.rb +0 -89
- data/lib/textus/application/writes/delete.rb +0 -33
- data/lib/textus/application/writes/mv.rb +0 -105
- data/lib/textus/application/writes/publish.rb +0 -162
- data/lib/textus/application/writes/put.rb +0 -37
- data/lib/textus/application/writes/reject.rb +0 -50
- data/lib/textus/cli/verb/intro.rb +0 -13
- data/lib/textus/infra/event_bus.rb +0 -27
- data/lib/textus/manifest/entry/validators/inject_intro.rb +0 -21
- data/lib/textus/operations.rb +0 -169
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
module Textus
|
|
2
|
+
module MCP
|
|
3
|
+
# JSON-Schema definitions for every MCP tool's inputSchema. Returned by
|
|
4
|
+
# the server in tools/list. Static today — a follow-up will enrich with
|
|
5
|
+
# manifest-derived enums for `zone`, `key`, etc.
|
|
6
|
+
module ToolSchemas
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
def all # rubocop:disable Metrics/MethodLength
|
|
10
|
+
[
|
|
11
|
+
tool("boot", "Return the orientation contract: zones, entries, schemas, write_flows, agent_quickstart.", {}, []),
|
|
12
|
+
tool("tick", "Delta since cursor. Returns {cursor, changed, stale, pending_review, doctor}.",
|
|
13
|
+
{ "since" => { "type" => "integer", "minimum" => 0 } }, []),
|
|
14
|
+
tool("find", "List keys filtered by zone and/or prefix.",
|
|
15
|
+
{ "zone" => { "type" => "string" }, "prefix" => { "type" => "string" } }, []),
|
|
16
|
+
tool("read", "Read one entry. Returns the envelope (uid, etag, _meta, body, freshness).",
|
|
17
|
+
{ "key" => { "type" => "string" } }, ["key"]),
|
|
18
|
+
tool("write", "Create or update an entry. Schema-validated. Returns {uid, etag}.",
|
|
19
|
+
{
|
|
20
|
+
"key" => { "type" => "string" },
|
|
21
|
+
"meta" => { "type" => "object" },
|
|
22
|
+
"body" => { "type" => "string" },
|
|
23
|
+
"content" => { "type" => "object" },
|
|
24
|
+
"if_etag" => { "type" => "string" },
|
|
25
|
+
}, %w[key meta]),
|
|
26
|
+
tool("propose", "Write a proposal to the session's propose_zone. Auto-prefixes the key.",
|
|
27
|
+
{
|
|
28
|
+
"key" => { "type" => "string", "description" => "Key relative to propose_zone, e.g. 'proposal.feature-x'" },
|
|
29
|
+
"meta" => { "type" => "object" },
|
|
30
|
+
"body" => { "type" => "string" },
|
|
31
|
+
}, %w[key meta]),
|
|
32
|
+
tool("refresh", "Run an intake refresh for one key. Returns the refresh Outcome.",
|
|
33
|
+
{ "key" => { "type" => "string" } }, ["key"]),
|
|
34
|
+
tool("refresh_stale", "Refresh all stale intake entries, optionally scoped by zone/prefix.",
|
|
35
|
+
{
|
|
36
|
+
"zone" => { "type" => "string" },
|
|
37
|
+
"prefix" => { "type" => "string" },
|
|
38
|
+
}, []),
|
|
39
|
+
tool("schema", "Return the schema (field shape) for an entry family.",
|
|
40
|
+
{ "family" => { "type" => "string" } }, ["family"]),
|
|
41
|
+
tool("rules", "Return effective rules for a key (refresh, promote, ...).",
|
|
42
|
+
{ "key" => { "type" => "string" } }, ["key"]),
|
|
43
|
+
tool("key_mv_prefix",
|
|
44
|
+
"Bulk-rename every leaf key under from_prefix to to_prefix. Dry-run returns a Plan; apply with dry_run: false.",
|
|
45
|
+
{ "from_prefix" => { "type" => "string" }, "to_prefix" => { "type" => "string" }, "dry_run" => { "type" => "boolean" } },
|
|
46
|
+
%w[from_prefix to_prefix]),
|
|
47
|
+
tool("key_delete_prefix", "Bulk-delete every leaf key under prefix.",
|
|
48
|
+
{ "prefix" => { "type" => "string" }, "dry_run" => { "type" => "boolean" } },
|
|
49
|
+
["prefix"]),
|
|
50
|
+
tool("zone_mv", "Rename a zone — manifest + files. Refuses if destination exists.",
|
|
51
|
+
{ "from" => { "type" => "string" }, "to" => { "type" => "string" }, "dry_run" => { "type" => "boolean" } },
|
|
52
|
+
%w[from to]),
|
|
53
|
+
tool("rule_lint", "Diff candidate manifest YAML's rules against the live manifest. No writes.",
|
|
54
|
+
{ "candidate_yaml" => { "type" => "string" } },
|
|
55
|
+
["candidate_yaml"]),
|
|
56
|
+
tool("migrate", "Run a YAML migration plan (multi-op).",
|
|
57
|
+
{ "plan_yaml" => { "type" => "string" }, "dry_run" => { "type" => "boolean" } },
|
|
58
|
+
["plan_yaml"]),
|
|
59
|
+
].freeze
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def tool(name, description, properties, required)
|
|
63
|
+
{
|
|
64
|
+
name: name,
|
|
65
|
+
description: description,
|
|
66
|
+
inputSchema: { type: "object", properties: properties, required: required },
|
|
67
|
+
}
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
module Textus
|
|
2
|
+
module MCP
|
|
3
|
+
# Dispatch table for MCP tool names → implementations. Each implementation
|
|
4
|
+
# receives (session:, store:, args:) and returns a JSON-encodable value.
|
|
5
|
+
# Tool errors are wrapped in ToolError; ContractDrift / CursorExpired
|
|
6
|
+
# propagate verbatim so the server can map them to JSON-RPC codes.
|
|
7
|
+
module Tools
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
def call(name, session:, store:, args:)
|
|
11
|
+
impl = REGISTRY[name] or raise ToolError.new("unknown tool: #{name}")
|
|
12
|
+
impl.call(session, store, args || {})
|
|
13
|
+
rescue ContractDrift, CursorExpired
|
|
14
|
+
raise
|
|
15
|
+
rescue Textus::Error => e
|
|
16
|
+
raise ToolError.new("#{name}: #{e.message}")
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def ops_for(session, store)
|
|
20
|
+
store.session(role: session.role)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
REGISTRY = {
|
|
24
|
+
"boot" => ->(_s, store, _a) { Textus::Boot.run(Textus::Session.for(store)) },
|
|
25
|
+
|
|
26
|
+
"find" => lambda do |s, store, args|
|
|
27
|
+
ops_for(s, store).list(zone: args["zone"], prefix: args["prefix"])
|
|
28
|
+
end,
|
|
29
|
+
|
|
30
|
+
"read" => lambda do |s, store, args|
|
|
31
|
+
key = args.fetch("key") { raise ToolError.new("read: missing key") }
|
|
32
|
+
env = ops_for(s, store).get(key)
|
|
33
|
+
env.to_h_for_wire
|
|
34
|
+
end,
|
|
35
|
+
|
|
36
|
+
"tick" => lambda do |s, store, args|
|
|
37
|
+
since = (args["since"] || s.cursor).to_i
|
|
38
|
+
ops_for(s, store).pulse(since: since)
|
|
39
|
+
end,
|
|
40
|
+
|
|
41
|
+
"write" => lambda do |s, store, args|
|
|
42
|
+
key = args.fetch("key") { raise ToolError.new("write: missing key") }
|
|
43
|
+
env = ops_for(s, store).put(
|
|
44
|
+
key,
|
|
45
|
+
meta: args["meta"] || {},
|
|
46
|
+
body: args["body"],
|
|
47
|
+
content: args["content"],
|
|
48
|
+
if_etag: args["if_etag"],
|
|
49
|
+
)
|
|
50
|
+
{ "uid" => env.uid, "etag" => env.etag }
|
|
51
|
+
end,
|
|
52
|
+
|
|
53
|
+
"propose" => lambda do |s, store, args|
|
|
54
|
+
raise ToolError.new("propose: session has no propose_zone") unless s.propose_zone
|
|
55
|
+
|
|
56
|
+
rel = args.fetch("key") { raise ToolError.new("propose: missing key") }
|
|
57
|
+
target = "#{s.propose_zone}.#{rel}"
|
|
58
|
+
env = ops_for(s, store).put(
|
|
59
|
+
target,
|
|
60
|
+
meta: args["meta"] || {},
|
|
61
|
+
body: args["body"],
|
|
62
|
+
content: args["content"],
|
|
63
|
+
)
|
|
64
|
+
{ "uid" => env.uid, "etag" => env.etag, "key" => target }
|
|
65
|
+
end,
|
|
66
|
+
|
|
67
|
+
"refresh" => lambda do |s, store, args|
|
|
68
|
+
key = args.fetch("key") { raise ToolError.new("refresh: missing key") }
|
|
69
|
+
outcome = ops_for(s, store).refresh(key)
|
|
70
|
+
{ "outcome" => outcome.class.name.split("::").last.downcase }
|
|
71
|
+
end,
|
|
72
|
+
|
|
73
|
+
"refresh_stale" => lambda do |s, store, args|
|
|
74
|
+
ops_for(s, store).refresh_all(zone: args["zone"], prefix: args["prefix"])
|
|
75
|
+
end,
|
|
76
|
+
|
|
77
|
+
"schema" => lambda do |_s, store, args|
|
|
78
|
+
family = args.fetch("family") { raise ToolError.new("schema: missing family") }
|
|
79
|
+
store.schemas.fetch(family)
|
|
80
|
+
end,
|
|
81
|
+
|
|
82
|
+
"rules" => lambda do |_s, store, args|
|
|
83
|
+
key = args.fetch("key") { raise ToolError.new("rules: missing key") }
|
|
84
|
+
set = store.manifest.rules.for(key)
|
|
85
|
+
{
|
|
86
|
+
"refresh" => set.refresh&.to_h,
|
|
87
|
+
"promote" => set.respond_to?(:promote) ? set.promote&.to_h : nil,
|
|
88
|
+
}.compact
|
|
89
|
+
end,
|
|
90
|
+
|
|
91
|
+
"key_mv_prefix" => lambda do |s, store, args|
|
|
92
|
+
ops_for(s, store).key_mv_prefix(
|
|
93
|
+
from_prefix: args.fetch("from_prefix") { raise ToolError.new("key_mv_prefix: missing from_prefix") },
|
|
94
|
+
to_prefix: args.fetch("to_prefix") { raise ToolError.new("key_mv_prefix: missing to_prefix") },
|
|
95
|
+
dry_run: args["dry_run"] || false,
|
|
96
|
+
).to_h
|
|
97
|
+
end,
|
|
98
|
+
|
|
99
|
+
"key_delete_prefix" => lambda do |s, store, args|
|
|
100
|
+
ops_for(s, store).key_delete_prefix(
|
|
101
|
+
prefix: args.fetch("prefix") { raise ToolError.new("key_delete_prefix: missing prefix") },
|
|
102
|
+
dry_run: args["dry_run"] || false,
|
|
103
|
+
).to_h
|
|
104
|
+
end,
|
|
105
|
+
|
|
106
|
+
"zone_mv" => lambda do |s, store, args|
|
|
107
|
+
ops_for(s, store).zone_mv(
|
|
108
|
+
from: args.fetch("from") { raise ToolError.new("zone_mv: missing from") },
|
|
109
|
+
to: args.fetch("to") { raise ToolError.new("zone_mv: missing to") },
|
|
110
|
+
dry_run: args["dry_run"] || false,
|
|
111
|
+
).to_h
|
|
112
|
+
end,
|
|
113
|
+
|
|
114
|
+
"rule_lint" => lambda do |s, store, args|
|
|
115
|
+
ops_for(s, store).rule_lint(
|
|
116
|
+
candidate_yaml: args.fetch("candidate_yaml") { raise ToolError.new("rule_lint: missing candidate_yaml") },
|
|
117
|
+
).to_h
|
|
118
|
+
end,
|
|
119
|
+
|
|
120
|
+
"migrate" => lambda do |s, store, args|
|
|
121
|
+
ops_for(s, store).migrate(
|
|
122
|
+
plan_yaml: args.fetch("plan_yaml") { raise ToolError.new("migrate: missing plan_yaml") },
|
|
123
|
+
dry_run: args["dry_run"] || false,
|
|
124
|
+
).to_h
|
|
125
|
+
end,
|
|
126
|
+
}.freeze
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
data/lib/textus/mcp.rb
ADDED
data/lib/textus/schema/tools.rb
CHANGED
|
@@ -6,7 +6,7 @@ module Textus
|
|
|
6
6
|
module Tools
|
|
7
7
|
# textus schema init NAME --from=KEY → infer YAML schema from an entry's frontmatter
|
|
8
8
|
def self.init(store, name:, from:)
|
|
9
|
-
env =
|
|
9
|
+
env = store.session.get(from)
|
|
10
10
|
meta = env.meta
|
|
11
11
|
schema = {
|
|
12
12
|
"name" => name,
|
|
@@ -25,7 +25,7 @@ module Textus
|
|
|
25
25
|
schema = load_schema(store, name)
|
|
26
26
|
drift = []
|
|
27
27
|
store.manifest.resolver.enumerate.each do |row|
|
|
28
|
-
env =
|
|
28
|
+
env = store.session.get(row[:key])
|
|
29
29
|
begin
|
|
30
30
|
schema.validate!(env.meta)
|
|
31
31
|
rescue SchemaViolation => e
|
|
@@ -49,14 +49,8 @@ module Textus
|
|
|
49
49
|
end
|
|
50
50
|
raise UsageError.new("schema migrate needs --rename=OLD:NEW or schema.evolution.migrate_from") if renames.empty?
|
|
51
51
|
|
|
52
|
-
authority = store
|
|
53
|
-
|
|
54
|
-
raise UsageError.new(
|
|
55
|
-
"schema migrate requires a role with kind :accept_authority in the manifest; " \
|
|
56
|
-
"none declared (add e.g. `- { name: owner, kind: accept_authority }` to roles:)",
|
|
57
|
-
)
|
|
58
|
-
end
|
|
59
|
-
ops = Textus::Operations.for(store, role: authority)
|
|
52
|
+
authority = accept_authority_for(store)
|
|
53
|
+
ops = store.session(role: authority)
|
|
60
54
|
touched = []
|
|
61
55
|
store.manifest.resolver.enumerate.each do |row|
|
|
62
56
|
env = ops.get(row[:key])
|
|
@@ -92,6 +86,16 @@ module Textus
|
|
|
92
86
|
rescue IoError
|
|
93
87
|
raise UsageError.new("schema not found: #{name}")
|
|
94
88
|
end
|
|
89
|
+
|
|
90
|
+
def self.accept_authority_for(store)
|
|
91
|
+
authority = store.manifest.policy.roles_with_kind(:accept_authority).first
|
|
92
|
+
return authority if authority
|
|
93
|
+
|
|
94
|
+
raise UsageError.new(
|
|
95
|
+
"schema migrate requires a role with kind :accept_authority in the manifest; " \
|
|
96
|
+
"none declared (add e.g. `- { name: owner, kind: accept_authority }` to roles:)",
|
|
97
|
+
)
|
|
98
|
+
end
|
|
95
99
|
end
|
|
96
100
|
end
|
|
97
101
|
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
module Textus
|
|
2
|
+
# Per-call session. Holds ctx (role, correlation_id, now, dry_run) and
|
|
3
|
+
# the three caps records. Generates one method per registered use case.
|
|
4
|
+
class Session
|
|
5
|
+
attr_reader :ctx, :read_caps, :write_caps, :hook_caps
|
|
6
|
+
|
|
7
|
+
def self.for(store, role: Role::DEFAULT, correlation_id: nil, dry_run: false)
|
|
8
|
+
read_caps, write_caps, hook_caps = Application.caps_from_store(store)
|
|
9
|
+
new(
|
|
10
|
+
ctx: Application::Context.build(role: role, correlation_id: correlation_id, dry_run: dry_run),
|
|
11
|
+
read_caps: read_caps, write_caps: write_caps, hook_caps: hook_caps
|
|
12
|
+
)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def initialize(ctx:, read_caps:, write_caps:, hook_caps:)
|
|
16
|
+
@ctx = ctx
|
|
17
|
+
@read_caps = read_caps
|
|
18
|
+
@write_caps = write_caps
|
|
19
|
+
@hook_caps = hook_caps
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def with_role(role)
|
|
23
|
+
self.class.new(
|
|
24
|
+
ctx: @ctx.with_role(role),
|
|
25
|
+
read_caps: @read_caps, write_caps: @write_caps, hook_caps: @hook_caps
|
|
26
|
+
)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def hook_context
|
|
30
|
+
@hook_context ||= Hooks::Context.new(session: self)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def rpc = @hook_caps.rpc
|
|
34
|
+
def events = @hook_caps.events
|
|
35
|
+
|
|
36
|
+
def envelope_reader
|
|
37
|
+
@envelope_reader ||= Application::Envelope::Reader.new(
|
|
38
|
+
file_store: @read_caps.file_store, manifest: @read_caps.manifest,
|
|
39
|
+
)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def envelope_writer
|
|
43
|
+
@envelope_writer ||= Application::Envelope::Writer.new(
|
|
44
|
+
file_store: @write_caps.file_store, manifest: @write_caps.manifest,
|
|
45
|
+
schemas: @write_caps.schemas, audit_log: @write_caps.audit_log,
|
|
46
|
+
ctx: @ctx, reader: envelope_reader
|
|
47
|
+
)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def boot(...) = Textus::Boot.run(self, ...)
|
|
51
|
+
def doctor(...) = Textus::Doctor.run(self, ...)
|
|
52
|
+
|
|
53
|
+
def refresh_orchestrator
|
|
54
|
+
@refresh_orchestrator ||= Application::Write::RefreshOrchestrator.new(
|
|
55
|
+
worker: refresh_worker,
|
|
56
|
+
store_root: @write_caps.root,
|
|
57
|
+
events: @write_caps.events,
|
|
58
|
+
ctx: @ctx,
|
|
59
|
+
hook_context: hook_context,
|
|
60
|
+
)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def refresh_worker
|
|
64
|
+
@refresh_worker ||= Application::Write::RefreshWorker::Impl.new(
|
|
65
|
+
ctx: @ctx, caps: @write_caps,
|
|
66
|
+
rpc: rpc, writer: envelope_writer, hook_context: hook_context
|
|
67
|
+
)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Generated dispatch methods. Defined AFTER all use-cases have registered
|
|
71
|
+
# (Zeitwerk.eager_load runs in lib/textus.rb, then session.rb is explicitly
|
|
72
|
+
# required so UseCase.entries is fully populated).
|
|
73
|
+
Application::UseCase.each do |entry|
|
|
74
|
+
verb = entry.verb
|
|
75
|
+
mod = entry.mod
|
|
76
|
+
caps_sym = entry.caps_kind
|
|
77
|
+
|
|
78
|
+
define_method(verb) do |*args, **kwargs|
|
|
79
|
+
fixed = { session: self, ctx: @ctx, caps: caps_sym == :read ? @read_caps : @write_caps }
|
|
80
|
+
mod.call(*args, **fixed, **kwargs)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
data/lib/textus/store.rb
CHANGED
|
@@ -2,7 +2,7 @@ require "fileutils"
|
|
|
2
2
|
|
|
3
3
|
module Textus
|
|
4
4
|
class Store
|
|
5
|
-
attr_reader :root, :manifest, :schemas, :file_store, :audit_log, :
|
|
5
|
+
attr_reader :root, :manifest, :schemas, :file_store, :audit_log, :events, :rpc
|
|
6
6
|
|
|
7
7
|
def self.discover(start_dir = Dir.pwd, root: nil)
|
|
8
8
|
explicit = root || ENV.fetch("TEXTUS_ROOT", nil)
|
|
@@ -33,13 +33,22 @@ module Textus
|
|
|
33
33
|
@manifest = Manifest.load(@root)
|
|
34
34
|
@schemas = Schemas.new(File.join(@root, "schemas"))
|
|
35
35
|
@file_store = Infra::Storage::FileStore.new
|
|
36
|
-
@audit_log = Infra::AuditLog.new(
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
@
|
|
36
|
+
@audit_log = Infra::AuditLog.new(
|
|
37
|
+
@root,
|
|
38
|
+
max_size: @manifest.data.audit_config[:max_size],
|
|
39
|
+
keep: @manifest.data.audit_config[:keep],
|
|
40
|
+
)
|
|
41
|
+
@events = Hooks::EventBus.new
|
|
42
|
+
@rpc = Hooks::RpcRegistry.new
|
|
43
|
+
Infra::AuditSubscriber.new(@audit_log).attach(@events)
|
|
44
|
+
Hooks::Builtin.register_all(events: @events, rpc: @rpc)
|
|
45
|
+
Hooks::Loader.new(events: @events, rpc: @rpc).load_dir(File.join(@root, "hooks"))
|
|
46
|
+
sess = Session.for(self, role: Role::DEFAULT)
|
|
47
|
+
@events.publish(:store_loaded, ctx: sess.hook_context)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def session(role: Role::DEFAULT, correlation_id: nil, dry_run: false)
|
|
51
|
+
Session.for(self, role: role, correlation_id: correlation_id, dry_run: dry_run)
|
|
43
52
|
end
|
|
44
53
|
end
|
|
45
54
|
end
|
data/lib/textus/version.rb
CHANGED
data/lib/textus.rb
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
require "zeitwerk"
|
|
2
2
|
require_relative "textus/version"
|
|
3
3
|
require_relative "textus/errors"
|
|
4
|
+
require_relative "textus/mcp"
|
|
5
|
+
require_relative "textus/mcp/errors"
|
|
4
6
|
|
|
5
7
|
loader = Zeitwerk::Loader.for_gem
|
|
6
8
|
loader.inflector.inflect(
|
|
@@ -8,11 +10,16 @@ loader.inflector.inflect(
|
|
|
8
10
|
"json" => "Json",
|
|
9
11
|
"yaml" => "Yaml",
|
|
10
12
|
"hook_dsl_scanner" => "HookDSLScanner",
|
|
11
|
-
"
|
|
13
|
+
"mcp" => "MCP",
|
|
14
|
+
"mcp_serve" => "MCPServe",
|
|
12
15
|
)
|
|
13
16
|
loader.ignore(File.expand_path("textus/errors.rb", __dir__))
|
|
17
|
+
loader.ignore(File.expand_path("textus/mcp.rb", __dir__))
|
|
18
|
+
loader.ignore(File.expand_path("textus/mcp/errors.rb", __dir__))
|
|
19
|
+
loader.ignore(File.expand_path("textus/session.rb", __dir__))
|
|
14
20
|
loader.setup
|
|
15
21
|
loader.eager_load
|
|
22
|
+
require_relative "textus/session"
|
|
16
23
|
|
|
17
24
|
module Textus
|
|
18
25
|
@hook_mutex = Mutex.new
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: textus
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.26.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Patrick
|
|
@@ -109,39 +109,47 @@ files:
|
|
|
109
109
|
- docs/conventions.md
|
|
110
110
|
- exe/textus
|
|
111
111
|
- lib/textus.rb
|
|
112
|
+
- lib/textus/application/caps.rb
|
|
112
113
|
- lib/textus/application/context.rb
|
|
113
|
-
- lib/textus/application/
|
|
114
|
-
- lib/textus/application/
|
|
115
|
-
- lib/textus/application/
|
|
114
|
+
- lib/textus/application/envelope/reader.rb
|
|
115
|
+
- lib/textus/application/envelope/writer.rb
|
|
116
|
+
- lib/textus/application/maintenance.rb
|
|
117
|
+
- lib/textus/application/maintenance/key_delete_prefix.rb
|
|
118
|
+
- lib/textus/application/maintenance/key_mv_prefix.rb
|
|
119
|
+
- lib/textus/application/maintenance/migrate.rb
|
|
120
|
+
- lib/textus/application/maintenance/rule_lint.rb
|
|
121
|
+
- lib/textus/application/maintenance/zone_mv.rb
|
|
116
122
|
- lib/textus/application/projection.rb
|
|
117
|
-
- lib/textus/application/
|
|
118
|
-
- lib/textus/application/
|
|
119
|
-
- lib/textus/application/
|
|
120
|
-
- lib/textus/application/
|
|
121
|
-
- lib/textus/application/
|
|
122
|
-
- lib/textus/application/
|
|
123
|
-
- lib/textus/application/
|
|
124
|
-
- lib/textus/application/
|
|
125
|
-
- lib/textus/application/
|
|
126
|
-
- lib/textus/application/
|
|
127
|
-
- lib/textus/application/
|
|
128
|
-
- lib/textus/application/
|
|
129
|
-
- lib/textus/application/
|
|
130
|
-
- lib/textus/application/
|
|
131
|
-
- lib/textus/application/
|
|
132
|
-
- lib/textus/application/
|
|
133
|
-
- lib/textus/application/
|
|
134
|
-
- lib/textus/application/
|
|
135
|
-
- lib/textus/application/
|
|
136
|
-
- lib/textus/application/
|
|
137
|
-
- lib/textus/application/
|
|
138
|
-
- lib/textus/application/
|
|
139
|
-
- lib/textus/application/
|
|
140
|
-
- lib/textus/application/
|
|
141
|
-
- lib/textus/application/
|
|
142
|
-
- lib/textus/application/
|
|
143
|
-
- lib/textus/application/
|
|
144
|
-
- lib/textus/application/
|
|
123
|
+
- lib/textus/application/read/audit.rb
|
|
124
|
+
- lib/textus/application/read/blame.rb
|
|
125
|
+
- lib/textus/application/read/deps.rb
|
|
126
|
+
- lib/textus/application/read/freshness.rb
|
|
127
|
+
- lib/textus/application/read/get.rb
|
|
128
|
+
- lib/textus/application/read/get_or_refresh.rb
|
|
129
|
+
- lib/textus/application/read/list.rb
|
|
130
|
+
- lib/textus/application/read/policy_explain.rb
|
|
131
|
+
- lib/textus/application/read/published.rb
|
|
132
|
+
- lib/textus/application/read/pulse.rb
|
|
133
|
+
- lib/textus/application/read/rdeps.rb
|
|
134
|
+
- lib/textus/application/read/schema_envelope.rb
|
|
135
|
+
- lib/textus/application/read/stale.rb
|
|
136
|
+
- lib/textus/application/read/uid.rb
|
|
137
|
+
- lib/textus/application/read/validate_all.rb
|
|
138
|
+
- lib/textus/application/read/validator.rb
|
|
139
|
+
- lib/textus/application/read/where.rb
|
|
140
|
+
- lib/textus/application/use_case.rb
|
|
141
|
+
- lib/textus/application/write/accept.rb
|
|
142
|
+
- lib/textus/application/write/authority_gate.rb
|
|
143
|
+
- lib/textus/application/write/delete.rb
|
|
144
|
+
- lib/textus/application/write/materializer.rb
|
|
145
|
+
- lib/textus/application/write/mv.rb
|
|
146
|
+
- lib/textus/application/write/publish.rb
|
|
147
|
+
- lib/textus/application/write/put.rb
|
|
148
|
+
- lib/textus/application/write/refresh_all.rb
|
|
149
|
+
- lib/textus/application/write/refresh_orchestrator.rb
|
|
150
|
+
- lib/textus/application/write/refresh_worker.rb
|
|
151
|
+
- lib/textus/application/write/reject.rb
|
|
152
|
+
- lib/textus/boot.rb
|
|
145
153
|
- lib/textus/builder/pipeline.rb
|
|
146
154
|
- lib/textus/builder/renderer.rb
|
|
147
155
|
- lib/textus/builder/renderer/json.rb
|
|
@@ -152,13 +160,16 @@ files:
|
|
|
152
160
|
- lib/textus/cli/group.rb
|
|
153
161
|
- lib/textus/cli/group/hook.rb
|
|
154
162
|
- lib/textus/cli/group/key.rb
|
|
163
|
+
- lib/textus/cli/group/mcp.rb
|
|
155
164
|
- lib/textus/cli/group/refresh.rb
|
|
156
165
|
- lib/textus/cli/group/rule.rb
|
|
157
166
|
- lib/textus/cli/group/schema.rb
|
|
167
|
+
- lib/textus/cli/group/zone.rb
|
|
158
168
|
- lib/textus/cli/verb.rb
|
|
159
169
|
- lib/textus/cli/verb/accept.rb
|
|
160
170
|
- lib/textus/cli/verb/audit.rb
|
|
161
171
|
- lib/textus/cli/verb/blame.rb
|
|
172
|
+
- lib/textus/cli/verb/boot.rb
|
|
162
173
|
- lib/textus/cli/verb/build.rb
|
|
163
174
|
- lib/textus/cli/verb/delete.rb
|
|
164
175
|
- lib/textus/cli/verb/deps.rb
|
|
@@ -168,16 +179,20 @@ files:
|
|
|
168
179
|
- lib/textus/cli/verb/hook_run.rb
|
|
169
180
|
- lib/textus/cli/verb/hooks.rb
|
|
170
181
|
- lib/textus/cli/verb/init.rb
|
|
171
|
-
- lib/textus/cli/verb/
|
|
182
|
+
- lib/textus/cli/verb/key_delete.rb
|
|
172
183
|
- lib/textus/cli/verb/list.rb
|
|
184
|
+
- lib/textus/cli/verb/mcp_serve.rb
|
|
185
|
+
- lib/textus/cli/verb/migrate.rb
|
|
173
186
|
- lib/textus/cli/verb/mv.rb
|
|
174
187
|
- lib/textus/cli/verb/published.rb
|
|
188
|
+
- lib/textus/cli/verb/pulse.rb
|
|
175
189
|
- lib/textus/cli/verb/put.rb
|
|
176
190
|
- lib/textus/cli/verb/rdeps.rb
|
|
177
191
|
- lib/textus/cli/verb/refresh.rb
|
|
178
192
|
- lib/textus/cli/verb/refresh_stale.rb
|
|
179
193
|
- lib/textus/cli/verb/reject.rb
|
|
180
194
|
- lib/textus/cli/verb/rule_explain.rb
|
|
195
|
+
- lib/textus/cli/verb/rule_lint.rb
|
|
181
196
|
- lib/textus/cli/verb/rule_list.rb
|
|
182
197
|
- lib/textus/cli/verb/schema.rb
|
|
183
198
|
- lib/textus/cli/verb/schema_diff.rb
|
|
@@ -185,6 +200,7 @@ files:
|
|
|
185
200
|
- lib/textus/cli/verb/schema_migrate.rb
|
|
186
201
|
- lib/textus/cli/verb/uid.rb
|
|
187
202
|
- lib/textus/cli/verb/where.rb
|
|
203
|
+
- lib/textus/cli/verb/zone_mv.rb
|
|
188
204
|
- lib/textus/doctor.rb
|
|
189
205
|
- lib/textus/doctor/check.rb
|
|
190
206
|
- lib/textus/doctor/check/audit_log.rb
|
|
@@ -212,7 +228,10 @@ files:
|
|
|
212
228
|
- lib/textus/domain/permission.rb
|
|
213
229
|
- lib/textus/domain/policy/handler_allowlist.rb
|
|
214
230
|
- lib/textus/domain/policy/matcher.rb
|
|
231
|
+
- lib/textus/domain/policy/predicates/accept_authority_signed.rb
|
|
232
|
+
- lib/textus/domain/policy/predicates/schema_valid.rb
|
|
215
233
|
- lib/textus/domain/policy/promote.rb
|
|
234
|
+
- lib/textus/domain/policy/promotion.rb
|
|
216
235
|
- lib/textus/domain/policy/refresh.rb
|
|
217
236
|
- lib/textus/domain/sentinel.rb
|
|
218
237
|
- lib/textus/domain/staleness.rb
|
|
@@ -228,25 +247,26 @@ files:
|
|
|
228
247
|
- lib/textus/errors.rb
|
|
229
248
|
- lib/textus/etag.rb
|
|
230
249
|
- lib/textus/hooks/builtin.rb
|
|
231
|
-
- lib/textus/hooks/bus.rb
|
|
232
250
|
- lib/textus/hooks/context.rb
|
|
251
|
+
- lib/textus/hooks/error_log.rb
|
|
252
|
+
- lib/textus/hooks/event_bus.rb
|
|
233
253
|
- lib/textus/hooks/fire_report.rb
|
|
234
254
|
- lib/textus/hooks/loader.rb
|
|
255
|
+
- lib/textus/hooks/rpc_registry.rb
|
|
235
256
|
- lib/textus/infra/audit_log.rb
|
|
236
257
|
- lib/textus/infra/audit_subscriber.rb
|
|
237
258
|
- lib/textus/infra/build_lock.rb
|
|
238
259
|
- lib/textus/infra/clock.rb
|
|
239
|
-
- lib/textus/infra/event_bus.rb
|
|
240
260
|
- lib/textus/infra/publisher.rb
|
|
241
261
|
- lib/textus/infra/refresh/detached.rb
|
|
242
262
|
- lib/textus/infra/refresh/lock.rb
|
|
243
263
|
- lib/textus/infra/storage/file_store.rb
|
|
244
264
|
- lib/textus/init.rb
|
|
245
|
-
- lib/textus/intro.rb
|
|
246
265
|
- lib/textus/key/distance.rb
|
|
247
266
|
- lib/textus/key/grammar.rb
|
|
248
267
|
- lib/textus/key/path.rb
|
|
249
268
|
- lib/textus/manifest.rb
|
|
269
|
+
- lib/textus/manifest/data.rb
|
|
250
270
|
- lib/textus/manifest/entry.rb
|
|
251
271
|
- lib/textus/manifest/entry/base.rb
|
|
252
272
|
- lib/textus/manifest/entry/derived.rb
|
|
@@ -258,18 +278,25 @@ files:
|
|
|
258
278
|
- lib/textus/manifest/entry/validators/events.rb
|
|
259
279
|
- lib/textus/manifest/entry/validators/format_matrix.rb
|
|
260
280
|
- lib/textus/manifest/entry/validators/index_filename.rb
|
|
261
|
-
- lib/textus/manifest/entry/validators/
|
|
281
|
+
- lib/textus/manifest/entry/validators/inject_boot.rb
|
|
262
282
|
- lib/textus/manifest/entry/validators/publish_each.rb
|
|
283
|
+
- lib/textus/manifest/policy.rb
|
|
263
284
|
- lib/textus/manifest/resolver.rb
|
|
264
285
|
- lib/textus/manifest/role_kinds.rb
|
|
265
286
|
- lib/textus/manifest/rules.rb
|
|
266
287
|
- lib/textus/manifest/schema.rb
|
|
288
|
+
- lib/textus/mcp.rb
|
|
289
|
+
- lib/textus/mcp/errors.rb
|
|
290
|
+
- lib/textus/mcp/server.rb
|
|
291
|
+
- lib/textus/mcp/session.rb
|
|
292
|
+
- lib/textus/mcp/tool_schemas.rb
|
|
293
|
+
- lib/textus/mcp/tools.rb
|
|
267
294
|
- lib/textus/mustache.rb
|
|
268
|
-
- lib/textus/operations.rb
|
|
269
295
|
- lib/textus/role.rb
|
|
270
296
|
- lib/textus/schema.rb
|
|
271
297
|
- lib/textus/schema/tools.rb
|
|
272
298
|
- lib/textus/schemas.rb
|
|
299
|
+
- lib/textus/session.rb
|
|
273
300
|
- lib/textus/store.rb
|
|
274
301
|
- lib/textus/uid.rb
|
|
275
302
|
- lib/textus/version.rb
|