Complete Phase 0 (Research) and Phase 1 (Design) of implementation planning workflow: - plan.md: Technical context, constitution check, and project structure - research.md: Technology decisions for AI integration, file storage, authentication, security - data-model.md: Domain entities with validation rules and state transitions - contracts/: API specifications for submission, dashboard, and admin endpoints - quickstart.md: Developer setup guide with test-first workflow - CLAUDE.md: Updated agent context with tech stack All constitutional requirements validated. Ready for task generation phase. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
15 KiB
Data Model: Anonymous Feedback Platform (Reklamator)
Branch: 001-build-an-application | Date: 2025-10-15
This document defines the domain entities, their attributes, relationships, validation rules, and state transitions for the Reklamator application.
Entity Overview
Product (1) ----< (N) Feedback
| |
| |---< (N) Attachment
| |
| |---- (1) AnalysisResult
|
|----< (N) ProductOwner
Administrator (manages all entities)
Entity Definitions
1. Feedback Submission
Description: Represents a single feedback item submitted by an anonymous user.
Storage Location: data/products/{product_id}/feedback/{feedback_id}/
Attributes
| Field | Type | Required | Validation | Description |
|---|---|---|---|---|
id |
UUID v4 | Yes | Auto-generated | Unique identifier |
product_id |
String | Yes | Must reference existing product | Associated product identifier |
original_text |
String | Yes* | 1-10,000 characters | Original feedback text (*empty if file-only submission) |
original_language |
String (ISO 639-1) | No | 2-char code | Detected language (e.g., "en", "de", "ja") |
submission_timestamp |
ISO 8601 DateTime | Yes | Auto-generated | When feedback was submitted (UTC) |
status |
Enum | Yes | See Status enum below | Current processing/review status |
category |
Enum | No | See Category enum below | AI-assigned category (null if analysis pending/failed) |
attachment_count |
Integer | Yes | 0-3 | Number of attached files |
Status Enum
submitted- Initial state after successful submissionanalyzing- AI analysis in progressanalysis_failed- AI analysis encountered erroranalyzed- AI analysis completed successfullyreviewed- Product owner has reviewedin_progress- Product owner marked as being worked onresolved- Product owner marked as resolvedrejected- Product owner marked as not actionable
Category Enum (AI-assigned)
idea- New concept or suggestionfeature_request- Request for specific functionalitybug- Problem or defect reportcomplaint- Negative feedback about existing functionality
Validation Rules
- FR-002:
original_textmax length 10,000 characters - FR-011: At least one of (
original_text,attachment_count > 0) must be true - FR-012: Cannot be empty (no text AND no attachments)
- FR-027:
submission_timestampimmutable after creation
State Transitions
submitted → analyzing → analyzed → {reviewed, in_progress, resolved, rejected}
↓
analysis_failed (terminal state until manual retry)
File Representation (metadata.yaml)
id: "a3f2c1d5-8b4e-4f1a-9c2d-7e6f5a4b3c2d"
product_id: "001-acme-app"
original_language: "en"
submission_timestamp: "2025-10-15T14:32:10Z"
status: "analyzed"
category: "bug"
attachment_count: 2
attachments:
- filename: "screenshot.png"
size_bytes: 245678
mime_type: "image/png"
- filename: "error_log.txt"
size_bytes: 1234
mime_type: "text/plain"
2. Product/Service
Description: Represents a product or service for which feedback can be collected.
Storage Location: data/products/{product_id}/config.yaml
Attributes
| Field | Type | Required | Validation | Description |
|---|---|---|---|---|
id |
String | Yes | Unique, URL-safe slug | Product identifier (e.g., "001-acme-app") |
name |
String | Yes | 1-100 characters | Display name |
description |
String | No | Max 500 characters | Product description |
target_language |
String (ISO 639-1) | Yes | 2-char code | Preferred language for AI translations |
submission_url_slug |
String | Yes | URL-safe, unique | URL path for submission form (e.g., "/submit/acme-app") |
created_date |
ISO 8601 Date | Yes | Auto-generated | When product was registered |
status |
Enum | Yes | "active" or "archived" | Current status |
assigned_owner_ids |
List[String] | No | Must reference existing users | Product owner user IDs |
Validation Rules
- FR-046:
idmust be unique across all products - FR-047:
target_languagemust be valid ISO 639-1 code - FR-049:
submission_url_slugmust be unique and URL-safe (alphanumeric + hyphens) - FR-053: Cannot accept new feedback if
statusis "archived"
State Transitions
active ⇄ archived (bidirectional, admin only)
File Representation (config.yaml)
id: "001-acme-app"
name: "Acme Mobile App"
description: "Customer feedback for Acme's flagship mobile application"
target_language: "en"
submission_url_slug: "acme-app"
created_date: "2025-10-01"
status: "active"
assigned_owner_ids:
- "owner-001"
- "owner-002"
statistics:
total_feedback_count: 127
last_submission: "2025-10-15T14:32:10Z"
3. Analysis Result
Description: Represents the AI-generated analysis of a feedback submission.
Storage Location: data/products/{product_id}/feedback/{feedback_id}/analysis.md
Additional Storage: Original text stored in content.txt for reference
Attributes (Markdown Format)
| Field | Type | Required | Description |
|---|---|---|---|
translated_text |
Markdown | Yes* | Feedback translated to target language (*if different from original) |
summary |
Markdown | Yes | Concise 2-3 sentence summary in target language |
detected_category |
Enum | Yes | Category assigned by AI (idea/feature_request/bug/complaint) |
confidence_score |
Float (0.0-1.0) | Yes | AI confidence in categorization |
analysis_timestamp |
ISO 8601 DateTime | Yes | When analysis completed |
model_used |
String | Yes | AI model identifier (e.g., "claude-3-haiku-20240307") |
error_message |
String | No | Error details if analysis failed |
Validation Rules
- FR-014:
summaryshould be 2-3 sentences maximum - FR-015:
translated_textrequired unless original language = target language - FR-016: Original text preserved in
content.txtalongside analysis - FR-017: Detected language stored in feedback metadata.yaml
original_language
File Representation (analysis.md)
# Feedback Analysis
**Analyzed**: 2025-10-15T14:35:22Z
**Model**: claude-3-haiku-20240307
**Category**: bug (confidence: 0.92)
**Original Language**: de → **Target Language**: en
## Summary
User reports that the app crashes when uploading large files. This appears to be a bug affecting the file upload module, preventing users from submitting documents over 5MB.
## Translation
**Original (German):**
> Die App stürzt ab, wenn ich versuche, große Dateien hochzuladen. Jedes Mal wenn ich eine PDF über 5MB hochlade, friert die App ein und schließt sich.
**Translated (English):**
The app crashes when I try to upload large files. Every time I upload a PDF over 5MB, the app freezes and closes.
## Attachments
- screenshot.png (245 KB)
- error_log.txt (1 KB)
4. Attachment
Description: Represents a file (document or image) uploaded with feedback.
Storage Location: data/products/{product_id}/feedback/{feedback_id}/attachments/{filename}
Attributes (stored in feedback metadata.yaml)
| Field | Type | Required | Validation | Description |
|---|---|---|---|---|
filename |
String | Yes | Sanitized, max 255 chars | Original filename (sanitized for safety) |
size_bytes |
Integer | Yes | Max 10,485,760 (10MB) | File size in bytes |
mime_type |
String | Yes | See allowed types | Validated MIME type |
upload_timestamp |
ISO 8601 DateTime | Yes | Auto-generated | When file was uploaded |
virus_scan_status |
Enum | Yes | "clean" or "infected" | ClamAV scan result |
Allowed MIME Types
- Documents:
application/pdf,application/vnd.openxmlformats-officedocument.wordprocessingml.document(DOCX),text/plain - Images:
image/jpeg,image/png,image/gif,image/webp
Validation Rules
- FR-004: Maximum 3 attachments per feedback
- FR-006: Maximum 10MB per file
- FR-026: Preserve original filename (sanitized)
- FR-059: Sanitize filename to prevent directory traversal
- FR-060: Must pass ClamAV virus scan before storage
Security Sanitization
- Remove directory traversal patterns:
../,..\\, absolute paths - Replace unsafe characters:
<>:"|?* - Limit filename length to 255 characters
- If duplicate filename, append counter:
file.pdf→file_2.pdf
5. Product Owner
Description: Represents an authenticated user responsible for reviewing feedback for one or more products.
Storage Location: data/users.yaml
Attributes
| Field | Type | Required | Validation | Description |
|---|---|---|---|---|
id |
String | Yes | Unique | User identifier (e.g., "owner-001") |
email |
String | Yes | Valid email, unique | Login email address |
password_hash |
String (bcrypt) | Yes | bcrypt format | Hashed password (never store plaintext) |
name |
String | Yes | 1-100 characters | Display name |
role |
Enum | Yes | "product_owner" | User role (always "product_owner" for this entity) |
assigned_product_ids |
List[String] | Yes | Must reference existing products | Products this owner can access |
created_date |
ISO 8601 Date | Yes | Auto-generated | Account creation date |
last_login |
ISO 8601 DateTime | No | Auto-updated | Last successful login |
Validation Rules
- FR-048: Can be assigned to multiple products
- FR-056: Must authenticate to access dashboard
- FR-063: Passwords hashed with bcrypt (cost factor 12)
- FR-033: Can only view feedback for
assigned_product_ids
File Representation (users.yaml entry)
users:
- id: "owner-001"
email: "jane.smith@example.com"
password_hash: "$2b$12$KIXxBt5H4vE2zT9vN8FqOe9Jx..."
name: "Jane Smith"
role: "product_owner"
assigned_product_ids:
- "001-acme-app"
- "002-beta-service"
created_date: "2025-09-15"
last_login: "2025-10-15T09:23:11Z"
6. Administrator
Description: Represents a privileged user who can register products, assign owners, and manage system configuration.
Storage Location: data/users.yaml (same file as Product Owners)
Attributes
| Field | Type | Required | Validation | Description |
|---|---|---|---|---|
id |
String | Yes | Unique | User identifier (e.g., "admin-001") |
email |
String | Yes | Valid email, unique | Login email address |
password_hash |
String (bcrypt) | Yes | bcrypt format | Hashed password |
name |
String | Yes | 1-100 characters | Display name |
role |
Enum | Yes | "admin" | User role (always "admin" for this entity) |
assigned_product_ids |
List[String] | Yes | Empty list | Empty = access to all products |
created_date |
ISO 8601 Date | Yes | Auto-generated | Account creation date |
last_login |
ISO 8601 DateTime | No | Auto-updated | Last successful login |
Validation Rules
- Admin role grants full access regardless of
assigned_product_ids - FR-045: Can create/modify/archive products
- FR-048: Can assign/unassign product owners
- Same authentication requirements as Product Owner (FR-056, FR-063)
File Representation (users.yaml entry)
users:
- id: "admin-001"
email: "admin@reklamator.local"
password_hash: "$2b$12$vL3Fx9..."
name: "System Administrator"
role: "admin"
assigned_product_ids: [] # Empty = all access
created_date: "2025-09-01"
last_login: "2025-10-15T10:45:33Z"
Domain Rules & Invariants
Cross-Entity Rules
-
Product-Feedback Relationship (1:N)
- Every Feedback must reference exactly one valid Product
- Product can have zero or many Feedback items
- Archived products cannot receive new feedback (FR-053)
-
Feedback-Attachment Relationship (1:N)
- Feedback can have 0-3 Attachments (FR-004, FR-010)
- Attachments cannot exist without parent Feedback (cascade delete)
-
Feedback-AnalysisResult Relationship (1:1)
- Every analyzed Feedback has exactly one AnalysisResult
- AnalysisResult created asynchronously after Feedback submission
- Original content preserved even if analysis fails (FR-020)
-
Product-Owner Relationship (N:M)
- Product can have 1 or more assigned Product Owners (FR-048)
- Product Owner can be assigned to multiple Products
- Admin users bypass assignment logic (implicit access to all)
-
Anonymity Constraint (Global)
- No IP addresses stored in Feedback metadata (FR-055, SC-010)
- No session tracking for anonymous submissions
- Rate limiting uses IP for abuse prevention only (not persisted)
Deletion Rules
- Feedback Deletion: Deletes metadata.yaml, content.txt, analysis.md, and all attachments/
- Product Archival: Sets status to "archived", preserves all feedback (FR-052)
- Product Owner Removal: Unassigns from products, does not delete feedback
- Cascade Protection: Cannot delete Product with active feedback (archive instead)
Storage Implementation Notes
Directory Structure Example
data/
├── users.yaml # All users (admins + owners)
└── products/
├── 001-acme-app/
│ ├── config.yaml # Product metadata
│ └── feedback/
│ ├── a3f2c1d5-8b4e-.../
│ │ ├── metadata.yaml # Feedback + attachment metadata
│ │ ├── content.txt # Original feedback text
│ │ ├── analysis.md # AI analysis report
│ │ └── attachments/
│ │ ├── screenshot.png
│ │ └── error_log.txt
│ └── b7e1f3d2-4a9c-.../
│ ├── metadata.yaml
│ ├── content.txt
│ └── analysis.md # No attachments/ for this one
└── 002-beta-service/
├── config.yaml
└── feedback/
└── ...
File Format Standards
- YAML: UTF-8 encoding, 2-space indentation, explicit type declarations
- Markdown: CommonMark specification, UTF-8 encoding
- Text Files: UTF-8 encoding with BOM handling
Indexing Strategy (Performance)
For dashboard performance (SC-008, SC-014):
- Cache product feedback counts in
config.yamlstatistics - Implement pagination (50 items per page)
- Load metadata.yaml only, defer analysis.md loading until detail view
- File modification times used for sorting (newest first per FR-041)
Validation Summary by Functional Requirement
| FR | Validation Location | Rule |
|---|---|---|
| FR-002 | Feedback.original_text | Max 10,000 characters |
| FR-004 | Feedback.attachment_count | 0-3 attachments |
| FR-006 | Attachment.size_bytes | Max 10MB per file |
| FR-011 | Feedback validation | At least text OR files required |
| FR-012 | Feedback validation | Cannot be completely empty |
| FR-046 | Product.id | Must be unique |
| FR-048 | Product.assigned_owner_ids | 1+ owners required |
| FR-053 | Product status check | Reject if archived |
| FR-063 | User.password_hash | bcrypt with cost 12 |
Next Steps: Define API contracts in /contracts/ directory