* feat: admin Settings tab — form-based editor replacing "coming soon" stub Section-grouped layout with collapsible headers for all ~40 ConfigStore settings (model, session, tools, server, mcp, ratelimit, health, judge, memory). Type-appropriate inputs: CSS toggle for bools, number with min/max/step, select for choices, text for strings. Secret fields shown read-only. Source badge (storage/default), amber restart indicator. Inline save per field with dirty detection, row flash on success, reset to default via styled confirm modal. Full WCAG keyboard accessibility (Enter/Space on section headers, aria-labels, focus-visible). Mobile responsive single-column at <700px. Reduced-motion safe. * fix: Settings tab polish — help tooltips, context_window auto-detect, UX fixes Settings UI: - Help tooltips: ? button on ~25 settings with plain-English explanations and optional reference links (arXiv, Fowler, MCP spec). Click to toggle popover, Escape to dismiss, aria-expanded for accessibility. - Sections start collapsed for scannable overview. - Restart badge: hidden by default, shows when dirty, persists after save with amber glow. Positioned left of source badge. - Secret row alignment fixed (transparent border matches input box model). - Docs link in toolbar → Swagger UI Settings section. - Number inputs: spin buttons hidden (Firefox/WebKit), empty value guard, numeric dirty detection (0.1 vs 0.10 no longer false positive). - Secret reset button enabled when source=storage (clear legacy overrides). - Space key repeat guard on section headers. - Sidebar: sticky + max-height:100vh, no longer stretches with content. Backend: - context_window default changed from 131072 to 0 (auto-detect). Fallback lowered from 131K to 32K (realistic for local models when detection fails). Session normalizes 0→32768 defensively. - Settings registry: help + reference_url fields on SettingDef, richer descriptions for model/session/tools/judge/memory settings. - Schema API includes help + reference_url. - Bootstrap system prompt: added Runtime Settings section. Docs: tab counts updated to 13 across README, architecture, console, governance.
12 KiB
Governance
Turnstone governance provides role-based access control (RBAC), tool execution policies, prompt templates, usage tracking, and audit logging for the admin console.
Architecture
See diagram: 19-governance-architecture.puml.
RBAC (Roles & Permissions)
The permission model has two layers:
- Scopes (legacy) —
read,write,approve. Checked byAuthMiddlewareon every request based on URL path classification. - Permissions (granular) — 15 permission strings checked per-endpoint by
require_permission().
Built-in roles (seeded by migration 008):
| Role | Permissions |
|---|---|
| admin | read, write, approve, admin.users, admin.roles, admin.orgs, admin.policies, admin.templates, admin.audit, admin.usage, admin.schedules, admin.watches, tools.approve, workstreams.create, workstreams.close |
| operator | read, write, workstreams.create, workstreams.close |
| viewer | read |
Custom roles can be created with any subset of the 15 valid permissions.
Auth flow:
- User logs in (password or API token) →
_load_user_permissions()aggregates permissions from all assigned roles _permissions_to_scopes()derives legacy scopes (anyadmin.*→approve)- JWT created with both
scopesandpermissionsclaims - Middleware checks scope → handler checks permission via
require_permission()
Tool Policies
Admin-defined rules that control tool execution:
- Pattern matching: Glob syntax via
fnmatch(e.g.,bash*,file_write,*) - Actions:
allow(auto-approve),deny(block),ask(normal approval flow) - Priority: Higher priority evaluated first, first match wins
- Enforcement:
evaluate_tool_policies_batch()called inWebUI.approve_tools()before theauto_approvecheck - MCP granular policies: MCP resources and prompts are evaluated using their
approval_labelfor fine-grained control:- Resource reads:
mcp_resource__{uri}(e.g.,mcp_resource__file:///docs/*to allow,mcp_resource__*to deny all) - Prompt invocations:
mcp__{server}__{prompt}(e.g.,mcp__trusted__*to allow,mcp__*to require approval for all) - Built-in tools continue to use
func_namefor backward compatibility
- Resource reads:
Prompt Templates
Admin-curated system message templates injected at workstream startup:
- Runtime behavior: Templates are loaded once at session creation and injected
into the system message before user
instructions. Templates set the baseline; instructions customize per-workstream behavior. - Default templates: All
is_default=truetemplates auto-apply to new workstreams, concatenated in alphabetical order by name. Use name prefixes (e.g.01-safety,02-style) to control ordering. - Explicit selection:
--template <name>CLI flag,templatefield onPOST /v1/api/workstreams/new, console creation modal dropdown, scheduled task config, and channel adapter config. An explicit template replaces defaults. - Variables: Three built-in placeholders resolved at load time:
{{model}}(active model name),{{ws_id}}(workstream ID),{{node_id}}(server node ID). Unrecognized placeholders are kept as-is. - Runtime switching:
/template <name>to switch,/template clearto revert to defaults,/templateto show current. Persisted across resume. - Categories: general, engineering, support, custom, mcp
- Content limit: 32 KB per template (enforced on create/update)
- Storage:
prompt_templatestable with JSONvariablesarray. Migration 010 addstemplatecolumn toscheduled_tasks. - MCP sync: MCP server prompts auto-sync into prompt_templates with
origin="mcp",mcp_serverset, andreadonly=True. Manual templates take precedence on name collision. MCP-synced content updates resetis_defaultto prevent compromised servers from injecting defaults. Admin UI shows origin badge and disables edit/delete for MCP-sourced templates.
Workstream Templates
Workstream templates are behavioral profiles applied at workstream creation — the next level beyond prompt templates. While prompt templates inject system message text, workstream templates define the complete workstream configuration.
What they define:
- System prompt (inline text OR reference to a prompt template by name)
- Model override (empty = server default)
- Temperature, reasoning effort, max tokens, agent max turns
- Auto-approve policy (blanket and/or per-tool list)
- Token budget (0 = unlimited; warns at 80%, requires approval at 100%)
- Completion notification config (stored for v2 dispatch)
Storage: workstream_templates table (migration 011) with auto-versioning. Edits snapshot the pre-update state into workstream_template_versions. Workstreams record which template and version spawned them via ws_template_id + ws_template_version columns.
Applied once at creation: Template settings are snapshot-applied to the workstream's config. Not a live binding — template updates don't affect running workstreams.
Prompt template drift detection: When a workstream template references a prompt template, a SHA-256 hash of the prompt content is stored at ws_template create/update time. At workstream creation, the server compares the stored hash against current content and logs a warning on mismatch.
Admin API: 7 endpoints under /v1/api/admin/ws-templates (list, create, get, update, delete, version history) plus a read-only summary at /v1/api/ws-templates. Permission: admin.ws_templates.
Console UI: "WS Templates" tab with CRUD table, create/edit modals (name, description, system prompt source toggle, model, auto-approve, per-tool auto-approve, temperature, reasoning effort, max tokens, agent max turns, token budget, enabled), and version history modal. "Profile" dropdown on workstream creation modal. "WS Template" dropdown on scheduler create/edit modals.
Token budget enforcement: Tracked in session.send(). At 80% consumption, emits an info message. At 100%, the next turn requires explicit approval via the __budget_override__ synthetic tool name (reuses existing approval UI — inline in browser, Discord buttons, bridge auto-approve). The synthetic name can be targeted by tool policies (e.g. __budget_override__ → allow for admins).
SDK: Python (list_ws_templates, create_ws_template, get_ws_template, update_ws_template, delete_ws_template, list_ws_template_versions) and TypeScript (listWsTemplates, createWsTemplate, etc.) on both sync and async console clients. ws_template parameter on create_workstream() for both server and console SDKs.
Usage Tracking
Per-LLM-request token and tool call metrics:
- Recording:
on_status()inWebUIrecords ausage_eventafter each LLM response with prompt/completion tokens, tool call count, model, ws_id - Querying:
GET /v1/api/admin/usagewithgroup_by(day/hour/model/user) and time range filtering - Pruning:
prune_usage_events(retention_days=90)andprune_audit_events(retention_days=365)run automatically via the console scheduler's periodic cleanup cycle
Audit Logging
Append-only trail of admin actions:
- Recording:
record_audit()helper called from all admin mutation handlers - Events captured: user.create, user.delete, token.create, token.revoke, channel.link, channel.unlink, role.create, role.update, role.delete, role.assign, role.unassign, policy.create, policy.update, policy.delete, template.create, template.update, template.delete, ws_template.create, ws_template.update, ws_template.delete, org.update
- Querying:
GET /v1/api/admin/auditwith action/user/time filters + pagination
Database Schema
Migration 008 adds 7 tables:
| Table | Purpose |
|---|---|
orgs |
Organizations (single default org for now) |
roles |
Named permission bundles (3 builtin + custom) |
user_roles |
User-to-role assignments (composite PK) |
tool_policies |
Per-tool approve/deny/ask rules |
prompt_templates |
Reusable system message templates |
usage_events |
Per-request token/tool metrics |
audit_events |
Admin action log |
Also adds org_id column to users table.
API Endpoints
All under /v1/api/admin/ (requires approve scope + granular permission).
| Group | Endpoints | Permission |
|---|---|---|
| Users / Tokens / Channels | 9 (CRUD) | admin.users |
| Roles | 7 (CRUD + assignment) | admin.roles / admin.users |
| Orgs | 3 (list, get, update) | admin.orgs |
| Tool Policies | 4 (CRUD) | admin.policies |
| Prompt Templates | 4 (CRUD) | admin.templates |
| Schedules | 6 (CRUD + runs) | admin.schedules |
| WS Templates | 7 (CRUD + versions + summary) | admin.ws_templates |
| Watches | 3 (list, create, cancel) | admin.watches |
| Usage | 1 (aggregated query) | admin.usage |
| Audit | 1 (paginated, filtered) | admin.audit |
Full OpenAPI spec at /openapi.json and Swagger UI at /docs.
Admin Console UI
6 new tabs added to the admin panel (11 total):
- Roles — CRUD roles, permission checkbox grid, user role assignment modal
- Policies — CRUD tool policies with colored action badges (green/red/amber)
- Templates — CRUD prompt templates with wide modal, textarea editor
- WS Templates — CRUD workstream templates with create/edit modals, version history
- Usage — Summary readouts + CSS bar chart, time range + group-by selectors
- Audit — Filterable log with relative timestamps, load-more pagination
Tabs are permission-gated: hidden if the user lacks the required permission.
SDK
Both Python and TypeScript console SDKs expose governance methods:
Python (TurnstoneConsole / AsyncTurnstoneConsole):
list_roles(),create_role(),update_role(),delete_role()list_user_roles(),assign_role(),unassign_role()list_orgs(),get_org(),update_org()list_policies(),create_policy(),update_policy(),delete_policy()list_templates(),create_template(),update_template(),delete_template()list_ws_templates(),create_ws_template(),get_ws_template(),update_ws_template(),delete_ws_template(),list_ws_template_versions()get_usage(since, group_by=...),get_audit(action=..., limit=...)
TypeScript (TurnstoneConsole):
- Same methods with camelCase naming and typed interfaces
Security Considerations
- Privilege escalation prevented:
admin_assign_roleblocks self-assignment and requires caller to hold a superset of the target role's permissions - Permission validation: Role create/update validates permissions against
a 15-item allowlist (
_VALID_PERMISSIONS) - Self-deletion blocked:
admin_delete_userrejects attempts to delete your own account (matching the self-assignment guard on role endpoints) - Field allowlists: Storage
update_*methods filter fields against allowlists (_ROLE_MUTABLE,_POLICY_MUTABLE, etc.) — handler bugs cannot overwriterole_id,builtin,created, or other protected columns - Bootstrap safety:
handle_auth_setupfails and rolls back if admin role assignment fails, preventing locked-out first user - API token RBAC:
_authenticate_api_tokenloads permissions from user's roles, ensuring API tokens are subject to RBAC enforcement - Policy evaluation is fail-open: If storage is unavailable, tool policies degrade to the existing approval flow (not auto-approve)
- Audit IP resolution:
_audit_context()prefersX-Forwarded-Forfor client IP when behind a reverse proxy, falling back torequest.client.host