skilldrop-cli 0.16.45 → 0.16.46

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 (229) hide show
  1. package/catalogue.json +1 -0
  2. package/model-routing.json +1 -0
  3. package/package.json +1 -1
  4. package/packs/solution-architect/skills/context-graph/SKILL.md +119 -0
  5. package/packs/solution-architect/skills/context-graph/evals/eval_queries.json +11 -0
  6. package/packs/solution-architect/skills/context-graph/evals/evals.json +225 -0
  7. package/packs/solution-architect/skills/context-graph/evals/files/1/docs/adr/0001-record-architecture-decisions.md +19 -0
  8. package/packs/solution-architect/skills/context-graph/evals/files/1/docs/adr/0002-postgres-as-primary-database.md +16 -0
  9. package/packs/solution-architect/skills/context-graph/evals/files/1/docs/adr/0003-use-redis-for-session-storage.md +24 -0
  10. package/packs/solution-architect/skills/context-graph/evals/files/1/docs/adr/0004-structured-json-logging.md +15 -0
  11. package/packs/solution-architect/skills/context-graph/evals/files/1/docs/adr/0005-session-lifetime-eight-hours.md +22 -0
  12. package/packs/solution-architect/skills/context-graph/evals/files/1/docs/adr/0006-move-sessions-to-postgres.md +16 -0
  13. package/packs/solution-architect/skills/context-graph/evals/files/1/docs/adr/0007-stateless-signed-session-cookies.md +35 -0
  14. package/packs/solution-architect/skills/context-graph/evals/files/1/services/api/__init__.py +0 -0
  15. package/packs/solution-architect/skills/context-graph/evals/files/1/services/api/db.py +0 -0
  16. package/packs/solution-architect/skills/context-graph/evals/files/1/services/api/logging_setup.py +0 -0
  17. package/packs/solution-architect/skills/context-graph/evals/files/1/services/auth/__init__.py +0 -0
  18. package/packs/solution-architect/skills/context-graph/evals/files/1/services/auth/cookie_sessions.py +7 -0
  19. package/packs/solution-architect/skills/context-graph/evals/files/1/services/auth/redis_sessions.py +7 -0
  20. package/packs/solution-architect/skills/context-graph/evals/files/10/README.md +8 -0
  21. package/packs/solution-architect/skills/context-graph/evals/files/10/docs/adr/0001-record-architecture-decisions.md +10 -0
  22. package/packs/solution-architect/skills/context-graph/evals/files/10/docs/adr/0004-service-auth-hmac-shared-keys.md +21 -0
  23. package/packs/solution-architect/skills/context-graph/evals/files/10/docs/adr/0007-secrets-from-vault-agent.md +19 -0
  24. package/packs/solution-architect/skills/context-graph/evals/files/10/docs/adr/0011-service-auth-mtls.md +23 -0
  25. package/packs/solution-architect/skills/context-graph/evals/files/10/services/billing-api/app.py +16 -0
  26. package/packs/solution-architect/skills/context-graph/evals/files/10/services/billing-api/auth/__init__.py +4 -0
  27. package/packs/solution-architect/skills/context-graph/evals/files/10/services/billing-api/auth/hmac_verify.py +31 -0
  28. package/packs/solution-architect/skills/context-graph/evals/files/10/services/billing-api/config/settings.py +14 -0
  29. package/packs/solution-architect/skills/context-graph/evals/files/10/services/ledger-worker/auth/__init__.py +4 -0
  30. package/packs/solution-architect/skills/context-graph/evals/files/10/services/ledger-worker/auth/mtls.py +18 -0
  31. package/packs/solution-architect/skills/context-graph/evals/files/11/README.md +4 -0
  32. package/packs/solution-architect/skills/context-graph/evals/files/11/app/models/base.py +3 -0
  33. package/packs/solution-architect/skills/context-graph/evals/files/11/app/models/line_item.py +14 -0
  34. package/packs/solution-architect/skills/context-graph/evals/files/11/app/models/order.py +24 -0
  35. package/packs/solution-architect/skills/context-graph/evals/files/11/eng/decisions/DR-003-money-as-minor-units.md +10 -0
  36. package/packs/solution-architect/skills/context-graph/evals/files/11/eng/decisions/DR-005-orders-typed-columns-only.md +10 -0
  37. package/packs/solution-architect/skills/context-graph/evals/files/11/eng/decisions/DR-009-line-items-immutable.md +11 -0
  38. package/packs/solution-architect/skills/context-graph/evals/files/11/eng/decisions/DR-014-optional-order-attributes-json.md +16 -0
  39. package/packs/solution-architect/skills/context-graph/evals/files/11/eng/decisions/DR-016-order-attributes-table.md +11 -0
  40. package/packs/solution-architect/skills/context-graph/evals/files/11/migrations/0007_create_orders.sql +11 -0
  41. package/packs/solution-architect/skills/context-graph/evals/files/11/migrations/0012_add_order_attributes.sql +2 -0
  42. package/packs/solution-architect/skills/context-graph/evals/files/12/README.md +8 -0
  43. package/packs/solution-architect/skills/context-graph/evals/files/12/config/delivery.yaml +4 -0
  44. package/packs/solution-architect/skills/context-graph/evals/files/12/config/export.yaml +2 -0
  45. package/packs/solution-architect/skills/context-graph/evals/files/12/config/partners.yaml +10 -0
  46. package/packs/solution-architect/skills/context-graph/evals/files/12/docs/adr/0001-record-architecture-decisions.md +10 -0
  47. package/packs/solution-architect/skills/context-graph/evals/files/12/docs/adr/0002-postgres-delivery-log.md +15 -0
  48. package/packs/solution-architect/skills/context-graph/evals/files/12/docs/adr/0003-redis-idempotency-keys.md +15 -0
  49. package/packs/solution-architect/skills/context-graph/evals/files/12/docs/adr/0004-retry-policy.md +16 -0
  50. package/packs/solution-architect/skills/context-graph/evals/files/12/docs/adr/0005-hmac-signed-payloads.md +10 -0
  51. package/packs/solution-architect/skills/context-graph/evals/files/12/docs/adr/0006-postgres-idempotency.md +15 -0
  52. package/packs/solution-architect/skills/context-graph/evals/files/12/docs/adr/0007-sftp-batch-export.md +12 -0
  53. package/packs/solution-architect/skills/context-graph/evals/files/12/docs/adr/0008-partner-config-in-yaml.md +12 -0
  54. package/packs/solution-architect/skills/context-graph/evals/files/12/docs/adr/0009-partner-config-admin-api.md +13 -0
  55. package/packs/solution-architect/skills/context-graph/evals/files/12/migrations/003_idempotency_keys.sql +6 -0
  56. package/packs/solution-architect/skills/context-graph/evals/files/12/migrations/004_partners.sql +7 -0
  57. package/packs/solution-architect/skills/context-graph/evals/files/12/src/delivery/dispatcher.py +29 -0
  58. package/packs/solution-architect/skills/context-graph/evals/files/12/src/delivery/retry.py +15 -0
  59. package/packs/solution-architect/skills/context-graph/evals/files/12/src/delivery/signing.py +13 -0
  60. package/packs/solution-architect/skills/context-graph/evals/files/12/src/export/s3_export.py +24 -0
  61. package/packs/solution-architect/skills/context-graph/evals/files/12/src/idempotency/pg_store.py +11 -0
  62. package/packs/solution-architect/skills/context-graph/evals/files/12/src/idempotency/redis_store.py +8 -0
  63. package/packs/solution-architect/skills/context-graph/evals/files/12/src/partners/loader.py +15 -0
  64. package/packs/solution-architect/skills/context-graph/evals/files/12/src/store/delivery_log.py +18 -0
  65. package/packs/solution-architect/skills/context-graph/evals/files/13/README.md +3 -0
  66. package/packs/solution-architect/skills/context-graph/evals/files/13/config/cache.yaml +2 -0
  67. package/packs/solution-architect/skills/context-graph/evals/files/13/config/settings.yaml +2 -0
  68. package/packs/solution-architect/skills/context-graph/evals/files/13/docs/decisions/RFC-008-pricing-from-erp.md +10 -0
  69. package/packs/solution-architect/skills/context-graph/evals/files/13/docs/decisions/RFC-012-pricing-cache.md +23 -0
  70. package/packs/solution-architect/skills/context-graph/evals/files/13/docs/decisions/RFC-015-checkout-price-lock.md +15 -0
  71. package/packs/solution-architect/skills/context-graph/evals/files/13/docs/decisions/RFC-019-pricing-service.md +17 -0
  72. package/packs/solution-architect/skills/context-graph/evals/files/13/shopfront/catalog/views.py +8 -0
  73. package/packs/solution-architect/skills/context-graph/evals/files/13/shopfront/checkout/lock.py +14 -0
  74. package/packs/solution-architect/skills/context-graph/evals/files/13/shopfront/pricing/cache.py +22 -0
  75. package/packs/solution-architect/skills/context-graph/evals/files/13/shopfront/pricing/client.py +10 -0
  76. package/packs/solution-architect/skills/context-graph/evals/files/14/docs/rfcs/0001-idempotency-keys.md +33 -0
  77. package/packs/solution-architect/skills/context-graph/evals/files/14/docs/rfcs/0002-processor-retry-policy.md +26 -0
  78. package/packs/solution-architect/skills/context-graph/evals/files/14/docs/rfcs/0003-money-in-minor-units.md +31 -0
  79. package/packs/solution-architect/skills/context-graph/evals/files/14/docs/rfcs/0004-webhook-signing.md +27 -0
  80. package/packs/solution-architect/skills/context-graph/evals/files/14/docs/rfcs/0005-retry-with-jitter.md +30 -0
  81. package/packs/solution-architect/skills/context-graph/evals/files/14/docs/rfcs/0006-idempotency-at-gateway.md +28 -0
  82. package/packs/solution-architect/skills/context-graph/evals/files/14/pr-summary.md +27 -0
  83. package/packs/solution-architect/skills/context-graph/evals/files/14/src/payments/amount.ts +10 -0
  84. package/packs/solution-architect/skills/context-graph/evals/files/14/src/payments/charge.ts +28 -0
  85. package/packs/solution-architect/skills/context-graph/evals/files/14/src/payments/idempotency.ts +4 -0
  86. package/packs/solution-architect/skills/context-graph/evals/files/14/src/payments/retry.ts +21 -0
  87. package/packs/solution-architect/skills/context-graph/evals/files/14/src/webhooks/send.ts +9 -0
  88. package/packs/solution-architect/skills/context-graph/evals/files/14/src/webhooks/sign.ts +6 -0
  89. package/packs/solution-architect/skills/context-graph/evals/files/15/deploy/services.yaml +14 -0
  90. package/packs/solution-architect/skills/context-graph/evals/files/15/doc/adr/0001-record-architecture-decisions.md +20 -0
  91. package/packs/solution-architect/skills/context-graph/evals/files/15/doc/adr/0002-use-postgresql-for-shipment-data.md +23 -0
  92. package/packs/solution-architect/skills/context-graph/evals/files/15/doc/adr/0003-store-vehicle-telemetry-in-cassandra.md +22 -0
  93. package/packs/solution-architect/skills/context-graph/evals/files/15/doc/adr/0004-use-redis-for-session-storage.md +22 -0
  94. package/packs/solution-architect/skills/context-graph/evals/files/15/doc/adr/0005-use-react-for-dispatcher-dashboard.md +19 -0
  95. package/packs/solution-architect/skills/context-graph/evals/files/15/doc/adr/0006-archive-delivered-shipments-to-object-storage.md +23 -0
  96. package/packs/solution-architect/skills/context-graph/evals/files/15/doc/adr/0007-move-shipment-records-to-mongodb.md +23 -0
  97. package/packs/solution-architect/skills/context-graph/evals/files/15/doc/adr/0008-store-vehicle-telemetry-in-timescaledb.md +28 -0
  98. package/packs/solution-architect/skills/context-graph/evals/files/15/doc/adr/0009-stateless-sessions-with-signed-cookies.md +24 -0
  99. package/packs/solution-architect/skills/context-graph/evals/files/15/services/api/session.py +17 -0
  100. package/packs/solution-architect/skills/context-graph/evals/files/15/services/shipments/db.py +30 -0
  101. package/packs/solution-architect/skills/context-graph/evals/files/15/services/shipments/migrations.sql +3 -0
  102. package/packs/solution-architect/skills/context-graph/evals/files/15/services/telemetry/store.py +23 -0
  103. package/packs/solution-architect/skills/context-graph/evals/files/15/web/dashboard/App.jsx +3 -0
  104. package/packs/solution-architect/skills/context-graph/evals/files/2/docs/adr/0001-record-architecture-decisions.md +11 -0
  105. package/packs/solution-architect/skills/context-graph/evals/files/2/docs/adr/0002-rabbitmq-for-background-jobs.md +15 -0
  106. package/packs/solution-architect/skills/context-graph/evals/files/2/docs/adr/0003-thumbnail-service.md +20 -0
  107. package/packs/solution-architect/skills/context-graph/evals/files/2/docs/adr/0004-money-as-integer-minor-units.md +17 -0
  108. package/packs/solution-architect/skills/context-graph/evals/files/2/docs/adr/0005-worker-retries.md +12 -0
  109. package/packs/solution-architect/skills/context-graph/evals/files/2/docs/adr/0006-fx-amounts-with-four-decimals.md +18 -0
  110. package/packs/solution-architect/skills/context-graph/evals/files/2/docs/adr/0007-feature-flags.md +16 -0
  111. package/packs/solution-architect/skills/context-graph/evals/files/2/docs/adr/0008-retire-legacy-search.md +14 -0
  112. package/packs/solution-architect/skills/context-graph/evals/files/2/libs/money/__init__.py +0 -0
  113. package/packs/solution-architect/skills/context-graph/evals/files/2/libs/money/amount.py +9 -0
  114. package/packs/solution-architect/skills/context-graph/evals/files/2/services/checkout/flags.py +1 -0
  115. package/packs/solution-architect/skills/context-graph/evals/files/2/services/checkout/publish.py +0 -0
  116. package/packs/solution-architect/skills/context-graph/evals/files/2/services/media/resize.py +5 -0
  117. package/packs/solution-architect/skills/context-graph/evals/files/2/services/worker/consumer.py +0 -0
  118. package/packs/solution-architect/skills/context-graph/evals/files/3/docs/rfcs/0001-how-we-write-rfcs.md +9 -0
  119. package/packs/solution-architect/skills/context-graph/evals/files/3/docs/rfcs/0002-payments-service-boundary.md +16 -0
  120. package/packs/solution-architect/skills/context-graph/evals/files/3/docs/rfcs/0003-webhook-retries-fixed-interval.md +19 -0
  121. package/packs/solution-architect/skills/context-graph/evals/files/3/docs/rfcs/0004-webhook-idempotency-keys.md +16 -0
  122. package/packs/solution-architect/skills/context-graph/evals/files/3/docs/rfcs/0005-notification-retries.md +16 -0
  123. package/packs/solution-architect/skills/context-graph/evals/files/3/docs/rfcs/0006-webhook-retry-backoff.md +26 -0
  124. package/packs/solution-architect/skills/context-graph/evals/files/3/docs/rfcs/0007-webhooks-on-the-job-queue.md +12 -0
  125. package/packs/solution-architect/skills/context-graph/evals/files/3/services/notifications/__init__.py +0 -0
  126. package/packs/solution-architect/skills/context-graph/evals/files/3/services/notifications/retry.py +4 -0
  127. package/packs/solution-architect/skills/context-graph/evals/files/3/services/payments/__init__.py +0 -0
  128. package/packs/solution-architect/skills/context-graph/evals/files/3/services/payments/webhooks/__init__.py +0 -0
  129. package/packs/solution-architect/skills/context-graph/evals/files/3/services/payments/webhooks/retry.py +15 -0
  130. package/packs/solution-architect/skills/context-graph/evals/files/3/services/payments/webhooks/send.py +0 -0
  131. package/packs/solution-architect/skills/context-graph/evals/files/4/docs/adr/0001-record-architecture-decisions.md +19 -0
  132. package/packs/solution-architect/skills/context-graph/evals/files/4/docs/adr/0002-postgres-as-system-of-record.md +18 -0
  133. package/packs/solution-architect/skills/context-graph/evals/files/4/docs/adr/0004-redis-locks-for-scheduler.md +19 -0
  134. package/packs/solution-architect/skills/context-graph/evals/files/4/docs/adr/0007-remove-redis.md +27 -0
  135. package/packs/solution-architect/skills/context-graph/evals/files/4/docs/adr/0009-money-as-integer-minor-units.md +19 -0
  136. package/packs/solution-architect/skills/context-graph/evals/files/4/docs/onboarding.md +12 -0
  137. package/packs/solution-architect/skills/context-graph/evals/files/4/src/ledgerline/__init__.py +0 -0
  138. package/packs/solution-architect/skills/context-graph/evals/files/4/src/ledgerline/db/__init__.py +22 -0
  139. package/packs/solution-architect/skills/context-graph/evals/files/4/src/ledgerline/invoicing.py +9 -0
  140. package/packs/solution-architect/skills/context-graph/evals/files/4/src/ledgerline/money.py +10 -0
  141. package/packs/solution-architect/skills/context-graph/evals/files/4/src/ledgerline/scheduler/__init__.py +0 -0
  142. package/packs/solution-architect/skills/context-graph/evals/files/4/src/ledgerline/scheduler/lock.py +23 -0
  143. package/packs/solution-architect/skills/context-graph/evals/files/4/src/ledgerline/scheduler/runner.py +17 -0
  144. package/packs/solution-architect/skills/context-graph/evals/files/5/lib/types/index.ts +13 -0
  145. package/packs/solution-architect/skills/context-graph/evals/files/5/rfcs/0001-service-layout.md +20 -0
  146. package/packs/solution-architect/skills/context-graph/evals/files/5/rfcs/0003-ingest-retry-policy.md +26 -0
  147. package/packs/solution-architect/skills/context-graph/evals/files/5/rfcs/0006-shared-retry-library.md +24 -0
  148. package/packs/solution-architect/skills/context-graph/evals/files/5/rfcs/0008-outbound-http-client.md +22 -0
  149. package/packs/solution-architect/skills/context-graph/evals/files/5/services/export/client.ts +15 -0
  150. package/packs/solution-architect/skills/context-graph/evals/files/5/services/export/retry.ts +12 -0
  151. package/packs/solution-architect/skills/context-graph/evals/files/5/services/ingest/client.ts +14 -0
  152. package/packs/solution-architect/skills/context-graph/evals/files/5/services/ingest/retry.ts +20 -0
  153. package/packs/solution-architect/skills/context-graph/evals/files/6/README.md +4 -0
  154. package/packs/solution-architect/skills/context-graph/evals/files/6/docs/adr/0001-monorepo-layout.md +10 -0
  155. package/packs/solution-architect/skills/context-graph/evals/files/6/docs/adr/0002-billing-on-postgres.md +13 -0
  156. package/packs/solution-architect/skills/context-graph/evals/files/6/docs/adr/0004-ledger-sync-nightly-batch.md +9 -0
  157. package/packs/solution-architect/skills/context-graph/evals/files/6/docs/rfcs/RFC-0003-invoice-ids.md +9 -0
  158. package/packs/solution-architect/skills/context-graph/evals/files/6/docs/rfcs/RFC-0007-invoice-ids-ulid.md +11 -0
  159. package/packs/solution-architect/skills/context-graph/evals/files/6/docs/rfcs/RFC-0009-billing-event-store.md +15 -0
  160. package/packs/solution-architect/skills/context-graph/evals/files/6/docs/rfcs/RFC-0010-revenue-service.md +13 -0
  161. package/packs/solution-architect/skills/context-graph/evals/files/6/libs/money/ulid.py +8 -0
  162. package/packs/solution-architect/skills/context-graph/evals/files/6/services/billing/src/events.py +10 -0
  163. package/packs/solution-architect/skills/context-graph/evals/files/6/services/billing/src/store.py +10 -0
  164. package/packs/solution-architect/skills/context-graph/evals/files/6/services/invoicing/ids/generator.py +7 -0
  165. package/packs/solution-architect/skills/context-graph/evals/files/6/services/ledger/README.md +4 -0
  166. package/packs/solution-architect/skills/context-graph/evals/files/6/services/ledger/sync.py +3 -0
  167. package/packs/solution-architect/skills/context-graph/evals/files/7/README.md +4 -0
  168. package/packs/solution-architect/skills/context-graph/evals/files/7/db/queries/orders.sql +2 -0
  169. package/packs/solution-architect/skills/context-graph/evals/files/7/db/sharding/router.py +9 -0
  170. package/packs/solution-architect/skills/context-graph/evals/files/7/design/decisions/DD-001-postgres-primary.md +8 -0
  171. package/packs/solution-architect/skills/context-graph/evals/files/7/design/decisions/DD-002-kafka-event-bus.md +20 -0
  172. package/packs/solution-architect/skills/context-graph/evals/files/7/design/decisions/DD-003-no-orm.md +9 -0
  173. package/packs/solution-architect/skills/context-graph/evals/files/7/design/decisions/DD-006-tenant-sharding.md +9 -0
  174. package/packs/solution-architect/skills/context-graph/evals/files/7/design/decisions/DD-011-orm-for-admin-console.md +11 -0
  175. package/packs/solution-architect/skills/context-graph/evals/files/7/pipelines/fanout/router.py +4 -0
  176. package/packs/solution-architect/skills/context-graph/evals/files/7/pipelines/ingest/consumer.py +5 -0
  177. package/packs/solution-architect/skills/context-graph/evals/files/7/pipelines/ingest/topics.yaml +8 -0
  178. package/packs/solution-architect/skills/context-graph/evals/files/7/rfcs/0004-region-sharding.md +15 -0
  179. package/packs/solution-architect/skills/context-graph/evals/files/7/services/admin-console/models.py +11 -0
  180. package/packs/solution-architect/skills/context-graph/evals/files/7/services/orders/repo.py +9 -0
  181. package/packs/solution-architect/skills/context-graph/evals/files/8/README.md +11 -0
  182. package/packs/solution-architect/skills/context-graph/evals/files/8/api/ledger.proto +30 -0
  183. package/packs/solution-architect/skills/context-graph/evals/files/8/config/http.yaml +10 -0
  184. package/packs/solution-architect/skills/context-graph/evals/files/8/config/messaging.yaml +8 -0
  185. package/packs/solution-architect/skills/context-graph/evals/files/8/docs/adr/0001-record-architecture-decisions.md +20 -0
  186. package/packs/solution-architect/skills/context-graph/evals/files/8/docs/adr/0002-postgresql-for-ledger.md +22 -0
  187. package/packs/solution-architect/skills/context-graph/evals/files/8/docs/adr/0003-rabbitmq-domain-events.md +22 -0
  188. package/packs/solution-architect/skills/context-graph/evals/files/8/docs/adr/0004-integer-minor-units.md +32 -0
  189. package/packs/solution-architect/skills/context-graph/evals/files/8/docs/adr/0005-provider-http-retries.md +26 -0
  190. package/packs/solution-architect/skills/context-graph/evals/files/8/docs/adr/0006-openapi-generated-clients.md +20 -0
  191. package/packs/solution-architect/skills/context-graph/evals/files/8/docs/adr/0007-kafka-domain-events.md +25 -0
  192. package/packs/solution-architect/skills/context-graph/evals/files/8/docs/adr/0008-structured-logging.md +18 -0
  193. package/packs/solution-architect/skills/context-graph/evals/files/8/docs/rfcs/RFC-012-idempotency-keys.md +19 -0
  194. package/packs/solution-architect/skills/context-graph/evals/files/8/services/ledger/api/idempotency.py +19 -0
  195. package/packs/solution-architect/skills/context-graph/evals/files/8/services/ledger/api/server.py +35 -0
  196. package/packs/solution-architect/skills/context-graph/evals/files/8/services/ledger/clients/provider.py +33 -0
  197. package/packs/solution-architect/skills/context-graph/evals/files/8/services/ledger/db/migrations/0001_init.sql +14 -0
  198. package/packs/solution-architect/skills/context-graph/evals/files/8/services/ledger/db/migrations/0008_idempotency_keys.sql +5 -0
  199. package/packs/solution-architect/skills/context-graph/evals/files/8/services/ledger/db/migrations/0009_fx_entries.sql +4 -0
  200. package/packs/solution-architect/skills/context-graph/evals/files/8/services/ledger/events/publisher.py +28 -0
  201. package/packs/solution-architect/skills/context-graph/evals/files/8/services/ledger/logging.py +25 -0
  202. package/packs/solution-architect/skills/context-graph/evals/files/8/services/ledger/models/entry.py +16 -0
  203. package/packs/solution-architect/skills/context-graph/evals/files/9/.env.example +3 -0
  204. package/packs/solution-architect/skills/context-graph/evals/files/9/README.md +5 -0
  205. package/packs/solution-architect/skills/context-graph/evals/files/9/docker-compose.yml +18 -0
  206. package/packs/solution-architect/skills/context-graph/evals/files/9/docs/decisions/001-session-storage.md +21 -0
  207. package/packs/solution-architect/skills/context-graph/evals/files/9/docs/decisions/002-api-rate-limiting.md +32 -0
  208. package/packs/solution-architect/skills/context-graph/evals/files/9/docs/decisions/003-jwt-sessions.md +29 -0
  209. package/packs/solution-architect/skills/context-graph/evals/files/9/docs/decisions/004-catalog-cache.md +19 -0
  210. package/packs/solution-architect/skills/context-graph/evals/files/9/src/catalog/cache.ts +13 -0
  211. package/packs/solution-architect/skills/context-graph/evals/files/9/src/catalog/repository.ts +13 -0
  212. package/packs/solution-architect/skills/context-graph/evals/files/9/src/jobs/email.ts +3 -0
  213. package/packs/solution-architect/skills/context-graph/evals/files/9/src/jobs/export.ts +3 -0
  214. package/packs/solution-architect/skills/context-graph/evals/files/9/src/jobs/queue.ts +10 -0
  215. package/packs/solution-architect/skills/context-graph/evals/files/9/src/jobs/worker.ts +8 -0
  216. package/packs/solution-architect/skills/context-graph/evals/files/9/src/lib/db.ts +10 -0
  217. package/packs/solution-architect/skills/context-graph/evals/files/9/src/lib/redis.ts +8 -0
  218. package/packs/solution-architect/skills/context-graph/evals/files/9/src/middleware/rateLimit.ts +26 -0
  219. package/packs/solution-architect/skills/context-graph/evals/files/9/src/server.ts +17 -0
  220. package/packs/solution-architect/skills/context-graph/evals/files/9/src/session/store.ts +10 -0
  221. package/packs/solution-architect/skills/context-graph/examples/inputs/order-events/docs/adr/0004-kafka-for-order-events.md +19 -0
  222. package/packs/solution-architect/skills/context-graph/examples/inputs/order-events/docs/adr/0006-email-receipts.md +7 -0
  223. package/packs/solution-architect/skills/context-graph/examples/inputs/order-events/docs/adr/0009-sqs-for-order-events.md +19 -0
  224. package/packs/solution-architect/skills/context-graph/examples/inputs/order-events/services/notifications/receipts.py +0 -0
  225. package/packs/solution-architect/skills/context-graph/examples/inputs/order-events/services/orders/publisher.py +0 -0
  226. package/packs/solution-architect/skills/context-graph/examples/order-events.md +63 -0
  227. package/packs/solution-architect/skills/context-graph/manifest.json +33 -0
  228. package/packs/solution-architect/skills/context-graph/reference.md +104 -0
  229. package/packs/solution-architect/skills/context-graph/scripts/context_graph.py +634 -0
@@ -0,0 +1,8 @@
1
+ # Tallowmere Payments Platform
2
+
3
+ Two services:
4
+
5
+ - `services/billing-api` — HTTP API that creates invoices and takes card-on-file charges.
6
+ - `services/ledger-worker` — consumes billing events and writes the double-entry ledger.
7
+
8
+ Architecture decisions live in `docs/adr/`.
@@ -0,0 +1,10 @@
1
+ # ADR-0001: Record architecture decisions
2
+
3
+ - Status: Accepted
4
+ - Date: 2022-11-02
5
+
6
+ ## Decision
7
+
8
+ We record significant architecture decisions as numbered markdown files in `docs/adr/`.
9
+ When a decision replaces an earlier one, the new record says `Supersedes: ADR-NNNN` and the
10
+ old record's status is changed to `Superseded by ADR-MMMM`.
@@ -0,0 +1,21 @@
1
+ # ADR-0004: Service-to-service authentication with HMAC shared keys
2
+
3
+ - Status: Accepted
4
+ - Date: 2023-03-14
5
+ - Governs: `services/billing-api/auth/`, `services/ledger-worker/auth/`
6
+
7
+ ## Context
8
+
9
+ billing-api and ledger-worker call each other over the internal network. We need callers to
10
+ prove who they are without standing up new infrastructure.
11
+
12
+ ## Decision
13
+
14
+ Every internal request carries an `X-Tallow-Signature` header: HMAC-SHA256 over the method,
15
+ path, timestamp and body, keyed with a shared secret per service pair. Requests older than
16
+ 300 seconds are rejected.
17
+
18
+ ## Consequences
19
+
20
+ - Shared keys must be distributed to both sides and rotated by hand.
21
+ - A leaked key lets an attacker impersonate either side of the pair.
@@ -0,0 +1,19 @@
1
+ # ADR-0007: Runtime secrets come from the Vault agent, never from environment variables
2
+
3
+ - Status: Accepted
4
+ - Date: 2023-09-02
5
+ - Governs: `services/billing-api/config/`, `services/ledger-worker/config/`
6
+
7
+ ## Context
8
+
9
+ A 2023 incident review found database passwords in a crash dump's environment block.
10
+
11
+ ## Decision
12
+
13
+ All runtime secrets (database credentials, signing keys, processor API keys) are read from
14
+ JSON files rendered by the Vault agent sidecar under `/vault/secrets/`. Services must not read
15
+ secrets from environment variables, and no secret may be committed to the repository.
16
+
17
+ ## Consequences
18
+
19
+ - Local development uses `/vault/secrets/` files rendered by `make dev-secrets`.
@@ -0,0 +1,23 @@
1
+ # ADR-0011: Service-to-service authentication with mutual TLS
2
+
3
+ - Status: Accepted
4
+ - Date: 2025-06-20
5
+ - Supersedes: ADR-0004
6
+ - Governs: `services/billing-api/auth/`, `services/ledger-worker/auth/`
7
+
8
+ ## Context
9
+
10
+ ADR-0004's shared HMAC keys are rotated by hand and a single leak compromises both sides of a
11
+ pair. The 2025 PCI pre-assessment flagged this.
12
+
13
+ ## Decision
14
+
15
+ Internal calls between billing-api and ledger-worker use mutual TLS. Each service gets a
16
+ short-lived client certificate from the internal Ironbark CA; the receiving side checks the
17
+ certificate's SPIFFE ID against an allow-list. HMAC signatures are removed once both services
18
+ are migrated. Certificate rotation follows ADR-0013.
19
+
20
+ ## Consequences
21
+
22
+ - No shared secrets between services.
23
+ - Each service needs the Ironbark CA bundle mounted.
@@ -0,0 +1,16 @@
1
+ """billing-api HTTP entry point."""
2
+ from flask import Flask, jsonify, request
3
+
4
+ from auth import require_internal_caller
5
+ from config.settings import load_settings
6
+
7
+ app = Flask(__name__)
8
+ settings = load_settings()
9
+
10
+
11
+ @app.post("/internal/charges")
12
+ @require_internal_caller
13
+ def create_charge():
14
+ body = request.get_json()
15
+ # ... create the charge against the processor using settings["processor_api_key"]
16
+ return jsonify({"status": "queued", "invoice_id": body["invoice_id"]}), 202
@@ -0,0 +1,4 @@
1
+ """Internal-caller authentication for billing-api."""
2
+ from .hmac_verify import require_hmac_signature as require_internal_caller
3
+
4
+ __all__ = ["require_internal_caller"]
@@ -0,0 +1,31 @@
1
+ """Verify the X-Tallow-Signature header on internal requests (see ADR-0004)."""
2
+ import hashlib
3
+ import hmac
4
+ import os
5
+ import time
6
+ from functools import wraps
7
+
8
+ from flask import abort, request
9
+
10
+ MAX_SKEW_SECONDS = 300
11
+
12
+
13
+ def _shared_key() -> bytes:
14
+ # Shared with ledger-worker.
15
+ return os.environ["BILLING_LEDGER_HMAC_KEY"].encode()
16
+
17
+
18
+ def require_hmac_signature(view):
19
+ @wraps(view)
20
+ def wrapper(*args, **kwargs):
21
+ ts = request.headers.get("X-Tallow-Timestamp", "0")
22
+ sig = request.headers.get("X-Tallow-Signature", "")
23
+ if abs(time.time() - int(ts)) > MAX_SKEW_SECONDS:
24
+ abort(401)
25
+ msg = f"{request.method}\n{request.path}\n{ts}\n".encode() + request.get_data()
26
+ expected = hmac.new(_shared_key(), msg, hashlib.sha256).hexdigest()
27
+ if not hmac.compare_digest(expected, sig):
28
+ abort(401)
29
+ return view(*args, **kwargs)
30
+
31
+ return wrapper
@@ -0,0 +1,14 @@
1
+ """Load billing-api runtime settings and secrets (see ADR-0007)."""
2
+ import json
3
+ from pathlib import Path
4
+
5
+ VAULT_DIR = Path("/vault/secrets")
6
+
7
+
8
+ def load_settings() -> dict:
9
+ db = json.loads((VAULT_DIR / "billing-db.json").read_text())
10
+ processor = json.loads((VAULT_DIR / "processor.json").read_text())
11
+ return {
12
+ "db_url": db["url"],
13
+ "processor_api_key": processor["api_key"],
14
+ }
@@ -0,0 +1,4 @@
1
+ """Internal-caller authentication for ledger-worker."""
2
+ from .mtls import require_peer_identity as require_internal_caller
3
+
4
+ __all__ = ["require_internal_caller"]
@@ -0,0 +1,18 @@
1
+ """Check the peer certificate's SPIFFE ID on internal requests (see ADR-0011)."""
2
+ from functools import wraps
3
+
4
+ ALLOWED_PEERS = {
5
+ "spiffe://tallowmere.internal/billing-api",
6
+ }
7
+ CA_BUNDLE = "/etc/ironbark/ca-bundle.pem"
8
+
9
+
10
+ def require_peer_identity(handler):
11
+ @wraps(handler)
12
+ def wrapper(event, peer_cert, *args, **kwargs):
13
+ spiffe_id = peer_cert.get("uri_san", "")
14
+ if spiffe_id not in ALLOWED_PEERS:
15
+ raise PermissionError(f"peer {spiffe_id!r} not allowed")
16
+ return handler(event, peer_cert, *args, **kwargs)
17
+
18
+ return wrapper
@@ -0,0 +1,4 @@
1
+ # Pebblecart
2
+
3
+ Small storefront backend. Models in `app/models/`, SQL migrations in `migrations/`.
4
+ Design decisions are kept in `eng/decisions/` (DR-NNN).
@@ -0,0 +1,3 @@
1
+ from sqlalchemy.orm import declarative_base
2
+
3
+ Base = declarative_base()
@@ -0,0 +1,14 @@
1
+ """Line item model. Immutable once the parent order is placed (DR-009)."""
2
+ from sqlalchemy import BigInteger, Column, ForeignKey, Integer, String
3
+
4
+ from .base import Base
5
+
6
+
7
+ class LineItem(Base):
8
+ __tablename__ = "line_items"
9
+
10
+ id = Column(BigInteger, primary_key=True)
11
+ order_id = Column(BigInteger, ForeignKey("orders.id"), nullable=False, index=True)
12
+ sku = Column(String(32), nullable=False)
13
+ quantity = Column(Integer, nullable=False)
14
+ unit_price_minor = Column(Integer, nullable=False)
@@ -0,0 +1,24 @@
1
+ """Order model."""
2
+ from sqlalchemy import JSON, BigInteger, Column, DateTime, Integer, String
3
+
4
+ from .base import Base
5
+
6
+ # Keys allowed in Order.attributes (DR-014). Review additions like a column.
7
+ ORDER_ATTRIBUTE_KEYS = {
8
+ "delivery_note",
9
+ "leave_with_neighbour",
10
+ }
11
+
12
+
13
+ class Order(Base):
14
+ __tablename__ = "orders"
15
+
16
+ id = Column(BigInteger, primary_key=True)
17
+ customer_id = Column(BigInteger, nullable=False, index=True)
18
+ status = Column(String(16), nullable=False, default="draft")
19
+ currency = Column(String(3), nullable=False)
20
+ subtotal_minor = Column(Integer, nullable=False)
21
+ shipping_minor = Column(Integer, nullable=False, default=0)
22
+ total_minor = Column(Integer, nullable=False)
23
+ placed_at = Column(DateTime(timezone=True))
24
+ attributes = Column(JSON, nullable=False, default=dict)
@@ -0,0 +1,10 @@
1
+ ---
2
+ id: DR-003
3
+ title: Store money as integer minor units with a currency code
4
+ status: Accepted
5
+ date: 2022-02-17
6
+ governs: [app/models/order.py, app/models/line_item.py]
7
+ ---
8
+
9
+ All monetary amounts are stored as integer minor units (`*_minor`, e.g. pence) alongside a
10
+ three-letter `currency` column. No floats or decimals for money anywhere in the schema.
@@ -0,0 +1,10 @@
1
+ ---
2
+ id: DR-005
3
+ title: Orders table uses typed columns only
4
+ status: Accepted
5
+ date: 2022-04-11
6
+ governs: [app/models/order.py, migrations/]
7
+ ---
8
+
9
+ Every field on an order is a typed, named column with a migration. We do not store JSON blobs
10
+ on the orders table: they hide schema changes from review and cannot be indexed.
@@ -0,0 +1,11 @@
1
+ ---
2
+ id: DR-009
3
+ title: Order line items are immutable once the order is placed
4
+ status: Accepted
5
+ date: 2023-01-30
6
+ governs: [app/models/line_item.py]
7
+ cites: [DR-003]
8
+ ---
9
+
10
+ After an order moves to `placed`, its line items are never updated. Price or quantity
11
+ corrections are written as rows in `order_adjustments`, amounts in minor units per DR-003.
@@ -0,0 +1,16 @@
1
+ ---
2
+ id: DR-014
3
+ title: Optional customer-supplied order fields go in orders.attributes
4
+ status: Accepted
5
+ date: 2024-02-08
6
+ amends: DR-005
7
+ governs: [app/models/order.py, migrations/0012_add_order_attributes.sql]
8
+ ---
9
+
10
+ DR-005's typed-columns rule produced a migration for every small checkout tweak. We relax it:
11
+
12
+ - New **optional, customer-supplied** fields that are not filtered, joined or reported on
13
+ (delivery notes, gift options, and similar) are stored as keys in the `orders.attributes`
14
+ JSON column. Each key is listed in `ORDER_ATTRIBUTE_KEYS` in `app/models/order.py` and is
15
+ reviewed like a column.
16
+ - Anything we filter, join, index or report on stays a typed column, as DR-005 says.
@@ -0,0 +1,11 @@
1
+ ---
2
+ id: DR-016
3
+ title: Move order attributes into a separate order_attributes table
4
+ status: Proposed
5
+ date: 2026-09-30
6
+ cites: [DR-014]
7
+ ---
8
+
9
+ Proposal (under discussion, not agreed): replace the `orders.attributes` JSON column from
10
+ DR-014 with an `order_attributes(order_id, key, value)` table so attributes can be indexed.
11
+ Open question: whether existing keys are back-filled or dual-written.
@@ -0,0 +1,11 @@
1
+ CREATE TABLE orders (
2
+ id BIGINT PRIMARY KEY,
3
+ customer_id BIGINT NOT NULL,
4
+ status VARCHAR(16) NOT NULL DEFAULT 'draft',
5
+ currency CHAR(3) NOT NULL,
6
+ subtotal_minor INTEGER NOT NULL,
7
+ shipping_minor INTEGER NOT NULL DEFAULT 0,
8
+ total_minor INTEGER NOT NULL,
9
+ placed_at TIMESTAMPTZ
10
+ );
11
+ CREATE INDEX orders_customer_id_idx ON orders (customer_id);
@@ -0,0 +1,2 @@
1
+ -- DR-014: optional customer-supplied order fields.
2
+ ALTER TABLE orders ADD COLUMN attributes JSONB NOT NULL DEFAULT '{}'::jsonb;
@@ -0,0 +1,8 @@
1
+ # parcelhook
2
+
3
+ Sends shipment-status webhooks (picked_up, in_transit, delivered, exception) from the
4
+ Corvane Freight tracking platform to partner endpoints. Owned by Platform Integrations.
5
+
6
+ - Entry point: `src/delivery/dispatcher.py`
7
+ - Decisions: `docs/adr/`
8
+ - Config: `config/`
@@ -0,0 +1,4 @@
1
+ retry:
2
+ max_attempts: 20
3
+ base_delay_seconds: 10
4
+ timeout_seconds: 10
@@ -0,0 +1,2 @@
1
+ schedule: "0 2 * * *"
2
+ bucket: corvane-parcelhook-exports
@@ -0,0 +1,10 @@
1
+ # Partner configuration (ADR-0008). Loaded at startup.
2
+ partners:
3
+ - id: velo
4
+ url: https://hooks.velo.example/parcel
5
+ events: [picked_up, delivered, exception]
6
+ secret_ref: vault://parcelhook/partners/velo
7
+ - id: northwind-retail
8
+ url: https://api.northwind.example/webhooks/shipments
9
+ events: [delivered]
10
+ secret_ref: vault://parcelhook/partners/northwind-retail
@@ -0,0 +1,10 @@
1
+ # ADR-0001: Record architecture decisions
2
+
3
+ - Status: Accepted
4
+ - Date: 2022-03-01
5
+ - Deciders: Platform Integrations
6
+
7
+ ## Decision
8
+ We record significant decisions about parcelhook as numbered ADRs in `docs/adr/`.
9
+ A record that replaces another says "Supersedes ADR-NNNN", and the replaced record's
10
+ status is changed to "Superseded by ADR-MMMM".
@@ -0,0 +1,15 @@
1
+ # ADR-0002: Postgres for the delivery log
2
+
3
+ - Status: Accepted
4
+ - Date: 2022-03-10
5
+ - Governs: `src/store/`
6
+
7
+ ## Context
8
+ Partners dispute whether a webhook was sent. We need a durable, queryable record of every attempt.
9
+
10
+ ## Decision
11
+ Every delivery attempt is written to the `delivery_attempts` table in the service's Postgres
12
+ database (event_id, partner_id, attempt_no, status_code, sent_at).
13
+
14
+ ## Consequences
15
+ Support can answer "did we send it?" with a query. The table grows ~2M rows/month; partition by month.
@@ -0,0 +1,15 @@
1
+ # ADR-0003: Idempotency keys in Redis
2
+
3
+ - Status: Accepted
4
+ - Date: 2022-05-02
5
+ - Governs: `src/idempotency/redis_store.py`
6
+
7
+ ## Context
8
+ The tracking platform delivers events at-least-once. Partners complained about duplicate webhooks.
9
+
10
+ ## Decision
11
+ Before dispatching, the dispatcher sets `idem:{event_id}:{partner_id}` in Redis with NX and a
12
+ 24h TTL. If the key already exists the event is dropped as a duplicate.
13
+
14
+ ## Consequences
15
+ Fast and simple. If Redis is flushed or evicts keys under memory pressure, duplicates can get through.
@@ -0,0 +1,16 @@
1
+ # ADR-0004: Retry policy for failed deliveries
2
+
3
+ - Status: Accepted
4
+ - Date: 2022-06-20
5
+ - Governs: `src/delivery/retry.py`, `config/delivery.yaml`
6
+
7
+ ## Context
8
+ Partner endpoints go down. Retrying too aggressively has had us rate-limited and blocked by two partners.
9
+
10
+ ## Decision
11
+ Retry failed deliveries with exponential backoff: delay = 30s * 2^(attempt-1), at most 6 attempts
12
+ (last retry roughly 16 minutes after the first). After the 6th failure, publish the event to the
13
+ dead-letter queue `parcelhook.dlq` for manual replay.
14
+
15
+ ## Consequences
16
+ A partner outage of more than ~30 minutes means events land in the DLQ and need replaying.
@@ -0,0 +1,10 @@
1
+ # ADR-0005: Sign payloads with HMAC-SHA256
2
+
3
+ - Status: Accepted
4
+ - Date: 2022-07-11
5
+ - Governs: `src/delivery/signing.py`
6
+
7
+ ## Decision
8
+ Each webhook body is signed with HMAC-SHA256 using the partner's shared secret. The hex digest
9
+ is sent in the `X-Parcelhook-Signature` header, together with `X-Parcelhook-Timestamp`, and the
10
+ timestamp is part of the signed string (`{timestamp}.{body}`) to prevent replay.
@@ -0,0 +1,15 @@
1
+ # ADR-0006: Move idempotency keys to Postgres
2
+
3
+ - Status: Accepted
4
+ - Date: 2023-09-04
5
+ - Supersedes: ADR-0003
6
+ - Governs: `src/idempotency/pg_store.py`, `migrations/003_idempotency_keys.sql`
7
+
8
+ ## Context
9
+ INC-1874 (2023-08-22): a Redis failover lost the idempotency keyspace and ~41,000 duplicate
10
+ webhooks went to partners in one hour.
11
+
12
+ ## Decision
13
+ Idempotency keys move to the `idempotency_keys` table in Postgres with a unique constraint on
14
+ (event_id, partner_id). The dispatcher inserts with ON CONFLICT DO NOTHING and only dispatches
15
+ if a row was inserted. Rows older than 7 days are purged nightly. Redis is no longer used for idempotency.
@@ -0,0 +1,12 @@
1
+ # ADR-0007: Nightly SFTP export of the delivery log
2
+
3
+ - Status: Accepted
4
+ - Date: 2023-02-15
5
+ - Governs: `src/export/sftp_export.py`
6
+
7
+ ## Context
8
+ Three partners reconcile against a daily file rather than our webhooks.
9
+
10
+ ## Decision
11
+ At 02:00 UTC, export the previous day's `delivery_attempts` rows per partner as CSV and push
12
+ them to each partner's SFTP server using `src/export/sftp_export.py`.
@@ -0,0 +1,12 @@
1
+ # ADR-0008: Partner configuration in a YAML file
2
+
3
+ - Status: Accepted
4
+ - Date: 2023-11-20
5
+ - Governs: `config/partners.yaml`, `src/partners/`
6
+
7
+ ## Decision
8
+ Partner endpoints, enabled event types and secret references live in `config/partners.yaml`,
9
+ reviewed by pull request. The service loads the file at startup.
10
+
11
+ ## Consequences
12
+ Onboarding a partner needs a deploy. Acceptable at our current 12 partners.
@@ -0,0 +1,13 @@
1
+ # ADR-0009: Partner configuration via an admin API
2
+
3
+ - Status: Accepted
4
+ - Date: 2024-05-13
5
+ - Governs: `src/partners/`, `migrations/004_partners.sql`
6
+
7
+ ## Context
8
+ We now have 60+ partners and onboarding through a YAML PR and a deploy takes days.
9
+
10
+ ## Decision
11
+ Partner configuration is stored in the `partners` table and edited through an internal admin API.
12
+ The service reloads partners from the table every 60 seconds. See ADR-0011 for the authentication
13
+ model of the admin API.
@@ -0,0 +1,6 @@
1
+ CREATE TABLE idempotency_keys (
2
+ event_id text NOT NULL,
3
+ partner_id text NOT NULL,
4
+ created_at timestamptz NOT NULL DEFAULT now(),
5
+ PRIMARY KEY (event_id, partner_id)
6
+ );
@@ -0,0 +1,7 @@
1
+ CREATE TABLE partners (
2
+ id text PRIMARY KEY,
3
+ url text NOT NULL,
4
+ events text[] NOT NULL,
5
+ secret_ref text NOT NULL,
6
+ enabled boolean NOT NULL DEFAULT true
7
+ );
@@ -0,0 +1,29 @@
1
+ """Consume tracking events and deliver them to partner endpoints."""
2
+ import json
3
+ import urllib.request
4
+
5
+ from src.idempotency import redis_store
6
+ from src.partners.loader import load_partners
7
+ from src.delivery.signing import sign
8
+ from src.delivery.retry import schedule_retry
9
+ from src.store.delivery_log import record_attempt
10
+
11
+
12
+ def dispatch(event, attempt_no=1):
13
+ for partner in load_partners():
14
+ if event["type"] not in partner["events"]:
15
+ continue
16
+ # drop duplicates from the at-least-once feed
17
+ if not redis_store.claim(event["id"], partner["id"]):
18
+ continue
19
+ body = json.dumps(event).encode()
20
+ headers = {"Content-Type": "application/json", **sign(partner, body)}
21
+ req = urllib.request.Request(partner["url"], data=body, headers=headers, method="POST")
22
+ try:
23
+ with urllib.request.urlopen(req, timeout=10) as resp:
24
+ status = resp.status
25
+ except Exception:
26
+ status = 0
27
+ record_attempt(event["id"], partner["id"], attempt_no, status)
28
+ if not 200 <= status < 300:
29
+ schedule_retry(event, partner, attempt_no)
@@ -0,0 +1,15 @@
1
+ """Retry scheduling for failed deliveries."""
2
+ from src.config import load_yaml
3
+ from src.store.delivery_log import mark_failed
4
+ from src.queue import enqueue_delayed
5
+
6
+ CFG = load_yaml("config/delivery.yaml")["retry"]
7
+
8
+
9
+ def schedule_retry(event, partner, attempt_no):
10
+ if attempt_no >= CFG["max_attempts"]:
11
+ mark_failed(event["id"], partner["id"])
12
+ return
13
+ # TODO(INC-2291): backoff disabled while partner Velo's endpoint flaps; restore afterwards
14
+ delay = CFG["base_delay_seconds"]
15
+ enqueue_delayed(event, partner, attempt_no + 1, delay_seconds=delay)
@@ -0,0 +1,13 @@
1
+ """HMAC-SHA256 signing of webhook bodies."""
2
+ import hashlib
3
+ import hmac
4
+ import time
5
+
6
+ from src.secrets import partner_secret
7
+
8
+
9
+ def sign(partner, body: bytes) -> dict:
10
+ ts = str(int(time.time()))
11
+ msg = ts.encode() + b"." + body
12
+ digest = hmac.new(partner_secret(partner["id"]), msg, hashlib.sha256).hexdigest()
13
+ return {"X-Parcelhook-Signature": digest, "X-Parcelhook-Timestamp": ts}
@@ -0,0 +1,24 @@
1
+ """Nightly export of the delivery log to per-partner S3 prefixes."""
2
+ import csv
3
+ import io
4
+
5
+ from src.config import load_yaml
6
+ from src.db import execute
7
+ from src.s3 import put_object
8
+
9
+ CFG = load_yaml("config/export.yaml")
10
+
11
+
12
+ def run(day):
13
+ rows = execute(
14
+ "SELECT partner_id, event_id, attempt_no, status_code, sent_at FROM delivery_attempts "
15
+ "WHERE sent_at::date = %s ORDER BY partner_id",
16
+ (day,),
17
+ )
18
+ by_partner = {}
19
+ for r in rows:
20
+ by_partner.setdefault(r[0], []).append(r)
21
+ for partner_id, prows in by_partner.items():
22
+ buf = io.StringIO()
23
+ csv.writer(buf).writerows(prows)
24
+ put_object(CFG["bucket"], f"{partner_id}/{day}.csv", buf.getvalue())
@@ -0,0 +1,11 @@
1
+ """Idempotency claims in Postgres (see ADR-0006)."""
2
+ from src.db import execute
3
+
4
+
5
+ def claim(event_id, partner_id) -> bool:
6
+ rows = execute(
7
+ "INSERT INTO idempotency_keys (event_id, partner_id) VALUES (%s, %s) "
8
+ "ON CONFLICT DO NOTHING RETURNING 1",
9
+ (event_id, partner_id),
10
+ )
11
+ return len(rows) == 1
@@ -0,0 +1,8 @@
1
+ """Idempotency claims in Redis (see ADR-0003)."""
2
+ from src.redis_client import redis
3
+
4
+ TTL_SECONDS = 24 * 3600
5
+
6
+
7
+ def claim(event_id, partner_id) -> bool:
8
+ return bool(redis.set(f"idem:{event_id}:{partner_id}", 1, nx=True, ex=TTL_SECONDS))
@@ -0,0 +1,15 @@
1
+ """Load partner configuration."""
2
+ import time
3
+
4
+ from src.db import execute
5
+
6
+ _cache = {"at": 0, "partners": []}
7
+ RELOAD_SECONDS = 60
8
+
9
+
10
+ def load_partners():
11
+ if time.time() - _cache["at"] > RELOAD_SECONDS:
12
+ rows = execute("SELECT id, url, events FROM partners WHERE enabled")
13
+ _cache["partners"] = [{"id": r[0], "url": r[1], "events": r[2]} for r in rows]
14
+ _cache["at"] = time.time()
15
+ return _cache["partners"]