# Atom Provenance Architecture

## Overview

This document defines the provenance system for tracking how atoms are created, what inputs/parameters were used, and the relationships between atoms. Provenance enables us to answer questions like:

- "What was this atom created from?"
- "What operations have been performed on this song?"
- "Show me all variations/derivatives of this original song"
- "What lyrics/image inspired this song?"

## Core Concepts

### What is Provenance?

Provenance captures the full context of how an atom was created:

1. **Operation Type**: What was done (cover, stem, remix, mashup, etc.)
2. **Multiple Inputs**: Some operations take multiple atoms (mashup 2 songs, cover with different lyrics)
3. **Input Roles**: Each input has a semantic role (source song, lyrics, style reference, etc.)
4. **Parameters**: Non-atom parameters (tags, make_instrumental, etc.)

### Example Scenarios

```
Operation: Cover Song
- Input: Original song atom (role: "source")
- Input: Lyrics atom (role: "lyrics")
- Input: Image atom (role: "style_inspiration")
- Params: { tags: "jazz, smooth" }
→ Output: New song atom with jazz style, original lyrics, inspired by image

Operation: Stem Generation
- Input: Song atom (role: "source")
- Params: { stemType: "vocals" }
→ Output: Song atom with isStem: true

Operation: Mashup
- Input: Song atom A (role: "source_a")
- Input: Song atom B (role: "source_b")
- Params: { blend_ratio: 0.5 }
→ Output: New song atom

Operation: MIDI Generation
- Input: Song or stem atom (role: "source")
- Params: {}
→ Output: MIDI atom
```

## Data Model

### Schema Changes

Add two new tables for tracking operations and their atom relationships:

```typescript
// In schema.ts

// Table 1: Operations - tracks each operation performed
operations: defineTable({
  // What operation was performed
  type: v.union(
    v.literal("generate"),        // Original creation (from text prompt)
    v.literal("cover"),           // Cover version of existing song
    v.literal("stem"),            // Stem extraction
    v.literal("midi"),            // MIDI generation
    v.literal("remix"),           // Remix operation
    v.literal("mashup"),          // Combine multiple songs
    v.literal("style_transfer"),  // Apply style from one song to another
    v.literal("upload"),          // User uploaded
    v.literal("continue")         // Continue/extend a song
  ),

  // Operation parameters (non-atom inputs)
  parameters: v.optional(v.any()), // { tags, makeInstrumental, stemType, etc. }

  // Metadata
  createdAt: v.number(),
  createdBy: v.id("users"),
})
  .index("by_type", ["type"])
  .index("by_user", ["createdBy"]),

// Table 2: Operation Atoms - links operations to atoms (both inputs and outputs)
operationAtoms: defineTable({
  operationId: v.id("operations"),
  atomId: v.id("atoms"),

  // Is this atom an input or output of the operation?
  direction: v.union(v.literal("input"), v.literal("output")),

  // For inputs: semantic role ("source", "lyrics", "style", etc.)
  // For outputs: output type ("primary", "variation", "alternate")
  role: v.optional(v.string()),
})
  .index("by_operation", ["operationId"])
  .index("by_atom", ["atomId"])                           // Find all operations involving this atom
  .index("by_atom_and_direction", ["atomId", "direction"]) // FAST: Find inputs or outputs for an atom
  .index("by_operation_and_direction", ["operationId", "direction"]) // FAST: Get all inputs or outputs for an operation
```

### Why Two Tables?

**Benefits of this normalized design:**

1. **Fully Indexable**: Can efficiently query "find all operations using atom X as input" via `operationAtoms.by_atom_and_direction`
2. **No Denormalization**: No need for `primaryInputAtomId` hack
3. **Multi-Output Support**: Operations can produce multiple atoms (e.g., song generation creates 2 variations)
4. **Flexible Queries**: Can query by role, atom, operation type, direction, etc.
5. **Clean Joins**: Easy to join operations with their atoms
6. **Unified Atom Tracking**: One place to see all atom-operation relationships
7. **Fewer Tables**: Simpler schema with 2 tables instead of 3

### Example Records

**Operation record:**
```json
{
  "_id": "op_abc123",
  "type": "stem",
  "parameters": {
    "stemType": "vocals",
    "stemTypeId": 91
  },
  "createdAt": 1234567890,
  "createdBy": "user_123"
}
```

**OperationAtoms record (input):**
```json
{
  "operationId": "op_abc123",
  "atomId": "song_def456",
  "direction": "input",
  "role": "source"
}
```

**OperationAtoms record (output):**
```json
{
  "operationId": "op_abc123",
  "atomId": "song_ghi789",
  "direction": "output",
  "role": "primary"
}
```

## Query Patterns

### 1. Get Provenance for an Atom

"How was this atom created?"

```typescript
export const getProvenanceByAtom = query({
  args: {
    atomId: v.id("atoms"),
  },
  handler: async (ctx, args) => {
    // Find the output record for this atom
    const outputRecord = await ctx.db
      .query("operationAtoms")
      .withIndex("by_atom_and_direction", (q) =>
        q.eq("atomId", args.atomId).eq("direction", "output")
      )
      .first();

    if (!outputRecord) return null;

    // Get the operation
    const operation = await ctx.db.get(outputRecord.operationId);
    if (!operation) return null;

    // Get all inputs for this operation
    const inputRecords = await ctx.db
      .query("operationAtoms")
      .withIndex("by_operation_and_direction", (q) =>
        q.eq("operationId", outputRecord.operationId).eq("direction", "input")
      )
      .collect();

    // Load the actual input atoms
    const inputs = await Promise.all(
      inputRecords.map(async (input) => {
        const atom = await ctx.db.get(input.atomId);
        return {
          atomId: input.atomId,
          role: input.role,
          atom, // Include the full atom for convenience
        };
      })
    );

    return {
      operation,
      inputs,
      outputRole: outputRecord.role, // "primary", "variation", etc.
    };
  }
});
```

**Returns:**
```json
{
  "operation": {
    "_id": "op_abc123",
    "type": "cover",
    "parameters": { "tags": "jazz, smooth" },
    "createdAt": 1234567890,
    "createdBy": "user_123"
  },
  "inputs": [
    {
      "atomId": "jd7def456",
      "role": "source",
      "atom": { /* full song atom */ }
    },
    {
      "atomId": "jd7ghi789",
      "role": "lyrics",
      "atom": { /* full lyrics atom */ }
    }
  ],
  "outputRole": "primary"
}
```

### 2. Get All Children/Derivatives of an Atom

"What has been created from this song?"

**Simple and fast** using the merged `operationAtoms` table with indexed `by_atom_and_direction`:

```typescript
export const getAtomChildren = query({
  args: {
    atomId: v.id("atoms"),
  },
  handler: async (ctx, args) => {
    // FAST: Find all operations where this atom was an input
    const inputRecords = await ctx.db
      .query("operationAtoms")
      .withIndex("by_atom_and_direction", (q) =>
        q.eq("atomId", args.atomId).eq("direction", "input")
      )
      .collect();

    // For each operation, get its outputs
    const children = await Promise.all(
      inputRecords.map(async (inputRec) => {
        // Get the operation
        const operation = await ctx.db.get(inputRec.operationId);
        if (!operation) return null;

        // Get output atoms for this operation
        const outputs = await ctx.db
          .query("operationAtoms")
          .withIndex("by_operation_and_direction", (q) =>
            q.eq("operationId", inputRec.operationId).eq("direction", "output")
          )
          .collect();

        // Load actual output atoms
        const outputAtoms = await Promise.all(
          outputs.map(async (out) => {
            const atom = await ctx.db.get(out.atomId);
            return { atom, outputRole: out.role };
          })
        );

        return {
          operation,
          inputRole: inputRec.role, // How the source atom was used
          outputs: outputAtoms.filter(o => o.atom !== null),
        };
      })
    );

    return children.filter(c => c !== null);
  }
});
```

**Returns:**
```json
[
  {
    "operation": {
      "_id": "op_stem123",
      "type": "stem",
      "parameters": { "stemType": "vocals" }
    },
    "inputRole": "source",
    "outputs": [
      {
        "atom": { /* vocal stem atom */ },
        "outputRole": "primary"
      }
    ]
  },
  {
    "operation": {
      "_id": "op_cover456",
      "type": "cover",
      "parameters": { "tags": "metal, aggressive" }
    },
    "inputRole": "source",
    "outputs": [
      {
        "atom": { /* cover variation 1 */ },
        "outputRole": "primary"
      },
      {
        "atom": { /* cover variation 2 */ },
        "outputRole": "variation"
      }
    ]
  }
]
```

**Note**: This query finds ALL operations where the atom was used as ANY input (source, lyrics, style, etc.), not just primary inputs. The indexed query is fast for all cases!

### 3. Get Full Provenance Tree

"Show me the entire lineage of this atom"

```typescript
export const getProvenanceTree = query({
  args: {
    atomId: v.id("atoms"),
    maxDepth: v.optional(v.number()), // Default 10
  },
  handler: async (ctx, args) => {
    const maxDepth = args.maxDepth ?? 10;
    const visited = new Set<string>();

    async function buildTree(atomId: Id<"atoms">, depth: number): Promise<any> {
      if (depth > maxDepth || visited.has(atomId)) return null;
      visited.add(atomId);

      const atom = await ctx.db.get(atomId);
      if (!atom) return null;

      const provenance = await ctx.db
        .query("atomProvenance")
        .withIndex("by_output_atom", (q) => q.eq("outputAtomId", atomId))
        .first();

      if (!provenance) {
        // Leaf node (original creation)
        return { atom, provenance: null, inputs: [] };
      }

      // Recursively build tree for inputs
      const inputTrees = await Promise.all(
        provenance.inputs.map(async (input) => ({
          role: input.role,
          tree: await buildTree(input.atomId, depth + 1),
        }))
      );

      return {
        atom,
        provenance,
        inputs: inputTrees.filter(t => t.tree !== null),
      };
    }

    return await buildTree(args.atomId, 0);
  }
});
```

**Returns nested tree structure:**
```json
{
  "atom": { /* current atom */ },
  "provenance": {
    "operationType": "cover",
    "inputs": [...]
  },
  "inputs": [
    {
      "role": "source",
      "tree": {
        "atom": { /* original song */ },
        "provenance": null,
        "inputs": []
      }
    },
    {
      "role": "lyrics",
      "tree": {
        "atom": { /* lyrics atom */ },
        "provenance": { /* how lyrics were created */ },
        "inputs": [...]
      }
    }
  ]
}
```

### 4. Find All Atoms Created by Operation Type

"Show me all covers I've made"

```typescript
export const getAtomsByOperation = query({
  args: {
    operationType: v.union(
      v.literal("generate"),
      v.literal("cover"),
      v.literal("stem"),
      v.literal("midi"),
      v.literal("remix"),
      v.literal("mashup"),
      v.literal("style_transfer"),
      v.literal("upload"),
      v.literal("continue")
    ),
    userId: v.optional(v.id("users")), // Filter by creator
  },
  handler: async (ctx, args) => {
    let query = ctx.db
      .query("atomProvenance")
      .withIndex("by_operation_type", (q) =>
        q.eq("operationType", args.operationType)
      );

    const provenance = await query.collect();

    // Filter by user if specified
    const filtered = args.userId
      ? provenance.filter(p => p.createdBy === args.userId)
      : provenance;

    // Load the atoms
    const results = await Promise.all(
      filtered.map(async (prov) => {
        const atom = await ctx.db.get(prov.outputAtomId);
        return { atom, provenance: prov };
      })
    );

    return results.filter(r => r.atom !== null);
  }
});
```

## Integration with Existing Operations

### Song Generation (Original Creation)

```typescript
// In atoms.ts - generateSong action
const atomIds = await Promise.all(
  songIds.map(async (songId: string) => {
    const atomId = await ctx.runMutation(internal.atoms.createInternal, {
      type: "song",
      spaceId: args.spaceId,
      ownerId: args.userId,
      metadata: { /* ... */ },
      status: "pending",
      progress: 0,
    });

    // Create provenance record
    await ctx.runMutation(internal.atoms.createProvenance, {
      outputAtomId: atomId,
      operationType: args.coverClipId ? "cover" : "generate",
      inputs: args.coverClipId
        ? [{ atomId: args.coverClipId, role: "source" }]
        : [],
      parameters: {
        lyrics: args.lyrics,
        tags: args.tags,
        makeInstrumental: args.makeInstrumental,
      },
      createdBy: args.userId,
    });

    return atomId;
  })
);
```

### Stem Generation

```typescript
// In atoms.ts - generateStems action (from STEMS_AND_MIDI.md)
const stemAtomId = await ctx.runMutation(internal.atoms.createInternal, {
  type: "song",
  spaceId: args.spaceId,
  ownerId: args.userId,
  metadata: {
    isStem: true,
    stemType: "vocals",
    // ...
  },
  status: "pending",
  progress: 0,
});

// Create provenance record
await ctx.runMutation(internal.atoms.createProvenance, {
  outputAtomId: stemAtomId,
  operationType: "stem",
  inputs: [
    { atomId: args.sourceAtomId, role: "source" }
  ],
  parameters: {
    stemType: "vocals",
    stemTypeId: 91,
  },
  createdBy: args.userId,
});
```

### MIDI Generation

```typescript
// In atoms.ts - generateMidi action
const midiAtomId = await ctx.runMutation(internal.atoms.createInternal, {
  type: "midi",
  spaceId: args.spaceId,
  ownerId: args.userId,
  metadata: { /* ... */ },
  status: "pending",
  progress: 0,
});

await ctx.runMutation(internal.atoms.createProvenance, {
  outputAtomId: midiAtomId,
  operationType: "midi",
  inputs: [
    { atomId: args.sourceAtomId, role: "source" }
  ],
  parameters: {},
  createdBy: args.userId,
});
```

### Mashup (Future)

```typescript
// Future operation
const mashupAtomId = await ctx.runMutation(internal.atoms.createInternal, {
  type: "song",
  spaceId: args.spaceId,
  ownerId: args.userId,
  metadata: { /* ... */ },
  status: "pending",
  progress: 0,
});

await ctx.runMutation(internal.atoms.createProvenance, {
  outputAtomId: mashupAtomId,
  operationType: "mashup",
  inputs: [
    { atomId: args.songAtomIdA, role: "source_a" },
    { atomId: args.songAtomIdB, role: "source_b" }
  ],
  parameters: {
    blendRatio: 0.5,
  },
  createdBy: args.userId,
});
```

### Cover with Custom Lyrics

```typescript
// Future operation
const coverAtomId = await ctx.runMutation(internal.atoms.createInternal, {
  type: "song",
  spaceId: args.spaceId,
  ownerId: args.userId,
  metadata: { /* ... */ },
  status: "pending",
  progress: 0,
});

await ctx.runMutation(internal.atoms.createProvenance, {
  outputAtomId: coverAtomId,
  operationType: "cover",
  inputs: [
    { atomId: args.sourceSongId, role: "source" },
    { atomId: args.lyricsAtomId, role: "lyrics" },
    { atomId: args.styleImageId, role: "style_inspiration" }
  ],
  parameters: {
    tags: "jazz, smooth",
  },
  createdBy: args.userId,
});
```

## Mutations

### Create Provenance

```typescript
export const createProvenance = internalMutation({
  args: {
    outputAtomId: v.id("atoms"),
    operationType: v.union(
      v.literal("generate"),
      v.literal("cover"),
      v.literal("stem"),
      v.literal("midi"),
      v.literal("remix"),
      v.literal("mashup"),
      v.literal("style_transfer"),
      v.literal("upload"),
      v.literal("continue")
    ),
    inputs: v.array(
      v.object({
        atomId: v.id("atoms"),
        role: v.string(),
      })
    ),
    parameters: v.optional(v.any()),
    createdBy: v.id("users"),
  },
  handler: async (ctx, args) => {
    // Determine primary input for indexing
    // For single-input operations, use the first input
    // For multi-input operations, use the input with role "source"
    let primaryInputAtomId: Id<"atoms"> | undefined;

    if (args.inputs.length === 1) {
      primaryInputAtomId = args.inputs[0].atomId;
    } else if (args.inputs.length > 1) {
      const sourceInput = args.inputs.find(input => input.role === "source");
      primaryInputAtomId = sourceInput?.atomId;
    }

    return await ctx.db.insert("atomProvenance", {
      outputAtomId: args.outputAtomId,
      operationType: args.operationType,
      inputs: args.inputs,
      parameters: args.parameters,
      createdAt: Date.now(),
      createdBy: args.createdBy,
      primaryInputAtomId,
    });
  }
});
```

## UI Representation

### Atom Detail View - Provenance Section

```typescript
// Component: AtomProvenanceCard.tsx

interface ProvenanceCardProps {
  atomId: Id<"atoms">;
}

export function AtomProvenanceCard({ atomId }: ProvenanceCardProps) {
  const provenance = useQuery(api.atoms.getProvenanceByAtom, { atomId });

  if (!provenance) {
    return <div>Original creation</div>;
  }

  return (
    <Card>
      <CardHeader>
        <CardTitle>Created via {operationTypeLabels[provenance.operationType]}</CardTitle>
      </CardHeader>
      <CardContent>
        {/* Render inputs */}
        {provenance.inputs.map((input) => (
          <div key={input.atomId}>
            <Badge>{input.role}</Badge>
            <AtomThumbnail atomId={input.atomId} />
          </div>
        ))}

        {/* Render parameters */}
        {provenance.parameters && (
          <div>
            <h4>Parameters</h4>
            <pre>{JSON.stringify(provenance.parameters, null, 2)}</pre>
          </div>
        )}
      </CardContent>
    </Card>
  );
}

const operationTypeLabels = {
  generate: "Generation",
  cover: "Cover",
  stem: "Stem Extraction",
  midi: "MIDI Generation",
  remix: "Remix",
  mashup: "Mashup",
  style_transfer: "Style Transfer",
  upload: "Upload",
  continue: "Continuation",
};
```

### Atom Detail View - Derivatives Section

```typescript
// Component: AtomDerivativesCard.tsx

export function AtomDerivativesCard({ atomId }: { atomId: Id<"atoms"> }) {
  const children = useQuery(api.atoms.getAtomChildren, { atomId });

  if (!children || children.length === 0) {
    return <div>No derivatives yet</div>;
  }

  return (
    <Card>
      <CardHeader>
        <CardTitle>Derivatives ({children.length})</CardTitle>
      </CardHeader>
      <CardContent>
        {children.map(({ atom, provenance }) => (
          <div key={atom._id}>
            <Badge>{provenance.operationType}</Badge>
            <AtomCard atom={atom} />
          </div>
        ))}
      </CardContent>
    </Card>
  );
}
```

### Provenance Visualization (Future)

Interactive tree/graph visualization showing the full lineage:

```
┌─────────────┐
│ Original    │
│ Song        │
└──────┬──────┘
       │
       ├─────┬─────────┬─────────┐
       │     │         │         │
   ┌───▼──┐ ┌▼──────┐ ┌▼──────┐ ┌▼──────┐
   │Vocals│ │Drums  │ │Bass   │ │MIDI   │
   │Stem  │ │Stem   │ │Stem   │ │       │
   └───┬──┘ └───────┘ └───────┘ └───────┘
       │
   ┌───▼────────┐
   │Karaoke Game│
   └────────────┘
```

## Role Naming Conventions

Standardized role names for consistency:

### Single Input Operations
- `"source"` - Primary input atom (stems, MIDI, remix, continue)

### Multi-Input Operations
- `"source"` - Primary source song
- `"lyrics"` - Lyrics atom (when using custom lyrics for cover)
- `"style_inspiration"` - Image/song for style reference
- `"source_a"`, `"source_b"`, ... - Multiple sources (mashup, blend)
- `"reference"` - Reference track (for matching style/tempo)

### Future Operations
- `"vocals"` - Vocal stem input
- `"instrumental"` - Instrumental stem input
- `"melody"` - Melody source
- `"backing"` - Backing track

## Implementation Checklist

### Schema & Core
- [ ] Add `atomProvenance` table to schema.ts
- [ ] Add indexes: `by_output_atom`, `by_operation_type`
- [ ] Implement `createProvenance` internal mutation
- [ ] Run TypeScript checks

### Queries
- [ ] Implement `getProvenanceByAtom` query
- [ ] Implement `getAtomChildren` query
- [ ] Implement `getProvenanceTree` query
- [ ] Implement `getAtomsByOperation` query

### Integration
- [ ] Update `generateSong` to create provenance records
- [ ] Update `generateStems` to create provenance records
- [ ] Update `generateMidi` to create provenance records
- [ ] Update `generateImage` to create provenance records

### UI Components
- [ ] Create `AtomProvenanceCard` component
- [ ] Create `AtomDerivativesCard` component
- [ ] Add provenance section to atom detail view
- [ ] Add derivatives section to atom detail view
- [ ] Display input roles as badges
- [ ] Make input atoms clickable (navigate to their detail view)

### Testing
- [ ] Test provenance creation for all operation types
- [ ] Test querying provenance by atom
- [ ] Test querying children/derivatives
- [ ] Test provenance tree building
- [ ] Test multi-input operations (when implemented)

### Documentation
- [ ] Update STEMS_AND_MIDI.md to reference provenance system
- [ ] Document role naming conventions
- [ ] Add examples for each operation type
