textus 0.52.0 → 0.54.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 +59 -0
- data/README.md +62 -54
- data/SPEC.md +64 -192
- data/docs/architecture/README.md +157 -156
- data/exe/textus +1 -1
- data/lib/textus/action/accept.rb +44 -0
- data/lib/textus/action/audit.rb +131 -0
- data/lib/textus/action/base.rb +42 -0
- data/lib/textus/{read → action}/blame.rb +28 -22
- data/lib/textus/action/boot.rb +17 -0
- data/lib/textus/action/data_mv.rb +69 -0
- data/lib/textus/action/deps.rb +34 -0
- data/lib/textus/action/doctor.rb +24 -0
- data/lib/textus/action/drain.rb +39 -0
- data/lib/textus/action/enqueue.rb +53 -0
- data/lib/textus/action/get.rb +78 -0
- data/lib/textus/action/ingest.rb +135 -0
- data/lib/textus/action/jobs.rb +36 -0
- data/lib/textus/action/key_delete.rb +36 -0
- data/lib/textus/action/key_delete_prefix.rb +44 -0
- data/lib/textus/action/key_mv.rb +129 -0
- data/lib/textus/action/key_mv_prefix.rb +57 -0
- data/lib/textus/action/list.rb +42 -0
- data/lib/textus/action/propose.rb +52 -0
- data/lib/textus/action/published.rb +24 -0
- data/lib/textus/action/pulse.rb +56 -0
- data/lib/textus/action/put.rb +52 -0
- data/lib/textus/action/rdeps.rb +39 -0
- data/lib/textus/action/reject.rb +37 -0
- data/lib/textus/action/rule_explain.rb +91 -0
- data/lib/textus/action/rule_lint.rb +68 -0
- data/lib/textus/action/rule_list.rb +42 -0
- data/lib/textus/action/schema_envelope.rb +29 -0
- data/lib/textus/action/uid.rb +33 -0
- data/lib/textus/action/where.rb +36 -0
- data/lib/textus/action/write_verb.rb +44 -0
- data/lib/textus/boot.rb +64 -132
- data/lib/textus/command.rb +41 -0
- data/lib/textus/container.rb +1 -1
- data/lib/textus/{domain → core}/duration.rb +1 -1
- data/lib/textus/{domain → core}/freshness/evaluator.rb +25 -41
- data/lib/textus/{domain → core}/freshness/verdict.rb +2 -2
- data/lib/textus/{domain → core}/freshness.rb +2 -2
- data/lib/textus/{domain → core}/retention/sweep.rb +7 -7
- data/lib/textus/{domain → core}/retention.rb +2 -2
- data/lib/textus/{domain → core}/sentinel.rb +1 -1
- data/lib/textus/doctor/check/generator_drift.rb +1 -1
- data/lib/textus/doctor/check/illegal_keys.rb +1 -1
- data/lib/textus/doctor/check/notebook_sources.rb +50 -0
- data/lib/textus/doctor/check/proposal_targets.rb +3 -3
- data/lib/textus/doctor/check/raw_asset_paths.rb +50 -0
- data/lib/textus/doctor/check/rule_ambiguity.rb +2 -2
- data/lib/textus/doctor/check/schema_violations.rb +8 -2
- data/lib/textus/doctor/check.rb +11 -9
- data/lib/textus/{read → doctor}/validator.rb +22 -13
- data/lib/textus/doctor.rb +4 -44
- data/lib/textus/envelope/reader.rb +46 -0
- data/lib/textus/envelope/writer.rb +209 -0
- data/lib/textus/envelope.rb +5 -3
- data/lib/textus/errors.rb +25 -13
- data/lib/textus/{entry → format}/base.rb +1 -6
- data/lib/textus/{entry → format}/json.rb +1 -4
- data/lib/textus/{entry → format}/markdown.rb +2 -6
- data/lib/textus/{entry → format}/text.rb +2 -6
- data/lib/textus/{entry → format}/yaml.rb +1 -4
- data/lib/textus/{entry.rb → format.rb} +9 -10
- data/lib/textus/gate/auth.rb +227 -0
- data/lib/textus/gate.rb +116 -0
- data/lib/textus/init.rb +59 -105
- data/lib/textus/jobs/base.rb +23 -0
- data/lib/textus/jobs/materialize.rb +20 -0
- data/lib/textus/jobs/plan.rb +9 -0
- data/lib/textus/jobs/planner.rb +101 -0
- data/lib/textus/jobs/retention.rb +48 -0
- data/lib/textus/jobs/sweep.rb +27 -0
- data/lib/textus/jobs/worker.rb +67 -0
- data/lib/textus/jobs.rb +15 -0
- data/lib/textus/key/path.rb +10 -3
- data/lib/textus/layout.rb +13 -0
- data/lib/textus/manifest/data.rb +14 -14
- data/lib/textus/manifest/entry/base.rb +14 -25
- data/lib/textus/manifest/entry/parser.rb +31 -14
- data/lib/textus/manifest/entry/produced.rb +13 -33
- data/lib/textus/manifest/entry/publish/mode.rb +1 -1
- data/lib/textus/manifest/entry/publish/to_paths.rb +4 -3
- data/lib/textus/manifest/entry/publish.rb +0 -12
- data/lib/textus/manifest/entry/validators/format_matrix.rb +1 -1
- data/lib/textus/manifest/entry/validators.rb +0 -1
- data/lib/textus/{domain → manifest}/policy/matcher.rb +2 -2
- data/lib/textus/{domain → manifest}/policy/publish_target.rb +2 -2
- data/lib/textus/manifest/policy/react.rb +30 -0
- data/lib/textus/{domain → manifest}/policy/retention.rb +3 -3
- data/lib/textus/manifest/policy/source.rb +30 -0
- data/lib/textus/manifest/policy.rb +38 -53
- data/lib/textus/manifest/resolver.rb +5 -4
- data/lib/textus/manifest/rules.rb +4 -4
- data/lib/textus/manifest/schema/keys.rb +20 -28
- data/lib/textus/manifest/schema/validator.rb +27 -33
- data/lib/textus/manifest/schema/vocabulary.rb +2 -1
- data/lib/textus/manifest/schema.rb +2 -2
- data/lib/textus/manifest.rb +2 -2
- data/lib/textus/ports/audit_log.rb +31 -5
- data/lib/textus/{domain/jobs → ports/job_store}/job.rb +19 -12
- data/lib/textus/ports/{queue.rb → job_store.rb} +7 -14
- data/lib/textus/ports/sentinel_store.rb +2 -2
- data/lib/textus/ports/watcher_lock.rb +48 -0
- data/lib/textus/produce/engine.rb +25 -70
- data/lib/textus/produce/render.rb +2 -7
- data/lib/textus/schema/tools.rb +4 -3
- data/lib/textus/session.rb +6 -3
- data/lib/textus/store.rb +12 -19
- data/lib/textus/surfaces/cli/group/data.rb +11 -0
- data/lib/textus/surfaces/cli/group/key.rb +11 -0
- data/lib/textus/surfaces/cli/group/mcp.rb +11 -0
- data/lib/textus/surfaces/cli/group/rule.rb +11 -0
- data/lib/textus/surfaces/cli/group/schema.rb +11 -0
- data/lib/textus/surfaces/cli/group.rb +50 -0
- data/lib/textus/surfaces/cli/runner.rb +236 -0
- data/lib/textus/surfaces/cli/verb/doctor.rb +21 -0
- data/lib/textus/surfaces/cli/verb/get.rb +21 -0
- data/lib/textus/surfaces/cli/verb/init.rb +20 -0
- data/lib/textus/surfaces/cli/verb/mcp_serve.rb +24 -0
- data/lib/textus/surfaces/cli/verb/put.rb +30 -0
- data/lib/textus/surfaces/cli/verb/schema_diff.rb +17 -0
- data/lib/textus/surfaces/cli/verb/schema_init.rb +21 -0
- data/lib/textus/surfaces/cli/verb/schema_migrate.rb +21 -0
- data/lib/textus/surfaces/cli/verb/watch.rb +19 -0
- data/lib/textus/surfaces/cli/verb.rb +111 -0
- data/lib/textus/surfaces/cli.rb +148 -0
- data/lib/textus/surfaces/mcp/catalog.rb +99 -0
- data/lib/textus/surfaces/mcp/errors.rb +25 -0
- data/lib/textus/surfaces/mcp/routing.rb +51 -0
- data/lib/textus/surfaces/mcp/server.rb +184 -0
- data/lib/textus/surfaces/mcp/session.rb +9 -0
- data/lib/textus/surfaces/mcp/tool_schemas.rb +17 -0
- data/lib/textus/surfaces/mcp.rb +8 -0
- data/lib/textus/surfaces/role_scope.rb +34 -0
- data/lib/textus/surfaces/watcher.rb +38 -0
- data/lib/textus/version.rb +1 -1
- data/lib/textus/workflow/collector.rb +27 -0
- data/lib/textus/workflow/context.rb +5 -0
- data/lib/textus/workflow/dsl.rb +44 -0
- data/lib/textus/workflow/loader.rb +17 -0
- data/lib/textus/workflow/pattern.rb +18 -0
- data/lib/textus/workflow/registry.rb +21 -0
- data/lib/textus/workflow/runner.rb +84 -0
- data/lib/textus/workflow.rb +28 -0
- data/lib/textus.rb +75 -22
- metadata +118 -132
- data/lib/textus/cli/group/hook.rb +0 -9
- data/lib/textus/cli/group/key.rb +0 -9
- data/lib/textus/cli/group/mcp.rb +0 -9
- data/lib/textus/cli/group/rule.rb +0 -9
- data/lib/textus/cli/group/schema.rb +0 -9
- data/lib/textus/cli/group/zone.rb +0 -9
- data/lib/textus/cli/group.rb +0 -48
- data/lib/textus/cli/runner.rb +0 -193
- data/lib/textus/cli/verb/doctor.rb +0 -17
- data/lib/textus/cli/verb/get.rb +0 -18
- data/lib/textus/cli/verb/hook_run.rb +0 -48
- data/lib/textus/cli/verb/hooks.rb +0 -50
- data/lib/textus/cli/verb/init.rb +0 -18
- data/lib/textus/cli/verb/mcp_serve.rb +0 -22
- data/lib/textus/cli/verb/put.rb +0 -30
- data/lib/textus/cli/verb/schema_diff.rb +0 -15
- data/lib/textus/cli/verb/schema_init.rb +0 -19
- data/lib/textus/cli/verb/schema_migrate.rb +0 -19
- data/lib/textus/cli/verb/serve.rb +0 -19
- data/lib/textus/cli/verb.rb +0 -116
- data/lib/textus/cli.rb +0 -138
- data/lib/textus/dispatcher.rb +0 -54
- data/lib/textus/doctor/check/handler_allowlist.rb +0 -34
- data/lib/textus/doctor/check/hooks.rb +0 -32
- data/lib/textus/doctor/check/intake_registration.rb +0 -46
- data/lib/textus/domain/action.rb +0 -9
- data/lib/textus/domain/jobs/registry.rb +0 -37
- data/lib/textus/domain/permission.rb +0 -7
- data/lib/textus/domain/policy/base_guards.rb +0 -25
- data/lib/textus/domain/policy/evaluation.rb +0 -15
- data/lib/textus/domain/policy/guard.rb +0 -35
- data/lib/textus/domain/policy/guard_factory.rb +0 -40
- data/lib/textus/domain/policy/handler_allowlist.rb +0 -17
- data/lib/textus/domain/policy/predicates/author_held.rb +0 -33
- data/lib/textus/domain/policy/predicates/etag_match.rb +0 -32
- data/lib/textus/domain/policy/predicates/fresh_within.rb +0 -59
- data/lib/textus/domain/policy/predicates/registry.rb +0 -39
- data/lib/textus/domain/policy/predicates/schema_valid.rb +0 -61
- data/lib/textus/domain/policy/predicates/target_is_canon.rb +0 -33
- data/lib/textus/domain/policy/predicates/zone_writable_by.rb +0 -39
- data/lib/textus/domain/policy/source.rb +0 -73
- data/lib/textus/envelope/io/reader.rb +0 -48
- data/lib/textus/envelope/io/writer.rb +0 -182
- data/lib/textus/hooks/builtin.rb +0 -70
- data/lib/textus/hooks/catalog.rb +0 -37
- data/lib/textus/hooks/context.rb +0 -72
- data/lib/textus/hooks/error_log.rb +0 -32
- data/lib/textus/hooks/event_bus.rb +0 -114
- data/lib/textus/hooks/fire_report.rb +0 -23
- data/lib/textus/hooks/loader.rb +0 -54
- data/lib/textus/hooks/rpc_registry.rb +0 -43
- data/lib/textus/hooks/signature.rb +0 -31
- data/lib/textus/init/templates/machine_intake.rb +0 -45
- data/lib/textus/init/templates/orientation_reducer.rb +0 -17
- data/lib/textus/jobs/handlers.rb +0 -62
- data/lib/textus/jobs/scheduler.rb +0 -36
- data/lib/textus/jobs/seeder.rb +0 -57
- data/lib/textus/maintenance/drain.rb +0 -42
- data/lib/textus/maintenance/key_delete_prefix.rb +0 -48
- data/lib/textus/maintenance/key_mv_prefix.rb +0 -68
- data/lib/textus/maintenance/retention/apply.rb +0 -52
- data/lib/textus/maintenance/rule_lint.rb +0 -66
- data/lib/textus/maintenance/serve.rb +0 -30
- data/lib/textus/maintenance/worker.rb +0 -74
- data/lib/textus/maintenance/zone_mv.rb +0 -64
- data/lib/textus/maintenance.rb +0 -15
- data/lib/textus/manifest/entry/validators/events.rb +0 -22
- data/lib/textus/mcp/catalog.rb +0 -70
- data/lib/textus/mcp/errors.rb +0 -32
- data/lib/textus/mcp/server.rb +0 -138
- data/lib/textus/mcp/session.rb +0 -7
- data/lib/textus/mcp/tool_schemas.rb +0 -15
- data/lib/textus/mcp.rb +0 -6
- data/lib/textus/mustache.rb +0 -117
- data/lib/textus/ports/audit_subscriber.rb +0 -42
- data/lib/textus/ports/produce_on_write_subscriber.rb +0 -73
- data/lib/textus/produce/acquire/handler.rb +0 -29
- data/lib/textus/produce/acquire/intake.rb +0 -130
- data/lib/textus/produce/acquire/projection.rb +0 -127
- data/lib/textus/produce/acquire/serializer/json.rb +0 -31
- data/lib/textus/produce/acquire/serializer/text.rb +0 -16
- data/lib/textus/produce/acquire/serializer/yaml.rb +0 -31
- data/lib/textus/produce/acquire/serializer.rb +0 -17
- data/lib/textus/produce/events.rb +0 -36
- data/lib/textus/projection.rb +0 -103
- data/lib/textus/read/audit.rb +0 -130
- data/lib/textus/read/boot.rb +0 -26
- data/lib/textus/read/capabilities.rb +0 -70
- data/lib/textus/read/deps.rb +0 -38
- data/lib/textus/read/doctor.rb +0 -27
- data/lib/textus/read/freshness.rb +0 -152
- data/lib/textus/read/get.rb +0 -73
- data/lib/textus/read/jobs.rb +0 -31
- data/lib/textus/read/list.rb +0 -24
- data/lib/textus/read/published.rb +0 -22
- data/lib/textus/read/pulse.rb +0 -98
- data/lib/textus/read/rdeps.rb +0 -39
- data/lib/textus/read/rule_explain.rb +0 -96
- data/lib/textus/read/rule_list.rb +0 -54
- data/lib/textus/read/schema_envelope.rb +0 -25
- data/lib/textus/read/uid.rb +0 -29
- data/lib/textus/read/validate_all.rb +0 -36
- data/lib/textus/read/where.rb +0 -24
- data/lib/textus/role_scope.rb +0 -78
- data/lib/textus/write/accept.rb +0 -58
- data/lib/textus/write/enqueue.rb +0 -50
- data/lib/textus/write/key_delete.rb +0 -65
- data/lib/textus/write/key_mv.rb +0 -141
- data/lib/textus/write/propose.rb +0 -54
- data/lib/textus/write/put.rb +0 -74
- data/lib/textus/write/reject.rb +0 -68
|
@@ -18,8 +18,6 @@ module Textus
|
|
|
18
18
|
# the block groups the two but does not make exclusivity structural, so
|
|
19
19
|
# this stays the one enforcement point (ADR 0052 D2).
|
|
20
20
|
def self.resolve(entry)
|
|
21
|
-
reject_removed_publish_each(entry)
|
|
22
|
-
|
|
23
21
|
set = []
|
|
24
22
|
set << "publish.to" unless Array(entry.publish_to).empty?
|
|
25
23
|
set << "publish.tree" unless entry.publish_tree.nil?
|
|
@@ -33,16 +31,6 @@ module Textus
|
|
|
33
31
|
mode_for(entry, set.first)
|
|
34
32
|
end
|
|
35
33
|
|
|
36
|
-
def self.reject_removed_publish_each(entry)
|
|
37
|
-
return unless entry.raw["publish_each"]
|
|
38
|
-
|
|
39
|
-
raise Textus::UsageError.new(
|
|
40
|
-
"entry '#{entry.key}': publish_each was removed in 0.42.0 (ADR 0051) — " \
|
|
41
|
-
"mirror the subtree with `publish: { tree: \"...\" }`.",
|
|
42
|
-
)
|
|
43
|
-
end
|
|
44
|
-
private_class_method :reject_removed_publish_each
|
|
45
|
-
|
|
46
34
|
def self.mode_for(entry, key)
|
|
47
35
|
case key
|
|
48
36
|
when "publish.to" then ToPaths.new(entry)
|
|
@@ -5,7 +5,7 @@ module Textus
|
|
|
5
5
|
module FormatMatrix
|
|
6
6
|
def self.call(entry, policy:) # rubocop:disable Lint/UnusedMethodArgument
|
|
7
7
|
begin
|
|
8
|
-
Textus::
|
|
8
|
+
Textus::Format.for(entry.format).validate_path_extension(entry.path, entry.nested?)
|
|
9
9
|
rescue UsageError => e
|
|
10
10
|
raise UsageError.new("entry '#{entry.key}': #{e.message}")
|
|
11
11
|
end
|
|
@@ -0,0 +1,30 @@
|
|
|
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,6 +1,6 @@
|
|
|
1
1
|
module Textus
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
class Manifest
|
|
3
|
+
class Policy
|
|
4
4
|
# Garbage collection (ADR 0093). A glob-matched rule slot: when an entry
|
|
5
5
|
# ages past `ttl`, retire it. Destructive only — runs on the full
|
|
6
6
|
# `converge` pass, never on a write (ADR 0079's invariant). Orthogonal to
|
|
@@ -21,7 +21,7 @@ module Textus
|
|
|
21
21
|
raise Textus::BadManifest.new("retention action must be one of #{ACTIONS.join("|")}, got #{raw["action"].inspect}")
|
|
22
22
|
end
|
|
23
23
|
|
|
24
|
-
def ttl_seconds = Textus::
|
|
24
|
+
def ttl_seconds = Textus::Core::Duration.seconds(@ttl)
|
|
25
25
|
def destructive? = true
|
|
26
26
|
end
|
|
27
27
|
end
|
|
@@ -0,0 +1,30 @@
|
|
|
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,23 +1,23 @@
|
|
|
1
1
|
module Textus
|
|
2
2
|
class Manifest
|
|
3
|
-
# Authority over
|
|
3
|
+
# Authority over lanes and roles derived from a Manifest::Data snapshot.
|
|
4
4
|
# Encapsulates the lookups previously living on Manifest itself
|
|
5
|
-
# (
|
|
6
|
-
# capabilities
|
|
7
|
-
# (Schema::KIND_REQUIRES_VERB) and a role may write a
|
|
8
|
-
# include that verb (
|
|
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
9
|
# proposal-queue status is authoritative via the declared-kind family
|
|
10
|
-
# (declared_kind, derived_entry?,
|
|
10
|
+
# (declared_kind, derived_entry?, queue_lane?, queue_lane).
|
|
11
11
|
class Policy
|
|
12
12
|
def initialize(data)
|
|
13
13
|
@data = data
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
-
# The capability a
|
|
17
|
-
#
|
|
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
18
|
# keyed by String.
|
|
19
|
-
def
|
|
20
|
-
kind = declared_kind(
|
|
19
|
+
def verb_for_lane(lane_name)
|
|
20
|
+
kind = declared_kind(lane_name)
|
|
21
21
|
kind && Schema::KIND_REQUIRES_VERB[kind.to_s]
|
|
22
22
|
end
|
|
23
23
|
|
|
@@ -41,67 +41,52 @@ module Textus
|
|
|
41
41
|
roles_with_capability(verb).first
|
|
42
42
|
end
|
|
43
43
|
|
|
44
|
-
# The
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
raise UsageError.new("undeclared zone '#{zone_name}'") unless @data.declared_zone_kinds.key?(zone_name)
|
|
48
|
-
|
|
49
|
-
roles_with_capability(verb_for_zone(zone_name))
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
def permission_for(zone_name)
|
|
53
|
-
Textus::Domain::Permission.new(
|
|
54
|
-
zone: zone_name,
|
|
55
|
-
writers: zone_writers(zone_name),
|
|
56
|
-
)
|
|
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]
|
|
57
47
|
end
|
|
58
48
|
|
|
59
|
-
#
|
|
60
|
-
|
|
61
|
-
|
|
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
|
|
62
53
|
end
|
|
63
54
|
|
|
64
|
-
#
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
@data.declared_zone_kinds.select { |_name, k| k == kind }.keys
|
|
55
|
+
# The single lane declaring `kind: queue`, or nil. Schema guarantees <=1.
|
|
56
|
+
def queue_lane
|
|
57
|
+
@data.declared_lane_kinds.key(:queue)
|
|
68
58
|
end
|
|
69
59
|
|
|
70
|
-
#
|
|
71
|
-
|
|
72
|
-
@data.declared_zone_kinds.key(:queue)
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
# ADR 0091: derived-ness is a property of the ENTRY, not its zone (one
|
|
76
|
-
# machine zone holds both intake and derived entries). Resolve the entry
|
|
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
|
|
77
62
|
# and ask it directly. Returns false if entries are not yet built
|
|
78
63
|
# (validator phase during Data#initialize) — validators must not rely on
|
|
79
64
|
# cross-entry state during construction.
|
|
80
|
-
def derived_entry?(
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
entry = @data.entries.find { |e| e.key == key } or return false
|
|
84
|
-
entry.derived?
|
|
65
|
+
def derived_entry?(_key)
|
|
66
|
+
false
|
|
85
67
|
end
|
|
86
68
|
|
|
87
|
-
# The single
|
|
88
|
-
def
|
|
89
|
-
@data.
|
|
69
|
+
# The single lane declaring kind: machine, or nil.
|
|
70
|
+
def machine_lane
|
|
71
|
+
@data.declared_lane_kinds.key(:machine)
|
|
90
72
|
end
|
|
91
73
|
|
|
92
|
-
# A
|
|
93
|
-
def
|
|
94
|
-
declared_kind(
|
|
74
|
+
# A lane is a proposal queue iff it declares kind: queue.
|
|
75
|
+
def queue_lane?(lane_name)
|
|
76
|
+
declared_kind(lane_name) == :queue
|
|
95
77
|
end
|
|
96
78
|
|
|
97
|
-
# The
|
|
79
|
+
# The lane a proposer role writes proposals into: the single lane that
|
|
98
80
|
# declares kind: queue, when the role can write it. Returns nil if there
|
|
99
|
-
# is no queue
|
|
100
|
-
def
|
|
81
|
+
# is no queue lane or the role cannot write it.
|
|
82
|
+
def propose_lane_for(role)
|
|
101
83
|
return nil if role.nil?
|
|
102
84
|
|
|
103
|
-
q =
|
|
104
|
-
return nil unless q
|
|
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)
|
|
105
90
|
|
|
106
91
|
q
|
|
107
92
|
end
|
|
@@ -53,8 +53,9 @@ module Textus
|
|
|
53
53
|
else
|
|
54
54
|
raise UnknownKey.new(key, suggestions: suggestions_for(key)) unless nested_entry?(entry)
|
|
55
55
|
|
|
56
|
-
primary_ext = Textus::
|
|
57
|
-
|
|
56
|
+
primary_ext = Textus::Format.for(entry.format).extensions.first
|
|
57
|
+
base = Textus::Key::Path.normalize_relative_path(entry.path)
|
|
58
|
+
path = File.join(@data.root, base, *remaining) + primary_ext
|
|
58
59
|
Resolution.new(entry: entry, path: path, remaining: remaining)
|
|
59
60
|
end
|
|
60
61
|
end
|
|
@@ -73,7 +74,7 @@ module Textus
|
|
|
73
74
|
# addressable store keys in the public `list` surface.
|
|
74
75
|
return [] if entry.publish_mode.keyless? && !include_keyless
|
|
75
76
|
|
|
76
|
-
base = File.join(@data.root,
|
|
77
|
+
base = File.join(@data.root, Textus::Key::Path.normalize_relative_path(entry.path))
|
|
77
78
|
return [] unless File.directory?(base)
|
|
78
79
|
|
|
79
80
|
Dir.glob(File.join(base, nested_glob(entry.format)))
|
|
@@ -110,7 +111,7 @@ module Textus
|
|
|
110
111
|
end
|
|
111
112
|
|
|
112
113
|
def nested_glob(format)
|
|
113
|
-
Textus::
|
|
114
|
+
Textus::Format.for(format).nested_glob
|
|
114
115
|
end
|
|
115
116
|
end
|
|
116
117
|
end
|
|
@@ -22,7 +22,7 @@ module Textus
|
|
|
22
22
|
def for(key)
|
|
23
23
|
slots = PICK_FIELDS.to_h { |f| [f, []] }
|
|
24
24
|
@blocks.each do |b|
|
|
25
|
-
next unless Textus::
|
|
25
|
+
next unless Textus::Manifest::Policy::Matcher.matches?(b.match, key)
|
|
26
26
|
|
|
27
27
|
slots.each_key { |slot| slots[slot] << b if b.public_send(slot) }
|
|
28
28
|
end
|
|
@@ -30,7 +30,7 @@ module Textus
|
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
def explain(key)
|
|
33
|
-
@blocks.select { |b| Textus::
|
|
33
|
+
@blocks.select { |b| Textus::Manifest::Policy::Matcher.matches?(b.match, key) }
|
|
34
34
|
end
|
|
35
35
|
|
|
36
36
|
private
|
|
@@ -39,7 +39,7 @@ module Textus
|
|
|
39
39
|
return nil if blocks.empty?
|
|
40
40
|
|
|
41
41
|
globs = blocks.map(&:match)
|
|
42
|
-
winning = Textus::
|
|
42
|
+
winning = Textus::Manifest::Policy::Matcher.pick_most_specific(globs, key: key)
|
|
43
43
|
blocks.find { |b| b.match == winning }&.public_send(slot)
|
|
44
44
|
end
|
|
45
45
|
|
|
@@ -57,7 +57,7 @@ module Textus
|
|
|
57
57
|
|
|
58
58
|
# One dispatch over the registry, replacing the four bespoke parse_*
|
|
59
59
|
# methods. :deferred carries the raw Hash after a shape check (its
|
|
60
|
-
# contents validate later — guard predicates at
|
|
60
|
+
# contents validate later — guard predicates at Dispatch::Auth check time,
|
|
61
61
|
# ADR 0031); :immediate instantiates the policy class now. :tagged passes
|
|
62
62
|
# the raw Hash straight to a policy class that is a tagged union and
|
|
63
63
|
# dispatches on its discriminator field (e.g. upkeep's on:). A mapping
|
|
@@ -4,28 +4,21 @@ module Textus
|
|
|
4
4
|
# The manifest's key whitelists and the rule-field registry — the schema's
|
|
5
5
|
# data tables (ADR 0109; the vocabulary lives in Schema::Vocabulary).
|
|
6
6
|
module Keys
|
|
7
|
-
ROOT_KEYS = %w[version roles
|
|
7
|
+
ROOT_KEYS = %w[version roles lanes entries rules audit].freeze
|
|
8
8
|
ROLE_KEYS = %w[name can].freeze
|
|
9
|
-
|
|
9
|
+
LANE_KEYS = %w[name kind owner desc].freeze
|
|
10
10
|
ENTRY_KEYS = %w[
|
|
11
|
-
key path
|
|
12
|
-
source publish
|
|
13
|
-
events ignore tracked
|
|
11
|
+
key path lane kind schema owner nested format
|
|
12
|
+
source publish tracked ignore
|
|
14
13
|
].freeze
|
|
15
14
|
# ADR 0052: the typed publish block — `publish: { to: [...] }` (file
|
|
16
15
|
# fan-out) xor `publish: { tree: "dir" }` (subtree mirror).
|
|
17
16
|
PUBLISH_KEYS = %w[to tree].freeze
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
|
|
23
|
-
# `inject_boot`/`provenance` fields are kept here so the schema walk can
|
|
24
|
-
# still emit the migration hint rather than a bare "unknown key".
|
|
25
|
-
SOURCE_KEYS = %w[
|
|
26
|
-
from handler config template project command sources ttl inject_boot provenance
|
|
27
|
-
select pluck sort_by transform
|
|
28
|
-
].freeze
|
|
17
|
+
# entry-level external-generator block — only `from: external` is valid.
|
|
18
|
+
# `from: fetch` and `from: derive` and their specific fields (handler,
|
|
19
|
+
# config, ttl, select, pluck, sort_by, transform) were removed in the
|
|
20
|
+
# workflow redesign.
|
|
21
|
+
SOURCE_KEYS = %w[from command sources].freeze
|
|
29
22
|
# ADR 0093: rule-level GC slot. drop/archive only (refresh gone).
|
|
30
23
|
RETENTION_KEYS = %w[ttl action].freeze
|
|
31
24
|
|
|
@@ -40,12 +33,11 @@ module Textus
|
|
|
40
33
|
# (in_rule_list / in_rule_explain)
|
|
41
34
|
#
|
|
42
35
|
# Per field:
|
|
43
|
-
# yaml_key manifest key
|
|
44
|
-
#
|
|
45
|
-
# policy_class the Domain::Policy backing the field (nil = raw value)
|
|
36
|
+
# yaml_key manifest key used in a rule block
|
|
37
|
+
# policy_class the Manifest::Policy backing the field (nil = raw value)
|
|
46
38
|
# validation :immediate (instantiate the policy at parse, surfacing
|
|
47
39
|
# shape errors eagerly), :deferred (shape-check + carry
|
|
48
|
-
# the raw Hash; guard predicates validate at
|
|
40
|
+
# the raw Hash; guard predicates validate at Dispatch::Auth
|
|
49
41
|
# build time, ADR 0031), or :tagged (pass the raw Hash to a
|
|
50
42
|
# tagged-union policy that dispatches on its discriminator
|
|
51
43
|
# field, e.g. upkeep's on:)
|
|
@@ -61,13 +53,6 @@ module Textus
|
|
|
61
53
|
# Key order here fixes the order of RULE_KEYS (after match), the slots,
|
|
62
54
|
# the RuleSet members, and the doctor SLOTS.
|
|
63
55
|
FIELD_REGISTRY = {
|
|
64
|
-
handler_allowlist: {
|
|
65
|
-
yaml_key: "intake_handler_allowlist",
|
|
66
|
-
policy_class: Textus::Domain::Policy::HandlerAllowlist,
|
|
67
|
-
validation: :immediate, sub_keys: nil, arg_key: :handlers,
|
|
68
|
-
in_pick: true, in_ambiguity: true,
|
|
69
|
-
in_rule_list: true, in_rule_explain: %i[detail]
|
|
70
|
-
},
|
|
71
56
|
guard: {
|
|
72
57
|
yaml_key: "guard",
|
|
73
58
|
policy_class: nil,
|
|
@@ -77,11 +62,18 @@ module Textus
|
|
|
77
62
|
},
|
|
78
63
|
retention: {
|
|
79
64
|
yaml_key: "retention",
|
|
80
|
-
policy_class: Textus::
|
|
65
|
+
policy_class: Textus::Manifest::Policy::Retention,
|
|
81
66
|
validation: :tagged, sub_keys: RETENTION_KEYS, arg_key: nil,
|
|
82
67
|
in_pick: true, in_ambiguity: true,
|
|
83
68
|
in_rule_list: true, in_rule_explain: %i[lean detail]
|
|
84
69
|
},
|
|
70
|
+
react: {
|
|
71
|
+
yaml_key: "react",
|
|
72
|
+
policy_class: Textus::Manifest::Policy::React,
|
|
73
|
+
validation: :immediate, sub_keys: nil, arg_key: :raw,
|
|
74
|
+
in_pick: true, in_ambiguity: true,
|
|
75
|
+
in_rule_list: true, in_rule_explain: %i[lean detail]
|
|
76
|
+
},
|
|
85
77
|
}.freeze
|
|
86
78
|
|
|
87
79
|
RULE_KEYS = (["match"] + FIELD_REGISTRY.values.map { |m| m[:yaml_key] }).freeze
|
|
@@ -14,34 +14,36 @@ module Textus
|
|
|
14
14
|
raise BadManifest.new("manifest must be a hash") unless raw.is_a?(Hash)
|
|
15
15
|
|
|
16
16
|
walk(raw, ROOT_KEYS, "$")
|
|
17
|
+
raise BadManifest.new("manifest must declare lanes:") if Array(raw["lanes"]).empty?
|
|
18
|
+
|
|
17
19
|
validate_roles!(raw["roles"])
|
|
18
|
-
|
|
20
|
+
validate_lanes!(raw["lanes"])
|
|
19
21
|
validate_entries!(raw["entries"])
|
|
20
|
-
validate_owners!(raw["
|
|
22
|
+
validate_owners!(raw["lanes"], raw["entries"])
|
|
21
23
|
validate_rules!(raw["rules"])
|
|
22
24
|
walk(raw["audit"], AUDIT_KEYS, "$.audit") if raw["audit"].is_a?(Hash)
|
|
23
25
|
validate_single_queue!(raw)
|
|
24
26
|
validate_single_machine!(raw)
|
|
25
|
-
|
|
27
|
+
validate_lane_kind_consistency!(raw)
|
|
26
28
|
end
|
|
27
29
|
|
|
28
|
-
def
|
|
29
|
-
Array(
|
|
30
|
-
walk(z,
|
|
30
|
+
def validate_lanes!(lanes)
|
|
31
|
+
Array(lanes).each_with_index do |z, i|
|
|
32
|
+
walk(z, LANE_KEYS, "$.lanes[#{i}]")
|
|
31
33
|
if z["kind"].nil?
|
|
32
|
-
raise BadManifest.new("
|
|
34
|
+
raise BadManifest.new("lane '#{z["name"]}' at '$.lanes[#{i}]' must declare a kind (one of: #{LANE_KINDS.join(", ")})")
|
|
33
35
|
end
|
|
34
|
-
next if
|
|
36
|
+
next if LANE_KINDS.include?(z["kind"])
|
|
35
37
|
|
|
36
38
|
if %w[quarantine derived].include?(z["kind"])
|
|
37
39
|
raise BadManifest.new(
|
|
38
|
-
"
|
|
40
|
+
"lane kind '#{z["kind"]}' at '$.lanes[#{i}]' was folded into 'machine' (ADR 0091) — " \
|
|
39
41
|
"use `kind: machine`",
|
|
40
42
|
)
|
|
41
43
|
end
|
|
42
44
|
|
|
43
45
|
raise BadManifest.new(
|
|
44
|
-
"unknown
|
|
46
|
+
"unknown lane kind '#{z["kind"]}' at '$.lanes[#{i}]' (known: #{LANE_KINDS.join(", ")})",
|
|
45
47
|
)
|
|
46
48
|
end
|
|
47
49
|
end
|
|
@@ -217,9 +219,9 @@ module Textus
|
|
|
217
219
|
# (ADR 0045 D1) so attribution can't bypass the closed-name guarantee.
|
|
218
220
|
# Applies to both zone owners and entry owners; owner is optional, so a
|
|
219
221
|
# nil owner is not an error.
|
|
220
|
-
def validate_owners!(
|
|
221
|
-
Array(
|
|
222
|
-
check_owner!(z["owner"], "$.
|
|
222
|
+
def validate_owners!(lanes, entries)
|
|
223
|
+
Array(lanes).each_with_index do |z, i|
|
|
224
|
+
check_owner!(z["owner"], "$.lanes[#{i}]")
|
|
223
225
|
end
|
|
224
226
|
Array(entries).each_with_index do |e, i|
|
|
225
227
|
check_owner!(e["owner"], "$.entries[#{i}]")
|
|
@@ -265,20 +267,20 @@ module Textus
|
|
|
265
267
|
end
|
|
266
268
|
|
|
267
269
|
def validate_single_queue!(raw)
|
|
268
|
-
queues = Array(raw["
|
|
270
|
+
queues = Array(raw["lanes"]).select { |z| z["kind"] == "queue" }.map { |z| z["name"] }
|
|
269
271
|
return if queues.size <= 1
|
|
270
272
|
|
|
271
273
|
raise BadManifest.new(
|
|
272
|
-
"at most one
|
|
274
|
+
"at most one lane may declare kind: queue (found: #{queues.join(", ")})",
|
|
273
275
|
)
|
|
274
276
|
end
|
|
275
277
|
|
|
276
278
|
def validate_single_machine!(raw)
|
|
277
|
-
machines = Array(raw["
|
|
279
|
+
machines = Array(raw["lanes"]).select { |z| z["kind"] == "machine" }.map { |z| z["name"] }
|
|
278
280
|
return if machines.size <= 1
|
|
279
281
|
|
|
280
282
|
raise BadManifest.new(
|
|
281
|
-
"at most one
|
|
283
|
+
"at most one lane may declare kind: machine (found: #{machines.join(", ")})",
|
|
282
284
|
)
|
|
283
285
|
end
|
|
284
286
|
|
|
@@ -287,33 +289,25 @@ module Textus
|
|
|
287
289
|
# out). Per ADR 0095 the produce-method is read from source.from on the one
|
|
288
290
|
# Produced kind, so there is no longer a kind to agree against the source.
|
|
289
291
|
# (Replaces validate_upkeep_kinds!.)
|
|
290
|
-
def validate_source_and_retention!(
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
next if retention.nil?
|
|
294
|
-
next unless entry.derived?
|
|
295
|
-
|
|
296
|
-
raise BadManifest.new(
|
|
297
|
-
"entry '#{entry.key}': a derived entry regenerates from its source; " \
|
|
298
|
-
"retention (drop/archive) is invalid",
|
|
299
|
-
)
|
|
300
|
-
end
|
|
292
|
+
def validate_source_and_retention!(_manifest)
|
|
293
|
+
# No-op: from: derive is removed; retention + produced entries are
|
|
294
|
+
# always valid (retention drives age-GC on workflow-produced entries).
|
|
301
295
|
end
|
|
302
296
|
|
|
303
|
-
# Write authority is derived from capabilities (ADR 0030): a
|
|
297
|
+
# Write authority is derived from capabilities (ADR 0030): a lane of a
|
|
304
298
|
# given kind can only be written by a role that holds the kind's required
|
|
305
|
-
# verb. Reject a manifest declaring a
|
|
299
|
+
# verb. Reject a manifest declaring a lane whose required verb is held by
|
|
306
300
|
# no role. Capabilities.resolve returns the defaults when `roles:` is nil,
|
|
307
301
|
# so the capability union is all four verbs and every kind is satisfied.
|
|
308
|
-
def
|
|
302
|
+
def validate_lane_kind_consistency!(raw)
|
|
309
303
|
held = Capabilities.resolve(raw["roles"]).values.flatten.uniq
|
|
310
304
|
|
|
311
|
-
Array(raw["
|
|
305
|
+
Array(raw["lanes"]).each_with_index do |z, i|
|
|
312
306
|
verb = KIND_REQUIRES_VERB[z["kind"]]
|
|
313
307
|
next if verb.nil? || held.include?(verb)
|
|
314
308
|
|
|
315
309
|
raise BadManifest.new(
|
|
316
|
-
"
|
|
310
|
+
"lane '#{z["name"]}' (#{z["kind"]}) at '$.lanes[#{i}]' " \
|
|
317
311
|
"needs a role with capability '#{verb}'; none declared",
|
|
318
312
|
)
|
|
319
313
|
end
|
|
@@ -13,9 +13,10 @@ module Textus
|
|
|
13
13
|
"workspace" => "keep",
|
|
14
14
|
"machine" => "converge",
|
|
15
15
|
"queue" => "propose",
|
|
16
|
+
"raw" => "ingest",
|
|
16
17
|
}.freeze
|
|
17
18
|
|
|
18
|
-
|
|
19
|
+
LANE_KINDS = LANES.keys.freeze
|
|
19
20
|
CAPABILITIES = LANES.values.uniq.freeze
|
|
20
21
|
KIND_REQUIRES_VERB = LANES
|
|
21
22
|
end
|
|
@@ -7,13 +7,13 @@ module Textus
|
|
|
7
7
|
module Schema
|
|
8
8
|
# Re-export the vocabulary.
|
|
9
9
|
LANES = Vocabulary::LANES
|
|
10
|
-
|
|
10
|
+
LANE_KINDS = Vocabulary::LANE_KINDS
|
|
11
11
|
CAPABILITIES = Vocabulary::CAPABILITIES
|
|
12
12
|
KIND_REQUIRES_VERB = Vocabulary::KIND_REQUIRES_VERB
|
|
13
13
|
# Re-export the keys + registry.
|
|
14
14
|
ROOT_KEYS = Keys::ROOT_KEYS
|
|
15
15
|
ROLE_KEYS = Keys::ROLE_KEYS
|
|
16
|
-
|
|
16
|
+
LANE_KEYS = Keys::LANE_KEYS
|
|
17
17
|
ENTRY_KEYS = Keys::ENTRY_KEYS
|
|
18
18
|
PUBLISH_KEYS = Keys::PUBLISH_KEYS
|
|
19
19
|
SOURCE_KEYS = Keys::SOURCE_KEYS
|
data/lib/textus/manifest.rb
CHANGED
|
@@ -6,8 +6,8 @@ module Textus
|
|
|
6
6
|
#
|
|
7
7
|
# * data — frozen value: raw, root, zones, entries, audit_config, role_caps
|
|
8
8
|
# * resolver — resolves keys → entry + path
|
|
9
|
-
# * policy — zone/role authority (
|
|
10
|
-
#
|
|
9
|
+
# * policy — zone/role authority (lane_writers, declared_kind, derived_entry?,
|
|
10
|
+
# queue_lane?, permission_for, …)
|
|
11
11
|
# * rules — match-block rule engine (lifecycle, handler allowlist, materialize, …)
|
|
12
12
|
#
|
|
13
13
|
# Use `manifest.data.entries`, `manifest.policy.declared_kind(z)`, etc.
|
|
@@ -78,16 +78,42 @@ module Textus
|
|
|
78
78
|
def verify_integrity
|
|
79
79
|
return [] unless File.exist?(@path)
|
|
80
80
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
81
|
+
[].tap do |out|
|
|
82
|
+
iterate_with_prev_seq do |line, lineno, prev_seq|
|
|
83
|
+
check_line_integrity(line, lineno, prev_seq, out)
|
|
84
|
+
end
|
|
85
85
|
end
|
|
86
|
-
out
|
|
87
86
|
end
|
|
88
87
|
|
|
89
88
|
private
|
|
90
89
|
|
|
90
|
+
def iterate_with_prev_seq
|
|
91
|
+
prev_seq = nil
|
|
92
|
+
File.foreach(@path).with_index(1) do |line, lineno|
|
|
93
|
+
yield line.chomp, lineno, prev_seq
|
|
94
|
+
parsed = parse_row(line.chomp)
|
|
95
|
+
prev_seq = parsed["seq"] if parsed&.dig("seq").is_a?(Integer)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def check_line_integrity(line, lineno, prev_seq, out)
|
|
100
|
+
violation = check_line(line, lineno)
|
|
101
|
+
if violation
|
|
102
|
+
out << violation
|
|
103
|
+
return
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
parsed = parse_row(line)
|
|
107
|
+
return unless parsed && (seq = parsed["seq"]).is_a?(Integer)
|
|
108
|
+
return unless prev_seq && seq != prev_seq + 1
|
|
109
|
+
|
|
110
|
+
out << {
|
|
111
|
+
"lineno" => lineno,
|
|
112
|
+
"reason" => "seq_gap",
|
|
113
|
+
"detail" => "expected #{prev_seq + 1}, got #{seq}",
|
|
114
|
+
}
|
|
115
|
+
end
|
|
116
|
+
|
|
91
117
|
def rotated(n)
|
|
92
118
|
File.join(Textus::Layout.audit_dir(@root), "audit.log.#{n}")
|
|
93
119
|
end
|