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,51 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Textus
|
|
4
|
-
module Dispatch
|
|
5
|
-
module Middleware
|
|
6
|
-
# Shadows successful write operations into the SQLite audit_events table
|
|
7
|
-
# synchronously after dispatch, without the command knowing. Read verbs
|
|
8
|
-
# and failed writes pass through unchanged.
|
|
9
|
-
class AuditIndex < Base
|
|
10
|
-
middleware_name :audit_index
|
|
11
|
-
|
|
12
|
-
INDEXED_CONTRACTS = [
|
|
13
|
-
Contracts::PutEntry,
|
|
14
|
-
Contracts::DeleteKey,
|
|
15
|
-
Contracts::MoveKey,
|
|
16
|
-
].freeze
|
|
17
|
-
|
|
18
|
-
def initialize(job_store:, audit_log:)
|
|
19
|
-
super()
|
|
20
|
-
@job_store = job_store
|
|
21
|
-
@audit_log = audit_log
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
def call(container:, command:, call:, next_handler:) # rubocop:disable Lint/UnusedMethodArgument
|
|
25
|
-
result = next_handler.call(command, call)
|
|
26
|
-
return result unless result.success? && INDEXED_CONTRACTS.include?(command.class)
|
|
27
|
-
|
|
28
|
-
key = command.respond_to?(:key) ? command.key : nil
|
|
29
|
-
return result unless key
|
|
30
|
-
|
|
31
|
-
seq = @audit_log.latest_seq
|
|
32
|
-
verb = command.class.name.split("::").last
|
|
33
|
-
.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
|
34
|
-
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
|
35
|
-
.downcase
|
|
36
|
-
|
|
37
|
-
@job_store.insert_audit_event(
|
|
38
|
-
seq: seq,
|
|
39
|
-
ts: Time.now.utc.iso8601,
|
|
40
|
-
role: call.role,
|
|
41
|
-
verb: verb,
|
|
42
|
-
key: key,
|
|
43
|
-
etag_before: nil,
|
|
44
|
-
etag_after: result.value.is_a?(Hash) ? result.value["etag"] : nil,
|
|
45
|
-
)
|
|
46
|
-
result
|
|
47
|
-
end
|
|
48
|
-
end
|
|
49
|
-
end
|
|
50
|
-
end
|
|
51
|
-
end
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
module Dispatch
|
|
3
|
-
module Middleware
|
|
4
|
-
class Auth < Base
|
|
5
|
-
middleware_name :auth
|
|
6
|
-
|
|
7
|
-
def call(container:, command:, call:, next_handler:)
|
|
8
|
-
verb = VerbRegistry.contract_to_verb!(command.class).to_sym
|
|
9
|
-
key = key_for(command)
|
|
10
|
-
|
|
11
|
-
rule_preds = key ? rule_declared_predicates(verb, container.manifest, key) : []
|
|
12
|
-
|
|
13
|
-
Manifest::Policy::Predicates.evaluate(
|
|
14
|
-
manifest: container.manifest, schemas: container.schemas,
|
|
15
|
-
action: verb, actor: call.role, key: key,
|
|
16
|
-
rule_predicates: rule_preds
|
|
17
|
-
)
|
|
18
|
-
|
|
19
|
-
next_handler.call(command, call)
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
private
|
|
23
|
-
|
|
24
|
-
def rule_declared_predicates(verb, manifest, key)
|
|
25
|
-
guard_map = manifest.rules.for(key).guard
|
|
26
|
-
return [] if guard_map.nil?
|
|
27
|
-
|
|
28
|
-
Array(guard_map[verb.to_s])
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
def key_for(command)
|
|
32
|
-
if command.respond_to?(:key) then command.key
|
|
33
|
-
elsif command.respond_to?(:old_key) then command.old_key
|
|
34
|
-
elsif command.respond_to?(:pending_key) then command.pending_key
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
end
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
|
-
end
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
module Dispatch
|
|
3
|
-
module Middleware
|
|
4
|
-
class Base
|
|
5
|
-
def self.inherited(subclass)
|
|
6
|
-
super
|
|
7
|
-
subclass.instance_variable_set(:@middleware_name, nil)
|
|
8
|
-
end
|
|
9
|
-
|
|
10
|
-
class << self
|
|
11
|
-
def middleware_name(name = nil)
|
|
12
|
-
if name
|
|
13
|
-
@middleware_name = name.to_s
|
|
14
|
-
else
|
|
15
|
-
@middleware_name || name.split("::").last.downcase
|
|
16
|
-
end
|
|
17
|
-
end
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
def call(container:, command:, call:, next_handler:)
|
|
21
|
-
raise NotImplementedError
|
|
22
|
-
end
|
|
23
|
-
end
|
|
24
|
-
end
|
|
25
|
-
end
|
|
26
|
-
end
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
module Dispatch
|
|
3
|
-
module Middleware
|
|
4
|
-
class Binder < Base
|
|
5
|
-
middleware_name :binder
|
|
6
|
-
|
|
7
|
-
def call(container:, command:, call:, next_handler:) # rubocop:disable Lint/UnusedMethodArgument
|
|
8
|
-
return next_handler.call(command, call) unless command.is_a?(Dispatch::Binder::Pending)
|
|
9
|
-
|
|
10
|
-
spec = command.spec
|
|
11
|
-
contract_class = VerbRegistry.contract_class_for(spec.verb) or
|
|
12
|
-
raise Textus::UsageError.new("unknown command verb: #{spec.verb}")
|
|
13
|
-
resolved = Dispatch::Binder.bind(spec, command.inputs)
|
|
14
|
-
built = Dispatch::Pipeline.build_command(contract_class, resolved)
|
|
15
|
-
next_handler.call(built, call)
|
|
16
|
-
end
|
|
17
|
-
end
|
|
18
|
-
end
|
|
19
|
-
end
|
|
20
|
-
end
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
module Dispatch
|
|
3
|
-
module Middleware
|
|
4
|
-
class Cascade < Base
|
|
5
|
-
middleware_name :cascade
|
|
6
|
-
|
|
7
|
-
CASCADE_VERBS = %i[put propose accept reject key_mv key_delete].freeze
|
|
8
|
-
|
|
9
|
-
TRIGGER_TYPE_MAP = {
|
|
10
|
-
Contracts::PutEntry => "entry.written",
|
|
11
|
-
Contracts::ProposeEntry => "entry.written",
|
|
12
|
-
Contracts::DeleteKey => "entry.deleted",
|
|
13
|
-
Contracts::MoveKey => "entry.moved",
|
|
14
|
-
Contracts::AcceptProposal => "proposal.accepted",
|
|
15
|
-
Contracts::RejectProposal => "proposal.rejected",
|
|
16
|
-
}.freeze
|
|
17
|
-
|
|
18
|
-
def call(container:, command:, call:, next_handler:)
|
|
19
|
-
result = next_handler.call(command, call)
|
|
20
|
-
return result unless result.success? && cascadable?(command)
|
|
21
|
-
|
|
22
|
-
key = cascade_key(command)
|
|
23
|
-
return result unless key
|
|
24
|
-
|
|
25
|
-
trigger_type = TRIGGER_TYPE_MAP[command.class]
|
|
26
|
-
jobs = Textus::Store::Jobs::Planner.new(container: container).plan(
|
|
27
|
-
trigger: { "type" => trigger_type, "target" => key },
|
|
28
|
-
role: call.role,
|
|
29
|
-
)
|
|
30
|
-
queue = Textus::Store::Jobs::Queue.new(store: container.job_store)
|
|
31
|
-
jobs.each { |j| queue.enqueue(j) }
|
|
32
|
-
result
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
private
|
|
36
|
-
|
|
37
|
-
def cascadable?(command)
|
|
38
|
-
CASCADE_VERBS.include?(VerbRegistry.contract_to_verb!(command.class).to_sym)
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
def cascade_key(command)
|
|
42
|
-
case command
|
|
43
|
-
when Contracts::PutEntry, Contracts::DeleteKey then command.key
|
|
44
|
-
when Contracts::MoveKey then command.new_key
|
|
45
|
-
when Contracts::AcceptProposal,
|
|
46
|
-
Contracts::RejectProposal then command.pending_key
|
|
47
|
-
when Contracts::ProposeEntry then command.key
|
|
48
|
-
end
|
|
49
|
-
end
|
|
50
|
-
end
|
|
51
|
-
end
|
|
52
|
-
end
|
|
53
|
-
end
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
module Dispatch
|
|
3
|
-
class Pipeline
|
|
4
|
-
attr_reader :container
|
|
5
|
-
|
|
6
|
-
def initialize(registry:, container:, middleware: [])
|
|
7
|
-
@registry = registry
|
|
8
|
-
@middleware = middleware
|
|
9
|
-
@container = container
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
def dispatch(command, call:)
|
|
13
|
-
stack = @middleware.reverse.reduce(->(cmd, c) { execute(cmd, c) }) do |next_mw, mw|
|
|
14
|
-
->(cmd, c) { mw.call(container: @container, command: cmd, call: c, next_handler: next_mw) }
|
|
15
|
-
end
|
|
16
|
-
stack.call(command, call)
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
def self.build_command(contract_class, inputs)
|
|
20
|
-
members = contract_class.members
|
|
21
|
-
kwargs = members.to_h do |member|
|
|
22
|
-
[member, inputs[member]]
|
|
23
|
-
end
|
|
24
|
-
contract_class.new(**kwargs)
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
private
|
|
28
|
-
|
|
29
|
-
def execute(command, call)
|
|
30
|
-
handler = @registry.for(command.class)
|
|
31
|
-
handler.call(command, call)
|
|
32
|
-
end
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
end
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
module Doctor
|
|
3
|
-
class Check
|
|
4
|
-
class AuditLog < Check
|
|
5
|
-
def call
|
|
6
|
-
path = Textus::Store::Layout.new(root).audit_log_path
|
|
7
|
-
Textus::Port::AuditLog.new(root).verify_integrity.map do |v|
|
|
8
|
-
{
|
|
9
|
-
"code" => "audit.parse_error",
|
|
10
|
-
"level" => "warning",
|
|
11
|
-
"subject" => "#{path}:#{v["lineno"]}",
|
|
12
|
-
"message" => violation_message(v),
|
|
13
|
-
"fix" => "inspect #{path} at line #{v["lineno"]} and remove the corrupted row",
|
|
14
|
-
}
|
|
15
|
-
end
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
private
|
|
19
|
-
|
|
20
|
-
def violation_message(v)
|
|
21
|
-
case v["reason"]
|
|
22
|
-
when "invalid_json"
|
|
23
|
-
"audit log line #{v["lineno"]} is invalid JSON: #{v["detail"]}"
|
|
24
|
-
when "short_tsv"
|
|
25
|
-
"audit log line #{v["lineno"]} #{v["detail"]} " \
|
|
26
|
-
"(consider migrating to NDJSON)"
|
|
27
|
-
else
|
|
28
|
-
v["detail"]
|
|
29
|
-
end
|
|
30
|
-
end
|
|
31
|
-
end
|
|
32
|
-
end
|
|
33
|
-
end
|
|
34
|
-
end
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
module Doctor
|
|
3
|
-
class Check
|
|
4
|
-
# ADR 0079: generator/build drift — a derived/external entry whose sources
|
|
5
|
-
# changed since its generated.at. Dependency-based (not age-based), so it
|
|
6
|
-
# stays OUT of the lifecycle/freshness unification and lives here as a
|
|
7
|
-
# health signal. This is the surviving home for what the removed `stale`
|
|
8
|
-
# verb reported.
|
|
9
|
-
class GeneratorDrift < Check
|
|
10
|
-
def call
|
|
11
|
-
detector = Textus::Store::Freshness::DriftDetector.new(
|
|
12
|
-
manifest: manifest,
|
|
13
|
-
file_stat: Textus::Port::Storage::FileStat.new,
|
|
14
|
-
clock: Textus::Port::Clock.new,
|
|
15
|
-
)
|
|
16
|
-
manifest.data.entries.flat_map { |m| detector.drift_rows(m) }.map do |row|
|
|
17
|
-
{
|
|
18
|
-
"code" => "generator_drift",
|
|
19
|
-
"level" => "warning",
|
|
20
|
-
"subject" => row["key"],
|
|
21
|
-
"message" => row["reason"],
|
|
22
|
-
"fix" => "rematerialize the entry: `textus drain`",
|
|
23
|
-
}
|
|
24
|
-
end
|
|
25
|
-
end
|
|
26
|
-
end
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
end
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
module Doctor
|
|
3
|
-
class Check
|
|
4
|
-
class IllegalKeys < Check
|
|
5
|
-
def call
|
|
6
|
-
out = []
|
|
7
|
-
manifest.data.entries.each do |entry|
|
|
8
|
-
next unless entry.nested?
|
|
9
|
-
next if entry.publish_mode.keyless? # publish_tree files are opaque payload, never keys (ADR 0047)
|
|
10
|
-
|
|
11
|
-
base = File.join(root, Textus::Key::Path.normalize_relative_path(entry.path))
|
|
12
|
-
next unless File.directory?(base)
|
|
13
|
-
|
|
14
|
-
check_all_paths(entry, base, out)
|
|
15
|
-
end
|
|
16
|
-
out
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
private
|
|
20
|
-
|
|
21
|
-
def check_all_paths(entry, base, out)
|
|
22
|
-
walk_nested(base) do |abs_path, is_dir|
|
|
23
|
-
rel = abs_path.sub(%r{\A#{Regexp.escape(base)}/?}, "")
|
|
24
|
-
next if entry.ignored?(rel)
|
|
25
|
-
|
|
26
|
-
basename = File.basename(abs_path)
|
|
27
|
-
stem = is_dir ? basename : basename.sub(/#{Regexp.escape(File.extname(basename))}\z/, "")
|
|
28
|
-
next if stem.match?(Key::Grammar::SEGMENT)
|
|
29
|
-
|
|
30
|
-
out << issue(abs_path, stem)
|
|
31
|
-
end
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
def issue(abs_path, stem)
|
|
35
|
-
{
|
|
36
|
-
"code" => "key.illegal",
|
|
37
|
-
"level" => "error",
|
|
38
|
-
"subject" => abs_path,
|
|
39
|
-
"path" => abs_path,
|
|
40
|
-
"message" => "illegal key segment '#{stem}' at #{abs_path}",
|
|
41
|
-
"fix" => "rename the file/directory so each segment matches [a-z0-9][a-z0-9-]* " \
|
|
42
|
-
"(lowercase, digits, hyphens)",
|
|
43
|
-
}
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
def walk_nested(root, &block)
|
|
47
|
-
Dir.each_child(root) do |name|
|
|
48
|
-
abs = File.join(root, name)
|
|
49
|
-
if File.directory?(abs)
|
|
50
|
-
walk_nested(abs, &block)
|
|
51
|
-
yield abs, true
|
|
52
|
-
else
|
|
53
|
-
yield abs, false
|
|
54
|
-
end
|
|
55
|
-
end
|
|
56
|
-
end
|
|
57
|
-
end
|
|
58
|
-
end
|
|
59
|
-
end
|
|
60
|
-
end
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
module Doctor
|
|
3
|
-
class Check
|
|
4
|
-
class ManifestFiles < Check
|
|
5
|
-
def call
|
|
6
|
-
manifest.data.entries.each_with_object([]) do |entry, out|
|
|
7
|
-
next if entry.nested?
|
|
8
|
-
|
|
9
|
-
path = Textus::Key::Path.resolve(manifest.data, entry)
|
|
10
|
-
next if File.exist?(path)
|
|
11
|
-
|
|
12
|
-
out << {
|
|
13
|
-
"code" => "manifest.missing_file",
|
|
14
|
-
"level" => "info",
|
|
15
|
-
"subject" => entry.key,
|
|
16
|
-
"message" => "declared entry has no file on disk at #{path}",
|
|
17
|
-
"fix" => "create the entry with 'textus put #{entry.key} --stdin --as=<role>' " \
|
|
18
|
-
"(or leave empty if not yet authored)",
|
|
19
|
-
}
|
|
20
|
-
end
|
|
21
|
-
end
|
|
22
|
-
end
|
|
23
|
-
end
|
|
24
|
-
end
|
|
25
|
-
end
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
module Doctor
|
|
3
|
-
class Check
|
|
4
|
-
# Flags published files whose recorded source no longer exists in the
|
|
5
|
-
# store. Per-leaf prune (ADR 0046) reconciles within a still-present
|
|
6
|
-
# leaf; a renamed or removed *whole* leaf orphans its entire target
|
|
7
|
-
# directory, which a per-entry build won't revisit. This check catches
|
|
8
|
-
# that drift without making `build` scan globally.
|
|
9
|
-
class OrphanedPublishTargets < Check
|
|
10
|
-
def call
|
|
11
|
-
sdir = Textus::Store::Layout.new(root).sentinels_root
|
|
12
|
-
return [] unless File.directory?(sdir)
|
|
13
|
-
|
|
14
|
-
repo_root = File.dirname(root)
|
|
15
|
-
store = Textus::Port::SentinelStore.new
|
|
16
|
-
glob = File.join(sdir, "**", "*#{Textus::Port::SentinelStore::SUFFIX}")
|
|
17
|
-
Dir.glob(glob).filter_map do |spath|
|
|
18
|
-
sentinel = store.load(spath, repo_root)
|
|
19
|
-
next nil if sentinel.nil? || sentinel.source.nil?
|
|
20
|
-
next nil if File.exist?(sentinel.source)
|
|
21
|
-
|
|
22
|
-
{
|
|
23
|
-
"code" => "publish.orphaned_target",
|
|
24
|
-
"level" => "warning",
|
|
25
|
-
"subject" => sentinel.target,
|
|
26
|
-
"message" => "published file #{sentinel.target} has no source in the store " \
|
|
27
|
-
"(recorded source #{sentinel.source} is gone) — likely a renamed or removed leaf",
|
|
28
|
-
"fix" => "remove the stale copy and its sentinel: rm '#{sentinel.target}' '#{spath}'",
|
|
29
|
-
}
|
|
30
|
-
end
|
|
31
|
-
end
|
|
32
|
-
end
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
end
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
module Doctor
|
|
3
|
-
class Check
|
|
4
|
-
# Flags pending proposals whose `proposal.target_key` cannot ever be
|
|
5
|
-
# accepted: it points at a non-canon zone or resolves to no declared
|
|
6
|
-
# entry (ADR 0035). Reads the live queue zone; silent when there is no
|
|
7
|
-
# queue zone. Warnings, not errors — they are stale junk, not store
|
|
8
|
-
# corruption (the accept gate already refuses them).
|
|
9
|
-
class ProposalTargets < Check
|
|
10
|
-
def call
|
|
11
|
-
queue = manifest.policy.queue_lane
|
|
12
|
-
return [] unless queue
|
|
13
|
-
|
|
14
|
-
dispatch(:list, lane: queue).filter_map { |row| issue_for(row["key"]) }
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
private
|
|
18
|
-
|
|
19
|
-
def issue_for(key)
|
|
20
|
-
target = dispatch(:get, key).meta&.dig("proposal", "target_key")
|
|
21
|
-
return nil if target.nil? # not a proposal entry — skip
|
|
22
|
-
|
|
23
|
-
zone = manifest.resolver.resolve(target).entry.lane
|
|
24
|
-
return nil if manifest.policy.declared_kind(zone.to_s) == :canon
|
|
25
|
-
|
|
26
|
-
{
|
|
27
|
-
"code" => "proposal.target_not_canon",
|
|
28
|
-
"level" => "warning",
|
|
29
|
-
"subject" => key,
|
|
30
|
-
"message" => "proposal '#{key}' targets '#{target}' in zone '#{zone}' (not canon); it can never be accepted",
|
|
31
|
-
"fix" => "delete the proposal, or repoint target_key at a canon zone",
|
|
32
|
-
}
|
|
33
|
-
rescue Textus::UnknownKey
|
|
34
|
-
{
|
|
35
|
-
"code" => "proposal.target_unresolved",
|
|
36
|
-
"level" => "warning",
|
|
37
|
-
"subject" => key,
|
|
38
|
-
"message" => "proposal '#{key}' targets '#{target}', which resolves to no declared entry",
|
|
39
|
-
"fix" => "delete the proposal, or fix target_key",
|
|
40
|
-
}
|
|
41
|
-
end
|
|
42
|
-
end
|
|
43
|
-
end
|
|
44
|
-
end
|
|
45
|
-
end
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
require "yaml"
|
|
2
|
-
|
|
3
|
-
module Textus
|
|
4
|
-
module Doctor
|
|
5
|
-
class Check
|
|
6
|
-
# Runs as a standalone module (Check::ProtocolVersion.run(root:)) and also
|
|
7
|
-
# as a class-based doctor check (ProtocolVersion.new(store).call).
|
|
8
|
-
class ProtocolVersion < Check
|
|
9
|
-
# Standalone interface: root is the project root (parent of .textus/).
|
|
10
|
-
def self.run(root:)
|
|
11
|
-
path = File.join(root, ".textus", "manifest.yaml")
|
|
12
|
-
return [] unless File.exist?(path)
|
|
13
|
-
|
|
14
|
-
doc = YAML.safe_load_file(path, aliases: false) || {}
|
|
15
|
-
version = doc["version"]
|
|
16
|
-
return [] if version == "textus/4"
|
|
17
|
-
|
|
18
|
-
[{
|
|
19
|
-
"code" => "protocol_mismatch",
|
|
20
|
-
"severity" => "error",
|
|
21
|
-
"message" => "Store reports version=#{version.inspect}; this gem expects textus/4.",
|
|
22
|
-
"hint" => "Upgrade the store's manifest version to textus/4 (see CHANGELOG for breaking changes).",
|
|
23
|
-
}]
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
# Doctor check interface: root is the .textus/ directory itself,
|
|
27
|
-
# so manifest.yaml lives directly inside it.
|
|
28
|
-
def call
|
|
29
|
-
path = geometry.manifest_path
|
|
30
|
-
return [] unless File.exist?(path)
|
|
31
|
-
|
|
32
|
-
doc = YAML.safe_load_file(path, aliases: false) || {}
|
|
33
|
-
version = doc["version"]
|
|
34
|
-
return [] if version == "textus/4"
|
|
35
|
-
|
|
36
|
-
[{
|
|
37
|
-
"code" => "protocol_mismatch",
|
|
38
|
-
"level" => "error",
|
|
39
|
-
"subject" => path,
|
|
40
|
-
"message" => "Store reports version=#{version.inspect}; this gem expects textus/4.",
|
|
41
|
-
"fix" => "Upgrade the store's manifest version to textus/4 (see CHANGELOG for breaking changes).",
|
|
42
|
-
}]
|
|
43
|
-
end
|
|
44
|
-
end
|
|
45
|
-
end
|
|
46
|
-
end
|
|
47
|
-
end
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
module Doctor
|
|
3
|
-
class Check
|
|
4
|
-
# ADR 0047 Decision 4. A publish_tree entry prunes its WHOLE target dir on
|
|
5
|
-
# every build. If a derived entry's publish_to writes a file into that same
|
|
6
|
-
# dir, the tree's prune will delete it unless the tree `ignore`s that
|
|
7
|
-
# filename. Warn so the author adds the ignore before prune eats the index.
|
|
8
|
-
class PublishTreeIndexOverlap < Check
|
|
9
|
-
def call
|
|
10
|
-
entries = manifest.data.entries
|
|
11
|
-
trees = entries.select { |e| e.nested? && e.publish_tree }
|
|
12
|
-
return [] if trees.empty?
|
|
13
|
-
|
|
14
|
-
derived_targets = entries.flat_map do |e|
|
|
15
|
-
Array(e.publish_to).map { |rel| [e, rel] }
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
trees.flat_map do |tree|
|
|
19
|
-
target_prefix = "#{tree.publish_tree.chomp("/")}/"
|
|
20
|
-
derived_targets.filter_map do |(derived, rel)|
|
|
21
|
-
next nil unless rel.start_with?(target_prefix)
|
|
22
|
-
|
|
23
|
-
rel_to_target = rel.delete_prefix(target_prefix)
|
|
24
|
-
next nil if tree.ignored?(rel_to_target)
|
|
25
|
-
|
|
26
|
-
issue(tree, derived, rel, rel_to_target)
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
private
|
|
32
|
-
|
|
33
|
-
def issue(tree, derived, rel, rel_to_target)
|
|
34
|
-
basename = File.basename(rel_to_target)
|
|
35
|
-
{
|
|
36
|
-
"code" => "publish.tree_index_overlap",
|
|
37
|
-
"level" => "warning",
|
|
38
|
-
"subject" => tree.key,
|
|
39
|
-
"message" => "publish_tree '#{tree.publish_tree}' overlaps derived entry " \
|
|
40
|
-
"'#{derived.key}' publish_to '#{rel}'; the tree's prune will delete it on rebuild",
|
|
41
|
-
"fix" => "add a glob covering '#{rel_to_target}' to entry '#{tree.key}' ignore " \
|
|
42
|
-
"(e.g. ignore: [\"**/#{basename}\"])",
|
|
43
|
-
}
|
|
44
|
-
end
|
|
45
|
-
end
|
|
46
|
-
end
|
|
47
|
-
end
|
|
48
|
-
end
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
module Doctor
|
|
3
|
-
class Check
|
|
4
|
-
class RawAssetPaths < Check
|
|
5
|
-
def call
|
|
6
|
-
raw_lane = manifest.policy.lanes_of_kind(:raw).first
|
|
7
|
-
return [] unless raw_lane
|
|
8
|
-
|
|
9
|
-
issues = []
|
|
10
|
-
manifest.resolver.enumerate.each do |row|
|
|
11
|
-
next unless row[:key].start_with?("raw.")
|
|
12
|
-
next unless row[:path] && File.exist?(row[:path])
|
|
13
|
-
|
|
14
|
-
raw_content = load_content(row[:path])
|
|
15
|
-
next unless raw_content.is_a?(Hash)
|
|
16
|
-
|
|
17
|
-
asset = raw_content["asset"]
|
|
18
|
-
next unless asset.is_a?(String) && !asset.empty?
|
|
19
|
-
|
|
20
|
-
asset_path = find_asset_path(asset)
|
|
21
|
-
next if File.exist?(asset_path)
|
|
22
|
-
|
|
23
|
-
issues << {
|
|
24
|
-
"code" => "raw_asset.missing_file",
|
|
25
|
-
"level" => "error",
|
|
26
|
-
"subject" => row[:key],
|
|
27
|
-
"message" => "raw entry '#{row[:key]}' references asset '#{asset}' " \
|
|
28
|
-
"which does not exist at #{asset_path}",
|
|
29
|
-
"fix" => "re-ingest the asset: textus key-delete #{row[:key]}, then textus ingest",
|
|
30
|
-
}
|
|
31
|
-
end
|
|
32
|
-
issues
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
private
|
|
36
|
-
|
|
37
|
-
def load_content(path)
|
|
38
|
-
require "yaml"
|
|
39
|
-
YAML.safe_load_file(path)
|
|
40
|
-
rescue StandardError
|
|
41
|
-
nil
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
def find_asset_path(asset_rel)
|
|
45
|
-
File.join(geometry.root, "assets", asset_rel)
|
|
46
|
-
end
|
|
47
|
-
end
|
|
48
|
-
end
|
|
49
|
-
end
|
|
50
|
-
end
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
module Doctor
|
|
3
|
-
class Check
|
|
4
|
-
# Flags entries whose key is matched by two or more rule blocks of the
|
|
5
|
-
# SAME specificity in the same slot (lifecycle / handler_permit /
|
|
6
|
-
# guard / materialize). Ties are non-deterministic in the parser's pick step, so
|
|
7
|
-
# they're a configuration smell — surface them.
|
|
8
|
-
class RuleAmbiguity < Check
|
|
9
|
-
SLOTS = Textus::Manifest::Schema::FIELD_REGISTRY.select { |_, m| m[:in_ambiguity] }.keys.freeze
|
|
10
|
-
|
|
11
|
-
def call
|
|
12
|
-
out = []
|
|
13
|
-
rules = manifest.rules
|
|
14
|
-
manifest.data.entries.each do |mentry|
|
|
15
|
-
matches = rules.explain(mentry.key)
|
|
16
|
-
next if matches.length < 2
|
|
17
|
-
|
|
18
|
-
SLOTS.each { |slot| out.concat(ambiguities_for(mentry, slot, matches)) }
|
|
19
|
-
end
|
|
20
|
-
out
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
private
|
|
24
|
-
|
|
25
|
-
def ambiguities_for(mentry, slot, matches)
|
|
26
|
-
carriers = matches.select { |b| b.public_send(slot) }
|
|
27
|
-
return [] if carriers.length < 2
|
|
28
|
-
|
|
29
|
-
by_specificity = carriers.group_by { |b| Textus::Manifest::Policy::Matcher.specificity(b.match) }
|
|
30
|
-
tied = by_specificity.values.select { |group| group.length > 1 }
|
|
31
|
-
tied.map { |group| issue_for(mentry, slot, group) }
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
def issue_for(mentry, slot, group)
|
|
35
|
-
globs = group.map(&:match).sort
|
|
36
|
-
{
|
|
37
|
-
"code" => "rule.ambiguity",
|
|
38
|
-
"level" => "warning",
|
|
39
|
-
"subject" => mentry.key,
|
|
40
|
-
"message" => "entry '#{mentry.key}' matches #{group.length} rule blocks at the same " \
|
|
41
|
-
"specificity for #{slot}: #{globs.join(", ")}",
|
|
42
|
-
"fix" => "narrow one of the conflicting match: globs in .textus/manifest.yaml so a single " \
|
|
43
|
-
"block wins for this key",
|
|
44
|
-
}
|
|
45
|
-
end
|
|
46
|
-
end
|
|
47
|
-
end
|
|
48
|
-
end
|
|
49
|
-
end
|