# Work with local seed data

> Guide: Use seed_cli, local_e2e fixtures, manifest aliases, reset SQL, and shared Playwright and Rust fixture loaders.

- Repository: macro-inc/macro
- GitHub: https://github.com/macro-inc/macro
- Human docs: https://grok-wiki.com/public/docs/macro-inc-macro-bb988e1a448e
- Complete Markdown: https://grok-wiki.com/public/docs/macro-inc-macro-bb988e1a448e/llms-full.txt

## Source Files

- `rust/cloud-storage/seed_cli/README.md`
- `rust/cloud-storage/seed_cli/src/main.rs`
- `rust/cloud-storage/seed_cli/seed/local_e2e/manifest.json`
- `rust/cloud-storage/seed_cli/seed/local_e2e/users.json`
- `rust/cloud-storage/seed_cli/seed/local_e2e/reset.sql`
- `js/app/tests/e2e/fixtures/local-e2e-seed.ts`
- `rust/cloud-storage/local_e2e_test_support/README.md`

---

---
title: "Work with local seed data"
description: "Guide: Use seed_cli, local_e2e fixtures, manifest aliases, reset SQL, and shared Playwright and Rust fixture loaders."
---

`seed_cli` owns the deterministic local fixture set in `rust/cloud-storage/seed_cli/seed`, and the repo-level local E2E harness applies it with `just local-e2e-seed` before Playwright or ignored Rust integration tests run against the local Docker stack.

## Fixture layout

:::files
```text
rust/cloud-storage/seed_cli/
  README.md
  justfile
  seed/
    channels.json
    channel_messages.json
    documents/
      documents.json
      files/
    local_e2e/
      manifest.json
      reset.sql
      users.json
  src/
    main.rs
    entity/
      scenario/mod.rs
      document/mod.rs
      channel/mod.rs
      channel_message/mod.rs
```
:::

| File | Purpose |
| --- | --- |
| `seed/local_e2e/manifest.json` | Stable aliases for the smoke user, smoke document, general channel, and canonical welcome message. |
| `seed/local_e2e/users.json` | Local user fixtures shared by database seed code, Playwright fixture loading, and Rust token generation. |
| `seed/local_e2e/reset.sql` | Destructive cleanup for seeded channel, message, document, mention, and share-permission ranges. |
| `seed/documents/documents.json` | Document rows with stable UUIDs, display names, fixture file names, and public flags. |
| `seed/channels.json` | Channel rows with stable UUIDs, optional names, channel types, and participants. |
| `seed/channel_messages.json` | Message rows with stable UUIDs, senders, optional thread IDs, and optional entity mentions. |

## Run the local seed

<Steps>
  <Step title="Start and seed the local database">
    ```bash
    just local-e2e-seed
    ```

    This starts local databases, drops and initializes `macrodb`, then runs the seed CLI local E2E scenario.
  </Step>

  <Step title="Run Playwright against the seeded stack">
    ```bash
    just local-e2e
    ```

    The harness starts the local E2E service subset, applies the seed, launches the frontend with local bearer-token auth, and runs `bunx playwright test` with `LOCAL_E2E=true`.
  </Step>

  <Step title="Run Rust integration tests against the same seed">
    ```bash
    just local-e2e-rust
    ```

    Rust local E2E tests are ignored by default and run with `SQLX_OFFLINE=true`.
  </Step>

  <Step title="Run both suites after one stack startup">
    ```bash
    just local-e2e-all
    ```
  </Step>
</Steps>

<Warning>
`scenario local-e2e-smoke` is destructive. It requires `LOCAL_E2E_SEED=true` and refuses database URLs that are not the local Docker database shape `postgres://user:...@(localhost|127.0.0.1|postgres):5432/macrodb`.
</Warning>

## Seed CLI behavior

The CLI entrypoint initializes the local Macro runtime, loads required environment variables, connects to Postgres, initializes FusionAuth and S3 clients, then dispatches an entity command.

The local smoke scenario is:

```bash
cd rust/cloud-storage/seed_cli
just local-e2e-smoke
```

Internally it runs:

```bash
cargo r -- scenario local-e2e-smoke
```

The `local-e2e-smoke` recipe supplies local-only defaults, including:

| Variable | Value used by recipe |
| --- | --- |
| `LOCAL_E2E_SEED` | `true` |
| `DATABASE_URL` | `postgres://user:password@localhost:5432/macrodb` |
| `LOCAL_AWS_URL` | `http://localhost:4566` |
| `DOCUMENT_STORAGE_BUCKET` | `doc-storage` |
| `FUSIONAUTH_BASE_URL` | `http://localhost:9011` |
| `SQLX_OFFLINE` | `true` |
| `ENVIRONMENT` | `local` |

### Local smoke scenario order

1. Load `local_e2e/manifest.json` and `local_e2e/users.json`.
2. Resolve the primary smoke user from `manifest.user.email`.
3. Delete local E2E contact backfill rows if `public.contacts_backfill_outbox` exists.
4. Execute `local_e2e/reset.sql`.
5. Delete and reinsert local user rows derived from `users.json`.
6. Seed documents from `seed/documents/documents.json`.
7. Seed channels from `seed/channels.json`.
8. Seed channel messages from `seed/channel_messages.json`.
9. Print `Local e2e smoke seed data ready for <user_id>`.

### Reset scope

`reset.sql` removes fixture data by deterministic UUID prefixes:

| Data | Reset condition |
| --- | --- |
| `entity_access` | Seed channel source IDs or seed document entity IDs. |
| `ChannelSharePermission` | Seed channel IDs. |
| `comms_entity_mentions` | Seed message IDs or seed document entity IDs. |
| `comms_activity` | Seed channel IDs. |
| `comms_channels` | Seed channel IDs. |
| `Document` | Seed document IDs. |

User cleanup is generated from `local_e2e/users.json` and deletes matching rows from `"User"` and `macro_user` by auth user ID, macro user ID, or email before reinsert.

## Manifest aliases

`local_e2e/manifest.json` is the stable contract for smoke tests. It does not duplicate full fixture rows; it names canonical fixture records that must exist in the seed files.

```json
{
  "user": {
    "email": "e2e@macro.local"
  },
  "documents": {
    "projectRoadmap": {
      "id": "00000000-0000-0000-0002-000000000001",
      "name": "Project Roadmap"
    }
  },
  "channels": {
    "general": {
      "id": "00000000-0000-0000-0000-000000000001",
      "name": "general",
      "message": "Welcome to the general channel everyone!"
    }
  }
}
```

When adding a new smoke alias, update the manifest and the underlying seed file in the same change. The shared Playwright and Rust loaders fail fast when an alias points at a missing user, document, channel, or message.

## Playwright fixture loader

`js/app/tests/e2e/fixtures/local-e2e-seed.ts` reads the seed JSON directly from the repository root. It locates the root by walking upward until `rust/cloud-storage/seed_cli/seed` exists.

The exported `localE2ESeed` object includes:

| Property | Contents |
| --- | --- |
| `user` | Primary smoke user resolved from `manifest.user.email`. |
| `users`, `documents`, `channels`, `channelMessages` | Full fixture arrays. |
| `usersById`, `usersByEmail`, `usersByMacroUserId` | User lookup maps. |
| `documentsById`, `documentsByName` | Document lookup maps. |
| `channelsById`, `channelsByName` | Channel lookup maps. |
| `channelMessagesById` | Message lookup map. |
| `channelMessagesByChannelId(channelId)` | Messages filtered by channel ID. |
| `smoke.projectRoadmap` | Document row for `manifest.documents.projectRoadmap.id`. |
| `smoke.generalChannel` | Channel row for `manifest.channels.general.id`. |
| `smoke.generalWelcomeMessage` | Message in the general channel matching `manifest.channels.general.message`. |

Local Playwright specs skip unless `LOCAL_E2E=true`. The Playwright config generates `LOCAL_JWT` automatically for local E2E mode unless `LOCAL_JWT` is already exported.

```bash
cd js/app
LOCAL_E2E=true bunx playwright test
```

Prefer the repo-level harness:

```bash
just local-e2e
```

It seeds first and starts the frontend with:

```bash
VITE_LOCAL_SERVERS=ALL
VITE_ENABLE_BEARER_TOKEN_AUTH=true
LOCAL_JWT=<generated token>
bun run dev
```

## Rust fixture loader

`local_e2e_test_support` is the Rust counterpart for ignored local integration tests. It reads the same seed files and exposes typed fixture accessors, service URL helpers, and local Macro API JWT generation.

```rust
use local_e2e_test_support::{
    LocalE2eConfig, LocalE2eSeed, LocalE2eServices, LocalJwtOptions,
    encode_local_jwt_with,
};

let config = LocalE2eConfig::load()?;
let seed = LocalE2eSeed::from_config(&config)?;
let services = LocalE2eServices::from_config(&config)?;

let user = seed.smoke_user()?;
let channel = seed.general_channel()?;
let token = encode_local_jwt_with(&config, LocalJwtOptions::new(user))?;
let ws_url = services.connection_gateway_ws_url_with_token(&token)?;
```

### Rust helper defaults

| Helper | Default |
| --- | --- |
| Seed directory | `<repo>/rust/cloud-storage/seed_cli/seed` |
| Document storage URL | `http://localhost:8086` |
| Connection gateway WebSocket URL | `ws://localhost:8082/` |
| Notification service URL | `http://localhost:8089` |
| JWT issuer | `MACRO_API_TOKEN_ISSUER`, or `local` |
| JWT expiry | `MACRO_API_TOKEN_EXPIRY_SECONDS`, or 8 hours |

Service URL overrides must remain local. The helper rejects non-local hosts and mismatched schemes for mutating local E2E tests.

| Override | Accepted schemes |
| --- | --- |
| `LOCAL_E2E_DOCUMENT_STORAGE_URL` | `http`, `https` |
| `LOCAL_E2E_CONNECTION_GATEWAY_WS_URL` | `ws`, `wss` |
| `LOCAL_E2E_NOTIFICATION_URL` | `http`, `https` |

## Generate a local E2E token

Playwright calls `js/app/scripts/generate-local-e2e-token.ts`, which shells out to the Rust binary in `local_e2e_test_support`.

```bash
cd js/app
bun scripts/generate-local-e2e-token.ts
```

Optional arguments are forwarded to the Rust generator:

```bash
bun scripts/generate-local-e2e-token.ts \
  --email bob@example.com \
  --expiry-seconds 3600 \
  --organization-id 1
```

| Argument | Default |
| --- | --- |
| `--email` | `manifest.user.email` |
| `--macro-user-id` | Matching `users.json` `user_id`, or `macro|<email>` |
| `--fusion-user-id` | Matching `users.json` `fusion_user_id`, then `macro_user_id`, then the first local fixture UUID |
| `--expiry-seconds` | Argument, then `MACRO_API_TOKEN_EXPIRY_SECONDS`, then 8 hours |
| `--organization-id` | Omitted |
| `--issuer` | Argument, then `MACRO_API_TOKEN_ISSUER`, then `local` |

`MACRO_API_TOKEN_PRIVATE_SECRET_KEY` is required in `.env` or the process environment.

## Update fixture data safely

### Add or change a seeded user

1. Edit `rust/cloud-storage/seed_cli/seed/local_e2e/users.json`.
2. Keep `macro_user_id`, `fusion_user_id`, and `user_id` stable once tests depend on them.
3. If this is the primary smoke user, update `manifest.user.email`.
4. Run `just local-e2e-seed`.
5. Run at least one consumer:
   ```bash
   just local-e2e
   # or
   just local-e2e-rust
   ```

### Add or change a seeded document

1. Add a row to `seed/documents/documents.json`.
2. Put the source file under `seed/documents/files/`.
3. Use a deterministic document ID in the `00000000-0000-0000-0002-*` range if it should be cleaned by `reset.sql`.
4. Update `manifest.documents` only for canonical smoke-test aliases.
5. Run `just local-e2e-seed`.

Document seeding creates database metadata and uploads the fixture file to the configured document storage bucket.

### Add or change a seeded channel

1. Edit `seed/channels.json`.
2. Use a deterministic channel ID in the `00000000-0000-0000-0000-00000000000*` range if it should be cleaned by `reset.sql`.
3. Set `channel_type` to a value accepted by the seed CLI model, such as `public`, `private`, or `direct_message`.
4. List participants excluding or including the owner; the seed command appends the scenario owner when absent.
5. Update `manifest.channels` only for canonical smoke-test aliases.

### Add or change a seeded message

1. Edit `seed/channel_messages.json`.
2. Use a deterministic message ID in the `00000000-0000-0000-0001-*` range if it should be cleaned by `reset.sql`.
3. Set `channel_id` to an existing seeded channel.
4. Set `sender_id` to a seeded auth user ID such as `macro|bob@example.com`.
5. Use `thread_id` only when the message is a reply.
6. Add `entity_mentions` when the message content references a document or user.

For non-user shareable mentions, the seed command attempts to create message mentions and update channel share permissions for the mentioned entity.

## Troubleshooting

| Symptom | Check |
| --- | --- |
| `refusing to run destructive local-e2e-smoke seed without LOCAL_E2E_SEED=true` | Use `just local-e2e-seed` or `just rust/cloud-storage/seed_cli/local-e2e-smoke` instead of invoking the scenario without the guard variable. |
| `refusing to run local-e2e-smoke seed against DATABASE_URL ...` | Point `DATABASE_URL` at the local Docker database with user `user`, database `macrodb`, and port `5432`. |
| Playwright cannot generate `LOCAL_JWT` | Run `just local-e2e-seed`, ensure `.env` exists from the local setup flow, or export `LOCAL_JWT` manually. |
| Missing fixture error in Playwright or Rust | Verify the alias in `local_e2e/manifest.json` matches an actual row in the corresponding JSON seed file. |
| Rust helper rejects a service URL | Use `localhost`, `127.0.0.1`, or `::1`; the helper refuses non-local service URLs. |
| Seed command reports an empty JSON file | Seed commands bail on empty document, channel, or message arrays. |

## Related pages

<CardGroup>
  <Card title="Run the repository locally" href="/running-locally">
    Local stack setup, local E2E commands, and environment prerequisites.
  </Card>
  <Card title="Local E2E integration tests" href="/rust-cloud-storage-integration-tests-local-e2e">
    Rust test harness behavior for the deterministic local E2E stack.
  </Card>
</CardGroup>
