From 66f3d557829430b9aab324a49dec5a80b4f8cc93 Mon Sep 17 00:00:00 2001 From: logikonline Date: Thu, 8 Jan 2026 09:09:08 -0500 Subject: [PATCH] docs: add architectural review and suggestions Comprehensive review covering: - Chunked upload implementation critique with fixes - API design improvements (batch ops, pagination, error format) - Error handling & observability (tracing, structured logging) - Reliability patterns (retry, circuit breaker, health checks) - AI & automation friendliness (idempotency, error codes) - Security considerations (audit logging, token scopes) - Performance optimizations (caching, compression) - Phased implementation roadmap --- suggestions.md | 1013 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1013 insertions(+) create mode 100644 suggestions.md diff --git a/suggestions.md b/suggestions.md new file mode 100644 index 0000000000..920c08ea31 --- /dev/null +++ b/suggestions.md @@ -0,0 +1,1013 @@ +# Gitea Architecture Review & Suggestions + +**Reviewer:** Senior Architect +**Date:** January 2026 +**Scope:** Full codebase review with focus on API, services, and recent chunked upload implementation + +--- + +## Executive Summary + +Gitea demonstrates a **well-designed, pragmatic architecture** suitable for a mature self-hosted Git platform. The codebase follows clean layered patterns (routers → services → models) with good separation of concerns. However, there are opportunities to improve **operational observability**, **reliability**, **developer experience**, and **AI/automation friendliness**. + +This document presents findings organized by priority and effort level. + +--- + +## Table of Contents + +1. [Chunked Upload Implementation Review](#1-chunked-upload-implementation-review) +2. [API Design Improvements](#2-api-design-improvements) +3. [Error Handling & Observability](#3-error-handling--observability) +4. [Reliability & Resilience](#4-reliability--resilience) +5. [Developer Experience](#5-developer-experience) +6. [AI & Automation Friendliness](#6-ai--automation-friendliness) +7. [Security Considerations](#7-security-considerations) +8. [Performance Optimizations](#8-performance-optimizations) +9. [Implementation Roadmap](#9-implementation-roadmap) + +--- + +## 1. Chunked Upload Implementation Review + +### What Was Done Well + +- Clean separation: model (`upload_session.go`), service (`chunked.go`), handler (`upload.go`) +- Resumable uploads with session status tracking +- Automatic cleanup via cron job +- Follows existing Gitea patterns for API handlers and models + +### Issues to Address + +#### 1.1 Missing Chunk Verification (HIGH) + +**Problem:** No checksum verification for uploaded chunks. Corrupted chunks will produce corrupted files. + +**Suggestion:** +```go +// Add to UploadSession model +type UploadSession struct { + // ... existing fields + ChecksumAlgorithm string `xorm:"DEFAULT 'sha256'"` // sha256, md5, none + ExpectedChecksum string `xorm:""` // Full file checksum +} + +// Add to chunk upload +type ChunkMetadata struct { + ChunkNumber int64 `json:"chunk_number"` + Checksum string `json:"checksum"` // Chunk checksum + Size int64 `json:"size"` +} +``` + +#### 1.2 No Concurrent Upload Protection (MEDIUM) + +**Problem:** Same chunk can be uploaded multiple times concurrently, causing race conditions. + +**Suggestion:** +```go +// Add file locking or atomic operations +func SaveChunk(ctx context.Context, session *UploadSession, chunkNum int64, ...) error { + lockPath := session.GetChunkPath(chunkNum) + ".lock" + lock, err := acquireFileLock(lockPath) + if err != nil { + return fmt.Errorf("chunk %d is being uploaded by another request", chunkNum) + } + defer lock.Release() + // ... save chunk +} +``` + +#### 1.3 Missing Progress Webhook (MEDIUM) + +**Problem:** No way to notify external systems of upload progress. + +**Suggestion:** +```go +// Emit webhook on upload milestones +if session.ChunksReceived % 10 == 0 || session.IsComplete() { + notify_service.UploadProgress(ctx, session) +} +``` + +#### 1.4 No Rate Limiting (MEDIUM) + +**Problem:** Unlimited chunk uploads could exhaust server resources. + +**Suggestion:** +```go +// Add rate limiting middleware for upload endpoints +m.Group("/uploads", func() { + // ... routes +}, rateLimit(100, time.Minute)) // 100 chunks per minute per user +``` + +#### 1.5 Temp File Cleanup on Failure (LOW) + +**Problem:** If `AssembleChunks` fails partway through, temp files may not be cleaned up. + +**Suggestion:** +```go +func AssembleChunks(...) (*Attachment, error) { + defer func() { + if r := recover(); r != nil { + cleanupTempFiles(session) + panic(r) + } + }() + // ... existing logic +} +``` + +--- + +## 2. API Design Improvements + +### 2.1 Standardized Error Response Format (HIGH) + +**Current State:** Error responses vary in format across endpoints. + +**Suggestion:** Adopt RFC 7807 Problem Details format: + +```go +// modules/structs/error.go +type APIError struct { + Type string `json:"type"` // URI reference + Title string `json:"title"` // Short summary + Status int `json:"status"` // HTTP status code + Detail string `json:"detail"` // Explanation + Instance string `json:"instance,omitempty"` // Request URI + Code string `json:"code"` // Machine-readable code + Errors []ValidationError `json:"errors,omitempty"` // Field-level errors +} + +// Example response: +{ + "type": "https://gitea.io/errors/validation", + "title": "Validation Failed", + "status": 422, + "detail": "The provided repository name is invalid", + "code": "ERR_VALIDATION_FAILED", + "errors": [ + {"field": "name", "message": "must be alphanumeric", "code": "ERR_INVALID_CHARS"} + ] +} +``` + +### 2.2 Batch Operations API (HIGH) + +**Current State:** No batch endpoints for bulk operations. + +**Suggestion:** Add batch endpoints for common operations: + +``` +POST /api/v1/repos/{owner}/{repo}/issues/batch +POST /api/v1/repos/{owner}/{repo}/labels/batch +POST /api/v1/admin/users/batch +``` + +```go +// Example batch request +{ + "operations": [ + {"action": "close", "issue_ids": [1, 2, 3]}, + {"action": "add_label", "issue_ids": [1, 2], "label_id": 5} + ] +} + +// Example batch response +{ + "results": [ + {"index": 0, "success": true, "affected": 3}, + {"index": 1, "success": false, "error": {"code": "ERR_NOT_FOUND", "detail": "Label 5 not found"}} + ], + "summary": {"total": 2, "succeeded": 1, "failed": 1} +} +``` + +### 2.3 Cursor-Based Pagination (MEDIUM) + +**Current State:** Offset-based pagination can be slow for large datasets. + +**Suggestion:** Add cursor-based pagination option: + +``` +GET /api/v1/repos/{owner}/{repo}/issues?cursor=eyJpZCI6MTAwfQ&limit=50 +``` + +```go +type PaginatedResponse struct { + Data []any `json:"data"` + NextCursor string `json:"next_cursor,omitempty"` + PrevCursor string `json:"prev_cursor,omitempty"` + HasMore bool `json:"has_more"` + Total int64 `json:"total,omitempty"` // Optional, expensive to compute +} +``` + +### 2.4 Field Selection (GraphQL-lite) (MEDIUM) + +**Current State:** All fields returned on every request. + +**Suggestion:** Add field selection parameter: + +``` +GET /api/v1/repos/{owner}/{repo}?fields=id,name,description,stars_count +``` + +```go +// Reduces payload and database load +func filterFields(obj any, fields []string) map[string]any { + // Return only requested fields +} +``` + +### 2.5 API Versioning Header (LOW) + +**Current State:** No API version negotiation. + +**Suggestion:** Add version header support: + +``` +Accept: application/vnd.gitea.v2+json +X-Gitea-API-Version: 2 +``` + +--- + +## 3. Error Handling & Observability + +### 3.1 Centralized Error Catalog (HIGH) + +**Current State:** 100+ custom error types scattered across packages. + +**Suggestion:** Create centralized error registry: + +```go +// modules/errors/catalog.go +package errors + +type ErrorCode string + +const ( + // Authentication errors (1xxx) + ErrAuthRequired ErrorCode = "ERR_1001" + ErrInvalidToken ErrorCode = "ERR_1002" + ErrTokenExpired ErrorCode = "ERR_1003" + + // Authorization errors (2xxx) + ErrForbidden ErrorCode = "ERR_2001" + ErrNotRepoMember ErrorCode = "ERR_2002" + + // Resource errors (3xxx) + ErrUserNotFound ErrorCode = "ERR_3001" + ErrRepoNotFound ErrorCode = "ERR_3002" + ErrIssueNotFound ErrorCode = "ERR_3003" + + // Validation errors (4xxx) + ErrInvalidInput ErrorCode = "ERR_4001" + ErrNameTooLong ErrorCode = "ERR_4002" + + // Upload errors (5xxx) + ErrUploadSessionExpired ErrorCode = "ERR_5001" + ErrChunkMissing ErrorCode = "ERR_5002" + ErrFileTooLarge ErrorCode = "ERR_5003" +) + +var errorMessages = map[ErrorCode]string{ + ErrAuthRequired: "Authentication required", + ErrInvalidToken: "Invalid or malformed token", + // ... +} + +func (e ErrorCode) Error() string { return errorMessages[e] } +func (e ErrorCode) HTTPStatus() int { /* return appropriate status */ } +``` + +### 3.2 Request Tracing (HIGH) + +**Current State:** No correlation IDs for request tracing. + +**Suggestion:** Add OpenTelemetry integration: + +```go +// modules/web/middleware/tracing.go +func TracingMiddleware() func(http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + traceID := r.Header.Get("X-Request-ID") + if traceID == "" { + traceID = uuid.New().String() + } + + ctx := context.WithValue(r.Context(), TraceIDKey, traceID) + w.Header().Set("X-Request-ID", traceID) + + // Add to structured logging + log := log.WithField("trace_id", traceID) + ctx = context.WithValue(ctx, LoggerKey, log) + + next.ServeHTTP(w, r.WithContext(ctx)) + }) + } +} +``` + +### 3.3 Structured Logging (MEDIUM) + +**Current State:** Mix of structured and unstructured logging. + +**Suggestion:** Standardize on JSON structured logs: + +```go +// All log entries should include: +{ + "timestamp": "2024-01-08T12:00:00Z", + "level": "info", + "trace_id": "abc-123", + "user_id": 42, + "repo_id": 100, + "action": "create_issue", + "duration_ms": 45, + "message": "Issue created successfully" +} +``` + +### 3.4 Metrics Endpoint Enhancement (MEDIUM) + +**Current State:** Basic Prometheus metrics. + +**Suggestion:** Add business metrics: + +```go +// Metrics to add: +gitea_api_requests_total{method, endpoint, status} +gitea_api_request_duration_seconds{method, endpoint} +gitea_upload_sessions_active +gitea_upload_bytes_total +gitea_webhook_deliveries_total{status, hook_type} +gitea_webhook_delivery_duration_seconds +gitea_repository_operations_total{operation} +``` + +--- + +## 4. Reliability & Resilience + +### 4.1 Webhook Retry with Exponential Backoff (HIGH) + +**Current State:** Limited retry logic visible. + +**Suggestion:** + +```go +// services/webhook/deliver.go +type RetryConfig struct { + MaxRetries int // Default: 5 + InitialBackoff time.Duration // Default: 1s + MaxBackoff time.Duration // Default: 5m + BackoffFactor float64 // Default: 2.0 +} + +func DeliverWithRetry(ctx context.Context, task *HookTask, cfg RetryConfig) error { + var lastErr error + backoff := cfg.InitialBackoff + + for attempt := 0; attempt <= cfg.MaxRetries; attempt++ { + err := Deliver(ctx, task) + if err == nil { + return nil + } + lastErr = err + + // Log retry + log.Warn("Webhook delivery failed, retrying", + "task_id", task.ID, + "attempt", attempt+1, + "next_backoff", backoff) + + select { + case <-ctx.Done(): + return ctx.Err() + case <-time.After(backoff): + } + + backoff = time.Duration(float64(backoff) * cfg.BackoffFactor) + if backoff > cfg.MaxBackoff { + backoff = cfg.MaxBackoff + } + } + + // Move to dead letter queue + if err := moveToDeadLetterQueue(ctx, task, lastErr); err != nil { + log.Error("Failed to move to DLQ", "task_id", task.ID, "error", err) + } + + return fmt.Errorf("webhook delivery failed after %d attempts: %w", cfg.MaxRetries, lastErr) +} +``` + +### 4.2 Circuit Breaker for External Services (MEDIUM) + +**Current State:** No circuit breaker pattern. + +**Suggestion:** + +```go +// modules/circuitbreaker/breaker.go +type CircuitBreaker struct { + name string + maxFailures int + resetTimeout time.Duration + state State // Closed, Open, HalfOpen + failures int + lastFailure time.Time +} + +func (cb *CircuitBreaker) Execute(fn func() error) error { + if cb.state == Open { + if time.Since(cb.lastFailure) > cb.resetTimeout { + cb.state = HalfOpen + } else { + return ErrCircuitOpen + } + } + + err := fn() + if err != nil { + cb.recordFailure() + return err + } + + cb.recordSuccess() + return nil +} + +// Usage for webhook delivery, OAuth providers, etc. +var webhookBreaker = NewCircuitBreaker("webhook", 5, time.Minute) +``` + +### 4.3 Graceful Degradation (MEDIUM) + +**Current State:** Services fail hard on dependency failures. + +**Suggestion:** Add fallback behaviors: + +```go +// Example: Search indexer fallback +func SearchIssues(ctx context.Context, query string) ([]Issue, error) { + if indexer.IsHealthy() { + return indexer.Search(ctx, query) + } + + // Fallback to database search (slower but works) + log.Warn("Search indexer unavailable, falling back to DB search") + return db.SearchIssuesByTitle(ctx, query) +} +``` + +### 4.4 Health Check Endpoints (LOW) + +**Current State:** Basic health check. + +**Suggestion:** Add detailed health checks: + +``` +GET /api/v1/health +GET /api/v1/health/live # Kubernetes liveness +GET /api/v1/health/ready # Kubernetes readiness +``` + +```json +{ + "status": "healthy", + "version": "1.24.0", + "checks": { + "database": {"status": "up", "latency_ms": 2}, + "redis": {"status": "up", "latency_ms": 1}, + "search_indexer": {"status": "degraded", "message": "High latency"}, + "storage": {"status": "up", "free_space_gb": 450} + } +} +``` + +--- + +## 5. Developer Experience + +### 5.1 OpenAPI Spec Improvements (HIGH) + +**Current State:** Swagger docs generated from code comments. + +**Suggestion:** Enhance with examples and better descriptions: + +```go +// swagger:operation POST /repos/{owner}/{repo}/issues repository createIssue +// --- +// summary: Create an issue +// description: | +// Creates a new issue in the repository. The authenticated user must have +// write access to the repository. +// +// ## Example +// ```json +// {"title": "Bug report", "body": "Description here", "labels": [1, 2]} +// ``` +// consumes: +// - application/json +// produces: +// - application/json +// parameters: +// - name: body +// in: body +// schema: +// "$ref": "#/definitions/CreateIssueOption" +// example: +// title: "Bug: Login fails" +// body: "When I try to login..." +// labels: [1, 2] +// responses: +// "201": +// description: Issue created +// schema: +// "$ref": "#/definitions/Issue" +// examples: +// application/json: +// id: 1 +// title: "Bug: Login fails" +// state: "open" +``` + +### 5.2 SDK Generation (MEDIUM) + +**Current State:** No official SDK libraries. + +**Suggestion:** Generate SDKs from OpenAPI spec: + +```bash +# Generate SDKs +openapi-generator generate -i api/v1/swagger.json -g python -o sdk/python +openapi-generator generate -i api/v1/swagger.json -g typescript-axios -o sdk/typescript +openapi-generator generate -i api/v1/swagger.json -g go -o sdk/go +``` + +### 5.3 CLI Tool Enhancement (MEDIUM) + +**Current State:** Basic CLI for Git operations. + +**Suggestion:** Add API CLI tool: + +```bash +# Proposed gitea-cli commands +gitea-cli auth login --server https://gitea.example.com +gitea-cli repo list +gitea-cli repo create myrepo --private +gitea-cli issue create --title "Bug" --body "Description" +gitea-cli release create v1.0.0 --attach ./binary.zip +gitea-cli upload --chunked ./large-file.zip # Uses chunked upload +``` + +### 5.4 Webhook Debugging Tools (LOW) + +**Current State:** Limited visibility into webhook delivery. + +**Suggestion:** Add webhook inspection endpoints: + +``` +GET /api/v1/repos/{owner}/{repo}/hooks/{id}/deliveries +GET /api/v1/repos/{owner}/{repo}/hooks/{id}/deliveries/{delivery_id} +POST /api/v1/repos/{owner}/{repo}/hooks/{id}/deliveries/{delivery_id}/redeliver +``` + +--- + +## 6. AI & Automation Friendliness + +### 6.1 Machine-Readable Error Codes (HIGH) + +**Current State:** Error messages are human-readable strings. + +**Suggestion:** Always include machine-readable codes: + +```json +{ + "code": "ERR_REPO_ARCHIVED", + "message": "Repository is archived and cannot be modified", + "documentation_url": "https://docs.gitea.io/errors/ERR_REPO_ARCHIVED" +} +``` + +### 6.2 Idempotency Keys (HIGH) + +**Current State:** No idempotency support for POST requests. + +**Suggestion:** Add idempotency key header: + +```go +// Header: Idempotency-Key: + +func IdempotencyMiddleware() func(http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method != "POST" && r.Method != "PUT" { + next.ServeHTTP(w, r) + return + } + + key := r.Header.Get("Idempotency-Key") + if key == "" { + next.ServeHTTP(w, r) + return + } + + // Check cache + if cached, found := idempotencyCache.Get(key); found { + w.Header().Set("X-Idempotency-Replayed", "true") + writeResponse(w, cached) + return + } + + // Capture response + rec := httptest.NewRecorder() + next.ServeHTTP(rec, r) + + // Cache successful responses + if rec.Code >= 200 && rec.Code < 300 { + idempotencyCache.Set(key, rec, 24*time.Hour) + } + + copyResponse(w, rec) + }) + } +} +``` + +### 6.3 Webhook Event Catalog (MEDIUM) + +**Current State:** Events documented but not discoverable via API. + +**Suggestion:** Add event discovery endpoint: + +``` +GET /api/v1/events +``` + +```json +{ + "events": [ + { + "name": "push", + "description": "Triggered when commits are pushed", + "payload_schema": {"$ref": "#/definitions/PushPayload"}, + "example": {...} + }, + { + "name": "issues", + "description": "Triggered for issue events", + "actions": ["opened", "closed", "edited", "labeled"], + "payload_schema": {"$ref": "#/definitions/IssuePayload"} + } + ] +} +``` + +### 6.4 Async Operation Pattern (MEDIUM) + +**Current State:** Long operations block HTTP requests. + +**Suggestion:** Add async operation pattern for long-running tasks: + +``` +POST /api/v1/repos/{owner}/{repo}/mirror-sync +Response: 202 Accepted +{ + "operation_id": "op_abc123", + "status": "pending", + "status_url": "/api/v1/operations/op_abc123", + "estimated_completion": "2024-01-08T12:05:00Z" +} + +GET /api/v1/operations/op_abc123 +{ + "operation_id": "op_abc123", + "status": "running", + "progress": 45, + "started_at": "2024-01-08T12:00:00Z" +} +``` + +### 6.5 Rate Limit Headers (MEDIUM) + +**Current State:** Limited rate limit visibility. + +**Suggestion:** Add standard rate limit headers: + +``` +X-RateLimit-Limit: 5000 +X-RateLimit-Remaining: 4990 +X-RateLimit-Reset: 1704720000 +X-RateLimit-Resource: api +``` + +### 6.6 Conditional Requests (LOW) + +**Current State:** Limited ETag/If-Match support. + +**Suggestion:** Add conditional request support: + +``` +GET /api/v1/repos/owner/repo +Response Headers: + ETag: "abc123" + Last-Modified: Wed, 08 Jan 2024 12:00:00 GMT + +GET /api/v1/repos/owner/repo +Request Headers: + If-None-Match: "abc123" +Response: 304 Not Modified + +PUT /api/v1/repos/owner/repo +Request Headers: + If-Match: "abc123" # Prevents lost updates +``` + +--- + +## 7. Security Considerations + +### 7.1 Audit Logging (HIGH) + +**Current State:** Limited audit trail. + +**Suggestion:** Add comprehensive audit logging: + +```go +type AuditEvent struct { + ID int64 `json:"id"` + Timestamp time.Time `json:"timestamp"` + ActorID int64 `json:"actor_id"` + ActorIP string `json:"actor_ip"` + Action string `json:"action"` // "repo.create", "user.delete" + Resource string `json:"resource"` // "repository", "user" + ResourceID int64 `json:"resource_id"` + OldValue string `json:"old_value,omitempty"` // JSON + NewValue string `json:"new_value,omitempty"` // JSON + UserAgent string `json:"user_agent"` + TraceID string `json:"trace_id"` +} + +// Store in separate audit_log table +// Expose via admin API: GET /api/v1/admin/audit-log +``` + +### 7.2 Token Scopes Enhancement (MEDIUM) + +**Current State:** Basic token scopes. + +**Suggestion:** Add granular scopes: + +``` +repo:read # Read repository data +repo:write # Push to repository +repo:delete # Delete repository +issues:read # Read issues +issues:write # Create/edit issues +releases:read # Read releases +releases:write # Create releases +releases:upload # Upload release assets (for CI) +admin:users # Manage users +admin:repos # Manage all repos +webhook:manage # Manage webhooks +``` + +### 7.3 Request Signing (LOW) + +**Current State:** Token-based auth only. + +**Suggestion:** Add HMAC request signing for webhooks: + +```go +// For high-security integrations +signature := hmac.New(sha256.New, []byte(secret)) +signature.Write([]byte(timestamp + "." + requestBody)) +expectedSig := hex.EncodeToString(signature.Sum(nil)) + +// Header: X-Gitea-Signature: sha256= +// Header: X-Gitea-Timestamp: +``` + +--- + +## 8. Performance Optimizations + +### 8.1 Response Compression (HIGH) + +**Current State:** Gzip available but not always used. + +**Suggestion:** Ensure compression for all JSON responses: + +```go +// Middleware to compress responses > 1KB +func CompressionMiddleware(next http.Handler) http.Handler { + return gziphandler.GzipHandler(next) +} +``` + +### 8.2 Database Query Optimization (MEDIUM) + +**Current State:** Some N+1 query patterns. + +**Suggestion:** Add eager loading for common associations: + +```go +// Instead of loading issues then loading users one by one +issues, _ := GetIssues(ctx, opts) +for _, issue := range issues { + issue.Poster, _ = GetUser(issue.PosterID) // N+1! +} + +// Use eager loading +issues, _ := GetIssuesWithUsers(ctx, opts) // Single JOIN query +``` + +### 8.3 Caching Layer (MEDIUM) + +**Current State:** Limited caching. + +**Suggestion:** Add Redis/memory caching for hot paths: + +```go +// Cache frequently accessed data +func GetRepository(ctx context.Context, id int64) (*Repository, error) { + cacheKey := fmt.Sprintf("repo:%d", id) + + if cached, found := cache.Get(cacheKey); found { + return cached.(*Repository), nil + } + + repo, err := db.GetRepository(ctx, id) + if err != nil { + return nil, err + } + + cache.Set(cacheKey, repo, 5*time.Minute) + return repo, nil +} +``` + +### 8.4 Connection Pooling Tuning (LOW) + +**Current State:** Default connection pool settings. + +**Suggestion:** Add configurable pool settings: + +```ini +[database] +MAX_OPEN_CONNS = 100 +MAX_IDLE_CONNS = 10 +CONN_MAX_LIFETIME = 1h +CONN_MAX_IDLE_TIME = 10m +``` + +--- + +## 9. Implementation Roadmap + +### Phase 1: Quick Wins (1-2 weeks) + +| Item | Priority | Effort | Impact | +|------|----------|--------|--------| +| Add request tracing (X-Request-ID) | HIGH | Low | High | +| Add rate limit headers | MEDIUM | Low | Medium | +| Add chunk checksum verification | HIGH | Low | High | +| Standardize error response format | HIGH | Medium | High | + +### Phase 2: Foundation (2-4 weeks) + +| Item | Priority | Effort | Impact | +|------|----------|--------|--------| +| Centralized error catalog | HIGH | Medium | High | +| Webhook retry with backoff | HIGH | Medium | High | +| Idempotency key support | HIGH | Medium | High | +| Audit logging | HIGH | Medium | High | +| Health check endpoints | LOW | Low | Medium | + +### Phase 3: Enhancement (4-8 weeks) + +| Item | Priority | Effort | Impact | +|------|----------|--------|--------| +| Batch operations API | HIGH | High | High | +| Cursor-based pagination | MEDIUM | Medium | Medium | +| SDK generation pipeline | MEDIUM | Medium | Medium | +| Circuit breaker pattern | MEDIUM | Medium | Medium | +| Event discovery endpoint | MEDIUM | Low | Medium | + +### Phase 4: Advanced (8+ weeks) + +| Item | Priority | Effort | Impact | +|------|----------|--------|--------| +| Async operation pattern | MEDIUM | High | Medium | +| Field selection (GraphQL-lite) | MEDIUM | High | Medium | +| API versioning | LOW | High | Low | +| Full OpenTelemetry integration | MEDIUM | High | High | + +--- + +## Appendix A: Chunked Upload Quick Fixes + +```go +// 1. Add checksum to chunk upload (upload.go) +func UploadChunk(ctx *context.APIContext) { + // Get expected checksum from header + expectedChecksum := ctx.Req.Header.Get("X-Chunk-Checksum") + + // ... save chunk ... + + // Verify checksum if provided + if expectedChecksum != "" { + actualChecksum := sha256sum(chunkData) + if actualChecksum != expectedChecksum { + ctx.APIError(http.StatusBadRequest, api.APIError{ + Code: "ERR_CHECKSUM_MISMATCH", + Detail: "Chunk checksum verification failed", + }) + return + } + } +} + +// 2. Add final file checksum verification (chunked.go) +func AssembleChunks(...) (*Attachment, error) { + // ... assemble chunks ... + + // Verify final checksum if expected + if session.ExpectedChecksum != "" { + actualChecksum := computeFileChecksum(finalPath) + if actualChecksum != session.ExpectedChecksum { + return nil, fmt.Errorf("file checksum mismatch: expected %s, got %s", + session.ExpectedChecksum, actualChecksum) + } + } + + // ... create attachment ... +} +``` + +--- + +## Appendix B: Sample Error Catalog + +```go +// modules/errors/codes.go + +package errors + +// Error codes organized by category +const ( + // 1xxx - Authentication + ErrCodeAuthRequired = "ERR_1001" + ErrCodeInvalidToken = "ERR_1002" + ErrCodeTokenExpired = "ERR_1003" + ErrCodeInvalidPassword = "ERR_1004" + ErrCode2FARequired = "ERR_1005" + + // 2xxx - Authorization + ErrCodeForbidden = "ERR_2001" + ErrCodeNotRepoMember = "ERR_2002" + ErrCodeNotOrgMember = "ERR_2003" + ErrCodeInsufficientPerm = "ERR_2004" + + // 3xxx - Resource Not Found + ErrCodeUserNotFound = "ERR_3001" + ErrCodeRepoNotFound = "ERR_3002" + ErrCodeIssueNotFound = "ERR_3003" + ErrCodeReleaseNotFound = "ERR_3004" + ErrCodeBranchNotFound = "ERR_3005" + + // 4xxx - Validation + ErrCodeInvalidInput = "ERR_4001" + ErrCodeNameTooLong = "ERR_4002" + ErrCodeInvalidEmail = "ERR_4003" + ErrCodeDuplicateName = "ERR_4004" + + // 5xxx - Upload + ErrCodeSessionExpired = "ERR_5001" + ErrCodeChunkMissing = "ERR_5002" + ErrCodeFileTooLarge = "ERR_5003" + ErrCodeChecksumMismatch = "ERR_5004" + ErrCodeInvalidFileType = "ERR_5005" + + // 6xxx - Rate Limiting + ErrCodeRateLimited = "ERR_6001" + ErrCodeTooManyRequests = "ERR_6002" + + // 7xxx - Server + ErrCodeInternal = "ERR_7001" + ErrCodeServiceDown = "ERR_7002" + ErrCodeTimeout = "ERR_7003" +) +``` + +--- + +*Document prepared for discussion. All suggestions should be evaluated against project priorities and resource constraints.*