Files

1 line
357 KiB
JSON

{"repo": "abhidya/customcard", "files": {"infra/README.md": {"54bf822d9f0ddb1b55dd967d2ab4410924ddace0": {"t": 1785747280.129479, "n": 16407, "h": "25229e5187d74a2b", "c": "# CustomCard Infrastructure\n\nThis directory is the deployable service skeleton for the production path.\n\n- `docker-compose.dev.yml` runs app, worker, Postgres, Redis, and MinIO for local development.\n- `docker-compose.droplet.yml` is the cheap single-host deployment shape for a small DigitalOcean droplet.\n- `k8s/app.yaml` is the cloud-native starting point for web and worker deployments.\n- `aws/artifact-store/` is the Terraform contract for a private, encrypted,\n versioned production artifact bucket and prefix-scoped app/worker IAM policy.\n- `migrations/001_initial_schema.sql` defines the durable service data model.\n- `env/.env.example` lists required secrets and kill switches.\n- `../scripts/api-server.mjs` serves `/api/health`, API bootstrap/readiness\n contracts, explicit contract/memory/Postgres runtime modes, and the built web\n app from `dist`.\n- `../src/artifactHandoff.ts` defines the render-packet artifact manifest and\n HMAC-signed URL contract used by the API/schema gates.\n- `../src/artifactStore.ts` writes render-packet artifacts to a temporary local\n filesystem object-store path and an injected S3-compatible client contract,\n then verifies readback without network calls.\n- `../scripts/persistence-doctor.mjs` validates auth-session, idempotency, queue\n job, and audit persistence signals.\n- `../scripts/demo-reset.mjs` validates the reviewer demo reset contract before\n anyone attempts to run seed data against a database.\n- `../scripts/deployment-readiness.mjs` emits the local deployment readiness\n report used by `npm run deployment:doctor`.\n- `../src/capacityPlan.ts` and `../src/capacityPlanData.mjs` define executable\n local-dev, cheap-droplet, cloud-native, and SaaS-scale capacity profiles used\n by `npm run capacity:doctor`.\n\nFor the droplet deployment, provide `POSTGRES_PASSWORD`, `CADDY_DOMAIN`,\n`CADDY_ACME_EMAIL`, and `OBJECT_STORE_*` values from a real secret/config source\nor local deployment `.env` that is never committed. The compose file exposes the\napp only behind Caddy on ports 80/443; Caddy obtains TLS, redirects HTTP to\nHTTPS, and sends `Strict-Transport-Security`. `OBJECT_STORE_URL` must be an\nHTTPS S3-compatible endpoint in production so signed storage requests do not\ncross the network in plaintext.\n\nFor Kubernetes, apply the `customcard-migrate` Job successfully before rolling\nthe web and worker Deployments. The deployment manifest keeps the live-order kill\nswitch explicit and uses probes so bad pods fail visibly.\n\nRun the local IaC readiness check before treating the manifests as reviewable:\n\n```sh\nnpm run deployment:doctor\nnpm run capacity:doctor\nnpm run cloud:doctor\nnpm run api:doctor\nnpm run api:doctor:memory\nnpm run api:doctor:postgres\nDATABASE_URL=postgres://... npm run api:doctor:postgres:live\nDATABASE_URL=postgres://... npm run api:doctor:postgres:http\nDATABASE_URL=postgres://... npm run account:doctor:live\nnpm run artifact:doctor\nCLOUDFLARE_ACCOUNT_ID=... CLOUDFLARE_API_TOKEN=... npm run r2:bucket:create:prod\nCLOUDFLARE_ACCOUNT_ID=... CLOUDFLARE_API_TOKEN=... npm run r2:bucket:list\nOBJECT_STORE_URL=http://127.0.0.1:9000 OBJECT_STORE_BUCKET=customcard-ci-artifacts OBJECT_STORE_ACCESS_KEY_ID=customcard OBJECT_STORE_SECRET_ACCESS_KEY=customcard-dev-only OBJECT_STORE_REGION=us-east-1 OBJECT_STORE_SIGNING_SECRET=test-object-store-signing-secret-32 npm run artifact:doctor:s3:live\nOBJECT_STORE_URL=https://<account-id>.r2.cloudflarestorage.com OBJECT_STORE_BUCKET=customcard-prod OBJECT_STORE_REGION=auto OBJECT_STORE_SIGNING_SECRET=test-object-store-signing-secret-32 npm run artifact:doctor:s3:live -- --bucket-mode existing\nnpm run persistence:doctor\nnpm run demo:doctor\n```\n\nThe report checks the local-dev, cheap-droplet, cloud-native, runtime, and data\nlanes, including cloud-storage checks for the AWS artifact-store module. Passing\nthis check means the committed deployment contracts are internally consistent;\nit does not mean a real droplet, AWS account, or Kubernetes cluster has been\nprovisioned.\n\n`npm run capacity:doctor` verifies the profile contract, tests, admin/API\nsurfaces, documentation, CI wiring, and no-live-traffic posture. The profiles are\nplanning data only; measured production benchmarks, provider spend reports, and\nautoscaler evidence remain separate launch evidence.\n\nThe Kubernetes web deployment probes `/api/health`, and the production Docker\nimage starts `scripts/api-server.mjs` so the same container can serve the static\nweb bundle and the contract-first API endpoints.\n\nThe API runtime defaults to `CUSTOMCARD_API_RUNTIME=contract` only for local\nreviewer/static serving. Production-shaped environments must set\n`CUSTOMCARD_API_RUNTIME=postgres`; `runtime:doctor` rejects contract or memory\nthere so auth sessions and idempotency records are durable. `npm run\napi:doctor:memory` passes `--local-auth-fallbacks` with test customer/admin\nsession tokens and validates Bearer auth plus `X-Idempotency-Key` replay without\na live database.\n`npm run api:doctor:postgres` injects a fake Postgres pool into the same runtime\npath and validates session lookup, wrong-role blocking, idempotency replay,\nconflict handling, repository-backed relationship-memory inserts,\nrepository-backed render-packet inserts, repository-backed import-preview\ninserts, repository-backed card-project inserts, manual handoff\norder/consent/event inserts, data-request privacy/consent inserts, audit inserts,\nand queue-job inserts without external credentials.\n`npm run\napi:doctor:postgres:live` creates an isolated temporary database on the configured\nPostgres server, applies the committed migration, seeds customer/admin sessions,\nverifies all repository-backed customer routes plus admin readiness through the\nreal `pg` runtime, exercises relationship-memory, render-packet, import-preview,\ncard-project, manual handoff, and data-request persistence, and drops the\ntemporary database before exiting.\n`npm run\napi:doctor:postgres:http` starts `scripts/api-server.mjs` against the same\nisolated migrated database shape and verifies public health/routes,\nadmin/customer Bearer auth, missing/wrong-role auth blocking, missing\nidempotency blocking, all 6 repository-backed customer HTTP mutations,\nidempotency replay/conflict, audit rows, queue jobs, and repository table counts\nbefore shutting the server down and dropping the temporary database.\nCI runs both live Postgres doctors against a Postgres service; deployed\nproduction account auth remains unclaimed.\n\n`npm run account:doctor:live` uses the\nsame isolated-database pattern to verify hosted account identity rows,\nprovider-subject uniqueness, hashed recovery challenges, durable sessions, and\naccount-recovery audit rows. CI runs it against the Postgres service; live hosted\ntoken verification remains unclaimed.\n\n`npm run artifact:doctor` writes the sample render packet package to a\ntemporary local filesystem object-store path and an injected S3-compatible client\ncontract, reads every artifact back, verifies checksums and byte lengths, stores\nboth handoff manifests, and keeps network calls plus real orders disabled.\n`npm run artifact:doctor:s3:live` writes\nthe same package to a live S3-compatible endpoint such as MinIO using path-style\nSigV4 requests, reads every object back, verifies checksums and byte lengths,\nstores the handoff manifest, reports `cloudWritesVerified: true`, cleans up the\nisolated bucket, and keeps external vendor calls plus real orders disabled. A\nproduction cloud bucket policy/IAM contract is now represented by\n`infra/aws/artifact-store` and checked by `npm run cloud:doctor`; live AWS apply,\nAccess Analyzer review, and account-specific IAM validation remain separate from\nthis repo-local proof.\n\nThe hosted API can persist render-packet image artifacts through\n`scripts/object-store-runtime.mjs` when object-store credentials are configured\nand `CUSTOMCARD_API_RUNTIME` is `memory` or `postgres`. For Cloudflare R2, set\n`OBJECT_STORE_URL` to the account S3 endpoint, `OBJECT_STORE_BUCKET` to the R2\nbucket, `OBJECT_STORE_REGION=auto`, `OBJECT_STORE_PUBLIC_BASE_URL` to the hosted\n`/api/artifacts` route, and mirror the write-capable S3 credentials into\n`OBJECT_STORE_ACCESS_KEY_ID` / `OBJECT_STORE_SECRET_ACCESS_KEY`. Optional\nread-only R2 credentials can be supplied through\n`OBJECT_STORE_READ_ACCESS_KEY_ID` / `OBJECT_STORE_READ_SECRET_ACCESS_KEY` for\nfuture download-only lanes while signed API URLs continue to use\n`OBJECT_STORE_SIGNING_SECRET`. The bucket must already exist; object-scoped R2\ntokens are enough for artifact writes and reads, but they do not create buckets.\nRun `npm run r2:bucket:create:prod` or `npm run r2:bucket:create --\n<bucket-name>` with a short-lived Cloudflare R2 account token when the bucket\nneeds to be provisioned through [Wrangler](https://developers.cloudflare.com/r2/buckets/create-buckets/).\nFor live R2 verification with object-scoped S3 credentials, set\n`--bucket-mode existing` so the doctor writes under\na temporary project prefix inside the configured bucket and only deletes the\nobjects it created.\n\nThe AWS artifact-store module provisions only the artifact storage boundary:\npublic access block, bucket-owner-enforced ownership, versioning, AES256\nserver-side encryption, lifecycle cleanup under `projects/`, an HTTPS-only and\nencrypted-upload bucket policy, and a least-privilege writer policy scoped to\n`projects/*`. Existing app and worker IAM roles can be attached by passing\n`app_role_name` and `worker_role_name`. Runtime outputs name the required\n`OBJECT_STORE_*` values that still need to be mirrored into the platform secret\nor config manager.\n\nThe persistence boundary requires auth-session storage, account identity storage,\nhashed recovery challenges, idempotency replay, queue job envelopes,\nrelationship-memory repository writes, render-packet repository writes,\nrender-packet artifact manifests, signed URL expiry, and append-only audit\nsignals in the migration before production Postgres handlers are claimed.\n\nThe Kubernetes `Secret` in `k8s/app.yaml` is intentionally empty and annotated as\npre-created by a secret manager. Production clusters should source the required\nkeys from the platform secret manager, External Secrets, Sealed Secrets, or an\nequivalent operator-approved mechanism. The `runtime:doctor` check rejects empty\nvalues, known placeholders, short auth/signing secrets, non-Postgres production\nAPI runtimes, and any live-order kill switch value other than `disabled`.\n\nSigned artifact handoff requires `OBJECT_STORE_SIGNING_SECRET` to be present and\nat least 32 characters. The committed manifests also keep\n`ARTIFACT_SIGNED_URL_TTL_MINUTES=15` explicit so download links remain\nshort-lived.\n\nReal external ordering stays disabled by admin safety controls until vendor sandbox tests and physical print certification are recorded.\n\n## Provider Adapter Secrets\n\n`env/.env.example` names the credential-gated adapters represented in\n`src/providerCatalog.ts`:\n\n- Google OAuth: `GOOGLE_OAUTH_CLIENT_ID`, `GOOGLE_OAUTH_CLIENT_SECRET`, `GOOGLE_OAUTH_REDIRECT_URI`, `GOOGLE_OAUTH_TOKEN_ENCRYPTION_KEY`, `GOOGLE_OAUTH_STATE_SECRET`.\n- Microsoft Graph: `MICROSOFT_CLIENT_ID`, `MICROSOFT_CLIENT_SECRET`,\n `MICROSOFT_TENANT_ID`.\n- Event platforms: `EVENTBRITE_CLIENT_ID`, `EVENTBRITE_CLIENT_SECRET`,\n `LUMA_API_KEY`, `MEETUP_CLIENT_ID`, and `MEETUP_CLIENT_SECRET`. Partiful is a\n contract-only/manual path until an official API exists.\n- Hosted identity providers: `CUSTOMCARD_AUTH_CALLBACK_URL`, `AUTH0_DOMAIN`,\n `AUTH0_CLIENT_ID`, `AUTH0_CLIENT_SECRET`, `AUTH0_AUDIENCE`,\n `CLERK_SECRET_KEY`, `CLERK_JWT_KEY`, `CLERK_AUTHORIZED_PARTIES`,\n `SUPABASE_URL`, `SUPABASE_ANON_KEY`, `SUPABASE_SERVICE_ROLE_KEY`,\n `FIREBASE_API_KEY`, `FIREBASE_PROJECT_ID`,\n `FIREBASE_SERVICE_ACCOUNT_JSON`, `COGNITO_DOMAIN`,\n `COGNITO_USER_POOL_ID`, `COGNITO_APP_CLIENT_ID`.\n- Contact import providers: `CARDDAV_BASE_URL`, `CARDDAV_USERNAME`,\n `CARDDAV_APP_PASSWORD`, `CARDDAV_ADDRESSBOOK_PATH`.\n- CRM/customer lifecycle providers: Salesforce, HubSpot, Zoho, Pipedrive,\n Dynamics, Shopify, Klaviyo, Mailchimp, ActiveCampaign, BigCommerce,\n WooCommerce, Square, Intercom, Monday.com, Amazon Selling Partner API, and\n Etsy keys listed in `env/.env.example`.\n- Text and image AI providers: `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`,\n `AZURE_OPENAI_ENDPOINT`, `AZURE_OPENAI_API_KEY`,\n `AZURE_OPENAI_CHAT_DEPLOYMENT`, `AZURE_OPENAI_IMAGE_DEPLOYMENT`,\n `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_REGION`,\n `CLOUDFLARE_ACCOUNT_ID`, `CLOUDFLARE_API_TOKEN`,\n `CLOUDFLARE_WORKERS_AI_TEXT_API_TOKEN`,\n `CLOUDFLARE_WORKERS_AI_IMAGE_API_TOKEN`,\n `GOOGLE_GENERATIVE_AI_API_KEY`, `MISTRAL_API_KEY`, `COHERE_API_KEY`,\n `PERPLEXITY_API_KEY`, `XAI_API_KEY`, `TOGETHER_API_KEY`, `GROQ_API_KEY`,\n `DEEPSEEK_API_KEY`, `FIREWORKS_API_KEY`, `STABILITY_API_KEY`,\n `HUGGINGFACE_API_TOKEN`, `DEEPAI_API_KEY`, `REPLICATE_API_TOKEN`, `IDEOGRAM_API_KEY`,\n `LEONARDO_API_KEY`, `FAL_KEY`, `BFL_API_KEY`.\n- Self-hosted model fallback: `SELF_HOSTED_LLM_BASE_URL`,\n `SELF_HOSTED_LLM_API_KEY`.\n\nCloudflare Workers AI can use one shared `CLOUDFLARE_API_TOKEN` for both\nchat and image generation, or separate lane-specific tokens through\n`CLOUDFLARE_WORKERS_AI_TEXT_API_TOKEN` and\n`CLOUDFLARE_WORKERS_AI_IMAGE_API_TOKEN`. Set provider, model, budget, queue, and\nworkflow policy in Admin Providers. Use\n`@cf/bytedance/stable-diffusion-xl-lightning` as the current Cloudflare image\ndefault, and keep `@cf/black-forest-labs/flux-1-schnell` as the higher-quality\nimage fallback when prompt adherence matters more than the absolute lowest cost.\nThe production-text local Comfy path does not require hosted Cloudflare image\ntokens; those hosted image keys are only needed when you intend to validate the\nlive Cloudflare image lane.\n- Notification providers: `RESEND_API_KEY`, `SENDGRID_API_KEY`,\n `POSTMARK_SERVER_TOKEN`, `MAILGUN_API_KEY`, `MAILGUN_DOMAIN`,\n `TWILIO_ACCOUNT_SID`, `TWILIO_AUTH_TOKEN`,\n `TWILIO_MESSAGING_SERVICE_SID`, `WHATSAPP_ACCESS_TOKEN`,\n `WHATSAPP_PHONE_NUMBER_ID`, `EXPO_ACCESS_TOKEN`, `FIREBASE_PROJECT_ID`,\n `FIREBASE_SERVICE_ACCOUNT_JSON`, `CUSTOMERIO_APP_API_KEY`,\n `CUSTOMERIO_TRANSACTIONAL_MESSAGE_ID`, `BRAZE_REST_ENDPOINT`,\n `BRAZE_REST_API_KEY`, `BRAZE_CANVAS_ID`, `ONESIGNAL_APP_ID`,\n `ONESIGNAL_REST_API_KEY`, `COURIER_AUTH_TOKEN`, `COURIER_TEMPLATE_ID`,\n `KNOCK_API_KEY`, `KNOCK_WORKFLOW_KEY`, `NOVU_API_KEY`, `NOVU_WORKFLOW_ID`,\n `TRANSACTIONAL_EMAIL_API_KEY`, `TRANSACTIONAL_EMAIL_FROM`.\n- Workflow providers: Zapier, Make, Slack, Microsoft Teams, Notion, Airtable,\n Google Sheets, n8n, Workato, and Pipedream keys listed in\n `env/.env.example`.\n- Payment sandbox providers: `STRIPE_SECRET_KEY`, `STRIPE_WEBHOOK_SECRET`,\n `CUSTOMCARD_PAYMENT_SUCCESS_URL`, `CUSTOMCARD_PAYMENT_CANCEL_URL`,\n `PAYPAL_CLIENT_ID`, `PAYPAL_CLIENT_SECRET`, `PAYPAL_WEBHOOK_ID`,\n `SQUARE_ACCESS_TOKEN`, `SQUARE_LOCATION_ID`,\n `SQUARE_WEBHOOK_SIGNATURE_KEY`, `ADYEN_API_KEY`,\n `ADYEN_MERCHANT_ACCOUNT`, `ADYEN_HMAC_KEY`.\n- Observability providers: `SENTRY_DSN`, `SENTRY_PROJECT_ID`,\n `SENTRY_ENVIRONMENT`, `POSTHOG_PROJECT_API_KEY`, `POSTHOG_HOST`,\n `OTEL_EXPORTER_OTLP_ENDPOINT`, `OTEL_EXPORTER_OTLP_HEADERS`,\n `GRAFANA_OTLP_ENDPOINT`, `GRAFANA_OTLP_INSTANCE_ID`,\n `GRAFANA_OTLP_API_KEY`, `DATADOG_API_KEY`, `DATADOG_SITE`,\n `BETTERSTACK_SOURCE_TOKEN`, `BETTERSTACK_INGESTING_HOST`.\n- Object-store artifact signer and live S3-compatible doctor:\n `OBJECT_STORE_URL`,\n `OBJECT_STORE_BUCKET`, `OBJECT_STORE_ACCESS_KEY_ID`,\n `OBJECT_STORE_SECRET_ACCESS_KEY`, `OBJECT_STORE_READ_ACCESS_KEY_ID`,\n `OBJECT_STORE_READ_SECRET_ACCESS_KEY`, `OBJECT_STORE_REGION`,\n `OBJECT_STORE_PUBLIC_BASE_URL`, `OBJECT_STORE_SIGNING_SECRET`,\n `ARTIFACT_SIGNED_URL_TTL_MINUTES`. Bucket-admin setup also uses\n `CLOUDFLARE_ACCOUNT_ID` and a short-lived `CLOUDFLARE_API_TOKEN`.\n- Live vendor adapters: mode and kill-switch state are persisted through admin\n safety controls, not environment variables.\n- Persistence controls: `CUSTOMCARD_API_RUNTIME`, `AUTH_SESSION_SECRET`,\n `CLERK_JWT_KEY`, `CLERK_AUTHORIZED_PARTIES`, `CLERK_ISSUER`,\n `CLERK_AUDIENCE`, `CUSTOMCARD_CUSTOMER_SESSION_TOKEN`,\n `CUSTOMCARD_ADMIN_SESSION_TOKEN`, `IDEMPOTENCY_KEY_TTL_HOURS`.\n\nThese keys are documented for deployment readiness only. The current repo state\ndoes not make live provider calls, and admin safety controls keep live orders\nclosed until certification is recorded.\n"}, "3c441234f95be40eeff239b8e54efdde416b3f5a": {"t": 1785772116.0352752, "n": 16407, "h": "25229e5187d74a2b", "c": "# CustomCard Infrastructure\n\nThis directory is the deployable service skeleton for the production path.\n\n- `docker-compose.dev.yml` runs app, worker, Postgres, Redis, and MinIO for local development.\n- `docker-compose.droplet.yml` is the cheap single-host deployment shape for a small DigitalOcean droplet.\n- `k8s/app.yaml` is the cloud-native starting point for web and worker deployments.\n- `aws/artifact-store/` is the Terraform contract for a private, encrypted,\n versioned production artifact bucket and prefix-scoped app/worker IAM policy.\n- `migrations/001_initial_schema.sql` defines the durable service data model.\n- `env/.env.example` lists required secrets and kill switches.\n- `../scripts/api-server.mjs` serves `/api/health`, API bootstrap/readiness\n contracts, explicit contract/memory/Postgres runtime modes, and the built web\n app from `dist`.\n- `../src/artifactHandoff.ts` defines the render-packet artifact manifest and\n HMAC-signed URL contract used by the API/schema gates.\n- `../src/artifactStore.ts` writes render-packet artifacts to a temporary local\n filesystem object-store path and an injected S3-compatible client contract,\n then verifies readback without network calls.\n- `../scripts/persistence-doctor.mjs` validates auth-session, idempotency, queue\n job, and audit persistence signals.\n- `../scripts/demo-reset.mjs` validates the reviewer demo reset contract before\n anyone attempts to run seed data against a database.\n- `../scripts/deployment-readiness.mjs` emits the local deployment readiness\n report used by `npm run deployment:doctor`.\n- `../src/capacityPlan.ts` and `../src/capacityPlanData.mjs` define executable\n local-dev, cheap-droplet, cloud-native, and SaaS-scale capacity profiles used\n by `npm run capacity:doctor`.\n\nFor the droplet deployment, provide `POSTGRES_PASSWORD`, `CADDY_DOMAIN`,\n`CADDY_ACME_EMAIL`, and `OBJECT_STORE_*` values from a real secret/config source\nor local deployment `.env` that is never committed. The compose file exposes the\napp only behind Caddy on ports 80/443; Caddy obtains TLS, redirects HTTP to\nHTTPS, and sends `Strict-Transport-Security`. `OBJECT_STORE_URL` must be an\nHTTPS S3-compatible endpoint in production so signed storage requests do not\ncross the network in plaintext.\n\nFor Kubernetes, apply the `customcard-migrate` Job successfully before rolling\nthe web and worker Deployments. The deployment manifest keeps the live-order kill\nswitch explicit and uses probes so bad pods fail visibly.\n\nRun the local IaC readiness check before treating the manifests as reviewable:\n\n```sh\nnpm run deployment:doctor\nnpm run capacity:doctor\nnpm run cloud:doctor\nnpm run api:doctor\nnpm run api:doctor:memory\nnpm run api:doctor:postgres\nDATABASE_URL=postgres://... npm run api:doctor:postgres:live\nDATABASE_URL=postgres://... npm run api:doctor:postgres:http\nDATABASE_URL=postgres://... npm run account:doctor:live\nnpm run artifact:doctor\nCLOUDFLARE_ACCOUNT_ID=... CLOUDFLARE_API_TOKEN=... npm run r2:bucket:create:prod\nCLOUDFLARE_ACCOUNT_ID=... CLOUDFLARE_API_TOKEN=... npm run r2:bucket:list\nOBJECT_STORE_URL=http://127.0.0.1:9000 OBJECT_STORE_BUCKET=customcard-ci-artifacts OBJECT_STORE_ACCESS_KEY_ID=customcard OBJECT_STORE_SECRET_ACCESS_KEY=customcard-dev-only OBJECT_STORE_REGION=us-east-1 OBJECT_STORE_SIGNING_SECRET=test-object-store-signing-secret-32 npm run artifact:doctor:s3:live\nOBJECT_STORE_URL=https://<account-id>.r2.cloudflarestorage.com OBJECT_STORE_BUCKET=customcard-prod OBJECT_STORE_REGION=auto OBJECT_STORE_SIGNING_SECRET=test-object-store-signing-secret-32 npm run artifact:doctor:s3:live -- --bucket-mode existing\nnpm run persistence:doctor\nnpm run demo:doctor\n```\n\nThe report checks the local-dev, cheap-droplet, cloud-native, runtime, and data\nlanes, including cloud-storage checks for the AWS artifact-store module. Passing\nthis check means the committed deployment contracts are internally consistent;\nit does not mean a real droplet, AWS account, or Kubernetes cluster has been\nprovisioned.\n\n`npm run capacity:doctor` verifies the profile contract, tests, admin/API\nsurfaces, documentation, CI wiring, and no-live-traffic posture. The profiles are\nplanning data only; measured production benchmarks, provider spend reports, and\nautoscaler evidence remain separate launch evidence.\n\nThe Kubernetes web deployment probes `/api/health`, and the production Docker\nimage starts `scripts/api-server.mjs` so the same container can serve the static\nweb bundle and the contract-first API endpoints.\n\nThe API runtime defaults to `CUSTOMCARD_API_RUNTIME=contract` only for local\nreviewer/static serving. Production-shaped environments must set\n`CUSTOMCARD_API_RUNTIME=postgres`; `runtime:doctor` rejects contract or memory\nthere so auth sessions and idempotency records are durable. `npm run\napi:doctor:memory` passes `--local-auth-fallbacks` with test customer/admin\nsession tokens and validates Bearer auth plus `X-Idempotency-Key` replay without\na live database.\n`npm run api:doctor:postgres` injects a fake Postgres pool into the same runtime\npath and validates session lookup, wrong-role blocking, idempotency replay,\nconflict handling, repository-backed relationship-memory inserts,\nrepository-backed render-packet inserts, repository-backed import-preview\ninserts, repository-backed card-project inserts, manual handoff\norder/consent/event inserts, data-request privacy/consent inserts, audit inserts,\nand queue-job inserts without external credentials.\n`npm run\napi:doctor:postgres:live` creates an isolated temporary database on the configured\nPostgres server, applies the committed migration, seeds customer/admin sessions,\nverifies all repository-backed customer routes plus admin readiness through the\nreal `pg` runtime, exercises relationship-memory, render-packet, import-preview,\ncard-project, manual handoff, and data-request persistence, and drops the\ntemporary database before exiting.\n`npm run\napi:doctor:postgres:http` starts `scripts/api-server.mjs` against the same\nisolated migrated database shape and verifies public health/routes,\nadmin/customer Bearer auth, missing/wrong-role auth blocking, missing\nidempotency blocking, all 6 repository-backed customer HTTP mutations,\nidempotency replay/conflict, audit rows, queue jobs, and repository table counts\nbefore shutting the server down and dropping the temporary database.\nCI runs both live Postgres doctors against a Postgres service; deployed\nproduction account auth remains unclaimed.\n\n`npm run account:doctor:live` uses the\nsame isolated-database pattern to verify hosted account identity rows,\nprovider-subject uniqueness, hashed recovery challenges, durable sessions, and\naccount-recovery audit rows. CI runs it against the Postgres service; live hosted\ntoken verification remains unclaimed.\n\n`npm run artifact:doctor` writes the sample render packet package to a\ntemporary local filesystem object-store path and an injected S3-compatible client\ncontract, reads every artifact back, verifies checksums and byte lengths, stores\nboth handoff manifests, and keeps network calls plus real orders disabled.\n`npm run artifact:doctor:s3:live` writes\nthe same package to a live S3-compatible endpoint such as MinIO using path-style\nSigV4 requests, reads every object back, verifies checksums and byte lengths,\nstores the handoff manifest, reports `cloudWritesVerified: true`, cleans up the\nisolated bucket, and keeps external vendor calls plus real orders disabled. A\nproduction cloud bucket policy/IAM contract is now represented by\n`infra/aws/artifact-store` and checked by `npm run cloud:doctor`; live AWS apply,\nAccess Analyzer review, and account-specific IAM validation remain separate from\nthis repo-local proof.\n\nThe hosted API can persist render-packet image artifacts through\n`scripts/object-store-runtime.mjs` when object-store credentials are configured\nand `CUSTOMCARD_API_RUNTIME` is `memory` or `postgres`. For Cloudflare R2, set\n`OBJECT_STORE_URL` to the account S3 endpoint, `OBJECT_STORE_BUCKET` to the R2\nbucket, `OBJECT_STORE_REGION=auto`, `OBJECT_STORE_PUBLIC_BASE_URL` to the hosted\n`/api/artifacts` route, and mirror the write-capable S3 credentials into\n`OBJECT_STORE_ACCESS_KEY_ID` / `OBJECT_STORE_SECRET_ACCESS_KEY`. Optional\nread-only R2 credentials can be supplied through\n`OBJECT_STORE_READ_ACCESS_KEY_ID` / `OBJECT_STORE_READ_SECRET_ACCESS_KEY` for\nfuture download-only lanes while signed API URLs continue to use\n`OBJECT_STORE_SIGNING_SECRET`. The bucket must already exist; object-scoped R2\ntokens are enough for artifact writes and reads, but they do not create buckets.\nRun `npm run r2:bucket:create:prod` or `npm run r2:bucket:create --\n<bucket-name>` with a short-lived Cloudflare R2 account token when the bucket\nneeds to be provisioned through [Wrangler](https://developers.cloudflare.com/r2/buckets/create-buckets/).\nFor live R2 verification with object-scoped S3 credentials, set\n`--bucket-mode existing` so the doctor writes under\na temporary project prefix inside the configured bucket and only deletes the\nobjects it created.\n\nThe AWS artifact-store module provisions only the artifact storage boundary:\npublic access block, bucket-owner-enforced ownership, versioning, AES256\nserver-side encryption, lifecycle cleanup under `projects/`, an HTTPS-only and\nencrypted-upload bucket policy, and a least-privilege writer policy scoped to\n`projects/*`. Existing app and worker IAM roles can be attached by passing\n`app_role_name` and `worker_role_name`. Runtime outputs name the required\n`OBJECT_STORE_*` values that still need to be mirrored into the platform secret\nor config manager.\n\nThe persistence boundary requires auth-session storage, account identity storage,\nhashed recovery challenges, idempotency replay, queue job envelopes,\nrelationship-memory repository writes, render-packet repository writes,\nrender-packet artifact manifests, signed URL expiry, and append-only audit\nsignals in the migration before production Postgres handlers are claimed.\n\nThe Kubernetes `Secret` in `k8s/app.yaml` is intentionally empty and annotated as\npre-created by a secret manager. Production clusters should source the required\nkeys from the platform secret manager, External Secrets, Sealed Secrets, or an\nequivalent operator-approved mechanism. The `runtime:doctor` check rejects empty\nvalues, known placeholders, short auth/signing secrets, non-Postgres production\nAPI runtimes, and any live-order kill switch value other than `disabled`.\n\nSigned artifact handoff requires `OBJECT_STORE_SIGNING_SECRET` to be present and\nat least 32 characters. The committed manifests also keep\n`ARTIFACT_SIGNED_URL_TTL_MINUTES=15` explicit so download links remain\nshort-lived.\n\nReal external ordering stays disabled by admin safety controls until vendor sandbox tests and physical print certification are recorded.\n\n## Provider Adapter Secrets\n\n`env/.env.example` names the credential-gated adapters represented in\n`src/providerCatalog.ts`:\n\n- Google OAuth: `GOOGLE_OAUTH_CLIENT_ID`, `GOOGLE_OAUTH_CLIENT_SECRET`, `GOOGLE_OAUTH_REDIRECT_URI`, `GOOGLE_OAUTH_TOKEN_ENCRYPTION_KEY`, `GOOGLE_OAUTH_STATE_SECRET`.\n- Microsoft Graph: `MICROSOFT_CLIENT_ID`, `MICROSOFT_CLIENT_SECRET`,\n `MICROSOFT_TENANT_ID`.\n- Event platforms: `EVENTBRITE_CLIENT_ID`, `EVENTBRITE_CLIENT_SECRET`,\n `LUMA_API_KEY`, `MEETUP_CLIENT_ID`, and `MEETUP_CLIENT_SECRET`. Partiful is a\n contract-only/manual path until an official API exists.\n- Hosted identity providers: `CUSTOMCARD_AUTH_CALLBACK_URL`, `AUTH0_DOMAIN`,\n `AUTH0_CLIENT_ID`, `AUTH0_CLIENT_SECRET`, `AUTH0_AUDIENCE`,\n `CLERK_SECRET_KEY`, `CLERK_JWT_KEY`, `CLERK_AUTHORIZED_PARTIES`,\n `SUPABASE_URL`, `SUPABASE_ANON_KEY`, `SUPABASE_SERVICE_ROLE_KEY`,\n `FIREBASE_API_KEY`, `FIREBASE_PROJECT_ID`,\n `FIREBASE_SERVICE_ACCOUNT_JSON`, `COGNITO_DOMAIN`,\n `COGNITO_USER_POOL_ID`, `COGNITO_APP_CLIENT_ID`.\n- Contact import providers: `CARDDAV_BASE_URL`, `CARDDAV_USERNAME`,\n `CARDDAV_APP_PASSWORD`, `CARDDAV_ADDRESSBOOK_PATH`.\n- CRM/customer lifecycle providers: Salesforce, HubSpot, Zoho, Pipedrive,\n Dynamics, Shopify, Klaviyo, Mailchimp, ActiveCampaign, BigCommerce,\n WooCommerce, Square, Intercom, Monday.com, Amazon Selling Partner API, and\n Etsy keys listed in `env/.env.example`.\n- Text and image AI providers: `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`,\n `AZURE_OPENAI_ENDPOINT`, `AZURE_OPENAI_API_KEY`,\n `AZURE_OPENAI_CHAT_DEPLOYMENT`, `AZURE_OPENAI_IMAGE_DEPLOYMENT`,\n `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_REGION`,\n `CLOUDFLARE_ACCOUNT_ID`, `CLOUDFLARE_API_TOKEN`,\n `CLOUDFLARE_WORKERS_AI_TEXT_API_TOKEN`,\n `CLOUDFLARE_WORKERS_AI_IMAGE_API_TOKEN`,\n `GOOGLE_GENERATIVE_AI_API_KEY`, `MISTRAL_API_KEY`, `COHERE_API_KEY`,\n `PERPLEXITY_API_KEY`, `XAI_API_KEY`, `TOGETHER_API_KEY`, `GROQ_API_KEY`,\n `DEEPSEEK_API_KEY`, `FIREWORKS_API_KEY`, `STABILITY_API_KEY`,\n `HUGGINGFACE_API_TOKEN`, `DEEPAI_API_KEY`, `REPLICATE_API_TOKEN`, `IDEOGRAM_API_KEY`,\n `LEONARDO_API_KEY`, `FAL_KEY`, `BFL_API_KEY`.\n- Self-hosted model fallback: `SELF_HOSTED_LLM_BASE_URL`,\n `SELF_HOSTED_LLM_API_KEY`.\n\nCloudflare Workers AI can use one shared `CLOUDFLARE_API_TOKEN` for both\nchat and image generation, or separate lane-specific tokens through\n`CLOUDFLARE_WORKERS_AI_TEXT_API_TOKEN` and\n`CLOUDFLARE_WORKERS_AI_IMAGE_API_TOKEN`. Set provider, model, budget, queue, and\nworkflow policy in Admin Providers. Use\n`@cf/bytedance/stable-diffusion-xl-lightning` as the current Cloudflare image\ndefault, and keep `@cf/black-forest-labs/flux-1-schnell` as the higher-quality\nimage fallback when prompt adherence matters more than the absolute lowest cost.\nThe production-text local Comfy path does not require hosted Cloudflare image\ntokens; those hosted image keys are only needed when you intend to validate the\nlive Cloudflare image lane.\n- Notification providers: `RESEND_API_KEY`, `SENDGRID_API_KEY`,\n `POSTMARK_SERVER_TOKEN`, `MAILGUN_API_KEY`, `MAILGUN_DOMAIN`,\n `TWILIO_ACCOUNT_SID`, `TWILIO_AUTH_TOKEN`,\n `TWILIO_MESSAGING_SERVICE_SID`, `WHATSAPP_ACCESS_TOKEN`,\n `WHATSAPP_PHONE_NUMBER_ID`, `EXPO_ACCESS_TOKEN`, `FIREBASE_PROJECT_ID`,\n `FIREBASE_SERVICE_ACCOUNT_JSON`, `CUSTOMERIO_APP_API_KEY`,\n `CUSTOMERIO_TRANSACTIONAL_MESSAGE_ID`, `BRAZE_REST_ENDPOINT`,\n `BRAZE_REST_API_KEY`, `BRAZE_CANVAS_ID`, `ONESIGNAL_APP_ID`,\n `ONESIGNAL_REST_API_KEY`, `COURIER_AUTH_TOKEN`, `COURIER_TEMPLATE_ID`,\n `KNOCK_API_KEY`, `KNOCK_WORKFLOW_KEY`, `NOVU_API_KEY`, `NOVU_WORKFLOW_ID`,\n `TRANSACTIONAL_EMAIL_API_KEY`, `TRANSACTIONAL_EMAIL_FROM`.\n- Workflow providers: Zapier, Make, Slack, Microsoft Teams, Notion, Airtable,\n Google Sheets, n8n, Workato, and Pipedream keys listed in\n `env/.env.example`.\n- Payment sandbox providers: `STRIPE_SECRET_KEY`, `STRIPE_WEBHOOK_SECRET`,\n `CUSTOMCARD_PAYMENT_SUCCESS_URL`, `CUSTOMCARD_PAYMENT_CANCEL_URL`,\n `PAYPAL_CLIENT_ID`, `PAYPAL_CLIENT_SECRET`, `PAYPAL_WEBHOOK_ID`,\n `SQUARE_ACCESS_TOKEN`, `SQUARE_LOCATION_ID`,\n `SQUARE_WEBHOOK_SIGNATURE_KEY`, `ADYEN_API_KEY`,\n `ADYEN_MERCHANT_ACCOUNT`, `ADYEN_HMAC_KEY`.\n- Observability providers: `SENTRY_DSN`, `SENTRY_PROJECT_ID`,\n `SENTRY_ENVIRONMENT`, `POSTHOG_PROJECT_API_KEY`, `POSTHOG_HOST`,\n `OTEL_EXPORTER_OTLP_ENDPOINT`, `OTEL_EXPORTER_OTLP_HEADERS`,\n `GRAFANA_OTLP_ENDPOINT`, `GRAFANA_OTLP_INSTANCE_ID`,\n `GRAFANA_OTLP_API_KEY`, `DATADOG_API_KEY`, `DATADOG_SITE`,\n `BETTERSTACK_SOURCE_TOKEN`, `BETTERSTACK_INGESTING_HOST`.\n- Object-store artifact signer and live S3-compatible doctor:\n `OBJECT_STORE_URL`,\n `OBJECT_STORE_BUCKET`, `OBJECT_STORE_ACCESS_KEY_ID`,\n `OBJECT_STORE_SECRET_ACCESS_KEY`, `OBJECT_STORE_READ_ACCESS_KEY_ID`,\n `OBJECT_STORE_READ_SECRET_ACCESS_KEY`, `OBJECT_STORE_REGION`,\n `OBJECT_STORE_PUBLIC_BASE_URL`, `OBJECT_STORE_SIGNING_SECRET`,\n `ARTIFACT_SIGNED_URL_TTL_MINUTES`. Bucket-admin setup also uses\n `CLOUDFLARE_ACCOUNT_ID` and a short-lived `CLOUDFLARE_API_TOKEN`.\n- Live vendor adapters: mode and kill-switch state are persisted through admin\n safety controls, not environment variables.\n- Persistence controls: `CUSTOMCARD_API_RUNTIME`, `AUTH_SESSION_SECRET`,\n `CLERK_JWT_KEY`, `CLERK_AUTHORIZED_PARTIES`, `CLERK_ISSUER`,\n `CLERK_AUDIENCE`, `CUSTOMCARD_CUSTOMER_SESSION_TOKEN`,\n `CUSTOMCARD_ADMIN_SESSION_TOKEN`, `IDEMPOTENCY_KEY_TTL_HOURS`.\n\nThese keys are documented for deployment readiness only. The current repo state\ndoes not make live provider calls, and admin safety controls keep live orders\nclosed until certification is recorded.\n"}, "064b0ade31502efa2842047c68ccd7bcfdb77963": {"t": 1785772117.5356743, "n": 16470, "h": "b10ca73985230429", "c": "# CustomCard Infrastructure\n\nThis directory is the deployable service skeleton for the production path.\n\n- `docker-compose.dev.yml` runs app, worker, Postgres, Redis, and MinIO for local development.\n- `docker-compose.droplet.yml` is the cheap single-host deployment shape for a small DigitalOcean droplet.\n- `k8s/app.yaml` is the cloud-native starting point for web and worker deployments.\n- `aws/artifact-store/` is the Terraform contract for a private, encrypted,\n versioned production artifact bucket and prefix-scoped app/worker IAM policy.\n- `migrations/001_initial_schema.sql` defines the durable service data model.\n- `env/.env.example` lists required secrets and kill switches.\n- `../scripts/api-server.mjs` serves `/api/health`, API bootstrap/readiness\n contracts, explicit contract/memory/Postgres runtime modes, and the built web\n app from `dist`.\n- `../src/artifactHandoff.ts` defines the render-packet artifact manifest and\n HMAC-signed URL contract used by the API/schema gates.\n- `../src/artifactStore.ts` writes render-packet artifacts to a temporary local\n filesystem object-store path and an injected S3-compatible client contract,\n then verifies readback without network calls.\n- `../scripts/persistence-doctor.mjs` validates auth-session, idempotency, queue\n job, and audit persistence signals.\n- `../scripts/demo-reset.mjs` validates the reviewer demo reset contract before\n anyone attempts to run seed data against a database.\n- `../scripts/deployment-readiness.mjs` emits the local deployment readiness\n report used by `npm run deployment:doctor`.\n- `../src/capacityPlan.ts` and `../src/capacityPlanData.mjs` define executable\n local-dev, cheap-droplet, cloud-native, and SaaS-scale capacity profiles used\n by `npm run capacity:doctor`.\n\nFor the droplet deployment, provide `POSTGRES_PASSWORD`, `CADDY_DOMAIN`,\n`CADDY_ACME_EMAIL`, and `OBJECT_STORE_*` values from a real secret/config source\nor local deployment `.env` that is never committed. The compose file exposes the\napp only behind Caddy on ports 80/443; Caddy obtains TLS, redirects HTTP to\nHTTPS, and sends `Strict-Transport-Security`. `OBJECT_STORE_URL` must be an\nHTTPS S3-compatible endpoint in production so signed storage requests do not\ncross the network in plaintext.\n\nFor Kubernetes, apply the `customcard-migrate` Job successfully before rolling\nthe web and worker Deployments. The deployment manifest keeps the live-order kill\nswitch explicit and uses probes so bad pods fail visibly.\n\nRun the local IaC readiness check before treating the manifests as reviewable:\n\n```sh\nnpm run deployment:doctor\nnpm run capacity:doctor\nnpm run cloud:doctor\nnpm run api:doctor\nnpm run api:doctor:memory\nnpm run api:doctor:postgres\nDATABASE_URL=postgres://... npm run api:doctor:postgres:live\nDATABASE_URL=postgres://... npm run api:doctor:postgres:http\nDATABASE_URL=postgres://... npm run account:doctor:live\nnpm run artifact:doctor\nCLOUDFLARE_ACCOUNT_ID=... CLOUDFLARE_API_TOKEN=... npm run r2:bucket:create:prod\nCLOUDFLARE_ACCOUNT_ID=... CLOUDFLARE_API_TOKEN=... npm run r2:bucket:list\nOBJECT_STORE_URL=http://127.0.0.1:9000 OBJECT_STORE_BUCKET=customcard-ci-artifacts OBJECT_STORE_ACCESS_KEY_ID=customcard OBJECT_STORE_SECRET_ACCESS_KEY=customcard-dev-only OBJECT_STORE_REGION=us-east-1 OBJECT_STORE_SIGNING_SECRET=test-object-store-signing-secret-32 npm run artifact:doctor:s3:live\nOBJECT_STORE_URL=https://<account-id>.r2.cloudflarestorage.com OBJECT_STORE_BUCKET=customcard-prod OBJECT_STORE_REGION=auto OBJECT_STORE_SIGNING_SECRET=test-object-store-signing-secret-32 npm run artifact:doctor:s3:live -- --bucket-mode existing\nnpm run persistence:doctor\nnpm run demo:doctor\n```\n\nThe report checks the local-dev, cheap-droplet, cloud-native, runtime, and data\nlanes, including cloud-storage checks for the AWS artifact-store module. Passing\nthis check means the committed deployment contracts are internally consistent;\nit does not mean a real droplet, AWS account, or Kubernetes cluster has been\nprovisioned.\n\n`npm run capacity:doctor` verifies the profile contract, tests, admin/API\nsurfaces, documentation, CI wiring, and no-live-traffic posture. The profiles are\nplanning data only; measured production benchmarks, provider spend reports, and\nautoscaler evidence remain separate launch evidence.\n\nThe Kubernetes web deployment probes `/api/health`, and the production Docker\nimage starts `scripts/api-server.mjs` so the same container can serve the static\nweb bundle and the contract-first API endpoints.\n\nThe API runtime defaults to `CUSTOMCARD_API_RUNTIME=contract` only for local\nreviewer/static serving. Production-shaped environments must set\n`CUSTOMCARD_API_RUNTIME=postgres`; `runtime:doctor` rejects contract or memory\nthere so auth sessions and idempotency records are durable. `npm run\napi:doctor:memory` passes `--local-auth-fallbacks` with test customer/admin\nsession tokens and validates Bearer auth plus `X-Idempotency-Key` replay without\na live database.\n`npm run api:doctor:postgres` injects a fake Postgres pool into the same runtime\npath and validates session lookup, wrong-role blocking, idempotency replay,\nconflict handling, repository-backed relationship-memory inserts,\nrepository-backed render-packet inserts, repository-backed import-preview\ninserts, repository-backed card-project inserts, manual handoff\norder/consent/event inserts, data-request privacy/consent inserts, audit inserts,\nand queue-job inserts without external credentials.\n`npm run\napi:doctor:postgres:live` creates an isolated temporary database on the configured\nPostgres server, applies the committed migration, seeds customer/admin sessions,\nverifies all repository-backed customer routes plus admin readiness through the\nreal `pg` runtime, exercises relationship-memory, render-packet, import-preview,\ncard-project, manual handoff, and data-request persistence, and drops the\ntemporary database before exiting.\n`npm run\napi:doctor:postgres:http` starts `scripts/api-server.mjs` against the same\nisolated migrated database shape and verifies public health/routes,\nadmin/customer Bearer auth, missing/wrong-role auth blocking, missing\nidempotency blocking, all 6 repository-backed customer HTTP mutations,\nidempotency replay/conflict, audit rows, queue jobs, and repository table counts\nbefore shutting the server down and dropping the temporary database.\nCI runs both live Postgres doctors against a Postgres service; deployed\nproduction account auth remains unclaimed.\n\n`npm run account:doctor:live` uses the\nsame isolated-database pattern to verify hosted account identity rows,\nprovider-subject uniqueness, hashed recovery challenges, durable sessions, and\naccount-recovery audit rows. CI runs it against the Postgres service; live hosted\ntoken verification remains unclaimed.\n\n`npm run artifact:doctor` writes the sample render packet package to a\ntemporary local filesystem object-store path and an injected S3-compatible client\ncontract, reads every artifact back, verifies checksums and byte lengths, stores\nboth handoff manifests, and keeps network calls plus real orders disabled.\n`npm run artifact:doctor:s3:live` writes\nthe same package to a live S3-compatible endpoint such as MinIO using path-style\nSigV4 requests, reads every object back, verifies checksums and byte lengths,\nstores the handoff manifest, reports `cloudWritesVerified: true`, cleans up the\nisolated bucket, and keeps external vendor calls plus real orders disabled. A\nproduction cloud bucket policy/IAM contract is now represented by\n`infra/aws/artifact-store` and checked by `npm run cloud:doctor`; live AWS apply,\nAccess Analyzer review, and account-specific IAM validation remain separate from\nthis repo-local proof.\n\nThe hosted API can persist render-packet image artifacts through\n`scripts/object-store-runtime.mjs` when object-store credentials are configured\nand `CUSTOMCARD_API_RUNTIME` is `memory` or `postgres`. For Cloudflare R2, set\n`OBJECT_STORE_URL` to the account S3 endpoint, `OBJECT_STORE_BUCKET` to the R2\nbucket, `OBJECT_STORE_REGION=auto`, `OBJECT_STORE_PUBLIC_BASE_URL` to the hosted\n`/api/artifacts` route, and mirror the write-capable S3 credentials into\n`OBJECT_STORE_ACCESS_KEY_ID` / `OBJECT_STORE_SECRET_ACCESS_KEY`. Optional\nread-only R2 credentials can be supplied through\n`OBJECT_STORE_READ_ACCESS_KEY_ID` / `OBJECT_STORE_READ_SECRET_ACCESS_KEY` for\nfuture download-only lanes while signed API URLs continue to use\n`OBJECT_STORE_SIGNING_SECRET`. The bucket must already exist; object-scoped R2\ntokens are enough for artifact writes and reads, but they do not create buckets.\nRun `npm run r2:bucket:create:prod` or `npm run r2:bucket:create --\n<bucket-name>` with a short-lived Cloudflare R2 account token when the bucket\nneeds to be provisioned through [Wrangler](https://developers.cloudflare.com/r2/buckets/create-buckets/).\nFor live R2 verification with object-scoped S3 credentials, set\n`--bucket-mode existing` so the doctor writes under\na temporary project prefix inside the configured bucket and only deletes the\nobjects it created.\n\nThe AWS artifact-store module provisions only the artifact storage boundary:\npublic access block, bucket-owner-enforced ownership, versioning, AES256\nserver-side encryption, lifecycle cleanup under `projects/`, an HTTPS-only and\nencrypted-upload bucket policy, and a least-privilege writer policy scoped to\n`projects/*`. Existing app and worker IAM roles can be attached by passing\n`app_role_name` and `worker_role_name`. Runtime outputs name the required\n`OBJECT_STORE_*` values that still need to be mirrored into the platform secret\nor config manager.\n\nThe persistence boundary requires auth-session storage, account identity storage,\nhashed recovery challenges, idempotency replay, queue job envelopes,\nrelationship-memory repository writes, render-packet repository writes,\nrender-packet artifact manifests, signed URL expiry, and append-only audit\nsignals in the migration before production Postgres handlers are claimed.\n\nThe Kubernetes `Secret` in `k8s/app.yaml` is intentionally empty and annotated as\npre-created by a secret manager. Production clusters should source the required\nkeys from the platform secret manager, External Secrets, Sealed Secrets, or an\nequivalent operator-approved mechanism. The `runtime:doctor` check rejects empty\nvalues, known placeholders, short auth/signing secrets, non-Postgres production\nAPI runtimes, and any live-order kill switch value other than `disabled`.\n\nSigned artifact handoff requires `OBJECT_STORE_SIGNING_SECRET` to be present and\nat least 32 characters. The committed manifests also keep\n`ARTIFACT_SIGNED_URL_TTL_MINUTES=15` explicit so download links remain\nshort-lived.\n\nReal external ordering stays disabled by admin safety controls until vendor sandbox tests and physical print certification are recorded.\n\n## Provider Adapter Secrets\n\n`env/.env.example` names the credential-gated adapters represented in\n`src/providerCatalog.ts`:\n\n- Google OAuth: `GOOGLE_OAUTH_CLIENT_ID`, `GOOGLE_OAUTH_CLIENT_SECRET`, `GOOGLE_OAUTH_REDIRECT_URI`, `GOOGLE_OAUTH_TOKEN_ENCRYPTION_KEY`, `GOOGLE_OAUTH_STATE_SECRET`.\n- Microsoft Graph: `MICROSOFT_CLIENT_ID`, `MICROSOFT_CLIENT_SECRET`,\n `MICROSOFT_TENANT_ID`.\n- Event platforms: `EVENTBRITE_CLIENT_ID`, `EVENTBRITE_CLIENT_SECRET`,\n `LUMA_API_KEY`, `MEETUP_CLIENT_ID`, and `MEETUP_CLIENT_SECRET`. Partiful is a\n contract-only/manual path until an official API exists.\n- Hosted identity providers: `CUSTOMCARD_AUTH_CALLBACK_URL`, `AUTH0_DOMAIN`,\n `AUTH0_CLIENT_ID`, `AUTH0_CLIENT_SECRET`, `AUTH0_AUDIENCE`,\n `CLERK_SECRET_KEY`, `CLERK_JWT_KEY`, `CLERK_AUTHORIZED_PARTIES`,\n `SUPABASE_URL`, `SUPABASE_ANON_KEY`, `SUPABASE_SERVICE_ROLE_KEY`,\n `FIREBASE_API_KEY`, `FIREBASE_PROJECT_ID`,\n `FIREBASE_SERVICE_ACCOUNT_JSON`, `COGNITO_DOMAIN`,\n `COGNITO_USER_POOL_ID`, `COGNITO_APP_CLIENT_ID`.\n- Contact import providers: `CARDDAV_BASE_URL`, `CARDDAV_USERNAME`,\n `CARDDAV_APP_PASSWORD`, `CARDDAV_ADDRESSBOOK_PATH`.\n- CRM/customer lifecycle providers: Salesforce, HubSpot, Zoho, Pipedrive,\n Dynamics, Shopify, Klaviyo, Mailchimp, ActiveCampaign, BigCommerce,\n WooCommerce, Square, Intercom, Monday.com, Amazon Selling Partner API, and\n Etsy keys listed in `env/.env.example`.\n- Text and image AI providers: `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`,\n `AZURE_OPENAI_ENDPOINT`, `AZURE_OPENAI_API_KEY`,\n `AZURE_OPENAI_CHAT_DEPLOYMENT`, `AZURE_OPENAI_IMAGE_DEPLOYMENT`,\n `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_REGION`,\n `CLOUDFLARE_ACCOUNT_ID`, `CLOUDFLARE_API_TOKEN`,\n `CLOUDFLARE_WORKERS_AI_TEXT_API_TOKEN`,\n `CLOUDFLARE_WORKERS_AI_IMAGE_API_TOKEN`,\n `GOOGLE_GENERATIVE_AI_API_KEY`, `MISTRAL_API_KEY`, `COHERE_API_KEY`,\n `PERPLEXITY_API_KEY`, `XAI_API_KEY`, `TOGETHER_API_KEY`, `GROQ_API_KEY`,\n `DEEPSEEK_API_KEY`, `FIREWORKS_API_KEY`, `STABILITY_API_KEY`,\n `HUGGINGFACE_API_TOKEN`, `DEEPAI_API_KEY`, `REPLICATE_API_TOKEN`, `IDEOGRAM_API_KEY`,\n `LEONARDO_API_KEY`, `FAL_KEY`, `BFL_API_KEY`.\n- Self-hosted model fallback: `SELF_HOSTED_LLM_BASE_URL`,\n `SELF_HOSTED_LLM_API_KEY`.\n\nCloudflare Workers AI can use one shared `CLOUDFLARE_API_TOKEN` for both\nchat and image generation, or separate lane-specific tokens through\n`CLOUDFLARE_WORKERS_AI_TEXT_API_TOKEN` and\n`CLOUDFLARE_WORKERS_AI_IMAGE_API_TOKEN`. Set provider, model, budget, queue, and\nworkflow policy in Admin Providers. Use\n`@cf/bytedance/stable-diffusion-xl-lightning` as the current Cloudflare image\ndefault, and keep `@cf/black-forest-labs/flux-1-schnell` as the higher-quality\nimage fallback when prompt adherence matters more than the absolute lowest cost.\nThe production-text local Comfy path does not require hosted Cloudflare image\ntokens; those hosted image keys are only needed when you intend to validate the\nlive Cloudflare image lane.\n- Notification providers: `RESEND_API_KEY`, `SENDGRID_API_KEY`,\n `POSTMARK_SERVER_TOKEN`, `MAILGUN_API_KEY`, `MAILGUN_DOMAIN`,\n `TWILIO_ACCOUNT_SID`, `TWILIO_AUTH_TOKEN`,\n `TWILIO_MESSAGING_SERVICE_SID`, `WHATSAPP_ACCESS_TOKEN`,\n `WHATSAPP_PHONE_NUMBER_ID`, `EXPO_ACCESS_TOKEN`, `FIREBASE_PROJECT_ID`,\n `FIREBASE_SERVICE_ACCOUNT_JSON`, `CUSTOMERIO_APP_API_KEY`,\n `CUSTOMERIO_TRANSACTIONAL_MESSAGE_ID`, `BRAZE_REST_ENDPOINT`,\n `BRAZE_REST_API_KEY`, `BRAZE_CANVAS_ID`, `ONESIGNAL_APP_ID`,\n `ONESIGNAL_REST_API_KEY`, `COURIER_AUTH_TOKEN`, `COURIER_TEMPLATE_ID`,\n `KNOCK_API_KEY`, `KNOCK_WORKFLOW_KEY`, `NOVU_API_KEY`, `NOVU_WORKFLOW_ID`,\n `TRANSACTIONAL_EMAIL_API_KEY`, `TRANSACTIONAL_EMAIL_FROM`.\n- Workflow providers: Zapier, Make, Slack, Microsoft Teams, Notion, Airtable,\n Google Sheets, n8n, Workato, and Pipedream keys listed in\n `env/.env.example`.\n- Payment sandbox providers: `STRIPE_SECRET_KEY`, `STRIPE_WEBHOOK_SECRET`,\n `CUSTOMCARD_PAYMENT_SUCCESS_URL`, `CUSTOMCARD_PAYMENT_CANCEL_URL`,\n `PAYPAL_CLIENT_ID`, `PAYPAL_CLIENT_SECRET`, `PAYPAL_WEBHOOK_ID`,\n `SQUARE_ACCESS_TOKEN`, `SQUARE_LOCATION_ID`,\n `SQUARE_WEBHOOK_SIGNATURE_KEY`, `ADYEN_API_KEY`,\n `ADYEN_MERCHANT_ACCOUNT`, `ADYEN_HMAC_KEY`.\n- Observability providers: `SENTRY_DSN`, `SENTRY_PROJECT_ID`,\n `SENTRY_ENVIRONMENT`, `POSTHOG_PROJECT_API_KEY`, `POSTHOG_HOST`,\n `OTEL_EXPORTER_OTLP_ENDPOINT`, `OTEL_EXPORTER_OTLP_HEADERS`,\n `GRAFANA_OTLP_ENDPOINT`, `GRAFANA_OTLP_INSTANCE_ID`,\n `GRAFANA_OTLP_API_KEY`, `DATADOG_API_KEY`, `DATADOG_SITE`,\n `BETTERSTACK_SOURCE_TOKEN`, `BETTERSTACK_INGESTING_HOST`.\n- Object-store artifact signer and live S3-compatible doctor:\n `OBJECT_STORE_URL`,\n `OBJECT_STORE_BUCKET`, `OBJECT_STORE_ACCESS_KEY_ID`,\n `OBJECT_STORE_SECRET_ACCESS_KEY`, `OBJECT_STORE_READ_ACCESS_KEY_ID`,\n `OBJECT_STORE_READ_SECRET_ACCESS_KEY`, `OBJECT_STORE_REGION`,\n `OBJECT_STORE_PUBLIC_BASE_URL`, `OBJECT_STORE_SIGNING_SECRET`,\n `ARTIFACT_SIGNED_URL_TTL_MINUTES`. Bucket-admin setup also uses\n `CLOUDFLARE_ACCOUNT_ID` and a short-lived `CLOUDFLARE_API_TOKEN`.\n- Live vendor adapters: `WALGREENS_VENDOR_MODE`, `CVS_VENDOR_MODE`,\n `FEDEX_VENDOR_MODE`, `WALMART_VENDOR_MODE`, `STAPLES_VENDOR_MODE`,\n `OFFICE_DEPOT_VENDOR_MODE`.\n- Persistence controls: `CUSTOMCARD_API_RUNTIME`, `AUTH_SESSION_SECRET`,\n `CLERK_JWT_KEY`, `CLERK_AUTHORIZED_PARTIES`, `CLERK_ISSUER`,\n `CLERK_AUDIENCE`, `CUSTOMCARD_CUSTOMER_SESSION_TOKEN`,\n `CUSTOMCARD_ADMIN_SESSION_TOKEN`, `IDEMPOTENCY_KEY_TTL_HOURS`.\n\nThese keys are documented for deployment readiness only. The current repo state\ndoes not make live provider calls, and vendor modes remain\n`disabled_until_certified` while admin safety controls keep live orders closed.\n"}, "59e10f2cbe5d138f61e58d3aed14c6a21a75bfdc": {"t": 1785772119.2659538, "n": 17375, "h": "9bc082f8f2150305", "c": "# CustomCard Infrastructure\n\nThis directory is the deployable service skeleton for the production path.\n\n- `docker-compose.dev.yml` runs app, worker, Postgres, Redis, and MinIO for local development.\n- `docker-compose.droplet.yml` is the cheap single-host deployment shape for a small DigitalOcean droplet.\n- `k8s/app.yaml` is the cloud-native starting point for web and worker deployments.\n- `aws/artifact-store/` is the Terraform contract for a private, encrypted,\n versioned production artifact bucket and prefix-scoped app/worker IAM policy.\n- `migrations/001_initial_schema.sql` defines the durable service data model.\n- `env/.env.example` lists required secrets and kill switches.\n- `../scripts/api-server.mjs` serves `/api/health`, API bootstrap/readiness\n contracts, explicit contract/memory/Postgres runtime modes, and the built web\n app from `dist`.\n- `../src/artifactHandoff.ts` defines the render-packet artifact manifest and\n HMAC-signed URL contract used by the API/schema gates.\n- `../src/artifactStore.ts` writes render-packet artifacts to a temporary local\n filesystem object-store path and an injected S3-compatible client contract,\n then verifies readback without network calls.\n- `../scripts/persistence-doctor.mjs` validates auth-session, idempotency, queue\n job, and audit persistence signals.\n- `../scripts/demo-reset.mjs` validates the reviewer demo reset contract before\n anyone attempts to run seed data against a database.\n- `../scripts/deployment-readiness.mjs` emits the local deployment readiness\n report used by `npm run deployment:doctor`.\n- `../src/capacityPlan.ts` and `../src/capacityPlanData.mjs` define executable\n local-dev, cheap-droplet, cloud-native, and SaaS-scale capacity profiles used\n by `npm run capacity:doctor`.\n\nFor the droplet deployment, provide `POSTGRES_PASSWORD`, `CADDY_DOMAIN`,\n`CADDY_ACME_EMAIL`, and `OBJECT_STORE_*` values from a real secret/config source\nor local deployment `.env` that is never committed. The compose file exposes the\napp only behind Caddy on ports 80/443; Caddy obtains TLS, redirects HTTP to\nHTTPS, and sends `Strict-Transport-Security`. `OBJECT_STORE_URL` must be an\nHTTPS S3-compatible endpoint in production so signed storage requests do not\ncross the network in plaintext.\n\nFor Kubernetes, apply the `customcard-migrate` Job successfully before rolling\nthe web and worker Deployments. The deployment manifest keeps the live-order kill\nswitch explicit and uses probes so bad pods fail visibly.\n\nRun the local IaC readiness check before treating the manifests as reviewable:\n\n```sh\nnpm run deployment:doctor\nnpm run capacity:doctor\nnpm run cloud:doctor\nnpm run api:doctor\nnpm run api:doctor:memory\nnpm run api:doctor:postgres\nCUSTOMCARD_POSTGRES_INTEGRATION_DOCTOR=enabled DATABASE_URL=postgres://... npm run api:doctor:postgres:live\nCUSTOMCARD_POSTGRES_API_HTTP_DOCTOR=enabled DATABASE_URL=postgres://... npm run api:doctor:postgres:http\nCUSTOMCARD_ACCOUNT_AUTH_DOCTOR=enabled DATABASE_URL=postgres://... npm run account:doctor:live\nnpm run artifact:doctor\nCLOUDFLARE_ACCOUNT_ID=... CLOUDFLARE_API_TOKEN=... npm run r2:bucket:create:prod\nCLOUDFLARE_ACCOUNT_ID=... CLOUDFLARE_API_TOKEN=... npm run r2:bucket:list\nCUSTOMCARD_S3_ARTIFACT_DOCTOR=enabled OBJECT_STORE_URL=http://127.0.0.1:9000 OBJECT_STORE_BUCKET=customcard-ci-artifacts OBJECT_STORE_ACCESS_KEY_ID=customcard OBJECT_STORE_SECRET_ACCESS_KEY=customcard-dev-only OBJECT_STORE_REGION=us-east-1 OBJECT_STORE_SIGNING_SECRET=test-object-store-signing-secret-32 npm run artifact:doctor:s3:live\nCUSTOMCARD_S3_ARTIFACT_DOCTOR=enabled CUSTOMCARD_S3_ARTIFACT_DOCTOR_BUCKET_MODE=existing OBJECT_STORE_URL=https://<account-id>.r2.cloudflarestorage.com OBJECT_STORE_BUCKET=customcard-prod OBJECT_STORE_REGION=auto OBJECT_STORE_SIGNING_SECRET=test-object-store-signing-secret-32 npm run artifact:doctor:s3:live\nnpm run persistence:doctor\nnpm run demo:doctor\n```\n\nThe report checks the local-dev, cheap-droplet, cloud-native, runtime, and data\nlanes, including cloud-storage checks for the AWS artifact-store module. Passing\nthis check means the committed deployment contracts are internally consistent;\nit does not mean a real droplet, AWS account, or Kubernetes cluster has been\nprovisioned.\n\n`npm run capacity:doctor` verifies the profile contract, tests, admin/API\nsurfaces, documentation, CI wiring, and no-live-traffic posture. The profiles are\nplanning data only; measured production benchmarks, provider spend reports, and\nautoscaler evidence remain separate launch evidence.\n\nThe Kubernetes web deployment probes `/api/health`, and the production Docker\nimage starts `scripts/api-server.mjs` so the same container can serve the static\nweb bundle and the contract-first API endpoints.\n\nThe API runtime defaults to `CUSTOMCARD_API_RUNTIME=contract` only for local\nreviewer/static serving. Production-shaped environments must set\n`CUSTOMCARD_API_RUNTIME=postgres`; `runtime:doctor` rejects contract or memory\nthere so auth sessions and idempotency records are durable. `npm run\napi:doctor:memory` sets `CUSTOMCARD_ENABLE_LOCAL_AUTH_FALLBACKS=enabled` with\ntest customer/admin session tokens and validates Bearer auth plus\n`X-Idempotency-Key` replay without a live database.\n`npm run api:doctor:postgres` injects a fake Postgres pool into the same runtime\npath and validates session lookup, wrong-role blocking, idempotency replay,\nconflict handling, repository-backed relationship-memory inserts,\nrepository-backed render-packet inserts, repository-backed import-preview\ninserts, repository-backed card-project inserts, manual handoff\norder/consent/event inserts, data-request privacy/consent inserts, audit inserts,\nand queue-job inserts without external credentials.\n`CUSTOMCARD_POSTGRES_INTEGRATION_DOCTOR=enabled npm run\napi:doctor:postgres:live` creates an isolated temporary database on the configured\nPostgres server, applies the committed migration, seeds customer/admin sessions,\nverifies all repository-backed customer routes plus admin readiness through the\nreal `pg` runtime, exercises relationship-memory, render-packet, import-preview,\ncard-project, manual handoff, and data-request persistence, and drops the\ntemporary database before exiting.\n`CUSTOMCARD_POSTGRES_API_HTTP_DOCTOR=enabled npm run\napi:doctor:postgres:http` starts `scripts/api-server.mjs` against the same\nisolated migrated database shape and verifies public health/routes,\nadmin/customer Bearer auth, missing/wrong-role auth blocking, missing\nidempotency blocking, all 6 repository-backed customer HTTP mutations,\nidempotency replay/conflict, audit rows, queue jobs, and repository table counts\nbefore shutting the server down and dropping the temporary database.\nCI runs both live Postgres doctors against a Postgres service; deployed\nproduction account auth remains unclaimed.\n\n`CUSTOMCARD_ACCOUNT_AUTH_DOCTOR=enabled npm run account:doctor:live` uses the\nsame isolated-database pattern to verify hosted account identity rows,\nprovider-subject uniqueness, hashed recovery challenges, durable sessions, and\naccount-recovery audit rows. CI runs it against the Postgres service; live hosted\ntoken verification remains unclaimed.\n\n`npm run artifact:doctor` writes the sample render packet package to a\ntemporary local filesystem object-store path and an injected S3-compatible client\ncontract, reads every artifact back, verifies checksums and byte lengths, stores\nboth handoff manifests, and keeps network calls plus real orders disabled.\n`CUSTOMCARD_S3_ARTIFACT_DOCTOR=enabled npm run artifact:doctor:s3:live` writes\nthe same package to a live S3-compatible endpoint such as MinIO using path-style\nSigV4 requests, reads every object back, verifies checksums and byte lengths,\nstores the handoff manifest, reports `cloudWritesVerified: true`, cleans up the\nisolated bucket, and keeps external vendor calls plus real orders disabled. A\nproduction cloud bucket policy/IAM contract is now represented by\n`infra/aws/artifact-store` and checked by `npm run cloud:doctor`; live AWS apply,\nAccess Analyzer review, and account-specific IAM validation remain separate from\nthis repo-local proof.\n\nThe hosted API can persist render-packet image artifacts through\n`scripts/object-store-runtime.mjs` when `CUSTOMCARD_ARTIFACT_PERSISTENCE=enabled`\nand `CUSTOMCARD_API_RUNTIME` is `memory` or `postgres`. For Cloudflare R2, set\n`OBJECT_STORE_URL` to the account S3 endpoint, `OBJECT_STORE_BUCKET` to the R2\nbucket, `OBJECT_STORE_REGION=auto`, `OBJECT_STORE_PUBLIC_BASE_URL` to the hosted\n`/api/artifacts` route, and mirror the write-capable S3 credentials into\n`OBJECT_STORE_ACCESS_KEY_ID` / `OBJECT_STORE_SECRET_ACCESS_KEY`. Optional\nread-only R2 credentials can be supplied through\n`OBJECT_STORE_READ_ACCESS_KEY_ID` / `OBJECT_STORE_READ_SECRET_ACCESS_KEY` for\nfuture download-only lanes while signed API URLs continue to use\n`OBJECT_STORE_SIGNING_SECRET`. The bucket must already exist; object-scoped R2\ntokens are enough for artifact writes and reads, but they do not create buckets.\nRun `npm run r2:bucket:create:prod` or `npm run r2:bucket:create --\n<bucket-name>` with a short-lived Cloudflare R2 account token when the bucket\nneeds to be provisioned through [Wrangler](https://developers.cloudflare.com/r2/buckets/create-buckets/).\nFor live R2 verification with object-scoped S3 credentials, set\n`CUSTOMCARD_S3_ARTIFACT_DOCTOR_BUCKET_MODE=existing` so the doctor writes under\na temporary project prefix inside the configured bucket and only deletes the\nobjects it created.\n\nThe AWS artifact-store module provisions only the artifact storage boundary:\npublic access block, bucket-owner-enforced ownership, versioning, AES256\nserver-side encryption, lifecycle cleanup under `projects/`, an HTTPS-only and\nencrypted-upload bucket policy, and a least-privilege writer policy scoped to\n`projects/*`. Existing app and worker IAM roles can be attached by passing\n`app_role_name` and `worker_role_name`. Runtime outputs name the required\n`OBJECT_STORE_*` values that still need to be mirrored into the platform secret\nor config manager.\n\nThe persistence boundary requires auth-session storage, account identity storage,\nhashed recovery challenges, idempotency replay, queue job envelopes,\nrelationship-memory repository writes, render-packet repository writes,\nrender-packet artifact manifests, signed URL expiry, and append-only audit\nsignals in the migration before production Postgres handlers are claimed.\n\nThe Kubernetes `Secret` in `k8s/app.yaml` is intentionally empty and annotated as\npre-created by a secret manager. Production clusters should source the required\nkeys from the platform secret manager, External Secrets, Sealed Secrets, or an\nequivalent operator-approved mechanism. The `runtime:doctor` check rejects empty\nvalues, known placeholders, short auth/signing secrets, non-Postgres production\nAPI runtimes, and any live-order kill switch value other than `disabled`.\n\nSigned artifact handoff requires `OBJECT_STORE_SIGNING_SECRET` to be present and\nat least 32 characters. The committed manifests also keep\n`ARTIFACT_SIGNED_URL_TTL_MINUTES=15` explicit so download links remain\nshort-lived.\n\nReal external ordering stays disabled with `REAL_ORDER_KILL_SWITCH=disabled` until vendor sandbox tests and physical print certification are recorded.\n\n## Provider Adapter Secrets\n\n`env/.env.example` names the credential-gated adapters represented in\n`src/providerCatalog.ts`:\n\n- Google OAuth: `GOOGLE_OAUTH_CLIENT_ID`, `GOOGLE_OAUTH_CLIENT_SECRET`, `GOOGLE_OAUTH_REDIRECT_URI`, `GOOGLE_OAUTH_TOKEN_ENCRYPTION_KEY`, `GOOGLE_OAUTH_STATE_SECRET`.\n- Microsoft Graph: `MICROSOFT_CLIENT_ID`, `MICROSOFT_CLIENT_SECRET`,\n `MICROSOFT_TENANT_ID`.\n- Event platforms: `EVENTBRITE_CLIENT_ID`, `EVENTBRITE_CLIENT_SECRET`,\n `LUMA_API_KEY`, `MEETUP_CLIENT_ID`, and `MEETUP_CLIENT_SECRET`. Partiful is a\n contract-only/manual path until an official API exists.\n- Hosted identity providers: `CUSTOMCARD_AUTH_CALLBACK_URL`, `AUTH0_DOMAIN`,\n `AUTH0_CLIENT_ID`, `AUTH0_CLIENT_SECRET`, `AUTH0_AUDIENCE`,\n `CLERK_SECRET_KEY`, `CLERK_JWT_KEY`, `CLERK_AUTHORIZED_PARTIES`,\n `SUPABASE_URL`, `SUPABASE_ANON_KEY`, `SUPABASE_SERVICE_ROLE_KEY`,\n `FIREBASE_API_KEY`, `FIREBASE_PROJECT_ID`,\n `FIREBASE_SERVICE_ACCOUNT_JSON`, `COGNITO_DOMAIN`,\n `COGNITO_USER_POOL_ID`, `COGNITO_APP_CLIENT_ID`.\n- Contact import providers: `CARDDAV_BASE_URL`, `CARDDAV_USERNAME`,\n `CARDDAV_APP_PASSWORD`, `CARDDAV_ADDRESSBOOK_PATH`.\n- CRM/customer lifecycle providers: Salesforce, HubSpot, Zoho, Pipedrive,\n Dynamics, Shopify, Klaviyo, Mailchimp, ActiveCampaign, BigCommerce,\n WooCommerce, Square, Intercom, Monday.com, Amazon Selling Partner API, and\n Etsy keys listed in `env/.env.example`.\n- Text and image AI providers: `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`,\n `AZURE_OPENAI_ENDPOINT`, `AZURE_OPENAI_API_KEY`,\n `AZURE_OPENAI_CHAT_DEPLOYMENT`, `AZURE_OPENAI_IMAGE_DEPLOYMENT`,\n `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_REGION`,\n `BEDROCK_TEXT_MODEL_ID`, `BEDROCK_IMAGE_MODEL_ID`,\n `CLOUDFLARE_ACCOUNT_ID`, `CLOUDFLARE_API_TOKEN`,\n `CLOUDFLARE_WORKERS_AI_TEXT_API_TOKEN`,\n `CLOUDFLARE_WORKERS_AI_IMAGE_API_TOKEN`,\n `CUSTOMCARD_AI_CARD_COPY_MODEL`, `CUSTOMCARD_CLOUDFLARE_TEXT_MODEL`,\n `CLOUDFLARE_WORKERS_AI_TEXT_MODEL`, `CLOUDFLARE_WORKERS_AI_IMAGE_MODEL`,\n `GOOGLE_GENERATIVE_AI_API_KEY`, `MISTRAL_API_KEY`, `COHERE_API_KEY`,\n `PERPLEXITY_API_KEY`, `XAI_API_KEY`, `TOGETHER_API_KEY`, `GROQ_API_KEY`,\n `DEEPSEEK_API_KEY`, `FIREWORKS_API_KEY`, `STABILITY_API_KEY`,\n `HUGGINGFACE_API_TOKEN`, `DEEPAI_API_KEY`, `REPLICATE_API_TOKEN`, `IDEOGRAM_API_KEY`,\n `LEONARDO_API_KEY`, `FAL_KEY`, `BFL_API_KEY`.\n- Self-hosted model fallback: `SELF_HOSTED_LLM_BASE_URL`,\n `SELF_HOSTED_LLM_API_KEY`.\n\nCloudflare Workers AI can use one shared `CLOUDFLARE_API_TOKEN` for both\nchat and image generation, or separate lane-specific tokens through\n`CLOUDFLARE_WORKERS_AI_TEXT_API_TOKEN` and\n`CLOUDFLARE_WORKERS_AI_IMAGE_API_TOKEN`. Set\n`CUSTOMCARD_AI_CARD_COPY_MODEL=@cf/qwen/qwen3-30b-a3b-fp8` to pin the hosted\nproduction card-copy route, and mirror that same 30B model into\n`CUSTOMCARD_CLOUDFLARE_TEXT_MODEL` / `CLOUDFLARE_WORKERS_AI_TEXT_MODEL` when you\nwant the generic Cloudflare text lane to match. Use\n`@cf/bytedance/stable-diffusion-xl-lightning` as the current Cloudflare image\ndefault, and keep `@cf/black-forest-labs/flux-1-schnell` as the higher-quality\nimage fallback when prompt adherence matters more than the absolute lowest cost.\nThe production-text local Comfy path does not require hosted Cloudflare image\ntokens or image model keys; those hosted image keys are only needed when you\nintend to validate the live Cloudflare image lane.\n- Notification providers: `RESEND_API_KEY`, `SENDGRID_API_KEY`,\n `POSTMARK_SERVER_TOKEN`, `MAILGUN_API_KEY`, `MAILGUN_DOMAIN`,\n `TWILIO_ACCOUNT_SID`, `TWILIO_AUTH_TOKEN`,\n `TWILIO_MESSAGING_SERVICE_SID`, `WHATSAPP_ACCESS_TOKEN`,\n `WHATSAPP_PHONE_NUMBER_ID`, `EXPO_ACCESS_TOKEN`, `FIREBASE_PROJECT_ID`,\n `FIREBASE_SERVICE_ACCOUNT_JSON`, `CUSTOMERIO_APP_API_KEY`,\n `CUSTOMERIO_TRANSACTIONAL_MESSAGE_ID`, `BRAZE_REST_ENDPOINT`,\n `BRAZE_REST_API_KEY`, `BRAZE_CANVAS_ID`, `ONESIGNAL_APP_ID`,\n `ONESIGNAL_REST_API_KEY`, `COURIER_AUTH_TOKEN`, `COURIER_TEMPLATE_ID`,\n `KNOCK_API_KEY`, `KNOCK_WORKFLOW_KEY`, `NOVU_API_KEY`, `NOVU_WORKFLOW_ID`,\n `TRANSACTIONAL_EMAIL_API_KEY`, `TRANSACTIONAL_EMAIL_FROM`.\n- Workflow providers: Zapier, Make, Slack, Microsoft Teams, Notion, Airtable,\n Google Sheets, n8n, Workato, and Pipedream keys listed in\n `env/.env.example`.\n- Payment sandbox providers: `STRIPE_SECRET_KEY`, `STRIPE_WEBHOOK_SECRET`,\n `CUSTOMCARD_PAYMENT_SUCCESS_URL`, `CUSTOMCARD_PAYMENT_CANCEL_URL`,\n `PAYPAL_CLIENT_ID`, `PAYPAL_CLIENT_SECRET`, `PAYPAL_WEBHOOK_ID`,\n `SQUARE_ACCESS_TOKEN`, `SQUARE_LOCATION_ID`,\n `SQUARE_WEBHOOK_SIGNATURE_KEY`, `ADYEN_API_KEY`,\n `ADYEN_MERCHANT_ACCOUNT`, `ADYEN_HMAC_KEY`.\n- Observability providers: `SENTRY_DSN`, `SENTRY_PROJECT_ID`,\n `SENTRY_ENVIRONMENT`, `POSTHOG_PROJECT_API_KEY`, `POSTHOG_HOST`,\n `OTEL_EXPORTER_OTLP_ENDPOINT`, `OTEL_EXPORTER_OTLP_HEADERS`,\n `GRAFANA_OTLP_ENDPOINT`, `GRAFANA_OTLP_INSTANCE_ID`,\n `GRAFANA_OTLP_API_KEY`, `DATADOG_API_KEY`, `DATADOG_SITE`,\n `BETTERSTACK_SOURCE_TOKEN`, `BETTERSTACK_INGESTING_HOST`.\n- Object-store artifact signer and live S3-compatible doctor:\n `CUSTOMCARD_ARTIFACT_PERSISTENCE`, `OBJECT_STORE_URL`,\n `OBJECT_STORE_BUCKET`, `OBJECT_STORE_ACCESS_KEY_ID`,\n `OBJECT_STORE_SECRET_ACCESS_KEY`, `OBJECT_STORE_READ_ACCESS_KEY_ID`,\n `OBJECT_STORE_READ_SECRET_ACCESS_KEY`, `OBJECT_STORE_REGION`,\n `OBJECT_STORE_PUBLIC_BASE_URL`, `OBJECT_STORE_SIGNING_SECRET`,\n `ARTIFACT_SIGNED_URL_TTL_MINUTES`. Bucket-admin setup also uses\n `CLOUDFLARE_ACCOUNT_ID` and a short-lived `CLOUDFLARE_API_TOKEN`.\n- Live vendor adapters: `WALGREENS_VENDOR_MODE`, `CVS_VENDOR_MODE`,\n `FEDEX_VENDOR_MODE`, `WALMART_VENDOR_MODE`, `STAPLES_VENDOR_MODE`,\n `OFFICE_DEPOT_VENDOR_MODE`.\n- Persistence controls: `CUSTOMCARD_API_RUNTIME`, `AUTH_SESSION_SECRET`,\n `CLERK_JWT_KEY`, `CLERK_AUTHORIZED_PARTIES`, `CLERK_ISSUER`,\n `CLERK_AUDIENCE`, `CUSTOMCARD_CUSTOMER_SESSION_TOKEN`,\n `CUSTOMCARD_ADMIN_SESSION_TOKEN`, `IDEMPOTENCY_KEY_TTL_HOURS`.\n\nThese keys are documented for deployment readiness only. The current repo state\ndoes not make live provider calls, and vendor modes remain\n`disabled_until_certified` while `REAL_ORDER_KILL_SWITCH=disabled`.\n"}, "2daa5c1fe750f817626b044f05cc676b4b9f5e1b": {"t": 1785772120.8206477, "n": 16951, "h": "ccce5325a870f5e2", "c": "# CustomCard Infrastructure\n\nThis directory is the deployable service skeleton for the production path.\n\n- `docker-compose.dev.yml` runs app, worker, Postgres, Redis, and MinIO for local development.\n- `docker-compose.droplet.yml` is the cheap single-host deployment shape for a small DigitalOcean droplet.\n- `k8s/app.yaml` is the cloud-native starting point for web and worker deployments.\n- `aws/artifact-store/` is the Terraform contract for a private, encrypted,\n versioned production artifact bucket and prefix-scoped app/worker IAM policy.\n- `migrations/001_initial_schema.sql` defines the durable service data model.\n- `env/.env.example` lists required secrets and kill switches.\n- `../scripts/api-server.mjs` serves `/api/health`, API bootstrap/readiness\n contracts, explicit contract/memory/Postgres runtime modes, and the built web\n app from `dist`.\n- `../src/artifactHandoff.ts` defines the render-packet artifact manifest and\n HMAC-signed URL contract used by the API/schema gates.\n- `../src/artifactStore.ts` writes render-packet artifacts to a temporary local\n filesystem object-store path and an injected S3-compatible client contract,\n then verifies readback without network calls.\n- `../scripts/persistence-doctor.mjs` validates auth-session, idempotency, queue\n job, and audit persistence signals.\n- `../scripts/demo-reset.mjs` validates the reviewer demo reset contract before\n anyone attempts to run seed data against a database.\n- `../scripts/deployment-readiness.mjs` emits the local deployment readiness\n report used by `npm run deployment:doctor`.\n- `../src/capacityPlan.ts` and `../src/capacityPlanData.mjs` define executable\n local-dev, cheap-droplet, cloud-native, and SaaS-scale capacity profiles used\n by `npm run capacity:doctor`.\n\nFor the droplet deployment, provide `POSTGRES_PASSWORD`, `CADDY_DOMAIN`,\n`CADDY_ACME_EMAIL`, and `OBJECT_STORE_*` values from a real secret/config source\nor local deployment `.env` that is never committed. The compose file exposes the\napp only behind Caddy on ports 80/443; Caddy obtains TLS, redirects HTTP to\nHTTPS, and sends `Strict-Transport-Security`. `OBJECT_STORE_URL` must be an\nHTTPS S3-compatible endpoint in production so signed storage requests do not\ncross the network in plaintext.\n\nFor Kubernetes, apply the `customcard-migrate` Job successfully before rolling\nthe web and worker Deployments. The deployment manifest keeps the live-order kill\nswitch explicit and uses probes so bad pods fail visibly.\n\nRun the local IaC readiness check before treating the manifests as reviewable:\n\n```sh\nnpm run deployment:doctor\nnpm run capacity:doctor\nnpm run cloud:doctor\nnpm run api:doctor\nnpm run api:doctor:memory\nnpm run api:doctor:postgres\nCUSTOMCARD_POSTGRES_INTEGRATION_DOCTOR=enabled DATABASE_URL=postgres://... npm run api:doctor:postgres:live\nCUSTOMCARD_POSTGRES_API_HTTP_DOCTOR=enabled DATABASE_URL=postgres://... npm run api:doctor:postgres:http\nCUSTOMCARD_ACCOUNT_AUTH_DOCTOR=enabled DATABASE_URL=postgres://... npm run account:doctor:live\nnpm run artifact:doctor\nCLOUDFLARE_ACCOUNT_ID=... CLOUDFLARE_API_TOKEN=... npm run r2:bucket:create:prod\nCLOUDFLARE_ACCOUNT_ID=... CLOUDFLARE_API_TOKEN=... npm run r2:bucket:list\nCUSTOMCARD_S3_ARTIFACT_DOCTOR=enabled OBJECT_STORE_URL=http://127.0.0.1:9000 OBJECT_STORE_BUCKET=customcard-ci-artifacts OBJECT_STORE_ACCESS_KEY_ID=customcard OBJECT_STORE_SECRET_ACCESS_KEY=customcard-dev-only OBJECT_STORE_REGION=us-east-1 OBJECT_STORE_SIGNING_SECRET=test-object-store-signing-secret-32 npm run artifact:doctor:s3:live\nCUSTOMCARD_S3_ARTIFACT_DOCTOR=enabled CUSTOMCARD_S3_ARTIFACT_DOCTOR_BUCKET_MODE=existing OBJECT_STORE_URL=https://<account-id>.r2.cloudflarestorage.com OBJECT_STORE_BUCKET=customcard-prod OBJECT_STORE_REGION=auto OBJECT_STORE_SIGNING_SECRET=test-object-store-signing-secret-32 npm run artifact:doctor:s3:live\nnpm run persistence:doctor\nnpm run demo:doctor\n```\n\nThe report checks the local-dev, cheap-droplet, cloud-native, runtime, and data\nlanes, including cloud-storage checks for the AWS artifact-store module. Passing\nthis check means the committed deployment contracts are internally consistent;\nit does not mean a real droplet, AWS account, or Kubernetes cluster has been\nprovisioned.\n\n`npm run capacity:doctor` verifies the profile contract, tests, admin/API\nsurfaces, documentation, CI wiring, and no-live-traffic posture. The profiles are\nplanning data only; measured production benchmarks, provider spend reports, and\nautoscaler evidence remain separate launch evidence.\n\nThe Kubernetes web deployment probes `/api/health`, and the production Docker\nimage starts `scripts/api-server.mjs` so the same container can serve the static\nweb bundle and the contract-first API endpoints.\n\nThe API runtime defaults to `CUSTOMCARD_API_RUNTIME=contract` only for local\nreviewer/static serving. Production-shaped environments must set\n`CUSTOMCARD_API_RUNTIME=postgres`; `runtime:doctor` rejects contract or memory\nthere so auth sessions and idempotency records are durable. `npm run\napi:doctor:memory` sets `CUSTOMCARD_ENABLE_LOCAL_AUTH_FALLBACKS=enabled` with\ntest customer/admin session tokens and validates Bearer auth plus\n`X-Idempotency-Key` replay without a live database.\n`npm run api:doctor:postgres` injects a fake Postgres pool into the same runtime\npath and validates session lookup, wrong-role blocking, idempotency replay,\nconflict handling, repository-backed relationship-memory inserts,\nrepository-backed render-packet inserts, repository-backed import-preview\ninserts, repository-backed card-project inserts, manual handoff\norder/consent/event inserts, data-request privacy/consent inserts, audit inserts,\nand queue-job inserts without external credentials.\n`CUSTOMCARD_POSTGRES_INTEGRATION_DOCTOR=enabled npm run\napi:doctor:postgres:live` creates an isolated temporary database on the configured\nPostgres server, applies the committed migration, seeds customer/admin sessions,\nverifies all repository-backed customer routes plus admin readiness through the\nreal `pg` runtime, exercises relationship-memory, render-packet, import-preview,\ncard-project, manual handoff, and data-request persistence, and drops the\ntemporary database before exiting.\n`CUSTOMCARD_POSTGRES_API_HTTP_DOCTOR=enabled npm run\napi:doctor:postgres:http` starts `scripts/api-server.mjs` against the same\nisolated migrated database shape and verifies public health/routes,\nadmin/customer Bearer auth, missing/wrong-role auth blocking, missing\nidempotency blocking, all 6 repository-backed customer HTTP mutations,\nidempotency replay/conflict, audit rows, queue jobs, and repository table counts\nbefore shutting the server down and dropping the temporary database.\nCI runs both live Postgres doctors against a Postgres service; deployed\nproduction account auth remains unclaimed.\n\n`CUSTOMCARD_ACCOUNT_AUTH_DOCTOR=enabled npm run account:doctor:live` uses the\nsame isolated-database pattern to verify hosted account identity rows,\nprovider-subject uniqueness, hashed recovery challenges, durable sessions, and\naccount-recovery audit rows. CI runs it against the Postgres service; live hosted\ntoken verification remains unclaimed.\n\n`npm run artifact:doctor` writes the sample render packet package to a\ntemporary local filesystem object-store path and an injected S3-compatible client\ncontract, reads every artifact back, verifies checksums and byte lengths, stores\nboth handoff manifests, and keeps network calls plus real orders disabled.\n`CUSTOMCARD_S3_ARTIFACT_DOCTOR=enabled npm run artifact:doctor:s3:live` writes\nthe same package to a live S3-compatible endpoint such as MinIO using path-style\nSigV4 requests, reads every object back, verifies checksums and byte lengths,\nstores the handoff manifest, reports `cloudWritesVerified: true`, cleans up the\nisolated bucket, and keeps external vendor calls plus real orders disabled. A\nproduction cloud bucket policy/IAM contract is now represented by\n`infra/aws/artifact-store` and checked by `npm run cloud:doctor`; live AWS apply,\nAccess Analyzer review, and account-specific IAM validation remain separate from\nthis repo-local proof.\n\nThe hosted API can persist render-packet image artifacts through\n`scripts/object-store-runtime.mjs` when `CUSTOMCARD_ARTIFACT_PERSISTENCE=enabled`\nand `CUSTOMCARD_API_RUNTIME` is `memory` or `postgres`. For Cloudflare R2, set\n`OBJECT_STORE_URL` to the account S3 endpoint, `OBJECT_STORE_BUCKET` to the R2\nbucket, `OBJECT_STORE_REGION=auto`, `OBJECT_STORE_PUBLIC_BASE_URL` to the hosted\n`/api/artifacts` route, and mirror the write-capable S3 credentials into\n`OBJECT_STORE_ACCESS_KEY_ID` / `OBJECT_STORE_SECRET_ACCESS_KEY`. Optional\nread-only R2 credentials can be supplied through\n`OBJECT_STORE_READ_ACCESS_KEY_ID` / `OBJECT_STORE_READ_SECRET_ACCESS_KEY` for\nfuture download-only lanes while signed API URLs continue to use\n`OBJECT_STORE_SIGNING_SECRET`. The bucket must already exist; object-scoped R2\ntokens are enough for artifact writes and reads, but they do not create buckets.\nRun `npm run r2:bucket:create:prod` or `npm run r2:bucket:create --\n<bucket-name>` with a short-lived Cloudflare R2 account token when the bucket\nneeds to be provisioned through [Wrangler](https://developers.cloudflare.com/r2/buckets/create-buckets/).\nFor live R2 verification with object-scoped S3 credentials, set\n`CUSTOMCARD_S3_ARTIFACT_DOCTOR_BUCKET_MODE=existing` so the doctor writes under\na temporary project prefix inside the configured bucket and only deletes the\nobjects it created.\n\nThe AWS artifact-store module provisions only the artifact storage boundary:\npublic access block, bucket-owner-enforced ownership, versioning, AES256\nserver-side encryption, lifecycle cleanup under `projects/`, an HTTPS-only and\nencrypted-upload bucket policy, and a least-privilege writer policy scoped to\n`projects/*`. Existing app and worker IAM roles can be attached by passing\n`app_role_name` and `worker_role_name`. Runtime outputs name the required\n`OBJECT_STORE_*` values that still need to be mirrored into the platform secret\nor config manager.\n\nThe persistence boundary requires auth-session storage, account identity storage,\nhashed recovery challenges, idempotency replay, queue job envelopes,\nrelationship-memory repository writes, render-packet repository writes,\nrender-packet artifact manifests, signed URL expiry, and append-only audit\nsignals in the migration before production Postgres handlers are claimed.\n\nThe Kubernetes `Secret` in `k8s/app.yaml` is intentionally empty and annotated as\npre-created by a secret manager. Production clusters should source the required\nkeys from the platform secret manager, External Secrets, Sealed Secrets, or an\nequivalent operator-approved mechanism. The `runtime:doctor` check rejects empty\nvalues, known placeholders, short auth/signing secrets, non-Postgres production\nAPI runtimes, and any live-order kill switch value other than `disabled`.\n\nSigned artifact handoff requires `OBJECT_STORE_SIGNING_SECRET` to be present and\nat least 32 characters. The committed manifests also keep\n`ARTIFACT_SIGNED_URL_TTL_MINUTES=15` explicit so download links remain\nshort-lived.\n\nReal external ordering stays disabled with `REAL_ORDER_KILL_SWITCH=disabled` until vendor sandbox tests and physical print certification are recorded.\n\n## Provider Adapter Secrets\n\n`env/.env.example` names the credential-gated adapters represented in\n`src/providerCatalog.ts`:\n\n- Google OAuth: `GOOGLE_OAUTH_CLIENT_ID`, `GOOGLE_OAUTH_CLIENT_SECRET`, `GOOGLE_OAUTH_REDIRECT_URI`, `GOOGLE_OAUTH_TOKEN_ENCRYPTION_KEY`, `GOOGLE_OAUTH_STATE_SECRET`.\n- Microsoft Graph: `MICROSOFT_CLIENT_ID`, `MICROSOFT_CLIENT_SECRET`,\n `MICROSOFT_TENANT_ID`.\n- Event platforms: `EVENTBRITE_CLIENT_ID`, `EVENTBRITE_CLIENT_SECRET`,\n `LUMA_API_KEY`, `MEETUP_CLIENT_ID`, and `MEETUP_CLIENT_SECRET`. Partiful is a\n contract-only/manual path until an official API exists.\n- Hosted identity providers: `CUSTOMCARD_AUTH_CALLBACK_URL`, `AUTH0_DOMAIN`,\n `AUTH0_CLIENT_ID`, `AUTH0_CLIENT_SECRET`, `AUTH0_AUDIENCE`,\n `CLERK_SECRET_KEY`, `CLERK_JWT_KEY`, `CLERK_AUTHORIZED_PARTIES`,\n `SUPABASE_URL`, `SUPABASE_ANON_KEY`, `SUPABASE_SERVICE_ROLE_KEY`,\n `FIREBASE_API_KEY`, `FIREBASE_PROJECT_ID`,\n `FIREBASE_SERVICE_ACCOUNT_JSON`, `COGNITO_DOMAIN`,\n `COGNITO_USER_POOL_ID`, `COGNITO_APP_CLIENT_ID`.\n- Contact import providers: `CARDDAV_BASE_URL`, `CARDDAV_USERNAME`,\n `CARDDAV_APP_PASSWORD`, `CARDDAV_ADDRESSBOOK_PATH`.\n- CRM/customer lifecycle providers: Salesforce, HubSpot, Zoho, Pipedrive,\n Dynamics, Shopify, Klaviyo, Mailchimp, ActiveCampaign, BigCommerce,\n WooCommerce, Square, Intercom, Monday.com, Amazon Selling Partner API, and\n Etsy keys listed in `env/.env.example`.\n- Text and image AI providers: `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`,\n `AZURE_OPENAI_ENDPOINT`, `AZURE_OPENAI_API_KEY`,\n `AZURE_OPENAI_CHAT_DEPLOYMENT`, `AZURE_OPENAI_IMAGE_DEPLOYMENT`,\n `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_REGION`,\n `BEDROCK_TEXT_MODEL_ID`, `BEDROCK_IMAGE_MODEL_ID`,\n `CLOUDFLARE_ACCOUNT_ID`, `CLOUDFLARE_API_TOKEN`,\n `CLOUDFLARE_WORKERS_AI_TEXT_API_TOKEN`,\n `CLOUDFLARE_WORKERS_AI_IMAGE_API_TOKEN`,\n `CLOUDFLARE_WORKERS_AI_TEXT_MODEL`, `CLOUDFLARE_WORKERS_AI_IMAGE_MODEL`,\n `GOOGLE_GENERATIVE_AI_API_KEY`, `MISTRAL_API_KEY`, `COHERE_API_KEY`,\n `PERPLEXITY_API_KEY`, `XAI_API_KEY`, `TOGETHER_API_KEY`, `GROQ_API_KEY`,\n `DEEPSEEK_API_KEY`, `FIREWORKS_API_KEY`, `STABILITY_API_KEY`,\n `HUGGINGFACE_API_TOKEN`, `DEEPAI_API_KEY`, `REPLICATE_API_TOKEN`, `IDEOGRAM_API_KEY`,\n `LEONARDO_API_KEY`, `FAL_KEY`, `BFL_API_KEY`.\n- Self-hosted model fallback: `SELF_HOSTED_LLM_BASE_URL`,\n `SELF_HOSTED_LLM_API_KEY`.\n\nCloudflare Workers AI can use one shared `CLOUDFLARE_API_TOKEN` for both\nchat and image generation, or separate lane-specific tokens through\n`CLOUDFLARE_WORKERS_AI_TEXT_API_TOKEN` and\n`CLOUDFLARE_WORKERS_AI_IMAGE_API_TOKEN`. The recommended low-cost defaults are\n`@cf/meta/llama-3.1-8b-instruct-fast` for JSON Mode card copy and prompt/chat\nwork and `@cf/bytedance/stable-diffusion-xl-lightning` for image generation.\nUse `@cf/meta/llama-3.3-70b-instruct-fp8-fast` as the text quality fallback and\n`@cf/black-forest-labs/flux-1-schnell` as the higher-quality image fallback\nwhen budget allows.\n- Notification providers: `RESEND_API_KEY`, `SENDGRID_API_KEY`,\n `POSTMARK_SERVER_TOKEN`, `MAILGUN_API_KEY`, `MAILGUN_DOMAIN`,\n `TWILIO_ACCOUNT_SID`, `TWILIO_AUTH_TOKEN`,\n `TWILIO_MESSAGING_SERVICE_SID`, `WHATSAPP_ACCESS_TOKEN`,\n `WHATSAPP_PHONE_NUMBER_ID`, `EXPO_ACCESS_TOKEN`, `FIREBASE_PROJECT_ID`,\n `FIREBASE_SERVICE_ACCOUNT_JSON`, `CUSTOMERIO_APP_API_KEY`,\n `CUSTOMERIO_TRANSACTIONAL_MESSAGE_ID`, `BRAZE_REST_ENDPOINT`,\n `BRAZE_REST_API_KEY`, `BRAZE_CANVAS_ID`, `ONESIGNAL_APP_ID`,\n `ONESIGNAL_REST_API_KEY`, `COURIER_AUTH_TOKEN`, `COURIER_TEMPLATE_ID`,\n `KNOCK_API_KEY`, `KNOCK_WORKFLOW_KEY`, `NOVU_API_KEY`, `NOVU_WORKFLOW_ID`,\n `TRANSACTIONAL_EMAIL_API_KEY`, `TRANSACTIONAL_EMAIL_FROM`.\n- Workflow providers: Zapier, Make, Slack, Microsoft Teams, Notion, Airtable,\n Google Sheets, n8n, Workato, and Pipedream keys listed in\n `env/.env.example`.\n- Payment sandbox providers: `STRIPE_SECRET_KEY`, `STRIPE_WEBHOOK_SECRET`,\n `CUSTOMCARD_PAYMENT_SUCCESS_URL`, `CUSTOMCARD_PAYMENT_CANCEL_URL`,\n `PAYPAL_CLIENT_ID`, `PAYPAL_CLIENT_SECRET`, `PAYPAL_WEBHOOK_ID`,\n `SQUARE_ACCESS_TOKEN`, `SQUARE_LOCATION_ID`,\n `SQUARE_WEBHOOK_SIGNATURE_KEY`, `ADYEN_API_KEY`,\n `ADYEN_MERCHANT_ACCOUNT`, `ADYEN_HMAC_KEY`.\n- Observability providers: `SENTRY_DSN`, `SENTRY_PROJECT_ID`,\n `SENTRY_ENVIRONMENT`, `POSTHOG_PROJECT_API_KEY`, `POSTHOG_HOST`,\n `OTEL_EXPORTER_OTLP_ENDPOINT`, `OTEL_EXPORTER_OTLP_HEADERS`,\n `GRAFANA_OTLP_ENDPOINT`, `GRAFANA_OTLP_INSTANCE_ID`,\n `GRAFANA_OTLP_API_KEY`, `DATADOG_API_KEY`, `DATADOG_SITE`,\n `BETTERSTACK_SOURCE_TOKEN`, `BETTERSTACK_INGESTING_HOST`.\n- Object-store artifact signer and live S3-compatible doctor:\n `CUSTOMCARD_ARTIFACT_PERSISTENCE`, `OBJECT_STORE_URL`,\n `OBJECT_STORE_BUCKET`, `OBJECT_STORE_ACCESS_KEY_ID`,\n `OBJECT_STORE_SECRET_ACCESS_KEY`, `OBJECT_STORE_READ_ACCESS_KEY_ID`,\n `OBJECT_STORE_READ_SECRET_ACCESS_KEY`, `OBJECT_STORE_REGION`,\n `OBJECT_STORE_PUBLIC_BASE_URL`, `OBJECT_STORE_SIGNING_SECRET`,\n `ARTIFACT_SIGNED_URL_TTL_MINUTES`. Bucket-admin setup also uses\n `CLOUDFLARE_ACCOUNT_ID` and a short-lived `CLOUDFLARE_API_TOKEN`.\n- Live vendor adapters: `WALGREENS_VENDOR_MODE`, `CVS_VENDOR_MODE`,\n `FEDEX_VENDOR_MODE`, `WALMART_VENDOR_MODE`, `STAPLES_VENDOR_MODE`,\n `OFFICE_DEPOT_VENDOR_MODE`.\n- Persistence controls: `CUSTOMCARD_API_RUNTIME`, `AUTH_SESSION_SECRET`,\n `CLERK_JWT_KEY`, `CLERK_AUTHORIZED_PARTIES`, `CLERK_ISSUER`,\n `CLERK_AUDIENCE`, `CUSTOMCARD_CUSTOMER_SESSION_TOKEN`,\n `CUSTOMCARD_ADMIN_SESSION_TOKEN`, `IDEMPOTENCY_KEY_TTL_HOURS`.\n\nThese keys are documented for deployment readiness only. The current repo state\ndoes not make live provider calls, and vendor modes remain\n`disabled_until_certified` while `REAL_ORDER_KILL_SWITCH=disabled`.\n"}, "41955df565fc31362a95142c29623fb1510fa693": {"t": 1785772125.1223817, "n": 16816, "h": "48d1878a92a94b34", "c": "# CustomCard Infrastructure\n\nThis directory is the deployable service skeleton for the production path.\n\n- `docker-compose.dev.yml` runs app, worker, Postgres, Redis, and MinIO for local development.\n- `docker-compose.droplet.yml` is the cheap single-host deployment shape for a small DigitalOcean droplet.\n- `k8s/app.yaml` is the cloud-native starting point for web and worker deployments.\n- `aws/artifact-store/` is the Terraform contract for a private, encrypted,\n versioned production artifact bucket and prefix-scoped app/worker IAM policy.\n- `migrations/001_initial_schema.sql` defines the durable service data model.\n- `env/.env.example` lists required secrets and kill switches.\n- `../scripts/api-server.mjs` serves `/api/health`, API bootstrap/readiness\n contracts, explicit contract/memory/Postgres runtime modes, and the built web\n app from `dist`.\n- `../src/artifactHandoff.ts` defines the render-packet artifact manifest and\n HMAC-signed URL contract used by the API/schema gates.\n- `../src/artifactStore.ts` writes render-packet artifacts to a temporary local\n filesystem object-store path and an injected S3-compatible client contract,\n then verifies readback without network calls.\n- `../scripts/persistence-doctor.mjs` validates auth-session, idempotency, queue\n job, and audit persistence signals.\n- `../scripts/demo-reset.mjs` validates the reviewer demo reset contract before\n anyone attempts to run seed data against a database.\n- `../scripts/deployment-readiness.mjs` emits the local deployment readiness\n report used by `npm run deployment:doctor`.\n- `../src/capacityPlan.ts` and `../src/capacityPlanData.mjs` define executable\n local-dev, cheap-droplet, cloud-native, and SaaS-scale capacity profiles used\n by `npm run capacity:doctor`.\n\nFor the droplet deployment, provide `POSTGRES_PASSWORD`, `CADDY_DOMAIN`,\n`CADDY_ACME_EMAIL`, and `OBJECT_STORE_*` values from a real secret/config source\nor local deployment `.env` that is never committed. The compose file exposes the\napp only behind Caddy on ports 80/443; Caddy obtains TLS, redirects HTTP to\nHTTPS, and sends `Strict-Transport-Security`. `OBJECT_STORE_URL` must be an\nHTTPS S3-compatible endpoint in production so signed storage requests do not\ncross the network in plaintext.\n\nFor Kubernetes, apply the `customcard-migrate` Job successfully before rolling\nthe web and worker Deployments. The deployment manifest keeps the live-order kill\nswitch explicit and uses probes so bad pods fail visibly.\n\nRun the local IaC readiness check before treating the manifests as reviewable:\n\n```sh\nnpm run deployment:doctor\nnpm run capacity:doctor\nnpm run cloud:doctor\nnpm run api:doctor\nnpm run api:doctor:memory\nnpm run api:doctor:postgres\nCUSTOMCARD_POSTGRES_INTEGRATION_DOCTOR=enabled DATABASE_URL=postgres://... npm run api:doctor:postgres:live\nCUSTOMCARD_POSTGRES_API_HTTP_DOCTOR=enabled DATABASE_URL=postgres://... npm run api:doctor:postgres:http\nCUSTOMCARD_ACCOUNT_AUTH_DOCTOR=enabled DATABASE_URL=postgres://... npm run account:doctor:live\nnpm run artifact:doctor\nCLOUDFLARE_ACCOUNT_ID=... CLOUDFLARE_API_TOKEN=... npm run r2:bucket:create:prod\nCLOUDFLARE_ACCOUNT_ID=... CLOUDFLARE_API_TOKEN=... npm run r2:bucket:list\nCUSTOMCARD_S3_ARTIFACT_DOCTOR=enabled OBJECT_STORE_URL=http://127.0.0.1:9000 OBJECT_STORE_BUCKET=customcard-ci-artifacts OBJECT_STORE_ACCESS_KEY_ID=customcard OBJECT_STORE_SECRET_ACCESS_KEY=customcard-dev-only OBJECT_STORE_REGION=us-east-1 OBJECT_STORE_SIGNING_SECRET=test-object-store-signing-secret-32 npm run artifact:doctor:s3:live\nCUSTOMCARD_S3_ARTIFACT_DOCTOR=enabled CUSTOMCARD_S3_ARTIFACT_DOCTOR_BUCKET_MODE=existing OBJECT_STORE_URL=https://<account-id>.r2.cloudflarestorage.com OBJECT_STORE_BUCKET=customcard-prod OBJECT_STORE_REGION=auto OBJECT_STORE_SIGNING_SECRET=test-object-store-signing-secret-32 npm run artifact:doctor:s3:live\nnpm run persistence:doctor\nnpm run demo:doctor\n```\n\nThe report checks the local-dev, cheap-droplet, cloud-native, runtime, and data\nlanes, including cloud-storage checks for the AWS artifact-store module. Passing\nthis check means the committed deployment contracts are internally consistent;\nit does not mean a real droplet, AWS account, or Kubernetes cluster has been\nprovisioned.\n\n`npm run capacity:doctor` verifies the profile contract, tests, admin/API\nsurfaces, documentation, CI wiring, and no-live-traffic posture. The profiles are\nplanning data only; measured production benchmarks, provider spend reports, and\nautoscaler evidence remain separate launch evidence.\n\nThe Kubernetes web deployment probes `/api/health`, and the production Docker\nimage starts `scripts/api-server.mjs` so the same container can serve the static\nweb bundle and the contract-first API endpoints.\n\nThe API runtime defaults to `CUSTOMCARD_API_RUNTIME=contract` only for local\nreviewer/static serving. Production-shaped environments must set\n`CUSTOMCARD_API_RUNTIME=postgres`; `runtime:doctor` rejects contract or memory\nthere so auth sessions and idempotency records are durable. `npm run\napi:doctor:memory` sets test customer/admin session tokens and\nvalidates Bearer auth plus `X-Idempotency-Key` replay without a live database.\n`npm run api:doctor:postgres` injects a fake Postgres pool into the same runtime\npath and validates session lookup, wrong-role blocking, idempotency replay,\nconflict handling, repository-backed relationship-memory inserts,\nrepository-backed render-packet inserts, repository-backed import-preview\ninserts, repository-backed card-project inserts, manual handoff\norder/consent/event inserts, data-request privacy/consent inserts, audit inserts,\nand queue-job inserts without external credentials.\n`CUSTOMCARD_POSTGRES_INTEGRATION_DOCTOR=enabled npm run\napi:doctor:postgres:live` creates an isolated temporary database on the configured\nPostgres server, applies the committed migration, seeds customer/admin sessions,\nverifies all repository-backed customer routes plus admin readiness through the\nreal `pg` runtime, exercises relationship-memory, render-packet, import-preview,\ncard-project, manual handoff, and data-request persistence, and drops the\ntemporary database before exiting.\n`CUSTOMCARD_POSTGRES_API_HTTP_DOCTOR=enabled npm run\napi:doctor:postgres:http` starts `scripts/api-server.mjs` against the same\nisolated migrated database shape and verifies public health/routes,\nadmin/customer Bearer auth, missing/wrong-role auth blocking, missing\nidempotency blocking, all 6 repository-backed customer HTTP mutations,\nidempotency replay/conflict, audit rows, queue jobs, and repository table counts\nbefore shutting the server down and dropping the temporary database.\nCI runs both live Postgres doctors against a Postgres service; deployed\nproduction account auth remains unclaimed.\n\n`CUSTOMCARD_ACCOUNT_AUTH_DOCTOR=enabled npm run account:doctor:live` uses the\nsame isolated-database pattern to verify hosted account identity rows,\nprovider-subject uniqueness, hashed recovery challenges, durable sessions, and\naccount-recovery audit rows. CI runs it against the Postgres service; live hosted\ntoken verification remains unclaimed.\n\n`npm run artifact:doctor` writes the sample render packet package to a\ntemporary local filesystem object-store path and an injected S3-compatible client\ncontract, reads every artifact back, verifies checksums and byte lengths, stores\nboth handoff manifests, and keeps network calls plus real orders disabled.\n`CUSTOMCARD_S3_ARTIFACT_DOCTOR=enabled npm run artifact:doctor:s3:live` writes\nthe same package to a live S3-compatible endpoint such as MinIO using path-style\nSigV4 requests, reads every object back, verifies checksums and byte lengths,\nstores the handoff manifest, reports `cloudWritesVerified: true`, cleans up the\nisolated bucket, and keeps external vendor calls plus real orders disabled. A\nproduction cloud bucket policy/IAM contract is now represented by\n`infra/aws/artifact-store` and checked by `npm run cloud:doctor`; live AWS apply,\nAccess Analyzer review, and account-specific IAM validation remain separate from\nthis repo-local proof.\n\nThe hosted API can persist render-packet image artifacts through\n`scripts/object-store-runtime.mjs` when `CUSTOMCARD_ARTIFACT_PERSISTENCE=enabled`\nand `CUSTOMCARD_API_RUNTIME` is `memory` or `postgres`. For Cloudflare R2, set\n`OBJECT_STORE_URL` to the account S3 endpoint, `OBJECT_STORE_BUCKET` to the R2\nbucket, `OBJECT_STORE_REGION=auto`, `OBJECT_STORE_PUBLIC_BASE_URL` to the hosted\n`/api/artifacts` route, and mirror the write-capable S3 credentials into\n`OBJECT_STORE_ACCESS_KEY_ID` / `OBJECT_STORE_SECRET_ACCESS_KEY`. Optional\nread-only R2 credentials can be supplied through\n`OBJECT_STORE_READ_ACCESS_KEY_ID` / `OBJECT_STORE_READ_SECRET_ACCESS_KEY` for\nfuture download-only lanes while signed API URLs continue to use\n`OBJECT_STORE_SIGNING_SECRET`. The bucket must already exist; object-scoped R2\ntokens are enough for artifact writes and reads, but they do not create buckets.\nRun `npm run r2:bucket:create:prod` or `npm run r2:bucket:create --\n<bucket-name>` with a short-lived Cloudflare R2 account token when the bucket\nneeds to be provisioned through [Wrangler](https://developers.cloudflare.com/r2/buckets/create-buckets/).\nFor live R2 verification with object-scoped S3 credentials, set\n`CUSTOMCARD_S3_ARTIFACT_DOCTOR_BUCKET_MODE=existing` so the doctor writes under\na temporary project prefix inside the configured bucket and only deletes the\nobjects it created.\n\nThe AWS artifact-store module provisions only the artifact storage boundary:\npublic access block, bucket-owner-enforced ownership, versioning, AES256\nserver-side encryption, lifecycle cleanup under `projects/`, an HTTPS-only and\nencrypted-upload bucket policy, and a least-privilege writer policy scoped to\n`projects/*`. Existing app and worker IAM roles can be attached by passing\n`app_role_name` and `worker_role_name`. Runtime outputs name the required\n`OBJECT_STORE_*` values that still need to be mirrored into the platform secret\nor config manager.\n\nThe persistence boundary requires auth-session storage, account identity storage,\nhashed recovery challenges, idempotency replay, queue job envelopes,\nrelationship-memory repository writes, render-packet repository writes,\nrender-packet artifact manifests, signed URL expiry, and append-only audit\nsignals in the migration before production Postgres handlers are claimed.\n\nThe Kubernetes `Secret` in `k8s/app.yaml` is intentionally empty and annotated as\npre-created by a secret manager. Production clusters should source the required\nkeys from the platform secret manager, External Secrets, Sealed Secrets, or an\nequivalent operator-approved mechanism. The `runtime:doctor` check rejects empty\nvalues, known placeholders, short auth/signing secrets, non-Postgres production\nAPI runtimes, and any live-order kill switch value other than `disabled`.\n\nSigned artifact handoff requires `OBJECT_STORE_SIGNING_SECRET` to be present and\nat least 32 characters. The committed manifests also keep\n`ARTIFACT_SIGNED_URL_TTL_MINUTES=15` explicit so download links remain\nshort-lived.\n\nReal external ordering stays disabled with `REAL_ORDER_KILL_SWITCH=disabled` until vendor sandbox tests and physical print certification are recorded.\n\n## Provider Adapter Secrets\n\n`env/.env.example` names the credential-gated adapters represented in\n`src/providerCatalog.ts`:\n\n- Google OAuth: `GOOGLE_OAUTH_CLIENT_ID`, `GOOGLE_OAUTH_CLIENT_SECRET`, `GOOGLE_OAUTH_REDIRECT_URI`, `GOOGLE_OAUTH_TOKEN_ENCRYPTION_KEY`, `GOOGLE_OAUTH_STATE_SECRET`.\n- Microsoft Graph: `MICROSOFT_CLIENT_ID`, `MICROSOFT_CLIENT_SECRET`,\n `MICROSOFT_TENANT_ID`.\n- Event platforms: `EVENTBRITE_CLIENT_ID`, `EVENTBRITE_CLIENT_SECRET`,\n `LUMA_API_KEY`, `MEETUP_CLIENT_ID`, and `MEETUP_CLIENT_SECRET`. Partiful is a\n contract-only/manual path until an official API exists.\n- Hosted identity providers: `CUSTOMCARD_AUTH_CALLBACK_URL`, `AUTH0_DOMAIN`,\n `AUTH0_CLIENT_ID`, `AUTH0_CLIENT_SECRET`, `AUTH0_AUDIENCE`,\n `CLERK_SECRET_KEY`, `CLERK_JWT_KEY`, `CLERK_AUTHORIZED_PARTIES`,\n `SUPABASE_URL`, `SUPABASE_ANON_KEY`, `SUPABASE_SERVICE_ROLE_KEY`,\n `FIREBASE_API_KEY`, `FIREBASE_PROJECT_ID`,\n `FIREBASE_SERVICE_ACCOUNT_JSON`, `COGNITO_DOMAIN`,\n `COGNITO_USER_POOL_ID`, `COGNITO_APP_CLIENT_ID`.\n- Contact import providers: `CARDDAV_BASE_URL`, `CARDDAV_USERNAME`,\n `CARDDAV_APP_PASSWORD`, `CARDDAV_ADDRESSBOOK_PATH`.\n- CRM/customer lifecycle providers: Salesforce, HubSpot, Zoho, Pipedrive,\n Dynamics, Shopify, Klaviyo, Mailchimp, ActiveCampaign, BigCommerce,\n WooCommerce, Square, Intercom, Monday.com, Amazon Selling Partner API, and\n Etsy keys listed in `env/.env.example`.\n- Text and image AI providers: `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`,\n `AZURE_OPENAI_ENDPOINT`, `AZURE_OPENAI_API_KEY`,\n `AZURE_OPENAI_CHAT_DEPLOYMENT`, `AZURE_OPENAI_IMAGE_DEPLOYMENT`,\n `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_REGION`,\n `BEDROCK_TEXT_MODEL_ID`, `BEDROCK_IMAGE_MODEL_ID`,\n `CLOUDFLARE_ACCOUNT_ID`, `CLOUDFLARE_API_TOKEN`,\n `CLOUDFLARE_WORKERS_AI_TEXT_API_TOKEN`,\n `CLOUDFLARE_WORKERS_AI_IMAGE_API_TOKEN`,\n `CLOUDFLARE_WORKERS_AI_TEXT_MODEL`, `CLOUDFLARE_WORKERS_AI_IMAGE_MODEL`,\n `GOOGLE_GENERATIVE_AI_API_KEY`, `MISTRAL_API_KEY`, `COHERE_API_KEY`,\n `PERPLEXITY_API_KEY`, `XAI_API_KEY`, `TOGETHER_API_KEY`, `GROQ_API_KEY`,\n `DEEPSEEK_API_KEY`, `FIREWORKS_API_KEY`, `STABILITY_API_KEY`,\n `HUGGINGFACE_API_TOKEN`, `DEEPAI_API_KEY`, `REPLICATE_API_TOKEN`, `IDEOGRAM_API_KEY`,\n `LEONARDO_API_KEY`, `FAL_KEY`, `BFL_API_KEY`.\n- Self-hosted model fallback: `SELF_HOSTED_LLM_BASE_URL`,\n `SELF_HOSTED_LLM_API_KEY`.\n\nCloudflare Workers AI can use one shared `CLOUDFLARE_API_TOKEN` for both\nchat and image generation, or separate lane-specific tokens through\n`CLOUDFLARE_WORKERS_AI_TEXT_API_TOKEN` and\n`CLOUDFLARE_WORKERS_AI_IMAGE_API_TOKEN`. The recommended low-cost defaults are\n`@cf/meta/llama-3.1-8b-instruct-fast` for JSON Mode card copy and prompt/chat\nwork and `@cf/bytedance/stable-diffusion-xl-lightning` for image generation.\nUse `@cf/meta/llama-3.3-70b-instruct-fp8-fast` as the text quality fallback and\n`@cf/black-forest-labs/flux-1-schnell` as the higher-quality image fallback\nwhen budget allows.\n- Notification providers: `RESEND_API_KEY`, `SENDGRID_API_KEY`,\n `POSTMARK_SERVER_TOKEN`, `MAILGUN_API_KEY`, `MAILGUN_DOMAIN`,\n `TWILIO_ACCOUNT_SID`, `TWILIO_AUTH_TOKEN`,\n `TWILIO_MESSAGING_SERVICE_SID`, `WHATSAPP_ACCESS_TOKEN`,\n `WHATSAPP_PHONE_NUMBER_ID`, `EXPO_ACCESS_TOKEN`, `FIREBASE_PROJECT_ID`,\n `FIREBASE_SERVICE_ACCOUNT_JSON`, `CUSTOMERIO_APP_API_KEY`,\n `CUSTOMERIO_TRANSACTIONAL_MESSAGE_ID`, `BRAZE_REST_ENDPOINT`,\n `BRAZE_REST_API_KEY`, `BRAZE_CANVAS_ID`, `ONESIGNAL_APP_ID`,\n `ONESIGNAL_REST_API_KEY`, `COURIER_AUTH_TOKEN`, `COURIER_TEMPLATE_ID`,\n `KNOCK_API_KEY`, `KNOCK_WORKFLOW_KEY`, `NOVU_API_KEY`, `NOVU_WORKFLOW_ID`,\n `TRANSACTIONAL_EMAIL_API_KEY`, `TRANSACTIONAL_EMAIL_FROM`.\n- Workflow providers: Zapier, Make, Slack, Microsoft Teams, Notion, Airtable,\n Google Sheets, n8n, Workato, and Pipedream keys listed in\n `env/.env.example`.\n- Payment sandbox providers: `STRIPE_SECRET_KEY`, `STRIPE_WEBHOOK_SECRET`,\n `CUSTOMCARD_PAYMENT_SUCCESS_URL`, `CUSTOMCARD_PAYMENT_CANCEL_URL`,\n `PAYPAL_CLIENT_ID`, `PAYPAL_CLIENT_SECRET`, `PAYPAL_WEBHOOK_ID`,\n `SQUARE_ACCESS_TOKEN`, `SQUARE_LOCATION_ID`,\n `SQUARE_WEBHOOK_SIGNATURE_KEY`, `ADYEN_API_KEY`,\n `ADYEN_MERCHANT_ACCOUNT`, `ADYEN_HMAC_KEY`.\n- Observability providers: `SENTRY_DSN`, `SENTRY_PROJECT_ID`,\n `SENTRY_ENVIRONMENT`, `POSTHOG_PROJECT_API_KEY`, `POSTHOG_HOST`,\n `OTEL_EXPORTER_OTLP_ENDPOINT`, `OTEL_EXPORTER_OTLP_HEADERS`,\n `GRAFANA_OTLP_ENDPOINT`, `GRAFANA_OTLP_INSTANCE_ID`,\n `GRAFANA_OTLP_API_KEY`, `DATADOG_API_KEY`, `DATADOG_SITE`,\n `BETTERSTACK_SOURCE_TOKEN`, `BETTERSTACK_INGESTING_HOST`.\n- Object-store artifact signer and live S3-compatible doctor:\n `CUSTOMCARD_ARTIFACT_PERSISTENCE`, `OBJECT_STORE_URL`,\n `OBJECT_STORE_BUCKET`, `OBJECT_STORE_ACCESS_KEY_ID`,\n `OBJECT_STORE_SECRET_ACCESS_KEY`, `OBJECT_STORE_READ_ACCESS_KEY_ID`,\n `OBJECT_STORE_READ_SECRET_ACCESS_KEY`, `OBJECT_STORE_REGION`,\n `OBJECT_STORE_PUBLIC_BASE_URL`, `OBJECT_STORE_SIGNING_SECRET`,\n `ARTIFACT_SIGNED_URL_TTL_MINUTES`. Bucket-admin setup also uses\n `CLOUDFLARE_ACCOUNT_ID` and a short-lived `CLOUDFLARE_API_TOKEN`.\n- Live vendor adapters: `WALGREENS_VENDOR_MODE`, `CVS_VENDOR_MODE`,\n `FEDEX_VENDOR_MODE`, `WALMART_VENDOR_MODE`, `STAPLES_VENDOR_MODE`,\n `OFFICE_DEPOT_VENDOR_MODE`.\n- Persistence controls: `CUSTOMCARD_API_RUNTIME`, `AUTH_SESSION_SECRET`,\n `CUSTOMCARD_CUSTOMER_SESSION_TOKEN`, `CUSTOMCARD_ADMIN_SESSION_TOKEN`,\n `IDEMPOTENCY_KEY_TTL_HOURS`.\n\nThese keys are documented for deployment readiness only. The current repo state\ndoes not make live provider calls, and vendor modes remain\n`disabled_until_certified` while `REAL_ORDER_KILL_SWITCH=disabled`.\n"}, "b3305fdb8271ceb280d9fcfe8976e7595c777a74": {"t": 1785772127.0367513, "n": 16603, "h": "78904b3616445597", "c": "# CustomCard Infrastructure\n\nThis directory is the deployable service skeleton for the production path.\n\n- `docker-compose.dev.yml` runs app, worker, Postgres, Redis, and MinIO for local development.\n- `docker-compose.droplet.yml` is the cheap single-host deployment shape for a small DigitalOcean droplet.\n- `k8s/app.yaml` is the cloud-native starting point for web and worker deployments.\n- `aws/artifact-store/` is the Terraform contract for a private, encrypted,\n versioned production artifact bucket and prefix-scoped app/worker IAM policy.\n- `migrations/001_initial_schema.sql` defines the durable service data model.\n- `env/.env.example` lists required secrets and kill switches.\n- `../scripts/api-server.mjs` serves `/api/health`, API bootstrap/readiness\n contracts, explicit contract/memory/Postgres runtime modes, and the built web\n app from `dist`.\n- `../src/artifactHandoff.ts` defines the render-packet artifact manifest and\n HMAC-signed URL contract used by the API/schema gates.\n- `../src/artifactStore.ts` writes render-packet artifacts to a temporary local\n filesystem object-store path and an injected S3-compatible client contract,\n then verifies readback without network calls.\n- `../scripts/persistence-doctor.mjs` validates auth-session, idempotency, queue\n job, and audit persistence signals.\n- `../scripts/demo-reset.mjs` validates the reviewer demo reset contract before\n anyone attempts to run seed data against a database.\n- `../scripts/deployment-readiness.mjs` emits the local deployment readiness\n report used by `npm run deployment:doctor`.\n- `../src/capacityPlan.ts` and `../src/capacityPlanData.mjs` define executable\n local-dev, cheap-droplet, cloud-native, and SaaS-scale capacity profiles used\n by `npm run capacity:doctor`.\n\nFor the droplet deployment, provide `POSTGRES_PASSWORD` from a real secret source\nor local deployment `.env` that is never committed. The compose file uses\n`${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD}` so the service fails closed instead\nof starting with an empty database password.\n\nFor Kubernetes, apply the `customcard-migrate` Job successfully before rolling\nthe web and worker Deployments. The deployment manifest keeps the live-order kill\nswitch explicit and uses probes so bad pods fail visibly.\n\nRun the local IaC readiness check before treating the manifests as reviewable:\n\n```sh\nnpm run deployment:doctor\nnpm run capacity:doctor\nnpm run cloud:doctor\nnpm run api:doctor\nnpm run api:doctor:memory\nnpm run api:doctor:postgres\nCUSTOMCARD_POSTGRES_INTEGRATION_DOCTOR=enabled DATABASE_URL=postgres://... npm run api:doctor:postgres:live\nCUSTOMCARD_POSTGRES_API_HTTP_DOCTOR=enabled DATABASE_URL=postgres://... npm run api:doctor:postgres:http\nCUSTOMCARD_ACCOUNT_AUTH_DOCTOR=enabled DATABASE_URL=postgres://... npm run account:doctor:live\nnpm run artifact:doctor\nCLOUDFLARE_ACCOUNT_ID=... CLOUDFLARE_API_TOKEN=... npm run r2:bucket:create:prod\nCLOUDFLARE_ACCOUNT_ID=... CLOUDFLARE_API_TOKEN=... npm run r2:bucket:list\nCUSTOMCARD_S3_ARTIFACT_DOCTOR=enabled OBJECT_STORE_URL=http://127.0.0.1:9000 OBJECT_STORE_BUCKET=customcard-ci-artifacts OBJECT_STORE_ACCESS_KEY_ID=customcard OBJECT_STORE_SECRET_ACCESS_KEY=customcard-dev-only OBJECT_STORE_REGION=us-east-1 OBJECT_STORE_SIGNING_SECRET=test-object-store-signing-secret-32 npm run artifact:doctor:s3:live\nCUSTOMCARD_S3_ARTIFACT_DOCTOR=enabled CUSTOMCARD_S3_ARTIFACT_DOCTOR_BUCKET_MODE=existing OBJECT_STORE_URL=https://<account-id>.r2.cloudflarestorage.com OBJECT_STORE_BUCKET=customcard-prod OBJECT_STORE_REGION=auto OBJECT_STORE_SIGNING_SECRET=test-object-store-signing-secret-32 npm run artifact:doctor:s3:live\nnpm run persistence:doctor\nnpm run demo:doctor\n```\n\nThe report checks the local-dev, cheap-droplet, cloud-native, runtime, and data\nlanes, including cloud-storage checks for the AWS artifact-store module. Passing\nthis check means the committed deployment contracts are internally consistent;\nit does not mean a real droplet, AWS account, or Kubernetes cluster has been\nprovisioned.\n\n`npm run capacity:doctor` verifies the profile contract, tests, admin/API\nsurfaces, documentation, CI wiring, and no-live-traffic posture. The profiles are\nplanning data only; measured production benchmarks, provider spend reports, and\nautoscaler evidence remain separate launch evidence.\n\nThe Kubernetes web deployment probes `/api/health`, and the production Docker\nimage starts `scripts/api-server.mjs` so the same container can serve the static\nweb bundle and the contract-first API endpoints.\n\nThe API runtime defaults to `CUSTOMCARD_API_RUNTIME=contract` only for local\nreviewer/static serving. Production-shaped environments must set\n`CUSTOMCARD_API_RUNTIME=postgres`; `runtime:doctor` rejects contract or memory\nthere so auth sessions and idempotency records are durable. `npm run\napi:doctor:memory` sets test customer/admin session tokens and\nvalidates Bearer auth plus `X-Idempotency-Key` replay without a live database.\n`npm run api:doctor:postgres` injects a fake Postgres pool into the same runtime\npath and validates session lookup, wrong-role blocking, idempotency replay,\nconflict handling, repository-backed relationship-memory inserts,\nrepository-backed render-packet inserts, repository-backed import-preview\ninserts, repository-backed card-project inserts, manual handoff\norder/consent/event inserts, data-request privacy/consent inserts, audit inserts,\nand queue-job inserts without external credentials.\n`CUSTOMCARD_POSTGRES_INTEGRATION_DOCTOR=enabled npm run\napi:doctor:postgres:live` creates an isolated temporary database on the configured\nPostgres server, applies the committed migration, seeds customer/admin sessions,\nverifies all repository-backed customer routes plus admin readiness through the\nreal `pg` runtime, exercises relationship-memory, render-packet, import-preview,\ncard-project, manual handoff, and data-request persistence, and drops the\ntemporary database before exiting.\n`CUSTOMCARD_POSTGRES_API_HTTP_DOCTOR=enabled npm run\napi:doctor:postgres:http` starts `scripts/api-server.mjs` against the same\nisolated migrated database shape and verifies public health/routes,\nadmin/customer Bearer auth, missing/wrong-role auth blocking, missing\nidempotency blocking, all 6 repository-backed customer HTTP mutations,\nidempotency replay/conflict, audit rows, queue jobs, and repository table counts\nbefore shutting the server down and dropping the temporary database.\nCI runs both live Postgres doctors against a Postgres service; deployed\nproduction account auth remains unclaimed.\n\n`CUSTOMCARD_ACCOUNT_AUTH_DOCTOR=enabled npm run account:doctor:live` uses the\nsame isolated-database pattern to verify hosted account identity rows,\nprovider-subject uniqueness, hashed recovery challenges, durable sessions, and\naccount-recovery audit rows. CI runs it against the Postgres service; live hosted\ntoken verification remains unclaimed.\n\n`npm run artifact:doctor` writes the sample render packet package to a\ntemporary local filesystem object-store path and an injected S3-compatible client\ncontract, reads every artifact back, verifies checksums and byte lengths, stores\nboth handoff manifests, and keeps network calls plus real orders disabled.\n`CUSTOMCARD_S3_ARTIFACT_DOCTOR=enabled npm run artifact:doctor:s3:live` writes\nthe same package to a live S3-compatible endpoint such as MinIO using path-style\nSigV4 requests, reads every object back, verifies checksums and byte lengths,\nstores the handoff manifest, reports `cloudWritesVerified: true`, cleans up the\nisolated bucket, and keeps external vendor calls plus real orders disabled. A\nproduction cloud bucket policy/IAM contract is now represented by\n`infra/aws/artifact-store` and checked by `npm run cloud:doctor`; live AWS apply,\nAccess Analyzer review, and account-specific IAM validation remain separate from\nthis repo-local proof.\n\nThe hosted API can persist render-packet image artifacts through\n`scripts/object-store-runtime.mjs` when `CUSTOMCARD_ARTIFACT_PERSISTENCE=enabled`\nand `CUSTOMCARD_API_RUNTIME` is `memory` or `postgres`. For Cloudflare R2, set\n`OBJECT_STORE_URL` to the account S3 endpoint, `OBJECT_STORE_BUCKET` to the R2\nbucket, `OBJECT_STORE_REGION=auto`, `OBJECT_STORE_PUBLIC_BASE_URL` to the hosted\n`/api/artifacts` route, and mirror the write-capable S3 credentials into\n`OBJECT_STORE_ACCESS_KEY_ID` / `OBJECT_STORE_SECRET_ACCESS_KEY`. Optional\nread-only R2 credentials can be supplied through\n`OBJECT_STORE_READ_ACCESS_KEY_ID` / `OBJECT_STORE_READ_SECRET_ACCESS_KEY` for\nfuture download-only lanes while signed API URLs continue to use\n`OBJECT_STORE_SIGNING_SECRET`. The bucket must already exist; object-scoped R2\ntokens are enough for artifact writes and reads, but they do not create buckets.\nRun `npm run r2:bucket:create:prod` or `npm run r2:bucket:create --\n<bucket-name>` with a short-lived Cloudflare R2 account token when the bucket\nneeds to be provisioned through [Wrangler](https://developers.cloudflare.com/r2/buckets/create-buckets/).\nFor live R2 verification with object-scoped S3 credentials, set\n`CUSTOMCARD_S3_ARTIFACT_DOCTOR_BUCKET_MODE=existing` so the doctor writes under\na temporary project prefix inside the configured bucket and only deletes the\nobjects it created.\n\nThe AWS artifact-store module provisions only the artifact storage boundary:\npublic access block, bucket-owner-enforced ownership, versioning, AES256\nserver-side encryption, lifecycle cleanup under `projects/`, an HTTPS-only and\nencrypted-upload bucket policy, and a least-privilege writer policy scoped to\n`projects/*`. Existing app and worker IAM roles can be attached by passing\n`app_role_name` and `worker_role_name`. Runtime outputs name the required\n`OBJECT_STORE_*` values that still need to be mirrored into the platform secret\nor config manager.\n\nThe persistence boundary requires auth-session storage, account identity storage,\nhashed recovery challenges, idempotency replay, queue job envelopes,\nrelationship-memory repository writes, render-packet repository writes,\nrender-packet artifact manifests, signed URL expiry, and append-only audit\nsignals in the migration before production Postgres handlers are claimed.\n\nThe Kubernetes `Secret` in `k8s/app.yaml` is intentionally empty and annotated as\npre-created by a secret manager. Production clusters should source the required\nkeys from the platform secret manager, External Secrets, Sealed Secrets, or an\nequivalent operator-approved mechanism. The `runtime:doctor` check rejects empty\nvalues, known placeholders, short auth/signing secrets, non-Postgres production\nAPI runtimes, and any live-order kill switch value other than `disabled`.\n\nSigned artifact handoff requires `OBJECT_STORE_SIGNING_SECRET` to be present and\nat least 32 characters. The committed manifests also keep\n`ARTIFACT_SIGNED_URL_TTL_MINUTES=15` explicit so download links remain\nshort-lived.\n\nReal external ordering stays disabled with `REAL_ORDER_KILL_SWITCH=disabled` until vendor sandbox tests and physical print certification are recorded.\n\n## Provider Adapter Secrets\n\n`env/.env.example` names the credential-gated adapters represented in\n`src/providerCatalog.ts`:\n\n- Google OAuth: `GOOGLE_OAUTH_CLIENT_ID`, `GOOGLE_OAUTH_CLIENT_SECRET`, `GOOGLE_OAUTH_REDIRECT_URI`, `GOOGLE_OAUTH_TOKEN_ENCRYPTION_KEY`, `GOOGLE_OAUTH_STATE_SECRET`.\n- Microsoft Graph: `MICROSOFT_CLIENT_ID`, `MICROSOFT_CLIENT_SECRET`,\n `MICROSOFT_TENANT_ID`.\n- Event platforms: `EVENTBRITE_CLIENT_ID`, `EVENTBRITE_CLIENT_SECRET`,\n `LUMA_API_KEY`, `MEETUP_CLIENT_ID`, and `MEETUP_CLIENT_SECRET`. Partiful is a\n contract-only/manual path until an official API exists.\n- Hosted identity providers: `CUSTOMCARD_AUTH_CALLBACK_URL`, `AUTH0_DOMAIN`,\n `AUTH0_CLIENT_ID`, `AUTH0_CLIENT_SECRET`, `AUTH0_AUDIENCE`,\n `CLERK_SECRET_KEY`, `CLERK_JWT_KEY`, `CLERK_AUTHORIZED_PARTIES`,\n `SUPABASE_URL`, `SUPABASE_ANON_KEY`, `SUPABASE_SERVICE_ROLE_KEY`,\n `FIREBASE_API_KEY`, `FIREBASE_PROJECT_ID`,\n `FIREBASE_SERVICE_ACCOUNT_JSON`, `COGNITO_DOMAIN`,\n `COGNITO_USER_POOL_ID`, `COGNITO_APP_CLIENT_ID`.\n- Contact import providers: `CARDDAV_BASE_URL`, `CARDDAV_USERNAME`,\n `CARDDAV_APP_PASSWORD`, `CARDDAV_ADDRESSBOOK_PATH`.\n- CRM/customer lifecycle providers: Salesforce, HubSpot, Zoho, Pipedrive,\n Dynamics, Shopify, Klaviyo, Mailchimp, ActiveCampaign, BigCommerce,\n WooCommerce, Square, Intercom, Monday.com, Amazon Selling Partner API, and\n Etsy keys listed in `env/.env.example`.\n- Text and image AI providers: `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`,\n `AZURE_OPENAI_ENDPOINT`, `AZURE_OPENAI_API_KEY`,\n `AZURE_OPENAI_CHAT_DEPLOYMENT`, `AZURE_OPENAI_IMAGE_DEPLOYMENT`,\n `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_REGION`,\n `BEDROCK_TEXT_MODEL_ID`, `BEDROCK_IMAGE_MODEL_ID`,\n `CLOUDFLARE_ACCOUNT_ID`, `CLOUDFLARE_API_TOKEN`,\n `CLOUDFLARE_WORKERS_AI_TEXT_API_TOKEN`,\n `CLOUDFLARE_WORKERS_AI_IMAGE_API_TOKEN`,\n `CLOUDFLARE_WORKERS_AI_TEXT_MODEL`, `CLOUDFLARE_WORKERS_AI_IMAGE_MODEL`,\n `GOOGLE_GENERATIVE_AI_API_KEY`, `MISTRAL_API_KEY`, `COHERE_API_KEY`,\n `PERPLEXITY_API_KEY`, `XAI_API_KEY`, `TOGETHER_API_KEY`, `GROQ_API_KEY`,\n `DEEPSEEK_API_KEY`, `FIREWORKS_API_KEY`, `STABILITY_API_KEY`,\n `HUGGINGFACE_API_TOKEN`, `DEEPAI_API_KEY`, `REPLICATE_API_TOKEN`, `IDEOGRAM_API_KEY`,\n `LEONARDO_API_KEY`, `FAL_KEY`, `BFL_API_KEY`.\n- Self-hosted model fallback: `SELF_HOSTED_LLM_BASE_URL`,\n `SELF_HOSTED_LLM_API_KEY`.\n\nCloudflare Workers AI can use one shared `CLOUDFLARE_API_TOKEN` for both\nchat and image generation, or separate lane-specific tokens through\n`CLOUDFLARE_WORKERS_AI_TEXT_API_TOKEN` and\n`CLOUDFLARE_WORKERS_AI_IMAGE_API_TOKEN`. The recommended low-cost defaults are\n`@cf/meta/llama-3.1-8b-instruct-fast` for JSON Mode card copy and prompt/chat\nwork and `@cf/bytedance/stable-diffusion-xl-lightning` for image generation.\nUse `@cf/meta/llama-3.3-70b-instruct-fp8-fast` as the text quality fallback and\n`@cf/black-forest-labs/flux-1-schnell` as the higher-quality image fallback\nwhen budget allows.\n- Notification providers: `RESEND_API_KEY`, `SENDGRID_API_KEY`,\n `POSTMARK_SERVER_TOKEN`, `MAILGUN_API_KEY`, `MAILGUN_DOMAIN`,\n `TWILIO_ACCOUNT_SID`, `TWILIO_AUTH_TOKEN`,\n `TWILIO_MESSAGING_SERVICE_SID`, `WHATSAPP_ACCESS_TOKEN`,\n `WHATSAPP_PHONE_NUMBER_ID`, `EXPO_ACCESS_TOKEN`, `FIREBASE_PROJECT_ID`,\n `FIREBASE_SERVICE_ACCOUNT_JSON`, `CUSTOMERIO_APP_API_KEY`,\n `CUSTOMERIO_TRANSACTIONAL_MESSAGE_ID`, `BRAZE_REST_ENDPOINT`,\n `BRAZE_REST_API_KEY`, `BRAZE_CANVAS_ID`, `ONESIGNAL_APP_ID`,\n `ONESIGNAL_REST_API_KEY`, `COURIER_AUTH_TOKEN`, `COURIER_TEMPLATE_ID`,\n `KNOCK_API_KEY`, `KNOCK_WORKFLOW_KEY`, `NOVU_API_KEY`, `NOVU_WORKFLOW_ID`,\n `TRANSACTIONAL_EMAIL_API_KEY`, `TRANSACTIONAL_EMAIL_FROM`.\n- Workflow providers: Zapier, Make, Slack, Microsoft Teams, Notion, Airtable,\n Google Sheets, n8n, Workato, and Pipedream keys listed in\n `env/.env.example`.\n- Payment sandbox providers: `STRIPE_SECRET_KEY`, `STRIPE_WEBHOOK_SECRET`,\n `CUSTOMCARD_PAYMENT_SUCCESS_URL`, `CUSTOMCARD_PAYMENT_CANCEL_URL`,\n `PAYPAL_CLIENT_ID`, `PAYPAL_CLIENT_SECRET`, `PAYPAL_WEBHOOK_ID`,\n `SQUARE_ACCESS_TOKEN`, `SQUARE_LOCATION_ID`,\n `SQUARE_WEBHOOK_SIGNATURE_KEY`, `ADYEN_API_KEY`,\n `ADYEN_MERCHANT_ACCOUNT`, `ADYEN_HMAC_KEY`.\n- Observability providers: `SENTRY_DSN`, `SENTRY_PROJECT_ID`,\n `SENTRY_ENVIRONMENT`, `POSTHOG_PROJECT_API_KEY`, `POSTHOG_HOST`,\n `OTEL_EXPORTER_OTLP_ENDPOINT`, `OTEL_EXPORTER_OTLP_HEADERS`,\n `GRAFANA_OTLP_ENDPOINT`, `GRAFANA_OTLP_INSTANCE_ID`,\n `GRAFANA_OTLP_API_KEY`, `DATADOG_API_KEY`, `DATADOG_SITE`,\n `BETTERSTACK_SOURCE_TOKEN`, `BETTERSTACK_INGESTING_HOST`.\n- Object-store artifact signer and live S3-compatible doctor:\n `CUSTOMCARD_ARTIFACT_PERSISTENCE`, `OBJECT_STORE_URL`,\n `OBJECT_STORE_BUCKET`, `OBJECT_STORE_ACCESS_KEY_ID`,\n `OBJECT_STORE_SECRET_ACCESS_KEY`, `OBJECT_STORE_READ_ACCESS_KEY_ID`,\n `OBJECT_STORE_READ_SECRET_ACCESS_KEY`, `OBJECT_STORE_REGION`,\n `OBJECT_STORE_PUBLIC_BASE_URL`, `OBJECT_STORE_SIGNING_SECRET`,\n `ARTIFACT_SIGNED_URL_TTL_MINUTES`. Bucket-admin setup also uses\n `CLOUDFLARE_ACCOUNT_ID` and a short-lived `CLOUDFLARE_API_TOKEN`.\n- Live vendor adapters: `WALGREENS_VENDOR_MODE`, `CVS_VENDOR_MODE`,\n `FEDEX_VENDOR_MODE`, `WALMART_VENDOR_MODE`, `STAPLES_VENDOR_MODE`,\n `OFFICE_DEPOT_VENDOR_MODE`.\n- Persistence controls: `CUSTOMCARD_API_RUNTIME`, `AUTH_SESSION_SECRET`,\n `CUSTOMCARD_CUSTOMER_SESSION_TOKEN`, `CUSTOMCARD_ADMIN_SESSION_TOKEN`,\n `IDEMPOTENCY_KEY_TTL_HOURS`.\n\nThese keys are documented for deployment readiness only. The current repo state\ndoes not make live provider calls, and vendor modes remain\n`disabled_until_certified` while `REAL_ORDER_KILL_SWITCH=disabled`.\n"}, "8fa47b80f2f3de3f4f6ef11c8d4c042c6a49721b": {"t": 1785772128.2345214, "n": 16537, "h": "12aa409cba67613b", "c": "# CustomCard Infrastructure\n\nThis directory is the deployable service skeleton for the production path.\n\n- `docker-compose.dev.yml` runs app, worker, Postgres, Redis, and MinIO for local development.\n- `docker-compose.droplet.yml` is the cheap single-host deployment shape for a small DigitalOcean droplet.\n- `k8s/app.yaml` is the cloud-native starting point for web and worker deployments.\n- `aws/artifact-store/` is the Terraform contract for a private, encrypted,\n versioned production artifact bucket and prefix-scoped app/worker IAM policy.\n- `migrations/001_initial_schema.sql` defines the durable service data model.\n- `env/.env.example` lists required secrets and kill switches.\n- `../scripts/api-server.mjs` serves `/api/health`, API bootstrap/readiness\n contracts, explicit contract/memory/Postgres runtime modes, and the built web\n app from `dist`.\n- `../src/artifactHandoff.ts` defines the render-packet artifact manifest and\n HMAC-signed URL contract used by the API/schema gates.\n- `../src/artifactStore.ts` writes render-packet artifacts to a temporary local\n filesystem object-store path and an injected S3-compatible client contract,\n then verifies readback without network calls.\n- `../scripts/persistence-doctor.mjs` validates auth-session, idempotency, queue\n job, and audit persistence signals.\n- `../scripts/demo-reset.mjs` validates the reviewer demo reset contract before\n anyone attempts to run seed data against a database.\n- `../scripts/deployment-readiness.mjs` emits the local deployment readiness\n report used by `npm run deployment:doctor`.\n- `../src/capacityPlan.ts` and `../src/capacityPlanData.mjs` define executable\n local-dev, cheap-droplet, cloud-native, and SaaS-scale capacity profiles used\n by `npm run capacity:doctor`.\n\nFor the droplet deployment, provide `POSTGRES_PASSWORD` from a real secret source\nor local deployment `.env` that is never committed. The compose file uses\n`${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD}` so the service fails closed instead\nof starting with an empty database password.\n\nFor Kubernetes, apply the `customcard-migrate` Job successfully before rolling\nthe web and worker Deployments. The deployment manifest keeps the live-order kill\nswitch explicit and uses probes so bad pods fail visibly.\n\nRun the local IaC readiness check before treating the manifests as reviewable:\n\n```sh\nnpm run deployment:doctor\nnpm run capacity:doctor\nnpm run cloud:doctor\nnpm run api:doctor\nnpm run api:doctor:memory\nnpm run api:doctor:postgres\nCUSTOMCARD_POSTGRES_INTEGRATION_DOCTOR=enabled DATABASE_URL=postgres://... npm run api:doctor:postgres:live\nCUSTOMCARD_POSTGRES_API_HTTP_DOCTOR=enabled DATABASE_URL=postgres://... npm run api:doctor:postgres:http\nCUSTOMCARD_ACCOUNT_AUTH_DOCTOR=enabled DATABASE_URL=postgres://... npm run account:doctor:live\nnpm run artifact:doctor\nCLOUDFLARE_ACCOUNT_ID=... CLOUDFLARE_API_TOKEN=... npm run r2:bucket:create:prod\nCLOUDFLARE_ACCOUNT_ID=... CLOUDFLARE_API_TOKEN=... npm run r2:bucket:list\nCUSTOMCARD_S3_ARTIFACT_DOCTOR=enabled OBJECT_STORE_URL=http://127.0.0.1:9000 OBJECT_STORE_BUCKET=customcard-ci-artifacts OBJECT_STORE_ACCESS_KEY_ID=customcard OBJECT_STORE_SECRET_ACCESS_KEY=customcard-dev-only OBJECT_STORE_REGION=us-east-1 OBJECT_STORE_SIGNING_SECRET=test-object-store-signing-secret-32 npm run artifact:doctor:s3:live\nCUSTOMCARD_S3_ARTIFACT_DOCTOR=enabled CUSTOMCARD_S3_ARTIFACT_DOCTOR_BUCKET_MODE=existing OBJECT_STORE_URL=https://<account-id>.r2.cloudflarestorage.com OBJECT_STORE_BUCKET=customcard-prod OBJECT_STORE_REGION=auto OBJECT_STORE_SIGNING_SECRET=test-object-store-signing-secret-32 npm run artifact:doctor:s3:live\nnpm run persistence:doctor\nnpm run demo:doctor\n```\n\nThe report checks the local-dev, cheap-droplet, cloud-native, runtime, and data\nlanes, including cloud-storage checks for the AWS artifact-store module. Passing\nthis check means the committed deployment contracts are internally consistent;\nit does not mean a real droplet, AWS account, or Kubernetes cluster has been\nprovisioned.\n\n`npm run capacity:doctor` verifies the profile contract, tests, admin/API\nsurfaces, documentation, CI wiring, and no-live-traffic posture. The profiles are\nplanning data only; measured production benchmarks, provider spend reports, and\nautoscaler evidence remain separate launch evidence.\n\nThe Kubernetes web deployment probes `/api/health`, and the production Docker\nimage starts `scripts/api-server.mjs` so the same container can serve the static\nweb bundle and the contract-first API endpoints.\n\nThe API runtime defaults to `CUSTOMCARD_API_RUNTIME=contract` only for local\nreviewer/static serving. Production-shaped environments must set\n`CUSTOMCARD_API_RUNTIME=postgres`; `runtime:doctor` rejects contract or memory\nthere so auth sessions and idempotency records are durable. `npm run\napi:doctor:memory` sets test customer/admin session tokens and\nvalidates Bearer auth plus `X-Idempotency-Key` replay without a live database.\n`npm run api:doctor:postgres` injects a fake Postgres pool into the same runtime\npath and validates session lookup, wrong-role blocking, idempotency replay,\nconflict handling, repository-backed relationship-memory inserts,\nrepository-backed render-packet inserts, repository-backed import-preview\ninserts, repository-backed card-project inserts, manual handoff\norder/consent/event inserts, data-request privacy/consent inserts, audit inserts,\nand queue-job inserts without external credentials.\n`CUSTOMCARD_POSTGRES_INTEGRATION_DOCTOR=enabled npm run\napi:doctor:postgres:live` creates an isolated temporary database on the configured\nPostgres server, applies the committed migration, seeds customer/admin sessions,\nverifies all repository-backed customer routes plus admin readiness through the\nreal `pg` runtime, exercises relationship-memory, render-packet, import-preview,\ncard-project, manual handoff, and data-request persistence, and drops the\ntemporary database before exiting.\n`CUSTOMCARD_POSTGRES_API_HTTP_DOCTOR=enabled npm run\napi:doctor:postgres:http` starts `scripts/api-server.mjs` against the same\nisolated migrated database shape and verifies public health/routes,\nadmin/customer Bearer auth, missing/wrong-role auth blocking, missing\nidempotency blocking, all 6 repository-backed customer HTTP mutations,\nidempotency replay/conflict, audit rows, queue jobs, and repository table counts\nbefore shutting the server down and dropping the temporary database.\nCI runs both live Postgres doctors against a Postgres service; deployed\nproduction account auth remains unclaimed.\n\n`CUSTOMCARD_ACCOUNT_AUTH_DOCTOR=enabled npm run account:doctor:live` uses the\nsame isolated-database pattern to verify hosted account identity rows,\nprovider-subject uniqueness, hashed recovery challenges, durable sessions, and\naccount-recovery audit rows. CI runs it against the Postgres service; live hosted\ntoken verification remains unclaimed.\n\n`npm run artifact:doctor` writes the sample render packet package to a\ntemporary local filesystem object-store path and an injected S3-compatible client\ncontract, reads every artifact back, verifies checksums and byte lengths, stores\nboth handoff manifests, and keeps network calls plus real orders disabled.\n`CUSTOMCARD_S3_ARTIFACT_DOCTOR=enabled npm run artifact:doctor:s3:live` writes\nthe same package to a live S3-compatible endpoint such as MinIO using path-style\nSigV4 requests, reads every object back, verifies checksums and byte lengths,\nstores the handoff manifest, reports `cloudWritesVerified: true`, cleans up the\nisolated bucket, and keeps external vendor calls plus real orders disabled. A\nproduction cloud bucket policy/IAM contract is now represented by\n`infra/aws/artifact-store` and checked by `npm run cloud:doctor`; live AWS apply,\nAccess Analyzer review, and account-specific IAM validation remain separate from\nthis repo-local proof.\n\nThe hosted API can persist render-packet image artifacts through\n`scripts/object-store-runtime.mjs` when `CUSTOMCARD_ARTIFACT_PERSISTENCE=enabled`\nand `CUSTOMCARD_API_RUNTIME` is `memory` or `postgres`. For Cloudflare R2, set\n`OBJECT_STORE_URL` to the account S3 endpoint, `OBJECT_STORE_BUCKET` to the R2\nbucket, `OBJECT_STORE_REGION=auto`, `OBJECT_STORE_PUBLIC_BASE_URL` to the hosted\n`/api/artifacts` route, and mirror the write-capable S3 credentials into\n`OBJECT_STORE_ACCESS_KEY_ID` / `OBJECT_STORE_SECRET_ACCESS_KEY`. Optional\nread-only R2 credentials can be supplied through\n`OBJECT_STORE_READ_ACCESS_KEY_ID` / `OBJECT_STORE_READ_SECRET_ACCESS_KEY` for\nfuture download-only lanes while signed API URLs continue to use\n`OBJECT_STORE_SIGNING_SECRET`. The bucket must already exist; object-scoped R2\ntokens are enough for artifact writes and reads, but they do not create buckets.\nRun `npm run r2:bucket:create:prod` or `npm run r2:bucket:create --\n<bucket-name>` with a short-lived Cloudflare R2 account token when the bucket\nneeds to be provisioned through [Wrangler](https://developers.cloudflare.com/r2/buckets/create-buckets/).\nFor live R2 verification with object-scoped S3 credentials, set\n`CUSTOMCARD_S3_ARTIFACT_DOCTOR_BUCKET_MODE=existing` so the doctor writes under\na temporary project prefix inside the configured bucket and only deletes the\nobjects it created.\n\nThe AWS artifact-store module provisions only the artifact storage boundary:\npublic access block, bucket-owner-enforced ownership, versioning, AES256\nserver-side encryption, lifecycle cleanup under `projects/`, an HTTPS-only and\nencrypted-upload bucket policy, and a least-privilege writer policy scoped to\n`projects/*`. Existing app and worker IAM roles can be attached by passing\n`app_role_name` and `worker_role_name`. Runtime outputs name the required\n`OBJECT_STORE_*` values that still need to be mirrored into the platform secret\nor config manager.\n\nThe persistence boundary requires auth-session storage, account identity storage,\nhashed recovery challenges, idempotency replay, queue job envelopes,\nrelationship-memory repository writes, render-packet repository writes,\nrender-packet artifact manifests, signed URL expiry, and append-only audit\nsignals in the migration before production Postgres handlers are claimed.\n\nThe Kubernetes `Secret` in `k8s/app.yaml` is intentionally empty and annotated as\npre-created by a secret manager. Production clusters should source the required\nkeys from the platform secret manager, External Secrets, Sealed Secrets, or an\nequivalent operator-approved mechanism. The `runtime:doctor` check rejects empty\nvalues, known placeholders, short auth/signing secrets, non-Postgres production\nAPI runtimes, and any live-order kill switch value other than `disabled`.\n\nSigned artifact handoff requires `OBJECT_STORE_SIGNING_SECRET` to be present and\nat least 32 characters. The committed manifests also keep\n`ARTIFACT_SIGNED_URL_TTL_MINUTES=15` explicit so download links remain\nshort-lived.\n\nReal external ordering stays disabled with `REAL_ORDER_KILL_SWITCH=disabled` until vendor sandbox tests and physical print certification are recorded.\n\n## Provider Adapter Secrets\n\n`env/.env.example` names the credential-gated adapters represented in\n`src/providerCatalog.ts`:\n\n- Google OAuth: `GOOGLE_OAUTH_CLIENT_ID`, `GOOGLE_OAUTH_CLIENT_SECRET`, `GOOGLE_OAUTH_REDIRECT_URI`.\n- Microsoft Graph: `MICROSOFT_CLIENT_ID`, `MICROSOFT_CLIENT_SECRET`,\n `MICROSOFT_TENANT_ID`.\n- Event platforms: `EVENTBRITE_CLIENT_ID`, `EVENTBRITE_CLIENT_SECRET`,\n `LUMA_API_KEY`, `MEETUP_CLIENT_ID`, and `MEETUP_CLIENT_SECRET`. Partiful is a\n contract-only/manual path until an official API exists.\n- Hosted identity providers: `CUSTOMCARD_AUTH_CALLBACK_URL`, `AUTH0_DOMAIN`,\n `AUTH0_CLIENT_ID`, `AUTH0_CLIENT_SECRET`, `AUTH0_AUDIENCE`,\n `CLERK_SECRET_KEY`, `CLERK_JWT_KEY`, `CLERK_AUTHORIZED_PARTIES`,\n `SUPABASE_URL`, `SUPABASE_ANON_KEY`, `SUPABASE_SERVICE_ROLE_KEY`,\n `FIREBASE_API_KEY`, `FIREBASE_PROJECT_ID`,\n `FIREBASE_SERVICE_ACCOUNT_JSON`, `COGNITO_DOMAIN`,\n `COGNITO_USER_POOL_ID`, `COGNITO_APP_CLIENT_ID`.\n- Contact import providers: `CARDDAV_BASE_URL`, `CARDDAV_USERNAME`,\n `CARDDAV_APP_PASSWORD`, `CARDDAV_ADDRESSBOOK_PATH`.\n- CRM/customer lifecycle providers: Salesforce, HubSpot, Zoho, Pipedrive,\n Dynamics, Shopify, Klaviyo, Mailchimp, ActiveCampaign, BigCommerce,\n WooCommerce, Square, Intercom, Monday.com, Amazon Selling Partner API, and\n Etsy keys listed in `env/.env.example`.\n- Text and image AI providers: `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`,\n `AZURE_OPENAI_ENDPOINT`, `AZURE_OPENAI_API_KEY`,\n `AZURE_OPENAI_CHAT_DEPLOYMENT`, `AZURE_OPENAI_IMAGE_DEPLOYMENT`,\n `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_REGION`,\n `BEDROCK_TEXT_MODEL_ID`, `BEDROCK_IMAGE_MODEL_ID`,\n `CLOUDFLARE_ACCOUNT_ID`, `CLOUDFLARE_API_TOKEN`,\n `CLOUDFLARE_WORKERS_AI_TEXT_API_TOKEN`,\n `CLOUDFLARE_WORKERS_AI_IMAGE_API_TOKEN`,\n `CLOUDFLARE_WORKERS_AI_TEXT_MODEL`, `CLOUDFLARE_WORKERS_AI_IMAGE_MODEL`,\n `GOOGLE_GENERATIVE_AI_API_KEY`, `MISTRAL_API_KEY`, `COHERE_API_KEY`,\n `PERPLEXITY_API_KEY`, `XAI_API_KEY`, `TOGETHER_API_KEY`, `GROQ_API_KEY`,\n `DEEPSEEK_API_KEY`, `FIREWORKS_API_KEY`, `STABILITY_API_KEY`,\n `HUGGINGFACE_API_TOKEN`, `DEEPAI_API_KEY`, `REPLICATE_API_TOKEN`, `IDEOGRAM_API_KEY`,\n `LEONARDO_API_KEY`, `FAL_KEY`, `BFL_API_KEY`.\n- Self-hosted model fallback: `SELF_HOSTED_LLM_BASE_URL`,\n `SELF_HOSTED_LLM_API_KEY`.\n\nCloudflare Workers AI can use one shared `CLOUDFLARE_API_TOKEN` for both\nchat and image generation, or separate lane-specific tokens through\n`CLOUDFLARE_WORKERS_AI_TEXT_API_TOKEN` and\n`CLOUDFLARE_WORKERS_AI_IMAGE_API_TOKEN`. The recommended low-cost defaults are\n`@cf/meta/llama-3.1-8b-instruct-fast` for JSON Mode card copy and prompt/chat\nwork and `@cf/bytedance/stable-diffusion-xl-lightning` for image generation.\nUse `@cf/meta/llama-3.3-70b-instruct-fp8-fast` as the text quality fallback and\n`@cf/black-forest-labs/flux-1-schnell` as the higher-quality image fallback\nwhen budget allows.\n- Notification providers: `RESEND_API_KEY`, `SENDGRID_API_KEY`,\n `POSTMARK_SERVER_TOKEN`, `MAILGUN_API_KEY`, `MAILGUN_DOMAIN`,\n `TWILIO_ACCOUNT_SID`, `TWILIO_AUTH_TOKEN`,\n `TWILIO_MESSAGING_SERVICE_SID`, `WHATSAPP_ACCESS_TOKEN`,\n `WHATSAPP_PHONE_NUMBER_ID`, `EXPO_ACCESS_TOKEN`, `FIREBASE_PROJECT_ID`,\n `FIREBASE_SERVICE_ACCOUNT_JSON`, `CUSTOMERIO_APP_API_KEY`,\n `CUSTOMERIO_TRANSACTIONAL_MESSAGE_ID`, `BRAZE_REST_ENDPOINT`,\n `BRAZE_REST_API_KEY`, `BRAZE_CANVAS_ID`, `ONESIGNAL_APP_ID`,\n `ONESIGNAL_REST_API_KEY`, `COURIER_AUTH_TOKEN`, `COURIER_TEMPLATE_ID`,\n `KNOCK_API_KEY`, `KNOCK_WORKFLOW_KEY`, `NOVU_API_KEY`, `NOVU_WORKFLOW_ID`,\n `TRANSACTIONAL_EMAIL_API_KEY`, `TRANSACTIONAL_EMAIL_FROM`.\n- Workflow providers: Zapier, Make, Slack, Microsoft Teams, Notion, Airtable,\n Google Sheets, n8n, Workato, and Pipedream keys listed in\n `env/.env.example`.\n- Payment sandbox providers: `STRIPE_SECRET_KEY`, `STRIPE_WEBHOOK_SECRET`,\n `CUSTOMCARD_PAYMENT_SUCCESS_URL`, `CUSTOMCARD_PAYMENT_CANCEL_URL`,\n `PAYPAL_CLIENT_ID`, `PAYPAL_CLIENT_SECRET`, `PAYPAL_WEBHOOK_ID`,\n `SQUARE_ACCESS_TOKEN`, `SQUARE_LOCATION_ID`,\n `SQUARE_WEBHOOK_SIGNATURE_KEY`, `ADYEN_API_KEY`,\n `ADYEN_MERCHANT_ACCOUNT`, `ADYEN_HMAC_KEY`.\n- Observability providers: `SENTRY_DSN`, `SENTRY_PROJECT_ID`,\n `SENTRY_ENVIRONMENT`, `POSTHOG_PROJECT_API_KEY`, `POSTHOG_HOST`,\n `OTEL_EXPORTER_OTLP_ENDPOINT`, `OTEL_EXPORTER_OTLP_HEADERS`,\n `GRAFANA_OTLP_ENDPOINT`, `GRAFANA_OTLP_INSTANCE_ID`,\n `GRAFANA_OTLP_API_KEY`, `DATADOG_API_KEY`, `DATADOG_SITE`,\n `BETTERSTACK_SOURCE_TOKEN`, `BETTERSTACK_INGESTING_HOST`.\n- Object-store artifact signer and live S3-compatible doctor:\n `CUSTOMCARD_ARTIFACT_PERSISTENCE`, `OBJECT_STORE_URL`,\n `OBJECT_STORE_BUCKET`, `OBJECT_STORE_ACCESS_KEY_ID`,\n `OBJECT_STORE_SECRET_ACCESS_KEY`, `OBJECT_STORE_READ_ACCESS_KEY_ID`,\n `OBJECT_STORE_READ_SECRET_ACCESS_KEY`, `OBJECT_STORE_REGION`,\n `OBJECT_STORE_PUBLIC_BASE_URL`, `OBJECT_STORE_SIGNING_SECRET`,\n `ARTIFACT_SIGNED_URL_TTL_MINUTES`. Bucket-admin setup also uses\n `CLOUDFLARE_ACCOUNT_ID` and a short-lived `CLOUDFLARE_API_TOKEN`.\n- Live vendor adapters: `WALGREENS_VENDOR_MODE`, `CVS_VENDOR_MODE`,\n `FEDEX_VENDOR_MODE`, `WALMART_VENDOR_MODE`, `STAPLES_VENDOR_MODE`,\n `OFFICE_DEPOT_VENDOR_MODE`.\n- Persistence controls: `CUSTOMCARD_API_RUNTIME`, `AUTH_SESSION_SECRET`,\n `CUSTOMCARD_CUSTOMER_SESSION_TOKEN`, `CUSTOMCARD_ADMIN_SESSION_TOKEN`,\n `IDEMPOTENCY_KEY_TTL_HOURS`.\n\nThese keys are documented for deployment readiness only. The current repo state\ndoes not make live provider calls, and vendor modes remain\n`disabled_until_certified` while `REAL_ORDER_KILL_SWITCH=disabled`.\n"}, "6e7e56f3e83c5da881cca437a0d3fcdf3ca32b60": {"t": 1785772129.6302583, "n": 15410, "h": "2c9e7e910a6c158f", "c": "# CustomCard Infrastructure\n\nThis directory is the deployable service skeleton for the production path.\n\n- `docker-compose.dev.yml` runs app, worker, Postgres, Redis, and MinIO for local development.\n- `docker-compose.droplet.yml` is the cheap single-host deployment shape for a small DigitalOcean droplet.\n- `k8s/app.yaml` is the cloud-native starting point for web and worker deployments.\n- `aws/artifact-store/` is the Terraform contract for a private, encrypted,\n versioned production artifact bucket and prefix-scoped app/worker IAM policy.\n- `migrations/001_initial_schema.sql` defines the durable service data model.\n- `env/.env.example` lists required secrets and kill switches.\n- `../scripts/api-server.mjs` serves `/api/health`, API bootstrap/readiness\n contracts, explicit contract/memory/Postgres runtime modes, and the built web\n app from `dist`.\n- `../src/artifactHandoff.ts` defines the render-packet artifact manifest and\n HMAC-signed URL contract used by the API/schema gates.\n- `../src/artifactStore.ts` writes render-packet artifacts to a temporary local\n filesystem object-store path and an injected S3-compatible client contract,\n then verifies readback without network calls.\n- `../scripts/persistence-doctor.mjs` validates auth-session, idempotency, queue\n job, and audit persistence signals.\n- `../scripts/demo-reset.mjs` validates the reviewer demo reset contract before\n anyone attempts to run seed data against a database.\n- `../scripts/deployment-readiness.mjs` emits the local deployment readiness\n report used by `npm run deployment:doctor`.\n- `../src/capacityPlan.ts` and `../src/capacityPlanData.mjs` define executable\n local-dev, cheap-droplet, cloud-native, and SaaS-scale capacity profiles used\n by `npm run capacity:doctor`.\n\nFor the droplet deployment, provide `POSTGRES_PASSWORD` from a real secret source\nor local deployment `.env` that is never committed. The compose file uses\n`${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD}` so the service fails closed instead\nof starting with an empty database password.\n\nFor Kubernetes, apply the `customcard-migrate` Job successfully before rolling\nthe web and worker Deployments. The deployment manifest keeps the live-order kill\nswitch explicit and uses probes so bad pods fail visibly.\n\nRun the local IaC readiness check before treating the manifests as reviewable:\n\n```sh\nnpm run deployment:doctor\nnpm run capacity:doctor\nnpm run cloud:doctor\nnpm run api:doctor\nnpm run api:doctor:memory\nnpm run api:doctor:postgres\nCUSTOMCARD_POSTGRES_INTEGRATION_DOCTOR=enabled DATABASE_URL=postgres://... npm run api:doctor:postgres:live\nCUSTOMCARD_POSTGRES_API_HTTP_DOCTOR=enabled DATABASE_URL=postgres://... npm run api:doctor:postgres:http\nCUSTOMCARD_ACCOUNT_AUTH_DOCTOR=enabled DATABASE_URL=postgres://... npm run account:doctor:live\nnpm run artifact:doctor\nCLOUDFLARE_ACCOUNT_ID=... CLOUDFLARE_API_TOKEN=... npm run r2:bucket:create:prod\nCLOUDFLARE_ACCOUNT_ID=... CLOUDFLARE_API_TOKEN=... npm run r2:bucket:list\nCUSTOMCARD_S3_ARTIFACT_DOCTOR=enabled OBJECT_STORE_URL=http://127.0.0.1:9000 OBJECT_STORE_BUCKET=customcard-ci-artifacts OBJECT_STORE_ACCESS_KEY_ID=customcard OBJECT_STORE_SECRET_ACCESS_KEY=customcard-dev-only OBJECT_STORE_REGION=us-east-1 OBJECT_STORE_SIGNING_SECRET=test-object-store-signing-secret-32 npm run artifact:doctor:s3:live\nCUSTOMCARD_S3_ARTIFACT_DOCTOR=enabled CUSTOMCARD_S3_ARTIFACT_DOCTOR_BUCKET_MODE=existing OBJECT_STORE_URL=https://<account-id>.r2.cloudflarestorage.com OBJECT_STORE_BUCKET=customcard-prod OBJECT_STORE_REGION=auto OBJECT_STORE_SIGNING_SECRET=test-object-store-signing-secret-32 npm run artifact:doctor:s3:live\nnpm run persistence:doctor\nnpm run demo:doctor\n```\n\nThe report checks the local-dev, cheap-droplet, cloud-native, runtime, and data\nlanes, including cloud-storage checks for the AWS artifact-store module. Passing\nthis check means the committed deployment contracts are internally consistent;\nit does not mean a real droplet, AWS account, or Kubernetes cluster has been\nprovisioned.\n\n`npm run capacity:doctor` verifies the profile contract, tests, admin/API\nsurfaces, documentation, CI wiring, and no-live-traffic posture. The profiles are\nplanning data only; measured production benchmarks, provider spend reports, and\nautoscaler evidence remain separate launch evidence.\n\nThe Kubernetes web deployment probes `/api/health`, and the production Docker\nimage starts `scripts/api-server.mjs` so the same container can serve the static\nweb bundle and the contract-first API endpoints.\n\nThe API runtime defaults to `CUSTOMCARD_API_RUNTIME=contract` only for local\nreviewer/static serving. Production-shaped environments must set\n`CUSTOMCARD_API_RUNTIME=postgres`; `runtime:doctor` rejects contract or memory\nthere so auth sessions and idempotency records are durable. `npm run\napi:doctor:memory` sets test customer/admin session tokens and\nvalidates Bearer auth plus `X-Idempotency-Key` replay without a live database.\n`npm run api:doctor:postgres` injects a fake Postgres pool into the same runtime\npath and validates session lookup, wrong-role blocking, idempotency replay,\nconflict handling, repository-backed relationship-memory inserts,\nrepository-backed render-packet inserts, repository-backed import-preview\ninserts, repository-backed card-project inserts, manual handoff\norder/consent/event inserts, data-request privacy/consent inserts, audit inserts,\nand queue-job inserts without external credentials.\n`CUSTOMCARD_POSTGRES_INTEGRATION_DOCTOR=enabled npm run\napi:doctor:postgres:live` creates an isolated temporary database on the configured\nPostgres server, applies the committed migration, seeds customer/admin sessions,\nverifies all repository-backed customer routes plus admin readiness through the\nreal `pg` runtime, exercises relationship-memory, render-packet, import-preview,\ncard-project, manual handoff, and data-request persistence, and drops the\ntemporary database before exiting.\n`CUSTOMCARD_POSTGRES_API_HTTP_DOCTOR=enabled npm run\napi:doctor:postgres:http` starts `scripts/api-server.mjs` against the same\nisolated migrated database shape and verifies public health/routes,\nadmin/customer Bearer auth, missing/wrong-role auth blocking, missing\nidempotency blocking, all 6 repository-backed customer HTTP mutations,\nidempotency replay/conflict, audit rows, queue jobs, and repository table counts\nbefore shutting the server down and dropping the temporary database.\nCI runs both live Postgres doctors against a Postgres service; deployed\nproduction account auth remains unclaimed.\n\n`CUSTOMCARD_ACCOUNT_AUTH_DOCTOR=enabled npm run account:doctor:live` uses the\nsame isolated-database pattern to verify hosted account identity rows,\nprovider-subject uniqueness, hashed recovery challenges, durable sessions, and\naccount-recovery audit rows. CI runs it against the Postgres service; live hosted\ntoken verification remains unclaimed.\n\n`npm run artifact:doctor` writes the sample render packet package to a\ntemporary local filesystem object-store path and an injected S3-compatible client\ncontract, reads every artifact back, verifies checksums and byte lengths, stores\nboth handoff manifests, and keeps network calls plus real orders disabled.\n`CUSTOMCARD_S3_ARTIFACT_DOCTOR=enabled npm run artifact:doctor:s3:live` writes\nthe same package to a live S3-compatible endpoint such as MinIO using path-style\nSigV4 requests, reads every object back, verifies checksums and byte lengths,\nstores the handoff manifest, reports `cloudWritesVerified: true`, cleans up the\nisolated bucket, and keeps external vendor calls plus real orders disabled. A\nproduction cloud bucket policy/IAM contract is now represented by\n`infra/aws/artifact-store` and checked by `npm run cloud:doctor`; live AWS apply,\nAccess Analyzer review, and account-specific IAM validation remain separate from\nthis repo-local proof.\n\nThe hosted API can persist render-packet image artifacts through\n`scripts/object-store-runtime.mjs` when `CUSTOMCARD_ARTIFACT_PERSISTENCE=enabled`\nand `CUSTOMCARD_API_RUNTIME` is `memory` or `postgres`. For Cloudflare R2, set\n`OBJECT_STORE_URL` to the account S3 endpoint, `OBJECT_STORE_BUCKET` to the R2\nbucket, `OBJECT_STORE_REGION=auto`, `OBJECT_STORE_PUBLIC_BASE_URL` to the hosted\n`/api/artifacts` route, and mirror the write-capable S3 credentials into\n`OBJECT_STORE_ACCESS_KEY_ID` / `OBJECT_STORE_SECRET_ACCESS_KEY`. Optional\nread-only R2 credentials can be supplied through\n`OBJECT_STORE_READ_ACCESS_KEY_ID` / `OBJECT_STORE_READ_SECRET_ACCESS_KEY` for\nfuture download-only lanes while signed API URLs continue to use\n`OBJECT_STORE_SIGNING_SECRET`. The bucket must already exist; object-scoped R2\ntokens are enough for artifact writes and reads, but they do not create buckets.\nRun `npm run r2:bucket:create:prod` or `npm run r2:bucket:create --\n<bucket-name>` with a short-lived Cloudflare R2 account token when the bucket\nneeds to be provisioned through [Wrangler](https://developers.cloudflare.com/r2/buckets/create-buckets/).\nFor live R2 verification with object-scoped S3 credentials, set\n`CUSTOMCARD_S3_ARTIFACT_DOCTOR_BUCKET_MODE=existing` so the doctor writes under\na temporary project prefix inside the configured bucket and only deletes the\nobjects it created.\n\nThe AWS artifact-store module provisions only the artifact storage boundary:\npublic access block, bucket-owner-enforced ownership, versioning, AES256\nserver-side encryption, lifecycle cleanup under `projects/`, an HTTPS-only and\nencrypted-upload bucket policy, and a least-privilege writer policy scoped to\n`projects/*`. Existing app and worker IAM roles can be attached by passing\n`app_role_name` and `worker_role_name`. Runtime outputs name the required\n`OBJECT_STORE_*` values that still need to be mirrored into the platform secret\nor config manager.\n\nThe persistence boundary requires auth-session storage, account identity storage,\nhashed recovery challenges, idempotency replay, queue job envelopes,\nrelationship-memory repository writes, render-packet repository writes,\nrender-packet artifact manifests, signed URL expiry, and append-only audit\nsignals in the migration before production Postgres handlers are claimed.\n\nThe Kubernetes `Secret` in `k8s/app.yaml` is intentionally empty and annotated as\npre-created by a secret manager. Production clusters should source the required\nkeys from the platform secret manager, External Secrets, Sealed Secrets, or an\nequivalent operator-approved mechanism. The `runtime:doctor` check rejects empty\nvalues, known placeholders, short auth/signing secrets, non-Postgres production\nAPI runtimes, and any live-order kill switch value other than `disabled`.\n\nSigned artifact handoff requires `OBJECT_STORE_SIGNING_SECRET` to be present and\nat least 32 characters. The committed manifests also keep\n`ARTIFACT_SIGNED_URL_TTL_MINUTES=15` explicit so download links remain\nshort-lived.\n\nReal external ordering stays disabled with `REAL_ORDER_KILL_SWITCH=disabled` until vendor sandbox tests and physical print certification are recorded.\n\n## Provider Adapter Secrets\n\n`env/.env.example` names the credential-gated adapters represented in\n`src/providerCatalog.ts`:\n\n- Google OAuth: `GOOGLE_OAUTH_CLIENT_ID`, `GOOGLE_OAUTH_CLIENT_SECRET`.\n- Microsoft Graph: `MICROSOFT_CLIENT_ID`, `MICROSOFT_CLIENT_SECRET`,\n `MICROSOFT_TENANT_ID`.\n- Hosted identity providers: `CUSTOMCARD_AUTH_CALLBACK_URL`, `AUTH0_DOMAIN`,\n `AUTH0_CLIENT_ID`, `AUTH0_CLIENT_SECRET`, `AUTH0_AUDIENCE`,\n `CLERK_SECRET_KEY`, `CLERK_JWT_KEY`, `CLERK_AUTHORIZED_PARTIES`,\n `SUPABASE_URL`, `SUPABASE_ANON_KEY`, `SUPABASE_SERVICE_ROLE_KEY`,\n `FIREBASE_API_KEY`, `FIREBASE_PROJECT_ID`,\n `FIREBASE_SERVICE_ACCOUNT_JSON`, `COGNITO_DOMAIN`,\n `COGNITO_USER_POOL_ID`, `COGNITO_APP_CLIENT_ID`.\n- Contact import providers: `CARDDAV_BASE_URL`, `CARDDAV_USERNAME`,\n `CARDDAV_APP_PASSWORD`, `CARDDAV_ADDRESSBOOK_PATH`.\n- Text and image AI providers: `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`,\n `AZURE_OPENAI_ENDPOINT`, `AZURE_OPENAI_API_KEY`,\n `AZURE_OPENAI_CHAT_DEPLOYMENT`, `AZURE_OPENAI_IMAGE_DEPLOYMENT`,\n `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_REGION`,\n `BEDROCK_TEXT_MODEL_ID`, `BEDROCK_IMAGE_MODEL_ID`,\n `CLOUDFLARE_ACCOUNT_ID`, `CLOUDFLARE_API_TOKEN`,\n `CLOUDFLARE_WORKERS_AI_TEXT_API_TOKEN`,\n `CLOUDFLARE_WORKERS_AI_IMAGE_API_TOKEN`,\n `CLOUDFLARE_WORKERS_AI_TEXT_MODEL`, `CLOUDFLARE_WORKERS_AI_IMAGE_MODEL`,\n `GOOGLE_GENERATIVE_AI_API_KEY`, `MISTRAL_API_KEY`, `COHERE_API_KEY`,\n `PERPLEXITY_API_KEY`, `XAI_API_KEY`, `TOGETHER_API_KEY`, `GROQ_API_KEY`,\n `DEEPSEEK_API_KEY`, `FIREWORKS_API_KEY`, `STABILITY_API_KEY`,\n `HUGGINGFACE_API_TOKEN`, `DEEPAI_API_KEY`, `REPLICATE_API_TOKEN`, `IDEOGRAM_API_KEY`,\n `LEONARDO_API_KEY`, `FAL_KEY`, `BFL_API_KEY`.\n- Self-hosted model fallback: `SELF_HOSTED_LLM_BASE_URL`,\n `SELF_HOSTED_LLM_API_KEY`.\n\nCloudflare Workers AI can use one shared `CLOUDFLARE_API_TOKEN` for both\nchat and image generation, or separate lane-specific tokens through\n`CLOUDFLARE_WORKERS_AI_TEXT_API_TOKEN` and\n`CLOUDFLARE_WORKERS_AI_IMAGE_API_TOKEN`. The recommended low-cost defaults are\n`@cf/meta/llama-3.2-3b-instruct` for prompt/chat work and\n`@cf/bytedance/stable-diffusion-xl-lightning` for image generation. Use\n`@cf/black-forest-labs/flux-1-schnell` as the higher-quality image fallback\nwhen budget allows.\n- Notification providers: `RESEND_API_KEY`, `SENDGRID_API_KEY`,\n `POSTMARK_SERVER_TOKEN`, `MAILGUN_API_KEY`, `MAILGUN_DOMAIN`,\n `TWILIO_ACCOUNT_SID`, `TWILIO_AUTH_TOKEN`,\n `TWILIO_MESSAGING_SERVICE_SID`, `WHATSAPP_ACCESS_TOKEN`,\n `WHATSAPP_PHONE_NUMBER_ID`, `EXPO_ACCESS_TOKEN`,\n `TRANSACTIONAL_EMAIL_API_KEY`, `TRANSACTIONAL_EMAIL_FROM`.\n- Payment sandbox providers: `STRIPE_SECRET_KEY`, `STRIPE_WEBHOOK_SECRET`,\n `CUSTOMCARD_PAYMENT_SUCCESS_URL`, `CUSTOMCARD_PAYMENT_CANCEL_URL`,\n `PAYPAL_CLIENT_ID`, `PAYPAL_CLIENT_SECRET`, `PAYPAL_WEBHOOK_ID`,\n `SQUARE_ACCESS_TOKEN`, `SQUARE_LOCATION_ID`,\n `SQUARE_WEBHOOK_SIGNATURE_KEY`, `ADYEN_API_KEY`,\n `ADYEN_MERCHANT_ACCOUNT`, `ADYEN_HMAC_KEY`.\n- Observability providers: `SENTRY_DSN`, `SENTRY_PROJECT_ID`,\n `SENTRY_ENVIRONMENT`, `POSTHOG_PROJECT_API_KEY`, `POSTHOG_HOST`,\n `OTEL_EXPORTER_OTLP_ENDPOINT`, `OTEL_EXPORTER_OTLP_HEADERS`,\n `GRAFANA_OTLP_ENDPOINT`, `GRAFANA_OTLP_INSTANCE_ID`,\n `GRAFANA_OTLP_API_KEY`, `DATADOG_API_KEY`, `DATADOG_SITE`,\n `BETTERSTACK_SOURCE_TOKEN`, `BETTERSTACK_INGESTING_HOST`.\n- Object-store artifact signer and live S3-compatible doctor:\n `CUSTOMCARD_ARTIFACT_PERSISTENCE`, `OBJECT_STORE_URL`,\n `OBJECT_STORE_BUCKET`, `OBJECT_STORE_ACCESS_KEY_ID`,\n `OBJECT_STORE_SECRET_ACCESS_KEY`, `OBJECT_STORE_READ_ACCESS_KEY_ID`,\n `OBJECT_STORE_READ_SECRET_ACCESS_KEY`, `OBJECT_STORE_REGION`,\n `OBJECT_STORE_PUBLIC_BASE_URL`, `OBJECT_STORE_SIGNING_SECRET`,\n `ARTIFACT_SIGNED_URL_TTL_MINUTES`. Bucket-admin setup also uses\n `CLOUDFLARE_ACCOUNT_ID` and a short-lived `CLOUDFLARE_API_TOKEN`.\n- Live vendor adapters: `WALGREENS_VENDOR_MODE`, `CVS_VENDOR_MODE`,\n `FEDEX_VENDOR_MODE`, `WALMART_VENDOR_MODE`, `STAPLES_VENDOR_MODE`,\n `OFFICE_DEPOT_VENDOR_MODE`.\n- Persistence controls: `CUSTOMCARD_API_RUNTIME`, `AUTH_SESSION_SECRET`,\n `CUSTOMCARD_CUSTOMER_SESSION_TOKEN`, `CUSTOMCARD_ADMIN_SESSION_TOKEN`,\n `IDEMPOTENCY_KEY_TTL_HOURS`.\n\nThese keys are documented for deployment readiness only. The current repo state\ndoes not make live provider calls, and vendor modes remain\n`disabled_until_certified` while `REAL_ORDER_KILL_SWITCH=disabled`.\n"}, "a161cfe022b640bfc6e7dcb6ed8922ce87796ff2": {"t": 1785772130.813932, "n": 15154, "h": "f2352c6b630f7d65", "c": "# CustomCard Infrastructure\n\nThis directory is the deployable service skeleton for the production path.\n\n- `docker-compose.dev.yml` runs app, worker, Postgres, Redis, and MinIO for local development.\n- `docker-compose.droplet.yml` is the cheap single-host deployment shape for a small DigitalOcean droplet.\n- `k8s/app.yaml` is the cloud-native starting point for web and worker deployments.\n- `aws/artifact-store/` is the Terraform contract for a private, encrypted,\n versioned production artifact bucket and prefix-scoped app/worker IAM policy.\n- `migrations/001_initial_schema.sql` defines the durable service data model.\n- `env/.env.example` lists required secrets and kill switches.\n- `../scripts/api-server.mjs` serves `/api/health`, API bootstrap/readiness\n contracts, explicit contract/memory/Postgres runtime modes, and the built web\n app from `dist`.\n- `../src/artifactHandoff.ts` defines the render-packet artifact manifest and\n HMAC-signed URL contract used by the API/schema gates.\n- `../src/artifactStore.ts` writes render-packet artifacts to a temporary local\n filesystem object-store path and an injected S3-compatible client contract,\n then verifies readback without network calls.\n- `../scripts/persistence-doctor.mjs` validates auth-session, idempotency, queue\n job, and audit persistence signals.\n- `../scripts/demo-reset.mjs` validates the reviewer demo reset contract before\n anyone attempts to run seed data against a database.\n- `../scripts/deployment-readiness.mjs` emits the local deployment readiness\n report used by `npm run deployment:doctor`.\n- `../src/capacityPlan.ts` and `../src/capacityPlanData.mjs` define executable\n local-dev, cheap-droplet, cloud-native, and SaaS-scale capacity profiles used\n by `npm run capacity:doctor`.\n\nFor the droplet deployment, provide `POSTGRES_PASSWORD` from a real secret source\nor local deployment `.env` that is never committed. The compose file uses\n`${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD}` so the service fails closed instead\nof starting with an empty database password.\n\nFor Kubernetes, apply the `customcard-migrate` Job successfully before rolling\nthe web and worker Deployments. The deployment manifest keeps the live-order kill\nswitch explicit and uses probes so bad pods fail visibly.\n\nRun the local IaC readiness check before treating the manifests as reviewable:\n\n```sh\nnpm run deployment:doctor\nnpm run capacity:doctor\nnpm run cloud:doctor\nnpm run api:doctor\nnpm run api:doctor:memory\nnpm run api:doctor:postgres\nCUSTOMCARD_POSTGRES_INTEGRATION_DOCTOR=enabled DATABASE_URL=postgres://... npm run api:doctor:postgres:live\nCUSTOMCARD_POSTGRES_API_HTTP_DOCTOR=enabled DATABASE_URL=postgres://... npm run api:doctor:postgres:http\nCUSTOMCARD_ACCOUNT_AUTH_DOCTOR=enabled DATABASE_URL=postgres://... npm run account:doctor:live\nnpm run artifact:doctor\nCLOUDFLARE_ACCOUNT_ID=... CLOUDFLARE_API_TOKEN=... npm run r2:bucket:create:prod\nCLOUDFLARE_ACCOUNT_ID=... CLOUDFLARE_API_TOKEN=... npm run r2:bucket:list\nCUSTOMCARD_S3_ARTIFACT_DOCTOR=enabled OBJECT_STORE_URL=http://127.0.0.1:9000 OBJECT_STORE_BUCKET=customcard-ci-artifacts OBJECT_STORE_ACCESS_KEY_ID=customcard OBJECT_STORE_SECRET_ACCESS_KEY=customcard-dev-only OBJECT_STORE_REGION=us-east-1 OBJECT_STORE_SIGNING_SECRET=test-object-store-signing-secret-32 npm run artifact:doctor:s3:live\nCUSTOMCARD_S3_ARTIFACT_DOCTOR=enabled CUSTOMCARD_S3_ARTIFACT_DOCTOR_BUCKET_MODE=existing OBJECT_STORE_URL=https://<account-id>.r2.cloudflarestorage.com OBJECT_STORE_BUCKET=customcard-prod OBJECT_STORE_REGION=auto OBJECT_STORE_SIGNING_SECRET=test-object-store-signing-secret-32 npm run artifact:doctor:s3:live\nnpm run persistence:doctor\nnpm run demo:doctor\n```\n\nThe report checks the local-dev, cheap-droplet, cloud-native, runtime, and data\nlanes, including cloud-storage checks for the AWS artifact-store module. Passing\nthis check means the committed deployment contracts are internally consistent;\nit does not mean a real droplet, AWS account, or Kubernetes cluster has been\nprovisioned.\n\n`npm run capacity:doctor` verifies the profile contract, tests, admin/API\nsurfaces, documentation, CI wiring, and no-live-traffic posture. The profiles are\nplanning data only; measured production benchmarks, provider spend reports, and\nautoscaler evidence remain separate launch evidence.\n\nThe Kubernetes web deployment probes `/api/health`, and the production Docker\nimage starts `scripts/api-server.mjs` so the same container can serve the static\nweb bundle and the contract-first API endpoints.\n\nThe API runtime defaults to `CUSTOMCARD_API_RUNTIME=contract` for reviewer/static\nserving. `npm run api:doctor:memory` sets test customer/admin session tokens and\nvalidates Bearer auth plus `X-Idempotency-Key` replay without a live database.\n`npm run api:doctor:postgres` injects a fake Postgres pool into the same runtime\npath and validates session lookup, wrong-role blocking, idempotency replay,\nconflict handling, repository-backed relationship-memory inserts,\nrepository-backed render-packet inserts, repository-backed import-preview\ninserts, repository-backed card-project inserts, manual handoff\norder/consent/event inserts, data-request privacy/consent inserts, audit inserts,\nand queue-job inserts without external credentials.\n`CUSTOMCARD_POSTGRES_INTEGRATION_DOCTOR=enabled npm run\napi:doctor:postgres:live` creates an isolated temporary database on the configured\nPostgres server, applies the committed migration, seeds customer/admin sessions,\nverifies all repository-backed customer routes plus admin readiness through the\nreal `pg` runtime, exercises relationship-memory, render-packet, import-preview,\ncard-project, manual handoff, and data-request persistence, and drops the\ntemporary database before exiting.\n`CUSTOMCARD_POSTGRES_API_HTTP_DOCTOR=enabled npm run\napi:doctor:postgres:http` starts `scripts/api-server.mjs` against the same\nisolated migrated database shape and verifies public health/routes,\nadmin/customer Bearer auth, missing/wrong-role auth blocking, missing\nidempotency blocking, all 6 repository-backed customer HTTP mutations,\nidempotency replay/conflict, audit rows, queue jobs, and repository table counts\nbefore shutting the server down and dropping the temporary database.\nCI runs both live Postgres doctors against a Postgres service; deployed\nproduction account auth remains unclaimed.\n\n`CUSTOMCARD_ACCOUNT_AUTH_DOCTOR=enabled npm run account:doctor:live` uses the\nsame isolated-database pattern to verify hosted account identity rows,\nprovider-subject uniqueness, hashed recovery challenges, durable sessions, and\naccount-recovery audit rows. CI runs it against the Postgres service; live hosted\ntoken verification remains unclaimed.\n\n`npm run artifact:doctor` writes the sample render packet package to a\ntemporary local filesystem object-store path and an injected S3-compatible client\ncontract, reads every artifact back, verifies checksums and byte lengths, stores\nboth handoff manifests, and keeps network calls plus real orders disabled.\n`CUSTOMCARD_S3_ARTIFACT_DOCTOR=enabled npm run artifact:doctor:s3:live` writes\nthe same package to a live S3-compatible endpoint such as MinIO using path-style\nSigV4 requests, reads every object back, verifies checksums and byte lengths,\nstores the handoff manifest, reports `cloudWritesVerified: true`, cleans up the\nisolated bucket, and keeps external vendor calls plus real orders disabled. A\nproduction cloud bucket policy/IAM contract is now represented by\n`infra/aws/artifact-store` and checked by `npm run cloud:doctor`; live AWS apply,\nAccess Analyzer review, and account-specific IAM validation remain separate from\nthis repo-local proof.\n\nThe hosted API can persist render-packet image artifacts through\n`scripts/object-store-runtime.mjs` when `CUSTOMCARD_ARTIFACT_PERSISTENCE=enabled`\nand `CUSTOMCARD_API_RUNTIME` is `memory` or `postgres`. For Cloudflare R2, set\n`OBJECT_STORE_URL` to the account S3 endpoint, `OBJECT_STORE_BUCKET` to the R2\nbucket, `OBJECT_STORE_REGION=auto`, `OBJECT_STORE_PUBLIC_BASE_URL` to the hosted\n`/api/artifacts` route, and mirror the write-capable S3 credentials into\n`OBJECT_STORE_ACCESS_KEY_ID` / `OBJECT_STORE_SECRET_ACCESS_KEY`. Optional\nread-only R2 credentials can be supplied through\n`OBJECT_STORE_READ_ACCESS_KEY_ID` / `OBJECT_STORE_READ_SECRET_ACCESS_KEY` for\nfuture download-only lanes while signed API URLs continue to use\n`OBJECT_STORE_SIGNING_SECRET`. The bucket must already exist; object-scoped R2\ntokens are enough for artifact writes and reads, but they do not create buckets.\nRun `npm run r2:bucket:create:prod` or `npm run r2:bucket:create --\n<bucket-name>` with a short-lived Cloudflare R2 account token when the bucket\nneeds to be provisioned through [Wrangler](https://developers.cloudflare.com/r2/buckets/create-buckets/).\nFor live R2 verification with object-scoped S3 credentials, set\n`CUSTOMCARD_S3_ARTIFACT_DOCTOR_BUCKET_MODE=existing` so the doctor writes under\na temporary project prefix inside the configured bucket and only deletes the\nobjects it created.\n\nThe AWS artifact-store module provisions only the artifact storage boundary:\npublic access block, bucket-owner-enforced ownership, versioning, AES256\nserver-side encryption, lifecycle cleanup under `projects/`, an HTTPS-only and\nencrypted-upload bucket policy, and a least-privilege writer policy scoped to\n`projects/*`. Existing app and worker IAM roles can be attached by passing\n`app_role_name` and `worker_role_name`. Runtime outputs name the required\n`OBJECT_STORE_*` values that still need to be mirrored into the platform secret\nor config manager.\n\nThe persistence boundary requires auth-session storage, account identity storage,\nhashed recovery challenges, idempotency replay, queue job envelopes,\nrelationship-memory repository writes, render-packet repository writes,\nrender-packet artifact manifests, signed URL expiry, and append-only audit\nsignals in the migration before production Postgres handlers are claimed.\n\nThe Kubernetes `Secret` in `k8s/app.yaml` is intentionally empty and annotated as\npre-created by a secret manager. Production clusters should source the required\nkeys from the platform secret manager, External Secrets, Sealed Secrets, or an\nequivalent operator-approved mechanism. The `runtime:doctor` check rejects empty\nvalues, known placeholders, and any live-order kill switch value other than\n`disabled`.\n\nSigned artifact handoff requires `OBJECT_STORE_SIGNING_SECRET` to be present and\nat least 32 characters. The committed manifests also keep\n`ARTIFACT_SIGNED_URL_TTL_MINUTES=15` explicit so download links remain\nshort-lived.\n\nReal external ordering stays disabled with `REAL_ORDER_KILL_SWITCH=disabled` until vendor sandbox tests and physical print certification are recorded.\n\n## Provider Adapter Secrets\n\n`env/.env.example` names the credential-gated adapters represented in\n`src/providerCatalog.ts`:\n\n- Google OAuth: `GOOGLE_OAUTH_CLIENT_ID`, `GOOGLE_OAUTH_CLIENT_SECRET`.\n- Microsoft Graph: `MICROSOFT_CLIENT_ID`, `MICROSOFT_CLIENT_SECRET`,\n `MICROSOFT_TENANT_ID`.\n- Hosted identity providers: `CUSTOMCARD_AUTH_CALLBACK_URL`, `AUTH0_DOMAIN`,\n `AUTH0_CLIENT_ID`, `AUTH0_CLIENT_SECRET`, `AUTH0_AUDIENCE`,\n `CLERK_SECRET_KEY`, `CLERK_JWT_KEY`, `CLERK_AUTHORIZED_PARTIES`,\n `SUPABASE_URL`, `SUPABASE_ANON_KEY`, `SUPABASE_SERVICE_ROLE_KEY`,\n `FIREBASE_API_KEY`, `FIREBASE_PROJECT_ID`,\n `FIREBASE_SERVICE_ACCOUNT_JSON`, `COGNITO_DOMAIN`,\n `COGNITO_USER_POOL_ID`, `COGNITO_APP_CLIENT_ID`.\n- Contact import providers: `CARDDAV_BASE_URL`, `CARDDAV_USERNAME`,\n `CARDDAV_APP_PASSWORD`, `CARDDAV_ADDRESSBOOK_PATH`.\n- Text and image AI providers: `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`,\n `AZURE_OPENAI_ENDPOINT`, `AZURE_OPENAI_API_KEY`,\n `AZURE_OPENAI_CHAT_DEPLOYMENT`, `AZURE_OPENAI_IMAGE_DEPLOYMENT`,\n `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_REGION`,\n `BEDROCK_TEXT_MODEL_ID`, `BEDROCK_IMAGE_MODEL_ID`,\n `CLOUDFLARE_ACCOUNT_ID`, `CLOUDFLARE_API_TOKEN`,\n `CLOUDFLARE_WORKERS_AI_TEXT_API_TOKEN`,\n `CLOUDFLARE_WORKERS_AI_IMAGE_API_TOKEN`,\n `CLOUDFLARE_WORKERS_AI_TEXT_MODEL`, `CLOUDFLARE_WORKERS_AI_IMAGE_MODEL`,\n `GOOGLE_GENERATIVE_AI_API_KEY`, `MISTRAL_API_KEY`, `COHERE_API_KEY`,\n `PERPLEXITY_API_KEY`, `XAI_API_KEY`, `TOGETHER_API_KEY`, `GROQ_API_KEY`,\n `DEEPSEEK_API_KEY`, `FIREWORKS_API_KEY`, `STABILITY_API_KEY`,\n `HUGGINGFACE_API_TOKEN`, `DEEPAI_API_KEY`, `REPLICATE_API_TOKEN`, `IDEOGRAM_API_KEY`,\n `LEONARDO_API_KEY`, `FAL_KEY`, `BFL_API_KEY`.\n- Self-hosted model fallback: `SELF_HOSTED_LLM_BASE_URL`,\n `SELF_HOSTED_LLM_API_KEY`.\n\nCloudflare Workers AI can use one shared `CLOUDFLARE_API_TOKEN` for both\nchat and image generation, or separate lane-specific tokens through\n`CLOUDFLARE_WORKERS_AI_TEXT_API_TOKEN` and\n`CLOUDFLARE_WORKERS_AI_IMAGE_API_TOKEN`. The recommended low-cost defaults are\n`@cf/meta/llama-3.2-3b-instruct` for prompt/chat work and\n`@cf/bytedance/stable-diffusion-xl-lightning` for image generation. Use\n`@cf/black-forest-labs/flux-1-schnell` as the higher-quality image fallback\nwhen budget allows.\n- Notification providers: `RESEND_API_KEY`, `SENDGRID_API_KEY`,\n `POSTMARK_SERVER_TOKEN`, `MAILGUN_API_KEY`, `MAILGUN_DOMAIN`,\n `TWILIO_ACCOUNT_SID`, `TWILIO_AUTH_TOKEN`,\n `TWILIO_MESSAGING_SERVICE_SID`, `WHATSAPP_ACCESS_TOKEN`,\n `WHATSAPP_PHONE_NUMBER_ID`, `EXPO_ACCESS_TOKEN`,\n `TRANSACTIONAL_EMAIL_API_KEY`, `TRANSACTIONAL_EMAIL_FROM`.\n- Payment sandbox providers: `STRIPE_SECRET_KEY`, `STRIPE_WEBHOOK_SECRET`,\n `CUSTOMCARD_PAYMENT_SUCCESS_URL`, `CUSTOMCARD_PAYMENT_CANCEL_URL`,\n `PAYPAL_CLIENT_ID`, `PAYPAL_CLIENT_SECRET`, `PAYPAL_WEBHOOK_ID`,\n `SQUARE_ACCESS_TOKEN`, `SQUARE_LOCATION_ID`,\n `SQUARE_WEBHOOK_SIGNATURE_KEY`, `ADYEN_API_KEY`,\n `ADYEN_MERCHANT_ACCOUNT`, `ADYEN_HMAC_KEY`.\n- Observability providers: `SENTRY_DSN`, `SENTRY_PROJECT_ID`,\n `SENTRY_ENVIRONMENT`, `POSTHOG_PROJECT_API_KEY`, `POSTHOG_HOST`,\n `OTEL_EXPORTER_OTLP_ENDPOINT`, `OTEL_EXPORTER_OTLP_HEADERS`,\n `GRAFANA_OTLP_ENDPOINT`, `GRAFANA_OTLP_INSTANCE_ID`,\n `GRAFANA_OTLP_API_KEY`, `DATADOG_API_KEY`, `DATADOG_SITE`,\n `BETTERSTACK_SOURCE_TOKEN`, `BETTERSTACK_INGESTING_HOST`.\n- Object-store artifact signer and live S3-compatible doctor:\n `CUSTOMCARD_ARTIFACT_PERSISTENCE`, `OBJECT_STORE_URL`,\n `OBJECT_STORE_BUCKET`, `OBJECT_STORE_ACCESS_KEY_ID`,\n `OBJECT_STORE_SECRET_ACCESS_KEY`, `OBJECT_STORE_READ_ACCESS_KEY_ID`,\n `OBJECT_STORE_READ_SECRET_ACCESS_KEY`, `OBJECT_STORE_REGION`,\n `OBJECT_STORE_PUBLIC_BASE_URL`, `OBJECT_STORE_SIGNING_SECRET`,\n `ARTIFACT_SIGNED_URL_TTL_MINUTES`. Bucket-admin setup also uses\n `CLOUDFLARE_ACCOUNT_ID` and a short-lived `CLOUDFLARE_API_TOKEN`.\n- Live vendor adapters: `WALGREENS_VENDOR_MODE`, `CVS_VENDOR_MODE`,\n `FEDEX_VENDOR_MODE`, `WALMART_VENDOR_MODE`, `STAPLES_VENDOR_MODE`,\n `OFFICE_DEPOT_VENDOR_MODE`.\n- Persistence controls: `CUSTOMCARD_API_RUNTIME`, `AUTH_SESSION_SECRET`,\n `CUSTOMCARD_CUSTOMER_SESSION_TOKEN`, `CUSTOMCARD_ADMIN_SESSION_TOKEN`,\n `IDEMPOTENCY_KEY_TTL_HOURS`.\n\nThese keys are documented for deployment readiness only. The current repo state\ndoes not make live provider calls, and vendor modes remain\n`disabled_until_certified` while `REAL_ORDER_KILL_SWITCH=disabled`.\n"}, "b0043d144c700fa08f230e3e11ea93e958872221": {"t": 1785772132.2900212, "n": 12987, "h": "677f93b2adf8d14a", "c": "# CustomCard Infrastructure\n\nThis directory is the deployable service skeleton for the production path.\n\n- `docker-compose.dev.yml` runs app, worker, Postgres, Redis, and MinIO for local development.\n- `docker-compose.droplet.yml` is the cheap single-host deployment shape for a small DigitalOcean droplet.\n- `k8s/app.yaml` is the cloud-native starting point for web and worker deployments.\n- `aws/artifact-store/` is the Terraform contract for a private, encrypted,\n versioned production artifact bucket and prefix-scoped app/worker IAM policy.\n- `migrations/001_initial_schema.sql` defines the durable service data model.\n- `env/.env.example` lists required secrets and kill switches.\n- `../scripts/api-server.mjs` serves `/api/health`, API bootstrap/readiness\n contracts, explicit contract/memory/Postgres runtime modes, and the built web\n app from `dist`.\n- `../src/artifactHandoff.ts` defines the render-packet artifact manifest and\n HMAC-signed URL contract used by the API/schema gates.\n- `../src/artifactStore.ts` writes render-packet artifacts to a temporary local\n filesystem object-store path and an injected S3-compatible client contract,\n then verifies readback without network calls.\n- `../scripts/persistence-doctor.mjs` validates auth-session, idempotency, queue\n job, and audit persistence signals.\n- `../scripts/demo-reset.mjs` validates the reviewer demo reset contract before\n anyone attempts to run seed data against a database.\n- `../scripts/deployment-readiness.mjs` emits the local deployment readiness\n report used by `npm run deployment:doctor`.\n- `../src/capacityPlan.ts` and `../src/capacityPlanData.mjs` define executable\n local-dev, cheap-droplet, cloud-native, and SaaS-scale capacity profiles used\n by `npm run capacity:doctor`.\n\nFor the droplet deployment, provide `POSTGRES_PASSWORD` from a real secret source\nor local deployment `.env` that is never committed. The compose file uses\n`${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD}` so the service fails closed instead\nof starting with an empty database password.\n\nFor Kubernetes, apply the `customcard-migrate` Job successfully before rolling\nthe web and worker Deployments. The deployment manifest keeps the live-order kill\nswitch explicit and uses probes so bad pods fail visibly.\n\nRun the local IaC readiness check before treating the manifests as reviewable:\n\n```sh\nnpm run deployment:doctor\nnpm run capacity:doctor\nnpm run cloud:doctor\nnpm run api:doctor\nnpm run api:doctor:memory\nnpm run api:doctor:postgres\nCUSTOMCARD_POSTGRES_INTEGRATION_DOCTOR=enabled DATABASE_URL=postgres://... npm run api:doctor:postgres:live\nCUSTOMCARD_POSTGRES_API_HTTP_DOCTOR=enabled DATABASE_URL=postgres://... npm run api:doctor:postgres:http\nCUSTOMCARD_ACCOUNT_AUTH_DOCTOR=enabled DATABASE_URL=postgres://... npm run account:doctor:live\nnpm run artifact:doctor\nCUSTOMCARD_S3_ARTIFACT_DOCTOR=enabled OBJECT_STORE_URL=http://127.0.0.1:9000 OBJECT_STORE_BUCKET=customcard-ci-artifacts OBJECT_STORE_ACCESS_KEY_ID=customcard OBJECT_STORE_SECRET_ACCESS_KEY=customcard-dev-only OBJECT_STORE_REGION=us-east-1 OBJECT_STORE_SIGNING_SECRET=test-object-store-signing-secret-32 npm run artifact:doctor:s3:live\nnpm run persistence:doctor\nnpm run demo:doctor\n```\n\nThe report checks the local-dev, cheap-droplet, cloud-native, runtime, and data\nlanes, including cloud-storage checks for the AWS artifact-store module. Passing\nthis check means the committed deployment contracts are internally consistent;\nit does not mean a real droplet, AWS account, or Kubernetes cluster has been\nprovisioned.\n\n`npm run capacity:doctor` verifies the profile contract, tests, admin/API\nsurfaces, documentation, CI wiring, and no-live-traffic posture. The profiles are\nplanning data only; measured production benchmarks, provider spend reports, and\nautoscaler evidence remain separate launch evidence.\n\nThe Kubernetes web deployment probes `/api/health`, and the production Docker\nimage starts `scripts/api-server.mjs` so the same container can serve the static\nweb bundle and the contract-first API endpoints.\n\nThe API runtime defaults to `CUSTOMCARD_API_RUNTIME=contract` for reviewer/static\nserving. `npm run api:doctor:memory` sets test customer/admin session tokens and\nvalidates Bearer auth plus `X-Idempotency-Key` replay without a live database.\n`npm run api:doctor:postgres` injects a fake Postgres pool into the same runtime\npath and validates session lookup, wrong-role blocking, idempotency replay,\nconflict handling, repository-backed relationship-memory inserts,\nrepository-backed render-packet inserts, repository-backed import-preview\ninserts, repository-backed card-project inserts, manual handoff\norder/consent/event inserts, data-request privacy/consent inserts, audit inserts,\nand queue-job inserts without external credentials.\n`CUSTOMCARD_POSTGRES_INTEGRATION_DOCTOR=enabled npm run\napi:doctor:postgres:live` creates an isolated temporary database on the configured\nPostgres server, applies the committed migration, seeds customer/admin sessions,\nverifies all repository-backed customer routes plus admin readiness through the\nreal `pg` runtime, exercises relationship-memory, render-packet, import-preview,\ncard-project, manual handoff, and data-request persistence, and drops the\ntemporary database before exiting.\n`CUSTOMCARD_POSTGRES_API_HTTP_DOCTOR=enabled npm run\napi:doctor:postgres:http` starts `scripts/api-server.mjs` against the same\nisolated migrated database shape and verifies public health/routes,\nadmin/customer Bearer auth, missing/wrong-role auth blocking, missing\nidempotency blocking, all 6 repository-backed customer HTTP mutations,\nidempotency replay/conflict, audit rows, queue jobs, and repository table counts\nbefore shutting the server down and dropping the temporary database.\nCI runs both live Postgres doctors against a Postgres service; deployed\nproduction account auth remains unclaimed.\n\n`CUSTOMCARD_ACCOUNT_AUTH_DOCTOR=enabled npm run account:doctor:live` uses the\nsame isolated-database pattern to verify hosted account identity rows,\nprovider-subject uniqueness, hashed recovery challenges, durable sessions, and\naccount-recovery audit rows. CI runs it against the Postgres service; live hosted\ntoken verification remains unclaimed.\n\n`npm run artifact:doctor` writes the sample render packet package to a\ntemporary local filesystem object-store path and an injected S3-compatible client\ncontract, reads every artifact back, verifies checksums and byte lengths, stores\nboth handoff manifests, and keeps network calls plus real orders disabled.\n`CUSTOMCARD_S3_ARTIFACT_DOCTOR=enabled npm run artifact:doctor:s3:live` writes\nthe same package to a live S3-compatible endpoint such as MinIO using path-style\nSigV4 requests, reads every object back, verifies checksums and byte lengths,\nstores the handoff manifest, reports `cloudWritesVerified: true`, cleans up the\nisolated bucket, and keeps external vendor calls plus real orders disabled. A\nproduction cloud bucket policy/IAM contract is now represented by\n`infra/aws/artifact-store` and checked by `npm run cloud:doctor`; live AWS apply,\nAccess Analyzer review, and account-specific IAM validation remain separate from\nthis repo-local proof.\n\nThe AWS artifact-store module provisions only the artifact storage boundary:\npublic access block, bucket-owner-enforced ownership, versioning, AES256\nserver-side encryption, lifecycle cleanup under `projects/`, an HTTPS-only and\nencrypted-upload bucket policy, and a least-privilege writer policy scoped to\n`projects/*`. Existing app and worker IAM roles can be attached by passing\n`app_role_name` and `worker_role_name`. Runtime outputs name the required\n`OBJECT_STORE_*` values that still need to be mirrored into the platform secret\nor config manager.\n\nThe persistence boundary requires auth-session storage, account identity storage,\nhashed recovery challenges, idempotency replay, queue job envelopes,\nrelationship-memory repository writes, render-packet repository writes,\nrender-packet artifact manifests, signed URL expiry, and append-only audit\nsignals in the migration before production Postgres handlers are claimed.\n\nThe Kubernetes `Secret` in `k8s/app.yaml` is intentionally empty and annotated as\npre-created by a secret manager. Production clusters should source the required\nkeys from the platform secret manager, External Secrets, Sealed Secrets, or an\nequivalent operator-approved mechanism. The `runtime:doctor` check rejects empty\nvalues, known placeholders, and any live-order kill switch value other than\n`disabled`.\n\nSigned artifact handoff requires `OBJECT_STORE_SIGNING_SECRET` to be present and\nat least 32 characters. The committed manifests also keep\n`ARTIFACT_SIGNED_URL_TTL_MINUTES=15` explicit so download links remain\nshort-lived.\n\nReal external ordering stays disabled with `REAL_ORDER_KILL_SWITCH=disabled` until vendor sandbox tests and physical print certification are recorded.\n\n## Provider Adapter Secrets\n\n`env/.env.example` names the credential-gated adapters represented in\n`src/providerCatalog.ts`:\n\n- Google OAuth: `GOOGLE_OAUTH_CLIENT_ID`, `GOOGLE_OAUTH_CLIENT_SECRET`.\n- Microsoft Graph: `MICROSOFT_CLIENT_ID`, `MICROSOFT_CLIENT_SECRET`,\n `MICROSOFT_TENANT_ID`.\n- Hosted identity providers: `CUSTOMCARD_AUTH_CALLBACK_URL`, `AUTH0_DOMAIN`,\n `AUTH0_CLIENT_ID`, `AUTH0_CLIENT_SECRET`, `AUTH0_AUDIENCE`,\n `CLERK_SECRET_KEY`, `CLERK_JWT_KEY`, `CLERK_AUTHORIZED_PARTIES`,\n `SUPABASE_URL`, `SUPABASE_ANON_KEY`, `SUPABASE_SERVICE_ROLE_KEY`,\n `FIREBASE_API_KEY`, `FIREBASE_PROJECT_ID`,\n `FIREBASE_SERVICE_ACCOUNT_JSON`, `COGNITO_DOMAIN`,\n `COGNITO_USER_POOL_ID`, `COGNITO_APP_CLIENT_ID`.\n- Contact import providers: `CARDDAV_BASE_URL`, `CARDDAV_USERNAME`,\n `CARDDAV_APP_PASSWORD`, `CARDDAV_ADDRESSBOOK_PATH`.\n- Text and image AI providers: `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`,\n `AZURE_OPENAI_ENDPOINT`, `AZURE_OPENAI_API_KEY`,\n `AZURE_OPENAI_CHAT_DEPLOYMENT`, `AZURE_OPENAI_IMAGE_DEPLOYMENT`,\n `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_REGION`,\n `BEDROCK_TEXT_MODEL_ID`, `BEDROCK_IMAGE_MODEL_ID`,\n `CLOUDFLARE_ACCOUNT_ID`, `CLOUDFLARE_API_TOKEN`,\n `CLOUDFLARE_WORKERS_AI_TEXT_API_TOKEN`,\n `CLOUDFLARE_WORKERS_AI_IMAGE_API_TOKEN`,\n `CLOUDFLARE_WORKERS_AI_TEXT_MODEL`, `CLOUDFLARE_WORKERS_AI_IMAGE_MODEL`,\n `GOOGLE_GENERATIVE_AI_API_KEY`, `MISTRAL_API_KEY`, `COHERE_API_KEY`,\n `PERPLEXITY_API_KEY`, `XAI_API_KEY`, `TOGETHER_API_KEY`, `GROQ_API_KEY`,\n `DEEPSEEK_API_KEY`, `FIREWORKS_API_KEY`, `STABILITY_API_KEY`,\n `HUGGINGFACE_API_TOKEN`, `DEEPAI_API_KEY`, `REPLICATE_API_TOKEN`, `IDEOGRAM_API_KEY`,\n `LEONARDO_API_KEY`, `FAL_KEY`, `BFL_API_KEY`.\n- Self-hosted model fallback: `SELF_HOSTED_LLM_BASE_URL`,\n `SELF_HOSTED_LLM_API_KEY`.\n\nCloudflare Workers AI can use one shared `CLOUDFLARE_API_TOKEN` for both\nchat and image generation, or separate lane-specific tokens through\n`CLOUDFLARE_WORKERS_AI_TEXT_API_TOKEN` and\n`CLOUDFLARE_WORKERS_AI_IMAGE_API_TOKEN`. The recommended low-cost defaults are\n`@cf/meta/llama-3.2-3b-instruct` for prompt/chat work and\n`@cf/bytedance/stable-diffusion-xl-lightning` for image generation. Use\n`@cf/black-forest-labs/flux-1-schnell` as the higher-quality image fallback\nwhen budget allows.\n- Notification providers: `RESEND_API_KEY`, `SENDGRID_API_KEY`,\n `POSTMARK_SERVER_TOKEN`, `MAILGUN_API_KEY`, `MAILGUN_DOMAIN`,\n `TWILIO_ACCOUNT_SID`, `TWILIO_AUTH_TOKEN`,\n `TWILIO_MESSAGING_SERVICE_SID`, `WHATSAPP_ACCESS_TOKEN`,\n `WHATSAPP_PHONE_NUMBER_ID`, `EXPO_ACCESS_TOKEN`,\n `TRANSACTIONAL_EMAIL_API_KEY`, `TRANSACTIONAL_EMAIL_FROM`.\n- Payment sandbox providers: `STRIPE_SECRET_KEY`, `STRIPE_WEBHOOK_SECRET`,\n `CUSTOMCARD_PAYMENT_SUCCESS_URL`, `CUSTOMCARD_PAYMENT_CANCEL_URL`,\n `PAYPAL_CLIENT_ID`, `PAYPAL_CLIENT_SECRET`, `PAYPAL_WEBHOOK_ID`,\n `SQUARE_ACCESS_TOKEN`, `SQUARE_LOCATION_ID`,\n `SQUARE_WEBHOOK_SIGNATURE_KEY`, `ADYEN_API_KEY`,\n `ADYEN_MERCHANT_ACCOUNT`, `ADYEN_HMAC_KEY`.\n- Observability providers: `SENTRY_DSN`, `SENTRY_PROJECT_ID`,\n `SENTRY_ENVIRONMENT`, `POSTHOG_PROJECT_API_KEY`, `POSTHOG_HOST`,\n `OTEL_EXPORTER_OTLP_ENDPOINT`, `OTEL_EXPORTER_OTLP_HEADERS`,\n `GRAFANA_OTLP_ENDPOINT`, `GRAFANA_OTLP_INSTANCE_ID`,\n `GRAFANA_OTLP_API_KEY`, `DATADOG_API_KEY`, `DATADOG_SITE`,\n `BETTERSTACK_SOURCE_TOKEN`, `BETTERSTACK_INGESTING_HOST`.\n- Object-store artifact signer and live S3-compatible doctor:\n `OBJECT_STORE_SIGNING_SECRET`, `OBJECT_STORE_ACCESS_KEY_ID`,\n `OBJECT_STORE_SECRET_ACCESS_KEY`, `OBJECT_STORE_REGION`.\n- Live vendor adapters: `WALGREENS_VENDOR_MODE`, `CVS_VENDOR_MODE`,\n `FEDEX_VENDOR_MODE`, `WALMART_VENDOR_MODE`, `STAPLES_VENDOR_MODE`,\n `OFFICE_DEPOT_VENDOR_MODE`.\n- Persistence controls: `CUSTOMCARD_API_RUNTIME`, `AUTH_SESSION_SECRET`,\n `CUSTOMCARD_CUSTOMER_SESSION_TOKEN`, `CUSTOMCARD_ADMIN_SESSION_TOKEN`,\n `IDEMPOTENCY_KEY_TTL_HOURS`.\n\nThese keys are documented for deployment readiness only. The current repo state\ndoes not make live provider calls, and vendor modes remain\n`disabled_until_certified` while `REAL_ORDER_KILL_SWITCH=disabled`.\n"}, "c707ac21928d4b3ff2e78a59457d0f58d69390be": {"t": 1785772133.5205417, "n": 12412, "h": "a78b985dc279bd89", "c": "# CustomCard Infrastructure\n\nThis directory is the deployable service skeleton for the production path.\n\n- `docker-compose.dev.yml` runs app, worker, Postgres, Redis, and MinIO for local development.\n- `docker-compose.droplet.yml` is the cheap single-host deployment shape for a small DigitalOcean droplet.\n- `k8s/app.yaml` is the cloud-native starting point for web and worker deployments.\n- `aws/artifact-store/` is the Terraform contract for a private, encrypted,\n versioned production artifact bucket and prefix-scoped app/worker IAM policy.\n- `migrations/001_initial_schema.sql` defines the durable service data model.\n- `env/.env.example` lists required secrets and kill switches.\n- `../scripts/api-server.mjs` serves `/api/health`, API bootstrap/readiness\n contracts, explicit contract/memory/Postgres runtime modes, and the built web\n app from `dist`.\n- `../src/artifactHandoff.ts` defines the render-packet artifact manifest and\n HMAC-signed URL contract used by the API/schema gates.\n- `../src/artifactStore.ts` writes render-packet artifacts to a temporary local\n filesystem object-store path and an injected S3-compatible client contract,\n then verifies readback without network calls.\n- `../scripts/persistence-doctor.mjs` validates auth-session, idempotency, queue\n job, and audit persistence signals.\n- `../scripts/demo-reset.mjs` validates the reviewer demo reset contract before\n anyone attempts to run seed data against a database.\n- `../scripts/deployment-readiness.mjs` emits the local deployment readiness\n report used by `npm run deployment:doctor`.\n- `../src/capacityPlan.ts` and `../src/capacityPlanData.mjs` define executable\n local-dev, cheap-droplet, cloud-native, and SaaS-scale capacity profiles used\n by `npm run capacity:doctor`.\n\nFor the droplet deployment, provide `POSTGRES_PASSWORD` from a real secret source\nor local deployment `.env` that is never committed. The compose file uses\n`${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD}` so the service fails closed instead\nof starting with an empty database password.\n\nFor Kubernetes, apply the `customcard-migrate` Job successfully before rolling\nthe web and worker Deployments. The deployment manifest keeps the live-order kill\nswitch explicit and uses probes so bad pods fail visibly.\n\nRun the local IaC readiness check before treating the manifests as reviewable:\n\n```sh\nnpm run deployment:doctor\nnpm run capacity:doctor\nnpm run cloud:doctor\nnpm run api:doctor\nnpm run api:doctor:memory\nnpm run api:doctor:postgres\nCUSTOMCARD_POSTGRES_INTEGRATION_DOCTOR=enabled DATABASE_URL=postgres://... npm run api:doctor:postgres:live\nCUSTOMCARD_POSTGRES_API_HTTP_DOCTOR=enabled DATABASE_URL=postgres://... npm run api:doctor:postgres:http\nCUSTOMCARD_ACCOUNT_AUTH_DOCTOR=enabled DATABASE_URL=postgres://... npm run account:doctor:live\nnpm run artifact:doctor\nCUSTOMCARD_S3_ARTIFACT_DOCTOR=enabled OBJECT_STORE_URL=http://127.0.0.1:9000 OBJECT_STORE_BUCKET=customcard-ci-artifacts OBJECT_STORE_ACCESS_KEY_ID=customcard OBJECT_STORE_SECRET_ACCESS_KEY=customcard-dev-only OBJECT_STORE_REGION=us-east-1 OBJECT_STORE_SIGNING_SECRET=test-object-store-signing-secret-32 npm run artifact:doctor:s3:live\nnpm run persistence:doctor\nnpm run demo:doctor\n```\n\nThe report checks the local-dev, cheap-droplet, cloud-native, runtime, and data\nlanes, including cloud-storage checks for the AWS artifact-store module. Passing\nthis check means the committed deployment contracts are internally consistent;\nit does not mean a real droplet, AWS account, or Kubernetes cluster has been\nprovisioned.\n\n`npm run capacity:doctor` verifies the profile contract, tests, admin/API\nsurfaces, documentation, CI wiring, and no-live-traffic posture. The profiles are\nplanning data only; measured production benchmarks, provider spend reports, and\nautoscaler evidence remain separate launch evidence.\n\nThe Kubernetes web deployment probes `/api/health`, and the production Docker\nimage starts `scripts/api-server.mjs` so the same container can serve the static\nweb bundle and the contract-first API endpoints.\n\nThe API runtime defaults to `CUSTOMCARD_API_RUNTIME=contract` for reviewer/static\nserving. `npm run api:doctor:memory` sets test customer/admin session tokens and\nvalidates Bearer auth plus `X-Idempotency-Key` replay without a live database.\n`npm run api:doctor:postgres` injects a fake Postgres pool into the same runtime\npath and validates session lookup, wrong-role blocking, idempotency replay,\nconflict handling, repository-backed relationship-memory inserts,\nrepository-backed render-packet inserts, repository-backed import-preview\ninserts, repository-backed card-project inserts, manual handoff\norder/consent/event inserts, data-request privacy/consent inserts, audit inserts,\nand queue-job inserts without external credentials.\n`CUSTOMCARD_POSTGRES_INTEGRATION_DOCTOR=enabled npm run\napi:doctor:postgres:live` creates an isolated temporary database on the configured\nPostgres server, applies the committed migration, seeds customer/admin sessions,\nverifies all repository-backed customer routes plus admin readiness through the\nreal `pg` runtime, exercises relationship-memory, render-packet, import-preview,\ncard-project, manual handoff, and data-request persistence, and drops the\ntemporary database before exiting.\n`CUSTOMCARD_POSTGRES_API_HTTP_DOCTOR=enabled npm run\napi:doctor:postgres:http` starts `scripts/api-server.mjs` against the same\nisolated migrated database shape and verifies public health/routes,\nadmin/customer Bearer auth, missing/wrong-role auth blocking, missing\nidempotency blocking, all 6 repository-backed customer HTTP mutations,\nidempotency replay/conflict, audit rows, queue jobs, and repository table counts\nbefore shutting the server down and dropping the temporary database.\nCI runs both live Postgres doctors against a Postgres service; deployed\nproduction account auth remains unclaimed.\n\n`CUSTOMCARD_ACCOUNT_AUTH_DOCTOR=enabled npm run account:doctor:live` uses the\nsame isolated-database pattern to verify hosted account identity rows,\nprovider-subject uniqueness, hashed recovery challenges, durable sessions, and\naccount-recovery audit rows. CI runs it against the Postgres service; live hosted\ntoken verification remains unclaimed.\n\n`npm run artifact:doctor` writes the sample render packet package to a\ntemporary local filesystem object-store path and an injected S3-compatible client\ncontract, reads every artifact back, verifies checksums and byte lengths, stores\nboth handoff manifests, and keeps network calls plus real orders disabled.\n`CUSTOMCARD_S3_ARTIFACT_DOCTOR=enabled npm run artifact:doctor:s3:live` writes\nthe same package to a live S3-compatible endpoint such as MinIO using path-style\nSigV4 requests, reads every object back, verifies checksums and byte lengths,\nstores the handoff manifest, reports `cloudWritesVerified: true`, cleans up the\nisolated bucket, and keeps external vendor calls plus real orders disabled. A\nproduction cloud bucket policy/IAM contract is now represented by\n`infra/aws/artifact-store` and checked by `npm run cloud:doctor`; live AWS apply,\nAccess Analyzer review, and account-specific IAM validation remain separate from\nthis repo-local proof.\n\nThe AWS artifact-store module provisions only the artifact storage boundary:\npublic access block, bucket-owner-enforced ownership, versioning, AES256\nserver-side encryption, lifecycle cleanup under `projects/`, an HTTPS-only and\nencrypted-upload bucket policy, and a least-privilege writer policy scoped to\n`projects/*`. Existing app and worker IAM roles can be attached by passing\n`app_role_name` and `worker_role_name`. Runtime outputs name the required\n`OBJECT_STORE_*` values that still need to be mirrored into the platform secret\nor config manager.\n\nThe persistence boundary requires auth-session storage, account identity storage,\nhashed recovery challenges, idempotency replay, queue job envelopes,\nrelationship-memory repository writes, render-packet repository writes,\nrender-packet artifact manifests, signed URL expiry, and append-only audit\nsignals in the migration before production Postgres handlers are claimed.\n\nThe Kubernetes `Secret` in `k8s/app.yaml` is intentionally empty and annotated as\npre-created by a secret manager. Production clusters should source the required\nkeys from the platform secret manager, External Secrets, Sealed Secrets, or an\nequivalent operator-approved mechanism. The `runtime:doctor` check rejects empty\nvalues, known placeholders, and any live-order kill switch value other than\n`disabled`.\n\nSigned artifact handoff requires `OBJECT_STORE_SIGNING_SECRET` to be present and\nat least 32 characters. The committed manifests also keep\n`ARTIFACT_SIGNED_URL_TTL_MINUTES=15` explicit so download links remain\nshort-lived.\n\nReal external ordering stays disabled with `REAL_ORDER_KILL_SWITCH=disabled` until vendor sandbox tests and physical print certification are recorded.\n\n## Provider Adapter Secrets\n\n`env/.env.example` names the credential-gated adapters represented in\n`src/providerCatalog.ts`:\n\n- Google OAuth: `GOOGLE_OAUTH_CLIENT_ID`, `GOOGLE_OAUTH_CLIENT_SECRET`.\n- Microsoft Graph: `MICROSOFT_CLIENT_ID`, `MICROSOFT_CLIENT_SECRET`,\n `MICROSOFT_TENANT_ID`.\n- Hosted identity providers: `CUSTOMCARD_AUTH_CALLBACK_URL`, `AUTH0_DOMAIN`,\n `AUTH0_CLIENT_ID`, `AUTH0_CLIENT_SECRET`, `AUTH0_AUDIENCE`,\n `CLERK_SECRET_KEY`, `CLERK_JWT_KEY`, `CLERK_AUTHORIZED_PARTIES`,\n `SUPABASE_URL`, `SUPABASE_ANON_KEY`, `SUPABASE_SERVICE_ROLE_KEY`,\n `FIREBASE_API_KEY`, `FIREBASE_PROJECT_ID`,\n `FIREBASE_SERVICE_ACCOUNT_JSON`, `COGNITO_DOMAIN`,\n `COGNITO_USER_POOL_ID`, `COGNITO_APP_CLIENT_ID`.\n- Contact import providers: `CARDDAV_BASE_URL`, `CARDDAV_USERNAME`,\n `CARDDAV_APP_PASSWORD`, `CARDDAV_ADDRESSBOOK_PATH`.\n- Text and image AI providers: `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`,\n `AZURE_OPENAI_ENDPOINT`, `AZURE_OPENAI_API_KEY`,\n `AZURE_OPENAI_CHAT_DEPLOYMENT`, `AZURE_OPENAI_IMAGE_DEPLOYMENT`,\n `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_REGION`,\n `BEDROCK_TEXT_MODEL_ID`, `BEDROCK_IMAGE_MODEL_ID`,\n `CLOUDFLARE_ACCOUNT_ID`, `CLOUDFLARE_API_TOKEN`,\n `CLOUDFLARE_WORKERS_AI_TEXT_MODEL`, `CLOUDFLARE_WORKERS_AI_IMAGE_MODEL`,\n `GOOGLE_GENERATIVE_AI_API_KEY`, `MISTRAL_API_KEY`, `COHERE_API_KEY`,\n `PERPLEXITY_API_KEY`, `XAI_API_KEY`, `TOGETHER_API_KEY`, `GROQ_API_KEY`,\n `DEEPSEEK_API_KEY`, `FIREWORKS_API_KEY`, `STABILITY_API_KEY`,\n `HUGGINGFACE_API_TOKEN`, `DEEPAI_API_KEY`, `REPLICATE_API_TOKEN`, `IDEOGRAM_API_KEY`,\n `LEONARDO_API_KEY`, `FAL_KEY`, `BFL_API_KEY`.\n- Self-hosted model fallback: `SELF_HOSTED_LLM_BASE_URL`,\n `SELF_HOSTED_LLM_API_KEY`.\n- Notification providers: `RESEND_API_KEY`, `SENDGRID_API_KEY`,\n `POSTMARK_SERVER_TOKEN`, `MAILGUN_API_KEY`, `MAILGUN_DOMAIN`,\n `TWILIO_ACCOUNT_SID`, `TWILIO_AUTH_TOKEN`,\n `TWILIO_MESSAGING_SERVICE_SID`, `WHATSAPP_ACCESS_TOKEN`,\n `WHATSAPP_PHONE_NUMBER_ID`, `EXPO_ACCESS_TOKEN`,\n `TRANSACTIONAL_EMAIL_API_KEY`, `TRANSACTIONAL_EMAIL_FROM`.\n- Payment sandbox providers: `STRIPE_SECRET_KEY`, `STRIPE_WEBHOOK_SECRET`,\n `CUSTOMCARD_PAYMENT_SUCCESS_URL`, `CUSTOMCARD_PAYMENT_CANCEL_URL`,\n `PAYPAL_CLIENT_ID`, `PAYPAL_CLIENT_SECRET`, `PAYPAL_WEBHOOK_ID`,\n `SQUARE_ACCESS_TOKEN`, `SQUARE_LOCATION_ID`,\n `SQUARE_WEBHOOK_SIGNATURE_KEY`, `ADYEN_API_KEY`,\n `ADYEN_MERCHANT_ACCOUNT`, `ADYEN_HMAC_KEY`.\n- Observability providers: `SENTRY_DSN`, `SENTRY_PROJECT_ID`,\n `SENTRY_ENVIRONMENT`, `POSTHOG_PROJECT_API_KEY`, `POSTHOG_HOST`,\n `OTEL_EXPORTER_OTLP_ENDPOINT`, `OTEL_EXPORTER_OTLP_HEADERS`,\n `GRAFANA_OTLP_ENDPOINT`, `GRAFANA_OTLP_INSTANCE_ID`,\n `GRAFANA_OTLP_API_KEY`, `DATADOG_API_KEY`, `DATADOG_SITE`,\n `BETTERSTACK_SOURCE_TOKEN`, `BETTERSTACK_INGESTING_HOST`.\n- Object-store artifact signer and live S3-compatible doctor:\n `OBJECT_STORE_SIGNING_SECRET`, `OBJECT_STORE_ACCESS_KEY_ID`,\n `OBJECT_STORE_SECRET_ACCESS_KEY`, `OBJECT_STORE_REGION`.\n- Live vendor adapters: `WALGREENS_VENDOR_MODE`, `CVS_VENDOR_MODE`,\n `FEDEX_VENDOR_MODE`, `WALMART_VENDOR_MODE`, `STAPLES_VENDOR_MODE`,\n `OFFICE_DEPOT_VENDOR_MODE`.\n- Persistence controls: `CUSTOMCARD_API_RUNTIME`, `AUTH_SESSION_SECRET`,\n `CUSTOMCARD_CUSTOMER_SESSION_TOKEN`, `CUSTOMCARD_ADMIN_SESSION_TOKEN`,\n `IDEMPOTENCY_KEY_TTL_HOURS`.\n\nThese keys are documented for deployment readiness only. The current repo state\ndoes not make live provider calls, and vendor modes remain\n`disabled_until_certified` while `REAL_ORDER_KILL_SWITCH=disabled`.\n"}, "1fe053ee9595efe3bfbe4577c59ef1c98da9a876": {"t": 1785772134.8321083, "n": 12268, "h": "96a384db2be00c0b", "c": "# CustomCard Infrastructure\n\nThis directory is the deployable service skeleton for the production path.\n\n- `docker-compose.dev.yml` runs app, worker, Postgres, Redis, and MinIO for local development.\n- `docker-compose.droplet.yml` is the cheap single-host deployment shape for a small DigitalOcean droplet.\n- `k8s/app.yaml` is the cloud-native starting point for web and worker deployments.\n- `aws/artifact-store/` is the Terraform contract for a private, encrypted,\n versioned production artifact bucket and prefix-scoped app/worker IAM policy.\n- `migrations/001_initial_schema.sql` defines the durable service data model.\n- `env/.env.example` lists required secrets and kill switches.\n- `../scripts/api-server.mjs` serves `/api/health`, API bootstrap/readiness\n contracts, explicit contract/memory/Postgres runtime modes, and the built web\n app from `dist`.\n- `../src/artifactHandoff.ts` defines the render-packet artifact manifest and\n HMAC-signed URL contract used by the API/schema gates.\n- `../src/artifactStore.ts` writes render-packet artifacts to a temporary local\n filesystem object-store path and an injected S3-compatible client contract,\n then verifies readback without network calls.\n- `../scripts/persistence-doctor.mjs` validates auth-session, idempotency, queue\n job, and audit persistence signals.\n- `../scripts/demo-reset.mjs` validates the reviewer demo reset contract before\n anyone attempts to run seed data against a database.\n- `../scripts/deployment-readiness.mjs` emits the local deployment readiness\n report used by `npm run deployment:doctor`.\n- `../src/capacityPlan.ts` and `../src/capacityPlanData.mjs` define executable\n local-dev, cheap-droplet, cloud-native, and SaaS-scale capacity profiles used\n by `npm run capacity:doctor`.\n\nFor the droplet deployment, provide `POSTGRES_PASSWORD` from a real secret source\nor local deployment `.env` that is never committed. The compose file uses\n`${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD}` so the service fails closed instead\nof starting with an empty database password.\n\nFor Kubernetes, apply the `customcard-migrate` Job successfully before rolling\nthe web and worker Deployments. The deployment manifest keeps the live-order kill\nswitch explicit and uses probes so bad pods fail visibly.\n\nRun the local IaC readiness check before treating the manifests as reviewable:\n\n```sh\nnpm run deployment:doctor\nnpm run capacity:doctor\nnpm run cloud:doctor\nnpm run api:doctor\nnpm run api:doctor:memory\nnpm run api:doctor:postgres\nCUSTOMCARD_POSTGRES_INTEGRATION_DOCTOR=enabled DATABASE_URL=postgres://... npm run api:doctor:postgres:live\nCUSTOMCARD_POSTGRES_API_HTTP_DOCTOR=enabled DATABASE_URL=postgres://... npm run api:doctor:postgres:http\nCUSTOMCARD_ACCOUNT_AUTH_DOCTOR=enabled DATABASE_URL=postgres://... npm run account:doctor:live\nnpm run artifact:doctor\nCUSTOMCARD_S3_ARTIFACT_DOCTOR=enabled OBJECT_STORE_URL=http://127.0.0.1:9000 OBJECT_STORE_BUCKET=customcard-ci-artifacts OBJECT_STORE_ACCESS_KEY_ID=customcard OBJECT_STORE_SECRET_ACCESS_KEY=customcard-dev-only OBJECT_STORE_REGION=us-east-1 OBJECT_STORE_SIGNING_SECRET=test-object-store-signing-secret-32 npm run artifact:doctor:s3:live\nnpm run persistence:doctor\nnpm run demo:doctor\n```\n\nThe report checks the local-dev, cheap-droplet, cloud-native, runtime, and data\nlanes, including cloud-storage checks for the AWS artifact-store module. Passing\nthis check means the committed deployment contracts are internally consistent;\nit does not mean a real droplet, AWS account, or Kubernetes cluster has been\nprovisioned.\n\n`npm run capacity:doctor` verifies the profile contract, tests, admin/API\nsurfaces, documentation, CI wiring, and no-live-traffic posture. The profiles are\nplanning data only; measured production benchmarks, provider spend reports, and\nautoscaler evidence remain separate launch evidence.\n\nThe Kubernetes web deployment probes `/api/health`, and the production Docker\nimage starts `scripts/api-server.mjs` so the same container can serve the static\nweb bundle and the contract-first API endpoints.\n\nThe API runtime defaults to `CUSTOMCARD_API_RUNTIME=contract` for reviewer/static\nserving. `npm run api:doctor:memory` sets test customer/admin session tokens and\nvalidates Bearer auth plus `X-Idempotency-Key` replay without a live database.\n`npm run api:doctor:postgres` injects a fake Postgres pool into the same runtime\npath and validates session lookup, wrong-role blocking, idempotency replay,\nconflict handling, repository-backed relationship-memory inserts,\nrepository-backed render-packet inserts, repository-backed import-preview\ninserts, repository-backed card-project inserts, manual handoff\norder/consent/event inserts, data-request privacy/consent inserts, audit inserts,\nand queue-job inserts without external credentials.\n`CUSTOMCARD_POSTGRES_INTEGRATION_DOCTOR=enabled npm run\napi:doctor:postgres:live` creates an isolated temporary database on the configured\nPostgres server, applies the committed migration, seeds customer/admin sessions,\nverifies all repository-backed customer routes plus admin readiness through the\nreal `pg` runtime, exercises relationship-memory, render-packet, import-preview,\ncard-project, manual handoff, and data-request persistence, and drops the\ntemporary database before exiting.\n`CUSTOMCARD_POSTGRES_API_HTTP_DOCTOR=enabled npm run\napi:doctor:postgres:http` starts `scripts/api-server.mjs` against the same\nisolated migrated database shape and verifies public health/routes,\nadmin/customer Bearer auth, missing/wrong-role auth blocking, missing\nidempotency blocking, all 6 repository-backed customer HTTP mutations,\nidempotency replay/conflict, audit rows, queue jobs, and repository table counts\nbefore shutting the server down and dropping the temporary database.\nCI runs both live Postgres doctors against a Postgres service; deployed\nproduction account auth remains unclaimed.\n\n`CUSTOMCARD_ACCOUNT_AUTH_DOCTOR=enabled npm run account:doctor:live` uses the\nsame isolated-database pattern to verify hosted account identity rows,\nprovider-subject uniqueness, hashed recovery challenges, durable sessions, and\naccount-recovery audit rows. CI runs it against the Postgres service; live hosted\ntoken verification remains unclaimed.\n\n`npm run artifact:doctor` writes the sample render packet package to a\ntemporary local filesystem object-store path and an injected S3-compatible client\ncontract, reads every artifact back, verifies checksums and byte lengths, stores\nboth handoff manifests, and keeps network calls plus real orders disabled.\n`CUSTOMCARD_S3_ARTIFACT_DOCTOR=enabled npm run artifact:doctor:s3:live` writes\nthe same package to a live S3-compatible endpoint such as MinIO using path-style\nSigV4 requests, reads every object back, verifies checksums and byte lengths,\nstores the handoff manifest, reports `cloudWritesVerified: true`, cleans up the\nisolated bucket, and keeps external vendor calls plus real orders disabled. A\nproduction cloud bucket policy/IAM contract is now represented by\n`infra/aws/artifact-store` and checked by `npm run cloud:doctor`; live AWS apply,\nAccess Analyzer review, and account-specific IAM validation remain separate from\nthis repo-local proof.\n\nThe AWS artifact-store module provisions only the artifact storage boundary:\npublic access block, bucket-owner-enforced ownership, versioning, AES256\nserver-side encryption, lifecycle cleanup under `projects/`, an HTTPS-only and\nencrypted-upload bucket policy, and a least-privilege writer policy scoped to\n`projects/*`. Existing app and worker IAM roles can be attached by passing\n`app_role_name` and `worker_role_name`. Runtime outputs name the required\n`OBJECT_STORE_*` values that still need to be mirrored into the platform secret\nor config manager.\n\nThe persistence boundary requires auth-session storage, account identity storage,\nhashed recovery challenges, idempotency replay, queue job envelopes,\nrelationship-memory repository writes, render-packet repository writes,\nrender-packet artifact manifests, signed URL expiry, and append-only audit\nsignals in the migration before production Postgres handlers are claimed.\n\nThe Kubernetes `Secret` in `k8s/app.yaml` is intentionally empty and annotated as\npre-created by a secret manager. Production clusters should source the required\nkeys from the platform secret manager, External Secrets, Sealed Secrets, or an\nequivalent operator-approved mechanism. The `runtime:doctor` check rejects empty\nvalues, known placeholders, and any live-order kill switch value other than\n`disabled`.\n\nSigned artifact handoff requires `OBJECT_STORE_SIGNING_SECRET` to be present and\nat least 32 characters. The committed manifests also keep\n`ARTIFACT_SIGNED_URL_TTL_MINUTES=15` explicit so download links remain\nshort-lived.\n\nReal external ordering stays disabled with `REAL_ORDER_KILL_SWITCH=disabled` until vendor sandbox tests and physical print certification are recorded.\n\n## Provider Adapter Secrets\n\n`env/.env.example` names the credential-gated adapters represented in\n`src/providerCatalog.ts`:\n\n- Google OAuth: `GOOGLE_OAUTH_CLIENT_ID`, `GOOGLE_OAUTH_CLIENT_SECRET`.\n- Microsoft Graph: `MICROSOFT_CLIENT_ID`, `MICROSOFT_CLIENT_SECRET`,\n `MICROSOFT_TENANT_ID`.\n- Hosted identity providers: `CUSTOMCARD_AUTH_CALLBACK_URL`, `AUTH0_DOMAIN`,\n `AUTH0_CLIENT_ID`, `AUTH0_CLIENT_SECRET`, `AUTH0_AUDIENCE`,\n `CLERK_SECRET_KEY`, `CLERK_JWT_KEY`, `CLERK_AUTHORIZED_PARTIES`,\n `SUPABASE_URL`, `SUPABASE_ANON_KEY`, `SUPABASE_SERVICE_ROLE_KEY`,\n `FIREBASE_API_KEY`, `FIREBASE_PROJECT_ID`,\n `FIREBASE_SERVICE_ACCOUNT_JSON`, `COGNITO_DOMAIN`,\n `COGNITO_USER_POOL_ID`, `COGNITO_APP_CLIENT_ID`.\n- Contact import providers: `CARDDAV_BASE_URL`, `CARDDAV_USERNAME`,\n `CARDDAV_APP_PASSWORD`, `CARDDAV_ADDRESSBOOK_PATH`.\n- Text and image AI providers: `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`,\n `AZURE_OPENAI_ENDPOINT`, `AZURE_OPENAI_API_KEY`,\n `AZURE_OPENAI_CHAT_DEPLOYMENT`, `AZURE_OPENAI_IMAGE_DEPLOYMENT`,\n `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_REGION`,\n `BEDROCK_TEXT_MODEL_ID`, `BEDROCK_IMAGE_MODEL_ID`,\n `GOOGLE_GENERATIVE_AI_API_KEY`, `MISTRAL_API_KEY`, `COHERE_API_KEY`,\n `PERPLEXITY_API_KEY`, `XAI_API_KEY`, `TOGETHER_API_KEY`, `GROQ_API_KEY`,\n `DEEPSEEK_API_KEY`, `FIREWORKS_API_KEY`, `STABILITY_API_KEY`,\n `HUGGINGFACE_API_TOKEN`, `REPLICATE_API_TOKEN`, `IDEOGRAM_API_KEY`,\n `LEONARDO_API_KEY`, `FAL_KEY`, `BFL_API_KEY`.\n- Self-hosted model fallback: `SELF_HOSTED_LLM_BASE_URL`,\n `SELF_HOSTED_LLM_API_KEY`.\n- Notification providers: `RESEND_API_KEY`, `SENDGRID_API_KEY`,\n `POSTMARK_SERVER_TOKEN`, `MAILGUN_API_KEY`, `MAILGUN_DOMAIN`,\n `TWILIO_ACCOUNT_SID`, `TWILIO_AUTH_TOKEN`,\n `TWILIO_MESSAGING_SERVICE_SID`, `WHATSAPP_ACCESS_TOKEN`,\n `WHATSAPP_PHONE_NUMBER_ID`, `EXPO_ACCESS_TOKEN`,\n `TRANSACTIONAL_EMAIL_API_KEY`, `TRANSACTIONAL_EMAIL_FROM`.\n- Payment sandbox providers: `STRIPE_SECRET_KEY`, `STRIPE_WEBHOOK_SECRET`,\n `CUSTOMCARD_PAYMENT_SUCCESS_URL`, `CUSTOMCARD_PAYMENT_CANCEL_URL`,\n `PAYPAL_CLIENT_ID`, `PAYPAL_CLIENT_SECRET`, `PAYPAL_WEBHOOK_ID`,\n `SQUARE_ACCESS_TOKEN`, `SQUARE_LOCATION_ID`,\n `SQUARE_WEBHOOK_SIGNATURE_KEY`, `ADYEN_API_KEY`,\n `ADYEN_MERCHANT_ACCOUNT`, `ADYEN_HMAC_KEY`.\n- Observability providers: `SENTRY_DSN`, `SENTRY_PROJECT_ID`,\n `SENTRY_ENVIRONMENT`, `POSTHOG_PROJECT_API_KEY`, `POSTHOG_HOST`,\n `OTEL_EXPORTER_OTLP_ENDPOINT`, `OTEL_EXPORTER_OTLP_HEADERS`,\n `GRAFANA_OTLP_ENDPOINT`, `GRAFANA_OTLP_INSTANCE_ID`,\n `GRAFANA_OTLP_API_KEY`, `DATADOG_API_KEY`, `DATADOG_SITE`,\n `BETTERSTACK_SOURCE_TOKEN`, `BETTERSTACK_INGESTING_HOST`.\n- Object-store artifact signer and live S3-compatible doctor:\n `OBJECT_STORE_SIGNING_SECRET`, `OBJECT_STORE_ACCESS_KEY_ID`,\n `OBJECT_STORE_SECRET_ACCESS_KEY`, `OBJECT_STORE_REGION`.\n- Live vendor adapters: `WALGREENS_VENDOR_MODE`, `CVS_VENDOR_MODE`,\n `FEDEX_VENDOR_MODE`, `WALMART_VENDOR_MODE`, `STAPLES_VENDOR_MODE`,\n `OFFICE_DEPOT_VENDOR_MODE`.\n- Persistence controls: `CUSTOMCARD_API_RUNTIME`, `AUTH_SESSION_SECRET`,\n `CUSTOMCARD_CUSTOMER_SESSION_TOKEN`, `CUSTOMCARD_ADMIN_SESSION_TOKEN`,\n `IDEMPOTENCY_KEY_TTL_HOURS`.\n\nThese keys are documented for deployment readiness only. The current repo state\ndoes not make live provider calls, and vendor modes remain\n`disabled_until_certified` while `REAL_ORDER_KILL_SWITCH=disabled`.\n"}, "1f229c11e7863cf29d4ab03b54694d72cc201c4b": {"t": 1785772136.0095747, "n": 11763, "h": "a6696a6e46d940cb", "c": "# CustomCard Infrastructure\n\nThis directory is the deployable service skeleton for the production path.\n\n- `docker-compose.dev.yml` runs app, worker, Postgres, Redis, and MinIO for local development.\n- `docker-compose.droplet.yml` is the cheap single-host deployment shape for a small DigitalOcean droplet.\n- `k8s/app.yaml` is the cloud-native starting point for web and worker deployments.\n- `aws/artifact-store/` is the Terraform contract for a private, encrypted,\n versioned production artifact bucket and prefix-scoped app/worker IAM policy.\n- `migrations/001_initial_schema.sql` defines the durable service data model.\n- `env/.env.example` lists required secrets and kill switches.\n- `../scripts/api-server.mjs` serves `/api/health`, API bootstrap/readiness\n contracts, explicit contract/memory/Postgres runtime modes, and the built web\n app from `dist`.\n- `../src/artifactHandoff.ts` defines the render-packet artifact manifest and\n HMAC-signed URL contract used by the API/schema gates.\n- `../src/artifactStore.ts` writes render-packet artifacts to a temporary local\n filesystem object-store path and an injected S3-compatible client contract,\n then verifies readback without network calls.\n- `../scripts/persistence-doctor.mjs` validates auth-session, idempotency, queue\n job, and audit persistence signals.\n- `../scripts/demo-reset.mjs` validates the reviewer demo reset contract before\n anyone attempts to run seed data against a database.\n- `../scripts/deployment-readiness.mjs` emits the local deployment readiness\n report used by `npm run deployment:doctor`.\n\nFor the droplet deployment, provide `POSTGRES_PASSWORD` from a real secret source\nor local deployment `.env` that is never committed. The compose file uses\n`${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD}` so the service fails closed instead\nof starting with an empty database password.\n\nFor Kubernetes, apply the `customcard-migrate` Job successfully before rolling\nthe web and worker Deployments. The deployment manifest keeps the live-order kill\nswitch explicit and uses probes so bad pods fail visibly.\n\nRun the local IaC readiness check before treating the manifests as reviewable:\n\n```sh\nnpm run deployment:doctor\nnpm run cloud:doctor\nnpm run api:doctor\nnpm run api:doctor:memory\nnpm run api:doctor:postgres\nCUSTOMCARD_POSTGRES_INTEGRATION_DOCTOR=enabled DATABASE_URL=postgres://... npm run api:doctor:postgres:live\nCUSTOMCARD_POSTGRES_API_HTTP_DOCTOR=enabled DATABASE_URL=postgres://... npm run api:doctor:postgres:http\nCUSTOMCARD_ACCOUNT_AUTH_DOCTOR=enabled DATABASE_URL=postgres://... npm run account:doctor:live\nnpm run artifact:doctor\nCUSTOMCARD_S3_ARTIFACT_DOCTOR=enabled OBJECT_STORE_URL=http://127.0.0.1:9000 OBJECT_STORE_BUCKET=customcard-ci-artifacts OBJECT_STORE_ACCESS_KEY_ID=customcard OBJECT_STORE_SECRET_ACCESS_KEY=customcard-dev-only OBJECT_STORE_REGION=us-east-1 OBJECT_STORE_SIGNING_SECRET=test-object-store-signing-secret-32 npm run artifact:doctor:s3:live\nnpm run persistence:doctor\nnpm run demo:doctor\n```\n\nThe report checks the local-dev, cheap-droplet, cloud-native, runtime, and data\nlanes, including cloud-storage checks for the AWS artifact-store module. Passing\nthis check means the committed deployment contracts are internally consistent;\nit does not mean a real droplet, AWS account, or Kubernetes cluster has been\nprovisioned.\n\nThe Kubernetes web deployment probes `/api/health`, and the production Docker\nimage starts `scripts/api-server.mjs` so the same container can serve the static\nweb bundle and the contract-first API endpoints.\n\nThe API runtime defaults to `CUSTOMCARD_API_RUNTIME=contract` for reviewer/static\nserving. `npm run api:doctor:memory` sets test customer/admin session tokens and\nvalidates Bearer auth plus `X-Idempotency-Key` replay without a live database.\n`npm run api:doctor:postgres` injects a fake Postgres pool into the same runtime\npath and validates session lookup, wrong-role blocking, idempotency replay,\nconflict handling, repository-backed relationship-memory inserts,\nrepository-backed render-packet inserts, repository-backed import-preview\ninserts, repository-backed card-project inserts, manual handoff\norder/consent/event inserts, data-request privacy/consent inserts, audit inserts,\nand queue-job inserts without external credentials.\n`CUSTOMCARD_POSTGRES_INTEGRATION_DOCTOR=enabled npm run\napi:doctor:postgres:live` creates an isolated temporary database on the configured\nPostgres server, applies the committed migration, seeds customer/admin sessions,\nverifies all repository-backed customer routes plus admin readiness through the\nreal `pg` runtime, exercises relationship-memory, render-packet, import-preview,\ncard-project, manual handoff, and data-request persistence, and drops the\ntemporary database before exiting.\n`CUSTOMCARD_POSTGRES_API_HTTP_DOCTOR=enabled npm run\napi:doctor:postgres:http` starts `scripts/api-server.mjs` against the same\nisolated migrated database shape and verifies public health/routes,\nadmin/customer Bearer auth, missing/wrong-role auth blocking, missing\nidempotency blocking, all 6 repository-backed customer HTTP mutations,\nidempotency replay/conflict, audit rows, queue jobs, and repository table counts\nbefore shutting the server down and dropping the temporary database.\nCI runs both live Postgres doctors against a Postgres service; deployed\nproduction account auth remains unclaimed.\n\n`CUSTOMCARD_ACCOUNT_AUTH_DOCTOR=enabled npm run account:doctor:live` uses the\nsame isolated-database pattern to verify hosted account identity rows,\nprovider-subject uniqueness, hashed recovery challenges, durable sessions, and\naccount-recovery audit rows. CI runs it against the Postgres service; live hosted\ntoken verification remains unclaimed.\n\n`npm run artifact:doctor` writes the sample render packet package to a\ntemporary local filesystem object-store path and an injected S3-compatible client\ncontract, reads every artifact back, verifies checksums and byte lengths, stores\nboth handoff manifests, and keeps network calls plus real orders disabled.\n`CUSTOMCARD_S3_ARTIFACT_DOCTOR=enabled npm run artifact:doctor:s3:live` writes\nthe same package to a live S3-compatible endpoint such as MinIO using path-style\nSigV4 requests, reads every object back, verifies checksums and byte lengths,\nstores the handoff manifest, reports `cloudWritesVerified: true`, cleans up the\nisolated bucket, and keeps external vendor calls plus real orders disabled. A\nproduction cloud bucket policy/IAM contract is now represented by\n`infra/aws/artifact-store` and checked by `npm run cloud:doctor`; live AWS apply,\nAccess Analyzer review, and account-specific IAM validation remain separate from\nthis repo-local proof.\n\nThe AWS artifact-store module provisions only the artifact storage boundary:\npublic access block, bucket-owner-enforced ownership, versioning, AES256\nserver-side encryption, lifecycle cleanup under `projects/`, an HTTPS-only and\nencrypted-upload bucket policy, and a least-privilege writer policy scoped to\n`projects/*`. Existing app and worker IAM roles can be attached by passing\n`app_role_name` and `worker_role_name`. Runtime outputs name the required\n`OBJECT_STORE_*` values that still need to be mirrored into the platform secret\nor config manager.\n\nThe persistence boundary requires auth-session storage, account identity storage,\nhashed recovery challenges, idempotency replay, queue job envelopes,\nrelationship-memory repository writes, render-packet repository writes,\nrender-packet artifact manifests, signed URL expiry, and append-only audit\nsignals in the migration before production Postgres handlers are claimed.\n\nThe Kubernetes `Secret` in `k8s/app.yaml` is intentionally empty and annotated as\npre-created by a secret manager. Production clusters should source the required\nkeys from the platform secret manager, External Secrets, Sealed Secrets, or an\nequivalent operator-approved mechanism. The `runtime:doctor` check rejects empty\nvalues, known placeholders, and any live-order kill switch value other than\n`disabled`.\n\nSigned artifact handoff requires `OBJECT_STORE_SIGNING_SECRET` to be present and\nat least 32 characters. The committed manifests also keep\n`ARTIFACT_SIGNED_URL_TTL_MINUTES=15` explicit so download links remain\nshort-lived.\n\nReal external ordering stays disabled with `REAL_ORDER_KILL_SWITCH=disabled` until vendor sandbox tests and physical print certification are recorded.\n\n## Provider Adapter Secrets\n\n`env/.env.example` names the credential-gated adapters represented in\n`src/providerCatalog.ts`:\n\n- Google OAuth: `GOOGLE_OAUTH_CLIENT_ID`, `GOOGLE_OAUTH_CLIENT_SECRET`.\n- Microsoft Graph: `MICROSOFT_CLIENT_ID`, `MICROSOFT_CLIENT_SECRET`,\n `MICROSOFT_TENANT_ID`.\n- Hosted identity providers: `CUSTOMCARD_AUTH_CALLBACK_URL`, `AUTH0_DOMAIN`,\n `AUTH0_CLIENT_ID`, `AUTH0_CLIENT_SECRET`, `AUTH0_AUDIENCE`,\n `CLERK_SECRET_KEY`, `CLERK_JWT_KEY`, `CLERK_AUTHORIZED_PARTIES`,\n `SUPABASE_URL`, `SUPABASE_ANON_KEY`, `SUPABASE_SERVICE_ROLE_KEY`,\n `FIREBASE_API_KEY`, `FIREBASE_PROJECT_ID`,\n `FIREBASE_SERVICE_ACCOUNT_JSON`, `COGNITO_DOMAIN`,\n `COGNITO_USER_POOL_ID`, `COGNITO_APP_CLIENT_ID`.\n- Contact import providers: `CARDDAV_BASE_URL`, `CARDDAV_USERNAME`,\n `CARDDAV_APP_PASSWORD`, `CARDDAV_ADDRESSBOOK_PATH`.\n- Text and image AI providers: `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`,\n `AZURE_OPENAI_ENDPOINT`, `AZURE_OPENAI_API_KEY`,\n `AZURE_OPENAI_CHAT_DEPLOYMENT`, `AZURE_OPENAI_IMAGE_DEPLOYMENT`,\n `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_REGION`,\n `BEDROCK_TEXT_MODEL_ID`, `BEDROCK_IMAGE_MODEL_ID`,\n `GOOGLE_GENERATIVE_AI_API_KEY`, `MISTRAL_API_KEY`, `COHERE_API_KEY`,\n `PERPLEXITY_API_KEY`, `XAI_API_KEY`, `TOGETHER_API_KEY`, `GROQ_API_KEY`,\n `DEEPSEEK_API_KEY`, `FIREWORKS_API_KEY`, `STABILITY_API_KEY`,\n `HUGGINGFACE_API_TOKEN`, `REPLICATE_API_TOKEN`, `IDEOGRAM_API_KEY`,\n `LEONARDO_API_KEY`, `FAL_KEY`, `BFL_API_KEY`.\n- Self-hosted model fallback: `SELF_HOSTED_LLM_BASE_URL`,\n `SELF_HOSTED_LLM_API_KEY`.\n- Notification providers: `RESEND_API_KEY`, `SENDGRID_API_KEY`,\n `POSTMARK_SERVER_TOKEN`, `MAILGUN_API_KEY`, `MAILGUN_DOMAIN`,\n `TWILIO_ACCOUNT_SID`, `TWILIO_AUTH_TOKEN`,\n `TWILIO_MESSAGING_SERVICE_SID`, `WHATSAPP_ACCESS_TOKEN`,\n `WHATSAPP_PHONE_NUMBER_ID`, `EXPO_ACCESS_TOKEN`,\n `TRANSACTIONAL_EMAIL_API_KEY`, `TRANSACTIONAL_EMAIL_FROM`.\n- Payment sandbox providers: `STRIPE_SECRET_KEY`, `STRIPE_WEBHOOK_SECRET`,\n `CUSTOMCARD_PAYMENT_SUCCESS_URL`, `CUSTOMCARD_PAYMENT_CANCEL_URL`,\n `PAYPAL_CLIENT_ID`, `PAYPAL_CLIENT_SECRET`, `PAYPAL_WEBHOOK_ID`,\n `SQUARE_ACCESS_TOKEN`, `SQUARE_LOCATION_ID`,\n `SQUARE_WEBHOOK_SIGNATURE_KEY`, `ADYEN_API_KEY`,\n `ADYEN_MERCHANT_ACCOUNT`, `ADYEN_HMAC_KEY`.\n- Observability providers: `SENTRY_DSN`, `SENTRY_PROJECT_ID`,\n `SENTRY_ENVIRONMENT`, `POSTHOG_PROJECT_API_KEY`, `POSTHOG_HOST`,\n `OTEL_EXPORTER_OTLP_ENDPOINT`, `OTEL_EXPORTER_OTLP_HEADERS`,\n `GRAFANA_OTLP_ENDPOINT`, `GRAFANA_OTLP_INSTANCE_ID`,\n `GRAFANA_OTLP_API_KEY`, `DATADOG_API_KEY`, `DATADOG_SITE`,\n `BETTERSTACK_SOURCE_TOKEN`, `BETTERSTACK_INGESTING_HOST`.\n- Object-store artifact signer and live S3-compatible doctor:\n `OBJECT_STORE_SIGNING_SECRET`, `OBJECT_STORE_ACCESS_KEY_ID`,\n `OBJECT_STORE_SECRET_ACCESS_KEY`, `OBJECT_STORE_REGION`.\n- Live vendor adapters: `WALGREENS_VENDOR_MODE`, `CVS_VENDOR_MODE`,\n `FEDEX_VENDOR_MODE`, `WALMART_VENDOR_MODE`, `STAPLES_VENDOR_MODE`,\n `OFFICE_DEPOT_VENDOR_MODE`.\n- Persistence controls: `CUSTOMCARD_API_RUNTIME`, `AUTH_SESSION_SECRET`,\n `CUSTOMCARD_CUSTOMER_SESSION_TOKEN`, `CUSTOMCARD_ADMIN_SESSION_TOKEN`,\n `IDEMPOTENCY_KEY_TTL_HOURS`.\n\nThese keys are documented for deployment readiness only. The current repo state\ndoes not make live provider calls, and vendor modes remain\n`disabled_until_certified` while `REAL_ORDER_KILL_SWITCH=disabled`.\n"}, "5e1482f8952359158715d6702d0aa6ce4ea9dc17": {"t": 1785772138.2752755, "n": 10789, "h": "9b2816566f0ca412", "c": "# CustomCard Infrastructure\n\nThis directory is the deployable service skeleton for the production path.\n\n- `docker-compose.dev.yml` runs app, worker, Postgres, Redis, and MinIO for local development.\n- `docker-compose.droplet.yml` is the cheap single-host deployment shape for a small DigitalOcean droplet.\n- `k8s/app.yaml` is the cloud-native starting point for web and worker deployments.\n- `migrations/001_initial_schema.sql` defines the durable service data model.\n- `env/.env.example` lists required secrets and kill switches.\n- `../scripts/api-server.mjs` serves `/api/health`, API bootstrap/readiness\n contracts, explicit contract/memory/Postgres runtime modes, and the built web\n app from `dist`.\n- `../src/artifactHandoff.ts` defines the render-packet artifact manifest and\n HMAC-signed URL contract used by the API/schema gates.\n- `../src/artifactStore.ts` writes render-packet artifacts to a temporary local\n filesystem object-store path and an injected S3-compatible client contract,\n then verifies readback without network calls.\n- `../scripts/persistence-doctor.mjs` validates auth-session, idempotency, queue\n job, and audit persistence signals.\n- `../scripts/demo-reset.mjs` validates the reviewer demo reset contract before\n anyone attempts to run seed data against a database.\n- `../scripts/deployment-readiness.mjs` emits the local deployment readiness\n report used by `npm run deployment:doctor`.\n\nFor the droplet deployment, provide `POSTGRES_PASSWORD` from a real secret source\nor local deployment `.env` that is never committed. The compose file uses\n`${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD}` so the service fails closed instead\nof starting with an empty database password.\n\nFor Kubernetes, apply the `customcard-migrate` Job successfully before rolling\nthe web and worker Deployments. The deployment manifest keeps the live-order kill\nswitch explicit and uses probes so bad pods fail visibly.\n\nRun the local IaC readiness check before treating the manifests as reviewable:\n\n```sh\nnpm run deployment:doctor\nnpm run api:doctor\nnpm run api:doctor:memory\nnpm run api:doctor:postgres\nCUSTOMCARD_POSTGRES_INTEGRATION_DOCTOR=enabled DATABASE_URL=postgres://... npm run api:doctor:postgres:live\nCUSTOMCARD_POSTGRES_API_HTTP_DOCTOR=enabled DATABASE_URL=postgres://... npm run api:doctor:postgres:http\nCUSTOMCARD_ACCOUNT_AUTH_DOCTOR=enabled DATABASE_URL=postgres://... npm run account:doctor:live\nnpm run artifact:doctor\nCUSTOMCARD_S3_ARTIFACT_DOCTOR=enabled OBJECT_STORE_URL=http://127.0.0.1:9000 OBJECT_STORE_BUCKET=customcard-ci-artifacts OBJECT_STORE_ACCESS_KEY_ID=customcard OBJECT_STORE_SECRET_ACCESS_KEY=customcard-dev-only OBJECT_STORE_REGION=us-east-1 OBJECT_STORE_SIGNING_SECRET=test-object-store-signing-secret-32 npm run artifact:doctor:s3:live\nnpm run persistence:doctor\nnpm run demo:doctor\n```\n\nThe report checks the local-dev, cheap-droplet, cloud-native, runtime, and data\nlanes. Passing this check means the committed deployment contracts are internally\nconsistent; it does not mean a real droplet or Kubernetes cluster has been\nprovisioned.\n\nThe Kubernetes web deployment probes `/api/health`, and the production Docker\nimage starts `scripts/api-server.mjs` so the same container can serve the static\nweb bundle and the contract-first API endpoints.\n\nThe API runtime defaults to `CUSTOMCARD_API_RUNTIME=contract` for reviewer/static\nserving. `npm run api:doctor:memory` sets test customer/admin session tokens and\nvalidates Bearer auth plus `X-Idempotency-Key` replay without a live database.\n`npm run api:doctor:postgres` injects a fake Postgres pool into the same runtime\npath and validates session lookup, wrong-role blocking, idempotency replay,\nconflict handling, repository-backed relationship-memory inserts,\nrepository-backed render-packet inserts, repository-backed import-preview\ninserts, repository-backed card-project inserts, manual handoff\norder/consent/event inserts, data-request privacy/consent inserts, audit inserts,\nand queue-job inserts without external credentials.\n`CUSTOMCARD_POSTGRES_INTEGRATION_DOCTOR=enabled npm run\napi:doctor:postgres:live` creates an isolated temporary database on the configured\nPostgres server, applies the committed migration, seeds customer/admin sessions,\nverifies all repository-backed customer routes plus admin readiness through the\nreal `pg` runtime, exercises relationship-memory, render-packet, import-preview,\ncard-project, manual handoff, and data-request persistence, and drops the\ntemporary database before exiting.\n`CUSTOMCARD_POSTGRES_API_HTTP_DOCTOR=enabled npm run\napi:doctor:postgres:http` starts `scripts/api-server.mjs` against the same\nisolated migrated database shape and verifies public health/routes,\nadmin/customer Bearer auth, missing/wrong-role auth blocking, missing\nidempotency blocking, all 6 repository-backed customer HTTP mutations,\nidempotency replay/conflict, audit rows, queue jobs, and repository table counts\nbefore shutting the server down and dropping the temporary database.\nCI runs both live Postgres doctors against a Postgres service; deployed\nproduction account auth remains unclaimed.\n\n`CUSTOMCARD_ACCOUNT_AUTH_DOCTOR=enabled npm run account:doctor:live` uses the\nsame isolated-database pattern to verify hosted account identity rows,\nprovider-subject uniqueness, hashed recovery challenges, durable sessions, and\naccount-recovery audit rows. CI runs it against the Postgres service; live hosted\ntoken verification remains unclaimed.\n\n`npm run artifact:doctor` writes the sample render packet package to a\ntemporary local filesystem object-store path and an injected S3-compatible client\ncontract, reads every artifact back, verifies checksums and byte lengths, stores\nboth handoff manifests, and keeps network calls plus real orders disabled.\n`CUSTOMCARD_S3_ARTIFACT_DOCTOR=enabled npm run artifact:doctor:s3:live` writes\nthe same package to a live S3-compatible endpoint such as MinIO using path-style\nSigV4 requests, reads every object back, verifies checksums and byte lengths,\nstores the handoff manifest, reports `cloudWritesVerified: true`, cleans up the\nisolated bucket, and keeps external vendor calls plus real orders disabled. A\nproduction cloud bucket policy/IAM review remains separate from this CI/local\nMinIO proof.\n\nThe persistence boundary requires auth-session storage, account identity storage,\nhashed recovery challenges, idempotency replay, queue job envelopes,\nrelationship-memory repository writes, render-packet repository writes,\nrender-packet artifact manifests, signed URL expiry, and append-only audit\nsignals in the migration before production Postgres handlers are claimed.\n\nThe Kubernetes `Secret` in `k8s/app.yaml` is intentionally empty and annotated as\npre-created by a secret manager. Production clusters should source the required\nkeys from the platform secret manager, External Secrets, Sealed Secrets, or an\nequivalent operator-approved mechanism. The `runtime:doctor` check rejects empty\nvalues, known placeholders, and any live-order kill switch value other than\n`disabled`.\n\nSigned artifact handoff requires `OBJECT_STORE_SIGNING_SECRET` to be present and\nat least 32 characters. The committed manifests also keep\n`ARTIFACT_SIGNED_URL_TTL_MINUTES=15` explicit so download links remain\nshort-lived.\n\nReal external ordering stays disabled with `REAL_ORDER_KILL_SWITCH=disabled` until vendor sandbox tests and physical print certification are recorded.\n\n## Provider Adapter Secrets\n\n`env/.env.example` names the credential-gated adapters represented in\n`src/providerCatalog.ts`:\n\n- Google OAuth: `GOOGLE_OAUTH_CLIENT_ID`, `GOOGLE_OAUTH_CLIENT_SECRET`.\n- Microsoft Graph: `MICROSOFT_CLIENT_ID`, `MICROSOFT_CLIENT_SECRET`,\n `MICROSOFT_TENANT_ID`.\n- Hosted identity providers: `CUSTOMCARD_AUTH_CALLBACK_URL`, `AUTH0_DOMAIN`,\n `AUTH0_CLIENT_ID`, `AUTH0_CLIENT_SECRET`, `AUTH0_AUDIENCE`,\n `CLERK_SECRET_KEY`, `CLERK_JWT_KEY`, `CLERK_AUTHORIZED_PARTIES`,\n `SUPABASE_URL`, `SUPABASE_ANON_KEY`, `SUPABASE_SERVICE_ROLE_KEY`,\n `FIREBASE_API_KEY`, `FIREBASE_PROJECT_ID`,\n `FIREBASE_SERVICE_ACCOUNT_JSON`, `COGNITO_DOMAIN`,\n `COGNITO_USER_POOL_ID`, `COGNITO_APP_CLIENT_ID`.\n- Contact import providers: `CARDDAV_BASE_URL`, `CARDDAV_USERNAME`,\n `CARDDAV_APP_PASSWORD`, `CARDDAV_ADDRESSBOOK_PATH`.\n- Text and image AI providers: `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`,\n `AZURE_OPENAI_ENDPOINT`, `AZURE_OPENAI_API_KEY`,\n `AZURE_OPENAI_CHAT_DEPLOYMENT`, `AZURE_OPENAI_IMAGE_DEPLOYMENT`,\n `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_REGION`,\n `BEDROCK_TEXT_MODEL_ID`, `BEDROCK_IMAGE_MODEL_ID`,\n `GOOGLE_GENERATIVE_AI_API_KEY`, `MISTRAL_API_KEY`, `COHERE_API_KEY`,\n `PERPLEXITY_API_KEY`, `XAI_API_KEY`, `TOGETHER_API_KEY`, `GROQ_API_KEY`,\n `DEEPSEEK_API_KEY`, `FIREWORKS_API_KEY`, `STABILITY_API_KEY`,\n `HUGGINGFACE_API_TOKEN`, `REPLICATE_API_TOKEN`, `IDEOGRAM_API_KEY`,\n `LEONARDO_API_KEY`, `FAL_KEY`, `BFL_API_KEY`.\n- Self-hosted model fallback: `SELF_HOSTED_LLM_BASE_URL`,\n `SELF_HOSTED_LLM_API_KEY`.\n- Notification providers: `RESEND_API_KEY`, `SENDGRID_API_KEY`,\n `POSTMARK_SERVER_TOKEN`, `MAILGUN_API_KEY`, `MAILGUN_DOMAIN`,\n `TWILIO_ACCOUNT_SID`, `TWILIO_AUTH_TOKEN`,\n `TWILIO_MESSAGING_SERVICE_SID`, `WHATSAPP_ACCESS_TOKEN`,\n `WHATSAPP_PHONE_NUMBER_ID`, `EXPO_ACCESS_TOKEN`,\n `TRANSACTIONAL_EMAIL_API_KEY`, `TRANSACTIONAL_EMAIL_FROM`.\n- Payment sandbox providers: `STRIPE_SECRET_KEY`, `STRIPE_WEBHOOK_SECRET`,\n `CUSTOMCARD_PAYMENT_SUCCESS_URL`, `CUSTOMCARD_PAYMENT_CANCEL_URL`,\n `PAYPAL_CLIENT_ID`, `PAYPAL_CLIENT_SECRET`, `PAYPAL_WEBHOOK_ID`,\n `SQUARE_ACCESS_TOKEN`, `SQUARE_LOCATION_ID`,\n `SQUARE_WEBHOOK_SIGNATURE_KEY`, `ADYEN_API_KEY`,\n `ADYEN_MERCHANT_ACCOUNT`, `ADYEN_HMAC_KEY`.\n- Observability providers: `SENTRY_DSN`, `SENTRY_PROJECT_ID`,\n `SENTRY_ENVIRONMENT`, `POSTHOG_PROJECT_API_KEY`, `POSTHOG_HOST`,\n `OTEL_EXPORTER_OTLP_ENDPOINT`, `OTEL_EXPORTER_OTLP_HEADERS`,\n `GRAFANA_OTLP_ENDPOINT`, `GRAFANA_OTLP_INSTANCE_ID`,\n `GRAFANA_OTLP_API_KEY`, `DATADOG_API_KEY`, `DATADOG_SITE`,\n `BETTERSTACK_SOURCE_TOKEN`, `BETTERSTACK_INGESTING_HOST`.\n- Object-store artifact signer and live S3-compatible doctor:\n `OBJECT_STORE_SIGNING_SECRET`, `OBJECT_STORE_ACCESS_KEY_ID`,\n `OBJECT_STORE_SECRET_ACCESS_KEY`, `OBJECT_STORE_REGION`.\n- Live vendor adapters: `WALGREENS_VENDOR_MODE`, `CVS_VENDOR_MODE`,\n `FEDEX_VENDOR_MODE`, `WALMART_VENDOR_MODE`, `STAPLES_VENDOR_MODE`,\n `OFFICE_DEPOT_VENDOR_MODE`.\n- Persistence controls: `CUSTOMCARD_API_RUNTIME`, `AUTH_SESSION_SECRET`,\n `CUSTOMCARD_CUSTOMER_SESSION_TOKEN`, `CUSTOMCARD_ADMIN_SESSION_TOKEN`,\n `IDEMPOTENCY_KEY_TTL_HOURS`.\n\nThese keys are documented for deployment readiness only. The current repo state\ndoes not make live provider calls, and vendor modes remain\n`disabled_until_certified` while `REAL_ORDER_KILL_SWITCH=disabled`.\n"}, "afc668da16003b83bd087b7fe8c3afc728ce80c6": {"t": 1785772140.6841817, "n": 9962, "h": "2ec3ead2244803d5", "c": "# CustomCard Infrastructure\n\nThis directory is the deployable service skeleton for the production path.\n\n- `docker-compose.dev.yml` runs app, worker, Postgres, Redis, and MinIO for local development.\n- `docker-compose.droplet.yml` is the cheap single-host deployment shape for a small DigitalOcean droplet.\n- `k8s/app.yaml` is the cloud-native starting point for web and worker deployments.\n- `migrations/001_initial_schema.sql` defines the durable service data model.\n- `env/.env.example` lists required secrets and kill switches.\n- `../scripts/api-server.mjs` serves `/api/health`, API bootstrap/readiness\n contracts, explicit contract/memory/Postgres runtime modes, and the built web\n app from `dist`.\n- `../src/artifactHandoff.ts` defines the render-packet artifact manifest and\n HMAC-signed URL contract used by the API/schema gates.\n- `../src/artifactStore.ts` writes render-packet artifacts to a temporary local\n filesystem object-store path and an injected S3-compatible client contract,\n then verifies readback without network calls.\n- `../scripts/persistence-doctor.mjs` validates auth-session, idempotency, queue\n job, and audit persistence signals.\n- `../scripts/demo-reset.mjs` validates the reviewer demo reset contract before\n anyone attempts to run seed data against a database.\n- `../scripts/deployment-readiness.mjs` emits the local deployment readiness\n report used by `npm run deployment:doctor`.\n\nFor the droplet deployment, provide `POSTGRES_PASSWORD` from a real secret source\nor local deployment `.env` that is never committed. The compose file uses\n`${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD}` so the service fails closed instead\nof starting with an empty database password.\n\nFor Kubernetes, apply the `customcard-migrate` Job successfully before rolling\nthe web and worker Deployments. The deployment manifest keeps the live-order kill\nswitch explicit and uses probes so bad pods fail visibly.\n\nRun the local IaC readiness check before treating the manifests as reviewable:\n\n```sh\nnpm run deployment:doctor\nnpm run api:doctor\nnpm run api:doctor:memory\nnpm run api:doctor:postgres\nCUSTOMCARD_POSTGRES_INTEGRATION_DOCTOR=enabled DATABASE_URL=postgres://... npm run api:doctor:postgres:live\nCUSTOMCARD_POSTGRES_API_HTTP_DOCTOR=enabled DATABASE_URL=postgres://... npm run api:doctor:postgres:http\nCUSTOMCARD_ACCOUNT_AUTH_DOCTOR=enabled DATABASE_URL=postgres://... npm run account:doctor:live\nnpm run artifact:doctor\nnpm run persistence:doctor\nnpm run demo:doctor\n```\n\nThe report checks the local-dev, cheap-droplet, cloud-native, runtime, and data\nlanes. Passing this check means the committed deployment contracts are internally\nconsistent; it does not mean a real droplet or Kubernetes cluster has been\nprovisioned.\n\nThe Kubernetes web deployment probes `/api/health`, and the production Docker\nimage starts `scripts/api-server.mjs` so the same container can serve the static\nweb bundle and the contract-first API endpoints.\n\nThe API runtime defaults to `CUSTOMCARD_API_RUNTIME=contract` for reviewer/static\nserving. `npm run api:doctor:memory` sets test customer/admin session tokens and\nvalidates Bearer auth plus `X-Idempotency-Key` replay without a live database.\n`npm run api:doctor:postgres` injects a fake Postgres pool into the same runtime\npath and validates session lookup, wrong-role blocking, idempotency replay,\nconflict handling, repository-backed relationship-memory inserts,\nrepository-backed render-packet inserts, repository-backed import-preview\ninserts, repository-backed card-project inserts, manual handoff\norder/consent/event inserts, data-request privacy/consent inserts, audit inserts,\nand queue-job inserts without external credentials.\n`CUSTOMCARD_POSTGRES_INTEGRATION_DOCTOR=enabled npm run\napi:doctor:postgres:live` creates an isolated temporary database on the configured\nPostgres server, applies the committed migration, seeds customer/admin sessions,\nverifies all repository-backed customer routes plus admin readiness through the\nreal `pg` runtime, exercises relationship-memory, render-packet, import-preview,\ncard-project, manual handoff, and data-request persistence, and drops the\ntemporary database before exiting.\n`CUSTOMCARD_POSTGRES_API_HTTP_DOCTOR=enabled npm run\napi:doctor:postgres:http` starts `scripts/api-server.mjs` against the same\nisolated migrated database shape and verifies public health/routes,\nadmin/customer Bearer auth, missing/wrong-role auth blocking, missing\nidempotency blocking, all 6 repository-backed customer HTTP mutations,\nidempotency replay/conflict, audit rows, queue jobs, and repository table counts\nbefore shutting the server down and dropping the temporary database.\nCI runs both live Postgres doctors against a Postgres service; deployed\nproduction account auth remains unclaimed.\n\n`CUSTOMCARD_ACCOUNT_AUTH_DOCTOR=enabled npm run account:doctor:live` uses the\nsame isolated-database pattern to verify hosted account identity rows,\nprovider-subject uniqueness, hashed recovery challenges, durable sessions, and\naccount-recovery audit rows. CI runs it against the Postgres service; live hosted\ntoken verification remains unclaimed.\n\n`npm run artifact:doctor` writes the sample render packet package to a\ntemporary local filesystem object-store path and an injected S3-compatible client\ncontract, reads every artifact back, verifies checksums and byte lengths, stores\nboth handoff manifests, and keeps network calls plus real orders disabled. Cloud\nS3/MinIO writes remain unclaimed until provider credentials, bucket policy, and\ndeployment storage are tested.\n\nThe persistence boundary requires auth-session storage, account identity storage,\nhashed recovery challenges, idempotency replay, queue job envelopes,\nrelationship-memory repository writes, render-packet repository writes,\nrender-packet artifact manifests, signed URL expiry, and append-only audit\nsignals in the migration before production Postgres handlers are claimed.\n\nThe Kubernetes `Secret` in `k8s/app.yaml` is intentionally empty and annotated as\npre-created by a secret manager. Production clusters should source the required\nkeys from the platform secret manager, External Secrets, Sealed Secrets, or an\nequivalent operator-approved mechanism. The `runtime:doctor` check rejects empty\nvalues, known placeholders, and any live-order kill switch value other than\n`disabled`.\n\nSigned artifact handoff requires `OBJECT_STORE_SIGNING_SECRET` to be present and\nat least 32 characters. The committed manifests also keep\n`ARTIFACT_SIGNED_URL_TTL_MINUTES=15` explicit so download links remain\nshort-lived.\n\nReal external ordering stays disabled with `REAL_ORDER_KILL_SWITCH=disabled` until vendor sandbox tests and physical print certification are recorded.\n\n## Provider Adapter Secrets\n\n`env/.env.example` names the credential-gated adapters represented in\n`src/providerCatalog.ts`:\n\n- Google OAuth: `GOOGLE_OAUTH_CLIENT_ID`, `GOOGLE_OAUTH_CLIENT_SECRET`.\n- Microsoft Graph: `MICROSOFT_CLIENT_ID`, `MICROSOFT_CLIENT_SECRET`,\n `MICROSOFT_TENANT_ID`.\n- Hosted identity providers: `CUSTOMCARD_AUTH_CALLBACK_URL`, `AUTH0_DOMAIN`,\n `AUTH0_CLIENT_ID`, `AUTH0_CLIENT_SECRET`, `AUTH0_AUDIENCE`,\n `CLERK_SECRET_KEY`, `CLERK_JWT_KEY`, `CLERK_AUTHORIZED_PARTIES`,\n `SUPABASE_URL`, `SUPABASE_ANON_KEY`, `SUPABASE_SERVICE_ROLE_KEY`,\n `FIREBASE_API_KEY`, `FIREBASE_PROJECT_ID`,\n `FIREBASE_SERVICE_ACCOUNT_JSON`, `COGNITO_DOMAIN`,\n `COGNITO_USER_POOL_ID`, `COGNITO_APP_CLIENT_ID`.\n- Contact import providers: `CARDDAV_BASE_URL`, `CARDDAV_USERNAME`,\n `CARDDAV_APP_PASSWORD`, `CARDDAV_ADDRESSBOOK_PATH`.\n- Text and image AI providers: `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`,\n `AZURE_OPENAI_ENDPOINT`, `AZURE_OPENAI_API_KEY`,\n `AZURE_OPENAI_CHAT_DEPLOYMENT`, `AZURE_OPENAI_IMAGE_DEPLOYMENT`,\n `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_REGION`,\n `BEDROCK_TEXT_MODEL_ID`, `BEDROCK_IMAGE_MODEL_ID`,\n `GOOGLE_GENERATIVE_AI_API_KEY`, `MISTRAL_API_KEY`, `COHERE_API_KEY`,\n `PERPLEXITY_API_KEY`, `XAI_API_KEY`, `TOGETHER_API_KEY`, `GROQ_API_KEY`,\n `DEEPSEEK_API_KEY`, `FIREWORKS_API_KEY`, `STABILITY_API_KEY`,\n `HUGGINGFACE_API_TOKEN`, `REPLICATE_API_TOKEN`, `IDEOGRAM_API_KEY`,\n `LEONARDO_API_KEY`, `FAL_KEY`, `BFL_API_KEY`.\n- Self-hosted model fallback: `SELF_HOSTED_LLM_BASE_URL`,\n `SELF_HOSTED_LLM_API_KEY`.\n- Notification providers: `RESEND_API_KEY`, `SENDGRID_API_KEY`,\n `POSTMARK_SERVER_TOKEN`, `MAILGUN_API_KEY`, `MAILGUN_DOMAIN`,\n `TWILIO_ACCOUNT_SID`, `TWILIO_AUTH_TOKEN`,\n `TWILIO_MESSAGING_SERVICE_SID`, `WHATSAPP_ACCESS_TOKEN`,\n `WHATSAPP_PHONE_NUMBER_ID`, `EXPO_ACCESS_TOKEN`,\n `TRANSACTIONAL_EMAIL_API_KEY`, `TRANSACTIONAL_EMAIL_FROM`.\n- Payment sandbox providers: `STRIPE_SECRET_KEY`, `STRIPE_WEBHOOK_SECRET`,\n `CUSTOMCARD_PAYMENT_SUCCESS_URL`, `CUSTOMCARD_PAYMENT_CANCEL_URL`,\n `PAYPAL_CLIENT_ID`, `PAYPAL_CLIENT_SECRET`, `PAYPAL_WEBHOOK_ID`,\n `SQUARE_ACCESS_TOKEN`, `SQUARE_LOCATION_ID`,\n `SQUARE_WEBHOOK_SIGNATURE_KEY`, `ADYEN_API_KEY`,\n `ADYEN_MERCHANT_ACCOUNT`, `ADYEN_HMAC_KEY`.\n- Observability providers: `SENTRY_DSN`, `SENTRY_PROJECT_ID`,\n `SENTRY_ENVIRONMENT`, `POSTHOG_PROJECT_API_KEY`, `POSTHOG_HOST`,\n `OTEL_EXPORTER_OTLP_ENDPOINT`, `OTEL_EXPORTER_OTLP_HEADERS`,\n `GRAFANA_OTLP_ENDPOINT`, `GRAFANA_OTLP_INSTANCE_ID`,\n `GRAFANA_OTLP_API_KEY`, `DATADOG_API_KEY`, `DATADOG_SITE`,\n `BETTERSTACK_SOURCE_TOKEN`, `BETTERSTACK_INGESTING_HOST`.\n- Object-store artifact signer: `OBJECT_STORE_SIGNING_SECRET`.\n- Live vendor adapters: `WALGREENS_VENDOR_MODE`, `CVS_VENDOR_MODE`,\n `FEDEX_VENDOR_MODE`, `WALMART_VENDOR_MODE`, `STAPLES_VENDOR_MODE`,\n `OFFICE_DEPOT_VENDOR_MODE`.\n- Persistence controls: `CUSTOMCARD_API_RUNTIME`, `AUTH_SESSION_SECRET`,\n `CUSTOMCARD_CUSTOMER_SESSION_TOKEN`, `CUSTOMCARD_ADMIN_SESSION_TOKEN`,\n `IDEMPOTENCY_KEY_TTL_HOURS`.\n\nThese keys are documented for deployment readiness only. The current repo state\ndoes not make live provider calls, and vendor modes remain\n`disabled_until_certified` while `REAL_ORDER_KILL_SWITCH=disabled`.\n"}, "d98a582150b3a31e1483171f54e117f915583231": {"t": 1785772142.047957, "n": 9360, "h": "9fe5b91ed0eb1ea6", "c": "# CustomCard Infrastructure\n\nThis directory is the deployable service skeleton for the production path.\n\n- `docker-compose.dev.yml` runs app, worker, Postgres, Redis, and MinIO for local development.\n- `docker-compose.droplet.yml` is the cheap single-host deployment shape for a small DigitalOcean droplet.\n- `k8s/app.yaml` is the cloud-native starting point for web and worker deployments.\n- `migrations/001_initial_schema.sql` defines the durable service data model.\n- `env/.env.example` lists required secrets and kill switches.\n- `../scripts/api-server.mjs` serves `/api/health`, API bootstrap/readiness\n contracts, explicit contract/memory/Postgres runtime modes, and the built web\n app from `dist`.\n- `../src/artifactHandoff.ts` defines the render-packet artifact manifest and\n HMAC-signed URL contract used by the API/schema gates.\n- `../src/artifactStore.ts` writes render-packet artifacts to a temporary local\n filesystem object-store path and an injected S3-compatible client contract,\n then verifies readback without network calls.\n- `../scripts/persistence-doctor.mjs` validates auth-session, idempotency, queue\n job, and audit persistence signals.\n- `../scripts/demo-reset.mjs` validates the reviewer demo reset contract before\n anyone attempts to run seed data against a database.\n- `../scripts/deployment-readiness.mjs` emits the local deployment readiness\n report used by `npm run deployment:doctor`.\n\nFor the droplet deployment, provide `POSTGRES_PASSWORD` from a real secret source\nor local deployment `.env` that is never committed. The compose file uses\n`${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD}` so the service fails closed instead\nof starting with an empty database password.\n\nFor Kubernetes, apply the `customcard-migrate` Job successfully before rolling\nthe web and worker Deployments. The deployment manifest keeps the live-order kill\nswitch explicit and uses probes so bad pods fail visibly.\n\nRun the local IaC readiness check before treating the manifests as reviewable:\n\n```sh\nnpm run deployment:doctor\nnpm run api:doctor\nnpm run api:doctor:memory\nnpm run api:doctor:postgres\nCUSTOMCARD_POSTGRES_INTEGRATION_DOCTOR=enabled DATABASE_URL=postgres://... npm run api:doctor:postgres:live\nCUSTOMCARD_ACCOUNT_AUTH_DOCTOR=enabled DATABASE_URL=postgres://... npm run account:doctor:live\nnpm run artifact:doctor\nnpm run persistence:doctor\nnpm run demo:doctor\n```\n\nThe report checks the local-dev, cheap-droplet, cloud-native, runtime, and data\nlanes. Passing this check means the committed deployment contracts are internally\nconsistent; it does not mean a real droplet or Kubernetes cluster has been\nprovisioned.\n\nThe Kubernetes web deployment probes `/api/health`, and the production Docker\nimage starts `scripts/api-server.mjs` so the same container can serve the static\nweb bundle and the contract-first API endpoints.\n\nThe API runtime defaults to `CUSTOMCARD_API_RUNTIME=contract` for reviewer/static\nserving. `npm run api:doctor:memory` sets test customer/admin session tokens and\nvalidates Bearer auth plus `X-Idempotency-Key` replay without a live database.\n`npm run api:doctor:postgres` injects a fake Postgres pool into the same runtime\npath and validates session lookup, wrong-role blocking, idempotency replay,\nconflict handling, repository-backed relationship-memory inserts,\nrepository-backed render-packet inserts, repository-backed import-preview\ninserts, repository-backed card-project inserts, manual handoff\norder/consent/event inserts, data-request privacy/consent inserts, audit inserts,\nand queue-job inserts without external credentials.\n`CUSTOMCARD_POSTGRES_INTEGRATION_DOCTOR=enabled npm run\napi:doctor:postgres:live` creates an isolated temporary database on the configured\nPostgres server, applies the committed migration, seeds customer/admin sessions,\nverifies all repository-backed customer routes plus admin readiness through the\nreal `pg` runtime, exercises relationship-memory, render-packet, import-preview,\ncard-project, manual handoff, and data-request persistence, and drops the\ntemporary database before exiting.\nCI runs that live doctor against a Postgres service; deployed production account\nauth remains unclaimed.\n\n`CUSTOMCARD_ACCOUNT_AUTH_DOCTOR=enabled npm run account:doctor:live` uses the\nsame isolated-database pattern to verify hosted account identity rows,\nprovider-subject uniqueness, hashed recovery challenges, durable sessions, and\naccount-recovery audit rows. CI runs it against the Postgres service; live hosted\ntoken verification remains unclaimed.\n\n`npm run artifact:doctor` writes the sample render packet package to a\ntemporary local filesystem object-store path and an injected S3-compatible client\ncontract, reads every artifact back, verifies checksums and byte lengths, stores\nboth handoff manifests, and keeps network calls plus real orders disabled. Cloud\nS3/MinIO writes remain unclaimed until provider credentials, bucket policy, and\ndeployment storage are tested.\n\nThe persistence boundary requires auth-session storage, account identity storage,\nhashed recovery challenges, idempotency replay, queue job envelopes,\nrelationship-memory repository writes, render-packet repository writes,\nrender-packet artifact manifests, signed URL expiry, and append-only audit\nsignals in the migration before production Postgres handlers are claimed.\n\nThe Kubernetes `Secret` in `k8s/app.yaml` is intentionally empty and annotated as\npre-created by a secret manager. Production clusters should source the required\nkeys from the platform secret manager, External Secrets, Sealed Secrets, or an\nequivalent operator-approved mechanism. The `runtime:doctor` check rejects empty\nvalues, known placeholders, and any live-order kill switch value other than\n`disabled`.\n\nSigned artifact handoff requires `OBJECT_STORE_SIGNING_SECRET` to be present and\nat least 32 characters. The committed manifests also keep\n`ARTIFACT_SIGNED_URL_TTL_MINUTES=15` explicit so download links remain\nshort-lived.\n\nReal external ordering stays disabled with `REAL_ORDER_KILL_SWITCH=disabled` until vendor sandbox tests and physical print certification are recorded.\n\n## Provider Adapter Secrets\n\n`env/.env.example` names the credential-gated adapters represented in\n`src/providerCatalog.ts`:\n\n- Google OAuth: `GOOGLE_OAUTH_CLIENT_ID`, `GOOGLE_OAUTH_CLIENT_SECRET`.\n- Microsoft Graph: `MICROSOFT_CLIENT_ID`, `MICROSOFT_CLIENT_SECRET`,\n `MICROSOFT_TENANT_ID`.\n- Hosted identity providers: `CUSTOMCARD_AUTH_CALLBACK_URL`, `AUTH0_DOMAIN`,\n `AUTH0_CLIENT_ID`, `AUTH0_CLIENT_SECRET`, `AUTH0_AUDIENCE`,\n `CLERK_SECRET_KEY`, `CLERK_JWT_KEY`, `CLERK_AUTHORIZED_PARTIES`,\n `SUPABASE_URL`, `SUPABASE_ANON_KEY`, `SUPABASE_SERVICE_ROLE_KEY`,\n `FIREBASE_API_KEY`, `FIREBASE_PROJECT_ID`,\n `FIREBASE_SERVICE_ACCOUNT_JSON`, `COGNITO_DOMAIN`,\n `COGNITO_USER_POOL_ID`, `COGNITO_APP_CLIENT_ID`.\n- Contact import providers: `CARDDAV_BASE_URL`, `CARDDAV_USERNAME`,\n `CARDDAV_APP_PASSWORD`, `CARDDAV_ADDRESSBOOK_PATH`.\n- Text and image AI providers: `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`,\n `AZURE_OPENAI_ENDPOINT`, `AZURE_OPENAI_API_KEY`,\n `AZURE_OPENAI_CHAT_DEPLOYMENT`, `AZURE_OPENAI_IMAGE_DEPLOYMENT`,\n `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_REGION`,\n `BEDROCK_TEXT_MODEL_ID`, `BEDROCK_IMAGE_MODEL_ID`,\n `GOOGLE_GENERATIVE_AI_API_KEY`, `MISTRAL_API_KEY`, `COHERE_API_KEY`,\n `PERPLEXITY_API_KEY`, `XAI_API_KEY`, `TOGETHER_API_KEY`, `GROQ_API_KEY`,\n `DEEPSEEK_API_KEY`, `FIREWORKS_API_KEY`, `STABILITY_API_KEY`,\n `HUGGINGFACE_API_TOKEN`, `REPLICATE_API_TOKEN`, `IDEOGRAM_API_KEY`,\n `LEONARDO_API_KEY`, `FAL_KEY`, `BFL_API_KEY`.\n- Self-hosted model fallback: `SELF_HOSTED_LLM_BASE_URL`,\n `SELF_HOSTED_LLM_API_KEY`.\n- Notification providers: `RESEND_API_KEY`, `SENDGRID_API_KEY`,\n `POSTMARK_SERVER_TOKEN`, `MAILGUN_API_KEY`, `MAILGUN_DOMAIN`,\n `TWILIO_ACCOUNT_SID`, `TWILIO_AUTH_TOKEN`,\n `TWILIO_MESSAGING_SERVICE_SID`, `WHATSAPP_ACCESS_TOKEN`,\n `WHATSAPP_PHONE_NUMBER_ID`, `EXPO_ACCESS_TOKEN`,\n `TRANSACTIONAL_EMAIL_API_KEY`, `TRANSACTIONAL_EMAIL_FROM`.\n- Payment sandbox providers: `STRIPE_SECRET_KEY`, `STRIPE_WEBHOOK_SECRET`,\n `CUSTOMCARD_PAYMENT_SUCCESS_URL`, `CUSTOMCARD_PAYMENT_CANCEL_URL`,\n `PAYPAL_CLIENT_ID`, `PAYPAL_CLIENT_SECRET`, `PAYPAL_WEBHOOK_ID`,\n `SQUARE_ACCESS_TOKEN`, `SQUARE_LOCATION_ID`,\n `SQUARE_WEBHOOK_SIGNATURE_KEY`, `ADYEN_API_KEY`,\n `ADYEN_MERCHANT_ACCOUNT`, `ADYEN_HMAC_KEY`.\n- Observability providers: `SENTRY_DSN`, `SENTRY_PROJECT_ID`,\n `SENTRY_ENVIRONMENT`, `POSTHOG_PROJECT_API_KEY`, `POSTHOG_HOST`,\n `OTEL_EXPORTER_OTLP_ENDPOINT`, `OTEL_EXPORTER_OTLP_HEADERS`,\n `GRAFANA_OTLP_ENDPOINT`, `GRAFANA_OTLP_INSTANCE_ID`,\n `GRAFANA_OTLP_API_KEY`, `DATADOG_API_KEY`, `DATADOG_SITE`,\n `BETTERSTACK_SOURCE_TOKEN`, `BETTERSTACK_INGESTING_HOST`.\n- Object-store artifact signer: `OBJECT_STORE_SIGNING_SECRET`.\n- Live vendor adapters: `WALGREENS_VENDOR_MODE`, `CVS_VENDOR_MODE`,\n `FEDEX_VENDOR_MODE`, `WALMART_VENDOR_MODE`, `STAPLES_VENDOR_MODE`,\n `OFFICE_DEPOT_VENDOR_MODE`.\n- Persistence controls: `CUSTOMCARD_API_RUNTIME`, `AUTH_SESSION_SECRET`,\n `CUSTOMCARD_CUSTOMER_SESSION_TOKEN`, `CUSTOMCARD_ADMIN_SESSION_TOKEN`,\n `IDEMPOTENCY_KEY_TTL_HOURS`.\n\nThese keys are documented for deployment readiness only. The current repo state\ndoes not make live provider calls, and vendor modes remain\n`disabled_until_certified` while `REAL_ORDER_KILL_SWITCH=disabled`.\n"}, "73acfc4d773874a73a61989c54a228aace16022b": {"t": 1785772143.4324608, "n": 9287, "h": "1e5ed245884903e2", "c": "# CustomCard Infrastructure\n\nThis directory is the deployable service skeleton for the production path.\n\n- `docker-compose.dev.yml` runs app, worker, Postgres, Redis, and MinIO for local development.\n- `docker-compose.droplet.yml` is the cheap single-host deployment shape for a small DigitalOcean droplet.\n- `k8s/app.yaml` is the cloud-native starting point for web and worker deployments.\n- `migrations/001_initial_schema.sql` defines the durable service data model.\n- `env/.env.example` lists required secrets and kill switches.\n- `../scripts/api-server.mjs` serves `/api/health`, API bootstrap/readiness\n contracts, explicit contract/memory/Postgres runtime modes, and the built web\n app from `dist`.\n- `../src/artifactHandoff.ts` defines the render-packet artifact manifest and\n HMAC-signed URL contract used by the API/schema gates.\n- `../src/artifactStore.ts` writes render-packet artifacts to a temporary local\n filesystem object-store path and an injected S3-compatible client contract,\n then verifies readback without network calls.\n- `../scripts/persistence-doctor.mjs` validates auth-session, idempotency, queue\n job, and audit persistence signals.\n- `../scripts/demo-reset.mjs` validates the reviewer demo reset contract before\n anyone attempts to run seed data against a database.\n- `../scripts/deployment-readiness.mjs` emits the local deployment readiness\n report used by `npm run deployment:doctor`.\n\nFor the droplet deployment, provide `POSTGRES_PASSWORD` from a real secret source\nor local deployment `.env` that is never committed. The compose file uses\n`${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD}` so the service fails closed instead\nof starting with an empty database password.\n\nFor Kubernetes, apply the `customcard-migrate` Job successfully before rolling\nthe web and worker Deployments. The deployment manifest keeps the live-order kill\nswitch explicit and uses probes so bad pods fail visibly.\n\nRun the local IaC readiness check before treating the manifests as reviewable:\n\n```sh\nnpm run deployment:doctor\nnpm run api:doctor\nnpm run api:doctor:memory\nnpm run api:doctor:postgres\nCUSTOMCARD_POSTGRES_INTEGRATION_DOCTOR=enabled DATABASE_URL=postgres://... npm run api:doctor:postgres:live\nCUSTOMCARD_ACCOUNT_AUTH_DOCTOR=enabled DATABASE_URL=postgres://... npm run account:doctor:live\nnpm run artifact:doctor\nnpm run persistence:doctor\nnpm run demo:doctor\n```\n\nThe report checks the local-dev, cheap-droplet, cloud-native, runtime, and data\nlanes. Passing this check means the committed deployment contracts are internally\nconsistent; it does not mean a real droplet or Kubernetes cluster has been\nprovisioned.\n\nThe Kubernetes web deployment probes `/api/health`, and the production Docker\nimage starts `scripts/api-server.mjs` so the same container can serve the static\nweb bundle and the contract-first API endpoints.\n\nThe API runtime defaults to `CUSTOMCARD_API_RUNTIME=contract` for reviewer/static\nserving. `npm run api:doctor:memory` sets test customer/admin session tokens and\nvalidates Bearer auth plus `X-Idempotency-Key` replay without a live database.\n`npm run api:doctor:postgres` injects a fake Postgres pool into the same runtime\npath and validates session lookup, wrong-role blocking, idempotency replay,\nconflict handling, repository-backed relationship-memory inserts,\nrepository-backed render-packet inserts, repository-backed import-preview\ninserts, repository-backed card-project inserts, manual handoff\norder/consent/event inserts, data-request privacy/consent inserts, audit inserts,\nand queue-job inserts without external credentials.\n`CUSTOMCARD_POSTGRES_INTEGRATION_DOCTOR=enabled npm run\napi:doctor:postgres:live` creates an isolated temporary database on the configured\nPostgres server, applies the committed migration, seeds customer/admin sessions,\nexercises the real `pg` runtime for relationship-memory, render-packet,\nimport-preview, card-project, manual handoff, and data-request persistence, and\ndrops the temporary database before exiting.\nCI runs that live doctor against a Postgres service; deployed production account\nauth remains unclaimed.\n\n`CUSTOMCARD_ACCOUNT_AUTH_DOCTOR=enabled npm run account:doctor:live` uses the\nsame isolated-database pattern to verify hosted account identity rows,\nprovider-subject uniqueness, hashed recovery challenges, durable sessions, and\naccount-recovery audit rows. CI runs it against the Postgres service; live hosted\ntoken verification remains unclaimed.\n\n`npm run artifact:doctor` writes the sample render packet package to a\ntemporary local filesystem object-store path and an injected S3-compatible client\ncontract, reads every artifact back, verifies checksums and byte lengths, stores\nboth handoff manifests, and keeps network calls plus real orders disabled. Cloud\nS3/MinIO writes remain unclaimed until provider credentials, bucket policy, and\ndeployment storage are tested.\n\nThe persistence boundary requires auth-session storage, account identity storage,\nhashed recovery challenges, idempotency replay, queue job envelopes,\nrelationship-memory repository writes, render-packet repository writes,\nrender-packet artifact manifests, signed URL expiry, and append-only audit\nsignals in the migration before production Postgres handlers are claimed.\n\nThe Kubernetes `Secret` in `k8s/app.yaml` is intentionally empty and annotated as\npre-created by a secret manager. Production clusters should source the required\nkeys from the platform secret manager, External Secrets, Sealed Secrets, or an\nequivalent operator-approved mechanism. The `runtime:doctor` check rejects empty\nvalues, known placeholders, and any live-order kill switch value other than\n`disabled`.\n\nSigned artifact handoff requires `OBJECT_STORE_SIGNING_SECRET` to be present and\nat least 32 characters. The committed manifests also keep\n`ARTIFACT_SIGNED_URL_TTL_MINUTES=15` explicit so download links remain\nshort-lived.\n\nReal external ordering stays disabled with `REAL_ORDER_KILL_SWITCH=disabled` until vendor sandbox tests and physical print certification are recorded.\n\n## Provider Adapter Secrets\n\n`env/.env.example` names the credential-gated adapters represented in\n`src/providerCatalog.ts`:\n\n- Google OAuth: `GOOGLE_OAUTH_CLIENT_ID`, `GOOGLE_OAUTH_CLIENT_SECRET`.\n- Microsoft Graph: `MICROSOFT_CLIENT_ID`, `MICROSOFT_CLIENT_SECRET`,\n `MICROSOFT_TENANT_ID`.\n- Hosted identity providers: `CUSTOMCARD_AUTH_CALLBACK_URL`, `AUTH0_DOMAIN`,\n `AUTH0_CLIENT_ID`, `AUTH0_CLIENT_SECRET`, `AUTH0_AUDIENCE`,\n `CLERK_SECRET_KEY`, `CLERK_JWT_KEY`, `CLERK_AUTHORIZED_PARTIES`,\n `SUPABASE_URL`, `SUPABASE_ANON_KEY`, `SUPABASE_SERVICE_ROLE_KEY`,\n `FIREBASE_API_KEY`, `FIREBASE_PROJECT_ID`,\n `FIREBASE_SERVICE_ACCOUNT_JSON`, `COGNITO_DOMAIN`,\n `COGNITO_USER_POOL_ID`, `COGNITO_APP_CLIENT_ID`.\n- Contact import providers: `CARDDAV_BASE_URL`, `CARDDAV_USERNAME`,\n `CARDDAV_APP_PASSWORD`, `CARDDAV_ADDRESSBOOK_PATH`.\n- Text and image AI providers: `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`,\n `AZURE_OPENAI_ENDPOINT`, `AZURE_OPENAI_API_KEY`,\n `AZURE_OPENAI_CHAT_DEPLOYMENT`, `AZURE_OPENAI_IMAGE_DEPLOYMENT`,\n `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_REGION`,\n `BEDROCK_TEXT_MODEL_ID`, `BEDROCK_IMAGE_MODEL_ID`,\n `GOOGLE_GENERATIVE_AI_API_KEY`, `MISTRAL_API_KEY`, `COHERE_API_KEY`,\n `PERPLEXITY_API_KEY`, `XAI_API_KEY`, `TOGETHER_API_KEY`, `GROQ_API_KEY`,\n `DEEPSEEK_API_KEY`, `FIREWORKS_API_KEY`, `STABILITY_API_KEY`,\n `HUGGINGFACE_API_TOKEN`, `REPLICATE_API_TOKEN`, `IDEOGRAM_API_KEY`,\n `LEONARDO_API_KEY`, `FAL_KEY`, `BFL_API_KEY`.\n- Self-hosted model fallback: `SELF_HOSTED_LLM_BASE_URL`,\n `SELF_HOSTED_LLM_API_KEY`.\n- Notification providers: `RESEND_API_KEY`, `SENDGRID_API_KEY`,\n `POSTMARK_SERVER_TOKEN`, `MAILGUN_API_KEY`, `MAILGUN_DOMAIN`,\n `TWILIO_ACCOUNT_SID`, `TWILIO_AUTH_TOKEN`,\n `TWILIO_MESSAGING_SERVICE_SID`, `WHATSAPP_ACCESS_TOKEN`,\n `WHATSAPP_PHONE_NUMBER_ID`, `EXPO_ACCESS_TOKEN`,\n `TRANSACTIONAL_EMAIL_API_KEY`, `TRANSACTIONAL_EMAIL_FROM`.\n- Payment sandbox providers: `STRIPE_SECRET_KEY`, `STRIPE_WEBHOOK_SECRET`,\n `CUSTOMCARD_PAYMENT_SUCCESS_URL`, `CUSTOMCARD_PAYMENT_CANCEL_URL`,\n `PAYPAL_CLIENT_ID`, `PAYPAL_CLIENT_SECRET`, `PAYPAL_WEBHOOK_ID`,\n `SQUARE_ACCESS_TOKEN`, `SQUARE_LOCATION_ID`,\n `SQUARE_WEBHOOK_SIGNATURE_KEY`, `ADYEN_API_KEY`,\n `ADYEN_MERCHANT_ACCOUNT`, `ADYEN_HMAC_KEY`.\n- Observability providers: `SENTRY_DSN`, `SENTRY_PROJECT_ID`,\n `SENTRY_ENVIRONMENT`, `POSTHOG_PROJECT_API_KEY`, `POSTHOG_HOST`,\n `OTEL_EXPORTER_OTLP_ENDPOINT`, `OTEL_EXPORTER_OTLP_HEADERS`,\n `GRAFANA_OTLP_ENDPOINT`, `GRAFANA_OTLP_INSTANCE_ID`,\n `GRAFANA_OTLP_API_KEY`, `DATADOG_API_KEY`, `DATADOG_SITE`,\n `BETTERSTACK_SOURCE_TOKEN`, `BETTERSTACK_INGESTING_HOST`.\n- Object-store artifact signer: `OBJECT_STORE_SIGNING_SECRET`.\n- Live vendor adapters: `WALGREENS_VENDOR_MODE`, `CVS_VENDOR_MODE`,\n `FEDEX_VENDOR_MODE`, `WALMART_VENDOR_MODE`, `STAPLES_VENDOR_MODE`,\n `OFFICE_DEPOT_VENDOR_MODE`.\n- Persistence controls: `CUSTOMCARD_API_RUNTIME`, `AUTH_SESSION_SECRET`,\n `CUSTOMCARD_CUSTOMER_SESSION_TOKEN`, `CUSTOMCARD_ADMIN_SESSION_TOKEN`,\n `IDEMPOTENCY_KEY_TTL_HOURS`.\n\nThese keys are documented for deployment readiness only. The current repo state\ndoes not make live provider calls, and vendor modes remain\n`disabled_until_certified` while `REAL_ORDER_KILL_SWITCH=disabled`.\n"}, "10220fbe02bfa24a2f8fe0e5998479b7d998022f": {"t": 1785772145.4344003, "n": 9200, "h": "3d64ac3c8f64324d", "c": "# CustomCard Infrastructure\n\nThis directory is the deployable service skeleton for the production path.\n\n- `docker-compose.dev.yml` runs app, worker, Postgres, Redis, and MinIO for local development.\n- `docker-compose.droplet.yml` is the cheap single-host deployment shape for a small DigitalOcean droplet.\n- `k8s/app.yaml` is the cloud-native starting point for web and worker deployments.\n- `migrations/001_initial_schema.sql` defines the durable service data model.\n- `env/.env.example` lists required secrets and kill switches.\n- `../scripts/api-server.mjs` serves `/api/health`, API bootstrap/readiness\n contracts, explicit contract/memory/Postgres runtime modes, and the built web\n app from `dist`.\n- `../src/artifactHandoff.ts` defines the render-packet artifact manifest and\n HMAC-signed URL contract used by the API/schema gates.\n- `../src/artifactStore.ts` writes render-packet artifacts to a temporary local\n filesystem object-store path and an injected S3-compatible client contract,\n then verifies readback without network calls.\n- `../scripts/persistence-doctor.mjs` validates auth-session, idempotency, queue\n job, and audit persistence signals.\n- `../scripts/demo-reset.mjs` validates the reviewer demo reset contract before\n anyone attempts to run seed data against a database.\n- `../scripts/deployment-readiness.mjs` emits the local deployment readiness\n report used by `npm run deployment:doctor`.\n\nFor the droplet deployment, provide `POSTGRES_PASSWORD` from a real secret source\nor local deployment `.env` that is never committed. The compose file uses\n`${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD}` so the service fails closed instead\nof starting with an empty database password.\n\nFor Kubernetes, apply the `customcard-migrate` Job successfully before rolling\nthe web and worker Deployments. The deployment manifest keeps the live-order kill\nswitch explicit and uses probes so bad pods fail visibly.\n\nRun the local IaC readiness check before treating the manifests as reviewable:\n\n```sh\nnpm run deployment:doctor\nnpm run api:doctor\nnpm run api:doctor:memory\nnpm run api:doctor:postgres\nCUSTOMCARD_POSTGRES_INTEGRATION_DOCTOR=enabled DATABASE_URL=postgres://... npm run api:doctor:postgres:live\nCUSTOMCARD_ACCOUNT_AUTH_DOCTOR=enabled DATABASE_URL=postgres://... npm run account:doctor:live\nnpm run artifact:doctor\nnpm run persistence:doctor\nnpm run demo:doctor\n```\n\nThe report checks the local-dev, cheap-droplet, cloud-native, runtime, and data\nlanes. Passing this check means the committed deployment contracts are internally\nconsistent; it does not mean a real droplet or Kubernetes cluster has been\nprovisioned.\n\nThe Kubernetes web deployment probes `/api/health`, and the production Docker\nimage starts `scripts/api-server.mjs` so the same container can serve the static\nweb bundle and the contract-first API endpoints.\n\nThe API runtime defaults to `CUSTOMCARD_API_RUNTIME=contract` for reviewer/static\nserving. `npm run api:doctor:memory` sets test customer/admin session tokens and\nvalidates Bearer auth plus `X-Idempotency-Key` replay without a live database.\n`npm run api:doctor:postgres` injects a fake Postgres pool into the same runtime\npath and validates session lookup, wrong-role blocking, idempotency replay,\nconflict handling, repository-backed render-packet inserts, repository-backed\nimport-preview inserts, repository-backed card-project inserts, manual handoff\norder/consent/event inserts, data-request privacy/consent inserts, audit inserts,\nand queue-job inserts without external credentials.\n`CUSTOMCARD_POSTGRES_INTEGRATION_DOCTOR=enabled npm run\napi:doctor:postgres:live` creates an isolated temporary database on the configured\nPostgres server, applies the committed migration, seeds customer/admin sessions\nand approved memory, exercises the real `pg` runtime for render-packet,\nimport-preview, card-project, manual handoff, and data-request persistence, and\ndrops the temporary database before exiting.\nCI runs that live doctor against a Postgres service; deployed production account\nauth remains unclaimed.\n\n`CUSTOMCARD_ACCOUNT_AUTH_DOCTOR=enabled npm run account:doctor:live` uses the\nsame isolated-database pattern to verify hosted account identity rows,\nprovider-subject uniqueness, hashed recovery challenges, durable sessions, and\naccount-recovery audit rows. CI runs it against the Postgres service; live hosted\ntoken verification remains unclaimed.\n\n`npm run artifact:doctor` writes the sample render packet package to a\ntemporary local filesystem object-store path and an injected S3-compatible client\ncontract, reads every artifact back, verifies checksums and byte lengths, stores\nboth handoff manifests, and keeps network calls plus real orders disabled. Cloud\nS3/MinIO writes remain unclaimed until provider credentials, bucket policy, and\ndeployment storage are tested.\n\nThe persistence boundary requires auth-session storage, account identity storage,\nhashed recovery challenges, idempotency replay, queue job envelopes,\nrender-packet repository writes, render-packet artifact manifests, signed URL\nexpiry, and append-only audit signals in the migration before production Postgres\nhandlers are claimed.\n\nThe Kubernetes `Secret` in `k8s/app.yaml` is intentionally empty and annotated as\npre-created by a secret manager. Production clusters should source the required\nkeys from the platform secret manager, External Secrets, Sealed Secrets, or an\nequivalent operator-approved mechanism. The `runtime:doctor` check rejects empty\nvalues, known placeholders, and any live-order kill switch value other than\n`disabled`.\n\nSigned artifact handoff requires `OBJECT_STORE_SIGNING_SECRET` to be present and\nat least 32 characters. The committed manifests also keep\n`ARTIFACT_SIGNED_URL_TTL_MINUTES=15` explicit so download links remain\nshort-lived.\n\nReal external ordering stays disabled with `REAL_ORDER_KILL_SWITCH=disabled` until vendor sandbox tests and physical print certification are recorded.\n\n## Provider Adapter Secrets\n\n`env/.env.example` names the credential-gated adapters represented in\n`src/providerCatalog.ts`:\n\n- Google OAuth: `GOOGLE_OAUTH_CLIENT_ID`, `GOOGLE_OAUTH_CLIENT_SECRET`.\n- Microsoft Graph: `MICROSOFT_CLIENT_ID`, `MICROSOFT_CLIENT_SECRET`,\n `MICROSOFT_TENANT_ID`.\n- Hosted identity providers: `CUSTOMCARD_AUTH_CALLBACK_URL`, `AUTH0_DOMAIN`,\n `AUTH0_CLIENT_ID`, `AUTH0_CLIENT_SECRET`, `AUTH0_AUDIENCE`,\n `CLERK_SECRET_KEY`, `CLERK_JWT_KEY`, `CLERK_AUTHORIZED_PARTIES`,\n `SUPABASE_URL`, `SUPABASE_ANON_KEY`, `SUPABASE_SERVICE_ROLE_KEY`,\n `FIREBASE_API_KEY`, `FIREBASE_PROJECT_ID`,\n `FIREBASE_SERVICE_ACCOUNT_JSON`, `COGNITO_DOMAIN`,\n `COGNITO_USER_POOL_ID`, `COGNITO_APP_CLIENT_ID`.\n- Contact import providers: `CARDDAV_BASE_URL`, `CARDDAV_USERNAME`,\n `CARDDAV_APP_PASSWORD`, `CARDDAV_ADDRESSBOOK_PATH`.\n- Text and image AI providers: `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`,\n `AZURE_OPENAI_ENDPOINT`, `AZURE_OPENAI_API_KEY`,\n `AZURE_OPENAI_CHAT_DEPLOYMENT`, `AZURE_OPENAI_IMAGE_DEPLOYMENT`,\n `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_REGION`,\n `BEDROCK_TEXT_MODEL_ID`, `BEDROCK_IMAGE_MODEL_ID`,\n `GOOGLE_GENERATIVE_AI_API_KEY`, `MISTRAL_API_KEY`, `COHERE_API_KEY`,\n `PERPLEXITY_API_KEY`, `XAI_API_KEY`, `TOGETHER_API_KEY`, `GROQ_API_KEY`,\n `DEEPSEEK_API_KEY`, `FIREWORKS_API_KEY`, `STABILITY_API_KEY`,\n `HUGGINGFACE_API_TOKEN`, `REPLICATE_API_TOKEN`, `IDEOGRAM_API_KEY`,\n `LEONARDO_API_KEY`, `FAL_KEY`, `BFL_API_KEY`.\n- Self-hosted model fallback: `SELF_HOSTED_LLM_BASE_URL`,\n `SELF_HOSTED_LLM_API_KEY`.\n- Notification providers: `RESEND_API_KEY`, `SENDGRID_API_KEY`,\n `POSTMARK_SERVER_TOKEN`, `MAILGUN_API_KEY`, `MAILGUN_DOMAIN`,\n `TWILIO_ACCOUNT_SID`, `TWILIO_AUTH_TOKEN`,\n `TWILIO_MESSAGING_SERVICE_SID`, `WHATSAPP_ACCESS_TOKEN`,\n `WHATSAPP_PHONE_NUMBER_ID`, `EXPO_ACCESS_TOKEN`,\n `TRANSACTIONAL_EMAIL_API_KEY`, `TRANSACTIONAL_EMAIL_FROM`.\n- Payment sandbox providers: `STRIPE_SECRET_KEY`, `STRIPE_WEBHOOK_SECRET`,\n `CUSTOMCARD_PAYMENT_SUCCESS_URL`, `CUSTOMCARD_PAYMENT_CANCEL_URL`,\n `PAYPAL_CLIENT_ID`, `PAYPAL_CLIENT_SECRET`, `PAYPAL_WEBHOOK_ID`,\n `SQUARE_ACCESS_TOKEN`, `SQUARE_LOCATION_ID`,\n `SQUARE_WEBHOOK_SIGNATURE_KEY`, `ADYEN_API_KEY`,\n `ADYEN_MERCHANT_ACCOUNT`, `ADYEN_HMAC_KEY`.\n- Observability providers: `SENTRY_DSN`, `SENTRY_PROJECT_ID`,\n `SENTRY_ENVIRONMENT`, `POSTHOG_PROJECT_API_KEY`, `POSTHOG_HOST`,\n `OTEL_EXPORTER_OTLP_ENDPOINT`, `OTEL_EXPORTER_OTLP_HEADERS`,\n `GRAFANA_OTLP_ENDPOINT`, `GRAFANA_OTLP_INSTANCE_ID`,\n `GRAFANA_OTLP_API_KEY`, `DATADOG_API_KEY`, `DATADOG_SITE`,\n `BETTERSTACK_SOURCE_TOKEN`, `BETTERSTACK_INGESTING_HOST`.\n- Object-store artifact signer: `OBJECT_STORE_SIGNING_SECRET`.\n- Live vendor adapters: `WALGREENS_VENDOR_MODE`, `CVS_VENDOR_MODE`,\n `FEDEX_VENDOR_MODE`, `WALMART_VENDOR_MODE`, `STAPLES_VENDOR_MODE`,\n `OFFICE_DEPOT_VENDOR_MODE`.\n- Persistence controls: `CUSTOMCARD_API_RUNTIME`, `AUTH_SESSION_SECRET`,\n `CUSTOMCARD_CUSTOMER_SESSION_TOKEN`, `CUSTOMCARD_ADMIN_SESSION_TOKEN`,\n `IDEMPOTENCY_KEY_TTL_HOURS`.\n\nThese keys are documented for deployment readiness only. The current repo state\ndoes not make live provider calls, and vendor modes remain\n`disabled_until_certified` while `REAL_ORDER_KILL_SWITCH=disabled`.\n"}, "b7b0068b227103e9ab5ae57ec9bf3e0d5ad04286": {"t": 1785772146.8200922, "n": 9115, "h": "857170c404ad68d5", "c": "# CustomCard Infrastructure\n\nThis directory is the deployable service skeleton for the production path.\n\n- `docker-compose.dev.yml` runs app, worker, Postgres, Redis, and MinIO for local development.\n- `docker-compose.droplet.yml` is the cheap single-host deployment shape for a small DigitalOcean droplet.\n- `k8s/app.yaml` is the cloud-native starting point for web and worker deployments.\n- `migrations/001_initial_schema.sql` defines the durable service data model.\n- `env/.env.example` lists required secrets and kill switches.\n- `../scripts/api-server.mjs` serves `/api/health`, API bootstrap/readiness\n contracts, explicit contract/memory/Postgres runtime modes, and the built web\n app from `dist`.\n- `../src/artifactHandoff.ts` defines the render-packet artifact manifest and\n HMAC-signed URL contract used by the API/schema gates.\n- `../src/artifactStore.ts` writes render-packet artifacts to a temporary local\n filesystem object-store path and an injected S3-compatible client contract,\n then verifies readback without network calls.\n- `../scripts/persistence-doctor.mjs` validates auth-session, idempotency, queue\n job, and audit persistence signals.\n- `../scripts/demo-reset.mjs` validates the reviewer demo reset contract before\n anyone attempts to run seed data against a database.\n- `../scripts/deployment-readiness.mjs` emits the local deployment readiness\n report used by `npm run deployment:doctor`.\n\nFor the droplet deployment, provide `POSTGRES_PASSWORD` from a real secret source\nor local deployment `.env` that is never committed. The compose file uses\n`${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD}` so the service fails closed instead\nof starting with an empty database password.\n\nFor Kubernetes, apply the `customcard-migrate` Job successfully before rolling\nthe web and worker Deployments. The deployment manifest keeps the live-order kill\nswitch explicit and uses probes so bad pods fail visibly.\n\nRun the local IaC readiness check before treating the manifests as reviewable:\n\n```sh\nnpm run deployment:doctor\nnpm run api:doctor\nnpm run api:doctor:memory\nnpm run api:doctor:postgres\nCUSTOMCARD_POSTGRES_INTEGRATION_DOCTOR=enabled DATABASE_URL=postgres://... npm run api:doctor:postgres:live\nCUSTOMCARD_ACCOUNT_AUTH_DOCTOR=enabled DATABASE_URL=postgres://... npm run account:doctor:live\nnpm run artifact:doctor\nnpm run persistence:doctor\nnpm run demo:doctor\n```\n\nThe report checks the local-dev, cheap-droplet, cloud-native, runtime, and data\nlanes. Passing this check means the committed deployment contracts are internally\nconsistent; it does not mean a real droplet or Kubernetes cluster has been\nprovisioned.\n\nThe Kubernetes web deployment probes `/api/health`, and the production Docker\nimage starts `scripts/api-server.mjs` so the same container can serve the static\nweb bundle and the contract-first API endpoints.\n\nThe API runtime defaults to `CUSTOMCARD_API_RUNTIME=contract` for reviewer/static\nserving. `npm run api:doctor:memory` sets test customer/admin session tokens and\nvalidates Bearer auth plus `X-Idempotency-Key` replay without a live database.\n`npm run api:doctor:postgres` injects a fake Postgres pool into the same runtime\npath and validates session lookup, wrong-role blocking, idempotency replay,\nconflict handling, repository-backed import-preview inserts, repository-backed\ncard-project inserts, manual handoff order/consent/event inserts, data-request\nprivacy/consent inserts, audit inserts, and queue-job inserts without external\ncredentials.\n`CUSTOMCARD_POSTGRES_INTEGRATION_DOCTOR=enabled npm run\napi:doctor:postgres:live` creates an isolated temporary database on the configured\nPostgres server, applies the committed migration, seeds customer/admin sessions\nand approved memory, exercises the real `pg` runtime for import-preview plus\ncard-project, manual handoff, and data-request persistence, and drops the\ntemporary database before exiting.\nCI runs that live doctor against a Postgres service; deployed production account\nauth remains unclaimed.\n\n`CUSTOMCARD_ACCOUNT_AUTH_DOCTOR=enabled npm run account:doctor:live` uses the\nsame isolated-database pattern to verify hosted account identity rows,\nprovider-subject uniqueness, hashed recovery challenges, durable sessions, and\naccount-recovery audit rows. CI runs it against the Postgres service; live hosted\ntoken verification remains unclaimed.\n\n`npm run artifact:doctor` writes the sample render packet package to a\ntemporary local filesystem object-store path and an injected S3-compatible client\ncontract, reads every artifact back, verifies checksums and byte lengths, stores\nboth handoff manifests, and keeps network calls plus real orders disabled. Cloud\nS3/MinIO writes remain unclaimed until provider credentials, bucket policy, and\ndeployment storage are tested.\n\nThe persistence boundary requires auth-session storage, account identity storage,\nhashed recovery challenges, idempotency replay, queue job envelopes,\nrender-packet artifact manifests, signed URL expiry, and append-only audit\nsignals in the migration before production Postgres handlers are claimed.\n\nThe Kubernetes `Secret` in `k8s/app.yaml` is intentionally empty and annotated as\npre-created by a secret manager. Production clusters should source the required\nkeys from the platform secret manager, External Secrets, Sealed Secrets, or an\nequivalent operator-approved mechanism. The `runtime:doctor` check rejects empty\nvalues, known placeholders, and any live-order kill switch value other than\n`disabled`.\n\nSigned artifact handoff requires `OBJECT_STORE_SIGNING_SECRET` to be present and\nat least 32 characters. The committed manifests also keep\n`ARTIFACT_SIGNED_URL_TTL_MINUTES=15` explicit so download links remain\nshort-lived.\n\nReal external ordering stays disabled with `REAL_ORDER_KILL_SWITCH=disabled` until vendor sandbox tests and physical print certification are recorded.\n\n## Provider Adapter Secrets\n\n`env/.env.example` names the credential-gated adapters represented in\n`src/providerCatalog.ts`:\n\n- Google OAuth: `GOOGLE_OAUTH_CLIENT_ID`, `GOOGLE_OAUTH_CLIENT_SECRET`.\n- Microsoft Graph: `MICROSOFT_CLIENT_ID`, `MICROSOFT_CLIENT_SECRET`,\n `MICROSOFT_TENANT_ID`.\n- Hosted identity providers: `CUSTOMCARD_AUTH_CALLBACK_URL`, `AUTH0_DOMAIN`,\n `AUTH0_CLIENT_ID`, `AUTH0_CLIENT_SECRET`, `AUTH0_AUDIENCE`,\n `CLERK_SECRET_KEY`, `CLERK_JWT_KEY`, `CLERK_AUTHORIZED_PARTIES`,\n `SUPABASE_URL`, `SUPABASE_ANON_KEY`, `SUPABASE_SERVICE_ROLE_KEY`,\n `FIREBASE_API_KEY`, `FIREBASE_PROJECT_ID`,\n `FIREBASE_SERVICE_ACCOUNT_JSON`, `COGNITO_DOMAIN`,\n `COGNITO_USER_POOL_ID`, `COGNITO_APP_CLIENT_ID`.\n- Contact import providers: `CARDDAV_BASE_URL`, `CARDDAV_USERNAME`,\n `CARDDAV_APP_PASSWORD`, `CARDDAV_ADDRESSBOOK_PATH`.\n- Text and image AI providers: `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`,\n `AZURE_OPENAI_ENDPOINT`, `AZURE_OPENAI_API_KEY`,\n `AZURE_OPENAI_CHAT_DEPLOYMENT`, `AZURE_OPENAI_IMAGE_DEPLOYMENT`,\n `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_REGION`,\n `BEDROCK_TEXT_MODEL_ID`, `BEDROCK_IMAGE_MODEL_ID`,\n `GOOGLE_GENERATIVE_AI_API_KEY`, `MISTRAL_API_KEY`, `COHERE_API_KEY`,\n `PERPLEXITY_API_KEY`, `XAI_API_KEY`, `TOGETHER_API_KEY`, `GROQ_API_KEY`,\n `DEEPSEEK_API_KEY`, `FIREWORKS_API_KEY`, `STABILITY_API_KEY`,\n `HUGGINGFACE_API_TOKEN`, `REPLICATE_API_TOKEN`, `IDEOGRAM_API_KEY`,\n `LEONARDO_API_KEY`, `FAL_KEY`, `BFL_API_KEY`.\n- Self-hosted model fallback: `SELF_HOSTED_LLM_BASE_URL`,\n `SELF_HOSTED_LLM_API_KEY`.\n- Notification providers: `RESEND_API_KEY`, `SENDGRID_API_KEY`,\n `POSTMARK_SERVER_TOKEN`, `MAILGUN_API_KEY`, `MAILGUN_DOMAIN`,\n `TWILIO_ACCOUNT_SID`, `TWILIO_AUTH_TOKEN`,\n `TWILIO_MESSAGING_SERVICE_SID`, `WHATSAPP_ACCESS_TOKEN`,\n `WHATSAPP_PHONE_NUMBER_ID`, `EXPO_ACCESS_TOKEN`,\n `TRANSACTIONAL_EMAIL_API_KEY`, `TRANSACTIONAL_EMAIL_FROM`.\n- Payment sandbox providers: `STRIPE_SECRET_KEY`, `STRIPE_WEBHOOK_SECRET`,\n `CUSTOMCARD_PAYMENT_SUCCESS_URL`, `CUSTOMCARD_PAYMENT_CANCEL_URL`,\n `PAYPAL_CLIENT_ID`, `PAYPAL_CLIENT_SECRET`, `PAYPAL_WEBHOOK_ID`,\n `SQUARE_ACCESS_TOKEN`, `SQUARE_LOCATION_ID`,\n `SQUARE_WEBHOOK_SIGNATURE_KEY`, `ADYEN_API_KEY`,\n `ADYEN_MERCHANT_ACCOUNT`, `ADYEN_HMAC_KEY`.\n- Observability providers: `SENTRY_DSN`, `SENTRY_PROJECT_ID`,\n `SENTRY_ENVIRONMENT`, `POSTHOG_PROJECT_API_KEY`, `POSTHOG_HOST`,\n `OTEL_EXPORTER_OTLP_ENDPOINT`, `OTEL_EXPORTER_OTLP_HEADERS`,\n `GRAFANA_OTLP_ENDPOINT`, `GRAFANA_OTLP_INSTANCE_ID`,\n `GRAFANA_OTLP_API_KEY`, `DATADOG_API_KEY`, `DATADOG_SITE`,\n `BETTERSTACK_SOURCE_TOKEN`, `BETTERSTACK_INGESTING_HOST`.\n- Object-store artifact signer: `OBJECT_STORE_SIGNING_SECRET`.\n- Live vendor adapters: `WALGREENS_VENDOR_MODE`, `CVS_VENDOR_MODE`,\n `FEDEX_VENDOR_MODE`, `WALMART_VENDOR_MODE`, `STAPLES_VENDOR_MODE`,\n `OFFICE_DEPOT_VENDOR_MODE`.\n- Persistence controls: `CUSTOMCARD_API_RUNTIME`, `AUTH_SESSION_SECRET`,\n `CUSTOMCARD_CUSTOMER_SESSION_TOKEN`, `CUSTOMCARD_ADMIN_SESSION_TOKEN`,\n `IDEMPOTENCY_KEY_TTL_HOURS`.\n\nThese keys are documented for deployment readiness only. The current repo state\ndoes not make live provider calls, and vendor modes remain\n`disabled_until_certified` while `REAL_ORDER_KILL_SWITCH=disabled`.\n"}, "457635c0ec2e3e977a57889a448bd5f63733d52f": {"t": 1785772148.6082513, "n": 8999, "h": "f4d728d163e8d424", "c": "# CustomCard Infrastructure\n\nThis directory is the deployable service skeleton for the production path.\n\n- `docker-compose.dev.yml` runs app, worker, Postgres, Redis, and MinIO for local development.\n- `docker-compose.droplet.yml` is the cheap single-host deployment shape for a small DigitalOcean droplet.\n- `k8s/app.yaml` is the cloud-native starting point for web and worker deployments.\n- `migrations/001_initial_schema.sql` defines the durable service data model.\n- `env/.env.example` lists required secrets and kill switches.\n- `../scripts/api-server.mjs` serves `/api/health`, API bootstrap/readiness\n contracts, explicit contract/memory/Postgres runtime modes, and the built web\n app from `dist`.\n- `../src/artifactHandoff.ts` defines the render-packet artifact manifest and\n HMAC-signed URL contract used by the API/schema gates.\n- `../src/artifactStore.ts` writes render-packet artifacts to a temporary local\n filesystem object-store path and an injected S3-compatible client contract,\n then verifies readback without network calls.\n- `../scripts/persistence-doctor.mjs` validates auth-session, idempotency, queue\n job, and audit persistence signals.\n- `../scripts/demo-reset.mjs` validates the reviewer demo reset contract before\n anyone attempts to run seed data against a database.\n- `../scripts/deployment-readiness.mjs` emits the local deployment readiness\n report used by `npm run deployment:doctor`.\n\nFor the droplet deployment, provide `POSTGRES_PASSWORD` from a real secret source\nor local deployment `.env` that is never committed. The compose file uses\n`${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD}` so the service fails closed instead\nof starting with an empty database password.\n\nFor Kubernetes, apply the `customcard-migrate` Job successfully before rolling\nthe web and worker Deployments. The deployment manifest keeps the live-order kill\nswitch explicit and uses probes so bad pods fail visibly.\n\nRun the local IaC readiness check before treating the manifests as reviewable:\n\n```sh\nnpm run deployment:doctor\nnpm run api:doctor\nnpm run api:doctor:memory\nnpm run api:doctor:postgres\nCUSTOMCARD_POSTGRES_INTEGRATION_DOCTOR=enabled DATABASE_URL=postgres://... npm run api:doctor:postgres:live\nCUSTOMCARD_ACCOUNT_AUTH_DOCTOR=enabled DATABASE_URL=postgres://... npm run account:doctor:live\nnpm run artifact:doctor\nnpm run persistence:doctor\nnpm run demo:doctor\n```\n\nThe report checks the local-dev, cheap-droplet, cloud-native, runtime, and data\nlanes. Passing this check means the committed deployment contracts are internally\nconsistent; it does not mean a real droplet or Kubernetes cluster has been\nprovisioned.\n\nThe Kubernetes web deployment probes `/api/health`, and the production Docker\nimage starts `scripts/api-server.mjs` so the same container can serve the static\nweb bundle and the contract-first API endpoints.\n\nThe API runtime defaults to `CUSTOMCARD_API_RUNTIME=contract` for reviewer/static\nserving. `npm run api:doctor:memory` sets test customer/admin session tokens and\nvalidates Bearer auth plus `X-Idempotency-Key` replay without a live database.\n`npm run api:doctor:postgres` injects a fake Postgres pool into the same runtime\npath and validates session lookup, wrong-role blocking, idempotency replay,\nconflict handling, repository-backed import-preview inserts, repository-backed\ncard-project inserts, audit inserts, and queue-job inserts without external\ncredentials.\n`CUSTOMCARD_POSTGRES_INTEGRATION_DOCTOR=enabled npm run\napi:doctor:postgres:live` creates an isolated temporary database on the configured\nPostgres server, applies the committed migration, seeds customer/admin sessions\nand approved memory, exercises the real `pg` runtime for import-preview plus\ncard-project persistence, and drops the temporary database before exiting.\nCI runs that live doctor against a Postgres service; deployed production account\nauth remains unclaimed.\n\n`CUSTOMCARD_ACCOUNT_AUTH_DOCTOR=enabled npm run account:doctor:live` uses the\nsame isolated-database pattern to verify hosted account identity rows,\nprovider-subject uniqueness, hashed recovery challenges, durable sessions, and\naccount-recovery audit rows. CI runs it against the Postgres service; live hosted\ntoken verification remains unclaimed.\n\n`npm run artifact:doctor` writes the sample render packet package to a\ntemporary local filesystem object-store path and an injected S3-compatible client\ncontract, reads every artifact back, verifies checksums and byte lengths, stores\nboth handoff manifests, and keeps network calls plus real orders disabled. Cloud\nS3/MinIO writes remain unclaimed until provider credentials, bucket policy, and\ndeployment storage are tested.\n\nThe persistence boundary requires auth-session storage, account identity storage,\nhashed recovery challenges, idempotency replay, queue job envelopes,\nrender-packet artifact manifests, signed URL expiry, and append-only audit\nsignals in the migration before production Postgres handlers are claimed.\n\nThe Kubernetes `Secret` in `k8s/app.yaml` is intentionally empty and annotated as\npre-created by a secret manager. Production clusters should source the required\nkeys from the platform secret manager, External Secrets, Sealed Secrets, or an\nequivalent operator-approved mechanism. The `runtime:doctor` check rejects empty\nvalues, known placeholders, and any live-order kill switch value other than\n`disabled`.\n\nSigned artifact handoff requires `OBJECT_STORE_SIGNING_SECRET` to be present and\nat least 32 characters. The committed manifests also keep\n`ARTIFACT_SIGNED_URL_TTL_MINUTES=15` explicit so download links remain\nshort-lived.\n\nReal external ordering stays disabled with `REAL_ORDER_KILL_SWITCH=disabled` until vendor sandbox tests and physical print certification are recorded.\n\n## Provider Adapter Secrets\n\n`env/.env.example` names the credential-gated adapters represented in\n`src/providerCatalog.ts`:\n\n- Google OAuth: `GOOGLE_OAUTH_CLIENT_ID`, `GOOGLE_OAUTH_CLIENT_SECRET`.\n- Microsoft Graph: `MICROSOFT_CLIENT_ID`, `MICROSOFT_CLIENT_SECRET`,\n `MICROSOFT_TENANT_ID`.\n- Hosted identity providers: `CUSTOMCARD_AUTH_CALLBACK_URL`, `AUTH0_DOMAIN`,\n `AUTH0_CLIENT_ID`, `AUTH0_CLIENT_SECRET`, `AUTH0_AUDIENCE`,\n `CLERK_SECRET_KEY`, `CLERK_JWT_KEY`, `CLERK_AUTHORIZED_PARTIES`,\n `SUPABASE_URL`, `SUPABASE_ANON_KEY`, `SUPABASE_SERVICE_ROLE_KEY`,\n `FIREBASE_API_KEY`, `FIREBASE_PROJECT_ID`,\n `FIREBASE_SERVICE_ACCOUNT_JSON`, `COGNITO_DOMAIN`,\n `COGNITO_USER_POOL_ID`, `COGNITO_APP_CLIENT_ID`.\n- Contact import providers: `CARDDAV_BASE_URL`, `CARDDAV_USERNAME`,\n `CARDDAV_APP_PASSWORD`, `CARDDAV_ADDRESSBOOK_PATH`.\n- Text and image AI providers: `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`,\n `AZURE_OPENAI_ENDPOINT`, `AZURE_OPENAI_API_KEY`,\n `AZURE_OPENAI_CHAT_DEPLOYMENT`, `AZURE_OPENAI_IMAGE_DEPLOYMENT`,\n `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_REGION`,\n `BEDROCK_TEXT_MODEL_ID`, `BEDROCK_IMAGE_MODEL_ID`,\n `GOOGLE_GENERATIVE_AI_API_KEY`, `MISTRAL_API_KEY`, `COHERE_API_KEY`,\n `PERPLEXITY_API_KEY`, `XAI_API_KEY`, `TOGETHER_API_KEY`, `GROQ_API_KEY`,\n `DEEPSEEK_API_KEY`, `FIREWORKS_API_KEY`, `STABILITY_API_KEY`,\n `HUGGINGFACE_API_TOKEN`, `REPLICATE_API_TOKEN`, `IDEOGRAM_API_KEY`,\n `LEONARDO_API_KEY`, `FAL_KEY`, `BFL_API_KEY`.\n- Self-hosted model fallback: `SELF_HOSTED_LLM_BASE_URL`,\n `SELF_HOSTED_LLM_API_KEY`.\n- Notification providers: `RESEND_API_KEY`, `SENDGRID_API_KEY`,\n `POSTMARK_SERVER_TOKEN`, `MAILGUN_API_KEY`, `MAILGUN_DOMAIN`,\n `TWILIO_ACCOUNT_SID`, `TWILIO_AUTH_TOKEN`,\n `TWILIO_MESSAGING_SERVICE_SID`, `WHATSAPP_ACCESS_TOKEN`,\n `WHATSAPP_PHONE_NUMBER_ID`, `EXPO_ACCESS_TOKEN`,\n `TRANSACTIONAL_EMAIL_API_KEY`, `TRANSACTIONAL_EMAIL_FROM`.\n- Payment sandbox providers: `STRIPE_SECRET_KEY`, `STRIPE_WEBHOOK_SECRET`,\n `CUSTOMCARD_PAYMENT_SUCCESS_URL`, `CUSTOMCARD_PAYMENT_CANCEL_URL`,\n `PAYPAL_CLIENT_ID`, `PAYPAL_CLIENT_SECRET`, `PAYPAL_WEBHOOK_ID`,\n `SQUARE_ACCESS_TOKEN`, `SQUARE_LOCATION_ID`,\n `SQUARE_WEBHOOK_SIGNATURE_KEY`, `ADYEN_API_KEY`,\n `ADYEN_MERCHANT_ACCOUNT`, `ADYEN_HMAC_KEY`.\n- Observability providers: `SENTRY_DSN`, `SENTRY_PROJECT_ID`,\n `SENTRY_ENVIRONMENT`, `POSTHOG_PROJECT_API_KEY`, `POSTHOG_HOST`,\n `OTEL_EXPORTER_OTLP_ENDPOINT`, `OTEL_EXPORTER_OTLP_HEADERS`,\n `GRAFANA_OTLP_ENDPOINT`, `GRAFANA_OTLP_INSTANCE_ID`,\n `GRAFANA_OTLP_API_KEY`, `DATADOG_API_KEY`, `DATADOG_SITE`,\n `BETTERSTACK_SOURCE_TOKEN`, `BETTERSTACK_INGESTING_HOST`.\n- Object-store artifact signer: `OBJECT_STORE_SIGNING_SECRET`.\n- Live vendor adapters: `WALGREENS_VENDOR_MODE`, `CVS_VENDOR_MODE`,\n `FEDEX_VENDOR_MODE`, `WALMART_VENDOR_MODE`, `STAPLES_VENDOR_MODE`,\n `OFFICE_DEPOT_VENDOR_MODE`.\n- Persistence controls: `CUSTOMCARD_API_RUNTIME`, `AUTH_SESSION_SECRET`,\n `CUSTOMCARD_CUSTOMER_SESSION_TOKEN`, `CUSTOMCARD_ADMIN_SESSION_TOKEN`,\n `IDEMPOTENCY_KEY_TTL_HOURS`.\n\nThese keys are documented for deployment readiness only. The current repo state\ndoes not make live provider calls, and vendor modes remain\n`disabled_until_certified` while `REAL_ORDER_KILL_SWITCH=disabled`.\n"}, "0626775bb4c1d1113743b42c3c5b0b048053b196": {"t": 1785772151.2486517, "n": 8918, "h": "a8edd2ae71b2a0a7", "c": "# CustomCard Infrastructure\n\nThis directory is the deployable service skeleton for the production path.\n\n- `docker-compose.dev.yml` runs app, worker, Postgres, Redis, and MinIO for local development.\n- `docker-compose.droplet.yml` is the cheap single-host deployment shape for a small DigitalOcean droplet.\n- `k8s/app.yaml` is the cloud-native starting point for web and worker deployments.\n- `migrations/001_initial_schema.sql` defines the durable service data model.\n- `env/.env.example` lists required secrets and kill switches.\n- `../scripts/api-server.mjs` serves `/api/health`, API bootstrap/readiness\n contracts, explicit contract/memory/Postgres runtime modes, and the built web\n app from `dist`.\n- `../src/artifactHandoff.ts` defines the render-packet artifact manifest and\n HMAC-signed URL contract used by the API/schema gates.\n- `../src/artifactStore.ts` writes render-packet artifacts to a temporary local\n filesystem object-store path and an injected S3-compatible client contract,\n then verifies readback without network calls.\n- `../scripts/persistence-doctor.mjs` validates auth-session, idempotency, queue\n job, and audit persistence signals.\n- `../scripts/demo-reset.mjs` validates the reviewer demo reset contract before\n anyone attempts to run seed data against a database.\n- `../scripts/deployment-readiness.mjs` emits the local deployment readiness\n report used by `npm run deployment:doctor`.\n\nFor the droplet deployment, provide `POSTGRES_PASSWORD` from a real secret source\nor local deployment `.env` that is never committed. The compose file uses\n`${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD}` so the service fails closed instead\nof starting with an empty database password.\n\nFor Kubernetes, apply the `customcard-migrate` Job successfully before rolling\nthe web and worker Deployments. The deployment manifest keeps the live-order kill\nswitch explicit and uses probes so bad pods fail visibly.\n\nRun the local IaC readiness check before treating the manifests as reviewable:\n\n```sh\nnpm run deployment:doctor\nnpm run api:doctor\nnpm run api:doctor:memory\nnpm run api:doctor:postgres\nCUSTOMCARD_POSTGRES_INTEGRATION_DOCTOR=enabled DATABASE_URL=postgres://... npm run api:doctor:postgres:live\nCUSTOMCARD_ACCOUNT_AUTH_DOCTOR=enabled DATABASE_URL=postgres://... npm run account:doctor:live\nnpm run artifact:doctor\nnpm run persistence:doctor\nnpm run demo:doctor\n```\n\nThe report checks the local-dev, cheap-droplet, cloud-native, runtime, and data\nlanes. Passing this check means the committed deployment contracts are internally\nconsistent; it does not mean a real droplet or Kubernetes cluster has been\nprovisioned.\n\nThe Kubernetes web deployment probes `/api/health`, and the production Docker\nimage starts `scripts/api-server.mjs` so the same container can serve the static\nweb bundle and the contract-first API endpoints.\n\nThe API runtime defaults to `CUSTOMCARD_API_RUNTIME=contract` for reviewer/static\nserving. `npm run api:doctor:memory` sets test customer/admin session tokens and\nvalidates Bearer auth plus `X-Idempotency-Key` replay without a live database.\n`npm run api:doctor:postgres` injects a fake Postgres pool into the same runtime\npath and validates session lookup, wrong-role blocking, idempotency replay,\nconflict handling, repository-backed card-project inserts, audit inserts, and\nqueue-job inserts without external credentials.\n`CUSTOMCARD_POSTGRES_INTEGRATION_DOCTOR=enabled npm run\napi:doctor:postgres:live` creates an isolated temporary database on the configured\nPostgres server, applies the committed migration, seeds customer/admin sessions\nand card-project dependencies, exercises the real `pg` runtime, and drops the\ntemporary database before exiting.\nCI runs that live doctor against a Postgres service; deployed production account\nauth remains unclaimed.\n\n`CUSTOMCARD_ACCOUNT_AUTH_DOCTOR=enabled npm run account:doctor:live` uses the\nsame isolated-database pattern to verify hosted account identity rows,\nprovider-subject uniqueness, hashed recovery challenges, durable sessions, and\naccount-recovery audit rows. CI runs it against the Postgres service; live hosted\ntoken verification remains unclaimed.\n\n`npm run artifact:doctor` writes the sample render packet package to a\ntemporary local filesystem object-store path and an injected S3-compatible client\ncontract, reads every artifact back, verifies checksums and byte lengths, stores\nboth handoff manifests, and keeps network calls plus real orders disabled. Cloud\nS3/MinIO writes remain unclaimed until provider credentials, bucket policy, and\ndeployment storage are tested.\n\nThe persistence boundary requires auth-session storage, account identity storage,\nhashed recovery challenges, idempotency replay, queue job envelopes,\nrender-packet artifact manifests, signed URL expiry, and append-only audit\nsignals in the migration before production Postgres handlers are claimed.\n\nThe Kubernetes `Secret` in `k8s/app.yaml` is intentionally empty and annotated as\npre-created by a secret manager. Production clusters should source the required\nkeys from the platform secret manager, External Secrets, Sealed Secrets, or an\nequivalent operator-approved mechanism. The `runtime:doctor` check rejects empty\nvalues, known placeholders, and any live-order kill switch value other than\n`disabled`.\n\nSigned artifact handoff requires `OBJECT_STORE_SIGNING_SECRET` to be present and\nat least 32 characters. The committed manifests also keep\n`ARTIFACT_SIGNED_URL_TTL_MINUTES=15` explicit so download links remain\nshort-lived.\n\nReal external ordering stays disabled with `REAL_ORDER_KILL_SWITCH=disabled` until vendor sandbox tests and physical print certification are recorded.\n\n## Provider Adapter Secrets\n\n`env/.env.example` names the credential-gated adapters represented in\n`src/providerCatalog.ts`:\n\n- Google OAuth: `GOOGLE_OAUTH_CLIENT_ID`, `GOOGLE_OAUTH_CLIENT_SECRET`.\n- Microsoft Graph: `MICROSOFT_CLIENT_ID`, `MICROSOFT_CLIENT_SECRET`,\n `MICROSOFT_TENANT_ID`.\n- Hosted identity providers: `CUSTOMCARD_AUTH_CALLBACK_URL`, `AUTH0_DOMAIN`,\n `AUTH0_CLIENT_ID`, `AUTH0_CLIENT_SECRET`, `AUTH0_AUDIENCE`,\n `CLERK_SECRET_KEY`, `CLERK_JWT_KEY`, `CLERK_AUTHORIZED_PARTIES`,\n `SUPABASE_URL`, `SUPABASE_ANON_KEY`, `SUPABASE_SERVICE_ROLE_KEY`,\n `FIREBASE_API_KEY`, `FIREBASE_PROJECT_ID`,\n `FIREBASE_SERVICE_ACCOUNT_JSON`, `COGNITO_DOMAIN`,\n `COGNITO_USER_POOL_ID`, `COGNITO_APP_CLIENT_ID`.\n- Contact import providers: `CARDDAV_BASE_URL`, `CARDDAV_USERNAME`,\n `CARDDAV_APP_PASSWORD`, `CARDDAV_ADDRESSBOOK_PATH`.\n- Text and image AI providers: `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`,\n `AZURE_OPENAI_ENDPOINT`, `AZURE_OPENAI_API_KEY`,\n `AZURE_OPENAI_CHAT_DEPLOYMENT`, `AZURE_OPENAI_IMAGE_DEPLOYMENT`,\n `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_REGION`,\n `BEDROCK_TEXT_MODEL_ID`, `BEDROCK_IMAGE_MODEL_ID`,\n `GOOGLE_GENERATIVE_AI_API_KEY`, `MISTRAL_API_KEY`, `COHERE_API_KEY`,\n `PERPLEXITY_API_KEY`, `XAI_API_KEY`, `TOGETHER_API_KEY`, `GROQ_API_KEY`,\n `DEEPSEEK_API_KEY`, `FIREWORKS_API_KEY`, `STABILITY_API_KEY`,\n `HUGGINGFACE_API_TOKEN`, `REPLICATE_API_TOKEN`, `IDEOGRAM_API_KEY`,\n `LEONARDO_API_KEY`, `FAL_KEY`, `BFL_API_KEY`.\n- Self-hosted model fallback: `SELF_HOSTED_LLM_BASE_URL`,\n `SELF_HOSTED_LLM_API_KEY`.\n- Notification providers: `RESEND_API_KEY`, `SENDGRID_API_KEY`,\n `POSTMARK_SERVER_TOKEN`, `MAILGUN_API_KEY`, `MAILGUN_DOMAIN`,\n `TWILIO_ACCOUNT_SID`, `TWILIO_AUTH_TOKEN`,\n `TWILIO_MESSAGING_SERVICE_SID`, `WHATSAPP_ACCESS_TOKEN`,\n `WHATSAPP_PHONE_NUMBER_ID`, `EXPO_ACCESS_TOKEN`,\n `TRANSACTIONAL_EMAIL_API_KEY`, `TRANSACTIONAL_EMAIL_FROM`.\n- Payment sandbox providers: `STRIPE_SECRET_KEY`, `STRIPE_WEBHOOK_SECRET`,\n `CUSTOMCARD_PAYMENT_SUCCESS_URL`, `CUSTOMCARD_PAYMENT_CANCEL_URL`,\n `PAYPAL_CLIENT_ID`, `PAYPAL_CLIENT_SECRET`, `PAYPAL_WEBHOOK_ID`,\n `SQUARE_ACCESS_TOKEN`, `SQUARE_LOCATION_ID`,\n `SQUARE_WEBHOOK_SIGNATURE_KEY`, `ADYEN_API_KEY`,\n `ADYEN_MERCHANT_ACCOUNT`, `ADYEN_HMAC_KEY`.\n- Observability providers: `SENTRY_DSN`, `SENTRY_PROJECT_ID`,\n `SENTRY_ENVIRONMENT`, `POSTHOG_PROJECT_API_KEY`, `POSTHOG_HOST`,\n `OTEL_EXPORTER_OTLP_ENDPOINT`, `OTEL_EXPORTER_OTLP_HEADERS`,\n `GRAFANA_OTLP_ENDPOINT`, `GRAFANA_OTLP_INSTANCE_ID`,\n `GRAFANA_OTLP_API_KEY`, `DATADOG_API_KEY`, `DATADOG_SITE`,\n `BETTERSTACK_SOURCE_TOKEN`, `BETTERSTACK_INGESTING_HOST`.\n- Object-store artifact signer: `OBJECT_STORE_SIGNING_SECRET`.\n- Live vendor adapters: `WALGREENS_VENDOR_MODE`, `CVS_VENDOR_MODE`,\n `FEDEX_VENDOR_MODE`, `WALMART_VENDOR_MODE`, `STAPLES_VENDOR_MODE`,\n `OFFICE_DEPOT_VENDOR_MODE`.\n- Persistence controls: `CUSTOMCARD_API_RUNTIME`, `AUTH_SESSION_SECRET`,\n `CUSTOMCARD_CUSTOMER_SESSION_TOKEN`, `CUSTOMCARD_ADMIN_SESSION_TOKEN`,\n `IDEMPOTENCY_KEY_TTL_HOURS`.\n\nThese keys are documented for deployment readiness only. The current repo state\ndoes not make live provider calls, and vendor modes remain\n`disabled_until_certified` while `REAL_ORDER_KILL_SWITCH=disabled`.\n"}, "6df4e0e9da473588b02e3f46011b526fe08326dc": {"t": 1785772152.4475658, "n": 8848, "h": "c383d73411a8cf02", "c": "# CustomCard Infrastructure\n\nThis directory is the deployable service skeleton for the production path.\n\n- `docker-compose.dev.yml` runs app, worker, Postgres, Redis, and MinIO for local development.\n- `docker-compose.droplet.yml` is the cheap single-host deployment shape for a small DigitalOcean droplet.\n- `k8s/app.yaml` is the cloud-native starting point for web and worker deployments.\n- `migrations/001_initial_schema.sql` defines the durable service data model.\n- `env/.env.example` lists required secrets and kill switches.\n- `../scripts/api-server.mjs` serves `/api/health`, API bootstrap/readiness\n contracts, explicit contract/memory/Postgres runtime modes, and the built web\n app from `dist`.\n- `../src/artifactHandoff.ts` defines the render-packet artifact manifest and\n HMAC-signed URL contract used by the API/schema gates.\n- `../src/artifactStore.ts` writes render-packet artifacts to a temporary local\n filesystem object-store path and an injected S3-compatible client contract,\n then verifies readback without network calls.\n- `../scripts/persistence-doctor.mjs` validates auth-session, idempotency, queue\n job, and audit persistence signals.\n- `../scripts/demo-reset.mjs` validates the reviewer demo reset contract before\n anyone attempts to run seed data against a database.\n- `../scripts/deployment-readiness.mjs` emits the local deployment readiness\n report used by `npm run deployment:doctor`.\n\nFor the droplet deployment, provide `POSTGRES_PASSWORD` from a real secret source\nor local deployment `.env` that is never committed. The compose file uses\n`${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD}` so the service fails closed instead\nof starting with an empty database password.\n\nFor Kubernetes, apply the `customcard-migrate` Job successfully before rolling\nthe web and worker Deployments. The deployment manifest keeps the live-order kill\nswitch explicit and uses probes so bad pods fail visibly.\n\nRun the local IaC readiness check before treating the manifests as reviewable:\n\n```sh\nnpm run deployment:doctor\nnpm run api:doctor\nnpm run api:doctor:memory\nnpm run api:doctor:postgres\nCUSTOMCARD_POSTGRES_INTEGRATION_DOCTOR=enabled DATABASE_URL=postgres://... npm run api:doctor:postgres:live\nCUSTOMCARD_ACCOUNT_AUTH_DOCTOR=enabled DATABASE_URL=postgres://... npm run account:doctor:live\nnpm run artifact:doctor\nnpm run persistence:doctor\nnpm run demo:doctor\n```\n\nThe report checks the local-dev, cheap-droplet, cloud-native, runtime, and data\nlanes. Passing this check means the committed deployment contracts are internally\nconsistent; it does not mean a real droplet or Kubernetes cluster has been\nprovisioned.\n\nThe Kubernetes web deployment probes `/api/health`, and the production Docker\nimage starts `scripts/api-server.mjs` so the same container can serve the static\nweb bundle and the contract-first API endpoints.\n\nThe API runtime defaults to `CUSTOMCARD_API_RUNTIME=contract` for reviewer/static\nserving. `npm run api:doctor:memory` sets test customer/admin session tokens and\nvalidates Bearer auth plus `X-Idempotency-Key` replay without a live database.\n`npm run api:doctor:postgres` injects a fake Postgres pool into the same runtime\npath and validates session lookup, wrong-role blocking, idempotency replay,\nconflict handling, audit inserts, and queue-job inserts without external\ncredentials. `CUSTOMCARD_POSTGRES_INTEGRATION_DOCTOR=enabled npm run\napi:doctor:postgres:live` creates an isolated temporary database on the configured\nPostgres server, applies the committed migration, seeds customer/admin sessions,\nexercises the real `pg` runtime, and drops the temporary database before exiting.\nCI runs that live doctor against a Postgres service; deployed production account\nauth remains unclaimed.\n\n`CUSTOMCARD_ACCOUNT_AUTH_DOCTOR=enabled npm run account:doctor:live` uses the\nsame isolated-database pattern to verify hosted account identity rows,\nprovider-subject uniqueness, hashed recovery challenges, durable sessions, and\naccount-recovery audit rows. CI runs it against the Postgres service; live hosted\ntoken verification remains unclaimed.\n\n`npm run artifact:doctor` writes the sample render packet package to a\ntemporary local filesystem object-store path and an injected S3-compatible client\ncontract, reads every artifact back, verifies checksums and byte lengths, stores\nboth handoff manifests, and keeps network calls plus real orders disabled. Cloud\nS3/MinIO writes remain unclaimed until provider credentials, bucket policy, and\ndeployment storage are tested.\n\nThe persistence boundary requires auth-session storage, account identity storage,\nhashed recovery challenges, idempotency replay, queue job envelopes,\nrender-packet artifact manifests, signed URL expiry, and append-only audit\nsignals in the migration before production Postgres handlers are claimed.\n\nThe Kubernetes `Secret` in `k8s/app.yaml` is intentionally empty and annotated as\npre-created by a secret manager. Production clusters should source the required\nkeys from the platform secret manager, External Secrets, Sealed Secrets, or an\nequivalent operator-approved mechanism. The `runtime:doctor` check rejects empty\nvalues, known placeholders, and any live-order kill switch value other than\n`disabled`.\n\nSigned artifact handoff requires `OBJECT_STORE_SIGNING_SECRET` to be present and\nat least 32 characters. The committed manifests also keep\n`ARTIFACT_SIGNED_URL_TTL_MINUTES=15` explicit so download links remain\nshort-lived.\n\nReal external ordering stays disabled with `REAL_ORDER_KILL_SWITCH=disabled` until vendor sandbox tests and physical print certification are recorded.\n\n## Provider Adapter Secrets\n\n`env/.env.example` names the credential-gated adapters represented in\n`src/providerCatalog.ts`:\n\n- Google OAuth: `GOOGLE_OAUTH_CLIENT_ID`, `GOOGLE_OAUTH_CLIENT_SECRET`.\n- Microsoft Graph: `MICROSOFT_CLIENT_ID`, `MICROSOFT_CLIENT_SECRET`,\n `MICROSOFT_TENANT_ID`.\n- Hosted identity providers: `CUSTOMCARD_AUTH_CALLBACK_URL`, `AUTH0_DOMAIN`,\n `AUTH0_CLIENT_ID`, `AUTH0_CLIENT_SECRET`, `AUTH0_AUDIENCE`,\n `CLERK_SECRET_KEY`, `CLERK_JWT_KEY`, `CLERK_AUTHORIZED_PARTIES`,\n `SUPABASE_URL`, `SUPABASE_ANON_KEY`, `SUPABASE_SERVICE_ROLE_KEY`,\n `FIREBASE_API_KEY`, `FIREBASE_PROJECT_ID`,\n `FIREBASE_SERVICE_ACCOUNT_JSON`, `COGNITO_DOMAIN`,\n `COGNITO_USER_POOL_ID`, `COGNITO_APP_CLIENT_ID`.\n- Contact import providers: `CARDDAV_BASE_URL`, `CARDDAV_USERNAME`,\n `CARDDAV_APP_PASSWORD`, `CARDDAV_ADDRESSBOOK_PATH`.\n- Text and image AI providers: `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`,\n `AZURE_OPENAI_ENDPOINT`, `AZURE_OPENAI_API_KEY`,\n `AZURE_OPENAI_CHAT_DEPLOYMENT`, `AZURE_OPENAI_IMAGE_DEPLOYMENT`,\n `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_REGION`,\n `BEDROCK_TEXT_MODEL_ID`, `BEDROCK_IMAGE_MODEL_ID`,\n `GOOGLE_GENERATIVE_AI_API_KEY`, `MISTRAL_API_KEY`, `COHERE_API_KEY`,\n `PERPLEXITY_API_KEY`, `XAI_API_KEY`, `TOGETHER_API_KEY`, `GROQ_API_KEY`,\n `DEEPSEEK_API_KEY`, `FIREWORKS_API_KEY`, `STABILITY_API_KEY`,\n `HUGGINGFACE_API_TOKEN`, `REPLICATE_API_TOKEN`, `IDEOGRAM_API_KEY`,\n `LEONARDO_API_KEY`, `FAL_KEY`, `BFL_API_KEY`.\n- Self-hosted model fallback: `SELF_HOSTED_LLM_BASE_URL`,\n `SELF_HOSTED_LLM_API_KEY`.\n- Notification providers: `RESEND_API_KEY`, `SENDGRID_API_KEY`,\n `POSTMARK_SERVER_TOKEN`, `MAILGUN_API_KEY`, `MAILGUN_DOMAIN`,\n `TWILIO_ACCOUNT_SID`, `TWILIO_AUTH_TOKEN`,\n `TWILIO_MESSAGING_SERVICE_SID`, `WHATSAPP_ACCESS_TOKEN`,\n `WHATSAPP_PHONE_NUMBER_ID`, `EXPO_ACCESS_TOKEN`,\n `TRANSACTIONAL_EMAIL_API_KEY`, `TRANSACTIONAL_EMAIL_FROM`.\n- Payment sandbox providers: `STRIPE_SECRET_KEY`, `STRIPE_WEBHOOK_SECRET`,\n `CUSTOMCARD_PAYMENT_SUCCESS_URL`, `CUSTOMCARD_PAYMENT_CANCEL_URL`,\n `PAYPAL_CLIENT_ID`, `PAYPAL_CLIENT_SECRET`, `PAYPAL_WEBHOOK_ID`,\n `SQUARE_ACCESS_TOKEN`, `SQUARE_LOCATION_ID`,\n `SQUARE_WEBHOOK_SIGNATURE_KEY`, `ADYEN_API_KEY`,\n `ADYEN_MERCHANT_ACCOUNT`, `ADYEN_HMAC_KEY`.\n- Observability providers: `SENTRY_DSN`, `SENTRY_PROJECT_ID`,\n `SENTRY_ENVIRONMENT`, `POSTHOG_PROJECT_API_KEY`, `POSTHOG_HOST`,\n `OTEL_EXPORTER_OTLP_ENDPOINT`, `OTEL_EXPORTER_OTLP_HEADERS`,\n `GRAFANA_OTLP_ENDPOINT`, `GRAFANA_OTLP_INSTANCE_ID`,\n `GRAFANA_OTLP_API_KEY`, `DATADOG_API_KEY`, `DATADOG_SITE`,\n `BETTERSTACK_SOURCE_TOKEN`, `BETTERSTACK_INGESTING_HOST`.\n- Object-store artifact signer: `OBJECT_STORE_SIGNING_SECRET`.\n- Live vendor adapters: `WALGREENS_VENDOR_MODE`, `CVS_VENDOR_MODE`,\n `FEDEX_VENDOR_MODE`, `WALMART_VENDOR_MODE`, `STAPLES_VENDOR_MODE`,\n `OFFICE_DEPOT_VENDOR_MODE`.\n- Persistence controls: `CUSTOMCARD_API_RUNTIME`, `AUTH_SESSION_SECRET`,\n `CUSTOMCARD_CUSTOMER_SESSION_TOKEN`, `CUSTOMCARD_ADMIN_SESSION_TOKEN`,\n `IDEMPOTENCY_KEY_TTL_HOURS`.\n\nThese keys are documented for deployment readiness only. The current repo state\ndoes not make live provider calls, and vendor modes remain\n`disabled_until_certified` while `REAL_ORDER_KILL_SWITCH=disabled`.\n"}, "b04a1aa6d5065037cc1272af85dd0b9ceaf3e63c": {"t": 1785772153.7531376, "n": 8750, "h": "c9b68381d33918fc", "c": "# CustomCard Infrastructure\n\nThis directory is the deployable service skeleton for the production path.\n\n- `docker-compose.dev.yml` runs app, worker, Postgres, Redis, and MinIO for local development.\n- `docker-compose.droplet.yml` is the cheap single-host deployment shape for a small DigitalOcean droplet.\n- `k8s/app.yaml` is the cloud-native starting point for web and worker deployments.\n- `migrations/001_initial_schema.sql` defines the durable service data model.\n- `env/.env.example` lists required secrets and kill switches.\n- `../scripts/api-server.mjs` serves `/api/health`, API bootstrap/readiness\n contracts, explicit contract/memory/Postgres runtime modes, and the built web\n app from `dist`.\n- `../src/artifactHandoff.ts` defines the render-packet artifact manifest and\n HMAC-signed URL contract used by the API/schema gates.\n- `../src/artifactStore.ts` writes render-packet artifacts to a temporary local\n filesystem object-store path and verifies readback without network calls.\n- `../scripts/persistence-doctor.mjs` validates auth-session, idempotency, queue\n job, and audit persistence signals.\n- `../scripts/demo-reset.mjs` validates the reviewer demo reset contract before\n anyone attempts to run seed data against a database.\n- `../scripts/deployment-readiness.mjs` emits the local deployment readiness\n report used by `npm run deployment:doctor`.\n\nFor the droplet deployment, provide `POSTGRES_PASSWORD` from a real secret source\nor local deployment `.env` that is never committed. The compose file uses\n`${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD}` so the service fails closed instead\nof starting with an empty database password.\n\nFor Kubernetes, apply the `customcard-migrate` Job successfully before rolling\nthe web and worker Deployments. The deployment manifest keeps the live-order kill\nswitch explicit and uses probes so bad pods fail visibly.\n\nRun the local IaC readiness check before treating the manifests as reviewable:\n\n```sh\nnpm run deployment:doctor\nnpm run api:doctor\nnpm run api:doctor:memory\nnpm run api:doctor:postgres\nCUSTOMCARD_POSTGRES_INTEGRATION_DOCTOR=enabled DATABASE_URL=postgres://... npm run api:doctor:postgres:live\nCUSTOMCARD_ACCOUNT_AUTH_DOCTOR=enabled DATABASE_URL=postgres://... npm run account:doctor:live\nnpm run artifact:doctor\nnpm run persistence:doctor\nnpm run demo:doctor\n```\n\nThe report checks the local-dev, cheap-droplet, cloud-native, runtime, and data\nlanes. Passing this check means the committed deployment contracts are internally\nconsistent; it does not mean a real droplet or Kubernetes cluster has been\nprovisioned.\n\nThe Kubernetes web deployment probes `/api/health`, and the production Docker\nimage starts `scripts/api-server.mjs` so the same container can serve the static\nweb bundle and the contract-first API endpoints.\n\nThe API runtime defaults to `CUSTOMCARD_API_RUNTIME=contract` for reviewer/static\nserving. `npm run api:doctor:memory` sets test customer/admin session tokens and\nvalidates Bearer auth plus `X-Idempotency-Key` replay without a live database.\n`npm run api:doctor:postgres` injects a fake Postgres pool into the same runtime\npath and validates session lookup, wrong-role blocking, idempotency replay,\nconflict handling, audit inserts, and queue-job inserts without external\ncredentials. `CUSTOMCARD_POSTGRES_INTEGRATION_DOCTOR=enabled npm run\napi:doctor:postgres:live` creates an isolated temporary database on the configured\nPostgres server, applies the committed migration, seeds customer/admin sessions,\nexercises the real `pg` runtime, and drops the temporary database before exiting.\nCI runs that live doctor against a Postgres service; deployed production account\nauth remains unclaimed.\n\n`CUSTOMCARD_ACCOUNT_AUTH_DOCTOR=enabled npm run account:doctor:live` uses the\nsame isolated-database pattern to verify hosted account identity rows,\nprovider-subject uniqueness, hashed recovery challenges, durable sessions, and\naccount-recovery audit rows. CI runs it against the Postgres service; live hosted\ntoken verification remains unclaimed.\n\n`npm run artifact:doctor` writes the sample render packet package to a\ntemporary local filesystem object-store path, reads every artifact back, verifies\nchecksums and byte lengths, stores the handoff manifest, and keeps network calls\nplus real orders disabled. Cloud S3/MinIO writes remain unclaimed until provider\ncredentials, bucket policy, and deployment storage are tested.\n\nThe persistence boundary requires auth-session storage, account identity storage,\nhashed recovery challenges, idempotency replay, queue job envelopes,\nrender-packet artifact manifests, signed URL expiry, and append-only audit\nsignals in the migration before production Postgres handlers are claimed.\n\nThe Kubernetes `Secret` in `k8s/app.yaml` is intentionally empty and annotated as\npre-created by a secret manager. Production clusters should source the required\nkeys from the platform secret manager, External Secrets, Sealed Secrets, or an\nequivalent operator-approved mechanism. The `runtime:doctor` check rejects empty\nvalues, known placeholders, and any live-order kill switch value other than\n`disabled`.\n\nSigned artifact handoff requires `OBJECT_STORE_SIGNING_SECRET` to be present and\nat least 32 characters. The committed manifests also keep\n`ARTIFACT_SIGNED_URL_TTL_MINUTES=15` explicit so download links remain\nshort-lived.\n\nReal external ordering stays disabled with `REAL_ORDER_KILL_SWITCH=disabled` until vendor sandbox tests and physical print certification are recorded.\n\n## Provider Adapter Secrets\n\n`env/.env.example` names the credential-gated adapters represented in\n`src/providerCatalog.ts`:\n\n- Google OAuth: `GOOGLE_OAUTH_CLIENT_ID`, `GOOGLE_OAUTH_CLIENT_SECRET`.\n- Microsoft Graph: `MICROSOFT_CLIENT_ID`, `MICROSOFT_CLIENT_SECRET`,\n `MICROSOFT_TENANT_ID`.\n- Hosted identity providers: `CUSTOMCARD_AUTH_CALLBACK_URL`, `AUTH0_DOMAIN`,\n `AUTH0_CLIENT_ID`, `AUTH0_CLIENT_SECRET`, `AUTH0_AUDIENCE`,\n `CLERK_SECRET_KEY`, `CLERK_JWT_KEY`, `CLERK_AUTHORIZED_PARTIES`,\n `SUPABASE_URL`, `SUPABASE_ANON_KEY`, `SUPABASE_SERVICE_ROLE_KEY`,\n `FIREBASE_API_KEY`, `FIREBASE_PROJECT_ID`,\n `FIREBASE_SERVICE_ACCOUNT_JSON`, `COGNITO_DOMAIN`,\n `COGNITO_USER_POOL_ID`, `COGNITO_APP_CLIENT_ID`.\n- Contact import providers: `CARDDAV_BASE_URL`, `CARDDAV_USERNAME`,\n `CARDDAV_APP_PASSWORD`, `CARDDAV_ADDRESSBOOK_PATH`.\n- Text and image AI providers: `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`,\n `AZURE_OPENAI_ENDPOINT`, `AZURE_OPENAI_API_KEY`,\n `AZURE_OPENAI_CHAT_DEPLOYMENT`, `AZURE_OPENAI_IMAGE_DEPLOYMENT`,\n `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_REGION`,\n `BEDROCK_TEXT_MODEL_ID`, `BEDROCK_IMAGE_MODEL_ID`,\n `GOOGLE_GENERATIVE_AI_API_KEY`, `MISTRAL_API_KEY`, `COHERE_API_KEY`,\n `PERPLEXITY_API_KEY`, `XAI_API_KEY`, `TOGETHER_API_KEY`, `GROQ_API_KEY`,\n `DEEPSEEK_API_KEY`, `FIREWORKS_API_KEY`, `STABILITY_API_KEY`,\n `HUGGINGFACE_API_TOKEN`, `REPLICATE_API_TOKEN`, `IDEOGRAM_API_KEY`,\n `LEONARDO_API_KEY`, `FAL_KEY`, `BFL_API_KEY`.\n- Self-hosted model fallback: `SELF_HOSTED_LLM_BASE_URL`,\n `SELF_HOSTED_LLM_API_KEY`.\n- Notification providers: `RESEND_API_KEY`, `SENDGRID_API_KEY`,\n `POSTMARK_SERVER_TOKEN`, `MAILGUN_API_KEY`, `MAILGUN_DOMAIN`,\n `TWILIO_ACCOUNT_SID`, `TWILIO_AUTH_TOKEN`,\n `TWILIO_MESSAGING_SERVICE_SID`, `WHATSAPP_ACCESS_TOKEN`,\n `WHATSAPP_PHONE_NUMBER_ID`, `EXPO_ACCESS_TOKEN`,\n `TRANSACTIONAL_EMAIL_API_KEY`, `TRANSACTIONAL_EMAIL_FROM`.\n- Payment sandbox providers: `STRIPE_SECRET_KEY`, `STRIPE_WEBHOOK_SECRET`,\n `CUSTOMCARD_PAYMENT_SUCCESS_URL`, `CUSTOMCARD_PAYMENT_CANCEL_URL`,\n `PAYPAL_CLIENT_ID`, `PAYPAL_CLIENT_SECRET`, `PAYPAL_WEBHOOK_ID`,\n `SQUARE_ACCESS_TOKEN`, `SQUARE_LOCATION_ID`,\n `SQUARE_WEBHOOK_SIGNATURE_KEY`, `ADYEN_API_KEY`,\n `ADYEN_MERCHANT_ACCOUNT`, `ADYEN_HMAC_KEY`.\n- Observability providers: `SENTRY_DSN`, `SENTRY_PROJECT_ID`,\n `SENTRY_ENVIRONMENT`, `POSTHOG_PROJECT_API_KEY`, `POSTHOG_HOST`,\n `OTEL_EXPORTER_OTLP_ENDPOINT`, `OTEL_EXPORTER_OTLP_HEADERS`,\n `GRAFANA_OTLP_ENDPOINT`, `GRAFANA_OTLP_INSTANCE_ID`,\n `GRAFANA_OTLP_API_KEY`, `DATADOG_API_KEY`, `DATADOG_SITE`,\n `BETTERSTACK_SOURCE_TOKEN`, `BETTERSTACK_INGESTING_HOST`.\n- Object-store artifact signer: `OBJECT_STORE_SIGNING_SECRET`.\n- Live vendor adapters: `WALGREENS_VENDOR_MODE`, `CVS_VENDOR_MODE`,\n `FEDEX_VENDOR_MODE`, `WALMART_VENDOR_MODE`, `STAPLES_VENDOR_MODE`,\n `OFFICE_DEPOT_VENDOR_MODE`.\n- Persistence controls: `CUSTOMCARD_API_RUNTIME`, `AUTH_SESSION_SECRET`,\n `CUSTOMCARD_CUSTOMER_SESSION_TOKEN`, `CUSTOMCARD_ADMIN_SESSION_TOKEN`,\n `IDEMPOTENCY_KEY_TTL_HOURS`.\n\nThese keys are documented for deployment readiness only. The current repo state\ndoes not make live provider calls, and vendor modes remain\n`disabled_until_certified` while `REAL_ORDER_KILL_SWITCH=disabled`.\n"}, "bbe286e32a9f7a37a6e40339a783c991839725f6": {"t": 1785772155.2759192, "n": 8191, "h": "2497250b6bad62cc", "c": "# CustomCard Infrastructure\n\nThis directory is the deployable service skeleton for the production path.\n\n- `docker-compose.dev.yml` runs app, worker, Postgres, Redis, and MinIO for local development.\n- `docker-compose.droplet.yml` is the cheap single-host deployment shape for a small DigitalOcean droplet.\n- `k8s/app.yaml` is the cloud-native starting point for web and worker deployments.\n- `migrations/001_initial_schema.sql` defines the durable service data model.\n- `env/.env.example` lists required secrets and kill switches.\n- `../scripts/api-server.mjs` serves `/api/health`, API bootstrap/readiness\n contracts, explicit contract/memory/Postgres runtime modes, and the built web\n app from `dist`.\n- `../src/artifactHandoff.ts` defines the render-packet artifact manifest and\n HMAC-signed URL contract used by the API/schema gates.\n- `../scripts/persistence-doctor.mjs` validates auth-session, idempotency, queue\n job, and audit persistence signals.\n- `../scripts/demo-reset.mjs` validates the reviewer demo reset contract before\n anyone attempts to run seed data against a database.\n- `../scripts/deployment-readiness.mjs` emits the local deployment readiness\n report used by `npm run deployment:doctor`.\n\nFor the droplet deployment, provide `POSTGRES_PASSWORD` from a real secret source\nor local deployment `.env` that is never committed. The compose file uses\n`${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD}` so the service fails closed instead\nof starting with an empty database password.\n\nFor Kubernetes, apply the `customcard-migrate` Job successfully before rolling\nthe web and worker Deployments. The deployment manifest keeps the live-order kill\nswitch explicit and uses probes so bad pods fail visibly.\n\nRun the local IaC readiness check before treating the manifests as reviewable:\n\n```sh\nnpm run deployment:doctor\nnpm run api:doctor\nnpm run api:doctor:memory\nnpm run api:doctor:postgres\nCUSTOMCARD_POSTGRES_INTEGRATION_DOCTOR=enabled DATABASE_URL=postgres://... npm run api:doctor:postgres:live\nCUSTOMCARD_ACCOUNT_AUTH_DOCTOR=enabled DATABASE_URL=postgres://... npm run account:doctor:live\nnpm run persistence:doctor\nnpm run demo:doctor\n```\n\nThe report checks the local-dev, cheap-droplet, cloud-native, runtime, and data\nlanes. Passing this check means the committed deployment contracts are internally\nconsistent; it does not mean a real droplet or Kubernetes cluster has been\nprovisioned.\n\nThe Kubernetes web deployment probes `/api/health`, and the production Docker\nimage starts `scripts/api-server.mjs` so the same container can serve the static\nweb bundle and the contract-first API endpoints.\n\nThe API runtime defaults to `CUSTOMCARD_API_RUNTIME=contract` for reviewer/static\nserving. `npm run api:doctor:memory` sets test customer/admin session tokens and\nvalidates Bearer auth plus `X-Idempotency-Key` replay without a live database.\n`npm run api:doctor:postgres` injects a fake Postgres pool into the same runtime\npath and validates session lookup, wrong-role blocking, idempotency replay,\nconflict handling, audit inserts, and queue-job inserts without external\ncredentials. `CUSTOMCARD_POSTGRES_INTEGRATION_DOCTOR=enabled npm run\napi:doctor:postgres:live` creates an isolated temporary database on the configured\nPostgres server, applies the committed migration, seeds customer/admin sessions,\nexercises the real `pg` runtime, and drops the temporary database before exiting.\nCI runs that live doctor against a Postgres service; deployed production account\nauth remains unclaimed.\n\n`CUSTOMCARD_ACCOUNT_AUTH_DOCTOR=enabled npm run account:doctor:live` uses the\nsame isolated-database pattern to verify hosted account identity rows,\nprovider-subject uniqueness, hashed recovery challenges, durable sessions, and\naccount-recovery audit rows. CI runs it against the Postgres service; live hosted\ntoken verification remains unclaimed.\n\nThe persistence boundary requires auth-session storage, account identity storage,\nhashed recovery challenges, idempotency replay, queue job envelopes,\nrender-packet artifact manifests, signed URL expiry, and append-only audit\nsignals in the migration before production Postgres handlers are claimed.\n\nThe Kubernetes `Secret` in `k8s/app.yaml` is intentionally empty and annotated as\npre-created by a secret manager. Production clusters should source the required\nkeys from the platform secret manager, External Secrets, Sealed Secrets, or an\nequivalent operator-approved mechanism. The `runtime:doctor` check rejects empty\nvalues, known placeholders, and any live-order kill switch value other than\n`disabled`.\n\nSigned artifact handoff requires `OBJECT_STORE_SIGNING_SECRET` to be present and\nat least 32 characters. The committed manifests also keep\n`ARTIFACT_SIGNED_URL_TTL_MINUTES=15` explicit so download links remain\nshort-lived.\n\nReal external ordering stays disabled with `REAL_ORDER_KILL_SWITCH=disabled` until vendor sandbox tests and physical print certification are recorded.\n\n## Provider Adapter Secrets\n\n`env/.env.example` names the credential-gated adapters represented in\n`src/providerCatalog.ts`:\n\n- Google OAuth: `GOOGLE_OAUTH_CLIENT_ID`, `GOOGLE_OAUTH_CLIENT_SECRET`.\n- Microsoft Graph: `MICROSOFT_CLIENT_ID`, `MICROSOFT_CLIENT_SECRET`,\n `MICROSOFT_TENANT_ID`.\n- Hosted identity providers: `CUSTOMCARD_AUTH_CALLBACK_URL`, `AUTH0_DOMAIN`,\n `AUTH0_CLIENT_ID`, `AUTH0_CLIENT_SECRET`, `AUTH0_AUDIENCE`,\n `CLERK_SECRET_KEY`, `CLERK_JWT_KEY`, `CLERK_AUTHORIZED_PARTIES`,\n `SUPABASE_URL`, `SUPABASE_ANON_KEY`, `SUPABASE_SERVICE_ROLE_KEY`,\n `FIREBASE_API_KEY`, `FIREBASE_PROJECT_ID`,\n `FIREBASE_SERVICE_ACCOUNT_JSON`, `COGNITO_DOMAIN`,\n `COGNITO_USER_POOL_ID`, `COGNITO_APP_CLIENT_ID`.\n- Contact import providers: `CARDDAV_BASE_URL`, `CARDDAV_USERNAME`,\n `CARDDAV_APP_PASSWORD`, `CARDDAV_ADDRESSBOOK_PATH`.\n- Text and image AI providers: `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`,\n `AZURE_OPENAI_ENDPOINT`, `AZURE_OPENAI_API_KEY`,\n `AZURE_OPENAI_CHAT_DEPLOYMENT`, `AZURE_OPENAI_IMAGE_DEPLOYMENT`,\n `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_REGION`,\n `BEDROCK_TEXT_MODEL_ID`, `BEDROCK_IMAGE_MODEL_ID`,\n `GOOGLE_GENERATIVE_AI_API_KEY`, `MISTRAL_API_KEY`, `COHERE_API_KEY`,\n `PERPLEXITY_API_KEY`, `XAI_API_KEY`, `TOGETHER_API_KEY`, `GROQ_API_KEY`,\n `DEEPSEEK_API_KEY`, `FIREWORKS_API_KEY`, `STABILITY_API_KEY`,\n `HUGGINGFACE_API_TOKEN`, `REPLICATE_API_TOKEN`, `IDEOGRAM_API_KEY`,\n `LEONARDO_API_KEY`, `FAL_KEY`, `BFL_API_KEY`.\n- Self-hosted model fallback: `SELF_HOSTED_LLM_BASE_URL`,\n `SELF_HOSTED_LLM_API_KEY`.\n- Notification providers: `RESEND_API_KEY`, `SENDGRID_API_KEY`,\n `POSTMARK_SERVER_TOKEN`, `MAILGUN_API_KEY`, `MAILGUN_DOMAIN`,\n `TWILIO_ACCOUNT_SID`, `TWILIO_AUTH_TOKEN`,\n `TWILIO_MESSAGING_SERVICE_SID`, `WHATSAPP_ACCESS_TOKEN`,\n `WHATSAPP_PHONE_NUMBER_ID`, `EXPO_ACCESS_TOKEN`,\n `TRANSACTIONAL_EMAIL_API_KEY`, `TRANSACTIONAL_EMAIL_FROM`.\n- Payment sandbox providers: `STRIPE_SECRET_KEY`, `STRIPE_WEBHOOK_SECRET`,\n `CUSTOMCARD_PAYMENT_SUCCESS_URL`, `CUSTOMCARD_PAYMENT_CANCEL_URL`,\n `PAYPAL_CLIENT_ID`, `PAYPAL_CLIENT_SECRET`, `PAYPAL_WEBHOOK_ID`,\n `SQUARE_ACCESS_TOKEN`, `SQUARE_LOCATION_ID`,\n `SQUARE_WEBHOOK_SIGNATURE_KEY`, `ADYEN_API_KEY`,\n `ADYEN_MERCHANT_ACCOUNT`, `ADYEN_HMAC_KEY`.\n- Observability providers: `SENTRY_DSN`, `SENTRY_PROJECT_ID`,\n `SENTRY_ENVIRONMENT`, `POSTHOG_PROJECT_API_KEY`, `POSTHOG_HOST`,\n `OTEL_EXPORTER_OTLP_ENDPOINT`, `OTEL_EXPORTER_OTLP_HEADERS`,\n `GRAFANA_OTLP_ENDPOINT`, `GRAFANA_OTLP_INSTANCE_ID`,\n `GRAFANA_OTLP_API_KEY`, `DATADOG_API_KEY`, `DATADOG_SITE`,\n `BETTERSTACK_SOURCE_TOKEN`, `BETTERSTACK_INGESTING_HOST`.\n- Object-store artifact signer: `OBJECT_STORE_SIGNING_SECRET`.\n- Live vendor adapters: `WALGREENS_VENDOR_MODE`, `CVS_VENDOR_MODE`,\n `FEDEX_VENDOR_MODE`, `WALMART_VENDOR_MODE`, `STAPLES_VENDOR_MODE`,\n `OFFICE_DEPOT_VENDOR_MODE`.\n- Persistence controls: `CUSTOMCARD_API_RUNTIME`, `AUTH_SESSION_SECRET`,\n `CUSTOMCARD_CUSTOMER_SESSION_TOKEN`, `CUSTOMCARD_ADMIN_SESSION_TOKEN`,\n `IDEMPOTENCY_KEY_TTL_HOURS`.\n\nThese keys are documented for deployment readiness only. The current repo state\ndoes not make live provider calls, and vendor modes remain\n`disabled_until_certified` while `REAL_ORDER_KILL_SWITCH=disabled`.\n"}, "2a33a41bef6912c197cc729d8a36189535cb0d9b": {"t": 1785772156.5598104, "n": 7693, "h": "d87111acdc9d4028", "c": "# CustomCard Infrastructure\n\nThis directory is the deployable service skeleton for the production path.\n\n- `docker-compose.dev.yml` runs app, worker, Postgres, Redis, and MinIO for local development.\n- `docker-compose.droplet.yml` is the cheap single-host deployment shape for a small DigitalOcean droplet.\n- `k8s/app.yaml` is the cloud-native starting point for web and worker deployments.\n- `migrations/001_initial_schema.sql` defines the durable service data model.\n- `env/.env.example` lists required secrets and kill switches.\n- `../scripts/api-server.mjs` serves `/api/health`, API bootstrap/readiness\n contracts, explicit contract/memory/Postgres runtime modes, and the built web\n app from `dist`.\n- `../src/artifactHandoff.ts` defines the render-packet artifact manifest and\n HMAC-signed URL contract used by the API/schema gates.\n- `../scripts/persistence-doctor.mjs` validates auth-session, idempotency, queue\n job, and audit persistence signals.\n- `../scripts/demo-reset.mjs` validates the reviewer demo reset contract before\n anyone attempts to run seed data against a database.\n- `../scripts/deployment-readiness.mjs` emits the local deployment readiness\n report used by `npm run deployment:doctor`.\n\nFor the droplet deployment, provide `POSTGRES_PASSWORD` from a real secret source\nor local deployment `.env` that is never committed. The compose file uses\n`${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD}` so the service fails closed instead\nof starting with an empty database password.\n\nFor Kubernetes, apply the `customcard-migrate` Job successfully before rolling\nthe web and worker Deployments. The deployment manifest keeps the live-order kill\nswitch explicit and uses probes so bad pods fail visibly.\n\nRun the local IaC readiness check before treating the manifests as reviewable:\n\n```sh\nnpm run deployment:doctor\nnpm run api:doctor\nnpm run api:doctor:memory\nnpm run api:doctor:postgres\nCUSTOMCARD_POSTGRES_INTEGRATION_DOCTOR=enabled DATABASE_URL=postgres://... npm run api:doctor:postgres:live\nnpm run persistence:doctor\nnpm run demo:doctor\n```\n\nThe report checks the local-dev, cheap-droplet, cloud-native, runtime, and data\nlanes. Passing this check means the committed deployment contracts are internally\nconsistent; it does not mean a real droplet or Kubernetes cluster has been\nprovisioned.\n\nThe Kubernetes web deployment probes `/api/health`, and the production Docker\nimage starts `scripts/api-server.mjs` so the same container can serve the static\nweb bundle and the contract-first API endpoints.\n\nThe API runtime defaults to `CUSTOMCARD_API_RUNTIME=contract` for reviewer/static\nserving. `npm run api:doctor:memory` sets test customer/admin session tokens and\nvalidates Bearer auth plus `X-Idempotency-Key` replay without a live database.\n`npm run api:doctor:postgres` injects a fake Postgres pool into the same runtime\npath and validates session lookup, wrong-role blocking, idempotency replay,\nconflict handling, audit inserts, and queue-job inserts without external\ncredentials. `CUSTOMCARD_POSTGRES_INTEGRATION_DOCTOR=enabled npm run\napi:doctor:postgres:live` creates an isolated temporary database on the configured\nPostgres server, applies the committed migration, seeds customer/admin sessions,\nexercises the real `pg` runtime, and drops the temporary database before exiting.\nCI runs that live doctor against a Postgres service; deployed production account\nauth remains unclaimed.\n\nThe persistence boundary requires auth-session storage, idempotency replay,\nqueue job envelopes, render-packet artifact manifests, signed URL expiry, and\nappend-only audit signals in the migration before production Postgres handlers\nare claimed.\n\nThe Kubernetes `Secret` in `k8s/app.yaml` is intentionally empty and annotated as\npre-created by a secret manager. Production clusters should source the required\nkeys from the platform secret manager, External Secrets, Sealed Secrets, or an\nequivalent operator-approved mechanism. The `runtime:doctor` check rejects empty\nvalues, known placeholders, and any live-order kill switch value other than\n`disabled`.\n\nSigned artifact handoff requires `OBJECT_STORE_SIGNING_SECRET` to be present and\nat least 32 characters. The committed manifests also keep\n`ARTIFACT_SIGNED_URL_TTL_MINUTES=15` explicit so download links remain\nshort-lived.\n\nReal external ordering stays disabled with `REAL_ORDER_KILL_SWITCH=disabled` until vendor sandbox tests and physical print certification are recorded.\n\n## Provider Adapter Secrets\n\n`env/.env.example` names the credential-gated adapters represented in\n`src/providerCatalog.ts`:\n\n- Google OAuth: `GOOGLE_OAUTH_CLIENT_ID`, `GOOGLE_OAUTH_CLIENT_SECRET`.\n- Microsoft Graph: `MICROSOFT_CLIENT_ID`, `MICROSOFT_CLIENT_SECRET`,\n `MICROSOFT_TENANT_ID`.\n- Hosted identity providers: `CUSTOMCARD_AUTH_CALLBACK_URL`, `AUTH0_DOMAIN`,\n `AUTH0_CLIENT_ID`, `AUTH0_CLIENT_SECRET`, `AUTH0_AUDIENCE`,\n `CLERK_SECRET_KEY`, `CLERK_JWT_KEY`, `CLERK_AUTHORIZED_PARTIES`,\n `SUPABASE_URL`, `SUPABASE_ANON_KEY`, `SUPABASE_SERVICE_ROLE_KEY`,\n `FIREBASE_API_KEY`, `FIREBASE_PROJECT_ID`,\n `FIREBASE_SERVICE_ACCOUNT_JSON`, `COGNITO_DOMAIN`,\n `COGNITO_USER_POOL_ID`, `COGNITO_APP_CLIENT_ID`.\n- Contact import providers: `CARDDAV_BASE_URL`, `CARDDAV_USERNAME`,\n `CARDDAV_APP_PASSWORD`, `CARDDAV_ADDRESSBOOK_PATH`.\n- Text and image AI providers: `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`,\n `AZURE_OPENAI_ENDPOINT`, `AZURE_OPENAI_API_KEY`,\n `AZURE_OPENAI_CHAT_DEPLOYMENT`, `AZURE_OPENAI_IMAGE_DEPLOYMENT`,\n `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_REGION`,\n `BEDROCK_TEXT_MODEL_ID`, `BEDROCK_IMAGE_MODEL_ID`,\n `GOOGLE_GENERATIVE_AI_API_KEY`, `MISTRAL_API_KEY`, `COHERE_API_KEY`,\n `PERPLEXITY_API_KEY`, `XAI_API_KEY`, `TOGETHER_API_KEY`, `GROQ_API_KEY`,\n `DEEPSEEK_API_KEY`, `FIREWORKS_API_KEY`, `STABILITY_API_KEY`,\n `HUGGINGFACE_API_TOKEN`, `REPLICATE_API_TOKEN`, `IDEOGRAM_API_KEY`,\n `LEONARDO_API_KEY`, `FAL_KEY`, `BFL_API_KEY`.\n- Self-hosted model fallback: `SELF_HOSTED_LLM_BASE_URL`,\n `SELF_HOSTED_LLM_API_KEY`.\n- Notification providers: `RESEND_API_KEY`, `SENDGRID_API_KEY`,\n `POSTMARK_SERVER_TOKEN`, `MAILGUN_API_KEY`, `MAILGUN_DOMAIN`,\n `TWILIO_ACCOUNT_SID`, `TWILIO_AUTH_TOKEN`,\n `TWILIO_MESSAGING_SERVICE_SID`, `WHATSAPP_ACCESS_TOKEN`,\n `WHATSAPP_PHONE_NUMBER_ID`, `EXPO_ACCESS_TOKEN`,\n `TRANSACTIONAL_EMAIL_API_KEY`, `TRANSACTIONAL_EMAIL_FROM`.\n- Payment sandbox providers: `STRIPE_SECRET_KEY`, `STRIPE_WEBHOOK_SECRET`,\n `CUSTOMCARD_PAYMENT_SUCCESS_URL`, `CUSTOMCARD_PAYMENT_CANCEL_URL`,\n `PAYPAL_CLIENT_ID`, `PAYPAL_CLIENT_SECRET`, `PAYPAL_WEBHOOK_ID`,\n `SQUARE_ACCESS_TOKEN`, `SQUARE_LOCATION_ID`,\n `SQUARE_WEBHOOK_SIGNATURE_KEY`, `ADYEN_API_KEY`,\n `ADYEN_MERCHANT_ACCOUNT`, `ADYEN_HMAC_KEY`.\n- Observability providers: `SENTRY_DSN`, `SENTRY_PROJECT_ID`,\n `SENTRY_ENVIRONMENT`, `POSTHOG_PROJECT_API_KEY`, `POSTHOG_HOST`,\n `OTEL_EXPORTER_OTLP_ENDPOINT`, `OTEL_EXPORTER_OTLP_HEADERS`,\n `GRAFANA_OTLP_ENDPOINT`, `GRAFANA_OTLP_INSTANCE_ID`,\n `GRAFANA_OTLP_API_KEY`, `DATADOG_API_KEY`, `DATADOG_SITE`,\n `BETTERSTACK_SOURCE_TOKEN`, `BETTERSTACK_INGESTING_HOST`.\n- Object-store artifact signer: `OBJECT_STORE_SIGNING_SECRET`.\n- Live vendor adapters: `WALGREENS_VENDOR_MODE`, `CVS_VENDOR_MODE`,\n `FEDEX_VENDOR_MODE`, `WALMART_VENDOR_MODE`, `STAPLES_VENDOR_MODE`,\n `OFFICE_DEPOT_VENDOR_MODE`.\n- Persistence controls: `CUSTOMCARD_API_RUNTIME`, `AUTH_SESSION_SECRET`,\n `CUSTOMCARD_CUSTOMER_SESSION_TOKEN`, `CUSTOMCARD_ADMIN_SESSION_TOKEN`,\n `IDEMPOTENCY_KEY_TTL_HOURS`.\n\nThese keys are documented for deployment readiness only. The current repo state\ndoes not make live provider calls, and vendor modes remain\n`disabled_until_certified` while `REAL_ORDER_KILL_SWITCH=disabled`.\n"}, "a8f4dcff29e7075cc4072bb6805bb60b3dc46549": {"t": 1785772157.9997966, "n": 7268, "h": "99566693af7f93a0", "c": "# CustomCard Infrastructure\n\nThis directory is the deployable service skeleton for the production path.\n\n- `docker-compose.dev.yml` runs app, worker, Postgres, Redis, and MinIO for local development.\n- `docker-compose.droplet.yml` is the cheap single-host deployment shape for a small DigitalOcean droplet.\n- `k8s/app.yaml` is the cloud-native starting point for web and worker deployments.\n- `migrations/001_initial_schema.sql` defines the durable service data model.\n- `env/.env.example` lists required secrets and kill switches.\n- `../scripts/api-server.mjs` serves `/api/health`, API bootstrap/readiness\n contracts, explicit contract/memory/Postgres runtime modes, and the built web\n app from `dist`.\n- `../src/artifactHandoff.ts` defines the render-packet artifact manifest and\n HMAC-signed URL contract used by the API/schema gates.\n- `../scripts/persistence-doctor.mjs` validates auth-session, idempotency, queue\n job, and audit persistence signals.\n- `../scripts/demo-reset.mjs` validates the reviewer demo reset contract before\n anyone attempts to run seed data against a database.\n- `../scripts/deployment-readiness.mjs` emits the local deployment readiness\n report used by `npm run deployment:doctor`.\n\nFor the droplet deployment, provide `POSTGRES_PASSWORD` from a real secret source\nor local deployment `.env` that is never committed. The compose file uses\n`${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD}` so the service fails closed instead\nof starting with an empty database password.\n\nFor Kubernetes, apply the `customcard-migrate` Job successfully before rolling\nthe web and worker Deployments. The deployment manifest keeps the live-order kill\nswitch explicit and uses probes so bad pods fail visibly.\n\nRun the local IaC readiness check before treating the manifests as reviewable:\n\n```sh\nnpm run deployment:doctor\nnpm run api:doctor\nnpm run api:doctor:memory\nnpm run persistence:doctor\nnpm run demo:doctor\n```\n\nThe report checks the local-dev, cheap-droplet, cloud-native, runtime, and data\nlanes. Passing this check means the committed deployment contracts are internally\nconsistent; it does not mean a real droplet or Kubernetes cluster has been\nprovisioned.\n\nThe Kubernetes web deployment probes `/api/health`, and the production Docker\nimage starts `scripts/api-server.mjs` so the same container can serve the static\nweb bundle and the contract-first API endpoints.\n\nThe API runtime defaults to `CUSTOMCARD_API_RUNTIME=contract` for reviewer/static\nserving. `npm run api:doctor:memory` sets test customer/admin session tokens and\nvalidates Bearer auth plus `X-Idempotency-Key` replay without a live database.\n`npm run api:doctor:postgres` injects a fake Postgres pool into the same runtime\npath and validates session lookup, wrong-role blocking, idempotency replay,\nconflict handling, audit inserts, and queue-job inserts without external\ncredentials. `CUSTOMCARD_API_RUNTIME=postgres` with a real `DATABASE_URL` is\nstill reserved for live database integration testing.\n\nThe persistence boundary requires auth-session storage, idempotency replay,\nqueue job envelopes, render-packet artifact manifests, signed URL expiry, and\nappend-only audit signals in the migration before production Postgres handlers\nare claimed.\n\nThe Kubernetes `Secret` in `k8s/app.yaml` is intentionally empty and annotated as\npre-created by a secret manager. Production clusters should source the required\nkeys from the platform secret manager, External Secrets, Sealed Secrets, or an\nequivalent operator-approved mechanism. The `runtime:doctor` check rejects empty\nvalues, known placeholders, and any live-order kill switch value other than\n`disabled`.\n\nSigned artifact handoff requires `OBJECT_STORE_SIGNING_SECRET` to be present and\nat least 32 characters. The committed manifests also keep\n`ARTIFACT_SIGNED_URL_TTL_MINUTES=15` explicit so download links remain\nshort-lived.\n\nReal external ordering stays disabled with `REAL_ORDER_KILL_SWITCH=disabled` until vendor sandbox tests and physical print certification are recorded.\n\n## Provider Adapter Secrets\n\n`env/.env.example` names the credential-gated adapters represented in\n`src/providerCatalog.ts`:\n\n- Google OAuth: `GOOGLE_OAUTH_CLIENT_ID`, `GOOGLE_OAUTH_CLIENT_SECRET`.\n- Microsoft Graph: `MICROSOFT_CLIENT_ID`, `MICROSOFT_CLIENT_SECRET`,\n `MICROSOFT_TENANT_ID`.\n- Hosted identity providers: `CUSTOMCARD_AUTH_CALLBACK_URL`, `AUTH0_DOMAIN`,\n `AUTH0_CLIENT_ID`, `AUTH0_CLIENT_SECRET`, `AUTH0_AUDIENCE`,\n `CLERK_SECRET_KEY`, `CLERK_JWT_KEY`, `CLERK_AUTHORIZED_PARTIES`,\n `SUPABASE_URL`, `SUPABASE_ANON_KEY`, `SUPABASE_SERVICE_ROLE_KEY`,\n `FIREBASE_API_KEY`, `FIREBASE_PROJECT_ID`,\n `FIREBASE_SERVICE_ACCOUNT_JSON`, `COGNITO_DOMAIN`,\n `COGNITO_USER_POOL_ID`, `COGNITO_APP_CLIENT_ID`.\n- Contact import providers: `CARDDAV_BASE_URL`, `CARDDAV_USERNAME`,\n `CARDDAV_APP_PASSWORD`, `CARDDAV_ADDRESSBOOK_PATH`.\n- Text and image AI providers: `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`,\n `AZURE_OPENAI_ENDPOINT`, `AZURE_OPENAI_API_KEY`,\n `AZURE_OPENAI_CHAT_DEPLOYMENT`, `AZURE_OPENAI_IMAGE_DEPLOYMENT`,\n `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_REGION`,\n `BEDROCK_TEXT_MODEL_ID`, `BEDROCK_IMAGE_MODEL_ID`,\n `GOOGLE_GENERATIVE_AI_API_KEY`, `MISTRAL_API_KEY`, `COHERE_API_KEY`,\n `PERPLEXITY_API_KEY`, `XAI_API_KEY`, `TOGETHER_API_KEY`, `GROQ_API_KEY`,\n `DEEPSEEK_API_KEY`, `FIREWORKS_API_KEY`, `STABILITY_API_KEY`,\n `HUGGINGFACE_API_TOKEN`, `REPLICATE_API_TOKEN`, `IDEOGRAM_API_KEY`,\n `LEONARDO_API_KEY`, `FAL_KEY`, `BFL_API_KEY`.\n- Self-hosted model fallback: `SELF_HOSTED_LLM_BASE_URL`,\n `SELF_HOSTED_LLM_API_KEY`.\n- Notification providers: `RESEND_API_KEY`, `SENDGRID_API_KEY`,\n `POSTMARK_SERVER_TOKEN`, `MAILGUN_API_KEY`, `MAILGUN_DOMAIN`,\n `TWILIO_ACCOUNT_SID`, `TWILIO_AUTH_TOKEN`,\n `TWILIO_MESSAGING_SERVICE_SID`, `WHATSAPP_ACCESS_TOKEN`,\n `WHATSAPP_PHONE_NUMBER_ID`, `EXPO_ACCESS_TOKEN`,\n `TRANSACTIONAL_EMAIL_API_KEY`, `TRANSACTIONAL_EMAIL_FROM`.\n- Payment sandbox providers: `STRIPE_SECRET_KEY`, `STRIPE_WEBHOOK_SECRET`,\n `CUSTOMCARD_PAYMENT_SUCCESS_URL`, `CUSTOMCARD_PAYMENT_CANCEL_URL`,\n `PAYPAL_CLIENT_ID`, `PAYPAL_CLIENT_SECRET`, `PAYPAL_WEBHOOK_ID`,\n `SQUARE_ACCESS_TOKEN`, `SQUARE_LOCATION_ID`,\n `SQUARE_WEBHOOK_SIGNATURE_KEY`, `ADYEN_API_KEY`,\n `ADYEN_MERCHANT_ACCOUNT`, `ADYEN_HMAC_KEY`.\n- Observability providers: `SENTRY_DSN`, `SENTRY_PROJECT_ID`,\n `SENTRY_ENVIRONMENT`, `POSTHOG_PROJECT_API_KEY`, `POSTHOG_HOST`,\n `OTEL_EXPORTER_OTLP_ENDPOINT`, `OTEL_EXPORTER_OTLP_HEADERS`,\n `GRAFANA_OTLP_ENDPOINT`, `GRAFANA_OTLP_INSTANCE_ID`,\n `GRAFANA_OTLP_API_KEY`, `DATADOG_API_KEY`, `DATADOG_SITE`,\n `BETTERSTACK_SOURCE_TOKEN`, `BETTERSTACK_INGESTING_HOST`.\n- Object-store artifact signer: `OBJECT_STORE_SIGNING_SECRET`.\n- Live vendor adapters: `WALGREENS_VENDOR_MODE`, `CVS_VENDOR_MODE`,\n `FEDEX_VENDOR_MODE`, `WALMART_VENDOR_MODE`, `STAPLES_VENDOR_MODE`,\n `OFFICE_DEPOT_VENDOR_MODE`.\n- Persistence controls: `CUSTOMCARD_API_RUNTIME`, `AUTH_SESSION_SECRET`,\n `CUSTOMCARD_CUSTOMER_SESSION_TOKEN`, `CUSTOMCARD_ADMIN_SESSION_TOKEN`,\n `IDEMPOTENCY_KEY_TTL_HOURS`.\n\nThese keys are documented for deployment readiness only. The current repo state\ndoes not make live provider calls, and vendor modes remain\n`disabled_until_certified` while `REAL_ORDER_KILL_SWITCH=disabled`.\n"}, "4c8261eb0604a5dda9bd940723c9b2609726e267": {"t": 1785772159.2875752, "n": 7020, "h": "5b4bb8193ce81dc8", "c": "# CustomCard Infrastructure\n\nThis directory is the deployable service skeleton for the production path.\n\n- `docker-compose.dev.yml` runs app, worker, Postgres, Redis, and MinIO for local development.\n- `docker-compose.droplet.yml` is the cheap single-host deployment shape for a small DigitalOcean droplet.\n- `k8s/app.yaml` is the cloud-native starting point for web and worker deployments.\n- `migrations/001_initial_schema.sql` defines the durable service data model.\n- `env/.env.example` lists required secrets and kill switches.\n- `../scripts/api-server.mjs` serves `/api/health`, API bootstrap/readiness\n contracts, explicit contract/memory/Postgres runtime modes, and the built web\n app from `dist`.\n- `../src/artifactHandoff.ts` defines the render-packet artifact manifest and\n HMAC-signed URL contract used by the API/schema gates.\n- `../scripts/persistence-doctor.mjs` validates auth-session, idempotency, queue\n job, and audit persistence signals.\n- `../scripts/demo-reset.mjs` validates the reviewer demo reset contract before\n anyone attempts to run seed data against a database.\n- `../scripts/deployment-readiness.mjs` emits the local deployment readiness\n report used by `npm run deployment:doctor`.\n\nFor the droplet deployment, provide `POSTGRES_PASSWORD` from a real secret source\nor local deployment `.env` that is never committed. The compose file uses\n`${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD}` so the service fails closed instead\nof starting with an empty database password.\n\nFor Kubernetes, apply the `customcard-migrate` Job successfully before rolling\nthe web and worker Deployments. The deployment manifest keeps the live-order kill\nswitch explicit and uses probes so bad pods fail visibly.\n\nRun the local IaC readiness check before treating the manifests as reviewable:\n\n```sh\nnpm run deployment:doctor\nnpm run api:doctor\nnpm run api:doctor:memory\nnpm run persistence:doctor\nnpm run demo:doctor\n```\n\nThe report checks the local-dev, cheap-droplet, cloud-native, runtime, and data\nlanes. Passing this check means the committed deployment contracts are internally\nconsistent; it does not mean a real droplet or Kubernetes cluster has been\nprovisioned.\n\nThe Kubernetes web deployment probes `/api/health`, and the production Docker\nimage starts `scripts/api-server.mjs` so the same container can serve the static\nweb bundle and the contract-first API endpoints.\n\nThe API runtime defaults to `CUSTOMCARD_API_RUNTIME=contract` for reviewer/static\nserving. `npm run api:doctor:memory` sets test customer/admin session tokens and\nvalidates Bearer auth plus `X-Idempotency-Key` replay without a live database.\n`CUSTOMCARD_API_RUNTIME=postgres` is reserved for live database integration\ntesting and requires `DATABASE_URL`.\n\nThe persistence boundary requires auth-session storage, idempotency replay,\nqueue job envelopes, render-packet artifact manifests, signed URL expiry, and\nappend-only audit signals in the migration before production Postgres handlers\nare claimed.\n\nThe Kubernetes `Secret` in `k8s/app.yaml` is intentionally empty and annotated as\npre-created by a secret manager. Production clusters should source the required\nkeys from the platform secret manager, External Secrets, Sealed Secrets, or an\nequivalent operator-approved mechanism. The `runtime:doctor` check rejects empty\nvalues, known placeholders, and any live-order kill switch value other than\n`disabled`.\n\nSigned artifact handoff requires `OBJECT_STORE_SIGNING_SECRET` to be present and\nat least 32 characters. The committed manifests also keep\n`ARTIFACT_SIGNED_URL_TTL_MINUTES=15` explicit so download links remain\nshort-lived.\n\nReal external ordering stays disabled with `REAL_ORDER_KILL_SWITCH=disabled` until vendor sandbox tests and physical print certification are recorded.\n\n## Provider Adapter Secrets\n\n`env/.env.example` names the credential-gated adapters represented in\n`src/providerCatalog.ts`:\n\n- Google OAuth: `GOOGLE_OAUTH_CLIENT_ID`, `GOOGLE_OAUTH_CLIENT_SECRET`.\n- Microsoft Graph: `MICROSOFT_CLIENT_ID`, `MICROSOFT_CLIENT_SECRET`,\n `MICROSOFT_TENANT_ID`.\n- Hosted identity providers: `CUSTOMCARD_AUTH_CALLBACK_URL`, `AUTH0_DOMAIN`,\n `AUTH0_CLIENT_ID`, `AUTH0_CLIENT_SECRET`, `AUTH0_AUDIENCE`,\n `CLERK_SECRET_KEY`, `CLERK_JWT_KEY`, `CLERK_AUTHORIZED_PARTIES`,\n `SUPABASE_URL`, `SUPABASE_ANON_KEY`, `SUPABASE_SERVICE_ROLE_KEY`,\n `FIREBASE_API_KEY`, `FIREBASE_PROJECT_ID`,\n `FIREBASE_SERVICE_ACCOUNT_JSON`, `COGNITO_DOMAIN`,\n `COGNITO_USER_POOL_ID`, `COGNITO_APP_CLIENT_ID`.\n- Contact import providers: `CARDDAV_BASE_URL`, `CARDDAV_USERNAME`,\n `CARDDAV_APP_PASSWORD`, `CARDDAV_ADDRESSBOOK_PATH`.\n- Text and image AI providers: `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`,\n `AZURE_OPENAI_ENDPOINT`, `AZURE_OPENAI_API_KEY`,\n `AZURE_OPENAI_CHAT_DEPLOYMENT`, `AZURE_OPENAI_IMAGE_DEPLOYMENT`,\n `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_REGION`,\n `BEDROCK_TEXT_MODEL_ID`, `BEDROCK_IMAGE_MODEL_ID`,\n `GOOGLE_GENERATIVE_AI_API_KEY`, `MISTRAL_API_KEY`, `COHERE_API_KEY`,\n `PERPLEXITY_API_KEY`, `XAI_API_KEY`, `TOGETHER_API_KEY`, `GROQ_API_KEY`,\n `DEEPSEEK_API_KEY`, `FIREWORKS_API_KEY`, `STABILITY_API_KEY`,\n `HUGGINGFACE_API_TOKEN`, `REPLICATE_API_TOKEN`, `IDEOGRAM_API_KEY`,\n `LEONARDO_API_KEY`, `FAL_KEY`, `BFL_API_KEY`.\n- Self-hosted model fallback: `SELF_HOSTED_LLM_BASE_URL`,\n `SELF_HOSTED_LLM_API_KEY`.\n- Notification providers: `RESEND_API_KEY`, `SENDGRID_API_KEY`,\n `POSTMARK_SERVER_TOKEN`, `MAILGUN_API_KEY`, `MAILGUN_DOMAIN`,\n `TWILIO_ACCOUNT_SID`, `TWILIO_AUTH_TOKEN`,\n `TWILIO_MESSAGING_SERVICE_SID`, `WHATSAPP_ACCESS_TOKEN`,\n `WHATSAPP_PHONE_NUMBER_ID`, `EXPO_ACCESS_TOKEN`,\n `TRANSACTIONAL_EMAIL_API_KEY`, `TRANSACTIONAL_EMAIL_FROM`.\n- Payment sandbox providers: `STRIPE_SECRET_KEY`, `STRIPE_WEBHOOK_SECRET`,\n `CUSTOMCARD_PAYMENT_SUCCESS_URL`, `CUSTOMCARD_PAYMENT_CANCEL_URL`,\n `PAYPAL_CLIENT_ID`, `PAYPAL_CLIENT_SECRET`, `PAYPAL_WEBHOOK_ID`,\n `SQUARE_ACCESS_TOKEN`, `SQUARE_LOCATION_ID`,\n `SQUARE_WEBHOOK_SIGNATURE_KEY`, `ADYEN_API_KEY`,\n `ADYEN_MERCHANT_ACCOUNT`, `ADYEN_HMAC_KEY`.\n- Observability providers: `SENTRY_DSN`, `SENTRY_PROJECT_ID`,\n `SENTRY_ENVIRONMENT`, `POSTHOG_PROJECT_API_KEY`, `POSTHOG_HOST`,\n `OTEL_EXPORTER_OTLP_ENDPOINT`, `OTEL_EXPORTER_OTLP_HEADERS`,\n `GRAFANA_OTLP_ENDPOINT`, `GRAFANA_OTLP_INSTANCE_ID`,\n `GRAFANA_OTLP_API_KEY`, `DATADOG_API_KEY`, `DATADOG_SITE`,\n `BETTERSTACK_SOURCE_TOKEN`, `BETTERSTACK_INGESTING_HOST`.\n- Object-store artifact signer: `OBJECT_STORE_SIGNING_SECRET`.\n- Live vendor adapters: `WALGREENS_VENDOR_MODE`, `CVS_VENDOR_MODE`,\n `FEDEX_VENDOR_MODE`, `WALMART_VENDOR_MODE`, `STAPLES_VENDOR_MODE`,\n `OFFICE_DEPOT_VENDOR_MODE`.\n- Persistence controls: `CUSTOMCARD_API_RUNTIME`, `AUTH_SESSION_SECRET`,\n `CUSTOMCARD_CUSTOMER_SESSION_TOKEN`, `CUSTOMCARD_ADMIN_SESSION_TOKEN`,\n `IDEMPOTENCY_KEY_TTL_HOURS`.\n\nThese keys are documented for deployment readiness only. The current repo state\ndoes not make live provider calls, and vendor modes remain\n`disabled_until_certified` while `REAL_ORDER_KILL_SWITCH=disabled`.\n"}, "358ad5fc4a90763637e0568cce244eaaf8c8f053": {"t": 1785772161.877389, "n": 6652, "h": "17b3a4ce0ad492c7", "c": "# CustomCard Infrastructure\n\nThis directory is the deployable service skeleton for the production path.\n\n- `docker-compose.dev.yml` runs app, worker, Postgres, Redis, and MinIO for local development.\n- `docker-compose.droplet.yml` is the cheap single-host deployment shape for a small DigitalOcean droplet.\n- `k8s/app.yaml` is the cloud-native starting point for web and worker deployments.\n- `migrations/001_initial_schema.sql` defines the durable service data model.\n- `env/.env.example` lists required secrets and kill switches.\n- `../scripts/api-server.mjs` serves `/api/health`, API bootstrap/readiness\n contracts, explicit contract/memory/Postgres runtime modes, and the built web\n app from `dist`.\n- `../src/artifactHandoff.ts` defines the render-packet artifact manifest and\n HMAC-signed URL contract used by the API/schema gates.\n- `../scripts/persistence-doctor.mjs` validates auth-session, idempotency, queue\n job, and audit persistence signals.\n- `../scripts/demo-reset.mjs` validates the reviewer demo reset contract before\n anyone attempts to run seed data against a database.\n- `../scripts/deployment-readiness.mjs` emits the local deployment readiness\n report used by `npm run deployment:doctor`.\n\nFor the droplet deployment, provide `POSTGRES_PASSWORD` from a real secret source\nor local deployment `.env` that is never committed. The compose file uses\n`${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD}` so the service fails closed instead\nof starting with an empty database password.\n\nFor Kubernetes, apply the `customcard-migrate` Job successfully before rolling\nthe web and worker Deployments. The deployment manifest keeps the live-order kill\nswitch explicit and uses probes so bad pods fail visibly.\n\nRun the local IaC readiness check before treating the manifests as reviewable:\n\n```sh\nnpm run deployment:doctor\nnpm run api:doctor\nnpm run api:doctor:memory\nnpm run persistence:doctor\nnpm run demo:doctor\n```\n\nThe report checks the local-dev, cheap-droplet, cloud-native, runtime, and data\nlanes. Passing this check means the committed deployment contracts are internally\nconsistent; it does not mean a real droplet or Kubernetes cluster has been\nprovisioned.\n\nThe Kubernetes web deployment probes `/api/health`, and the production Docker\nimage starts `scripts/api-server.mjs` so the same container can serve the static\nweb bundle and the contract-first API endpoints.\n\nThe API runtime defaults to `CUSTOMCARD_API_RUNTIME=contract` for reviewer/static\nserving. `npm run api:doctor:memory` sets test customer/admin session tokens and\nvalidates Bearer auth plus `X-Idempotency-Key` replay without a live database.\n`CUSTOMCARD_API_RUNTIME=postgres` is reserved for live database integration\ntesting and requires `DATABASE_URL`.\n\nThe persistence boundary requires auth-session storage, idempotency replay,\nqueue job envelopes, render-packet artifact manifests, signed URL expiry, and\nappend-only audit signals in the migration before production Postgres handlers\nare claimed.\n\nThe Kubernetes `Secret` in `k8s/app.yaml` is intentionally empty and annotated as\npre-created by a secret manager. Production clusters should source the required\nkeys from the platform secret manager, External Secrets, Sealed Secrets, or an\nequivalent operator-approved mechanism. The `runtime:doctor` check rejects empty\nvalues, known placeholders, and any live-order kill switch value other than\n`disabled`.\n\nSigned artifact handoff requires `OBJECT_STORE_SIGNING_SECRET` to be present and\nat least 32 characters. The committed manifests also keep\n`ARTIFACT_SIGNED_URL_TTL_MINUTES=15` explicit so download links remain\nshort-lived.\n\nReal external ordering stays disabled with `REAL_ORDER_KILL_SWITCH=disabled` until vendor sandbox tests and physical print certification are recorded.\n\n## Provider Adapter Secrets\n\n`env/.env.example` names the credential-gated adapters represented in\n`src/providerCatalog.ts`:\n\n- Google OAuth: `GOOGLE_OAUTH_CLIENT_ID`, `GOOGLE_OAUTH_CLIENT_SECRET`.\n- Microsoft Graph: `MICROSOFT_CLIENT_ID`, `MICROSOFT_CLIENT_SECRET`,\n `MICROSOFT_TENANT_ID`.\n- Hosted identity providers: `CUSTOMCARD_AUTH_CALLBACK_URL`, `AUTH0_DOMAIN`,\n `AUTH0_CLIENT_ID`, `AUTH0_CLIENT_SECRET`, `AUTH0_AUDIENCE`,\n `CLERK_SECRET_KEY`, `CLERK_JWT_KEY`, `CLERK_AUTHORIZED_PARTIES`,\n `SUPABASE_URL`, `SUPABASE_ANON_KEY`, `SUPABASE_SERVICE_ROLE_KEY`,\n `FIREBASE_API_KEY`, `FIREBASE_PROJECT_ID`,\n `FIREBASE_SERVICE_ACCOUNT_JSON`, `COGNITO_DOMAIN`,\n `COGNITO_USER_POOL_ID`, `COGNITO_APP_CLIENT_ID`.\n- Contact import providers: `CARDDAV_BASE_URL`, `CARDDAV_USERNAME`,\n `CARDDAV_APP_PASSWORD`, `CARDDAV_ADDRESSBOOK_PATH`.\n- Text and image AI providers: `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`,\n `AZURE_OPENAI_ENDPOINT`, `AZURE_OPENAI_API_KEY`,\n `AZURE_OPENAI_CHAT_DEPLOYMENT`, `AZURE_OPENAI_IMAGE_DEPLOYMENT`,\n `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_REGION`,\n `BEDROCK_TEXT_MODEL_ID`, `BEDROCK_IMAGE_MODEL_ID`,\n `GOOGLE_GENERATIVE_AI_API_KEY`, `MISTRAL_API_KEY`, `COHERE_API_KEY`,\n `PERPLEXITY_API_KEY`, `XAI_API_KEY`, `TOGETHER_API_KEY`, `GROQ_API_KEY`,\n `DEEPSEEK_API_KEY`, `FIREWORKS_API_KEY`, `STABILITY_API_KEY`,\n `HUGGINGFACE_API_TOKEN`, `REPLICATE_API_TOKEN`, `IDEOGRAM_API_KEY`,\n `LEONARDO_API_KEY`, `FAL_KEY`, `BFL_API_KEY`.\n- Self-hosted model fallback: `SELF_HOSTED_LLM_BASE_URL`,\n `SELF_HOSTED_LLM_API_KEY`.\n- Notification providers: `RESEND_API_KEY`, `SENDGRID_API_KEY`,\n `POSTMARK_SERVER_TOKEN`, `MAILGUN_API_KEY`, `MAILGUN_DOMAIN`,\n `TWILIO_ACCOUNT_SID`, `TWILIO_AUTH_TOKEN`,\n `TWILIO_MESSAGING_SERVICE_SID`, `WHATSAPP_ACCESS_TOKEN`,\n `WHATSAPP_PHONE_NUMBER_ID`, `EXPO_ACCESS_TOKEN`,\n `TRANSACTIONAL_EMAIL_API_KEY`, `TRANSACTIONAL_EMAIL_FROM`.\n- Payment sandbox providers: `STRIPE_SECRET_KEY`, `STRIPE_WEBHOOK_SECRET`,\n `CUSTOMCARD_PAYMENT_SUCCESS_URL`, `CUSTOMCARD_PAYMENT_CANCEL_URL`,\n `PAYPAL_CLIENT_ID`, `PAYPAL_CLIENT_SECRET`, `PAYPAL_WEBHOOK_ID`,\n `SQUARE_ACCESS_TOKEN`, `SQUARE_LOCATION_ID`,\n `SQUARE_WEBHOOK_SIGNATURE_KEY`, `ADYEN_API_KEY`,\n `ADYEN_MERCHANT_ACCOUNT`, `ADYEN_HMAC_KEY`.\n- Object-store artifact signer: `OBJECT_STORE_SIGNING_SECRET`.\n- Live vendor adapters: `WALGREENS_VENDOR_MODE`, `CVS_VENDOR_MODE`,\n `FEDEX_VENDOR_MODE`, `WALMART_VENDOR_MODE`, `STAPLES_VENDOR_MODE`,\n `OFFICE_DEPOT_VENDOR_MODE`.\n- Persistence controls: `CUSTOMCARD_API_RUNTIME`, `AUTH_SESSION_SECRET`,\n `CUSTOMCARD_CUSTOMER_SESSION_TOKEN`, `CUSTOMCARD_ADMIN_SESSION_TOKEN`,\n `IDEMPOTENCY_KEY_TTL_HOURS`.\n\nThese keys are documented for deployment readiness only. The current repo state\ndoes not make live provider calls, and vendor modes remain\n`disabled_until_certified` while `REAL_ORDER_KILL_SWITCH=disabled`.\n"}, "ae6d2b02ecb3109452af405d9636cd2850bef959": {"t": 1785772163.2182326, "n": 6297, "h": "6a89b404f2861b79", "c": "# CustomCard Infrastructure\n\nThis directory is the deployable service skeleton for the production path.\n\n- `docker-compose.dev.yml` runs app, worker, Postgres, Redis, and MinIO for local development.\n- `docker-compose.droplet.yml` is the cheap single-host deployment shape for a small DigitalOcean droplet.\n- `k8s/app.yaml` is the cloud-native starting point for web and worker deployments.\n- `migrations/001_initial_schema.sql` defines the durable service data model.\n- `env/.env.example` lists required secrets and kill switches.\n- `../scripts/api-server.mjs` serves `/api/health`, API bootstrap/readiness\n contracts, explicit contract/memory/Postgres runtime modes, and the built web\n app from `dist`.\n- `../src/artifactHandoff.ts` defines the render-packet artifact manifest and\n HMAC-signed URL contract used by the API/schema gates.\n- `../scripts/persistence-doctor.mjs` validates auth-session, idempotency, queue\n job, and audit persistence signals.\n- `../scripts/demo-reset.mjs` validates the reviewer demo reset contract before\n anyone attempts to run seed data against a database.\n- `../scripts/deployment-readiness.mjs` emits the local deployment readiness\n report used by `npm run deployment:doctor`.\n\nFor the droplet deployment, provide `POSTGRES_PASSWORD` from a real secret source\nor local deployment `.env` that is never committed. The compose file uses\n`${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD}` so the service fails closed instead\nof starting with an empty database password.\n\nFor Kubernetes, apply the `customcard-migrate` Job successfully before rolling\nthe web and worker Deployments. The deployment manifest keeps the live-order kill\nswitch explicit and uses probes so bad pods fail visibly.\n\nRun the local IaC readiness check before treating the manifests as reviewable:\n\n```sh\nnpm run deployment:doctor\nnpm run api:doctor\nnpm run api:doctor:memory\nnpm run persistence:doctor\nnpm run demo:doctor\n```\n\nThe report checks the local-dev, cheap-droplet, cloud-native, runtime, and data\nlanes. Passing this check means the committed deployment contracts are internally\nconsistent; it does not mean a real droplet or Kubernetes cluster has been\nprovisioned.\n\nThe Kubernetes web deployment probes `/api/health`, and the production Docker\nimage starts `scripts/api-server.mjs` so the same container can serve the static\nweb bundle and the contract-first API endpoints.\n\nThe API runtime defaults to `CUSTOMCARD_API_RUNTIME=contract` for reviewer/static\nserving. `npm run api:doctor:memory` sets test customer/admin session tokens and\nvalidates Bearer auth plus `X-Idempotency-Key` replay without a live database.\n`CUSTOMCARD_API_RUNTIME=postgres` is reserved for live database integration\ntesting and requires `DATABASE_URL`.\n\nThe persistence boundary requires auth-session storage, idempotency replay,\nqueue job envelopes, render-packet artifact manifests, signed URL expiry, and\nappend-only audit signals in the migration before production Postgres handlers\nare claimed.\n\nThe Kubernetes `Secret` in `k8s/app.yaml` is intentionally empty and annotated as\npre-created by a secret manager. Production clusters should source the required\nkeys from the platform secret manager, External Secrets, Sealed Secrets, or an\nequivalent operator-approved mechanism. The `runtime:doctor` check rejects empty\nvalues, known placeholders, and any live-order kill switch value other than\n`disabled`.\n\nSigned artifact handoff requires `OBJECT_STORE_SIGNING_SECRET` to be present and\nat least 32 characters. The committed manifests also keep\n`ARTIFACT_SIGNED_URL_TTL_MINUTES=15` explicit so download links remain\nshort-lived.\n\nReal external ordering stays disabled with `REAL_ORDER_KILL_SWITCH=disabled` until vendor sandbox tests and physical print certification are recorded.\n\n## Provider Adapter Secrets\n\n`env/.env.example` names the credential-gated adapters represented in\n`src/providerCatalog.ts`:\n\n- Google OAuth: `GOOGLE_OAUTH_CLIENT_ID`, `GOOGLE_OAUTH_CLIENT_SECRET`.\n- Microsoft Graph: `MICROSOFT_CLIENT_ID`, `MICROSOFT_CLIENT_SECRET`,\n `MICROSOFT_TENANT_ID`.\n- Hosted identity providers: `CUSTOMCARD_AUTH_CALLBACK_URL`, `AUTH0_DOMAIN`,\n `AUTH0_CLIENT_ID`, `AUTH0_CLIENT_SECRET`, `AUTH0_AUDIENCE`,\n `CLERK_SECRET_KEY`, `CLERK_JWT_KEY`, `CLERK_AUTHORIZED_PARTIES`,\n `SUPABASE_URL`, `SUPABASE_ANON_KEY`, `SUPABASE_SERVICE_ROLE_KEY`,\n `FIREBASE_API_KEY`, `FIREBASE_PROJECT_ID`,\n `FIREBASE_SERVICE_ACCOUNT_JSON`, `COGNITO_DOMAIN`,\n `COGNITO_USER_POOL_ID`, `COGNITO_APP_CLIENT_ID`.\n- Contact import providers: `CARDDAV_BASE_URL`, `CARDDAV_USERNAME`,\n `CARDDAV_APP_PASSWORD`, `CARDDAV_ADDRESSBOOK_PATH`.\n- Text and image AI providers: `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`,\n `AZURE_OPENAI_ENDPOINT`, `AZURE_OPENAI_API_KEY`,\n `AZURE_OPENAI_CHAT_DEPLOYMENT`, `AZURE_OPENAI_IMAGE_DEPLOYMENT`,\n `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_REGION`,\n `BEDROCK_TEXT_MODEL_ID`, `BEDROCK_IMAGE_MODEL_ID`,\n `GOOGLE_GENERATIVE_AI_API_KEY`, `MISTRAL_API_KEY`, `COHERE_API_KEY`,\n `PERPLEXITY_API_KEY`, `XAI_API_KEY`, `TOGETHER_API_KEY`, `GROQ_API_KEY`,\n `DEEPSEEK_API_KEY`, `FIREWORKS_API_KEY`, `STABILITY_API_KEY`,\n `HUGGINGFACE_API_TOKEN`, `REPLICATE_API_TOKEN`, `IDEOGRAM_API_KEY`,\n `LEONARDO_API_KEY`, `FAL_KEY`, `BFL_API_KEY`.\n- Self-hosted model fallback: `SELF_HOSTED_LLM_BASE_URL`,\n `SELF_HOSTED_LLM_API_KEY`.\n- Notification providers: `RESEND_API_KEY`, `SENDGRID_API_KEY`,\n `POSTMARK_SERVER_TOKEN`, `MAILGUN_API_KEY`, `MAILGUN_DOMAIN`,\n `TWILIO_ACCOUNT_SID`, `TWILIO_AUTH_TOKEN`,\n `TWILIO_MESSAGING_SERVICE_SID`, `WHATSAPP_ACCESS_TOKEN`,\n `WHATSAPP_PHONE_NUMBER_ID`, `EXPO_ACCESS_TOKEN`,\n `TRANSACTIONAL_EMAIL_API_KEY`, `TRANSACTIONAL_EMAIL_FROM`.\n- Object-store artifact signer: `OBJECT_STORE_SIGNING_SECRET`.\n- Live vendor adapters: `WALGREENS_VENDOR_MODE`, `CVS_VENDOR_MODE`,\n `FEDEX_VENDOR_MODE`, `WALMART_VENDOR_MODE`, `STAPLES_VENDOR_MODE`,\n `OFFICE_DEPOT_VENDOR_MODE`.\n- Persistence controls: `CUSTOMCARD_API_RUNTIME`, `AUTH_SESSION_SECRET`,\n `CUSTOMCARD_CUSTOMER_SESSION_TOKEN`, `CUSTOMCARD_ADMIN_SESSION_TOKEN`,\n `IDEMPOTENCY_KEY_TTL_HOURS`.\n\nThese keys are documented for deployment readiness only. The current repo state\ndoes not make live provider calls, and vendor modes remain\n`disabled_until_certified` while `REAL_ORDER_KILL_SWITCH=disabled`.\n"}, "571c21da9f3d8ef65b2692f5ea5d047b36aab94e": {"t": 1785772164.5759885, "n": 6039, "h": "1e6dbbd4c1efed72", "c": "# CustomCard Infrastructure\n\nThis directory is the deployable service skeleton for the production path.\n\n- `docker-compose.dev.yml` runs app, worker, Postgres, Redis, and MinIO for local development.\n- `docker-compose.droplet.yml` is the cheap single-host deployment shape for a small DigitalOcean droplet.\n- `k8s/app.yaml` is the cloud-native starting point for web and worker deployments.\n- `migrations/001_initial_schema.sql` defines the durable service data model.\n- `env/.env.example` lists required secrets and kill switches.\n- `../scripts/api-server.mjs` serves `/api/health`, API bootstrap/readiness\n contracts, explicit contract/memory/Postgres runtime modes, and the built web\n app from `dist`.\n- `../src/artifactHandoff.ts` defines the render-packet artifact manifest and\n HMAC-signed URL contract used by the API/schema gates.\n- `../scripts/persistence-doctor.mjs` validates auth-session, idempotency, queue\n job, and audit persistence signals.\n- `../scripts/demo-reset.mjs` validates the reviewer demo reset contract before\n anyone attempts to run seed data against a database.\n- `../scripts/deployment-readiness.mjs` emits the local deployment readiness\n report used by `npm run deployment:doctor`.\n\nFor the droplet deployment, provide `POSTGRES_PASSWORD` from a real secret source\nor local deployment `.env` that is never committed. The compose file uses\n`${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD}` so the service fails closed instead\nof starting with an empty database password.\n\nFor Kubernetes, apply the `customcard-migrate` Job successfully before rolling\nthe web and worker Deployments. The deployment manifest keeps the live-order kill\nswitch explicit and uses probes so bad pods fail visibly.\n\nRun the local IaC readiness check before treating the manifests as reviewable:\n\n```sh\nnpm run deployment:doctor\nnpm run api:doctor\nnpm run api:doctor:memory\nnpm run persistence:doctor\nnpm run demo:doctor\n```\n\nThe report checks the local-dev, cheap-droplet, cloud-native, runtime, and data\nlanes. Passing this check means the committed deployment contracts are internally\nconsistent; it does not mean a real droplet or Kubernetes cluster has been\nprovisioned.\n\nThe Kubernetes web deployment probes `/api/health`, and the production Docker\nimage starts `scripts/api-server.mjs` so the same container can serve the static\nweb bundle and the contract-first API endpoints.\n\nThe API runtime defaults to `CUSTOMCARD_API_RUNTIME=contract` for reviewer/static\nserving. `npm run api:doctor:memory` sets test customer/admin session tokens and\nvalidates Bearer auth plus `X-Idempotency-Key` replay without a live database.\n`CUSTOMCARD_API_RUNTIME=postgres` is reserved for live database integration\ntesting and requires `DATABASE_URL`.\n\nThe persistence boundary requires auth-session storage, idempotency replay,\nqueue job envelopes, render-packet artifact manifests, signed URL expiry, and\nappend-only audit signals in the migration before production Postgres handlers\nare claimed.\n\nThe Kubernetes `Secret` in `k8s/app.yaml` is intentionally empty and annotated as\npre-created by a secret manager. Production clusters should source the required\nkeys from the platform secret manager, External Secrets, Sealed Secrets, or an\nequivalent operator-approved mechanism. The `runtime:doctor` check rejects empty\nvalues, known placeholders, and any live-order kill switch value other than\n`disabled`.\n\nSigned artifact handoff requires `OBJECT_STORE_SIGNING_SECRET` to be present and\nat least 32 characters. The committed manifests also keep\n`ARTIFACT_SIGNED_URL_TTL_MINUTES=15` explicit so download links remain\nshort-lived.\n\nReal external ordering stays disabled with `REAL_ORDER_KILL_SWITCH=disabled` until vendor sandbox tests and physical print certification are recorded.\n\n## Provider Adapter Secrets\n\n`env/.env.example` names the credential-gated adapters represented in\n`src/providerCatalog.ts`:\n\n- Google OAuth: `GOOGLE_OAUTH_CLIENT_ID`, `GOOGLE_OAUTH_CLIENT_SECRET`.\n- Microsoft Graph: `MICROSOFT_CLIENT_ID`, `MICROSOFT_CLIENT_SECRET`,\n `MICROSOFT_TENANT_ID`.\n- Hosted identity providers: `CUSTOMCARD_AUTH_CALLBACK_URL`, `AUTH0_DOMAIN`,\n `AUTH0_CLIENT_ID`, `AUTH0_CLIENT_SECRET`, `AUTH0_AUDIENCE`,\n `CLERK_SECRET_KEY`, `CLERK_JWT_KEY`, `CLERK_AUTHORIZED_PARTIES`,\n `SUPABASE_URL`, `SUPABASE_ANON_KEY`, `SUPABASE_SERVICE_ROLE_KEY`,\n `FIREBASE_API_KEY`, `FIREBASE_PROJECT_ID`,\n `FIREBASE_SERVICE_ACCOUNT_JSON`, `COGNITO_DOMAIN`,\n `COGNITO_USER_POOL_ID`, `COGNITO_APP_CLIENT_ID`.\n- Contact import providers: `CARDDAV_BASE_URL`, `CARDDAV_USERNAME`,\n `CARDDAV_APP_PASSWORD`, `CARDDAV_ADDRESSBOOK_PATH`.\n- Text and image AI providers: `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`,\n `AZURE_OPENAI_ENDPOINT`, `AZURE_OPENAI_API_KEY`,\n `AZURE_OPENAI_CHAT_DEPLOYMENT`, `AZURE_OPENAI_IMAGE_DEPLOYMENT`,\n `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_REGION`,\n `BEDROCK_TEXT_MODEL_ID`, `BEDROCK_IMAGE_MODEL_ID`,\n `GOOGLE_GENERATIVE_AI_API_KEY`, `MISTRAL_API_KEY`, `COHERE_API_KEY`,\n `PERPLEXITY_API_KEY`, `XAI_API_KEY`, `TOGETHER_API_KEY`, `GROQ_API_KEY`,\n `DEEPSEEK_API_KEY`, `FIREWORKS_API_KEY`, `STABILITY_API_KEY`,\n `HUGGINGFACE_API_TOKEN`, `REPLICATE_API_TOKEN`, `IDEOGRAM_API_KEY`,\n `LEONARDO_API_KEY`, `FAL_KEY`, `BFL_API_KEY`.\n- Self-hosted model fallback: `SELF_HOSTED_LLM_BASE_URL`,\n `SELF_HOSTED_LLM_API_KEY`.\n- Notification contract: `TRANSACTIONAL_EMAIL_API_KEY`,\n `TRANSACTIONAL_EMAIL_FROM`.\n- Object-store artifact signer: `OBJECT_STORE_SIGNING_SECRET`.\n- Live vendor adapters: `WALGREENS_VENDOR_MODE`, `CVS_VENDOR_MODE`,\n `FEDEX_VENDOR_MODE`, `WALMART_VENDOR_MODE`, `STAPLES_VENDOR_MODE`,\n `OFFICE_DEPOT_VENDOR_MODE`.\n- Persistence controls: `CUSTOMCARD_API_RUNTIME`, `AUTH_SESSION_SECRET`,\n `CUSTOMCARD_CUSTOMER_SESSION_TOKEN`, `CUSTOMCARD_ADMIN_SESSION_TOKEN`,\n `IDEMPOTENCY_KEY_TTL_HOURS`.\n\nThese keys are documented for deployment readiness only. The current repo state\ndoes not make live provider calls, and vendor modes remain\n`disabled_until_certified` while `REAL_ORDER_KILL_SWITCH=disabled`.\n"}}}}