This repository has been archived on 2026-04-03. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
core/docs/ICD/workspaces.md
2026-03-13 16:09:16 +00:00

9.2 KiB

Workspaces & Git

The Workspace is a virtual filesystem for the editor surface. Each workspace has a root directory, file operations, optional Git integration, and full-text indexing for code search.

All endpoints require: Auth() middleware (JWT bearer token). Workspace-scoped endpoints additionally verify ownership via loadAndAuthorize (user owns workspace, or is member of the owning team/project/channel).


Workspace CRUD

GET    /workspaces                  → { "data": [...] }
POST   /workspaces                  ← { "name", "owner_type", "owner_id" }
GET    /workspaces/:id              → workspace object
PATCH  /workspaces/:id              ← partial update
DELETE /workspaces/:id

Auth: Authenticated user. GET /workspaces returns user-owned workspaces plus those owned by the user's teams. All :id endpoints verify the caller can access the workspace's owner entity.

Workspace object:

{
  "id": "uuid",
  "name": "my-project",
  "owner_type": "user",
  "owner_id": "uuid",
  "status": "active",
  "max_bytes": 52428800,
  "indexing_enabled": false,
  "git_remote_url": "https://github.com/...",
  "git_branch": "main",
  "git_credential_id": "uuid",
  "git_last_sync": "2025-06-15T14:30:00Z",
  "file_count": 42,
  "total_bytes": 1048576,
  "created_at": "2025-06-15T14:30:00Z",
  "updated_at": "2025-06-15T14:30:00Z"
}
Field Type Notes
id string UUIDv4
name string Max 200 chars
owner_type "user" | "project" | "channel" | "team" Owner entity type
owner_id string UUID of owner entity
status "active" | "archived" | "deleting" Lifecycle state
max_bytes int | null Quota ceiling. Omitted if unset.
indexing_enabled bool Full-text search indexing
git_remote_url string | null Git remote. Omitted if unset.
git_branch string | null Tracked branch. Omitted if unset.
git_credential_id string | null FK to git credential. Omitted if unset.
git_last_sync string | null ISO 8601. Omitted if never synced.
file_count int Computed. Enriched on GET /:id only.
total_bytes int Computed. Enriched on GET /:id only.
created_at string ISO 8601
updated_at string ISO 8601

Note: root_path is a server-internal filesystem path and is never exposed in API responses (json tag "-").

Create request:

{
  "name": "my-project",
  "owner_type": "user",
  "owner_id": "uuid",
  "max_bytes": 52428800
}

Returns 201 with the workspace object.

PATCH request (all fields optional):

{
  "name": "new-name",
  "status": "archived",
  "max_bytes": 104857600,
  "indexing_enabled": true,
  "git_remote_url": "https://...",
  "git_branch": "develop",
  "git_credential_id": "uuid"
}

Returns updated workspace object.

DELETE returns {"ok": true}. Deletion is async (status set to "deleting", filesystem cleanup runs in background).


File Operations

All paths are relative to the workspace root. File path is passed as ?path= query parameter.

GET    /workspaces/:id/files?path=/           → { "data": [entries] }
GET    /workspaces/:id/files/read?path=/a.md  → raw file content (binary stream)
PUT    /workspaces/:id/files/write?path=/a.md ← raw body (Content-Length required)
DELETE /workspaces/:id/files/delete?path=/a.md
POST   /workspaces/:id/files/mkdir?path=/src

Auth: Authenticated user with workspace access.

GET .../files accepts ?recursive=true for deep listing. Returns {"data": [...]} (never bare array). Nil slice guarded.

File entry (from WorkspaceFile model):

{
  "id": "uuid",
  "workspace_id": "uuid",
  "path": "/src/main.go",
  "is_directory": false,
  "content_type": "text/x-go",
  "size_bytes": 4096,
  "sha256": "abc123...",
  "index_status": "ready",
  "chunk_count": 3,
  "created_at": "2025-06-15T14:30:00Z",
  "updated_at": "2025-06-15T14:30:00Z"
}

GET .../files/read returns raw file content with appropriate Content-Type header and Content-Length. This is a binary stream, not JSON.

PUT .../files/write reads the request body as raw content. Returns {"ok": true, "path": "/a.md"}. Returns 413 if the write would exceed the workspace quota.

DELETE .../files/delete accepts ?recursive=true for directory removal. Returns {"ok": true}.

POST .../files/mkdir returns 201 {"ok": true, "path": "/src"}.


Archive

Download (full workspace as archive):

GET /workspaces/:id/archive/download?format=zip

Auth: Authenticated user with workspace access.

format query param: zip (default), tar.gz, tgz. Returns binary stream with Content-Disposition: attachment.

Upload (restore from archive):

POST /workspaces/:id/archive/upload
Content-Type: multipart/form-data (field: "file")

Auth: Authenticated user with workspace access.

Accepts .zip, .tar.gz, .tgz archives. Format detected from filename extension.

Returns:

{
  "ok": true,
  "files_extracted": 42
}

Reconcile, Stats, Index Status

Reconcile (sync filesystem state with DB metadata):

POST /workspaces/:id/reconcile

Auth: Authenticated user with workspace access.

Returns:

{
  "added": 5,
  "removed": 2,
  "updated": 3
}

Stats:

GET /workspaces/:id/stats

Auth: Authenticated user with workspace access.

Returns a WorkspaceStats object:

{
  "workspace_id": "uuid",
  "file_count": 42,
  "dir_count": 8,
  "total_bytes": 1048576,
  "max_bytes": 52428800
}

Index status:

GET /workspaces/:id/index-status

Auth: Authenticated user with workspace access.

Returns indexing progress for full-text search:

{
  "workspace_id": "uuid",
  "indexing_enabled": true,
  "status_counts": {
    "pending": 3,
    "indexing": 1,
    "ready": 38,
    "error": 0,
    "skipped": 2
  },
  "total_chunks": 156
}

Git Integration

All Git operations are workspace-scoped. Require the workspace to have a Git remote configured.

POST /workspaces/:id/git/clone     ← { "url", "branch", "credential_id" }
POST /workspaces/:id/git/pull
POST /workspaces/:id/git/push
GET  /workspaces/:id/git/status    → GitStatus object
GET  /workspaces/:id/git/diff      → { "diff": "..." }
POST /workspaces/:id/git/commit    ← { "message", "paths": [...] }
GET  /workspaces/:id/git/log       → { "data": [GitLogEntry] }
GET  /workspaces/:id/git/branches  → { "branches": [...], "current": "main" }
POST /workspaces/:id/git/checkout  ← { "branch": "feature-x" }

Auth: Authenticated user with workspace access.

POST .../git/clone request:

{
  "url": "https://github.com/user/repo.git",
  "branch": "main",
  "credential_id": "uuid"
}

branch and credential_id are optional. Returns {"status": "cloned"}.

POST .../git/pull returns {"status": "pulled"}.

POST .../git/push returns {"status": "pushed"}.

GET .../git/status returns the full GitStatus object:

{
  "branch": "main",
  "clean": false,
  "ahead": 2,
  "behind": 0,
  "staged": [{ "path": "file.go", "status": "M" }],
  "modified": [{ "path": "other.go", "status": "M" }],
  "untracked": ["new-file.txt"]
}

GET .../git/diff accepts optional ?path=file.go for single-file diff. Returns {"diff": "..."} (unified diff string).

POST .../git/commit request:

{
  "message": "fix: resolve null pointer",
  "paths": ["/src/main.go", "/src/util.go"]
}

paths is optional (commits all staged if omitted). Returns {"status": "committed"}.

GET .../git/log accepts optional ?n=20 (default 20, max 100). Returns {"data": [GitLogEntry]}:

{
  "data": [
    {
      "hash": "abc123...",
      "short_hash": "abc123",
      "author": "Jane Doe",
      "date": "2025-06-15T14:30:00Z",
      "message": "Initial commit"
    }
  ]
}

GET .../git/branches returns:

{
  "current": "main",
  "branches": ["main", "develop", "feature-x"]
}

POST .../git/checkout returns {"status": "checked out", "branch": "feature-x"}.


Git Credentials

User-owned, encrypted credential storage for Git operations. Encrypted with the user's UEK via the platform vault (same pattern as BYOK keys — admins cannot recover).

Independent of workspace scope — credentials are user-level.

POST   /git-credentials             ← { "name", "auth_type", ... }
GET    /git-credentials             → { "data": [GitCredentialSummary] }
DELETE /git-credentials/:id

Auth: Authenticated user. Credentials are scoped to the requesting user.

Create request:

auth_type determines which fields are required:

auth_type Required fields
https_pat token
https_basic username, password
ssh_key private_key (+ optional passphrase)

Returns 201 with the credential summary (never exposes encrypted data).

List response:

{
  "data": [
    {
      "id": "uuid",
      "name": "GitHub PAT",
      "auth_type": "https_pat",
      "created_at": "2025-06-15T14:30:00Z"
    }
  ]
}

DELETE returns {"deleted": true}. Returns 404 if credential not found or not owned by the requesting user.