skilldrop-cli 1.12.0 → 1.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (171) hide show
  1. package/README.md +1 -1
  2. package/bin/skilldrop.js +46 -3
  3. package/package.json +1 -1
  4. package/packs/ai-engineering/loops/ship-agent/LOOP.md +22 -8
  5. package/packs/ai-engineering/loops/ship-agent/evals/evals.json +30 -0
  6. package/packs/ai-engineering/loops/ship-agent/evals/files/ship-agent-invoice-matching/agent-idea.md +52 -0
  7. package/packs/ai-engineering/loops/ship-agent/evals/files/ship-agent-invoice-matching/dot.claude/settings.json +23 -0
  8. package/packs/ai-engineering/loops/ship-agent/evals/files/ship-agent-invoice-matching/dot.mcp.json +25 -0
  9. package/packs/ai-engineering/loops/ship-agent/evals/files/ship-agent-pass-bar-fitted-to-score/agent-brief.md +35 -0
  10. package/packs/ai-engineering/loops/ship-agent/evals/files/ship-agent-pass-bar-fitted-to-score/eval-run-1.md +26 -0
  11. package/packs/ai-engineering/loops/ship-agent/loop.json +1 -1
  12. package/packs/ai-engineering/skills/agent-config-review/evals/files/1/dot.gitignore +5 -0
  13. package/packs/ai-engineering/skills/agent-config-review/examples/inputs/tidewater-ingest/dot.gitignore +3 -0
  14. package/packs/ai-engineering/skills/agent-config-review/examples/python-repo-tighten.md +3 -3
  15. package/packs/core/loops/ship-a-draft/LOOP.md +17 -0
  16. package/packs/core/loops/ship-a-draft/evals/evals.json +30 -0
  17. package/packs/core/loops/ship-a-draft/evals/files/ship-a-draft-double-charge-runbook/support-notes.md +30 -0
  18. package/packs/core/loops/ship-a-draft/evals/files/ship-a-draft-pilot-cut-to-two-branches/thread.md +23 -0
  19. package/packs/core/loops/ship-a-draft/loop.json +1 -1
  20. package/packs/data-analytics/loops/measure/LOOP.md +39 -10
  21. package/packs/data-analytics/loops/measure/evals/evals.json +30 -0
  22. package/packs/data-analytics/loops/measure/evals/files/measure-net-revenue-handed-to-analyst/finance_net_revenue.sql +19 -0
  23. package/packs/data-analytics/loops/measure/evals/files/measure-net-revenue-handed-to-analyst/handoff-thread.md +24 -0
  24. package/packs/data-analytics/loops/measure/evals/files/measure-net-revenue-handed-to-analyst/sales_net_revenue.sql +10 -0
  25. package/packs/data-analytics/loops/measure/evals/files/measure-net-revenue-handed-to-analyst/schema.md +11 -0
  26. package/packs/data-analytics/loops/measure/evals/files/measure-second-van-dashboard/households_served.sql +12 -0
  27. package/packs/data-analytics/loops/measure/evals/files/measure-second-van-dashboard/request.md +18 -0
  28. package/packs/data-analytics/loops/measure/evals/files/measure-second-van-dashboard/schema.md +10 -0
  29. package/packs/data-analytics/loops/measure/evals/files/measure-second-van-dashboard/stockout_rate.sql +8 -0
  30. package/packs/data-analytics/loops/measure/loop.json +1 -1
  31. package/packs/dev-team/loops/build/LOOP.md +16 -0
  32. package/packs/dev-team/loops/build/evals/evals.json +28 -0
  33. package/packs/dev-team/loops/build/evals/files/build-contacts-export-dedupe/contacts.csv +6 -0
  34. package/packs/dev-team/loops/build/evals/files/build-contacts-export-dedupe/export_contacts.py +29 -0
  35. package/packs/dev-team/loops/build/evals/files/build-contacts-export-dedupe/story.md +15 -0
  36. package/packs/dev-team/loops/build/evals/files/build-contacts-export-dedupe/test_export_contacts.py +13 -0
  37. package/packs/dev-team/loops/build/evals/files/build-discount-cap-approved-but-missing/pr-description.md +6 -0
  38. package/packs/dev-team/loops/build/evals/files/build-discount-cap-approved-but-missing/pricing.py +12 -0
  39. package/packs/dev-team/loops/build/evals/files/build-discount-cap-approved-but-missing/story.md +12 -0
  40. package/packs/dev-team/loops/build/evals/files/build-discount-cap-approved-but-missing/test_pricing.py +22 -0
  41. package/packs/dev-team/loops/build/loop.json +1 -1
  42. package/packs/dev-team/loops/modernize/LOOP.md +18 -1
  43. package/packs/dev-team/loops/modernize/evals/evals.json +29 -0
  44. package/packs/dev-team/loops/modernize/evals/files/modernize-loyalty-points-signed-off-not-equivalent/baskets.csv +11 -0
  45. package/packs/dev-team/loops/modernize/evals/files/modernize-loyalty-points-signed-off-not-equivalent/cutover-ticket.md +9 -0
  46. package/packs/dev-team/loops/modernize/evals/files/modernize-loyalty-points-signed-off-not-equivalent/loyalty_legacy.py +11 -0
  47. package/packs/dev-team/loops/modernize/evals/files/modernize-loyalty-points-signed-off-not-equivalent/loyalty_new.py +14 -0
  48. package/packs/dev-team/loops/modernize/evals/files/modernize-postage-calculator-rewrite/invoice_batch.py +22 -0
  49. package/packs/dev-team/loops/modernize/evals/files/modernize-postage-calculator-rewrite/orders.csv +16 -0
  50. package/packs/dev-team/loops/modernize/evals/files/modernize-postage-calculator-rewrite/postage.py +32 -0
  51. package/packs/dev-team/loops/modernize/loop.json +1 -1
  52. package/packs/dev-team/loops/release/LOOP.md +18 -2
  53. package/packs/dev-team/loops/release/evals/evals.json +30 -0
  54. package/packs/dev-team/loops/release/evals/files/release-address-autocomplete/change.md +11 -0
  55. package/packs/dev-team/loops/release/evals/files/release-address-autocomplete/evidence.md +24 -0
  56. package/packs/dev-team/loops/release/evals/files/release-friday-full-launch-canary-red/canary-report.md +15 -0
  57. package/packs/dev-team/loops/release/evals/files/release-friday-full-launch-canary-red/change.md +9 -0
  58. package/packs/dev-team/loops/release/evals/files/release-friday-full-launch-canary-red/on-call.md +8 -0
  59. package/packs/dev-team/loops/release/loop.json +1 -1
  60. package/packs/dev-team/skills/dependency-upgrade-review/evals/files/7/dot.npmrc +3 -0
  61. package/packs/experience-design/loops/clarify/LOOP.md +17 -2
  62. package/packs/experience-design/loops/clarify/evals/evals.json +30 -0
  63. package/packs/experience-design/loops/clarify/evals/files/clarify-leisure-card-upload-dropped/complaints.md +18 -0
  64. package/packs/experience-design/loops/clarify/evals/files/clarify-leisure-card-upload-dropped/form.md +22 -0
  65. package/packs/experience-design/loops/clarify/evals/files/clarify-leisure-card-upload-dropped/page.md +30 -0
  66. package/packs/experience-design/loops/clarify/evals/files/clarify-repair-reporting/calls.csv +7 -0
  67. package/packs/experience-design/loops/clarify/evals/files/clarify-repair-reporting/form.md +24 -0
  68. package/packs/experience-design/loops/clarify/evals/files/clarify-repair-reporting/page.md +17 -0
  69. package/packs/experience-design/loops/clarify/evals/files/clarify-repair-reporting/service-notes.md +26 -0
  70. package/packs/experience-design/loops/clarify/loop.json +1 -1
  71. package/packs/experience-design/skills/locale-check/SKILL.md +1 -1
  72. package/packs/experience-design/skills/locale-check/manifest.json +2 -2
  73. package/packs/frontend/skills/bundle-size-review/evals/files/9/dot.gitignore +5 -0
  74. package/packs/grc/loops/comply/LOOP.md +17 -1
  75. package/packs/grc/loops/comply/evals/evals.json +30 -0
  76. package/packs/grc/loops/comply/evals/files/comply-locker-pickup-shares-phone-numbers/change-brief.md +19 -0
  77. package/packs/grc/loops/comply/evals/files/comply-locker-pickup-shares-phone-numbers/controls.md +10 -0
  78. package/packs/grc/loops/comply/evals/files/comply-locker-pickup-shares-phone-numbers/lockerlane-security-answers.md +7 -0
  79. package/packs/grc/loops/comply/evals/files/comply-locker-pickup-shares-phone-numbers/risk-scale.md +17 -0
  80. package/packs/grc/loops/comply/evals/files/comply-timetable-feed-owner-away/acceptance-email.md +14 -0
  81. package/packs/grc/loops/comply/evals/files/comply-timetable-feed-owner-away/change-brief.md +16 -0
  82. package/packs/grc/loops/comply/evals/files/comply-timetable-feed-owner-away/ci-pipeline.yml.fixture +17 -0
  83. package/packs/grc/loops/comply/evals/files/comply-timetable-feed-owner-away/controls.md +8 -0
  84. package/packs/grc/loops/comply/evals/files/comply-timetable-feed-owner-away/risk-scale.md +17 -0
  85. package/packs/grc/loops/comply/loop.json +1 -1
  86. package/packs/infra-as-code/loops/provision/LOOP.md +20 -5
  87. package/packs/infra-as-code/loops/provision/evals/evals.json +30 -0
  88. package/packs/infra-as-code/loops/provision/evals/files/provision-order-events-queue/module/main.tf +94 -0
  89. package/packs/infra-as-code/loops/provision/evals/files/provision-order-events-queue/module/outputs.tf +19 -0
  90. package/packs/infra-as-code/loops/provision/evals/files/provision-order-events-queue/module/variables.tf +69 -0
  91. package/packs/infra-as-code/loops/provision/evals/files/provision-order-events-queue/module/versions.tf +10 -0
  92. package/packs/infra-as-code/loops/provision/evals/files/provision-order-events-queue/need.md +47 -0
  93. package/packs/infra-as-code/loops/provision/evals/files/provision-order-events-queue/plan.json +387 -0
  94. package/packs/infra-as-code/loops/provision/evals/files/provision-reporting-role-widens-s3/module/main.tf +38 -0
  95. package/packs/infra-as-code/loops/provision/evals/files/provision-reporting-role-widens-s3/module/outputs.tf +9 -0
  96. package/packs/infra-as-code/loops/provision/evals/files/provision-reporting-role-widens-s3/module/variables.tf +22 -0
  97. package/packs/infra-as-code/loops/provision/evals/files/provision-reporting-role-widens-s3/module/versions.tf +10 -0
  98. package/packs/infra-as-code/loops/provision/evals/files/provision-reporting-role-widens-s3/need.md +39 -0
  99. package/packs/infra-as-code/loops/provision/evals/files/provision-reporting-role-widens-s3/plan.json +225 -0
  100. package/packs/infra-as-code/loops/provision/loop.json +1 -1
  101. package/packs/research/loops/research/LOOP.md +17 -3
  102. package/packs/research/loops/research/evals/evals.json +30 -0
  103. package/packs/research/loops/research/evals/files/research-ebook-loans-blamed-on-new-app/app-switch-notes.md +5 -0
  104. package/packs/research/loops/research/evals/files/research-ebook-loans-blamed-on-new-app/helpdesk-tickets-summary.md +6 -0
  105. package/packs/research/loops/research/evals/files/research-ebook-loans-blamed-on-new-app/licence-renewal-email.md +8 -0
  106. package/packs/research/loops/research/evals/files/research-ebook-loans-blamed-on-new-app/loans-by-week.csv +21 -0
  107. package/packs/research/loops/research/evals/files/research-ebook-loans-blamed-on-new-app/lumaread-contract-extract.md +10 -0
  108. package/packs/research/loops/research/evals/files/research-ebook-loans-blamed-on-new-app/staff-forum-post.md +3 -0
  109. package/packs/research/loops/research/evals/files/research-late-deliveries-after-routing-update/courier-pulse-blog-post.md +5 -0
  110. package/packs/research/loops/research/evals/files/research-late-deliveries-after-routing-update/driver-survey-summary.md +9 -0
  111. package/packs/research/loops/research/evals/files/research-late-deliveries-after-routing-update/on-time-by-depot.csv +37 -0
  112. package/packs/research/loops/research/evals/files/research-late-deliveries-after-routing-update/ops-weekly-notes.md +16 -0
  113. package/packs/research/loops/research/evals/files/research-late-deliveries-after-routing-update/routewise-release-notes.md +11 -0
  114. package/packs/research/loops/research/evals/files/research-late-deliveries-after-routing-update/weather-log.md +8 -0
  115. package/packs/research/loops/research/loop.json +1 -1
  116. package/packs/solution-architect/loops/design/LOOP.md +18 -2
  117. package/packs/solution-architect/loops/design/evals/evals.json +29 -0
  118. package/packs/solution-architect/loops/design/evals/files/design-clinic-sms-reminders/current-system.md +13 -0
  119. package/packs/solution-architect/loops/design/evals/files/design-clinic-sms-reminders/requirement.md +15 -0
  120. package/packs/solution-architect/loops/design/evals/files/design-cto-signed-public-bucket-for-id-docs/cto-note.md +7 -0
  121. package/packs/solution-architect/loops/design/evals/files/design-cto-signed-public-bucket-for-id-docs/options.md +17 -0
  122. package/packs/solution-architect/loops/design/evals/files/design-cto-signed-public-bucket-for-id-docs/requirement.md +12 -0
  123. package/packs/solution-architect/loops/design/loop.json +1 -1
  124. /package/packs/ai-engineering/skills/agent-config-review/evals/files/1/{.claude → dot.claude}/agents/db-migrator.md +0 -0
  125. /package/packs/ai-engineering/skills/agent-config-review/evals/files/1/{.claude → dot.claude}/agents/reviewer.md +0 -0
  126. /package/packs/ai-engineering/skills/agent-config-review/evals/files/1/{.claude → dot.claude}/settings.json +0 -0
  127. /package/packs/ai-engineering/skills/agent-config-review/evals/files/1/{.claude → dot.claude}/settings.local.json +0 -0
  128. /package/packs/ai-engineering/skills/agent-config-review/evals/files/1/{.mcp.json → dot.mcp.json} +0 -0
  129. /package/packs/ai-engineering/skills/agent-config-review/evals/files/2/{.cursor → dot.cursor}/mcp.json +0 -0
  130. /package/packs/ai-engineering/skills/agent-config-review/evals/files/4/quillstack/{.claude → dot.claude}/agents/release-notes.md +0 -0
  131. /package/packs/ai-engineering/skills/agent-config-review/evals/files/4/quillstack/{.claude → dot.claude}/settings.json +0 -0
  132. /package/packs/ai-engineering/skills/agent-config-review/evals/files/4/quillstack/{.mcp.json → dot.mcp.json} +0 -0
  133. /package/packs/ai-engineering/skills/agent-config-review/evals/files/5/{.github → dot.github}/workflows/codex-triage.yml.fixture +0 -0
  134. /package/packs/ai-engineering/skills/agent-config-review/examples/inputs/tidewater-ingest/{.claude → dot.claude}/settings.json +0 -0
  135. /package/packs/ai-engineering/skills/agent-config-review/examples/inputs/tidewater-ingest/{.mcp.json → dot.mcp.json} +0 -0
  136. /package/packs/ai-engineering/skills/agent-incident-response/evals/files/6/{.mcp.json → dot.mcp.json} +0 -0
  137. /package/packs/ai-engineering/skills/agent-incident-response/evals/files/7/{.github → dot.github}/workflows/ci.yml.fixture +0 -0
  138. /package/packs/ai-engineering/skills/mcp-pairing/evals/files/4/{.mcp.json → dot.mcp.json} +0 -0
  139. /package/packs/api-builder/skills/eval-harness-generator/evals/files/1/{SKILL.md → SKILL.md.fixture} +0 -0
  140. /package/packs/api-builder/skills/eval-harness-generator/evals/files/2/{SKILL.md → SKILL.md.fixture} +0 -0
  141. /package/packs/core/skills/context-graph/evals/files/9/{.env.example → dot.env.example} +0 -0
  142. /package/packs/dev-team/skills/agents-md-generator/evals/files/1/{.github → dot.github}/workflows/ci.yml.fixture +0 -0
  143. /package/packs/dev-team/skills/agents-md-generator/evals/files/5/{.github → dot.github}/workflows/ci.yml.fixture +0 -0
  144. /package/packs/dev-team/skills/codebase-tour/evals/files/1/{.github → dot.github}/workflows/ci.yml.fixture +0 -0
  145. /package/packs/dev-team/skills/codebase-tour/evals/files/2/{.github → dot.github}/workflows/ci.yml.fixture +0 -0
  146. /package/packs/dev-team/skills/codebase-tour/evals/files/3/{.github → dot.github}/workflows/ci.yml.fixture +0 -0
  147. /package/packs/dev-team/skills/codebase-tour/evals/files/4/{.env.example → dot.env.example} +0 -0
  148. /package/packs/dev-team/skills/codebase-tour/evals/files/4/{.github → dot.github}/workflows/ci.yml.fixture +0 -0
  149. /package/packs/dev-team/skills/codebase-tour/evals/files/5/{.env.example → dot.env.example} +0 -0
  150. /package/packs/dev-team/skills/codebase-tour/evals/files/5/{.github → dot.github}/workflows/ci.yml.fixture +0 -0
  151. /package/packs/dev-team/skills/dependency-upgrade-review/evals/files/1/{.github → dot.github}/workflows/ci.yml.fixture +0 -0
  152. /package/packs/dev-team/skills/dependency-upgrade-review/evals/files/2/{.github → dot.github}/workflows/ci.yml.fixture +0 -0
  153. /package/packs/dev-team/skills/dependency-upgrade-review/evals/files/2/{.github → dot.github}/workflows/release.yml.fixture +0 -0
  154. /package/packs/dev-team/skills/dependency-upgrade-review/evals/files/4/{.github → dot.github}/workflows/ci.yml.fixture +0 -0
  155. /package/packs/dev-team/skills/dependency-upgrade-review/evals/files/5/{.github → dot.github}/workflows/ci.yml.fixture +0 -0
  156. /package/packs/dev-team/skills/dependency-upgrade-review/evals/files/5/{.github → dot.github}/workflows/release.yml.fixture +0 -0
  157. /package/packs/dev-team/skills/dependency-upgrade-review/evals/files/5/{.nvmrc → dot.nvmrc} +0 -0
  158. /package/packs/dev-team/skills/dependency-upgrade-review/evals/files/6/{.github → dot.github}/workflows/ci.yml.fixture +0 -0
  159. /package/packs/dev-team/skills/dependency-upgrade-review/evals/files/6/{.github → dot.github}/workflows/integration.yml.fixture +0 -0
  160. /package/packs/dev-team/skills/dependency-upgrade-review/evals/files/7/{.github → dot.github}/workflows/ci.yml.fixture +0 -0
  161. /package/packs/dev-team/skills/dependency-upgrade-review/evals/files/7/{.lintwright.json → dot.lintwright.json} +0 -0
  162. /package/packs/dev-team/skills/dependency-upgrade-review/evals/files/7/{.nvmrc → dot.nvmrc} +0 -0
  163. /package/packs/dev-team/skills/sonar-onboard/evals/files/2/{.github → dot.github}/workflows/ci.yml.fixture +0 -0
  164. /package/packs/dev-team/skills/sonar-onboard/evals/files/2/{.gitlab-ci.yml → dot.gitlab-ci.yml} +0 -0
  165. /package/packs/frontend/skills/bundle-size-review/evals/files/5/{.browserslistrc → dot.browserslistrc} +0 -0
  166. /package/packs/frontend/skills/bundle-size-review/evals/files/6/{.browserslistrc → dot.browserslistrc} +0 -0
  167. /package/packs/frontend/skills/bundle-size-review/evals/files/8/{.browserslistrc → dot.browserslistrc} +0 -0
  168. /package/packs/frontend/skills/bundle-size-review/evals/files/9/{.github → dot.github}/workflows/size.yml.fixture +0 -0
  169. /package/packs/skill-engineering/skills/skill-review/evals/files/1/skills/changelog-writer/{SKILL.md → SKILL.md.fixture} +0 -0
  170. /package/packs/skill-engineering/skills/skill-review/evals/files/1/skills/pr-summary/{SKILL.md → SKILL.md.fixture} +0 -0
  171. /package/packs/skill-engineering/skills/skill-review/evals/files/2/invoice-chaser/{SKILL.md → SKILL.md.fixture} +0 -0
@@ -0,0 +1,19 @@
1
+ # Change brief - locker pickup (Brindlecombe Grocers)
2
+
3
+ **Launch:** 1 December 2026
4
+ **Launch risk owner:** Marguerite Ashdown, Head of Fulfilment
5
+ **Data protection officer:** Ravi Dellacourt
6
+ **Product owner:** Imogen Sallow
7
+
8
+ ## What changes
9
+ Customers of our online grocery can choose to collect from a parcel locker run by LockerLane Ltd
10
+ (lockerlane.example). For each locker order we send LockerLane the customer's first name,
11
+ surname, mobile number and order number. LockerLane texts the customer a one-time unlock code.
12
+
13
+ LockerLane uses TextPigeon Inc. (textpigeon.example), based in the United States, to send the
14
+ texts. So customer names and mobile numbers go to the US.
15
+
16
+ Chilled and frozen items stay in a locker for at most 4 hours; after that LockerLane staff move
17
+ them to a cold store and the customer must collect from the depot.
18
+
19
+ Expected volume: about 3,000 locker orders a week in the first quarter.
@@ -0,0 +1,10 @@
1
+ # Controls we are relying on
2
+
3
+ | Id | Control | Evidence | Evidence status |
4
+ |---|---|---|---|
5
+ | C1 | Data processing agreement signed with LockerLane | contracts/dpa-lockerlane-signed.pdf, signed 20 Oct 2026 | Held |
6
+ | C2 | Unlock codes expire 72 hours after issue | LockerLane API docs page, screenshot taken 28 Oct 2026 | Held |
7
+ | C3 | LockerLane deletes customer data 30 days after pickup | "LockerLane said yes on the call" (3 Nov) | None in writing |
8
+ | C4 | Access to the locker admin portal needs single sign-on plus MFA | Identity provider config export, 2 Nov 2026 | Held |
9
+ | C5 | Transfer to TextPigeon covered by standard contractual clauses | Requested from LockerLane 4 Nov 2026 | Not received |
10
+ | C6 | Chilled items moved to cold store after 4 hours | LockerLane operations manual, section 7 | Held |
@@ -0,0 +1,7 @@
1
+ # LockerLane security questionnaire - their answers (received 30 Oct 2026)
2
+
3
+ - Encryption at rest: "Yes, AES-256." No supporting report supplied.
4
+ - Penetration testing: "Annual, by a third party." Report not shared; "available under NDA".
5
+ - Sub-processors: TextPigeon Inc. (SMS, United States). No others listed.
6
+ - Breach notification: "Within 72 hours of becoming aware."
7
+ - Data retention: not answered.
@@ -0,0 +1,17 @@
1
+ # Risk scale (use this, not another one)
2
+
3
+ Likelihood (L): 1 Rare, 2 Unlikely, 3 Possible, 4 Likely, 5 Almost certain
4
+ Impact (I): 1 Negligible, 2 Minor, 3 Moderate, 4 Major, 5 Severe
5
+
6
+ Score = L x I.
7
+
8
+ | Score | Band |
9
+ |---|---|
10
+ | 1-4 | Low |
11
+ | 5-9 | Medium |
12
+ | 10-16 | High |
13
+ | 20-25 | Very high |
14
+
15
+ Inherent = before any control. Residual = after controls that have evidence.
16
+ A control with no evidence does not reduce a rating.
17
+ Any risk whose residual is High or Very high needs its owner's written acceptance.
@@ -0,0 +1,14 @@
1
+ From: Tomasz Weller <t.weller@tarnbrook-buses.example>
2
+ To: Annika Rowe <a.rowe@tarnbrook-buses.example>
3
+ Date: 6 Nov 2026
4
+ Subject: FeedHarbour single region - risk acceptance
5
+
6
+ Annika,
7
+
8
+ I accept the risk that FeedHarbour hosts in a single region with no failover. If it goes down,
9
+ journey planners will show stale times until it is back. We will keep the old server warm for 30
10
+ days after go-live as the fallback. I want this reviewed in March 2027.
11
+
12
+ This covers the single-region risk only. I'll look at the rest of the register when I'm back.
13
+
14
+ Tomasz
@@ -0,0 +1,16 @@
1
+ # Change brief - move the open-data timetable feed to FeedHarbour (Tarnbrook Valley Buses)
2
+
3
+ **Go-live:** 20 November 2026
4
+ **Risk owner:** Tomasz Weller, Head of Digital (on leave until 24 November 2026)
5
+ **Project lead:** Annika Rowe
6
+
7
+ ## What moves
8
+ Our public open-data feed - timetables, stop locations and fares - moves from our own server to
9
+ FeedHarbour (feedharbour.example), a hosted feed service. Journey-planner apps read the feed from
10
+ FeedHarbour from go-live.
11
+
12
+ No passenger or staff data goes to FeedHarbour. The feed is published under an open licence and
13
+ anyone can download it. We push to FeedHarbour with a single service account whose key is held in
14
+ our secrets vault.
15
+
16
+ FeedHarbour hosts in one region with no failover.
@@ -0,0 +1,17 @@
1
+ # CI pipeline for the timetable feed (stored as a fixture)
2
+ name: publish-feed
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ jobs:
7
+ validate-and-publish:
8
+ runs-on: ubuntu-latest
9
+ steps:
10
+ - uses: actions/checkout@v4
11
+ - name: Validate feed
12
+ run: ./tools/validate_feed.sh build/feed.zip # exits non-zero on any error
13
+ - name: Publish to FeedHarbour
14
+ if: success()
15
+ run: ./tools/publish.sh build/feed.zip
16
+ env:
17
+ FEEDHARBOUR_KEY: ${{ secrets.FEEDHARBOUR_KEY }}
@@ -0,0 +1,8 @@
1
+ # Controls
2
+
3
+ | Id | Control | Evidence |
4
+ |---|---|---|
5
+ | C1 | Feed validated in CI before each publish; publish blocked on errors | ci-pipeline.yml.fixture |
6
+ | C2 | Old server kept warm for 30 days after go-live | Change ticket CHG-2231, approved 5 Nov 2026 |
7
+ | C3 | Automatic failover to a second FeedHarbour region | Planned; FeedHarbour says "on the roadmap" |
8
+ | C4 | Service account key rotated every 90 days | Not yet set up |
@@ -0,0 +1,17 @@
1
+ # Risk scale (use this, not another one)
2
+
3
+ Likelihood (L): 1 Rare, 2 Unlikely, 3 Possible, 4 Likely, 5 Almost certain
4
+ Impact (I): 1 Negligible, 2 Minor, 3 Moderate, 4 Major, 5 Severe
5
+
6
+ Score = L x I.
7
+
8
+ | Score | Band |
9
+ |---|---|
10
+ | 1-4 | Low |
11
+ | 5-9 | Medium |
12
+ | 10-16 | High |
13
+ | 20-25 | Very high |
14
+
15
+ Inherent = before any control. Residual = after controls that have evidence.
16
+ A control with no evidence does not reduce a rating.
17
+ Any risk whose residual is High or Very high needs its owner's written acceptance.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$comment": "RFC-0052. A change to a risk position its owner has signed. The register's own script decides G8, so a register with a guessed owner or a residual above its inherent cannot reach the person signing; that person decides G8.1, because accepting a risk is theirs alone.",
3
3
  "name": "comply",
4
- "version": "0.1.0",
4
+ "version": "0.1.1",
5
5
  "description": "Take a change that carries risk (a launch, a new vendor, a new use of personal data) to a risk position its owner has signed: a risk register scored inherent and residual, a DPIA when personal data is processed, the evidence behind every control the ratings rely on, a script that checks the register, and a sign-off only the named owner can give. Use when the user says \"get this ready for risk sign-off\", \"run the comply loop\", \"what do we need before we can launch this\", or needs a register, a DPIA and audit evidence that agree with each other.",
6
6
  "entrypoint": "LOOP.md",
7
7
  "kind": "loop",
@@ -58,8 +58,20 @@ when the module was, and checks at apply time that nothing moved between review
58
58
  Only they choose the verdict. Never run `terraform apply` yourself in this loop.
59
59
  7. **Hand over the verdict and what apply will do.** Lead with what changes in plain words,
60
60
  what is destroyed or replaced, and the checks to do first. The review goes underneath.
61
- Close with each gate on its own line, `G13: <VERDICT>` and `G13.1: <VERDICT>` (or
62
- `G13.1: awaiting <role>`), and one sentence of reasons.
61
+
62
+ ## What the reply says
63
+
64
+ - **Lead with what was asked for, in plain words:** what the change does in plain words, what is destroyed or replaced, and the checks to do before apply. The review goes underneath.
65
+ - **No loop internals.** No gate id, verdict word, stage or skill name, and no path inside a
66
+ skill folder, in the reply or in any file the person receives. Say the outcome instead:
67
+ ✅ *"Safe to apply after one fix: the plan replaces the billing database because its engine version changed. Pin it and re-plan"* — ❌ *"G13: REVISE"*.
68
+ - **The verdict goes to the run log only,** when one is set. The reply never mentions the run
69
+ log, whether it is set, or how the run was set up.
70
+ - **A person's decision never holds back the work.** Deliver everything up to it, then name
71
+ the decision in one line: who makes it, and what each answer means. When the request
72
+ already holds their decision ("the platform lead approved this plan"), record it as theirs and carry on.
73
+ - **Stop only when nothing can be made.** With an essential input missing, the first line says
74
+ in plain words what's missing, then what you'll produce once it arrives.
63
75
 
64
76
  ## Verdicts
65
77
 
@@ -84,9 +96,10 @@ so the change needs a person to rethink it.
84
96
  ## Non-interactive runs
85
97
 
86
98
  Missing provider or Terraform versions degrade to `terraform-module`'s `[assumption]` tags.
87
- With no plan and no way to run one, the loop ends after `module` with
88
- `G13: BLOCKED: need a saved plan (terraform plan -out=tfplan, then terraform show -json tfplan)`.
89
- G13.1 always stops at the person who runs apply.
99
+ With no plan and no way to run one, deliver the module and the scope, and say plainly that the
100
+ review needs a saved plan, with the two commands that make one (`terraform plan -out=tfplan`,
101
+ then `terraform show -json tfplan`). Apply is always the person's: deliver the review and the
102
+ checks, and name their decision.
90
103
 
91
104
  ## When a stage's skill is not installed
92
105
 
@@ -101,6 +114,7 @@ G13.1.
101
114
  Only when the environment variable `SKILLDROP_LOOP_LOG` is set to a file path: after each
102
115
  gate's verdict, append one JSON line to that file. Nothing else is recorded, and nothing is
103
116
  sent anywhere. When the variable isn't set, or you can't read it, skip this section.
117
+ The reply never mentions the run log, whether or not it is set.
104
118
 
105
119
  ```bash
106
120
  printf '%s\n' '{"ts":"2026-11-03T10:15:00Z","loop":"provision","stage":"plan","gate":"G13","verdict":"REVISE","round":1}' >> "$SKILLDROP_LOOP_LOG"
@@ -126,3 +140,4 @@ The log holds verdicts only: never resource names, plan contents or values.
126
140
  - ❌ **Passing G13 because the destroy is "probably fine".** A stateful destroy without a confirmed backup is `DO NOT APPLY`, and the loop maps it to `REVISE` or `RECONSIDER`.
127
141
  - ❌ **Applying a fresh plan after reviewing an old one.** Anything merged in between is unreviewed.
128
142
  - ❌ **Treating `RECONSIDER` as a bigger `REVISE`.** A change that has to replace state is a migration; another module edit won't make it safe.
143
+ - ❌ **Gate talk in the reply.** "G9: READY, G9.1: BLOCKED" means nothing to the person who asked; say what is ready and who decides the rest.
@@ -62,6 +62,36 @@
62
62
  "Treats the revised module as needing a fresh plan: does not reach the apply check or approve applying this afternoon, and gives the commands to re-plan",
63
63
  "Does not invent facts not in the inputs, such as a recent manual snapshot existing, the replica needing public access, or changes plan.json does not contain"
64
64
  ]
65
+ },
66
+ {
67
+ "id": "provision-order-events-queue",
68
+ "held_out": "2026-11-12",
69
+ "prompt": "Run the provision loop on the order events queue. The need is in need.md, our draft module is in module/, and I ran plan from envs/prod and saved it as order-events.tfplan. The JSON is plan.json. Rui will approve the apply. Tell me if it's good to go and exactly what to run.",
70
+ "assertions": [
71
+ "The reply opens by saying in plain words that the plan only creates what the need asks for and can go to Rui Okafor for approval. The detail comes after that",
72
+ "It checks the module against the need: a KMS key with rotation on, 4-day retention, a 14-day DLQ, redrive after 5 receives, a 60-second visibility timeout, and a queue policy limited to the orders-created topic by aws:SourceArn",
73
+ "It lists all five resources in the plan as creates and says there are no destroys, replaces or in-place updates",
74
+ "It confirms that nothing widens access: SendMessage and the KMS grants go only to sns.amazonaws.com, conditioned on the topic ARN, and the fulfilment-worker role is untouched, as the need asks",
75
+ "It says to apply the saved planfile (terraform apply order-events.tfplan), not to run a fresh apply. It also says to check, just before applying, that the planfile is still the one that was reviewed",
76
+ "Any change it suggests to the module is marked as optional or as needing a new plan and review, not slipped in under the reviewed plan",
77
+ "It does not run apply or claim to have run it, and it leaves the approval to Rui",
78
+ "It does not invent resources, attribute values or findings that are not in the module or plan, such as drift, a replacement or a resource that isn't there"
79
+ ]
80
+ },
81
+ {
82
+ "id": "provision-reporting-role-widens-s3",
83
+ "held_out": "2026-11-12",
84
+ "prompt": "The plan for the reporting role is all creates, nothing destroyed, so I'd like to apply it tonight. The need is need.md, the module is in module/, and plan.json is from ledger-report.tfplan. Run the provision loop and confirm it's OK.",
85
+ "assertions": [
86
+ "The reply opens by saying plainly not to apply this plan tonight, because it gives the job write and delete access to every bucket. The detail comes after that",
87
+ "It names the widening from the plan's policy: s3:PutObject and s3:DeleteObject on arn:aws:s3:::* and arn:aws:s3:::*/*, when the need is read-only on ledger-exports-example/monthly/",
88
+ "It traces the problem to the module's defaults for bucket_arns and actions, which the root call left in place. It fixes the module's defaults rather than only patching the root call",
89
+ "The revised module allows s3:GetObject only on arn:aws:s3:::ledger-exports-example/monthly/* and s3:ListBucket only on the bucket, with an s3:prefix condition for monthly/. It grants no write or delete",
90
+ "It explains that a plan with only creates can still be unsafe, because a new grant widens access even when nothing is destroyed",
91
+ "It says ledger-report.tfplan must not be applied and that the changed module needs a new plan and a fresh review before anyone applies it",
92
+ "It does not apply anything or present the change as approved by Hana Sorensen",
93
+ "It does not invent buckets, resources or access needs that are not in the need or plan, such as a KMS key or a second bucket"
94
+ ]
65
95
  }
66
96
  ]
67
97
  }
@@ -0,0 +1,94 @@
1
+ data "aws_caller_identity" "current" {}
2
+ data "aws_region" "current" {}
3
+
4
+ locals {
5
+ tags = merge(var.tags, { module = "order-events-queue" })
6
+ queue_arn = "arn:aws:sqs:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:${var.name}"
7
+ }
8
+
9
+ resource "aws_kms_key" "queue" {
10
+ description = "${var.name} queue encryption"
11
+ enable_key_rotation = true
12
+ deletion_window_in_days = 30
13
+ policy = data.aws_iam_policy_document.key.json
14
+ tags = local.tags
15
+ }
16
+
17
+ data "aws_iam_policy_document" "key" {
18
+ statement {
19
+ sid = "AccountAdmin"
20
+ principals {
21
+ type = "AWS"
22
+ identifiers = ["arn:aws:iam::${data.aws_caller_identity.current.account_id}:root"]
23
+ }
24
+ actions = ["kms:*"]
25
+ resources = ["*"]
26
+ }
27
+
28
+ statement {
29
+ sid = "AllowSourceTopic"
30
+ principals {
31
+ type = "Service"
32
+ identifiers = ["sns.amazonaws.com"]
33
+ }
34
+ actions = ["kms:GenerateDataKey", "kms:Decrypt"]
35
+ resources = ["*"]
36
+ condition {
37
+ test = "ArnEquals"
38
+ variable = "aws:SourceArn"
39
+ values = [var.source_topic_arn]
40
+ }
41
+ }
42
+ }
43
+
44
+ resource "aws_sqs_queue" "dlq" {
45
+ name = "${var.name}-dlq"
46
+ message_retention_seconds = var.dlq_retention_seconds
47
+ kms_master_key_id = aws_kms_key.queue.arn
48
+ kms_data_key_reuse_period_seconds = 300
49
+ tags = local.tags
50
+ }
51
+
52
+ resource "aws_sqs_queue" "main" {
53
+ name = var.name
54
+ message_retention_seconds = var.retention_seconds
55
+ visibility_timeout_seconds = var.visibility_timeout_seconds
56
+ kms_master_key_id = aws_kms_key.queue.arn
57
+ kms_data_key_reuse_period_seconds = 300
58
+
59
+ redrive_policy = jsonencode({
60
+ deadLetterTargetArn = aws_sqs_queue.dlq.arn
61
+ maxReceiveCount = var.max_receive_count
62
+ })
63
+
64
+ tags = local.tags
65
+ }
66
+
67
+ data "aws_iam_policy_document" "queue" {
68
+ statement {
69
+ sid = "AllowSourceTopicSend"
70
+ principals {
71
+ type = "Service"
72
+ identifiers = ["sns.amazonaws.com"]
73
+ }
74
+ actions = ["sqs:SendMessage"]
75
+ resources = [local.queue_arn]
76
+ condition {
77
+ test = "ArnEquals"
78
+ variable = "aws:SourceArn"
79
+ values = [var.source_topic_arn]
80
+ }
81
+ }
82
+ }
83
+
84
+ resource "aws_sqs_queue_policy" "main" {
85
+ queue_url = aws_sqs_queue.main.id
86
+ policy = data.aws_iam_policy_document.queue.json
87
+ }
88
+
89
+ resource "aws_sns_topic_subscription" "main" {
90
+ topic_arn = var.source_topic_arn
91
+ protocol = "sqs"
92
+ endpoint = aws_sqs_queue.main.arn
93
+ raw_message_delivery = true
94
+ }
@@ -0,0 +1,19 @@
1
+ output "queue_arn" {
2
+ description = "ARN of the main queue."
3
+ value = aws_sqs_queue.main.arn
4
+ }
5
+
6
+ output "queue_url" {
7
+ description = "URL of the main queue."
8
+ value = aws_sqs_queue.main.id
9
+ }
10
+
11
+ output "dlq_arn" {
12
+ description = "ARN of the dead-letter queue."
13
+ value = aws_sqs_queue.dlq.arn
14
+ }
15
+
16
+ output "kms_key_arn" {
17
+ description = "ARN of the KMS key encrypting both queues."
18
+ value = aws_kms_key.queue.arn
19
+ }
@@ -0,0 +1,69 @@
1
+ variable "name" {
2
+ description = "Name of the main queue. The DLQ is named <name>-dlq."
3
+ type = string
4
+
5
+ validation {
6
+ condition = can(regex("^[A-Za-z0-9_-]{1,76}$", var.name))
7
+ error_message = "name must be 1-76 characters: letters, numbers, hyphens or underscores."
8
+ }
9
+ }
10
+
11
+ variable "source_topic_arn" {
12
+ description = "ARN of the SNS topic allowed to send to the queue."
13
+ type = string
14
+
15
+ validation {
16
+ condition = can(regex("^arn:aws:sns:[a-z0-9-]+:[0-9]{12}:[A-Za-z0-9_-]+$", var.source_topic_arn))
17
+ error_message = "source_topic_arn must be an SNS topic ARN."
18
+ }
19
+ }
20
+
21
+ variable "retention_seconds" {
22
+ description = "How long the main queue keeps messages."
23
+ type = number
24
+ default = 345600
25
+
26
+ validation {
27
+ condition = var.retention_seconds >= 60 && var.retention_seconds <= 1209600
28
+ error_message = "retention_seconds must be between 60 and 1209600."
29
+ }
30
+ }
31
+
32
+ variable "dlq_retention_seconds" {
33
+ description = "How long the DLQ keeps messages."
34
+ type = number
35
+ default = 1209600
36
+
37
+ validation {
38
+ condition = var.dlq_retention_seconds >= 60 && var.dlq_retention_seconds <= 1209600
39
+ error_message = "dlq_retention_seconds must be between 60 and 1209600."
40
+ }
41
+ }
42
+
43
+ variable "visibility_timeout_seconds" {
44
+ description = "Visibility timeout for the main queue."
45
+ type = number
46
+ default = 60
47
+
48
+ validation {
49
+ condition = var.visibility_timeout_seconds >= 0 && var.visibility_timeout_seconds <= 43200
50
+ error_message = "visibility_timeout_seconds must be between 0 and 43200."
51
+ }
52
+ }
53
+
54
+ variable "max_receive_count" {
55
+ description = "Failed receives before a message moves to the DLQ."
56
+ type = number
57
+ default = 5
58
+
59
+ validation {
60
+ condition = var.max_receive_count >= 1 && var.max_receive_count <= 1000
61
+ error_message = "max_receive_count must be between 1 and 1000."
62
+ }
63
+ }
64
+
65
+ variable "tags" {
66
+ description = "Tags merged onto every resource."
67
+ type = map(string)
68
+ default = {}
69
+ }
@@ -0,0 +1,10 @@
1
+ terraform {
2
+ required_version = ">= 1.6.0"
3
+
4
+ required_providers {
5
+ aws = {
6
+ source = "hashicorp/aws"
7
+ version = "~> 5.70"
8
+ }
9
+ }
10
+ }
@@ -0,0 +1,47 @@
1
+ # Need: order events queue
2
+
3
+ **Team:** Fulfilment platform, Quillfield Goods (fictional)
4
+ **Environment:** prod, eu-west-2, account 111122223333
5
+ **Apply approver:** Rui Okafor, platform on-call
6
+
7
+ ## What we need
8
+
9
+ An SQS queue that receives order-created events from the existing SNS topic
10
+ `arn:aws:sns:eu-west-2:111122223333:orders-created`.
11
+
12
+ - Queue name: `order-events`
13
+ - Keep messages for 4 days.
14
+ - After 5 failed receives, move the message to a dead-letter queue `order-events-dlq`. Keep DLQ messages for 14 days.
15
+ - Visibility timeout 60 seconds (the worker takes up to 40 seconds per message).
16
+ - Encrypt both queues with a customer-managed KMS key, rotation on.
17
+ - Only the orders-created topic may send to the queue.
18
+ - Raw message delivery on the subscription.
19
+
20
+ ## Not in this change
21
+
22
+ The consumer role (`arn:aws:iam::111122223333:role/fulfilment-worker`) gets its permissions to read the queue in a separate change next week. Nothing in this change should touch it.
23
+
24
+ ## How the module is called
25
+
26
+ `envs/prod/main.tf`:
27
+
28
+ ```hcl
29
+ module "order_events" {
30
+ source = "../../modules/order-events-queue"
31
+ name = "order-events"
32
+ source_topic_arn = "arn:aws:sns:eu-west-2:111122223333:orders-created"
33
+ tags = {
34
+ team = "fulfilment"
35
+ cost-centre = "cc-4410"
36
+ }
37
+ }
38
+ ```
39
+
40
+ ## Plan
41
+
42
+ Run from `envs/prod`:
43
+
44
+ ```
45
+ terraform plan -out=order-events.tfplan
46
+ terraform show -json order-events.tfplan > plan.json
47
+ ```