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,28 @@
1
+ # 8. Store vehicle telemetry in TimescaleDB
2
+
3
+ Date: 2023-04-11
4
+
5
+ ## Status
6
+
7
+ Accepted
8
+
9
+ Supersedes [3. Store vehicle telemetry in Cassandra](0003-store-vehicle-telemetry-in-cassandra.md)
10
+
11
+ ## Context
12
+
13
+ The Cassandra cluster from ADR 3 needs specialist operations knowledge
14
+ nobody on the team has, and geo queries over telemetry are now a product
15
+ requirement.
16
+
17
+ ## Decision
18
+
19
+ Vehicle telemetry moves to TimescaleDB (a PostgreSQL extension) in a
20
+ separate database, `haulmark_telemetry`, written by `services/telemetry/`,
21
+ using a hypertable partitioned by time with PostGIS for geo queries.
22
+ Raw points are kept for 30 days and downsampled after that, as set out in
23
+ [10. Telemetry retention and downsampling](0010-telemetry-retention-and-downsampling.md).
24
+
25
+ ## Consequences
26
+
27
+ One database technology to operate instead of two. Write throughput must be
28
+ watched as the fleet grows past 2,000 trucks.
@@ -0,0 +1,24 @@
1
+ # 9. Stateless sessions with signed cookies
2
+
3
+ Date: 2024-05-07
4
+
5
+ ## Status
6
+
7
+ Accepted
8
+
9
+ Supersedes [4. Use Redis for session storage](0004-use-redis-for-session-storage.md)
10
+
11
+ ## Context
12
+
13
+ Redis was the only stateful dependency of `services/api/` and caused two
14
+ outages in Q1 2024 when its primary failed over.
15
+
16
+ ## Decision
17
+
18
+ Sessions are stateless: a signed, encrypted cookie carries the dispatcher's
19
+ user ID, role and an 8-hour expiry. Redis is removed from `services/api/`.
20
+
21
+ ## Consequences
22
+
23
+ No session store to operate. Revoking a single session before expiry needs a
24
+ short deny-list, kept in `haulmark_core`.
@@ -0,0 +1,17 @@
1
+ """Dispatcher sessions for services/api (ADR 9): signed, encrypted cookies, no server store."""
2
+ import os
3
+ import time
4
+
5
+ from itsdangerous import URLSafeTimedSerializer
6
+
7
+ SECRET = os.environ.get("SESSION_SECRET", "placeholder-not-a-real-secret")
8
+ MAX_AGE_SECONDS = 8 * 3600
9
+ _serializer = URLSafeTimedSerializer(SECRET, salt="haulmark-session")
10
+
11
+
12
+ def issue(user_id: str, role: str) -> str:
13
+ return _serializer.dumps({"uid": user_id, "role": role, "iat": int(time.time())})
14
+
15
+
16
+ def read(cookie: str):
17
+ return _serializer.loads(cookie, max_age=MAX_AGE_SECONDS)
@@ -0,0 +1,30 @@
1
+ """Shipment persistence for services/shipments (ADR 2)."""
2
+ import os
3
+
4
+ import psycopg
5
+
6
+ DSN = os.environ.get("SHIPMENTS_DSN", "postgresql://haulmark:placeholder@localhost:5432/haulmark_core")
7
+
8
+
9
+ def connect():
10
+ return psycopg.connect(DSN)
11
+
12
+
13
+ def get_shipment(conn, shipment_id: str):
14
+ with conn.cursor() as cur:
15
+ cur.execute(
16
+ "SELECT id, customer_id, status, custom_fields FROM shipments WHERE id = %s",
17
+ (shipment_id,),
18
+ )
19
+ return cur.fetchone()
20
+
21
+
22
+ def mark_stop_delivered(conn, shipment_id: str, stop_id: str):
23
+ with conn.transaction():
24
+ with conn.cursor() as cur:
25
+ cur.execute("UPDATE stops SET status = 'delivered' WHERE id = %s", (stop_id,))
26
+ cur.execute(
27
+ "UPDATE shipments SET status = 'delivered' WHERE id = %s "
28
+ "AND NOT EXISTS (SELECT 1 FROM stops WHERE shipment_id = %s AND status <> 'delivered')",
29
+ (shipment_id, shipment_id),
30
+ )
@@ -0,0 +1,3 @@
1
+ -- 2022-09-30: per-customer custom fields as JSONB instead of new columns
2
+ ALTER TABLE shipments ADD COLUMN custom_fields JSONB NOT NULL DEFAULT '{}'::jsonb;
3
+ CREATE INDEX shipments_custom_fields_gin ON shipments USING GIN (custom_fields);
@@ -0,0 +1,23 @@
1
+ """Telemetry writer for services/telemetry (ADR 8)."""
2
+ import os
3
+
4
+ import psycopg
5
+
6
+ DSN = os.environ.get("TELEMETRY_DSN", "postgresql://haulmark:placeholder@localhost:5432/haulmark_telemetry")
7
+
8
+ SCHEMA = """
9
+ CREATE TABLE IF NOT EXISTS positions (
10
+ vehicle_id TEXT NOT NULL,
11
+ recorded_at TIMESTAMPTZ NOT NULL,
12
+ location GEOGRAPHY(POINT) NOT NULL
13
+ );
14
+ SELECT create_hypertable('positions', 'recorded_at', if_not_exists => TRUE);
15
+ """
16
+
17
+
18
+ def write_position(conn, vehicle_id: str, recorded_at, lon: float, lat: float):
19
+ with conn.cursor() as cur:
20
+ cur.execute(
21
+ "INSERT INTO positions VALUES (%s, %s, ST_MakePoint(%s, %s)::geography)",
22
+ (vehicle_id, recorded_at, lon, lat),
23
+ )
@@ -0,0 +1,3 @@
1
+ export default function App() {
2
+ return <h1>Haulmark dispatch</h1>;
3
+ }
@@ -0,0 +1,11 @@
1
+ # 1. Record architecture decisions
2
+
3
+ Date: 2023-04-03
4
+
5
+ ## Status
6
+
7
+ Accepted
8
+
9
+ ## Decision
10
+
11
+ We record architecture decisions as ADRs in `docs/adr/`, one numbered file each.
@@ -0,0 +1,15 @@
1
+ ---
2
+ status: accepted
3
+ date: 2023-05-22
4
+ ---
5
+
6
+ # RabbitMQ for background jobs
7
+
8
+ ## Context and Problem Statement
9
+
10
+ Checkout sends receipts and stock updates inline, which slows the request.
11
+
12
+ ## Decision Outcome
13
+
14
+ Chosen option: "RabbitMQ", because the hosting provider offers it managed. Jobs are consumed by
15
+ `services/worker/`, and `services/checkout/` only publishes.
@@ -0,0 +1,20 @@
1
+ # 3. A separate thumbnail service
2
+
3
+ Date: 2023-09-11
4
+
5
+ ## Status
6
+
7
+ Accepted
8
+
9
+ ## Context
10
+
11
+ Resizing product images inside the API blocks request threads.
12
+
13
+ ## Decision
14
+
15
+ Image resizing runs in its own service, `services/thumbnailer/`, which consumes `resize` jobs
16
+ from the queue described in ADR-0002.
17
+
18
+ ## Consequences
19
+
20
+ The API never touches image bytes after upload.
@@ -0,0 +1,17 @@
1
+ ---
2
+ status: accepted
3
+ date: 2024-01-15
4
+ deciders: payments guild
5
+ ---
6
+
7
+ # Money as integer minor units
8
+
9
+ ## Context and Problem Statement
10
+
11
+ A float rounding error put a basket total out by 1p, and the refund did not match the charge.
12
+
13
+ ## Decision Outcome
14
+
15
+ Chosen option: "integer minor units". Every amount in `libs/money/` is stored and computed as an
16
+ integer count of minor units (pence, cents). "No floats and no Decimal anywhere in money code:
17
+ one representation, exact by construction."
@@ -0,0 +1,12 @@
1
+ # 5. Retry failed background jobs
2
+
3
+ Date: 2024-03-04
4
+
5
+ ## Status
6
+
7
+ Accepted
8
+
9
+ ## Decision
10
+
11
+ A failed job in `services/worker/` is retried three times, then moved to a dead-letter queue.
12
+ Retry timing follows the backoff policy in ADR-0011.
@@ -0,0 +1,18 @@
1
+ ---
2
+ status: accepted
3
+ date: 2025-02-27
4
+ deciders: marketplace team
5
+ ---
6
+
7
+ # FX amounts with four decimal places
8
+
9
+ ## Context and Problem Statement
10
+
11
+ Marketplace sellers are paid in their own currency. Converting a price in whole minor units and
12
+ then converting back loses sub-penny amounts, and seller statements drift.
13
+
14
+ ## Decision Outcome
15
+
16
+ This builds on ADR-0004. Amounts in `libs/money/` are held as `Decimal` with four decimal places,
17
+ "so that a conversion and its reverse return the original amount", and are rounded to minor units
18
+ only when charged or paid out.
@@ -0,0 +1,16 @@
1
+ # 7. Feature flags in config, not a flag service
2
+
3
+ Date: 2025-06-09
4
+
5
+ ## Context
6
+
7
+ We want to ship checkout changes dark and turn them on per region.
8
+
9
+ ## Decision
10
+
11
+ Flags live in `services/checkout/flags.py` as a plain dictionary read at start-up. No external flag
12
+ service for now.
13
+
14
+ ## Consequences
15
+
16
+ Turning a flag on needs a deploy.
@@ -0,0 +1,14 @@
1
+ ---
2
+ status: deprecated
3
+ date: 2023-06-30
4
+ ---
5
+
6
+ # Legacy search on the database
7
+
8
+ ## Context and Problem Statement
9
+
10
+ Product search runs as SQL `LIKE` queries from `services/legacy-search/`.
11
+
12
+ ## Decision Outcome
13
+
14
+ Deprecated in 2024 when search moved to the hosted search provider. Kept for history.
@@ -0,0 +1,9 @@
1
+ from decimal import Decimal
2
+
3
+ PLACES = Decimal("0.0001")
4
+
5
+
6
+ class Amount:
7
+ def __init__(self, value, currency):
8
+ self.value = Decimal(value).quantize(PLACES)
9
+ self.currency = currency
@@ -0,0 +1,5 @@
1
+ """Resize product images on upload. Consumes 'resize' jobs."""
2
+
3
+
4
+ def handle_resize(job):
5
+ raise NotImplementedError
@@ -0,0 +1,9 @@
1
+ ---
2
+ id: RFC-0001
3
+ title: How we write RFCs
4
+ status: accepted
5
+ date: 2024-01-08
6
+ ---
7
+
8
+ Design decisions are written as RFCs in `docs/rfcs/`. An RFC starts as Proposed and becomes
9
+ Accepted or Rejected after review. A replaced RFC is marked "Superseded by" its successor.
@@ -0,0 +1,16 @@
1
+ ---
2
+ id: RFC-0002
3
+ title: Payments service boundary
4
+ status: accepted
5
+ date: 2024-06-10
6
+ authors: payments team
7
+ ---
8
+
9
+ ## Decision
10
+
11
+ Everything under `services/payments/` is owned by the payments team. Changes there need a review
12
+ from a payments engineer, and no other service imports from it directly; they call its API.
13
+
14
+ ## Why
15
+
16
+ "Card data and payment state stay behind one boundary, so the audit scope stays one service wide."
@@ -0,0 +1,19 @@
1
+ # RFC-0003: Fixed-interval webhook retries
2
+
3
+ Date: 2024-08-01
4
+
5
+ ## Status
6
+
7
+ Superseded by [RFC-0006](0006-webhook-retry-backoff.md)
8
+
9
+ ## Context
10
+
11
+ Merchants miss payment events when their endpoint is briefly down.
12
+
13
+ ## Decision
14
+
15
+ `services/payments/webhooks/retry.py` retries a failed webhook every 30 seconds, up to 100 times.
16
+
17
+ ## Consequences
18
+
19
+ Simple to reason about. A merchant endpoint that is down gets a request every 30 seconds.
@@ -0,0 +1,16 @@
1
+ ---
2
+ id: RFC-0004
3
+ title: Idempotency keys on every webhook
4
+ status: accepted
5
+ date: 2024-11-19
6
+ ---
7
+
8
+ ## Decision
9
+
10
+ Every webhook sent from `services/payments/webhooks/` carries an `Idempotency-Key` header. The key
11
+ is generated once per event and "every retry of that event resends the same key, so a merchant who
12
+ receives it twice can drop the duplicate".
13
+
14
+ ## Consequences
15
+
16
+ Merchants are told in the integration guide to deduplicate on the key.
@@ -0,0 +1,16 @@
1
+ # RFC-0005: Retrying customer notifications
2
+
3
+ Date: 2025-01-27
4
+
5
+ ## Status
6
+
7
+ Accepted
8
+
9
+ ## Decision
10
+
11
+ Email and SMS sends that fail are retried by `services/notifications/retry.py` every 5 minutes for
12
+ up to 24 hours, then dropped and logged.
13
+
14
+ ## Consequences
15
+
16
+ A customer may get a receipt email up to a day late.
@@ -0,0 +1,26 @@
1
+ ---
2
+ id: RFC-0006
3
+ title: Exponential backoff for webhook retries
4
+ status: accepted
5
+ date: 2025-04-22
6
+ supersedes: RFC-0003
7
+ authors: payments team
8
+ ---
9
+
10
+ ## Context and Problem Statement
11
+
12
+ During the 2025-03-06 incident a large merchant's endpoint was down for an hour. Fixed 30-second
13
+ retries from every queued event "hammered their endpoint as it came back and knocked it over
14
+ again".
15
+
16
+ ## Decision Outcome
17
+
18
+ `services/payments/webhooks/retry.py` retries with exponential backoff: the first retry after 2
19
+ seconds, doubling each time, capped at 10 minutes between attempts, and at most 8 attempts in all.
20
+ A 4xx response is not retried, except 429. After the last attempt the event is marked failed and
21
+ the merchant can replay it from the dashboard.
22
+
23
+ ### Consequences
24
+
25
+ * Good, because a recovering endpoint gets a trickle, not a flood.
26
+ * Bad, because an event can now be given up on within about four minutes.
@@ -0,0 +1,12 @@
1
+ ---
2
+ id: RFC-0007
3
+ title: Send webhooks from the job queue
4
+ status: proposed
5
+ date: 2025-09-15
6
+ ---
7
+
8
+ ## Proposal
9
+
10
+ Move delivery out of `services/payments/webhooks/` and onto the shared job queue, so retries
11
+ survive a deploy. Open questions: who owns the queue, and whether payment events may leave the
12
+ payments boundary in RFC-0002.
@@ -0,0 +1,4 @@
1
+ """Retry failed email and SMS sends (RFC-0005)."""
2
+
3
+ INTERVAL_S = 300
4
+ GIVE_UP_AFTER_S = 24 * 60 * 60
@@ -0,0 +1,15 @@
1
+ """Webhook retry policy (RFC-0006)."""
2
+
3
+ FIRST_DELAY_S = 2
4
+ MAX_DELAY_S = 600
5
+ MAX_ATTEMPTS = 8
6
+
7
+
8
+ def should_retry(status_code, attempt):
9
+ if attempt >= MAX_ATTEMPTS:
10
+ return False
11
+ return status_code == 429 or status_code >= 500
12
+
13
+
14
+ def delay_for(attempt):
15
+ return min(FIRST_DELAY_S * 2 ** attempt, MAX_DELAY_S)
@@ -0,0 +1,19 @@
1
+ # ADR-0001: Record architecture decisions
2
+
3
+ - Status: Accepted
4
+ - Date: 2023-01-09
5
+ - Deciders: Priya Okafor, Tomasz Lind
6
+
7
+ ## Context
8
+
9
+ Ledgerline is growing past the two people who wrote it. Decisions live in Slack threads.
10
+
11
+ ## Decision
12
+
13
+ We will record significant architecture decisions as numbered Markdown files in `docs/adr/`.
14
+ A decision that replaces an earlier one says so in a `Supersedes:` line, and the earlier
15
+ record's status is changed to `Superseded by ADR-NNNN`.
16
+
17
+ ## Consequences
18
+
19
+ Every new ADR is reviewed in the same PR as the code it governs.
@@ -0,0 +1,18 @@
1
+ # ADR-0002: Postgres is the system of record
2
+
3
+ - Status: Accepted
4
+ - Date: 2023-01-16
5
+ - Deciders: Priya Okafor
6
+
7
+ ## Context
8
+
9
+ Invoices, line items and payment allocations need transactional integrity.
10
+
11
+ ## Decision
12
+
13
+ All persistent state lives in a single Postgres 15 database. Access goes through
14
+ `src/ledgerline/db/`.
15
+
16
+ ## Consequences
17
+
18
+ No second datastore is introduced without a new ADR.
@@ -0,0 +1,19 @@
1
+ # ADR-0004: Redis locks for the nightly invoice run
2
+
3
+ - Status: Accepted
4
+ - Date: 2023-05-02
5
+ - Deciders: Tomasz Lind
6
+
7
+ ## Context
8
+
9
+ We run three worker replicas. On 2023-04-27 two replicas both started the nightly invoice
10
+ run and 41 customers were billed twice.
11
+
12
+ ## Decision
13
+
14
+ The nightly run takes a Redis `SET NX PX` lock (key `ledgerline:invoice-run`, 15 minute TTL,
15
+ renewed every 60 s) before it starts. Implemented in `src/ledgerline/scheduler/redis_lock.py`.
16
+
17
+ ## Consequences
18
+
19
+ Adds Redis to the stack. This is an exception to ADR-0002, accepted for speed.
@@ -0,0 +1,27 @@
1
+ # ADR-0007: Remove Redis; use Postgres advisory locks for the invoice run
2
+
3
+ - Status: Accepted
4
+ - Date: 2024-02-20
5
+ - Deciders: Priya Okafor, Tomasz Lind, Wen Haddad
6
+ - Supersedes: ADR-0004
7
+ - Relates to: ADR-0002
8
+
9
+ ## Context
10
+
11
+ Redis is used for exactly one thing: the invoice-run lock from ADR-0004. Running it costs a
12
+ managed instance and an on-call runbook. In January 2024 a Redis failover dropped the lock
13
+ mid-run and a second replica started; the idempotency check on `invoice_runs` caught it, but
14
+ only just.
15
+
16
+ ## Decision
17
+
18
+ Remove Redis. The nightly run takes a session-level Postgres advisory lock with
19
+ `pg_try_advisory_lock(727001)` on a dedicated connection and holds it for the whole run.
20
+ Implemented in `src/ledgerline/scheduler/lock.py`.
21
+
22
+ ## Consequences
23
+
24
+ - Back to one datastore, as ADR-0002 intended.
25
+ - Advisory locks are session-scoped: the lock connection must NOT go through PgBouncer in
26
+ transaction-pooling mode, or the lock is released as soon as the transaction ends.
27
+ - If the lock-holding process dies, Postgres releases the lock when the session closes.
@@ -0,0 +1,19 @@
1
+ # ADR-0009: Store money as integer minor units
2
+
3
+ - Status: Accepted
4
+ - Date: 2024-06-03
5
+ - Deciders: Wen Haddad
6
+
7
+ ## Context
8
+
9
+ Float rounding produced one-cent mismatches between invoice totals and line-item sums.
10
+
11
+ ## Decision
12
+
13
+ Amounts are stored and computed as integers in the currency's minor unit (cents for USD/EUR).
14
+ Rounding rules for currencies with three minor digits follow ADR-0005.
15
+ Implemented in `src/ledgerline/money.py`.
16
+
17
+ ## Consequences
18
+
19
+ All API payloads carry `amount_minor` plus `currency`.
@@ -0,0 +1,12 @@
1
+ # Welcome to Ledgerline
2
+
3
+ Ledgerline generates and sends customer invoices.
4
+
5
+ ## Architecture in one page
6
+
7
+ - Postgres holds everything (ADR-0002).
8
+ - The nightly invoice run is scheduled by `src/ledgerline/scheduler/runner.py`. Only one
9
+ worker may run it at a time, so it takes a Redis lock first (see ADR-0004).
10
+ - Money is integer cents (ADR-0009).
11
+
12
+ Last reviewed: 2023-09-14
@@ -0,0 +1,22 @@
1
+ """Postgres access (ADR-0002)."""
2
+
3
+ import os
4
+ from contextlib import contextmanager
5
+
6
+ import psycopg
7
+
8
+ # DATABASE_URL goes through PgBouncer (transaction pooling); DATABASE_DIRECT_URL does not.
9
+ POOLED_URL = os.environ.get("DATABASE_URL", "postgresql://app:CHANGE_ME@pgbouncer:6432/ledgerline")
10
+ DIRECT_URL = os.environ.get("DATABASE_DIRECT_URL", "postgresql://app:CHANGE_ME@db:5432/ledgerline")
11
+
12
+
13
+ @contextmanager
14
+ def pooled_connection():
15
+ with psycopg.connect(POOLED_URL) as conn:
16
+ yield conn
17
+
18
+
19
+ @contextmanager
20
+ def direct_connection():
21
+ with psycopg.connect(DIRECT_URL, autocommit=True) as conn:
22
+ yield conn
@@ -0,0 +1,9 @@
1
+ """Invoice generation."""
2
+
3
+
4
+ def run_nightly():
5
+ """Generate invoices for every customer whose billing date is today.
6
+
7
+ Guarded by an idempotency row in `invoice_runs` keyed on the run date.
8
+ """
9
+ raise NotImplementedError("fixture")
@@ -0,0 +1,10 @@
1
+ """Money as integer minor units (ADR-0009)."""
2
+
3
+ MINOR_DIGITS = {"USD": 2, "EUR": 2, "JPY": 0}
4
+
5
+
6
+ def to_minor(amount_str: str, currency: str) -> int:
7
+ digits = MINOR_DIGITS[currency]
8
+ whole, _, frac = amount_str.partition(".")
9
+ frac = (frac + "0" * digits)[:digits]
10
+ return int(whole) * 10**digits + (int(frac) if digits else 0)
@@ -0,0 +1,23 @@
1
+ """Single-runner guard for the nightly invoice run (ADR-0007)."""
2
+
3
+ from contextlib import contextmanager
4
+
5
+ INVOICE_RUN_LOCK_KEY = 727001
6
+
7
+
8
+ @contextmanager
9
+ def invoice_run_lock(conn):
10
+ """Hold a session-level advisory lock for the duration of the run.
11
+
12
+ `conn` must be a dedicated, direct connection (not via PgBouncer transaction pooling).
13
+ Yields True if this process holds the lock, False if another worker already does.
14
+ """
15
+ with conn.cursor() as cur:
16
+ cur.execute("SELECT pg_try_advisory_lock(%s)", (INVOICE_RUN_LOCK_KEY,))
17
+ acquired = cur.fetchone()[0]
18
+ try:
19
+ yield acquired
20
+ finally:
21
+ if acquired:
22
+ with conn.cursor() as cur:
23
+ cur.execute("SELECT pg_advisory_unlock(%s)", (INVOICE_RUN_LOCK_KEY,))