--- ## How to read this document - **Dependencies** list task IDs that must be complete before this task starts - **Parallel group** identifies tasks that can run simultaneously within a phase - **Target** identifies which repo and branch the work goes into - Tasks are numbered `P{phase}-{sequence}` (e.g., P0-3) - Acceptance criteria are binary — pass or fail, no judgment calls --- ## Phase 1: Single-User Serverless Wiki **Goal:** Port the existing Otterwiki + API + semantic search + MCP stack to run on Lambda + EFS. Single user, single wiki, no multi-tenancy. **Status: COMPLETE.** All tasks done, gate passed 2026-03-13. See [[Dev/Phase_1_Gate_Results]] for full validation results. ### P1-1: Mangum Adapter for Otterwiki **Parallel group:** Can start during Phase 0 (upstream work) **Dependencies:** None (upstream contribution, independent of wikibot-io infra) **Target:** `otterwiki` fork, PR to `main` **Status: COMPLETE** **Description:** Add Lambda compatibility to Otterwiki via the Mangum adapter. Mangum wraps Flask/WSGI apps for Lambda invocation. The adapter should be opt-in — Otterwiki continues to work as a normal Flask app when not on Lambda. Investigate Otterwiki's filesystem assumptions beyond the git repo: config files, static assets, session storage, temporary files. Document what needs to live on EFS vs. what can be ephemeral. **Note:** Ended up using apig-wsgi (not Mangum) because Mangum is ASGI-only since v0.15+ and Otterwiki is a WSGI/Flask app. Custom `lambda_init.py` handler in `wikibot-io/app/otterwiki/`. **Deliverables:** - `wikibot-io/app/otterwiki/lambda_init.py` — apig-wsgi handler with WSGI middleware for non-ASCII PATH_INFO - Documentation of filesystem assumptions and EFS requirements - No changes to existing Otterwiki behavior **Acceptance criteria:** - [x] Otterwiki can be invoked via Lambda handler (apig-wsgi, not Mangum) - [x] Existing test suite still passes - [x] Static assets served correctly - [x] Config file loading works from EFS path - [x] Git repo operations work with EFS-mounted repo path - [x] No import errors when running outside Lambda --- ### P1-2: FAISS Backend for Semantic Search **Parallel group:** Can start during Phase 0 (upstream work) **Dependencies:** None (upstream contribution, independent of wikibot-io infra) **Target:** `otterwiki-semantic-search` repo, PR to `main` **Status: COMPLETE** **Description:** Add FAISS as an alternative vector store backend alongside the existing ChromaDB backend. FAISS stores indexes as files on disk (suitable for EFS). The backend is selected via configuration. The embedding function must be pluggable: the existing `all-MiniLM-L6-v2` sentence-transformer for local/ChromaDB use, and a Bedrock `titan-embed-text-v2` adapter for Lambda use. The Bedrock adapter is a new addition but should be a clean interface implementation, not Lambda-specific. Reference the PRD's FAISS details: `IndexFlatIP`, sidecar metadata in `embeddings.json`, search deduplication by page. **Deliverables:** - `otterwiki_semantic_search/backends/faiss_backend.py` — FAISS index management (create, upsert, delete, search) - `otterwiki_semantic_search/backends/chroma_backend.py` — extracted from existing code - `otterwiki_semantic_search/backends/base.py` — abstract backend interface - `otterwiki_semantic_search/embeddings/bedrock.py` — Bedrock titan-embed-text-v2 adapter - `otterwiki_semantic_search/embeddings/base.py` — abstract embedding interface - Configuration switch: `VECTOR_BACKEND=faiss|chroma`, `EMBEDDING_MODEL=local|bedrock` - Unit tests for FAISS backend (using `/tmp` for index files) - Unit tests for Bedrock embedding adapter (mocked) - Existing ChromaDB tests still pass **Acceptance criteria:** - [x] FAISS backend stores/retrieves/deletes vectors correctly - [x] Sidecar metadata (`embeddings.json`) maps index positions to page paths and chunk text - [x] Search deduplicates by page, returns top N unique pages - [x] Bedrock adapter produces vectors of correct dimensionality - [x] Backend is selected by config, defaults to ChromaDB for backward compatibility - [x] Existing tests pass without modification - [x] New tests cover FAISS CRUD and search operations --- ### P1-3: Otterwiki on Lambda **Parallel group:** Phase 1 core **Dependencies:** P0-2 (EFS + Lambda infra), P1-1 (Mangum adapter) **Target:** `wikibot-io` repo, `feat/P1-3-otterwiki-lambda` **Status: COMPLETE** **Description:** Deploy Otterwiki to Lambda using the apig-wsgi adapter from P1-1. Configure it to use a git repo on EFS. Verify the web UI works end-to-end: browse pages, create pages, edit pages, view history. **Deliverables:** - Pulumi Lambda function for Otterwiki with EFS mount - Otterwiki configuration for Lambda environment (EFS paths, settings) - API Gateway route for web UI (`/{path+}`) - Integration test: create page via web UI API, read it back **Acceptance criteria:** - [x] Otterwiki web UI loads in a browser - [x] Can create, edit, and delete pages - [x] Page history works - [x] Static assets (CSS, JS) load correctly - [x] Git repo on EFS persists across invocations --- ### P1-4: REST API on Lambda **Parallel group:** Phase 1 (parallel with P1-5, P1-6 once P1-3 lands) **Dependencies:** P1-3 **Target:** `wikibot-io` repo, `feat/P1-4-api-lambda` **Status: COMPLETE** **Description:** Deploy the existing `otterwiki-api` plugin on Lambda alongside Otterwiki. Verify all API endpoints work. The API plugin is already a Flask blueprint — it should load naturally in the Lambda environment. **Deliverables:** - API plugin installed and loaded in Lambda Otterwiki instance - API Gateway routes for `/api/v1/*` - Integration tests: CRUD pages via API, search, link graph, changelog **Acceptance criteria:** - [x] All existing API endpoints respond correctly - [x] API key auth works via env var - [x] WikiLink index builds on startup - [x] Full-text search returns results - [x] Page create/read/update/delete cycle works - [x] Link graph queries return correct data **Note:** Minor issue found during gate: `GET /pages/{path}/links` returns 404 due to Flask route priority. The MCP `get_links` tool works via a different URL pattern. Low priority fix. --- ### P1-5: MCP Server on Lambda **Parallel group:** Phase 1 (parallel with P1-4, P1-6) **Dependencies:** P1-3, P0-7 (MCP + WorkOS on Lambda) **Target:** `wikibot-io` repo, `feat/P1-5-mcp-lambda` **Status: COMPLETE** **Description:** Deploy the existing `otterwiki-mcp` server on Lambda. Adapt from SSE to Streamable HTTP transport. The MCP server calls the REST API (P1-4) internally. Auth via WorkOS OAuth (from P0-7). Decision: Separate Lambdas. Otterwiki needs VPC + EFS; MCP is stateless HTTP proxy. Coupling them would add VPC complexity to MCP for no benefit. **Deliverables:** - MCP server deployed on separate Lambda (stateless, no VPC) - Streamable HTTP transport via `json_response=True` (API Gateway doesn't support SSE) - All 12 MCP tools functional - Integration test: read_note, write_note, search_notes via MCP protocol **Acceptance criteria:** - [x] All MCP tools return correct results - [x] OAuth authentication works (WorkOS AuthKit, fixed in P1-9) - [x] Bearer token authentication works - [x] Streamable HTTP transport functions correctly - [x] Architecture decision (same vs. separate Lambda) documented with rationale --- ### P1-6: Semantic Search on Lambda **Parallel group:** Phase 1 (parallel with P1-4, P1-5) **Dependencies:** P1-3, P1-2 (FAISS backend) **Target:** `wikibot-io` repo, `feat/P1-6-semantic-search-lambda` **Status: COMPLETE** **Description:** Deploy the semantic search plugin on Lambda using the FAISS backend from P1-2. FAISS index stored on EFS alongside the git repo. Embedding via Bedrock `titan-embed-text-v2` (requires a VPC interface endpoint for Bedrock — add to Pulumi if not already present). **Deliverables:** - Semantic search plugin deployed with FAISS backend - Bedrock VPC endpoint added to Pulumi - FAISS index persists on EFS - FAISS stripped to AVX2-only to fit 250MB Lambda package limit (163MB total) **Acceptance criteria:** - [x] Semantic search endpoint returns relevant results - [x] FAISS index persists across Lambda invocations - [x] Reindex endpoint works (full rebuild from repo) - [x] Bedrock embedding calls succeed from VPC Lambda --- ### P1-7: Routing and TLS **Parallel group:** Phase 1 **Dependencies:** P1-3 **Target:** `wikibot-io` repo, `feat/P1-7-routing-tls` **Status: COMPLETE** **Description:** Configure API Gateway routing for all endpoints: web UI, REST API, MCP. Set up a custom domain with TLS via ACM. Route 53 DNS record pointing to API Gateway. **Deliverables:** - `infra/components/api_gateway.py` — routes for web UI, API, MCP - `infra/components/dns.py` — Route 53 record + ACM certificate - Pulumi outputs for endpoint URLs **Acceptance criteria:** - [x] `https://dev.wikibot.io/` serves Otterwiki web UI - [x] `https://dev.wikibot.io/api/v1/health` returns 200 - [x] `https://dev.wikibot.io/mcp` accepts MCP connections - [x] TLS certificate valid and auto-renewing (ACM) — TLSv1.3, Amazon RSA 2048, expires 2026-09-26 - [x] All routes pass through to correct Lambda handlers --- ### P1-8: Phase 1 E2E Test **Parallel group:** Phase 1 (final — all other P1 tasks must complete) **Dependencies:** P1-4, P1-5, P1-6, P1-7 **Target:** `wikibot-io` repo, `feat/P1-8-e2e` **Status: COMPLETE** (automated smoke test + manual gate validation) **Description:** End-to-end test covering the full single-user workflow: browser creates a page, API reads it, MCP reads it, semantic search finds it. **Deliverables:** - `tests/test_e2e.py` — automated smoke test (API CRUD, search, semantic search endpoint checks, MCP initialize) - Manual gate validation documented in [[Dev/Phase_1_Gate_Results]] **Note on test coverage:** The automated test (`tests/test_e2e.py`) is a per-subsystem smoke test, not a cross-cutting E2E. The cross-write-path criteria below were validated manually during the Phase 1 gate by human + agent testing, not by the automated test. MCP tests are `xfail` (bearer token not configured in test environment). **Acceptance criteria:** - [x] Create page via Otterwiki web UI → readable via API and MCP — *validated manually during gate* - [x] Create page via API → visible in web UI and searchable — *automated: API CRUD; manual: web UI visibility* - [x] Write via MCP → visible in web UI and API — *validated manually during gate* - [x] Semantic search finds pages by meaning, not just keywords — *automated: endpoint smoke; manual: relevance verified (distances 0.46–0.79)* - [x] Git history shows correct authorship for all write paths — *validated by gate agent: "Claude (MCP)" confirmed* ### P1-9: Self Hosting **Dependencies**: P1-8 **Target:** `wikibot-io` repo (config), dev wiki repo (content) **Status: COMPLETE** **Description:** Migrate the local development wiki from docker-compose to dev.wikibot.io. Use Otterwiki's built-in Git HTTP server (`GIT_WEB_SERVER=True`) to `git push` the dev wiki repo directly, preserving full git history. Platform config already set: `RETAIN_PAGE_NAME_CASE=True`, `TREAT_UNDERSCORE_AS_SPACE_FOR_TITLES=True`, `GIT_WEB_SERVER=True`. **Additional work done during P1-9:** - Fixed WSGI PATH_INFO encoding for non-ASCII characters (em dashes, etc.) - Implemented space→underscore filename normalization (otterwiki fork `feat/space-underscore-normalization`) - Added `resolve_filename` migration fallback in otterwiki-api - Renamed all 30 wiki pages to use underscores - Fixed MCP OAuth discovery routing (P1-9 addendum, see [[Tasks/P1-9_MCP_OAuth_Routing]]) **Deliverables:** - dev.wikibot.io hosts the complete dev wiki with full git history - Claude Code MCP config updated to use remote server - Local docker-compose dev wiki shut down **Acceptance Criteria:** - [x] `git push` succeeds with full history preserved - [x] User spot checks `https://dev.wikibot.io/` to confirm content looks correct - [x] Agent spot checks via remote MCP to confirm content matches local - [x] Semantic search returns results on remote instance (233 chunks, 30 pages) - [x] Agent can read/write wiki pages via remote MCP to continue Phase 2 work