> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pcnaid.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Pcnaid Safe Bulk architecture

> Runtime boundary, tenant model, governed change workflow, billing model, and reliability design.

# Pcnaid Safe Bulk architecture

## Runtime boundary

The application is a TypeScript Cloudflare Worker using Hono. Shopify App Bridge supplies staff session tokens, the Admin GraphQL API supplies catalog and subscription truth, the Worker connects to PostgreSQL, KV holds bounded rate-limit and recovery markers, and R2 holds import and privacy-export artifacts. PostgreSQL is authoritative for workflows, tenancy, replay control, billing history, and audit state.

```
Shopify Admin UI
  -> App Bridge token -> Worker route -> authorization + live billing check
  -> domain service -> Shopify Admin GraphQL
  -> tenant-scoped PostgreSQL rows -> audit/event response

Cloudflare Cron
  -> RLS-safe maintenance scan -> lifecycle-webhook backfill + stale/deferred-redaction alerts
  -> KV tenant marker -> installed-shop resolution + token refresh
  -> RLS tenant context -> atomic playbook/job claim -> Shopify + PostgreSQL
```

## Tenant and identity model

The verified `dest` in a Shopify session token resolves the installed shop. The Worker sets both shop UUID and domain PostgreSQL session variables before tenant SQL. All tenant tables enable and force row-level security. Three narrow security-definer functions are owned by a dedicated role; runtime SQL uses a separate application role that cannot bypass RLS.

The verified token `sub` is the immutable staff identity. Static assignments in `SAFE_BULK_AUTHZ_POLICY` and tenant `staff_roles` map it to one of four roles: operator, approver, executor, or admin. Route-level action checks occur after session authentication and before paid or mutating service calls.

Shopify OAuth access and refresh tokens are encrypted with AES-GCM before database storage. The encryption key supports rotation: a previous key stays valid for decryption while new writes use the current key.

## Governed change model

`bulk_projects` group `data_sources`, `change_sets`, and `playbooks`. A change set owns immutable intent rows in `change_items`, validation results, approvals, execution jobs, and rollback snapshots. Every cross-table reference includes `shop_id`; composite foreign keys reject a valid child UUID paired with the wrong tenant.

CSV import and playbook paths converge at the same diff, risk, and approval service. This keeps scheduling from bypassing governance.

### Change-set lifecycle

```
draft -> importing -> ready_for_approval -> approved -> executed -> rolled_back
                                    \-> rejected
```

* Import enqueues a durable background job that reads Shopify product snapshots in batches, computes field-level diffs, and persists items.
* Approval requires a configurable number of distinct approvers depending on risk score and critical items.
* Execution claims an approved set atomically, saves before-value snapshots, applies each item through Shopify, and records outcomes.
* Rollback reverses executed items from their captured snapshots.
* AI review is available during the approval phase, sampling up to 200 items for a draft assessment.

## Billing model

Two plan tiers are available:

| Limit | Safe Bulk Standard | Safe Bulk Growth |
| - | - | - |
| CSV rows per import | 5,000 | 25,000 |
| CSV bytes per import | 2 MiB | 4 MiB |
| Enabled scheduled playbooks | 20 | 100 |

`shops.subscription_*` is a deliberately disposable current-state projection. Shopify `currentAppInstallation.activeSubscriptions` is queried on callbacks, cancellations, and paid operations. A deployment setting controls whether paid writes enforce an active subscription. Verified Shopify development stores receive Growth access through a developer entitlement override.

`shop_billing_lifecycle` records first trial use, checkout reservations, and the last selected handle separately from the subscription projection to prevent granting a second trial or reducing a paid tier.

## Reliability model

Database uniqueness controls OAuth installs, webhook deliveries, idempotency requests, seed rows, and approvals. Webhook rows use a bounded processing lease so a crash after insertion is retryable.

CSV imports are durable, resumable background jobs. Upload enforcement of plan limits (byte and row ceilings) happens before any R2 write or database write. The worker processes imports in atomic 500-row batches, committing diff items together with row offset progress. A recovery-tenant marker is registered before any external or database write.

The scheduler runs every ten minutes and handles:

* Reclaiming queued or stale-running jobs
* Running due scheduled playbooks
* Lifecycle maintenance (webhook backfill, deferred redaction)
* Expired privacy-export artifact cleanup

### Scheduler and durable jobs

A key-value store holds tenant registry markers. PostgreSQL atomically claims due playbooks, execution and rollback jobs, and durable import jobs with `FOR UPDATE SKIP LOCKED`. A request starts execution immediately for responsive feedback, while CSV imports return HTTP 202 and process asynchronously.

Every running claim has an opaque lease token. Each committed batch conditionally advances its expected `next_offset` under that token before inserting items. Execution and rollback heartbeat the token at least once per item. Recovery caps attempts at five and removes inactive markers.

### GraphQL rate-limit handling

Admin GraphQL requests route through a unified client that monitors Shopify's leaky-bucket rate limiter via `extensions.cost.throttleStatus`. The client pre-waits when projected quota is below estimated cost. Interactive execution uses a 5-second request budget per call plus a 20-second total inline budget; background recovery uses the 60-second per-call job budget. GraphQL `THROTTLED` and HTTP 429 responses are retried. Mutations that are value-idempotent (including handle-keyed `productSet`) set `idempotent: true` for safe retry.

## Browser rendering boundary

`@pcnaid/shopify-worker-core` supplies the shared browser runtime and HTML-escaping primitive. The UI table helper treats every heading, caption, and ordinary cell as text. A cell may contain markup only when application code constructs it with the runtime's branded `trustedHtmlCell()` helper. Link destinations pass through `appRelativePath()` to prevent `javascript:` and cross-origin links.

## Public routes

The app exposes these public routes:

* `GET /health` — returns app identity, build SHA, and billing configuration status
* `GET /sitemap.xml` — XML sitemap with public support page URLs
* `GET /robots.txt` — robot exclusion rules
* `GET /support` — public support page
* `GET /privacy` and `GET /terms` — HTTP 301 redirects to docs.pcnaid.com

[Pcnaid Documentation](https://docs.pcnaid.com/namespace/index.md) › [Shopify Ecosystem](https://docs.pcnaid.com/hubs/shopify-ecosystem/index.md) › [Pcnaid Safe Bulk](https://docs.pcnaid.com/nodes/shopify/safe-bulk/index.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.