Commit c8a262

2026-03-13 01:50:52 Claude (Dev): [mcp] Port Phase 2 tasks to wiki
/dev/null .. tasks/phase 2.md
@@ 0,0 1,277 @@
+ ## 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 2: Multi-Tenancy and Auth
+
+ **Goal:** Multiple users, multiple wikis, ACL enforcement. Management API and CLI.
+
+ ### P2-1: DynamoDB Tables
+
+ **Parallel group:** Phase 2 start (parallel with P2-6, P2-8)
+ **Dependencies:** P0-1 (VPC with DynamoDB gateway endpoint)
+ **Target:** `wikibot-io` repo, `feat/P2-1-dynamodb`
+
+ **Description:**
+ Create DynamoDB tables for Users, Wikis, and ACLs as specified in the PRD data model. Include GSIs for lookup patterns: user by OAuth provider+sub, wikis by owner, ACLs by wiki.
+
+ **Deliverables:**
+ - `infra/components/dynamodb.py` — table definitions with GSIs
+ - `app/models/user.py`, `app/models/wiki.py`, `app/models/acl.py` — data access layer
+ - Unit tests with moto: CRUD operations, GSI queries, conditional writes
+
+ **Acceptance criteria:**
+ - [ ] Users table: create, read, update by ID; lookup by (oauth_provider, oauth_provider_sub)
+ - [ ] Wikis table: create, read, update, delete by (owner_id, wiki_slug); list by owner
+ - [ ] ACLs table: create, read, delete by (wiki_id, grantee_id); list by wiki
+ - [ ] PITR enabled on all tables
+ - [ ] Unit tests pass with moto
+ - [ ] Integration test: CRUD against real DynamoDB
+
+ ---
+
+ ### P2-2: Auth Middleware
+
+ **Parallel group:** Phase 2 (parallel with P2-1)
+ **Dependencies:** P0-6 (WorkOS setup)
+ **Target:** `wikibot-io` repo, `feat/P2-2-auth-middleware`
+
+ **Description:**
+ Platform authentication middleware that handles two token flows:
+
+ 1. **Browser/API — Platform JWT:** Validate JWTs signed with our RS256 key. Key stored in Pulumi config (dev) or Secrets Manager (prod). Middleware extracts user identity from JWT claims.
+ 2. **MCP — WorkOS access token:** Validate against WorkOS JWKS. FastMCP integration handles this for MCP routes.
+
+ Both flows resolve to a User record in DynamoDB. The middleware sets a request-local user context for downstream handlers.
+
+ Also implement the platform JWT issuance: `/auth/token` endpoint that exchanges a WorkOS auth code for a platform JWT.
+
+ **Deliverables:**
+ - `app/auth/middleware.py` — JWT validation, user resolution
+ - `app/auth/jwt.py` — JWT creation and validation (RS256)
+ - `app/auth/workos.py` — WorkOS integration (auth code exchange, user profile retrieval, raw provider sub storage)
+ - Unit tests with mocked WorkOS and mocked JWT validation
+ - Integration test: full auth flow (WorkOS → platform JWT → authenticated request)
+
+ **Acceptance criteria:**
+ - [ ] Valid platform JWT → request proceeds with user context
+ - [ ] Expired/invalid JWT → 401
+ - [ ] WorkOS access token validated against JWKS
+ - [ ] Auth code exchange returns platform JWT with correct claims
+ - [ ] User record created in DynamoDB on first login (with oauth_provider, oauth_provider_sub)
+ - [ ] Subsequent logins resolve to existing user record
+ - [ ] Unit tests pass
+
+ ---
+
+ ### P2-3: ACL Enforcement Middleware
+
+ **Parallel group:** Phase 2
+ **Dependencies:** P2-1, P2-2
+ **Target:** `wikibot-io` repo, `feat/P2-3-acl-middleware`
+
+ **Description:**
+ Authorization middleware that checks whether the authenticated user can access the requested wiki with the requested permission level. Resolves wiki from URL path, looks up ACL in DynamoDB, maps ACL role to Otterwiki permission headers.
+
+ Handles public wikis: injects synthetic anonymous user with READ permission.
+
+ **Deliverables:**
+ - `app/auth/acl.py` — ACL lookup, role-to-permission mapping, public wiki handling
+ - `app/auth/permissions.py` — permission header injection for Otterwiki PROXY_HEADER mode
+ - Unit tests with moto: all role mappings, public wiki access, no-grant rejection
+
+ **Acceptance criteria:**
+ - [ ] Owner → READ, WRITE, UPLOAD, ADMIN headers
+ - [ ] Editor → READ, WRITE, UPLOAD headers
+ - [ ] Viewer → READ header
+ - [ ] No grant + private wiki → 403
+ - [ ] No grant + public wiki → READ (synthetic anonymous user)
+ - [ ] Invalid wiki slug → 404
+ - [ ] ACL lookup uses DynamoDB, not hardcoded
+
+ ---
+
+ ### P2-4: Management API
+
+ **Parallel group:** Phase 2
+ **Dependencies:** P2-1, P2-3
+ **Target:** `wikibot-io` repo, `feat/P2-4-management-api`
+
+ **Description:**
+ REST API for wiki lifecycle management. Authenticated via platform JWT. Endpoints as specified in the PRD's Management API section.
+
+ On wiki creation: initialize bare git repo on EFS, populate with bootstrap template, create DynamoDB records, generate MCP bearer token (bcrypt hash stored, plaintext returned once).
+
+ **Deliverables:**
+ - `app/management/routes.py` — all management endpoints
+ - `app/management/wiki_init.py` — repo initialization and template population
+ - `app/management/token.py` — bearer token generation and hashing
+ - Unit tests with moto + `/tmp` filesystem
+ - Integration test: create wiki, list wikis, delete wiki
+
+ **Acceptance criteria:**
+ - [ ] `POST /admin/wikis` creates wiki (EFS repo + DynamoDB records + bootstrap template)
+ - [ ] `GET /admin/wikis` lists caller's wikis
+ - [ ] `GET /admin/wikis/{slug}` returns wiki details with MCP endpoint URL
+ - [ ] `DELETE /admin/wikis/{slug}` removes repo and records
+ - [ ] `POST /admin/wikis/{slug}/token` regenerates bearer token
+ - [ ] `POST /admin/wikis/{slug}/acl` grants access (by email)
+ - [ ] `DELETE /admin/wikis/{slug}/acl/{user_id}` revokes access
+ - [ ] `GET /admin/wikis/{slug}/acl` lists grants
+ - [ ] Tier limits enforced (1 wiki free, 500 pages, 3 collaborators)
+ - [ ] Bearer token shown once on create, stored as bcrypt hash
+
+ ---
+
+ ### P2-5: Per-Wiki Routing
+
+ **Parallel group:** Phase 2
+ **Dependencies:** P1-7, P2-3
+ **Target:** `wikibot-io` repo, `feat/P2-5-wiki-routing`
+
+ **Description:**
+ Multi-tenant URL routing: `{username}.wikibot.io/{wiki}/` routes to the correct wiki instance. API Gateway with wildcard subdomain (`*.wikibot.io`). Lambda resolver extracts username and wiki slug from the request, loads wiki config from DynamoDB, sets up Otterwiki with the correct EFS repo path.
+
+ **Deliverables:**
+ - API Gateway wildcard domain configuration
+ - `app/routing/resolver.py` — extract user + wiki from hostname + path, load wiki config
+ - ACM wildcard certificate for `*.wikibot.io`
+ - Route 53 wildcard DNS record
+ - Integration test: requests to different subdomains resolve to different wikis
+
+ **Acceptance criteria:**
+ - [ ] `user1.wikibot.io/wiki1/` → user1's wiki1
+ - [ ] `user1.wikibot.io/wiki1/api/v1/health` → wiki1's API
+ - [ ] `user1.wikibot.io/wiki1/mcp` → wiki1's MCP endpoint
+ - [ ] `user2.wikibot.io/wiki2/` → user2's wiki2
+ - [ ] Nonexistent user/wiki → 404
+ - [ ] Wildcard TLS works
+
+ ---
+
+ ### P2-6: Wiki Bootstrap Template
+
+ **Parallel group:** Phase 2 start (independent, can start immediately)
+ **Dependencies:** None
+ **Target:** `wikibot-io` repo, `feat/P2-6-bootstrap-template`
+
+ **Description:**
+ Create the starter page set that new wikis are initialized with. Parameterized by wiki name, purpose, and category set. Reference the Third Gulf War `Meta/Wiki Usage Guide` (read via MCP) as the exemplar for structure and tone, but generalize for any research domain.
+
+ **Deliverables:**
+ - `app/templates/wiki/Home.md` — parameterized landing page
+ - `app/templates/wiki/Meta/Wiki Usage Guide.md` — tool reference, session protocol, conventions
+ - `app/templates/wiki/Meta/Page Template.md` — reference page showing frontmatter and structure
+ - `app/management/template.py` — template engine (string substitution for wiki name, purpose, categories)
+ - Unit tests: template rendering with various parameters
+
+ **Acceptance criteria:**
+ - [ ] Template renders correctly with wiki name and purpose substituted
+ - [ ] Wiki Usage Guide covers all MCP tools, session protocol, frontmatter schema, WikiLink syntax, page size guidance, gardening
+ - [ ] Page Template shows correct frontmatter format
+ - [ ] Default category set matches PRD (actor, event, trend, hypothesis, variable, reference, index)
+ - [ ] Template is generic — no Third Gulf War specific content
+
+ ---
+
+ ### P2-7: Otterwiki PROXY_HEADER Integration
+
+ **Parallel group:** Phase 2
+ **Dependencies:** P2-3
+ **Target:** `otterwiki` fork, PR to `main` (if changes needed); `wikibot-io` for config
+
+ **Description:**
+ Configure Otterwiki to run in `PROXY_HEADER` auth mode, reading user identity and permissions from headers set by the ACL middleware (P2-3). Verify that all permission levels work correctly: admin sees admin panel, editors can edit, viewers can only read.
+
+ **Deliverables:**
+ - Otterwiki configuration for PROXY_HEADER mode
+ - Verification that header-based auth works with all permission levels
+ - Any needed patches to Otterwiki's PROXY_HEADER handling (upstream PR if changes required)
+
+ **Acceptance criteria:**
+ - [ ] `x-otterwiki-email`, `x-otterwiki-name`, `x-otterwiki-permissions` headers → correct user context
+ - [ ] ADMIN permission → admin panel accessible
+ - [ ] WRITE permission → can edit pages
+ - [ ] READ permission → can view but not edit
+ - [ ] No permission headers → rejected (defense in depth)
+
+ ---
+
+ ### P2-8: Admin Panel Hiding
+
+ **Parallel group:** Phase 2 start (independent)
+ **Dependencies:** None
+ **Target:** `otterwiki` fork, `wikibot/prod` branch
+
+ **Description:**
+ Hide admin panel sections that conflict with platform-managed settings: Repository Management, Permissions and Registration, User Management, Mail Preferences. Keep Application Preferences, Sidebar Preferences, Content and Editing.
+
+ Override the admin navigation template to hide disabled sections. Return 404 from disabled routes in middleware.
+
+ **Deliverables:**
+ - Modified `templates/settings.html` — conditionally render nav items based on config flag
+ - Middleware/decorator on disabled routes returning 404
+ - Config flag: `PLATFORM_MODE=true` enables hiding (off by default — doesn't affect standalone Otterwiki)
+ - Tests: disabled routes return 404, enabled routes work normally
+
+ **Acceptance criteria:**
+ - [ ] With `PLATFORM_MODE=true`: disabled sections hidden from nav, routes return 404
+ - [ ] With `PLATFORM_MODE=false` (or unset): all sections visible (backward compatible)
+ - [ ] Enabled sections (Application Preferences, Sidebar Preferences, Content and Editing) work normally in both modes
+ - [ ] Existing test suite passes
+
+ ---
+
+ ### P2-9: CLI Tool
+
+ **Parallel group:** Phase 2
+ **Dependencies:** P2-4
+ **Target:** `wikibot-io` repo, `feat/P2-9-cli`
+
+ **Description:**
+ Command-line tool for wiki management. Calls the Management API. Used as the MVP frontend before the SPA is built.
+
+ **Deliverables:**
+ - `app/cli/wiki.py` — CLI commands using `click` or `typer`
+ - Commands: `wiki create`, `wiki list`, `wiki delete`, `wiki token`, `wiki grant`, `wiki revoke`
+ - Auth: stores platform JWT in `~/.wikibot/token` after login flow
+ - Unit tests with mocked HTTP
+
+ **Acceptance criteria:**
+ - [ ] `wiki create <slug> <name>` creates wiki, displays MCP token and connection instructions
+ - [ ] `wiki list` shows user's wikis with page counts
+ - [ ] `wiki delete <slug>` deletes wiki (with confirmation prompt)
+ - [ ] `wiki token <slug>` regenerates and displays MCP bearer token
+ - [ ] `wiki grant <slug> <email> <role>` grants access
+ - [ ] `wiki revoke <slug> <email>` revokes access
+ - [ ] Login flow works (opens browser for OAuth, receives callback)
+
+ ---
+
+ ### P2-10: Phase 2 E2E Test
+
+ **Parallel group:** Phase 2 (final)
+ **Dependencies:** All P2 tasks
+ **Target:** `wikibot-io` repo, `feat/P2-10-e2e`
+
+ **Description:**
+ End-to-end test: two users, two wikis, ACL enforcement. Verify complete isolation between tenants.
+
+ **Deliverables:**
+ - `tests/e2e/test_phase2.py`
+ - Results documented in wiki
+
+ **Acceptance criteria:**
+ - [ ] User A creates wiki, writes a page → User B cannot read it (403)
+ - [ ] User A grants User B editor access → User B can read and write
+ - [ ] User A revokes access → User B gets 403 again
+ - [ ] User A's wiki is completely invisible to User B (not in list, not in search)
+ - [ ] Public wiki toggle: User B can read (but not write) User A's public wiki without a grant
+ - [ ] MCP access respects same ACL rules
+ - [ ] Tier limits enforced: free user cannot create a second wiki
\ No newline at end of file
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9