<!-- Generated by scripts/build-sdk-docs.mjs from docs/languages/typescript/validation.md. Edit the source, not this copy. -->
# Validating scenarios (TypeScript)

Scenario data is often generated by an LLM, so validate it against a real database before it reaches production. `checkScenario` runs the full `up` then `down` cycle through the same handler the platform hits, using your real factories, and reports exactly where it failed.

## checkScenario(factories, scenario, options?)

```typescript
import { checkScenario } from '@autonoma-ai/sdk'
import { factories } from '../factories'

const result = await checkScenario(factories, {
  create: {
    Organization: [{ _alias: 'org', name: 'Test Org', slug: 'test-org' }],
    User: [{ _alias: 'admin', name: 'Admin', email: 'admin@test.com', organizationId: { _ref: 'org' } }],
    Member: [{ role: 'owner', organizationId: { _ref: 'org' }, userId: { _ref: 'admin' } }],
  },
}, { scopeField: 'organizationId' })

if (!result.valid) {
  console.log(`Failed at phase: ${result.phase}`)
  for (const err of result.errors) console.log(`[${err.phase}] ${err.message}`)
}
```

The first argument is your **factory registry**, not an adapter - `checkScenario` calls the real factories, so they must point at a real (test) database. `options` accepts `scopeField`, `auth`, `sharedSecret`, and `signingSecret`; all have safe defaults for a dry run.

### The result

| Field | Meaning |
|-------|---------|
| `valid` | `true` if the full create + teardown cycle succeeded. |
| `phase` | `'ok'` if it passed, else `'up'` (create rejected) or `'down'` (teardown failed). |
| `errors` | Array of `{ phase, message }` - the message is the underlying database or SDK error. |
| `timing` | `{ upMs, downMs }` in milliseconds. |

## The testcontainers harness

The reliable way to validate locally is a real Postgres in Docker. Point your factories' database client at the container, drop every scenario file into a folder, and run one test over all of them.

```typescript
// tests/validate-scenarios.test.ts
import { describe, it, expect, beforeAll, afterAll } from 'vitest'
import { PostgreSqlContainer } from '@testcontainers/postgresql'
import { checkScenario } from '@autonoma-ai/sdk'
import { readFileSync, readdirSync } from 'node:fs'
import { join } from 'node:path'
import { execSync } from 'node:child_process'
import { factories } from '../factories'   // must use a client pointed at DATABASE_URL

const SCENARIOS_DIR = join(__dirname, '../scenarios')

describe('scenario validation', () => {
  let container: Awaited<ReturnType<PostgreSqlContainer['start']>>

  beforeAll(async () => {
    container = await new PostgreSqlContainer('postgres:16-alpine').start()
    process.env.DATABASE_URL = container.getConnectionUri()
    execSync('npx prisma db push --skip-generate --accept-data-loss', {
      env: { ...process.env }, stdio: 'pipe',
    })
  }, 60_000)

  afterAll(async () => { await container.stop() })

  for (const file of readdirSync(SCENARIOS_DIR).filter(f => f.endsWith('.json'))) {
    it(`validates ${file}`, async () => {
      const scenario = JSON.parse(readFileSync(join(SCENARIOS_DIR, file), 'utf-8'))
      const result = await checkScenario(factories, scenario, { scopeField: 'organizationId' })
      if (!result.valid) result.errors.forEach(e => console.log(`[${e.phase}] ${e.message}`))
      expect(result.valid).toBe(true)
    })
  }
})
```

```bash
pnpm add -D vitest testcontainers @testcontainers/postgresql
npx vitest run tests/validate-scenarios.test.ts
```

`checkAllScenarios(factories, scenarios, options?)` runs an array of scenarios and returns an array of results, if you prefer to load them yourself.

## The fix loop

Validation is iterative, especially for LLM-generated data:

1. Run `checkScenario`.
2. If it fails, read `result.errors[].message`.
3. Edit the scenario JSON and re-run.
4. Repeat until `valid` is `true`.

Common failures and fixes:

| Message contains | Cause | Fix |
|------------------|-------|-----|
| `Unique constraint failed ... slug` | Two records share a unique value | Make each unique field distinct across records. |
| `references unknown alias(es)` | A `_ref` targets an alias no record declares | Add the target record with that `_alias`, or fix the name. |
| `must not be null` / `Invalid input` | A required field is missing | Add the field; check it is in the factory's `inputSchema`. |
| `no factory registered for model "X"` | Model in `create` has no factory | Register a factory for `X` and add it to the registry. |
