# Lucos Cloud Client — Indexing & Retrieval API

Lucos desktop daemon APIs for workspace indexing and semantic retrieval.  
**Public entry:** `api.lucos.com` proxies to this service with JWT auth and trusted headers.  
**This service:** `lucos-cloud-client` on port **6006** (internal).

Related JIRA: [TW-177](https://admedia-jira.atlassian.net/browse/TW-177), TW-200, TW-201, TW-203.

**Local E2E runbook** (services, auth bypass, troubleshooting):  
[`docs/lucos-indexing-e2e-local.md`](../../docs/lucos-indexing-e2e-local.md)

---

## Architecture

```text
Lucos daemon  →  api.lucos.com (JWT)  →  lucos-cloud-client (:6006)  →  embedding-engine (:6004)
```

- Daemon uploads **normalized chunks** (text + metadata).
- **embedding-engine** generates vectors and serves `/internal/retrieve`.
- **rag-service** is **not** used for Lucos agent retrieval.

---

## Authentication

This service does **not** validate JWT. The gateway validates the user and forwards trusted headers:

| Header | Required | Description |
|--------|----------|-------------|
| `X-Request-Id` | yes | Correlation ID |
| `X-Org-Id` | yes | Organization |
| `X-User-Id` | yes | Acting user |
| `X-User-Email` | no | User email |
| `X-Org-Role` | no | `owner`, `developer`, `viewer` |
| `X-Plan-Code` | no | `free`, `pro` |
| `X-Team-Ids` | no | Comma-separated team IDs |
| `X-Repo-Ids` | no | Present for **team-only** members; omitted for org-wide access |

Missing required headers → **401** `TRUSTED_CONTEXT_REQUIRED`.

---

## Workspace `repo_id`

Lucos workspace repos are **not** GitHub import repos. They are registered on first chunk upload.

**Format:**

```text
lucos:ws:{workspace_fingerprint}
```

- `workspace_fingerprint` = first 16 hex chars of `sha256(normalized_workspace_root_path)`.
- Path normalization: resolve to absolute path, lowercase on case-insensitive filesystems.
- Scoped to `X-Org-Id` in Mongo (`workspace_repos` collection).

**Example:** `lucos:ws:a1b2c3d4e5f67890`

Daemon may send the same `repo_id` on every upload for a workspace. Cloud creates the repo record on first upload if missing (Sprint 2).

---

## Chunk schema

Daemon sends pre-chunked payloads. Cloud validates and maps `source_type` to embedding-engine types.

| Lucos `source_type` | embedding-engine `source_type` |
|---------------------|--------------------------------|
| `repo_code` | `code` |
| `repo_doc` | `docs` |

### Chunk object

| Field | Type | Required | Notes |
|-------|------|----------|-------|
| `chunk_id` | string | yes | Stable ID per file region |
| `chunk_hash` | string | yes | e.g. `sha256:...` for dedup |
| `file_path` | string | yes | Repo-relative path |
| `language` | string | no | e.g. `typescript` |
| `start_line` | int | yes | 1-based |
| `end_line` | int | yes | ≥ `start_line` |
| `content` | string | yes | Chunk text |
| `source_type` | enum | yes | `repo_code` \| `repo_doc` |
| `symbol_name` | string | no | When available |

**Limits (configurable):** max 200 chunks/request, max 64 KiB content per chunk.

---

## Endpoints (gateway public paths)

Gateway (`api.lucos.com`) exposes these under `/api/v1` and proxies to this service with JWT auth + trusted headers.

| Gateway path | Internal path |
|--------------|---------------|
| `POST /api/v1/indexing/chunks` | `POST /api/v1/indexing/chunks` |
| `GET /api/v1/indexing/status` | `GET /api/v1/indexing/status` |
| `POST /api/v1/retrieval/search` | `POST /api/v1/retrieval/search` |

Configure gateway: `LUCOS_CLOUD_CLIENT_URL=http://lucos-cloud-client:6006`

### `POST /indexing/chunks` — TW-200

Incremental workspace chunk upload.

**Request:**

```json
{
  "repo_id": "lucos:ws:a1b2c3d4e5f67890",
  "commit_sha": "abc123",
  "workspace_id": "ws-uuid-optional",
  "chunks": [
    {
      "chunk_id": "chk_001",
      "chunk_hash": "sha256:deadbeef",
      "file_path": "src/auth/middleware.ts",
      "language": "typescript",
      "start_line": 10,
      "end_line": 42,
      "content": "export function authMiddleware() { ... }",
      "source_type": "repo_code"
    }
  ],
  "deleted_chunk_ids": ["chk_old"]
}
```

**Response (200):**

```json
{
  "repo_id": "lucos:ws:a1b2c3d4e5f67890",
  "counts": {
    "indexed": 12,
    "skipped": 3,
    "failed": 0
  },
  "request_id": "uuid"
}
```

**Errors:** `400 INVALID_PAYLOAD`, `403` repo ACL / plan limit, `401` trusted headers.

---

### `GET /indexing/status?repo_id=` — TW-203

**Response (200):**

```json
{
  "repo_id": "lucos:ws:a1b2c3d4e5f67890",
  "state": "indexing",
  "files_total": 120,
  "files_indexed": 45,
  "chunks_total": 890,
  "stale_count": 2,
  "last_indexed_at": "2026-07-01T12:00:00Z"
}
```

`state`: `pending` | `indexing` | `ready` | `stale`

---

### `POST /retrieval/search` — TW-201

Semantic search returning **chunk references** for the agent (not LLM answers).

**Request:**

```json
{
  "repo_ids": ["lucos:ws:a1b2c3d4e5f67890"],
  "query": "auth middleware implementation",
  "top_k": 20,
  "filters": {
    "paths": ["src/**"]
  }
}
```

**Response (200):**

```json
{
  "results": [
    {
      "file_path": "src/auth/middleware.ts",
      "start_line": 10,
      "end_line": 42,
      "score": 0.87,
      "chunk_hash": "sha256:deadbeef",
      "chunk_id": "chk_001",
      "snippet": "export function authMiddleware() { ... }",
      "source_type": "repo_code"
    }
  ],
  "request_id": "uuid"
}
```

**Latency target:** p95 &lt; 500ms (documented in Sprint 4).

Daemon should `read_file` locally after retrieval — local disk is authoritative.

---

## Health

| Path | Description |
|------|-------------|
| `GET /healthz` | Liveness |
| `GET /readyz` | Mongo + embedding-engine readiness |
| `GET /api/v1/health/` | Liveness (versioned) |
| `GET /api/v1/version` | Service version |

---

## Sprint status

| Endpoint | Sprint | Status |
|----------|--------|--------|
| Health / version | 0 | Implemented |
| Trusted headers | 0 | Implemented |
| `POST /indexing/chunks` | 2 | **Implemented** |
| `GET /indexing/status` | 3 | **Implemented** |
| `POST /retrieval/search` | 4 | **Implemented** |
