Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -624,10 +624,10 @@ When backend code uses the plugin read-path (`/updates`, `/stats`, `/channel_sel
- Logical replication replicates **table data**, not derived objects like **views** and **SQL functions**.
- Treat the read replica (what you see in PlanetScale) as the source of truth for what is queryable from plugin endpoints.
- Do **not** query credits ledger tables/views from the replica (e.g. `usage_credit_*` / `usage_credit_balances`). If plugin logic needs a “has credits” signal, **materialize it into a replicated column/table** (example: an org-level boolean flag that is refreshed by primary-side jobs).
- `/updates`, `/stats`, and `/channel_self` are extremely hot paths and can be called hundreds of times per second.
- Those endpoints must not call the primary Supabase/Postgres database in-request or through `backgroundTask()` side effects unless there is no other practical option.
- Background work is not an exception: do not enqueue primary-DB RPCs, writes, or lookups from these plugin endpoints just because the response is returned first.
- If an unavoidable primary write remains for one of these endpoints, keep it minimal, document the reason inline, and treat it as an exception that requires extra review.
`/updates`, `/stats`, and `/channel_self` are extremely hot paths and can be called hundreds of times per second, so they must never add direct primary-DB work, service-role RPCs, queue writes, or any other Postgres side effect from the live request path.
Those endpoints must not call the primary Supabase/Postgres database in-request or through `backgroundTask()` side effects unless there is no other practical option.
Background work is not an exception: do not enqueue primary-DB RPCs, writes, or lookups from these plugin endpoints just because the response is returned first.
If a feature seems to require direct DB work from these endpoints, stop and design an alternative that keeps the request path replica-safe and off the primary DB; treat such request-path side effects as forbidden unless explicitly approved, and document any unavoidable primary writes inline as an exception that needs extra review.

## Pull Request Guidelines

Expand Down
36 changes: 36 additions & 0 deletions tests/process-cron-stats-jobs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,40 @@ describe('cron_stat_app queue resilience', () => {
expect(queuedMessages[0]?.message?.payload?.orgId).toBe(ORG_ID_CRON_QUEUE)
expect(queuedMessages[0]?.message?.payload?.todayOnly).toBe(false)
})

it.concurrent('live /stats traffic does not enqueue cron_stat_app directly', async () => {
await clearCronStatAppMessages(liveQueueAppId)

const versionName = '1.0.0'
await createAppVersions(versionName, liveQueueAppId)

const baseData = getBaseData(liveQueueAppId)
const payload = {
...baseData,
action: 'set',
device_id: randomUUID().toLowerCase(),
version_build: versionName,
version_name: versionName,
}

const firstResponse = await fetch(getEndpointUrl('/stats'), {
method: 'POST',
headers,
body: JSON.stringify(payload),
})
expect(firstResponse.status).toBe(200)

const secondResponse = await fetch(getEndpointUrl('/stats'), {
method: 'POST',
headers,
body: JSON.stringify({
...payload,
device_id: randomUUID().toLowerCase(),
}),
})
expect(secondResponse.status).toBe(200)

const queuedMessages = await getCronStatAppMessages(liveQueueAppId)
expect(queuedMessages).toHaveLength(0)
})
})
Loading