textus 0.8.0 → 0.9.2
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 +245 -0
- data/README.md +54 -26
- data/SPEC.md +194 -63
- data/docs/architecture.md +22 -4
- data/docs/conventions.md +24 -17
- data/lib/textus/application/context.rb +68 -0
- data/lib/textus/application/reads/audit.rb +69 -0
- data/lib/textus/application/reads/blame.rb +79 -0
- data/lib/textus/application/reads/freshness.rb +77 -0
- data/lib/textus/application/reads/get.rb +62 -0
- data/lib/textus/application/reads/policy_explain.rb +39 -0
- data/lib/textus/application/refresh/all.rb +41 -0
- data/lib/textus/application/refresh/orchestrator.rb +68 -0
- data/lib/textus/application/refresh/worker.rb +79 -0
- data/lib/textus/application/writes/accept.rb +43 -0
- data/lib/textus/application/writes/build.rb +24 -0
- data/lib/textus/application/writes/delete.rb +37 -0
- data/lib/textus/application/writes/publish.rb +25 -0
- data/lib/textus/application/writes/put.rb +44 -0
- data/lib/textus/builder.rb +27 -14
- data/lib/textus/cli/group/policy.rb +11 -0
- data/lib/textus/cli/verb/accept.rb +2 -1
- data/lib/textus/cli/verb/audit.rb +31 -0
- data/lib/textus/cli/verb/blame.rb +17 -0
- data/lib/textus/cli/verb/build.rb +2 -1
- data/lib/textus/cli/verb/delete.rb +2 -1
- data/lib/textus/cli/verb/freshness.rb +17 -0
- data/lib/textus/cli/verb/get.rb +8 -1
- data/lib/textus/cli/verb/hook_run.rb +3 -3
- data/lib/textus/cli/verb/policy_explain.rb +15 -0
- data/lib/textus/cli/verb/policy_list.rb +25 -0
- data/lib/textus/cli/verb/put.rb +5 -4
- data/lib/textus/cli/verb/refresh.rb +2 -1
- data/lib/textus/cli/verb/refresh_stale.rb +19 -0
- data/lib/textus/cli/verb/reject.rb +15 -0
- data/lib/textus/cli.rb +16 -2
- data/lib/textus/composition.rb +71 -0
- data/lib/textus/doctor/check/handler_allowlist.rb +33 -0
- data/lib/textus/doctor/check/intake_registration.rb +46 -0
- data/lib/textus/doctor/check/legacy_intake_fields.rb +57 -0
- data/lib/textus/doctor/check/policy_ambiguity.rb +49 -0
- data/lib/textus/doctor.rb +5 -1
- data/lib/textus/domain/action.rb +9 -0
- data/lib/textus/domain/freshness/evaluator.rb +30 -0
- data/lib/textus/domain/freshness/policy.rb +18 -0
- data/lib/textus/domain/freshness/verdict.rb +12 -0
- data/lib/textus/domain/outcome.rb +10 -0
- data/lib/textus/domain/permission.rb +15 -0
- data/lib/textus/domain/policy/handler_allowlist.rb +17 -0
- data/lib/textus/domain/policy/matcher.rb +51 -0
- data/lib/textus/domain/policy/promote.rb +24 -0
- data/lib/textus/domain/policy/refresh.rb +48 -0
- data/lib/textus/domain/policy.rb +7 -0
- data/lib/textus/hooks/builtin.rb +5 -5
- data/lib/textus/hooks/dispatcher.rb +15 -1
- data/lib/textus/hooks/dsl.rb +18 -0
- data/lib/textus/hooks/registry.rb +12 -5
- data/lib/textus/infra/clock.rb +9 -0
- data/lib/textus/infra/event_bus.rb +27 -0
- data/lib/textus/infra/publisher.rb +73 -0
- data/lib/textus/infra/refresh/detached.rb +38 -0
- data/lib/textus/infra/refresh/lock.rb +44 -0
- data/lib/textus/init.rb +71 -28
- data/lib/textus/intro.rb +22 -14
- data/lib/textus/manifest/entry.rb +18 -9
- data/lib/textus/manifest/policies.rb +83 -0
- data/lib/textus/manifest.rb +30 -0
- data/lib/textus/proposal.rb +4 -21
- data/lib/textus/publisher.rb +4 -69
- data/lib/textus/refresh.rb +9 -44
- data/lib/textus/store/mover.rb +14 -9
- data/lib/textus/store/reader.rb +10 -8
- data/lib/textus/store/staleness.rb +4 -16
- data/lib/textus/store/validator.rb +46 -20
- data/lib/textus/store/view.rb +8 -19
- data/lib/textus/store/writer.rb +51 -14
- data/lib/textus/store.rb +32 -12
- data/lib/textus/version.rb +1 -1
- data/lib/textus.rb +1 -0
- metadata +46 -2
- data/lib/textus/cli/verb/stale.rb +0 -14
data/lib/textus/store/mover.rb
CHANGED
|
@@ -4,14 +4,15 @@ module Textus
|
|
|
4
4
|
class Store
|
|
5
5
|
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
|
6
6
|
class Mover
|
|
7
|
-
def initialize(reader:, writer:, manifest:, audit_log:)
|
|
7
|
+
def initialize(store:, reader:, writer:, manifest:, audit_log:)
|
|
8
|
+
@store = store
|
|
8
9
|
@reader = reader
|
|
9
10
|
@writer = writer
|
|
10
11
|
@manifest = manifest
|
|
11
12
|
@audit_log = audit_log
|
|
12
13
|
end
|
|
13
14
|
|
|
14
|
-
def call(old_key, new_key, as: Role::DEFAULT, dry_run: false)
|
|
15
|
+
def call(old_key, new_key, as: Role::DEFAULT, dry_run: false, correlation_id: nil)
|
|
15
16
|
@manifest.validate_key!(old_key)
|
|
16
17
|
@manifest.validate_key!(new_key)
|
|
17
18
|
raise UsageError.new("mv: old and new keys are identical") if old_key == new_key
|
|
@@ -69,23 +70,27 @@ module Textus
|
|
|
69
70
|
rewrite_name_for_mv!(new_mentry, new_path, new_key)
|
|
70
71
|
etag_after = Etag.for_file(new_path)
|
|
71
72
|
|
|
73
|
+
extras = {
|
|
74
|
+
"from_key" => old_key, "to_key" => new_key,
|
|
75
|
+
"from_path" => old_path, "to_path" => new_path,
|
|
76
|
+
"uid" => current_uid
|
|
77
|
+
}
|
|
78
|
+
extras["correlation_id"] = correlation_id if correlation_id
|
|
79
|
+
|
|
72
80
|
@audit_log.append(
|
|
73
81
|
role: as, verb: "mv", key: new_key,
|
|
74
82
|
etag_before: etag_before, etag_after: etag_after,
|
|
75
|
-
extras:
|
|
76
|
-
"from_key" => old_key, "to_key" => new_key,
|
|
77
|
-
"from_path" => old_path, "to_path" => new_path,
|
|
78
|
-
"uid" => current_uid
|
|
79
|
-
}
|
|
83
|
+
extras: extras
|
|
80
84
|
)
|
|
81
85
|
|
|
82
|
-
|
|
86
|
+
new_envelope = @reader.get(new_key)
|
|
87
|
+
@store.fire_event(:mv, key: new_key, from_key: old_key, to_key: new_key, envelope: new_envelope)
|
|
83
88
|
{
|
|
84
89
|
"protocol" => PROTOCOL, "ok" => true,
|
|
85
90
|
"from_key" => old_key, "to_key" => new_key,
|
|
86
91
|
"from_path" => old_path, "to_path" => new_path,
|
|
87
92
|
"uid" => current_uid,
|
|
88
|
-
"envelope" =>
|
|
93
|
+
"envelope" => new_envelope
|
|
89
94
|
}
|
|
90
95
|
end
|
|
91
96
|
|
data/lib/textus/store/reader.rb
CHANGED
|
@@ -7,20 +7,22 @@ module Textus
|
|
|
7
7
|
end
|
|
8
8
|
|
|
9
9
|
def get(key)
|
|
10
|
+
read_raw_envelope(key) || raise(UnknownKey.new(key, suggestions: @manifest.suggestions_for(key)))
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Reads the current on-disk state of key as a bare envelope, skipping
|
|
14
|
+
# freshness annotation to avoid recursion. Used by Freshness.refresh_sync
|
|
15
|
+
# after a sync refresh completes.
|
|
16
|
+
def read_raw_envelope(key)
|
|
10
17
|
mentry, path, = @manifest.resolve(key)
|
|
11
|
-
|
|
18
|
+
return nil unless File.exist?(path)
|
|
12
19
|
|
|
13
20
|
raw = File.binread(path)
|
|
14
21
|
parsed = Entry.for_format(mentry.format).parse(raw, path: path)
|
|
15
|
-
meta = parsed["_meta"]
|
|
16
|
-
content = parsed["content"]
|
|
17
|
-
@store.writer.enforce_name_match!(path, meta, mentry.format)
|
|
18
|
-
schema = @store.schema_for(mentry.schema)
|
|
19
|
-
Entry.for_format(mentry.format).validate_against(schema, parsed) if schema
|
|
20
22
|
Envelope.build(
|
|
21
23
|
key: key, mentry: mentry, path: path,
|
|
22
|
-
meta:
|
|
23
|
-
etag: Etag.for_bytes(raw), content: content
|
|
24
|
+
meta: parsed["_meta"], body: parsed["body"],
|
|
25
|
+
etag: Etag.for_bytes(raw), content: parsed["content"]
|
|
24
26
|
)
|
|
25
27
|
end
|
|
26
28
|
|
|
@@ -47,11 +47,12 @@ module Textus
|
|
|
47
47
|
end
|
|
48
48
|
|
|
49
49
|
@manifest.entries.each do |mentry|
|
|
50
|
-
next unless mentry.
|
|
50
|
+
next unless mentry.intake_handler
|
|
51
51
|
next if zone && mentry.zone != zone
|
|
52
52
|
next if prefix && !(mentry.key == prefix || mentry.key.start_with?("#{prefix}."))
|
|
53
53
|
|
|
54
|
-
|
|
54
|
+
policy_set = @manifest.policies_for(mentry.key)
|
|
55
|
+
ttl = policy_set.refresh&.ttl_seconds
|
|
55
56
|
next unless ttl
|
|
56
57
|
|
|
57
58
|
path = Textus::Key::Path.resolve(@manifest, mentry)
|
|
@@ -102,21 +103,8 @@ module Textus
|
|
|
102
103
|
nil
|
|
103
104
|
end
|
|
104
105
|
|
|
105
|
-
def parse_ttl(s)
|
|
106
|
-
return nil unless s
|
|
107
|
-
|
|
108
|
-
m = s.to_s.match(/\A(\d+)([smhd])\z/) or return nil
|
|
109
|
-
n = m[1].to_i
|
|
110
|
-
case m[2]
|
|
111
|
-
when "s" then n
|
|
112
|
-
when "m" then n * 60
|
|
113
|
-
when "h" then n * 3600
|
|
114
|
-
when "d" then n * 86_400
|
|
115
|
-
end
|
|
116
|
-
end
|
|
117
|
-
|
|
118
106
|
def intake_stale_row(mentry, path, reason)
|
|
119
|
-
{ "key" => mentry.key, "path" => path, "
|
|
107
|
+
{ "key" => mentry.key, "path" => path, "handler" => mentry.intake_handler, "reason" => reason }
|
|
120
108
|
end
|
|
121
109
|
|
|
122
110
|
def stale_row(mentry, path, reason)
|
|
@@ -10,14 +10,30 @@ module Textus
|
|
|
10
10
|
|
|
11
11
|
def call
|
|
12
12
|
violations = []
|
|
13
|
+
check_content_violations(violations)
|
|
14
|
+
check_role_authority_violations(violations)
|
|
15
|
+
{ "protocol" => PROTOCOL, "ok" => violations.empty?, "violations" => violations }
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def check_content_violations(violations)
|
|
13
21
|
@manifest.enumerate.each do |row|
|
|
22
|
+
key = row[:key]
|
|
23
|
+
mentry = row[:manifest_entry]
|
|
24
|
+
env = fetch_envelope(key, violations) or next
|
|
25
|
+
schema = mentry.schema && @schema_for.call(mentry.schema)
|
|
26
|
+
next unless schema
|
|
27
|
+
|
|
14
28
|
begin
|
|
15
|
-
|
|
29
|
+
validate_schema!(schema, env, mentry.format)
|
|
16
30
|
rescue Textus::Error => e
|
|
17
|
-
violations << { "key" =>
|
|
31
|
+
violations << { "key" => key, "code" => e.code, "message" => e.message }
|
|
18
32
|
end
|
|
19
33
|
end
|
|
34
|
+
end
|
|
20
35
|
|
|
36
|
+
def check_role_authority_violations(violations)
|
|
21
37
|
@manifest.enumerate.each do |row|
|
|
22
38
|
mentry = row[:manifest_entry]
|
|
23
39
|
next unless mentry.schema
|
|
@@ -30,26 +46,36 @@ module Textus
|
|
|
30
46
|
rescue StandardError
|
|
31
47
|
next
|
|
32
48
|
end
|
|
33
|
-
|
|
34
|
-
next if last_writer.nil?
|
|
35
|
-
|
|
36
|
-
env["_meta"].each_key do |field|
|
|
37
|
-
owner = schema.maintained_by(field)
|
|
38
|
-
next if owner.nil?
|
|
39
|
-
next if last_writer == owner
|
|
40
|
-
next if last_writer == "human"
|
|
41
|
-
|
|
42
|
-
violations << {
|
|
43
|
-
"key" => row[:key],
|
|
44
|
-
"code" => "role_authority",
|
|
45
|
-
"field" => field,
|
|
46
|
-
"expected" => owner,
|
|
47
|
-
"last_writer" => last_writer,
|
|
48
|
-
}
|
|
49
|
-
end
|
|
49
|
+
append_authority_violations(violations, row[:key], env, schema)
|
|
50
50
|
end
|
|
51
|
+
end
|
|
51
52
|
|
|
52
|
-
|
|
53
|
+
def append_authority_violations(violations, key, env, schema)
|
|
54
|
+
last_writer = @audit_log.last_writer_for(key)
|
|
55
|
+
return if last_writer.nil?
|
|
56
|
+
|
|
57
|
+
env["_meta"].each_key do |field|
|
|
58
|
+
owner = schema.maintained_by(field)
|
|
59
|
+
next if owner.nil? || last_writer == owner || last_writer == "human"
|
|
60
|
+
|
|
61
|
+
violations << { "key" => key, "code" => "role_authority",
|
|
62
|
+
"field" => field, "expected" => owner, "last_writer" => last_writer }
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def fetch_envelope(key, violations)
|
|
67
|
+
@reader.get(key)
|
|
68
|
+
rescue Textus::Error => e
|
|
69
|
+
violations << { "key" => key, "code" => e.code, "message" => e.message }
|
|
70
|
+
nil
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def validate_schema!(schema, envelope, format)
|
|
74
|
+
payload = case format
|
|
75
|
+
when "json", "yaml" then envelope["content"] || {}
|
|
76
|
+
else envelope["_meta"] || {}
|
|
77
|
+
end
|
|
78
|
+
schema.validate!(payload)
|
|
53
79
|
end
|
|
54
80
|
end
|
|
55
81
|
end
|
data/lib/textus/store/view.rb
CHANGED
|
@@ -1,28 +1,17 @@
|
|
|
1
1
|
module Textus
|
|
2
2
|
class Store
|
|
3
|
+
# Deprecated as of 0.9.1: use Textus::Application::Context instead.
|
|
4
|
+
# Removal scheduled for 0.10.0.
|
|
3
5
|
class View
|
|
4
|
-
|
|
5
|
-
|
|
6
|
+
def self.new(store, writable: false, as: nil)
|
|
7
|
+
unless @warned_once
|
|
8
|
+
warn "[textus] Store::View is deprecated; use Application::Context (will be removed in 0.10.0)"
|
|
9
|
+
@warned_once = true
|
|
10
|
+
end
|
|
6
11
|
|
|
7
|
-
def initialize(store, writable: false, as: nil)
|
|
8
12
|
raise UsageError.new("writable Store::View requires an as: role") if writable && (as.nil? || as.to_s.empty?)
|
|
9
13
|
|
|
10
|
-
|
|
11
|
-
@writable = writable
|
|
12
|
-
@as = as
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
READ_METHODS.each do |m|
|
|
16
|
-
define_method(m) { |*args, **kw| @store.reader.public_send(m, *args, **kw) }
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
WRITE_METHODS.each do |m|
|
|
20
|
-
define_method(m) do |*args, **kw|
|
|
21
|
-
raise UsageError.new("Store::View is read-only") unless @writable
|
|
22
|
-
|
|
23
|
-
kw[:as] = @as unless kw.key?(:as)
|
|
24
|
-
@store.public_send(m, *args, **kw)
|
|
25
|
-
end
|
|
14
|
+
Textus::Application::Context.new(store: store, role: as || "human")
|
|
26
15
|
end
|
|
27
16
|
end
|
|
28
17
|
end
|
data/lib/textus/store/writer.rb
CHANGED
|
@@ -10,11 +10,19 @@ module Textus
|
|
|
10
10
|
@reader = store.reader
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
+
# Backward-compat shim — orchestration now lives in Application::Writes::Put.
|
|
13
14
|
def put(key, meta: nil, body: nil, content: nil, if_etag: nil, as: Role::DEFAULT, suppress_events: false)
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
ctx = Textus::Application::Context.new(store: @store, role: as)
|
|
16
|
+
Textus::Application::Writes::Put.new(ctx: ctx, bus: @store.bus).call(
|
|
17
|
+
key, meta: meta, body: body, content: content, if_etag: if_etag, suppress_events: suppress_events
|
|
18
|
+
)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Pure I/O: validate, serialize, etag-check, write to disk, audit. No
|
|
22
|
+
# permission check and no event firing — those are handled by the caller
|
|
23
|
+
# (Application::Writes::Put).
|
|
24
|
+
def write_envelope_to_disk(key, mentry:, meta: nil, body: nil, content: nil, if_etag: nil, as: Role::DEFAULT, correlation_id: nil)
|
|
25
|
+
_, path, = @manifest.resolve(key)
|
|
18
26
|
|
|
19
27
|
meta ||= {}
|
|
20
28
|
strategy = Entry.for_format(mentry.format)
|
|
@@ -43,13 +51,15 @@ module Textus
|
|
|
43
51
|
FileUtils.mkdir_p(File.dirname(path))
|
|
44
52
|
File.binwrite(path, bytes)
|
|
45
53
|
etag_after = Etag.for_bytes(bytes)
|
|
46
|
-
@store.audit_log.append(
|
|
47
|
-
|
|
54
|
+
@store.audit_log.append(
|
|
55
|
+
role: as, verb: "put", key: key,
|
|
56
|
+
etag_before: etag_before, etag_after: etag_after,
|
|
57
|
+
extras: correlation_id ? { "correlation_id" => correlation_id } : nil
|
|
58
|
+
)
|
|
59
|
+
Envelope.build(
|
|
48
60
|
key: key, mentry: mentry, path: path,
|
|
49
61
|
meta: eff_meta, body: eff_body, etag: etag_after, content: eff_content
|
|
50
62
|
)
|
|
51
|
-
@store.fire_event(:put, key: key, envelope: envelope) unless suppress_events
|
|
52
|
-
envelope
|
|
53
63
|
end
|
|
54
64
|
|
|
55
65
|
def existing_uid_for(mentry, path)
|
|
@@ -108,24 +118,51 @@ module Textus
|
|
|
108
118
|
end
|
|
109
119
|
end
|
|
110
120
|
|
|
121
|
+
# Backward-compat shim — orchestration now lives in Application::Writes::Delete.
|
|
111
122
|
def delete(key, if_etag: nil, as: Role::DEFAULT, suppress_events: false)
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
123
|
+
ctx = Textus::Application::Context.new(store: @store, role: as)
|
|
124
|
+
Textus::Application::Writes::Delete.new(ctx: ctx, bus: @store.bus).call(
|
|
125
|
+
key, if_etag: if_etag, suppress_events: suppress_events
|
|
126
|
+
)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Pure I/O: resolve path, validate etag, delete from disk, audit. No
|
|
130
|
+
# permission check and no event firing — those are handled by the caller
|
|
131
|
+
# (Application::Writes::Delete).
|
|
132
|
+
def delete_envelope_from_disk(key, if_etag: nil, as: Role::DEFAULT, correlation_id: nil)
|
|
133
|
+
_, path, = @manifest.resolve(key)
|
|
115
134
|
raise UnknownKey.new(key, suggestions: @manifest.suggestions_for(key)) unless File.exist?(path)
|
|
116
135
|
|
|
117
136
|
etag_before = Etag.for_file(path)
|
|
118
137
|
raise EtagMismatch.new(key, if_etag, etag_before) if if_etag && if_etag != etag_before
|
|
119
138
|
|
|
120
139
|
File.delete(path)
|
|
121
|
-
@store.audit_log.append(
|
|
122
|
-
|
|
123
|
-
|
|
140
|
+
@store.audit_log.append(
|
|
141
|
+
role: as, verb: "delete", key: key,
|
|
142
|
+
etag_before: etag_before, etag_after: nil,
|
|
143
|
+
extras: correlation_id ? { "correlation_id" => correlation_id } : nil
|
|
144
|
+
)
|
|
124
145
|
end
|
|
125
146
|
|
|
126
147
|
def accept(key, as:)
|
|
127
148
|
Proposal.accept(@store, key, as: as)
|
|
128
149
|
end
|
|
150
|
+
|
|
151
|
+
def reject(pending_key, as: Role::DEFAULT)
|
|
152
|
+
raise ProposalError.new("only human role can reject proposals; got '#{as}'") unless as == "human"
|
|
153
|
+
|
|
154
|
+
mentry, = @store.manifest.resolve(pending_key)
|
|
155
|
+
raise ProposalError.new("reject: '#{pending_key}' is not a pending entry (zone=#{mentry.zone})") unless mentry.zone == "pending"
|
|
156
|
+
|
|
157
|
+
env = @store.get(pending_key)
|
|
158
|
+
proposal = env.dig("_meta", "proposal") or
|
|
159
|
+
raise ProposalError.new("entry has no proposal block: #{pending_key}")
|
|
160
|
+
target_key = proposal["target_key"] or raise ProposalError.new("proposal missing target_key")
|
|
161
|
+
|
|
162
|
+
delete(pending_key, as: as)
|
|
163
|
+
@store.fire_event(:reject, key: pending_key, target_key: target_key)
|
|
164
|
+
{ "protocol" => PROTOCOL, "rejected" => pending_key, "target_key" => target_key }
|
|
165
|
+
end
|
|
129
166
|
end
|
|
130
167
|
# rubocop:enable Metrics/ParameterLists
|
|
131
168
|
end
|
data/lib/textus/store.rb
CHANGED
|
@@ -42,22 +42,23 @@ module Textus
|
|
|
42
42
|
@bus = Hooks::Dispatcher.new(audit_log: audit_log)
|
|
43
43
|
@registry = Hooks::Registry.new(dispatcher: @bus)
|
|
44
44
|
@schemas = {}
|
|
45
|
-
|
|
45
|
+
load_hooks
|
|
46
46
|
@reader = Reader.new(self)
|
|
47
47
|
@writer = Writer.new(self)
|
|
48
|
+
fire_event(:loaded)
|
|
48
49
|
end
|
|
49
50
|
|
|
50
|
-
def
|
|
51
|
+
def load_hooks
|
|
51
52
|
Textus.with_registry(@registry) do
|
|
52
53
|
Hooks::Builtin.register_all
|
|
53
54
|
dir = File.join(@root, "hooks")
|
|
54
55
|
return unless File.directory?(dir)
|
|
55
56
|
|
|
56
|
-
Dir.glob(File.join(dir, "
|
|
57
|
+
Dir.glob(File.join(dir, "**/*.rb")).sort.each do |f| # rubocop:disable Lint/RedundantDirGlobSort
|
|
57
58
|
begin
|
|
58
59
|
load(f)
|
|
59
60
|
rescue StandardError, ScriptError => e
|
|
60
|
-
raise UsageError.new("failed loading
|
|
61
|
+
raise UsageError.new("failed loading hook #{File.basename(f)}: #{e.class}: #{e.message}")
|
|
61
62
|
end
|
|
62
63
|
end
|
|
63
64
|
end
|
|
@@ -74,24 +75,43 @@ module Textus
|
|
|
74
75
|
end
|
|
75
76
|
end
|
|
76
77
|
|
|
77
|
-
def get(key)
|
|
78
|
-
|
|
78
|
+
def get(key, as: Textus::Role::DEFAULT)
|
|
79
|
+
ctx = Textus::Composition.context(self, role: as)
|
|
80
|
+
result = Textus::Composition.reads_get(ctx).call(key)
|
|
81
|
+
raise UnknownKey.new(key, suggestions: manifest.suggestions_for(key)) if result.nil?
|
|
82
|
+
|
|
83
|
+
result
|
|
79
84
|
end
|
|
80
85
|
|
|
81
86
|
def where(key) = @reader.where(key)
|
|
82
87
|
def list(**) = @reader.list(**)
|
|
83
88
|
def schema_envelope(key) = @reader.schema_envelope(key)
|
|
84
89
|
|
|
85
|
-
|
|
90
|
+
# rubocop:disable Metrics/ParameterLists
|
|
91
|
+
def put(key, meta: nil, body: nil, content: nil, if_etag: nil, as: Role::DEFAULT, suppress_events: false)
|
|
92
|
+
ctx = Textus::Composition.context(self, role: as)
|
|
93
|
+
Textus::Composition.writes_put(ctx).call(
|
|
94
|
+
key, meta: meta, body: body, content: content, if_etag: if_etag, suppress_events: suppress_events
|
|
95
|
+
)
|
|
96
|
+
end
|
|
97
|
+
# rubocop:enable Metrics/ParameterLists
|
|
86
98
|
|
|
87
|
-
def delete(
|
|
99
|
+
def delete(key, if_etag: nil, as: Role::DEFAULT, suppress_events: false)
|
|
100
|
+
ctx = Textus::Composition.context(self, role: as)
|
|
101
|
+
Textus::Composition.writes_delete(ctx).call(key, if_etag: if_etag, suppress_events: suppress_events)
|
|
102
|
+
end
|
|
88
103
|
|
|
89
104
|
def fire_event(event, **)
|
|
90
105
|
view = Store::View.new(self)
|
|
91
106
|
@bus.publish(event, store: view, **)
|
|
92
107
|
end
|
|
93
108
|
|
|
94
|
-
def accept(
|
|
109
|
+
def accept(key, as: Role::DEFAULT)
|
|
110
|
+
ctx = Textus::Composition.context(self, role: as)
|
|
111
|
+
Textus::Composition.writes_accept(ctx).call(key)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def reject(...) = @writer.reject(...)
|
|
95
115
|
|
|
96
116
|
def deps(key) = @reader.deps(key)
|
|
97
117
|
def rdeps(key) = @reader.rdeps(key)
|
|
@@ -104,9 +124,9 @@ module Textus
|
|
|
104
124
|
# Move an entry from old_key to new_key within the same zone. Preserves
|
|
105
125
|
# uid (minting one first if absent), validates both keys against the
|
|
106
126
|
# manifest, refuses to clobber, and writes one mv audit row.
|
|
107
|
-
def mv(old_key, new_key, as: Role::DEFAULT, dry_run: false)
|
|
108
|
-
Mover.new(reader: @reader, writer: @writer, manifest: @manifest, audit_log: audit_log)
|
|
109
|
-
.call(old_key, new_key, as: as, dry_run: dry_run)
|
|
127
|
+
def mv(old_key, new_key, as: Role::DEFAULT, dry_run: false, correlation_id: nil)
|
|
128
|
+
Mover.new(store: self, reader: @reader, writer: @writer, manifest: @manifest, audit_log: audit_log)
|
|
129
|
+
.call(old_key, new_key, as: as, dry_run: dry_run, correlation_id: correlation_id)
|
|
110
130
|
end
|
|
111
131
|
|
|
112
132
|
def audit_log
|
data/lib/textus/version.rb
CHANGED
data/lib/textus.rb
CHANGED
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.9.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Patrick
|
|
@@ -109,6 +109,20 @@ files:
|
|
|
109
109
|
- docs/conventions.md
|
|
110
110
|
- exe/textus
|
|
111
111
|
- lib/textus.rb
|
|
112
|
+
- lib/textus/application/context.rb
|
|
113
|
+
- lib/textus/application/reads/audit.rb
|
|
114
|
+
- lib/textus/application/reads/blame.rb
|
|
115
|
+
- lib/textus/application/reads/freshness.rb
|
|
116
|
+
- lib/textus/application/reads/get.rb
|
|
117
|
+
- lib/textus/application/reads/policy_explain.rb
|
|
118
|
+
- lib/textus/application/refresh/all.rb
|
|
119
|
+
- lib/textus/application/refresh/orchestrator.rb
|
|
120
|
+
- lib/textus/application/refresh/worker.rb
|
|
121
|
+
- lib/textus/application/writes/accept.rb
|
|
122
|
+
- lib/textus/application/writes/build.rb
|
|
123
|
+
- lib/textus/application/writes/delete.rb
|
|
124
|
+
- lib/textus/application/writes/publish.rb
|
|
125
|
+
- lib/textus/application/writes/put.rb
|
|
112
126
|
- lib/textus/builder.rb
|
|
113
127
|
- lib/textus/builder/pipeline.rb
|
|
114
128
|
- lib/textus/builder/renderer.rb
|
|
@@ -120,13 +134,17 @@ files:
|
|
|
120
134
|
- lib/textus/cli/group.rb
|
|
121
135
|
- lib/textus/cli/group/hook.rb
|
|
122
136
|
- lib/textus/cli/group/key.rb
|
|
137
|
+
- lib/textus/cli/group/policy.rb
|
|
123
138
|
- lib/textus/cli/group/schema.rb
|
|
124
139
|
- lib/textus/cli/verb.rb
|
|
125
140
|
- lib/textus/cli/verb/accept.rb
|
|
141
|
+
- lib/textus/cli/verb/audit.rb
|
|
142
|
+
- lib/textus/cli/verb/blame.rb
|
|
126
143
|
- lib/textus/cli/verb/build.rb
|
|
127
144
|
- lib/textus/cli/verb/delete.rb
|
|
128
145
|
- lib/textus/cli/verb/deps.rb
|
|
129
146
|
- lib/textus/cli/verb/doctor.rb
|
|
147
|
+
- lib/textus/cli/verb/freshness.rb
|
|
130
148
|
- lib/textus/cli/verb/get.rb
|
|
131
149
|
- lib/textus/cli/verb/hook_run.rb
|
|
132
150
|
- lib/textus/cli/verb/hooks.rb
|
|
@@ -135,29 +153,48 @@ files:
|
|
|
135
153
|
- lib/textus/cli/verb/list.rb
|
|
136
154
|
- lib/textus/cli/verb/migrate_keys.rb
|
|
137
155
|
- lib/textus/cli/verb/mv.rb
|
|
156
|
+
- lib/textus/cli/verb/policy_explain.rb
|
|
157
|
+
- lib/textus/cli/verb/policy_list.rb
|
|
138
158
|
- lib/textus/cli/verb/published.rb
|
|
139
159
|
- lib/textus/cli/verb/put.rb
|
|
140
160
|
- lib/textus/cli/verb/rdeps.rb
|
|
141
161
|
- lib/textus/cli/verb/refresh.rb
|
|
162
|
+
- lib/textus/cli/verb/refresh_stale.rb
|
|
163
|
+
- lib/textus/cli/verb/reject.rb
|
|
142
164
|
- lib/textus/cli/verb/schema.rb
|
|
143
165
|
- lib/textus/cli/verb/schema_diff.rb
|
|
144
166
|
- lib/textus/cli/verb/schema_init.rb
|
|
145
167
|
- lib/textus/cli/verb/schema_migrate.rb
|
|
146
|
-
- lib/textus/cli/verb/stale.rb
|
|
147
168
|
- lib/textus/cli/verb/uid.rb
|
|
148
169
|
- lib/textus/cli/verb/where.rb
|
|
170
|
+
- lib/textus/composition.rb
|
|
149
171
|
- lib/textus/dependencies.rb
|
|
150
172
|
- lib/textus/doctor.rb
|
|
151
173
|
- lib/textus/doctor/check.rb
|
|
152
174
|
- lib/textus/doctor/check/audit_log.rb
|
|
175
|
+
- lib/textus/doctor/check/handler_allowlist.rb
|
|
153
176
|
- lib/textus/doctor/check/hooks.rb
|
|
154
177
|
- lib/textus/doctor/check/illegal_keys.rb
|
|
178
|
+
- lib/textus/doctor/check/intake_registration.rb
|
|
179
|
+
- lib/textus/doctor/check/legacy_intake_fields.rb
|
|
155
180
|
- lib/textus/doctor/check/manifest_files.rb
|
|
181
|
+
- lib/textus/doctor/check/policy_ambiguity.rb
|
|
156
182
|
- lib/textus/doctor/check/schema_violations.rb
|
|
157
183
|
- lib/textus/doctor/check/schemas.rb
|
|
158
184
|
- lib/textus/doctor/check/sentinels.rb
|
|
159
185
|
- lib/textus/doctor/check/templates.rb
|
|
160
186
|
- lib/textus/doctor/check/unowned_schema_fields.rb
|
|
187
|
+
- lib/textus/domain/action.rb
|
|
188
|
+
- lib/textus/domain/freshness/evaluator.rb
|
|
189
|
+
- lib/textus/domain/freshness/policy.rb
|
|
190
|
+
- lib/textus/domain/freshness/verdict.rb
|
|
191
|
+
- lib/textus/domain/outcome.rb
|
|
192
|
+
- lib/textus/domain/permission.rb
|
|
193
|
+
- lib/textus/domain/policy.rb
|
|
194
|
+
- lib/textus/domain/policy/handler_allowlist.rb
|
|
195
|
+
- lib/textus/domain/policy/matcher.rb
|
|
196
|
+
- lib/textus/domain/policy/promote.rb
|
|
197
|
+
- lib/textus/domain/policy/refresh.rb
|
|
161
198
|
- lib/textus/entry.rb
|
|
162
199
|
- lib/textus/entry/base.rb
|
|
163
200
|
- lib/textus/entry/json.rb
|
|
@@ -169,8 +206,14 @@ files:
|
|
|
169
206
|
- lib/textus/etag.rb
|
|
170
207
|
- lib/textus/hooks/builtin.rb
|
|
171
208
|
- lib/textus/hooks/dispatcher.rb
|
|
209
|
+
- lib/textus/hooks/dsl.rb
|
|
172
210
|
- lib/textus/hooks/loader.rb
|
|
173
211
|
- lib/textus/hooks/registry.rb
|
|
212
|
+
- lib/textus/infra/clock.rb
|
|
213
|
+
- lib/textus/infra/event_bus.rb
|
|
214
|
+
- lib/textus/infra/publisher.rb
|
|
215
|
+
- lib/textus/infra/refresh/detached.rb
|
|
216
|
+
- lib/textus/infra/refresh/lock.rb
|
|
174
217
|
- lib/textus/init.rb
|
|
175
218
|
- lib/textus/intro.rb
|
|
176
219
|
- lib/textus/key/distance.rb
|
|
@@ -178,6 +221,7 @@ files:
|
|
|
178
221
|
- lib/textus/key/path.rb
|
|
179
222
|
- lib/textus/manifest.rb
|
|
180
223
|
- lib/textus/manifest/entry.rb
|
|
224
|
+
- lib/textus/manifest/policies.rb
|
|
181
225
|
- lib/textus/migrate_keys.rb
|
|
182
226
|
- lib/textus/mustache.rb
|
|
183
227
|
- lib/textus/projection.rb
|