feat(gastown): add org-level towns, rigs, and auth middleware#1153
Merged
feat(gastown): add org-level towns, rigs, and auth middleware#1153
Conversation
Contributor
Code Review SummaryStatus: 2 Issues Found | Recommendation: Address before merge Overview
Fix these issues in Kilo Cloud Issue Details (click to expand)WARNING
Other Observations (not in diff)Issues found in unchanged code that cannot receive inline comments:
Files Reviewed (14 files)
Reviewed by gpt-5.4-20260305 · 977,716 tokens |
src/app/(app)/organizations/[id]/gastown/OrgTownListPageClient.tsx
Outdated
Show resolved
Hide resolved
7403c1a to
ba3fb04
Compare
pandemicsyn
approved these changes
Mar 17, 2026
src/app/(app)/organizations/[id]/gastown/[townId]/settings/page.tsx
Outdated
Show resolved
Hide resolved
…stown UI Implement organization-owned towns as described in #447. Adds GastownOrgDO, org auth/town auth middleware, REST + tRPC endpoints for org towns and rigs, org-aware ownership verification, and Next.js org gastown pages.
…are ordering, org-aware rig CRUD - listUserOrgIds now filters out billing_manager memberships - Replace townOwnershipMiddleware with townAuthMiddleware for /api/towns/:townId/* catch-all - Add resolveRigOwnerStub to route rig CRUD to correct DO (user vs org) - Fix listRigs/createRig/deleteRig/deleteTown/ensureMayor to use resolveRigOwnerStub
…ive lookups Add orgMemberships claim to the gastown JWT, populated at token mint time from the DB. All org membership checks in the worker now read from JWT claims instead of querying Hyperdrive, eliminating DB round-trips from every org-town tRPC call and REST handler.
…r takeover for org gastown - createOrgTown now mints a kilocode_token on the TownDO config at creation time (matching personal createTown), so ensureMayor can start the mayor without waiting for rig creation. - GastownTownSidebar accepts basePath/backHref overrides for org routes. - AppSidebar detects /organizations/[id]/gastown/[townId] paths and renders the town-specific sidebar with org-prefixed navigation links.
…orrect team Add organizationId to StartAgentRequest and propagate it from TownConfig through container-dispatch to the agent's KILO_CONFIG_CONTENT as provider.kilo.options.kilocodeOrganizationId, matching how cloud-agent-next handles org billing attribution.
…ials use the creator's identity Org towns now set owner_user_id to the creator's user ID on the TownDO config. This ensures all credential flows (container JWT, kilocode token, agent callbacks) use the creator's identity for billing and auth. Also fixes ensureMayor falling back to an empty string for userId when owner_user_id was missing, which caused 401 errors on mayor tool calls.
handleMayorListRigs and verifyRigBelongsToTown now check the TownDO config for owner_type and route to GastownOrgDO for org-owned towns instead of always using GastownUserDO. Fixes empty rig list returned by gt_list_rigs when the mayor calls tools in an org town.
…n pages - Add fullBleed prop to OrganizationByPageLayout/OrganizationTrialWrapper to bypass the PageContainer max-width wrapper. All org gastown sub-pages now render edge-to-edge like personal gastown. - Add basePath prop to TownOverviewPageClient, TerminalBar, and MayorTerminalBar. Org pages pass /organizations/[id]/gastown/[townId] so all in-app navigation stays within the org context. - Fix hardcoded /gastown/ links in TownOverviewPageClient (rig click, agents link, topology rig select) and TerminalBar navigation map.
…fix auth gap, remove dead code - verifyRigOwnership now uses Promise.all for parallel org DO fan-out instead of sequential iteration (O(1) round-trip vs O(N)) - handleCreateOrgRig returns 401 when userId is missing instead of silently falling back to empty string - Delete dead org-membership.util.ts (replaced by JWT-based lookups) - Import UserRigRecord from db/tables instead of local re-declaration - Extract shared resolveTownOwnership to deduplicate resolveRigOwnerStub and verifyTownOwnership
…org towns - deleteTown now calls resolveTownOwnership and checks owner role for org towns, closing the auth gap where any member could delete via the generic endpoint - deleteRig does the same check via resolveTownOwnership on rig.town_id - verifyTownOwnership returns orgTown.created_by_user_id as owner_user_id instead of fabricating the calling user's ID
…d validation to org input schemas - handleCreateOrgRig now checks orgDO.getTownAsync(town_id) to verify the town belongs to the org before creating the rig - All organizationId tRPC input schemas now use z.string().uuid() for consistency with townId/rigId validation
…, UI role gating - handleDeleteOrgTown verifies town belongs to org BEFORE calling destroy - townAuthMiddleware rejects uninitialized towns (no owner set) - updateTownConfig strips ownership fields and requires owner role for org towns - OrgTownListPageClient hides delete button for non-owner members - OrganizationAppSidebar hides Gastown nav entry for billing_manager users
…ve dev-mode auth bypasses - createOrgRig now mints kilocode_token using the town's owner_user_id from TownDO config instead of the calling member's identity, preventing credential rotation when non-owner members add rigs. - Remove dev-mode auth bypasses for kilo/town/org middleware. The JWT flow works e2e in dev, and skipping auth created bugs (missing kiloUserId/kiloOrgMemberships on org REST handlers).
…n org towns - createRig, createOrgRig: only re-mint kilocode_token when the caller IS the town owner (has their api_token_pepper). Non-owner members keep the existing town token instead of overwriting with an unusable one. - sling, ensureMayor: refreshGitCredentials now uses the town owner's userId from TownDO config, not the calling member's identity. - configureRig userId uses the town owner for org towns.
… installations refreshGitCredentials now accepts an optional orgId parameter and passes it through to GIT_TOKEN_SERVICE.getTokenForRepo(). For org towns, the organization_id from TownDO config is passed so the installation lookup query can match org-owned GitHub App installations via the owned_by_organization_id column in platform_integrations.
…or org towns CreateRigDialog now accepts an optional organizationId prop. When set: - GitHub/GitLab repo queries use organizations.cloudAgentNext endpoints which call fetchGitHubRepositoriesForOrganization instead of the personal fetchGitHubRepositoriesForUser - The 'Connect GitHub or GitLab' link points to the org integrations page (/organizations/[id]/integrations) instead of /integrations The org town overview page passes organizationId through TownOverviewPageClient to CreateRigDialog.
New town config fields: - github_cli_pat: GitHub PAT used exclusively for gh CLI operations (PRs, issues). When set, GH_TOKEN prefers this over the integration token, so PRs appear under the user's GitHub identity. - git_author_name / git_author_email: override the commit author. When set, the user becomes the primary author and the AI agent name is exposed as GASTOWN_AI_AGENT_NAME for co-authorship trailers. - disable_ai_coauthor: omit the AI Co-authored-by trailer. Changes: - TownConfig + TownConfigUpdateSchema: new fields added - container-dispatch.ts: maps new fields to GITHUB_CLI_PAT, GASTOWN_GIT_AUTHOR_NAME, GASTOWN_GIT_AUTHOR_EMAIL, GASTOWN_DISABLE_AI_COAUTHOR env vars - agent-runner.ts: uses custom author if set, exposes AI agent identity for co-authorship, prefers GITHUB_CLI_PAT for GH_TOKEN - TownSettingsPageClient: new GitHub CLI and Commit Identity sections - router.d.ts: updated tRPC type declarations
- updateTownConfig tRPC now calls syncConfigToContainer() after saving, which pushes config-derived env vars (GIT_TOKEN, GITHUB_CLI_PAT, GASTOWN_GIT_AUTHOR_NAME/EMAIL, GASTOWN_DISABLE_AI_COAUTHOR) to the running container via TownContainerDO.setEnvVar(). New agent processes spawned after the update inherit the new values. - buildContainerConfig now includes github_cli_pat, git_author_name, git_author_email, disable_ai_coauthor in the X-Town-Config header.
…r, read-only settings Security: - getTownConfig masks kilocode_token, github_cli_pat, git_auth tokens, and env_vars for non-owner org members (shows ****last4) - REST createOrgRig no longer accepts kilocode_token in request body — the town's existing token (minted by owner) is always used Container: - Add TownContainerDO.deleteEnvVar() for removing cleared settings - syncConfigToContainer now calls deleteEnvVar when a value is cleared, preventing stale credentials from persisting UI: - Org town settings page passes role from OrganizationByPageLayout - TownSettingsPageClient accepts readOnly prop — hides save buttons and shows 'View only' badge for non-owner org members
8ccefc9 to
27b75f5
Compare
83 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds organization-level town ownership to Gastown, enabling any org member to create, manage, and interact with shared towns. This is the foundational infrastructure for the collaborative multi-player Gastown described in #447.
Summary
GastownOrgDO— a per-org Durable Object (SQLite, keyed by orgId) storing org-owned towns and rigs, with a watchdog alarm to periodically health-check town alarms. Registered asGASTOWN_ORGbinding with a v2 migration tag inwrangler.jsonc.POST /api/gastown/token) now queries the user's org memberships and includes them as anorgMembershipsclaim. All org membership checks in the worker read from JWT claims instead of querying Hyperdrive, eliminating DB round-trips from every org-town operation.org-auth.middleware.ts: verifies org membership via JWT claims, blocksbilling_managerrole, and setsorgId/orgRoleon the Hono context.town-auth.middleware.ts: verifies the caller owns or is an org member of a town for/api/towns/:townId/*routes via JWT claims; handles both personal and org-owned towns with legacy fallback. Replaces the oldtownOwnershipMiddlewarefor the catch-all route.org-towns.handler.tswith 8 REST handlers (create/list/get/delete for towns and rigs) scoped under/api/orgs/:orgId/*.listOrgTowns,createOrgTown,deleteOrgTown,listOrgRigs,createOrgRigwith per-procedure org membership checks (all via JWT).verifyTownOwnershipandverifyRigOwnershipto be org-aware: personal towns use the fast-path user DO lookup; org towns fall back to TownDO config + JWT membership verification.resolveRigOwnerStub— a helper that returns the correct DO stub (GastownUserDO or GastownOrgDO) for rig CRUD operations based on town ownership. Used bylistRigs,createRig,deleteRig,deleteTown, andensureMayor./organizations/[id]/gastown— town list/create/delete page, with sub-pages reusing existing personal gastown components wrapped inOrganizationByPageLayout.OrganizationAppSidebar.Closes #447 (org-level towns portion).
Verification
pnpm typecheck— all 30 workspace projects passpnpm lint— clean across all packagesprettier --check— all files formatted correctlyVisual Changes
N/A
Reviewer Notes
resolveRigOwnerStubabstraction avoidsif (isOrg) … else …branching across procedures. It resolves the correct DO stub once, and callers use a commonRigOwnerStubinterface for rig/town CRUD.deleteOrgTowntRPC and personaldeleteTowntRPC both now calltownStub.destroy()before deleting the owner record.GastownOrgDOreuses theuser_rigstable schema for org rigs. This works because each DO has isolated SQLite storage, but the naming is semantically misleading. A follow-up could rename this to a genericrigstable.org-membership.util.tsstill exists but is no longer imported by any worker code — it can be removed in a cleanup pass.