diff --git a/DEV_PORTAL_M01_APP_PACKAGE.md b/DEV_PORTAL_M01_APP_PACKAGE.md
new file mode 100644
index 0000000..5fd742e
--- /dev/null
+++ b/DEV_PORTAL_M01_APP_PACKAGE.md
@@ -0,0 +1,372 @@
+# Milestone 1: App Package Format
+
+**Status**: Planning
+**Goal**: Define how apps are bundled, signed, and validated.
+
+---
+
+## Overview
+
+The app package format is the foundation of the entire ecosystem. Every tool (CLI, portal, device) needs to understand this format.
+
+---
+
+## Questions to Answer
+
+1. What files comprise an app package?
+2. How is the manifest structured?
+3. How are apps signed for integrity?
+4. How are updates handled (full vs delta)?
+5. What metadata is required (name, version, permissions, icons)?
+
+---
+
+## Package Format Options
+
+### Option A: ZIP Archive
+
+```
+myapp.zip
+├── manifest.json
+├── icon.png
+└── assets/
+ ├── main.rml
+ ├── styles.rcss
+ └── app.lua
+```
+
+| Pros | Cons |
+|------|------|
+| Simple, standard tooling | No built-in signing |
+| Easy to inspect | No metadata in filename |
+| Wide compatibility | Need separate signature file |
+
+### Option B: Custom Format (.mosis)
+
+```
+myapp.mosis (binary format)
+┌─────────────────────────┐
+│ Magic bytes (4) │
+│ Version (2) │
+│ Manifest length (4) │
+│ Manifest JSON │
+│ Signature (256) │
+│ Compressed payload │
+└─────────────────────────┘
+```
+
+| Pros | Cons |
+|------|------|
+| Signature built-in | Custom tooling needed |
+| Efficient metadata access | Harder to inspect |
+| Single file | More complex implementation |
+
+### Option C: Signed ZIP (Recommended)
+
+```
+myapp.mosis/ (actually a ZIP)
+├── manifest.json
+├── META-INF/
+│ ├── MANIFEST.MF # File hashes
+│ └── SIGNATURE.SF # Signed manifest
+├── icon.png
+└── assets/
+ └── ...
+```
+
+| Pros | Cons |
+|------|------|
+| Standard ZIP tooling | Slightly more complex |
+| JAR/APK-style signing | |
+| Easy inspection | |
+| Proven approach | |
+
+---
+
+## Proposed Structure
+
+```
+com.developer.appname-1.0.0.mosis
+├── manifest.json # App metadata
+├── META-INF/
+│ ├── CERT.PEM # Developer certificate
+│ ├── CERT.SIG # Signature of MANIFEST.MF
+│ └── MANIFEST.MF # SHA-256 hashes of all files
+├── icons/
+│ ├── icon-32.png
+│ ├── icon-64.png
+│ └── icon-128.png
+├── assets/
+│ ├── main.rml # Entry point
+│ ├── screens/
+│ │ ├── home.rml
+│ │ └── settings.rml
+│ ├── styles/
+│ │ └── theme.rcss
+│ └── scripts/
+│ ├── main.lua
+│ └── utils.lua
+└── locales/ # Optional i18n
+ ├── en.json
+ └── es.json
+```
+
+---
+
+## Manifest Schema
+
+### Required Fields
+
+```json
+{
+ "$schema": "https://mosis.dev/schemas/manifest-v1.json",
+ "id": "com.developer.appname",
+ "name": "My App",
+ "version": "1.0.0",
+ "version_code": 1,
+ "entry": "assets/main.rml",
+ "min_mosis_version": "1.0.0"
+}
+```
+
+### Full Schema
+
+```json
+{
+ "$schema": "https://mosis.dev/schemas/manifest-v1.json",
+
+ "id": "com.developer.appname",
+ "name": "My App",
+ "version": "1.0.0",
+ "version_code": 1,
+
+ "description": "A short description of the app",
+ "entry": "assets/main.rml",
+
+ "author": {
+ "name": "Developer Name",
+ "email": "dev@example.com",
+ "url": "https://developer.com"
+ },
+
+ "permissions": [
+ "storage",
+ "network",
+ "camera"
+ ],
+
+ "icons": {
+ "32": "icons/icon-32.png",
+ "64": "icons/icon-64.png",
+ "128": "icons/icon-128.png"
+ },
+
+ "min_mosis_version": "1.0.0",
+ "target_mosis_version": "1.2.0",
+
+ "category": "utilities",
+ "tags": ["productivity", "tools"],
+
+ "orientation": "portrait",
+ "background_color": "#FFFFFF",
+
+ "locales": ["en", "es", "fr"],
+ "default_locale": "en"
+}
+```
+
+### Field Definitions
+
+| Field | Type | Required | Description |
+|-------|------|----------|-------------|
+| `id` | string | Yes | Unique package identifier (reverse domain) |
+| `name` | string | Yes | Display name (max 30 chars) |
+| `version` | string | Yes | Semantic version (X.Y.Z) |
+| `version_code` | integer | Yes | Incremental build number |
+| `entry` | string | Yes | Path to entry RML file |
+| `min_mosis_version` | string | Yes | Minimum Mosis version required |
+| `description` | string | No | Short description (max 80 chars) |
+| `author` | object | No | Author information |
+| `permissions` | array | No | Required permissions |
+| `icons` | object | No | Icon paths by size |
+| `category` | string | No | App store category |
+| `tags` | array | No | Searchable tags |
+| `orientation` | string | No | portrait, landscape, any |
+| `background_color` | string | No | Hex color for loading |
+| `locales` | array | No | Supported locales |
+
+---
+
+## Signing Mechanism
+
+### Algorithm
+
+- **Key type**: Ed25519 (fast, secure, small signatures)
+- **Hash**: SHA-256 for file manifests
+- **Format**: PEM for keys, base64 for signatures
+
+### MANIFEST.MF Format
+
+```
+Manifest-Version: 1.0
+Created-By: mosis-cli 1.0.0
+
+Name: manifest.json
+SHA-256-Digest: base64encodedHash==
+
+Name: assets/main.rml
+SHA-256-Digest: base64encodedHash==
+
+Name: assets/scripts/main.lua
+SHA-256-Digest: base64encodedHash==
+```
+
+### Signing Flow
+
+```
+1. Generate MANIFEST.MF with SHA-256 of each file
+2. Sign MANIFEST.MF with developer's Ed25519 private key
+3. Store signature in META-INF/CERT.SIG
+4. Include developer's public key in META-INF/CERT.PEM
+```
+
+### Verification Flow
+
+```
+1. Extract META-INF/MANIFEST.MF
+2. Verify signature using CERT.PEM
+3. Verify CERT.PEM is registered with developer account
+4. Verify each file hash matches MANIFEST.MF
+```
+
+---
+
+## Size Limits
+
+| Limit | Value | Rationale |
+|-------|-------|-----------|
+| Max package size | 50 MB | Reasonable for mobile |
+| Max individual file | 10 MB | Prevent abuse |
+| Max files count | 1000 | Prevent zip bombs |
+| Max path length | 256 chars | Filesystem compat |
+| Max manifest size | 64 KB | Prevent abuse |
+
+---
+
+## Validation Rules
+
+### Manifest Validation
+
+- [ ] Valid JSON
+- [ ] All required fields present
+- [ ] `id` matches reverse domain pattern: `^[a-z][a-z0-9]*(\.[a-z][a-z0-9]*)+$`
+- [ ] `version` matches semver pattern
+- [ ] `version_code` is positive integer
+- [ ] `entry` file exists in package
+- [ ] All `permissions` are valid permission names
+- [ ] All `icons` paths exist and are valid images
+
+### Package Validation
+
+- [ ] Valid ZIP format
+- [ ] manifest.json at root
+- [ ] No path traversal (../)
+- [ ] No absolute paths
+- [ ] No symlinks
+- [ ] Under size limits
+- [ ] No duplicate files
+- [ ] Valid file extensions only
+
+### Signature Validation
+
+- [ ] MANIFEST.MF present
+- [ ] CERT.SIG present
+- [ ] CERT.PEM present
+- [ ] Signature valid for MANIFEST.MF
+- [ ] All file hashes match
+- [ ] Certificate registered with developer account (for store)
+
+---
+
+## File Extension Rules
+
+### Allowed Extensions
+
+| Category | Extensions |
+|----------|------------|
+| UI | .rml |
+| Styles | .rcss |
+| Scripts | .lua |
+| Images | .png, .jpg, .jpeg, .tga, .webp |
+| Fonts | .ttf, .otf |
+| Data | .json |
+| Localization | .json (in locales/) |
+| Audio | .ogg, .wav, .mp3 |
+
+### Forbidden
+
+- Executables: .exe, .dll, .so, .dylib
+- Scripts: .sh, .bat, .ps1, .py, .js
+- Archives: .zip, .tar, .gz (nested)
+
+---
+
+## Update Handling
+
+### Full Update
+
+- Download complete new package
+- Verify signature
+- Atomic replacement of app directory
+- Preserve user data in separate location
+
+### Delta Updates (Future)
+
+- bsdiff-style patches
+- Reduces bandwidth for minor updates
+- More complex implementation
+- Consider for v2
+
+---
+
+## Deliverables
+
+- [ ] JSON Schema for manifest validation
+- [ ] Package format specification document
+- [ ] Reference implementation: package creator (Go/Rust)
+- [ ] Reference implementation: package validator
+- [ ] Reference implementation: signature tools
+- [ ] Integration with mosis-cli
+
+---
+
+## Test Cases
+
+| Test | Description |
+|------|-------------|
+| ValidPackage | Accept well-formed package |
+| InvalidManifest | Reject malformed JSON |
+| MissingRequired | Reject missing required fields |
+| BadSignature | Reject invalid signature |
+| TamperedFile | Reject if file hash mismatch |
+| PathTraversal | Reject ../ in paths |
+| OversizePackage | Reject over size limit |
+| BadExtension | Reject forbidden file types |
+| DuplicateFiles | Reject duplicate entries |
+
+---
+
+## Open Questions
+
+1. Should we support multiple entry points (e.g., widget vs full app)?
+2. Should icons be required or have defaults?
+3. Delta updates in v1 or defer to v2?
+4. Support for app bundles (multiple apps in one package)?
+
+---
+
+## References
+
+- [Android APK format](https://developer.android.com/guide/components/fundamentals)
+- [JAR signing](https://docs.oracle.com/javase/tutorial/deployment/jar/signing.html)
+- [Ed25519 specification](https://ed25519.cr.yp.to/)
diff --git a/DEV_PORTAL_M02_WEB_STACK.md b/DEV_PORTAL_M02_WEB_STACK.md
new file mode 100644
index 0000000..3404392
--- /dev/null
+++ b/DEV_PORTAL_M02_WEB_STACK.md
@@ -0,0 +1,333 @@
+# Milestone 2: Web Stack Selection
+
+**Status**: Planning
+**Goal**: Choose backend technologies for the developer portal and app store API.
+
+---
+
+## Overview
+
+The web stack powers the developer portal, app store API, and telemetry ingestion. This decision affects development speed, hosting costs, and long-term maintenance.
+
+---
+
+## Requirements
+
+### Functional
+
+- REST API for developer portal
+- File upload handling (app packages)
+- Authentication (OAuth2, API keys)
+- Database integration
+- Background jobs (review queue, notifications)
+- WebSocket support (optional, for real-time updates)
+
+### Non-Functional
+
+- Handle 1000+ developers initially
+- Scale to 10,000+ apps
+- 99.9% uptime target
+- < 200ms API response time (p95)
+- Secure by default
+
+---
+
+## Options Analysis
+
+### Option A: Node.js + TypeScript
+
+#### Stack
+```
+Runtime: Node.js 20 LTS
+Language: TypeScript
+Framework: Fastify or Hono
+ORM: Prisma or Drizzle
+Validation: Zod
+Auth: Lucia or custom JWT
+```
+
+#### Pros
+| Advantage | Details |
+|-----------|---------|
+| Fast development | Large ecosystem, familiar syntax |
+| Type safety | TypeScript catches errors early |
+| Easy hiring | Many JS/TS developers |
+| Ecosystem | npm has libraries for everything |
+| Hosting | Vercel, Railway, Render, any VPS |
+
+#### Cons
+| Disadvantage | Details |
+|--------------|---------|
+| Single-threaded | Need clustering for CPU tasks |
+| Memory usage | Higher than compiled languages |
+| Callback complexity | Async can get messy |
+
+#### Example Structure
+```
+src/
+├── index.ts
+├── routes/
+│ ├── auth.ts
+│ ├── apps.ts
+│ └── telemetry.ts
+├── services/
+│ ├── auth.service.ts
+│ └── storage.service.ts
+├── db/
+│ ├── schema.ts
+│ └── client.ts
+└── utils/
+```
+
+#### Hosting Cost Estimate
+| Service | Cost/month |
+|---------|------------|
+| Railway (starter) | $5-20 |
+| Vercel Pro | $20 |
+| VPS (4GB) | $20-40 |
+
+---
+
+### Option B: Go
+
+#### Stack
+```
+Language: Go 1.22+
+Framework: Chi, Echo, or Gin
+ORM: sqlc or GORM
+Validation: go-playground/validator
+Auth: Custom JWT
+```
+
+#### Pros
+| Advantage | Details |
+|-----------|---------|
+| Performance | Fast, low memory |
+| Single binary | Easy deployment |
+| Concurrency | Goroutines handle load well |
+| Standard library | HTTP, JSON, crypto built-in |
+
+#### Cons
+| Disadvantage | Details |
+|--------------|---------|
+| Verbose | Error handling boilerplate |
+| Smaller ecosystem | Fewer ready-made solutions |
+| Learning curve | Different paradigm |
+
+#### Example Structure
+```
+cmd/
+├── server/
+│ └── main.go
+internal/
+├── api/
+│ ├── handlers/
+│ ├── middleware/
+│ └── routes.go
+├── domain/
+│ ├── app.go
+│ └── developer.go
+├── repository/
+│ └── postgres/
+└── service/
+```
+
+#### Hosting Cost Estimate
+| Service | Cost/month |
+|---------|------------|
+| Fly.io (shared) | $5-15 |
+| Cloud Run | Pay per use |
+| VPS (2GB) | $10-20 |
+
+---
+
+### Option C: Rust + Axum
+
+#### Stack
+```
+Language: Rust
+Framework: Axum
+ORM: sqlx or SeaORM
+Validation: validator crate
+Auth: Custom JWT
+```
+
+#### Pros
+| Advantage | Details |
+|-----------|---------|
+| Maximum performance | Fastest option |
+| Memory safety | No runtime errors |
+| Single binary | Easy deployment |
+| Long-term reliability | Compiler catches bugs |
+
+#### Cons
+| Disadvantage | Details |
+|--------------|---------|
+| Steep learning curve | Borrow checker takes time |
+| Slower development | More code to write |
+| Smaller ecosystem | Fewer web libraries |
+| Compile times | Can be slow |
+
+#### Example Structure
+```
+src/
+├── main.rs
+├── api/
+│ ├── mod.rs
+│ ├── handlers/
+│ └── middleware/
+├── domain/
+├── repository/
+└── service/
+```
+
+#### Hosting Cost Estimate
+| Service | Cost/month |
+|---------|------------|
+| Fly.io | $5-10 |
+| Shuttle | Free tier available |
+| VPS (1GB) | $5-10 |
+
+---
+
+### Option D: .NET (ASP.NET Core)
+
+#### Stack
+```
+Language: C#
+Framework: ASP.NET Core 8
+ORM: Entity Framework Core
+Validation: FluentValidation
+Auth: ASP.NET Identity
+```
+
+#### Pros
+| Advantage | Details |
+|-----------|---------|
+| Enterprise-ready | Battle-tested at scale |
+| Great tooling | Visual Studio, debugging |
+| Performance | Very fast (Kestrel) |
+| Full-featured | Auth, validation, DI built-in |
+
+#### Cons
+| Disadvantage | Details |
+|--------------|---------|
+| Heavier runtime | Larger container images |
+| Microsoft ecosystem | May feel locked in |
+| Verbose | More boilerplate |
+
+#### Hosting Cost Estimate
+| Service | Cost/month |
+|---------|------------|
+| Azure App Service | $15-50 |
+| VPS (4GB) | $20-40 |
+
+---
+
+## Evaluation Matrix
+
+| Criteria | Weight | Node.js | Go | Rust | .NET |
+|----------|--------|---------|-----|------|------|
+| Dev speed | 25% | 9 | 7 | 5 | 7 |
+| Performance | 20% | 6 | 9 | 10 | 8 |
+| Hosting cost | 15% | 7 | 9 | 9 | 6 |
+| Ecosystem | 15% | 9 | 7 | 6 | 8 |
+| Team familiarity | 15% | ? | ? | ? | ? |
+| Long-term maintenance | 10% | 7 | 8 | 9 | 8 |
+| **Weighted Score** | | **7.4** | **7.8** | **7.2** | **7.3** |
+
+*Team familiarity needs to be filled in based on actual experience*
+
+---
+
+## Framework Comparison
+
+### Node.js Frameworks
+
+| Framework | Req/sec | Features | Learning Curve |
+|-----------|---------|----------|----------------|
+| Express | 15k | Minimal, flexible | Easy |
+| Fastify | 45k | Fast, schema validation | Medium |
+| Hono | 50k | Ultra-light, edge-ready | Easy |
+| NestJS | 20k | Full-featured, Angular-like | Steep |
+
+### Go Frameworks
+
+| Framework | Req/sec | Features | Learning Curve |
+|-----------|---------|----------|----------------|
+| net/http | 60k | Standard library | Medium |
+| Chi | 55k | Lightweight router | Easy |
+| Gin | 50k | Popular, middleware | Easy |
+| Echo | 55k | Similar to Gin | Easy |
+
+---
+
+## Prototype Plan
+
+### Phase 1: Build minimal API in top 2 choices
+
+Endpoints to implement:
+```
+POST /auth/register
+POST /auth/login
+GET /apps
+POST /apps
+POST /apps/:id/versions
+```
+
+### Phase 2: Benchmark
+
+- Requests per second
+- Memory usage under load
+- Development time tracking
+- Code complexity comparison
+
+### Phase 3: Decision
+
+Based on:
+- Benchmark results
+- Developer experience during prototype
+- Hosting cost analysis
+
+---
+
+## Recommendation
+
+**Primary: Go with Chi/Echo**
+- Best balance of performance and simplicity
+- Low hosting costs
+- Single binary deployment
+- Good enough ecosystem for our needs
+
+**Fallback: Node.js with Hono/Fastify**
+- If Go feels too slow to develop in
+- Larger ecosystem for edge cases
+- More developers familiar with it
+
+---
+
+## Deliverables
+
+- [ ] Prototype API in Go
+- [ ] Prototype API in Node.js (if needed)
+- [ ] Benchmark comparison document
+- [ ] Final selection with rationale
+- [ ] Project structure template
+- [ ] Development environment setup guide
+
+---
+
+## Open Questions
+
+1. Do we need GraphQL or is REST sufficient?
+2. Do we need real-time features (WebSocket)?
+3. What's the team's current language experience?
+4. Any preference for specific cloud providers?
+
+---
+
+## References
+
+- [TechEmpower Benchmarks](https://www.techempower.com/benchmarks/)
+- [Go vs Node.js comparison](https://blog.logrocket.com/node-js-vs-golang/)
+- [Fastify benchmarks](https://fastify.dev/benchmarks/)
diff --git a/DEV_PORTAL_M03_DATABASE.md b/DEV_PORTAL_M03_DATABASE.md
new file mode 100644
index 0000000..c441c0b
--- /dev/null
+++ b/DEV_PORTAL_M03_DATABASE.md
@@ -0,0 +1,418 @@
+# Milestone 3: Database Selection
+
+**Status**: Planning
+**Goal**: Choose database for developer accounts, app metadata, and analytics.
+
+---
+
+## Overview
+
+The database stores all persistent data: developer accounts, app metadata, versions, telemetry events, and audit logs.
+
+---
+
+## Requirements
+
+### Data Characteristics
+
+| Data Type | Volume | Access Pattern | Consistency |
+|-----------|--------|----------------|-------------|
+| Developers | 10K rows | Read-heavy, low write | Strong |
+| Apps | 100K rows | Read-heavy | Strong |
+| Versions | 500K rows | Read-heavy | Strong |
+| API Keys | 50K rows | Read-heavy | Strong |
+| Telemetry | 100M+ rows | Write-heavy, append | Eventual OK |
+| Audit Logs | 10M+ rows | Write-heavy, append | Eventual OK |
+
+### Query Patterns
+
+- Get developer by email
+- List apps by developer
+- Get app with latest version
+- Search apps by name/tags
+- Aggregate telemetry by app/day
+- Time-range queries on events
+
+---
+
+## Options Analysis
+
+### Option A: PostgreSQL
+
+#### Characteristics
+```
+Type: Relational (SQL)
+ACID: Full
+JSON: Native JSONB support
+Full-text: Built-in tsvector
+Scaling: Vertical + read replicas
+```
+
+#### Pros
+| Advantage | Details |
+|-----------|---------|
+| Battle-tested | Decades of reliability |
+| ACID compliance | Strong consistency |
+| JSON support | JSONB for flexible data |
+| Full-text search | No separate search engine needed |
+| Extensions | PostGIS, pg_trgm, etc. |
+| Tooling | pgAdmin, great ORMs |
+
+#### Cons
+| Disadvantage | Details |
+|--------------|---------|
+| Ops overhead | Need connection pooling |
+| Scaling writes | Vertical scaling limits |
+| Time-series | Not optimized for telemetry |
+
+#### Hosting Options
+| Provider | Free Tier | Paid |
+|----------|-----------|------|
+| Supabase | 500MB | $25/mo |
+| Neon | 512MB | $19/mo |
+| Railway | 1GB | $5/mo |
+| AWS RDS | - | $15/mo+ |
+| Self-hosted | - | VPS cost |
+
+---
+
+### Option B: SQLite + Litestream
+
+#### Characteristics
+```
+Type: Embedded relational
+ACID: Full
+Scaling: Single writer
+Backup: Litestream to S3
+```
+
+#### Pros
+| Advantage | Details |
+|-----------|---------|
+| Zero ops | No separate DB server |
+| Fast reads | In-process, no network |
+| Simple backup | Litestream handles replication |
+| Low cost | Just storage costs |
+| Portable | Easy local development |
+
+#### Cons
+| Disadvantage | Details |
+|--------------|---------|
+| Single writer | Limits write concurrency |
+| No horizontal scale | One server only |
+| Limited features | No full-text (without FTS5) |
+
+#### Cost Estimate
+| Component | Cost/month |
+|-----------|------------|
+| S3 storage (10GB) | $0.25 |
+| Compute | Included in app server |
+
+---
+
+### Option C: PostgreSQL + TimescaleDB
+
+#### Characteristics
+```
+Type: Time-series extension
+Base: PostgreSQL
+Scaling: Automatic partitioning
+Compression: Native
+```
+
+#### Pros
+| Advantage | Details |
+|-----------|---------|
+| Best of both | Relational + time-series |
+| Auto-partition | Handles telemetry scale |
+| Compression | 90%+ compression ratio |
+| Continuous aggregates | Pre-computed rollups |
+
+#### Cons
+| Disadvantage | Details |
+|--------------|---------|
+| Complexity | More to manage |
+| Cost | Higher than plain Postgres |
+| Learning curve | New concepts |
+
+---
+
+### Option D: Hybrid Approach
+
+```
+PostgreSQL → Developers, Apps, Versions, API Keys
+ClickHouse/QuestDB → Telemetry, Analytics
+Redis → Caching, Sessions
+```
+
+#### Pros
+| Advantage | Details |
+|-----------|---------|
+| Right tool for job | Optimized for each use case |
+| Scale independently | Telemetry won't affect main DB |
+| Performance | Best possible for each workload |
+
+#### Cons
+| Disadvantage | Details |
+|--------------|---------|
+| Complexity | Multiple systems to manage |
+| Cost | More infrastructure |
+| Consistency | Cross-DB transactions hard |
+
+---
+
+## Schema Design
+
+### Core Tables
+
+```sql
+-- Developers
+CREATE TABLE developers (
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+ email VARCHAR(255) UNIQUE NOT NULL,
+ name VARCHAR(100) NOT NULL,
+ password_hash VARCHAR(255),
+ oauth_provider VARCHAR(50),
+ oauth_id VARCHAR(255),
+ verified BOOLEAN DEFAULT FALSE,
+ created_at TIMESTAMPTZ DEFAULT NOW(),
+ updated_at TIMESTAMPTZ DEFAULT NOW()
+);
+
+-- API Keys
+CREATE TABLE api_keys (
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+ developer_id UUID REFERENCES developers(id) ON DELETE CASCADE,
+ name VARCHAR(100) NOT NULL,
+ key_hash VARCHAR(255) NOT NULL,
+ key_prefix VARCHAR(10) NOT NULL, -- For display: "mk_abc..."
+ permissions JSONB DEFAULT '[]',
+ last_used_at TIMESTAMPTZ,
+ expires_at TIMESTAMPTZ,
+ created_at TIMESTAMPTZ DEFAULT NOW()
+);
+
+-- Apps
+CREATE TABLE apps (
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+ developer_id UUID REFERENCES developers(id) ON DELETE CASCADE,
+ package_id VARCHAR(255) UNIQUE NOT NULL, -- com.dev.app
+ name VARCHAR(100) NOT NULL,
+ description TEXT,
+ category VARCHAR(50),
+ tags VARCHAR(50)[] DEFAULT '{}',
+ status VARCHAR(20) DEFAULT 'draft', -- draft, published, suspended
+ created_at TIMESTAMPTZ DEFAULT NOW(),
+ updated_at TIMESTAMPTZ DEFAULT NOW()
+);
+
+-- App Versions
+CREATE TABLE app_versions (
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+ app_id UUID REFERENCES apps(id) ON DELETE CASCADE,
+ version_code INTEGER NOT NULL,
+ version_name VARCHAR(20) NOT NULL,
+ package_url TEXT NOT NULL,
+ package_size BIGINT NOT NULL,
+ signature VARCHAR(512) NOT NULL,
+ permissions JSONB DEFAULT '[]',
+ min_mosis_version VARCHAR(20),
+ release_notes TEXT,
+ status VARCHAR(20) DEFAULT 'draft', -- draft, review, approved, published, rejected
+ review_notes TEXT,
+ published_at TIMESTAMPTZ,
+ created_at TIMESTAMPTZ DEFAULT NOW(),
+ UNIQUE(app_id, version_code)
+);
+
+-- Developer Signing Keys
+CREATE TABLE signing_keys (
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+ developer_id UUID REFERENCES developers(id) ON DELETE CASCADE,
+ name VARCHAR(100) NOT NULL,
+ public_key TEXT NOT NULL,
+ fingerprint VARCHAR(64) NOT NULL,
+ is_active BOOLEAN DEFAULT TRUE,
+ created_at TIMESTAMPTZ DEFAULT NOW()
+);
+```
+
+### Telemetry Tables (if using PostgreSQL)
+
+```sql
+-- Telemetry Events (consider partitioning by time)
+CREATE TABLE telemetry_events (
+ id BIGSERIAL,
+ app_id UUID NOT NULL,
+ device_id VARCHAR(64) NOT NULL, -- Hashed
+ event_type VARCHAR(50) NOT NULL,
+ event_data JSONB,
+ mosis_version VARCHAR(20),
+ timestamp TIMESTAMPTZ NOT NULL,
+ PRIMARY KEY (timestamp, id)
+) PARTITION BY RANGE (timestamp);
+
+-- Create monthly partitions
+CREATE TABLE telemetry_events_2024_01 PARTITION OF telemetry_events
+ FOR VALUES FROM ('2024-01-01') TO ('2024-02-01');
+
+-- Crash Reports
+CREATE TABLE crash_reports (
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+ app_id UUID NOT NULL,
+ app_version VARCHAR(20) NOT NULL,
+ device_id VARCHAR(64) NOT NULL,
+ crash_type VARCHAR(50) NOT NULL,
+ message TEXT,
+ stack_trace TEXT,
+ context JSONB,
+ mosis_version VARCHAR(20),
+ timestamp TIMESTAMPTZ NOT NULL,
+ created_at TIMESTAMPTZ DEFAULT NOW()
+);
+
+-- Daily aggregates (materialized or computed)
+CREATE TABLE telemetry_daily (
+ app_id UUID NOT NULL,
+ date DATE NOT NULL,
+ event_type VARCHAR(50) NOT NULL,
+ count BIGINT NOT NULL,
+ unique_devices BIGINT NOT NULL,
+ PRIMARY KEY (app_id, date, event_type)
+);
+```
+
+### Indexes
+
+```sql
+-- Developers
+CREATE INDEX idx_developers_email ON developers(email);
+CREATE INDEX idx_developers_oauth ON developers(oauth_provider, oauth_id);
+
+-- Apps
+CREATE INDEX idx_apps_developer ON apps(developer_id);
+CREATE INDEX idx_apps_package ON apps(package_id);
+CREATE INDEX idx_apps_status ON apps(status);
+CREATE INDEX idx_apps_search ON apps USING gin(to_tsvector('english', name || ' ' || COALESCE(description, '')));
+
+-- Versions
+CREATE INDEX idx_versions_app ON app_versions(app_id);
+CREATE INDEX idx_versions_status ON app_versions(status);
+
+-- Telemetry
+CREATE INDEX idx_telemetry_app ON telemetry_events(app_id, timestamp);
+CREATE INDEX idx_telemetry_type ON telemetry_events(event_type, timestamp);
+
+-- Crashes
+CREATE INDEX idx_crashes_app ON crash_reports(app_id, timestamp);
+CREATE INDEX idx_crashes_type ON crash_reports(crash_type);
+```
+
+---
+
+## Migration Strategy
+
+### Approach: Incremental Migrations
+
+```
+migrations/
+├── 001_create_developers.sql
+├── 002_create_apps.sql
+├── 003_create_versions.sql
+├── 004_create_telemetry.sql
+└── ...
+```
+
+### Tools
+- **Go**: golang-migrate, goose
+- **Node.js**: Prisma Migrate, Drizzle Kit
+- **Rust**: sqlx migrate, refinery
+
+### Rollback Strategy
+- Every migration has up/down
+- Test rollbacks in staging
+- Keep migrations small and focused
+
+---
+
+## Backup Strategy
+
+### PostgreSQL
+```bash
+# Daily full backup
+pg_dump -Fc $DATABASE_URL > backup_$(date +%Y%m%d).dump
+
+# Continuous WAL archiving to S3
+archive_command = 'aws s3 cp %p s3://backups/wal/%f'
+```
+
+### SQLite + Litestream
+```yaml
+# litestream.yml
+dbs:
+ - path: /data/mosis.db
+ replicas:
+ - url: s3://backups/mosis
+ retention: 720h # 30 days
+```
+
+### Recovery Time Objectives
+| Scenario | RTO | RPO |
+|----------|-----|-----|
+| Hardware failure | 1 hour | 5 minutes |
+| Data corruption | 4 hours | 1 hour |
+| Disaster recovery | 24 hours | 24 hours |
+
+---
+
+## Recommendation
+
+### For MVP/Early Stage
+**SQLite + Litestream**
+- Simplest to operate
+- Lowest cost
+- Good enough for initial scale
+- Easy migration to PostgreSQL later
+
+### For Production Scale
+**PostgreSQL + TimescaleDB**
+- Handles all data types well
+- Time-series for telemetry
+- Proven at scale
+- Good tooling ecosystem
+
+### Hybrid (If needed later)
+```
+PostgreSQL → Core data (developers, apps)
+TimescaleDB → Telemetry (same cluster, extension)
+Redis → Caching, rate limiting
+```
+
+---
+
+## Deliverables
+
+- [ ] Final database selection
+- [ ] Complete schema design
+- [ ] Migration scripts
+- [ ] Backup/restore procedures
+- [ ] Connection pooling setup (if PostgreSQL)
+- [ ] Monitoring queries
+
+---
+
+## Open Questions
+
+1. Expected telemetry volume per day?
+2. How long to retain raw telemetry?
+3. Need for real-time analytics vs batch?
+4. Multi-region requirements?
+
+---
+
+## References
+
+- [PostgreSQL JSONB performance](https://www.postgresql.org/docs/current/datatype-json.html)
+- [TimescaleDB vs InfluxDB](https://www.timescale.com/blog/timescaledb-vs-influxdb/)
+- [Litestream documentation](https://litestream.io/)
+- [SQLite at scale](https://www.sqlite.org/whentouse.html)
diff --git a/DEV_PORTAL_M04_AUTH.md b/DEV_PORTAL_M04_AUTH.md
new file mode 100644
index 0000000..c99973c
--- /dev/null
+++ b/DEV_PORTAL_M04_AUTH.md
@@ -0,0 +1,429 @@
+# Milestone 4: Authentication System
+
+**Status**: Planning
+**Goal**: Secure developer authentication and app signing infrastructure.
+
+---
+
+## Overview
+
+Authentication covers two areas:
+1. **Developer authentication** - Login to portal, API access
+2. **App signing** - Package integrity and developer verification
+
+---
+
+## Developer Authentication
+
+### Methods Required
+
+| Method | Use Case | Priority |
+|--------|----------|----------|
+| OAuth2 (GitHub) | Primary login | P0 |
+| OAuth2 (Google) | Alternative login | P1 |
+| Email + Password | Fallback | P2 |
+| API Keys | CLI tools, CI/CD | P0 |
+
+### OAuth2 Flow
+
+```
+┌─────────┐ ┌─────────┐ ┌──────────┐ ┌─────────┐
+│ Browser │────►│ Portal │────►│ Provider │────►│ Callback│
+└─────────┘ └─────────┘ │(GitHub) │ └────┬────┘
+ └──────────┘ │
+ ▼
+ ┌─────────────┐
+ │ Create/Link │
+ │ Account │
+ └─────────────┘
+```
+
+### OAuth2 Implementation
+
+#### GitHub OAuth
+
+```
+Authorization URL: https://github.com/login/oauth/authorize
+Token URL: https://github.com/login/oauth/access_token
+User Info: https://api.github.com/user
+Scopes: read:user, user:email
+```
+
+#### Google OAuth
+
+```
+Authorization URL: https://accounts.google.com/o/oauth2/v2/auth
+Token URL: https://oauth2.googleapis.com/token
+User Info: https://www.googleapis.com/oauth2/v2/userinfo
+Scopes: openid, email, profile
+```
+
+### Session Management
+
+#### JWT Tokens
+
+```json
+{
+ "sub": "dev_uuid",
+ "email": "dev@example.com",
+ "iat": 1704067200,
+ "exp": 1704153600,
+ "type": "access"
+}
+```
+
+| Token Type | Lifetime | Storage |
+|------------|----------|---------|
+| Access Token | 1 hour | Memory/Cookie |
+| Refresh Token | 30 days | HttpOnly Cookie |
+
+#### Token Refresh Flow
+
+```
+1. Access token expires
+2. Client sends refresh token
+3. Server validates refresh token
+4. Issue new access + refresh tokens
+5. Invalidate old refresh token
+```
+
+### API Key Authentication
+
+#### Key Format
+
+```
+mk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+│ │ └── 32 random bytes (base62)
+│ └── Environment (live/test)
+└── Prefix (mosis key)
+```
+
+#### Key Storage
+
+```sql
+-- Only store hash, never the key itself
+INSERT INTO api_keys (
+ developer_id,
+ name,
+ key_hash, -- bcrypt or argon2
+ key_prefix, -- "mk_live_abc" for display
+ permissions
+) VALUES (...);
+```
+
+#### Key Permissions
+
+```json
+{
+ "permissions": [
+ "apps:read",
+ "apps:write",
+ "versions:upload",
+ "telemetry:read"
+ ]
+}
+```
+
+### Rate Limiting
+
+| Endpoint Category | Limit | Window |
+|-------------------|-------|--------|
+| Auth endpoints | 10 | 1 minute |
+| API (authenticated) | 1000 | 1 hour |
+| API (per key) | 100 | 1 minute |
+| Upload | 10 | 1 hour |
+
+---
+
+## App Signing
+
+### Key Generation
+
+#### Algorithm: Ed25519
+
+```
+Private key: 32 bytes (256 bits)
+Public key: 32 bytes (256 bits)
+Signature: 64 bytes (512 bits)
+```
+
+#### Why Ed25519?
+- Fast signing and verification
+- Small key and signature sizes
+- No configuration choices (secure by default)
+- Widely supported
+
+### Key Generation Flow
+
+```bash
+# Developer generates keypair locally
+mosis keys generate
+
+# Output:
+# Private key saved to: ~/.mosis/signing_key.pem
+# Public key saved to: ~/.mosis/signing_key.pub
+#
+# Fingerprint: SHA256:xxxxx
+#
+# IMPORTANT: Keep your private key secure!
+# Upload your public key to the developer portal.
+```
+
+### Key Format
+
+#### Private Key (PEM)
+
+```
+-----BEGIN PRIVATE KEY-----
+MC4CAQAwBQYDK2VwBCIEIGxxxxx...
+-----END PRIVATE KEY-----
+```
+
+#### Public Key (PEM)
+
+```
+-----BEGIN PUBLIC KEY-----
+MCowBQYDK2VwAyEAxxxxx...
+-----END PUBLIC KEY-----
+```
+
+### Signing Flow
+
+```
+1. Build package (ZIP all files)
+2. Generate MANIFEST.MF with SHA-256 hashes
+3. Sign MANIFEST.MF with private key
+4. Store signature in META-INF/CERT.SIG
+5. Include public key in META-INF/CERT.PEM
+```
+
+#### MANIFEST.MF Example
+
+```
+Manifest-Version: 1.0
+Created-By: mosis-cli 1.0.0
+Package-Id: com.developer.myapp
+Version-Code: 1
+
+Name: manifest.json
+SHA-256-Digest: K7gNU3sdo+OL0wNhqoVWhr3g6s1xYv72ol/pe/Unols=
+
+Name: assets/main.rml
+SHA-256-Digest: uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek=
+```
+
+### Verification Flow
+
+```
+1. Extract MANIFEST.MF from package
+2. Extract CERT.SIG (signature)
+3. Extract CERT.PEM (public key)
+4. Verify signature of MANIFEST.MF using public key
+5. Verify CERT.PEM matches registered developer key
+6. Verify each file hash matches MANIFEST.MF entry
+```
+
+### Key Registration
+
+```
+Developer Portal:
+├── Go to Settings > Signing Keys
+├── Click "Add Key"
+├── Paste public key (PEM format)
+├── Verify fingerprint matches local
+└── Key is now registered
+
+Server stores:
+├── public_key (PEM text)
+├── fingerprint (SHA256 of public key)
+├── created_at
+└── is_active
+```
+
+### Key Rotation
+
+```
+1. Generate new keypair
+2. Register new public key in portal
+3. Sign new versions with new key
+4. (Optional) Revoke old key after transition period
+```
+
+### Trust Model
+
+```
+┌─────────────────────────────────────────────────┐
+│ Trust Chain │
+├─────────────────────────────────────────────────┤
+│ │
+│ Developer │
+│ │ │
+│ ▼ │
+│ Private Key ──signs──► Package │
+│ │ │
+│ ▼ │
+│ Public Key ──registered──► Portal │
+│ │ │
+│ ▼ │
+│ Portal ──verifies──► Signature │
+│ │ │
+│ ▼ │
+│ Device ──trusts──► Portal-verified packages │
+│ │
+└─────────────────────────────────────────────────┘
+```
+
+---
+
+## Security Considerations
+
+### Password Storage (if used)
+
+```
+Algorithm: Argon2id
+Memory: 64 MB
+Iterations: 3
+Parallelism: 4
+Salt: 16 bytes random
+```
+
+### Token Security
+
+- Access tokens: Short-lived, in-memory only
+- Refresh tokens: HttpOnly, Secure, SameSite=Strict
+- API keys: Hashed with bcrypt, shown once on creation
+
+### Key Security
+
+- Private keys never leave developer's machine
+- Public keys verified via fingerprint
+- Key compromise: Revoke immediately, re-sign apps
+
+### Audit Logging
+
+```sql
+INSERT INTO auth_audit_log (
+ developer_id,
+ action, -- login, logout, key_create, key_revoke
+ ip_address,
+ user_agent,
+ success,
+ failure_reason,
+ timestamp
+) VALUES (...);
+```
+
+---
+
+## API Endpoints
+
+### Authentication
+
+```
+POST /auth/oauth/github # Start GitHub OAuth
+GET /auth/oauth/github/callback # GitHub callback
+POST /auth/oauth/google # Start Google OAuth
+GET /auth/oauth/google/callback # Google callback
+POST /auth/refresh # Refresh tokens
+POST /auth/logout # Invalidate tokens
+GET /auth/me # Get current user
+```
+
+### API Keys
+
+```
+GET /api-keys # List keys
+POST /api-keys # Create key
+DELETE /api-keys/:id # Revoke key
+```
+
+### Signing Keys
+
+```
+GET /signing-keys # List keys
+POST /signing-keys # Register key
+DELETE /signing-keys/:id # Revoke key
+GET /signing-keys/:id/verify # Verify a signature
+```
+
+---
+
+## Implementation Libraries
+
+### Node.js
+
+```json
+{
+ "passport": "OAuth strategies",
+ "jose": "JWT handling",
+ "@noble/ed25519": "Ed25519 signing",
+ "argon2": "Password hashing"
+}
+```
+
+### Go
+
+```go
+import (
+ "golang.org/x/oauth2"
+ "github.com/golang-jwt/jwt/v5"
+ "crypto/ed25519"
+ "golang.org/x/crypto/argon2"
+)
+```
+
+### Rust
+
+```toml
+[dependencies]
+oauth2 = "4.4"
+jsonwebtoken = "9"
+ed25519-dalek = "2"
+argon2 = "0.5"
+```
+
+---
+
+## Deliverables
+
+- [ ] OAuth2 integration (GitHub)
+- [ ] OAuth2 integration (Google)
+- [ ] JWT token management
+- [ ] API key generation and validation
+- [ ] Ed25519 key generation tool
+- [ ] Signature creation and verification
+- [ ] Key registration API
+- [ ] Audit logging
+
+---
+
+## Test Cases
+
+| Test | Description |
+|------|-------------|
+| OAuthLogin | Complete OAuth flow |
+| TokenRefresh | Refresh expired access token |
+| InvalidToken | Reject tampered JWT |
+| APIKeyAuth | Authenticate with API key |
+| KeyGeneration | Generate valid Ed25519 keypair |
+| SignPackage | Sign and verify package |
+| InvalidSignature | Reject tampered package |
+| KeyRevocation | Revoked key fails verification |
+
+---
+
+## Open Questions
+
+1. Support for hardware security keys (YubiKey)?
+2. Multi-factor authentication for portal?
+3. Team accounts with role-based access?
+4. Key escrow for enterprise customers?
+
+---
+
+## References
+
+- [OAuth 2.0 RFC 6749](https://tools.ietf.org/html/rfc6749)
+- [JWT RFC 7519](https://tools.ietf.org/html/rfc7519)
+- [Ed25519 paper](https://ed25519.cr.yp.to/ed25519-20110926.pdf)
+- [OWASP Authentication Cheatsheet](https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html)
diff --git a/DEV_PORTAL_M05_FRONTEND.md b/DEV_PORTAL_M05_FRONTEND.md
new file mode 100644
index 0000000..746f02e
--- /dev/null
+++ b/DEV_PORTAL_M05_FRONTEND.md
@@ -0,0 +1,464 @@
+# Milestone 5: Developer Portal Frontend
+
+**Status**: Planning
+**Goal**: Web interface for developer account and app management.
+
+---
+
+## Overview
+
+The developer portal is the primary interface for developers to manage their accounts, create apps, submit versions, and view analytics.
+
+---
+
+## Pages Required
+
+### Public Pages
+
+| Page | URL | Purpose |
+|------|-----|---------|
+| Landing | `/` | Marketing, sign up CTA |
+| Sign In | `/login` | OAuth + password login |
+| Sign Up | `/register` | Create account |
+| Docs | `/docs/*` | Documentation (separate site?) |
+
+### Authenticated Pages
+
+| Page | URL | Purpose |
+|------|-----|---------|
+| Dashboard | `/dashboard` | App list, quick stats |
+| App Details | `/apps/:id` | Single app overview |
+| App Settings | `/apps/:id/settings` | Edit app metadata |
+| App Versions | `/apps/:id/versions` | Version history |
+| Submit Version | `/apps/:id/versions/new` | Upload new version |
+| App Analytics | `/apps/:id/analytics` | Telemetry dashboard |
+| Create App | `/apps/new` | New app wizard |
+| API Keys | `/settings/keys` | Manage API keys |
+| Signing Keys | `/settings/signing` | Manage signing keys |
+| Profile | `/settings/profile` | Account settings |
+
+---
+
+## Wireframes
+
+### Dashboard
+
+```
+┌─────────────────────────────────────────────────────────────┐
+│ [Logo] Dashboard Apps Docs Settings [Avatar ▼] │
+├─────────────────────────────────────────────────────────────┤
+│ │
+│ Welcome back, Developer! │
+│ │
+│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
+│ │ Total Apps │ │ Downloads │ │ Active Users│ │
+│ │ 12 │ │ 45,230 │ │ 1,234 │ │
+│ └─────────────┘ └─────────────┘ └─────────────┘ │
+│ │
+│ Your Apps [+ New App] │
+│ ┌──────────────────────────────────────────────────────┐ │
+│ │ [Icon] My Calculator v1.2.0 Published 1.2K ↓ │ │
+│ │ [Icon] Notes App v2.0.1 Published 5.4K ↓ │ │
+│ │ [Icon] Weather Widget v0.9.0 In Review --- │ │
+│ └──────────────────────────────────────────────────────┘ │
+│ │
+└─────────────────────────────────────────────────────────────┘
+```
+
+### App Details
+
+```
+┌─────────────────────────────────────────────────────────────┐
+│ ← Back to Apps │
+├─────────────────────────────────────────────────────────────┤
+│ │
+│ [Icon] My Calculator │
+│ com.developer.calculator │
+│ ● Published │
+│ │
+│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
+│ │ Overview │ │ Versions │ │Analytics │ │ Settings │ │
+│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
+│ │
+│ Latest Version: 1.2.0 [Submit New Version] │
+│ Published: Jan 15, 2024 │
+│ Downloads: 1,234 │
+│ │
+│ ┌────────────────────────────────────────┐ │
+│ │ Downloads over time │ │
+│ │ [Chart: line graph] │ │
+│ └────────────────────────────────────────┘ │
+│ │
+│ Recent Crashes [View All →] │
+│ • attempt to index nil (v1.2.0) - 23 reports │
+│ • memory limit exceeded (v1.1.0) - 5 reports │
+│ │
+└─────────────────────────────────────────────────────────────┘
+```
+
+### Submit Version
+
+```
+┌─────────────────────────────────────────────────────────────┐
+│ Submit New Version - My Calculator │
+├─────────────────────────────────────────────────────────────┤
+│ │
+│ Step 2 of 3: Upload Package │
+│ ○ Details ● Upload ○ Review │
+│ │
+│ ┌─────────────────────────────────────────────────────┐ │
+│ │ │ │
+│ │ ┌─────────┐ │ │
+│ │ │ .mosis │ Drop your package here │ │
+│ │ │ 📦 │ or click to browse │ │
+│ │ └─────────┘ │ │
+│ │ │ │
+│ │ Max size: 50 MB │ │
+│ │ │ │
+│ └─────────────────────────────────────────────────────┘ │
+│ │
+│ ✓ Package validated │
+│ ✓ Signature verified │
+│ ✓ Permissions: storage, network │
+│ │
+│ [Back] [Continue to Review] │
+│ │
+└─────────────────────────────────────────────────────────────┘
+```
+
+---
+
+## Tech Stack Options
+
+### Option A: Next.js 14
+
+```
+Framework: Next.js 14 (App Router)
+UI: shadcn/ui + Tailwind CSS
+State: React Query + Zustand
+Forms: React Hook Form + Zod
+Charts: Recharts or Tremor
+Auth: NextAuth.js
+```
+
+| Pros | Cons |
+|------|------|
+| SSR for SEO landing page | Complexity |
+| Great developer experience | React knowledge required |
+| Full-stack capability | Heavier bundle |
+| Large ecosystem | |
+
+### Option B: SvelteKit
+
+```
+Framework: SvelteKit
+UI: Skeleton UI or custom
+State: Svelte stores
+Forms: Superforms
+Charts: Chart.js or LayerCake
+Auth: Lucia
+```
+
+| Pros | Cons |
+|------|------|
+| Fast, small bundles | Smaller ecosystem |
+| Simple state management | Fewer UI libraries |
+| Great DX | Less hiring pool |
+
+### Option C: Astro + React Islands
+
+```
+Framework: Astro
+UI: React components (islands)
+State: Nanostores
+Forms: React Hook Form
+Charts: Recharts
+Auth: Custom
+```
+
+| Pros | Cons |
+|------|------|
+| Ultra-fast static pages | More setup |
+| Partial hydration | Newer approach |
+| Use React where needed | Less documented patterns |
+
+### Option D: htmx + Go Templates
+
+```
+Framework: Go templates + htmx
+UI: Tailwind CSS
+State: Server-side
+Forms: Native HTML
+Charts: Chart.js
+Auth: Server sessions
+```
+
+| Pros | Cons |
+|------|------|
+| Simple, fast | Limited interactivity |
+| No JS framework | Less polished UX |
+| Server-rendered | Complex UI harder |
+
+---
+
+## UI Component Library
+
+### Option A: shadcn/ui
+
+```
+Base: Radix UI primitives
+Styling: Tailwind CSS
+Approach: Copy-paste components
+```
+
+| Pros | Cons |
+|------|------|
+| High quality | React only |
+| Full control | Manual updates |
+| Accessible | |
+
+### Option B: Tailwind UI
+
+```
+Base: Headless UI
+Styling: Tailwind CSS
+Approach: Copy-paste templates
+```
+
+| Pros | Cons |
+|------|------|
+| Beautiful designs | Paid ($299) |
+| Production-ready | Templates, not components |
+
+### Option C: Mantine
+
+```
+Base: Custom components
+Styling: CSS-in-JS or CSS
+Approach: npm package
+```
+
+| Pros | Cons |
+|------|------|
+| Complete solution | Opinionated |
+| Many components | Larger bundle |
+| Good docs | |
+
+---
+
+## Key Features
+
+### File Upload
+
+```typescript
+// Drag and drop with progress
+
+
+ Drop your package here
+
+```
+
+### Real-time Validation
+
+```typescript
+// Validate package client-side before upload
+async function validatePackage(file: File) {
+ const zip = await JSZip.loadAsync(file);
+
+ // Check manifest
+ const manifest = await zip.file('manifest.json')?.async('text');
+ if (!manifest) throw new Error('Missing manifest.json');
+
+ const parsed = JSON.parse(manifest);
+ ManifestSchema.parse(parsed);
+
+ // Check required files
+ const entry = parsed.entry;
+ if (!zip.file(entry)) {
+ throw new Error(`Entry file not found: ${entry}`);
+ }
+
+ return parsed;
+}
+```
+
+### Analytics Dashboard
+
+```typescript
+// Recharts example
+
+
+
+
+
+
+```
+
+---
+
+## State Management
+
+### Server State (React Query)
+
+```typescript
+// Fetch apps
+const { data: apps, isLoading } = useQuery({
+ queryKey: ['apps'],
+ queryFn: () => api.get('/apps'),
+});
+
+// Create app mutation
+const createApp = useMutation({
+ mutationFn: (data) => api.post('/apps', data),
+ onSuccess: () => {
+ queryClient.invalidateQueries(['apps']);
+ },
+});
+```
+
+### Client State (Zustand)
+
+```typescript
+// UI state
+const useStore = create((set) => ({
+ sidebarOpen: true,
+ toggleSidebar: () => set((s) => ({ sidebarOpen: !s.sidebarOpen })),
+}));
+```
+
+---
+
+## Authentication Flow
+
+### Login Page
+
+```tsx
+export default function LoginPage() {
+ return (
+
+
+
+ Sign in to Mosis
+
+
+
+
+
+
+
+
+
+ );
+}
+```
+
+### Protected Routes
+
+```tsx
+// Middleware (Next.js)
+export function middleware(request: NextRequest) {
+ const token = request.cookies.get('token');
+
+ if (!token && request.nextUrl.pathname.startsWith('/dashboard')) {
+ return NextResponse.redirect(new URL('/login', request.url));
+ }
+}
+```
+
+---
+
+## Responsive Design
+
+### Breakpoints
+
+| Size | Width | Target |
+|------|-------|--------|
+| sm | 640px | Mobile landscape |
+| md | 768px | Tablet |
+| lg | 1024px | Desktop |
+| xl | 1280px | Large desktop |
+
+### Mobile Navigation
+
+```tsx
+// Hamburger menu for mobile
+
+
+
+
+
+
+
+
+```
+
+---
+
+## Accessibility
+
+### Requirements
+
+- [ ] Keyboard navigation
+- [ ] Screen reader support
+- [ ] Color contrast (WCAG AA)
+- [ ] Focus indicators
+- [ ] Alt text for images
+- [ ] Form labels
+- [ ] Error announcements
+
+### Testing
+
+```bash
+# Lighthouse audit
+npx lighthouse http://localhost:3000 --view
+
+# axe-core
+npm install @axe-core/react
+```
+
+---
+
+## Deliverables
+
+- [ ] Framework selection
+- [ ] UI component library selection
+- [ ] Design system (colors, typography)
+- [ ] Page wireframes
+- [ ] Authentication flow
+- [ ] Dashboard implementation
+- [ ] App management pages
+- [ ] Version submission flow
+- [ ] Settings pages
+- [ ] Responsive design
+- [ ] Accessibility audit
+
+---
+
+## Open Questions
+
+1. Dark mode support?
+2. Internationalization (i18n)?
+3. Custom domain for docs vs integrated?
+4. Email notifications UI?
+
+---
+
+## References
+
+- [shadcn/ui](https://ui.shadcn.com/)
+- [Next.js App Router](https://nextjs.org/docs/app)
+- [Tailwind CSS](https://tailwindcss.com/)
+- [React Query](https://tanstack.com/query/latest)
diff --git a/DEV_PORTAL_M06_API.md b/DEV_PORTAL_M06_API.md
new file mode 100644
index 0000000..2cf9b29
--- /dev/null
+++ b/DEV_PORTAL_M06_API.md
@@ -0,0 +1,604 @@
+# Milestone 6: App Store Backend API
+
+**Status**: Planning
+**Goal**: REST API for app submission, review, and distribution.
+
+---
+
+## Overview
+
+The backend API serves the developer portal, CLI tools, and device-side app management. It handles authentication, app lifecycle, file storage, and telemetry ingestion.
+
+---
+
+## API Design Principles
+
+1. **RESTful** - Standard HTTP methods and status codes
+2. **JSON** - Request and response bodies in JSON
+3. **Versioned** - `/v1/` prefix for breaking changes
+4. **Consistent** - Same patterns across all endpoints
+5. **Documented** - OpenAPI specification
+
+---
+
+## Base URL
+
+```
+Production: https://api.mosis.dev/v1
+Staging: https://api.staging.mosis.dev/v1
+Local: http://localhost:8080/v1
+```
+
+---
+
+## Authentication
+
+### Headers
+
+```
+Authorization: Bearer
+# or
+X-API-Key: mk_live_xxxxxxxx
+```
+
+### Scopes
+
+| Scope | Description |
+|-------|-------------|
+| `apps:read` | Read app metadata |
+| `apps:write` | Create/update apps |
+| `versions:upload` | Upload new versions |
+| `versions:publish` | Publish versions |
+| `telemetry:read` | Read analytics |
+| `keys:manage` | Manage API keys |
+
+---
+
+## Endpoints
+
+### Authentication
+
+```yaml
+POST /v1/auth/oauth/github:
+ summary: Start GitHub OAuth flow
+ response: { redirect_url: string }
+
+GET /v1/auth/oauth/github/callback:
+ summary: GitHub OAuth callback
+ query:
+ code: string
+ state: string
+ response: { access_token, refresh_token, user }
+
+POST /v1/auth/oauth/google:
+ summary: Start Google OAuth flow
+ response: { redirect_url: string }
+
+GET /v1/auth/oauth/google/callback:
+ summary: Google OAuth callback
+
+POST /v1/auth/refresh:
+ summary: Refresh access token
+ body: { refresh_token: string }
+ response: { access_token, refresh_token }
+
+POST /v1/auth/logout:
+ summary: Invalidate tokens
+ auth: required
+
+GET /v1/auth/me:
+ summary: Get current user
+ auth: required
+ response: Developer
+```
+
+### Apps
+
+```yaml
+GET /v1/apps:
+ summary: List developer's apps
+ auth: required
+ query:
+ status: draft | published | suspended
+ page: number
+ limit: number
+ response: { apps: App[], total: number }
+
+POST /v1/apps:
+ summary: Create new app
+ auth: required
+ body:
+ package_id: string # com.developer.appname
+ name: string
+ description?: string
+ category?: string
+ response: App
+
+GET /v1/apps/:id:
+ summary: Get app details
+ auth: required
+ response: App
+
+PATCH /v1/apps/:id:
+ summary: Update app metadata
+ auth: required
+ body:
+ name?: string
+ description?: string
+ category?: string
+ tags?: string[]
+ response: App
+
+DELETE /v1/apps/:id:
+ summary: Delete app (if no published versions)
+ auth: required
+ response: { success: true }
+```
+
+### App Versions
+
+```yaml
+GET /v1/apps/:id/versions:
+ summary: List app versions
+ auth: required
+ query:
+ status: draft | review | approved | published | rejected
+ page: number
+ limit: number
+ response: { versions: AppVersion[], total: number }
+
+POST /v1/apps/:id/versions:
+ summary: Create new version (get upload URL)
+ auth: required
+ body:
+ version_name: string # 1.0.0
+ version_code: number # 1
+ release_notes?: string
+ response:
+ version: AppVersion
+ upload_url: string # Presigned S3 URL
+ upload_expires: string # ISO timestamp
+
+PUT /v1/apps/:id/versions/:vid/upload-complete:
+ summary: Mark upload as complete, trigger validation
+ auth: required
+ response: AppVersion
+
+GET /v1/apps/:id/versions/:vid:
+ summary: Get version details
+ auth: required
+ response: AppVersion
+
+POST /v1/apps/:id/versions/:vid/submit:
+ summary: Submit version for review
+ auth: required
+ response: AppVersion # status: review
+
+POST /v1/apps/:id/versions/:vid/publish:
+ summary: Publish approved version
+ auth: required
+ response: AppVersion # status: published
+
+DELETE /v1/apps/:id/versions/:vid:
+ summary: Delete draft version
+ auth: required
+ response: { success: true }
+```
+
+### Public App Store
+
+```yaml
+GET /v1/store/apps:
+ summary: Browse/search published apps
+ auth: none
+ query:
+ q: string # Search query
+ category: string
+ sort: popular | recent | name
+ page: number
+ limit: number
+ response: { apps: PublicApp[], total: number }
+
+GET /v1/store/apps/:package_id:
+ summary: Get app store listing
+ auth: none
+ response: PublicApp
+
+GET /v1/store/apps/:package_id/download:
+ summary: Get download URL for latest version
+ auth: none (or device token)
+ response:
+ download_url: string
+ version: string
+ size: number
+ signature: string
+
+GET /v1/store/apps/:package_id/versions/:version_code/download:
+ summary: Get download URL for specific version
+ auth: none
+ response: { download_url, version, size, signature }
+```
+
+### API Keys
+
+```yaml
+GET /v1/keys:
+ summary: List API keys
+ auth: required
+ response: { keys: APIKey[] }
+
+POST /v1/keys:
+ summary: Create API key
+ auth: required
+ body:
+ name: string
+ permissions: string[]
+ expires_at?: string
+ response:
+ key: APIKey
+ secret: string # Only shown once!
+
+DELETE /v1/keys/:id:
+ summary: Revoke API key
+ auth: required
+ response: { success: true }
+```
+
+### Signing Keys
+
+```yaml
+GET /v1/signing-keys:
+ summary: List signing keys
+ auth: required
+ response: { keys: SigningKey[] }
+
+POST /v1/signing-keys:
+ summary: Register signing key
+ auth: required
+ body:
+ name: string
+ public_key: string # PEM format
+ response: SigningKey
+
+DELETE /v1/signing-keys/:id:
+ summary: Revoke signing key
+ auth: required
+ response: { success: true }
+```
+
+### Telemetry
+
+```yaml
+POST /v1/telemetry/events:
+ summary: Submit telemetry events (batch)
+ auth: device token or API key
+ body:
+ events:
+ - app_id: string
+ event_type: string
+ event_data: object
+ timestamp: string
+ response: { received: number }
+
+POST /v1/telemetry/crash:
+ summary: Submit crash report
+ auth: device token or API key
+ body:
+ app_id: string
+ app_version: string
+ crash_type: string
+ message: string
+ stack_trace: string
+ context: object
+ timestamp: string
+ response: { id: string }
+
+GET /v1/apps/:id/analytics:
+ summary: Get app analytics
+ auth: required
+ query:
+ start_date: string
+ end_date: string
+ metrics: downloads | active_users | crashes
+ response:
+ data:
+ - date: string
+ downloads: number
+ active_users: number
+ crashes: number
+
+GET /v1/apps/:id/crashes:
+ summary: Get crash reports
+ auth: required
+ query:
+ version?: string
+ page: number
+ limit: number
+ response: { crashes: CrashReport[], total: number }
+```
+
+---
+
+## Data Models
+
+### Developer
+
+```typescript
+interface Developer {
+ id: string;
+ email: string;
+ name: string;
+ avatar_url?: string;
+ verified: boolean;
+ created_at: string;
+ updated_at: string;
+}
+```
+
+### App
+
+```typescript
+interface App {
+ id: string;
+ package_id: string;
+ name: string;
+ description?: string;
+ category?: string;
+ tags: string[];
+ status: 'draft' | 'published' | 'suspended';
+ icon_url?: string;
+ latest_version?: AppVersion;
+ created_at: string;
+ updated_at: string;
+}
+```
+
+### AppVersion
+
+```typescript
+interface AppVersion {
+ id: string;
+ app_id: string;
+ version_name: string;
+ version_code: number;
+ package_url?: string;
+ package_size?: number;
+ signature?: string;
+ permissions: string[];
+ min_mosis_version?: string;
+ release_notes?: string;
+ status: 'draft' | 'uploading' | 'validating' | 'review' | 'approved' | 'published' | 'rejected';
+ review_notes?: string;
+ published_at?: string;
+ created_at: string;
+}
+```
+
+### PublicApp
+
+```typescript
+interface PublicApp {
+ package_id: string;
+ name: string;
+ description?: string;
+ category?: string;
+ tags: string[];
+ icon_url?: string;
+ author_name: string;
+ latest_version: string;
+ download_count: number;
+ rating?: number;
+ created_at: string;
+ updated_at: string;
+}
+```
+
+### APIKey
+
+```typescript
+interface APIKey {
+ id: string;
+ name: string;
+ key_prefix: string; // "mk_live_abc..."
+ permissions: string[];
+ last_used_at?: string;
+ expires_at?: string;
+ created_at: string;
+}
+```
+
+### CrashReport
+
+```typescript
+interface CrashReport {
+ id: string;
+ app_id: string;
+ app_version: string;
+ crash_type: string;
+ message: string;
+ stack_trace: string;
+ context: object;
+ occurrences: number;
+ first_seen: string;
+ last_seen: string;
+}
+```
+
+---
+
+## Error Handling
+
+### Error Response Format
+
+```json
+{
+ "error": {
+ "code": "VALIDATION_ERROR",
+ "message": "Invalid package_id format",
+ "details": {
+ "field": "package_id",
+ "constraint": "Must match pattern: ^[a-z][a-z0-9]*(\\.[a-z][a-z0-9]*)+$"
+ }
+ }
+}
+```
+
+### Error Codes
+
+| Code | HTTP Status | Description |
+|------|-------------|-------------|
+| `UNAUTHORIZED` | 401 | Missing or invalid auth |
+| `FORBIDDEN` | 403 | Insufficient permissions |
+| `NOT_FOUND` | 404 | Resource not found |
+| `VALIDATION_ERROR` | 400 | Invalid request body |
+| `CONFLICT` | 409 | Resource already exists |
+| `RATE_LIMITED` | 429 | Too many requests |
+| `INTERNAL_ERROR` | 500 | Server error |
+
+---
+
+## Rate Limiting
+
+### Headers
+
+```
+X-RateLimit-Limit: 1000
+X-RateLimit-Remaining: 999
+X-RateLimit-Reset: 1704067200
+```
+
+### Limits
+
+| Endpoint Category | Limit | Window |
+|-------------------|-------|--------|
+| Auth | 10 | 1 minute |
+| Read | 1000 | 1 hour |
+| Write | 100 | 1 hour |
+| Upload | 10 | 1 hour |
+| Telemetry | 10000 | 1 hour |
+
+---
+
+## Pagination
+
+### Request
+
+```
+GET /v1/apps?page=2&limit=20
+```
+
+### Response
+
+```json
+{
+ "apps": [...],
+ "pagination": {
+ "page": 2,
+ "limit": 20,
+ "total": 45,
+ "total_pages": 3
+ }
+}
+```
+
+---
+
+## Webhooks (Future)
+
+```yaml
+POST /v1/webhooks:
+ summary: Register webhook
+ body:
+ url: string
+ events: string[] # version.published, crash.new
+ secret: string
+
+Webhook Payload:
+ headers:
+ X-Mosis-Signature: sha256=xxx
+ body:
+ event: string
+ data: object
+ timestamp: string
+```
+
+---
+
+## OpenAPI Specification
+
+Full OpenAPI 3.0 spec will be generated and hosted at:
+- `https://api.mosis.dev/v1/openapi.json`
+- `https://api.mosis.dev/v1/docs` (Swagger UI)
+
+---
+
+## Implementation Structure
+
+```
+src/
+├── main.go (or index.ts)
+├── api/
+│ ├── routes.go
+│ ├── middleware/
+│ │ ├── auth.go
+│ │ ├── ratelimit.go
+│ │ └── logging.go
+│ └── handlers/
+│ ├── auth.go
+│ ├── apps.go
+│ ├── versions.go
+│ ├── store.go
+│ ├── keys.go
+│ └── telemetry.go
+├── service/
+│ ├── app_service.go
+│ ├── version_service.go
+│ ├── auth_service.go
+│ └── storage_service.go
+├── repository/
+│ ├── app_repo.go
+│ ├── version_repo.go
+│ └── developer_repo.go
+├── domain/
+│ ├── app.go
+│ ├── version.go
+│ └── developer.go
+└── pkg/
+ ├── validator/
+ └── crypto/
+```
+
+---
+
+## Deliverables
+
+- [ ] OpenAPI specification
+- [ ] Authentication middleware
+- [ ] Rate limiting middleware
+- [ ] Auth endpoints
+- [ ] Apps CRUD endpoints
+- [ ] Versions endpoints with upload flow
+- [ ] Store public endpoints
+- [ ] API keys management
+- [ ] Signing keys management
+- [ ] Telemetry ingestion
+- [ ] Error handling
+- [ ] Request validation
+- [ ] Integration tests
+
+---
+
+## Open Questions
+
+1. GraphQL alongside REST?
+2. WebSocket for real-time review status?
+3. Batch operations for bulk updates?
+4. API versioning strategy (URL vs header)?
+
+---
+
+## References
+
+- [REST API Design Guidelines](https://github.com/microsoft/api-guidelines)
+- [OpenAPI 3.0 Specification](https://swagger.io/specification/)
+- [HTTP Status Codes](https://httpstatuses.com/)
diff --git a/DEV_PORTAL_M07_STORAGE.md b/DEV_PORTAL_M07_STORAGE.md
new file mode 100644
index 0000000..ef85dad
--- /dev/null
+++ b/DEV_PORTAL_M07_STORAGE.md
@@ -0,0 +1,451 @@
+# Milestone 7: CDN & Storage
+
+**Status**: Planning
+**Goal**: Scalable storage for app packages and assets with global distribution.
+
+---
+
+## Overview
+
+Storage handles app packages, icons, screenshots, and serves downloads to devices worldwide. Must be cost-effective, fast, and reliable.
+
+---
+
+## Requirements
+
+### Functional
+
+- Store app packages (.mosis files)
+- Store icons (multiple sizes)
+- Store screenshots (optional)
+- Serve downloads globally
+- Support presigned upload URLs
+- Version retention (keep old versions)
+
+### Non-Functional
+
+| Requirement | Target |
+|-------------|--------|
+| Upload speed | < 30s for 50MB |
+| Download latency | < 100ms (p50) |
+| Availability | 99.9% |
+| Durability | 99.999999999% (11 nines) |
+| Cost | Minimize egress fees |
+
+---
+
+## Storage Structure
+
+```
+/packages/
+ /{developer_id}/
+ /{app_id}/
+ /{version_code}/
+ package.mosis
+ manifest.json (extracted for quick access)
+
+/assets/
+ /{app_id}/
+ icon-32.png
+ icon-64.png
+ icon-128.png
+ screenshots/
+ 1.png
+ 2.png
+ 3.png
+
+/temp/
+ /{upload_id}/
+ package.mosis (pending validation)
+```
+
+---
+
+## Options Analysis
+
+### Option A: Cloudflare R2
+
+```
+Storage: Object storage (S3-compatible)
+CDN: Cloudflare network (automatic)
+Egress: FREE (zero egress fees)
+Pricing: $0.015/GB storage, $4.50/million requests
+```
+
+| Pros | Cons |
+|------|------|
+| No egress fees | Newer service |
+| Global CDN included | Fewer regions than S3 |
+| S3-compatible API | Less tooling |
+| Workers integration | |
+
+#### Cost Estimate (10K apps, 100GB)
+
+| Component | Monthly Cost |
+|-----------|--------------|
+| Storage (100GB) | $1.50 |
+| Requests (1M) | $4.50 |
+| Egress | $0 |
+| **Total** | **~$6** |
+
+---
+
+### Option B: AWS S3 + CloudFront
+
+```
+Storage: S3 Standard
+CDN: CloudFront
+Egress: $0.085-0.12/GB (varies by region)
+Pricing: $0.023/GB storage
+```
+
+| Pros | Cons |
+|------|------|
+| Most mature | Egress costs add up |
+| Best tooling | Complex pricing |
+| All regions | Need CloudFront config |
+| IAM integration | |
+
+#### Cost Estimate (10K apps, 100GB, 1TB egress)
+
+| Component | Monthly Cost |
+|-----------|--------------|
+| Storage (100GB) | $2.30 |
+| CloudFront (1TB) | $85 |
+| Requests | ~$5 |
+| **Total** | **~$92** |
+
+---
+
+### Option C: Backblaze B2 + Cloudflare
+
+```
+Storage: Backblaze B2
+CDN: Cloudflare (free egress via Bandwidth Alliance)
+Egress: FREE (through Cloudflare)
+Pricing: $0.005/GB storage
+```
+
+| Pros | Cons |
+|------|------|
+| Cheapest storage | Two services to manage |
+| Free egress via CF | B2 API slightly different |
+| Good reliability | Need CF proxy setup |
+
+#### Cost Estimate (10K apps, 100GB)
+
+| Component | Monthly Cost |
+|-----------|--------------|
+| Storage (100GB) | $0.50 |
+| Egress (via CF) | $0 |
+| Requests | ~$0.40 |
+| **Total** | **~$1** |
+
+---
+
+### Option D: Self-hosted MinIO
+
+```
+Storage: MinIO on VPS
+CDN: Cloudflare proxy
+Egress: VPS bandwidth
+Pricing: VPS cost only
+```
+
+| Pros | Cons |
+|------|------|
+| Full control | Ops overhead |
+| S3-compatible | Need to manage |
+| Predictable cost | Scaling complexity |
+
+#### Cost Estimate
+
+| Component | Monthly Cost |
+|-----------|--------------|
+| VPS (500GB SSD) | $20-50 |
+| Cloudflare | $0 (free tier) |
+| **Total** | **~$20-50** |
+
+---
+
+## Recommendation
+
+**Primary: Cloudflare R2**
+- Zero egress fees (biggest cost saver)
+- S3-compatible (easy migration)
+- Built-in global CDN
+- Good enough tooling
+
+**Fallback: Backblaze B2 + Cloudflare**
+- If R2 has issues
+- Even cheaper storage
+- Slightly more setup
+
+---
+
+## Upload Flow
+
+### Presigned URL Approach
+
+```
+┌────────┐ ┌─────────┐ ┌─────────┐
+│ Client │───►│ API │───►│ R2 │
+└────────┘ └─────────┘ └─────────┘
+ │ │ │
+ │ 1. Request upload URL │
+ │◄─────────────┤ │
+ │ (presigned URL) │
+ │ │
+ │ 2. Upload directly │
+ │────────────────────────────►│
+ │ │
+ │ 3. Notify complete │
+ │─────────────►│ │
+ │ │ 4. Validate │
+ │ │─────────────►│
+ │ │ │
+ │ 5. Confirm │ │
+ │◄─────────────┤ │
+```
+
+### API Implementation
+
+```go
+// 1. Request upload URL
+func CreateVersion(c *gin.Context) {
+ // Create version record
+ version := Version{
+ AppID: appID,
+ VersionCode: req.VersionCode,
+ Status: "uploading",
+ }
+ db.Create(&version)
+
+ // Generate presigned URL
+ key := fmt.Sprintf("temp/%s/package.mosis", version.ID)
+ url, err := r2.PresignPut(key, 15*time.Minute)
+
+ c.JSON(200, gin.H{
+ "version": version,
+ "upload_url": url,
+ "expires": time.Now().Add(15 * time.Minute),
+ })
+}
+
+// 3. Upload complete notification
+func UploadComplete(c *gin.Context) {
+ // Move from temp to final location
+ tempKey := fmt.Sprintf("temp/%s/package.mosis", versionID)
+ finalKey := fmt.Sprintf("packages/%s/%s/%d/package.mosis",
+ developerID, appID, versionCode)
+
+ r2.Copy(tempKey, finalKey)
+ r2.Delete(tempKey)
+
+ // Update version status
+ version.Status = "validating"
+ version.PackageURL = finalKey
+
+ // Trigger async validation
+ queue.Publish("validate-package", version.ID)
+}
+```
+
+---
+
+## Download Flow
+
+### Public Downloads
+
+```go
+// Get download URL (short-lived)
+func GetDownloadURL(c *gin.Context) {
+ version := getLatestPublishedVersion(packageID)
+
+ // Generate presigned download URL (1 hour)
+ url, _ := r2.PresignGet(version.PackageURL, 1*time.Hour)
+
+ c.JSON(200, gin.H{
+ "download_url": url,
+ "version": version.VersionName,
+ "size": version.PackageSize,
+ "signature": version.Signature,
+ })
+}
+```
+
+### CDN Caching
+
+```
+Cache-Control: public, max-age=86400
+```
+
+- Packages are immutable (version code = unique)
+- Cache aggressively at edge
+- Invalidate only if package is pulled
+
+---
+
+## Icon/Screenshot Handling
+
+### Upload
+
+```go
+// Icons uploaded with app creation/update
+func UploadIcon(c *gin.Context) {
+ file, _ := c.FormFile("icon")
+
+ // Validate dimensions
+ img, _ := png.Decode(file)
+ if img.Bounds().Dx() != img.Bounds().Dy() {
+ return error("Icon must be square")
+ }
+
+ // Generate multiple sizes
+ sizes := []int{32, 64, 128}
+ for _, size := range sizes {
+ resized := resize(img, size, size)
+ key := fmt.Sprintf("assets/%s/icon-%d.png", appID, size)
+ r2.Put(key, resized)
+ }
+}
+```
+
+### Serving
+
+```
+https://cdn.mosis.dev/assets/{app_id}/icon-64.png
+```
+
+- Public read access
+- Long cache TTL (icons rarely change)
+- Cloudflare image optimization (optional)
+
+---
+
+## Retention Policy
+
+### Packages
+
+| Status | Retention |
+|--------|-----------|
+| Published (current) | Forever |
+| Published (old) | 1 year |
+| Draft | 30 days |
+| Rejected | 7 days |
+| Failed validation | 24 hours |
+
+### Temp Uploads
+
+- Delete after 1 hour if not completed
+- Lifecycle rule on `/temp/` prefix
+
+### Implementation
+
+```yaml
+# R2 Lifecycle Rules
+rules:
+ - prefix: "temp/"
+ expiration_days: 1
+
+ - prefix: "packages/"
+ tags:
+ status: draft
+ expiration_days: 30
+```
+
+---
+
+## Backup Strategy
+
+### R2 Built-in
+
+- 11 nines durability
+- Automatic replication within region
+- No additional backup needed for packages
+
+### Metadata Backup
+
+- Package metadata in PostgreSQL
+- PostgreSQL backups cover this
+- Can regenerate URLs from DB
+
+---
+
+## Monitoring
+
+### Metrics to Track
+
+| Metric | Source |
+|--------|--------|
+| Upload success rate | API logs |
+| Download latency | Cloudflare analytics |
+| Storage usage | R2 dashboard |
+| Bandwidth | R2 dashboard |
+| Error rate | API logs |
+
+### Alerts
+
+- Upload failure rate > 5%
+- Download error rate > 1%
+- Storage > 80% of budget
+
+---
+
+## Security
+
+### Access Control
+
+```
+Packages:
+ - Write: API only (presigned URLs)
+ - Read: Public (presigned URLs)
+
+Assets:
+ - Write: API only
+ - Read: Public (direct CDN)
+
+Temp:
+ - Write: Presigned URLs (15 min)
+ - Read: None (API internal only)
+```
+
+### Signed URLs
+
+```go
+// Presigned URL with expiration
+url := r2.PresignPut(key, 15*time.Minute, PutOptions{
+ ContentType: "application/octet-stream",
+ ContentLength: maxSize,
+})
+```
+
+---
+
+## Deliverables
+
+- [ ] R2 bucket setup
+- [ ] Presigned URL generation
+- [ ] Upload flow implementation
+- [ ] Download URL generation
+- [ ] Icon/screenshot upload
+- [ ] Lifecycle rules for cleanup
+- [ ] Monitoring dashboard
+- [ ] Cost tracking
+
+---
+
+## Open Questions
+
+1. Multi-region storage for lower latency?
+2. Package compression (gzip/brotli)?
+3. Delta updates storage structure?
+4. Screenshot requirements (dimensions, count)?
+
+---
+
+## References
+
+- [Cloudflare R2 Documentation](https://developers.cloudflare.com/r2/)
+- [S3 Presigned URLs](https://docs.aws.amazon.com/AmazonS3/latest/userguide/PresignedUrlUploadObject.html)
+- [Backblaze B2 + Cloudflare](https://www.backblaze.com/b2/docs/cloud_to_cloud.html)
diff --git a/DEV_PORTAL_M08_TELEMETRY.md b/DEV_PORTAL_M08_TELEMETRY.md
new file mode 100644
index 0000000..045901f
--- /dev/null
+++ b/DEV_PORTAL_M08_TELEMETRY.md
@@ -0,0 +1,503 @@
+# Milestone 8: Telemetry System
+
+**Status**: Planning
+**Goal**: Collect app usage analytics and crash reports while respecting privacy.
+
+---
+
+## Overview
+
+Telemetry provides developers with insights into app usage, performance, and crashes. Must balance usefulness with user privacy.
+
+---
+
+## Privacy Principles
+
+1. **Minimal collection** - Only what's necessary
+2. **No PII by default** - Anonymized device IDs
+3. **Transparency** - Users know what's collected
+4. **Opt-out available** - Users can disable
+5. **Data retention limits** - Auto-delete old data
+6. **GDPR compliance** - Export/delete on request
+
+---
+
+## Event Types
+
+### Automatic Events (Default)
+
+| Event | Description | Data |
+|-------|-------------|------|
+| `app_start` | App launched | version, mosis_version |
+| `app_stop` | App closed | duration_seconds |
+| `app_crash` | Unhandled error | crash_type, message |
+| `lua_error` | Lua runtime error | message, stack (no user data) |
+
+### Performance Events (Default)
+
+| Event | Description | Data |
+|-------|-------------|------|
+| `perf_frame` | Frame time (sampled) | avg_ms, p95_ms |
+| `perf_memory` | Memory usage | used_mb, limit_mb |
+| `perf_startup` | Startup time | duration_ms |
+
+### Usage Events (Opt-in)
+
+| Event | Description | Data |
+|-------|-------------|------|
+| `screen_view` | Screen navigation | screen_name |
+| `button_click` | UI interaction | element_id |
+| `feature_used` | Feature usage | feature_name |
+
+---
+
+## Data Schema
+
+### Event Payload
+
+```json
+{
+ "app_id": "com.developer.myapp",
+ "app_version": "1.2.0",
+ "mosis_version": "1.0.0",
+ "device_id": "sha256_hashed_id",
+ "session_id": "uuid",
+ "events": [
+ {
+ "type": "app_start",
+ "timestamp": "2024-01-15T10:30:00Z",
+ "data": {}
+ },
+ {
+ "type": "screen_view",
+ "timestamp": "2024-01-15T10:30:05Z",
+ "data": {
+ "screen_name": "home"
+ }
+ }
+ ]
+}
+```
+
+### Crash Report Payload
+
+```json
+{
+ "app_id": "com.developer.myapp",
+ "app_version": "1.2.0",
+ "mosis_version": "1.0.0",
+ "device_id": "sha256_hashed_id",
+ "timestamp": "2024-01-15T10:35:00Z",
+ "crash": {
+ "type": "lua_error",
+ "message": "attempt to index nil value 'user'",
+ "stack_trace": "main.lua:42: in function 'loadUser'\nmain.lua:15: in main chunk",
+ "context": {
+ "screen": "profile.rml",
+ "memory_mb": 45,
+ "uptime_seconds": 300
+ }
+ }
+}
+```
+
+### Device ID Hashing
+
+```lua
+-- On device
+local raw_id = get_android_id() -- or similar
+local hashed = sha256(raw_id .. "mosis_salt_" .. app_id)
+-- Result: "a3f2b1c4d5e6..."
+
+-- Cannot reverse to original device ID
+-- Different per app (can't track across apps)
+```
+
+---
+
+## Collection Architecture
+
+```
+┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
+│ Device │────►│ Batch │────►│ API │────►│ Storage │
+│ │ │ Queue │ │ │ │ │
+└──────────┘ └──────────┘ └──────────┘ └──────────┘
+ │
+ │ Every 60s or
+ │ on app close
+ ▼
+ ┌──────────┐
+ │ Upload │
+ └──────────┘
+```
+
+### Client-Side Batching
+
+```lua
+-- TelemetryManager on device
+local events = {}
+local last_flush = os.time()
+
+function track(event_type, data)
+ if not telemetry_enabled then return end
+
+ table.insert(events, {
+ type = event_type,
+ timestamp = os.date("!%Y-%m-%dT%H:%M:%SZ"),
+ data = data or {}
+ })
+
+ -- Flush if batch is large or time elapsed
+ if #events >= 50 or (os.time() - last_flush) > 60 then
+ flush()
+ end
+end
+
+function flush()
+ if #events == 0 then return end
+
+ local payload = {
+ app_id = APP_ID,
+ app_version = APP_VERSION,
+ device_id = HASHED_DEVICE_ID,
+ events = events
+ }
+
+ -- Async HTTP POST
+ http.post(TELEMETRY_URL, json.encode(payload))
+
+ events = {}
+ last_flush = os.time()
+end
+```
+
+---
+
+## Storage Options
+
+### Option A: PostgreSQL + TimescaleDB
+
+```sql
+-- Hypertable for time-series data
+CREATE TABLE telemetry_events (
+ time TIMESTAMPTZ NOT NULL,
+ app_id TEXT NOT NULL,
+ device_id TEXT NOT NULL,
+ session_id TEXT,
+ event_type TEXT NOT NULL,
+ event_data JSONB,
+ app_version TEXT,
+ mosis_version TEXT
+);
+
+SELECT create_hypertable('telemetry_events', 'time');
+
+-- Continuous aggregate for daily stats
+CREATE MATERIALIZED VIEW daily_stats
+WITH (timescaledb.continuous) AS
+SELECT
+ time_bucket('1 day', time) AS day,
+ app_id,
+ event_type,
+ COUNT(*) as count,
+ COUNT(DISTINCT device_id) as unique_devices
+FROM telemetry_events
+GROUP BY day, app_id, event_type;
+```
+
+### Option B: ClickHouse
+
+```sql
+CREATE TABLE telemetry_events (
+ timestamp DateTime,
+ app_id String,
+ device_id String,
+ session_id String,
+ event_type String,
+ event_data String, -- JSON
+ app_version String,
+ mosis_version String
+) ENGINE = MergeTree()
+PARTITION BY toYYYYMM(timestamp)
+ORDER BY (app_id, timestamp);
+```
+
+### Option C: Custom + PostgreSQL
+
+```
+Raw events → Write to append-only log
+Aggregator → Process hourly → Write to PostgreSQL
+Cleanup → Delete raw after 24h
+```
+
+---
+
+## Aggregation
+
+### Pre-computed Metrics
+
+| Metric | Granularity | Retention |
+|--------|-------------|-----------|
+| Daily active users | Day | 2 years |
+| Event counts | Day | 1 year |
+| Crash counts | Day | 1 year |
+| Session duration | Day | 90 days |
+| Performance percentiles | Day | 90 days |
+
+### Aggregation Queries
+
+```sql
+-- Daily active users
+SELECT
+ DATE_TRUNC('day', time) as day,
+ COUNT(DISTINCT device_id) as dau
+FROM telemetry_events
+WHERE app_id = $1
+ AND event_type = 'app_start'
+ AND time > NOW() - INTERVAL '30 days'
+GROUP BY day
+ORDER BY day;
+
+-- Crash rate by version
+SELECT
+ app_version,
+ COUNT(*) FILTER (WHERE event_type = 'app_crash') as crashes,
+ COUNT(*) FILTER (WHERE event_type = 'app_start') as starts,
+ ROUND(
+ 100.0 * COUNT(*) FILTER (WHERE event_type = 'app_crash') /
+ NULLIF(COUNT(*) FILTER (WHERE event_type = 'app_start'), 0),
+ 2
+ ) as crash_rate
+FROM telemetry_events
+WHERE app_id = $1
+ AND time > NOW() - INTERVAL '7 days'
+GROUP BY app_version;
+```
+
+---
+
+## Crash Grouping
+
+### Stack Trace Fingerprinting
+
+```go
+func fingerprintCrash(crash CrashReport) string {
+ // Normalize stack trace
+ normalized := normalizeStackTrace(crash.StackTrace)
+
+ // Hash key components
+ key := fmt.Sprintf("%s:%s:%s",
+ crash.CrashType,
+ crash.Message,
+ normalized,
+ )
+
+ return sha256(key)[:16]
+}
+
+func normalizeStackTrace(stack string) string {
+ // Remove line numbers (they change with code updates)
+ // Remove memory addresses
+ // Keep function names and file names
+ re := regexp.MustCompile(`:\d+:`)
+ return re.ReplaceAllString(stack, ":?:")
+}
+```
+
+### Crash Groups Table
+
+```sql
+CREATE TABLE crash_groups (
+ id UUID PRIMARY KEY,
+ app_id TEXT NOT NULL,
+ fingerprint TEXT NOT NULL,
+ crash_type TEXT NOT NULL,
+ message TEXT,
+ sample_stack_trace TEXT,
+ first_seen TIMESTAMPTZ NOT NULL,
+ last_seen TIMESTAMPTZ NOT NULL,
+ occurrence_count INT DEFAULT 1,
+ affected_versions TEXT[],
+ status TEXT DEFAULT 'open', -- open, resolved, ignored
+ UNIQUE(app_id, fingerprint)
+);
+```
+
+---
+
+## Developer Dashboard
+
+### Metrics View
+
+```
+┌─────────────────────────────────────────────────────────────┐
+│ Analytics - My Calculator │
+├─────────────────────────────────────────────────────────────┤
+│ │
+│ Date Range: [Last 30 days ▼] │
+│ │
+│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
+│ │ Daily Users │ │ Crashes │ │ Crash-free │ │
+│ │ 1,234 │ │ 23 │ │ 98.1% │ │
+│ │ ▲ +12% │ │ ▼ -45% │ │ ▲ +2% │ │
+│ └─────────────┘ └─────────────┘ └─────────────┘ │
+│ │
+│ ┌────────────────────────────────────────────────────┐ │
+│ │ Daily Active Users │ │
+│ │ [Line chart showing DAU over time] │ │
+│ └────────────────────────────────────────────────────┘ │
+│ │
+│ ┌────────────────────────────────────────────────────┐ │
+│ │ Version Distribution │ │
+│ │ [Pie chart: v1.2.0: 60%, v1.1.0: 30%, v1.0.0: 10%]│ │
+│ └────────────────────────────────────────────────────┘ │
+│ │
+└─────────────────────────────────────────────────────────────┘
+```
+
+### Crashes View
+
+```
+┌─────────────────────────────────────────────────────────────┐
+│ Crashes - My Calculator │
+├─────────────────────────────────────────────────────────────┤
+│ │
+│ Filter: [All versions ▼] [Open ▼] │
+│ │
+│ ┌──────────────────────────────────────────────────────┐ │
+│ │ ● attempt to index nil value 'user' │ │
+│ │ lua_error • 156 occurrences • v1.2.0 │ │
+│ │ First: Jan 10 • Last: Jan 15 │ │
+│ │ [View] │ │
+│ ├──────────────────────────────────────────────────────┤ │
+│ │ ● memory limit exceeded │ │
+│ │ sandbox_error • 23 occurrences • v1.1.0, v1.2.0 │ │
+│ │ First: Jan 5 • Last: Jan 14 │ │
+│ │ [View] │ │
+│ └──────────────────────────────────────────────────────┘ │
+│ │
+└─────────────────────────────────────────────────────────────┘
+```
+
+---
+
+## API Endpoints
+
+```yaml
+# Ingestion (from devices)
+POST /v1/telemetry/events:
+ auth: device_token or api_key
+ body: { app_id, device_id, events[] }
+ response: { received: number }
+
+POST /v1/telemetry/crash:
+ auth: device_token or api_key
+ body: { app_id, device_id, crash }
+ response: { id: string }
+
+# Dashboard (for developers)
+GET /v1/apps/:id/analytics/overview:
+ auth: required
+ query: { start_date, end_date }
+ response: { dau, crashes, crash_free_rate, ... }
+
+GET /v1/apps/:id/analytics/events:
+ auth: required
+ query: { start_date, end_date, event_type }
+ response: { data: [{ date, count, unique_devices }] }
+
+GET /v1/apps/:id/crashes:
+ auth: required
+ query: { version, status, page, limit }
+ response: { crashes: CrashGroup[], total }
+
+GET /v1/apps/:id/crashes/:fingerprint:
+ auth: required
+ response: { crash_group, recent_occurrences[] }
+
+PATCH /v1/apps/:id/crashes/:fingerprint:
+ auth: required
+ body: { status: 'resolved' | 'ignored' }
+ response: { crash_group }
+```
+
+---
+
+## Data Retention
+
+| Data Type | Retention | Reason |
+|-----------|-----------|--------|
+| Raw events | 7 days | Debugging |
+| Daily aggregates | 2 years | Trends |
+| Crash reports | 90 days | Investigation |
+| Crash groups | Forever | Issue tracking |
+
+### Cleanup Job
+
+```sql
+-- Run daily
+DELETE FROM telemetry_events
+WHERE time < NOW() - INTERVAL '7 days';
+
+DELETE FROM crash_reports
+WHERE timestamp < NOW() - INTERVAL '90 days';
+```
+
+---
+
+## Privacy Controls
+
+### User Settings
+
+```
+Settings > Privacy > Analytics
+├── [✓] Send crash reports (helps developers fix bugs)
+├── [ ] Send usage analytics (how you use apps)
+└── [Request Data Deletion]
+```
+
+### GDPR Endpoints
+
+```yaml
+# User requests their data
+GET /v1/privacy/export:
+ auth: user_token
+ response: { download_url } # JSON export of all data
+
+# User requests deletion
+DELETE /v1/privacy/data:
+ auth: user_token
+ response: { status: 'scheduled' } # Delete within 30 days
+```
+
+---
+
+## Deliverables
+
+- [ ] Event schema specification
+- [ ] Client-side SDK for batching
+- [ ] Ingestion API endpoints
+- [ ] Storage setup (TimescaleDB or ClickHouse)
+- [ ] Aggregation jobs
+- [ ] Crash grouping logic
+- [ ] Developer dashboard
+- [ ] Privacy controls
+- [ ] Data retention automation
+- [ ] GDPR export/delete
+
+---
+
+## Open Questions
+
+1. Real-time crash alerts (email/Slack)?
+2. Sampling for high-volume apps?
+3. Custom events API for developers?
+4. Benchmarks/comparisons with similar apps?
+
+---
+
+## References
+
+- [GDPR Requirements](https://gdpr.eu/)
+- [TimescaleDB Best Practices](https://docs.timescale.com/timescaledb/latest/)
+- [Sentry Crash Grouping](https://docs.sentry.io/product/data-management-settings/event-grouping/)
diff --git a/DEV_PORTAL_M09_REVIEW.md b/DEV_PORTAL_M09_REVIEW.md
new file mode 100644
index 0000000..df811f8
--- /dev/null
+++ b/DEV_PORTAL_M09_REVIEW.md
@@ -0,0 +1,474 @@
+# Milestone 9: App Review System
+
+**Status**: Planning
+**Goal**: Automated and manual review process for app submissions.
+
+---
+
+## Overview
+
+The review system ensures apps meet quality and security standards before publication. Balances automation with manual review for edge cases.
+
+---
+
+## Review Pipeline
+
+```
+┌─────────┐ ┌───────────┐ ┌─────────┐ ┌──────────┐ ┌───────────┐
+│ Submit │──►│ Automated │──►│ Manual │──►│ Approved │──►│ Published │
+│ │ │ Checks │ │ Review │ │ │ │ │
+└─────────┘ └───────────┘ └─────────┘ └──────────┘ └───────────┘
+ │ │
+ ▼ ▼
+ ┌─────────┐ ┌─────────┐
+ │ Failed │ │Rejected │
+ │(auto-fix)│ │(feedback)│
+ └─────────┘ └─────────┘
+```
+
+---
+
+## Automated Checks
+
+### Tier 1: Package Validation (Blocking)
+
+| Check | Description | Action on Fail |
+|-------|-------------|----------------|
+| Valid ZIP | Package is valid ZIP format | Reject |
+| Manifest exists | manifest.json at root | Reject |
+| Manifest valid | JSON parses, required fields | Reject |
+| Signature valid | Package signed with registered key | Reject |
+| Entry exists | Entry point file exists | Reject |
+| Size limits | Under max package size | Reject |
+| No forbidden files | No .exe, .dll, etc. | Reject |
+
+### Tier 2: Content Validation (Blocking)
+
+| Check | Description | Action on Fail |
+|-------|-------------|----------------|
+| RML valid | All RML files parse | Reject |
+| RCSS valid | All RCSS files parse | Reject |
+| Lua syntax | All Lua files parse | Reject |
+| Icons valid | Icons are valid images | Reject |
+| Icon sizes | Required icon sizes present | Reject |
+| Path safety | No path traversal attempts | Reject |
+
+### Tier 3: Security Analysis (Warning/Flag)
+
+| Check | Description | Action on Fail |
+|-------|-------------|----------------|
+| Dangerous patterns | Known malicious Lua patterns | Flag for review |
+| Excessive permissions | Unusual permission combos | Flag for review |
+| Obfuscated code | Heavily obfuscated Lua | Flag for review |
+| External URLs | Hardcoded external URLs | Flag for review |
+| Large assets | Unusually large files | Warning |
+
+### Tier 4: Quality Checks (Warning)
+
+| Check | Description | Action on Fail |
+|-------|-------------|----------------|
+| Description length | Meaningful description | Warning |
+| Release notes | Non-empty release notes | Warning |
+| Icon quality | Not placeholder/blank | Warning |
+| Localization | Locale files complete | Warning |
+
+---
+
+## Implementation
+
+### Validation Worker
+
+```go
+type ValidationResult struct {
+ Passed bool
+ Errors []ValidationError
+ Warnings []ValidationWarning
+ Flags []ReviewFlag
+}
+
+type ValidationError struct {
+ Code string
+ Message string
+ File string
+ Line int
+}
+
+func ValidatePackage(packagePath string) ValidationResult {
+ result := ValidationResult{Passed: true}
+
+ // Tier 1: Package validation
+ if err := validateZip(packagePath); err != nil {
+ result.AddError("INVALID_ZIP", err.Error())
+ return result
+ }
+
+ manifest, err := extractManifest(packagePath)
+ if err != nil {
+ result.AddError("INVALID_MANIFEST", err.Error())
+ return result
+ }
+
+ if err := validateSignature(packagePath, manifest); err != nil {
+ result.AddError("INVALID_SIGNATURE", err.Error())
+ return result
+ }
+
+ // Tier 2: Content validation
+ files, _ := listFiles(packagePath)
+ for _, file := range files {
+ switch filepath.Ext(file) {
+ case ".rml":
+ if err := validateRML(file); err != nil {
+ result.AddError("INVALID_RML", err.Error(), file)
+ }
+ case ".rcss":
+ if err := validateRCSS(file); err != nil {
+ result.AddError("INVALID_RCSS", err.Error(), file)
+ }
+ case ".lua":
+ if err := validateLua(file); err != nil {
+ result.AddError("INVALID_LUA", err.Error(), file)
+ }
+ if flags := analyzeLuaSecurity(file); len(flags) > 0 {
+ result.Flags = append(result.Flags, flags...)
+ }
+ }
+ }
+
+ // Tier 3: Security analysis
+ if hasDangerousPatterns(files) {
+ result.AddFlag("DANGEROUS_PATTERNS", "Code contains suspicious patterns")
+ }
+
+ // Tier 4: Quality checks
+ if len(manifest.Description) < 10 {
+ result.AddWarning("SHORT_DESCRIPTION", "Description is very short")
+ }
+
+ result.Passed = len(result.Errors) == 0
+ return result
+}
+```
+
+### Dangerous Pattern Detection
+
+```go
+var dangerousPatterns = []struct {
+ Pattern *regexp.Regexp
+ Reason string
+}{
+ {
+ regexp.MustCompile(`loadstring\s*\(`),
+ "Dynamic code execution",
+ },
+ {
+ regexp.MustCompile(`debug\s*\.\s*\w+`),
+ "Debug library usage",
+ },
+ {
+ regexp.MustCompile(`os\s*\.\s*execute`),
+ "OS command execution",
+ },
+ {
+ regexp.MustCompile(`io\s*\.\s*\w+`),
+ "Direct I/O operations",
+ },
+ {
+ regexp.MustCompile(`ffi\s*\.\s*\w+`),
+ "FFI usage",
+ },
+ {
+ regexp.MustCompile(`package\s*\.\s*loadlib`),
+ "Native library loading",
+ },
+}
+
+func analyzeLuaSecurity(content string) []ReviewFlag {
+ var flags []ReviewFlag
+ for _, dp := range dangerousPatterns {
+ if dp.Pattern.MatchString(content) {
+ flags = append(flags, ReviewFlag{
+ Type: "SECURITY",
+ Reason: dp.Reason,
+ })
+ }
+ }
+ return flags
+}
+```
+
+---
+
+## Manual Review
+
+### When Required
+
+| Trigger | Reason |
+|---------|--------|
+| New developer | First app submission |
+| Dangerous permissions | camera, microphone, contacts, location |
+| Security flags | Automated checks flagged concerns |
+| User reports | Existing app reported |
+| Appeal | Developer contests rejection |
+
+### Review Queue UI
+
+```
+┌─────────────────────────────────────────────────────────────┐
+│ Review Queue [14 pending] │
+├─────────────────────────────────────────────────────────────┤
+│ │
+│ Filter: [All ▼] [Flagged first ▼] │
+│ │
+│ ┌──────────────────────────────────────────────────────┐ │
+│ │ 🚩 Weather Pro v2.0.0 │ │
+│ │ com.newdev.weather • New developer │ │
+│ │ Permissions: location, network │ │
+│ │ Flags: First submission │ │
+│ │ Submitted: 2 hours ago │ │
+│ │ [Review] [Auto-approve]│ │
+│ ├──────────────────────────────────────────────────────┤ │
+│ │ 🚩 Photo Editor v1.5.0 │ │
+│ │ com.trusted.photos • Verified developer │ │
+│ │ Permissions: camera, storage │ │
+│ │ Flags: DANGEROUS_PATTERNS (1) │ │
+│ │ Submitted: 5 hours ago │ │
+│ │ [Review] [Auto-approve]│ │
+│ └──────────────────────────────────────────────────────┘ │
+│ │
+└─────────────────────────────────────────────────────────────┘
+```
+
+### Review Detail View
+
+```
+┌─────────────────────────────────────────────────────────────┐
+│ Review: Weather Pro v2.0.0 │
+├─────────────────────────────────────────────────────────────┤
+│ │
+│ Package: com.newdev.weather │
+│ Developer: newdev@example.com (New - first app) │
+│ Submitted: Jan 15, 2024 10:30 AM │
+│ │
+│ ┌─────────────────────────────────────────────────────┐ │
+│ │ Automated Checks │ │
+│ │ ✓ Package validation passed │ │
+│ │ ✓ Content validation passed │ │
+│ │ ⚠ Warning: SHORT_DESCRIPTION │ │
+│ │ 🚩 Flag: First submission from new developer │ │
+│ └─────────────────────────────────────────────────────┘ │
+│ │
+│ Permissions Requested: │
+│ • location (fine) - Used for weather forecasts │
+│ • network - Required for API calls │
+│ │
+│ Files: [Expand to browse] │
+│ ├── manifest.json │
+│ ├── assets/ │
+│ │ ├── main.rml │
+│ │ └── scripts/ │
+│ │ └── weather.lua [View source] │
+│ └── icons/ │
+│ │
+│ Reviewer Notes: │
+│ ┌─────────────────────────────────────────────────────┐ │
+│ │ │ │
+│ └─────────────────────────────────────────────────────┘ │
+│ │
+│ [Approve] [Reject with feedback] [Request changes] │
+│ │
+└─────────────────────────────────────────────────────────────┘
+```
+
+---
+
+## Review States
+
+```
+ ┌─────────┐
+ │ Draft │
+ └────┬────┘
+ │ submit
+ ▼
+ ┌─────────┐
+ ┌───────│Uploaded │
+ │ └────┬────┘
+ │ │ validation
+ │ ▼
+ │ ┌──────────┐
+ failed │ │Validating│
+ │ └────┬─────┘
+ │ │
+ │ ┌───────┴───────┐
+ │ │ │
+ ▼ ▼ ▼
+ ┌────────┐ ┌─────────┐
+ │ Failed │ │In Review│
+ └────────┘ └────┬────┘
+ │
+ ┌───────────┼───────────┐
+ │ │ │
+ ▼ ▼ ▼
+ ┌──────────┐ ┌────────┐ ┌──────────┐
+ │ Approved │ │Rejected│ │ Changes │
+ └────┬─────┘ └────────┘ │ Requested│
+ │ └──────────┘
+ │ publish
+ ▼
+ ┌──────────┐
+ │Published │
+ └──────────┘
+```
+
+---
+
+## Rejection Feedback
+
+### Feedback Template
+
+```json
+{
+ "reason": "SECURITY_CONCERN",
+ "message": "Your app contains patterns that raise security concerns.",
+ "details": [
+ {
+ "file": "scripts/main.lua",
+ "line": 42,
+ "issue": "Usage of loadstring() is not allowed",
+ "suggestion": "Use static Lua code instead of dynamic evaluation"
+ }
+ ],
+ "can_resubmit": true,
+ "appeal_available": true
+}
+```
+
+### Common Rejection Reasons
+
+| Code | Description |
+|------|-------------|
+| `SECURITY_CONCERN` | Security issues found |
+| `QUALITY_ISSUE` | Doesn't meet quality standards |
+| `POLICY_VIOLATION` | Violates content policy |
+| `METADATA_ISSUE` | Incorrect/misleading metadata |
+| `PERMISSION_ABUSE` | Unnecessary permissions |
+| `COPYRIGHT` | Copyright/trademark issues |
+
+---
+
+## Appeal Process
+
+```
+1. Developer receives rejection
+2. Developer clicks "Appeal" (within 14 days)
+3. Provides justification
+4. Different reviewer examines
+5. Final decision (approve/uphold rejection)
+```
+
+---
+
+## Review SLA
+
+| Submission Type | Target Time |
+|-----------------|-------------|
+| Auto-approved | Instant |
+| New developer | 24 hours |
+| Flagged | 48 hours |
+| Appeal | 72 hours |
+
+---
+
+## API Endpoints
+
+```yaml
+# Developer endpoints
+POST /v1/apps/:id/versions/:vid/submit:
+ summary: Submit for review
+ response: { version, estimated_review_time }
+
+GET /v1/apps/:id/versions/:vid/review-status:
+ summary: Get review status
+ response: { status, feedback?, estimated_completion }
+
+POST /v1/apps/:id/versions/:vid/appeal:
+ summary: Appeal rejection
+ body: { justification }
+ response: { appeal_id, status }
+
+# Internal review endpoints (admin only)
+GET /v1/admin/review-queue:
+ summary: Get pending reviews
+ response: { items[], total }
+
+GET /v1/admin/review/:version_id:
+ summary: Get review details
+ response: { version, validation_result, flags }
+
+POST /v1/admin/review/:version_id/approve:
+ summary: Approve version
+ body: { notes? }
+ response: { version }
+
+POST /v1/admin/review/:version_id/reject:
+ summary: Reject version
+ body: { reason, message, details[] }
+ response: { version }
+```
+
+---
+
+## Metrics
+
+### Review Performance
+
+| Metric | Target |
+|--------|--------|
+| Auto-approval rate | > 70% |
+| Average review time | < 24 hours |
+| Rejection rate | < 20% |
+| Appeal overturn rate | < 10% |
+
+### Dashboard
+
+```sql
+-- Review stats
+SELECT
+ DATE_TRUNC('week', submitted_at) as week,
+ COUNT(*) as submissions,
+ COUNT(*) FILTER (WHERE status = 'published') as approved,
+ COUNT(*) FILTER (WHERE status = 'rejected') as rejected,
+ AVG(EXTRACT(EPOCH FROM (reviewed_at - submitted_at))/3600) as avg_hours
+FROM app_versions
+WHERE submitted_at > NOW() - INTERVAL '30 days'
+GROUP BY week;
+```
+
+---
+
+## Deliverables
+
+- [ ] Validation worker implementation
+- [ ] Dangerous pattern database
+- [ ] Review queue UI
+- [ ] Reviewer tools
+- [ ] Rejection feedback system
+- [ ] Appeal workflow
+- [ ] Review metrics dashboard
+- [ ] SLA monitoring
+
+---
+
+## Open Questions
+
+1. Automated approval for trusted developers?
+2. Community moderators?
+3. Content policy document?
+4. Rate limiting resubmissions?
+
+---
+
+## References
+
+- [Apple App Review Guidelines](https://developer.apple.com/app-store/review/guidelines/)
+- [Google Play Policy](https://play.google.com/about/developer-content-policy/)
diff --git a/DEV_PORTAL_M10_DEVICE.md b/DEV_PORTAL_M10_DEVICE.md
new file mode 100644
index 0000000..c5e27e3
--- /dev/null
+++ b/DEV_PORTAL_M10_DEVICE.md
@@ -0,0 +1,603 @@
+# Milestone 10: Device-Side App Management
+
+**Status**: Planning
+**Goal**: Install, update, and manage apps on Mosis devices.
+
+---
+
+## Overview
+
+Device-side app management handles the full lifecycle of apps on user devices: discovery, installation, updates, and removal.
+
+---
+
+## Components
+
+```
+┌─────────────────────────────────────────────────────────────┐
+│ MosisService │
+├─────────────────────────────────────────────────────────────┤
+│ │
+│ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ │
+│ │ AppManager │ │ UpdateService │ │ AppStore UI │ │
+│ │ (C++ class) │ │ (Background) │ │ (System App) │ │
+│ └───────────────┘ └───────────────┘ └───────────────┘ │
+│ │ │ │ │
+│ └───────────────────┼───────────────────┘ │
+│ │ │
+│ ┌────────┴────────┐ │
+│ │ LuaSandboxManager│ │
+│ └─────────────────┘ │
+│ │
+└─────────────────────────────────────────────────────────────┘
+```
+
+---
+
+## Storage Layout
+
+```
+/data/mosis/
+├── config/
+│ ├── device.json # Device ID, settings
+│ └── apps.json # Installed apps registry
+├── apps/
+│ └── {package_id}/
+│ ├── package/ # Extracted app files
+│ │ ├── manifest.json
+│ │ └── assets/
+│ ├── data/ # App data (VirtualFS)
+│ ├── cache/ # App cache
+│ └── db/ # SQLite databases
+├── downloads/ # Temp download location
+└── backups/ # App data backups (before update)
+```
+
+---
+
+## AppManager Class
+
+### Interface
+
+```cpp
+namespace mosis {
+
+struct InstalledApp {
+ std::string package_id;
+ std::string name;
+ std::string version_name;
+ int version_code;
+ std::string install_path;
+ std::vector permissions;
+ std::chrono::system_clock::time_point installed_at;
+ std::chrono::system_clock::time_point updated_at;
+ int64_t package_size;
+ int64_t data_size;
+};
+
+struct InstallProgress {
+ enum class Stage {
+ Downloading,
+ Verifying,
+ Extracting,
+ Registering,
+ Complete,
+ Failed
+ };
+ Stage stage;
+ float progress; // 0.0 - 1.0
+ std::string error;
+};
+
+using ProgressCallback = std::function;
+
+class AppManager {
+public:
+ explicit AppManager(const std::string& data_root);
+ ~AppManager();
+
+ // Installation
+ bool Install(const std::string& package_url,
+ const std::string& signature,
+ ProgressCallback callback);
+ bool InstallFromFile(const std::string& package_path,
+ ProgressCallback callback);
+
+ // Uninstallation
+ bool Uninstall(const std::string& package_id, bool keep_data = false);
+
+ // Updates
+ bool Update(const std::string& package_id,
+ const std::string& package_url,
+ const std::string& signature,
+ ProgressCallback callback);
+
+ // Queries
+ std::vector GetInstalledApps() const;
+ std::optional GetApp(const std::string& package_id) const;
+ bool IsInstalled(const std::string& package_id) const;
+
+ // Data management
+ int64_t GetAppDataSize(const std::string& package_id) const;
+ bool ClearAppData(const std::string& package_id);
+ bool ClearAppCache(const std::string& package_id);
+ bool BackupAppData(const std::string& package_id);
+ bool RestoreAppData(const std::string& package_id);
+
+ // Integration with sandbox
+ void SetSandboxManager(LuaSandboxManager* manager);
+
+private:
+ std::string m_data_root;
+ LuaSandboxManager* m_sandbox_manager = nullptr;
+ mutable std::mutex m_mutex;
+ std::map m_installed_apps;
+
+ bool VerifyPackage(const std::string& path, const std::string& signature);
+ bool ExtractPackage(const std::string& path, const std::string& dest);
+ void LoadInstalledApps();
+ void SaveInstalledApps();
+};
+
+} // namespace mosis
+```
+
+### Implementation
+
+```cpp
+bool AppManager::Install(const std::string& package_url,
+ const std::string& signature,
+ ProgressCallback callback) {
+ callback({InstallProgress::Stage::Downloading, 0.0f, ""});
+
+ // Download package
+ std::string download_path = m_data_root + "/downloads/" + GenerateUUID();
+ if (!DownloadFile(package_url, download_path, [&](float p) {
+ callback({InstallProgress::Stage::Downloading, p, ""});
+ })) {
+ callback({InstallProgress::Stage::Failed, 0.0f, "Download failed"});
+ return false;
+ }
+
+ callback({InstallProgress::Stage::Verifying, 0.0f, ""});
+
+ // Verify signature
+ if (!VerifyPackage(download_path, signature)) {
+ std::filesystem::remove(download_path);
+ callback({InstallProgress::Stage::Failed, 0.0f, "Signature verification failed"});
+ return false;
+ }
+
+ // Extract manifest to get package_id
+ auto manifest = ExtractManifest(download_path);
+ if (!manifest) {
+ std::filesystem::remove(download_path);
+ callback({InstallProgress::Stage::Failed, 0.0f, "Invalid manifest"});
+ return false;
+ }
+
+ callback({InstallProgress::Stage::Extracting, 0.0f, ""});
+
+ // Check if already installed (update path)
+ std::string install_path = m_data_root + "/apps/" + manifest->package_id;
+ if (std::filesystem::exists(install_path + "/package")) {
+ // Backup existing data
+ BackupAppData(manifest->package_id);
+ // Remove old package
+ std::filesystem::remove_all(install_path + "/package");
+ }
+
+ // Extract package
+ std::filesystem::create_directories(install_path + "/package");
+ if (!ExtractPackage(download_path, install_path + "/package")) {
+ callback({InstallProgress::Stage::Failed, 0.0f, "Extraction failed"});
+ return false;
+ }
+
+ // Clean up download
+ std::filesystem::remove(download_path);
+
+ callback({InstallProgress::Stage::Registering, 0.0f, ""});
+
+ // Create data directories
+ std::filesystem::create_directories(install_path + "/data");
+ std::filesystem::create_directories(install_path + "/cache");
+ std::filesystem::create_directories(install_path + "/db");
+
+ // Register app
+ InstalledApp app{
+ .package_id = manifest->package_id,
+ .name = manifest->name,
+ .version_name = manifest->version,
+ .version_code = manifest->version_code,
+ .install_path = install_path,
+ .permissions = manifest->permissions,
+ .installed_at = std::chrono::system_clock::now(),
+ .updated_at = std::chrono::system_clock::now(),
+ .package_size = std::filesystem::file_size(download_path)
+ };
+
+ {
+ std::lock_guard lock(m_mutex);
+ m_installed_apps[manifest->package_id] = app;
+ SaveInstalledApps();
+ }
+
+ callback({InstallProgress::Stage::Complete, 1.0f, ""});
+ return true;
+}
+
+bool AppManager::Uninstall(const std::string& package_id, bool keep_data) {
+ std::lock_guard lock(m_mutex);
+
+ auto it = m_installed_apps.find(package_id);
+ if (it == m_installed_apps.end()) {
+ return false;
+ }
+
+ // Stop app if running
+ if (m_sandbox_manager && m_sandbox_manager->IsAppRunning(package_id)) {
+ m_sandbox_manager->StopApp(package_id);
+ }
+
+ // Remove files
+ std::string install_path = it->second.install_path;
+ std::filesystem::remove_all(install_path + "/package");
+ std::filesystem::remove_all(install_path + "/cache");
+
+ if (!keep_data) {
+ std::filesystem::remove_all(install_path + "/data");
+ std::filesystem::remove_all(install_path + "/db");
+ std::filesystem::remove_all(install_path);
+ }
+
+ // Unregister
+ m_installed_apps.erase(it);
+ SaveInstalledApps();
+
+ return true;
+}
+```
+
+---
+
+## Update Service
+
+### Background Update Checker
+
+```cpp
+class UpdateService {
+public:
+ UpdateService(AppManager* app_manager, const std::string& api_base);
+
+ // Start background checking
+ void Start(std::chrono::hours interval = std::chrono::hours(24));
+ void Stop();
+
+ // Manual check
+ std::vector CheckForUpdates();
+
+ // Download and install update
+ bool ApplyUpdate(const std::string& package_id, ProgressCallback callback);
+
+ // Settings
+ void SetAutoUpdate(bool enabled);
+ void SetWifiOnly(bool wifi_only);
+
+private:
+ void CheckLoop();
+
+ AppManager* m_app_manager;
+ std::string m_api_base;
+ std::thread m_check_thread;
+ std::atomic m_running{false};
+ bool m_auto_update = false;
+ bool m_wifi_only = true;
+};
+```
+
+### Update Check Flow
+
+```
+1. Get list of installed apps
+2. Call API: GET /store/apps/updates?packages=com.a,com.b,com.c
+3. API returns list of available updates
+4. If auto-update enabled and on WiFi:
+ - Download and install in background
+ - Notify user of completed updates
+5. If manual:
+ - Show notification with update count
+ - User opens App Store to review
+```
+
+---
+
+## App Store System App
+
+### UI Screens
+
+```
+Home
+├── Featured apps
+├── Categories
+├── Search
+└── My Apps
+ ├── Installed
+ ├── Updates available
+ └── Previously installed
+
+App Detail
+├── Icon, name, developer
+├── Screenshots
+├── Description
+├── Permissions
+├── Reviews (future)
+└── [Install] / [Update] / [Open]
+
+Settings
+├── Auto-update (WiFi only)
+├── Storage usage
+└── Clear all caches
+```
+
+### RML Structure
+
+```xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+
Home
+
+
+

+
Categories
+
+
+

+
My Apps
+
+
+
+
+```
+
+### Lua Store Logic
+
+```lua
+-- store.lua
+local api = require("store_api")
+local ui = require("ui")
+
+local state = {
+ screen = "home",
+ featured = {},
+ categories = {},
+ installed = {},
+ updates = {}
+}
+
+function init()
+ -- Load installed apps
+ state.installed = mosis.apps.getInstalled()
+
+ -- Fetch featured apps
+ api.getFeatured(function(apps)
+ state.featured = apps
+ render()
+ end)
+
+ -- Check for updates
+ checkUpdates()
+end
+
+function checkUpdates()
+ local package_ids = {}
+ for _, app in ipairs(state.installed) do
+ table.insert(package_ids, app.package_id)
+ end
+
+ api.checkUpdates(package_ids, function(updates)
+ state.updates = updates
+ render()
+ end)
+end
+
+function installApp(package_id)
+ local app = findApp(package_id)
+ if not app then return end
+
+ showProgress(app.name)
+
+ mosis.apps.install(app.download_url, app.signature, function(progress)
+ updateProgress(progress.stage, progress.progress)
+
+ if progress.stage == "complete" then
+ hideProgress()
+ showToast(app.name .. " installed")
+ state.installed = mosis.apps.getInstalled()
+ render()
+ elseif progress.stage == "failed" then
+ hideProgress()
+ showError("Installation failed: " .. progress.error)
+ end
+ end)
+end
+
+function openApp(package_id)
+ mosis.apps.launch(package_id)
+end
+
+function uninstallApp(package_id)
+ showConfirm("Uninstall " .. getAppName(package_id) .. "?", function(confirmed)
+ if confirmed then
+ mosis.apps.uninstall(package_id)
+ state.installed = mosis.apps.getInstalled()
+ render()
+ end
+ end)
+end
+```
+
+---
+
+## Lua API for Apps
+
+### Exposed to System Apps
+
+```lua
+-- mosis.apps namespace (system apps only)
+
+-- Get installed apps
+local apps = mosis.apps.getInstalled()
+-- Returns: [{package_id, name, version_name, version_code, installed_at}]
+
+-- Install from store
+mosis.apps.install(url, signature, callback)
+-- callback(progress): {stage, progress, error}
+
+-- Uninstall
+mosis.apps.uninstall(package_id)
+
+-- Launch app
+mosis.apps.launch(package_id)
+
+-- Get app info
+local info = mosis.apps.getInfo(package_id)
+
+-- Storage management
+local size = mosis.apps.getDataSize(package_id)
+mosis.apps.clearCache(package_id)
+mosis.apps.clearData(package_id)
+```
+
+### Exposed to All Apps
+
+```lua
+-- mosis.app namespace (current app only)
+
+-- Get own package info
+local info = mosis.app.info()
+-- Returns: {package_id, name, version_name, version_code}
+
+-- Check for update
+mosis.app.checkUpdate(function(available, new_version)
+ if available then
+ showUpdatePrompt(new_version)
+ end
+end)
+
+-- Open store page for self
+mosis.app.openStorePage()
+```
+
+---
+
+## Permissions for App Management
+
+```lua
+-- Required permission to use mosis.apps.*
+permissions = {"system.app_management"}
+
+-- Only granted to:
+-- - App Store system app
+-- - Settings system app
+-- - Other OEM system apps
+```
+
+---
+
+## Installation Intents
+
+### From Deep Links
+
+```
+mosis://store/app/com.developer.myapp
+mosis://store/install?url=...&sig=...
+```
+
+### From App Store
+
+```lua
+-- User taps Install button
+installApp("com.developer.myapp")
+```
+
+### From ADB (Development)
+
+```bash
+adb shell am broadcast -a com.omixlab.mosis.INSTALL_APP \
+ --es package_path "/sdcard/myapp.mosis"
+```
+
+---
+
+## Security
+
+### Package Verification
+
+1. Verify ZIP integrity
+2. Verify Ed25519 signature
+3. Verify signer is registered developer
+4. Verify app not in blocklist
+5. Verify permissions are declared
+
+### Installation Sources
+
+| Source | Allowed |
+|--------|---------|
+| Official store | Always |
+| Developer sideload | If enabled in settings |
+| Unknown APK | Never (MosisService only) |
+
+### Sandboxing
+
+- All apps run in LuaSandbox
+- File access limited to app's data directory
+- Network access requires permission
+- Hardware access requires permission + user gesture
+
+---
+
+## Deliverables
+
+- [ ] AppManager C++ class
+- [ ] UpdateService background checker
+- [ ] App Store system app
+- [ ] Lua API bindings (mosis.apps, mosis.app)
+- [ ] Installation progress UI
+- [ ] Uninstall confirmation UI
+- [ ] Storage management UI
+- [ ] Deep link handling
+
+---
+
+## Open Questions
+
+1. App backup to cloud?
+2. Family sharing / multiple devices?
+3. Enterprise MDM integration?
+4. Sideloading policy?
+
+---
+
+## References
+
+- [Android PackageManager](https://developer.android.com/reference/android/content/pm/PackageManager)
+- [iOS App Installation](https://developer.apple.com/documentation/devicemanagement/installing_apps)
diff --git a/DEV_PORTAL_M11_CLI.md b/DEV_PORTAL_M11_CLI.md
new file mode 100644
index 0000000..c061389
--- /dev/null
+++ b/DEV_PORTAL_M11_CLI.md
@@ -0,0 +1,609 @@
+# Milestone 11: Developer CLI Tool
+
+**Status**: Planning
+**Goal**: Command-line tool for app development workflow.
+
+---
+
+## Overview
+
+The CLI tool (`mosis`) streamlines the developer workflow: project creation, building, testing, signing, and publishing.
+
+---
+
+## Commands Overview
+
+```
+mosis
+├── init Create new app project
+├── validate Validate manifest and assets
+├── build Create .mosis package
+├── sign Sign package with developer key
+├── run Run in local designer/emulator
+├── test Run automated tests
+├── login Authenticate with portal
+├── logout Clear authentication
+├── publish Upload and submit for review
+├── status Check review status
+├── keys
+│ ├── generate Generate signing keypair
+│ ├── list List registered keys
+│ └── register Upload public key to portal
+└── config
+ ├── get Get config value
+ └── set Set config value
+```
+
+---
+
+## Command Details
+
+### `mosis init`
+
+Create a new app project with boilerplate.
+
+```bash
+$ mosis init
+
+? App name: My Calculator
+? Package ID: com.myname.calculator
+? Description: A simple calculator app
+? Author name: John Doe
+? Author email: john@example.com
+
+Creating project structure...
+
+✓ Created manifest.json
+✓ Created assets/main.rml
+✓ Created assets/styles/theme.rcss
+✓ Created assets/scripts/app.lua
+✓ Created icons/ (placeholder icons)
+
+Project created! Next steps:
+ cd my-calculator
+ mosis run # Preview in designer
+ mosis build # Create package
+ mosis publish # Submit to store
+```
+
+#### Generated Structure
+
+```
+my-calculator/
+├── manifest.json
+├── assets/
+│ ├── main.rml
+│ ├── styles/
+│ │ └── theme.rcss
+│ └── scripts/
+│ └── app.lua
+├── icons/
+│ ├── icon-32.png
+│ ├── icon-64.png
+│ └── icon-128.png
+└── .mosisignore # Files to exclude from package
+```
+
+---
+
+### `mosis validate`
+
+Validate project without building.
+
+```bash
+$ mosis validate
+
+Validating manifest.json...
+✓ Required fields present
+✓ Package ID format valid
+✓ Version format valid
+
+Validating assets...
+✓ Entry point exists: assets/main.rml
+✓ All RML files valid (3 files)
+✓ All RCSS files valid (2 files)
+✓ All Lua files valid (4 files)
+
+Validating icons...
+✓ icon-32.png (32x32)
+✓ icon-64.png (64x64)
+✓ icon-128.png (128x128)
+
+Checking permissions...
+✓ Permissions declared: storage, network
+
+All validations passed!
+```
+
+---
+
+### `mosis build`
+
+Create a .mosis package.
+
+```bash
+$ mosis build
+
+Reading manifest.json...
+Package: com.myname.calculator v1.0.0 (1)
+
+Collecting files...
+✓ manifest.json
+✓ assets/main.rml
+✓ assets/styles/theme.rcss
+✓ assets/scripts/app.lua
+✓ icons/icon-32.png
+✓ icons/icon-64.png
+✓ icons/icon-128.png
+
+Creating package...
+✓ Package created: dist/com.myname.calculator-1.0.0.mosis (45.2 KB)
+
+⚠ Package is unsigned. Run 'mosis sign' before publishing.
+```
+
+#### Options
+
+```bash
+mosis build [options]
+
+Options:
+ -o, --output Output path (default: dist/)
+ --no-compress Skip compression
+ --include-source Include .lua source maps
+```
+
+---
+
+### `mosis sign`
+
+Sign a package with developer key.
+
+```bash
+$ mosis sign dist/com.myname.calculator-1.0.0.mosis
+
+Using key: ~/.mosis/signing_key.pem
+Fingerprint: SHA256:abc123...
+
+Generating file hashes...
+Signing MANIFEST.MF...
+
+✓ Package signed: dist/com.myname.calculator-1.0.0.mosis
+
+Signature details:
+ Algorithm: Ed25519
+ Key fingerprint: SHA256:abc123...
+ Files signed: 7
+```
+
+#### Options
+
+```bash
+mosis sign [options]
+
+Options:
+ -k, --key Path to private key (default: ~/.mosis/signing_key.pem)
+ --verify Verify existing signature
+```
+
+---
+
+### `mosis run`
+
+Launch in desktop designer for testing.
+
+```bash
+$ mosis run
+
+Starting Mosis Designer...
+Loading: assets/main.rml
+
+Designer running at http://localhost:8080
+Press Ctrl+C to stop
+
+[Hot reload enabled - changes auto-refresh]
+```
+
+#### Options
+
+```bash
+mosis run [options]
+
+Options:
+ --entry Override entry point
+ --port Designer port (default: 8080)
+ --no-hot-reload Disable hot reload
+ --device Emulate device (phone, tablet)
+```
+
+---
+
+### `mosis login`
+
+Authenticate with developer portal.
+
+```bash
+$ mosis login
+
+Opening browser for authentication...
+Waiting for authorization...
+
+✓ Logged in as john@example.com
+ API key stored in ~/.mosis/credentials
+```
+
+#### Options
+
+```bash
+mosis login [options]
+
+Options:
+ --api-key Use API key instead of browser auth
+ --portal Portal URL (default: https://portal.mosis.dev)
+```
+
+---
+
+### `mosis publish`
+
+Upload and submit for review.
+
+```bash
+$ mosis publish
+
+Checking authentication...
+✓ Logged in as john@example.com
+
+Building package...
+✓ Package created: dist/com.myname.calculator-1.0.0.mosis
+
+Signing package...
+✓ Package signed
+
+Uploading...
+████████████████████████████████ 100%
+
+Submitting for review...
+✓ Version 1.0.0 submitted
+
+Review status: In Review
+Estimated review time: 24 hours
+
+Track status: mosis status
+```
+
+#### Options
+
+```bash
+mosis publish [options]
+
+Options:
+ --package Use existing package
+ --notes Release notes
+ --notes-file Release notes from file
+ --draft Upload without submitting for review
+```
+
+---
+
+### `mosis status`
+
+Check app/version status.
+
+```bash
+$ mosis status
+
+App: My Calculator (com.myname.calculator)
+
+Versions:
+ v1.0.0 (1) Published Jan 10, 2024 1,234 downloads
+ v1.1.0 (2) In Review Jan 15, 2024 Submitted 2h ago
+
+Latest review:
+ Status: In Review
+ Submitted: Jan 15, 2024 10:30 AM
+ Estimated completion: Jan 16, 2024
+
+Run 'mosis status --watch' to monitor in real-time.
+```
+
+---
+
+### `mosis keys generate`
+
+Generate Ed25519 signing keypair.
+
+```bash
+$ mosis keys generate
+
+Generating Ed25519 keypair...
+
+Private key saved to: ~/.mosis/signing_key.pem
+Public key saved to: ~/.mosis/signing_key.pub
+
+Fingerprint: SHA256:abc123def456...
+
+⚠ IMPORTANT: Keep your private key secure!
+ - Never share or commit signing_key.pem
+ - Back it up securely
+ - If compromised, revoke immediately
+
+Next step: Register your public key
+ mosis keys register
+```
+
+---
+
+### `mosis keys register`
+
+Upload public key to portal.
+
+```bash
+$ mosis keys register
+
+Reading public key from ~/.mosis/signing_key.pub
+Fingerprint: SHA256:abc123def456...
+
+? Key name: MacBook Pro 2024
+
+Uploading to portal...
+✓ Key registered successfully
+
+Your signing key is now active. Packages signed with this
+key will be accepted for review.
+```
+
+---
+
+## Configuration
+
+### Config File Location
+
+```
+~/.mosis/
+├── config.json # CLI configuration
+├── credentials # Auth tokens (encrypted)
+├── signing_key.pem # Private key
+└── signing_key.pub # Public key
+```
+
+### Config Options
+
+```json
+{
+ "portal_url": "https://portal.mosis.dev",
+ "api_url": "https://api.mosis.dev",
+ "designer_path": "/usr/local/bin/mosis-designer",
+ "default_author": {
+ "name": "John Doe",
+ "email": "john@example.com"
+ }
+}
+```
+
+---
+
+## Implementation
+
+### Tech Stack Options
+
+#### Option A: Go
+
+```go
+// Using cobra for CLI framework
+package main
+
+import (
+ "github.com/spf13/cobra"
+)
+
+func main() {
+ rootCmd := &cobra.Command{
+ Use: "mosis",
+ Short: "Mosis app development CLI",
+ }
+
+ rootCmd.AddCommand(initCmd())
+ rootCmd.AddCommand(buildCmd())
+ rootCmd.AddCommand(signCmd())
+ rootCmd.AddCommand(publishCmd())
+ // ...
+
+ rootCmd.Execute()
+}
+```
+
+**Pros**: Single binary, fast, cross-platform
+**Cons**: More code to write
+
+#### Option B: Node.js (oclif)
+
+```typescript
+// Using oclif framework
+import { Command } from '@oclif/core'
+
+export default class Build extends Command {
+ static description = 'Build .mosis package'
+
+ async run() {
+ const manifest = await this.readManifest()
+ const files = await this.collectFiles()
+ const package = await this.createPackage(files)
+ this.log(`✓ Package created: ${package.path}`)
+ }
+}
+```
+
+**Pros**: Fast development, npm distribution
+**Cons**: Requires Node.js runtime
+
+#### Option C: Rust (clap)
+
+```rust
+use clap::{Parser, Subcommand};
+
+#[derive(Parser)]
+#[command(name = "mosis")]
+struct Cli {
+ #[command(subcommand)]
+ command: Commands,
+}
+
+#[derive(Subcommand)]
+enum Commands {
+ Init { name: Option },
+ Build { output: Option },
+ Sign { package: PathBuf },
+ Publish,
+}
+
+fn main() {
+ let cli = Cli::parse();
+ match cli.command {
+ Commands::Init { name } => init::run(name),
+ Commands::Build { output } => build::run(output),
+ // ...
+ }
+}
+```
+
+**Pros**: Single binary, very fast
+**Cons**: Slower development
+
+---
+
+## Distribution
+
+### npm (Node.js version)
+
+```bash
+npm install -g @mosis/cli
+```
+
+### Homebrew (macOS)
+
+```bash
+brew tap mosis/tap
+brew install mosis
+```
+
+### Direct Download
+
+```bash
+# Linux/macOS
+curl -fsSL https://mosis.dev/install.sh | sh
+
+# Windows
+irm https://mosis.dev/install.ps1 | iex
+```
+
+### Package Managers
+
+| Platform | Package Manager | Command |
+|----------|-----------------|---------|
+| macOS | Homebrew | `brew install mosis` |
+| Windows | Scoop | `scoop install mosis` |
+| Linux | apt (deb) | `apt install mosis` |
+| Any | npm | `npm install -g @mosis/cli` |
+
+---
+
+## Error Handling
+
+### User-Friendly Errors
+
+```bash
+$ mosis build
+
+Error: manifest.json not found
+
+Are you in a Mosis project directory?
+Run 'mosis init' to create a new project.
+```
+
+### Verbose Mode
+
+```bash
+$ mosis build --verbose
+
+[DEBUG] Reading manifest from ./manifest.json
+[DEBUG] Manifest parsed: {id: "com.example.app", ...}
+[DEBUG] Collecting files from ./assets
+[DEBUG] Found 7 files
+[DEBUG] Creating ZIP archive
+[DEBUG] Writing to dist/com.example.app-1.0.0.mosis
+[DEBUG] Package size: 45234 bytes
+✓ Package created
+```
+
+---
+
+## CI/CD Integration
+
+### GitHub Actions
+
+```yaml
+name: Build and Publish
+
+on:
+ push:
+ tags:
+ - 'v*'
+
+jobs:
+ publish:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Setup Mosis CLI
+ run: npm install -g @mosis/cli
+
+ - name: Build and Sign
+ env:
+ MOSIS_SIGNING_KEY: ${{ secrets.MOSIS_SIGNING_KEY }}
+ run: |
+ echo "$MOSIS_SIGNING_KEY" > signing_key.pem
+ mosis build
+ mosis sign dist/*.mosis --key signing_key.pem
+
+ - name: Publish
+ env:
+ MOSIS_API_KEY: ${{ secrets.MOSIS_API_KEY }}
+ run: mosis publish --api-key "$MOSIS_API_KEY"
+```
+
+---
+
+## Deliverables
+
+- [ ] CLI framework selection
+- [ ] `init` command
+- [ ] `validate` command
+- [ ] `build` command
+- [ ] `sign` command
+- [ ] `run` command (designer integration)
+- [ ] `login/logout` commands
+- [ ] `publish` command
+- [ ] `status` command
+- [ ] `keys` subcommands
+- [ ] Configuration management
+- [ ] Distribution packages
+- [ ] CI/CD examples
+
+---
+
+## Open Questions
+
+1. Should CLI auto-update itself?
+2. Offline mode for build/sign?
+3. Plugin system for custom commands?
+4. IDE integrations (VS Code extension)?
+
+---
+
+## References
+
+- [Cobra CLI Framework](https://cobra.dev/)
+- [oclif Framework](https://oclif.io/)
+- [Clap for Rust](https://docs.rs/clap/)
diff --git a/DEV_PORTAL_M12_DOCS.md b/DEV_PORTAL_M12_DOCS.md
new file mode 100644
index 0000000..3812ac7
--- /dev/null
+++ b/DEV_PORTAL_M12_DOCS.md
@@ -0,0 +1,617 @@
+# Milestone 12: Documentation Site
+
+**Status**: Planning
+**Goal**: Comprehensive documentation for Mosis app developers.
+
+---
+
+## Overview
+
+The documentation site is the primary resource for developers learning to build Mosis apps. Must be clear, searchable, and up-to-date.
+
+---
+
+## Information Architecture
+
+```
+docs.mosis.dev/
+├── Getting Started
+│ ├── Introduction
+│ ├── Quick Start (5 min)
+│ ├── Your First App
+│ └── Project Structure
+├── Guides
+│ ├── UI Design
+│ │ ├── RML Basics
+│ │ ├── Styling with RCSS
+│ │ ├── Layouts
+│ │ └── Components
+│ ├── Lua Scripting
+│ │ ├── Basics
+│ │ ├── Event Handling
+│ │ ├── State Management
+│ │ └── Async Operations
+│ ├── Data & Storage
+│ │ ├── Local Storage
+│ │ ├── SQLite Database
+│ │ └── Files
+│ ├── Networking
+│ │ ├── HTTP Requests
+│ │ └── WebSockets
+│ ├── Hardware
+│ │ ├── Camera
+│ │ ├── Microphone
+│ │ ├── Location
+│ │ └── Sensors
+│ ├── Permissions
+│ │ ├── Permission Model
+│ │ ├── Requesting Permissions
+│ │ └── User Gestures
+│ └── Publishing
+│ ├── Preparing for Release
+│ ├── App Signing
+│ └── Store Guidelines
+├── API Reference
+│ ├── Lua APIs
+│ │ ├── mosis.storage
+│ │ ├── mosis.db
+│ │ ├── mosis.http
+│ │ ├── mosis.ws
+│ │ ├── mosis.camera
+│ │ ├── mosis.microphone
+│ │ ├── mosis.location
+│ │ ├── mosis.sensors
+│ │ └── ...
+│ ├── RML Elements
+│ ├── RCSS Properties
+│ └── Manifest Schema
+├── CLI Reference
+│ ├── mosis init
+│ ├── mosis build
+│ ├── mosis sign
+│ ├── mosis publish
+│ └── ...
+├── Best Practices
+│ ├── Performance
+│ ├── Security
+│ ├── UX Guidelines
+│ └── Accessibility
+├── Troubleshooting
+│ ├── Common Errors
+│ ├── Debugging Tips
+│ └── FAQ
+└── Changelog
+```
+
+---
+
+## Content Types
+
+### Tutorials (Step-by-step)
+
+```markdown
+# Build a Weather App
+
+In this tutorial, you'll build a weather app that:
+- Fetches weather data from an API
+- Displays current conditions
+- Shows a 5-day forecast
+- Requests location permission
+
+**Time:** 30 minutes
+**Prerequisites:** Completed Quick Start
+
+## Step 1: Create the Project
+
+```bash
+mosis init weather-app
+cd weather-app
+```
+
+## Step 2: Design the UI
+
+Open `assets/main.rml` and add...
+```
+
+### Guides (Conceptual)
+
+```markdown
+# Understanding Permissions
+
+Mosis uses a permission system to protect user privacy.
+Apps must declare permissions in their manifest and
+request them at runtime.
+
+## Permission Categories
+
+| Category | Description | Examples |
+|----------|-------------|----------|
+| Normal | Low risk, auto-granted | storage, network |
+| Dangerous | User data, requires prompt | camera, location |
+| Signature | System only | app_management |
+
+## When Permissions Are Checked
+
+Permissions are checked when your app calls...
+```
+
+### API Reference (Technical)
+
+```markdown
+# mosis.http
+
+HTTP client for making network requests.
+
+## Functions
+
+### `mosis.http.get(url, options)`
+
+Make a GET request.
+
+**Parameters:**
+- `url` (string): The URL to fetch
+- `options` (table, optional):
+ - `headers` (table): Request headers
+ - `timeout` (number): Timeout in ms (default: 30000)
+
+**Returns:** Promise that resolves to Response
+
+**Example:**
+```lua
+local response = mosis.http.get("https://api.example.com/data")
+if response.ok then
+ local data = json.decode(response.body)
+ print(data.name)
+end
+```
+
+**Errors:**
+- `NETWORK_ERROR`: Network unavailable
+- `TIMEOUT`: Request timed out
+- `INVALID_URL`: Malformed URL
+```
+
+---
+
+## Tech Stack Options
+
+### Option A: Docusaurus
+
+```
+Framework: Docusaurus 3
+Language: MDX (Markdown + React)
+Search: Algolia DocSearch
+Deploy: Vercel/Cloudflare Pages
+```
+
+| Pros | Cons |
+|------|------|
+| Versioning built-in | React knowledge needed |
+| Great search | Can be heavy |
+| Plugin ecosystem | |
+| Used by many OSS | |
+
+### Option B: VitePress
+
+```
+Framework: VitePress
+Language: Markdown + Vue
+Search: Built-in local search
+Deploy: Any static host
+```
+
+| Pros | Cons |
+|------|------|
+| Very fast | Fewer features |
+| Vue-powered | Less ecosystem |
+| Simple setup | |
+
+### Option C: Astro Starlight
+
+```
+Framework: Astro + Starlight
+Language: MDX
+Search: Pagefind (local)
+Deploy: Any static host
+```
+
+| Pros | Cons |
+|------|------|
+| Very fast | Newer |
+| Great defaults | Less customizable |
+| Built-in i18n | |
+
+### Option D: MkDocs Material
+
+```
+Framework: MkDocs
+Language: Markdown
+Search: Built-in
+Deploy: Any static host
+```
+
+| Pros | Cons |
+|------|------|
+| Simple | Less interactive |
+| Great theme | Python-based |
+| Fast builds | |
+
+---
+
+## Features Required
+
+### Must Have
+
+- [ ] Full-text search
+- [ ] Syntax highlighting
+- [ ] Mobile responsive
+- [ ] Dark mode
+- [ ] Version selector
+- [ ] Edit on GitHub links
+- [ ] Copy code buttons
+- [ ] Table of contents
+- [ ] Previous/Next navigation
+
+### Nice to Have
+
+- [ ] API playground
+- [ ] Interactive examples
+- [ ] Video tutorials
+- [ ] Community translations
+- [ ] Feedback widget
+
+---
+
+## Code Examples
+
+### Runnable Examples
+
+```html
+
+
+local greeting = "Hello, Mosis!"
+print(greeting)
+
+```
+
+### Multi-file Examples
+
+```markdown
+:::code-group
+
+```rml [main.rml]
+
+ 0
+
+
+```
+
+```lua [app.lua]
+local count = 0
+
+function increment()
+ count = count + 1
+ document:GetElementById("count").inner_rml = tostring(count)
+end
+```
+
+```rcss [styles.rcss]
+#counter {
+ display: flex;
+ gap: 10px;
+}
+```
+
+:::
+```
+
+---
+
+## API Documentation Generation
+
+### From Lua Annotations
+
+```lua
+--- Make an HTTP GET request.
+--- @param url string The URL to fetch
+--- @param options? HttpOptions Request options
+--- @return HttpResponse response The response object
+--- @example
+--- local resp = mosis.http.get("https://api.example.com/data")
+--- print(resp.body)
+function mosis.http.get(url, options)
+ -- implementation
+end
+```
+
+### Generated Output
+
+```markdown
+## `mosis.http.get(url, options?)`
+
+Make an HTTP GET request.
+
+### Parameters
+
+| Name | Type | Description |
+|------|------|-------------|
+| url | string | The URL to fetch |
+| options | HttpOptions? | Request options |
+
+### Returns
+
+`HttpResponse` - The response object
+
+### Example
+
+```lua
+local resp = mosis.http.get("https://api.example.com/data")
+print(resp.body)
+```
+```
+
+---
+
+## Versioning
+
+### URL Structure
+
+```
+docs.mosis.dev/ # Latest stable
+docs.mosis.dev/v1.0/ # Version 1.0
+docs.mosis.dev/v1.1/ # Version 1.1
+docs.mosis.dev/next/ # Development (unreleased)
+```
+
+### Version Dropdown
+
+```
+┌────────────────────┐
+│ Version: 1.1 (latest) ▼ │
+├────────────────────┤
+│ 1.1 (latest) │
+│ 1.0 │
+│ next (unreleased) │
+└────────────────────┘
+```
+
+---
+
+## Search
+
+### Algolia DocSearch (Free for OSS)
+
+```javascript
+// docusaurus.config.js
+themeConfig: {
+ algolia: {
+ appId: 'YOUR_APP_ID',
+ apiKey: 'YOUR_SEARCH_KEY',
+ indexName: 'mosis',
+ },
+}
+```
+
+### Local Search (Pagefind)
+
+```javascript
+// For Astro/VitePress
+// Indexes at build time, searches client-side
+// No external service needed
+```
+
+---
+
+## Internationalization
+
+### Directory Structure
+
+```
+docs/
+├── en/
+│ ├── getting-started/
+│ └── guides/
+├── es/
+│ ├── getting-started/
+│ └── guides/
+└── zh/
+ ├── getting-started/
+ └── guides/
+```
+
+### Language Switcher
+
+```
+┌──────────┐
+│ 🌐 EN ▼ │
+├──────────┤
+│ English │
+│ Español │
+│ 中文 │
+└──────────┘
+```
+
+---
+
+## Content Guidelines
+
+### Writing Style
+
+1. **Be concise** - Get to the point quickly
+2. **Use active voice** - "The function returns..." not "A value is returned..."
+3. **Show, don't tell** - Code examples over explanations
+4. **Assume beginner** - Don't assume prior knowledge
+5. **Test all examples** - Every code block must work
+
+### Code Style
+
+```lua
+-- Good: Clear, commented
+local response = mosis.http.get(API_URL)
+if response.ok then
+ local data = json.decode(response.body)
+ updateUI(data)
+else
+ showError("Failed to load data")
+end
+
+-- Bad: Unclear, no error handling
+local d = json.decode(mosis.http.get(u).body)
+```
+
+### Screenshots
+
+- Use consistent device frame
+- Show relevant UI only
+- Add callouts for important areas
+- Keep file sizes small (WebP)
+
+---
+
+## Deployment
+
+### CI/CD Pipeline
+
+```yaml
+# .github/workflows/docs.yml
+name: Deploy Docs
+
+on:
+ push:
+ branches: [main]
+ paths:
+ - 'docs/**'
+
+jobs:
+ deploy:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Setup Node
+ uses: actions/setup-node@v4
+ with:
+ node-version: 20
+
+ - name: Install and Build
+ run: |
+ cd docs
+ npm install
+ npm run build
+
+ - name: Deploy to Cloudflare Pages
+ uses: cloudflare/pages-action@v1
+ with:
+ apiToken: ${{ secrets.CF_API_TOKEN }}
+ accountId: ${{ secrets.CF_ACCOUNT_ID }}
+ projectName: mosis-docs
+ directory: docs/build
+```
+
+---
+
+## Analytics
+
+### Track (Privacy-Friendly)
+
+- Page views (which docs are popular)
+- Search queries (what are people looking for)
+- 404 pages (missing content)
+- Time on page (engagement)
+
+### Tools
+
+- Plausible (privacy-focused)
+- Simple Analytics
+- Cloudflare Analytics (free)
+
+---
+
+## Feedback
+
+### Per-Page Feedback
+
+```
+┌────────────────────────────────────┐
+│ Was this page helpful? │
+│ │
+│ [👍 Yes] [👎 No] │
+│ │
+│ [Edit this page on GitHub] │
+└────────────────────────────────────┘
+```
+
+### Feedback Collection
+
+```javascript
+// Send to analytics or issue tracker
+function submitFeedback(page, helpful, comment) {
+ fetch('/api/feedback', {
+ method: 'POST',
+ body: JSON.stringify({ page, helpful, comment })
+ })
+}
+```
+
+---
+
+## Deliverables
+
+- [ ] Framework selection
+- [ ] Information architecture
+- [ ] Getting Started content
+- [ ] UI design guides
+- [ ] Lua scripting guides
+- [ ] API reference (all namespaces)
+- [ ] CLI reference
+- [ ] Best practices
+- [ ] Search integration
+- [ ] Version selector
+- [ ] Deploy pipeline
+- [ ] Feedback system
+
+---
+
+## Content Prioritization
+
+### Phase 1 (Launch)
+
+1. Quick Start
+2. Your First App
+3. Project Structure
+4. RML Basics
+5. Lua Basics
+6. API Reference (core APIs)
+7. CLI Reference
+
+### Phase 2
+
+1. Complete API Reference
+2. All hardware guides
+3. Best practices
+4. Troubleshooting
+
+### Phase 3
+
+1. Advanced guides
+2. Video tutorials
+3. Translations
+4. Community contributions
+
+---
+
+## Open Questions
+
+1. Host docs separately or under main domain?
+2. Community wiki/contributions?
+3. Video tutorial platform (YouTube, embedded)?
+4. Glossary/terminology page?
+
+---
+
+## References
+
+- [Docusaurus](https://docusaurus.io/)
+- [VitePress](https://vitepress.dev/)
+- [Astro Starlight](https://starlight.astro.build/)
+- [Divio Documentation System](https://documentation.divio.com/)
diff --git a/DEV_PORTAL_MILESTONES.md b/DEV_PORTAL_MILESTONES.md
new file mode 100644
index 0000000..639de56
--- /dev/null
+++ b/DEV_PORTAL_MILESTONES.md
@@ -0,0 +1,714 @@
+# Developer Portal & App Ecosystem Milestones
+
+Planning document for the Mosis app development, distribution, and monitoring ecosystem.
+
+---
+
+## Overview
+
+```
+Developer Mosis Platform User Device
+─────────────────────────────────────────────────────────────────────────────────
+Register Account ──────────────► Developer Portal
+ │
+Create App Project ────────────► App Registry
+ │
+Design UI (RML/RCSS/Lua) ──────► Designer Tool (local)
+ │
+Test Locally ──────────────────► Desktop Designer / Emulator
+ │
+Submit for Review ─────────────► App Store Backend
+ │
+Publish ───────────────────────► CDN / Distribution
+ │
+ Install ◄─────────────────── User Request
+ │
+ Run in Sandbox ◄──────────── Launch App
+ │
+ Telemetry/Crashes ──────────► Analytics Backend
+```
+
+---
+
+## Decision Areas
+
+| Area | Options | Status |
+|------|---------|--------|
+| Web Stack | Node/Express, Go, Rust/Axum, .NET | TBD |
+| Database | PostgreSQL, SQLite, MongoDB | TBD |
+| Auth | OAuth2/OIDC, API keys, JWT | TBD |
+| CDN/Storage | S3, Cloudflare R2, self-hosted | TBD |
+| Telemetry | Custom, PostHog, Plausible | TBD |
+| Crash Reports | Sentry, custom, Crashlytics | TBD |
+| App Format | ZIP, custom package, signed | TBD |
+
+---
+
+## Milestone 1: App Package Format
+
+**Goal**: Define how apps are bundled, signed, and validated.
+
+### Questions to Answer
+
+1. What files comprise an app package?
+2. How is the manifest structured?
+3. How are apps signed for integrity?
+4. How are updates handled (full vs delta)?
+5. What metadata is required (name, version, permissions, icons)?
+
+### Considerations
+
+| Approach | Pros | Cons |
+|----------|------|------|
+| **ZIP archive** | Simple, standard tooling | No built-in signing |
+| **Custom format (.mosis)** | Can embed signature, metadata | Custom tooling needed |
+| **Signed ZIP** | Best of both, detached signature | Slightly more complex |
+
+### Proposed Structure
+
+```
+myapp.mosis/
+├── manifest.json # App metadata, permissions, entry point
+├── signature.sig # Detached signature (optional for dev)
+├── icon.png # App icon (multiple sizes?)
+├── assets/
+│ ├── main.rml # Entry point UI
+│ ├── styles.rcss # Stylesheets
+│ └── scripts/
+│ └── app.lua # Lua code
+└── locales/ # i18n (optional)
+ ├── en.json
+ └── es.json
+```
+
+### Manifest Schema (Draft)
+
+```json
+{
+ "id": "com.developer.appname",
+ "name": "My App",
+ "version": "1.0.0",
+ "version_code": 1,
+ "entry": "assets/main.rml",
+ "permissions": ["storage", "network"],
+ "min_mosis_version": "1.0.0",
+ "author": {
+ "name": "Developer Name",
+ "email": "dev@example.com"
+ },
+ "icons": {
+ "32": "icon-32.png",
+ "64": "icon-64.png",
+ "128": "icon-128.png"
+ }
+}
+```
+
+### Deliverables
+
+- [ ] Manifest JSON schema specification
+- [ ] Package format specification
+- [ ] Signing mechanism (key format, algorithm)
+- [ ] Package validation tool (CLI)
+- [ ] Package creation tool (CLI or integrated in designer)
+
+---
+
+## Milestone 2: Web Stack Selection
+
+**Goal**: Choose backend technologies for the developer portal and app store.
+
+### Options Analysis
+
+#### Option A: Node.js + Express/Fastify
+
+| Aspect | Details |
+|--------|---------|
+| Language | TypeScript |
+| Framework | Express, Fastify, or Hono |
+| Pros | Large ecosystem, easy hiring, fast development |
+| Cons | Single-threaded, callback complexity |
+| Hosting | Vercel, Railway, any VPS |
+
+#### Option B: Go
+
+| Aspect | Details |
+|--------|---------|
+| Language | Go |
+| Framework | Gin, Echo, or Chi |
+| Pros | Fast, low memory, single binary deployment |
+| Cons | Smaller ecosystem, verbose error handling |
+| Hosting | Any VPS, Cloud Run |
+
+#### Option C: Rust + Axum
+
+| Aspect | Details |
+|--------|---------|
+| Language | Rust |
+| Framework | Axum, Actix-web |
+| Pros | Maximum performance, memory safety |
+| Cons | Steep learning curve, slower development |
+| Hosting | Any VPS, Fly.io |
+
+#### Option D: .NET
+
+| Aspect | Details |
+|--------|---------|
+| Language | C# |
+| Framework | ASP.NET Core |
+| Pros | Enterprise-ready, great tooling, fast |
+| Cons | Heavier runtime, Microsoft ecosystem |
+| Hosting | Azure, any VPS |
+
+### Evaluation Criteria
+
+1. **Development speed** - How fast can we iterate?
+2. **Performance** - Can it handle scale?
+3. **Hosting cost** - Monthly infrastructure cost
+4. **Team familiarity** - Learning curve
+5. **Ecosystem** - Libraries for auth, storage, etc.
+
+### Deliverables
+
+- [ ] Prototype API in top 2 candidates
+- [ ] Benchmark comparison
+- [ ] Final selection with rationale
+
+---
+
+## Milestone 3: Database Selection
+
+**Goal**: Choose database for developer accounts, app metadata, analytics.
+
+### Options Analysis
+
+#### Option A: PostgreSQL
+
+| Aspect | Details |
+|--------|---------|
+| Type | Relational |
+| Pros | ACID, JSON support, mature, scalable |
+| Cons | Requires management, connection pooling |
+| Hosting | Supabase, Neon, RDS, self-hosted |
+
+#### Option B: SQLite + Litestream
+
+| Aspect | Details |
+|--------|---------|
+| Type | Embedded relational |
+| Pros | Zero ops, fast reads, simple backup |
+| Cons | Single-writer, limited concurrency |
+| Hosting | Embedded in app server |
+
+#### Option C: MongoDB
+
+| Aspect | Details |
+|--------|---------|
+| Type | Document |
+| Pros | Flexible schema, easy start |
+| Cons | Less ACID, can get expensive |
+| Hosting | Atlas, self-hosted |
+
+### Data Models Preview
+
+```
+developers
+├── id (UUID)
+├── email
+├── name
+├── api_keys[]
+├── created_at
+└── verified
+
+apps
+├── id (UUID)
+├── developer_id (FK)
+├── package_id (com.dev.app)
+├── name
+├── description
+├── versions[]
+├── status (draft/review/published/suspended)
+├── created_at
+└── updated_at
+
+app_versions
+├── id (UUID)
+├── app_id (FK)
+├── version_code
+├── version_name
+├── package_url
+├── signature
+├── release_notes
+├── status
+└── published_at
+
+telemetry_events
+├── id
+├── app_id
+├── device_id (anonymized)
+├── event_type
+├── event_data (JSON)
+├── timestamp
+└── mosis_version
+```
+
+### Deliverables
+
+- [ ] Schema design for all tables
+- [ ] Migration strategy
+- [ ] Backup/restore plan
+- [ ] Final selection with rationale
+
+---
+
+## Milestone 4: Authentication System
+
+**Goal**: Secure developer authentication and app signing.
+
+### Developer Authentication
+
+| Method | Use Case |
+|--------|----------|
+| OAuth2 (GitHub/Google) | Portal login |
+| Email + Password | Alternative login |
+| API Keys | CLI tools, CI/CD |
+| JWT | Session tokens |
+
+### App Signing
+
+| Approach | Details |
+|----------|---------|
+| **Developer keypair** | Dev signs with private key, we verify with public |
+| **Platform signing** | We sign after review (like iOS) |
+| **Both** | Dev signs, we countersign after review |
+
+### Key Management
+
+- Developer generates keypair locally
+- Public key uploaded to portal
+- Private key never leaves developer machine
+- Key rotation supported
+
+### Deliverables
+
+- [ ] OAuth2 integration (GitHub, Google)
+- [ ] API key generation and management
+- [ ] Developer keypair registration
+- [ ] App signature verification
+- [ ] JWT token handling
+
+---
+
+## Milestone 5: Developer Portal Frontend
+
+**Goal**: Web interface for developer account and app management.
+
+### Pages Required
+
+| Page | Features |
+|------|----------|
+| **Landing** | Sign up, sign in, overview |
+| **Dashboard** | App list, quick stats |
+| **App Details** | Versions, analytics, settings |
+| **Create App** | Wizard for new app |
+| **Submit Version** | Upload, release notes, submit |
+| **API Keys** | Generate, revoke keys |
+| **Profile** | Account settings, keys |
+| **Docs** | SDK docs, API reference |
+
+### Tech Options
+
+| Option | Pros | Cons |
+|--------|------|------|
+| Next.js | SSR, React, full-stack | Complexity |
+| SvelteKit | Fast, simple, SSR | Smaller ecosystem |
+| Astro + React | Static + islands | Newer |
+| Plain HTML + htmx | Simple, fast | Limited interactivity |
+
+### Deliverables
+
+- [ ] Framework selection
+- [ ] UI component library selection
+- [ ] Page wireframes
+- [ ] Implementation
+
+---
+
+## Milestone 6: App Store Backend API
+
+**Goal**: REST/GraphQL API for app submission, review, and distribution.
+
+### API Endpoints (Draft)
+
+```
+Auth
+├── POST /auth/register
+├── POST /auth/login
+├── POST /auth/logout
+├── GET /auth/me
+
+Apps
+├── GET /apps # List developer's apps
+├── POST /apps # Create new app
+├── GET /apps/:id # Get app details
+├── PATCH /apps/:id # Update app metadata
+├── DELETE /apps/:id # Delete app (if no published versions)
+
+Versions
+├── GET /apps/:id/versions # List versions
+├── POST /apps/:id/versions # Upload new version
+├── GET /apps/:id/versions/:vid # Get version details
+├── POST /apps/:id/versions/:vid/submit # Submit for review
+├── POST /apps/:id/versions/:vid/publish # Publish (after review)
+
+Public (App Store)
+├── GET /store/apps # Browse/search apps
+├── GET /store/apps/:id # App store listing
+├── GET /store/apps/:id/download # Download latest version
+
+API Keys
+├── GET /keys # List API keys
+├── POST /keys # Generate new key
+├── DELETE /keys/:id # Revoke key
+
+Telemetry (device → server)
+├── POST /telemetry/events # Batch event upload
+├── POST /telemetry/crash # Crash report
+```
+
+### Deliverables
+
+- [ ] OpenAPI specification
+- [ ] Rate limiting strategy
+- [ ] Authentication middleware
+- [ ] Implementation
+
+---
+
+## Milestone 7: CDN & Storage
+
+**Goal**: Scalable storage for app packages and assets.
+
+### Requirements
+
+1. Store app packages (.mosis files)
+2. Serve downloads globally with low latency
+3. Handle icons and screenshots
+4. Version retention policy
+5. Bandwidth cost management
+
+### Options
+
+| Option | Pros | Cons |
+|--------|------|------|
+| **Cloudflare R2** | No egress fees, global | Newer service |
+| **AWS S3 + CloudFront** | Mature, reliable | Egress costs |
+| **Backblaze B2 + Cloudflare** | Cheap storage, free egress via CF | More setup |
+| **Self-hosted MinIO** | Full control | Ops burden |
+
+### Storage Structure
+
+```
+/packages/
+ /{app_id}/
+ /{version_code}/
+ package.mosis
+ signature.sig
+
+/assets/
+ /{app_id}/
+ icon-32.png
+ icon-64.png
+ icon-128.png
+ screenshots/
+ 1.png
+ 2.png
+```
+
+### Deliverables
+
+- [ ] Storage provider selection
+- [ ] CDN configuration
+- [ ] Upload flow (presigned URLs vs direct)
+- [ ] Download URL generation
+- [ ] Retention/cleanup policy
+
+---
+
+## Milestone 8: Telemetry System
+
+**Goal**: Collect app usage analytics and crash reports.
+
+### Event Types
+
+| Category | Events |
+|----------|--------|
+| **Lifecycle** | app_start, app_stop, app_crash |
+| **Performance** | frame_time, memory_usage, lua_errors |
+| **Usage** | screen_view, button_click (opt-in) |
+| **System** | mosis_version, device_info |
+
+### Privacy Considerations
+
+1. **No PII by default** - Device ID is hashed, no user data
+2. **Opt-in for detailed analytics** - User consent required
+3. **Data retention** - Auto-delete after X days
+4. **GDPR compliance** - Export/delete on request
+5. **Aggregation** - Store aggregates, drop raw after processing
+
+### Options
+
+| Option | Pros | Cons |
+|--------|------|------|
+| **Custom** | Full control, no vendor lock | Build everything |
+| **PostHog** | Self-hostable, feature-rich | Can be heavy |
+| **Plausible** | Privacy-focused, simple | Limited features |
+| **Segment + warehouse** | Flexible routing | Complex, costly |
+
+### Crash Report Schema
+
+```json
+{
+ "app_id": "com.dev.app",
+ "app_version": "1.0.0",
+ "mosis_version": "1.0.0",
+ "device_id": "hashed",
+ "timestamp": "2024-01-15T10:30:00Z",
+ "crash_type": "lua_error",
+ "message": "attempt to index nil value",
+ "stack_trace": "...",
+ "context": {
+ "screen": "main.rml",
+ "memory_mb": 45,
+ "uptime_seconds": 120
+ }
+}
+```
+
+### Deliverables
+
+- [ ] Event schema specification
+- [ ] Collection endpoint
+- [ ] Storage strategy (time-series DB?)
+- [ ] Dashboard for developers
+- [ ] Privacy controls
+
+---
+
+## Milestone 9: App Review System
+
+**Goal**: Automated and manual review process for app submissions.
+
+### Automated Checks
+
+| Check | Description |
+|-------|-------------|
+| **Manifest validation** | Required fields, valid permissions |
+| **Package integrity** | Signature verification |
+| **Static analysis** | Dangerous Lua patterns |
+| **Asset validation** | Icons present, correct sizes |
+| **Size limits** | Package under max size |
+| **Duplicate detection** | Same app ID collision |
+
+### Manual Review (Optional)
+
+- Flag for manual review based on:
+ - New developer (first app)
+ - Dangerous permissions requested
+ - Automated check warnings
+ - User reports
+
+### Review States
+
+```
+draft → submitted → in_review → approved → published
+ ↘ rejected (with feedback)
+```
+
+### Deliverables
+
+- [ ] Automated validation pipeline
+- [ ] Review queue UI
+- [ ] Rejection feedback system
+- [ ] Appeal process
+
+---
+
+## Milestone 10: Device-Side App Management
+
+**Goal**: Install, update, and manage apps on Mosis devices.
+
+### Components
+
+| Component | Location | Purpose |
+|-----------|----------|---------|
+| App Manager | MosisService | Install/uninstall/update apps |
+| App Store Client | System app | Browse, search, install UI |
+| Update Checker | Background service | Check for updates |
+
+### Installation Flow
+
+```
+1. User taps "Install" in App Store
+2. Download package from CDN
+3. Verify signature
+4. Extract to app directory
+5. Register with LuaSandboxManager
+6. Add to home screen
+```
+
+### Update Flow
+
+```
+1. Background check for updates (daily?)
+2. Notify user of available updates
+3. Download new version
+4. Verify signature
+5. Replace app files (atomic swap)
+6. Restart app if running
+```
+
+### Storage Layout
+
+```
+/data/mosis/
+ /apps/
+ /com.dev.app/
+ /package/ # Extracted app files
+ /data/ # App data (VirtualFS)
+ /cache/ # App cache
+ /db/ # SQLite databases
+```
+
+### Deliverables
+
+- [ ] AppManager class in MosisService
+- [ ] App Store system app
+- [ ] Update checking service
+- [ ] Uninstall with data cleanup
+
+---
+
+## Milestone 11: Developer CLI Tool
+
+**Goal**: Command-line tool for app development workflow.
+
+### Commands
+
+```bash
+# Project management
+mosis init # Create new app project
+mosis validate # Validate manifest and assets
+
+# Packaging
+mosis build # Create .mosis package
+mosis sign # Sign package with developer key
+
+# Testing
+mosis run # Run in local designer/emulator
+mosis test # Run automated tests
+
+# Publishing
+mosis login # Authenticate with portal
+mosis publish # Upload and submit for review
+mosis status # Check review status
+
+# Keys
+mosis keys generate # Generate signing keypair
+mosis keys register # Upload public key to portal
+```
+
+### Implementation Options
+
+| Option | Pros | Cons |
+|--------|------|------|
+| Node.js (oclif) | Easy to build, npm distribution | Requires Node |
+| Go | Single binary, fast | Slower development |
+| Rust (clap) | Single binary, fast | Slower development |
+
+### Deliverables
+
+- [ ] CLI framework selection
+- [ ] Core commands implementation
+- [ ] Distribution (npm, homebrew, direct download)
+- [ ] Documentation
+
+---
+
+## Milestone 12: Documentation Site
+
+**Goal**: Comprehensive docs for developers.
+
+### Sections
+
+| Section | Content |
+|---------|---------|
+| **Getting Started** | Quick start, first app tutorial |
+| **Guides** | UI design, Lua scripting, permissions |
+| **API Reference** | All Lua APIs, manifest schema |
+| **CLI Reference** | All commands and options |
+| **Best Practices** | Performance, security, UX |
+| **Troubleshooting** | Common issues, FAQ |
+
+### Tech Options
+
+| Option | Pros | Cons |
+|--------|------|------|
+| Docusaurus | React-based, versioning | Heavy |
+| VitePress | Vue-based, fast | Less features |
+| Astro Starlight | Fast, modern | Newer |
+| MkDocs | Python, simple | Less customizable |
+
+### Deliverables
+
+- [ ] Framework selection
+- [ ] Information architecture
+- [ ] Content writing
+- [ ] API docs generation from code
+- [ ] Search integration
+
+---
+
+## Summary
+
+| Phase | Milestones | Description |
+|-------|------------|-------------|
+| **Foundation** | 1-4 | Package format, web stack, database, auth |
+| **Portal** | 5-6 | Developer portal frontend and API |
+| **Distribution** | 7, 10 | CDN/storage, device-side app management |
+| **Quality** | 8-9 | Telemetry, crash reports, review system |
+| **Tooling** | 11-12 | CLI tool, documentation |
+
+### Recommended Order
+
+1. **Milestone 1** - Package format (needed by everything)
+2. **Milestone 2** - Web stack selection
+3. **Milestone 3** - Database selection
+4. **Milestone 4** - Authentication
+5. **Milestone 6** - Backend API
+6. **Milestone 5** - Portal frontend
+7. **Milestone 7** - CDN/storage
+8. **Milestone 10** - Device-side management
+9. **Milestone 11** - CLI tool
+10. **Milestone 9** - Review system
+11. **Milestone 8** - Telemetry
+12. **Milestone 12** - Documentation
+
+---
+
+## Open Questions
+
+1. **Monetization model?** - Free only, paid apps, subscriptions?
+2. **Enterprise/self-hosted?** - Can companies run private app stores?
+3. **App categories?** - Predefined or free-form tags?
+4. **Rating/reviews?** - User reviews for apps?
+5. **Analytics dashboard?** - What metrics do developers see?
+6. **Localization?** - Multi-language portal and apps?
+7. **Beta testing?** - TestFlight-like distribution?
+8. **Team accounts?** - Multiple developers per app?
+
+---
+
+## Next Steps
+
+Begin with Milestone 1 (App Package Format) to establish the foundation, then proceed with technology selections in Milestones 2-4 before building the portal.