Reference
Chapter 15

Scenarios

#What Scenarios Are

Scenarios are seed scripts that put the dev database into a specific state. Instead of manually creating data through the app every time you want to test something, you run a scenario and get a repeatable starting point.

A scenario is an async function that uses the same db.push() calls as methods. If you can write a method, you can write a scenario.

#What Scenarios Don't Touch

Scenarios seed database tables. Nothing else. They do not touch file stores or data sources, and that's deliberate: both are durable, shared across dev and prod, and have no per-release copy to reset. There is nothing for a scenario to truncate.

So don't reach for a scenario to put documents into a data source, and don't write clear()-style reset helpers for one. To get a test corpus in place, add it once from the CLI:

bash
mindstudio-prod datasources add --source policies --wait fixtures/*.pdf

Re-running that is free: documents are content-addressed, so an unchanged corpus transfers nothing and embeds nothing. That's what makes it safe to keep in a setup script alongside your scenarios.


#Defining Scenarios

In mindstudio.json:

json
{
  "scenarios": [
    {
      "id": "ap-overdue-invoices",
      "name": "AP: Overdue Invoices",
      "description": "AP user with two invoices past due date.",
      "path": "dist/methods/.scenarios/apOverdueInvoices.ts",
      "export": "apOverdueInvoices",
      "roles": ["ap"]
    },
    {
      "id": "empty-requester",
      "name": "Empty Requester",
      "description": "Brand new user, no data.",
      "path": "dist/methods/.scenarios/emptyRequester.ts",
      "export": "emptyRequester",
      "roles": ["requester"]
    }
  ]
}
FieldDescription
idKebab-case identifier
nameDisplay name (shown in CLI and dev panel)
descriptionWhat state this scenario creates
pathPath to the TypeScript file
exportNamed export (the async function)
rolesRoles assigned to the dev test user after seeding (requires app auth)

#Writing a Scenario

Scenarios live at dist/methods/.scenarios/. They're inside the methods package scope, so @mindstudio-ai/agent resolves normally and table imports are relative.

#Full Example

typescript
// dist/methods/.scenarios/apOverdueInvoices.ts

import { db } from '@mindstudio-ai/agent';
import { Vendors } from '../src/tables/vendors';
import { PurchaseOrders } from '../src/tables/purchase-orders';
import { Invoices } from '../src/tables/invoices';

export async function apOverdueInvoices() {
  const vendor = await Vendors.push({
    name: 'Acme Corp',
    contactEmail: 'billing@acme.com',
    status: 'approved',
  });

  const po = await PurchaseOrders.push({
    vendorId: vendor.id,
    requestedBy: 'user-requester-1',
    totalAmountCents: 500000,
    status: 'active',
  });

  await Invoices.push([
    {
      poId: po.id,
      invoiceNumber: 'INV-001',
      amountCents: 150000,
      dueDate: db.ago(db.days(5)),
      status: 'pending_review',
    },
    {
      poId: po.id,
      invoiceNumber: 'INV-002',
      amountCents: 100000,
      dueDate: db.ago(db.days(2)),
      status: 'approved',
    },
  ]);
}

#Empty Scenario

typescript
// dist/methods/.scenarios/emptyRequester.ts

export async function emptyRequester() {
  // No data — the truncate clears everything.
  // This scenario exists so you can switch to 0 state
  // without manually deleting records.
}

#Composable Helpers

Shared setup code goes in dist/methods/.scenarios/_helpers/:

typescript
// _helpers/createTestVendor.ts
export async function createTestVendor(overrides = {}) {
  return Vendors.push({
    name: 'Test Vendor',
    contactEmail: 'test@example.com',
    status: 'approved',
    ...overrides,
  });
}

#Execution Flow

Running a scenario is three steps:

#1. Truncate

text
POST /_internal/v2/apps/{appId}/dev/manage/reset
Body: { "mode": "truncate" }

Deletes all rows from all tables, preserving schema and IDs, so the seed function starts from empty.

#2. Execute the Seed

text
POST /_internal/v2/apps/{appId}/dev/manage/token
→ { "authorizationToken": "~~internal_identity_token~~..." }

The CLI gets a fresh callback token scoped to the dev release, then transpiles and executes the scenario file in a child process with CALLBACK_TOKEN set. The SDK's db.push() calls route through the token to the correct dev database.

#3. Assign roles to the test user

text
POST /_internal/v2/apps/{appId}/dev/create-auth-session
Body: { "email": "remy@mindstudio.ai", "roles": ["ap"] }

Assigns the scenario's roles to the dev test user, a real write to that user's row (requires app auth). Signing in as the test account now shows the app from the AP user's perspective.


#CLI Integration

#TUI Mode

Select "Run Scenario" from the actions menu:

text
Select Scenario
  ❯ AP: Overdue Invoices — AP user with two invoices past due
    Empty Requester — Brand new user, no data
    Admin: Busy Org — Admin with lots of pending approvals
    Back

After running:

text
✓ Scenario "AP: Overdue Invoices" applied
  Reset database, seeded data, test user roles: ap
  Sign in as the test account to see the new state.

#Headless Mode

JSON events on stdout:

json
{"event":"scenario-start","id":"ap-overdue-invoices","name":"AP: Overdue Invoices"}
{"event":"scenario-reset"}
{"event":"scenario-seeded","duration":234}
{"event":"scenario-complete","roles":["ap"]}

The C&C server (in the sandbox) triggers scenarios via control messages to the tunnel.


#Why Scenarios Matter

  • Living documentation. Each scenario is an executable description of an app state. "What does the AP dashboard look like with overdue invoices?" Run it and see.
  • Deterministic. The same scenario always produces the same state: no accumulated test data, no "it worked on my machine."
  • Demo-ready. Scenarios double as demo data for stakeholders.
  • Visual regression. Screenshot each scenario and diff against the previous run to catch UI regressions across roles and data states.