moku6 0.1.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.
Files changed (83) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +21 -0
  3. data/README.md +131 -0
  4. data/exe/moku6 +6 -0
  5. data/lib/moku6/catalog.rb +23 -0
  6. data/lib/moku6/cli.rb +118 -0
  7. data/lib/moku6/config.rb +61 -0
  8. data/lib/moku6/differ.rb +74 -0
  9. data/lib/moku6/envelope_schema.rb +56 -0
  10. data/lib/moku6/event.rb +54 -0
  11. data/lib/moku6/generate.rb +95 -0
  12. data/lib/moku6/generators/base_generator.rb +31 -0
  13. data/lib/moku6/generators/bigquery_generator.rb +29 -0
  14. data/lib/moku6/generators/cloud_events_generator.rb +47 -0
  15. data/lib/moku6/generators/docs_generator.rb +49 -0
  16. data/lib/moku6/generators/event_catalog_generator.rb +57 -0
  17. data/lib/moku6/generators/example_generator.rb +66 -0
  18. data/lib/moku6/generators/json_schema_generator.rb +23 -0
  19. data/lib/moku6/generators/open_api_generator.rb +60 -0
  20. data/lib/moku6/generators/outbox_generator.rb +96 -0
  21. data/lib/moku6/generators/rails_generator.rb +84 -0
  22. data/lib/moku6/generators/ruby_generator.rb +27 -0
  23. data/lib/moku6/generators/typescript_generator.rb +59 -0
  24. data/lib/moku6/generators.rb +29 -0
  25. data/lib/moku6/initializer.rb +44 -0
  26. data/lib/moku6/linter.rb +60 -0
  27. data/lib/moku6/loader.rb +31 -0
  28. data/lib/moku6/offense.rb +13 -0
  29. data/lib/moku6/reporter.rb +71 -0
  30. data/lib/moku6/result.rb +23 -0
  31. data/lib/moku6/rules/action_naming_rule.rb +18 -0
  32. data/lib/moku6/rules/base_rule.rb +37 -0
  33. data/lib/moku6/rules/example_consistency_rule.rb +53 -0
  34. data/lib/moku6/rules/label_description_rule.rb +21 -0
  35. data/lib/moku6/rules/pii_field_name_heuristic_rule.rb +45 -0
  36. data/lib/moku6/rules/privacy_masking_rule.rb +21 -0
  37. data/lib/moku6/rules/retention_rule.rb +19 -0
  38. data/lib/moku6/rules/schema_rule.rb +31 -0
  39. data/lib/moku6/rules/uniqueness_rule.rb +22 -0
  40. data/lib/moku6/rules/visibility_rule.rb +27 -0
  41. data/lib/moku6/version.rb +6 -0
  42. data/lib/moku6.rb +55 -0
  43. data/schemas/audit-event.schema.json +85 -0
  44. data/sig/generated/moku6/catalog.rbs +22 -0
  45. data/sig/generated/moku6/config.rbs +43 -0
  46. data/sig/generated/moku6/differ.rbs +28 -0
  47. data/sig/generated/moku6/envelope_schema.rbs +19 -0
  48. data/sig/generated/moku6/event.rbs +51 -0
  49. data/sig/generated/moku6/generators/base_generator.rbs +22 -0
  50. data/sig/generated/moku6/generators/bigquery_generator.rbs +12 -0
  51. data/sig/generated/moku6/generators/cloud_events_generator.rbs +19 -0
  52. data/sig/generated/moku6/generators/docs_generator.rbs +18 -0
  53. data/sig/generated/moku6/generators/event_catalog_generator.rbs +16 -0
  54. data/sig/generated/moku6/generators/example_generator.rbs +23 -0
  55. data/sig/generated/moku6/generators/json_schema_generator.rbs +12 -0
  56. data/sig/generated/moku6/generators/open_api_generator.rbs +20 -0
  57. data/sig/generated/moku6/generators/outbox_generator.rbs +23 -0
  58. data/sig/generated/moku6/generators/rails_generator.rbs +23 -0
  59. data/sig/generated/moku6/generators/ruby_generator.rbs +15 -0
  60. data/sig/generated/moku6/generators/typescript_generator.rbs +20 -0
  61. data/sig/generated/moku6/generators.rbs +13 -0
  62. data/sig/generated/moku6/initializer.rbs +23 -0
  63. data/sig/generated/moku6/linter.rbs +31 -0
  64. data/sig/generated/moku6/loader.rbs +15 -0
  65. data/sig/generated/moku6/reporter.rbs +30 -0
  66. data/sig/generated/moku6/result.rbs +22 -0
  67. data/sig/generated/moku6/rules/action_naming_rule.rbs +10 -0
  68. data/sig/generated/moku6/rules/base_rule.rbs +23 -0
  69. data/sig/generated/moku6/rules/example_consistency_rule.rbs +18 -0
  70. data/sig/generated/moku6/rules/label_description_rule.rbs +15 -0
  71. data/sig/generated/moku6/rules/pii_field_name_heuristic_rule.rbs +21 -0
  72. data/sig/generated/moku6/rules/privacy_masking_rule.rbs +10 -0
  73. data/sig/generated/moku6/rules/retention_rule.rbs +10 -0
  74. data/sig/generated/moku6/rules/schema_rule.rbs +15 -0
  75. data/sig/generated/moku6/rules/uniqueness_rule.rbs +11 -0
  76. data/sig/generated/moku6/rules/visibility_rule.rbs +10 -0
  77. data/sig/generated/moku6/version.rbs +5 -0
  78. data/sig/generated/moku6.rbs +9 -0
  79. data/sig/manual/dependencies.rbs +13 -0
  80. data/sig/manual/offense.rbs +13 -0
  81. data/templates/init/.moku6.yml +18 -0
  82. data/templates/init/catalog/employee.updated.yaml +36 -0
  83. metadata +141 -0
@@ -0,0 +1,60 @@
1
+ # rbs_inline: enabled
2
+ # frozen_string_literal: true
3
+
4
+ module Moku6
5
+ class Linter
6
+ EVENT_RULES = [
7
+ Rules::SchemaRule,
8
+ Rules::ActionNamingRule,
9
+ Rules::LabelDescriptionRule,
10
+ Rules::PrivacyMaskingRule,
11
+ Rules::VisibilityRule,
12
+ Rules::RetentionRule,
13
+ Rules::ExampleConsistencyRule,
14
+ Rules::PiiFieldNameHeuristicRule
15
+ ].freeze #: Array[singleton(Rules::BaseRule)]
16
+
17
+ CATALOG_RULES = [
18
+ Rules::UniquenessRule
19
+ ].freeze #: Array[singleton(Rules::BaseRule)]
20
+
21
+ # @rbs @catalog: Catalog
22
+ # @rbs @config: Config
23
+
24
+ class << self
25
+ # Extension points for consumers to register custom rules (design section 13).
26
+
27
+ #: (singleton(Rules::BaseRule) klass) -> Array[singleton(Rules::BaseRule)]
28
+ def register_event_rule(klass) = custom_event_rules << klass
29
+
30
+ #: (singleton(Rules::BaseRule) klass) -> Array[singleton(Rules::BaseRule)]
31
+ def register_catalog_rule(klass) = custom_catalog_rules << klass
32
+
33
+ #: () -> Array[singleton(Rules::BaseRule)]
34
+ def custom_event_rules = @custom_event_rules ||= []
35
+
36
+ #: () -> Array[singleton(Rules::BaseRule)]
37
+ def custom_catalog_rules = @custom_catalog_rules ||= []
38
+ end
39
+
40
+ #: (Catalog catalog, Config config) -> void
41
+ def initialize(catalog, config)
42
+ @catalog = catalog
43
+ @config = config
44
+ end
45
+
46
+ #: () -> Result
47
+ def run
48
+ offenses = []
49
+ (CATALOG_RULES + self.class.custom_catalog_rules).each do |rule_class|
50
+ offenses.concat(rule_class.new(@config).check_catalog(@catalog)) # steep:ignore NoMethod
51
+ end
52
+ @catalog.events.each do |event|
53
+ (EVENT_RULES + self.class.custom_event_rules).each do |rule_class|
54
+ offenses.concat(rule_class.new(@config).check(event))
55
+ end
56
+ end
57
+ Result.new(offenses)
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,31 @@
1
+ # rbs_inline: enabled
2
+ # frozen_string_literal: true
3
+
4
+ module Moku6
5
+ class Loader
6
+ PERMITTED_CLASSES = [Symbol].freeze #: Array[Class]
7
+
8
+ # @rbs @catalog_dir: String
9
+
10
+ #: (String catalog_dir) -> void
11
+ def initialize(catalog_dir) = @catalog_dir = catalog_dir
12
+
13
+ #: () -> Catalog
14
+ def load
15
+ unless Dir.exist?(@catalog_dir)
16
+ raise UsageError, "catalog directory not found: #{@catalog_dir}"
17
+ end
18
+
19
+ paths = Dir.glob(File.join(@catalog_dir, "**", "*.{yml,yaml}")).sort
20
+ events = paths.map do |path|
21
+ data = begin
22
+ YAML.safe_load_file(path, permitted_classes: PERMITTED_CLASSES)
23
+ rescue Psych::SyntaxError => e
24
+ raise UsageError, "failed to parse YAML (#{path}): #{e.message}"
25
+ end
26
+ Event.new(data, source_path: path)
27
+ end
28
+ Catalog.new(events)
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Moku6
4
+ # keyword_init is required on Ruby 3.1 (required_ruby_version >= 3.1.0).
5
+ Offense = Struct.new(
6
+ :rule, # String rule id
7
+ :severity, # Symbol :error | :warning
8
+ :action, # String target action (nil if unknown)
9
+ :file, # String source file path
10
+ :message, # String human-readable message
11
+ keyword_init: true # standard:disable Style/RedundantStructKeywordInit
12
+ )
13
+ end
@@ -0,0 +1,71 @@
1
+ # rbs_inline: enabled
2
+ # frozen_string_literal: true
3
+
4
+ module Moku6
5
+ module Reporter
6
+ #: (String? format) -> untyped
7
+ def self.for(format)
8
+ case format
9
+ when "json" then Json
10
+ when "markdown" then Markdown
11
+ else Text
12
+ end
13
+ end
14
+
15
+ module Text
16
+ #: (Result result) -> void
17
+ def self.report(result)
18
+ result.offenses.each do |o|
19
+ tag = (o.severity == :error) ? "ERROR" : "WARN "
20
+ puts "#{tag} #{o.file} #{o.action} #{o.message}"
21
+ end
22
+ e = result.errors.size
23
+ w = result.warnings.size
24
+ puts "\n#{e} error#{"s" if e != 1}, #{w} warning#{"s" if w != 1}"
25
+ end
26
+ end
27
+
28
+ module Json
29
+ #: (Result result) -> void
30
+ def self.report(result)
31
+ puts JSON.pretty_generate(
32
+ summary: {errors: result.errors.size, warnings: result.warnings.size},
33
+ offenses: result.offenses.map(&:to_h)
34
+ )
35
+ end
36
+ end
37
+
38
+ # Review-friendly Markdown report (e.g. for posting as a PR comment).
39
+ module Markdown
40
+ #: (Result result) -> void
41
+ def self.report(result) = puts render(result)
42
+
43
+ #: (Result result) -> String
44
+ def self.render(result)
45
+ errors = result.errors
46
+ warnings = result.warnings
47
+ out = ["# Audit catalog report", ""]
48
+ if result.empty?
49
+ out << "No issues detected. ✅"
50
+ return out.join("\n") + "\n"
51
+ end
52
+ out << "**#{errors.size} error#{"s" if errors.size != 1}, " \
53
+ "#{warnings.size} warning#{"s" if warnings.size != 1}** detected."
54
+ out.concat(section("❌ Errors", errors))
55
+ out.concat(section("⚠️ Warnings", warnings))
56
+ out.join("\n") + "\n"
57
+ end
58
+
59
+ #: (String title, Array[Offense] offenses) -> Array[String]
60
+ def self.section(title, offenses)
61
+ return [] if offenses.empty?
62
+
63
+ lines = ["", "## #{title}", ""]
64
+ offenses.each do |o|
65
+ lines << "- `#{o.action}` — #{o.message} (`#{o.file}`)"
66
+ end
67
+ lines
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,23 @@
1
+ # rbs_inline: enabled
2
+ # frozen_string_literal: true
3
+
4
+ module Moku6
5
+ class Result
6
+ attr_reader :offenses #: Array[Offense]
7
+
8
+ #: (Array[Offense] offenses) -> void
9
+ def initialize(offenses) = @offenses = offenses
10
+
11
+ #: () -> Array[Offense]
12
+ def errors = offenses.select { |o| o.severity == :error }
13
+
14
+ #: () -> Array[Offense]
15
+ def warnings = offenses.select { |o| o.severity == :warning }
16
+
17
+ #: () -> bool
18
+ def errors? = errors.any?
19
+
20
+ #: () -> bool
21
+ def empty? = offenses.empty?
22
+ end
23
+ end
@@ -0,0 +1,18 @@
1
+ # rbs_inline: enabled
2
+ # frozen_string_literal: true
3
+
4
+ module Moku6
5
+ module Rules
6
+ class ActionNamingRule < BaseRule
7
+ #: (Event event) -> Array[Offense]
8
+ def check(event)
9
+ pattern = Regexp.new(@config.naming_pattern.to_s)
10
+ return [] if event.action.to_s.match?(pattern)
11
+
12
+ [offense(event, :error,
13
+ "action '#{event.action}' violates the naming convention " \
14
+ "(expected: namespace.verb form, lowercase, dot-separated).")]
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,37 @@
1
+ # rbs_inline: enabled
2
+ # frozen_string_literal: true
3
+
4
+ module Moku6
5
+ module Rules
6
+ class BaseRule
7
+ # @rbs @config: Config
8
+
9
+ #: (Config config) -> void
10
+ def initialize(config) = @config = config
11
+
12
+ #: (Event event) -> Array[Offense]
13
+ def check(_event) = raise NotImplementedError
14
+
15
+ private
16
+
17
+ #: (Event event, Symbol severity, String message, ?rule: String) -> Offense
18
+ def offense(event, severity, message, rule: rule_id)
19
+ Offense.new(
20
+ rule: rule,
21
+ severity: severity,
22
+ action: event.action,
23
+ file: event.source_path,
24
+ message: message
25
+ )
26
+ end
27
+
28
+ #: () -> String
29
+ def rule_id
30
+ self.class.name.split("::").last
31
+ .gsub(/Rule\z/, "")
32
+ .gsub(/([a-z])([A-Z])/, '\1_\2')
33
+ .downcase
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,53 @@
1
+ # rbs_inline: enabled
2
+ # frozen_string_literal: true
3
+
4
+ require "senko"
5
+
6
+ module Moku6
7
+ module Rules
8
+ class ExampleConsistencyRule < BaseRule
9
+ #: (Event event) -> Array[Offense]
10
+ def check(event)
11
+ examples = collect_examples(event)
12
+ return [] if examples.empty?
13
+
14
+ schemer = Senko.compile(EnvelopeSchema.for(event))
15
+ examples.each_with_index.flat_map do |example, idx|
16
+ result = schemer.validate(example)
17
+ next [] if result.valid?
18
+
19
+ result.errors.map do |err|
20
+ h = err.to_h
21
+ location = h["instanceLocation"].to_s
22
+ pointer = location.empty? ? "(root)" : location
23
+ keyword = h["keywordLocation"].to_s.split("/").last
24
+ offense(event, :error,
25
+ "examples[#{idx}] does not match the definition #{pointer}: #{keyword}",
26
+ rule: "example_consistency")
27
+ end
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ #: (Event event) -> Array[untyped]
34
+ def collect_examples(event)
35
+ inline = event.examples
36
+ return inline if inline.is_a?(Array) && !inline.empty?
37
+
38
+ path = external_path(event)
39
+ return [] unless path && File.exist?(path)
40
+
41
+ data = JSON.parse(File.read(path))
42
+ data.is_a?(Array) ? data : [data]
43
+ end
44
+
45
+ #: (Event event) -> String?
46
+ def external_path(event)
47
+ return nil unless event.action
48
+
49
+ File.join(@config.examples_dir, "#{event.action}.json")
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,21 @@
1
+ # rbs_inline: enabled
2
+ # frozen_string_literal: true
3
+
4
+ module Moku6
5
+ module Rules
6
+ class LabelDescriptionRule < BaseRule
7
+ #: (Event event) -> Array[Offense]
8
+ def check(event)
9
+ offenses = []
10
+ offenses << offense(event, :error, "label is missing.") if blank?(event.label)
11
+ offenses << offense(event, :error, "description is missing.") if blank?(event.description)
12
+ offenses
13
+ end
14
+
15
+ private
16
+
17
+ #: (untyped value) -> bool
18
+ def blank?(value) = value.nil? || value.to_s.strip.empty?
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,45 @@
1
+ # rbs_inline: enabled
2
+ # frozen_string_literal: true
3
+
4
+ module Moku6
5
+ module Rules
6
+ # Warns when a field name looks like PII (email/phone/ssn, ...) but is not
7
+ # covered by privacy.masked_fields (design section 10.1, v0.2).
8
+ class PiiFieldNameHeuristicRule < BaseRule
9
+ PII_PATTERN = /
10
+ email | phone | tel | mobile | fax |
11
+ ssn | mynumber | my_number | passport | license |
12
+ credit_?card | card_?number | cvv |
13
+ address | postal | zip |
14
+ birth | dob | password | secret
15
+ /xi #: Regexp
16
+
17
+ #: (Event event) -> Array[Offense]
18
+ def check(event)
19
+ return [] unless @config.warn_pii_field_names?
20
+
21
+ masked = masked_field_tokens(event)
22
+ event.fields.filter_map do |name, _f|
23
+ next unless name.to_s.match?(PII_PATTERN)
24
+ next if masked.include?(name.to_s)
25
+
26
+ offense(event, :warning,
27
+ "field '#{name}' looks like personal data but is not listed in privacy.masked_fields.",
28
+ rule: "pii_field_name_heuristic")
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ # Tokens that count a field as masked: the full masked path and each of its
35
+ # dot-separated segments (e.g. "before.email" covers "email").
36
+ #: (Event event) -> Array[String]
37
+ def masked_field_tokens(event)
38
+ list = (event.privacy.is_a?(Hash) ? event.privacy["masked_fields"] : nil) #: Array[untyped]?
39
+ return [] unless list.is_a?(Array)
40
+
41
+ list.flat_map { |path| [path.to_s] + path.to_s.split(".") }
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,21 @@
1
+ # rbs_inline: enabled
2
+ # frozen_string_literal: true
3
+
4
+ module Moku6
5
+ module Rules
6
+ class PrivacyMaskingRule < BaseRule
7
+ #: (Event event) -> Array[Offense]
8
+ def check(event)
9
+ privacy = event.privacy
10
+ return [] unless privacy && privacy["contains_personal_data"]
11
+
12
+ masked = privacy["masked_fields"]
13
+ return [] if masked.is_a?(Array) && masked.any?
14
+
15
+ [offense(event, :error,
16
+ "contains personal data (contains_personal_data: true) but masked_fields is not set. " \
17
+ "List the fields to mask under privacy.masked_fields.")]
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,19 @@
1
+ # rbs_inline: enabled
2
+ # frozen_string_literal: true
3
+
4
+ module Moku6
5
+ module Rules
6
+ class RetentionRule < BaseRule
7
+ #: (Event event) -> Array[Offense]
8
+ def check(event)
9
+ retention = event.retention
10
+ years = retention.is_a?(Hash) ? retention["years"] : nil
11
+ return [] if years.is_a?(Integer) && years >= 1
12
+
13
+ [offense(event, :error,
14
+ "retention.years must be a positive integer.",
15
+ rule: "retention_present")]
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,31 @@
1
+ # rbs_inline: enabled
2
+ # frozen_string_literal: true
3
+
4
+ require "senko"
5
+
6
+ module Moku6
7
+ module Rules
8
+ class SchemaRule < BaseRule
9
+ SCHEMA_PATH = File.expand_path("../../../schemas/audit-event.schema.json", __dir__.to_s) #: String
10
+
11
+ #: () -> untyped
12
+ def self.schemer
13
+ @schemer ||= Senko.compile(JSON.parse(File.read(SCHEMA_PATH)))
14
+ end
15
+
16
+ #: (Event event) -> Array[Offense]
17
+ def check(event)
18
+ result = self.class.schemer.validate(event.to_h)
19
+ return [] if result.valid?
20
+
21
+ result.errors.map do |err|
22
+ h = err.to_h
23
+ location = h["instanceLocation"].to_s
24
+ pointer = location.empty? ? "(root)" : location
25
+ keyword = h["keywordLocation"].to_s.split("/").last
26
+ offense(event, :error, "schema violation #{pointer}: #{keyword} (#{h["error"] || "invalid"})")
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,22 @@
1
+ # rbs_inline: enabled
2
+ # frozen_string_literal: true
3
+
4
+ module Moku6
5
+ module Rules
6
+ class UniquenessRule < BaseRule
7
+ # catalog-level rule
8
+ #: (Catalog catalog) -> Array[Offense]
9
+ def check_catalog(catalog)
10
+ dupes = catalog.actions.compact.tally.select { |_, n| n > 1 }.keys
11
+ dupes.map do |action|
12
+ files = catalog.events.select { |e| e.action == action }.map(&:source_path)
13
+ Offense.new(
14
+ rule: "uniqueness", severity: :error, action: action,
15
+ file: files.join(", "),
16
+ message: "action '#{action}' is duplicated."
17
+ )
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,27 @@
1
+ # rbs_inline: enabled
2
+ # frozen_string_literal: true
3
+
4
+ module Moku6
5
+ module Rules
6
+ class VisibilityRule < BaseRule
7
+ #: (Event event) -> Array[Offense]
8
+ def check(event)
9
+ visibility = event.visibility
10
+ unless visibility.is_a?(Hash) &&
11
+ visibility.key?("customer_visible") && visibility.key?("internal_only")
12
+ return [offense(event, :error,
13
+ "customer_visible / internal_only must be set explicitly.",
14
+ rule: "visibility_explicit")]
15
+ end
16
+
17
+ if visibility["customer_visible"] && visibility["internal_only"]
18
+ return [offense(event, :warning,
19
+ "customer_visible and internal_only are contradictory.",
20
+ rule: "visibility_consistency")]
21
+ end
22
+
23
+ []
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,6 @@
1
+ # rbs_inline: enabled
2
+ # frozen_string_literal: true
3
+
4
+ module Moku6
5
+ VERSION = "0.1.0" #: String
6
+ end
data/lib/moku6.rb ADDED
@@ -0,0 +1,55 @@
1
+ # rbs_inline: enabled
2
+ # frozen_string_literal: true
3
+
4
+ require "yaml"
5
+ require "json"
6
+
7
+ require_relative "moku6/version"
8
+
9
+ module Moku6
10
+ class Error < StandardError; end
11
+
12
+ class UsageError < Error; end # mapped to exit code 2
13
+ end
14
+
15
+ require_relative "moku6/config"
16
+ require_relative "moku6/event"
17
+ require_relative "moku6/catalog"
18
+ require_relative "moku6/loader"
19
+ require_relative "moku6/offense"
20
+ require_relative "moku6/result"
21
+ require_relative "moku6/reporter"
22
+ require_relative "moku6/envelope_schema"
23
+
24
+ # rules
25
+ require_relative "moku6/rules/base_rule"
26
+ require_relative "moku6/rules/schema_rule"
27
+ require_relative "moku6/rules/action_naming_rule"
28
+ require_relative "moku6/rules/label_description_rule"
29
+ require_relative "moku6/rules/uniqueness_rule"
30
+ require_relative "moku6/rules/privacy_masking_rule"
31
+ require_relative "moku6/rules/visibility_rule"
32
+ require_relative "moku6/rules/retention_rule"
33
+ require_relative "moku6/rules/example_consistency_rule"
34
+ require_relative "moku6/rules/pii_field_name_heuristic_rule"
35
+ require_relative "moku6/linter"
36
+ require_relative "moku6/differ"
37
+
38
+ # generators
39
+ require_relative "moku6/generators/base_generator"
40
+ require_relative "moku6/generators/docs_generator"
41
+ require_relative "moku6/generators/json_schema_generator"
42
+ require_relative "moku6/generators/typescript_generator"
43
+ require_relative "moku6/generators/ruby_generator"
44
+ require_relative "moku6/generators/bigquery_generator"
45
+ require_relative "moku6/generators/cloud_events_generator"
46
+ require_relative "moku6/generators/event_catalog_generator"
47
+ require_relative "moku6/generators/open_api_generator"
48
+ require_relative "moku6/generators/rails_generator"
49
+ require_relative "moku6/generators/outbox_generator"
50
+ require_relative "moku6/generators/example_generator"
51
+ require_relative "moku6/generators"
52
+
53
+ require_relative "moku6/initializer"
54
+ require_relative "moku6/generate"
55
+ require_relative "moku6/cli"
@@ -0,0 +1,85 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://moku6/schemas/audit-event.schema.json",
4
+ "title": "Audit Event Definition",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["action", "label", "description", "category", "actor", "target", "privacy", "visibility", "retention"],
8
+ "properties": {
9
+ "action": {
10
+ "type": "string",
11
+ "pattern": "^[a-z0-9_]+(\\.[a-z0-9_]+)+$"
12
+ },
13
+ "label": { "type": "string", "minLength": 1 },
14
+ "description": { "type": "string", "minLength": 1 },
15
+ "category": { "type": "string", "minLength": 1 },
16
+ "required": { "type": "boolean" },
17
+ "actor": {
18
+ "type": "object",
19
+ "additionalProperties": false,
20
+ "required": ["required"],
21
+ "properties": {
22
+ "required": { "type": "boolean" },
23
+ "type": { "type": "string" }
24
+ }
25
+ },
26
+ "target": {
27
+ "type": "object",
28
+ "additionalProperties": false,
29
+ "required": ["type", "required"],
30
+ "properties": {
31
+ "type": { "type": "string" },
32
+ "required": { "type": "boolean" }
33
+ }
34
+ },
35
+ "fields": {
36
+ "type": "object",
37
+ "additionalProperties": {
38
+ "type": "object",
39
+ "additionalProperties": false,
40
+ "required": ["type", "required"],
41
+ "properties": {
42
+ "type": {
43
+ "enum": ["string", "integer", "number", "boolean", "array", "object", "timestamp"]
44
+ },
45
+ "required": { "type": "boolean" },
46
+ "description": { "type": "string" },
47
+ "items": { "type": "object" }
48
+ }
49
+ }
50
+ },
51
+ "privacy": {
52
+ "type": "object",
53
+ "additionalProperties": false,
54
+ "required": ["contains_personal_data"],
55
+ "properties": {
56
+ "contains_personal_data": { "type": "boolean" },
57
+ "masked_fields": {
58
+ "type": "array",
59
+ "items": { "type": "string" }
60
+ }
61
+ }
62
+ },
63
+ "visibility": {
64
+ "type": "object",
65
+ "additionalProperties": false,
66
+ "required": ["customer_visible", "internal_only"],
67
+ "properties": {
68
+ "customer_visible": { "type": "boolean" },
69
+ "internal_only": { "type": "boolean" }
70
+ }
71
+ },
72
+ "retention": {
73
+ "type": "object",
74
+ "additionalProperties": false,
75
+ "required": ["years"],
76
+ "properties": {
77
+ "years": { "type": "integer", "minimum": 1 }
78
+ }
79
+ },
80
+ "examples": {
81
+ "type": "array",
82
+ "items": { "type": "object" }
83
+ }
84
+ }
85
+ }
@@ -0,0 +1,22 @@
1
+ # Generated from lib/moku6/catalog.rb with RBS::Inline
2
+
3
+ module Moku6
4
+ class Catalog
5
+ attr_reader events: Array[Event]
6
+
7
+ # : (Array[Event] events) -> void
8
+ def initialize: (Array[Event] events) -> void
9
+
10
+ # : () -> Array[String?]
11
+ def actions: () -> Array[String?]
12
+
13
+ # : () -> Array[Event]
14
+ def sorted: () -> Array[Event]
15
+
16
+ # : (String? action) -> Event?
17
+ def find: (String? action) -> Event?
18
+
19
+ # : () -> bool
20
+ def empty?: () -> bool
21
+ end
22
+ end