nthlayer-generate 2.0.2__tar.gz → 2.0.3__tar.gz

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 (350) hide show
  1. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/PKG-INFO +1 -1
  2. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/pyproject.toml +1 -1
  3. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/__init__.py +1 -1
  4. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/cli/init.py +202 -30
  5. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/specs/manifest.py +55 -2
  6. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/specs/validator.py +6 -5
  7. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate.egg-info/PKG-INFO +1 -1
  8. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate.egg-info/SOURCES.txt +1 -0
  9. nthlayer_generate-2.0.3/tests/test_init.py +1805 -0
  10. nthlayer_generate-2.0.3/tests/test_source_citations.py +117 -0
  11. nthlayer_generate-2.0.2/tests/test_init.py +0 -621
  12. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/README.md +0 -0
  13. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/setup.cfg +0 -0
  14. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/alertmanager/__init__.py +0 -0
  15. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/alertmanager/config.py +0 -0
  16. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/alerts/__init__.py +0 -0
  17. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/alerts/loader.py +0 -0
  18. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/alerts/models.py +0 -0
  19. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/alerts/validator.py +0 -0
  20. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/cli/__init__.py +0 -0
  21. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/cli/alerts.py +0 -0
  22. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/cli/apply.py +0 -0
  23. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/cli/backstage.py +0 -0
  24. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/cli/dashboard.py +0 -0
  25. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/cli/dashboard_validate.py +0 -0
  26. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/cli/dependencies.py +0 -0
  27. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/cli/docs.py +0 -0
  28. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/cli/environments.py +0 -0
  29. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/cli/formatters/__init__.py +0 -0
  30. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/cli/formatters/json_fmt.py +0 -0
  31. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/cli/formatters/junit.py +0 -0
  32. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/cli/formatters/markdown.py +0 -0
  33. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/cli/formatters/models.py +0 -0
  34. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/cli/formatters/sarif.py +0 -0
  35. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/cli/generate.py +0 -0
  36. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/cli/generate_alerts.py +0 -0
  37. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/cli/generate_loki.py +0 -0
  38. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/cli/identity.py +0 -0
  39. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/cli/lint.py +0 -0
  40. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/cli/migrate.py +0 -0
  41. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/cli/migrate_manifest.py +0 -0
  42. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/cli/ownership.py +0 -0
  43. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/cli/pagerduty.py +0 -0
  44. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/cli/plan.py +0 -0
  45. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/cli/recommend_metrics.py +0 -0
  46. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/cli/recording_rules.py +0 -0
  47. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/cli/setup.py +0 -0
  48. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/cli/simulate.py +0 -0
  49. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/cli/slo.py +0 -0
  50. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/cli/templates.py +0 -0
  51. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/cli/topology.py +0 -0
  52. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/cli/ux.py +0 -0
  53. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/cli/validate.py +0 -0
  54. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/cli/validate_metadata.py +0 -0
  55. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/cli/validate_slo.py +0 -0
  56. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/cli/validate_spec.py +0 -0
  57. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/clients/__init__.py +0 -0
  58. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/clients/base.py +0 -0
  59. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/clients/cortex.py +0 -0
  60. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/clients/pagerduty.py +0 -0
  61. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/clients/slack.py +0 -0
  62. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/config/__init__.py +0 -0
  63. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/config/cli.py +0 -0
  64. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/config/integrations.py +0 -0
  65. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/config/loader.py +0 -0
  66. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/config/secrets/__init__.py +0 -0
  67. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/config/secrets/backends.py +0 -0
  68. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/config/settings.py +0 -0
  69. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/core/__init__.py +0 -0
  70. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/core/errors.py +0 -0
  71. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/core/tiers.py +0 -0
  72. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/dashboards/__init__.py +0 -0
  73. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/dashboards/builder_sdk.py +0 -0
  74. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/dashboards/intents.py +0 -0
  75. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/dashboards/manifest_builder.py +0 -0
  76. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/dashboards/models.py +0 -0
  77. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/dashboards/panel_spec.py +0 -0
  78. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/dashboards/resolver.py +0 -0
  79. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/dashboards/sdk_adapter.py +0 -0
  80. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/dashboards/templates/__init__.py +0 -0
  81. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/dashboards/templates/base.py +0 -0
  82. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/dashboards/templates/base_intent.py +0 -0
  83. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/dashboards/templates/consul_intent.py +0 -0
  84. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/dashboards/templates/elasticsearch_intent.py +0 -0
  85. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/dashboards/templates/etcd_intent.py +0 -0
  86. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/dashboards/templates/haproxy_intent.py +0 -0
  87. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/dashboards/templates/http_intent.py +0 -0
  88. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/dashboards/templates/kafka_intent.py +0 -0
  89. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/dashboards/templates/kubernetes.py +0 -0
  90. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/dashboards/templates/mongodb_intent.py +0 -0
  91. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/dashboards/templates/mysql_intent.py +0 -0
  92. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/dashboards/templates/nats_intent.py +0 -0
  93. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/dashboards/templates/nginx_intent.py +0 -0
  94. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/dashboards/templates/postgresql_intent.py +0 -0
  95. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/dashboards/templates/pulsar_intent.py +0 -0
  96. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/dashboards/templates/rabbitmq_intent.py +0 -0
  97. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/dashboards/templates/redis_intent.py +0 -0
  98. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/dashboards/templates/stream_intent.py +0 -0
  99. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/dashboards/templates/traefik_intent.py +0 -0
  100. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/dashboards/templates/worker_intent.py +0 -0
  101. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/dashboards/validator.py +0 -0
  102. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/demo.py +0 -0
  103. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/dependencies/__init__.py +0 -0
  104. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/dependencies/discovery.py +0 -0
  105. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/dependencies/models.py +0 -0
  106. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/dependencies/providers/__init__.py +0 -0
  107. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/dependencies/providers/backstage.py +0 -0
  108. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/dependencies/providers/base.py +0 -0
  109. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/dependencies/providers/consul.py +0 -0
  110. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/dependencies/providers/etcd.py +0 -0
  111. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/dependencies/providers/kubernetes.py +0 -0
  112. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/dependencies/providers/prometheus.py +0 -0
  113. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/dependencies/providers/zookeeper.py +0 -0
  114. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/discovery/__init__.py +0 -0
  115. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/discovery/classifier.py +0 -0
  116. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/discovery/client.py +0 -0
  117. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/discovery/models.py +0 -0
  118. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/domain/__init__.py +0 -0
  119. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/domain/models.py +0 -0
  120. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/generators/__init__.py +0 -0
  121. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/generators/alerts.py +0 -0
  122. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/generators/backstage.py +0 -0
  123. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/generators/docs.py +0 -0
  124. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/generators/sloth.py +0 -0
  125. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/identity/__init__.py +0 -0
  126. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/identity/models.py +0 -0
  127. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/identity/normalizer.py +0 -0
  128. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/identity/ownership.py +0 -0
  129. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/identity/ownership_providers/__init__.py +0 -0
  130. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/identity/ownership_providers/backstage.py +0 -0
  131. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/identity/ownership_providers/base.py +0 -0
  132. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/identity/ownership_providers/codeowners.py +0 -0
  133. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/identity/ownership_providers/declared.py +0 -0
  134. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/identity/ownership_providers/kubernetes.py +0 -0
  135. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/identity/ownership_providers/pagerduty.py +0 -0
  136. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/identity/resolver.py +0 -0
  137. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/integrations/__init__.py +0 -0
  138. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/integrations/pagerduty.py +0 -0
  139. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/logging.py +0 -0
  140. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/loki/__init__.py +0 -0
  141. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/loki/generator.py +0 -0
  142. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/loki/models.py +0 -0
  143. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/loki/templates.py +0 -0
  144. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/metrics/__init__.py +0 -0
  145. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/metrics/discovery.py +0 -0
  146. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/metrics/models.py +0 -0
  147. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/metrics/recommender.py +0 -0
  148. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/metrics/runtime/__init__.py +0 -0
  149. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/metrics/runtime/go.py +0 -0
  150. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/metrics/runtime/jvm.py +0 -0
  151. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/metrics/runtime/nodejs.py +0 -0
  152. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/metrics/runtime/python.py +0 -0
  153. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/metrics/standards/__init__.py +0 -0
  154. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/metrics/standards/aliases.py +0 -0
  155. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/metrics/standards/otel_semconv.py +0 -0
  156. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/metrics/templates/__init__.py +0 -0
  157. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/metrics/templates/api.py +0 -0
  158. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/metrics/templates/cache.py +0 -0
  159. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/metrics/templates/database_client.py +0 -0
  160. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/metrics/templates/gateway.py +0 -0
  161. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/metrics/templates/grpc.py +0 -0
  162. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/metrics/templates/queue_consumer.py +0 -0
  163. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/metrics/templates/registry.py +0 -0
  164. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/metrics/templates/worker.py +0 -0
  165. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/orchestration/__init__.py +0 -0
  166. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/orchestration/engine.py +0 -0
  167. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/orchestration/handlers.py +0 -0
  168. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/orchestration/plan_builder.py +0 -0
  169. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/orchestration/registry.py +0 -0
  170. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/orchestration/results.py +0 -0
  171. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/orchestrator.py +0 -0
  172. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/pagerduty/__init__.py +0 -0
  173. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/pagerduty/defaults.py +0 -0
  174. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/pagerduty/naming.py +0 -0
  175. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/pagerduty/orchestration.py +0 -0
  176. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/pagerduty/resources.py +0 -0
  177. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/policies/__init__.py +0 -0
  178. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/policies/engine.py +0 -0
  179. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/policies/models.py +0 -0
  180. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/policies/rules.py +0 -0
  181. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/providers/__init__.py +0 -0
  182. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/providers/__main__.py +0 -0
  183. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/providers/base.py +0 -0
  184. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/providers/cli.py +0 -0
  185. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/providers/grafana.py +0 -0
  186. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/providers/lock.py +0 -0
  187. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/providers/mimir.py +0 -0
  188. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/providers/pagerduty.py +0 -0
  189. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/providers/prometheus.py +0 -0
  190. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/providers/registry.py +0 -0
  191. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/recording_rules/__init__.py +0 -0
  192. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/recording_rules/builder.py +0 -0
  193. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/recording_rules/manifest_builder.py +0 -0
  194. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/recording_rules/models.py +0 -0
  195. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/simulate/__init__.py +0 -0
  196. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/simulate/engine.py +0 -0
  197. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/simulate/graph.py +0 -0
  198. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/simulate/models.py +0 -0
  199. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/simulate/output.py +0 -0
  200. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/simulate/what_if.py +0 -0
  201. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/slos/__init__.py +0 -0
  202. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/slos/alerts.py +0 -0
  203. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/slos/calculator.py +0 -0
  204. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/slos/ceiling.py +0 -0
  205. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/slos/dependencies.py +0 -0
  206. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/slos/models.py +0 -0
  207. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/slos/parser.py +0 -0
  208. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/slos/pipeline.py +0 -0
  209. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/specs/__init__.py +0 -0
  210. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/specs/alerting.py +0 -0
  211. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/specs/builtin_templates/__init__.py +0 -0
  212. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/specs/contracts.py +0 -0
  213. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/specs/custom_templates.py +0 -0
  214. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/specs/environment_alerts.py +0 -0
  215. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/specs/environment_detection.py +0 -0
  216. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/specs/environment_gates.py +0 -0
  217. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/specs/environment_merger.py +0 -0
  218. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/specs/environments.py +0 -0
  219. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/specs/helpers.py +0 -0
  220. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/specs/loader.py +0 -0
  221. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/specs/models.py +0 -0
  222. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/specs/opensrm_parser.py +0 -0
  223. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/specs/parser.py +0 -0
  224. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/specs/template.py +0 -0
  225. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/specs/template_loader.py +0 -0
  226. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/specs/templates.py +0 -0
  227. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/specs/variable_substitution.py +0 -0
  228. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/topology/__init__.py +0 -0
  229. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/topology/enrichment.py +0 -0
  230. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/topology/models.py +0 -0
  231. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/topology/serializers.py +0 -0
  232. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/user_config.py +0 -0
  233. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/validation/__init__.py +0 -0
  234. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/validation/conftest.py +0 -0
  235. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/validation/metadata.py +0 -0
  236. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/validation/promql.py +0 -0
  237. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate/validation/promruval.py +0 -0
  238. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate.egg-info/dependency_links.txt +0 -0
  239. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate.egg-info/entry_points.txt +0 -0
  240. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate.egg-info/requires.txt +0 -0
  241. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/src/nthlayer_generate.egg-info/top_level.txt +0 -0
  242. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_alert_pipeline.py +0 -0
  243. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_alerting_config.py +0 -0
  244. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_alertmanager.py +0 -0
  245. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_alerts.py +0 -0
  246. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_backstage_generator.py +0 -0
  247. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_budget_policy.py +0 -0
  248. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_cli_alerts.py +0 -0
  249. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_cli_apply.py +0 -0
  250. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_cli_dashboard.py +0 -0
  251. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_cli_dashboard_validate.py +0 -0
  252. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_cli_environments.py +0 -0
  253. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_cli_formatters.py +0 -0
  254. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_cli_generate.py +0 -0
  255. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_cli_generate_alerts.py +0 -0
  256. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_cli_generate_loki.py +0 -0
  257. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_cli_identity.py +0 -0
  258. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_cli_lint.py +0 -0
  259. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_cli_migrate_manifest.py +0 -0
  260. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_cli_pagerduty.py +0 -0
  261. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_cli_plan.py +0 -0
  262. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_cli_setup.py +0 -0
  263. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_cli_slo.py +0 -0
  264. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_cli_templates.py +0 -0
  265. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_cli_ux.py +0 -0
  266. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_cli_validate.py +0 -0
  267. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_cli_validate_metadata.py +0 -0
  268. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_cli_validate_slo.py +0 -0
  269. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_cli_validate_spec.py +0 -0
  270. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_clients.py +0 -0
  271. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_config_cli.py +0 -0
  272. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_config_loader.py +0 -0
  273. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_config_secrets_backends.py +0 -0
  274. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_config_secrets_init.py +0 -0
  275. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_conftest_validation.py +0 -0
  276. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_contract_validation.py +0 -0
  277. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_core_errors.py +0 -0
  278. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_core_tiers.py +0 -0
  279. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_critical_models.py +0 -0
  280. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_custom_templates.py +0 -0
  281. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_dashboard_generation.py +0 -0
  282. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_dashboard_templates.py +0 -0
  283. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_dashboards_builder_sdk.py +0 -0
  284. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_dashboards_models.py +0 -0
  285. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_dashboards_resolver.py +0 -0
  286. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_dashboards_validator.py +0 -0
  287. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_demo.py +0 -0
  288. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_demo_app_auth.py +0 -0
  289. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_dependencies.py +0 -0
  290. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_dependency_declarations.py +0 -0
  291. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_deps_discovery.py +0 -0
  292. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_deps_provider_backstage.py +0 -0
  293. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_deps_provider_consul.py +0 -0
  294. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_deps_provider_etcd.py +0 -0
  295. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_deps_provider_kubernetes.py +0 -0
  296. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_deps_provider_zookeeper.py +0 -0
  297. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_discovery_client.py +0 -0
  298. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_docs_generator.py +0 -0
  299. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_domain_models.py +0 -0
  300. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_env_flag.py +0 -0
  301. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_environment_commands.py +0 -0
  302. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_generators_alerts.py +0 -0
  303. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_generators_backstage.py +0 -0
  304. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_generators_docs.py +0 -0
  305. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_generators_sloth.py +0 -0
  306. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_hybrid_dashboard_builder.py +0 -0
  307. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_identity.py +0 -0
  308. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_init_documented_flags.py +0 -0
  309. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_integrations_pagerduty.py +0 -0
  310. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_intent_templates.py +0 -0
  311. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_loki.py +0 -0
  312. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_metadata_validation.py +0 -0
  313. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_metrics_discovery.py +0 -0
  314. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_metrics_recommender.py +0 -0
  315. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_mimir_provider.py +0 -0
  316. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_multi_environment_integration.py +0 -0
  317. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_opensrm.py +0 -0
  318. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_orchestrator.py +0 -0
  319. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_ownership.py +0 -0
  320. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_pagerduty.py +0 -0
  321. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_pagerduty_module.py +0 -0
  322. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_pagerduty_orchestration.py +0 -0
  323. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_pagerduty_resources.py +0 -0
  324. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_policy_engine.py +0 -0
  325. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_policy_handler.py +0 -0
  326. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_policy_models.py +0 -0
  327. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_policy_rules.py +0 -0
  328. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_prometheus_provider.py +0 -0
  329. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_provider_grafana.py +0 -0
  330. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_provider_pagerduty.py +0 -0
  331. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_providers_lock.py +0 -0
  332. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_recording_rules.py +0 -0
  333. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_sdk_adapter.py +0 -0
  334. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_service_type_agreement.py +0 -0
  335. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_simulate_cli.py +0 -0
  336. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_simulate_engine.py +0 -0
  337. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_simulate_graph.py +0 -0
  338. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_simulate_models.py +0 -0
  339. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_simulate_what_if.py +0 -0
  340. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_slo_alerts.py +0 -0
  341. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_slo_ceiling.py +0 -0
  342. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_slo_cli.py +0 -0
  343. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_slos.py +0 -0
  344. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_specs.py +0 -0
  345. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_sync_awesome_alerts.py +0 -0
  346. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_templates.py +0 -0
  347. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_topology.py +0 -0
  348. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_user_config.py +0 -0
  349. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_validation.py +0 -0
  350. {nthlayer_generate-2.0.2 → nthlayer_generate-2.0.3}/tests/test_validation_promruval.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nthlayer-generate
3
- Version: 2.0.2
3
+ Version: 2.0.3
4
4
  Summary: NthLayer - The Missing Layer of Reliability
5
5
  Requires-Python: >=3.11
6
6
  Description-Content-Type: text/markdown
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "nthlayer-generate"
7
- version = "2.0.2"
7
+ version = "2.0.3"
8
8
  description = "NthLayer - The Missing Layer of Reliability"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -2,4 +2,4 @@
2
2
 
3
3
  __all__ = ["__version__"]
4
4
 
5
- __version__ = "2.0.2"
5
+ __version__ = "2.0.3"
@@ -1,7 +1,9 @@
1
1
  """CLI command for initializing new NthLayer services."""
2
2
 
3
+ import re
3
4
  from pathlib import Path
4
5
 
6
+ import yaml
5
7
  from nthlayer_common.manifest.models import resolve_service_type
6
8
 
7
9
  from nthlayer_generate.cli.ux import (
@@ -17,6 +19,7 @@ from nthlayer_generate.cli.ux import (
17
19
  )
18
20
  from nthlayer_generate.core.tiers import TIER_CONFIGS
19
21
  from nthlayer_generate.specs.custom_templates import CustomTemplateLoader
22
+ from nthlayer_generate.specs.manifest import is_valid_service_name
20
23
 
21
24
  # The service-type menu. INVARIANT: every key is a value a manifest can
22
25
  # store verbatim — nothing here is translated on the way out (opensrm-8qpd).
@@ -115,12 +118,22 @@ def init_command(
115
118
  if not team and interactive:
116
119
  team = text_input("Team name", placeholder="e.g., platform, payments")
117
120
 
118
- if not team:
121
+ # `not team` first: team is `str | None` here, so `.strip()` alone would
122
+ # raise on None and also lose the narrowing the calls below rely on. The
123
+ # `.strip()` half is what catches a whitespace-only --team here, where the
124
+ # message fits it, rather than at the _is_valid_team branch below, whose
125
+ # message would be false for it.
126
+ if not team or not team.strip():
119
127
  error("Team name is required")
120
128
  if not interactive:
121
129
  console.print(" [muted]Pass it with --team <team>[/muted]")
122
130
  return 1
123
131
 
132
+ if not _is_valid_team(team):
133
+ error("Invalid team name")
134
+ console.print(" [muted]Team name must not contain line breaks, tabs or NUL[/muted]")
135
+ return 1
136
+
124
137
  # Select service tier using interactive menu
125
138
  tier = None
126
139
  if interactive:
@@ -196,10 +209,25 @@ def init_command(
196
209
  service_name, team, tier, service_type, dependencies, template_obj
197
210
  )
198
211
 
212
+ # encoding="utf-8" explicitly, and UnicodeError caught alongside OSError
213
+ # (opensrm-t4rd edge-case pass). Without the encoding, `write_text` uses the
214
+ # locale's, so on a non-UTF-8 system an ordinary accented team name raised
215
+ # UnicodeEncodeError -- which is a ValueError, NOT an OSError, so the handler
216
+ # below missed it and init died by traceback. Worse, `write_text` had already
217
+ # created the file, so a ZERO-BYTE manifest was left behind and every later
218
+ # run then hit the `exists()` guard above and refused, permanently, until
219
+ # someone deleted it by hand.
220
+ #
221
+ # The unlink is the rollback half of this bead's defect 3: a run that fails
222
+ # must not leave a partial artifact that blocks the next one.
199
223
  try:
200
- service_file.write_text(service_content)
201
- except OSError as e:
224
+ service_file.write_text(service_content, encoding="utf-8")
225
+ except (OSError, UnicodeError) as e:
202
226
  error(f"Error creating service file: {e}")
227
+ try:
228
+ service_file.unlink(missing_ok=True)
229
+ except OSError:
230
+ pass
203
231
  return 1
204
232
 
205
233
  # Create .nthlayer directory
@@ -209,20 +237,40 @@ def init_command(
209
237
  except OSError as e:
210
238
  warning(f"Could not create .nthlayer directory: {e}")
211
239
 
212
- # Create config file if it doesn't exist
240
+ # Create config file if it doesn't exist.
241
+ #
242
+ # `is_file()`, NOT `exists()` -- the same confusion this bead fixed for
243
+ # `nthlayer_dir` below. With `.nthlayer/config.yaml` present as a DIRECTORY,
244
+ # `exists()` is True because it is a directory, so the write was skipped and
245
+ # init reported success for a config it had never written, with no warning.
213
246
  config_file = nthlayer_dir / "config.yaml"
214
- if not config_file.exists():
247
+ if not config_file.is_file():
215
248
  config_content = _generate_config_yaml()
216
249
  try:
217
- config_file.write_text(config_content)
218
- except OSError as e:
250
+ config_file.write_text(config_content, encoding="utf-8")
251
+ except (OSError, UnicodeError) as e:
219
252
  warning(f"Could not create config file: {e}")
220
253
 
221
254
  # Success message
222
255
  console.print()
223
256
  success(f"Created {service_file}")
224
- if nthlayer_dir.exists():
257
+ # is_dir(), NOT exists() (opensrm-t4rd): with .nthlayer a regular FILE,
258
+ # exists() is True *because it is a file*, so init claimed to have created
259
+ # a directory it had not.
260
+ if nthlayer_dir.is_dir():
225
261
  success(f"Created {nthlayer_dir}/")
262
+ else:
263
+ # NOT fatal, including under --no-interactive. Decided deliberately
264
+ # (opensrm-t4rd acceptance): the manifest is the primary artifact and it
265
+ # was written correctly, so a CI gate keying on the exit code is right to
266
+ # pass. What was wrong before was claiming the directory had been created;
267
+ # the warning below states plainly that it was not, and names the fix.
268
+ # Making it fatal would fail runs whose manifest is perfectly good merely
269
+ # because .nthlayer could not be created (read-only parent, a stray file).
270
+ warning(
271
+ f"{nthlayer_dir}/ was not created — config was not written. "
272
+ f"It already exists as a file, or the parent is not writable."
273
+ )
226
274
 
227
275
  console.print()
228
276
  console.print("[bold]Next steps:[/bold]")
@@ -241,28 +289,147 @@ def init_command(
241
289
  return 0
242
290
 
243
291
 
244
- def _is_valid_service_name(name: str) -> bool:
245
- """Check if service name is valid (lowercase with hyphens).
292
+ # WHY THE SCALAR HELPERS BELOW EXIST (opensrm-t4rd).
293
+ #
294
+ # generate's hard rule 5 — no raw string construction for generated output —
295
+ # applied at the one input that is scriptable and unvalidated. `--team` was
296
+ # interpolated raw with only a truthiness check. Measured before the fix, all
297
+ # exiting 0:
298
+ #
299
+ # --team 'Platform: Core' -> the manifest failed to parse
300
+ # --team $'ops\nname: hijacked' -> injected a second service.name
301
+ # --team $'ops\ntier: critical' -> manifest VALIDATED CLEAN carrying a
302
+ # bogus tier shadowed by the real one
303
+ #
304
+ # A colon in a team name is ordinary, so quoting has to be available. It is
305
+ # applied conditionally, for the reason in `_yaml_scalar`, and done with
306
+ # pyyaml rather than `json.dumps`, for the reason in `_quoted_yaml_scalar`.
307
+ #
308
+ # The allowlist is deliberately narrow: anything outside it is quoted rather
309
+ # than reasoned about. It excludes every YAML indicator character (`:` `#`
310
+ # `-` at the start, `{` `[` `&` `*` `!` `|` `>` `%` `@` and a backtick),
311
+ # leading whitespace, and anything non-ASCII. Matching it is necessary but NOT
312
+ # sufficient — see `_emits_as_same_string`.
313
+ _PLAIN_SCALAR_RE = re.compile(r"[A-Za-z0-9][A-Za-z0-9 ._-]*")
246
314
 
247
- Args:
248
- name: Service name to validate
249
315
 
250
- Returns:
251
- True if valid
316
+ def _yaml_scalar(value: object) -> str:
317
+ """Render *value* as a YAML scalar that cannot alter the document.
318
+
319
+ Plain when it provably round-trips, quoted otherwise. Quoting is
320
+ conditional so the documented example output stays byte-identical to what
321
+ init writes (``test_block_is_byte_identical_to_real_output``, the
322
+ docs-vs-reality guard from opensrm-noc6).
323
+
324
+ A non-str is quoted rather than rejected: values arrive straight from a
325
+ YAML loader, so a template declaring ``name: on`` makes this a bool and
326
+ ``re.fullmatch`` would raise on it.
252
327
  """
253
- if not name:
328
+ if not isinstance(value, str):
329
+ return _quoted_yaml_scalar(str(value))
330
+ if _PLAIN_SCALAR_RE.fullmatch(value) and _emits_as_same_string(value):
331
+ return value
332
+ return _quoted_yaml_scalar(value)
333
+
334
+
335
+ # pyyaml wraps long scalars by default, which would emit a second line into a
336
+ # document built by string interpolation. Large enough to never wrap.
337
+ _NO_WRAP = 10**9
338
+
339
+
340
+ def _quoted_yaml_scalar(value: str) -> str:
341
+ """Render *value* as a double-quoted YAML scalar on exactly one line.
342
+
343
+ ``default_style='"'`` forces the quoted form for every input, so the result
344
+ is always a single line and never a block scalar.
345
+
346
+ ``json.dumps`` was used here first, on the reasoning that a JSON string is
347
+ also a valid YAML double-quoted scalar. True for the BMP, false above it:
348
+ with ``ensure_ascii=True`` json escapes a non-BMP character as a UTF-16
349
+ surrogate PAIR, and pyyaml resolves each 16-bit escape separately without
350
+ recombining them, so an emoji team name read back as two lone surrogates,
351
+ validated clean, and could not be re-encoded to UTF-8 at all.
352
+ ``ensure_ascii=False`` is no better: it mismatches on U+0085 and makes
353
+ pyyaml raise on U+007F.
354
+
355
+ ``allow_unicode=True`` is readability only, not correctness: it emits BMP
356
+ non-ASCII literally (``cafe`` with its accent, rather than an escape).
357
+ Above the BMP pyyaml escapes regardless of the flag -- but as ONE 32-bit
358
+ escape, which its own reader resolves back to one character. That single
359
+ escape, not the flag, is what json's surrogate pair got wrong. Measured:
360
+ both settings round-trip every value in the corpus.
361
+
362
+ The trailing newline pyyaml adds is stripped. No document-end marker is
363
+ stripped, because ``default_style='"'`` means only pyyaml's
364
+ double-quoted writer ever runs, and that writer never emits one.
365
+ """
366
+ return yaml.safe_dump(value, default_style='"', allow_unicode=True, width=_NO_WRAP).rstrip("\n")
367
+
368
+
369
+ def _emits_as_same_string(value: str) -> bool:
370
+ """True if *value* written as a plain YAML scalar reads back identically.
371
+
372
+ The allowlist excludes every YAML *indicator*, but says nothing about
373
+ implicit *type resolution*, which is the half that was missing. Measured,
374
+ every one of these matched the allowlist, was emitted unquoted, and loaded
375
+ back as something other than the string written -- all at exit 0:
376
+
377
+ --team null -> None -> validate FAILS, "team is required"
378
+ --team yes/on/true -> True -> validates CLEAN, bool in a str field
379
+ --team no/off/false -> False -> validate FAILS
380
+ --team 123 -> 123 -> int
381
+ --team 1_000 -> 1000 -> int
382
+ --team 1.5 -> 1.5 -> float
383
+ --team 0x1f -> 31 -> int
384
+ --team 2026-01-01 -> date(...) -> datetime.date, validates CLEAN
385
+ --team 'ops ' -> 'ops' -> trailing space silently stripped
386
+
387
+ Case variants resolve too (``NULL``, ``Yes``), which is why this asks the
388
+ loader instead of carrying a deny list that would have to track the YAML 1.1
389
+ type schema -- and stay correct as pyyaml changes. Both shapes above are the
390
+ bead's own defects one field over: the first writes a manifest generate's
391
+ validator rejects, the second a clean manifest carrying a wrong value.
392
+
393
+ Checking the bare scalar is faithful to the mapping-value position it is
394
+ emitted into, because the allowlist already excludes every character whose
395
+ resolution differs between those two contexts.
396
+
397
+ ``ValueError`` is caught alongside ``YAMLError`` because pyyaml's integer
398
+ constructor raises it rather than a YAML error on an allowlist-matching
399
+ input: ``0b_`` reaches ``int("", 2)``. Uncaught, that crashed ``nthlayer
400
+ init`` with a traceback and wrote nothing. Fuzzing every
401
+ allowlist-matching string up to length 4 (10248 of them) reaches exactly
402
+ these two types, ``ValueError`` on 3 inputs -- ``0b_``, ``0x_``, ``0b__``.
403
+
404
+ Either way the answer is the same: a value that will not load is not
405
+ provably a plain string, so it gets quoted.
406
+ """
407
+ try:
408
+ loaded = yaml.safe_load(value)
409
+ except (yaml.YAMLError, ValueError):
254
410
  return False
411
+ return isinstance(loaded, str) and loaded == value
255
412
 
256
- # Must be lowercase, numbers, and hyphens only
257
- # Must not start or end with hyphen
258
- if name[0] == "-" or name[-1] == "-":
259
- return False
260
413
 
261
- for char in name:
262
- if not (char.islower() or char.isdigit() or char == "-"):
263
- return False
414
+ def _is_valid_team(team: str) -> bool:
415
+ r"""True if *team* contains no line break, tab or NUL.
416
+
417
+ Those four only, not every control character: the rest are quoted correctly
418
+ by ``_yaml_scalar`` and are merely odd, not dangerous. Blankness is not
419
+ checked here either — the caller rejects it first, with a message that
420
+ actually fits it.
421
+
422
+ Quoting alone already makes the document safe, so this is not what prevents
423
+ injection. It is what turns a newline in --team into a clear error rather
424
+ than a silently escaped ``\n`` in the output, because a team name spanning
425
+ lines is a mistake every time.
426
+ """
427
+ return not any(ch in team for ch in "\n\r\t\x00")
428
+
264
429
 
265
- return True
430
+ def _is_valid_service_name(name: str) -> bool:
431
+ """Delegates to the one service-name rule; see specs/manifest for why."""
432
+ return is_valid_service_name(name)
266
433
 
267
434
 
268
435
  def _generate_service_yaml_v2(
@@ -293,14 +460,14 @@ def _generate_service_yaml_v2(
293
460
  resources_yaml = _build_resources_yaml(service_name, tier, service_type, dependencies)
294
461
 
295
462
  # Template line if using a template
296
- template_line = f" template: {template.name}\n" if template else ""
463
+ template_line = f" template: {_yaml_scalar(template.name)}\n" if template else ""
297
464
 
298
465
  return f"""# {service_name} Service Definition
299
466
  # Generated by NthLayer
300
467
 
301
468
  service:
302
- name: {service_name}
303
- team: {team}
469
+ name: {_yaml_scalar(service_name)}
470
+ team: {_yaml_scalar(team)}
304
471
  tier: {tier}
305
472
  type: {service_type}
306
473
  {template_line}
@@ -457,11 +624,11 @@ def _generate_service_yaml(service_name: str, team: str, template) -> str:
457
624
  # Generated by NthLayer
458
625
 
459
626
  service:
460
- name: {service_name}
461
- team: {team}
627
+ name: {_yaml_scalar(service_name)}
628
+ team: {_yaml_scalar(team)}
462
629
  tier: {template.tier} # critical | standard | low
463
630
  type: {template.type} # api | worker | stream | batch | database | ai-gate | x-web
464
- template: {template.name}
631
+ template: {_yaml_scalar(template.name)}
465
632
 
466
633
  # Template provides:
467
634
  {_format_template_resources(template)}
@@ -493,7 +660,12 @@ def _format_template_resources(template) -> str:
493
660
  """
494
661
  lines = []
495
662
  for resource in template.resources:
496
- lines.append(f"# - {resource.kind}: {resource.name}")
663
+ # A comment, but still a line in the document: `resource.kind` and
664
+ # `resource.name` come from a template file on disk with no
665
+ # validation, so a newline in either would escape the `#` and land
666
+ # at top level. `_yaml_scalar` escapes it instead, and leaves the
667
+ # ordinary values (`SLO`, `availability`) unquoted.
668
+ lines.append(f"# - {_yaml_scalar(resource.kind)}: {_yaml_scalar(resource.name)}")
497
669
  return "\n".join(lines) if lines else "# (no resources)"
498
670
 
499
671
 
@@ -11,6 +11,7 @@ this unified model, ensuring format-agnostic processing.
11
11
 
12
12
  from __future__ import annotations
13
13
 
14
+ import re
14
15
  from dataclasses import dataclass, field
15
16
  from enum import Enum
16
17
  from typing import TYPE_CHECKING, Any
@@ -45,6 +46,59 @@ VALID_TIERS = {
45
46
  "low",
46
47
  }
47
48
 
49
+ # THE service-name rule for the init path (opensrm-t4rd). Two copies of it
50
+ # disagreed:
51
+ # cli/init.py's guard looped over `char.islower() or char.isdigit()`, which are
52
+ # UNICODE predicates, and rejected only a leading or trailing hyphen — so
53
+ # `1-svc`, `café` and fullwidth `abc` all passed the CLI and were then rejected
54
+ # by specs/validator.py, which applies the pattern below. `nthlayer init` exited
55
+ # 0 having written a manifest its own validator refuses, and an exit code is
56
+ # what a CI gate keys on.
57
+ #
58
+ # NOT exhaustive: cli/setup.py has a third, still-divergent copy that accepts
59
+ # `123`, `café` and `1-svc` (it does reject both hyphen ends). Tracked in
60
+ # opensrm-h9fq, and asserted by
61
+ # test_init.py::TestSetupGuardStillDiverges so this note cannot rot quietly.
62
+ #
63
+ # Same shape as the service-type divergence recorded below under opensrm-z3ab,
64
+ # which is why the rule lives here with it rather than in either caller.
65
+ #
66
+ # AUTHORITY: this pattern is generate's own, and is deliberately STRICTER than
67
+ # the opensrm schema, so schema-valid documents exist that `init` refuses to
68
+ # write. That gap is a spec decision, tracked in opensrm-fwnp — which is also
69
+ # where the schema's actual patterns are recorded, rather than quoted here
70
+ # where they would rot. A test cannot hold them: generate's CI installs
71
+ # nthlayer-common from PyPI and has no `opensrm/` checkout, so a schema.json
72
+ # assertion would `pytest.skip` in exactly the environment meant to gate the
73
+ # merge (the reasoning is spelled out in test_service_type_agreement.py).
74
+ #
75
+ # The rule is the union of what the two old guards each enforced:
76
+ # - must start with a lowercase letter (validator.py had this; the CLI did not)
77
+ # - must not end with a hyphen (the CLI had this; validator.py did not)
78
+ # Neither was right alone, so aligning to either one would have loosened the
79
+ # other. Checked before tightening: no leading- or trailing-hyphen service name
80
+ # was in use anywhere in this repo.
81
+ SERVICE_NAME_PATTERN = r"[a-z]([a-z0-9-]*[a-z0-9])?"
82
+ _SERVICE_NAME_RE = re.compile(SERVICE_NAME_PATTERN)
83
+
84
+
85
+ def is_valid_service_name(name: str) -> bool:
86
+ """True if *name* is a legal service name for generated manifests.
87
+
88
+ Uses ``fullmatch``, not ``match`` with a trailing ``$``. In Python ``$``
89
+ also matches just before a final newline, so ``re.match(r"...$", "svc\n")``
90
+ SUCCEEDS — and a trailing newline is an ordinary authoring accident from a
91
+ YAML block scalar.
92
+
93
+ ``isinstance`` is checked first because the loader hands this whatever YAML
94
+ resolved: a manifest with ``name: yes`` yields ``True``, which passes
95
+ ``bool(name)`` and then makes ``fullmatch`` raise ``TypeError``, so
96
+ ``nthlayer validate`` died with a traceback instead of reporting the error.
97
+ A non-str name is never valid, so returning False is both safe and correct.
98
+ """
99
+ return isinstance(name, str) and bool(name) and _SERVICE_NAME_RE.fullmatch(name) is not None
100
+
101
+
48
102
  # Service types come from nthlayer-common, which is the single source of
49
103
  # truth for the rule (opensrm-z3ab). generate kept its own copy until the
50
104
  # two silently disagreed: it accepted `web`, which schema.json rejects,
@@ -539,8 +593,7 @@ class ReliabilityManifest:
539
593
  resolved_type = resolve_service_type(self.type)
540
594
  if resolved_type is None:
541
595
  raise ValueError(
542
- f"Invalid type '{self.type}'. "
543
- f"Must be one of: {valid_service_types_phrase()}."
596
+ f"Invalid type '{self.type}'. Must be one of: {valid_service_types_phrase()}."
544
597
  )
545
598
  self.type = resolved_type
546
599
 
@@ -6,7 +6,6 @@ Supports both OpenSRM format (apiVersion: srm/v1) and legacy NthLayer format.
6
6
 
7
7
  from __future__ import annotations
8
8
 
9
- import re
10
9
  import warnings as stdlib_warnings
11
10
  from dataclasses import dataclass, field
12
11
  from pathlib import Path
@@ -27,7 +26,7 @@ from nthlayer_generate.specs.loader import (
27
26
  ManifestLoadError,
28
27
  load_manifest,
29
28
  )
30
- from nthlayer_generate.specs.manifest import VALID_TIERS
29
+ from nthlayer_generate.specs.manifest import VALID_TIERS, is_valid_service_name
31
30
  from nthlayer_generate.specs.models import VALID_RESOURCE_KINDS
32
31
  from nthlayer_generate.specs.parser import ServiceParseError, parse_service_file
33
32
  from nthlayer_generate.specs.template import validate_template_variables
@@ -199,11 +198,13 @@ def validate_service_file(
199
198
  )
200
199
 
201
200
  # Validate service name format
202
- if not re.match(r"^[a-z][a-z0-9-]*$", service_context.name):
201
+ # Shared with cli/init.py's guard via specs/manifest (opensrm-t4rd). Two
202
+ # copies disagreed, so init exited 0 writing names this rejected.
203
+ if not is_valid_service_name(service_context.name):
203
204
  errors.append(
204
205
  f"Invalid service name: '{service_context.name}'. "
205
- "Must start with lowercase letter and contain only lowercase letters, "
206
- "numbers, and hyphens."
206
+ "Must start with a lowercase letter, contain only lowercase letters, "
207
+ "numbers and hyphens, and not end with a hyphen."
207
208
  )
208
209
 
209
210
  # Validate tier
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nthlayer-generate
3
- Version: 2.0.2
3
+ Version: 2.0.3
4
4
  Summary: NthLayer - The Missing Layer of Reliability
5
5
  Requires-Python: >=3.11
6
6
  Description-Content-Type: text/markdown
@@ -337,6 +337,7 @@ tests/test_slo_alerts.py
337
337
  tests/test_slo_ceiling.py
338
338
  tests/test_slo_cli.py
339
339
  tests/test_slos.py
340
+ tests/test_source_citations.py
340
341
  tests/test_specs.py
341
342
  tests/test_sync_awesome_alerts.py
342
343
  tests/test_templates.py