Properties
## 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 GSIsapp/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:
- 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.
- 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 resolutionapp/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 handlingapp/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 endpointsapp/management/wiki_init.py— repo initialization and template populationapp/management/token.py— bearer token generation and hashing- Unit tests with moto +
/tmpfilesystem - Integration test: create wiki, list wikis, delete wiki
Acceptance criteria:
POST /admin/wikiscreates wiki (EFS repo + DynamoDB records + bootstrap template)GET /admin/wikislists caller's wikisGET /admin/wikis/{slug}returns wiki details with MCP endpoint URLDELETE /admin/wikis/{slug}removes repo and recordsPOST /admin/wikis/{slug}/tokenregenerates bearer tokenPOST /admin/wikis/{slug}/aclgrants access (by email)DELETE /admin/wikis/{slug}/acl/{user_id}revokes accessGET /admin/wikis/{slug}/acllists 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 wiki1user1.wikibot.io/wiki1/api/v1/health→ wiki1's APIuser1.wikibot.io/wiki1/mcp→ wiki1's MCP endpointuser2.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 pageapp/templates/wiki/Meta/Wiki Usage Guide.md— tool reference, session protocol, conventionsapp/templates/wiki/Meta/Page Template.md— reference page showing frontmatter and structureapp/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-permissionsheaders → 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=trueenables 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 usingclickortyper- Commands:
wiki create,wiki list,wiki delete,wiki token,wiki grant,wiki revoke - Auth: stores platform JWT in
~/.wikibot/tokenafter login flow - Unit tests with mocked HTTP
Acceptance criteria:
wiki create <slug> <name>creates wiki, displays MCP token and connection instructionswiki listshows user's wikis with page countswiki delete <slug>deletes wiki (with confirmation prompt)wiki token <slug>regenerates and displays MCP bearer tokenwiki grant <slug> <email> <role>grants accesswiki 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 written to Dev/Phase 2 Summary per Agent Conventions documentation loop
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