# Unpublish / Delete Published Level Spec
## Objective
Add safe author-controlled removal for published community levels without hard-deleting data.
Current gap:
- MCP supports draft deletion only.
- Published levels cannot be removed via MCP.
## Design Decision
Use **soft delete (unpublish)** as the primary action.
Why:
- Reversible (supports accidental publish recovery).
- Preserves moderation/audit context.
- Avoids dangling references.
## Data Model Changes (`communityLevels`)
Add fields:
- `status: "ACTIVE" | "UNPUBLISHED"`
- `unpublishedAt: number | null` (server timestamp millis)
- `unpublishedBy: string | null` (author user id)
- `unpublishReason: string | null` (optional short text)
Backward compatibility:
- Missing `status` is treated as `"ACTIVE"`.
Read filtering:
- Public/community listing queries must return only `status == "ACTIVE"`.
- Author-owned listing can return both statuses.
## Backend API Contract
## `listMyPublishedLevels`
Request:
- `auth`: required
- optional `statusFilter`: `"ACTIVE" | "UNPUBLISHED" | "ALL"` (default `"ALL"`)
Response:
- Array of `{ levelId, name, status, createdAt, unpublishedAt }`
## `unpublishLevel`
Request:
- `auth`: required
- `levelId: string`
- optional `reason: string`
Behavior:
- Validate level exists.
- Validate caller is the author.
- If already unpublished, return idempotent success.
- Set:
- `status = "UNPUBLISHED"`
- `unpublishedAt = server timestamp`
- `unpublishedBy = caller uid`
- `unpublishReason = reason || null`
Response:
- `{ success: true, levelId, status: "UNPUBLISHED" }`
## `restoreLevel` (optional but recommended)
Request:
- `auth`: required
- `levelId: string`
Behavior:
- Author-only.
- Set `status = "ACTIVE"` and clear unpublish metadata.
Response:
- `{ success: true, levelId, status: "ACTIVE" }`
## Firestore Rules / Authorization
- Only `request.auth.uid == resource.data.authorId` may unpublish/restore.
- Public reads must exclude `UNPUBLISHED`.
- Admin/moderator overrides are out-of-scope for MVP.
## MCP Tool Additions
## `list_my_published_levels`
Input:
- optional `statusFilter` enum: `active|unpublished|all`
Output:
- Human-readable list with ids and status.
## `unpublish_level`
Input:
- `levelId: string`
- optional `reason: string`
Output:
- Confirmation text + reminder how to restore (if implemented).
## `restore_published_level` (optional)
Input:
- `levelId: string`
Output:
- Confirmation text.
## UX Notes
- `publish_level` response should include warning:
- "MCP currently cannot hard-delete published levels."
- `check_publish_readiness` should surface same warning.
## Error Handling
Standard errors:
- `NOT_AUTHENTICATED`
- `NOT_FOUND`
- `FORBIDDEN_NOT_AUTHOR`
- `INVALID_ARGUMENT`
- `INTERNAL`
MCP should map these to clear user-facing text.
## Rollout Plan
1. Backend: add fields + cloud functions + author checks.
2. Backend: update public query filters to `status == "ACTIVE"`.
3. MCP: add tools + docs.
4. UI: add "Unpublished" badge for author views.
5. Optional: add restore flow.
## Testing
- Unit:
- author can unpublish own level.
- non-author cannot unpublish.
- idempotent unpublish.
- restore returns to active.
- Integration:
- unpublished levels disappear from public listings.
- author listing still shows unpublished levels.