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