Skip to content

Root bootstrap and separation of duties

Status: implemented as a follow-up to SaaS phase 4d (see Implementation below). Phase 4d removed implicit platform access, but a platform administrator with explicit organisation membership could still act on repository grants; the bootstrap root account no longer can. Phase 4e telemetry continues separately; root enforcement is not part of its completion claim.

Identity and permitted work

Persist an immutable bootstrap-root account classification in PostgreSQL. It is separate from the mutable platform-administrator flag and from organisation roles. Neither granting platform administration to a standard user nor changing the root account's email changes this classification. Identify the existing bootstrap account from authoritative setup history during migration; ambiguous installations require an explicit audited migration decision, never a guess from names or email addresses.

The root account may manage global settings, platform upgrades and health, telemetry, security and audit logs, and system debugging or disaster recovery. These capabilities do not include reading repository symbols, summaries, reviews, memory or other tenant content. Recovery cannot impersonate a standard user or create a grant that lets root perform business work.

Root must not run ordinary workflows or create business data: repository registration or indexing, code queries, reviews, memory writes or recalls, architecture proposals, annotations, reuse records and organisation business settings are denied. Explicit organisation membership, a direct repository grant, team membership, an API key, a queued job or a platform-admin flag cannot bypass this restriction. Standard users continue to use ordinary membership and repository-permission checks.

Initial setup and handoff

A root session that has not completed bootstrap enters a dedicated setup flow. Its immediate task is to provision an organisation and at least one distinct, active standard-user account associated with that organisation. Grant that standard user the organisation-owner role so the operator can complete normal setup after handoff. The root account does not become an organisation member or repository administrator as a side effect.

Create the organisation, standard account, owner membership, setup completion state and audit event in one transaction. Validate the password through the existing account policy; never log, return or persist clear-text credentials beyond the operator's submitted request. Keep completion idempotent so retries cannot create extra accounts or organisations. Concurrent submissions must commit at most one completed bootstrap. Failure leaves a retryable setup state without partial ownership.

After successful provisioning, end the root setup session and clear its browser cookie. Show: “Setup is complete. Sign in with the standard account for organisation work.” Do not mint a standard-user session or automatically impersonate that account. The operator must authenticate with the new credentials. Later root logins open the administration area and retain only governance capabilities. Setup completion cannot be reset through ordinary user APIs; a disaster-recovery procedure must be explicit and audited.

For existing installations, require a verified active standard owner before completing migration or revoking the final working setup path. Do not silently convert a root session into a standard session. Retain an audited governance recovery path if the standard owner loses access, while still requiring a fresh standard-user login for business tasks.

Enforcement

Add the root restriction to the shared accounts authorization layer and enforce it before repository or organisation business checks. API route policies and the MCP tool policy table must classify every action explicitly. Unknown actions fail closed. HTTP and stdio MCP authentication, resumed sessions and queued/background work must recheck the account classification. A root credential must never authorize deferred tenant work.

The only root tenant mutation exception is the narrow initial-bootstrap transaction. Account suspension and credential recovery remain governance actions, with existing last-active-admin protections. Organisation permission recovery must target a distinct standard user and must not disclose tenant content or give root a business session.

Return a consistent permission error for an ordinary action attempted as root: “Root accounts cannot perform organisation work. Sign out and sign in with a standard account in your organisation.” Audit the denied action without logging credentials or request content. The UI hides business navigation and provides the sign-out action; backend enforcement remains authoritative.

Acceptance checks

  • Fresh setup cannot finish without an organisation and an active standard owner. Invalid credentials, audit failure and concurrent retries leave no partial or duplicate bootstrap records.
  • A completed bootstrap revokes the setup session, clears its cookie and requires a real standard-user login. Refreshing the completion page cannot restore the session.
  • Root cannot execute any business API route or MCP tool, including read-only tools, with direct grants, team grants, organisation membership, API keys, resumed sessions or forged tenant fields.
  • Jobs queued before a root restriction or migration are denied before execution and before persistence or push. Standard-user jobs continue under their own current permissions.
  • Root can use the explicit governance allowlist, inspect scoped platform telemetry and audit logs, and perform audited account recovery without receiving repository contents.
  • Changing the mutable platform-admin flag never changes bootstrap identity. Migration ambiguity fails closed without locking operators out of the documented recovery procedure.
  • Single-tenant/offline operation has an explicit documented applicability rule; it must not quietly treat a root credential as unrestricted offline access.

Complete this follow-up with account-store contracts, API/MCP authorization tests, UI handoff tests, migration and recovery tests, then commit, push and open its own pull request. Kubernetes work stays in the final deployment phase.

Implementation

Classification. Migration 0010_bootstrap_root.sql adds users.bootstrap_root with a unique partial index (one root) and a trigger that refuses clearing it, plus a one-row installation_setup table (pending, complete, ambiguous) whose completed state a second trigger makes permanent. The memory store mirrors both rules. PlatformAdmin stays separate: root is created with it for governance, and granting or revoking it never changes the classification.

First-start administrator. A fresh installation creates root itself at API start-up (Service.EnsureInitialAdmin, initialadmin.go): user admin, admin@codesynapse.local, users.auto_created, with the password from CODESYNAPSE_ADMIN_PASSWORD (compose writes a random one to .env; the chart generates the <release>-admin Secret) or, if unset, a random one logged once. CODESYNAPSE_AUTO_ADMIN=false restores the manual flow below. Only this auto-created root can be replaced: POST /api/v1/auth/replace-admin (Service.ReplaceRoot) creates the operator's own account, moves the root classification and the setup record to it (migration 0020 lets a completed setup change root_user_id only), deletes the old account and ends its sessions, audited as setup.root_replaced. codesynapse-api reset-admin (Service.ResetRoot) is the operator's audited way back in. See First run and sign-in.

Fresh setup without it. The first sign-up creates root, with platform administration and no organisation, inside the serialized policy transaction, so concurrent first sign-ups produce one root. While root is the only account, other sign-ups are refused (ErrSetupRequired). POST /api/v1/auth/setup (Service.CompleteSetup) validates a distinct standard owner, creates the organisation (or uses an existing one on a migrated installation), makes the owner its owner, marks setup complete, deletes root's sessions and audits setup.complete, all in one transaction; the handler clears the cookie and returns the completion message. A retry gets ErrSetupComplete; failures leave nothing behind. The dashboard never forces setup: a welcome guide (WelcomeSteps.vue) offers "replace the admin" (/replace-admin) and "create the first organisation" (/setup) as skippable steps on root's landing page and reopens from the account menu; the handover lands on /setup/done, which needs no session.

Enforcement. businessAllowed returns ErrRootAccount for root (and, on an ambiguous installation, every platform administrator). It runs on the freshly stored account in accessOf (repositories, MCP reach, jobs, schedules), RoleIn (organisation work, keys), API-key authentication (MCP and API keys), organisation creation and execution sessions. Root's grants do not count as a working repository administrator, and root's ownership does not count toward the last-owner rule. In the API every route is classified: s.route policies default to business unless wrapped in governance(...) (version, health, settings, platform telemetry, rules, AI routing), and s.account names public, governance (profile, password, administration, setup, owner recovery) or business routes. A refused call answers 403 with the plan's sentence and audits root.denied with the route pattern only. MCP refuses root at key verification on every request, so live and resumed sessions stop at once; the response body carries the same sentence, and the per-tool authorization layer refuses root independently. The dashboard hides business navigation, does not poll repositories for root and shows a notice; the API remains authoritative.

Recovery. POST /api/v1/admin/orgs/{id}/owner (RecoverOrgOwner, audited) promotes an existing, active standard member to owner; it gives root no membership or content. Suspension, resumption and the last-active-admin rule are unchanged.

Existing installations. Service.ReconcileBootstrap runs once at API start-up. It records the root only when two sources agree: the earliest user.signup audit event names the earliest-created account, and that account is a platform administrator. Setup is then complete if an active standard owner exists, pending otherwise (root hands over an existing organisation through the setup page). Anything else, such as history removed by audit retention, records ambiguous: every platform administrator is limited to governance until an operator runs codesynapse-api designate-root --email <root> (DesignateRoot, audited, once). Standard users are never affected, and the operator path needs only database access.

Single-tenant mode. Without accounts there is no root: the installation token is the operator credential behind the outer bearer check, as before. In accounts mode the installation token is not an account; it keeps its existing empty tenant scope.

Tests. accountstest.RunBootstrap runs against memstore and PostgreSQL (fresh setup, concurrent first sign-ups and handovers, denial through memberships, grants, keys and execution sessions, governance and owner recovery, the three migration outcomes, queued work after migration). TestRootRefusedEveryBusinessRoute calls every registered business route as root with a membership, a direct grant and an organisation header; TestEveryRouteIsClassified rejects unclassified registrations; TestAccountsFlow covers the HTTP handover; TestRootIsDeniedEveryTool covers a live MCP session that becomes restricted. The dashboard pages have no automated tests (the web app has no test framework); they pass the typecheck and production build.