# Modyo Versioning System

## Overview

Modyo implements a comprehensive versioning system for all publishable resources (widgets, snippets, templates, pages, menus). Understanding this system is **critical** for building reliable tools and workflows.

---

## Core Principle: IDs Change, UUIDs Don't

**The Golden Rule**: When a resource is published in Modyo:
1. ❌ The `id` field **WILL CHANGE** (new version created)
2. ✅ The `uuid` field **REMAINS STABLE** (identifies resource across versions)
3. 📦 The old version is **ARCHIVED** (available for rollback)
4. ↩️ You can **ROLLBACK** to any previous version via Modyo UI

---

## Why IDs Change

### Versioning Architecture

Modyo uses a **copy-on-publish** architecture:

```
Draft State:
  Resource (id: 100, uuid: "abc123", status: "draft")

Publish Action:
  1. Create new version with new ID
  2. Archive old version
  3. Update published resource pointer

Published State:
  Old Version (id: 100, uuid: "abc123", version: 1, archived: true)
  New Version (id: 101, uuid: "abc123", version: 2, status: "published")
```

**Why?**
- **Version History**: Every publish creates a snapshot
- **Rollback Capability**: Can restore any previous version
- **Audit Trail**: Track what was published when
- **Draft/Published Separation**: Draft edits don't affect live version

---

## Affected Resources

All publishable Modyo resources follow this pattern:

| Resource Type | Stable ID | Changes on Publish |
|--------------|-----------|-------------------|
| **Widgets** | `definition_uuid` (OID) | `id` field |
| **Snippets** | `uuid` | `id` field |
| **Templates** | `uuid` | `id` field |
| **Pages** | `uuid` | `id` field |
| **Menus** | `uuid` | `id` field |
| **Layouts** | `uuid` | `id` field |

**Key**: Always use the **Stable ID** column for references and storage.

---

## Complete Versioning Lifecycle

### Widgets Example

```typescript
// 1. CREATE
widget-definitions-create({ name: "Product Card" })
// Returns: { id: 100, definition_uuid: "ac2fe2d381f5436e52b1cea8a37e0a8a2faf5260" }

// 2. EDIT (Draft)
widget-definition-update({ widgetId: 100, html: "<div>v1</div>" })
// Still: { id: 100, definition_uuid: "ac2fe2d381f5436e52b1cea8a37e0a8a2faf5260", status: "draft" }

// 3. PUBLISH (Version 1)
release-create({ data: { widgetDefinition: [{ id: 100, selected: true }] } })
// Result:
//   Archived: { id: 100, version: 1, definition_uuid: "ac2fe2d..." }
//   Published: { id: 101, version: 2, definition_uuid: "ac2fe2d...", status: "published" }

// 4. EDIT AGAIN (Draft)
widget-definition-update({ widgetId: 101, html: "<div>v2</div>" })
// Now: { id: 101, definition_uuid: "ac2fe2d...", status: "draft" }

// 5. PUBLISH AGAIN (Version 2)
release-create({ data: { widgetDefinition: [{ id: 101, selected: true }] } })
// Result:
//   Archived: { id: 100, version: 1, definition_uuid: "ac2fe2d..." }
//   Archived: { id: 101, version: 2, definition_uuid: "ac2fe2d..." }
//   Published: { id: 102, version: 3, definition_uuid: "ac2fe2d...", status: "published" }

// 6. ROLLBACK to Version 1 (via UI)
// Modyo restores id: 100 content but creates NEW version
// Result:
//   All previous versions still archived
//   Published: { id: 103, version: 4, definition_uuid: "ac2fe2d...", content: "<div>v1</div>" }
```

**Key Observation**: After 3 publishes and 1 rollback:
- `id` went from 100 → 101 → 102 → 103
- `definition_uuid` remained "ac2fe2d..." throughout
- 4 versions exist in history (v1, v2, v3, v4)

---

## Snippets Example

```typescript
// CREATE
template-create({ name: "product_card", type: "custom_snippet", body: "v1" })
// Returns: { id: 200, uuid: "def456", status: "draft" }

// PUBLISH
release-create({ data: { template: [{ id: 200, selected: true }] } })
// Result: { id: 201, uuid: "def456", version: 2, status: "published" }

// EDIT & PUBLISH AGAIN
template-update({ templateId: 201, template: { body: "v2" } })
release-create({ data: { template: [{ id: 201, selected: true }] } })
// Result: { id: 202, uuid: "def456", version: 3, status: "published" }
```

**Same Pattern**: ID changes (200 → 201 → 202), UUID stable ("def456")

---

## Pages Example

```typescript
// CREATE
page-create({ name: "Homepage", path: "/" })
// Returns: { id: 300, uuid: "ghi789", status: "draft" }

// PUBLISH
release-create({ data: { page: [{ id: 300, selected: true }] } })
// Result: { id: 301, uuid: "ghi789", version: 2, status: "published" }
```

**Same Pattern**: ID changes, UUID stable

---

## Implications for Tool Development

### ❌ Anti-Patterns (Don't Do This)

**1. Hardcoding IDs**
```typescript
// ❌ WRONG
const widgetId = 100;
function updateWidget() {
  widget-definition-update({ widgetId, html: "..." });  // Breaks after publish!
}
```

**2. Storing IDs in External Systems**
```typescript
// ❌ WRONG
database.save({
  page: "homepage",
  widgetId: 100  // This ID becomes invalid after publish
});
```

**3. ID-Based Lookups**
```typescript
// ❌ WRONG
function getWidget(id: number) {
  return widget-definition-get({ widgetId: id });  // May return archived version
}
```

**4. Assuming ID Stability**
```typescript
// ❌ WRONG
const widgetBefore = widget-definition-get({ widgetId: 100 });
publishWidget(100);
const widgetAfter = widget-definition-get({ widgetId: 100 });  // May fail or return archived version
```

### ✅ Correct Patterns

**1. Use UUIDs for References**
```typescript
// ✅ CORRECT
const widgetUuid = "ac2fe2d381f5436e52b1cea8a37e0a8a2faf5260";

function updateWidget() {
  // Get current ID from UUID
  const widget = findWidgetByUuid(widgetUuid);
  widget-definition-update({ widgetId: widget.id, html: "..." });
}
```

**2. Store UUIDs in External Systems**
```typescript
// ✅ CORRECT
database.save({
  page: "homepage",
  widgetUuid: "ac2fe2d381f5436e52b1cea8a37e0a8a2faf5260"  // Stable across versions
});
```

**3. UUID-Based Lookups**
```typescript
// ✅ CORRECT
function getWidgetByUuid(uuid: string) {
  const widgets = widget-definitions-list({ siteId });
  return widgets.find(w => w.definition_uuid === uuid);
}
```

**4. Always Fetch Current State**
```typescript
// ✅ CORRECT
function updateAndPublish(uuid: string) {
  // Get current version
  const widget = getWidgetByUuid(uuid);

  // Update draft
  widget-definition-update({ widgetId: widget.id, html: "..." });

  // Publish (ID will change)
  release-create({ data: { widgetDefinition: [{ id: widget.id, selected: true }] } });

  // Get NEW version for next operation
  const updatedWidget = getWidgetByUuid(uuid);
  console.log(`New ID: ${updatedWidget.id}`);  // Different from widget.id
}
```

---

## UUID Formats

Different resources use different UUID formats:

### Widgets: OID (Object Identifier)

**Format**: 40-character SHA-1 hash
**Example**: `ac2fe2d381f5436e52b1cea8a37e0a8a2faf5260`

**Characteristics**:
- Not RFC 4122 standard UUID
- No hyphens
- Content-addressable (based on widget content/definition)
- Modyo calls these "definition_uuid" or "widget_definition_uuid"

**Validation**:
```typescript
// ❌ WRONG - Don't use UUID validation
z.string().uuid()  // Fails for OIDs

// ✅ CORRECT - Validate as string or hex
z.string().length(40).regex(/^[a-f0-9]{40}$/)
```

### Snippets/Templates/Pages: Standard UUID

**Format**: RFC 4122 UUID
**Example**: `550e8400-e29b-41d4-a716-446655440000`

**Characteristics**:
- Standard UUID format
- Includes hyphens
- Randomly generated

**Validation**:
```typescript
// ✅ CORRECT
z.string().uuid()  // Standard UUID validation
```

---

## Rollback Behavior

### How Rollback Works

1. **Via Modyo UI**: Administrator selects "Revert to Version X"
2. **Content Restoration**: Modyo copies content from version X
3. **New Version Created**: Creates NEW version with NEW ID
4. **UUID Preserved**: Same UUID throughout

### Example

```typescript
// Version History:
// v1: id: 100, content: "old", uuid: "abc"
// v2: id: 101, content: "new", uuid: "abc" (current published)

// Admin rolls back to v1 via UI

// Result:
// v1: id: 100, content: "old", uuid: "abc" (archived)
// v2: id: 101, content: "new", uuid: "abc" (archived)
// v3: id: 102, content: "old", uuid: "abc" (published) ← NEW version with v1 content
```

**Key**: Rollback creates a **new forward version**, not a true revert.

### Implications

- ✅ Version history is append-only (never deleted)
- ✅ Can rollback to any version at any time
- ✅ Each rollback creates new version with new ID
- ✅ UUID always remains stable

---

## Version History Storage

### Where Versions Are Stored

**Database**:
- All versions stored in same table
- `version` column tracks version number
- `archived` flag indicates current vs archived

**Example Schema**:
```sql
widget_definitions:
  id: 100, uuid: "abc", version: 1, archived: true
  id: 101, uuid: "abc", version: 2, archived: true
  id: 102, uuid: "abc", version: 3, archived: false  ← Current
```

### Accessing Version History

**Via Modyo UI**:
- Template/Widget/Page editor → "Version History"
- Shows all versions with timestamps and authors
- Can preview any version
- Can rollback to any version

**Via API**: ❌ No direct API access to version history
- Can only access current published/draft versions
- Cannot list or retrieve archived versions via API
- Version history is UI-only feature

---

## Best Practices

### 1. UUID-First Architecture

**Always design around UUIDs**:
```typescript
interface WidgetReference {
  uuid: string;           // ✅ Store this
  name: string;           // ✅ For human reference
  lastKnownId?: number;   // ⚠️ Optional, for cache only
}
```

### 2. Refresh After Publish

**Always fetch fresh data after publishing**:
```typescript
// Before publish
const widgetId = 100;

// Publish
await publishWidget(widgetId);

// ❌ WRONG - Don't reuse old ID
await widget-definition-update({ widgetId: 100, ... });  // May fail

// ✅ CORRECT - Fetch new version
const widget = await getWidgetByUuid(uuid);
await widget-definition-update({ widgetId: widget.id, ... });
```

### 3. UUID Lookup Helper

**Create reusable UUID lookup function**:
```typescript
async function getResourceByUuid(
  resourceType: "widget" | "template" | "page",
  uuid: string
): Promise<Resource> {
  const resources = await listResources(resourceType);
  const resource = resources.find(r => r.uuid === uuid);

  if (!resource) {
    throw new Error(`${resourceType} with UUID ${uuid} not found`);
  }

  return resource;
}
```

### 4. Document ID Volatility

**In tool documentation, always note**:
```markdown
**⚠️ Important**: The `id` parameter may change after publishing.
Always use `uuid` for stable references across versions.
```

### 5. Automated Workflows

**For CI/CD pipelines**:
```typescript
async function deployWidget(uuid: string, updates: Updates) {
  // 1. Get current version
  const widget = await getWidgetByUuid(uuid);

  // 2. Update draft
  await widget-definition-update({ widgetId: widget.id, ...updates });

  // 3. Publish
  await release-create({ data: { widgetDefinition: [{ id: widget.id, selected: true }] } });

  // 4. Get new version for verification
  const published = await getWidgetByUuid(uuid);

  console.log(`Deployed widget ${uuid}`);
  console.log(`Old ID: ${widget.id}, New ID: ${published.id}`);

  return published;
}
```

---

## Testing Versioning Behavior

### Test Case 1: ID Changes After Publish

```typescript
test("widget ID changes after publish", async () => {
  // Create widget
  const created = await widget-definitions-create({ name: "Test Widget" });
  const originalId = created.id;
  const uuid = created.definition_uuid;

  // Publish
  await release-create({ data: { widgetDefinition: [{ id: originalId, selected: true }] } });

  // Get published version
  const published = await getWidgetByUuid(uuid);

  // Assert ID changed
  expect(published.id).not.toBe(originalId);
  expect(published.definition_uuid).toBe(uuid);  // UUID stable
});
```

### Test Case 2: UUID Lookup After Multiple Publishes

```typescript
test("UUID lookup works after multiple publishes", async () => {
  const widget = await widget-definitions-create({ name: "Test" });
  const uuid = widget.definition_uuid;

  // Publish 3 times
  for (let i = 0; i < 3; i++) {
    const current = await getWidgetByUuid(uuid);
    await widget-definition-update({ widgetId: current.id, html: `<div>v${i}</div>` });
    await release-create({ data: { widgetDefinition: [{ id: current.id, selected: true }] } });
  }

  // UUID still finds current version
  const final = await getWidgetByUuid(uuid);
  expect(final.definition_uuid).toBe(uuid);
  expect(final.html).toContain("v2");  // Last update
});
```

---

## Common Errors and Solutions

### Error 1: "Resource not found"

**Symptom**: Tool fails after publishing
**Cause**: Using old ID after publish changed it
**Solution**: Refresh resource using UUID

```typescript
// ❌ Causes error
const id = 100;
await publishResource(id);
await updateResource(id);  // Error: Resource 100 not found

// ✅ Correct
const uuid = "abc123...";
let resource = await getByUuid(uuid);
await publishResource(resource.id);
resource = await getByUuid(uuid);  // Refresh
await updateResource(resource.id);  // Works
```

### Error 2: "Cannot update archived resource"

**Symptom**: Update operation fails with archived error
**Cause**: Trying to update old version after publish
**Solution**: Get current version by UUID

```typescript
// ❌ Causes error
const old = await getResource(100);
await publishResource(100);
await updateResource(old.id);  // Error: Cannot update archived

// ✅ Correct
const uuid = old.uuid;
await publishResource(old.id);
const current = await getByUuid(uuid);
await updateResource(current.id);  // Works
```

### Error 3: "Unexpected version mismatch"

**Symptom**: Resource has unexpected content or properties
**Cause**: Cached old version after publish
**Solution**: Always fetch fresh after publish

---

## Summary Checklist

When working with Modyo resources:

- [ ] Store UUIDs, not IDs, in external systems
- [ ] Refresh resource data after every publish operation
- [ ] Use UUID-based lookups for resource retrieval
- [ ] Document that IDs are volatile in tool docs
- [ ] Validate widget UUIDs as 40-char hex, not standard UUIDs
- [ ] Never hardcode resource IDs in code
- [ ] Create UUID lookup helpers for common operations
- [ ] Test workflows with multiple publish cycles
- [ ] Handle ID changes gracefully in error messages
- [ ] Use UUIDs in API integration contracts

---

## Related Documentation

- [MODYO_SNIPPETS_ARCHITECTURE.md](./MODYO_SNIPPETS_ARCHITECTURE.md) - Snippet versioning section
- [tools/WIDGET_TOOLS.md](./tools/WIDGET_TOOLS.md) - Widget versioning section
- [tools/RELEASE_TOOLS.md](./tools/RELEASE_TOOLS.md) - Publishing workflow
- [CONTEXT_FOR_NEW_TOOLS.md](./CONTEXT_FOR_NEW_TOOLS.md) - Tool development guide

---

**Document Version**: 1.0.0
**Last Updated**: 2025-01-09
**Maintained By**: Modyo MCP Development Team

**Key Takeaway**: Modyo's versioning system changes resource IDs on every publish to maintain version history and enable rollback. Always use UUIDs for stable references and refresh resource data after publishing operations.
