diff --git a/docs/providers.md b/docs/providers.md
index 002eb9111..43155d413 100644
--- a/docs/providers.md
+++ b/docs/providers.md
@@ -12,7 +12,7 @@ Tale connects to AI models through **providers** — OpenAI-compatible API endpo
Providers are managed in **Settings > Providers** in the management UI. Admins can:
- **Add a provider** with a name, display name, base URL, API key, and one or more models
-- **Edit a provider** to update its configuration or add/remove models
+- **Edit a provider** to update its display name, description, base URL, and default models. The description is shown in the provider list to help users understand what the provider is for. Default models let you pre-select which model is used for chat, vision, and embedding when users pick this provider.
- **Delete a provider** to remove it entirely
Each model definition includes an ID (must match the model name expected by the API), a display name, and one or more tags (`chat`, `vision`, `embedding`) that control where the model appears in the platform.
diff --git a/docs/settings.md b/docs/settings.md
index b41403859..a1fd0f54d 100644
--- a/docs/settings.md
+++ b/docs/settings.md
@@ -66,6 +66,30 @@ Customize the look of the platform for your organization. Admin only. Available
Available to all users. Change your password here. If you signed up via SSO, you can also set a regular password from this page to enable direct login.
+## Governance
+
+Admin only. Configure organization-wide AI policies and controls. The governance page is organized into three groups accessible from a left-hand navigation:
+
+### Content & Models
+
+- **System Prompt**: set a global system prompt prepended to every AI conversation in the organization.
+- **Default Models**: choose the default chat, vision, and embedding models used when users don't pick one explicitly.
+- **Model Access**: control which models are available to specific teams or users.
+
+### Policies & Limits
+
+- **Budgets**: set spending limits per user, team, or the entire organization with configurable periods and thresholds.
+- **Upload Policy**: restrict file uploads by type, size, or count.
+- **Retention**: configure how long conversations and files are kept before automatic deletion.
+- **Feature Controls**: toggle platform features (e.g., file uploads, web search, image generation) on or off organization-wide.
+
+### Security & Monitoring
+
+- **PII Detection**: enable automatic detection and masking (or blocking) of personally identifiable information in messages. Supports built-in patterns and custom regex rules.
+- **Usage Dashboard**: view token consumption, cost breakdowns, and usage trends across the organization.
+
## Audit logs
Admin only. A time-ordered record of significant actions taken in the organization. Categories include authentication events, member changes, data operations, integration updates, workflow publications, security events, and admin actions. Useful for compliance and troubleshooting.
+
+Admins can export audit logs as **CSV** or **JSON** using the export buttons above the log table. Exports respect the currently active category filter.
diff --git a/examples/providers/openrouter.secrets.json b/examples/providers/openrouter.secrets.json
index e58ac25c9..e7b0ebbdd 100644
--- a/examples/providers/openrouter.secrets.json
+++ b/examples/providers/openrouter.secrets.json
@@ -1,20 +1,15 @@
{
- "apiKey": "ENC[AES256_GCM,data:MJKxyEKDQFk2Y+E3R7eK13LwIxO9FQ6TxAYc93NlRc56As9XMaLTx0CNfM82MyoHyQCduKgCf3a8RK9r2bWVgo8NnVnd2YzGOA==,iv:OqTfslCeZbiYkEQqAI/Jj0NLuJW1h0hdZPus8Sel3cU=,tag:yaBSdC8+gmPdEcfLmECXOg==,type:str]",
+ "apiKey": "ENC[AES256_GCM,data:Os9IU5ZlUhe7QWMhqrhsVIFNKVD2FzszIkHGOb9l8Oa8z57AZUek+XK7qM//OyY60sMQQjVkbnygU7175lgO05lXlWRLqWiksA==,iv:2rpxkfK022BbwBCDwhu2YvlbF+HXv+KPaLPu6dQWX/k=,tag:gqMQcSCc9Q2wGJaer5V3jw==,type:str]",
"sops": {
- "kms": null,
- "gcp_kms": null,
- "azure_kv": null,
- "hc_vault": null,
"age": [
{
- "recipient": "age1xsc5y9x0dref9kd6fwv2356pw2zl5s7gp5v6jam9h4q7mv6fm9aqumvqhj",
- "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBPYnV2WjVHR3ZFSGRlYmxR\ncnUybXVRcWNrVFkrMVlnTkZ1bFhNRmk3WjBVCmtSZ2RsbFZCdWY5dk94RlhhU1dN\nQnZnejFvYW4vVTBOUWVBUHJRaEovQmcKLS0tIENlVUJmTjFDcXZjWjV4RitnZHlv\nN09UeFo5Y2xnZXRwQ3RabEc2QVgyckUK7u0Avecl9B628T56Np6gGxQ1+yCSRjXa\n8HBlTjMa4g1OR8d4isfZ9VE+lETdNaVlultd9F1GWEvgv/p7WxpeUg==\n-----END AGE ENCRYPTED FILE-----\n"
+ "recipient": "age18ylfcfvf9we4rc5hpza6n9tvhwuw55jfun9wdpd5uhux5rs3qf4q95y2y4",
+ "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSByUU1FMndnUDAzeTZySW1x\nMFJwNEhuYXF4UlZZWGRQaS85SjNkbmVpMG44CjVhTEh4c1BNZ1VXZXN3R2cxeFBt\nVjB6Sm5BOXc1NFdCcGMzS1BEWDJHeWMKLS0tIEFUekUrOHFmMSsraFdMOVU4d3FJ\nTzdwWWtiNytmTVNRQW5MTExRVEk3MmcKHmSVxvF3Nz/yMGZ3lFY+oDcryUdoOT7/\nmxnY/U4ZX3sCkLphYdUVuAA6nqstfzVn57X1ND8vqaFXPn2lhVc00Q==\n-----END AGE ENCRYPTED FILE-----\n"
}
],
- "lastmodified": "2026-04-05T11:39:10Z",
- "mac": "ENC[AES256_GCM,data:2OYyLhDFsCM/spi9pp6bUXwuShtuqumEv7brGMrcRMdziaX8tQTnEL8Kcd774ne3BFDg+sfAnX2SH7e66qUxXd1qMXJWNN96lkjStc422rilpXWvq29RDKjatuAf7qSZoF0rWFNK9sAeQ0J+6bHytQ5FT3DXnS0SoXozMxCvz3A=,iv:8fx48QGvx8Nudqo1ByyWnPsebKn6BpAHQzw5rvj7zdg=,tag:21dyPzSJgJNpnStSvxs+yA==,type:str]",
- "pgp": null,
+ "lastmodified": "2026-04-12T18:34:34Z",
+ "mac": "ENC[AES256_GCM,data:dTQzeY02n+I8wUXpfhlHBAQ8EZgMmPo1lMKZFtvbyEl9VWgYc9Q9+Bde0g6hmpXJNKqNx4VNhiQV6NFl0oyQQNpaN6yAu9Ka1gs64eymLUwuiBigSvSoiBOoICGaHx9rNhdgu55vU3xCGpp1C4lwGdj6lpKYJ7Pefdc0ZS1rnLo=,iv:ZBoTH20q8nPOp5H11WQiiH/VrKMao2RcaBiu9TeSVx4=,tag:HiqFbmIQMpO5Id0S7ls6Gw==,type:str]",
"unencrypted_suffix": "_unencrypted",
- "version": "3.9.4"
+ "version": "3.12.2"
}
-}
\ No newline at end of file
+}
diff --git a/services/platform/app/components/ui/forms/input.test.tsx b/services/platform/app/components/ui/forms/input.test.tsx
index bfda670d5..dd1e97d2f 100644
--- a/services/platform/app/components/ui/forms/input.test.tsx
+++ b/services/platform/app/components/ui/forms/input.test.tsx
@@ -188,7 +188,7 @@ describe('Input', () => {
it('sets autocomplete for password', () => {
render();
const input = screen.getByLabelText('Password');
- expect(input).toHaveAttribute('autocomplete', 'current-password');
+ expect(input).toHaveAttribute('autocomplete', 'off');
});
it('allows custom autocomplete', () => {
diff --git a/services/platform/app/components/ui/forms/input.tsx b/services/platform/app/components/ui/forms/input.tsx
index ad7a89dac..9b26207dc 100644
--- a/services/platform/app/components/ui/forms/input.tsx
+++ b/services/platform/app/components/ui/forms/input.tsx
@@ -82,7 +82,7 @@ export const Input = forwardRef(
const [showShake, setShowShake] = useState(false);
const inputType = isPassword ? (show ? 'text' : 'password') : type;
const resolvedAutoComplete =
- autoComplete ?? (isPassword ? 'current-password' : undefined);
+ autoComplete ?? (isPassword ? 'off' : undefined);
const hasError = !!errorMessage;
const showInvalid = hasError || !!isInvalid;
const describedBy =
diff --git a/services/platform/app/components/ui/navigation/tabs.tsx b/services/platform/app/components/ui/navigation/tabs.tsx
index 825158786..1806dad77 100644
--- a/services/platform/app/components/ui/navigation/tabs.tsx
+++ b/services/platform/app/components/ui/navigation/tabs.tsx
@@ -19,6 +19,8 @@ interface TabsProps {
onValueChange?: (value: string) => void;
className?: string;
listClassName?: string;
+ /** Optional actions rendered to the right of the tab list */
+ actions?: ReactNode;
}
export function Tabs({
@@ -28,6 +30,7 @@ export function Tabs({
onValueChange,
className,
listClassName,
+ actions,
}: TabsProps) {
return (
-
- {items.map((item) => (
-
- {item.label}
-
- ))}
-
+