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
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
class Manifest
|
|
3
|
-
class Entry
|
|
4
|
-
module Validators
|
|
5
|
-
module FormatMatrix
|
|
6
|
-
def self.call(entry, policy:) # rubocop:disable Lint/UnusedMethodArgument
|
|
7
|
-
begin
|
|
8
|
-
Textus::Format.for(entry.format).validate_path_extension(entry.path, entry.nested?)
|
|
9
|
-
rescue UsageError => e
|
|
10
|
-
raise UsageError.new("entry '#{entry.key}': #{e.message}")
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
return unless entry.format == "text" && !entry.schema.nil?
|
|
14
|
-
|
|
15
|
-
raise UsageError.new("entry '#{entry.key}': text format must not declare a schema")
|
|
16
|
-
end
|
|
17
|
-
end
|
|
18
|
-
end
|
|
19
|
-
end
|
|
20
|
-
end
|
|
21
|
-
end
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
class Manifest
|
|
3
|
-
class Entry
|
|
4
|
-
module Validators
|
|
5
|
-
# Validates the per-entry `ignore:` field (ADR 0042): a list of
|
|
6
|
-
# non-empty glob strings, allowed only on nested entries.
|
|
7
|
-
module Ignore
|
|
8
|
-
def self.call(entry, policy: nil) # rubocop:disable Lint/UnusedMethodArgument
|
|
9
|
-
patterns = entry.raw["ignore"]
|
|
10
|
-
return if patterns.nil?
|
|
11
|
-
|
|
12
|
-
raise UsageError.new("entry '#{entry.key}': ignore requires nested: true") unless entry.nested?
|
|
13
|
-
|
|
14
|
-
raise UsageError.new("entry '#{entry.key}': ignore must be a list of glob strings") unless patterns.is_a?(Array)
|
|
15
|
-
|
|
16
|
-
patterns.each do |pat|
|
|
17
|
-
next if pat.is_a?(String) && !pat.empty?
|
|
18
|
-
|
|
19
|
-
raise UsageError.new(
|
|
20
|
-
"entry '#{entry.key}': each ignore pattern must be a non-empty string (got #{pat.inspect})",
|
|
21
|
-
)
|
|
22
|
-
end
|
|
23
|
-
end
|
|
24
|
-
end
|
|
25
|
-
end
|
|
26
|
-
end
|
|
27
|
-
end
|
|
28
|
-
end
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
class Manifest
|
|
3
|
-
class Entry
|
|
4
|
-
module Validators
|
|
5
|
-
# ADR 0049: one publish validator. Exclusivity among the publish keys is
|
|
6
|
-
# enforced structurally by Publish.resolve (reached via #publish_mode),
|
|
7
|
-
# and each mode's shape rules run *because that mode resolved* — replacing
|
|
8
|
-
# the scattered pairwise "not-both" guards of the old PublishEach +
|
|
9
|
-
# PublishTree validators. Misuse on a non-nested entry is still caught
|
|
10
|
-
# here from raw, since the typed attrs stub nil on Base. (publish_each was
|
|
11
|
-
# removed in 0.42.0 — ADR 0051; the publish_to/publish_tree keys were folded
|
|
12
|
-
# into the `publish:` block in 0.43.0 — ADR 0052; Schema rejects the retired
|
|
13
|
-
# flat keys at load.)
|
|
14
|
-
module Publish
|
|
15
|
-
def self.call(entry, policy: nil) # rubocop:disable Lint/UnusedMethodArgument
|
|
16
|
-
unless entry.nested?
|
|
17
|
-
# ADR 0094: publish: is now a list; use publish_tree (derived reader)
|
|
18
|
-
# rather than raw.dig("publish", "tree") which breaks on an Array.
|
|
19
|
-
raise UsageError.new("entry '#{entry.key}': publish.tree requires nested: true") if entry.publish_tree
|
|
20
|
-
|
|
21
|
-
return
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
entry.publish_mode.validate!
|
|
25
|
-
end
|
|
26
|
-
end
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
end
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
class Manifest
|
|
3
|
-
class Entry
|
|
4
|
-
module Validators
|
|
5
|
-
REGISTERED = [
|
|
6
|
-
Publish,
|
|
7
|
-
Ignore,
|
|
8
|
-
FormatMatrix,
|
|
9
|
-
].freeze
|
|
10
|
-
|
|
11
|
-
def self.run_all(entry, policy:)
|
|
12
|
-
REGISTERED.each { |v| v.call(entry, policy: policy) }
|
|
13
|
-
nil
|
|
14
|
-
end
|
|
15
|
-
end
|
|
16
|
-
end
|
|
17
|
-
end
|
|
18
|
-
end
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
class Manifest
|
|
3
|
-
class Policy
|
|
4
|
-
module Matcher
|
|
5
|
-
module_function
|
|
6
|
-
|
|
7
|
-
def matches?(glob, key)
|
|
8
|
-
glob_segs = glob.split(".")
|
|
9
|
-
key_segs = key.split(".")
|
|
10
|
-
consume(glob_segs, key_segs)
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
def specificity(glob)
|
|
14
|
-
glob.split(".").reduce(0) do |s, seg|
|
|
15
|
-
s + case seg
|
|
16
|
-
when "**" then 0
|
|
17
|
-
when "*" then 1
|
|
18
|
-
else 10
|
|
19
|
-
end
|
|
20
|
-
end
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
def pick_most_specific(globs, key:)
|
|
24
|
-
matching = globs.select { |g| matches?(g, key) }
|
|
25
|
-
return nil if matching.empty?
|
|
26
|
-
|
|
27
|
-
matching.max_by { |g| [specificity(g), -g.length, g] }
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
def self.consume(glob_segs, key_segs)
|
|
31
|
-
return key_segs.empty? if glob_segs.empty?
|
|
32
|
-
|
|
33
|
-
head = glob_segs.first
|
|
34
|
-
rest = glob_segs[1..]
|
|
35
|
-
|
|
36
|
-
if head == "**"
|
|
37
|
-
return true if rest.empty?
|
|
38
|
-
|
|
39
|
-
(0..key_segs.length).any? { |i| consume(rest, key_segs[i..]) }
|
|
40
|
-
elsif key_segs.empty?
|
|
41
|
-
false
|
|
42
|
-
elsif head == "*" || head == key_segs.first
|
|
43
|
-
consume(rest, key_segs[1..])
|
|
44
|
-
else
|
|
45
|
-
false
|
|
46
|
-
end
|
|
47
|
-
end
|
|
48
|
-
end
|
|
49
|
-
end
|
|
50
|
-
end
|
|
51
|
-
end
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
class Manifest
|
|
3
|
-
class Policy
|
|
4
|
-
module Predicates
|
|
5
|
-
class AuthorHeld
|
|
6
|
-
def self.call(manifest:, actor:, action:, key:, schemas: nil, envelope: nil, extra: {})
|
|
7
|
-
holders = manifest.policy.roles_with_capability("author")
|
|
8
|
-
pass = holders.include?(actor.to_s)
|
|
9
|
-
reason = if pass
|
|
10
|
-
nil
|
|
11
|
-
elsif holders.empty?
|
|
12
|
-
"no role holds the 'author' capability; #{action} is disabled"
|
|
13
|
-
else
|
|
14
|
-
"role '#{actor}' lacks the 'author' capability (held by: #{holders.join(", ")})"
|
|
15
|
-
end
|
|
16
|
-
{ pass:, reason: }
|
|
17
|
-
end
|
|
18
|
-
end
|
|
19
|
-
end
|
|
20
|
-
end
|
|
21
|
-
end
|
|
22
|
-
end
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
class Manifest
|
|
3
|
-
class Policy
|
|
4
|
-
module Predicates
|
|
5
|
-
class EtagMatch
|
|
6
|
-
def self.call(manifest:, actor:, action:, key:, schemas: nil, envelope: nil, extra: {})
|
|
7
|
-
if_etag = extra[:if_etag]
|
|
8
|
-
return { pass: true } if if_etag.nil?
|
|
9
|
-
|
|
10
|
-
current = envelope&.etag
|
|
11
|
-
pass = current.nil? || current == if_etag
|
|
12
|
-
{ pass:, error: pass ? nil : Textus::EtagMismatch.new(key, if_etag, current) }
|
|
13
|
-
end
|
|
14
|
-
end
|
|
15
|
-
end
|
|
16
|
-
end
|
|
17
|
-
end
|
|
18
|
-
end
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
class Manifest
|
|
3
|
-
class Policy
|
|
4
|
-
module Predicates
|
|
5
|
-
class LaneDeletableBy
|
|
6
|
-
def self.call(manifest:, actor:, action:, key:, schemas: nil, envelope: nil, extra: {})
|
|
7
|
-
return { pass: true } if key.nil?
|
|
8
|
-
|
|
9
|
-
mentry = manifest.resolver.resolve(key).entry
|
|
10
|
-
is_raw = manifest.policy.declared_kind(mentry.lane.to_s) == :raw
|
|
11
|
-
lane_verb = manifest.policy.verb_for_lane(mentry.lane.to_s)
|
|
12
|
-
caps = Set.new(manifest.data.role_caps.fetch(actor.to_s, []))
|
|
13
|
-
|
|
14
|
-
pass = if is_raw
|
|
15
|
-
caps.include?("author")
|
|
16
|
-
else
|
|
17
|
-
caps.include?(lane_verb.to_s) || caps.include?("author")
|
|
18
|
-
end
|
|
19
|
-
return { pass: true } if pass
|
|
20
|
-
|
|
21
|
-
extra_holders = is_raw ? ["author"] : [lane_verb.to_s, "author"]
|
|
22
|
-
holders = extra_holders.flat_map { |v| manifest.policy.roles_with_capability(v) }.uniq
|
|
23
|
-
{ pass: false, error: Textus::WriteForbidden.new(mentry.key, mentry.lane, verb: lane_verb, holders:) }
|
|
24
|
-
rescue Textus::UnknownKey
|
|
25
|
-
{ pass: true }
|
|
26
|
-
end
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
end
|
|
31
|
-
end
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
class Manifest
|
|
3
|
-
class Policy
|
|
4
|
-
module Predicates
|
|
5
|
-
class LaneWritableBy
|
|
6
|
-
def self.call(manifest:, actor:, action:, key:, schemas: nil, envelope: nil, extra: {})
|
|
7
|
-
return { pass: true } if key.nil?
|
|
8
|
-
|
|
9
|
-
mentry = manifest.resolver.resolve(key).entry
|
|
10
|
-
lane_verb = manifest.policy.verb_for_lane(mentry.lane.to_s)
|
|
11
|
-
caps = Set.new(manifest.data.role_caps.fetch(actor.to_s, []))
|
|
12
|
-
return { pass: true } if caps.include?(lane_verb.to_s)
|
|
13
|
-
|
|
14
|
-
holders = manifest.policy.roles_with_capability(lane_verb.to_s)
|
|
15
|
-
{ pass: false, error: Textus::WriteForbidden.new(mentry.key, mentry.lane, verb: lane_verb, holders:) }
|
|
16
|
-
rescue Textus::UnknownKey
|
|
17
|
-
{ pass: true }
|
|
18
|
-
end
|
|
19
|
-
end
|
|
20
|
-
end
|
|
21
|
-
end
|
|
22
|
-
end
|
|
23
|
-
end
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
class Manifest
|
|
3
|
-
class Policy
|
|
4
|
-
module Predicates
|
|
5
|
-
class RawLaneIngestOnly
|
|
6
|
-
def self.call(manifest:, actor:, action:, key:, schemas: nil, envelope: nil, extra: {})
|
|
7
|
-
return { pass: true } if key.nil?
|
|
8
|
-
|
|
9
|
-
mentry = manifest.resolver.resolve(key).entry
|
|
10
|
-
return { pass: true } unless manifest.policy.declared_kind(mentry.lane.to_s) == :raw
|
|
11
|
-
return { pass: true } if action == :ingest
|
|
12
|
-
|
|
13
|
-
{ pass: false, error: Textus::Error.new(
|
|
14
|
-
:raw_lane_ingest_only,
|
|
15
|
-
"raw lane '#{mentry.lane}' only accepts `textus ingest` — " \
|
|
16
|
-
"use that verb instead of '#{action}'",
|
|
17
|
-
) }
|
|
18
|
-
rescue Textus::UnknownKey
|
|
19
|
-
{ pass: true }
|
|
20
|
-
end
|
|
21
|
-
end
|
|
22
|
-
end
|
|
23
|
-
end
|
|
24
|
-
end
|
|
25
|
-
end
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
class Manifest
|
|
3
|
-
class Policy
|
|
4
|
-
module Predicates
|
|
5
|
-
class RawWriteOnce
|
|
6
|
-
def self.call(manifest:, actor:, action:, key:, schemas: nil, envelope: nil, extra: {})
|
|
7
|
-
return { pass: true } if key.nil?
|
|
8
|
-
|
|
9
|
-
path = manifest.resolver.resolve(key).path
|
|
10
|
-
return { pass: true } unless File.exist?(path)
|
|
11
|
-
|
|
12
|
-
{ pass: false, error: Textus::Error.new(
|
|
13
|
-
:raw_write_once,
|
|
14
|
-
"raw entry '#{key}' already exists; " \
|
|
15
|
-
"delete it first (`textus key-delete #{key}`), then re-ingest",
|
|
16
|
-
) }
|
|
17
|
-
rescue Textus::UnknownKey
|
|
18
|
-
{ pass: true }
|
|
19
|
-
end
|
|
20
|
-
end
|
|
21
|
-
end
|
|
22
|
-
end
|
|
23
|
-
end
|
|
24
|
-
end
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
class Manifest
|
|
3
|
-
class Policy
|
|
4
|
-
module Predicates
|
|
5
|
-
class SchemaValid
|
|
6
|
-
def self.call(manifest:, actor:, action:, key:, schemas: nil, envelope: nil, extra: {})
|
|
7
|
-
return { pass: true } unless envelope
|
|
8
|
-
return { pass: true } if key.nil?
|
|
9
|
-
|
|
10
|
-
mentry = manifest.resolver.resolve(key).entry
|
|
11
|
-
schema_ref = mentry.schema
|
|
12
|
-
return { pass: true } unless schema_ref
|
|
13
|
-
return { pass: true } unless schemas
|
|
14
|
-
|
|
15
|
-
schema = schemas.fetch_or_nil(schema_ref)
|
|
16
|
-
return { pass: true } unless schema
|
|
17
|
-
|
|
18
|
-
frontmatter = envelope.meta&.dig("_meta") || envelope.meta || {}
|
|
19
|
-
begin
|
|
20
|
-
schema.validate!(frontmatter)
|
|
21
|
-
{ pass: true }
|
|
22
|
-
rescue Textus::SchemaViolation => e
|
|
23
|
-
{ pass: false, reason: schema_reason(e) }
|
|
24
|
-
end
|
|
25
|
-
rescue Textus::UnknownKey
|
|
26
|
-
{ pass: true }
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
def self.schema_reason(err)
|
|
30
|
-
d = err.details
|
|
31
|
-
return err.message.dup unless d.is_a?(Hash)
|
|
32
|
-
return "missing required fields: #{Array(d["missing"]).join(", ")}" if d["missing"]
|
|
33
|
-
return "field '#{d["field"]}': #{d["reason"]}" if d["field"]
|
|
34
|
-
|
|
35
|
-
err.message.dup
|
|
36
|
-
end
|
|
37
|
-
end
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
|
-
end
|
|
41
|
-
end
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
class Manifest
|
|
3
|
-
class Policy
|
|
4
|
-
module Predicates
|
|
5
|
-
class TargetIsCanon
|
|
6
|
-
def self.call(manifest:, actor:, action:, key:, schemas: nil, envelope: nil, extra: {})
|
|
7
|
-
return { pass: true } if key.nil?
|
|
8
|
-
|
|
9
|
-
mentry = manifest.resolver.resolve(key).entry
|
|
10
|
-
kind = manifest.policy.declared_kind(mentry.lane.to_s)
|
|
11
|
-
pass = kind == :canon
|
|
12
|
-
{ pass:, reason: pass ? nil : "target lane '#{mentry.lane}' is not canon (kind: #{kind})" }
|
|
13
|
-
rescue Textus::UnknownKey
|
|
14
|
-
{ pass: true }
|
|
15
|
-
end
|
|
16
|
-
end
|
|
17
|
-
end
|
|
18
|
-
end
|
|
19
|
-
end
|
|
20
|
-
end
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
class Manifest
|
|
3
|
-
class Policy
|
|
4
|
-
module Predicates
|
|
5
|
-
FLOOR = {
|
|
6
|
-
put: %w[lane_writable_by raw_lane_ingest_only],
|
|
7
|
-
key_delete: %w[lane_deletable_by],
|
|
8
|
-
key_mv: %w[lane_writable_by raw_lane_ingest_only],
|
|
9
|
-
accept: %w[author_held],
|
|
10
|
-
reject: %w[author_held],
|
|
11
|
-
propose: %w[lane_writable_by raw_lane_ingest_only],
|
|
12
|
-
key_mv_prefix: %w[lane_writable_by raw_lane_ingest_only],
|
|
13
|
-
key_delete_prefix: %w[lane_writable_by raw_lane_ingest_only],
|
|
14
|
-
ingest: %w[lane_writable_by raw_write_once],
|
|
15
|
-
}.freeze
|
|
16
|
-
|
|
17
|
-
CLASSES = {
|
|
18
|
-
"lane_writable_by" => "LaneWritableBy",
|
|
19
|
-
"author_held" => "AuthorHeld",
|
|
20
|
-
"target_is_canon" => "TargetIsCanon",
|
|
21
|
-
"etag_match" => "EtagMatch",
|
|
22
|
-
"schema_valid" => "SchemaValid",
|
|
23
|
-
"fresh_within" => "FreshWithin",
|
|
24
|
-
"raw_lane_ingest_only" => "RawLaneIngestOnly",
|
|
25
|
-
"raw_write_once" => "RawWriteOnce",
|
|
26
|
-
"lane_deletable_by" => "LaneDeletableBy",
|
|
27
|
-
}.freeze
|
|
28
|
-
|
|
29
|
-
module_function
|
|
30
|
-
|
|
31
|
-
def by_name(name)
|
|
32
|
-
short = CLASSES.fetch(name.to_s) do
|
|
33
|
-
raise Textus::UsageError.new("unknown predicate '#{name}'")
|
|
34
|
-
end
|
|
35
|
-
const_get(short)
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
def evaluate(manifest:, action:, actor:, key:, schemas: nil, envelope: nil, extra: {}, rule_predicates: [])
|
|
39
|
-
failures = []
|
|
40
|
-
(FLOOR.fetch(action, []) + rule_predicates).uniq.each do |pred_name|
|
|
41
|
-
result = by_name(pred_name).call(
|
|
42
|
-
manifest:, schemas:, actor:, action:, key:, envelope:, extra:,
|
|
43
|
-
)
|
|
44
|
-
next if result[:pass]
|
|
45
|
-
raise result[:error] if result[:error]
|
|
46
|
-
|
|
47
|
-
failures << [pred_name, result[:reason]]
|
|
48
|
-
end
|
|
49
|
-
raise Textus::GuardFailed.new(failures) unless failures.empty?
|
|
50
|
-
end
|
|
51
|
-
end
|
|
52
|
-
end
|
|
53
|
-
end
|
|
54
|
-
end
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
class Manifest
|
|
3
|
-
class Policy
|
|
4
|
-
# One publish destination (ADR 0094). Exactly one of:
|
|
5
|
-
# to-target { to:, template:?, inject_boot:? } — render data through a
|
|
6
|
-
# template, or copy verbatim when no template
|
|
7
|
-
# tree-target { tree: } — ADR 0052 subtree mirror
|
|
8
|
-
# Provenance is NOT a publish flag — it lives in the data's `_meta`.
|
|
9
|
-
class PublishTarget
|
|
10
|
-
attr_reader :to, :tree, :template, :inject_boot
|
|
11
|
-
|
|
12
|
-
def initialize(raw)
|
|
13
|
-
if raw.key?("provenance")
|
|
14
|
-
raise Textus::BadManifest.new("publish `provenance:` was removed (ADR 0094): provenance lives in the data's `_meta`")
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
@to = raw["to"]
|
|
18
|
-
@tree = raw["tree"]
|
|
19
|
-
raise Textus::BadManifest.new("a publish target needs exactly one of `to:` or `tree:`") unless @to.nil? ^ @tree.nil?
|
|
20
|
-
|
|
21
|
-
@template = raw["template"]
|
|
22
|
-
@inject_boot = raw["inject_boot"] == true
|
|
23
|
-
return unless tree_target? && (@template || @inject_boot)
|
|
24
|
-
|
|
25
|
-
raise Textus::BadManifest.new("a tree target takes no template/inject_boot (ADR 0094)")
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
def to_target? = !@to.nil?
|
|
29
|
-
def tree_target? = !@tree.nil?
|
|
30
|
-
def renders? = to_target? && !@template.nil?
|
|
31
|
-
end
|
|
32
|
-
end
|
|
33
|
-
end
|
|
34
|
-
end
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
class Manifest
|
|
3
|
-
class Policy
|
|
4
|
-
class React
|
|
5
|
-
ALLOWED_KEYS = %w[on when do scope budget idempotency observe priority].freeze
|
|
6
|
-
|
|
7
|
-
attr_reader :raw
|
|
8
|
-
|
|
9
|
-
def initialize(raw:)
|
|
10
|
-
raise Textus::BadManifest.new("react: must be a map") unless raw.is_a?(Hash)
|
|
11
|
-
|
|
12
|
-
raw = raw.each_with_object({}) do |(key, value), out|
|
|
13
|
-
normalized = key == true ? "on" : key.to_s
|
|
14
|
-
out[normalized] = value
|
|
15
|
-
end
|
|
16
|
-
raise Textus::BadManifest.new("react.ttl is invalid; ttl belongs only to source.ttl or retention.ttl") if raw.key?("ttl")
|
|
17
|
-
|
|
18
|
-
unknown = raw.keys - ALLOWED_KEYS
|
|
19
|
-
raise Textus::BadManifest.new("react: unknown key(s): #{unknown.join(", ")}") unless unknown.empty?
|
|
20
|
-
|
|
21
|
-
@raw = raw
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
def to_h
|
|
25
|
-
@raw
|
|
26
|
-
end
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
end
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
class Manifest
|
|
3
|
-
class Policy
|
|
4
|
-
# Garbage collection (ADR 0093). A glob-matched rule slot: when an entry
|
|
5
|
-
# ages past `ttl`, retire it. Destructive only — runs on the full
|
|
6
|
-
# `converge` pass, never on a write (ADR 0079's invariant). Orthogonal to
|
|
7
|
-
# production (`source:`): an intake entry can re-pull hourly AND archive
|
|
8
|
-
# after 90 days. `warn`/`refresh` are gone (refresh is implied by an
|
|
9
|
-
# intake source; warn never fired after ADR 0089's pure-read get).
|
|
10
|
-
class Retention
|
|
11
|
-
ACTIONS = %i[drop archive].freeze
|
|
12
|
-
|
|
13
|
-
attr_reader :action
|
|
14
|
-
|
|
15
|
-
def initialize(raw)
|
|
16
|
-
@ttl = raw["ttl"] or
|
|
17
|
-
raise Textus::BadManifest.new("retention requires a 'ttl:'")
|
|
18
|
-
@action = (raw["action"] || "").to_s.to_sym
|
|
19
|
-
return if ACTIONS.include?(@action)
|
|
20
|
-
|
|
21
|
-
raise Textus::BadManifest.new("retention action must be one of #{ACTIONS.join("|")}, got #{raw["action"].inspect}")
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
def ttl_seconds = Textus::Value::Duration.seconds(@ttl)
|
|
25
|
-
def destructive? = true
|
|
26
|
-
end
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
end
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
class Manifest
|
|
3
|
-
class Policy
|
|
4
|
-
# An entry's external-generator declaration. `source:` now means ONLY
|
|
5
|
-
# `from: external` — out-of-band runner; textus never invokes it, only
|
|
6
|
-
# detects drift (doctor generator_drift check).
|
|
7
|
-
# `from: fetch` and `from: derive` are removed; those concerns now live
|
|
8
|
-
# in workflow files under .textus/workflows/.
|
|
9
|
-
class Source
|
|
10
|
-
attr_reader :command, :sources
|
|
11
|
-
|
|
12
|
-
def initialize(raw)
|
|
13
|
-
from = raw["from"].to_s
|
|
14
|
-
unless from == "external"
|
|
15
|
-
raise Textus::BadManifest.new(
|
|
16
|
-
"from: #{from} is removed — use a workflow file under .textus/workflows/ instead",
|
|
17
|
-
)
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
@command = raw["command"] or
|
|
21
|
-
raise Textus::BadManifest.new("source (from: external) requires a `command:` field")
|
|
22
|
-
@sources = raw["sources"] || []
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
def external? = true
|
|
26
|
-
def kind = :external
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
end
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
class Manifest
|
|
3
|
-
# Authority over lanes and roles derived from a Manifest::Data snapshot.
|
|
4
|
-
# Encapsulates the lookups previously living on Manifest itself
|
|
5
|
-
# (lane_writers, permission_for). Write authority is derived from
|
|
6
|
-
# capabilities x lane-kind (ADR 0030): each lane-kind requires one verb
|
|
7
|
-
# (Schema::KIND_REQUIRES_VERB) and a role may write a lane iff its caps
|
|
8
|
-
# include that verb (verb_for_lane, roles_with_capability). Derived /
|
|
9
|
-
# proposal-queue status is authoritative via the declared-kind family
|
|
10
|
-
# (declared_kind, derived_entry?, queue_lane?, queue_lane).
|
|
11
|
-
class Policy
|
|
12
|
-
def initialize(data)
|
|
13
|
-
@data = data
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
# The capability a lane's kind requires to be written, or nil if the
|
|
17
|
-
# lane declares no kind. declared_kind returns a Symbol; the table is
|
|
18
|
-
# keyed by String.
|
|
19
|
-
def verb_for_lane(lane_name)
|
|
20
|
-
kind = declared_kind(lane_name)
|
|
21
|
-
kind && Schema::KIND_REQUIRES_VERB[kind.to_s]
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
# Names of roles whose declared caps include `verb`.
|
|
25
|
-
def roles_with_capability(verb)
|
|
26
|
-
@data.role_caps.select { |_name, caps| caps.include?(verb) }.keys
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
# The conventional automated proposer: a role that can propose but is not
|
|
30
|
-
# the author-anchor (so it resolves to `agent`, not `human`, under the
|
|
31
|
-
# default mapping). Falls back to the first proposer, then nil.
|
|
32
|
-
def proposer_role
|
|
33
|
-
proposers = roles_with_capability("propose")
|
|
34
|
-
(proposers - roles_with_capability("author")).first || proposers.first
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
# The role textus acts AS for a system-initiated operation requiring
|
|
38
|
-
# `verb` (no human passed --as). Capability-derived — a role name that
|
|
39
|
-
# exists in the manifest, or nil. Never a hardcoded literal (ADR 0044).
|
|
40
|
-
def actor_for(verb)
|
|
41
|
-
roles_with_capability(verb).first
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
# The kind declared on a lane in the manifest, or nil if undeclared.
|
|
45
|
-
def declared_kind(lane_name)
|
|
46
|
-
@data.declared_lane_kinds[lane_name]
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
# Lane names declaring `kind` (a Symbol), in manifest order. Lets callers
|
|
50
|
-
# (boot) name a kind's live lane instance(s) instead of hardcoding names.
|
|
51
|
-
def lanes_of_kind(kind)
|
|
52
|
-
@data.declared_lane_kinds.select { |_name, k| k == kind }.keys
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
# The single lane declaring `kind: queue`, or nil. Schema guarantees <=1.
|
|
56
|
-
def queue_lane
|
|
57
|
-
@data.declared_lane_kinds.key(:queue)
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
# ADR 0091: derived-ness is a property of the ENTRY, not its lane (one
|
|
61
|
-
# machine lane holds both intake and derived entries). Resolve the entry
|
|
62
|
-
# and ask it directly. Returns false if entries are not yet built
|
|
63
|
-
# (validator phase during Data#initialize) — validators must not rely on
|
|
64
|
-
# cross-entry state during construction.
|
|
65
|
-
def derived_entry?(_key)
|
|
66
|
-
false
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
# The single lane declaring kind: machine, or nil.
|
|
70
|
-
def machine_lane
|
|
71
|
-
@data.declared_lane_kinds.key(:machine)
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
# A lane is a proposal queue iff it declares kind: queue.
|
|
75
|
-
def queue_lane?(lane_name)
|
|
76
|
-
declared_kind(lane_name) == :queue
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
# The lane a proposer role writes proposals into: the single lane that
|
|
80
|
-
# declares kind: queue, when the role can write it. Returns nil if there
|
|
81
|
-
# is no queue lane or the role cannot write it.
|
|
82
|
-
def propose_lane_for(role)
|
|
83
|
-
return nil if role.nil?
|
|
84
|
-
|
|
85
|
-
q = queue_lane
|
|
86
|
-
return nil unless q
|
|
87
|
-
|
|
88
|
-
q_verb = verb_for_lane(q)
|
|
89
|
-
return nil unless roles_with_capability(q_verb).include?(role)
|
|
90
|
-
|
|
91
|
-
q
|
|
92
|
-
end
|
|
93
|
-
end
|
|
94
|
-
end
|
|
95
|
-
end
|