textus 0.15.0 → 0.18.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 +14 -14
- data/CHANGELOG.md +313 -0
- data/README.md +13 -9
- data/SPEC.md +13 -10
- data/docs/conventions.md +2 -2
- data/lib/textus/application/context.rb +24 -0
- data/lib/textus/application/reads/audit.rb +1 -1
- data/lib/textus/application/reads/blame.rb +3 -1
- data/lib/textus/application/reads/deps.rb +1 -1
- data/lib/textus/application/reads/freshness.rb +12 -3
- data/lib/textus/application/reads/get.rb +32 -8
- data/lib/textus/application/reads/get_or_refresh.rb +5 -5
- data/lib/textus/application/reads/list.rb +3 -1
- data/lib/textus/application/reads/published.rb +1 -1
- data/lib/textus/application/reads/rdeps.rb +1 -1
- data/lib/textus/application/reads/schema_envelope.rb +3 -1
- data/lib/textus/application/reads/stale.rb +1 -1
- data/lib/textus/application/reads/uid.rb +1 -1
- data/lib/textus/application/reads/validate_all.rb +6 -1
- data/lib/textus/application/reads/validator.rb +84 -0
- data/lib/textus/application/reads/where.rb +4 -1
- data/lib/textus/application/refresh/all.rb +8 -1
- data/lib/textus/application/refresh/orchestrator.rb +2 -3
- data/lib/textus/application/refresh/worker.rb +18 -15
- data/lib/textus/application/writes/accept.rb +12 -12
- data/lib/textus/application/writes/build.rb +3 -4
- data/lib/textus/application/writes/delete.rb +10 -15
- data/lib/textus/application/writes/envelope_io.rb +106 -0
- data/lib/textus/application/writes/mv.rb +25 -27
- data/lib/textus/application/writes/publish.rb +8 -9
- data/lib/textus/application/writes/put.rb +12 -16
- data/lib/textus/application/writes/reject.rb +10 -10
- data/lib/textus/builder/pipeline.rb +2 -2
- data/lib/textus/cli/group/hook.rb +1 -3
- data/lib/textus/cli/group/key.rb +1 -4
- data/lib/textus/cli/group/refresh.rb +1 -2
- data/lib/textus/cli/group/rule.rb +1 -3
- data/lib/textus/cli/group/schema.rb +1 -5
- data/lib/textus/cli/group.rb +12 -16
- data/lib/textus/cli/verb/accept.rb +3 -1
- data/lib/textus/cli/verb/audit.rb +3 -1
- data/lib/textus/cli/verb/blame.rb +3 -1
- data/lib/textus/cli/verb/build.rb +4 -2
- data/lib/textus/cli/verb/delete.rb +3 -1
- data/lib/textus/cli/verb/deps.rb +3 -1
- data/lib/textus/cli/verb/doctor.rb +2 -0
- data/lib/textus/cli/verb/freshness.rb +3 -1
- data/lib/textus/cli/verb/get.rb +3 -1
- data/lib/textus/cli/verb/hook_run.rb +3 -0
- data/lib/textus/cli/verb/hooks.rb +3 -0
- data/lib/textus/cli/verb/init.rb +2 -0
- data/lib/textus/cli/verb/intro.rb +2 -0
- data/lib/textus/cli/verb/key_normalize.rb +3 -0
- data/lib/textus/cli/verb/list.rb +3 -1
- data/lib/textus/cli/verb/mv.rb +4 -1
- data/lib/textus/cli/verb/published.rb +3 -1
- data/lib/textus/cli/verb/put.rb +3 -1
- data/lib/textus/cli/verb/rdeps.rb +3 -1
- data/lib/textus/cli/verb/refresh.rb +1 -1
- data/lib/textus/cli/verb/refresh_stale.rb +3 -0
- data/lib/textus/cli/verb/reject.rb +3 -1
- data/lib/textus/cli/verb/rule_explain.rb +4 -1
- data/lib/textus/cli/verb/rule_list.rb +3 -0
- data/lib/textus/cli/verb/schema.rb +4 -1
- data/lib/textus/cli/verb/schema_diff.rb +3 -0
- data/lib/textus/cli/verb/schema_init.rb +3 -0
- data/lib/textus/cli/verb/schema_migrate.rb +3 -0
- data/lib/textus/cli/verb/uid.rb +4 -1
- data/lib/textus/cli/verb/where.rb +3 -1
- data/lib/textus/cli/verb.rb +30 -0
- data/lib/textus/cli.rb +18 -27
- data/lib/textus/doctor/check/audit_log.rb +1 -1
- data/lib/textus/doctor/check/hooks.rb +3 -1
- data/lib/textus/doctor/check/intake_registration.rb +3 -3
- data/lib/textus/doctor/check/schema_violations.rb +1 -1
- data/lib/textus/doctor/check/sentinels.rb +2 -2
- data/lib/textus/domain/freshness/evaluator.rb +1 -1
- data/lib/textus/domain/freshness/policy.rb +1 -1
- data/lib/textus/domain/freshness/verdict.rb +1 -1
- data/lib/textus/domain/freshness.rb +40 -0
- data/lib/textus/domain/policy/predicates/schema_valid.rb +2 -2
- data/lib/textus/{store → domain}/sentinel.rb +1 -1
- data/lib/textus/{store → domain}/staleness/generator_check.rb +1 -1
- data/lib/textus/{store → domain}/staleness/intake_check.rb +1 -1
- data/lib/textus/{store → domain}/staleness.rb +1 -1
- data/lib/textus/entry/json.rb +1 -1
- data/lib/textus/entry/markdown.rb +1 -1
- data/lib/textus/entry/yaml.rb +1 -1
- data/lib/textus/envelope.rb +7 -3
- data/lib/textus/errors.rb +19 -0
- data/lib/textus/hooks/builtin.rb +6 -6
- data/lib/textus/hooks/dispatcher.rb +17 -9
- data/lib/textus/hooks/loader.rb +20 -17
- data/lib/textus/hooks/registry.rb +4 -0
- data/lib/textus/{store → infra}/audit_log.rb +1 -1
- data/lib/textus/infra/audit_subscriber.rb +43 -0
- data/lib/textus/infra/publisher.rb +3 -3
- data/lib/textus/infra/storage/file_store.rb +26 -0
- data/lib/textus/init.rb +11 -9
- data/lib/textus/manifest/resolution.rb +5 -0
- data/lib/textus/manifest.rb +4 -3
- data/lib/textus/migrate_keys.rb +1 -1
- data/lib/textus/operations.rb +83 -16
- data/lib/textus/projection.rb +2 -2
- data/lib/textus/refresh.rb +1 -1
- data/lib/textus/schema/tools.rb +5 -5
- data/lib/textus/schemas.rb +46 -0
- data/lib/textus/store.rb +12 -49
- data/lib/textus/uid.rb +18 -0
- data/lib/textus/version.rb +1 -1
- data/lib/textus.rb +17 -1
- metadata +14 -13
- data/lib/textus/hooks/dsl.rb +0 -11
- data/lib/textus/operations/reads.rb +0 -56
- data/lib/textus/operations/refresh.rb +0 -27
- data/lib/textus/operations/writes.rb +0 -21
- data/lib/textus/store/reader.rb +0 -69
- data/lib/textus/store/validator.rb +0 -82
- data/lib/textus/store/writer.rb +0 -102
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
class Operations
|
|
3
|
-
class Reads
|
|
4
|
-
# `get` — pure read; returns envelope + freshness verdict;
|
|
5
|
-
# never triggers refresh; no orchestrator dependency.
|
|
6
|
-
# `get_or_refresh` — composes `get` with the refresh orchestrator; runs
|
|
7
|
-
# refresh per policy when the verdict says stale.
|
|
8
|
-
# Use this for interactive reads where the caller
|
|
9
|
-
# wants the freshest envelope obtainable.
|
|
10
|
-
#
|
|
11
|
-
# Pick `get` for materialization paths (build, projection, schema tooling).
|
|
12
|
-
# Pick `get_or_refresh` for interactive `textus get` and equivalent.
|
|
13
|
-
def initialize(ctx)
|
|
14
|
-
@ctx = ctx
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
def get
|
|
18
|
-
Application::Reads::Get.new(ctx: @ctx)
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
def get_or_refresh # rubocop:disable Naming/AccessorMethodName
|
|
22
|
-
Application::Reads::GetOrRefresh.new(
|
|
23
|
-
ctx: @ctx,
|
|
24
|
-
get: get,
|
|
25
|
-
orchestrator: orchestrator,
|
|
26
|
-
)
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
def freshness = Application::Reads::Freshness.new(ctx: @ctx)
|
|
30
|
-
def audit = Application::Reads::Audit.new(ctx: @ctx)
|
|
31
|
-
def blame = Application::Reads::Blame.new(ctx: @ctx)
|
|
32
|
-
def policy_explain = Application::Reads::PolicyExplain.new(ctx: @ctx)
|
|
33
|
-
def list = Application::Reads::List.new(ctx: @ctx)
|
|
34
|
-
def where = Application::Reads::Where.new(ctx: @ctx)
|
|
35
|
-
def uid = Application::Reads::Uid.new(ctx: @ctx)
|
|
36
|
-
def schema_envelope = Application::Reads::SchemaEnvelope.new(ctx: @ctx)
|
|
37
|
-
def deps = Application::Reads::Deps.new(ctx: @ctx)
|
|
38
|
-
def rdeps = Application::Reads::Rdeps.new(ctx: @ctx)
|
|
39
|
-
def published = Application::Reads::Published.new(ctx: @ctx)
|
|
40
|
-
def stale = Application::Reads::Stale.new(ctx: @ctx)
|
|
41
|
-
def validate_all = Application::Reads::ValidateAll.new(ctx: @ctx)
|
|
42
|
-
|
|
43
|
-
private
|
|
44
|
-
|
|
45
|
-
def orchestrator
|
|
46
|
-
Application::Refresh::Orchestrator.new(
|
|
47
|
-
worker: Application::Refresh::Worker.new(ctx: @ctx, bus: @ctx.store.bus),
|
|
48
|
-
bus: @ctx.store.bus,
|
|
49
|
-
store_root: @ctx.store.root,
|
|
50
|
-
store: @ctx.store,
|
|
51
|
-
role: @ctx.role,
|
|
52
|
-
)
|
|
53
|
-
end
|
|
54
|
-
end
|
|
55
|
-
end
|
|
56
|
-
end
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
class Operations
|
|
3
|
-
class Refresh
|
|
4
|
-
def initialize(ctx)
|
|
5
|
-
@ctx = ctx
|
|
6
|
-
end
|
|
7
|
-
|
|
8
|
-
def worker
|
|
9
|
-
Application::Refresh::Worker.new(ctx: @ctx, bus: @ctx.store.bus)
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
def orchestrator
|
|
13
|
-
Application::Refresh::Orchestrator.new(
|
|
14
|
-
worker: worker,
|
|
15
|
-
bus: @ctx.store.bus,
|
|
16
|
-
store_root: @ctx.store.root,
|
|
17
|
-
store: @ctx.store,
|
|
18
|
-
role: @ctx.role,
|
|
19
|
-
)
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
def all
|
|
23
|
-
Application::Refresh::All.new(ctx: @ctx, bus: @ctx.store.bus)
|
|
24
|
-
end
|
|
25
|
-
end
|
|
26
|
-
end
|
|
27
|
-
end
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
class Operations
|
|
3
|
-
class Writes
|
|
4
|
-
def initialize(ctx)
|
|
5
|
-
@ctx = ctx
|
|
6
|
-
end
|
|
7
|
-
|
|
8
|
-
def put = Application::Writes::Put.new(ctx: @ctx, bus: bus)
|
|
9
|
-
def delete = Application::Writes::Delete.new(ctx: @ctx, bus: bus)
|
|
10
|
-
def mv = Application::Writes::Mv.new(ctx: @ctx, bus: bus)
|
|
11
|
-
def accept = Application::Writes::Accept.new(ctx: @ctx, bus: bus)
|
|
12
|
-
def build = Application::Writes::Build.new(ctx: @ctx, bus: bus)
|
|
13
|
-
def publish = Application::Writes::Publish.new(ctx: @ctx, bus: bus)
|
|
14
|
-
def reject = Application::Writes::Reject.new(ctx: @ctx, bus: bus)
|
|
15
|
-
|
|
16
|
-
private
|
|
17
|
-
|
|
18
|
-
def bus = @ctx.store.bus
|
|
19
|
-
end
|
|
20
|
-
end
|
|
21
|
-
end
|
data/lib/textus/store/reader.rb
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
class Store
|
|
3
|
-
class Reader
|
|
4
|
-
def initialize(store)
|
|
5
|
-
@store = store
|
|
6
|
-
@manifest = store.manifest
|
|
7
|
-
end
|
|
8
|
-
|
|
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)
|
|
17
|
-
mentry, path, = @manifest.resolve(key)
|
|
18
|
-
return nil unless File.exist?(path)
|
|
19
|
-
|
|
20
|
-
raw = File.binread(path)
|
|
21
|
-
parsed = Entry.for_format(mentry.format).parse(raw, path: path)
|
|
22
|
-
Envelope.build(
|
|
23
|
-
key: key, mentry: mentry, path: path,
|
|
24
|
-
meta: parsed["_meta"], body: parsed["body"],
|
|
25
|
-
etag: Etag.for_bytes(raw), content: parsed["content"]
|
|
26
|
-
)
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
def list(prefix: nil, zone: nil)
|
|
30
|
-
rows = @manifest.enumerate(prefix: prefix)
|
|
31
|
-
rows = rows.select { |r| r[:manifest_entry].zone == zone } if zone
|
|
32
|
-
rows.map { |row| { "key" => row[:key], "zone" => row[:manifest_entry].zone, "path" => row[:path] } }
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
def where(key)
|
|
36
|
-
mentry, path, = @manifest.resolve(key)
|
|
37
|
-
{ "protocol" => PROTOCOL, "key" => key, "zone" => mentry.zone, "owner" => mentry.owner, "path" => path }
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
def schema_envelope(key)
|
|
41
|
-
mentry, = @manifest.resolve(key)
|
|
42
|
-
schema = @store.schema_for(mentry.schema)
|
|
43
|
-
{ "protocol" => PROTOCOL, "key" => key, "schema_ref" => mentry.schema, "schema" => schema&.to_h }
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
# Returns the Textus UID for a key (or nil if the entry has none yet).
|
|
47
|
-
# Raises UnknownKey if the key doesn't resolve to a real file.
|
|
48
|
-
def uid(key)
|
|
49
|
-
get(key).uid
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
def deps(key) = Dependencies.deps_of(@manifest, key)
|
|
53
|
-
def rdeps(key) = Dependencies.rdeps_of(@manifest, key)
|
|
54
|
-
def published = Dependencies.published_of(@manifest)
|
|
55
|
-
|
|
56
|
-
def stale(prefix: nil, zone: nil)
|
|
57
|
-
Staleness.new(manifest: @manifest).call(prefix: prefix, zone: zone)
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
def validate_all
|
|
61
|
-
Validator.new(
|
|
62
|
-
reader: self, manifest: @manifest,
|
|
63
|
-
audit_log: @store.audit_log,
|
|
64
|
-
schema_for: ->(name) { @store.schema_for(name) }
|
|
65
|
-
).call
|
|
66
|
-
end
|
|
67
|
-
end
|
|
68
|
-
end
|
|
69
|
-
end
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
class Store
|
|
3
|
-
class Validator
|
|
4
|
-
def initialize(reader:, manifest:, audit_log:, schema_for:)
|
|
5
|
-
@reader = reader
|
|
6
|
-
@manifest = manifest
|
|
7
|
-
@audit_log = audit_log
|
|
8
|
-
@schema_for = schema_for
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
def call
|
|
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)
|
|
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
|
-
|
|
28
|
-
begin
|
|
29
|
-
validate_schema!(schema, env, mentry.format)
|
|
30
|
-
rescue Textus::Error => e
|
|
31
|
-
violations << { "key" => key, "code" => e.code, "message" => e.message }
|
|
32
|
-
end
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def check_role_authority_violations(violations)
|
|
37
|
-
@manifest.enumerate.each do |row|
|
|
38
|
-
mentry = row[:manifest_entry]
|
|
39
|
-
next unless mentry.schema
|
|
40
|
-
|
|
41
|
-
schema = @schema_for.call(mentry.schema)
|
|
42
|
-
next unless schema
|
|
43
|
-
|
|
44
|
-
env = begin
|
|
45
|
-
@reader.get(row[:key])
|
|
46
|
-
rescue StandardError
|
|
47
|
-
next
|
|
48
|
-
end
|
|
49
|
-
append_authority_violations(violations, row[:key], env, schema)
|
|
50
|
-
end
|
|
51
|
-
end
|
|
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)
|
|
79
|
-
end
|
|
80
|
-
end
|
|
81
|
-
end
|
|
82
|
-
end
|
data/lib/textus/store/writer.rb
DELETED
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
require "fileutils"
|
|
2
|
-
|
|
3
|
-
module Textus
|
|
4
|
-
class Store
|
|
5
|
-
class Writer
|
|
6
|
-
Payload = Data.define(:meta, :body, :content)
|
|
7
|
-
|
|
8
|
-
def initialize(store)
|
|
9
|
-
@store = store
|
|
10
|
-
@manifest = store.manifest
|
|
11
|
-
@reader = store.reader
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
# Pure I/O: validate, serialize, etag-check, write to disk, audit. No
|
|
15
|
-
# permission check and no event firing — those are handled by the caller
|
|
16
|
-
# (Application::Writes::Put).
|
|
17
|
-
def write_envelope_to_disk(key, mentry:, payload:, ctx:, if_etag: nil)
|
|
18
|
-
_, path, = @manifest.resolve(key)
|
|
19
|
-
|
|
20
|
-
meta = payload.meta || {}
|
|
21
|
-
strategy = Entry.for_format(mentry.format)
|
|
22
|
-
|
|
23
|
-
existing_uid = existing_uid_for(mentry, path)
|
|
24
|
-
meta, content = ensure_uid(mentry.format, meta, payload.content, existing_uid)
|
|
25
|
-
|
|
26
|
-
bytes, eff_meta, eff_body, eff_content = serialize_for_put(
|
|
27
|
-
mentry: mentry, path: path, strategy: strategy,
|
|
28
|
-
meta: meta, body: payload.body, content: content
|
|
29
|
-
)
|
|
30
|
-
|
|
31
|
-
enforce_name_match!(path, eff_meta, mentry.format)
|
|
32
|
-
|
|
33
|
-
schema = @store.schema_for(mentry.schema)
|
|
34
|
-
if schema
|
|
35
|
-
Entry.for_format(mentry.format).validate_against(
|
|
36
|
-
schema,
|
|
37
|
-
{ "_meta" => eff_meta, "content" => eff_content },
|
|
38
|
-
)
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
etag_before = File.exist?(path) ? Etag.for_file(path) : nil
|
|
42
|
-
raise EtagMismatch.new(key, if_etag, etag_before) if if_etag && (etag_before != if_etag)
|
|
43
|
-
|
|
44
|
-
FileUtils.mkdir_p(File.dirname(path))
|
|
45
|
-
File.binwrite(path, bytes)
|
|
46
|
-
etag_after = Etag.for_bytes(bytes)
|
|
47
|
-
@store.audit_log.append(
|
|
48
|
-
role: ctx.role, verb: "put", key: key,
|
|
49
|
-
etag_before: etag_before, etag_after: etag_after,
|
|
50
|
-
extras: ctx.correlation_id ? { "correlation_id" => ctx.correlation_id } : nil
|
|
51
|
-
)
|
|
52
|
-
Envelope.build(
|
|
53
|
-
key: key, mentry: mentry, path: path,
|
|
54
|
-
meta: eff_meta, body: eff_body, etag: etag_after, content: eff_content
|
|
55
|
-
)
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
def existing_uid_for(mentry, path)
|
|
59
|
-
return nil unless File.exist?(path)
|
|
60
|
-
|
|
61
|
-
raw = File.binread(path)
|
|
62
|
-
parsed = Entry.for_format(mentry.format).parse(raw, path: path)
|
|
63
|
-
Envelope.extract_uid(parsed["_meta"])
|
|
64
|
-
rescue StandardError
|
|
65
|
-
nil
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
def ensure_uid(format, meta, content, existing_uid)
|
|
69
|
-
Textus::Entry.for_format(format).inject_uid(meta, content, existing_uid)
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
def enforce_name_match!(path, meta, format)
|
|
73
|
-
Textus::Entry.for_format(format).enforce_name_match!(path, meta)
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
def serialize_for_put(mentry:, path:, strategy:, meta:, body:, content:)
|
|
77
|
-
_ = strategy
|
|
78
|
-
Textus::Entry.for_format(mentry.format).serialize_for_put(
|
|
79
|
-
meta: meta, body: body, content: content, path: path,
|
|
80
|
-
)
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
# Pure I/O: resolve path, validate etag, delete from disk, audit. No
|
|
84
|
-
# permission check and no event firing — those are handled by the caller
|
|
85
|
-
# (Application::Writes::Delete).
|
|
86
|
-
def delete_envelope_from_disk(key, ctx:, if_etag: nil)
|
|
87
|
-
_, path, = @manifest.resolve(key)
|
|
88
|
-
raise UnknownKey.new(key, suggestions: @manifest.suggestions_for(key)) unless File.exist?(path)
|
|
89
|
-
|
|
90
|
-
etag_before = Etag.for_file(path)
|
|
91
|
-
raise EtagMismatch.new(key, if_etag, etag_before) if if_etag && if_etag != etag_before
|
|
92
|
-
|
|
93
|
-
File.delete(path)
|
|
94
|
-
@store.audit_log.append(
|
|
95
|
-
role: ctx.role, verb: "delete", key: key,
|
|
96
|
-
etag_before: etag_before, etag_after: nil,
|
|
97
|
-
extras: ctx.correlation_id ? { "correlation_id" => ctx.correlation_id } : nil
|
|
98
|
-
)
|
|
99
|
-
end
|
|
100
|
-
end
|
|
101
|
-
end
|
|
102
|
-
end
|