skilldrop-cli 0.16.44 → 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/converters/skills/md-to-html/manifest.json +1 -1
- package/packs/converters/skills/md-to-html/scripts/md_to_html.py +3 -0
- package/packs/design/skills/brand-kit/manifest.json +1 -1
- package/packs/design/skills/brand-kit/scripts/check_brand.py +11 -1
- package/packs/dev-team/skills/behaviour-equivalence/manifest.json +1 -1
- package/packs/dev-team/skills/behaviour-equivalence/scripts/golden_diff.py +11 -1
- package/packs/frontend/skills/bundle-size-review/manifest.json +1 -1
- package/packs/frontend/skills/bundle-size-review/scripts/bundle_stats.py +13 -1
- package/packs/frontend/skills/web-performance-review/manifest.json +1 -1
- package/packs/frontend/skills/web-performance-review/scripts/lh_summary.py +12 -2
- package/packs/grc/skills/risk-register/manifest.json +1 -1
- package/packs/grc/skills/risk-register/scripts/check_register.py +9 -1
- package/packs/infra-as-code/skills/terraform-plan-review/manifest.json +1 -1
- package/packs/infra-as-code/skills/terraform-plan-review/scripts/plan_summary.py +12 -2
- package/packs/product-manager/skills/feedback-synthesis/manifest.json +1 -1
- package/packs/product-manager/skills/feedback-synthesis/scripts/theme_counts.py +6 -1
- package/packs/research/skills/hypothesis-comparison/manifest.json +1 -1
- package/packs/research/skills/hypothesis-comparison/scripts/ach_matrix.py +10 -2
- 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
- package/packs/solution-architect/skills/event-contract-draft/manifest.json +1 -1
- package/packs/solution-architect/skills/event-contract-draft/scripts/check_asyncapi.py +14 -2
- package/packs/sre-oncall/skills/cloud-cost-review/manifest.json +1 -1
- package/packs/sre-oncall/skills/cloud-cost-review/scripts/cost_summary.py +2 -0
- package/packs/trackers/skills/backlog-triage/manifest.json +1 -1
- package/packs/trackers/skills/backlog-triage/scripts/triage_backlog.py +4 -1
- package/packs/trackers/skills/team-status-report/manifest.json +1 -1
- package/packs/trackers/skills/team-status-report/scripts/status_counts.py +4 -1
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.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
---
|
|
2
|
+
status: accepted
|
|
3
|
+
date: 2026-02-10
|
|
4
|
+
---
|
|
5
|
+
# Order events on SQS
|
|
6
|
+
|
|
7
|
+
## Context
|
|
8
|
+
|
|
9
|
+
Only two consumers ever replayed, and the Kafka cluster is the team's largest on-call load.
|
|
10
|
+
|
|
11
|
+
## Decision
|
|
12
|
+
|
|
13
|
+
Supersedes ADR-0004. Order events move to SQS queues, one per consumer, published from
|
|
14
|
+
`services/orders/`. The bridge in `services/kafka-bridge/` is removed once billing has moved.
|
|
15
|
+
|
|
16
|
+
## Consequences
|
|
17
|
+
|
|
18
|
+
One fewer system to run, and the volume fits SQS. Replay for a new consumer now means a
|
|
19
|
+
backfill from the orders table.
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Worked example: why do order events go through Kafka?
|
|
2
|
+
|
|
3
|
+
A made-up repository, in [`inputs/order-events/`](inputs/order-events/), with three ADRs:
|
|
4
|
+
- **ADR-0004** chose Kafka for order events in 2025.
|
|
5
|
+
- **ADR-0006** has receipts sent when the notifications service reads an OrderPlaced event, and
|
|
6
|
+
cites ADR-0004.
|
|
7
|
+
- **ADR-0009** moved order events to SQS in 2026, and says it supersedes ADR-0004. Nobody updated
|
|
8
|
+
ADR-0004's Status line, and ADR-0009 names a bridge folder that has since been deleted.
|
|
9
|
+
|
|
10
|
+
## The request
|
|
11
|
+
|
|
12
|
+
> I'm changing `services/orders/publisher.py`. Why do we publish order events to Kafka, and is
|
|
13
|
+
> that still the plan?
|
|
14
|
+
|
|
15
|
+
## Script runs
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
python3 scripts/context_graph.py --root examples/inputs/order-events --why services/orders/publisher.py
|
|
19
|
+
python3 scripts/context_graph.py --root examples/inputs/order-events
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
The `--why` output:
|
|
23
|
+
|
|
24
|
+
```text
|
|
25
|
+
# Decisions governing services/orders/publisher.py
|
|
26
|
+
|
|
27
|
+
- **ADR-0009 Order events on SQS** (accepted, current, 2026-02-10), via `services/orders/`: docs/adr/0009-sqs-for-order-events.md
|
|
28
|
+
- **ADR-0004 Kafka for order events** (marked accepted, but superseded by ADR-0009, 2025-03-02), via `services/orders/`: docs/adr/0004-kafka-for-order-events.md
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
The full check, which exits 1 because it found problems:
|
|
32
|
+
|
|
33
|
+
```text
|
|
34
|
+
## Problems (2)
|
|
35
|
+
|
|
36
|
+
- ADR-0004 is still marked Accepted, but ADR-0009 supersedes it: mark it 'Superseded by ADR-0009'
|
|
37
|
+
- ADR-0009 is accepted but points at code that is gone: services/kafka-bridge
|
|
38
|
+
|
|
39
|
+
## Warnings (1)
|
|
40
|
+
|
|
41
|
+
- ADR-0006 is current but cites ADR-0004, which ADR-0009 supersedes: check it still holds
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## The reply
|
|
45
|
+
|
|
46
|
+
It isn't the plan any more. ADR-0009 (accepted 2026-02-10) moved order events from Kafka to SQS
|
|
47
|
+
queues, one per consumer, published from `services/orders/`. Its reasons: "only two consumers
|
|
48
|
+
ever replayed, and the Kafka cluster is the team's largest on-call load". It replaces ADR-0004,
|
|
49
|
+
which chose Kafka in 2025. ADR-0004's own Status line still says Accepted, so anyone reading it on
|
|
50
|
+
its own gets the wrong answer.
|
|
51
|
+
|
|
52
|
+
Before you change the publisher:
|
|
53
|
+
|
|
54
|
+
1. **ADR-0004 needs its status fixed.** Change `## Status` to `Superseded by ADR-0009`, linked
|
|
55
|
+
to `0009-sqs-for-order-events.md`.
|
|
56
|
+
2. **ADR-0009 names `services/kafka-bridge/`, which no longer exists.** If billing has moved, add a
|
|
57
|
+
line saying the bridge was removed. If it hasn't, find where the bridge went.
|
|
58
|
+
3. **ADR-0006 (receipts) still cites ADR-0004.** It has the notifications service read OrderPlaced
|
|
59
|
+
events, which now come from an SQS queue. Check that the receipts consumer has its queue before
|
|
60
|
+
you change what the publisher sends.
|
|
61
|
+
|
|
62
|
+
What the records don't say: when billing moved off Kafka, and whether replay for a new consumer
|
|
63
|
+
(now a backfill from the orders table, per ADR-0009) has been tested.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "context-graph",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Map the decisions behind a codebase from its ADRs, RFCs and design records — each record's status and date, what supersedes or amends what, which records cite which, the files and folders each decision governs, and with git the commits that implemented it — then answer \"why is it like this?\" from the record and check the record is still true. Finds supersessions only one side records, current decisions pointing at code that is gone, two current decisions governing the same path, and references to records that don't exist. Use when the user asks \"why do we use X\", \"which decisions govern this file or service\", \"is this ADR still current\", \"are our ADRs out of date\", \"what was decided before I change this\", wants a map of the architecture decisions, or is onboarding onto a codebase's history.",
|
|
5
|
+
"entrypoint": "SKILL.md",
|
|
6
|
+
"deps": { "npm": [], "pip": [] },
|
|
7
|
+
"env": { "required": [], "optional": [] },
|
|
8
|
+
"related": [
|
|
9
|
+
"adr-generator",
|
|
10
|
+
"codebase-tour",
|
|
11
|
+
"reverse-architecture",
|
|
12
|
+
"decision-log"
|
|
13
|
+
],
|
|
14
|
+
"handoff": [
|
|
15
|
+
{
|
|
16
|
+
"to": "adr-generator",
|
|
17
|
+
"when": "a decision is live but unrecorded, or a record needs superseding, deprecating or correcting",
|
|
18
|
+
"purpose": "Write the new or corrected record, with the supersession link the graph found missing.",
|
|
19
|
+
"fallback": "If adr-generator is absent, give the record's title, status line, the context in two sentences, the decision and its consequences, and the exact Superseded by line for the old record."
|
|
20
|
+
}
|
|
21
|
+
],
|
|
22
|
+
"tags": ["architecture", "adr", "decision-records", "onboarding", "documentation"],
|
|
23
|
+
"permissions": {
|
|
24
|
+
"network": [],
|
|
25
|
+
"commands": ["git"],
|
|
26
|
+
"files": "named-paths",
|
|
27
|
+
"notes": "context_graph.py reads the decision-record folders under --root and checks whether the paths they name exist. With --git it runs `git log` read-only. It writes only to the -o and --mermaid paths."
|
|
28
|
+
},
|
|
29
|
+
"model": {
|
|
30
|
+
"tier": "standard",
|
|
31
|
+
"rationale": "The graph and its checks are computed by the script; the model reads records, quotes the rationale and writes the fixes."
|
|
32
|
+
}
|
|
33
|
+
}
|