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.
- package/catalogue.json +1 -0
- package/model-routing.json +1 -0
- package/package.json +1 -1
- package/packs/solution-architect/skills/context-graph/SKILL.md +119 -0
- package/packs/solution-architect/skills/context-graph/evals/eval_queries.json +11 -0
- package/packs/solution-architect/skills/context-graph/evals/evals.json +225 -0
- package/packs/solution-architect/skills/context-graph/evals/files/1/docs/adr/0001-record-architecture-decisions.md +19 -0
- package/packs/solution-architect/skills/context-graph/evals/files/1/docs/adr/0002-postgres-as-primary-database.md +16 -0
- package/packs/solution-architect/skills/context-graph/evals/files/1/docs/adr/0003-use-redis-for-session-storage.md +24 -0
- package/packs/solution-architect/skills/context-graph/evals/files/1/docs/adr/0004-structured-json-logging.md +15 -0
- package/packs/solution-architect/skills/context-graph/evals/files/1/docs/adr/0005-session-lifetime-eight-hours.md +22 -0
- package/packs/solution-architect/skills/context-graph/evals/files/1/docs/adr/0006-move-sessions-to-postgres.md +16 -0
- package/packs/solution-architect/skills/context-graph/evals/files/1/docs/adr/0007-stateless-signed-session-cookies.md +35 -0
- package/packs/solution-architect/skills/context-graph/evals/files/1/services/api/__init__.py +0 -0
- package/packs/solution-architect/skills/context-graph/evals/files/1/services/api/db.py +0 -0
- package/packs/solution-architect/skills/context-graph/evals/files/1/services/api/logging_setup.py +0 -0
- package/packs/solution-architect/skills/context-graph/evals/files/1/services/auth/__init__.py +0 -0
- package/packs/solution-architect/skills/context-graph/evals/files/1/services/auth/cookie_sessions.py +7 -0
- package/packs/solution-architect/skills/context-graph/evals/files/1/services/auth/redis_sessions.py +7 -0
- package/packs/solution-architect/skills/context-graph/evals/files/10/README.md +8 -0
- package/packs/solution-architect/skills/context-graph/evals/files/10/docs/adr/0001-record-architecture-decisions.md +10 -0
- package/packs/solution-architect/skills/context-graph/evals/files/10/docs/adr/0004-service-auth-hmac-shared-keys.md +21 -0
- package/packs/solution-architect/skills/context-graph/evals/files/10/docs/adr/0007-secrets-from-vault-agent.md +19 -0
- package/packs/solution-architect/skills/context-graph/evals/files/10/docs/adr/0011-service-auth-mtls.md +23 -0
- package/packs/solution-architect/skills/context-graph/evals/files/10/services/billing-api/app.py +16 -0
- package/packs/solution-architect/skills/context-graph/evals/files/10/services/billing-api/auth/__init__.py +4 -0
- package/packs/solution-architect/skills/context-graph/evals/files/10/services/billing-api/auth/hmac_verify.py +31 -0
- package/packs/solution-architect/skills/context-graph/evals/files/10/services/billing-api/config/settings.py +14 -0
- package/packs/solution-architect/skills/context-graph/evals/files/10/services/ledger-worker/auth/__init__.py +4 -0
- package/packs/solution-architect/skills/context-graph/evals/files/10/services/ledger-worker/auth/mtls.py +18 -0
- package/packs/solution-architect/skills/context-graph/evals/files/11/README.md +4 -0
- package/packs/solution-architect/skills/context-graph/evals/files/11/app/models/base.py +3 -0
- package/packs/solution-architect/skills/context-graph/evals/files/11/app/models/line_item.py +14 -0
- package/packs/solution-architect/skills/context-graph/evals/files/11/app/models/order.py +24 -0
- package/packs/solution-architect/skills/context-graph/evals/files/11/eng/decisions/DR-003-money-as-minor-units.md +10 -0
- package/packs/solution-architect/skills/context-graph/evals/files/11/eng/decisions/DR-005-orders-typed-columns-only.md +10 -0
- package/packs/solution-architect/skills/context-graph/evals/files/11/eng/decisions/DR-009-line-items-immutable.md +11 -0
- package/packs/solution-architect/skills/context-graph/evals/files/11/eng/decisions/DR-014-optional-order-attributes-json.md +16 -0
- package/packs/solution-architect/skills/context-graph/evals/files/11/eng/decisions/DR-016-order-attributes-table.md +11 -0
- package/packs/solution-architect/skills/context-graph/evals/files/11/migrations/0007_create_orders.sql +11 -0
- package/packs/solution-architect/skills/context-graph/evals/files/11/migrations/0012_add_order_attributes.sql +2 -0
- package/packs/solution-architect/skills/context-graph/evals/files/12/README.md +8 -0
- package/packs/solution-architect/skills/context-graph/evals/files/12/config/delivery.yaml +4 -0
- package/packs/solution-architect/skills/context-graph/evals/files/12/config/export.yaml +2 -0
- package/packs/solution-architect/skills/context-graph/evals/files/12/config/partners.yaml +10 -0
- package/packs/solution-architect/skills/context-graph/evals/files/12/docs/adr/0001-record-architecture-decisions.md +10 -0
- package/packs/solution-architect/skills/context-graph/evals/files/12/docs/adr/0002-postgres-delivery-log.md +15 -0
- package/packs/solution-architect/skills/context-graph/evals/files/12/docs/adr/0003-redis-idempotency-keys.md +15 -0
- package/packs/solution-architect/skills/context-graph/evals/files/12/docs/adr/0004-retry-policy.md +16 -0
- package/packs/solution-architect/skills/context-graph/evals/files/12/docs/adr/0005-hmac-signed-payloads.md +10 -0
- package/packs/solution-architect/skills/context-graph/evals/files/12/docs/adr/0006-postgres-idempotency.md +15 -0
- package/packs/solution-architect/skills/context-graph/evals/files/12/docs/adr/0007-sftp-batch-export.md +12 -0
- package/packs/solution-architect/skills/context-graph/evals/files/12/docs/adr/0008-partner-config-in-yaml.md +12 -0
- package/packs/solution-architect/skills/context-graph/evals/files/12/docs/adr/0009-partner-config-admin-api.md +13 -0
- package/packs/solution-architect/skills/context-graph/evals/files/12/migrations/003_idempotency_keys.sql +6 -0
- package/packs/solution-architect/skills/context-graph/evals/files/12/migrations/004_partners.sql +7 -0
- package/packs/solution-architect/skills/context-graph/evals/files/12/src/delivery/dispatcher.py +29 -0
- package/packs/solution-architect/skills/context-graph/evals/files/12/src/delivery/retry.py +15 -0
- package/packs/solution-architect/skills/context-graph/evals/files/12/src/delivery/signing.py +13 -0
- package/packs/solution-architect/skills/context-graph/evals/files/12/src/export/s3_export.py +24 -0
- package/packs/solution-architect/skills/context-graph/evals/files/12/src/idempotency/pg_store.py +11 -0
- package/packs/solution-architect/skills/context-graph/evals/files/12/src/idempotency/redis_store.py +8 -0
- package/packs/solution-architect/skills/context-graph/evals/files/12/src/partners/loader.py +15 -0
- package/packs/solution-architect/skills/context-graph/evals/files/12/src/store/delivery_log.py +18 -0
- package/packs/solution-architect/skills/context-graph/evals/files/13/README.md +3 -0
- package/packs/solution-architect/skills/context-graph/evals/files/13/config/cache.yaml +2 -0
- package/packs/solution-architect/skills/context-graph/evals/files/13/config/settings.yaml +2 -0
- package/packs/solution-architect/skills/context-graph/evals/files/13/docs/decisions/RFC-008-pricing-from-erp.md +10 -0
- package/packs/solution-architect/skills/context-graph/evals/files/13/docs/decisions/RFC-012-pricing-cache.md +23 -0
- package/packs/solution-architect/skills/context-graph/evals/files/13/docs/decisions/RFC-015-checkout-price-lock.md +15 -0
- package/packs/solution-architect/skills/context-graph/evals/files/13/docs/decisions/RFC-019-pricing-service.md +17 -0
- package/packs/solution-architect/skills/context-graph/evals/files/13/shopfront/catalog/views.py +8 -0
- package/packs/solution-architect/skills/context-graph/evals/files/13/shopfront/checkout/lock.py +14 -0
- package/packs/solution-architect/skills/context-graph/evals/files/13/shopfront/pricing/cache.py +22 -0
- package/packs/solution-architect/skills/context-graph/evals/files/13/shopfront/pricing/client.py +10 -0
- package/packs/solution-architect/skills/context-graph/evals/files/14/docs/rfcs/0001-idempotency-keys.md +33 -0
- package/packs/solution-architect/skills/context-graph/evals/files/14/docs/rfcs/0002-processor-retry-policy.md +26 -0
- package/packs/solution-architect/skills/context-graph/evals/files/14/docs/rfcs/0003-money-in-minor-units.md +31 -0
- package/packs/solution-architect/skills/context-graph/evals/files/14/docs/rfcs/0004-webhook-signing.md +27 -0
- package/packs/solution-architect/skills/context-graph/evals/files/14/docs/rfcs/0005-retry-with-jitter.md +30 -0
- package/packs/solution-architect/skills/context-graph/evals/files/14/docs/rfcs/0006-idempotency-at-gateway.md +28 -0
- package/packs/solution-architect/skills/context-graph/evals/files/14/pr-summary.md +27 -0
- package/packs/solution-architect/skills/context-graph/evals/files/14/src/payments/amount.ts +10 -0
- package/packs/solution-architect/skills/context-graph/evals/files/14/src/payments/charge.ts +28 -0
- package/packs/solution-architect/skills/context-graph/evals/files/14/src/payments/idempotency.ts +4 -0
- package/packs/solution-architect/skills/context-graph/evals/files/14/src/payments/retry.ts +21 -0
- package/packs/solution-architect/skills/context-graph/evals/files/14/src/webhooks/send.ts +9 -0
- package/packs/solution-architect/skills/context-graph/evals/files/14/src/webhooks/sign.ts +6 -0
- package/packs/solution-architect/skills/context-graph/evals/files/15/deploy/services.yaml +14 -0
- package/packs/solution-architect/skills/context-graph/evals/files/15/doc/adr/0001-record-architecture-decisions.md +20 -0
- package/packs/solution-architect/skills/context-graph/evals/files/15/doc/adr/0002-use-postgresql-for-shipment-data.md +23 -0
- package/packs/solution-architect/skills/context-graph/evals/files/15/doc/adr/0003-store-vehicle-telemetry-in-cassandra.md +22 -0
- package/packs/solution-architect/skills/context-graph/evals/files/15/doc/adr/0004-use-redis-for-session-storage.md +22 -0
- package/packs/solution-architect/skills/context-graph/evals/files/15/doc/adr/0005-use-react-for-dispatcher-dashboard.md +19 -0
- package/packs/solution-architect/skills/context-graph/evals/files/15/doc/adr/0006-archive-delivered-shipments-to-object-storage.md +23 -0
- package/packs/solution-architect/skills/context-graph/evals/files/15/doc/adr/0007-move-shipment-records-to-mongodb.md +23 -0
- package/packs/solution-architect/skills/context-graph/evals/files/15/doc/adr/0008-store-vehicle-telemetry-in-timescaledb.md +28 -0
- package/packs/solution-architect/skills/context-graph/evals/files/15/doc/adr/0009-stateless-sessions-with-signed-cookies.md +24 -0
- package/packs/solution-architect/skills/context-graph/evals/files/15/services/api/session.py +17 -0
- package/packs/solution-architect/skills/context-graph/evals/files/15/services/shipments/db.py +30 -0
- package/packs/solution-architect/skills/context-graph/evals/files/15/services/shipments/migrations.sql +3 -0
- package/packs/solution-architect/skills/context-graph/evals/files/15/services/telemetry/store.py +23 -0
- package/packs/solution-architect/skills/context-graph/evals/files/15/web/dashboard/App.jsx +3 -0
- package/packs/solution-architect/skills/context-graph/evals/files/2/docs/adr/0001-record-architecture-decisions.md +11 -0
- package/packs/solution-architect/skills/context-graph/evals/files/2/docs/adr/0002-rabbitmq-for-background-jobs.md +15 -0
- package/packs/solution-architect/skills/context-graph/evals/files/2/docs/adr/0003-thumbnail-service.md +20 -0
- package/packs/solution-architect/skills/context-graph/evals/files/2/docs/adr/0004-money-as-integer-minor-units.md +17 -0
- package/packs/solution-architect/skills/context-graph/evals/files/2/docs/adr/0005-worker-retries.md +12 -0
- package/packs/solution-architect/skills/context-graph/evals/files/2/docs/adr/0006-fx-amounts-with-four-decimals.md +18 -0
- package/packs/solution-architect/skills/context-graph/evals/files/2/docs/adr/0007-feature-flags.md +16 -0
- package/packs/solution-architect/skills/context-graph/evals/files/2/docs/adr/0008-retire-legacy-search.md +14 -0
- package/packs/solution-architect/skills/context-graph/evals/files/2/libs/money/__init__.py +0 -0
- package/packs/solution-architect/skills/context-graph/evals/files/2/libs/money/amount.py +9 -0
- package/packs/solution-architect/skills/context-graph/evals/files/2/services/checkout/flags.py +1 -0
- package/packs/solution-architect/skills/context-graph/evals/files/2/services/checkout/publish.py +0 -0
- package/packs/solution-architect/skills/context-graph/evals/files/2/services/media/resize.py +5 -0
- package/packs/solution-architect/skills/context-graph/evals/files/2/services/worker/consumer.py +0 -0
- package/packs/solution-architect/skills/context-graph/evals/files/3/docs/rfcs/0001-how-we-write-rfcs.md +9 -0
- package/packs/solution-architect/skills/context-graph/evals/files/3/docs/rfcs/0002-payments-service-boundary.md +16 -0
- package/packs/solution-architect/skills/context-graph/evals/files/3/docs/rfcs/0003-webhook-retries-fixed-interval.md +19 -0
- package/packs/solution-architect/skills/context-graph/evals/files/3/docs/rfcs/0004-webhook-idempotency-keys.md +16 -0
- package/packs/solution-architect/skills/context-graph/evals/files/3/docs/rfcs/0005-notification-retries.md +16 -0
- package/packs/solution-architect/skills/context-graph/evals/files/3/docs/rfcs/0006-webhook-retry-backoff.md +26 -0
- package/packs/solution-architect/skills/context-graph/evals/files/3/docs/rfcs/0007-webhooks-on-the-job-queue.md +12 -0
- package/packs/solution-architect/skills/context-graph/evals/files/3/services/notifications/__init__.py +0 -0
- package/packs/solution-architect/skills/context-graph/evals/files/3/services/notifications/retry.py +4 -0
- package/packs/solution-architect/skills/context-graph/evals/files/3/services/payments/__init__.py +0 -0
- package/packs/solution-architect/skills/context-graph/evals/files/3/services/payments/webhooks/__init__.py +0 -0
- package/packs/solution-architect/skills/context-graph/evals/files/3/services/payments/webhooks/retry.py +15 -0
- package/packs/solution-architect/skills/context-graph/evals/files/3/services/payments/webhooks/send.py +0 -0
- package/packs/solution-architect/skills/context-graph/evals/files/4/docs/adr/0001-record-architecture-decisions.md +19 -0
- package/packs/solution-architect/skills/context-graph/evals/files/4/docs/adr/0002-postgres-as-system-of-record.md +18 -0
- package/packs/solution-architect/skills/context-graph/evals/files/4/docs/adr/0004-redis-locks-for-scheduler.md +19 -0
- package/packs/solution-architect/skills/context-graph/evals/files/4/docs/adr/0007-remove-redis.md +27 -0
- package/packs/solution-architect/skills/context-graph/evals/files/4/docs/adr/0009-money-as-integer-minor-units.md +19 -0
- package/packs/solution-architect/skills/context-graph/evals/files/4/docs/onboarding.md +12 -0
- package/packs/solution-architect/skills/context-graph/evals/files/4/src/ledgerline/__init__.py +0 -0
- package/packs/solution-architect/skills/context-graph/evals/files/4/src/ledgerline/db/__init__.py +22 -0
- package/packs/solution-architect/skills/context-graph/evals/files/4/src/ledgerline/invoicing.py +9 -0
- package/packs/solution-architect/skills/context-graph/evals/files/4/src/ledgerline/money.py +10 -0
- package/packs/solution-architect/skills/context-graph/evals/files/4/src/ledgerline/scheduler/__init__.py +0 -0
- package/packs/solution-architect/skills/context-graph/evals/files/4/src/ledgerline/scheduler/lock.py +23 -0
- package/packs/solution-architect/skills/context-graph/evals/files/4/src/ledgerline/scheduler/runner.py +17 -0
- package/packs/solution-architect/skills/context-graph/evals/files/5/lib/types/index.ts +13 -0
- package/packs/solution-architect/skills/context-graph/evals/files/5/rfcs/0001-service-layout.md +20 -0
- package/packs/solution-architect/skills/context-graph/evals/files/5/rfcs/0003-ingest-retry-policy.md +26 -0
- package/packs/solution-architect/skills/context-graph/evals/files/5/rfcs/0006-shared-retry-library.md +24 -0
- package/packs/solution-architect/skills/context-graph/evals/files/5/rfcs/0008-outbound-http-client.md +22 -0
- package/packs/solution-architect/skills/context-graph/evals/files/5/services/export/client.ts +15 -0
- package/packs/solution-architect/skills/context-graph/evals/files/5/services/export/retry.ts +12 -0
- package/packs/solution-architect/skills/context-graph/evals/files/5/services/ingest/client.ts +14 -0
- package/packs/solution-architect/skills/context-graph/evals/files/5/services/ingest/retry.ts +20 -0
- package/packs/solution-architect/skills/context-graph/evals/files/6/README.md +4 -0
- package/packs/solution-architect/skills/context-graph/evals/files/6/docs/adr/0001-monorepo-layout.md +10 -0
- package/packs/solution-architect/skills/context-graph/evals/files/6/docs/adr/0002-billing-on-postgres.md +13 -0
- package/packs/solution-architect/skills/context-graph/evals/files/6/docs/adr/0004-ledger-sync-nightly-batch.md +9 -0
- package/packs/solution-architect/skills/context-graph/evals/files/6/docs/rfcs/RFC-0003-invoice-ids.md +9 -0
- package/packs/solution-architect/skills/context-graph/evals/files/6/docs/rfcs/RFC-0007-invoice-ids-ulid.md +11 -0
- package/packs/solution-architect/skills/context-graph/evals/files/6/docs/rfcs/RFC-0009-billing-event-store.md +15 -0
- package/packs/solution-architect/skills/context-graph/evals/files/6/docs/rfcs/RFC-0010-revenue-service.md +13 -0
- package/packs/solution-architect/skills/context-graph/evals/files/6/libs/money/ulid.py +8 -0
- package/packs/solution-architect/skills/context-graph/evals/files/6/services/billing/src/events.py +10 -0
- package/packs/solution-architect/skills/context-graph/evals/files/6/services/billing/src/store.py +10 -0
- package/packs/solution-architect/skills/context-graph/evals/files/6/services/invoicing/ids/generator.py +7 -0
- package/packs/solution-architect/skills/context-graph/evals/files/6/services/ledger/README.md +4 -0
- package/packs/solution-architect/skills/context-graph/evals/files/6/services/ledger/sync.py +3 -0
- package/packs/solution-architect/skills/context-graph/evals/files/7/README.md +4 -0
- package/packs/solution-architect/skills/context-graph/evals/files/7/db/queries/orders.sql +2 -0
- package/packs/solution-architect/skills/context-graph/evals/files/7/db/sharding/router.py +9 -0
- package/packs/solution-architect/skills/context-graph/evals/files/7/design/decisions/DD-001-postgres-primary.md +8 -0
- package/packs/solution-architect/skills/context-graph/evals/files/7/design/decisions/DD-002-kafka-event-bus.md +20 -0
- package/packs/solution-architect/skills/context-graph/evals/files/7/design/decisions/DD-003-no-orm.md +9 -0
- package/packs/solution-architect/skills/context-graph/evals/files/7/design/decisions/DD-006-tenant-sharding.md +9 -0
- package/packs/solution-architect/skills/context-graph/evals/files/7/design/decisions/DD-011-orm-for-admin-console.md +11 -0
- package/packs/solution-architect/skills/context-graph/evals/files/7/pipelines/fanout/router.py +4 -0
- package/packs/solution-architect/skills/context-graph/evals/files/7/pipelines/ingest/consumer.py +5 -0
- package/packs/solution-architect/skills/context-graph/evals/files/7/pipelines/ingest/topics.yaml +8 -0
- package/packs/solution-architect/skills/context-graph/evals/files/7/rfcs/0004-region-sharding.md +15 -0
- package/packs/solution-architect/skills/context-graph/evals/files/7/services/admin-console/models.py +11 -0
- package/packs/solution-architect/skills/context-graph/evals/files/7/services/orders/repo.py +9 -0
- package/packs/solution-architect/skills/context-graph/evals/files/8/README.md +11 -0
- package/packs/solution-architect/skills/context-graph/evals/files/8/api/ledger.proto +30 -0
- package/packs/solution-architect/skills/context-graph/evals/files/8/config/http.yaml +10 -0
- package/packs/solution-architect/skills/context-graph/evals/files/8/config/messaging.yaml +8 -0
- package/packs/solution-architect/skills/context-graph/evals/files/8/docs/adr/0001-record-architecture-decisions.md +20 -0
- package/packs/solution-architect/skills/context-graph/evals/files/8/docs/adr/0002-postgresql-for-ledger.md +22 -0
- package/packs/solution-architect/skills/context-graph/evals/files/8/docs/adr/0003-rabbitmq-domain-events.md +22 -0
- package/packs/solution-architect/skills/context-graph/evals/files/8/docs/adr/0004-integer-minor-units.md +32 -0
- package/packs/solution-architect/skills/context-graph/evals/files/8/docs/adr/0005-provider-http-retries.md +26 -0
- package/packs/solution-architect/skills/context-graph/evals/files/8/docs/adr/0006-openapi-generated-clients.md +20 -0
- package/packs/solution-architect/skills/context-graph/evals/files/8/docs/adr/0007-kafka-domain-events.md +25 -0
- package/packs/solution-architect/skills/context-graph/evals/files/8/docs/adr/0008-structured-logging.md +18 -0
- package/packs/solution-architect/skills/context-graph/evals/files/8/docs/rfcs/RFC-012-idempotency-keys.md +19 -0
- package/packs/solution-architect/skills/context-graph/evals/files/8/services/ledger/api/idempotency.py +19 -0
- package/packs/solution-architect/skills/context-graph/evals/files/8/services/ledger/api/server.py +35 -0
- package/packs/solution-architect/skills/context-graph/evals/files/8/services/ledger/clients/provider.py +33 -0
- package/packs/solution-architect/skills/context-graph/evals/files/8/services/ledger/db/migrations/0001_init.sql +14 -0
- package/packs/solution-architect/skills/context-graph/evals/files/8/services/ledger/db/migrations/0008_idempotency_keys.sql +5 -0
- package/packs/solution-architect/skills/context-graph/evals/files/8/services/ledger/db/migrations/0009_fx_entries.sql +4 -0
- package/packs/solution-architect/skills/context-graph/evals/files/8/services/ledger/events/publisher.py +28 -0
- package/packs/solution-architect/skills/context-graph/evals/files/8/services/ledger/logging.py +25 -0
- package/packs/solution-architect/skills/context-graph/evals/files/8/services/ledger/models/entry.py +16 -0
- package/packs/solution-architect/skills/context-graph/evals/files/9/.env.example +3 -0
- package/packs/solution-architect/skills/context-graph/evals/files/9/README.md +5 -0
- package/packs/solution-architect/skills/context-graph/evals/files/9/docker-compose.yml +18 -0
- package/packs/solution-architect/skills/context-graph/evals/files/9/docs/decisions/001-session-storage.md +21 -0
- package/packs/solution-architect/skills/context-graph/evals/files/9/docs/decisions/002-api-rate-limiting.md +32 -0
- package/packs/solution-architect/skills/context-graph/evals/files/9/docs/decisions/003-jwt-sessions.md +29 -0
- package/packs/solution-architect/skills/context-graph/evals/files/9/docs/decisions/004-catalog-cache.md +19 -0
- package/packs/solution-architect/skills/context-graph/evals/files/9/src/catalog/cache.ts +13 -0
- package/packs/solution-architect/skills/context-graph/evals/files/9/src/catalog/repository.ts +13 -0
- package/packs/solution-architect/skills/context-graph/evals/files/9/src/jobs/email.ts +3 -0
- package/packs/solution-architect/skills/context-graph/evals/files/9/src/jobs/export.ts +3 -0
- package/packs/solution-architect/skills/context-graph/evals/files/9/src/jobs/queue.ts +10 -0
- package/packs/solution-architect/skills/context-graph/evals/files/9/src/jobs/worker.ts +8 -0
- package/packs/solution-architect/skills/context-graph/evals/files/9/src/lib/db.ts +10 -0
- package/packs/solution-architect/skills/context-graph/evals/files/9/src/lib/redis.ts +8 -0
- package/packs/solution-architect/skills/context-graph/evals/files/9/src/middleware/rateLimit.ts +26 -0
- package/packs/solution-architect/skills/context-graph/evals/files/9/src/server.ts +17 -0
- package/packs/solution-architect/skills/context-graph/evals/files/9/src/session/store.ts +10 -0
- package/packs/solution-architect/skills/context-graph/examples/inputs/order-events/docs/adr/0004-kafka-for-order-events.md +19 -0
- package/packs/solution-architect/skills/context-graph/examples/inputs/order-events/docs/adr/0006-email-receipts.md +7 -0
- package/packs/solution-architect/skills/context-graph/examples/inputs/order-events/docs/adr/0009-sqs-for-order-events.md +19 -0
- package/packs/solution-architect/skills/context-graph/examples/inputs/order-events/services/notifications/receipts.py +0 -0
- package/packs/solution-architect/skills/context-graph/examples/inputs/order-events/services/orders/publisher.py +0 -0
- package/packs/solution-architect/skills/context-graph/examples/order-events.md +63 -0
- package/packs/solution-architect/skills/context-graph/manifest.json +33 -0
- package/packs/solution-architect/skills/context-graph/reference.md +104 -0
- package/packs/solution-architect/skills/context-graph/scripts/context_graph.py +634 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: ADR-0004
|
|
3
|
+
title: Store money as integer minor units
|
|
4
|
+
status: accepted
|
|
5
|
+
date: 2024-03-05
|
|
6
|
+
deciders: [m.okafor, j.lindqvist]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Store money as integer minor units
|
|
10
|
+
|
|
11
|
+
## Context and Problem Statement
|
|
12
|
+
|
|
13
|
+
Floating point and arbitrary-precision decimals have both caused rounding bugs on previous
|
|
14
|
+
projects. How should monetary amounts be represented in the ledger?
|
|
15
|
+
|
|
16
|
+
## Considered Options
|
|
17
|
+
|
|
18
|
+
* `NUMERIC` columns with Python `Decimal`
|
|
19
|
+
* Integer minor units (cents, pence) with a separate ISO 4217 currency code
|
|
20
|
+
|
|
21
|
+
## Decision Outcome
|
|
22
|
+
|
|
23
|
+
Chosen option: "Integer minor units", because sums are exact and every currency we support
|
|
24
|
+
has a fixed minor-unit exponent.
|
|
25
|
+
|
|
26
|
+
Every amount column in `services/ledger/db/migrations/` is `BIGINT` named `*_minor`, paired
|
|
27
|
+
with a `currency CHAR(3)` column. `services/ledger/models/entry.py` exposes amounts as `int`.
|
|
28
|
+
|
|
29
|
+
### Consequences
|
|
30
|
+
|
|
31
|
+
* Good, because addition is exact and fast.
|
|
32
|
+
* Bad, because display code must apply the currency exponent.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# ADR 0005: Retry and timeout policy for payment-provider calls
|
|
2
|
+
|
|
3
|
+
* Status: Accepted
|
|
4
|
+
* Date: 2024-04-18
|
|
5
|
+
|
|
6
|
+
## Context
|
|
7
|
+
|
|
8
|
+
The payment provider occasionally returns 502/503 during their deploys. Unbounded retries
|
|
9
|
+
during an outage amplified load on them last year and held our worker threads.
|
|
10
|
+
|
|
11
|
+
## Decision
|
|
12
|
+
|
|
13
|
+
Calls from `services/ledger/clients/provider.py` to the provider:
|
|
14
|
+
|
|
15
|
+
| Setting | Value |
|
|
16
|
+
|--------------------|-----------------------------------------|
|
|
17
|
+
| Request timeout | 5 seconds |
|
|
18
|
+
| Max retries | 3 |
|
|
19
|
+
| Backoff | exponential, 200 ms base, full jitter |
|
|
20
|
+
| Retry on | GET and idempotent POST (with key) only |
|
|
21
|
+
|
|
22
|
+
Values are read from `config/http.yaml`; the code must not hard-code them.
|
|
23
|
+
|
|
24
|
+
## Consequences
|
|
25
|
+
|
|
26
|
+
Worst-case latency of a provider call is bounded at roughly 21 seconds.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# 6. Generate service clients from an OpenAPI spec
|
|
2
|
+
|
|
3
|
+
Date: 2024-06-03
|
|
4
|
+
|
|
5
|
+
## Status
|
|
6
|
+
|
|
7
|
+
Accepted
|
|
8
|
+
|
|
9
|
+
## Context
|
|
10
|
+
|
|
11
|
+
Three internal teams call the ledger and each hand-wrote a client.
|
|
12
|
+
|
|
13
|
+
## Decision
|
|
14
|
+
|
|
15
|
+
The ledger's public interface is defined in `api/openapi.yaml`. Clients are generated from it
|
|
16
|
+
into `clients/generated/` on every release; hand-written clients are not accepted.
|
|
17
|
+
|
|
18
|
+
## Consequences
|
|
19
|
+
|
|
20
|
+
- The spec is the source of truth; the server is checked against it in CI.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: ADR-0007
|
|
3
|
+
title: Move domain events to Kafka
|
|
4
|
+
status: accepted
|
|
5
|
+
date: 2025-03-11
|
|
6
|
+
supersedes: ADR-0003
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Move domain events to Kafka
|
|
10
|
+
|
|
11
|
+
## Context and Problem Statement
|
|
12
|
+
|
|
13
|
+
Reporting now needs to replay the full history of posted entries, which RabbitMQ queues
|
|
14
|
+
cannot give us once a message is acknowledged.
|
|
15
|
+
|
|
16
|
+
## Decision Outcome
|
|
17
|
+
|
|
18
|
+
Domain events are published to the Kafka topic `ledger.entry-events`, keyed by account id,
|
|
19
|
+
with 30-day retention. This supersedes ADR-0003. Publishing stays in
|
|
20
|
+
`services/ledger/events/`; broker settings stay in `config/messaging.yaml`.
|
|
21
|
+
|
|
22
|
+
### Consequences
|
|
23
|
+
|
|
24
|
+
* Good, because consumers can replay from any offset within retention.
|
|
25
|
+
* Bad, because the team now operates a Kafka cluster.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# ADR 0008: Structured JSON logging
|
|
2
|
+
|
|
3
|
+
* Status: Accepted
|
|
4
|
+
* Date: 2025-05-20
|
|
5
|
+
|
|
6
|
+
## Context
|
|
7
|
+
|
|
8
|
+
Log search across services is painful because each one logs free text differently.
|
|
9
|
+
|
|
10
|
+
## Decision
|
|
11
|
+
|
|
12
|
+
All ledger logging goes through `services/ledger/logging.py`, which emits one JSON object per
|
|
13
|
+
line with `ts`, `level`, `msg`, `trace_id` and `account_id` fields. The `trace_id` comes from
|
|
14
|
+
the tracing setup described in ADR-0011.
|
|
15
|
+
|
|
16
|
+
## Consequences
|
|
17
|
+
|
|
18
|
+
Plain `print` and ad-hoc `logging.basicConfig` calls are not allowed in service code.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# RFC-012: Idempotency keys for posting entries
|
|
2
|
+
|
|
3
|
+
- **State:** Draft
|
|
4
|
+
- **Author:** j.lindqvist
|
|
5
|
+
- **Opened:** 2025-08-02
|
|
6
|
+
|
|
7
|
+
## Summary
|
|
8
|
+
|
|
9
|
+
Clients send an `Idempotency-Key` with every `PostEntry` call. The ledger stores the key and
|
|
10
|
+
the response for 24 hours and returns the stored response on a repeat.
|
|
11
|
+
|
|
12
|
+
## Proposed implementation
|
|
13
|
+
|
|
14
|
+
`services/ledger/api/idempotency.py`, backed by a table `idempotency_keys` in PostgreSQL.
|
|
15
|
+
|
|
16
|
+
## Open questions
|
|
17
|
+
|
|
18
|
+
- Is 24 hours long enough for the batch importer, which can retry after a day?
|
|
19
|
+
- Should a key reused with a different body be a 409 or a 422?
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from datetime import timedelta
|
|
3
|
+
|
|
4
|
+
KEY_TTL = timedelta(hours=24)
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def lookup(conn, key: str):
|
|
8
|
+
row = conn.execute(
|
|
9
|
+
"SELECT response FROM idempotency_keys WHERE key = %s AND created_at > now() - %s",
|
|
10
|
+
(key, KEY_TTL),
|
|
11
|
+
).fetchone()
|
|
12
|
+
return json.loads(row[0]) if row else None
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def store(conn, key: str, response: dict) -> None:
|
|
16
|
+
conn.execute(
|
|
17
|
+
"INSERT INTO idempotency_keys (key, response) VALUES (%s, %s) ON CONFLICT DO NOTHING",
|
|
18
|
+
(key, json.dumps(response)),
|
|
19
|
+
)
|
package/packs/solution-architect/skills/context-graph/evals/files/8/services/ledger/api/server.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from concurrent import futures
|
|
2
|
+
|
|
3
|
+
import grpc
|
|
4
|
+
|
|
5
|
+
from services.ledger.api import idempotency
|
|
6
|
+
from services.ledger.api.gen import ledger_pb2, ledger_pb2_grpc
|
|
7
|
+
from services.ledger.logging import get_logger
|
|
8
|
+
|
|
9
|
+
log = get_logger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class LedgerServicer(ledger_pb2_grpc.LedgerServicer):
|
|
13
|
+
def __init__(self, db):
|
|
14
|
+
self.db = db
|
|
15
|
+
|
|
16
|
+
def PostEntry(self, request, context):
|
|
17
|
+
with self.db.transaction() as conn:
|
|
18
|
+
cached = idempotency.lookup(conn, request.idempotency_key)
|
|
19
|
+
if cached:
|
|
20
|
+
return ledger_pb2.PostEntryResponse(**cached)
|
|
21
|
+
entry_id = self.db.post_entry(conn, request)
|
|
22
|
+
idempotency.store(conn, request.idempotency_key, {"entry_id": entry_id})
|
|
23
|
+
return ledger_pb2.PostEntryResponse(entry_id=entry_id)
|
|
24
|
+
|
|
25
|
+
def GetBalance(self, request, context):
|
|
26
|
+
return self.db.balance(request.account_id)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def serve(db, port: int = 50051) -> None:
|
|
30
|
+
server = grpc.server(futures.ThreadPoolExecutor(max_workers=16))
|
|
31
|
+
ledger_pb2_grpc.add_LedgerServicer_to_server(LedgerServicer(db), server)
|
|
32
|
+
server.add_insecure_port(f"[::]:{port}")
|
|
33
|
+
server.start()
|
|
34
|
+
log.info("ledger gRPC server started")
|
|
35
|
+
server.wait_for_termination()
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import random
|
|
2
|
+
import time
|
|
3
|
+
|
|
4
|
+
import httpx
|
|
5
|
+
import yaml
|
|
6
|
+
|
|
7
|
+
with open("config/http.yaml") as fh:
|
|
8
|
+
_cfg = yaml.safe_load(fh)["provider"]
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _sleep(attempt: int) -> None:
|
|
12
|
+
base = _cfg["backoff"]["base_ms"] / 1000
|
|
13
|
+
time.sleep(random.uniform(0, base * (2 ** attempt)))
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def call(method: str, path: str, **kwargs) -> httpx.Response:
|
|
17
|
+
retries = _cfg["max_retries"] if method in _cfg["retry_methods"] else 0
|
|
18
|
+
for attempt in range(retries + 1):
|
|
19
|
+
try:
|
|
20
|
+
resp = httpx.request(
|
|
21
|
+
method,
|
|
22
|
+
_cfg["base_url"] + path,
|
|
23
|
+
timeout=_cfg["timeout_seconds"],
|
|
24
|
+
headers={"Authorization": f"Bearer {_cfg['api_key']}"},
|
|
25
|
+
**kwargs,
|
|
26
|
+
)
|
|
27
|
+
if resp.status_code not in (502, 503):
|
|
28
|
+
return resp
|
|
29
|
+
except httpx.TransportError:
|
|
30
|
+
if attempt == retries:
|
|
31
|
+
raise
|
|
32
|
+
_sleep(attempt)
|
|
33
|
+
return resp
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
CREATE TABLE accounts (
|
|
2
|
+
id UUID PRIMARY KEY,
|
|
3
|
+
currency CHAR(3) NOT NULL,
|
|
4
|
+
balance_minor BIGINT NOT NULL DEFAULT 0,
|
|
5
|
+
frozen BOOLEAN NOT NULL DEFAULT FALSE
|
|
6
|
+
);
|
|
7
|
+
|
|
8
|
+
CREATE TABLE entries (
|
|
9
|
+
id UUID PRIMARY KEY,
|
|
10
|
+
account_id UUID NOT NULL REFERENCES accounts(id),
|
|
11
|
+
amount_minor BIGINT NOT NULL,
|
|
12
|
+
currency CHAR(3) NOT NULL,
|
|
13
|
+
posted_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
14
|
+
);
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
import yaml
|
|
4
|
+
from confluent_kafka import Producer
|
|
5
|
+
|
|
6
|
+
from services.ledger.logging import get_logger
|
|
7
|
+
|
|
8
|
+
log = get_logger(__name__)
|
|
9
|
+
|
|
10
|
+
with open("config/messaging.yaml") as fh:
|
|
11
|
+
_cfg = yaml.safe_load(fh)
|
|
12
|
+
|
|
13
|
+
_producer = Producer({
|
|
14
|
+
"bootstrap.servers": ",".join(_cfg["bootstrap_servers"]),
|
|
15
|
+
"acks": _cfg["acks"],
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def publish_entry_posted(entry) -> None:
|
|
20
|
+
payload = {
|
|
21
|
+
"type": "EntryPosted",
|
|
22
|
+
"entry_id": entry.id,
|
|
23
|
+
"account_id": entry.account_id,
|
|
24
|
+
"amount_minor": entry.amount_minor,
|
|
25
|
+
"currency": entry.currency,
|
|
26
|
+
}
|
|
27
|
+
_producer.produce(_cfg["topic"], key=entry.account_id, value=json.dumps(payload))
|
|
28
|
+
log.info("event published", extra={"account_id": entry.account_id})
|
package/packs/solution-architect/skills/context-graph/evals/files/8/services/ledger/logging.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import logging
|
|
3
|
+
import sys
|
|
4
|
+
from datetime import datetime, timezone
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class _JsonFormatter(logging.Formatter):
|
|
8
|
+
def format(self, record: logging.LogRecord) -> str:
|
|
9
|
+
return json.dumps({
|
|
10
|
+
"ts": datetime.now(timezone.utc).isoformat(),
|
|
11
|
+
"level": record.levelname,
|
|
12
|
+
"msg": record.getMessage(),
|
|
13
|
+
"trace_id": getattr(record, "trace_id", None),
|
|
14
|
+
"account_id": getattr(record, "account_id", None),
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def get_logger(name: str) -> logging.Logger:
|
|
19
|
+
logger = logging.getLogger(name)
|
|
20
|
+
if not logger.handlers:
|
|
21
|
+
handler = logging.StreamHandler(sys.stdout)
|
|
22
|
+
handler.setFormatter(_JsonFormatter())
|
|
23
|
+
logger.addHandler(handler)
|
|
24
|
+
logger.setLevel(logging.INFO)
|
|
25
|
+
return logger
|
package/packs/solution-architect/skills/context-graph/evals/files/8/services/ledger/models/entry.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from decimal import Decimal
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass(frozen=True)
|
|
8
|
+
class Entry:
|
|
9
|
+
id: str
|
|
10
|
+
account_id: str
|
|
11
|
+
amount_minor: int
|
|
12
|
+
currency: str
|
|
13
|
+
posted_at: datetime
|
|
14
|
+
fx_amount: Optional[Decimal] = None
|
|
15
|
+
fx_currency: Optional[str] = None
|
|
16
|
+
fx_rate: Optional[Decimal] = None
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
services:
|
|
2
|
+
app:
|
|
3
|
+
build: .
|
|
4
|
+
ports: ["3000:3000"]
|
|
5
|
+
env_file: .env
|
|
6
|
+
depends_on: [postgres, redis]
|
|
7
|
+
worker:
|
|
8
|
+
build: .
|
|
9
|
+
command: ["node", "dist/jobs/worker.js"]
|
|
10
|
+
env_file: .env
|
|
11
|
+
depends_on: [redis]
|
|
12
|
+
postgres:
|
|
13
|
+
image: postgres:16
|
|
14
|
+
environment:
|
|
15
|
+
POSTGRES_PASSWORD: example-only
|
|
16
|
+
redis:
|
|
17
|
+
image: redis:7
|
|
18
|
+
ports: ["6379:6379"]
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# 001. Store user sessions in Redis
|
|
2
|
+
|
|
3
|
+
Date: 2023-05-09
|
|
4
|
+
|
|
5
|
+
## Status
|
|
6
|
+
|
|
7
|
+
Superseded by [003](003-jwt-sessions.md)
|
|
8
|
+
|
|
9
|
+
## Context
|
|
10
|
+
|
|
11
|
+
We run three app containers behind the load balancer. In-memory sessions log users out
|
|
12
|
+
whenever a request lands on a different container.
|
|
13
|
+
|
|
14
|
+
## Decision
|
|
15
|
+
|
|
16
|
+
Sessions are stored in Redis via `connect-redis`, configured in `src/session/store.ts`,
|
|
17
|
+
with a 7-day TTL.
|
|
18
|
+
|
|
19
|
+
## Consequences
|
|
20
|
+
|
|
21
|
+
Redis becomes a hard dependency of login.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
---
|
|
2
|
+
status: accepted
|
|
3
|
+
date: 2023-11-20
|
|
4
|
+
deciders: platform team
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Rate-limit the public API with a shared token bucket
|
|
8
|
+
|
|
9
|
+
## Context and Problem Statement
|
|
10
|
+
|
|
11
|
+
A partner's misconfigured sync job hit `/v1/products` 40 times a second for an hour. We need
|
|
12
|
+
per-API-key limits that hold across all app containers.
|
|
13
|
+
|
|
14
|
+
## Considered Options
|
|
15
|
+
|
|
16
|
+
* Per-container in-memory limiter
|
|
17
|
+
* Token bucket in Redis, shared by all containers
|
|
18
|
+
* Limit at the CDN edge
|
|
19
|
+
|
|
20
|
+
## Decision Outcome
|
|
21
|
+
|
|
22
|
+
Chosen option: "Token bucket in Redis", because limits must be global and the CDN plan we
|
|
23
|
+
are on cannot key on API key.
|
|
24
|
+
|
|
25
|
+
Implemented in `src/middleware/rateLimit.ts`: 600 requests per minute per API key, using a
|
|
26
|
+
Lua script so refill-and-take is atomic. Redis availability and failover are covered in
|
|
27
|
+
DR-007.
|
|
28
|
+
|
|
29
|
+
### Consequences
|
|
30
|
+
|
|
31
|
+
* Good, because limits are exact across containers.
|
|
32
|
+
* Bad, because if Redis is down the limiter fails open.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# RFC 003 — Stateless JWT sessions
|
|
2
|
+
|
|
3
|
+
| Field | Value |
|
|
4
|
+
|---------|------------|
|
|
5
|
+
| State | Proposed |
|
|
6
|
+
| Author | r.achebe |
|
|
7
|
+
| Created | 2026-09-02 |
|
|
8
|
+
| Replaces | 001 |
|
|
9
|
+
|
|
10
|
+
## Motivation
|
|
11
|
+
|
|
12
|
+
Mobile clients want a bearer token rather than a cookie, and we would like login to keep
|
|
13
|
+
working during a Redis restart.
|
|
14
|
+
|
|
15
|
+
## Proposal
|
|
16
|
+
|
|
17
|
+
Issue a signed access token (15 minutes) and a refresh token (7 days). The app verifies the
|
|
18
|
+
signature locally; no session lookup per request.
|
|
19
|
+
|
|
20
|
+
## Open questions
|
|
21
|
+
|
|
22
|
+
1. **Revocation.** Logout and "sign out everywhere" need to invalidate tokens before they
|
|
23
|
+
expire. Current thinking: keep a denylist of revoked token ids in Redis with a TTL equal to
|
|
24
|
+
the token's remaining lifetime.
|
|
25
|
+
2. Key rotation schedule — needs a decision before this is accepted.
|
|
26
|
+
|
|
27
|
+
## Status of discussion
|
|
28
|
+
|
|
29
|
+
Not yet reviewed by the platform team.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# 004. Cache the product catalogue in process
|
|
2
|
+
|
|
3
|
+
- Status: Accepted
|
|
4
|
+
- Date: 2024-02-27
|
|
5
|
+
|
|
6
|
+
## Context
|
|
7
|
+
|
|
8
|
+
Catalogue reads are about 70% of API traffic, and the catalogue changes a few times a day.
|
|
9
|
+
|
|
10
|
+
## Decision
|
|
11
|
+
|
|
12
|
+
Each app container holds the catalogue in an in-process LRU cache (`lru-cache`, 5,000 items,
|
|
13
|
+
10-minute TTL) in `src/catalog/cache.ts`. We deliberately do **not** put the catalogue in
|
|
14
|
+
Redis: it adds a network hop to the hottest path for data that is fine to be a few minutes
|
|
15
|
+
stale per container.
|
|
16
|
+
|
|
17
|
+
## Consequences
|
|
18
|
+
|
|
19
|
+
Containers may briefly disagree after a catalogue update. That is acceptable.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { redis } from "../lib/redis";
|
|
2
|
+
import { loadProduct, type Product } from "./repository";
|
|
3
|
+
|
|
4
|
+
const TTL_SECONDS = 600;
|
|
5
|
+
|
|
6
|
+
// Moved to Redis so all containers see catalogue updates at once (see #482).
|
|
7
|
+
export async function getProduct(id: string): Promise<Product | null> {
|
|
8
|
+
const hit = await redis.get(`catalog:${id}`);
|
|
9
|
+
if (hit) return JSON.parse(hit) as Product;
|
|
10
|
+
const product = await loadProduct(id);
|
|
11
|
+
if (product) await redis.set(`catalog:${id}`, JSON.stringify(product), { EX: TTL_SECONDS });
|
|
12
|
+
return product;
|
|
13
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { db } from "../lib/db";
|
|
2
|
+
|
|
3
|
+
export interface Product {
|
|
4
|
+
id: string;
|
|
5
|
+
name: string;
|
|
6
|
+
priceCents: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export async function loadProduct(id: string): Promise<Product | null> {
|
|
10
|
+
const rows = await db.query("SELECT id, name, price_cents FROM products WHERE id = $1", [id]);
|
|
11
|
+
if (rows.length === 0) return null;
|
|
12
|
+
return { id: rows[0].id, name: rows[0].name, priceCents: rows[0].price_cents };
|
|
13
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Queue } from "bullmq";
|
|
2
|
+
|
|
3
|
+
const connection = { url: process.env.REDIS_URL };
|
|
4
|
+
|
|
5
|
+
export const emailQueue = new Queue("email", { connection });
|
|
6
|
+
export const orderExportQueue = new Queue("order-export", { connection });
|
|
7
|
+
|
|
8
|
+
export async function enqueueOrderConfirmation(orderId: string, to: string) {
|
|
9
|
+
await emailQueue.add("order-confirmation", { orderId, to }, { attempts: 5 });
|
|
10
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Worker } from "bullmq";
|
|
2
|
+
import { sendEmail } from "./email";
|
|
3
|
+
import { exportOrder } from "./export";
|
|
4
|
+
|
|
5
|
+
const connection = { url: process.env.REDIS_URL };
|
|
6
|
+
|
|
7
|
+
new Worker("email", async (job) => sendEmail(job.data), { connection });
|
|
8
|
+
new Worker("order-export", async (job) => exportOrder(job.data), { connection });
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Pool } from "pg";
|
|
2
|
+
|
|
3
|
+
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
|
|
4
|
+
|
|
5
|
+
export const db = {
|
|
6
|
+
async query(text: string, params: unknown[] = []) {
|
|
7
|
+
const result = await pool.query(text, params);
|
|
8
|
+
return result.rows;
|
|
9
|
+
},
|
|
10
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { createClient } from "redis";
|
|
2
|
+
|
|
3
|
+
export const redis = createClient({ url: process.env.REDIS_URL });
|
|
4
|
+
redis.on("error", (err) => console.error("redis error", err));
|
|
5
|
+
|
|
6
|
+
export async function connectRedis(): Promise<void> {
|
|
7
|
+
if (!redis.isOpen) await redis.connect();
|
|
8
|
+
}
|
package/packs/solution-architect/skills/context-graph/evals/files/9/src/middleware/rateLimit.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Request, Response, NextFunction } from "express";
|
|
2
|
+
import { redis } from "../lib/redis";
|
|
3
|
+
|
|
4
|
+
const LIMIT_PER_MINUTE = 600;
|
|
5
|
+
|
|
6
|
+
const TAKE_TOKEN = `
|
|
7
|
+
local tokens = tonumber(redis.call('GET', KEYS[1]) or ARGV[1])
|
|
8
|
+
if tokens <= 0 then return 0 end
|
|
9
|
+
redis.call('SET', KEYS[1], tokens - 1, 'EX', 60, 'KEEPTTL')
|
|
10
|
+
return 1
|
|
11
|
+
`;
|
|
12
|
+
|
|
13
|
+
export async function rateLimit(req: Request, res: Response, next: NextFunction) {
|
|
14
|
+
const apiKey = req.header("x-api-key");
|
|
15
|
+
if (!apiKey) return next();
|
|
16
|
+
try {
|
|
17
|
+
const ok = await redis.eval(TAKE_TOKEN, {
|
|
18
|
+
keys: [`rl:${apiKey}`],
|
|
19
|
+
arguments: [String(LIMIT_PER_MINUTE)],
|
|
20
|
+
});
|
|
21
|
+
if (ok === 0) return res.status(429).json({ error: "rate_limited" });
|
|
22
|
+
} catch {
|
|
23
|
+
// fail open, per decision 002
|
|
24
|
+
}
|
|
25
|
+
next();
|
|
26
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import express from "express";
|
|
2
|
+
import { connectRedis } from "./lib/redis";
|
|
3
|
+
import { sessionMiddleware } from "./session/store";
|
|
4
|
+
import { rateLimit } from "./middleware/rateLimit";
|
|
5
|
+
import { getProduct } from "./catalog/cache";
|
|
6
|
+
|
|
7
|
+
const app = express();
|
|
8
|
+
app.use(sessionMiddleware);
|
|
9
|
+
app.use("/v1", rateLimit);
|
|
10
|
+
|
|
11
|
+
app.get("/v1/products/:id", async (req, res) => {
|
|
12
|
+
const product = await getProduct(req.params.id);
|
|
13
|
+
if (!product) return res.status(404).end();
|
|
14
|
+
res.json(product);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
connectRedis().then(() => app.listen(3000));
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import session from "express-session";
|
|
2
|
+
import RedisStore from "connect-redis";
|
|
3
|
+
import { redis } from "../lib/redis";
|
|
4
|
+
|
|
5
|
+
export const sessionMiddleware = session({
|
|
6
|
+
store: new RedisStore({ client: redis, prefix: "sess:", ttl: 60 * 60 * 24 * 7 }),
|
|
7
|
+
secret: process.env.SESSION_SECRET ?? "",
|
|
8
|
+
resave: false,
|
|
9
|
+
saveUninitialized: false,
|
|
10
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# ADR-0004: Kafka for order events
|
|
2
|
+
|
|
3
|
+
## Status
|
|
4
|
+
|
|
5
|
+
Accepted
|
|
6
|
+
|
|
7
|
+
Date: 2025-03-02
|
|
8
|
+
|
|
9
|
+
## Context
|
|
10
|
+
|
|
11
|
+
Order events feed billing, notifications and the warehouse. We need replay for new consumers.
|
|
12
|
+
|
|
13
|
+
## Decision
|
|
14
|
+
|
|
15
|
+
Order events are published to Kafka from `services/orders/`, one topic per event type.
|
|
16
|
+
|
|
17
|
+
## Consequences
|
|
18
|
+
|
|
19
|
+
Replay works for a new consumer. We run a Kafka cluster.
|