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
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""Check the peer certificate's SPIFFE ID on internal requests (see ADR-0011)."""
|
|
2
|
+
from functools import wraps
|
|
3
|
+
|
|
4
|
+
ALLOWED_PEERS = {
|
|
5
|
+
"spiffe://tallowmere.internal/billing-api",
|
|
6
|
+
}
|
|
7
|
+
CA_BUNDLE = "/etc/ironbark/ca-bundle.pem"
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def require_peer_identity(handler):
|
|
11
|
+
@wraps(handler)
|
|
12
|
+
def wrapper(event, peer_cert, *args, **kwargs):
|
|
13
|
+
spiffe_id = peer_cert.get("uri_san", "")
|
|
14
|
+
if spiffe_id not in ALLOWED_PEERS:
|
|
15
|
+
raise PermissionError(f"peer {spiffe_id!r} not allowed")
|
|
16
|
+
return handler(event, peer_cert, *args, **kwargs)
|
|
17
|
+
|
|
18
|
+
return wrapper
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Line item model. Immutable once the parent order is placed (DR-009)."""
|
|
2
|
+
from sqlalchemy import BigInteger, Column, ForeignKey, Integer, String
|
|
3
|
+
|
|
4
|
+
from .base import Base
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class LineItem(Base):
|
|
8
|
+
__tablename__ = "line_items"
|
|
9
|
+
|
|
10
|
+
id = Column(BigInteger, primary_key=True)
|
|
11
|
+
order_id = Column(BigInteger, ForeignKey("orders.id"), nullable=False, index=True)
|
|
12
|
+
sku = Column(String(32), nullable=False)
|
|
13
|
+
quantity = Column(Integer, nullable=False)
|
|
14
|
+
unit_price_minor = Column(Integer, nullable=False)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Order model."""
|
|
2
|
+
from sqlalchemy import JSON, BigInteger, Column, DateTime, Integer, String
|
|
3
|
+
|
|
4
|
+
from .base import Base
|
|
5
|
+
|
|
6
|
+
# Keys allowed in Order.attributes (DR-014). Review additions like a column.
|
|
7
|
+
ORDER_ATTRIBUTE_KEYS = {
|
|
8
|
+
"delivery_note",
|
|
9
|
+
"leave_with_neighbour",
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Order(Base):
|
|
14
|
+
__tablename__ = "orders"
|
|
15
|
+
|
|
16
|
+
id = Column(BigInteger, primary_key=True)
|
|
17
|
+
customer_id = Column(BigInteger, nullable=False, index=True)
|
|
18
|
+
status = Column(String(16), nullable=False, default="draft")
|
|
19
|
+
currency = Column(String(3), nullable=False)
|
|
20
|
+
subtotal_minor = Column(Integer, nullable=False)
|
|
21
|
+
shipping_minor = Column(Integer, nullable=False, default=0)
|
|
22
|
+
total_minor = Column(Integer, nullable=False)
|
|
23
|
+
placed_at = Column(DateTime(timezone=True))
|
|
24
|
+
attributes = Column(JSON, nullable=False, default=dict)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: DR-003
|
|
3
|
+
title: Store money as integer minor units with a currency code
|
|
4
|
+
status: Accepted
|
|
5
|
+
date: 2022-02-17
|
|
6
|
+
governs: [app/models/order.py, app/models/line_item.py]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
All monetary amounts are stored as integer minor units (`*_minor`, e.g. pence) alongside a
|
|
10
|
+
three-letter `currency` column. No floats or decimals for money anywhere in the schema.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: DR-005
|
|
3
|
+
title: Orders table uses typed columns only
|
|
4
|
+
status: Accepted
|
|
5
|
+
date: 2022-04-11
|
|
6
|
+
governs: [app/models/order.py, migrations/]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
Every field on an order is a typed, named column with a migration. We do not store JSON blobs
|
|
10
|
+
on the orders table: they hide schema changes from review and cannot be indexed.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: DR-009
|
|
3
|
+
title: Order line items are immutable once the order is placed
|
|
4
|
+
status: Accepted
|
|
5
|
+
date: 2023-01-30
|
|
6
|
+
governs: [app/models/line_item.py]
|
|
7
|
+
cites: [DR-003]
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
After an order moves to `placed`, its line items are never updated. Price or quantity
|
|
11
|
+
corrections are written as rows in `order_adjustments`, amounts in minor units per DR-003.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: DR-014
|
|
3
|
+
title: Optional customer-supplied order fields go in orders.attributes
|
|
4
|
+
status: Accepted
|
|
5
|
+
date: 2024-02-08
|
|
6
|
+
amends: DR-005
|
|
7
|
+
governs: [app/models/order.py, migrations/0012_add_order_attributes.sql]
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
DR-005's typed-columns rule produced a migration for every small checkout tweak. We relax it:
|
|
11
|
+
|
|
12
|
+
- New **optional, customer-supplied** fields that are not filtered, joined or reported on
|
|
13
|
+
(delivery notes, gift options, and similar) are stored as keys in the `orders.attributes`
|
|
14
|
+
JSON column. Each key is listed in `ORDER_ATTRIBUTE_KEYS` in `app/models/order.py` and is
|
|
15
|
+
reviewed like a column.
|
|
16
|
+
- Anything we filter, join, index or report on stays a typed column, as DR-005 says.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: DR-016
|
|
3
|
+
title: Move order attributes into a separate order_attributes table
|
|
4
|
+
status: Proposed
|
|
5
|
+
date: 2026-09-30
|
|
6
|
+
cites: [DR-014]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
Proposal (under discussion, not agreed): replace the `orders.attributes` JSON column from
|
|
10
|
+
DR-014 with an `order_attributes(order_id, key, value)` table so attributes can be indexed.
|
|
11
|
+
Open question: whether existing keys are back-filled or dual-written.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
CREATE TABLE orders (
|
|
2
|
+
id BIGINT PRIMARY KEY,
|
|
3
|
+
customer_id BIGINT NOT NULL,
|
|
4
|
+
status VARCHAR(16) NOT NULL DEFAULT 'draft',
|
|
5
|
+
currency CHAR(3) NOT NULL,
|
|
6
|
+
subtotal_minor INTEGER NOT NULL,
|
|
7
|
+
shipping_minor INTEGER NOT NULL DEFAULT 0,
|
|
8
|
+
total_minor INTEGER NOT NULL,
|
|
9
|
+
placed_at TIMESTAMPTZ
|
|
10
|
+
);
|
|
11
|
+
CREATE INDEX orders_customer_id_idx ON orders (customer_id);
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# parcelhook
|
|
2
|
+
|
|
3
|
+
Sends shipment-status webhooks (picked_up, in_transit, delivered, exception) from the
|
|
4
|
+
Corvane Freight tracking platform to partner endpoints. Owned by Platform Integrations.
|
|
5
|
+
|
|
6
|
+
- Entry point: `src/delivery/dispatcher.py`
|
|
7
|
+
- Decisions: `docs/adr/`
|
|
8
|
+
- Config: `config/`
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Partner configuration (ADR-0008). Loaded at startup.
|
|
2
|
+
partners:
|
|
3
|
+
- id: velo
|
|
4
|
+
url: https://hooks.velo.example/parcel
|
|
5
|
+
events: [picked_up, delivered, exception]
|
|
6
|
+
secret_ref: vault://parcelhook/partners/velo
|
|
7
|
+
- id: northwind-retail
|
|
8
|
+
url: https://api.northwind.example/webhooks/shipments
|
|
9
|
+
events: [delivered]
|
|
10
|
+
secret_ref: vault://parcelhook/partners/northwind-retail
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# ADR-0001: Record architecture decisions
|
|
2
|
+
|
|
3
|
+
- Status: Accepted
|
|
4
|
+
- Date: 2022-03-01
|
|
5
|
+
- Deciders: Platform Integrations
|
|
6
|
+
|
|
7
|
+
## Decision
|
|
8
|
+
We record significant decisions about parcelhook as numbered ADRs in `docs/adr/`.
|
|
9
|
+
A record that replaces another says "Supersedes ADR-NNNN", and the replaced record's
|
|
10
|
+
status is changed to "Superseded by ADR-MMMM".
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# ADR-0002: Postgres for the delivery log
|
|
2
|
+
|
|
3
|
+
- Status: Accepted
|
|
4
|
+
- Date: 2022-03-10
|
|
5
|
+
- Governs: `src/store/`
|
|
6
|
+
|
|
7
|
+
## Context
|
|
8
|
+
Partners dispute whether a webhook was sent. We need a durable, queryable record of every attempt.
|
|
9
|
+
|
|
10
|
+
## Decision
|
|
11
|
+
Every delivery attempt is written to the `delivery_attempts` table in the service's Postgres
|
|
12
|
+
database (event_id, partner_id, attempt_no, status_code, sent_at).
|
|
13
|
+
|
|
14
|
+
## Consequences
|
|
15
|
+
Support can answer "did we send it?" with a query. The table grows ~2M rows/month; partition by month.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# ADR-0003: Idempotency keys in Redis
|
|
2
|
+
|
|
3
|
+
- Status: Accepted
|
|
4
|
+
- Date: 2022-05-02
|
|
5
|
+
- Governs: `src/idempotency/redis_store.py`
|
|
6
|
+
|
|
7
|
+
## Context
|
|
8
|
+
The tracking platform delivers events at-least-once. Partners complained about duplicate webhooks.
|
|
9
|
+
|
|
10
|
+
## Decision
|
|
11
|
+
Before dispatching, the dispatcher sets `idem:{event_id}:{partner_id}` in Redis with NX and a
|
|
12
|
+
24h TTL. If the key already exists the event is dropped as a duplicate.
|
|
13
|
+
|
|
14
|
+
## Consequences
|
|
15
|
+
Fast and simple. If Redis is flushed or evicts keys under memory pressure, duplicates can get through.
|
package/packs/solution-architect/skills/context-graph/evals/files/12/docs/adr/0004-retry-policy.md
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# ADR-0004: Retry policy for failed deliveries
|
|
2
|
+
|
|
3
|
+
- Status: Accepted
|
|
4
|
+
- Date: 2022-06-20
|
|
5
|
+
- Governs: `src/delivery/retry.py`, `config/delivery.yaml`
|
|
6
|
+
|
|
7
|
+
## Context
|
|
8
|
+
Partner endpoints go down. Retrying too aggressively has had us rate-limited and blocked by two partners.
|
|
9
|
+
|
|
10
|
+
## Decision
|
|
11
|
+
Retry failed deliveries with exponential backoff: delay = 30s * 2^(attempt-1), at most 6 attempts
|
|
12
|
+
(last retry roughly 16 minutes after the first). After the 6th failure, publish the event to the
|
|
13
|
+
dead-letter queue `parcelhook.dlq` for manual replay.
|
|
14
|
+
|
|
15
|
+
## Consequences
|
|
16
|
+
A partner outage of more than ~30 minutes means events land in the DLQ and need replaying.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# ADR-0005: Sign payloads with HMAC-SHA256
|
|
2
|
+
|
|
3
|
+
- Status: Accepted
|
|
4
|
+
- Date: 2022-07-11
|
|
5
|
+
- Governs: `src/delivery/signing.py`
|
|
6
|
+
|
|
7
|
+
## Decision
|
|
8
|
+
Each webhook body is signed with HMAC-SHA256 using the partner's shared secret. The hex digest
|
|
9
|
+
is sent in the `X-Parcelhook-Signature` header, together with `X-Parcelhook-Timestamp`, and the
|
|
10
|
+
timestamp is part of the signed string (`{timestamp}.{body}`) to prevent replay.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# ADR-0006: Move idempotency keys to Postgres
|
|
2
|
+
|
|
3
|
+
- Status: Accepted
|
|
4
|
+
- Date: 2023-09-04
|
|
5
|
+
- Supersedes: ADR-0003
|
|
6
|
+
- Governs: `src/idempotency/pg_store.py`, `migrations/003_idempotency_keys.sql`
|
|
7
|
+
|
|
8
|
+
## Context
|
|
9
|
+
INC-1874 (2023-08-22): a Redis failover lost the idempotency keyspace and ~41,000 duplicate
|
|
10
|
+
webhooks went to partners in one hour.
|
|
11
|
+
|
|
12
|
+
## Decision
|
|
13
|
+
Idempotency keys move to the `idempotency_keys` table in Postgres with a unique constraint on
|
|
14
|
+
(event_id, partner_id). The dispatcher inserts with ON CONFLICT DO NOTHING and only dispatches
|
|
15
|
+
if a row was inserted. Rows older than 7 days are purged nightly. Redis is no longer used for idempotency.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# ADR-0007: Nightly SFTP export of the delivery log
|
|
2
|
+
|
|
3
|
+
- Status: Accepted
|
|
4
|
+
- Date: 2023-02-15
|
|
5
|
+
- Governs: `src/export/sftp_export.py`
|
|
6
|
+
|
|
7
|
+
## Context
|
|
8
|
+
Three partners reconcile against a daily file rather than our webhooks.
|
|
9
|
+
|
|
10
|
+
## Decision
|
|
11
|
+
At 02:00 UTC, export the previous day's `delivery_attempts` rows per partner as CSV and push
|
|
12
|
+
them to each partner's SFTP server using `src/export/sftp_export.py`.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# ADR-0008: Partner configuration in a YAML file
|
|
2
|
+
|
|
3
|
+
- Status: Accepted
|
|
4
|
+
- Date: 2023-11-20
|
|
5
|
+
- Governs: `config/partners.yaml`, `src/partners/`
|
|
6
|
+
|
|
7
|
+
## Decision
|
|
8
|
+
Partner endpoints, enabled event types and secret references live in `config/partners.yaml`,
|
|
9
|
+
reviewed by pull request. The service loads the file at startup.
|
|
10
|
+
|
|
11
|
+
## Consequences
|
|
12
|
+
Onboarding a partner needs a deploy. Acceptable at our current 12 partners.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# ADR-0009: Partner configuration via an admin API
|
|
2
|
+
|
|
3
|
+
- Status: Accepted
|
|
4
|
+
- Date: 2024-05-13
|
|
5
|
+
- Governs: `src/partners/`, `migrations/004_partners.sql`
|
|
6
|
+
|
|
7
|
+
## Context
|
|
8
|
+
We now have 60+ partners and onboarding through a YAML PR and a deploy takes days.
|
|
9
|
+
|
|
10
|
+
## Decision
|
|
11
|
+
Partner configuration is stored in the `partners` table and edited through an internal admin API.
|
|
12
|
+
The service reloads partners from the table every 60 seconds. See ADR-0011 for the authentication
|
|
13
|
+
model of the admin API.
|
package/packs/solution-architect/skills/context-graph/evals/files/12/src/delivery/dispatcher.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""Consume tracking events and deliver them to partner endpoints."""
|
|
2
|
+
import json
|
|
3
|
+
import urllib.request
|
|
4
|
+
|
|
5
|
+
from src.idempotency import redis_store
|
|
6
|
+
from src.partners.loader import load_partners
|
|
7
|
+
from src.delivery.signing import sign
|
|
8
|
+
from src.delivery.retry import schedule_retry
|
|
9
|
+
from src.store.delivery_log import record_attempt
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def dispatch(event, attempt_no=1):
|
|
13
|
+
for partner in load_partners():
|
|
14
|
+
if event["type"] not in partner["events"]:
|
|
15
|
+
continue
|
|
16
|
+
# drop duplicates from the at-least-once feed
|
|
17
|
+
if not redis_store.claim(event["id"], partner["id"]):
|
|
18
|
+
continue
|
|
19
|
+
body = json.dumps(event).encode()
|
|
20
|
+
headers = {"Content-Type": "application/json", **sign(partner, body)}
|
|
21
|
+
req = urllib.request.Request(partner["url"], data=body, headers=headers, method="POST")
|
|
22
|
+
try:
|
|
23
|
+
with urllib.request.urlopen(req, timeout=10) as resp:
|
|
24
|
+
status = resp.status
|
|
25
|
+
except Exception:
|
|
26
|
+
status = 0
|
|
27
|
+
record_attempt(event["id"], partner["id"], attempt_no, status)
|
|
28
|
+
if not 200 <= status < 300:
|
|
29
|
+
schedule_retry(event, partner, attempt_no)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Retry scheduling for failed deliveries."""
|
|
2
|
+
from src.config import load_yaml
|
|
3
|
+
from src.store.delivery_log import mark_failed
|
|
4
|
+
from src.queue import enqueue_delayed
|
|
5
|
+
|
|
6
|
+
CFG = load_yaml("config/delivery.yaml")["retry"]
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def schedule_retry(event, partner, attempt_no):
|
|
10
|
+
if attempt_no >= CFG["max_attempts"]:
|
|
11
|
+
mark_failed(event["id"], partner["id"])
|
|
12
|
+
return
|
|
13
|
+
# TODO(INC-2291): backoff disabled while partner Velo's endpoint flaps; restore afterwards
|
|
14
|
+
delay = CFG["base_delay_seconds"]
|
|
15
|
+
enqueue_delayed(event, partner, attempt_no + 1, delay_seconds=delay)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""HMAC-SHA256 signing of webhook bodies."""
|
|
2
|
+
import hashlib
|
|
3
|
+
import hmac
|
|
4
|
+
import time
|
|
5
|
+
|
|
6
|
+
from src.secrets import partner_secret
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def sign(partner, body: bytes) -> dict:
|
|
10
|
+
ts = str(int(time.time()))
|
|
11
|
+
msg = ts.encode() + b"." + body
|
|
12
|
+
digest = hmac.new(partner_secret(partner["id"]), msg, hashlib.sha256).hexdigest()
|
|
13
|
+
return {"X-Parcelhook-Signature": digest, "X-Parcelhook-Timestamp": ts}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Nightly export of the delivery log to per-partner S3 prefixes."""
|
|
2
|
+
import csv
|
|
3
|
+
import io
|
|
4
|
+
|
|
5
|
+
from src.config import load_yaml
|
|
6
|
+
from src.db import execute
|
|
7
|
+
from src.s3 import put_object
|
|
8
|
+
|
|
9
|
+
CFG = load_yaml("config/export.yaml")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def run(day):
|
|
13
|
+
rows = execute(
|
|
14
|
+
"SELECT partner_id, event_id, attempt_no, status_code, sent_at FROM delivery_attempts "
|
|
15
|
+
"WHERE sent_at::date = %s ORDER BY partner_id",
|
|
16
|
+
(day,),
|
|
17
|
+
)
|
|
18
|
+
by_partner = {}
|
|
19
|
+
for r in rows:
|
|
20
|
+
by_partner.setdefault(r[0], []).append(r)
|
|
21
|
+
for partner_id, prows in by_partner.items():
|
|
22
|
+
buf = io.StringIO()
|
|
23
|
+
csv.writer(buf).writerows(prows)
|
|
24
|
+
put_object(CFG["bucket"], f"{partner_id}/{day}.csv", buf.getvalue())
|
package/packs/solution-architect/skills/context-graph/evals/files/12/src/idempotency/pg_store.py
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""Idempotency claims in Postgres (see ADR-0006)."""
|
|
2
|
+
from src.db import execute
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def claim(event_id, partner_id) -> bool:
|
|
6
|
+
rows = execute(
|
|
7
|
+
"INSERT INTO idempotency_keys (event_id, partner_id) VALUES (%s, %s) "
|
|
8
|
+
"ON CONFLICT DO NOTHING RETURNING 1",
|
|
9
|
+
(event_id, partner_id),
|
|
10
|
+
)
|
|
11
|
+
return len(rows) == 1
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Load partner configuration."""
|
|
2
|
+
import time
|
|
3
|
+
|
|
4
|
+
from src.db import execute
|
|
5
|
+
|
|
6
|
+
_cache = {"at": 0, "partners": []}
|
|
7
|
+
RELOAD_SECONDS = 60
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def load_partners():
|
|
11
|
+
if time.time() - _cache["at"] > RELOAD_SECONDS:
|
|
12
|
+
rows = execute("SELECT id, url, events FROM partners WHERE enabled")
|
|
13
|
+
_cache["partners"] = [{"id": r[0], "url": r[1], "events": r[2]} for r in rows]
|
|
14
|
+
_cache["at"] = time.time()
|
|
15
|
+
return _cache["partners"]
|
package/packs/solution-architect/skills/context-graph/evals/files/12/src/store/delivery_log.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""Delivery attempt log (see ADR-0002)."""
|
|
2
|
+
from src.db import execute
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def record_attempt(event_id, partner_id, attempt_no, status_code):
|
|
6
|
+
execute(
|
|
7
|
+
"INSERT INTO delivery_attempts (event_id, partner_id, attempt_no, status_code, sent_at) "
|
|
8
|
+
"VALUES (%s, %s, %s, %s, now())",
|
|
9
|
+
(event_id, partner_id, attempt_no, status_code),
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def mark_failed(event_id, partner_id):
|
|
14
|
+
execute(
|
|
15
|
+
"UPDATE delivery_attempts SET final_status = 'failed' "
|
|
16
|
+
"WHERE event_id = %s AND partner_id = %s",
|
|
17
|
+
(event_id, partner_id),
|
|
18
|
+
)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# RFC-008: Read prices from the Halvorsen ERP
|
|
2
|
+
|
|
3
|
+
Status: Accepted
|
|
4
|
+
Date: 2023-02-07
|
|
5
|
+
Author: M. Okafor
|
|
6
|
+
Governs: shopfront/pricing/erp_client.py
|
|
7
|
+
|
|
8
|
+
## Decision
|
|
9
|
+
Shopfront reads the current price for a SKU from the Halvorsen ERP `GetItemPrice` SOAP call
|
|
10
|
+
through `shopfront/pricing/erp_client.py`. The ERP is the single source of truth for prices.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# RFC-012: Cache prices in Redis
|
|
2
|
+
|
|
3
|
+
Status: Accepted
|
|
4
|
+
Date: 2023-06-14
|
|
5
|
+
Author: M. Okafor
|
|
6
|
+
Builds on: RFC-008
|
|
7
|
+
Governs: shopfront/pricing/cache.py, config/cache.yaml
|
|
8
|
+
|
|
9
|
+
## Context
|
|
10
|
+
The Halvorsen ERP limits `GetItemPrice` to 40 requests/second per client and its p95 latency is
|
|
11
|
+
~900 ms. During the June sale the product page timed out and the ERP throttled us for 20 minutes.
|
|
12
|
+
|
|
13
|
+
## Decision
|
|
14
|
+
Put a read-through Redis cache in front of pricing. Key `price:{sku}`, TTL 60 seconds. A price
|
|
15
|
+
change in the ERP can therefore take up to a minute to show on the site; merchandising agreed
|
|
16
|
+
this is acceptable.
|
|
17
|
+
|
|
18
|
+
## Alternatives considered
|
|
19
|
+
- Nightly price snapshot: rejected, price changes during sales must show within minutes.
|
|
20
|
+
- Asking Halvorsen to raise the limit: rejected by the vendor.
|
|
21
|
+
|
|
22
|
+
## Revisit when
|
|
23
|
+
The ERP rate limit no longer applies to us.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# RFC-015: Lock the price for the duration of checkout
|
|
2
|
+
|
|
3
|
+
Status: Accepted
|
|
4
|
+
Date: 2024-01-09
|
|
5
|
+
Author: J. Lindqvist
|
|
6
|
+
Amends: RFC-012
|
|
7
|
+
Governs: shopfront/checkout/lock.py
|
|
8
|
+
|
|
9
|
+
## Context
|
|
10
|
+
Customers were charged a different price from the one shown when a price changed mid-checkout.
|
|
11
|
+
|
|
12
|
+
## Decision
|
|
13
|
+
When a cart enters checkout, the price a customer saw must be the price they are charged for
|
|
14
|
+
15 minutes. We do this by reusing the pricing cache: checkout keeps the cached price for each
|
|
15
|
+
SKU in the cart alive for 15 minutes.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# RFC-019: Read prices from pricing-svc instead of the ERP
|
|
2
|
+
|
|
3
|
+
Status: Proposed
|
|
4
|
+
Date: 2025-03-18
|
|
5
|
+
Author: P. Achterberg
|
|
6
|
+
Supersedes: RFC-008
|
|
7
|
+
|
|
8
|
+
## Context
|
|
9
|
+
The pricing team now runs `pricing-svc`, a REST service that replicates ERP prices within
|
|
10
|
+
5 seconds of a change. It has no per-client rate limit and a p95 of 35 ms.
|
|
11
|
+
|
|
12
|
+
## Proposal
|
|
13
|
+
Shopfront calls `GET {PRICING_SVC_URL}/v1/prices/{sku}` instead of the ERP SOAP call, and
|
|
14
|
+
`erp_client.py` is deleted.
|
|
15
|
+
|
|
16
|
+
## Open questions
|
|
17
|
+
- Do we still need the Redis cache from RFC-012 once the ERP is out of the path?
|
package/packs/solution-architect/skills/context-graph/evals/files/13/shopfront/checkout/lock.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Hold prices steady while a cart is in checkout (RFC-015)."""
|
|
2
|
+
from shopfront.pricing import cache
|
|
3
|
+
|
|
4
|
+
LOCK_SECONDS = 15 * 60
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def lock_cart_prices(cart) -> None:
|
|
8
|
+
for line in cart.lines:
|
|
9
|
+
cache.get_price(line.sku) # make sure the entry exists
|
|
10
|
+
cache.touch(line.sku, LOCK_SECONDS) # keep it alive for the checkout window
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def price_for_charge(sku: str) -> dict:
|
|
14
|
+
return cache.get_price(sku)
|
package/packs/solution-architect/skills/context-graph/evals/files/13/shopfront/pricing/cache.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""Read-through price cache (RFC-012)."""
|
|
2
|
+
import json
|
|
3
|
+
|
|
4
|
+
from shopfront.redis_client import redis
|
|
5
|
+
from shopfront.settings import load_yaml
|
|
6
|
+
from shopfront.pricing.client import fetch_price
|
|
7
|
+
|
|
8
|
+
TTL = load_yaml("config/cache.yaml")["pricing"]["ttl_seconds"]
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def get_price(sku: str) -> dict:
|
|
12
|
+
key = f"price:{sku}"
|
|
13
|
+
hit = redis.get(key)
|
|
14
|
+
if hit is not None:
|
|
15
|
+
return json.loads(hit)
|
|
16
|
+
price = fetch_price(sku)
|
|
17
|
+
redis.set(key, json.dumps(price), ex=TTL)
|
|
18
|
+
return price
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def touch(sku: str, ttl: int) -> None:
|
|
22
|
+
redis.expire(f"price:{sku}", ttl)
|
package/packs/solution-architect/skills/context-graph/evals/files/13/shopfront/pricing/client.py
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""Fetch the current price for a SKU."""
|
|
2
|
+
import json
|
|
3
|
+
import urllib.request
|
|
4
|
+
|
|
5
|
+
from shopfront.settings import PRICING_SVC_URL
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def fetch_price(sku: str) -> dict:
|
|
9
|
+
with urllib.request.urlopen(f"{PRICING_SVC_URL}/v1/prices/{sku}", timeout=2) as resp:
|
|
10
|
+
return json.load(resp) # {"sku": ..., "amount_minor": ..., "currency": ...}
|