agents/openai.yaml
interface:
display_name: "InsForge Debug"
short_description: "Diagnose InsForge backend errors and run health audits."
brand_color: "#17B26A"
default_prompt: "Use $insforge-debug to diagnose this backend error."
references/advisor.md
# Advisor
Static-scan engine that audits the project against a rule catalog and returns issue rows. The primary primitive for **proactive audits** — security misconfigurations, performance regressions, and system health concerns surfaced **without a specific failing request**.
## Command
```bash
npx @insforge/cli diagnose advisor [--severity critical|warning|info] [--category security|performance|health] [--limit <n>] [--json]
```
Default limit: 50. Requires Platform login — **not available on backends linked via `--api-key`**.
**Add `--json` when you need the full issue payload.** Human-readable output is a 4-column table (severity / category / affectedObject / title). Fields like `ruleId`, `description`, `recommendation`, `isResolved` are only emitted by `--json` — without the flag, you can't read or act on the recommendation programmatically.
## What you see
Each scan returns:
**Scan summary** — `scanId`, `scannedAt`, status, and counts by severity (critical / warning / info).
**Issue rows** — each issue has:
| Field | Meaning |
|-------|---------|
| `ruleId` | Stable identifier of the rule that fired (e.g., `security.rls.missing-policy`) |
| `severity` | `critical` / `warning` / `info` |
| `category` | `security` / `performance` / `health` |
| `affectedObject` | The specific schema object (table, function, policy, secret name) the issue applies to |
| `title` | Short human label |
| `description` | What the rule detected |
| `recommendation` | Suggested fix, often a concrete SQL/CLI step |
| `isResolved` | `true` after a re-scan confirms the fix |
## Categories
| Category | Typical rules | Fix in |
|----------|--------------|--------|
| `security` | RLS missing/permissive on a table, expired/exposed secrets, weak JWT config, public bucket on sensitive data | `db migrations` (RLS), `secrets` (key rotation), `metadata` (auth/bucket config) |
| `performance` | Missing index on heavy filter column, bloat over threshold, sequential scans on large tables, slow recurring query | `db migrations` (indexes), query rewrite, vacuum tuning |
| `health` | Connection pool near limit, EC2 disk filling, deprecated feature in use, version drift | Infra resize, code change, version upgrade |
## How to read
1. **Start with severity**: `--severity critical` first; critical issues block launch.
2. **Group by category** to keep the fix mode coherent (don't context-switch between RLS edits and index migrations).
3. **`affectedObject` tells you where to fix** — it names the concrete schema object.
4. **`recommendation` is usually actionable as-is**. Verify it makes sense (the recommendation may be generic), then apply via the appropriate primitive's tooling.
## Iteration workflow
```text
1. Scan: diagnose advisor --severity critical --json
2. Triage: pick one issue, read affectedObject + recommendation
3. Verify: inspect the affected object (db query / db policies / metadata)
4. Fix: apply the change (migration / RLS edit / secret rotation / config update)
5. Re-scan: diagnose advisor --json — confirm isResolved=true for that ruleId
6. Repeat with next critical, then warnings, then info
```
## Boundaries
- **Scans are not real-time.** A new scan triggers when the platform schedules it; recommendations lag behind very recent changes. Force a fresh scan if needed.
- **Recommendations are static suggestions, not auto-fixes.** Always validate against current schema state before applying.
- **`affectedObject` is a string, not a typed reference.** It names the object but doesn't link to it — combine with [metadata](metadata.md) / [policies](policies.md) to inspect.
- **Not available when linked via `--api-key`.** Requires `insforge login` (Platform auth).
## Example
Pre-launch audit:
```bash
# Full scan, critical only first
npx @insforge/cli diagnose advisor --severity critical
# Security focus
npx @insforge/cli diagnose advisor --category security
# Performance focus (often pairs with db-health to verify)
npx @insforge/cli diagnose advisor --category performance
npx @insforge/cli diagnose db --check slow-queries,index-usage
# Re-scan after fixes
npx @insforge/cli diagnose advisor --severity critical
```
## Frequently paired with
- [policies](policies.md) — security category issues often name a table with missing/broken RLS policy
- [metadata](metadata.md) — security/health issues often name a configured object (bucket, secret, auth provider) whose state needs inspecting
- [db-health](db-health.md) — performance category overlaps with `slow-queries` / `index-usage` / `bloat`; cross-verify
- [metrics](metrics.md) — health category issues (pool exhaustion, disk fill) line up with metric trends
references/ai-assisted.md
# AI assist
The meta-primitive: hand a natural-language problem description to a backend-side LLM agent that combines the other primitives ([logs](logs.md), [metrics](metrics.md), [db-health](db-health.md), [advisor](advisor.md), [policies](policies.md), [metadata](metadata.md)) on its own and returns a diagnosis plus suggested solutions.
**Unlike every other primitive in this skill, `diagnose --ai` returns suggestions, not just observations.** Verify before acting.
## Command
```bash
npx @insforge/cli diagnose --ai "<issue description>"
```
The description should include: the error / failing URL / HTTP status / function slug — whatever concrete signal the user has.
## When to use first
- User pasted an error, request URL, or status code and asks "why?"
- You want a fast first pass before deciding which primitive to drill into
- The problem spans multiple subsystems (frontend + backend + database) and the right starting primitive isn't obvious
## When to skip
- The symptom clearly maps to one primitive (e.g., "Vercel deploy failed" → go straight to [deploy-state](deploy-state.md))
- You're doing a proactive audit (no concrete error — that's [advisor](advisor.md))
- You already know exactly which log source has the error
## How to verify the output
The agent's diagnosis names primitives and observations. Re-check each:
| If the diagnosis says... | Verify with... |
|--------------------------|----------------|
| "An RLS policy is blocking the request" | [policies](policies.md) — read the actual policy on that table |
| "Slow query on table X" | [db-health](db-health.md) `slow-queries` + [logs](logs.md) `postgres.logs` for the actual query |
| "Function is timing out" | [logs](logs.md) `function.logs` — read the actual timeout/error stack |
| "Connection pool exhausted" | [db-health](db-health.md) `connections` — confirm the count and idle-in-transaction state |
| "Missing index on column Y" | [db-health](db-health.md) `index-usage` + [advisor](advisor.md) performance — both should agree |
If the verification disagrees with the diagnosis, **trust the primitive observation**, not the suggestion. Suggestions can be plausible-sounding but wrong (LLM may pattern-match on similar errors); raw `pg_stat` numbers and log lines can't lie.
## Boundaries
- **Returns suggestions, not just data.** Different from every other primitive — treat the output as a starting hypothesis, not a verdict.
- **Doesn't replace [advisor](advisor.md).** Advisor surfaces issues based on a static rule catalog; `--ai` reasons about a specific reported symptom. They serve different goals.
- **Consumes the other primitives.** When this fails or seems off, fall back to the primitives directly.
## Example
User pastes: "I invoked `https://kttprzh4.functions.insforge.app/newton` and got `508: Loop Detected (LOOP_DETECTED). Recursive requests to the same deployment cannot be processed.`"
```bash
npx @insforge/cli diagnose --ai "I invoked edge function https://kttprzh4.functions.insforge.app/newton, got error: 508: Loop Detected (LOOP_DETECTED)\n\nRecursive requests to the same deployment cannot be processed."
```
Read the diagnosis and suggestions, then verify with:
```bash
# Verify with function logs
npx @insforge/cli logs function.logs --limit 50
# Verify the function code doesn't actually call itself
npx @insforge/cli functions code newton
```
## Frequently paired with
- All other primitives — `--ai` consumes them and you verify back against them. Treat AI assist as a router that points at primitives; the primitives are the ground truth.
references/db-health.md
# DB health
Postgres system views (`pg_stat_*`, `pg_locks`, `pg_class`) exposed as named checks. The primary primitive for **current database state** — connection pool, query performance, lock contention, storage, index efficacy.
## Command
```bash
npx @insforge/cli diagnose db [--check <checks>]
```
`--check` accepts comma-separated names. Default `all`. Run no-arg first when triaging unknown DB issues.
## Checks
| Check | Underlying view | What it tells you |
|-------|-----------------|-------------------|
| `connections` | `pg_stat_activity` | Active vs idle vs idle-in-transaction count; how close to pool limit |
| `slow-queries` | `pg_stat_statements` | Queries by total/mean exec time; which queries dominate load |
| `bloat` | `pg_class`, `pg_stats` | Table/index dead-row bloat (vacuum lag, write-heavy tables) |
| `size` | `pg_class` size functions | Table and index disk footprint |
| `index-usage` | `pg_stat_user_indexes` | Indexes that are never scanned (unused) vs heavy hitters |
| `locks` | `pg_locks` + `pg_stat_activity` | Lock contention, blocking queries, deadlock candidates |
| `cache-hit` | `pg_statio_*` | Buffer cache hit ratio (low = working set exceeds RAM) |
## How to read
| Reading | Likely problem | Next step |
|---------|---------------|-----------|
| `connections` near pool limit + many idle-in-transaction | Connection leak in client code | Find client missing `release()` or transaction not committing |
| `slow-queries` top entry called frequently | Missing index or bad plan | Check `index-usage` for that table; consider migration |
| `bloat` high on actively-written table | Vacuum not keeping up | Schedule vacuum tighter or rewrite query pattern |
| `index-usage` shows unused indexes | Wasted write cost | Drop unused indexes (after confirming they're truly unused) |
| `locks` with blocker/blocked pairs | Long transactions or deadlocks | Kill blocking PID after investigation, fix the lock-holding query |
| `cache-hit` < 0.99 | Working set exceeds RAM | Tune queries to reduce buffer churn, or scale |
## Boundaries
- **Current snapshot, not history.** `pg_stat_*` resets on Postgres restart; numbers are cumulative since last reset, not a time series.
- **Doesn't show query text inline.** `slow-queries` shows hashed query templates — get the actual SQL from [logs](logs.md) (`postgres.logs`) or `pg_stat_statements.query` directly via `db query`.
- **Doesn't evaluate RLS.** For "which policy is making this query slow," use [policies](policies.md).
## Example
User reports: "this one query is slow — `SELECT * FROM orders WHERE user_id = ... ORDER BY created_at DESC`".
```bash
# 1. Confirm it's in the slow-query log and check index usage on the table
npx @insforge/cli diagnose db --check slow-queries,index-usage
# 2. Verify no lock contention from a concurrent writer
npx @insforge/cli diagnose db --check locks
# 3. Cross-reference postgres.logs for the actual query plan / errors
npx @insforge/cli logs postgres.logs --limit 100
```
## Frequently paired with
- [logs](logs.md) — `postgres.logs` has the query text, plans, and error context behind `slow-queries`/`locks` aggregates
- [policies](policies.md) — when slow queries are RLS-gated, the policy may be adding hidden joins
- [metrics](metrics.md) — DB pressure usually shows as EC2 CPU/memory pressure; cross-reference timestamps
- [advisor](advisor.md) — performance category often pre-flags the same issues `slow-queries` / `index-usage` would surface
references/deploy-state.md
# Deploy state
History and per-deploy metadata for both frontend deployments (Vercel) and edge function deploys. The primary primitive for debugging **what happened during a deploy** — separate from runtime logs because deploy failures often never leave a runtime trace.
## Commands
```bash
# Frontend (Vercel)
npx @insforge/cli deployments list
npx @insforge/cli deployments status <id> [--sync] [--json]
# Edge function deploys
npx @insforge/cli logs function-deploy.logs [--limit <n>]
npx @insforge/cli functions list
```
## Frontend deployments
`deployments list` returns recent deploys with status. `deployments status <id> --json` returns per-deploy metadata:
| Field | Meaning |
|-------|---------|
| `status` | `pending` / `building` / `ready` / `error` / `canceled` |
| `metadata.target` | Vercel deploy target (production / preview) |
| `metadata.fileCount` | Number of files uploaded |
| `metadata.projectId` | Vercel project ID |
| `metadata.startedAt` | Build start time |
| `metadata.envVarKeys` | Env var keys baked into the build (values redacted) |
| `metadata.webhookEventType` | e.g., `deployment.succeeded`, `deployment.error` |
`--sync` re-fetches from Vercel (use when the local cached status looks stale).
## Edge function deploys
`function-deploy.logs` captures backend deploy events (compile errors, push failures, registration errors). `functions list` confirms the final state — if the function isn't there or `status != active`, the deploy didn't fully take.
## How to read
For "frontend deploy failed":
1. `deployments list` — find the failing deploy id
2. `deployments status <id> --json` — read `status` and the `metadata` block; `webhookEventType` usually names the failure stage
3. Reproduce locally: `npm run build` — server deploys often surface the same error that local build would (faster to debug locally)
4. Verify `envVarKeys` matches what the app needs at runtime
For "function deploy failed":
1. `npx @insforge/cli logs function-deploy.logs --limit 50` — find the build/push error
2. `npx @insforge/cli functions list` — confirm the function did or didn't make it into the active list
3. Re-run `npx @insforge/cli functions deploy <slug>` if needed and capture stdout for the explicit error
## Boundaries
- **Doesn't surface Vercel build logs inline.** Detailed Vercel build output lives in the Vercel dashboard or via `vercel logs`; this primitive surfaces the deploy *event metadata*.
- **Doesn't include compute service (Fly) deploy errors.** For compute services use `npx @insforge/cli compute events <id>` (machine lifecycle); container stdout/stderr is not yet exposed.
- **Distinct from runtime errors.** A function with a successful deploy can still error at invoke time — that's [logs](logs.md) `function.logs`, not this primitive.
## Example
User reports: "I ran `deployments deploy` and got an error."
```bash
# 1. Get the most recent deploy id and status
npx @insforge/cli deployments list
# 2. Full metadata on the failed one
npx @insforge/cli deployments status <id> --json
# 3. Reproduce locally to see the actual build error
npm run build
```
User reports: "`functions deploy my-handler` failed."
```bash
# 1. Re-run to capture the explicit error
npx @insforge/cli functions deploy my-handler
# 2. Backend-side deploy log
npx @insforge/cli logs function-deploy.logs --limit 50
# 3. Confirm the function isn't half-deployed
npx @insforge/cli functions list
```
## Frequently paired with
- [logs](logs.md) — `function-deploy.logs` is the source; `function.logs` is separate (runtime, not deploy)
- [metadata](metadata.md) — verify the function ended up `active` after deploy
references/error-objects.md
# Error objects
The error envelope returned by SDK calls and HTTP responses. The **entry-point primitive** — almost every reactive debug starts by reading the error and routing to the right log source / primitive.
This primitive isn't a command; it's the protocol for reading what the client got back, plus the routing tables that turn an error into "look here next."
## SDK error envelope
```ts
const { data, error } = await client.database.from('posts').select('*')
// error: { code: string, message: string, details?: string }
```
Read all three fields. `code` routes to the right log source; `message` and `details` provide context.
## Error code → log source
| Code prefix | Source | Look in |
|-------------|--------|---------|
| `PGRST*` (PostgREST errors: `PGRST204`, `PGRST301`, etc.) | PostgREST API layer | [logs](logs.md) `postgREST.logs` |
| SQL state codes (`23505` unique violation, `42P01` undefined table, etc.) | Postgres | [logs](logs.md) `postgres.logs` |
| `AUTH_*` / OAuth errors | Backend auth | [logs](logs.md) `insforge.logs` |
| Generic 500 / no code | Server error in backend | [logs](logs.md) — start with `diagnose logs` aggregate, then drill |
## HTTP status routing
| Status | What it means | Where to look |
|--------|---------------|---------------|
| **400** | Request payload/params malformed | [logs](logs.md) `postgREST.logs` for validation error |
| **401** | Auth token missing / invalid / expired | [logs](logs.md) `insforge.logs` + [metadata](metadata.md) auth config |
| **403** | RLS policy or permission denied | [logs](logs.md) `postgREST.logs` + [policies](policies.md) + [metadata](metadata.md) |
| **404** | Endpoint or resource doesn't exist | [metadata](metadata.md) — verify the table/function/bucket exists |
| **429** | Rate limit hit | **No logs** — see 429 note below |
| **500** | Server-side error | [logs](logs.md) `diagnose logs` (aggregate) → drill into specific source |
| **502 / 503 / 504** | Gateway / upstream timeout | Route by URL subsystem — see 5xx gateway note |
## 429 — special case
429 responses are **not logged** in any source and the backend does **not return** `Retry-After` or `X-RateLimit-*` headers. Checking logs is useless.
What to do:
1. Read the client code that issued the request. Look for: loops without throttling, missing debounce, retry-on-failure without exponential backoff, parallel calls that could be batched.
2. Check [metrics](metrics.md) for overall backend load context — is the system being slammed?
3. **The fix is always client-side**: reduce frequency, add backoff/debounce, batch operations.
## 5xx gateway timeout (502 / 503 / 504) — route by URL subsystem
The status alone doesn't tell you which subsystem timed out. Route by the URL path:
| URL pattern | Subsystem | Look in |
|-------------|-----------|---------|
| `/api/database/records/...` or `/api/database/...` | PostgREST → Postgres | [logs](logs.md) `postgREST.logs` then `postgres.logs`; [db-health](db-health.md) `locks`, `slow-queries` |
| `/functions/<slug>` | Edge function | [logs](logs.md) `function.logs`; check function isn't crash-looping |
| `/api/auth/...` | Auth backend | [logs](logs.md) `insforge.logs` |
| `/api/storage/...` | Storage | [logs](logs.md) `insforge.logs` |
| Any path during a system-wide spike | EC2 saturation | [metrics](metrics.md) `--range 1h` |
## Example
User pastes: `POST /api/database/records/posts returned { code: "PGRST204", message: "Column not found" }`.
1. Read code: `PGRST*` → PostgREST source. `PGRST204` specifically means **column not found** — a `select` / `columns` / `order` / filter parameter references a column that doesn't exist on the table.
2. Read `message` / `details` to identify the offending column name. Verify the table schema with `npx @insforge/cli db tables` or `npx @insforge/cli metadata --json`.
3. Drop into `postgREST.logs` for the full error context if `details` is empty:
```bash
npx @insforge/cli logs postgREST.logs --limit 50
```
## Frequently paired with
- [logs](logs.md) — every code routes to a log source for the actual error context
- [policies](policies.md) — 403 always needs policy inspection
- [metadata](metadata.md) — 404 / config-related codes need metadata to confirm the configured state
- [ai-assisted](ai-assisted.md) — when the code is unfamiliar or the error spans subsystems, hand it to `diagnose --ai`
references/logs.md
# Logs
Time-stream of events emitted by each backend service. The primary primitive for finding **when** something failed and **what the backend was doing at that moment**.
## Command
```bash
npx @insforge/cli logs <source> [--limit <n>]
```
Default limit: 20. Source names are **case-insensitive** (`postgrest.logs` works the same as `postgREST.logs`).
## Sources
| Source | What it contains | Reach for when |
|--------|------------------|----------------|
| `insforge.logs` | Main backend (auth, API gateway, realtime, edge function dispatcher, deploy controller) | Auth/OAuth errors, realtime WS errors, generic 5xx, signup/login failures |
| `postgREST.logs` | PostgREST API layer (REST/CRUD over the database) | 400 (payload), 403 (RLS denied), PGRST* error codes |
| `postgres.logs` | PostgreSQL itself | SQL errors, query warnings, slow query log, deadlock, constraint violations |
| `function.logs` | Edge function **execution** (per-invocation runtime) | Function threw / timed out / unhandled rejection |
| `function-deploy.logs` | Edge function **deploy** (build + push) | `functions deploy` failed; function isn't in `functions list` |
## Cross-source aggregate
```bash
npx @insforge/cli diagnose logs [--source <name>] [--limit <n>]
```
Aggregates **error-level only** rows across all sources. Use first when you don't know which source the error lives in.
## How to read
Each line has timestamp + source + level + message. When chasing a known-time symptom:
1. Get the approximate timestamp from the user (when did the request fail?)
2. Increase `--limit` until the window covers it (start 50, bump to 200 if needed)
3. Look for the level (`ERROR` / `WARN`) and message — the message usually names the failing component
For request-correlated symptoms (single failing URL), look for the request line in `postgREST.logs` (REST calls) or `insforge.logs` (auth/realtime/function dispatch) — both include the URL path.
## Boundaries
- **Logs are streamed, not retained forever.** If the user reports something from days ago, you may not find it. State this explicitly instead of guessing.
- **429 responses are NOT logged.** Rate limit hits don't appear in any source — confirm via [error-objects](error-objects.md) status code and check [metrics](metrics.md) for backend load context.
- **`diagnose logs` filters to errors only.** For warnings or info-level activity, query the specific source directly.
## Example
User reports: `POST /api/database/records/posts returned 500 around 14:32`.
```bash
# Aggregate first to see if the error surfaces anywhere
npx @insforge/cli diagnose logs --limit 100
# If it's a CRUD path, postgREST is the likely source
npx @insforge/cli logs postgREST.logs --limit 100
# If postgREST log shows "SQL error", drop into postgres
npx @insforge/cli logs postgres.logs --limit 100
```
## Frequently paired with
- [error-objects](error-objects.md) — start there to pick the right source from the error code/HTTP status
- [db-health](db-health.md) — when postgres.logs shows slow/locked queries, confirm with `pg_stat_*`
- [policies](policies.md) — when postgREST.logs shows RLS denial, inspect which policy fired
- [metadata](metadata.md) — when logs show auth/function/channel errors, verify the configured state
references/metadata.md
# Metadata
Declarative dump of the backend's configured state: auth provider config, database tables, storage buckets, edge functions, AI models, realtime channels. The primary primitive for **what the project is set up to do** — used to confirm configuration matches expectations, find misconfiguration, and detect drift.
## Command
```bash
npx @insforge/cli metadata [--json]
```
`--json` for structured output (preferred when extracting fields for follow-up commands).
## What you see
| Section | Contains |
|---------|----------|
| **Auth** | Configured providers (email/password, OAuth providers, third-party JWT), JWT config, allowed redirect URLs |
| **Database** | Tables with schema (columns, types), indexes, triggers |
| **Storage** | Buckets with `public` flag and size limits |
| **Functions** | Edge functions with slug, `status` (`active` / `inactive`), runtime |
| **AI** | Configured models, OpenRouter key presence |
| **Realtime** | Channel patterns and enabled flag |
## How to read
For "is this thing configured the way I think?":
| Symptom | Section to check | What to look for |
|---------|------------------|------------------|
| OAuth callback errors | Auth | Provider enabled? Redirect URLs match the callback in the request? |
| 401 / token-expired everywhere | Auth | JWT secret rotation, third-party provider integration mismatch |
| 404 on `/api/database/records/<name>` | Database | Table exists in the dump? Spelling? |
| Storage upload silently public | Storage | Bucket `public: true` when it should be `false` |
| Edge function returns 404 | Functions | Function in list with `status: "active"`? |
| `functions deploy` succeeded but invoke fails | Functions | Function `status` — may be inactive |
| Realtime channel "doesn't exist" | Realtime | Channel pattern matches what client subscribes to? `enabled: true`? |
## Boundaries
- **Configuration state, not runtime state.** Tells you what's *declared*, not what's *currently broken*. A function with `status: active` may still be crashing on every invocation — pair with [logs](logs.md) (`function.logs`).
- **Doesn't show RLS policies.** For RLS use [policies](policies.md); metadata only confirms the table exists.
- **Snapshot at query time.** Just-applied migrations or deploys may not yet be reflected — wait a moment and re-query if you suspect staleness.
## Example
User reports: "OAuth login with Google redirects but then errors out."
```bash
# 1. Pull current auth config
npx @insforge/cli metadata --json
# 2. In the auth section, confirm:
# - google provider enabled: true
# - redirect URLs include the exact callback the app uses
# (e.g., https://myapp.com/auth/callback — protocol + host + path must match)
# 3. If config looks right, check insforge.logs for the OAuth error
npx @insforge/cli logs insforge.logs --limit 50
```
## Frequently paired with
- [logs](logs.md) — metadata says "configured" but logs say "actually broken"; pair to distinguish config drift vs runtime failure
- [policies](policies.md) — metadata confirms the table; policies show the RLS gating it
- [advisor](advisor.md) — security/health issues often name a configured object (bucket, secret, function) for inspection
references/metrics.md
# Metrics
EC2 instance time-series telemetry: CPU, memory, disk, network. The primary primitive for confirming **system-wide resource pressure** vs an isolated request issue.
## Command
```bash
npx @insforge/cli diagnose metrics [--range 1h|6h|24h|7d] [--metrics <list>]
```
Default range: `1h`.
## What you see
| Metric family | Indicates |
|---------------|-----------|
| **CPU** | Compute saturation (sustained >80% = trouble; spike & recover = normal) |
| **Memory** | Memory pressure (rising over time → leak; near limit + OOM kills → resize) |
| **Disk** | Storage fill rate, IO saturation (read/write throughput, queue depth) |
| **Network** | Inbound/outbound bandwidth, packet rate (sudden spike = traffic surge or attack) |
## Range selection
| Symptom | Range | Why |
|---------|-------|-----|
| Active incident ("everything is slow right now") | `1h` | High-resolution to catch the spike |
| "It was slow ~6 hours ago, what happened?" | `6h` | Cover the window with reasonable resolution |
| "Has performance degraded this week?" | `24h` or `7d` | Trend analysis, not point-in-time |
| Pre-launch baseline | `7d` | Establish normal range before traffic |
## How to read
1. **Start from baseline**: what does the normal range look like for this metric? Always look at the trend, not a single point.
2. **Correlate to events**: spike at a specific timestamp → cross-reference [logs](logs.md) for what was happening then.
3. **Distinguish saturation vs spike**:
- Sustained high = saturation → scale up or fix the load source
- Brief spike + recovery = normal burst → not actionable on its own
## Boundaries
- **Instance-level only.** Metrics show the EC2 box's resource use, not per-request latency or per-query cost. For request-specific perf, combine with [logs](logs.md) (`postgres.logs` for slow queries) and [db-health](db-health.md).
- **Doesn't explain causes.** Metrics show *symptoms* (CPU high), not *causes* (which query, which function). Pair with [logs](logs.md) or [db-health](db-health.md) for root cause.
- **Edge function execution is separate.** Functions run in their own runtime; their resource use isn't in EC2 metrics.
## Example
User reports: "API has been slow for the last 2 hours."
```bash
# 1. Check resource pressure over the right window
npx @insforge/cli diagnose metrics --range 6h
# 2. If CPU/memory spiked at a timestamp, line it up with errors
npx @insforge/cli diagnose logs --limit 200
# 3. If DB is the bottleneck (Postgres-heavy CPU patterns)
npx @insforge/cli diagnose db
```
## Frequently paired with
- [db-health](db-health.md) — DB is the most common bottleneck behind CPU/memory pressure
- [logs](logs.md) — correlate metric spikes to log events at the same timestamp
- [advisor](advisor.md) — `--severity critical` may already flag the underlying cause (e.g., missing index)
references/policies.md
# Policies
Active RLS (Row-Level Security) rules pulled from Postgres's `pg_policies` system view. The primary primitive for **what RLS is currently allowing or denying** — distinct from "what config we *meant* to deploy" (that lives in [metadata](metadata.md) / migration history).
## Command
```bash
npx @insforge/cli db policies
```
Returns every policy: table, schema, policy name, command (`SELECT` / `INSERT` / `UPDATE` / `DELETE` / `ALL`), target role, `USING` expression, `WITH CHECK` expression.
Only specific InsForge-managed tables allow developer RLS changes. Check the relevant module skill or CLI reference before writing policy SQL for a managed table. If that table is listed as allowing RLS changes, normal RLS operations are allowed and should go in migrations.
## Policy anatomy
| Field | Meaning |
|-------|---------|
| `tablename` | Which table the policy applies to |
| `cmd` | Which operation it gates (read/write/all) |
| `roles` | Which DB roles the policy applies to (typically `authenticated`, `anon`, `public`) |
| `qual` (USING) | Filter expression: **which existing rows the role can see/modify**. Applied to `SELECT`, `UPDATE`, `DELETE`. |
| `with_check` | Validation expression: **which new/modified rows are allowed**. Applied to `INSERT`, `UPDATE`. |
| `permissive` / `restrictive` | Permissive policies OR together; restrictive AND. Most InsForge projects use permissive. |
## How to read
For "why was this request denied?":
1. Identify the table from the request URL (`/api/database/records/<table>`).
2. List policies for that table — note `cmd` and `roles`.
3. Walk the `USING` / `WITH CHECK` expressions against the actual request:
- **No policy for that role+cmd combo** → denied by default. Need to add a policy.
- **`USING` evaluates false for this row** → row is invisible / not modifiable. Confirm the helper (e.g., `auth.uid()` returns the expected user_id).
- **`WITH CHECK` evaluates false on insert** → the new row's columns violate the policy. The insert payload is wrong, or the policy is too strict.
## Common RLS bug shapes
| Symptom | Likely cause |
|---------|-------------|
| All authenticated users see all rows (no isolation) | Policy is `USING (true)` — too permissive; restrict by `user_id` column |
| Authenticated user gets empty result on own data | Wrong helper function (`auth.uid()` returns UUID; if `user_id` column is TEXT from third-party auth, use `requesting_user_id()` instead) |
| Insert fails for owner with "new row violates RLS policy" | Missing `WITH CHECK` matching the `USING`, or `WITH CHECK` references columns not in payload |
| Third-party auth (Clerk/Auth0/etc.) users get blanket deny | Wrong helper (`auth.uid()` expects InsForge-issued JWT; third-party providers need `requesting_user_id()` with the right claim extraction) |
| Anonymous user can read sensitive table | RLS not enabled on the table (forgot `ALTER TABLE ... ENABLE ROW LEVEL SECURITY`), or a policy applies to `public` / `anon` role without filter, or the connection is using a role with `BYPASSRLS` |
## Boundaries
- **Lists active policies, doesn't simulate.** Doesn't tell you "this specific request would be allowed" — combine with [logs](logs.md) (`postgREST.logs`) to see the actual denial event.
- **Doesn't include the helper function bodies.** `auth.uid()` / `requesting_user_id()` are SQL functions; inspect via `db query` if you need to verify they return what you expect.
- **Only listed managed tables allow RLS changes.** Check the relevant module skill or CLI reference before changing RLS on a managed table. If the table is listed, put normal RLS operations in [migrations](../../insforge-cli/references/database/migrations.md); keep normal schema changes in `public`.
## Example
User reports: "logged-in user gets 403 trying to `GET /api/database/records/posts`".
```bash
# 1. See the denial event
npx @insforge/cli logs postgREST.logs --limit 50
# 2. List policies on the posts table
npx @insforge/cli db policies
# 3. If the project uses third-party auth, verify the helper
npx @insforge/cli db query "SELECT requesting_user_id()"
# 4. Confirm the user's JWT contains the expected claim (auth config in metadata)
npx @insforge/cli metadata --json
```
## Frequently paired with
- [logs](logs.md) — `postgREST.logs` shows the actual RLS denial events; pair with policies to identify which rule fired
- [metadata](metadata.md) — auth config determines which claim feeds `auth.uid()` / `requesting_user_id()`
- [advisor](advisor.md) — security category often flags missing/overly-permissive RLS policies