skilldrop-cli 0.16.45 → 0.16.46
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/catalogue.json +1 -0
- package/model-routing.json +1 -0
- package/package.json +1 -1
- package/packs/solution-architect/skills/context-graph/SKILL.md +119 -0
- package/packs/solution-architect/skills/context-graph/evals/eval_queries.json +11 -0
- package/packs/solution-architect/skills/context-graph/evals/evals.json +225 -0
- package/packs/solution-architect/skills/context-graph/evals/files/1/docs/adr/0001-record-architecture-decisions.md +19 -0
- package/packs/solution-architect/skills/context-graph/evals/files/1/docs/adr/0002-postgres-as-primary-database.md +16 -0
- package/packs/solution-architect/skills/context-graph/evals/files/1/docs/adr/0003-use-redis-for-session-storage.md +24 -0
- package/packs/solution-architect/skills/context-graph/evals/files/1/docs/adr/0004-structured-json-logging.md +15 -0
- package/packs/solution-architect/skills/context-graph/evals/files/1/docs/adr/0005-session-lifetime-eight-hours.md +22 -0
- package/packs/solution-architect/skills/context-graph/evals/files/1/docs/adr/0006-move-sessions-to-postgres.md +16 -0
- package/packs/solution-architect/skills/context-graph/evals/files/1/docs/adr/0007-stateless-signed-session-cookies.md +35 -0
- package/packs/solution-architect/skills/context-graph/evals/files/1/services/api/__init__.py +0 -0
- package/packs/solution-architect/skills/context-graph/evals/files/1/services/api/db.py +0 -0
- package/packs/solution-architect/skills/context-graph/evals/files/1/services/api/logging_setup.py +0 -0
- package/packs/solution-architect/skills/context-graph/evals/files/1/services/auth/__init__.py +0 -0
- package/packs/solution-architect/skills/context-graph/evals/files/1/services/auth/cookie_sessions.py +7 -0
- package/packs/solution-architect/skills/context-graph/evals/files/1/services/auth/redis_sessions.py +7 -0
- package/packs/solution-architect/skills/context-graph/evals/files/10/README.md +8 -0
- package/packs/solution-architect/skills/context-graph/evals/files/10/docs/adr/0001-record-architecture-decisions.md +10 -0
- package/packs/solution-architect/skills/context-graph/evals/files/10/docs/adr/0004-service-auth-hmac-shared-keys.md +21 -0
- package/packs/solution-architect/skills/context-graph/evals/files/10/docs/adr/0007-secrets-from-vault-agent.md +19 -0
- package/packs/solution-architect/skills/context-graph/evals/files/10/docs/adr/0011-service-auth-mtls.md +23 -0
- package/packs/solution-architect/skills/context-graph/evals/files/10/services/billing-api/app.py +16 -0
- package/packs/solution-architect/skills/context-graph/evals/files/10/services/billing-api/auth/__init__.py +4 -0
- package/packs/solution-architect/skills/context-graph/evals/files/10/services/billing-api/auth/hmac_verify.py +31 -0
- package/packs/solution-architect/skills/context-graph/evals/files/10/services/billing-api/config/settings.py +14 -0
- package/packs/solution-architect/skills/context-graph/evals/files/10/services/ledger-worker/auth/__init__.py +4 -0
- package/packs/solution-architect/skills/context-graph/evals/files/10/services/ledger-worker/auth/mtls.py +18 -0
- package/packs/solution-architect/skills/context-graph/evals/files/11/README.md +4 -0
- package/packs/solution-architect/skills/context-graph/evals/files/11/app/models/base.py +3 -0
- package/packs/solution-architect/skills/context-graph/evals/files/11/app/models/line_item.py +14 -0
- package/packs/solution-architect/skills/context-graph/evals/files/11/app/models/order.py +24 -0
- package/packs/solution-architect/skills/context-graph/evals/files/11/eng/decisions/DR-003-money-as-minor-units.md +10 -0
- package/packs/solution-architect/skills/context-graph/evals/files/11/eng/decisions/DR-005-orders-typed-columns-only.md +10 -0
- package/packs/solution-architect/skills/context-graph/evals/files/11/eng/decisions/DR-009-line-items-immutable.md +11 -0
- package/packs/solution-architect/skills/context-graph/evals/files/11/eng/decisions/DR-014-optional-order-attributes-json.md +16 -0
- package/packs/solution-architect/skills/context-graph/evals/files/11/eng/decisions/DR-016-order-attributes-table.md +11 -0
- package/packs/solution-architect/skills/context-graph/evals/files/11/migrations/0007_create_orders.sql +11 -0
- package/packs/solution-architect/skills/context-graph/evals/files/11/migrations/0012_add_order_attributes.sql +2 -0
- package/packs/solution-architect/skills/context-graph/evals/files/12/README.md +8 -0
- package/packs/solution-architect/skills/context-graph/evals/files/12/config/delivery.yaml +4 -0
- package/packs/solution-architect/skills/context-graph/evals/files/12/config/export.yaml +2 -0
- package/packs/solution-architect/skills/context-graph/evals/files/12/config/partners.yaml +10 -0
- package/packs/solution-architect/skills/context-graph/evals/files/12/docs/adr/0001-record-architecture-decisions.md +10 -0
- package/packs/solution-architect/skills/context-graph/evals/files/12/docs/adr/0002-postgres-delivery-log.md +15 -0
- package/packs/solution-architect/skills/context-graph/evals/files/12/docs/adr/0003-redis-idempotency-keys.md +15 -0
- package/packs/solution-architect/skills/context-graph/evals/files/12/docs/adr/0004-retry-policy.md +16 -0
- package/packs/solution-architect/skills/context-graph/evals/files/12/docs/adr/0005-hmac-signed-payloads.md +10 -0
- package/packs/solution-architect/skills/context-graph/evals/files/12/docs/adr/0006-postgres-idempotency.md +15 -0
- package/packs/solution-architect/skills/context-graph/evals/files/12/docs/adr/0007-sftp-batch-export.md +12 -0
- package/packs/solution-architect/skills/context-graph/evals/files/12/docs/adr/0008-partner-config-in-yaml.md +12 -0
- package/packs/solution-architect/skills/context-graph/evals/files/12/docs/adr/0009-partner-config-admin-api.md +13 -0
- package/packs/solution-architect/skills/context-graph/evals/files/12/migrations/003_idempotency_keys.sql +6 -0
- package/packs/solution-architect/skills/context-graph/evals/files/12/migrations/004_partners.sql +7 -0
- package/packs/solution-architect/skills/context-graph/evals/files/12/src/delivery/dispatcher.py +29 -0
- package/packs/solution-architect/skills/context-graph/evals/files/12/src/delivery/retry.py +15 -0
- package/packs/solution-architect/skills/context-graph/evals/files/12/src/delivery/signing.py +13 -0
- package/packs/solution-architect/skills/context-graph/evals/files/12/src/export/s3_export.py +24 -0
- package/packs/solution-architect/skills/context-graph/evals/files/12/src/idempotency/pg_store.py +11 -0
- package/packs/solution-architect/skills/context-graph/evals/files/12/src/idempotency/redis_store.py +8 -0
- package/packs/solution-architect/skills/context-graph/evals/files/12/src/partners/loader.py +15 -0
- package/packs/solution-architect/skills/context-graph/evals/files/12/src/store/delivery_log.py +18 -0
- package/packs/solution-architect/skills/context-graph/evals/files/13/README.md +3 -0
- package/packs/solution-architect/skills/context-graph/evals/files/13/config/cache.yaml +2 -0
- package/packs/solution-architect/skills/context-graph/evals/files/13/config/settings.yaml +2 -0
- package/packs/solution-architect/skills/context-graph/evals/files/13/docs/decisions/RFC-008-pricing-from-erp.md +10 -0
- package/packs/solution-architect/skills/context-graph/evals/files/13/docs/decisions/RFC-012-pricing-cache.md +23 -0
- package/packs/solution-architect/skills/context-graph/evals/files/13/docs/decisions/RFC-015-checkout-price-lock.md +15 -0
- package/packs/solution-architect/skills/context-graph/evals/files/13/docs/decisions/RFC-019-pricing-service.md +17 -0
- package/packs/solution-architect/skills/context-graph/evals/files/13/shopfront/catalog/views.py +8 -0
- package/packs/solution-architect/skills/context-graph/evals/files/13/shopfront/checkout/lock.py +14 -0
- package/packs/solution-architect/skills/context-graph/evals/files/13/shopfront/pricing/cache.py +22 -0
- package/packs/solution-architect/skills/context-graph/evals/files/13/shopfront/pricing/client.py +10 -0
- package/packs/solution-architect/skills/context-graph/evals/files/14/docs/rfcs/0001-idempotency-keys.md +33 -0
- package/packs/solution-architect/skills/context-graph/evals/files/14/docs/rfcs/0002-processor-retry-policy.md +26 -0
- package/packs/solution-architect/skills/context-graph/evals/files/14/docs/rfcs/0003-money-in-minor-units.md +31 -0
- package/packs/solution-architect/skills/context-graph/evals/files/14/docs/rfcs/0004-webhook-signing.md +27 -0
- package/packs/solution-architect/skills/context-graph/evals/files/14/docs/rfcs/0005-retry-with-jitter.md +30 -0
- package/packs/solution-architect/skills/context-graph/evals/files/14/docs/rfcs/0006-idempotency-at-gateway.md +28 -0
- package/packs/solution-architect/skills/context-graph/evals/files/14/pr-summary.md +27 -0
- package/packs/solution-architect/skills/context-graph/evals/files/14/src/payments/amount.ts +10 -0
- package/packs/solution-architect/skills/context-graph/evals/files/14/src/payments/charge.ts +28 -0
- package/packs/solution-architect/skills/context-graph/evals/files/14/src/payments/idempotency.ts +4 -0
- package/packs/solution-architect/skills/context-graph/evals/files/14/src/payments/retry.ts +21 -0
- package/packs/solution-architect/skills/context-graph/evals/files/14/src/webhooks/send.ts +9 -0
- package/packs/solution-architect/skills/context-graph/evals/files/14/src/webhooks/sign.ts +6 -0
- package/packs/solution-architect/skills/context-graph/evals/files/15/deploy/services.yaml +14 -0
- package/packs/solution-architect/skills/context-graph/evals/files/15/doc/adr/0001-record-architecture-decisions.md +20 -0
- package/packs/solution-architect/skills/context-graph/evals/files/15/doc/adr/0002-use-postgresql-for-shipment-data.md +23 -0
- package/packs/solution-architect/skills/context-graph/evals/files/15/doc/adr/0003-store-vehicle-telemetry-in-cassandra.md +22 -0
- package/packs/solution-architect/skills/context-graph/evals/files/15/doc/adr/0004-use-redis-for-session-storage.md +22 -0
- package/packs/solution-architect/skills/context-graph/evals/files/15/doc/adr/0005-use-react-for-dispatcher-dashboard.md +19 -0
- package/packs/solution-architect/skills/context-graph/evals/files/15/doc/adr/0006-archive-delivered-shipments-to-object-storage.md +23 -0
- package/packs/solution-architect/skills/context-graph/evals/files/15/doc/adr/0007-move-shipment-records-to-mongodb.md +23 -0
- package/packs/solution-architect/skills/context-graph/evals/files/15/doc/adr/0008-store-vehicle-telemetry-in-timescaledb.md +28 -0
- package/packs/solution-architect/skills/context-graph/evals/files/15/doc/adr/0009-stateless-sessions-with-signed-cookies.md +24 -0
- package/packs/solution-architect/skills/context-graph/evals/files/15/services/api/session.py +17 -0
- package/packs/solution-architect/skills/context-graph/evals/files/15/services/shipments/db.py +30 -0
- package/packs/solution-architect/skills/context-graph/evals/files/15/services/shipments/migrations.sql +3 -0
- package/packs/solution-architect/skills/context-graph/evals/files/15/services/telemetry/store.py +23 -0
- package/packs/solution-architect/skills/context-graph/evals/files/15/web/dashboard/App.jsx +3 -0
- package/packs/solution-architect/skills/context-graph/evals/files/2/docs/adr/0001-record-architecture-decisions.md +11 -0
- package/packs/solution-architect/skills/context-graph/evals/files/2/docs/adr/0002-rabbitmq-for-background-jobs.md +15 -0
- package/packs/solution-architect/skills/context-graph/evals/files/2/docs/adr/0003-thumbnail-service.md +20 -0
- package/packs/solution-architect/skills/context-graph/evals/files/2/docs/adr/0004-money-as-integer-minor-units.md +17 -0
- package/packs/solution-architect/skills/context-graph/evals/files/2/docs/adr/0005-worker-retries.md +12 -0
- package/packs/solution-architect/skills/context-graph/evals/files/2/docs/adr/0006-fx-amounts-with-four-decimals.md +18 -0
- package/packs/solution-architect/skills/context-graph/evals/files/2/docs/adr/0007-feature-flags.md +16 -0
- package/packs/solution-architect/skills/context-graph/evals/files/2/docs/adr/0008-retire-legacy-search.md +14 -0
- package/packs/solution-architect/skills/context-graph/evals/files/2/libs/money/__init__.py +0 -0
- package/packs/solution-architect/skills/context-graph/evals/files/2/libs/money/amount.py +9 -0
- package/packs/solution-architect/skills/context-graph/evals/files/2/services/checkout/flags.py +1 -0
- package/packs/solution-architect/skills/context-graph/evals/files/2/services/checkout/publish.py +0 -0
- package/packs/solution-architect/skills/context-graph/evals/files/2/services/media/resize.py +5 -0
- package/packs/solution-architect/skills/context-graph/evals/files/2/services/worker/consumer.py +0 -0
- package/packs/solution-architect/skills/context-graph/evals/files/3/docs/rfcs/0001-how-we-write-rfcs.md +9 -0
- package/packs/solution-architect/skills/context-graph/evals/files/3/docs/rfcs/0002-payments-service-boundary.md +16 -0
- package/packs/solution-architect/skills/context-graph/evals/files/3/docs/rfcs/0003-webhook-retries-fixed-interval.md +19 -0
- package/packs/solution-architect/skills/context-graph/evals/files/3/docs/rfcs/0004-webhook-idempotency-keys.md +16 -0
- package/packs/solution-architect/skills/context-graph/evals/files/3/docs/rfcs/0005-notification-retries.md +16 -0
- package/packs/solution-architect/skills/context-graph/evals/files/3/docs/rfcs/0006-webhook-retry-backoff.md +26 -0
- package/packs/solution-architect/skills/context-graph/evals/files/3/docs/rfcs/0007-webhooks-on-the-job-queue.md +12 -0
- package/packs/solution-architect/skills/context-graph/evals/files/3/services/notifications/__init__.py +0 -0
- package/packs/solution-architect/skills/context-graph/evals/files/3/services/notifications/retry.py +4 -0
- package/packs/solution-architect/skills/context-graph/evals/files/3/services/payments/__init__.py +0 -0
- package/packs/solution-architect/skills/context-graph/evals/files/3/services/payments/webhooks/__init__.py +0 -0
- package/packs/solution-architect/skills/context-graph/evals/files/3/services/payments/webhooks/retry.py +15 -0
- package/packs/solution-architect/skills/context-graph/evals/files/3/services/payments/webhooks/send.py +0 -0
- package/packs/solution-architect/skills/context-graph/evals/files/4/docs/adr/0001-record-architecture-decisions.md +19 -0
- package/packs/solution-architect/skills/context-graph/evals/files/4/docs/adr/0002-postgres-as-system-of-record.md +18 -0
- package/packs/solution-architect/skills/context-graph/evals/files/4/docs/adr/0004-redis-locks-for-scheduler.md +19 -0
- package/packs/solution-architect/skills/context-graph/evals/files/4/docs/adr/0007-remove-redis.md +27 -0
- package/packs/solution-architect/skills/context-graph/evals/files/4/docs/adr/0009-money-as-integer-minor-units.md +19 -0
- package/packs/solution-architect/skills/context-graph/evals/files/4/docs/onboarding.md +12 -0
- package/packs/solution-architect/skills/context-graph/evals/files/4/src/ledgerline/__init__.py +0 -0
- package/packs/solution-architect/skills/context-graph/evals/files/4/src/ledgerline/db/__init__.py +22 -0
- package/packs/solution-architect/skills/context-graph/evals/files/4/src/ledgerline/invoicing.py +9 -0
- package/packs/solution-architect/skills/context-graph/evals/files/4/src/ledgerline/money.py +10 -0
- package/packs/solution-architect/skills/context-graph/evals/files/4/src/ledgerline/scheduler/__init__.py +0 -0
- package/packs/solution-architect/skills/context-graph/evals/files/4/src/ledgerline/scheduler/lock.py +23 -0
- package/packs/solution-architect/skills/context-graph/evals/files/4/src/ledgerline/scheduler/runner.py +17 -0
- package/packs/solution-architect/skills/context-graph/evals/files/5/lib/types/index.ts +13 -0
- package/packs/solution-architect/skills/context-graph/evals/files/5/rfcs/0001-service-layout.md +20 -0
- package/packs/solution-architect/skills/context-graph/evals/files/5/rfcs/0003-ingest-retry-policy.md +26 -0
- package/packs/solution-architect/skills/context-graph/evals/files/5/rfcs/0006-shared-retry-library.md +24 -0
- package/packs/solution-architect/skills/context-graph/evals/files/5/rfcs/0008-outbound-http-client.md +22 -0
- package/packs/solution-architect/skills/context-graph/evals/files/5/services/export/client.ts +15 -0
- package/packs/solution-architect/skills/context-graph/evals/files/5/services/export/retry.ts +12 -0
- package/packs/solution-architect/skills/context-graph/evals/files/5/services/ingest/client.ts +14 -0
- package/packs/solution-architect/skills/context-graph/evals/files/5/services/ingest/retry.ts +20 -0
- package/packs/solution-architect/skills/context-graph/evals/files/6/README.md +4 -0
- package/packs/solution-architect/skills/context-graph/evals/files/6/docs/adr/0001-monorepo-layout.md +10 -0
- package/packs/solution-architect/skills/context-graph/evals/files/6/docs/adr/0002-billing-on-postgres.md +13 -0
- package/packs/solution-architect/skills/context-graph/evals/files/6/docs/adr/0004-ledger-sync-nightly-batch.md +9 -0
- package/packs/solution-architect/skills/context-graph/evals/files/6/docs/rfcs/RFC-0003-invoice-ids.md +9 -0
- package/packs/solution-architect/skills/context-graph/evals/files/6/docs/rfcs/RFC-0007-invoice-ids-ulid.md +11 -0
- package/packs/solution-architect/skills/context-graph/evals/files/6/docs/rfcs/RFC-0009-billing-event-store.md +15 -0
- package/packs/solution-architect/skills/context-graph/evals/files/6/docs/rfcs/RFC-0010-revenue-service.md +13 -0
- package/packs/solution-architect/skills/context-graph/evals/files/6/libs/money/ulid.py +8 -0
- package/packs/solution-architect/skills/context-graph/evals/files/6/services/billing/src/events.py +10 -0
- package/packs/solution-architect/skills/context-graph/evals/files/6/services/billing/src/store.py +10 -0
- package/packs/solution-architect/skills/context-graph/evals/files/6/services/invoicing/ids/generator.py +7 -0
- package/packs/solution-architect/skills/context-graph/evals/files/6/services/ledger/README.md +4 -0
- package/packs/solution-architect/skills/context-graph/evals/files/6/services/ledger/sync.py +3 -0
- package/packs/solution-architect/skills/context-graph/evals/files/7/README.md +4 -0
- package/packs/solution-architect/skills/context-graph/evals/files/7/db/queries/orders.sql +2 -0
- package/packs/solution-architect/skills/context-graph/evals/files/7/db/sharding/router.py +9 -0
- package/packs/solution-architect/skills/context-graph/evals/files/7/design/decisions/DD-001-postgres-primary.md +8 -0
- package/packs/solution-architect/skills/context-graph/evals/files/7/design/decisions/DD-002-kafka-event-bus.md +20 -0
- package/packs/solution-architect/skills/context-graph/evals/files/7/design/decisions/DD-003-no-orm.md +9 -0
- package/packs/solution-architect/skills/context-graph/evals/files/7/design/decisions/DD-006-tenant-sharding.md +9 -0
- package/packs/solution-architect/skills/context-graph/evals/files/7/design/decisions/DD-011-orm-for-admin-console.md +11 -0
- package/packs/solution-architect/skills/context-graph/evals/files/7/pipelines/fanout/router.py +4 -0
- package/packs/solution-architect/skills/context-graph/evals/files/7/pipelines/ingest/consumer.py +5 -0
- package/packs/solution-architect/skills/context-graph/evals/files/7/pipelines/ingest/topics.yaml +8 -0
- package/packs/solution-architect/skills/context-graph/evals/files/7/rfcs/0004-region-sharding.md +15 -0
- package/packs/solution-architect/skills/context-graph/evals/files/7/services/admin-console/models.py +11 -0
- package/packs/solution-architect/skills/context-graph/evals/files/7/services/orders/repo.py +9 -0
- package/packs/solution-architect/skills/context-graph/evals/files/8/README.md +11 -0
- package/packs/solution-architect/skills/context-graph/evals/files/8/api/ledger.proto +30 -0
- package/packs/solution-architect/skills/context-graph/evals/files/8/config/http.yaml +10 -0
- package/packs/solution-architect/skills/context-graph/evals/files/8/config/messaging.yaml +8 -0
- package/packs/solution-architect/skills/context-graph/evals/files/8/docs/adr/0001-record-architecture-decisions.md +20 -0
- package/packs/solution-architect/skills/context-graph/evals/files/8/docs/adr/0002-postgresql-for-ledger.md +22 -0
- package/packs/solution-architect/skills/context-graph/evals/files/8/docs/adr/0003-rabbitmq-domain-events.md +22 -0
- package/packs/solution-architect/skills/context-graph/evals/files/8/docs/adr/0004-integer-minor-units.md +32 -0
- package/packs/solution-architect/skills/context-graph/evals/files/8/docs/adr/0005-provider-http-retries.md +26 -0
- package/packs/solution-architect/skills/context-graph/evals/files/8/docs/adr/0006-openapi-generated-clients.md +20 -0
- package/packs/solution-architect/skills/context-graph/evals/files/8/docs/adr/0007-kafka-domain-events.md +25 -0
- package/packs/solution-architect/skills/context-graph/evals/files/8/docs/adr/0008-structured-logging.md +18 -0
- package/packs/solution-architect/skills/context-graph/evals/files/8/docs/rfcs/RFC-012-idempotency-keys.md +19 -0
- package/packs/solution-architect/skills/context-graph/evals/files/8/services/ledger/api/idempotency.py +19 -0
- package/packs/solution-architect/skills/context-graph/evals/files/8/services/ledger/api/server.py +35 -0
- package/packs/solution-architect/skills/context-graph/evals/files/8/services/ledger/clients/provider.py +33 -0
- package/packs/solution-architect/skills/context-graph/evals/files/8/services/ledger/db/migrations/0001_init.sql +14 -0
- package/packs/solution-architect/skills/context-graph/evals/files/8/services/ledger/db/migrations/0008_idempotency_keys.sql +5 -0
- package/packs/solution-architect/skills/context-graph/evals/files/8/services/ledger/db/migrations/0009_fx_entries.sql +4 -0
- package/packs/solution-architect/skills/context-graph/evals/files/8/services/ledger/events/publisher.py +28 -0
- package/packs/solution-architect/skills/context-graph/evals/files/8/services/ledger/logging.py +25 -0
- package/packs/solution-architect/skills/context-graph/evals/files/8/services/ledger/models/entry.py +16 -0
- package/packs/solution-architect/skills/context-graph/evals/files/9/.env.example +3 -0
- package/packs/solution-architect/skills/context-graph/evals/files/9/README.md +5 -0
- package/packs/solution-architect/skills/context-graph/evals/files/9/docker-compose.yml +18 -0
- package/packs/solution-architect/skills/context-graph/evals/files/9/docs/decisions/001-session-storage.md +21 -0
- package/packs/solution-architect/skills/context-graph/evals/files/9/docs/decisions/002-api-rate-limiting.md +32 -0
- package/packs/solution-architect/skills/context-graph/evals/files/9/docs/decisions/003-jwt-sessions.md +29 -0
- package/packs/solution-architect/skills/context-graph/evals/files/9/docs/decisions/004-catalog-cache.md +19 -0
- package/packs/solution-architect/skills/context-graph/evals/files/9/src/catalog/cache.ts +13 -0
- package/packs/solution-architect/skills/context-graph/evals/files/9/src/catalog/repository.ts +13 -0
- package/packs/solution-architect/skills/context-graph/evals/files/9/src/jobs/email.ts +3 -0
- package/packs/solution-architect/skills/context-graph/evals/files/9/src/jobs/export.ts +3 -0
- package/packs/solution-architect/skills/context-graph/evals/files/9/src/jobs/queue.ts +10 -0
- package/packs/solution-architect/skills/context-graph/evals/files/9/src/jobs/worker.ts +8 -0
- package/packs/solution-architect/skills/context-graph/evals/files/9/src/lib/db.ts +10 -0
- package/packs/solution-architect/skills/context-graph/evals/files/9/src/lib/redis.ts +8 -0
- package/packs/solution-architect/skills/context-graph/evals/files/9/src/middleware/rateLimit.ts +26 -0
- package/packs/solution-architect/skills/context-graph/evals/files/9/src/server.ts +17 -0
- package/packs/solution-architect/skills/context-graph/evals/files/9/src/session/store.ts +10 -0
- package/packs/solution-architect/skills/context-graph/examples/inputs/order-events/docs/adr/0004-kafka-for-order-events.md +19 -0
- package/packs/solution-architect/skills/context-graph/examples/inputs/order-events/docs/adr/0006-email-receipts.md +7 -0
- package/packs/solution-architect/skills/context-graph/examples/inputs/order-events/docs/adr/0009-sqs-for-order-events.md +19 -0
- package/packs/solution-architect/skills/context-graph/examples/inputs/order-events/services/notifications/receipts.py +0 -0
- package/packs/solution-architect/skills/context-graph/examples/inputs/order-events/services/orders/publisher.py +0 -0
- package/packs/solution-architect/skills/context-graph/examples/order-events.md +63 -0
- package/packs/solution-architect/skills/context-graph/manifest.json +33 -0
- package/packs/solution-architect/skills/context-graph/reference.md +104 -0
- package/packs/solution-architect/skills/context-graph/scripts/context_graph.py +634 -0
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": ...}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
---
|
|
2
|
+
rfc: 0001
|
|
3
|
+
title: Idempotency keys on every charge request
|
|
4
|
+
status: accepted
|
|
5
|
+
date: 2024-02-12
|
|
6
|
+
authors: [mira.okafor]
|
|
7
|
+
governs:
|
|
8
|
+
- src/payments/charge.ts
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# RFC-0001: Idempotency keys on every charge request
|
|
12
|
+
|
|
13
|
+
## Problem
|
|
14
|
+
|
|
15
|
+
Mobile clients retry on flaky networks. Without a dedupe key, a retried
|
|
16
|
+
`POST /charges` can bill a customer twice. We had 14 double-charge tickets in
|
|
17
|
+
January 2024.
|
|
18
|
+
|
|
19
|
+
## Decision
|
|
20
|
+
|
|
21
|
+
Every `POST /charges` request MUST carry an `Idempotency-Key` header. The
|
|
22
|
+
charge handler rejects requests without one (HTTP 400) and stores the key with
|
|
23
|
+
the charge for 24 hours. A repeat key returns the original response instead of
|
|
24
|
+
creating a new charge.
|
|
25
|
+
|
|
26
|
+
The check lives in the charge handler itself, not in the retry layer: the
|
|
27
|
+
retry layer only sees our own outbound retries to the card processor, not
|
|
28
|
+
inbound client retries.
|
|
29
|
+
|
|
30
|
+
## Consequences
|
|
31
|
+
|
|
32
|
+
- Clients must generate a UUID per logical charge.
|
|
33
|
+
- The `idempotency_keys` table grows; rows expire after 24 hours.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
---
|
|
2
|
+
rfc: 0002
|
|
3
|
+
title: Fixed-interval retries to the card processor
|
|
4
|
+
status: superseded
|
|
5
|
+
superseded_by: 0005
|
|
6
|
+
date: 2024-02-20
|
|
7
|
+
authors: [dev.raman]
|
|
8
|
+
governs:
|
|
9
|
+
- src/payments/retry.ts
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# RFC-0002: Fixed-interval retries to the card processor
|
|
13
|
+
|
|
14
|
+
## Problem
|
|
15
|
+
|
|
16
|
+
Calls to the card processor occasionally time out. We need a retry policy.
|
|
17
|
+
|
|
18
|
+
## Decision
|
|
19
|
+
|
|
20
|
+
Retry failed processor calls up to 3 times with a fixed 2-second delay
|
|
21
|
+
between attempts.
|
|
22
|
+
|
|
23
|
+
## Consequences
|
|
24
|
+
|
|
25
|
+
- Simple to reason about.
|
|
26
|
+
- Under a processor outage every instance retries in lockstep.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
rfc: 0003
|
|
3
|
+
title: Represent money as integer minor units
|
|
4
|
+
status: accepted
|
|
5
|
+
date: 2024-03-04
|
|
6
|
+
authors: [mira.okafor]
|
|
7
|
+
governs:
|
|
8
|
+
- src/payments/amount.ts
|
|
9
|
+
- src/payments/charge.ts
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# RFC-0003: Represent money as integer minor units
|
|
13
|
+
|
|
14
|
+
## Problem
|
|
15
|
+
|
|
16
|
+
A refund of 19.99 was stored as 19.989999999999998 after a floating-point
|
|
17
|
+
round trip, and the ledger failed to reconcile.
|
|
18
|
+
|
|
19
|
+
## Decision
|
|
20
|
+
|
|
21
|
+
All monetary amounts inside the service and on the wire are integers in the
|
|
22
|
+
currency's minor unit (cents for USD, pence for GBP), in a field named
|
|
23
|
+
`amountMinor`, paired with an ISO 4217 `currency` code. No decimal or
|
|
24
|
+
floating-point money fields anywhere in `src/payments/`. Conversion to a
|
|
25
|
+
display string happens only at the edge, via `formatAmount()` in
|
|
26
|
+
`src/payments/amount.ts`.
|
|
27
|
+
|
|
28
|
+
## Consequences
|
|
29
|
+
|
|
30
|
+
- API consumers must send `amountMinor: 1999`, not `amount: 19.99`.
|
|
31
|
+
- Currencies with three minor digits (e.g. KWD) work without special cases.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
---
|
|
2
|
+
rfc: 0004
|
|
3
|
+
title: HMAC-SHA256 signatures on outbound webhooks
|
|
4
|
+
status: accepted
|
|
5
|
+
date: 2024-04-15
|
|
6
|
+
authors: [dev.raman]
|
|
7
|
+
governs:
|
|
8
|
+
- src/webhooks/sign.ts
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# RFC-0004: HMAC-SHA256 signatures on outbound webhooks
|
|
12
|
+
|
|
13
|
+
## Problem
|
|
14
|
+
|
|
15
|
+
Merchants cannot tell a genuine `charge.succeeded` webhook from a forged one.
|
|
16
|
+
|
|
17
|
+
## Decision
|
|
18
|
+
|
|
19
|
+
Every outbound webhook carries a `Ledgerline-Signature` header: an
|
|
20
|
+
HMAC-SHA256 of `timestamp + "." + body`, keyed with the merchant's webhook
|
|
21
|
+
secret. Signing is implemented in one place, `src/webhooks/sign.ts`, and every
|
|
22
|
+
sender must call `signPayload()` from it.
|
|
23
|
+
|
|
24
|
+
## Consequences
|
|
25
|
+
|
|
26
|
+
- Merchants verify with a constant-time compare.
|
|
27
|
+
- Rotating a merchant secret invalidates in-flight retries.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
---
|
|
2
|
+
rfc: 0005
|
|
3
|
+
title: Exponential backoff with full jitter for processor retries
|
|
4
|
+
status: accepted
|
|
5
|
+
supersedes: 0002
|
|
6
|
+
date: 2024-06-03
|
|
7
|
+
authors: [dev.raman, mira.okafor]
|
|
8
|
+
governs:
|
|
9
|
+
- src/payments/retry.ts
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# RFC-0005: Exponential backoff with full jitter for processor retries
|
|
13
|
+
|
|
14
|
+
## Problem
|
|
15
|
+
|
|
16
|
+
During the processor outage on 2024-05-21, RFC-0002's fixed 2-second retries
|
|
17
|
+
made all 40 instances hit the processor in synchronised waves, extending the
|
|
18
|
+
outage on our side by about 25 minutes.
|
|
19
|
+
|
|
20
|
+
## Decision
|
|
21
|
+
|
|
22
|
+
Replace RFC-0002. Retry failed processor calls up to 4 times using
|
|
23
|
+
exponential backoff with full jitter: before attempt `n`, wait a random delay
|
|
24
|
+
between 0 and `min(8000, 250 * 2^n)` milliseconds. Never retry a 4xx response
|
|
25
|
+
from the processor.
|
|
26
|
+
|
|
27
|
+
## Consequences
|
|
28
|
+
|
|
29
|
+
- Retries spread out; no lockstep under outage.
|
|
30
|
+
- Worst-case added latency is bounded at roughly 15 seconds.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
rfc: 0006
|
|
3
|
+
title: Move idempotency handling to the API gateway
|
|
4
|
+
status: proposed
|
|
5
|
+
date: 2024-09-30
|
|
6
|
+
authors: [tomas.vey]
|
|
7
|
+
governs:
|
|
8
|
+
- src/payments/charge.ts
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# RFC-0006: Move idempotency handling to the API gateway
|
|
12
|
+
|
|
13
|
+
## Problem
|
|
14
|
+
|
|
15
|
+
Three services now implement their own idempotency-key tables, each slightly
|
|
16
|
+
differently.
|
|
17
|
+
|
|
18
|
+
## Proposal
|
|
19
|
+
|
|
20
|
+
Enforce `Idempotency-Key` once at the API gateway and remove the per-service
|
|
21
|
+
checks, including the one RFC-0001 put in the charge handler.
|
|
22
|
+
|
|
23
|
+
## Open questions
|
|
24
|
+
|
|
25
|
+
- The gateway team has not yet committed to a delivery date.
|
|
26
|
+
- How are responses replayed for keys the gateway has seen?
|
|
27
|
+
|
|
28
|
+
Not yet reviewed. Until this is accepted, RFC-0001 stands.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# PR #412 — "Simplify charge path" (author: tomas.vey)
|
|
2
|
+
|
|
3
|
+
Description from the author:
|
|
4
|
+
> Cleanup of the charge path. Retries are back to the simple policy from RFC-0002,
|
|
5
|
+
> which is much easier to reason about. Dedupe now happens in the retry layer so the
|
|
6
|
+
> header check in the handler is redundant. All in line with our RFCs.
|
|
7
|
+
|
|
8
|
+
## Files changed
|
|
9
|
+
|
|
10
|
+
### src/payments/charge.ts (+9 / -14)
|
|
11
|
+
- `ChargeRequest.amountMinor: number` renamed to `amount: number`; value is now
|
|
12
|
+
decimal major units (e.g. `19.99`) "to match what the mobile app sends".
|
|
13
|
+
- Removed the `Idempotency-Key` header check and the `idempotencyStore.get/put`
|
|
14
|
+
calls. Comment added: `// dedupe handled by retry layer`.
|
|
15
|
+
- `assertMinorUnits(req.amountMinor)` call removed.
|
|
16
|
+
|
|
17
|
+
### src/payments/retry.ts (+6 / -12)
|
|
18
|
+
- `MAX_ATTEMPTS` 4 -> 3.
|
|
19
|
+
- Backoff replaced with a fixed `await sleep(2000)` between attempts.
|
|
20
|
+
- 4xx short-circuit kept.
|
|
21
|
+
- Header comment changed to `// Processor retry policy: RFC-0002`.
|
|
22
|
+
|
|
23
|
+
### src/webhooks/sign.ts -> src/webhooks/signature.ts (rename, no content change)
|
|
24
|
+
- `send.ts` import updated to `./signature`.
|
|
25
|
+
|
|
26
|
+
### src/webhooks/send.ts (+1 / -1)
|
|
27
|
+
- `console.log` replaced with `logger.info` (same fields).
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// RFC-0003: money is integer minor units.
|
|
2
|
+
export function assertMinorUnits(amountMinor: number): void {
|
|
3
|
+
if (!Number.isInteger(amountMinor) || amountMinor < 0) {
|
|
4
|
+
throw new Error("amountMinor must be a non-negative integer");
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function formatAmount(amountMinor: number, currency: string, digits = 2): string {
|
|
9
|
+
return `${(amountMinor / 10 ** digits).toFixed(digits)} ${currency}`;
|
|
10
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { retryProcessorCall } from "./retry";
|
|
2
|
+
import { assertMinorUnits } from "./amount";
|
|
3
|
+
import { idempotencyStore } from "./idempotency";
|
|
4
|
+
import { sendWebhook } from "../webhooks/send";
|
|
5
|
+
|
|
6
|
+
export interface ChargeRequest {
|
|
7
|
+
amountMinor: number; // integer minor units, see RFC-0003
|
|
8
|
+
currency: string; // ISO 4217
|
|
9
|
+
cardToken: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export async function createCharge(req: ChargeRequest, headers: Record<string, string>) {
|
|
13
|
+
const key = headers["idempotency-key"];
|
|
14
|
+
if (!key) {
|
|
15
|
+
return { status: 400, body: { error: "Idempotency-Key header is required" } }; // RFC-0001
|
|
16
|
+
}
|
|
17
|
+
const previous = await idempotencyStore.get(key);
|
|
18
|
+
if (previous) return previous;
|
|
19
|
+
|
|
20
|
+
assertMinorUnits(req.amountMinor);
|
|
21
|
+
const result = await retryProcessorCall(() => processor.charge(req));
|
|
22
|
+
const response = { status: 201, body: result };
|
|
23
|
+
await idempotencyStore.put(key, response, 24 * 3600);
|
|
24
|
+
await sendWebhook("charge.succeeded", result);
|
|
25
|
+
return response;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
declare const processor: { charge(req: ChargeRequest): Promise<unknown> };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Processor retry policy: RFC-0005 (supersedes RFC-0002).
|
|
2
|
+
const MAX_ATTEMPTS = 4;
|
|
3
|
+
const BASE_MS = 250;
|
|
4
|
+
const CAP_MS = 8000;
|
|
5
|
+
|
|
6
|
+
export async function retryProcessorCall<T>(fn: () => Promise<T>): Promise<T> {
|
|
7
|
+
let lastErr: unknown;
|
|
8
|
+
for (let n = 0; n < MAX_ATTEMPTS; n++) {
|
|
9
|
+
try {
|
|
10
|
+
return await fn();
|
|
11
|
+
} catch (err: any) {
|
|
12
|
+
if (err?.status >= 400 && err?.status < 500) throw err; // never retry 4xx
|
|
13
|
+
lastErr = err;
|
|
14
|
+
const ceiling = Math.min(CAP_MS, BASE_MS * 2 ** n);
|
|
15
|
+
await sleep(Math.random() * ceiling); // full jitter
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
throw lastErr;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { signPayload } from "./sign";
|
|
2
|
+
|
|
3
|
+
export async function sendWebhook(event: string, payload: unknown): Promise<void> {
|
|
4
|
+
const body = JSON.stringify({ event, payload });
|
|
5
|
+
const ts = Math.floor(Date.now() / 1000);
|
|
6
|
+
const signature = signPayload(process.env.WEBHOOK_SECRET ?? "placeholder-secret", ts, body);
|
|
7
|
+
console.log("webhook", event, signature.slice(0, 8));
|
|
8
|
+
// HTTP delivery omitted in this excerpt
|
|
9
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { createHmac } from "crypto";
|
|
2
|
+
|
|
3
|
+
// RFC-0004: the single place outbound webhooks are signed.
|
|
4
|
+
export function signPayload(secret: string, timestamp: number, body: string): string {
|
|
5
|
+
return createHmac("sha256", secret).update(`${timestamp}.${body}`).digest("hex");
|
|
6
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Services deployed to the production cluster
|
|
2
|
+
services:
|
|
3
|
+
- name: shipments
|
|
4
|
+
path: services/shipments
|
|
5
|
+
datastores: [postgres:haulmark_core]
|
|
6
|
+
- name: telemetry
|
|
7
|
+
path: services/telemetry
|
|
8
|
+
datastores: [postgres:haulmark_telemetry]
|
|
9
|
+
- name: api
|
|
10
|
+
path: services/api
|
|
11
|
+
datastores: [postgres:haulmark_core]
|
|
12
|
+
- name: dashboard
|
|
13
|
+
path: web/dashboard
|
|
14
|
+
datastores: []
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# 1. Record architecture decisions
|
|
2
|
+
|
|
3
|
+
Date: 2021-01-18
|
|
4
|
+
|
|
5
|
+
## Status
|
|
6
|
+
|
|
7
|
+
Accepted
|
|
8
|
+
|
|
9
|
+
## Context
|
|
10
|
+
|
|
11
|
+
We need to record the architectural decisions made on Haulmark.
|
|
12
|
+
|
|
13
|
+
## Decision
|
|
14
|
+
|
|
15
|
+
We will use Architecture Decision Records, as described by Michael Nygard,
|
|
16
|
+
kept in `doc/adr/`.
|
|
17
|
+
|
|
18
|
+
## Consequences
|
|
19
|
+
|
|
20
|
+
See Michael Nygard's article "Documenting Architecture Decisions".
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# 2. Use PostgreSQL for shipment data
|
|
2
|
+
|
|
3
|
+
Date: 2021-02-10
|
|
4
|
+
|
|
5
|
+
## Status
|
|
6
|
+
|
|
7
|
+
Accepted
|
|
8
|
+
|
|
9
|
+
## Context
|
|
10
|
+
|
|
11
|
+
Shipments, stops and consignees are relational and need transactional
|
|
12
|
+
updates when a stop is marked delivered. The team already runs PostgreSQL 13.
|
|
13
|
+
|
|
14
|
+
## Decision
|
|
15
|
+
|
|
16
|
+
Shipment data lives in a single PostgreSQL database, `haulmark_core`, owned by
|
|
17
|
+
`services/shipments/`. Other services read shipment data only through the
|
|
18
|
+
shipments API, never by connecting to the database directly.
|
|
19
|
+
|
|
20
|
+
## Consequences
|
|
21
|
+
|
|
22
|
+
Strong consistency for status transitions. Schema migrations are needed for
|
|
23
|
+
new shipment fields.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# 3. Store vehicle telemetry in Cassandra
|
|
2
|
+
|
|
3
|
+
Date: 2021-06-02
|
|
4
|
+
|
|
5
|
+
## Status
|
|
6
|
+
|
|
7
|
+
Superseded by [8. Store vehicle telemetry in TimescaleDB](0008-store-vehicle-telemetry-in-timescaledb.md)
|
|
8
|
+
|
|
9
|
+
## Context
|
|
10
|
+
|
|
11
|
+
Each truck reports GPS position every 5 seconds. At 1,200 trucks this is
|
|
12
|
+
about 20 million rows a day, too much write volume for `haulmark_core`.
|
|
13
|
+
|
|
14
|
+
## Decision
|
|
15
|
+
|
|
16
|
+
Vehicle telemetry is written to a 3-node Cassandra cluster by
|
|
17
|
+
`services/telemetry/`, partitioned by vehicle and day.
|
|
18
|
+
|
|
19
|
+
## Consequences
|
|
20
|
+
|
|
21
|
+
High write throughput. Ad-hoc queries (e.g. "all trucks within 5 km of the
|
|
22
|
+
depot yesterday") are hard to express.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# 4. Use Redis for session storage
|
|
2
|
+
|
|
3
|
+
Date: 2021-07-19
|
|
4
|
+
|
|
5
|
+
## Status
|
|
6
|
+
|
|
7
|
+
Accepted
|
|
8
|
+
|
|
9
|
+
## Context
|
|
10
|
+
|
|
11
|
+
The dispatcher web app needs server-side sessions shared across the API
|
|
12
|
+
instances in `services/api/`.
|
|
13
|
+
|
|
14
|
+
## Decision
|
|
15
|
+
|
|
16
|
+
Sessions are stored in Redis with a 12-hour TTL, keyed by an opaque session
|
|
17
|
+
ID held in a cookie.
|
|
18
|
+
|
|
19
|
+
## Consequences
|
|
20
|
+
|
|
21
|
+
One more piece of infrastructure to run. Logging out everywhere is a single
|
|
22
|
+
key delete.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# 5. Use React for the dispatcher dashboard
|
|
2
|
+
|
|
3
|
+
Date: 2021-09-06
|
|
4
|
+
|
|
5
|
+
## Status
|
|
6
|
+
|
|
7
|
+
Accepted
|
|
8
|
+
|
|
9
|
+
## Context
|
|
10
|
+
|
|
11
|
+
The dispatcher dashboard is moving off server-rendered templates.
|
|
12
|
+
|
|
13
|
+
## Decision
|
|
14
|
+
|
|
15
|
+
We will build the dashboard in React.
|
|
16
|
+
|
|
17
|
+
## Consequences
|
|
18
|
+
|
|
19
|
+
The front-end team needs a separate build pipeline.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# 6. Archive delivered shipments to object storage
|
|
2
|
+
|
|
3
|
+
Date: 2022-03-14
|
|
4
|
+
|
|
5
|
+
## Status
|
|
6
|
+
|
|
7
|
+
Accepted
|
|
8
|
+
|
|
9
|
+
## Context
|
|
10
|
+
|
|
11
|
+
`haulmark_core` has grown to 900 GB, most of it shipments delivered more
|
|
12
|
+
than a year ago that are only read for audits.
|
|
13
|
+
|
|
14
|
+
## Decision
|
|
15
|
+
|
|
16
|
+
A nightly job in `services/archiver/` moves shipments delivered more than
|
|
17
|
+
90 days ago out of `haulmark_core` into S3-compatible object storage as
|
|
18
|
+
Parquet files, one file per depot per day, and deletes them from PostgreSQL.
|
|
19
|
+
|
|
20
|
+
## Consequences
|
|
21
|
+
|
|
22
|
+
`haulmark_core` stays small. Audit queries over archived shipments go
|
|
23
|
+
through the object store, not the shipments API.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# 7. Move shipment records to MongoDB
|
|
2
|
+
|
|
3
|
+
Date: 2022-08-22
|
|
4
|
+
|
|
5
|
+
## Status
|
|
6
|
+
|
|
7
|
+
Accepted
|
|
8
|
+
|
|
9
|
+
## Context
|
|
10
|
+
|
|
11
|
+
Enterprise customers want custom fields on shipments (purchase order numbers,
|
|
12
|
+
temperature bands, hazmat codes) that differ per customer. Every new field
|
|
13
|
+
currently needs a PostgreSQL migration.
|
|
14
|
+
|
|
15
|
+
## Decision
|
|
16
|
+
|
|
17
|
+
Shipment records will be stored as documents in MongoDB, owned by
|
|
18
|
+
`services/shipments/`, so per-customer fields need no schema migration.
|
|
19
|
+
|
|
20
|
+
## Consequences
|
|
21
|
+
|
|
22
|
+
Status transitions that touch several stops lose multi-row transactions
|
|
23
|
+
unless we use MongoDB multi-document transactions.
|