textus 0.55.1 → 0.56.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 +175 -2768
- data/README.md +17 -18
- data/lib/textus/artifact.rb +8 -0
- data/lib/textus/errors.rb +2 -39
- data/lib/textus/human.rb +7 -0
- data/lib/textus/infra/adapters/concurrency_adapter.rb +15 -0
- data/lib/textus/infra/clock.rb +7 -0
- data/lib/textus/infra/database.rb +127 -0
- data/lib/textus/infra/file_system.rb +54 -0
- data/lib/textus/infra/locks.rb +50 -0
- data/lib/textus/infra/port/clock.rb +18 -0
- data/lib/textus/infra/port/publisher.rb +71 -0
- data/lib/textus/infra/port/sentinel.rb +25 -0
- data/lib/textus/infra/port/sentinel_store.rb +93 -0
- data/lib/textus/infra/store/audit_log.rb +42 -0
- data/lib/textus/infra/store/base.rb +25 -0
- data/lib/textus/infra/store/cursor_store.rb +27 -0
- data/lib/textus/infra/store/entry_index.rb +31 -0
- data/lib/textus/infra/store/envelope/meta.rb +63 -0
- data/lib/textus/infra/store/file_store.rb +23 -0
- data/lib/textus/infra/store/index/builder.rb +28 -0
- data/lib/textus/infra/store/index/lookup.rb +23 -0
- data/lib/textus/infra/store/link_store.rb +43 -0
- data/lib/textus/infra/store/workflow_queue.rb +54 -0
- data/lib/textus/infra/store.rb +29 -0
- data/lib/textus/ingest.rb +8 -0
- data/lib/textus/lanes/artifact/cache.rb +35 -0
- data/lib/textus/lanes/artifact/handlers.rb +29 -0
- data/lib/textus/lanes/artifact/lifecycle.rb +23 -0
- data/lib/textus/lanes/artifact/verbs.rb +18 -0
- data/lib/textus/lanes/ingest/dedup.rb +17 -0
- data/lib/textus/lanes/ingest/entry_types/asset.rb +45 -0
- data/lib/textus/lanes/ingest/entry_types/link.rb +16 -0
- data/lib/textus/lanes/ingest/entry_types/text.rb +17 -0
- data/lib/textus/lanes/ingest/entry_types.rb +25 -0
- data/lib/textus/lanes/ingest/handlers.rb +54 -0
- data/lib/textus/lanes/ingest/index_rebuilder.rb +13 -0
- data/lib/textus/lanes/ingest/key_builder.rb +33 -0
- data/lib/textus/lanes/ingest/resolver.rb +21 -0
- data/lib/textus/lanes/knowledge/freeform.rb +15 -0
- data/lib/textus/lanes/knowledge/handlers.rb +53 -0
- data/lib/textus/lanes/knowledge/runner.rb +45 -0
- data/lib/textus/lanes/knowledge/verbs.rb +16 -0
- data/lib/textus/lanes/scratchpad/handlers.rb +164 -0
- data/lib/textus/lanes/scratchpad/verbs.rb +73 -0
- data/lib/textus/{gate → protocol}/binder.rb +2 -10
- data/lib/textus/protocol/boot/catalog.rb +34 -0
- data/lib/textus/protocol/boot/context.rb +124 -0
- data/lib/textus/protocol/boot/protocol.rb +33 -0
- data/lib/textus/protocol/boot.rb +42 -0
- data/lib/textus/protocol/builder/domain.rb +17 -0
- data/lib/textus/protocol/builder/infrastructure.rb +17 -0
- data/lib/textus/protocol/builder/runtime.rb +46 -0
- data/lib/textus/protocol/builder.rb +11 -0
- data/lib/textus/protocol/container.rb +22 -0
- data/lib/textus/protocol/diff.rb +170 -0
- data/lib/textus/protocol/entry_constraint.rb +54 -0
- data/lib/textus/protocol/envelope.rb +70 -0
- data/lib/textus/protocol/format/base.rb +80 -0
- data/lib/textus/protocol/format/bash.rb +10 -0
- data/lib/textus/protocol/format/javascript.rb +10 -0
- data/lib/textus/protocol/format/json.rb +82 -0
- data/lib/textus/protocol/format/markdown.rb +53 -0
- data/lib/textus/protocol/format/python.rb +10 -0
- data/lib/textus/protocol/format/ruby.rb +10 -0
- data/lib/textus/protocol/format/script.rb +55 -0
- data/lib/textus/protocol/format/text.rb +48 -0
- data/lib/textus/protocol/format/yaml.rb +105 -0
- data/lib/textus/protocol/format.rb +73 -0
- data/lib/textus/protocol/freshness/ttl_evaluator.rb +61 -0
- data/lib/textus/protocol/gate.rb +61 -0
- data/lib/textus/protocol/handlers/command_context.rb +19 -0
- data/lib/textus/protocol/handlers/concern.rb +15 -0
- data/lib/textus/protocol/handlers/ops/audit_filter.rb +37 -0
- data/lib/textus/protocol/handlers/ops/bulk_planner.rb +38 -0
- data/lib/textus/protocol/handlers/ops/data_mv.rb +49 -0
- data/lib/textus/protocol/handlers/ops/git_blame.rb +65 -0
- data/lib/textus/protocol/handlers/ops/pulse_coordinator.rb +37 -0
- data/lib/textus/protocol/handlers/ops/source_expander.rb +41 -0
- data/lib/textus/protocol/handlers/query_context.rb +41 -0
- data/lib/textus/protocol/handlers/read.rb +67 -0
- data/lib/textus/protocol/handlers/system.rb +93 -0
- data/lib/textus/protocol/handlers/write.rb +57 -0
- data/lib/textus/protocol/init.rb +143 -0
- data/lib/textus/protocol/key/grammar.rb +35 -0
- data/lib/textus/protocol/key/matching.rb +23 -0
- data/lib/textus/protocol/key/path.rb +23 -0
- data/lib/textus/protocol/key.rb +25 -0
- data/lib/textus/protocol/lane.rb +12 -0
- data/lib/textus/{store/geometry.rb → protocol/layout.rb} +32 -22
- data/lib/textus/protocol/links/resolver.rb +33 -0
- data/lib/textus/protocol/links/uri_rewriter.rb +30 -0
- data/lib/textus/protocol/links.rb +10 -0
- data/lib/textus/protocol/manifest/data.rb +90 -0
- data/lib/textus/protocol/manifest/entry/base.rb +95 -0
- data/lib/textus/protocol/manifest/entry/leaf.rb +19 -0
- data/lib/textus/protocol/manifest/entry/nested.rb +19 -0
- data/lib/textus/protocol/manifest/entry/parser.rb +71 -0
- data/lib/textus/protocol/manifest/entry/publish.rb +209 -0
- data/lib/textus/protocol/manifest/entry.rb +10 -0
- data/lib/textus/protocol/manifest/policy/publish_target.rb +36 -0
- data/lib/textus/protocol/manifest/policy.rb +69 -0
- data/lib/textus/protocol/manifest/resolver.rb +156 -0
- data/lib/textus/protocol/manifest/schema/contract.rb +53 -0
- data/lib/textus/protocol/manifest/schema/validator.rb +52 -0
- data/lib/textus/protocol/manifest/schema.rb +40 -0
- data/lib/textus/protocol/manifest.rb +62 -0
- data/lib/textus/protocol/produce/render/context.rb +43 -0
- data/lib/textus/protocol/produce/render.rb +44 -0
- data/lib/textus/protocol/schema/registry.rb +44 -0
- data/lib/textus/protocol/schema/tools.rb +104 -0
- data/lib/textus/protocol/schema.rb +117 -0
- data/lib/textus/protocol/session/bridge.rb +11 -0
- data/lib/textus/protocol/session/context.rb +5 -0
- data/lib/textus/protocol/session.rb +87 -0
- data/lib/textus/protocol/store_engine/delete.rb +37 -0
- data/lib/textus/protocol/store_engine/move.rb +109 -0
- data/lib/textus/protocol/store_engine/pipeline.rb +61 -0
- data/lib/textus/protocol/store_engine/put.rb +104 -0
- data/lib/textus/protocol/store_engine.rb +86 -0
- data/lib/textus/protocol/v4.rb +25 -0
- data/lib/textus/protocol/verb_registry/arg_spec.rb +19 -0
- data/lib/textus/protocol/verb_registry/core_verbs.rb +164 -0
- data/lib/textus/protocol/verb_registry/verb_spec.rb +42 -0
- data/lib/textus/protocol/verb_registry.rb +42 -0
- data/lib/textus/surface/cli/group/{rule.rb → web.rb} +2 -2
- data/lib/textus/surface/cli/runner.rb +25 -25
- data/lib/textus/surface/cli/sources.rb +1 -3
- data/lib/textus/surface/cli/verb/get.rb +3 -6
- data/lib/textus/surface/cli/verb/init.rb +1 -1
- data/lib/textus/surface/cli/verb/put.rb +8 -6
- data/lib/textus/surface/cli/verb/web_serve.rb +22 -0
- data/lib/textus/surface/cli.rb +25 -6
- data/lib/textus/surface/dispatch.rb +17 -0
- data/lib/textus/surface/mcp/adapters/mcp_adapter.rb +28 -0
- data/lib/textus/surface/mcp/catalog.rb +26 -62
- data/lib/textus/surface/mcp/cursor_manager.rb +22 -0
- data/lib/textus/surface/mcp/drift_annotation.rb +19 -0
- data/lib/textus/surface/mcp/errors.rb +0 -10
- data/lib/textus/surface/mcp/server.rb +46 -46
- data/lib/textus/surface/watcher.rb +18 -12
- data/lib/textus/surface/web.rb +50 -0
- data/lib/textus/value/call.rb +1 -1
- data/lib/textus/{core → value}/duration.rb +1 -4
- data/lib/textus/value/etag.rb +7 -7
- data/lib/textus/value/payload.rb +7 -0
- data/lib/textus/version.rb +1 -1
- data/lib/textus/workflow/consumer.rb +39 -0
- data/lib/textus/workflow/dsl/lifecycle.rb +32 -0
- data/lib/textus/workflow/dsl/pattern_matcher.rb +20 -0
- data/lib/textus/workflow/dsl/step_builder.rb +56 -0
- data/lib/textus/workflow/dsl.rb +111 -21
- data/lib/textus/workflow/event_emitter.rb +21 -0
- data/lib/textus/workflow/publisher.rb +59 -0
- data/lib/textus/workflow/registry.rb +31 -8
- data/lib/textus/workflow/runner.rb +25 -61
- data/lib/textus/workflow/scheduler.rb +45 -0
- data/lib/textus/workflow/step_executor.rb +78 -0
- data/lib/textus/workflow/step_helpers.rb +102 -0
- data/lib/textus/workflow.rb +2 -5
- data/lib/textus.rb +16 -80
- metadata +167 -183
- data/SPEC.md +0 -908
- data/docs/architecture/README.md +0 -273
- data/docs/reference/conventions.md +0 -138
- data/lib/textus/action/accept.rb +0 -46
- data/lib/textus/action/audit.rb +0 -94
- data/lib/textus/action/base.rb +0 -42
- data/lib/textus/action/blame.rb +0 -79
- data/lib/textus/action/boot.rb +0 -15
- data/lib/textus/action/data_mv.rb +0 -58
- data/lib/textus/action/deps.rb +0 -19
- data/lib/textus/action/doctor.rb +0 -17
- data/lib/textus/action/drain.rb +0 -31
- data/lib/textus/action/enqueue.rb +0 -37
- data/lib/textus/action/get.rb +0 -34
- data/lib/textus/action/ingest.rb +0 -199
- data/lib/textus/action/jobs.rb +0 -27
- data/lib/textus/action/key_delete.rb +0 -26
- data/lib/textus/action/key_delete_prefix.rb +0 -35
- data/lib/textus/action/key_mv.rb +0 -122
- data/lib/textus/action/key_mv_prefix.rb +0 -48
- data/lib/textus/action/list.rb +0 -28
- data/lib/textus/action/propose.rb +0 -42
- data/lib/textus/action/published.rb +0 -22
- data/lib/textus/action/pulse.rb +0 -49
- data/lib/textus/action/put.rb +0 -38
- data/lib/textus/action/rdeps.rb +0 -24
- data/lib/textus/action/reject.rb +0 -28
- data/lib/textus/action/rule_explain.rb +0 -81
- data/lib/textus/action/rule_lint.rb +0 -62
- data/lib/textus/action/rule_list.rb +0 -38
- data/lib/textus/action/schema_envelope.rb +0 -22
- data/lib/textus/action/uid.rb +0 -19
- data/lib/textus/action/where.rb +0 -21
- data/lib/textus/boot.rb +0 -202
- data/lib/textus/contract/arg.rb +0 -10
- data/lib/textus/contract/dsl.rb +0 -88
- data/lib/textus/contract/spec.rb +0 -25
- data/lib/textus/contract.rb +0 -12
- data/lib/textus/core/freshness/evaluator.rb +0 -150
- data/lib/textus/core/freshness/verdict.rb +0 -34
- data/lib/textus/core/freshness.rb +0 -11
- data/lib/textus/core/retention/sweep.rb +0 -57
- data/lib/textus/core/retention.rb +0 -11
- data/lib/textus/core/sentinel.rb +0 -30
- data/lib/textus/doctor/check/audit_log.rb +0 -34
- data/lib/textus/doctor/check/generator_drift.rb +0 -29
- data/lib/textus/doctor/check/illegal_keys.rb +0 -60
- data/lib/textus/doctor/check/manifest_files.rb +0 -25
- data/lib/textus/doctor/check/notebook_sources.rb +0 -50
- data/lib/textus/doctor/check/orphaned_publish_targets.rb +0 -35
- data/lib/textus/doctor/check/proposal_targets.rb +0 -45
- data/lib/textus/doctor/check/protocol_version.rb +0 -47
- data/lib/textus/doctor/check/publish_tree_index_overlap.rb +0 -48
- data/lib/textus/doctor/check/raw_asset_paths.rb +0 -50
- data/lib/textus/doctor/check/rule_ambiguity.rb +0 -49
- data/lib/textus/doctor/check/schema_parse_error.rb +0 -28
- data/lib/textus/doctor/check/schema_violations.rb +0 -28
- data/lib/textus/doctor/check/schemas.rb +0 -26
- data/lib/textus/doctor/check/sentinels.rb +0 -60
- data/lib/textus/doctor/check/stale_reviewed_stamp.rb +0 -54
- data/lib/textus/doctor/check/templates.rb +0 -29
- data/lib/textus/doctor/check/unowned_schema_fields.rb +0 -40
- data/lib/textus/doctor/check.rb +0 -41
- data/lib/textus/doctor/validator.rb +0 -93
- data/lib/textus/doctor.rb +0 -54
- data/lib/textus/format/base.rb +0 -49
- data/lib/textus/format/json.rb +0 -102
- data/lib/textus/format/markdown.rb +0 -80
- data/lib/textus/format/shared.rb +0 -17
- data/lib/textus/format/text.rb +0 -55
- data/lib/textus/format/yaml.rb +0 -125
- data/lib/textus/format.rb +0 -46
- data/lib/textus/gate/auth.rb +0 -212
- data/lib/textus/gate.rb +0 -92
- data/lib/textus/init.rb +0 -157
- data/lib/textus/jobs.rb +0 -9
- data/lib/textus/key/distance.rb +0 -55
- data/lib/textus/key/grammar.rb +0 -33
- data/lib/textus/key/matching.rb +0 -24
- data/lib/textus/key/path.rb +0 -28
- data/lib/textus/manifest/capabilities.rb +0 -29
- data/lib/textus/manifest/data.rb +0 -102
- data/lib/textus/manifest/entry/base.rb +0 -105
- data/lib/textus/manifest/entry/ignore_matcher.rb +0 -46
- data/lib/textus/manifest/entry/leaf.rb +0 -17
- data/lib/textus/manifest/entry/nested.rb +0 -37
- data/lib/textus/manifest/entry/parser.rb +0 -99
- data/lib/textus/manifest/entry/produced.rb +0 -36
- data/lib/textus/manifest/entry/publish/mode.rb +0 -45
- data/lib/textus/manifest/entry/publish/none.rb +0 -14
- data/lib/textus/manifest/entry/publish/subtree_mirror.rb +0 -73
- data/lib/textus/manifest/entry/publish/template.rb +0 -16
- data/lib/textus/manifest/entry/publish/to_paths.rb +0 -76
- data/lib/textus/manifest/entry/publish/tree.rb +0 -50
- data/lib/textus/manifest/entry/publish.rb +0 -45
- data/lib/textus/manifest/entry/validators/format_matrix.rb +0 -21
- data/lib/textus/manifest/entry/validators/ignore.rb +0 -28
- data/lib/textus/manifest/entry/validators/publish.rb +0 -30
- data/lib/textus/manifest/entry/validators.rb +0 -18
- data/lib/textus/manifest/entry.rb +0 -8
- data/lib/textus/manifest/policy/matcher.rb +0 -51
- data/lib/textus/manifest/policy/publish_target.rb +0 -34
- data/lib/textus/manifest/policy/react.rb +0 -30
- data/lib/textus/manifest/policy/retention.rb +0 -29
- data/lib/textus/manifest/policy/source.rb +0 -30
- data/lib/textus/manifest/policy.rb +0 -95
- data/lib/textus/manifest/resolver.rb +0 -118
- data/lib/textus/manifest/rules.rb +0 -86
- data/lib/textus/manifest/schema/contract.rb +0 -61
- data/lib/textus/manifest/schema/keys.rb +0 -90
- data/lib/textus/manifest/schema/semantics/cross_field.rb +0 -53
- data/lib/textus/manifest/schema/semantics/invariants.rb +0 -125
- data/lib/textus/manifest/schema/semantics/migration.rb +0 -83
- data/lib/textus/manifest/schema/semantics.rb +0 -27
- data/lib/textus/manifest/schema/validator.rb +0 -48
- data/lib/textus/manifest/schema/vocabulary.rb +0 -25
- data/lib/textus/manifest/schema.rb +0 -30
- data/lib/textus/manifest.rb +0 -65
- data/lib/textus/meta.rb +0 -54
- data/lib/textus/port/audit_log.rb +0 -268
- data/lib/textus/port/build_lock.rb +0 -64
- data/lib/textus/port/clock.rb +0 -10
- data/lib/textus/port/publisher.rb +0 -60
- data/lib/textus/port/sentinel_store.rb +0 -83
- data/lib/textus/port/storage/file_stat.rb +0 -19
- data/lib/textus/port/storage/file_store.rb +0 -26
- data/lib/textus/port/store.rb +0 -93
- data/lib/textus/port/watcher_lock.rb +0 -48
- data/lib/textus/produce/engine.rb +0 -50
- data/lib/textus/produce/render.rb +0 -20
- data/lib/textus/schema/tools.rb +0 -115
- data/lib/textus/schema.rb +0 -100
- data/lib/textus/schemas.rb +0 -54
- data/lib/textus/store/compositor.rb +0 -34
- data/lib/textus/store/container.rb +0 -43
- data/lib/textus/store/cursor.rb +0 -26
- data/lib/textus/store/envelope/reader.rb +0 -43
- data/lib/textus/store/envelope/writer.rb +0 -195
- data/lib/textus/store/index/builder.rb +0 -74
- data/lib/textus/store/index/lookup.rb +0 -60
- data/lib/textus/store/jobs/base.rb +0 -13
- data/lib/textus/store/jobs/index.rb +0 -15
- data/lib/textus/store/jobs/materialize.rb +0 -15
- data/lib/textus/store/jobs/plan.rb +0 -11
- data/lib/textus/store/jobs/planner.rb +0 -104
- data/lib/textus/store/jobs/queue.rb +0 -154
- data/lib/textus/store/jobs/registry.rb +0 -19
- data/lib/textus/store/jobs/retention.rb +0 -50
- data/lib/textus/store/jobs/sweep.rb +0 -21
- data/lib/textus/store/jobs/worker.rb +0 -64
- data/lib/textus/store/session.rb +0 -37
- data/lib/textus/store.rb +0 -99
- data/lib/textus/surface/cli/verb/doctor.rb +0 -19
- data/lib/textus/surface/cli/verb/schema_diff.rb +0 -17
- data/lib/textus/surface/cli/verb/schema_init.rb +0 -21
- data/lib/textus/surface/cli/verb/schema_migrate.rb +0 -21
- data/lib/textus/surface/mcp.rb +0 -8
- data/lib/textus/surface/projector.rb +0 -27
- data/lib/textus/surface/role_scope.rb +0 -34
- data/lib/textus/value/command.rb +0 -16
- data/lib/textus/value/envelope.rb +0 -89
- data/lib/textus/value/result.rb +0 -26
- data/lib/textus/workflow/collector.rb +0 -27
- data/lib/textus/workflow/context.rb +0 -5
- data/lib/textus/workflow/loader.rb +0 -17
- data/lib/textus/workflow/pattern.rb +0 -18
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
module Textus
|
|
2
|
+
module Protocol
|
|
3
|
+
module Produce
|
|
4
|
+
class Render
|
|
5
|
+
class Context
|
|
6
|
+
def self.for(locals:, resolver: nil, from_path: nil, from_key: nil, edge_store: nil)
|
|
7
|
+
new(locals, resolver, from_path, from_key, edge_store)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def initialize(locals, resolver, from_path, from_key, edge_store)
|
|
11
|
+
@locals = locals
|
|
12
|
+
@resolver = resolver
|
|
13
|
+
@from_path = from_path
|
|
14
|
+
@from_key = from_key
|
|
15
|
+
@edge_store = edge_store
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def to_erb_binding
|
|
19
|
+
mod = Module.new
|
|
20
|
+
@locals.each { |k, v| mod.define_method(k.to_sym) { v } }
|
|
21
|
+
obj = Object.new.extend(mod)
|
|
22
|
+
|
|
23
|
+
if @resolver && @from_path
|
|
24
|
+
resolver = @resolver
|
|
25
|
+
from_path = @from_path
|
|
26
|
+
from_key = @from_key
|
|
27
|
+
edge_store = @edge_store
|
|
28
|
+
obj.define_singleton_method(:textus_link) do |key|
|
|
29
|
+
resolved = resolver.resolve(key: key, from_path: from_path)
|
|
30
|
+
edge_store&.record(from_key: from_key, to_key: key)
|
|
31
|
+
resolved
|
|
32
|
+
rescue Textus::Protocol::Links::Resolver::UnknownKeyError
|
|
33
|
+
"`textus get #{key}`"
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
obj.instance_eval { binding }
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
module Textus
|
|
2
|
+
module Protocol
|
|
3
|
+
module Produce
|
|
4
|
+
class Render
|
|
5
|
+
def initialize(template_loader:, manifest: nil, source_publish_path: nil, entry_key: nil, edge_store: nil)
|
|
6
|
+
@template_loader = template_loader
|
|
7
|
+
@manifest = manifest
|
|
8
|
+
@source_publish_path = source_publish_path
|
|
9
|
+
@entry_key = entry_key
|
|
10
|
+
@edge_store = edge_store
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def bytes_for(target:, data:, boot:)
|
|
14
|
+
raise ArgumentError.new("Produce::Render called for a verbatim target #{target.to.inspect}") unless target.renders?
|
|
15
|
+
|
|
16
|
+
locals = target.inject_boot ? data.merge("boot" => boot) : data
|
|
17
|
+
resolver = @manifest ? Textus::Protocol::Links::Resolver.new(manifest: @manifest) : nil
|
|
18
|
+
ctx = Render::Context.for(
|
|
19
|
+
locals: locals,
|
|
20
|
+
resolver: resolver,
|
|
21
|
+
from_path: target.to,
|
|
22
|
+
from_key: @entry_key,
|
|
23
|
+
edge_store: @edge_store,
|
|
24
|
+
)
|
|
25
|
+
raw = ERB.new(@template_loader.call(target.template), trim_mode: "-").result(ctx.to_erb_binding)
|
|
26
|
+
rewrite(raw, target.to, resolver)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def rewrite(bytes, from_path, resolver)
|
|
32
|
+
return bytes unless resolver && bytes.include?("textus:")
|
|
33
|
+
|
|
34
|
+
Textus::Protocol::Links::UriRewriter.new(
|
|
35
|
+
resolver: resolver,
|
|
36
|
+
from_path: from_path,
|
|
37
|
+
from_key: @entry_key,
|
|
38
|
+
edge_store: @edge_store,
|
|
39
|
+
).rewrite(bytes)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
module Textus
|
|
2
|
+
module Protocol
|
|
3
|
+
class Schema
|
|
4
|
+
class Registry
|
|
5
|
+
def initialize(dir)
|
|
6
|
+
@dir = dir
|
|
7
|
+
@schemas = {}
|
|
8
|
+
load_all
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def fetch(name)
|
|
12
|
+
@schemas[name] || raise(Textus::IoError.new("schema not found: #{File.join(@dir, "#{name}.yaml")}"))
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def fetch_or_nil(name)
|
|
16
|
+
return nil if name.nil?
|
|
17
|
+
|
|
18
|
+
fetch(name)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def all
|
|
22
|
+
@schemas.values
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def by_name
|
|
26
|
+
@schemas.dup
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def load_all
|
|
32
|
+
return unless File.directory?(@dir)
|
|
33
|
+
|
|
34
|
+
Dir.glob(File.join(@dir, "**", "*.yaml")).each do |path|
|
|
35
|
+
name = path.delete_prefix("#{@dir}/").sub(/\.yaml\z/, "")
|
|
36
|
+
@schemas[name] = Textus::Protocol::Schema.load(path)
|
|
37
|
+
rescue StandardError => e
|
|
38
|
+
warn "textus: failed to load schema '#{name}' at #{path}: #{e.message}"
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
require "yaml"
|
|
2
|
+
require "fileutils"
|
|
3
|
+
|
|
4
|
+
module Textus
|
|
5
|
+
module Protocol
|
|
6
|
+
class Schema
|
|
7
|
+
module Tools
|
|
8
|
+
def self.init(store, name:, from:)
|
|
9
|
+
env = pure_get(store, Textus::Value::Role::DEFAULT, from)
|
|
10
|
+
meta = env["_meta"]
|
|
11
|
+
schema = {
|
|
12
|
+
"name" => name,
|
|
13
|
+
"required" => meta.keys,
|
|
14
|
+
"optional" => [],
|
|
15
|
+
"fields" => meta.each_with_object({}) { |(k, v), h| h[k] = { "type" => infer_type(v) } },
|
|
16
|
+
}
|
|
17
|
+
geom = Textus::Protocol::Layout.new(store.root)
|
|
18
|
+
FileUtils.mkdir_p(geom.schemas_dir)
|
|
19
|
+
target = geom.schema_path(name)
|
|
20
|
+
File.write(target, YAML.dump(schema))
|
|
21
|
+
{ "protocol" => PROTOCOL, "schema_name" => name, "path" => target }
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def self.diff(store, name:)
|
|
25
|
+
schema = load_schema(store, name)
|
|
26
|
+
drift = []
|
|
27
|
+
store.manifest.resolver.enumerate.each do |row|
|
|
28
|
+
env = pure_get(store, Textus::Value::Role::DEFAULT, row[:key])
|
|
29
|
+
begin
|
|
30
|
+
schema.validate!(env["_meta"])
|
|
31
|
+
rescue SchemaViolation => e
|
|
32
|
+
drift << { "key" => row[:key], "details" => e.details }
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
{ "protocol" => PROTOCOL, "schema_name" => name, "drift" => drift }
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def self.migrate(store, name:, rename: nil)
|
|
39
|
+
renames =
|
|
40
|
+
if rename
|
|
41
|
+
old_field, new_field = rename.split(":", 2)
|
|
42
|
+
raise UsageError.new("--rename=OLD:NEW") unless old_field && new_field && !new_field.empty?
|
|
43
|
+
|
|
44
|
+
{ old_field => new_field }
|
|
45
|
+
else
|
|
46
|
+
load_schema(store, name).evolution["migrate_from"] || {}
|
|
47
|
+
end
|
|
48
|
+
raise UsageError.new("schema migrate needs --rename=OLD:NEW or schema.evolution.migrate_from") if renames.empty?
|
|
49
|
+
|
|
50
|
+
authority = accept_role_for(store)
|
|
51
|
+
ops = store.with_role(authority)
|
|
52
|
+
touched = []
|
|
53
|
+
store.manifest.resolver.enumerate.each do |row|
|
|
54
|
+
env = pure_get(store, authority, row[:key])
|
|
55
|
+
meta = (env["_meta"] || {}).dup
|
|
56
|
+
changed = false
|
|
57
|
+
renames.each do |old, new|
|
|
58
|
+
if meta.key?(old)
|
|
59
|
+
meta[new] = meta.delete(old)
|
|
60
|
+
changed = true
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
next unless changed
|
|
64
|
+
|
|
65
|
+
ops.put(key: row[:key], meta: meta, body: env["body"])
|
|
66
|
+
touched << row[:key]
|
|
67
|
+
end
|
|
68
|
+
{ "protocol" => PROTOCOL, "migrated" => touched, "renames" => renames }
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def self.infer_type(value)
|
|
72
|
+
case value
|
|
73
|
+
when String then "string"
|
|
74
|
+
when Numeric then "number"
|
|
75
|
+
when true, false then "boolean"
|
|
76
|
+
when Array then "array"
|
|
77
|
+
when Hash then "object"
|
|
78
|
+
else "string"
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def self.pure_get(store, role, key)
|
|
83
|
+
store.with_role(role).get(key: key)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def self.load_schema(store, name)
|
|
87
|
+
store.schemas.fetch(name)
|
|
88
|
+
rescue IoError
|
|
89
|
+
raise UsageError.new("schema not found: #{name}")
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def self.accept_role_for(store)
|
|
93
|
+
authority = store.manifest.policy.writers_for("knowledge").first
|
|
94
|
+
return authority if authority
|
|
95
|
+
|
|
96
|
+
raise UsageError.new(
|
|
97
|
+
"schema migrate requires a role that can write knowledge; " \
|
|
98
|
+
"none found in the fixed lane table",
|
|
99
|
+
)
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
require "yaml"
|
|
2
|
+
|
|
3
|
+
module Textus
|
|
4
|
+
module Protocol
|
|
5
|
+
class Schema
|
|
6
|
+
attr_reader :name, :required, :optional, :fields, :raw
|
|
7
|
+
|
|
8
|
+
def self.load(path)
|
|
9
|
+
raw = YAML.safe_load_file(path, permitted_classes: [Symbol, Date, Time], aliases: false)
|
|
10
|
+
new(raw)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def initialize(raw)
|
|
14
|
+
@raw = raw || {}
|
|
15
|
+
@name = @raw["name"]
|
|
16
|
+
@required = Array(@raw["required"])
|
|
17
|
+
@optional = Array(@raw["optional"])
|
|
18
|
+
@fields = @raw["fields"] || {}
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def to_h
|
|
22
|
+
@raw
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def maintained_by(field)
|
|
26
|
+
meta = @fields[field] or return nil
|
|
27
|
+
meta["maintained_by"]
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def unowned_fields
|
|
31
|
+
@fields.each_with_object([]) do |(name, spec), acc|
|
|
32
|
+
next unless spec.is_a?(Hash)
|
|
33
|
+
|
|
34
|
+
acc << name if spec["maintained_by"].nil?
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def evolution
|
|
39
|
+
raw = @raw["evolution"] || {}
|
|
40
|
+
raw.each_with_object({}) do |(k, v), h|
|
|
41
|
+
h[k] = v.is_a?(Date) || v.is_a?(Time) ? v.to_s : v
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def validate!(frontmatter)
|
|
46
|
+
missing = @required - frontmatter.keys
|
|
47
|
+
raise Textus::SchemaViolation.new("missing" => missing) unless missing.empty?
|
|
48
|
+
|
|
49
|
+
known = (@required + @optional).uniq
|
|
50
|
+
frontmatter.each do |k, v|
|
|
51
|
+
next unless @fields.key?(k)
|
|
52
|
+
|
|
53
|
+
check_type!(k, v, @fields[k])
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
warnings = frontmatter.keys - known
|
|
57
|
+
warnings.map { |w| "unknown field: #{w}" }
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
TYPE_VALIDATORS = {
|
|
61
|
+
"string" => :validate_string,
|
|
62
|
+
"number" => :validate_number,
|
|
63
|
+
"boolean" => :validate_boolean,
|
|
64
|
+
"enum" => :validate_enum,
|
|
65
|
+
"array" => :validate_array,
|
|
66
|
+
"object" => :validate_object,
|
|
67
|
+
}.freeze
|
|
68
|
+
|
|
69
|
+
private
|
|
70
|
+
|
|
71
|
+
def check_type!(field, value, spec)
|
|
72
|
+
type = spec["type"]
|
|
73
|
+
return unless type
|
|
74
|
+
|
|
75
|
+
send(TYPE_VALIDATORS[type], field, value, spec)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def validate_string(field, value, spec)
|
|
79
|
+
bad!(field, "expected string") unless value.is_a?(String)
|
|
80
|
+
return unless (max = spec["max"]) && value.bytesize > max
|
|
81
|
+
|
|
82
|
+
bad!(field, "exceeds max #{max}")
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def validate_number(field, value, _spec)
|
|
86
|
+
bad!(field, "expected number") unless value.is_a?(Numeric)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def validate_boolean(field, value, _spec)
|
|
90
|
+
bad!(field, "expected boolean") unless [true, false].include?(value)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def validate_enum(field, value, spec)
|
|
94
|
+
values = Array(spec["values"])
|
|
95
|
+
bad!(field, "not in enum #{values.inspect}") unless values.include?(value)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def validate_array(field, value, spec)
|
|
99
|
+
bad!(field, "expected array") unless value.is_a?(Array)
|
|
100
|
+
return unless (items = spec["items"])
|
|
101
|
+
|
|
102
|
+
value.each_with_index { |v, i| check_type!("#{field}[#{i}]", v, items) }
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def validate_object(field, value, spec)
|
|
106
|
+
bad!(field, "expected object") unless value.is_a?(Hash)
|
|
107
|
+
return unless (sub = spec["fields"])
|
|
108
|
+
|
|
109
|
+
sub.each { |fk, fspec| check_type!("#{field}.#{fk}", value[fk], fspec) if value.key?(fk) }
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def bad!(field, reason)
|
|
113
|
+
raise Textus::SchemaViolation.new("field" => field, "reason" => reason)
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
Textus::Protocol::Session::Bridge = Data.define(:container, :gate) do
|
|
2
|
+
def store = container.store
|
|
3
|
+
def manifest = container.manifest
|
|
4
|
+
def schemas = container.schemas
|
|
5
|
+
def layout = container.layout
|
|
6
|
+
def workflows = container.workflows
|
|
7
|
+
def root = container.root
|
|
8
|
+
def ttl_evaluator = container.ttl_evaluator
|
|
9
|
+
|
|
10
|
+
def dispatch(...) = gate.dispatch(...)
|
|
11
|
+
end
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
require "forwardable"
|
|
2
|
+
|
|
3
|
+
module Textus
|
|
4
|
+
module Protocol
|
|
5
|
+
class Session
|
|
6
|
+
extend Forwardable
|
|
7
|
+
|
|
8
|
+
attr_reader :context, :bridge
|
|
9
|
+
|
|
10
|
+
DOMAIN_VERBS = (VerbRegistry::ENTRY_VERBS + VerbRegistry::OPS_VERBS).to_set.freeze
|
|
11
|
+
|
|
12
|
+
def_delegators :context, :role, :cursor, :correlation_id, :contract_etag, :propose_lane
|
|
13
|
+
def_delegator :context, :dry_run, :dry_run?
|
|
14
|
+
def_delegators :bridge, :root, :container, :store, :manifest, :schemas, :layout, :workflows, :gate, :ttl_evaluator
|
|
15
|
+
|
|
16
|
+
def self.open(root, role: Value::Role::DEFAULT, correlation_id: nil, dry_run: false)
|
|
17
|
+
root = File.expand_path(root)
|
|
18
|
+
container = Protocol::Builder.new.call(root)
|
|
19
|
+
gate = container.gate
|
|
20
|
+
bridge = Session::Bridge.new(container:, gate:)
|
|
21
|
+
context = Session::Context.new(
|
|
22
|
+
role: role.to_s,
|
|
23
|
+
correlation_id: correlation_id || SecureRandom.uuid,
|
|
24
|
+
dry_run: dry_run,
|
|
25
|
+
cursor: container.store.audit_log.latest[:seq],
|
|
26
|
+
contract_etag: Value::Etag.for_contract(bridge.layout),
|
|
27
|
+
propose_lane: container.manifest.policy.propose_lane_for(role.to_s),
|
|
28
|
+
)
|
|
29
|
+
new(bridge:, context:)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def initialize(bridge:, context:)
|
|
33
|
+
@bridge = bridge
|
|
34
|
+
@context = context
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def method_missing(name, *args, **kwargs)
|
|
38
|
+
if DOMAIN_VERBS.include?(name)
|
|
39
|
+
raise ArgumentError.new("#{name} accepts keyword arguments only") unless args.empty?
|
|
40
|
+
|
|
41
|
+
return bridge.dispatch(role.to_sym, name, kwargs, call: context.call)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
return bridge.public_send(name, *args, **kwargs) if bridge.respond_to?(name)
|
|
45
|
+
|
|
46
|
+
super
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def respond_to_missing?(name, include_private = false)
|
|
50
|
+
DOMAIN_VERBS.include?(name) || bridge.respond_to?(name) || super
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def with_correlation_id(cid)
|
|
54
|
+
self.class.new(bridge:, context: context.with(correlation_id: cid))
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def with_role(new_role)
|
|
58
|
+
self.class.new(
|
|
59
|
+
bridge:,
|
|
60
|
+
context: context.with(
|
|
61
|
+
role: new_role.to_s,
|
|
62
|
+
cursor: container.store.audit_log.latest[:seq],
|
|
63
|
+
contract_etag: Value::Etag.for_contract(container.layout),
|
|
64
|
+
propose_lane: manifest.policy.propose_lane_for(new_role.to_s),
|
|
65
|
+
),
|
|
66
|
+
)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def advance_cursor(new_cursor)
|
|
70
|
+
@context = context.with(cursor: new_cursor)
|
|
71
|
+
self
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def check_etag!(observed_etag)
|
|
75
|
+
return if observed_etag == contract_etag
|
|
76
|
+
|
|
77
|
+
raise Textus::ContractDrift.new(
|
|
78
|
+
"contract changed (was #{short_etag(contract_etag)}, now #{short_etag(observed_etag)})",
|
|
79
|
+
)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
private
|
|
83
|
+
|
|
84
|
+
def short_etag(etag) = etag.to_s.delete_prefix("sha256:")[0, 8]
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
module Textus
|
|
2
|
+
module Protocol
|
|
3
|
+
class StoreEngine
|
|
4
|
+
module Delete
|
|
5
|
+
module_function
|
|
6
|
+
|
|
7
|
+
def call(key, deps:, if_etag: nil)
|
|
8
|
+
path = Pipeline.resolve_path(key, deps)
|
|
9
|
+
assert_exists!(key, path, deps)
|
|
10
|
+
etag_before = Pipeline.check_etag(key, path, if_etag, deps.file_system)
|
|
11
|
+
deps.file_system.delete(path)
|
|
12
|
+
deps.entry_index.remove(key:)
|
|
13
|
+
Pipeline.prune_parents(path, deps)
|
|
14
|
+
append_audit(key, etag_before, deps)
|
|
15
|
+
Pipeline.emit_event(deps, event_type: "entry.deleted",
|
|
16
|
+
pending_key: key, emit_key: key)
|
|
17
|
+
nil
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def assert_exists!(key, path, deps)
|
|
21
|
+
return if deps.file_system.exists?(path)
|
|
22
|
+
|
|
23
|
+
raise Textus::UnknownKey.new(key,
|
|
24
|
+
suggestions: deps.manifest.resolver.suggestions_for(key))
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def append_audit(key, etag_before, deps)
|
|
28
|
+
deps.audit_log.append(
|
|
29
|
+
role: deps.call.role, verb: "key_delete", key:,
|
|
30
|
+
etag_before:, etag_after: nil,
|
|
31
|
+
correlation_id: deps.call.correlation_id
|
|
32
|
+
)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
module Textus
|
|
2
|
+
module Protocol
|
|
3
|
+
class StoreEngine
|
|
4
|
+
module Move
|
|
5
|
+
MoveResult = Data.define(:from_key, :to_key, :from_path, :to_path, :dry_run, :envelope)
|
|
6
|
+
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
def call(from_key:, to_key:, deps:, if_etag: nil, dry_run: false)
|
|
10
|
+
validate!(from_key, to_key)
|
|
11
|
+
from_path, to_path, from_entry, to_entry = resolve_paths(from_key, to_key, deps)
|
|
12
|
+
assert_source_exists!(from_key, from_path, deps)
|
|
13
|
+
assert_target_not_exists!(to_path)
|
|
14
|
+
check_cross_zone!(from_entry, to_entry)
|
|
15
|
+
ensure_uid!(from_key, from_entry, from_path, deps)
|
|
16
|
+
etag_before = Pipeline.check_etag(from_key, from_path, if_etag, deps.file_system)
|
|
17
|
+
|
|
18
|
+
if dry_run
|
|
19
|
+
return MoveResult.new(from_key:, to_key:, from_path:, to_path:,
|
|
20
|
+
dry_run: true, envelope: nil)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
deps.file_system.mv(from_path, to_path)
|
|
24
|
+
Pipeline.prune_parents(from_path, deps)
|
|
25
|
+
rewrite_basename!(to_key, to_path, to_entry)
|
|
26
|
+
etag_after = Value::Etag.for_file(to_path)
|
|
27
|
+
envelope = deps.reader.read(key: to_key)
|
|
28
|
+
append_audit(to_key, etag_before, etag_after, deps)
|
|
29
|
+
reindex_entry(from_key, to_key, to_entry, envelope, deps)
|
|
30
|
+
Pipeline.emit_event(deps, event_type: "entry.moved",
|
|
31
|
+
pending_key: from_key, emit_key: to_key,
|
|
32
|
+
emit_lane: to_entry.lane,
|
|
33
|
+
pending_keys: [from_key, to_key])
|
|
34
|
+
MoveResult.new(from_key:, to_key:, from_path:, to_path:,
|
|
35
|
+
dry_run: false, envelope:)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def validate!(from_key, to_key)
|
|
39
|
+
Manifest::Data.validate_key!(from_key)
|
|
40
|
+
Manifest::Data.validate_key!(to_key)
|
|
41
|
+
raise UsageError.new("old and new keys are identical") if from_key == to_key
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def resolve_paths(from_key, to_key, deps)
|
|
45
|
+
from = deps.manifest.resolver.resolve(from_key)
|
|
46
|
+
to = deps.manifest.resolver.resolve(to_key)
|
|
47
|
+
[from.path, to.path, from.entry, to.entry]
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def assert_source_exists!(key, path, deps)
|
|
51
|
+
return if deps.file_system.exists?(path)
|
|
52
|
+
|
|
53
|
+
raise UnknownKey.new(key, suggestions: deps.manifest.resolver.suggestions_for(key))
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def assert_target_not_exists!(path)
|
|
57
|
+
return unless path && File.exist?(path)
|
|
58
|
+
|
|
59
|
+
raise UsageError.new("target already exists")
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def check_cross_zone!(from_entry, to_entry)
|
|
63
|
+
return if from_entry.lane == to_entry.lane
|
|
64
|
+
|
|
65
|
+
raise UsageError.new("cross-zone moves forbidden (#{from_entry.lane} → #{to_entry.lane})")
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def ensure_uid!(_key, entry, path, deps)
|
|
69
|
+
return if Infra::Store::Envelope::Meta::NO_META_FORMATS.include?(entry.format)
|
|
70
|
+
return unless path && deps.file_system.exists?(path)
|
|
71
|
+
|
|
72
|
+
raw = deps.file_system.read(path)
|
|
73
|
+
parsed = Format.for(entry.format).parse(raw, path:)
|
|
74
|
+
meta = parsed["_meta"] || {}
|
|
75
|
+
return if meta["uid"].is_a?(String) && !meta["uid"].empty?
|
|
76
|
+
|
|
77
|
+
new_meta, = Infra::Store::Envelope::Meta.inject_all(meta, parsed["content"], {},
|
|
78
|
+
format: entry.format)
|
|
79
|
+
bytes, = Format.for(entry.format).serialize_for_put(
|
|
80
|
+
meta: new_meta, body: parsed["body"] || "", content: parsed["content"], path:,
|
|
81
|
+
)
|
|
82
|
+
deps.file_system.write(path, bytes)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def rewrite_basename!(key, path, entry)
|
|
86
|
+
basename = key.split(".").last
|
|
87
|
+
Format.for(entry.format).rewrite_name(path, basename)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def append_audit(key, etag_before, etag_after, deps)
|
|
91
|
+
deps.audit_log.append(
|
|
92
|
+
role: deps.call.role, verb: "key_mv", key:,
|
|
93
|
+
etag_before:, etag_after:,
|
|
94
|
+
correlation_id: deps.call.correlation_id
|
|
95
|
+
)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def reindex_entry(from_key, to_key, to_entry, envelope, deps)
|
|
99
|
+
deps.entry_index.remove(key: from_key)
|
|
100
|
+
content = envelope.content ? envelope.content.to_json : envelope.body
|
|
101
|
+
deps.entry_index.record(
|
|
102
|
+
key: to_key, lane: to_entry.lane, format: to_entry.format,
|
|
103
|
+
schema: to_entry.schema, content:
|
|
104
|
+
)
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
module Textus
|
|
2
|
+
module Protocol
|
|
3
|
+
class StoreEngine
|
|
4
|
+
module Pipeline
|
|
5
|
+
WriteDeps = Data.define(:file_system, :manifest, :store, :call, :reader, :layout, :event_emitter, :schemas) do
|
|
6
|
+
def audit_log = store.audit_log
|
|
7
|
+
def entry_index = store.entry_index
|
|
8
|
+
def file_store = store.file_store
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
DeleteDeps = Data.define(:file_system, :manifest, :store, :call, :layout, :event_emitter) do
|
|
12
|
+
def audit_log = store.audit_log
|
|
13
|
+
def entry_index = store.entry_index
|
|
14
|
+
def workflow_queue = store.workflow_queue
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
MoveDeps = Data.define(:file_system, :manifest, :store, :call, :reader, :layout, :event_emitter) do
|
|
18
|
+
def audit_log = store.audit_log
|
|
19
|
+
def entry_index = store.entry_index
|
|
20
|
+
def workflow_queue = store.workflow_queue
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
module_function
|
|
24
|
+
|
|
25
|
+
def resolve_path(key, deps)
|
|
26
|
+
deps.manifest.resolver.resolve(key).path
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def check_etag(key, path, if_etag, file_system)
|
|
30
|
+
etag_before = file_system.exists?(path) ? file_system.etag(path) : nil
|
|
31
|
+
raise Textus::EtagMismatch.new(key, if_etag, etag_before) if if_etag && (etag_before != if_etag)
|
|
32
|
+
|
|
33
|
+
etag_before
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def prune_parents(path, deps)
|
|
37
|
+
floor = deps.layout.lane_floor(path)
|
|
38
|
+
return unless floor
|
|
39
|
+
|
|
40
|
+
dir = File.dirname(path)
|
|
41
|
+
while dir.start_with?("#{floor}/") && deps.file_system.dir_empty?(dir)
|
|
42
|
+
deps.file_system.rmdir(dir)
|
|
43
|
+
dir = File.dirname(dir)
|
|
44
|
+
end
|
|
45
|
+
rescue SystemCallError
|
|
46
|
+
# race: dir may have been emptied by another writer
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def emit_event(deps, event_type:, pending_key:, emit_key:, emit_lane: nil, pending_keys: [])
|
|
50
|
+
return unless deps.event_emitter
|
|
51
|
+
return if deps.store.workflow_queue.pending?(event_type:, key: pending_key)
|
|
52
|
+
return if pending_keys.any? { |k| deps.store.workflow_queue.pending?(event_type:, key: k) }
|
|
53
|
+
|
|
54
|
+
deps.event_emitter.emit(event_type:, key: emit_key, lane: emit_lane || "artifacts")
|
|
55
|
+
rescue StandardError => e
|
|
56
|
+
warn "textus: emit_workflow_event(#{event_type}, #{emit_key}) failed: #{e.message}"
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|