Files
kandev/docs/specs/feature-toggles/spec.md
T
2026-06-15 13:33:45 +01:00

294 lines
11 KiB
Markdown

---
status: draft
created: 2026-06-14
owner: tbd
---
# Feature Toggles
## Why
Users can already opt into runtime features through environment variables and
`profiles.yaml`, but those controls are invisible from the app and require
terminal access. Users need a settings page that explains experimental
features, lets them choose install-level overrides, and clearly handles the
restart required for startup-gated features such as Office mode and Debug mode.
## What
- Kandev adds a **Feature Toggles** settings page at
`/settings/system/feature-toggles`.
- The page shows user-facing runtime toggles, not every profile/env knob.
- V1 exposes two user-facing toggles:
- **Office mode** — enables the autonomous-agent Office surface.
- **Debug mode** — enables local diagnostic/debug behavior, including agent
message debug logs.
- Office mode is marked **Experimental** and includes a concise description of
what it does and the risks of enabling it.
- Debug mode is a single user-facing toggle. Agent message debug logs are part
of Debug mode, not a duplicate top-level toggle.
- Every v1 toggle requires restart before the changed value takes effect.
- The page distinguishes effective value, saved override, default value, and
environment-locked value.
- Explicit environment variables remain authoritative. If an env var controls a
flag, the UI shows the flag as locked and does not allow overriding it.
- Users can reset a saved override back to the profile/env default.
- After saving any restart-required change, the UI shows a persistent restart
banner and offers a restart action when Kandev can safely restart itself.
- If automatic restart is unsupported, the UI shows manual restart guidance.
- `/api/v1/features` continues to return effective feature booleans for SSR
gating and existing `useFeature()` callers.
## Tech stack
- Backend: Go, Gin HTTP handlers, SQLite persistence.
- Frontend: Next.js App Router, React, Zustand, `@kandev/ui` shadcn
components.
- E2E: Playwright under `apps/web/e2e`.
- Runtime defaults: `profiles.yaml` embedded by `apps/backend/internal/profiles`.
## Commands
```bash
make fmt
make typecheck test lint
make -C apps/backend test
cd apps && pnpm --filter @kandev/web typecheck
cd apps && pnpm --filter @kandev/web lint
cd apps/web && pnpm e2e -- settings/feature-toggles.spec.ts settings/mobile-feature-toggles.spec.ts
```
## Project structure
```text
apps/backend/internal/runtimeflags/ Runtime flag registry, store, service, handlers
apps/backend/internal/system/restart/ Restart capability/request support
apps/backend/cmd/kandev/ Startup override application and route wiring
apps/web/app/settings/system/feature-toggles/page.tsx
apps/web/components/settings/system/feature-toggles-settings.tsx
apps/web/lib/api/domains/runtime-flags-api.ts
docs/decisions/0018-runtime-settings-overrides.md
docs/decisions/0019-restart-supervisor.md
```
Planned E2E coverage:
```text
apps/web/e2e/settings/feature-toggles.spec.ts TODO
apps/web/e2e/settings/mobile-feature-toggles.spec.ts TODO
```
## Code style
Use typed contracts at the backend boundary and keep env-var knowledge in the
backend registry rather than duplicating it in React components.
```go
type RuntimeFlagDefinition struct {
Key string
EnvVar string
Kind RuntimeFlagKind
Label string
Description string
Stability RuntimeFlagStability
RiskLevel RuntimeFlagRiskLevel
RiskDescription string
RestartRequired bool
Mutable bool
}
```
Frontend components render the API shape and avoid hardcoding flag-specific
logic except for layout/grouping.
## Data model
Feature Toggle overrides are install-scoped and stored in SQLite.
`runtime_flag_overrides`
| Field | Type | Constraint |
|---|---|---|
| `key` | `TEXT` | primary key, e.g. `features.office` |
| `value` | `INTEGER` | required, `0` or `1` |
| `created_at` | `DATETIME` | required |
| `updated_at` | `DATETIME` | required |
Absence of a row means "use the effective default". A `false` row is a real
override and must not be treated as missing.
## API surface
`GET /api/v1/runtime-flags`
Returns all user-facing runtime toggle states and metadata.
```json
{
"flags": [
{
"key": "features.office",
"kind": "feature",
"label": "Office mode",
"description": "Enables autonomous agent office workflows and related settings.",
"stability": "experimental",
"risk_level": "medium",
"risk_description": "Office mode is still evolving. Workflows, routes, and background automation may change between releases and should be reviewed before relying on them.",
"effective_value": false,
"default_value": false,
"override_value": true,
"source": "override",
"env_var": "KANDEV_FEATURES_OFFICE",
"env_locked": false,
"restart_required": true,
"requires_restart_to_apply": true
}
]
}
```
`PATCH /api/v1/runtime-flags/:key`
```json
{ "override": true }
```
`override` can be `true`, `false`, or `null`. `null` clears the saved override.
Unknown keys return `404`. Env-locked keys reject writes with `409`.
`GET /api/v1/system/restart-capability`
```json
{
"supported": true,
"mode": "cli",
"reason": ""
}
```
`mode` is `cli`, `systemd`, `launchd`, or `unsupported`.
`POST /api/v1/system/restart`
Requests a restart. The response is flushed before shutdown starts so the UI
can enter a waiting state and poll `/health`.
`GET /api/v1/features`
Existing contract remains unchanged: effective feature booleans only.
## State machine
Runtime flag state:
```text
profile default -> override saved -> restart pending -> restarted/effective
\-> override cleared -> restart pending -> restarted/default
```
Restart request state:
```text
idle -> requested -> backend unavailable -> backend healthy -> complete
\-> timeout -> manual recovery shown
```
## Permissions
Kandev is single-user in v1. Every user with settings access can view and change
Feature Toggles. Future multi-user support must restrict Feature Toggles,
especially Debug mode, to admins.
## Failure modes
- **Environment-locked flag** — UI disables the switch and shows the controlling
env var. API rejects override writes with `409`.
- **Override store unavailable** — runtime flags API fails with a recoverable
error; existing `/api/v1/features` continues to use startup-effective config.
- **Restart unsupported** — restart banner shows manual restart instructions
and no dead action button.
- **Restart requested while one is pending** — API rejects or idempotently
reports the pending restart.
- **Backend does not return after restart** — frontend times out and shows
manual recovery guidance.
- **Debug mode enabled** — UI warns that local diagnostic endpoints and agent
message logs may include sensitive prompt, file, and tool content.
## Persistence guarantees
- Saved overrides survive Kandev restarts.
- Effective runtime values are applied at backend startup.
- V1 toggles are startup-gated; changing a value does not affect already
registered routes, constructed services, or running agentctl processes until
restart.
- Environment variables always win over saved overrides.
## Testing strategy
- Backend unit tests cover registry metadata, override persistence, precedence,
env locking, and pending-restart computation.
- Backend handler tests cover runtime flag APIs and restart capability/request
behavior.
- Frontend component tests cover Office experimental copy, Debug mode copy,
env-locked rows, save/reset actions, and restart banner states.
- Desktop and mobile Playwright tests cover the user workflow from settings
navigation through saving a restart-required toggle.
## Boundaries
- Always: keep `profiles.yaml` as immutable shipped defaults.
- Always: keep env vars authoritative over DB overrides.
- Always: show restart-required state for v1 toggles.
- Always: include mobile coverage for the settings page.
- Ask first: adding a new external flag service or changing the auth model.
- Ask first: exposing mocks/e2e tuning as user-facing toggles.
- Never: make the Go backend fork an unmanaged replacement process for restart.
- Never: expose agent message debug logs as a separate top-level toggle in v1.
## Scenarios
- **GIVEN** Office mode is using the production default, **WHEN** the user opens
`/settings/system/feature-toggles`, **THEN** Office mode appears off with a
`Default` source, an `Experimental` badge, and risk text.
- **GIVEN** Office mode is off, **WHEN** the user enables it, **THEN** the
override is saved and the page shows that restart is required before Office
mode takes effect.
- **GIVEN** `KANDEV_FEATURES_OFFICE=true` is set in the environment, **WHEN** the
user opens Feature Toggles, **THEN** Office mode appears on, env-locked, and
cannot be changed from the UI.
- **GIVEN** Debug mode is off, **WHEN** the user enables it, **THEN** the page
explains that local diagnostic endpoints and agent message debug logs will be
enabled after restart.
- **GIVEN** a toggle override exists, **WHEN** the user clicks `Reset to default`,
**THEN** the override is removed and the page shows restart-required state if
the effective runtime value differs from the restored default.
- **GIVEN** restart capability is supported, **WHEN** the user clicks
`Restart Kandev`, **THEN** the UI enters `Restarting...`, polls `/health`, and
refreshes the page after the backend returns healthy.
- **GIVEN** restart capability is unsupported, **WHEN** a restart-required change
is saved, **THEN** the UI shows manual restart instructions instead of a
clickable restart button.
- **GIVEN** a mobile viewport, **WHEN** the user opens Feature Toggles, **THEN**
all descriptions, badges, switches, reset buttons, and restart actions are
reachable without horizontal scrolling.
## Success criteria
- A user can enable or disable Office mode from Feature Toggles and understand
that restart is required.
- Office mode clearly communicates experimental status and risk.
- A user can enable or disable Debug mode without seeing duplicate agent-message
debug toggles.
- Env-controlled flags are accurately shown as locked.
- Restart-required changes have a clear supported or manual restart path.
- Existing SSR feature gating keeps working through `/api/v1/features`.
- Desktop and mobile tests cover the core workflow.
## Out of scope
- Live-applying Office mode or Debug mode without restart.
- Per-user, workspace-scoped, or percentage rollout flags.
- Exposing mock providers, e2e tuning, or arbitrary env vars as user toggles.
- A third-party feature flag service.
- Multi-user admin enforcement in v1.