nthlayer-workers 1.7.1__tar.gz → 2.0.1__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 (192) hide show
  1. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/PKG-INFO +2 -2
  2. nthlayer_workers-2.0.1/pyproject.toml +110 -0
  3. nthlayer_workers-2.0.1/src/nthlayer_workers/cli_output.py +26 -0
  4. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/learn/cli.py +9 -0
  5. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/learn/retrospective.py +77 -24
  6. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/learn/worker.py +2 -2
  7. nthlayer_workers-2.0.1/src/nthlayer_workers/measure/adapters/prometheus.py +483 -0
  8. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/measure/cli.py +22 -11
  9. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/observe/cli.py +9 -2
  10. nthlayer_workers-2.0.1/src/nthlayer_workers/observe/slo/spec_loader.py +91 -0
  11. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers.egg-info/PKG-INFO +2 -2
  12. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers.egg-info/SOURCES.txt +2 -0
  13. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers.egg-info/requires.txt +1 -1
  14. nthlayer_workers-2.0.1/tests/test_dependency_declarations.py +297 -0
  15. nthlayer_workers-1.7.1/pyproject.toml +0 -72
  16. nthlayer_workers-1.7.1/src/nthlayer_workers/measure/adapters/prometheus.py +0 -290
  17. nthlayer_workers-1.7.1/src/nthlayer_workers/observe/slo/spec_loader.py +0 -40
  18. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/README.md +0 -0
  19. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/setup.cfg +0 -0
  20. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/__init__.py +0 -0
  21. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/cli.py +0 -0
  22. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/correlate/__init__.py +0 -0
  23. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/correlate/cli.py +0 -0
  24. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/correlate/config.py +0 -0
  25. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/correlate/correlation/__init__.py +0 -0
  26. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/correlate/correlation/changes.py +0 -0
  27. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/correlate/correlation/dedup.py +0 -0
  28. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/correlate/correlation/engine.py +0 -0
  29. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/correlate/correlation/temporal.py +0 -0
  30. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/correlate/correlation/topology.py +0 -0
  31. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/correlate/ingestion/__init__.py +0 -0
  32. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/correlate/ingestion/protocol.py +0 -0
  33. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/correlate/ingestion/severity.py +0 -0
  34. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/correlate/ingestion/webhook.py +0 -0
  35. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/correlate/notifications.py +0 -0
  36. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/correlate/prometheus.py +0 -0
  37. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/correlate/reasoning.py +0 -0
  38. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/correlate/session.py +0 -0
  39. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/correlate/snapshot/__init__.py +0 -0
  40. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/correlate/snapshot/generator.py +0 -0
  41. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/correlate/snapshot/model.py +0 -0
  42. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/correlate/snapshot/token.py +0 -0
  43. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/correlate/state.py +0 -0
  44. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/correlate/store/__init__.py +0 -0
  45. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/correlate/store/protocol.py +0 -0
  46. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/correlate/store/sqlite.py +0 -0
  47. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/correlate/summary.py +0 -0
  48. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/correlate/traces/__init__.py +0 -0
  49. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/correlate/traces/protocol.py +0 -0
  50. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/correlate/traces/tempo.py +0 -0
  51. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/correlate/traces/topology.py +0 -0
  52. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/correlate/types.py +0 -0
  53. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/correlate/verdict_chain.py +0 -0
  54. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/correlate/worker.py +0 -0
  55. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/learn/__init__.py +0 -0
  56. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/learn/__main__.py +0 -0
  57. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/learn/_apply.py +0 -0
  58. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/learn/_gh.py +0 -0
  59. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/learn/_interactive.py +0 -0
  60. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/learn/_interactive_app.py +0 -0
  61. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/learn/_preview.py +0 -0
  62. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/learn/_trigger.py +0 -0
  63. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/learn/_yaml.py +0 -0
  64. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/learn/recommendations.py +0 -0
  65. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/learn/trends.py +0 -0
  66. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/measure/__init__.py +0 -0
  67. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/measure/__main__.py +0 -0
  68. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/measure/_parsing.py +0 -0
  69. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/measure/adapters/__init__.py +0 -0
  70. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/measure/adapters/_util.py +0 -0
  71. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/measure/adapters/devin.py +0 -0
  72. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/measure/adapters/gastown.py +0 -0
  73. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/measure/adapters/protocol.py +0 -0
  74. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/measure/adapters/webhook.py +0 -0
  75. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/measure/calibration/__init__.py +0 -0
  76. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/measure/calibration/loop.py +0 -0
  77. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/measure/calibration/slos.py +0 -0
  78. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/measure/calibration/verdict_calibration.py +0 -0
  79. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/measure/config.py +0 -0
  80. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/measure/detection/__init__.py +0 -0
  81. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/measure/detection/detector.py +0 -0
  82. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/measure/detection/protocol.py +0 -0
  83. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/measure/governance/__init__.py +0 -0
  84. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/measure/governance/engine.py +0 -0
  85. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/measure/manifest.py +0 -0
  86. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/measure/notifications.py +0 -0
  87. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/measure/pipeline/__init__.py +0 -0
  88. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/measure/pipeline/evaluator.py +0 -0
  89. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/measure/pipeline/router.py +0 -0
  90. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/measure/store/__init__.py +0 -0
  91. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/measure/store/protocol.py +0 -0
  92. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/measure/store/sqlite.py +0 -0
  93. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/measure/telemetry.py +0 -0
  94. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/measure/tiering/__init__.py +0 -0
  95. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/measure/tiering/classifier.py +0 -0
  96. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/measure/tiering/promotion.py +0 -0
  97. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/measure/trends/__init__.py +0 -0
  98. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/measure/trends/tracker.py +0 -0
  99. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/measure/types.py +0 -0
  100. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/measure/worker.py +0 -0
  101. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/observe/__init__.py +0 -0
  102. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/observe/__main__.py +0 -0
  103. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/observe/api/__init__.py +0 -0
  104. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/observe/assessment.py +0 -0
  105. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/observe/config.py +0 -0
  106. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/observe/db/__init__.py +0 -0
  107. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/observe/decision_records.py +0 -0
  108. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/observe/dependencies/__init__.py +0 -0
  109. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/observe/dependencies/discovery.py +0 -0
  110. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/observe/dependencies/providers/__init__.py +0 -0
  111. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/observe/dependencies/providers/backstage.py +0 -0
  112. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/observe/dependencies/providers/base.py +0 -0
  113. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/observe/dependencies/providers/consul.py +0 -0
  114. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/observe/dependencies/providers/etcd.py +0 -0
  115. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/observe/dependencies/providers/kubernetes.py +0 -0
  116. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/observe/dependencies/providers/prometheus.py +0 -0
  117. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/observe/dependencies/providers/zookeeper.py +0 -0
  118. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/observe/deployments/__init__.py +0 -0
  119. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/observe/discovery/__init__.py +0 -0
  120. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/observe/discovery/classifier.py +0 -0
  121. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/observe/discovery/client.py +0 -0
  122. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/observe/discovery/models.py +0 -0
  123. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/observe/drift/__init__.py +0 -0
  124. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/observe/drift/analyzer.py +0 -0
  125. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/observe/drift/models.py +0 -0
  126. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/observe/drift/patterns.py +0 -0
  127. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/observe/explanation.py +0 -0
  128. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/observe/gate/__init__.py +0 -0
  129. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/observe/gate/conditions.py +0 -0
  130. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/observe/gate/correlator.py +0 -0
  131. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/observe/gate/evaluator.py +0 -0
  132. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/observe/gate/policies.py +0 -0
  133. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/observe/gate_adapter.py +0 -0
  134. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/observe/incident.py +0 -0
  135. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/observe/portfolio/__init__.py +0 -0
  136. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/observe/portfolio/aggregator.py +0 -0
  137. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/observe/portfolio/scorer.py +0 -0
  138. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/observe/slo/__init__.py +0 -0
  139. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/observe/slo/collector.py +0 -0
  140. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/observe/sqlite_store.py +0 -0
  141. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/observe/store.py +0 -0
  142. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/observe/verification/__init__.py +0 -0
  143. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/observe/verification/exporter_guidance.py +0 -0
  144. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/observe/verification/extractor.py +0 -0
  145. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/observe/verification/models.py +0 -0
  146. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/observe/verification/verifier.py +0 -0
  147. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/observe/worker.py +0 -0
  148. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/respond/__init__.py +0 -0
  149. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/respond/__main__.py +0 -0
  150. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/respond/agents/__init__.py +0 -0
  151. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/respond/agents/base.py +0 -0
  152. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/respond/agents/communication.py +0 -0
  153. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/respond/agents/investigation.py +0 -0
  154. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/respond/agents/remediation.py +0 -0
  155. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/respond/agents/response_models.py +0 -0
  156. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/respond/agents/triage.py +0 -0
  157. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/respond/cli.py +0 -0
  158. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/respond/config.py +0 -0
  159. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/respond/context_store.py +0 -0
  160. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/respond/coordinator.py +0 -0
  161. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/respond/metrics.py +0 -0
  162. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/respond/notification_backends/__init__.py +0 -0
  163. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/respond/notification_backends/ntfy_backend.py +0 -0
  164. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/respond/notification_backends/protocol.py +0 -0
  165. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/respond/notification_backends/slack_backend.py +0 -0
  166. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/respond/notification_backends/stdout_backend.py +0 -0
  167. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/respond/notifications.py +0 -0
  168. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/respond/oncall/__init__.py +0 -0
  169. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/respond/oncall/escalation.py +0 -0
  170. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/respond/oncall/runner.py +0 -0
  171. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/respond/oncall/schedule.py +0 -0
  172. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/respond/safe_actions/__init__.py +0 -0
  173. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/respond/safe_actions/actions.py +0 -0
  174. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/respond/safe_actions/registry.py +0 -0
  175. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/respond/safe_actions/webhook.py +0 -0
  176. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/respond/server.py +0 -0
  177. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/respond/sre/__init__.py +0 -0
  178. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/respond/sre/brief.py +0 -0
  179. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/respond/sre/delegation.py +0 -0
  180. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/respond/sre/post_incident.py +0 -0
  181. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/respond/sre/shift_report.py +0 -0
  182. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/respond/sre/suppression.py +0 -0
  183. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/respond/types.py +0 -0
  184. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/respond/verdict_submission.py +0 -0
  185. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/respond/worker.py +0 -0
  186. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/respond/worker_helpers.py +0 -0
  187. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers/runner.py +0 -0
  188. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers.egg-info/dependency_links.txt +0 -0
  189. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers.egg-info/entry_points.txt +0 -0
  190. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/src/nthlayer_workers.egg-info/top_level.txt +0 -0
  191. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/tests/test_cli_gate.py +0 -0
  192. {nthlayer_workers-1.7.1 → nthlayer_workers-2.0.1}/tests/test_runner.py +0 -0
@@ -1,10 +1,10 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nthlayer-workers
3
- Version: 1.7.1
3
+ Version: 2.0.1
4
4
  Summary: NthLayer workers — Tier 2 background computation (observe, measure, correlate, respond, learn)
5
5
  Requires-Python: >=3.11
6
6
  Description-Content-Type: text/markdown
7
- Requires-Dist: nthlayer-common<2.0.0,>=1.5.0
7
+ Requires-Dist: nthlayer-common<4.0.0,>=3.0.0
8
8
  Requires-Dist: httpx>=0.27
9
9
  Requires-Dist: pyyaml>=6.0
10
10
  Requires-Dist: structlog>=24.1.0
@@ -0,0 +1,110 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "nthlayer-workers"
7
+ version = "2.0.1"
8
+ description = "NthLayer workers — Tier 2 background computation (observe, measure, correlate, respond, learn)"
9
+ readme = "README.md"
10
+ requires-python = ">=3.11"
11
+ dependencies = [
12
+ # >=3.0.0, NOT >=2.1.2. Two of the six breaks documented in
13
+ # nthlayer-common/docs/upgrading-3.0.md reach this package. Labelled rather
14
+ # than numbered, because that doc numbers its items 1-6 globally and its
15
+ # item 2 is a different break from either of these.
16
+ #
17
+ # BREAK A (that doc's item 1) — target conversion moved to the INBOUND
18
+ # manifest boundary (opensrm-ocvu), so a v2 objective target of 0.999 now
19
+ # parses to 99.9 where it used to parse to 0.999. The measure adapter
20
+ # compares SLODefinition.target against a measured value directly, so the
21
+ # two conventions disagree on the v2 path — exactly 100x for classical
22
+ # targets, while judgment maxima both complement AND scale (0.05 -> 95.0).
23
+ #
24
+ # BREAK B (that doc's item 6) — iter_manifest_files returns one manifest per
25
+ # stem (opensrm-xvwt), which retires this package's own same-stem
26
+ # duplicate handling. The remaining service-name duplicate branch in
27
+ # learn/retrospective.py is still live and is covered by
28
+ # TestDuplicateServiceAcrossDifferentStems.
29
+ #
30
+ # So the floor must admit only the convention this code is written against.
31
+ #
32
+ # The historical 2.1.2 floor is inherited and still satisfied: 2.1.0's
33
+ # foreign_yaml_reason had an INERT body-recovery step (it tested spec.slos
34
+ # as a list, but v1 writes a mapping and v2 has no such key), fixed across
35
+ # 2.1.1 and 2.1.2 — and what made it 2.1.2 specifically rather than 2.1.1
36
+ # is that 2.1.1 still counted every Backstage catalog-info.yaml as a broken
37
+ # manifest.
38
+ #
39
+ # CI resolves nthlayer-common from a sibling checkout, so it would not
40
+ # notice a bad resolve. tests/observe/test_slo_spec_loader.py's
41
+ # TestSharedGateResolvesCorrectly exists for that: it uses the shapes the
42
+ # PARSERS produce and fails against 2.1.0.
43
+ #
44
+ # tests/test_dependency_declarations.py guards THIS declaration as of
45
+ # [opensrm-ir5m] — ported from nthlayer-core once a mutation showed the
46
+ # range could be reverted to >=2.1.2,<3.0.0 with all 1958 tests still
47
+ # green. Still missing, and tracked as opensrm-p62o: the EXPECTED_MAJORS
48
+ # half, which reads the BUILT wheel's own metadata in the release
49
+ # container, where deps come from PyPI instead of the sibling checkout.
50
+ "nthlayer-common>=3.0.0,<4.0.0",
51
+ "httpx>=0.27",
52
+ "pyyaml>=6.0",
53
+ "structlog>=24.1.0",
54
+ "scipy>=1.11",
55
+ "numpy>=1.24",
56
+ "starlette>=0.40",
57
+ "uvicorn>=0.30",
58
+ # Required by ApprovalServer's Slack interaction endpoint at
59
+ # `respond/server.py:197` — `await request.form()` hard-imports
60
+ # python-multipart via Starlette. Operators running with
61
+ # `slack_signing_secret` set need this at runtime, not just for
62
+ # tests; declared as a top-level production dep.
63
+ "python-multipart>=0.0.18",
64
+ "ruamel.yaml>=0.18",
65
+ "textual>=1.0",
66
+ ]
67
+
68
+ [project.scripts]
69
+ nthlayer-workers = "nthlayer_workers.cli:main"
70
+ nthlayer-learn = "nthlayer_workers.learn.cli:main"
71
+
72
+ [project.optional-dependencies]
73
+ dev = [
74
+ "pytest>=8.2",
75
+ "pytest-asyncio>=0.23",
76
+ "respx>=0.21",
77
+ "ruff>=0.8",
78
+ ]
79
+
80
+ [tool.uv.sources]
81
+ nthlayer-common = { path = "../nthlayer-common", editable = true }
82
+
83
+ [tool.setuptools.packages.find]
84
+ where = ["src"]
85
+
86
+ [tool.pytest.ini_options]
87
+ asyncio_mode = "auto"
88
+ testpaths = ["tests"]
89
+
90
+ # Ruff configuration (opensrm-d96q floor). Explicit defaults so future
91
+ # violations show up as new findings rather than blending in. Tightening
92
+ # beyond E/F (isort/pyupgrade/simplify/bugbear) is tracked in
93
+ # opensrm-d96q.1 .. d96q.4 — each rule family is its own bead because
94
+ # the cumulative diff across all four is ~640 findings.
95
+ [tool.ruff]
96
+ target-version = "py311"
97
+ line-length = 100
98
+
99
+ [tool.ruff.lint]
100
+ # Mirrors ruff's literal default select (E4/E7/E9 + F) plus I (isort,
101
+ # opensrm-ncy0), UP (pyupgrade, opensrm-6nn1), SIM (simplify,
102
+ # opensrm-y7dd), and B (bugbear, opensrm-po23). E501 / W are separate
103
+ # hygiene calls (line-too-long has 393 hits today).
104
+ select = ["E4", "E7", "E9", "F", "I", "UP", "SIM", "B"]
105
+
106
+ [tool.ruff.lint.per-file-ignores]
107
+ # Correlate test files defer some imports until after pytest.importorskip
108
+ # / pytestmark blocks (the modules under test optional-import heavy deps).
109
+ # Pin E402 here instead of scattering noqa: E402 across the call sites.
110
+ "tests/correlate/test_*.py" = ["E402"]
@@ -0,0 +1,26 @@
1
+ """Operator-facing CLI output shared across worker modules."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import sys
6
+ from pathlib import Path
7
+
8
+
9
+ def warn_parse_failures(parse_failures: int, specs_dir: str | Path) -> None:
10
+ """Tell the operator when the SLOs that follow cover only part of a directory.
11
+
12
+ Surfaced on stderr rather than only logged: whoever reads SLO output is
13
+ the one who needs to know the view is partial, and a warning in a log
14
+ they are not tailing does not reach them (opensrm-3470).
15
+
16
+ Takes the count, not a ``LoadedSpecs`` — observe and measure each have
17
+ their own, carrying differently-shaped SLOs under differently-named
18
+ fields (``service_slos`` vs ``slos``). The only thing they share is this
19
+ number, so that is the whole parameter (opensrm-fxln).
20
+ """
21
+ if parse_failures:
22
+ print(
23
+ f"Warning: {parse_failures} manifest(s) in {specs_dir} "
24
+ f"failed to parse and were not evaluated",
25
+ file=sys.stderr,
26
+ )
@@ -139,6 +139,15 @@ def _cmd_retrospective(args: argparse.Namespace) -> None:
139
139
  impact = custom.get("financial_impact")
140
140
  if impact:
141
141
  print(f" Financial impact: ${impact.get('estimated', 0):.2f} ({impact.get('failure_mode')})")
142
+ # opensrm-oh27: an unparseable manifest drops its service from the
143
+ # figures above. Print the count so the reader knows the numbers
144
+ # were computed over a subset rather than over everything.
145
+ parse_failures = custom.get("manifest_parse_failures", 0)
146
+ if parse_failures:
147
+ print(
148
+ f" Manifest parse failures: {parse_failures} "
149
+ "(figures computed over a subset; see manifest_parse_failed logs)"
150
+ )
142
151
  except KeyError as e:
143
152
  print(f"Error: {e}", file=__import__("sys").stderr)
144
153
  __import__("sys").exit(1)
@@ -1,6 +1,7 @@
1
1
  """Retrospective builder — walks verdict lineage to produce a post-incident analysis."""
2
2
  from __future__ import annotations
3
3
 
4
+ from dataclasses import dataclass
4
5
  from datetime import UTC, datetime, timedelta
5
6
  from pathlib import Path
6
7
  from typing import Any
@@ -9,6 +10,8 @@ import structlog
9
10
  from nthlayer_common.manifest import (
10
11
  ManifestLoadError,
11
12
  extract_declared_dependencies,
13
+ foreign_yaml_reason,
14
+ iter_manifest_files,
12
15
  load_manifest,
13
16
  )
14
17
  from nthlayer_common.outcomes import (
@@ -116,8 +119,8 @@ def build_retrospective(
116
119
  # ground-truth view of operator-declared deps that downstream
117
120
  # add_dependency recommendation logic compares against the
118
121
  # incident's observed call graph.
119
- loaded_manifests = _load_manifests_from_specs(specs_dir)
120
- declared_dependencies_by_service = _extract_declared_dependencies(loaded_manifests)
122
+ loaded_specs = _load_manifests_from_specs(specs_dir)
123
+ declared_dependencies_by_service = _extract_declared_dependencies(loaded_specs.manifests)
121
124
 
122
125
  # Financial impact (if specs available)
123
126
  financial_impact = _compute_financial_impact(
@@ -125,7 +128,7 @@ def build_retrospective(
125
128
  duration_minutes,
126
129
  breach_counts_by_service,
127
130
  specs_dir,
128
- loaded_manifests=loaded_manifests,
131
+ manifests_by_service=loaded_specs.manifests,
129
132
  )
130
133
 
131
134
  # Generate recommendations
@@ -155,6 +158,10 @@ def build_retrospective(
155
158
  "financial_impact": financial_impact,
156
159
  "recommendations": recommendations,
157
160
  "declared_dependencies_by_service": declared_dependencies_by_service,
161
+ # opensrm-oh27: always present, 0 on a clean load. An absent key
162
+ # would read the same as "no failures", which is the ambiguity
163
+ # the silent skip created in the first place.
164
+ "manifest_parse_failures": loaded_specs.parse_failures,
158
165
  }
159
166
  if trigger_service is not None:
160
167
  retro_custom["trigger_service"] = trigger_service
@@ -225,32 +232,74 @@ def _parse_ts(ts: Any) -> datetime:
225
232
  return dt
226
233
 
227
234
 
228
- def _load_manifests_from_specs(specs_dir: str | None) -> dict[str, Any]:
235
+ @dataclass(frozen=True)
236
+ class LoadedManifests:
237
+ """Outcome of scanning a specs directory (opensrm-oh27).
238
+
239
+ ``manifests`` is the parsed view keyed by service name; ``parse_failures``
240
+ counts *files* that raised ``ManifestLoadError``, not services missing
241
+ from ``manifests`` — a failed environment overlay for a service whose
242
+ base manifest parsed still counts one. The count is deliberately a
243
+ coverage-doubt signal rather than a precise miss-count, and it travels
244
+ with the manifests because the downstream financial figure is computed
245
+ over ``manifests`` alone: without it, a partial view is
246
+ indistinguishable from a complete one. The per-file
247
+ ``manifest_parse_failed`` warning carries which file and why.
248
+ """
249
+
250
+ manifests: dict[str, Any]
251
+ parse_failures: int = 0
252
+
253
+
254
+ def _load_manifests_from_specs(specs_dir: str | None) -> LoadedManifests:
229
255
  """Load all ``*.yaml`` manifests under ``specs_dir`` once, keyed by
230
256
  ``manifest.name`` (the canonical service identity).
231
257
 
232
- Returns an empty dict when ``specs_dir`` is None or not a directory,
258
+ Returns empty manifests when ``specs_dir`` is None or not a directory,
233
259
  so callers can treat the result uniformly without re-checking the
234
260
  filesystem. Duplicate manifests for the same service (template forks,
235
261
  environment overlays) are deduplicated by keeping the first one
236
262
  encountered and emitting a warning — this matches the pre-jmy.21
237
263
  behaviour of ``_compute_financial_impact``.
238
264
 
239
- Parse failures from ``load_manifest`` are silently skipped (consistent
240
- with the prior inline ``ManifestLoadError`` swallow); the financial-
241
- impact and dependency-extraction paths both tolerate a partial view.
265
+ Parse failures from ``load_manifest`` are logged and counted, then
266
+ skipped (opensrm-oh27). ``UnicodeDecodeError`` is caught alongside
267
+ ``ManifestLoadError`` because ``load_manifest`` opens with the default
268
+ encoding and only wraps ``YAMLError`` — non-UTF-8 bytes would otherwise
269
+ abort the whole retrospective, a hard stop where the design says log and
270
+ continue. The financial-impact and dependency-extraction
271
+ paths both tolerate a partial view, but the caller is told how partial
272
+ it is via ``LoadedManifests.parse_failures``.
242
273
  """
243
274
  if not specs_dir:
244
- return {}
275
+ return LoadedManifests(manifests={})
245
276
  specs_path = Path(specs_dir)
246
277
  if not specs_path.is_dir():
247
- return {}
278
+ return LoadedManifests(manifests={})
248
279
 
249
280
  manifests: dict[str, Any] = {}
250
- for spec_file in specs_path.glob("*.yaml"):
281
+ parse_failures = 0
282
+ for spec_file in iter_manifest_files(specs_path):
251
283
  try:
252
284
  manifest = load_manifest(str(spec_file))
253
- except ManifestLoadError:
285
+ except (ManifestLoadError, UnicodeDecodeError) as exc:
286
+ foreign_reason = foreign_yaml_reason(spec_file)
287
+ if foreign_reason is not None:
288
+ # Foreign YAML sharing the directory, not a broken manifest.
289
+ # Recorded rather than dropped without trace — skipping
290
+ # without saying so is the shape this bead exists to remove.
291
+ log.debug(
292
+ "manifest_file_ignored",
293
+ spec_file=str(spec_file),
294
+ reason=foreign_reason,
295
+ )
296
+ continue
297
+ parse_failures += 1
298
+ log.warning(
299
+ "manifest_parse_failed",
300
+ spec_file=str(spec_file),
301
+ error=str(exc),
302
+ )
254
303
  continue
255
304
  if manifest.name in manifests:
256
305
  log.warning(
@@ -260,16 +309,16 @@ def _load_manifests_from_specs(specs_dir: str | None) -> dict[str, Any]:
260
309
  )
261
310
  continue
262
311
  manifests[manifest.name] = manifest
263
- return manifests
312
+ return LoadedManifests(manifests=manifests, parse_failures=parse_failures)
264
313
 
265
314
 
266
315
  def _extract_declared_dependencies(
267
- loaded_manifests: dict[str, Any],
316
+ manifests_by_service: dict[str, Any],
268
317
  ) -> dict[str, list[str]]:
269
318
  """Wrapper around the shared helper (opensrm-dpws). The CLI path
270
319
  works with Manifest dataclass instances loaded via load_manifest().
271
320
  """
272
- return extract_declared_dependencies(from_manifests=loaded_manifests)
321
+ return extract_declared_dependencies(from_manifests=manifests_by_service)
273
322
 
274
323
 
275
324
  def _compute_financial_impact(
@@ -278,7 +327,7 @@ def _compute_financial_impact(
278
327
  breach_counts_by_service: dict[str, int],
279
328
  specs_dir: str | None,
280
329
  *,
281
- loaded_manifests: dict[str, Any] | None = None,
330
+ manifests_by_service: dict[str, Any] | None = None,
282
331
  ) -> dict | None:
283
332
  """Compute spec § 1 financial impact across blast-radius services.
284
333
 
@@ -289,10 +338,14 @@ def _compute_financial_impact(
289
338
  services that share a currency; mixed-currency manifests are
290
339
  skipped with a warning.
291
340
 
292
- ``loaded_manifests`` is the shared cache produced by
293
- ``_load_manifests_from_specs`` (opensrm-jmy.21). When omitted the
294
- function loads manifests itself — preserves the pre-jmy.21 calling
295
- convention used by ``tests/learn/test_retrospective_financial.py``.
341
+ ``manifests_by_service`` is the shared cache produced by
342
+ ``_load_manifests_from_specs(...).manifests`` (opensrm-jmy.21). When
343
+ omitted the function loads manifests itself — preserves the pre-jmy.21
344
+ calling convention used by ``tests/learn/test_retrospective_financial.py``.
345
+ That self-loading path discards the parse-failure count; only
346
+ ``build_retrospective`` surfaces it (as
347
+ ``metadata.custom["manifest_parse_failures"]``), and the per-file
348
+ warning log fires either way.
296
349
  """
297
350
  if not specs_dir or not blast_radius:
298
351
  return None
@@ -307,9 +360,9 @@ def _compute_financial_impact(
307
360
  )
308
361
  return None
309
362
 
310
- if loaded_manifests is None:
311
- loaded_manifests = _load_manifests_from_specs(specs_dir)
312
- if not loaded_manifests:
363
+ if manifests_by_service is None:
364
+ manifests_by_service = _load_manifests_from_specs(specs_dir).manifests
365
+ if not manifests_by_service:
313
366
  return None
314
367
 
315
368
  affected_services = {
@@ -322,7 +375,7 @@ def _compute_financial_impact(
322
375
  currency: str | None = None
323
376
  aggregate_source: str | None = None
324
377
 
325
- for service_name, manifest in loaded_manifests.items():
378
+ for service_name, manifest in manifests_by_service.items():
326
379
  if service_name not in affected_services:
327
380
  continue
328
381
  if manifest.outcomes is None:
@@ -323,8 +323,8 @@ class LearnRetrospectiveModule:
323
323
  ]
324
324
  if len(trigger_matches) > 1:
325
325
  # First-wins (matches the existing
326
- # _load_manifests_from_specs dedup behaviour
327
- # on the CLI side, retrospective.py line 238).
326
+ # _load_manifests_from_specs dedup behaviour on the
327
+ # CLI side, which logs manifest_duplicate_skipped).
328
328
  logger.warning(
329
329
  "learn_manifest_duplicate_skipped",
330
330
  service=trigger_service,