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
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
class Manifest
|
|
3
|
-
# Resolves a manifest's `roles:` block (or the absence of one) into a
|
|
4
|
-
# capability map: { role_name => [verbs] }. Verbs are a subset of the
|
|
5
|
-
# closed capability set (Schema::CAPABILITIES). See ADR 0030.
|
|
6
|
-
module Capabilities
|
|
7
|
-
# Fallback role set for a manifest that omits `roles:` entirely. Agent
|
|
8
|
-
# is intentionally minimal here (`propose` only) — narrower than the
|
|
9
|
-
# `textus init` scaffold, which declares `agent: [propose, keep]` so the
|
|
10
|
-
# default `notebook` workspace is writable. A roles-less manifest that
|
|
11
|
-
# declares a `kind: workspace` zone is therefore rejected at load (no
|
|
12
|
-
# `keep`-holder); declare `roles:` to opt into a workspace lane (ADR 0033).
|
|
13
|
-
DEFAULT_MAPPING = {
|
|
14
|
-
Textus::Value::Role::HUMAN => %w[author propose].freeze,
|
|
15
|
-
Textus::Value::Role::AGENT => %w[propose].freeze,
|
|
16
|
-
Textus::Value::Role::AUTOMATION => %w[converge].freeze,
|
|
17
|
-
}.freeze
|
|
18
|
-
|
|
19
|
-
# Returns { role_name => [verbs] }. When `roles:` is declared we use
|
|
20
|
-
# exactly that; defaults are *not* layered in (declaring roles is an
|
|
21
|
-
# opt-in to a fully user-defined vocabulary).
|
|
22
|
-
def self.resolve(raw_roles)
|
|
23
|
-
return DEFAULT_MAPPING if raw_roles.nil?
|
|
24
|
-
|
|
25
|
-
raw_roles.to_h { |r| [r["name"], Array(r["can"]).freeze] }.freeze
|
|
26
|
-
end
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
end
|
data/lib/textus/manifest/data.rb
DELETED
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
require_relative "schema"
|
|
2
|
-
require_relative "capabilities"
|
|
3
|
-
|
|
4
|
-
module Textus
|
|
5
|
-
class Manifest
|
|
6
|
-
# Immutable, parsed view of a manifest YAML document.
|
|
7
|
-
#
|
|
8
|
-
# Holds raw structural data (lanes, entries, audit_config, role_caps)
|
|
9
|
-
# but no behaviour beyond accessors. Behaviour (lane authority, key
|
|
10
|
-
# resolution, rules) lives on Manifest::Policy / Resolver / Rules.
|
|
11
|
-
class Data
|
|
12
|
-
AUDIT_DEFAULTS = { max_size: 10_485_760, keep: 5 }.freeze
|
|
13
|
-
WORKER_DEFAULTS = { pool: 4, poll: 5, lease_ttl: 60, max_attempts: 3 }.freeze
|
|
14
|
-
|
|
15
|
-
attr_reader :raw, :root, :entries, :declared_lane_kinds,
|
|
16
|
-
:lane_descs, :lane_owners,
|
|
17
|
-
:audit_config, :worker_config, :role_caps, :policy
|
|
18
|
-
|
|
19
|
-
def self.validate_key!(key)
|
|
20
|
-
raise UsageError.new("empty key") if key.nil? || key.empty?
|
|
21
|
-
|
|
22
|
-
Key::Grammar.validate!(key)
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
# Forwarder used by Resolver and Entry classes that received a Data
|
|
26
|
-
# but were written against the historical Manifest API.
|
|
27
|
-
def validate_key!(key) = self.class.validate_key!(key)
|
|
28
|
-
|
|
29
|
-
def self.parse(raw, root:)
|
|
30
|
-
raise BadFrontmatter.new(File.join(root.to_s, "manifest.yaml"), "manifest must declare lanes:") if Array(raw["lanes"]).empty?
|
|
31
|
-
|
|
32
|
-
Schema.validate!(raw)
|
|
33
|
-
new(raw: raw, root: root)
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def initialize(raw:, root:)
|
|
37
|
-
@raw = raw
|
|
38
|
-
@root = root
|
|
39
|
-
# Write authority is derived from capabilities × lane-kind (ADR 0030),
|
|
40
|
-
# not a per-lane writer list. "Which lanes are declared" lives in the
|
|
41
|
-
# one kind-keyed map below (declared_lane_kinds); membership checks by
|
|
42
|
-
# read-side callers (boot, maintenance/data_mv) use its keyset (ADR 0034).
|
|
43
|
-
@declared_lane_kinds = Array(raw["lanes"]).to_h do |z|
|
|
44
|
-
[z["name"], z["kind"]&.to_sym]
|
|
45
|
-
end
|
|
46
|
-
@lane_descs = Array(raw["lanes"]).to_h { |z| [z["name"], z["desc"]] }
|
|
47
|
-
# Only lanes that actually declare an owner — keep nil-tombstones out so a
|
|
48
|
-
# future `lane_owners.key?(name)` means "owner declared", not "lane exists".
|
|
49
|
-
@lane_owners = Array(raw["lanes"]).to_h { |z| [z["name"], z["owner"]] }.compact
|
|
50
|
-
@audit_config = build_audit_config(raw)
|
|
51
|
-
@worker_config = build_worker_config(raw)
|
|
52
|
-
@role_caps = Capabilities.resolve(raw["roles"])
|
|
53
|
-
# Policy is constructed before entries because Entry validators
|
|
54
|
-
# use the entry's own `derived?` and similar helpers that call into
|
|
55
|
-
# Policy; Policy must exist before entries are built.
|
|
56
|
-
@policy = Policy.new(self)
|
|
57
|
-
@entries = build_entries(raw)
|
|
58
|
-
validate_declared_keys!
|
|
59
|
-
freeze
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
private
|
|
63
|
-
|
|
64
|
-
def build_audit_config(raw)
|
|
65
|
-
a = raw["audit"] || {}
|
|
66
|
-
{
|
|
67
|
-
max_size: a["max_size"] || AUDIT_DEFAULTS[:max_size],
|
|
68
|
-
keep: a["keep"] || AUDIT_DEFAULTS[:keep],
|
|
69
|
-
}.freeze
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
# Worker/queue tunables (ADR: job-queue execution model). All optional;
|
|
73
|
-
# the daemon (serve) and batch drain read these, falling back to defaults
|
|
74
|
-
# so a manifest with no `worker:` block runs the queue out of the box.
|
|
75
|
-
def build_worker_config(raw)
|
|
76
|
-
w = raw["worker"] || {}
|
|
77
|
-
{
|
|
78
|
-
pool: w["pool"] || WORKER_DEFAULTS[:pool],
|
|
79
|
-
poll: w["poll"] || WORKER_DEFAULTS[:poll],
|
|
80
|
-
lease_ttl: w["lease_ttl"] || WORKER_DEFAULTS[:lease_ttl],
|
|
81
|
-
max_attempts: w["max_attempts"] || WORKER_DEFAULTS[:max_attempts],
|
|
82
|
-
}.freeze
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
def build_entries(raw)
|
|
86
|
-
Array(raw["entries"]).map do |e|
|
|
87
|
-
entry = Manifest::Entry::Parser.call(e)
|
|
88
|
-
Manifest::Entry::Validators.run_all(entry, policy: @policy)
|
|
89
|
-
entry
|
|
90
|
-
end.freeze
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
def validate_declared_keys!
|
|
94
|
-
@entries.each do |e|
|
|
95
|
-
raise UsageError.new("empty key") if e.key.nil? || e.key.empty?
|
|
96
|
-
|
|
97
|
-
Key::Grammar.validate!(e.key)
|
|
98
|
-
end
|
|
99
|
-
end
|
|
100
|
-
end
|
|
101
|
-
end
|
|
102
|
-
end
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
class Manifest
|
|
3
|
-
class Entry
|
|
4
|
-
class Base < Entry
|
|
5
|
-
attr_reader :raw, :key, :path, :lane, :schema, :owner, :format, :publish_targets
|
|
6
|
-
|
|
7
|
-
# rubocop:disable Metrics/ParameterLists, Lint/MissingSuper
|
|
8
|
-
def initialize(raw:, key:, path:, lane:, schema:, owner:, format:, publish_targets: [])
|
|
9
|
-
@raw = raw
|
|
10
|
-
@key = key
|
|
11
|
-
@path = path
|
|
12
|
-
@lane = lane
|
|
13
|
-
@schema = schema
|
|
14
|
-
@owner = owner
|
|
15
|
-
@format = format
|
|
16
|
-
@publish_targets = Array(publish_targets)
|
|
17
|
-
end
|
|
18
|
-
# rubocop:enable Metrics/ParameterLists, Lint/MissingSuper
|
|
19
|
-
|
|
20
|
-
def lane_writers(policy)
|
|
21
|
-
verb = policy.verb_for_lane(@lane)
|
|
22
|
-
policy.roles_with_capability(verb)
|
|
23
|
-
rescue UsageError => e
|
|
24
|
-
raise UsageError.new("entry '#{@key}': #{e.message}")
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
def in_proposal_lane?(policy) = policy.queue_lane?(@lane)
|
|
28
|
-
|
|
29
|
-
def nested? = false
|
|
30
|
-
def derived? = false
|
|
31
|
-
def intake? = false
|
|
32
|
-
def leaf? = false
|
|
33
|
-
|
|
34
|
-
# Production traits. Default false on Base (a leaf/intake entry is neither
|
|
35
|
-
# an out-of-band command nor a projection); Produced overrides both from
|
|
36
|
-
# its source. Lets publish modes call these without a `respond_to?` guard.
|
|
37
|
-
def external? = false
|
|
38
|
-
def projection? = false
|
|
39
|
-
|
|
40
|
-
alias zone lane
|
|
41
|
-
alias in_proposal_zone? in_proposal_lane?
|
|
42
|
-
|
|
43
|
-
# Whether git should track this entry's file. Default true; an entry
|
|
44
|
-
# marked `tracked: false` in the manifest stays protocol-readable but is
|
|
45
|
-
# listed in the generated `.gitignore` (ADR 0043). Cross-cutting, so it
|
|
46
|
-
# reads from raw here rather than threading through every constructor.
|
|
47
|
-
def tracked? = @raw["tracked"] != false
|
|
48
|
-
|
|
49
|
-
# Single source of truth is @publish_targets (ADR 0094). These
|
|
50
|
-
# derive the ADR-0049/0052 views the publish modes consume.
|
|
51
|
-
def publish_to = @publish_targets.select(&:to_target?).map(&:to)
|
|
52
|
-
def publish_tree = @publish_targets.find(&:tree_target?)&.tree
|
|
53
|
-
|
|
54
|
-
# Nil stubs for cross-cutting optional attrs. Subclasses override the
|
|
55
|
-
# ones they own. Validators and serializers can call these directly
|
|
56
|
-
# without `respond_to?` guards.
|
|
57
|
-
def events = {}
|
|
58
|
-
def ignore = []
|
|
59
|
-
|
|
60
|
-
# Per-entry ignore (ADR 0042). Base entries enumerate no tree, so
|
|
61
|
-
# nothing is ever ignored; Nested overrides with real patterns.
|
|
62
|
-
def ignored?(_rel_path) = false
|
|
63
|
-
|
|
64
|
-
# Minimal context object passed into entry `publish_via` hooks.
|
|
65
|
-
# Everything beyond the three primitives is derived.
|
|
66
|
-
PublishContext = ::Data.define(:container, :call, :reader) do
|
|
67
|
-
def manifest = container.manifest
|
|
68
|
-
def root = container.root
|
|
69
|
-
def repo_root = File.dirname(container.root)
|
|
70
|
-
|
|
71
|
-
# No-op: event bus removed in workflow redesign; callers that fire
|
|
72
|
-
# :entry_published / :entry_produced remain unchanged in the source.
|
|
73
|
-
def emit(_event, **_payload) = nil
|
|
74
|
-
|
|
75
|
-
# Read a named template from the store's templates/ directory.
|
|
76
|
-
# Raises TemplateError when the file doesn't exist.
|
|
77
|
-
def read_template(name)
|
|
78
|
-
path = container.geometry.template_path(name)
|
|
79
|
-
unless File.exist?(path)
|
|
80
|
-
raise Textus::TemplateError.new(
|
|
81
|
-
"template '#{name}' not found",
|
|
82
|
-
template_name: name,
|
|
83
|
-
)
|
|
84
|
-
end
|
|
85
|
-
File.read(path)
|
|
86
|
-
end
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
# ADR 0049: an entry resolves, once, to one Publish::* mode that owns its
|
|
90
|
-
# publish algorithm. A plain entry publishes via ToPaths (publish_to) or
|
|
91
|
-
# None; Nested resolves among the key/path-driven modes. Derived
|
|
92
|
-
# overrides publish_via to materialize first.
|
|
93
|
-
def publish_mode
|
|
94
|
-
@publish_mode ||= Publish.resolve(self)
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
# Returns: { kind: :built|:leaves, value: ... } to be accumulated by
|
|
98
|
-
# Write::Build, or nil to skip.
|
|
99
|
-
def publish_via(pctx, prefix: nil)
|
|
100
|
-
publish_mode.publish(pctx, prefix: prefix)
|
|
101
|
-
end
|
|
102
|
-
end
|
|
103
|
-
end
|
|
104
|
-
end
|
|
105
|
-
end
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
class Manifest
|
|
3
|
-
class Entry
|
|
4
|
-
# Pure glob matcher backing per-entry `ignore:` patterns (ADR 0042).
|
|
5
|
-
# `rel_path` is the slash-joined path of a candidate file relative to the
|
|
6
|
-
# entry's base directory.
|
|
7
|
-
#
|
|
8
|
-
# Matching is segment-wise so the `**` globstar means "zero or more path
|
|
9
|
-
# segments" — `File.fnmatch` alone cannot express this (under
|
|
10
|
-
# FNM_PATHNAME a trailing `**` will not cross a `/`; without it a leading
|
|
11
|
-
# `**/` will not match zero leading segments). So `**/node_modules/**`
|
|
12
|
-
# catches the `node_modules` subtree at any depth, including the store
|
|
13
|
-
# root, and the directory entry itself.
|
|
14
|
-
#
|
|
15
|
-
# Within a single segment, matching delegates to `File.fnmatch` with
|
|
16
|
-
# FNM_EXTGLOB, so a single `*` is anchored to that segment (it does not
|
|
17
|
-
# cross `/`) and `{a,b}` alternation works.
|
|
18
|
-
module IgnoreMatcher
|
|
19
|
-
SEGMENT_FLAGS = File::FNM_EXTGLOB
|
|
20
|
-
|
|
21
|
-
def self.match?(patterns, rel_path)
|
|
22
|
-
path_segs = rel_path.split("/").reject(&:empty?)
|
|
23
|
-
Array(patterns).any? do |pat|
|
|
24
|
-
match_segments(pat.split("/").reject(&:empty?), path_segs)
|
|
25
|
-
end
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
# Classic globstar matcher. `**` matches zero or more whole segments;
|
|
29
|
-
# any other pattern segment matches exactly one path segment via fnmatch.
|
|
30
|
-
def self.match_segments(pat_segs, path_segs)
|
|
31
|
-
return path_segs.empty? if pat_segs.empty?
|
|
32
|
-
|
|
33
|
-
if pat_segs.first == "**"
|
|
34
|
-
match_segments(pat_segs[1..], path_segs) ||
|
|
35
|
-
(!path_segs.empty? && match_segments(pat_segs, path_segs[1..]))
|
|
36
|
-
else
|
|
37
|
-
!path_segs.empty? &&
|
|
38
|
-
File.fnmatch?(pat_segs.first, path_segs.first, SEGMENT_FLAGS) &&
|
|
39
|
-
match_segments(pat_segs[1..], path_segs[1..])
|
|
40
|
-
end
|
|
41
|
-
end
|
|
42
|
-
private_class_method :match_segments
|
|
43
|
-
end
|
|
44
|
-
end
|
|
45
|
-
end
|
|
46
|
-
end
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
class Manifest
|
|
3
|
-
class Entry
|
|
4
|
-
# A directory entry: enumerates a tree of leaves and resolves to one
|
|
5
|
-
# publish mode (ADR 0049). The publish algorithms themselves live in
|
|
6
|
-
# Entry::Publish::* — Nested is just the value (attributes + ignore
|
|
7
|
-
# predicate) those modes read.
|
|
8
|
-
class Nested < Base
|
|
9
|
-
attr_reader :ignore
|
|
10
|
-
|
|
11
|
-
def initialize(ignore: nil, **rest)
|
|
12
|
-
super(**rest)
|
|
13
|
-
@ignore = Array(ignore)
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
def nested? = true
|
|
17
|
-
|
|
18
|
-
# True when `rel_path` (slash-joined, relative to the entry base) matches
|
|
19
|
-
# any configured ignore glob. Evaluated ABOVE key-legality (ADR 0042):
|
|
20
|
-
# an ignored path is excluded, never judged.
|
|
21
|
-
def ignored?(rel_path) = IgnoreMatcher.match?(@ignore, rel_path)
|
|
22
|
-
|
|
23
|
-
KIND = :nested
|
|
24
|
-
|
|
25
|
-
def self.from_raw(common, raw)
|
|
26
|
-
# publish_tree is derived from publish_targets (ADR 0094) via Base#publish_tree
|
|
27
|
-
new(
|
|
28
|
-
ignore: raw["ignore"],
|
|
29
|
-
**common,
|
|
30
|
-
)
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
Entry::REGISTRY[KIND] = self
|
|
34
|
-
end
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
end
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
class Manifest
|
|
3
|
-
class Entry
|
|
4
|
-
module Parser
|
|
5
|
-
def self.call(raw)
|
|
6
|
-
key = raw["key"] or raise UsageError.new("manifest entry missing key")
|
|
7
|
-
lane = raw["lane"] or raise UsageError.new("manifest entry '#{key}' missing lane")
|
|
8
|
-
|
|
9
|
-
raw_kind = raw["kind"] or raise BadManifest.new("entry '#{key}' missing required `kind:` (#{Entry::REGISTRY.keys.join("|")})")
|
|
10
|
-
kind = raw_kind.to_sym
|
|
11
|
-
if %i[derived intake].include?(kind)
|
|
12
|
-
raise BadManifest.new(
|
|
13
|
-
"entry '#{key}': kind: #{kind} was collapsed into `kind: produced` (ADR 0095) — " \
|
|
14
|
-
"the produce method is `source.from` (#{kind == :intake ? "handler" : "project|command"})",
|
|
15
|
-
)
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
explicit_path = raw["path"]
|
|
19
|
-
format = resolve_format(raw, explicit_path)
|
|
20
|
-
path = explicit_path || derive_path(key, kind, format)
|
|
21
|
-
|
|
22
|
-
common = {
|
|
23
|
-
raw: raw,
|
|
24
|
-
key: key, path: path, lane: lane,
|
|
25
|
-
schema: raw["schema"], owner: raw["owner"],
|
|
26
|
-
format: format,
|
|
27
|
-
publish_targets: publish_targets(raw)
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
klass = Entry::REGISTRY[kind] or
|
|
31
|
-
raise BadManifest.new("entry '#{key}': unknown kind: #{kind.inspect} (known: #{Entry::REGISTRY.keys.join(", ")})")
|
|
32
|
-
klass.from_raw(common, raw)
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
# Parse the optional `source:` block. Returns nil when absent (workflow
|
|
36
|
-
# produced entries register their produce logic in .textus/workflows/).
|
|
37
|
-
def self.parse_source(raw, _key)
|
|
38
|
-
block = raw["source"]
|
|
39
|
-
return nil if block.nil?
|
|
40
|
-
|
|
41
|
-
Textus::Manifest::Policy::Source.new(block)
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
# ADR 0094: `publish:` is a LIST of target objects — to-targets
|
|
45
|
-
# [{to, template?, inject_boot?}] and/or a tree-target [{tree}]. The
|
|
46
|
-
# ADR-0052 map forms ({to: […]} / {tree: …}) are retired.
|
|
47
|
-
def self.publish_targets(raw)
|
|
48
|
-
block = raw["publish"]
|
|
49
|
-
return [] if block.nil?
|
|
50
|
-
|
|
51
|
-
unless block.is_a?(Array)
|
|
52
|
-
raise BadManifest.new(
|
|
53
|
-
"entry '#{raw["key"]}': `publish:` must be a list of targets " \
|
|
54
|
-
"[{to:, template:?} | {tree:}] (ADR 0094); the `publish: { … }` map form was retired",
|
|
55
|
-
)
|
|
56
|
-
end
|
|
57
|
-
block.map { |t| Textus::Manifest::Policy::PublishTarget.new(t) }
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
def self.resolve_format(raw, path)
|
|
61
|
-
declared = raw["format"]
|
|
62
|
-
|
|
63
|
-
return declared || "markdown" if path.nil? || path.empty?
|
|
64
|
-
|
|
65
|
-
ext = File.extname(path)
|
|
66
|
-
inferred = Textus::Format.infer_from_extension(ext)
|
|
67
|
-
|
|
68
|
-
if declared.nil?
|
|
69
|
-
return inferred if inferred
|
|
70
|
-
|
|
71
|
-
return "markdown"
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
raise UsageError.new("entry '#{raw["key"]}': unknown format #{declared.inspect}") unless Textus::Format.formats.include?(declared)
|
|
75
|
-
|
|
76
|
-
if ext != "" && inferred && inferred != declared
|
|
77
|
-
raise UsageError.new(
|
|
78
|
-
"entry '#{raw["key"]}': path extension #{ext.inspect} does not match declared format #{declared.inspect}",
|
|
79
|
-
)
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
declared
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
# Derives the manifest-relative path from key + kind + format.
|
|
86
|
-
# Key::Path.normalize_relative_path will prepend data/ at resolution time.
|
|
87
|
-
def self.derive_path(key, kind_sym, format)
|
|
88
|
-
dir_path = key.split(".").join("/")
|
|
89
|
-
return dir_path if kind_sym == :nested
|
|
90
|
-
|
|
91
|
-
ext = Textus::Format.for(format).extensions.first
|
|
92
|
-
"#{dir_path}#{ext}"
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
private_class_method :derive_path
|
|
96
|
-
end
|
|
97
|
-
end
|
|
98
|
-
end
|
|
99
|
-
end
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
class Manifest
|
|
3
|
-
class Entry
|
|
4
|
-
# A produced entry — `kind: produced` with an optional `source:` block.
|
|
5
|
-
# When `source:` is present it must be `from: external` (out-of-band
|
|
6
|
-
# generator; textus detects drift but never runs it). When absent the
|
|
7
|
-
# entry is produced by a workflow file in .textus/workflows/.
|
|
8
|
-
class Produced < Base
|
|
9
|
-
attr_reader :source
|
|
10
|
-
|
|
11
|
-
def initialize(source:, **rest)
|
|
12
|
-
super(**rest)
|
|
13
|
-
@source = source
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
def external? = @source&.external? || false
|
|
17
|
-
def nested? = !!@raw["nested"]
|
|
18
|
-
|
|
19
|
-
KIND = :produced
|
|
20
|
-
|
|
21
|
-
# Publish existing store bytes via the shared publish mode (Publish::ToPaths
|
|
22
|
-
# or Publish::None). Workflow runners handle the produce step; this method
|
|
23
|
-
# only publishes whatever bytes are already on disk.
|
|
24
|
-
def publish_via(pctx, prefix: nil)
|
|
25
|
-
publish_mode.publish(pctx, prefix: prefix)
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
def self.from_raw(common, raw)
|
|
29
|
-
new(source: Parser.parse_source(raw, common[:key]), **common)
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
Entry::REGISTRY[KIND] = self
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
end
|
|
36
|
-
end
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
class Manifest
|
|
3
|
-
class Entry
|
|
4
|
-
module Publish
|
|
5
|
-
# Base for every publish mode: wraps the resolved entry and owns the one
|
|
6
|
-
# repo-root escape guard the writing modes share (ADR 0049). Subclasses
|
|
7
|
-
# implement `#publish(pctx, prefix:)` returning the existing
|
|
8
|
-
# `{ kind:, value:, pruned: }` shape (or nil), and `#validate!` for the
|
|
9
|
-
# per-mode shape rules reached *because* this mode resolved.
|
|
10
|
-
class Mode
|
|
11
|
-
def initialize(entry)
|
|
12
|
-
@entry = entry
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
attr_reader :entry
|
|
16
|
-
|
|
17
|
-
# No shape rules by default — ToPaths/None publish without templating.
|
|
18
|
-
def validate!; end
|
|
19
|
-
|
|
20
|
-
# Whether this entry's subtree files are opaque payload that must
|
|
21
|
-
# never be enumerated as keys. Only Tree (publish_tree, ADR 0047)
|
|
22
|
-
# overrides to true; doctor's IllegalKeys and the resolver consult
|
|
23
|
-
# this so they stop key-walking a keyless mirror's files.
|
|
24
|
-
def keyless? = false
|
|
25
|
-
|
|
26
|
-
private
|
|
27
|
-
|
|
28
|
-
# Expand `rel` under repo_root and confirm it stays inside it.
|
|
29
|
-
def repo_abs(pctx, rel)
|
|
30
|
-
File.expand_path(File.join(pctx.repo_root, rel))
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
def inside_repo?(pctx, abs)
|
|
34
|
-
abs.start_with?(File.expand_path(pctx.repo_root) + File::SEPARATOR)
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
# Store-side directory this entry's tree lives under.
|
|
38
|
-
def store_base(pctx)
|
|
39
|
-
File.join(pctx.root, Textus::Key::Path.normalize_relative_path(entry.path))
|
|
40
|
-
end
|
|
41
|
-
end
|
|
42
|
-
end
|
|
43
|
-
end
|
|
44
|
-
end
|
|
45
|
-
end
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
class Manifest
|
|
3
|
-
class Entry
|
|
4
|
-
module Publish
|
|
5
|
-
# An entry with no publish_* key — nothing to publish.
|
|
6
|
-
class None < Mode
|
|
7
|
-
def publish(_pctx, prefix: nil) # rubocop:disable Lint/UnusedMethodArgument
|
|
8
|
-
nil
|
|
9
|
-
end
|
|
10
|
-
end
|
|
11
|
-
end
|
|
12
|
-
end
|
|
13
|
-
end
|
|
14
|
-
end
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
class Manifest
|
|
3
|
-
class Entry
|
|
4
|
-
module Publish
|
|
5
|
-
# ADR 0049: the one walk->publish->prune pipeline behind Tree (whole-entry
|
|
6
|
-
# mirror, ADR 0047). It was once shared with the per-leaf publish_each
|
|
7
|
-
# mode too; ADR 0051 removed publish_each, leaving Tree the only caller.
|
|
8
|
-
# The `walk_root`/`prune_honors_ignore:` parameters survive from that
|
|
9
|
-
# shared shape — Tree always walks at `base` and honors `ignore` in the
|
|
10
|
-
# prune (ADR 0047 D4, so a derived index in the mirrored dir survives).
|
|
11
|
-
class SubtreeMirror
|
|
12
|
-
def initialize(entry, pctx, publisher: Textus::Port::Publisher.new)
|
|
13
|
-
@entry = entry
|
|
14
|
-
@pctx = pctx
|
|
15
|
-
@publisher = publisher
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
# base: store dir the entry owns — the root `ignored?` globs are
|
|
19
|
-
# relative to (ADR 0042).
|
|
20
|
-
# walk_root: dir the glob is rooted at (== base for Tree). dst paths
|
|
21
|
-
# mirror rel-to-walk_root.
|
|
22
|
-
# target_dir: repo-side destination root.
|
|
23
|
-
# key/envelope: emitted per file; envelope is nil for the keyless Tree.
|
|
24
|
-
# prune_honors_ignore: when true a managed file the entry `ignore`s
|
|
25
|
-
# survives the prune (ADR 0047 D4 — lets a derived index live in the
|
|
26
|
-
# mirrored dir); when false every unwritten managed file is pruned.
|
|
27
|
-
def mirror(base:, walk_root:, target_dir:, key:, envelope:, prune_honors_ignore:)
|
|
28
|
-
return { written: [], pruned: [] } unless File.directory?(walk_root)
|
|
29
|
-
|
|
30
|
-
written = publish_files(base: base, walk_root: walk_root, target_dir: target_dir, key: key, envelope: envelope)
|
|
31
|
-
{ written: written, pruned: prune(target_dir, written, prune_honors_ignore) }
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
private
|
|
35
|
-
|
|
36
|
-
def publish_files(base:, walk_root:, target_dir:, key:, envelope:)
|
|
37
|
-
# FNM_DOTMATCH includes dotfiles; File.file? below skips dirs (and
|
|
38
|
-
# symlinks-to-dirs). Trees are authored content, not symlink graphs.
|
|
39
|
-
Dir.glob(File.join(walk_root, "**", "*"), File::FNM_DOTMATCH).sort.filter_map do |src|
|
|
40
|
-
next nil unless File.file?(src)
|
|
41
|
-
next nil if @entry.ignored?(relative(src, base))
|
|
42
|
-
|
|
43
|
-
dst = File.join(target_dir, relative(src, walk_root))
|
|
44
|
-
@publisher.publish(source: src, target: dst, store_root: @pctx.root)
|
|
45
|
-
@pctx.emit(:entry_published, key: key, envelope: envelope, source: src, target: dst)
|
|
46
|
-
{ "key" => key, "source" => src, "target" => dst }
|
|
47
|
-
end
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
# Scoped to target_dir. Safe across leaves because ADR 0046 D5
|
|
51
|
-
# (shallowest-index-wins) keeps leaf target dirs non-nesting, so
|
|
52
|
-
# targets_under can't reach another leaf's sentinels.
|
|
53
|
-
def prune(target_dir, written, honor_ignore)
|
|
54
|
-
kept = written.map { |w| File.expand_path(w["target"]) }
|
|
55
|
-
store = Textus::Port::SentinelStore.new
|
|
56
|
-
store.targets_under(target_dir, @pctx.root).filter_map do |managed|
|
|
57
|
-
abs = File.expand_path(managed)
|
|
58
|
-
next nil if kept.include?(abs)
|
|
59
|
-
next nil if honor_ignore && @entry.ignored?(relative(abs, target_dir))
|
|
60
|
-
|
|
61
|
-
@publisher.unpublish(target: managed, store_root: @pctx.root)
|
|
62
|
-
managed
|
|
63
|
-
end
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
def relative(path, root)
|
|
67
|
-
path.sub(%r{\A#{Regexp.escape(root)}/}, "")
|
|
68
|
-
end
|
|
69
|
-
end
|
|
70
|
-
end
|
|
71
|
-
end
|
|
72
|
-
end
|
|
73
|
-
end
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
class Manifest
|
|
3
|
-
class Entry
|
|
4
|
-
module Publish
|
|
5
|
-
# Template-variable detection for publish targets. The only surviving
|
|
6
|
-
# use after ADR 0051 (which removed publish_each and its `{leaf}`/
|
|
7
|
-
# `{basename}`/`{key}`/`{ext}` vocabulary) is Tree.validate!, which uses
|
|
8
|
-
# VAR_RE to reject any `{var}` in a publish_tree value — that key names a
|
|
9
|
-
# single directory by plain path and interprets no variables.
|
|
10
|
-
module Template
|
|
11
|
-
VAR_RE = /\{([a-z]+)\}/
|
|
12
|
-
end
|
|
13
|
-
end
|
|
14
|
-
end
|
|
15
|
-
end
|
|
16
|
-
end
|