textus 0.55.2 → 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 +8 -9
- data/lib/textus/artifact.rb +8 -0
- data/lib/textus/errors.rb +0 -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/{dispatch → protocol}/binder.rb +1 -7
- 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/{store → protocol}/freshness/ttl_evaluator.rb +7 -3
- 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 → protocol}/layout.rb +16 -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 +22 -21
- data/lib/textus/surface/cli/sources.rb +1 -3
- data/lib/textus/surface/cli/verb/get.rb +2 -7
- data/lib/textus/surface/cli/verb/init.rb +1 -1
- data/lib/textus/surface/cli/verb/put.rb +7 -7
- 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 +12 -12
- 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/server.rb +35 -24
- 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/value/etag.rb +7 -7
- 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 -53
- 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 -16
- metadata +169 -185
- data/SPEC.md +0 -909
- data/docs/architecture/README.md +0 -273
- data/docs/reference/conventions.md +0 -141
- data/lib/textus/boot.rb +0 -181
- data/lib/textus/dispatch/contracts.rb +0 -63
- data/lib/textus/dispatch/handler_registry.rb +0 -21
- data/lib/textus/dispatch/middleware/audit_index.rb +0 -51
- data/lib/textus/dispatch/middleware/auth.rb +0 -40
- data/lib/textus/dispatch/middleware/base.rb +0 -26
- data/lib/textus/dispatch/middleware/binder.rb +0 -20
- data/lib/textus/dispatch/middleware/cascade.rb +0 -53
- data/lib/textus/dispatch/pipeline.rb +0 -35
- 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/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/scratchpad_sources.rb +0 -55
- 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 -43
- data/lib/textus/doctor/validator.rb +0 -93
- data/lib/textus/doctor.rb +0 -54
- data/lib/textus/format/base.rb +0 -77
- data/lib/textus/format/json.rb +0 -81
- data/lib/textus/format/markdown.rb +0 -59
- data/lib/textus/format/text.rb +0 -55
- data/lib/textus/format/yaml.rb +0 -104
- data/lib/textus/format.rb +0 -61
- data/lib/textus/handlers/maintenance/boot_store.rb +0 -15
- data/lib/textus/handlers/maintenance/doctor_store.rb +0 -15
- data/lib/textus/handlers/maintenance/drain_store.rb +0 -21
- data/lib/textus/handlers/maintenance/ingest_entry.rb +0 -159
- data/lib/textus/handlers/maintenance/jobs_action.rb +0 -21
- data/lib/textus/handlers/maintenance/published_entries.rb +0 -17
- data/lib/textus/handlers/maintenance/rule_explain.rb +0 -77
- data/lib/textus/handlers/maintenance/rule_lint.rb +0 -54
- data/lib/textus/handlers/maintenance/rule_list.rb +0 -32
- data/lib/textus/handlers/maintenance/schema_envelope.rb +0 -19
- data/lib/textus/handlers/read/audit_entries.rb +0 -48
- data/lib/textus/handlers/read/blame_entry.rb +0 -71
- data/lib/textus/handlers/read/deps_entry.rb +0 -17
- data/lib/textus/handlers/read/get_entry.rb +0 -68
- data/lib/textus/handlers/read/list_keys.rb +0 -36
- data/lib/textus/handlers/read/pulse_entries.rb +0 -66
- data/lib/textus/handlers/read/rdeps_entry.rb +0 -21
- data/lib/textus/handlers/read/uid_entry.rb +0 -18
- data/lib/textus/handlers/read/where_entry.rb +0 -18
- data/lib/textus/handlers/write/accept_proposal.rb +0 -39
- data/lib/textus/handlers/write/data_mv.rb +0 -55
- data/lib/textus/handlers/write/delete_key.rb +0 -17
- data/lib/textus/handlers/write/enqueue_job.rb +0 -27
- data/lib/textus/handlers/write/key_delete_prefix.rb +0 -32
- data/lib/textus/handlers/write/key_mv_prefix.rb +0 -45
- data/lib/textus/handlers/write/move_key.rb +0 -80
- data/lib/textus/handlers/write/propose_entry.rb +0 -29
- data/lib/textus/handlers/write/put_entry.rb +0 -29
- data/lib/textus/handlers/write/reject_proposal.rb +0 -29
- 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/predicates/author_held.rb +0 -22
- data/lib/textus/manifest/policy/predicates/etag_match.rb +0 -18
- data/lib/textus/manifest/policy/predicates/fresh_within.rb +0 -13
- data/lib/textus/manifest/policy/predicates/lane_deletable_by.rb +0 -31
- data/lib/textus/manifest/policy/predicates/lane_writable_by.rb +0 -23
- data/lib/textus/manifest/policy/predicates/raw_lane_ingest_only.rb +0 -25
- data/lib/textus/manifest/policy/predicates/raw_write_once.rb +0 -24
- data/lib/textus/manifest/policy/predicates/schema_valid.rb +0 -41
- data/lib/textus/manifest/policy/predicates/target_is_canon.rb +0 -20
- data/lib/textus/manifest/policy/predicates.rb +0 -54
- 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/orchestration.rb +0 -55
- 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.rb +0 -25
- 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 -49
- data/lib/textus/port/storage/interface.rb +0 -17
- data/lib/textus/port/store.rb +0 -149
- data/lib/textus/port/watcher_lock.rb +0 -48
- data/lib/textus/produce/engine.rb +0 -40
- data/lib/textus/produce/publisher.rb +0 -21
- data/lib/textus/produce/render.rb +0 -20
- data/lib/textus/schema/registry.rb +0 -42
- data/lib/textus/schema/tools.rb +0 -108
- data/lib/textus/schema.rb +0 -100
- data/lib/textus/store/container.rb +0 -173
- data/lib/textus/store/cursor.rb +0 -26
- data/lib/textus/store/entry/reader.rb +0 -47
- data/lib/textus/store/entry/writer.rb +0 -219
- data/lib/textus/store/envelope/meta.rb +0 -61
- data/lib/textus/store/freshness/drift_detector.rb +0 -93
- data/lib/textus/store/freshness/evaluator.rb +0 -20
- data/lib/textus/store/freshness/verdict.rb +0 -24
- data/lib/textus/store/freshness.rb +0 -8
- data/lib/textus/store/index/builder.rb +0 -76
- 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 -124
- data/lib/textus/store/jobs/queue.rb +0 -162
- data/lib/textus/store/jobs/registry.rb +0 -19
- data/lib/textus/store/jobs/retention/base.rb +0 -52
- data/lib/textus/store/jobs/retention/sweep.rb +0 -55
- data/lib/textus/store/jobs/retention.rb +0 -8
- data/lib/textus/store/jobs/sweep.rb +0 -21
- data/lib/textus/store/jobs/worker.rb +0 -64
- data/lib/textus/store.rb +0 -122
- data/lib/textus/surface/cli/verb/doctor.rb +0 -20
- 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/projector.rb +0 -20
- data/lib/textus/surface/mcp.rb +0 -8
- data/lib/textus/value/command.rb +0 -16
- data/lib/textus/value/envelope.rb +0 -90
- data/lib/textus/value/result.rb +0 -46
- data/lib/textus/verb_registry.rb +0 -417
- 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,41 @@
|
|
|
1
|
+
module Textus
|
|
2
|
+
module Protocol
|
|
3
|
+
module Handlers
|
|
4
|
+
class QueryContext
|
|
5
|
+
def initialize(store_engine, container, call)
|
|
6
|
+
@store_engine = store_engine
|
|
7
|
+
@container = container
|
|
8
|
+
@call = call
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
attr_reader :call, :store_engine, :container
|
|
12
|
+
|
|
13
|
+
# StoreEngine delegation (read side)
|
|
14
|
+
def read(key:) = @store_engine.read(key:)
|
|
15
|
+
def resolve(key:) = @store_engine.resolve(key:)
|
|
16
|
+
def list(...) = @store_engine.list(...)
|
|
17
|
+
def exists?(key:) = @store_engine.exists?(key:)
|
|
18
|
+
def mentry_for(key:) = @store_engine.mentry_for(key:)
|
|
19
|
+
|
|
20
|
+
# Narrow file queries (avoid raw file_system access)
|
|
21
|
+
def file_exists?(key)
|
|
22
|
+
path = @store_engine.resolve(key:).path
|
|
23
|
+
@store_engine.file_system.exists?(path)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def etag_for(key)
|
|
27
|
+
path = @store_engine.resolve(key:).path
|
|
28
|
+
@store_engine.file_system.etag(path) if @store_engine.file_system.exists?(path)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Container metadata (read-only)
|
|
32
|
+
def manifest = @container.manifest
|
|
33
|
+
def schemas = @container.schemas
|
|
34
|
+
def layout = @container.layout
|
|
35
|
+
def link_store = @container.store.link_store
|
|
36
|
+
def entry_index = @container.store.entry_index
|
|
37
|
+
def cursor_store = @container.store.cursor_store
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
module Textus
|
|
2
|
+
module Protocol
|
|
3
|
+
module Handlers
|
|
4
|
+
module Read
|
|
5
|
+
include Concern
|
|
6
|
+
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
def get(key:, ctx:, call:)
|
|
10
|
+
env = ctx.read(key:)
|
|
11
|
+
unless env
|
|
12
|
+
suggestions = ctx.manifest.resolver.suggestions_for(key)
|
|
13
|
+
raise Textus::UnknownKey.new(key, suggestions:)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
Ops::SourceExpander.new.call(env, ctx:, depth: 0).to_h_for_wire
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def list(ctx:, call:, prefix: nil, lane: nil, q: nil, schema: nil)
|
|
20
|
+
ctx.list(prefix:, lane:, q:, schema:)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def where(key:, ctx:, call:)
|
|
24
|
+
res = ctx.resolve(key:)
|
|
25
|
+
response(key:, lane: res.entry.lane, path: res.path)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def uid(key:, ctx:, call:)
|
|
29
|
+
env = ctx.read(key:)
|
|
30
|
+
raise Textus::UnknownKey.new(key) unless env
|
|
31
|
+
|
|
32
|
+
env.uid
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def deps(key:, ctx:, call:)
|
|
36
|
+
entry = ctx.manifest.data.entries.find { |e| e.key == key }
|
|
37
|
+
deps_list = entry&.external? ? Array(entry.source&.sources).compact : []
|
|
38
|
+
{ "key" => key, "deps" => deps_list.uniq }
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def rdeps(key:, ctx:, call:)
|
|
42
|
+
source_rdeps = ctx.manifest.data.entries.each_with_object([]) do |entry, acc|
|
|
43
|
+
next unless entry.external?
|
|
44
|
+
|
|
45
|
+
sources = Array(entry.source&.sources).compact
|
|
46
|
+
acc << entry.key if sources.any? { |s| s == key || key.start_with?("#{s}.") }
|
|
47
|
+
end
|
|
48
|
+
link_rdeps = ctx.link_store.links_to(key)
|
|
49
|
+
{ "key" => key, "rdeps" => (source_rdeps + link_rdeps).uniq.sort }
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def graph(key:, ctx:, call:, depth: nil)
|
|
53
|
+
from = ctx.link_store.links_from(key)
|
|
54
|
+
to = ctx.link_store.links_to(key)
|
|
55
|
+
{ "key" => key, "neighbors" => (from + to).uniq.sort,
|
|
56
|
+
"reachable" => ctx.link_store.reachable(from: key, depth:).sort }
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def schema_show(key:, ctx:, call:)
|
|
60
|
+
mentry = ctx.mentry_for(key:)
|
|
61
|
+
schema = ctx.schemas.fetch_or_nil(mentry.schema)
|
|
62
|
+
response(key:, schema_ref: mentry.schema, schema: schema&.to_h)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
module Textus
|
|
2
|
+
module Protocol
|
|
3
|
+
module Handlers
|
|
4
|
+
module System
|
|
5
|
+
include Concern
|
|
6
|
+
extend self
|
|
7
|
+
|
|
8
|
+
def boot(ctx:, call:)
|
|
9
|
+
proxy = Textus::Protocol::Container.new(
|
|
10
|
+
store: ctx.container.store,
|
|
11
|
+
layout: ctx.layout,
|
|
12
|
+
manifest: ctx.manifest, schemas: ctx.schemas,
|
|
13
|
+
workflows: nil,
|
|
14
|
+
root: ctx.layout.root
|
|
15
|
+
)
|
|
16
|
+
Textus::Protocol::Boot.build(container: proxy)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def schema_list(ctx:, call:)
|
|
20
|
+
Textus::Protocol::Boot.build_schemas(ctx.schemas)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def audit(ctx:, call:, key: nil, lane: nil, role: nil, verb: nil, since: nil, seq_since: nil, correlation_id: nil, limit: nil)
|
|
24
|
+
filters = { key:, lane:, role:, verb:, since:, seq_since:, correlation_id:, limit: }
|
|
25
|
+
Ops::AuditFilter.new.call(ctx:, filters:)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def blame(key:, ctx:, call:)
|
|
29
|
+
audit_result = audit(key:, ctx:, call:)
|
|
30
|
+
audit_rows = audit_result["rows"] || []
|
|
31
|
+
git_map = Ops::GitBlame.new.call(key:, ctx:)
|
|
32
|
+
audit_rows.map { |row| row.merge("git" => git_map ? git_map[row["ts"]] : nil) }
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def pulse(ctx:, call:, since: nil)
|
|
36
|
+
Ops::PulseCoordinator.new.call(role: call.role, ctx:, since:)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def workflow_spec(name:, ctx:, call:)
|
|
40
|
+
registry = ctx.workflow_registry || Textus::Workflow::Registry.new(ctx.workflows)
|
|
41
|
+
wf = registry.for(name)
|
|
42
|
+
{
|
|
43
|
+
"name" => wf.name, "type" => wf.workflow_type,
|
|
44
|
+
"match_patterns" => wf.on_patterns || [],
|
|
45
|
+
"handles_events" => wf.handled_events,
|
|
46
|
+
"steps" => wf.steps.map { |s| { "name" => s.name.to_s, "type" => step_type(s) } },
|
|
47
|
+
"publish_targets" => wf.publish_targets.map do |t|
|
|
48
|
+
h = {}
|
|
49
|
+
h["to"] = t.to if t.to
|
|
50
|
+
h["template"] = t.template if t.template
|
|
51
|
+
h
|
|
52
|
+
end,
|
|
53
|
+
"priority" => wf.priority,
|
|
54
|
+
"max_attempts" => wf.max_attempts,
|
|
55
|
+
"ttl" => wf.ttl&.to_s,
|
|
56
|
+
"on_expire" => wf.on_expire
|
|
57
|
+
}
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def drain(ctx:, call:)
|
|
61
|
+
registry = ctx.workflow_registry || Textus::Workflow::Registry.new(ctx.workflows)
|
|
62
|
+
reader = ->(key:) { ctx.read(key:) }
|
|
63
|
+
|
|
64
|
+
Textus::Workflow::Scheduler.new(
|
|
65
|
+
registry:, workflow_queue: ctx.workflow_queue, reader:,
|
|
66
|
+
).seed_expired
|
|
67
|
+
|
|
68
|
+
consumer = Textus::Workflow::Consumer.new(registry:, container: ctx.container, call:)
|
|
69
|
+
completed = 0
|
|
70
|
+
failed = 0
|
|
71
|
+
loop do
|
|
72
|
+
row = ctx.workflow_queue.pop
|
|
73
|
+
break unless row
|
|
74
|
+
|
|
75
|
+
result = consumer.consume(row, ctx.workflow_queue)
|
|
76
|
+
result[:state] == "done" ? completed += 1 : failed += 1
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
response(ok: failed.zero?, completed:, failed:)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
private
|
|
83
|
+
|
|
84
|
+
def step_type(step)
|
|
85
|
+
case step
|
|
86
|
+
when Textus::Workflow::DSL::Parallel then "parallel"
|
|
87
|
+
else "step"
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
module Textus
|
|
2
|
+
module Protocol
|
|
3
|
+
module Handlers
|
|
4
|
+
module Write
|
|
5
|
+
include Concern
|
|
6
|
+
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
def put(key:, ctx:, call:, meta: nil, if_etag: nil, format: nil, data: nil)
|
|
10
|
+
body, content = data.is_a?(Hash) ? [nil, data] : [data, nil]
|
|
11
|
+
env = ctx.put(key:, meta: meta || {}, body:, content:, if_etag:, call:, format:)
|
|
12
|
+
response(ok: true, key:, uid: env.uid, etag: env.etag)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def delete(key:, ctx:, call:, if_etag: nil)
|
|
16
|
+
ctx.delete(key:, if_etag:, call:)
|
|
17
|
+
response(ok: true, key:, deleted: true)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def key_delete(key:, ctx:, call:, if_etag: nil)
|
|
21
|
+
delete(key:, ctx:, call:, if_etag:)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def move(old_key:, new_key:, ctx:, call:, if_etag: nil, dry_run: false)
|
|
25
|
+
mctx = ctx.move(from_key: old_key, to_key: new_key, call:, if_etag:, dry_run:)
|
|
26
|
+
base = response(ok: true, from_key: mctx.from_key, to_key: mctx.to_key,
|
|
27
|
+
from_path: mctx.from_path, to_path: mctx.to_path)
|
|
28
|
+
return base.merge("dry_run" => true) if mctx.dry_run
|
|
29
|
+
|
|
30
|
+
base.merge("uid" => mctx.envelope.uid, "envelope" => mctx.envelope.to_h_for_wire)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def key_mv_prefix(from_prefix:, to_prefix:, ctx:, call:, dry_run: false)
|
|
34
|
+
planner = Ops::BulkPlanner.new
|
|
35
|
+
plan = planner.plan_mv(from_prefix:, to_prefix:, ctx:)
|
|
36
|
+
return plan.to_h if dry_run
|
|
37
|
+
|
|
38
|
+
plan.steps.each { |s| move(old_key: s["from"], new_key: s["to"], ctx:, call:) }
|
|
39
|
+
plan.to_h
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def key_delete_prefix(prefix:, ctx:, call:, dry_run: false)
|
|
43
|
+
planner = Ops::BulkPlanner.new
|
|
44
|
+
plan = planner.plan_delete(prefix:, ctx:)
|
|
45
|
+
return plan.to_h if dry_run
|
|
46
|
+
|
|
47
|
+
plan.steps.each { |s| delete(key: s["key"], ctx:, call:) }
|
|
48
|
+
plan.to_h
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def data_mv(from:, to:, ctx:, call:, dry_run: false)
|
|
52
|
+
Ops::DataMv.new.call(from:, to:, ctx:, dry_run:)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
require "fileutils"
|
|
2
|
+
require "pathname"
|
|
3
|
+
require "yaml"
|
|
4
|
+
|
|
5
|
+
module Textus
|
|
6
|
+
module Protocol
|
|
7
|
+
module Init
|
|
8
|
+
ZONES = %w[knowledge scratchpad proposals artifacts raw].freeze
|
|
9
|
+
|
|
10
|
+
DEFAULT_MANIFEST = <<~YAML
|
|
11
|
+
version: textus/4
|
|
12
|
+
data:
|
|
13
|
+
knowledge:
|
|
14
|
+
- key: knowledge.loop.execution
|
|
15
|
+
naming: sequential
|
|
16
|
+
schema: runbook
|
|
17
|
+
- key: knowledge.loop.intent
|
|
18
|
+
naming: sequential
|
|
19
|
+
- key: knowledge.loop.constraint
|
|
20
|
+
- key: knowledge.loop.judgment
|
|
21
|
+
- key: knowledge.loop.evidence
|
|
22
|
+
naming: sequential
|
|
23
|
+
- key: knowledge.loop.feedback
|
|
24
|
+
scratchpad:
|
|
25
|
+
- key: scratchpad.notes
|
|
26
|
+
tracked: false
|
|
27
|
+
- key: scratchpad.sessions
|
|
28
|
+
tracked: false
|
|
29
|
+
- key: scratchpad.scripts
|
|
30
|
+
tracked: false
|
|
31
|
+
proposals:
|
|
32
|
+
- key: proposals.notes
|
|
33
|
+
raw:
|
|
34
|
+
- key: raw
|
|
35
|
+
format: yaml
|
|
36
|
+
artifacts:
|
|
37
|
+
- key: artifacts.boot
|
|
38
|
+
format: json
|
|
39
|
+
YAML
|
|
40
|
+
|
|
41
|
+
AGENT_ENTRIES = <<~YAML
|
|
42
|
+
data:
|
|
43
|
+
artifacts:
|
|
44
|
+
- key: artifacts.derived.orientation
|
|
45
|
+
format: json
|
|
46
|
+
YAML
|
|
47
|
+
|
|
48
|
+
def self.run(target_root, with_agent: false)
|
|
49
|
+
check_target!(target_root)
|
|
50
|
+
scaffold_dir = File.expand_path("init/templates", __dir__)
|
|
51
|
+
create_directories(target_root)
|
|
52
|
+
write_manifest(target_root, with_agent:)
|
|
53
|
+
mcp_status = scaffold_agent(target_root, scaffold_dir, with_agent:)
|
|
54
|
+
write_gitignore(target_root)
|
|
55
|
+
build_result(target_root, with_agent:, mcp_status:)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def self.check_target!(target_root)
|
|
59
|
+
raise UsageError.new(".textus/ already exists at #{target_root}") if File.directory?(target_root)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
ASSET_ZONES = %w[raw].freeze
|
|
63
|
+
|
|
64
|
+
def self.create_directories(target_root)
|
|
65
|
+
FileUtils.mkdir_p(File.join(target_root, "schemas"))
|
|
66
|
+
FileUtils.mkdir_p(File.join(target_root, "templates"))
|
|
67
|
+
FileUtils.mkdir_p(File.join(target_root, "workflows"))
|
|
68
|
+
ZONES.each do |z|
|
|
69
|
+
dir = File.join(target_root, "data", z)
|
|
70
|
+
FileUtils.mkdir_p(dir)
|
|
71
|
+
File.write(File.join(dir, ".gitkeep"), "")
|
|
72
|
+
end
|
|
73
|
+
ASSET_ZONES.each do |z|
|
|
74
|
+
dir = File.join(target_root, "assets", z)
|
|
75
|
+
FileUtils.mkdir_p(dir)
|
|
76
|
+
File.write(File.join(dir, ".gitkeep"), "")
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def self.write_manifest(target_root, with_agent:)
|
|
81
|
+
File.write(File.join(target_root, "manifest.yaml"), manifest_yaml(with_agent: with_agent))
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def self.scaffold_agent(target_root, scaffold_dir, with_agent:)
|
|
85
|
+
return nil unless with_agent
|
|
86
|
+
|
|
87
|
+
scaffold_agent_profile(target_root, scaffold_dir)
|
|
88
|
+
write_mcp_config(target_root, scaffold_dir)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def self.write_gitignore(target_root)
|
|
92
|
+
File.write(File.join(target_root, ".gitignore"), derived_gitignore(target_root))
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def self.build_result(target_root, with_agent:, mcp_status:)
|
|
96
|
+
result = { "protocol" => PROTOCOL, "initialized" => target_root, "profile" => with_agent ? "agent" : "default" }
|
|
97
|
+
result["mcp_config"] = mcp_status if with_agent
|
|
98
|
+
result
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def self.manifest_yaml(with_agent:)
|
|
102
|
+
return DEFAULT_MANIFEST unless with_agent
|
|
103
|
+
|
|
104
|
+
base = YAML.safe_load(DEFAULT_MANIFEST)
|
|
105
|
+
extra = YAML.safe_load(AGENT_ENTRIES)
|
|
106
|
+
base["data"].merge!(extra["data"]) { |_lane, existing, added| existing + added }
|
|
107
|
+
YAML.dump(base).sub(/\A---\n/, "")
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def self.scaffold_agent_profile(target_root, scaffold_dir)
|
|
111
|
+
{
|
|
112
|
+
"project.schema.yaml" => File.join("schemas", "project.yaml"),
|
|
113
|
+
"runbook.schema.yaml" => File.join("schemas", "runbook.yaml"),
|
|
114
|
+
"orientation.erb" => File.join("templates", "orientation.erb"),
|
|
115
|
+
}.each do |src, dest|
|
|
116
|
+
File.write(File.join(target_root, dest), File.read(File.join(scaffold_dir, src)))
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def self.write_mcp_config(target_root, scaffold_dir)
|
|
121
|
+
dest = File.join(File.dirname(target_root), ".mcp.json")
|
|
122
|
+
return "skipped" if File.exist?(dest)
|
|
123
|
+
|
|
124
|
+
File.write(dest, File.read(File.join(scaffold_dir, "mcp.json")))
|
|
125
|
+
"written"
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def self.derived_gitignore(target_root)
|
|
129
|
+
manifest = Textus::Protocol::Manifest.load(target_root)
|
|
130
|
+
root = Pathname.new(target_root)
|
|
131
|
+
untracked = manifest.data.entries.reject(&:tracked?).map do |e|
|
|
132
|
+
if e.nested?
|
|
133
|
+
rel = e.path.start_with?("data/") ? e.path : File.join("data", e.path)
|
|
134
|
+
"#{rel}/"
|
|
135
|
+
else
|
|
136
|
+
Pathname.new(Textus::Protocol::Key::Path.resolve(manifest.data, e)).relative_path_from(root).to_s
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
Textus::Protocol::Layout.new(target_root).gitignore_body(untracked_entries: untracked)
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
module Textus
|
|
2
|
+
module Protocol
|
|
3
|
+
module Key
|
|
4
|
+
module Grammar
|
|
5
|
+
SEGMENT = /\A[a-z0-9][a-z0-9-]*\z/
|
|
6
|
+
MAX_SEGMENTS = 8
|
|
7
|
+
MAX_SEGMENT_LEN = 64
|
|
8
|
+
|
|
9
|
+
module_function
|
|
10
|
+
|
|
11
|
+
def validate!(key)
|
|
12
|
+
raise UsageError.new("key must be a String") unless key.is_a?(String)
|
|
13
|
+
raise UsageError.new("empty key") if key.empty?
|
|
14
|
+
|
|
15
|
+
segs = key.split(".")
|
|
16
|
+
raise UsageError.new("key '#{key}' has #{segs.length} segments (max #{MAX_SEGMENTS})") if segs.length > MAX_SEGMENTS
|
|
17
|
+
|
|
18
|
+
segs.each do |seg|
|
|
19
|
+
if seg.empty?
|
|
20
|
+
raise UsageError.new("empty segment in key '#{key}'")
|
|
21
|
+
elsif seg.length > MAX_SEGMENT_LEN
|
|
22
|
+
raise UsageError.new("segment '#{seg}' in key '#{key}' exceeds #{MAX_SEGMENT_LEN} chars")
|
|
23
|
+
elsif !seg.match?(SEGMENT)
|
|
24
|
+
raise UsageError.new(
|
|
25
|
+
"invalid key segment '#{seg}' in '#{key}': must match [a-z0-9][a-z0-9-]* " \
|
|
26
|
+
"(lowercase, digits, hyphens; no underscores or uppercase)",
|
|
27
|
+
)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
true
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module Textus
|
|
2
|
+
module Protocol
|
|
3
|
+
module Key
|
|
4
|
+
module Matching
|
|
5
|
+
module_function
|
|
6
|
+
|
|
7
|
+
def matches_prefix?(key, prefix, nested: false)
|
|
8
|
+
return true if key == prefix || key.start_with?("#{prefix}.")
|
|
9
|
+
|
|
10
|
+
nested && prefix.start_with?("#{key}.")
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def match?(pattern, key)
|
|
14
|
+
segs = pattern.split(".")
|
|
15
|
+
key_segs = key.split(".")
|
|
16
|
+
return false if segs.length != key_segs.length
|
|
17
|
+
|
|
18
|
+
segs.zip(key_segs).all? { |p, k| p == "*" || p == k }
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module Textus
|
|
2
|
+
module Protocol
|
|
3
|
+
module Key
|
|
4
|
+
module Path
|
|
5
|
+
def self.resolve(data, mentry)
|
|
6
|
+
primary_ext = Textus::Protocol::Format.for(mentry.format).extensions.first
|
|
7
|
+
rel_path = normalize_relative_path(mentry.path)
|
|
8
|
+
if File.extname(mentry.path) == ""
|
|
9
|
+
File.join(data.root, rel_path + primary_ext)
|
|
10
|
+
else
|
|
11
|
+
File.join(data.root, rel_path)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.normalize_relative_path(path)
|
|
16
|
+
return path if path.start_with?("data/")
|
|
17
|
+
|
|
18
|
+
File.join("data", path)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module Textus
|
|
2
|
+
module Protocol
|
|
3
|
+
module Key
|
|
4
|
+
module_function
|
|
5
|
+
|
|
6
|
+
Resolution = ::Data.define(:entry, :remaining)
|
|
7
|
+
|
|
8
|
+
def resolve(key, entries)
|
|
9
|
+
entry = entries.find { |e| e.key == key }
|
|
10
|
+
return Resolution.new(entry:, remaining: []) if entry
|
|
11
|
+
|
|
12
|
+
candidates = entries.select { |e| e.nested? && key.start_with?("#{e.key}.") }
|
|
13
|
+
return nil if candidates.empty?
|
|
14
|
+
|
|
15
|
+
best = candidates.max_by { |e| e.key.length }
|
|
16
|
+
tail = key.delete_prefix("#{best.key}.")
|
|
17
|
+
Resolution.new(entry: best, remaining: tail.split("."))
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def match?(pattern, key)
|
|
21
|
+
Matching.match?(pattern, key)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
module Textus
|
|
2
|
+
module Protocol
|
|
3
|
+
module Lane
|
|
4
|
+
def self.handler_for(lane_name)
|
|
5
|
+
ns = lane_name.to_s.split("_").map(&:capitalize).join
|
|
6
|
+
Textus::Lanes.const_get(ns)::Handlers
|
|
7
|
+
rescue NameError
|
|
8
|
+
raise ArgumentError.new("no handler for lane: #{lane_name}")
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
module Textus
|
|
2
|
-
|
|
2
|
+
module Protocol
|
|
3
3
|
class Layout
|
|
4
|
-
RUN = ".state"
|
|
5
4
|
DATA = "data"
|
|
6
5
|
ASSETS = "assets"
|
|
7
6
|
|
|
@@ -12,12 +11,11 @@ module Textus
|
|
|
12
11
|
|
|
13
12
|
attr_reader :root
|
|
14
13
|
|
|
15
|
-
# -- data paths --
|
|
16
14
|
def data_root = File.join(@root, DATA)
|
|
17
15
|
def lane_path(lane_name) = File.join(data_root, lane_name.to_s)
|
|
18
16
|
|
|
19
17
|
def entry_path(mentry)
|
|
20
|
-
primary_ext = Format.for(mentry.format).extensions.first
|
|
18
|
+
primary_ext = Textus::Protocol::Format.for(mentry.format).extensions.first
|
|
21
19
|
rel = normalize_relative_path(mentry.path)
|
|
22
20
|
if File.extname(mentry.path) == ""
|
|
23
21
|
File.join(@root, rel + primary_ext)
|
|
@@ -26,19 +24,15 @@ module Textus
|
|
|
26
24
|
end
|
|
27
25
|
end
|
|
28
26
|
|
|
29
|
-
|
|
30
|
-
def
|
|
31
|
-
def
|
|
32
|
-
def
|
|
33
|
-
def
|
|
34
|
-
def
|
|
35
|
-
def
|
|
36
|
-
def
|
|
37
|
-
|
|
38
|
-
def sentinels_root = File.join(run_root, "tracking", "sentinels")
|
|
39
|
-
def store_db_path = File.join(run_root, "store.db")
|
|
40
|
-
|
|
41
|
-
# -- asset paths --
|
|
27
|
+
def db_root = File.join(@root, "db")
|
|
28
|
+
def db_path = File.join(db_root, "store.db")
|
|
29
|
+
def log_root = File.join(@root, "log")
|
|
30
|
+
def run_root = File.join(@root, "run")
|
|
31
|
+
def lock_path(name) = File.join(run_root, "locks", "#{name}.lock")
|
|
32
|
+
def track_root = File.join(@root, "track")
|
|
33
|
+
def sentinels_root = File.join(track_root, "sentinels")
|
|
34
|
+
def cursor_path(role) = File.join(track_root, "cursors", role.to_s)
|
|
35
|
+
|
|
42
36
|
def asset_raw_dir(date_path, zone)
|
|
43
37
|
File.join(@root, ASSETS, "raw", date_path, zone.to_s)
|
|
44
38
|
end
|
|
@@ -56,7 +50,6 @@ module Textus
|
|
|
56
50
|
File.join(@root, "archive", rel)
|
|
57
51
|
end
|
|
58
52
|
|
|
59
|
-
# -- config paths --
|
|
60
53
|
def manifest_path = File.join(@root, "manifest.yaml")
|
|
61
54
|
def schemas_dir = File.join(@root, "schemas")
|
|
62
55
|
def schema_path(name) = File.join(schemas_dir, "#{name}.yaml")
|
|
@@ -65,10 +58,8 @@ module Textus
|
|
|
65
58
|
def hooks_dir = File.join(@root, "hooks")
|
|
66
59
|
def schemas_glob = File.join(schemas_dir, "**", "*")
|
|
67
60
|
|
|
68
|
-
# -- gitignore --
|
|
69
61
|
def gitignore_body(untracked_entries: [])
|
|
70
|
-
lines = ["# textus runtime artifacts — safe to delete, never commit",
|
|
71
|
-
"#{RUN}/"]
|
|
62
|
+
lines = ["# textus runtime artifacts — safe to delete, never commit", *runtime_ignore_dirs]
|
|
72
63
|
unless untracked_entries.empty?
|
|
73
64
|
lines << "# tracked:false entries — protocol-readable, not committed"
|
|
74
65
|
lines.concat(untracked_entries)
|
|
@@ -76,7 +67,6 @@ module Textus
|
|
|
76
67
|
"#{lines.join("\n")}\n"
|
|
77
68
|
end
|
|
78
69
|
|
|
79
|
-
# -- lane boundary (replaces Writer#zone_floor) --
|
|
80
70
|
def lane_floor(path)
|
|
81
71
|
prefix = "#{data_root}/"
|
|
82
72
|
return nil unless path.start_with?(prefix)
|
|
@@ -87,6 +77,10 @@ module Textus
|
|
|
87
77
|
|
|
88
78
|
private
|
|
89
79
|
|
|
80
|
+
def runtime_ignore_dirs
|
|
81
|
+
["db/", "log/", "run/", "track/", ".state/"]
|
|
82
|
+
end
|
|
83
|
+
|
|
90
84
|
def normalize_relative_path(path)
|
|
91
85
|
return path if path.start_with?("data/")
|
|
92
86
|
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
require "pathname"
|
|
2
|
+
|
|
3
|
+
module Textus
|
|
4
|
+
module Protocol
|
|
5
|
+
class Links
|
|
6
|
+
class Resolver
|
|
7
|
+
class UnknownKeyError < StandardError
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def initialize(manifest:)
|
|
11
|
+
@manifest = manifest
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def resolve(key:, from_path:)
|
|
15
|
+
entry = @manifest.data.entries.find { |e| e.key == key }
|
|
16
|
+
raise UnknownKeyError.new("unknown key: #{key}") unless entry
|
|
17
|
+
|
|
18
|
+
to = Array(entry.publish_to).first
|
|
19
|
+
return "`textus get #{key}`" if to.nil?
|
|
20
|
+
|
|
21
|
+
relative_path(from: from_path, to: to)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def relative_path(from:, to:)
|
|
27
|
+
from_dir = Pathname.new(from).dirname
|
|
28
|
+
Pathname.new(to).relative_path_from(from_dir).to_s
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
module Textus
|
|
2
|
+
module Protocol
|
|
3
|
+
class Links
|
|
4
|
+
class UriRewriter
|
|
5
|
+
TEXTUS_URI = /\[([^\]]*)\]\(textus:([^#)\s]+)(#[^)\s]*)?\)/
|
|
6
|
+
|
|
7
|
+
def initialize(resolver:, from_path:, from_key: nil, edge_store: nil)
|
|
8
|
+
@resolver = resolver
|
|
9
|
+
@from_path = from_path
|
|
10
|
+
@from_key = from_key
|
|
11
|
+
@edge_store = edge_store
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def rewrite(content)
|
|
15
|
+
content.gsub(TEXTUS_URI) do
|
|
16
|
+
text = Regexp.last_match(1)
|
|
17
|
+
key = Regexp.last_match(2)
|
|
18
|
+
anchor = Regexp.last_match(3).to_s
|
|
19
|
+
|
|
20
|
+
resolved = @resolver.resolve(key: key, from_path: @from_path)
|
|
21
|
+
@edge_store&.record(from_key: @from_key, to_key: key)
|
|
22
|
+
"[#{text}](#{resolved}#{anchor})"
|
|
23
|
+
rescue Resolver::UnknownKeyError
|
|
24
|
+
"[#{text}](`textus get #{key}`)"
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|