skilldrop-cli 0.16.35 → 0.16.37

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 (298) hide show
  1. package/README.md +1 -0
  2. package/agents/README.md +4 -1
  3. package/agents/docs-reviewer.md +47 -0
  4. package/agents/finding-adjudicator.md +49 -0
  5. package/agents/test-reviewer.md +52 -0
  6. package/catalogue.json +19 -7
  7. package/model-routing.json +48 -0
  8. package/package.json +1 -1
  9. package/packs/ai-engineering/skills/agent-config-review/SKILL.md +116 -0
  10. package/packs/ai-engineering/skills/agent-config-review/evals/eval_queries.json +12 -0
  11. package/packs/ai-engineering/skills/agent-config-review/evals/evals.json +75 -0
  12. package/packs/ai-engineering/skills/agent-config-review/evals/files/1/.claude/agents/db-migrator.md +8 -0
  13. package/packs/ai-engineering/skills/agent-config-review/evals/files/1/.claude/agents/reviewer.md +9 -0
  14. package/packs/ai-engineering/skills/agent-config-review/evals/files/1/.claude/settings.json +48 -0
  15. package/packs/ai-engineering/skills/agent-config-review/evals/files/1/.claude/settings.local.json +5 -0
  16. package/packs/ai-engineering/skills/agent-config-review/evals/files/1/.mcp.json +22 -0
  17. package/packs/ai-engineering/skills/agent-config-review/evals/files/1/package.json +12 -0
  18. package/packs/ai-engineering/skills/agent-config-review/evals/files/2/.cursor/mcp.json +17 -0
  19. package/packs/ai-engineering/skills/agent-config-review/evals/files/2/config.toml +21 -0
  20. package/packs/ai-engineering/skills/agent-config-review/evals/files/4/my-user-settings.json +11 -0
  21. package/packs/ai-engineering/skills/agent-config-review/evals/files/4/quillstack/.claude/agents/release-notes.md +13 -0
  22. package/packs/ai-engineering/skills/agent-config-review/evals/files/4/quillstack/.claude/settings.json +39 -0
  23. package/packs/ai-engineering/skills/agent-config-review/evals/files/4/quillstack/.mcp.json +18 -0
  24. package/packs/ai-engineering/skills/agent-config-review/evals/files/4/quillstack/package.json +14 -0
  25. package/packs/ai-engineering/skills/agent-config-review/evals/files/5/.github/workflows/codex-triage.yml +29 -0
  26. package/packs/ai-engineering/skills/agent-config-review/evals/files/5/AGENTS.md +16 -0
  27. package/packs/ai-engineering/skills/agent-config-review/evals/files/5/codex/config.toml +34 -0
  28. package/packs/ai-engineering/skills/agent-config-review/examples/inputs/tidewater-ingest/.claude/settings.json +22 -0
  29. package/packs/ai-engineering/skills/agent-config-review/examples/inputs/tidewater-ingest/.mcp.json +9 -0
  30. package/packs/ai-engineering/skills/agent-config-review/examples/python-repo-tighten.md +81 -0
  31. package/packs/ai-engineering/skills/agent-config-review/manifest.json +60 -0
  32. package/packs/ai-engineering/skills/agent-config-review/reference.md +205 -0
  33. package/packs/ai-engineering/skills/agent-config-review/scripts/agent_config_scan.py +989 -0
  34. package/packs/ai-engineering/skills/agent-incident-response/SKILL.md +175 -0
  35. package/packs/ai-engineering/skills/agent-incident-response/evals/eval_queries.json +14 -0
  36. package/packs/ai-engineering/skills/agent-incident-response/evals/evals.json +107 -0
  37. package/packs/ai-engineering/skills/agent-incident-response/evals/files/1/agent-session-excerpt.txt +11 -0
  38. package/packs/ai-engineering/skills/agent-incident-response/evals/files/1/ci-build-log.txt +24 -0
  39. package/packs/ai-engineering/skills/agent-incident-response/evals/files/1/incident-notes.md +19 -0
  40. package/packs/ai-engineering/skills/agent-incident-response/evals/files/2/mcp-config.json +20 -0
  41. package/packs/ai-engineering/skills/agent-incident-response/evals/files/2/session-transcript.txt +8 -0
  42. package/packs/ai-engineering/skills/agent-incident-response/evals/files/4/agent-session.log +70 -0
  43. package/packs/ai-engineering/skills/agent-incident-response/evals/files/4/infra/staging-db.tf +25 -0
  44. package/packs/ai-engineering/skills/agent-incident-response/evals/files/5/jobs/nightly-triage/job.yaml +19 -0
  45. package/packs/ai-engineering/skills/agent-incident-response/evals/files/5/jobs/nightly-triage/run.log +13 -0
  46. package/packs/ai-engineering/skills/agent-incident-response/evals/files/5/usage-export-2026-10-20.csv +13 -0
  47. package/packs/ai-engineering/skills/agent-incident-response/evals/files/6/.mcp.json +20 -0
  48. package/packs/ai-engineering/skills/agent-incident-response/evals/files/6/git-log.txt +12 -0
  49. package/packs/ai-engineering/skills/agent-incident-response/evals/files/6/notesync-excerpt.txt +30 -0
  50. package/packs/ai-engineering/skills/agent-incident-response/evals/files/6/notesync.log +15 -0
  51. package/packs/ai-engineering/skills/agent-incident-response/evals/files/6/ssh-config.txt +16 -0
  52. package/packs/ai-engineering/skills/agent-incident-response/evals/files/7/.github/workflows/ci.yml +28 -0
  53. package/packs/ai-engineering/skills/agent-incident-response/evals/files/7/SECURITY.md +8 -0
  54. package/packs/ai-engineering/skills/agent-incident-response/evals/files/7/issue-479.md +35 -0
  55. package/packs/ai-engineering/skills/agent-incident-response/evals/files/7/package.json +15 -0
  56. package/packs/ai-engineering/skills/agent-incident-response/evals/files/7/pr-482.diff +20 -0
  57. package/packs/ai-engineering/skills/agent-incident-response/evals/files/7/run-log.txt +30 -0
  58. package/packs/ai-engineering/skills/agent-incident-response/examples/injected-issue-comment.md +57 -0
  59. package/packs/ai-engineering/skills/agent-incident-response/manifest.json +55 -0
  60. package/packs/ai-engineering/skills/agent-incident-response/reference.md +152 -0
  61. package/packs/ai-engineering/skills/agent-incident-response/scripts/find_secret_exposure.py +239 -0
  62. package/packs/ai-engineering/skills/agent-incident-response/scripts/usage_split.py +194 -0
  63. package/packs/ai-engineering/skills/judge-audit/SKILL.md +157 -0
  64. package/packs/ai-engineering/skills/judge-audit/evals/eval_queries.json +11 -0
  65. package/packs/ai-engineering/skills/judge-audit/evals/evals.json +136 -0
  66. package/packs/ai-engineering/skills/judge-audit/evals/files/1/judgements.csv +321 -0
  67. package/packs/ai-engineering/skills/judge-audit/evals/files/1/notes.md +28 -0
  68. package/packs/ai-engineering/skills/judge-audit/evals/files/2/pairwise.csv +101 -0
  69. package/packs/ai-engineering/skills/judge-audit/evals/files/2/run-notes.md +16 -0
  70. package/packs/ai-engineering/skills/judge-audit/evals/files/4/grades.csv +97 -0
  71. package/packs/ai-engineering/skills/judge-audit/evals/files/5/pairwise_judgements.jsonl +120 -0
  72. package/packs/ai-engineering/skills/judge-audit/evals/files/6/rag-judge/README.md +36 -0
  73. package/packs/ai-engineering/skills/judge-audit/evals/files/6/rag-judge/judgments.csv +301 -0
  74. package/packs/ai-engineering/skills/judge-audit/evals/files/7/moderation/gold_labels.csv +101 -0
  75. package/packs/ai-engineering/skills/judge-audit/evals/files/7/moderation/run_1.jsonl +100 -0
  76. package/packs/ai-engineering/skills/judge-audit/evals/files/7/moderation/run_2.jsonl +100 -0
  77. package/packs/ai-engineering/skills/judge-audit/evals/files/8/patches.csv +241 -0
  78. package/packs/ai-engineering/skills/judge-audit/evals/files/9/adequacy_ratings.csv +151 -0
  79. package/packs/ai-engineering/skills/judge-audit/examples/summary-panel.md +70 -0
  80. package/packs/ai-engineering/skills/judge-audit/manifest.json +44 -0
  81. package/packs/ai-engineering/skills/judge-audit/reference.md +176 -0
  82. package/packs/ai-engineering/skills/judge-audit/scripts/judge_stats.py +1735 -0
  83. package/packs/converters/skills/file-to-markdown/SKILL.md +2 -1
  84. package/packs/converters/skills/file-to-markdown/evals/eval_queries.json +4 -0
  85. package/packs/converters/skills/file-to-markdown/manifest.json +2 -2
  86. package/packs/converters/skills/file-to-markdown/scripts/to_markdown.py +253 -3
  87. package/packs/converters/skills/md-to-confluence/SKILL.md +139 -0
  88. package/packs/converters/skills/md-to-confluence/evals/eval_queries.json +42 -0
  89. package/packs/converters/skills/md-to-confluence/evals/evals.json +89 -0
  90. package/packs/converters/skills/md-to-confluence/evals/files/1/img/latency-dashboard.png +0 -0
  91. package/packs/converters/skills/md-to-confluence/evals/files/1/payments-runbook.md +60 -0
  92. package/packs/converters/skills/md-to-confluence/evals/files/2/deploy-checklist.md +33 -0
  93. package/packs/converters/skills/md-to-confluence/evals/files/3/img/after/screen.png +0 -0
  94. package/packs/converters/skills/md-to-confluence/evals/files/3/img/before/screen.png +0 -0
  95. package/packs/converters/skills/md-to-confluence/evals/files/3/release-notes-4.2.md +49 -0
  96. package/packs/converters/skills/md-to-confluence/evals/files/4/docs/img/topology.png +0 -0
  97. package/packs/converters/skills/md-to-confluence/evals/files/4/docs/ingest-pipeline-design.md +65 -0
  98. package/packs/converters/skills/md-to-confluence/evals/files/5/support/troubleshooting-sso.md +57 -0
  99. package/packs/converters/skills/md-to-confluence/examples/inputs/img/queue-depth.png +0 -0
  100. package/packs/converters/skills/md-to-confluence/examples/inputs/queue-runbook.fixed.md +21 -0
  101. package/packs/converters/skills/md-to-confluence/examples/inputs/queue-runbook.md +21 -0
  102. package/packs/converters/skills/md-to-confluence/examples/queue-runbook.md +57 -0
  103. package/packs/converters/skills/md-to-confluence/manifest.json +52 -0
  104. package/packs/converters/skills/md-to-confluence/reference.md +92 -0
  105. package/packs/converters/skills/md-to-confluence/scripts/md_to_confluence.py +821 -0
  106. package/packs/converters/skills/md-to-pptx/SKILL.md +131 -0
  107. package/packs/converters/skills/md-to-pptx/evals/eval_queries.json +12 -0
  108. package/packs/converters/skills/md-to-pptx/evals/evals.json +84 -0
  109. package/packs/converters/skills/md-to-pptx/evals/files/1/img/defect-burndown.png +0 -0
  110. package/packs/converters/skills/md-to-pptx/evals/files/1/status-report.md +75 -0
  111. package/packs/converters/skills/md-to-pptx/evals/files/3/brand.json +12 -0
  112. package/packs/converters/skills/md-to-pptx/evals/files/3/night-shift-induction.md +53 -0
  113. package/packs/converters/skills/md-to-pptx/evals/files/4/charts/ontime.png +0 -0
  114. package/packs/converters/skills/md-to-pptx/evals/files/4/qbr-q3.md +85 -0
  115. package/packs/converters/skills/md-to-pptx/evals/files/5/vendor-sync-notes.md +28 -0
  116. package/packs/converters/skills/md-to-pptx/examples/support-weekly-input.md +48 -0
  117. package/packs/converters/skills/md-to-pptx/examples/support-weekly.md +75 -0
  118. package/packs/converters/skills/md-to-pptx/examples/tickets-by-week.png +0 -0
  119. package/packs/converters/skills/md-to-pptx/manifest.json +49 -0
  120. package/packs/converters/skills/md-to-pptx/reference.md +139 -0
  121. package/packs/converters/skills/md-to-pptx/scripts/md_to_pptx.py +1841 -0
  122. package/packs/dev-team/loops/modernize/LOOP.md +124 -0
  123. package/packs/dev-team/loops/modernize/evals/eval_queries.json +9 -0
  124. package/packs/dev-team/loops/modernize/evals/evals.json +27 -0
  125. package/packs/dev-team/loops/modernize/loop.json +60 -0
  126. package/packs/dev-team/skills/behaviour-equivalence/SKILL.md +155 -0
  127. package/packs/dev-team/skills/behaviour-equivalence/evals/eval_queries.json +11 -0
  128. package/packs/dev-team/skills/behaviour-equivalence/evals/evals.json +76 -0
  129. package/packs/dev-team/skills/behaviour-equivalence/evals/files/1/cases.jsonl +12 -0
  130. package/packs/dev-team/skills/behaviour-equivalence/evals/files/1/new.jsonl +12 -0
  131. package/packs/dev-team/skills/behaviour-equivalence/evals/files/1/notes.md +15 -0
  132. package/packs/dev-team/skills/behaviour-equivalence/evals/files/1/old.jsonl +12 -0
  133. package/packs/dev-team/skills/behaviour-equivalence/evals/files/1/old_rerun.jsonl +12 -0
  134. package/packs/dev-team/skills/behaviour-equivalence/evals/files/1/pricing_v1.py +45 -0
  135. package/packs/dev-team/skills/behaviour-equivalence/evals/files/1/pricing_v2.py +65 -0
  136. package/packs/dev-team/skills/behaviour-equivalence/evals/files/1/rules.json +10 -0
  137. package/packs/dev-team/skills/behaviour-equivalence/evals/files/3/diff-summary.txt +16 -0
  138. package/packs/dev-team/skills/behaviour-equivalence/evals/files/4/diff.txt +13 -0
  139. package/packs/dev-team/skills/behaviour-equivalence/evals/files/4/fee_calc_py2.py +16 -0
  140. package/packs/dev-team/skills/behaviour-equivalence/evals/files/4/fee_calc_py3.py +24 -0
  141. package/packs/dev-team/skills/behaviour-equivalence/evals/files/4/input.csv +13 -0
  142. package/packs/dev-team/skills/behaviour-equivalence/evals/files/4/new_output.csv +13 -0
  143. package/packs/dev-team/skills/behaviour-equivalence/evals/files/4/old_output.csv +13 -0
  144. package/packs/dev-team/skills/behaviour-equivalence/evals/files/5/TEAM_NOTES.md +19 -0
  145. package/packs/dev-team/skills/behaviour-equivalence/evals/files/5/normalisation.yaml +12 -0
  146. package/packs/dev-team/skills/behaviour-equivalence/evals/files/5/recordings/new_run.jsonl +8 -0
  147. package/packs/dev-team/skills/behaviour-equivalence/evals/files/5/recordings/old_run1.jsonl +8 -0
  148. package/packs/dev-team/skills/behaviour-equivalence/evals/files/5/recordings/old_run2.jsonl +8 -0
  149. package/packs/dev-team/skills/behaviour-equivalence/examples/delivery-date.md +78 -0
  150. package/packs/dev-team/skills/behaviour-equivalence/manifest.json +33 -0
  151. package/packs/dev-team/skills/behaviour-equivalence/reference.md +97 -0
  152. package/packs/dev-team/skills/behaviour-equivalence/scripts/golden_diff.py +524 -0
  153. package/packs/dev-team/skills/behaviour-equivalence/templates/accepted.json +10 -0
  154. package/packs/dev-team/skills/behaviour-equivalence/templates/rules.json +7 -0
  155. package/packs/frontend/pack.json +61 -0
  156. package/packs/frontend/skills/bundle-size-review/SKILL.md +114 -0
  157. package/packs/frontend/skills/bundle-size-review/evals/eval_queries.json +12 -0
  158. package/packs/frontend/skills/bundle-size-review/evals/evals.json +108 -0
  159. package/packs/frontend/skills/bundle-size-review/evals/files/1/package.json +28 -0
  160. package/packs/frontend/skills/bundle-size-review/evals/files/1/src/utils/format.js +26 -0
  161. package/packs/frontend/skills/bundle-size-review/evals/files/1/stats.json +1334 -0
  162. package/packs/frontend/skills/bundle-size-review/evals/files/2/PR.md +21 -0
  163. package/packs/frontend/skills/bundle-size-review/evals/files/2/meta-after.json +440 -0
  164. package/packs/frontend/skills/bundle-size-review/evals/files/2/meta-before.json +360 -0
  165. package/packs/frontend/skills/bundle-size-review/evals/files/3/package.json +29 -0
  166. package/packs/frontend/skills/bundle-size-review/evals/files/3/stats.json +1583 -0
  167. package/packs/frontend/skills/bundle-size-review/evals/files/4/src/components/CommentBox.jsx +24 -0
  168. package/packs/frontend/skills/bundle-size-review/evals/files/4/stats/after.json +299 -0
  169. package/packs/frontend/skills/bundle-size-review/evals/files/4/stats/before.json +275 -0
  170. package/packs/frontend/skills/bundle-size-review/evals/files/5/.browserslistrc +3 -0
  171. package/packs/frontend/skills/bundle-size-review/evals/files/5/build/meta.json +371 -0
  172. package/packs/frontend/skills/bundle-size-review/evals/files/5/build.mjs +18 -0
  173. package/packs/frontend/skills/bundle-size-review/evals/files/5/package-lock.json +39 -0
  174. package/packs/frontend/skills/bundle-size-review/evals/files/5/package.json +22 -0
  175. package/packs/frontend/skills/bundle-size-review/evals/files/5/src/components/Icon.jsx +8 -0
  176. package/packs/frontend/skills/bundle-size-review/evals/files/5/src/polyfills.js +7 -0
  177. package/packs/frontend/skills/bundle-size-review/evals/files/5/src/utils/groupRows.js +4 -0
  178. package/packs/frontend/skills/bundle-size-review/evals/files/6/.browserslistrc +3 -0
  179. package/packs/frontend/skills/bundle-size-review/evals/files/6/package.json +25 -0
  180. package/packs/frontend/skills/bundle-size-review/evals/files/6/src/App.tsx +8 -0
  181. package/packs/frontend/skills/bundle-size-review/evals/files/6/src/i18n/chartLocales.ts +16 -0
  182. package/packs/frontend/skills/bundle-size-review/evals/files/6/src/i18n/index.ts +8 -0
  183. package/packs/frontend/skills/bundle-size-review/evals/files/6/src/i18n/messages/de.ts +5 -0
  184. package/packs/frontend/skills/bundle-size-review/evals/files/6/src/i18n/messages/en.ts +5 -0
  185. package/packs/frontend/skills/bundle-size-review/evals/files/6/src/i18n/useLocale.ts +14 -0
  186. package/packs/frontend/skills/bundle-size-review/evals/files/6/src/main.tsx +10 -0
  187. package/packs/frontend/skills/bundle-size-review/evals/files/6/src/pages/Home.tsx +14 -0
  188. package/packs/frontend/skills/bundle-size-review/evals/files/6/src/pages/Reports.tsx +31 -0
  189. package/packs/frontend/skills/bundle-size-review/evals/files/6/src/routes.tsx +17 -0
  190. package/packs/frontend/skills/bundle-size-review/evals/files/6/stats.json +1869 -0
  191. package/packs/frontend/skills/bundle-size-review/evals/files/6/vite.config.ts +13 -0
  192. package/packs/frontend/skills/bundle-size-review/evals/files/7/apps/backoffice/package.json +26 -0
  193. package/packs/frontend/skills/bundle-size-review/evals/files/7/apps/backoffice/src/App.tsx +27 -0
  194. package/packs/frontend/skills/bundle-size-review/evals/files/7/apps/backoffice/src/pages/Analytics.tsx +10 -0
  195. package/packs/frontend/skills/bundle-size-review/evals/files/7/apps/backoffice/src/pages/Users.tsx +13 -0
  196. package/packs/frontend/skills/bundle-size-review/evals/files/7/apps/backoffice/stats.json +1421 -0
  197. package/packs/frontend/skills/bundle-size-review/evals/files/7/apps/backoffice/webpack.config.js +25 -0
  198. package/packs/frontend/skills/bundle-size-review/evals/files/7/apps/storefront/package.json +27 -0
  199. package/packs/frontend/skills/bundle-size-review/evals/files/7/apps/storefront/src/App.tsx +18 -0
  200. package/packs/frontend/skills/bundle-size-review/evals/files/7/apps/storefront/src/index.tsx +4 -0
  201. package/packs/frontend/skills/bundle-size-review/evals/files/7/apps/storefront/src/pages/Home.tsx +10 -0
  202. package/packs/frontend/skills/bundle-size-review/evals/files/7/apps/storefront/src/pages/Product.tsx +13 -0
  203. package/packs/frontend/skills/bundle-size-review/evals/files/7/apps/storefront/stats.json +1496 -0
  204. package/packs/frontend/skills/bundle-size-review/evals/files/7/apps/storefront/webpack.config.js +26 -0
  205. package/packs/frontend/skills/bundle-size-review/evals/files/7/package.json +9 -0
  206. package/packs/frontend/skills/bundle-size-review/evals/files/7/packages/ui/dist/Button.css +3 -0
  207. package/packs/frontend/skills/bundle-size-review/evals/files/7/packages/ui/dist/Button.js +14 -0
  208. package/packs/frontend/skills/bundle-size-review/evals/files/7/packages/ui/dist/Chart.js +21 -0
  209. package/packs/frontend/skills/bundle-size-review/evals/files/7/packages/ui/dist/index.js +22 -0
  210. package/packs/frontend/skills/bundle-size-review/evals/files/7/packages/ui/package.json +20 -0
  211. package/packs/frontend/skills/bundle-size-review/evals/files/7/packages/ui/src/Button.css +3 -0
  212. package/packs/frontend/skills/bundle-size-review/evals/files/7/packages/ui/src/Button.tsx +9 -0
  213. package/packs/frontend/skills/bundle-size-review/evals/files/7/packages/ui/src/Chart.tsx +14 -0
  214. package/packs/frontend/skills/bundle-size-review/evals/files/7/packages/ui/src/index.ts +6 -0
  215. package/packs/frontend/skills/bundle-size-review/evals/files/7/packages/ui/tsconfig.json +14 -0
  216. package/packs/frontend/skills/bundle-size-review/examples/inputs/stats-after.json +1358 -0
  217. package/packs/frontend/skills/bundle-size-review/examples/inputs/stats-before.json +1334 -0
  218. package/packs/frontend/skills/bundle-size-review/examples/larkspur-sparkline.md +52 -0
  219. package/packs/frontend/skills/bundle-size-review/manifest.json +52 -0
  220. package/packs/frontend/skills/bundle-size-review/reference.md +147 -0
  221. package/packs/frontend/skills/bundle-size-review/scripts/bundle_stats.py +1633 -0
  222. package/packs/frontend/skills/rendering-strategy/SKILL.md +149 -0
  223. package/packs/frontend/skills/rendering-strategy/evals/eval_queries.json +38 -0
  224. package/packs/frontend/skills/rendering-strategy/evals/evals.json +77 -0
  225. package/packs/frontend/skills/rendering-strategy/evals/files/1/fernhill-app.md +40 -0
  226. package/packs/frontend/skills/rendering-strategy/evals/files/3/routes.md +33 -0
  227. package/packs/frontend/skills/rendering-strategy/evals/files/4/build-notes.txt +5 -0
  228. package/packs/frontend/skills/rendering-strategy/evals/files/4/nuxt.config.ts +19 -0
  229. package/packs/frontend/skills/rendering-strategy/evals/files/4/pages/recipes/[slug].vue +37 -0
  230. package/packs/frontend/skills/rendering-strategy/evals/files/4/routes.md +15 -0
  231. package/packs/frontend/skills/rendering-strategy/evals/files/5/routes.txt +18 -0
  232. package/packs/frontend/skills/rendering-strategy/evals/files/5/src/hooks.server.ts +14 -0
  233. package/packs/frontend/skills/rendering-strategy/evals/files/5/src/routes/(marketing)/pricing/+page.server.ts +10 -0
  234. package/packs/frontend/skills/rendering-strategy/evals/files/5/src/routes/+layout.server.ts +10 -0
  235. package/packs/frontend/skills/rendering-strategy/evals/files/5/svelte.config.js +10 -0
  236. package/packs/frontend/skills/rendering-strategy/evals/files/5/traffic.md +18 -0
  237. package/packs/frontend/skills/rendering-strategy/examples/saas-site.md +42 -0
  238. package/packs/frontend/skills/rendering-strategy/manifest.json +50 -0
  239. package/packs/frontend/skills/rendering-strategy/reference.md +116 -0
  240. package/packs/frontend/skills/rendering-strategy/scripts/render_load.py +181 -0
  241. package/packs/frontend/skills/web-performance-review/SKILL.md +106 -0
  242. package/packs/frontend/skills/web-performance-review/evals/eval_queries.json +38 -0
  243. package/packs/frontend/skills/web-performance-review/evals/evals.json +76 -0
  244. package/packs/frontend/skills/web-performance-review/evals/files/1/psi.json +1065 -0
  245. package/packs/frontend/skills/web-performance-review/evals/files/2/after.json +359 -0
  246. package/packs/frontend/skills/web-performance-review/evals/files/2/before.json +375 -0
  247. package/packs/frontend/skills/web-performance-review/evals/files/4/psi-pdp-mobile-2026-10-19.json +171 -0
  248. package/packs/frontend/skills/web-performance-review/evals/files/4/rum-inp-attribution-oct14-19.csv +6 -0
  249. package/packs/frontend/skills/web-performance-review/evals/files/4/rum-inp-daily.csv +15 -0
  250. package/packs/frontend/skills/web-performance-review/evals/files/5/crux-home-phone.json +35 -0
  251. package/packs/frontend/skills/web-performance-review/evals/files/5/lighthouse-home-mobile.json +173 -0
  252. package/packs/frontend/skills/web-performance-review/examples/quayside-article-lhr.json +480 -0
  253. package/packs/frontend/skills/web-performance-review/examples/quayside-article.md +103 -0
  254. package/packs/frontend/skills/web-performance-review/manifest.json +49 -0
  255. package/packs/frontend/skills/web-performance-review/reference.md +105 -0
  256. package/packs/frontend/skills/web-performance-review/scripts/lh_summary.py +743 -0
  257. package/packs/product-manager/skills/lean-canvas/SKILL.md +162 -0
  258. package/packs/product-manager/skills/lean-canvas/evals/eval_queries.json +42 -0
  259. package/packs/product-manager/skills/lean-canvas/evals/evals.json +82 -0
  260. package/packs/product-manager/skills/lean-canvas/evals/files/1/pawpass-notes.md +54 -0
  261. package/packs/product-manager/skills/lean-canvas/evals/files/3/lunchloop-pitch.md +30 -0
  262. package/packs/product-manager/skills/lean-canvas/evals/files/4/clubkit-notes.md +44 -0
  263. package/packs/product-manager/skills/lean-canvas/evals/files/5/cost-model.csv +5 -0
  264. package/packs/product-manager/skills/lean-canvas/evals/files/5/proposal.md +40 -0
  265. package/packs/product-manager/skills/lean-canvas/evals/files/5/stakeholder-emails.txt +18 -0
  266. package/packs/product-manager/skills/lean-canvas/examples/bike-repair.md +72 -0
  267. package/packs/product-manager/skills/lean-canvas/manifest.json +34 -0
  268. package/packs/product-manager/skills/lean-canvas/reference.md +102 -0
  269. package/packs/product-manager/skills/lean-canvas/scripts/unit_economics.py +211 -0
  270. package/packs/solution-architect/skills/api-contract-draft/SKILL.md +2 -2
  271. package/packs/solution-architect/skills/api-contract-draft/manifest.json +28 -6
  272. package/packs/solution-architect/skills/event-contract-draft/SKILL.md +167 -0
  273. package/packs/solution-architect/skills/event-contract-draft/evals/eval_queries.json +38 -0
  274. package/packs/solution-architect/skills/event-contract-draft/evals/evals.json +76 -0
  275. package/packs/solution-architect/skills/event-contract-draft/evals/files/1/order-events-brief.md +45 -0
  276. package/packs/solution-architect/skills/event-contract-draft/evals/files/4/NOTES.md +23 -0
  277. package/packs/solution-architect/skills/event-contract-draft/evals/files/4/schemas/shipment-update.json +40 -0
  278. package/packs/solution-architect/skills/event-contract-draft/evals/files/5/invoice.example.json +25 -0
  279. package/packs/solution-architect/skills/event-contract-draft/examples/order-service.asyncapi.json +246 -0
  280. package/packs/solution-architect/skills/event-contract-draft/examples/order-service.md +92 -0
  281. package/packs/solution-architect/skills/event-contract-draft/manifest.json +25 -0
  282. package/packs/solution-architect/skills/event-contract-draft/reference.md +113 -0
  283. package/packs/solution-architect/skills/event-contract-draft/scripts/check_asyncapi.py +502 -0
  284. package/packs/solution-architect/skills/event-contract-draft/templates/asyncapi-skeleton.json +144 -0
  285. package/packs/sre-oncall/skills/slo-spec/SKILL.md +161 -0
  286. package/packs/sre-oncall/skills/slo-spec/evals/eval_queries.json +38 -0
  287. package/packs/sre-oncall/skills/slo-spec/evals/evals.json +75 -0
  288. package/packs/sre-oncall/skills/slo-spec/evals/files/1/order-service.md +51 -0
  289. package/packs/sre-oncall/skills/slo-spec/evals/files/3/search-slo-status.md +30 -0
  290. package/packs/sre-oncall/skills/slo-spec/evals/files/4/alerts/payments-api.rules.yaml +16 -0
  291. package/packs/sre-oncall/skills/slo-spec/evals/files/4/docs/dependencies.md +8 -0
  292. package/packs/sre-oncall/skills/slo-spec/evals/files/4/metrics/summary-90d.md +51 -0
  293. package/packs/sre-oncall/skills/slo-spec/evals/files/5/ops-notes.md +34 -0
  294. package/packs/sre-oncall/skills/slo-spec/examples/ticket-booking.md +88 -0
  295. package/packs/sre-oncall/skills/slo-spec/manifest.json +34 -0
  296. package/packs/sre-oncall/skills/slo-spec/reference.md +113 -0
  297. package/packs/sre-oncall/skills/slo-spec/scripts/slo_math.py +449 -0
  298. package/packs/sre-oncall/skills/slo-spec/templates/slo-spec.md +35 -0
@@ -0,0 +1,144 @@
1
+ {
2
+ "asyncapi": "3.0.0",
3
+ "info": {
4
+ "title": "Billing events",
5
+ "version": "1.0.0",
6
+ "description": "Events the billing service publishes about invoices.\n\nConsumers must: (1) deduplicate on eventId, because delivery is at-least-once; (2) ignore fields they do not recognise; (3) drop an event whose invoiceRevision is lower than the last one applied for that invoice; (4) never depend on order across different invoices.\n\nVersioning: within a version only optional fields are added. A breaking change (rename, removal, type or meaning change, a new required field) ships as a new event version on a new channel, published alongside the old one until the old one's x-sunset date."
7
+ },
8
+ "defaultContentType": "application/json",
9
+ "servers": {
10
+ "production": {
11
+ "host": "kafka.example.internal:9092",
12
+ "protocol": "kafka",
13
+ "description": "Placeholder host; replace with the real bootstrap servers."
14
+ }
15
+ },
16
+ "channels": {
17
+ "invoiceLifecycleV1": {
18
+ "address": "billing.invoice-lifecycle.v1",
19
+ "description": "Every state change of an invoice, in the order it happened for that invoice.",
20
+ "x-partition-key": "invoiceId",
21
+ "x-ordering": "Ordered per invoiceId: all events for one invoice arrive in the order billing published them. No order across invoices and no global order. Retries can redeliver an earlier event; consumers compare invoiceRevision.",
22
+ "x-delivery": "at-least-once; duplicates happen on producer retry and consumer rebalance",
23
+ "x-retention": "7 days",
24
+ "x-replay": "Consumers can reset their offset to any point inside the retention window.",
25
+ "x-dead-letter": "A consumer moves a message it cannot process after 5 attempts to <consumer>.billing.invoice-lifecycle.v1.dlq; the consuming team owns and watches its own DLQ.",
26
+ "messages": {
27
+ "InvoiceIssued": { "$ref": "#/components/messages/InvoiceIssued" },
28
+ "InvoicePaid": { "$ref": "#/components/messages/InvoicePaid" }
29
+ }
30
+ }
31
+ },
32
+ "operations": {
33
+ "publishInvoiceLifecycle": {
34
+ "action": "send",
35
+ "channel": { "$ref": "#/channels/invoiceLifecycleV1" },
36
+ "messages": [
37
+ { "$ref": "#/channels/invoiceLifecycleV1/messages/InvoiceIssued" },
38
+ { "$ref": "#/channels/invoiceLifecycleV1/messages/InvoicePaid" }
39
+ ]
40
+ }
41
+ },
42
+ "components": {
43
+ "messages": {
44
+ "InvoiceIssued": {
45
+ "name": "InvoiceIssued",
46
+ "title": "Invoice issued",
47
+ "summary": "An invoice was issued to a customer.",
48
+ "description": "Event-carried state: the full invoice as issued, so consumers need no call back to billing.",
49
+ "x-event-style": "event-carried-state",
50
+ "bindings": { "kafka": { "key": { "type": "string", "description": "invoiceId" } } },
51
+ "payload": {
52
+ "allOf": [
53
+ { "$ref": "#/components/schemas/Envelope" },
54
+ {
55
+ "type": "object",
56
+ "required": ["data"],
57
+ "properties": {
58
+ "eventType": { "type": "string", "const": "InvoiceIssued" },
59
+ "data": { "$ref": "#/components/schemas/InvoiceIssuedData" }
60
+ }
61
+ }
62
+ ]
63
+ },
64
+ "examples": [
65
+ {
66
+ "name": "issued",
67
+ "payload": {
68
+ "eventId": "5b0c6f4e-2a91-4f3e-9d7c-1e8a2b3c4d5e",
69
+ "eventType": "InvoiceIssued",
70
+ "eventVersion": 1,
71
+ "occurredAt": "2026-10-19T14:03:11Z",
72
+ "producer": "billing-service",
73
+ "correlationId": "c2f1a9d0-7b3e-4c55-8a10-2d9e6f7a8b90",
74
+ "causationId": "0d4e8f12-6a7b-4c3d-9e2f-1a0b9c8d7e6f",
75
+ "data": {
76
+ "invoiceId": "inv_4T7Q",
77
+ "invoiceRevision": 1,
78
+ "customerId": "cus_31F8",
79
+ "totalMinor": 4250,
80
+ "currency": "GBP"
81
+ }
82
+ }
83
+ }
84
+ ]
85
+ },
86
+ "InvoicePaid": {
87
+ "name": "InvoicePaid",
88
+ "title": "Invoice paid",
89
+ "summary": "An invoice was paid in full.",
90
+ "description": "Notification: ids and the amount paid only; fetch the invoice from the billing API for anything else.",
91
+ "x-event-style": "notification",
92
+ "bindings": { "kafka": { "key": { "type": "string", "description": "invoiceId" } } },
93
+ "payload": {
94
+ "allOf": [
95
+ { "$ref": "#/components/schemas/Envelope" },
96
+ {
97
+ "type": "object",
98
+ "required": ["data"],
99
+ "properties": {
100
+ "eventType": { "type": "string", "const": "InvoicePaid" },
101
+ "data": {
102
+ "type": "object",
103
+ "required": ["invoiceId", "invoiceRevision", "paidMinor", "currency"],
104
+ "properties": {
105
+ "invoiceId": { "type": "string" },
106
+ "invoiceRevision": { "type": "integer", "minimum": 1, "description": "Increases by 1 on every change to this invoice." },
107
+ "paidMinor": { "type": "integer", "minimum": 0, "description": "Amount paid in minor units (pence)." },
108
+ "currency": { "type": "string", "pattern": "^[A-Z]{3}$" }
109
+ }
110
+ }
111
+ }
112
+ }
113
+ ]
114
+ }
115
+ }
116
+ },
117
+ "schemas": {
118
+ "Envelope": {
119
+ "type": "object",
120
+ "required": ["eventId", "eventType", "eventVersion", "occurredAt", "producer", "correlationId"],
121
+ "properties": {
122
+ "eventId": { "type": "string", "format": "uuid", "description": "Unique per event; the same on every redelivery. Consumers deduplicate on it." },
123
+ "eventType": { "type": "string", "description": "Past-tense event name; one per message schema." },
124
+ "eventVersion": { "type": "integer", "minimum": 1, "description": "Major version of this event type's schema." },
125
+ "occurredAt": { "type": "string", "format": "date-time", "description": "When the fact happened, UTC, RFC 3339 with Z." },
126
+ "producer": { "type": "string", "description": "The service that owns the fact." },
127
+ "correlationId": { "type": "string", "description": "Shared by every event and request in one business flow." },
128
+ "causationId": { "type": "string", "description": "eventId (or request id) of what directly caused this event." }
129
+ }
130
+ },
131
+ "InvoiceIssuedData": {
132
+ "type": "object",
133
+ "required": ["invoiceId", "invoiceRevision", "customerId", "totalMinor", "currency"],
134
+ "properties": {
135
+ "invoiceId": { "type": "string" },
136
+ "invoiceRevision": { "type": "integer", "minimum": 1, "description": "Increases by 1 on every change to this invoice." },
137
+ "customerId": { "type": "string", "description": "Opaque id; no name or contact details on the event." },
138
+ "totalMinor": { "type": "integer", "minimum": 0, "description": "Invoice total in minor units (pence)." },
139
+ "currency": { "type": "string", "pattern": "^[A-Z]{3}$" }
140
+ }
141
+ }
142
+ }
143
+ }
144
+ }
@@ -0,0 +1,161 @@
1
+ ---
2
+ name: slo-spec
3
+ description: Define service level objectives for a service from its critical user journeys — each SLI a good-events / valid-events ratio with both sides defined and measured where the user feels it, a target set from user need and history (checked against dependency SLOs), the error budget computed in minutes and bad requests at current traffic, multi-window burn-rate alerts with the budget consumed when each fires, low-traffic handling, and an error-budget policy with owners. Use when the user asks to "define SLOs", "write our SLIs", "how much error budget do we have", "what burn-rate alerts should page", "is 99.99% realistic", or "write an error-budget policy".
4
+ ---
5
+
6
+ # slo-spec
7
+
8
+ Turns a service's user journeys into SLOs a team can defend: what counts as a good and a valid
9
+ event, the target and window, the budget in real units, the alerts that fire on it, and what
10
+ the team does when it's spent. This is the reliability contract. The wider set of
11
+ non-functional targets for a design is `nfr-spec`; what to instrument, log and trace and which
12
+ dashboards to build is `observability-plan`; the procedure on-call follows when an alert fires
13
+ is `runbook-generator`; DORA delivery metrics are `delivery-metrics-report`.
14
+
15
+ ## How to respond
16
+
17
+ **Four rules come before the steps and outrank them:**
18
+
19
+ - **Answer what was asked, first.** Open with the answer, the decision or the artifact, in plain words. Scores, matrices, frameworks and tags come after it, and anything that doesn't change the answer is cut.
20
+ - **Use only what you were given.** Don't add facts, names, numbers, incidents, history, steps or sections the input doesn't contain. What you need and don't have is left out of the artifact and listed once at the end under "To confirm".
21
+ - **Deliver from what you have.** When the request gives you something to work on, state your assumptions in a line and produce the result. When it gives you nothing to work on, ask for it in one or two plain sentences and say what you will do once you have it.
22
+ - **Write for someone who has never heard of this skill.** No skill names, no paths or scripts from this folder, no internal terms, and nothing about how the run was set up. A next step is one plain sentence at the end that describes the work.
23
+ - **For this skill:** Every budget, threshold and alert number is computed, never estimated, and shown in real units (minutes, bad requests). A target that can't be met — above a dependency's SLO, or measured where it can't see the user's wait — is said in the first lines, not in a footnote.
24
+
25
+ 1. **Start from 2–4 critical user journeys, not from the metrics that exist.** A journey is
26
+ something a user does and would complain about losing: ✅ *"place an order and see it
27
+ confirmed"* — ❌ *"order-worker latency"*, ❌ *"CPU < 80%"*. Rank by what an outage costs the
28
+ user. Everything past four is a dashboard, not an SLO. An internal or staff-only journey
29
+ still counts if people depend on it, but gets its own target and alerting (step 6).
30
+
31
+ 2. **Write each SLI as good events / valid events, with both sides defined.** Name the
32
+ request set, what makes one *valid*, and what makes one *good*:
33
+ - ✅ *"Valid: `POST /bookings` from customers, excluding 4xx validation errors and synthetic
34
+ test bookings. Good: reaches 'confirmed' or 'declined by bank' within 20 s of the request
35
+ arriving at the load balancer."*
36
+ - ❌ *"Checkout availability: 99.9%"* — of what? A card decline is a correct outcome, not a
37
+ failure; a 429 we caused is a failure, not an exclusion.
38
+ Latency SLIs are a threshold count (✅ *"% of searches answered in < 800 ms"*), never an
39
+ average or "p99 = X". An asynchronous journey is good only when the user sees the outcome,
40
+ so the SLI spans submit → result, not the accepting `202`.
41
+
42
+ 3. **Measure as close to the user as possible.** Prefer, in order: client / real-user beacon,
43
+ load balancer or edge, then the app. Reject a timer that can't see queueing:
44
+ ✅ *"from LB request timestamp to the confirmation event"* — ❌ *"worker process time"* when
45
+ orders wait in a queue first (the worker shows 200 ms while customers wait 90 s). If
46
+ the input shows a measured SLI disagreeing with what users reported, that is the finding:
47
+ lead with it. Name what the chosen point still misses (client-side failures for an LB SLI).
48
+
49
+ 4. **Set targets from what users need and from history — never 100%, never five nines by
50
+ default.** Start at or slightly below what the journey achieved over the last 1–3 months
51
+ when users weren't complaining; tighten only with a reason. History measured at the wrong
52
+ point (the accepting response, not the outcome) overstates the journey: say so and mark the
53
+ target provisional until a month of the new SLI exists. Check every hard dependency the journey calls
54
+ synchronously: a target above the product of their SLOs is **not achievable without
55
+ redundancy** (a second provider, a cache, a queue-and-retry fallback) — say so and name the
56
+ redundancy it would take. ✅ *"99.99% isn't reachable while every booking needs the card
57
+ processor at 99.9%; the processor alone would spend 10× the budget."* — ❌ quietly writing
58
+ 99.99%. A target equal to a dependency's SLO leaves nothing for the service's own failures; flag that too.
59
+ Default window: 30 days rolling `[assumption]` unless the team reviews on another cadence.
60
+
61
+ 5. **Compute the error budget in real units — run the script, don't estimate.**
62
+
63
+ ```bash
64
+ # Claude Code
65
+ python3 "${CLAUDE_SKILL_DIR}/scripts/slo_math.py" --target 99.9 --window 30 --rpd 1000000 --dep 99.95
66
+ # Other IDEs (from the skill folder)
67
+ python3 scripts/slo_math.py --target 99.9 --window 30 --rpd 1000000 --dep 99.95
68
+ # Price a past outage against the budget: --outage 40 (minutes, repeatable)
69
+ # Several SLOs at once: a JSON list (shape in the script header), or - for stdin
70
+ python3 scripts/slo_math.py --batch slos.json --json
71
+ ```
72
+
73
+ It prints the budget as minutes of full outage and as bad requests per window at the given
74
+ traffic, the alert table, the dependency ceiling, the share a given outage spends, and
75
+ flags. ✅ *"99.9% over 30 days = 43.2
76
+ minutes, or 30,000 failed requests at 1,000,000 a day."* — ❌ *"about 40 minutes a month."* Use
77
+ average traffic and say so; peak hours burn budget faster than the minutes suggest. If no
78
+ script can run, do the arithmetic in the reply and show it (formulas in
79
+ [`reference.md`](reference.md)).
80
+
81
+ 6. **Alert on burn rate, not on raw error rate.** Default to the multi-window, multi-burn-rate
82
+ set: 14.4× over 1 h with a 5 m short window → page (2% of a 30-day budget gone); 6× over 6 h
83
+ with 30 m → page (5%); 1× over 3 d with 6 h → ticket (10%). Both windows must breach. Every
84
+ row states the error rate that trips it, the budget consumed when it fires and the time to
85
+ exhaustion, from the script. ❌ *"Page if error rate > 1% for 5 minutes"* — it pages on a blip
86
+ that costs 0.1% of the budget and stays quiet through a slow burn that spends all of it.
87
+ If the window isn't 30 days, keep the burn rates and report the consumed share the script
88
+ gives; don't pretend 14.4× still means exactly 2%.
89
+
90
+ 7. **Low-traffic journeys: say the ratio is noisy, then change the method.** When the script
91
+ flags a window with fewer than ~10 expected bad events, an alert there fires on two
92
+ failures. Pick one and say why: a longer SLO window (90 days), longer alert windows, a
93
+ synthetic probe at a fixed rate that sets the event count, or ticket-only alerting in the
94
+ hours people use it. ✅ *"Back-office API: 800 requests/day gives 0.48 bad requests in the 1 h
95
+ window — a page would fire on one error. Ticket on 3 failures in 6 h during office hours
96
+ and add a probe every minute."* — ❌ copying the paging set from the busy journeys.
97
+
98
+ 8. **Write the error-budget policy with owners, agreed in advance.** It says what changes at
99
+ each budget level and who decides: ✅ *"Budget spent: release freeze except fixes and
100
+ security patches; the next sprint's top item is the largest budget consumer; owner — the
101
+ team's engineering lead; exception — the head of engineering signs a written exception."*
102
+ Include: thresholds (e.g. 50% spent in the first half of the window → reliability work
103
+ jumps the queue; 100% → freeze), what's exempt, what happens when a dependency spent the
104
+ budget (escalate to its owner; the freeze still covers risky changes), and when it's
105
+ reviewed. Owners are roles or names from the input; never invent a person.
106
+
107
+ 9. **Write it up with [`templates/slo-spec.md`](templates/slo-spec.md)** — the findings first
108
+ (unachievable targets, wrong measurement point, noisy journeys), then per journey: SLI
109
+ with good/valid definitions and measurement point, target and window, budget in minutes
110
+ and requests, alert table, then the policy. For a chat question ("how much budget is
111
+ 99.95%?") answer in a few lines with the numbers and one alert table; no document.
112
+
113
+ **Non-interactive runs** (subagent, CI, headless): a missing window becomes 30 days rolling,
114
+ a missing target becomes one set from the stated history, and missing traffic leaves the
115
+ budget in minutes only — each tagged `[assumption]` and listed first. If the input names no
116
+ service, journey or user-facing behaviour at all, emit `BLOCKED: need the service and what its
117
+ users do with it`, because SLIs invented without journeys measure nothing.
118
+
119
+ ## Useful references in this skill
120
+
121
+ - [`reference.md`](reference.md) — SLI menu by journey type, measurement points and what each misses, valid-event exclusions, budget and burn-rate formulas, dependency ceilings, low-traffic options, and a policy skeleton
122
+ - [`templates/slo-spec.md`](templates/slo-spec.md) — the write-up skeleton
123
+ - [`scripts/slo_math.py`](scripts/slo_math.py) — budget in minutes and requests, burn-rate alert table, noise and dependency flags (stdlib only, `--json`)
124
+ - [`examples/ticket-booking.md`](examples/ticket-booking.md) — worked example with real script output
125
+
126
+ ## Quality bar
127
+
128
+ - **2–4 journeys, each with an SLI written as good / valid events,** both sides defined, including what's excluded from valid.
129
+ - **The measurement point is named and can see what the user waits for** — including queue time on async paths.
130
+ - **No target is 100%, none is five nines without a reason in the input,** and none sits above the hard dependencies' combined SLO without naming the redundancy it needs.
131
+ - **Every budget is in minutes and bad requests at stated traffic,** matching the script output.
132
+ - **Alerts are burn-rate rows with long and short windows,** each with the trip rate, the budget consumed at fire and the time to exhaustion.
133
+ - **Low-traffic journeys are flagged with the count that makes them noisy** and get a different method, with the reason.
134
+ - **The policy names owners, thresholds, the freeze rule, the exception route and the dependency case.**
135
+ - **Nothing in it is invented:** traffic, history, dependency SLOs and people all come from the input or are tagged `[assumption]`.
136
+
137
+ ## When to use this skill
138
+
139
+ - ✅ "Define SLOs / SLIs for this service" from a description of what users do
140
+ - ✅ "How much error budget is 99.9% over 28 days at our traffic?"
141
+ - ✅ "What burn-rate alerts should page us?" or "is our error-rate alert right?"
142
+ - ✅ "Is 99.99% realistic for checkout?" — target pushback against history and dependencies
143
+ - ✅ "Write our error-budget policy"
144
+
145
+ ## When NOT to use this skill
146
+
147
+ - ❌ The full set of non-functional targets for a design — latency, durability, privacy, cost — that's `nfr-spec`
148
+ - ❌ What to instrument, metrics/logs/traces split, cardinality, dashboards — that's `observability-plan`
149
+ - ❌ The step-by-step procedure when an alert fires — that's `runbook-generator`
150
+ - ❌ Deployment frequency, change fail rate, lead time — that's `delivery-metrics-report`
151
+
152
+ ## Anti-patterns to avoid
153
+
154
+ - ❌ **Metrics-first SLIs.** Starting from what Prometheus already has and calling CPU, pod restarts or worker time an SLO.
155
+ - ❌ **The SLI behind the queue.** An app or worker timer that starts after the request waited, so the SLO stays green while users stare at a spinner.
156
+ - ❌ **Undefined "valid".** Counting card declines and bot traffic as failures, or dropping our own 429s and timeouts from the denominator.
157
+ - ❌ **Nines by ambition.** 99.99% because "it's revenue", on top of a provider at 99.9%.
158
+ - ❌ **"About 40 minutes".** A budget guessed, rounded, or given only as a percentage.
159
+ - ❌ **Raw error-rate paging.** "Error rate > 1% for 5 m" instead of burn rate over two windows.
160
+ - ❌ **One alert set for every journey.** Paging rules copied onto an 800-request/day API, where one failure is an incident.
161
+ - ❌ **A policy with no owner or exception route** — so the first time the budget runs out, the freeze is argued from scratch and dropped.
@@ -0,0 +1,38 @@
1
+ [
2
+ {
3
+ "query": "Define SLOs for our checkout service: which SLIs, what targets, and the error budget",
4
+ "should_trigger": true
5
+ },
6
+ {
7
+ "query": "How much error budget is 99.9% over 28 days if we do 3 million requests a day?",
8
+ "should_trigger": true
9
+ },
10
+ {
11
+ "query": "What burn-rate alerts should page us for a 99.95% availability SLO?",
12
+ "should_trigger": true
13
+ },
14
+ {
15
+ "query": "Product wants 99.99% for the payments API. Is that realistic when our card processor only promises 99.9%?",
16
+ "should_trigger": true
17
+ },
18
+ {
19
+ "query": "Write an error-budget policy: what happens when our SLO budget runs out",
20
+ "should_trigger": true
21
+ },
22
+ {
23
+ "query": "List the non-functional requirements for the new booking feature: latency, availability, data retention, accessibility",
24
+ "should_trigger": false
25
+ },
26
+ {
27
+ "query": "What should we instrument in the order service, and which dashboards and log fields do we need?",
28
+ "should_trigger": false
29
+ },
30
+ {
31
+ "query": "Write the on-call runbook for the payments service, including rollback and escalation",
32
+ "should_trigger": false
33
+ },
34
+ {
35
+ "query": "Compute our change failure rate and deployment frequency from this deploy log",
36
+ "should_trigger": false
37
+ }
38
+ ]
@@ -0,0 +1,75 @@
1
+ {
2
+ "skill_name": "slo-spec",
3
+ "evals": [
4
+ {
5
+ "id": 1,
6
+ "prompt": "We need SLOs for our order service before the reliability review next week. Everything I know about it is in order-service.md: the journeys, traffic, what we measure today and the last 90 days. Give us SLIs, targets, error budgets, the alerts that should page, and an error-budget policy.",
7
+ "assertions": [
8
+ "Opens with the decisions and blockers in the first lines: that 99.99% for checkout is not achievable while every order depends on PayRail at 99.9%, and that the proposed worker timer cannot be the checkout SLI",
9
+ "Rejects order_worker_process_seconds as the checkout SLI because it starts after the queue wait, cites the 14 September backlog (about 3 minutes of queue wait while p99 stayed 400 ms) as the evidence, and measures checkout end to end from the load balancer request to the confirmation the customer sees",
10
+ "Defines every SLI as good events over valid events with both sides spelled out, including what is excluded from valid (for example card declines or 4xx validation errors), and does not use CPU, pod restarts or queue depth as an SLI",
11
+ "Sets a checkout target at or below 99.9% with the reason (PayRail's 99.9% SLO and the history), and says what redundancy or fallback a higher target would need",
12
+ "Gives each error budget in minutes and in bad requests at the stated traffic, and the numbers match the formulas (for example 99.9% over 30 days is 43.2 minutes, or 144,000 bad requests at 4,800,000 search requests a day)",
13
+ "Alerts use burn rate over a long and a short window (such as 14.4x over 1 h with 5 m, 6x over 6 h with 30 m, 1x over 3 d ticketing), each with the budget consumed when it fires, and none is a raw error-rate threshold",
14
+ "Treats the admin API as low traffic: shows that about 600 requests a day gives well under one expected bad request per alert window, and does not copy the customer paging set onto it (uses a longer window, a synthetic probe, or office-hours tickets, with the reason)",
15
+ "The error-budget policy names owners from the input (Priya Raman, Tom Okafor, Dana Whitlock) or roles, a release freeze except fixes when the budget is spent, and an exception route; it does not invent people, traffic figures, history or dependency SLOs that are not in the file"
16
+ ]
17
+ },
18
+ {
19
+ "id": 2,
20
+ "prompt": "Quick one: our API's SLO is 99.95% over 28 days and we do about 2 million requests a day. How much error budget is that, and what burn-rate alerts should page us? Right now we page whenever the error rate goes above 0.05% for 5 minutes.",
21
+ "assertions": [
22
+ "Answers in the first lines with the budget: 20.16 minutes of full outage and 28,000 bad requests per 28 days",
23
+ "Says the current rule (above 0.05% for 5 minutes) is a 1x burn over 5 minutes that pages on about 3–4 failed requests and a negligible share of the budget, and recommends replacing it",
24
+ "Recommends multi-window burn-rate alerts with long and short windows, two paging and one ticketing (for example 14.4x over 1 h with 5 m, 6x over 6 h with 30 m, 1x over 3 d with 6 h)",
25
+ "States for each alert the error rate that trips it (for example 0.72% for 14.4x) and the budget consumed when it fires, adjusted for the 28-day window (about 2.14%, 5.36% and 10.71%) rather than the 30-day 2/5/10%",
26
+ "Numbers are computed, not rounded guesses: no 'about 20 minutes' or 'roughly 30k'",
27
+ "Stays a short chat answer: no full SLO document, no error-budget policy section, no sections the question did not ask for",
28
+ "Does not invent journeys, dependencies, team names or traffic patterns not in the prompt"
29
+ ]
30
+ },
31
+ {
32
+ "id": 3,
33
+ "prompt": "Can you write our error-budget policy for catalogue-search? The status page is in search-slo-status.md. Our VP also wants to bump the target to 99.99% so the team takes it seriously; I'd like your view on that too.",
34
+ "assertions": [
35
+ "Opens by advising against 99.99% in plain words: the service missed 99.5% in both windows (about 99.3% and 99.43% achieved), and 99.99% shrinks the budget from 201.6 minutes / 168,000 bad requests to about 4 minutes / 3,360 bad requests per 28 days",
36
+ "Says the problem is that nothing changed when the budget was spent (releases continued at 3–4 a week), which a policy fixes and a tighter target does not",
37
+ "The policy has thresholds tied to budget states, with what changes at each: reliability work prioritised before the budget is gone, and a release freeze except fixes and security patches when it is spent",
38
+ "Names owners from the input (Marta Lindqvist, Owen Achterberg, Helen Varga) or their roles, and says the policy is agreed in advance by engineering and product",
39
+ "Includes an exception route: who can approve a release during a freeze, in writing, and how it is recorded",
40
+ "Handles budget spent by a dependency: the shared Postgres cluster accounted for about 62% and 30% of the two windows, so the policy escalates to its owner (Kofi Mensah, Data Platform) with the numbers rather than only freezing search releases, while noting that search's own releases also overspent the budget",
41
+ "Does not invent incidents, people, dependency SLOs or numbers not in the file"
42
+ ]
43
+ },
44
+ {
45
+ "id": 4,
46
+ "held_out": "2026-10-20",
47
+ "prompt": "I'm the tech lead on payments-api at Larkspur Pay. Our only alert is the one in alerts/payments-api.rules.yaml and it paged on-call 31 times in the last 90 days, mostly for nothing. Leadership wants proper SLOs for the charges API before Q1. Can you define SLOs for POST /v1/charges and GET /v1/charges/{id}, work out the error budget, and give me burn-rate alerts to replace the current rule (what pages vs what's a ticket)? 90-day numbers are in metrics/summary-90d.md and our dependencies are in docs/dependencies.md. Also, our VP of product wants 99.99% on charges. Is that realistic?",
48
+ "assertions": [
49
+ "Answers the 99.99% question directly with a no (or 'not today'), and grounds it in the inputs: the Halberd gateway's 99.95% SLA alone allows 0.05% failures, five times a 0.01% budget, and there is no secondary acquirer until Corvid in Q2 2027; it may also note that Halberd and the Ledger DB in series (99.95% x 99.95%) come to about 99.90%.",
50
+ "Spots that the current rule counts 402 card_declined as errors, and that the 26 card-testing decline spikes caused most of the 31 pages. Its availability SLI treats a correct decline (and other 4xx such as 400/422/409/429) as not-bad, and leaves /healthz probe traffic out of valid events.",
51
+ "Measures the availability SLI from the ingress LB metric (lb_requests_total), not the app metric http_server_requests_total, and says why: the LB-only 502s during pod restarts, worth about 0.02% (LB 0.06% vs app 0.04% 5xx on POST /v1/charges).",
52
+ "Picks a POST /v1/charges availability target near 99.9% (anything from 99.9% to 99.95% is fine if justified against the ~99.91-99.96% monthly history and the dependency SLAs), and states the 30-day budget correctly. At 99.9% that is 43.2 minutes and about 93,312 bad requests at 36 req/s average (93.3M requests); at 99.95% it is 21.6 minutes and about 46,656.",
53
+ "Gives multi-window burn-rate alerts with a page/ticket split, and states for each one the burn rate, the windows, and the share of the 30-day budget used when it fires (e.g. 14.4x over 1h with a 5m short window is 2%, 6x over 6h/30m is 5%, and 1x over 3d as a ticket is 10%). It converts at least the fast-burn threshold into an error rate for the chosen target (14.4x at 99.9% is a 1.44% error ratio).",
54
+ "Defines a separate latency SLI for POST /v1/charges, with a threshold taken from the given distribution (e.g. under 2,000 ms, met by 99.12% today, or under 3,000 ms at 99.71%), not an arbitrary p99. It treats GET /v1/charges/{id} as its own SLO with its own budget (about 57,024 bad requests per 30 days at 99.9% and 22 req/s).",
55
+ "Flags at least one dependency risk that is not obvious: the idempotency Redis has no SLO and fails closed (503), or the fraud-scoring 99.9% SLO does not cap the charge SLO because it fails open.",
56
+ "Invents nothing that isn't in the inputs: no metric names other than lb_requests_total / lb_request_duration_seconds_bucket / http_server_requests_total / http_server_request_duration_seconds_bucket, no traffic figures, dependency SLAs or incident counts beyond those given. Any replacement rule YAML it writes uses those metric names and labels."
57
+ ]
58
+ },
59
+ {
60
+ "id": 5,
61
+ "held_out": "2026-10-20",
62
+ "prompt": "hey, Dana (my manager) wants 'four nines' for Tally (our internal expense approval app) so it can go in the quarterly ops review. im the only dev on it. can you write up the SLO + whatever alerting makes sense? my notes are in ops-notes.md. we dont have prometheus, app logs go to Loki and theres an uptime checker pinging /health every minute",
63
+ "assertions": [
64
+ "Tells the user plainly, near the top, that 99.99% is not a realistic or useful target for Tally, with numbers: at ~41,800 requests/month (1,900 x 22) it allows only about 4 failed requests a month (or 4.32 minutes per 30 days), and PeopleCore's 99.5% SLA alone allows up to 0.5% (about 216 minutes a month) with no cache or fallback.",
65
+ "Proposes a target based on the history in the notes: about 99.85% in July (61/40,300), 99.94% in August (23/37,900) and 99.67% in September (140/42,600), with the PeopleCore outage driving September. The target lands somewhere around 99.5% to 99.9%, not 99.99%.",
66
+ "Catches that PingDeck's 99.95% says almost nothing about users: /health is a static {\"ok\": true} that doesn't touch Postgres or PeopleCore, and checks are paused during the Sunday reboot. It bases the SLI on nginx access logs in Loki (good = non-5xx over valid requests), or on a deep synthetic check, not on the current /health.",
67
+ "Says outright whether the SLI counts requests or minutes, and why that matters here: the weekly 02:00-02:20 Sunday reboot (~87 minutes a month) affects almost no requests under a request-based SLI, but under a time-based one it would use up more than a 99.9% budget (43.2 min) by itself.",
68
+ "Handles low traffic explicitly, for example a minimum request count before an alert can fire, long windows (hours/days instead of 5 minutes), or alerting on absolute counts of failed submissions. It notes that with ~16 requests per 5 minutes in business hours, one error looks like a huge burn rate.",
69
+ "Fits the alerting to a single developer with no on-call: nothing pages 24/7. Alerts go to Jonah during business hours as notifications or tickets, with month-end (Marisol Teague's close) as the time that matters most.",
70
+ "Includes a short error-budget policy that names real owners from the notes (Jonah Okafor, Dana Whitfield, optionally Marisol Teague as stakeholder) and concrete actions such as pausing feature work, or adding an approver cache or fallback for PeopleCore. It stays short and doesn't invent a team, rota or process the notes don't support.",
71
+ "Invents nothing that isn't in the inputs: no Prometheus metrics (the user said they don't have Prometheus), no traffic, error or SLA figures beyond those in ops-notes.md, and no additional people or systems. Any Loki/LogQL queries reference only the nginx access-log fields the notes mention (status, path, request_time)."
72
+ ]
73
+ }
74
+ ]
75
+ }
@@ -0,0 +1,51 @@
1
+ # Tidewater Grocers — order service (reliability review input)
2
+
3
+ Owner: Order team (engineering lead: Priya Raman). Product owner: Tom Okafor.
4
+ Head of Engineering: Dana Whitlock.
5
+
6
+ ## What customers do with it
7
+
8
+ 1. **Search and browse products** — web and app call `GET /products` and `GET /search`.
9
+ About 4,800,000 requests a day. Customers abandon if results take more than about a second.
10
+ 2. **Place an order (checkout)** — the customer presses "Place order". The app calls
11
+ `POST /orders`, which validates the basket and puts the order on the `orders` queue
12
+ (RabbitMQ), then returns `202 Accepted`. An `order-worker` takes it off the queue, charges
13
+ the card through PayRail (our only payment provider, called synchronously from the worker)
14
+ and writes the confirmation. The app polls `GET /orders/{id}` until it shows "confirmed"
15
+ or "declined". About 240,000 `POST /orders` calls a day, including retries.
16
+ 3. **Book a delivery slot** — `GET /slots` and `POST /slots/{id}/hold`. About 360,000 requests
17
+ a day. If it fails the customer can usually try again a minute later.
18
+ 4. **Store-ops admin API** — staff in 40 stores adjust stock and mark items unavailable.
19
+ About 600 requests a day, nearly all between 08:00 and 16:00 on weekdays.
20
+
21
+ ## What we measure today
22
+
23
+ - Load balancer access logs for every public endpoint: status code and total request time.
24
+ - `order_worker_process_seconds` histogram, timed inside the worker from the moment it takes an
25
+ order off the queue until it writes the confirmation. The team proposes this as the checkout
26
+ latency SLI ("p99 is 400 ms, we're in great shape").
27
+ - RabbitMQ `orders` queue depth.
28
+ - CPU, memory and pod restarts for every deployment.
29
+ - A browser beacon (real-user monitoring) on the search results page only.
30
+
31
+ ## History (last 90 days, from load balancer logs)
32
+
33
+ | Journey | Success rate at the load balancer |
34
+ |---|---|
35
+ | Search and browse | 99.95% |
36
+ | `POST /orders` accepted | 99.97% |
37
+ | Delivery slots | 99.7% |
38
+ | Admin API | 99.6% (about 2 failed requests a week) |
39
+
40
+ On 14 September (a promotion day) the `orders` queue backed up for about 50 minutes. Queue wait
41
+ reached roughly 3 minutes and support logged 212 complaints about orders "stuck on processing".
42
+ `order_worker_process_seconds` p99 stayed at 400 ms throughout.
43
+
44
+ PayRail's published SLO is 99.9% monthly availability. PayRail had two incidents this quarter,
45
+ each around 40 minutes long.
46
+
47
+ ## What we've been asked for
48
+
49
+ Tom wants checkout at **99.99%** "because checkout is revenue". Dana wants an error-budget
50
+ policy written down before the next quarter's planning. The admin API should have "the same
51
+ paging as everything else".
@@ -0,0 +1,30 @@
1
+ # catalogue-search — SLO status (Bramblecote Travel)
2
+
3
+ Service: `catalogue-search`, the holiday search behind the website and app.
4
+ Traffic: about 1,200,000 search requests a day.
5
+
6
+ People:
7
+ - Search team engineering lead: Marta Lindqvist
8
+ - Product manager, search: Owen Achterberg
9
+ - VP Engineering: Helen Varga
10
+ - Data Platform team lead (owns the shared Postgres cluster): Kofi Mensah
11
+
12
+ ## Current SLO
13
+
14
+ - SLI: share of valid search requests answered with a non-5xx response in under 800 ms,
15
+ measured at the load balancer. Valid excludes health checks and the partner scraper.
16
+ - Target: 99.5% over a rolling 28 days.
17
+
18
+ ## The last two windows
19
+
20
+ | Window | Budget used | Biggest consumers |
21
+ |---|---|---|
22
+ | 4 Aug – 31 Aug | 140% | Shared Postgres cluster failover, 21 Aug: about 62% of the window's budget. Two search releases with a slow ranking query: about 55%. Remaining: scattered timeouts. |
23
+ | 1 Sep – 28 Sep | 115% | Search release 4.18 (index rebuild during peak): about 70%. Postgres replica lag, 12 Sep: about 30%. Remaining: scattered timeouts. |
24
+
25
+ Nothing changed after either window. Releases continued at 3–4 a week.
26
+
27
+ ## The ask
28
+
29
+ Helen wants a written error-budget policy before Q4 planning, and has suggested raising the
30
+ target to 99.99% "so the team takes it seriously".
@@ -0,0 +1,16 @@
1
+ groups:
2
+ - name: payments-api
3
+ rules:
4
+ # Added 2025-11 after the Halberd outage. 402 included because a burst of
5
+ # declines "can mean the gateway is broken".
6
+ - alert: PaymentsApiHighErrorRate
7
+ expr: |
8
+ sum(rate(http_server_requests_total{service="payments-api", status=~"5..|402"}[5m]))
9
+ /
10
+ sum(rate(http_server_requests_total{service="payments-api"}[5m]))
11
+ > 0.01
12
+ for: 5m
13
+ labels:
14
+ severity: page
15
+ annotations:
16
+ summary: "payments-api error rate above 1%"
@@ -0,0 +1,8 @@
1
+ # payments-api dependencies
2
+
3
+ | Dependency | Used by | Commitment | Behaviour when it fails |
4
+ |---|---|---|---|
5
+ | Halberd Payments Gateway (acquirer) | POST /v1/charges (synchronous, every request) | Contractual SLA 99.95% monthly uptime; we measured 99.97% over the last 90 days | Request fails with 502/504. No secondary acquirer today — Corvid Acquiring integration is on the Q2 2027 roadmap. |
6
+ | Ledger DB (managed Postgres, multi-AZ) | POST and GET | Provider SLA 99.95% monthly | Request fails with 503. |
7
+ | Fraud scoring (internal, team Sentinel) | POST /v1/charges | Internal SLO 99.9% availability, p99 180 ms | Called with a 250 ms timeout. On timeout or error we fall back to rules-based scoring and proceed (fail-open); logged as `fraud_fallback=true`. |
8
+ | Idempotency cache (self-hosted Redis, single primary + replica) | POST /v1/charges | No SLO | Fail-closed: request returns 503. |
@@ -0,0 +1,51 @@
1
+ # payments-api — 90-day summary (2026-07-22 to 2026-10-19)
2
+
3
+ Two metric sources exist:
4
+
5
+ - **App:** `http_server_requests_total{service="payments-api", route, status}` and
6
+ `http_server_request_duration_seconds_bucket{service="payments-api", route, le}` — emitted by the pods.
7
+ - **Ingress LB:** `lb_requests_total{backend="payments-api", route, code}` and
8
+ `lb_request_duration_seconds_bucket{backend="payments-api", route, le}` — emitted by the ingress load balancer.
9
+
10
+ The LB sees ~0.02% more 5xx on POST /v1/charges than the app does: 502s returned by the LB while pods
11
+ restart during deploys (about 4 deploys/week) never reach the app, so the app metric doesn't count them.
12
+
13
+ ## Traffic
14
+
15
+ | Route | Avg req/s | Daily peak req/s | Notes |
16
+ |---|---|---|---|
17
+ | POST /v1/charges | 36 | 95 (12:00–13:00 UTC); 210 during merchant batch runs | |
18
+ | GET /v1/charges/{id} | 22 | 60 | |
19
+ | GET /healthz | 30 | 30 | LB probes + kubelet; always 200 |
20
+
21
+ ## POST /v1/charges — status breakdown (LB-measured, 90d)
22
+
23
+ | Status | Share |
24
+ |---|---|
25
+ | 201 created | 98.52% |
26
+ | 402 card_declined | 0.71% baseline; 1.5–4% during card-testing attacks on a single merchant (26 such spikes, 10–40 min each) |
27
+ | 400 / 422 validation | 0.55% |
28
+ | 409 idempotency conflict | 0.08% |
29
+ | 429 rate limited | 0.08% |
30
+ | 5xx | 0.06% (502: 0.02%, 503: 0.01%, 504: 0.03%) |
31
+
32
+ App-measured 5xx on the same route: 0.04%.
33
+
34
+ Monthly non-5xx share, LB-measured: Aug 99.95%, Sep 99.91%, Oct (1–19) 99.96%.
35
+
36
+ ## POST /v1/charges — latency (LB-measured, 90d)
37
+
38
+ p50 310 ms · p90 640 ms · p95 820 ms · p99 1,850 ms · p99.9 4,200 ms
39
+
40
+ Share of requests completing under: 1,000 ms → 96.4% · 2,000 ms → 99.12% · 3,000 ms → 99.71%
41
+
42
+ ## GET /v1/charges/{id} (LB-measured, 90d)
43
+
44
+ 5xx 0.02% · p99 120 ms · share under 250 ms → 99.93%
45
+
46
+ ## Pages from PaymentsApiHighErrorRate (90d)
47
+
48
+ 31 pages total:
49
+ - 26 — card-testing decline spikes (402s), no service fault
50
+ - 3 — 502 bursts during deploys (resolved on their own within 6 min)
51
+ - 2 — Halberd gateway incidents (Sep 9: 504s on ~9% of POST /v1/charges for 22 min; Oct 2: 7 min)
@@ -0,0 +1,34 @@
1
+ # Tally — ops notes (Jonah)
2
+
3
+ Tally = internal expense approval app. Employees submit expenses, their approver approves, finance pays.
4
+
5
+ ## Setup
6
+ - Django + gunicorn behind nginx, single VM `tally-vm-01`. Postgres runs on the same VM.
7
+ - I deploy by hand, ~2x/month. gunicorn restart takes ~30s; nginx returns 502 for anything that comes in meanwhile.
8
+ - IT auto-patches and reboots the VM every Sunday 02:00–02:20. PingDeck checks are paused during that window.
9
+
10
+ ## Users / traffic
11
+ - ~300 employees submit, ~40 approvers, finance team of 6 (lead: Marisol Teague — month-end close is when she cares most).
12
+ - ~1,900 requests per working day on average, almost all 08:00–18:00 Mon–Fri, ~22 working days a month.
13
+ Last 3 working days of the month are roughly 3x busier. Nights and weekends: under 20 requests total.
14
+ - Monthly requests (nginx access logs in Loki): Jul 40,300 · Aug 37,900 · Sep 42,600
15
+ - ~350 403s a month — people clicking approval links for reports they aren't the approver on.
16
+
17
+ ## Errors (nginx 5xx, from Loki)
18
+ - Jul 61 · Aug 23 · Sep 140
19
+ - Sep 16: PeopleCore outage, ~2.5h. Every submit/approve failed with 500 because approver lookup failed.
20
+
21
+ ## Monitoring
22
+ - Loki has nginx access logs (status, path, request_time) and Django app logs.
23
+ - PingDeck hits `GET /health` every minute. Reports 99.95% uptime over the last 90 days.
24
+ `/health` just returns `{"ok": true}` from Django — doesn't touch Postgres or PeopleCore.
25
+ - Alerts: none right now apart from PingDeck emailing me.
26
+
27
+ ## Dependencies
28
+ - Corp SSO (IT Identity team) — published SLO 99.9%. Needed to log in.
29
+ - PeopleCore HR API (vendor) — SLA 99.5% monthly. Approver lookup on every submit and every approval. No cache.
30
+ - Corporate SMTP relay — notification emails, async with retries; failures don't fail the request.
31
+
32
+ ## People
33
+ - Me (Jonah Okafor) — only dev. Reachable business hours, no on-call rota.
34
+ - Dana Whitfield — my manager.