Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/en/api/08-admin.md
Original file line number Diff line number Diff line change
Expand Up @@ -956,7 +956,7 @@ Create a new workspace with its first admin user.
| settings | object | No | `null` | Initial Account runtime configuration. ROOT-only; supports `feishu`, `github`, `acl`, `agent_evolution`, `vlm`, `query_planner`, `embedding`, and `vectordb`. `vectordb` and Embedding create-only fields can only be set here |

**Notes:**
- File-backed account creation rejects an `account_id` over 255 UTF-8 bytes with `INVALID_ARGUMENT` before writing the account registry. Trusted auto-registration rejects such new accounts on the request, so they cannot block other identities from registering. Existing accounts and users remain supported; user and peer ID rules are unchanged.
- File-backed account creation rejects an `account_id` over 255 UTF-8 bytes with `INVALID_ARGUMENT` before writing the account registry. Newly persisted user IDs are limited to 255 characters across account-store providers. Trusted mode rejects an oversized new user or a new oversized account before enqueueing auto-registration, so invalid identities cannot block other identities from registering. Existing oversized File users and accounts remain readable.
- In `trusted` mode, only identity and role are created: no personal key, hash, or key index is generated. The response omits `user_key`, and `seed` is unused.
- Omit `seed` for the default random API key. Set `OV_KEY_SEED` to a securely generated secret before running a deterministic example; the Go example also requires `import "os"`. Short or predictable seeds make the key guessable.
- `settings` receives structural and effective Embedding/VectorDB pair validation before the Account and its directories are created. A failed validation leaves no Account, user, or configuration file.
Expand Down
107 changes: 106 additions & 1 deletion docs/en/guides/01-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -1898,7 +1898,7 @@ When running OpenViking as an HTTP service, add a `server` section to `ov.conf`:
| `port` | int | Bind port | `1933` |
| `auth_mode` | str / null | Built-in modes: `"dev"`, `"api_key"`, `"trusted"`, `"oidc"`, `"ldap"`. When omitted/null, infer `api_key` from a non-empty `root_api_key`; otherwise infer `dev`. | `null` |
| `root_api_key` | str | Root API key for multi-tenant auth in `api_key` mode. In `trusted` mode it is required on every deployment, including localhost; it does not become the source of user identity | `null` |
| `account_store.provider` | str | Complete Account store provider. The built-in `"file"` provider owns account, user, group, and API-key records while preserving the existing AGFS JSON registry and process-local lookup indexes. | `"file"` |
| `account_store.provider` | str | Complete Account store provider. Built-in values are `"file"`, `"mysql"`, and `"redis_mysql"`; it owns account, user, group, and API-key records. `"file"` preserves the existing AGFS JSON registry and owns its process-local lookup indexes. `"redis_mysql"` keeps MySQL authoritative and adds a shared Redis cache for authentication and request identity reads. | `"file"` |
| `account_store.params` | object | Provider-specific startup parameters. Framework-owned filesystem and File-provider API-key hashing dependencies cannot be overridden here. | `{}` |
| `profile_enabled` | bool | Whether to allow request-scoped cProfile via `profile=1` on HTTP requests. When disabled, the server ignores that query parameter. When enabled, the CLI can display the returned `profile`, while the Python HTTP client currently triggers profiling but does not automatically attach the top-level `profile` field to most SDK return values. | `false` |
| `cors_origins` | list | Allowed CORS origins | `["*"]` |
Expand All @@ -1913,6 +1913,111 @@ When running OpenViking as an HTTP service, add a `server` section to `ov.conf`:
| `user_config_defaults.auto_commit_policy` | object | Deployment default auto-commit policy for newly created sessions without an explicit policy. | `null` |
| `agent_evolution.enabled` | bool | Startup cluster default for Agent Evolution. Account and Cluster Admin settings may override it at runtime. When enabled, session commits may generate or update cases, trajectories, and experiences according to the session `memory_policy`. Existing memories remain readable and searchable when disabled. | `false` |

The MySQL provider stores a versioned SHA-256 digest of each high-entropy API
key by default and uses a separate digest index for constant-cost lookup. Set
`credential_storage` to `"encrypted"` to retain keys in AES-GCM encrypted form
instead; this requires `credential_encryption_key`, a base64-encoded 32-byte
key. MySQL never persists API-key plaintext. Hash mode exposes only key prefixes
in user listings, while encrypted mode can decrypt the key for the same listing
workflow. `credential_storage` controls the format of newly written credentials;
existing rows are decoded from their `material` prefix. When migrating from
encrypted storage to hash storage, retain `credential_encryption_key` until all
encrypted credentials have been rotated. Replacing that encryption key directly
is not supported because `aesgcm:v1` records do not carry a key identifier.

Install the MySQL extra and configure the top-level `service_name`. Its value is
stored in the `service_name` column on every account-store table and is the
deployment boundary when multiple OpenViking clusters share a database. Changing
`service_name` selects a different logical account-store partition.

The provider never creates or migrates database tables. Create the normalized
tables in `openviking/server/account_stores/sql/init.sql` through your normal
DBA or deployment process before starting the server. Future reviewed schema
changes are stored as ordered SQL files in
`openviking/server/account_stores/sql/changes/`. The five `ov_accounts*` table
names are fixed; use distinct top-level `service_name` values rather than
separate table prefixes to isolate OpenViking clusters that share a database.

```json
{
"service_name": "production",
"server": {
"account_store": {
"provider": "mysql",
"params": {
"host": "db.example.com",
"user": "openviking",
"password": "secret",
"database": "openviking",
"credential_storage": "encrypted",
"credential_encryption_key": "base64-encoded-32-byte-key"
}
}
}
}
```

`redis_mysql` requires the `redis-mysql` extra and wraps the same MySQL
parameters. Its `cache` block follows the top-level `cache` `provider/params`
shape. The Python client currently supports `standalone`, `cluster`, and
`sentinel` Redis deployments. It caches API-key bindings, users, per-user group
IDs, and deletion records for a fixed 60 seconds by default. Cache fills and account mutations
share an account-scoped Redis lease; normal cache hits do not acquire that
lease. Mutations commit to MySQL first, then invalidate affected cache entries.
If Redis is unavailable, the MySQL mutation still succeeds and stale cache
entries remain valid only until their fixed TTL expires. Cache fills also
maintain an
account-scoped, expiry-scored key index. Each indexed cache fill removes expired
index members, allowing account deletion or recreation to clear only that
account's current cached users, groups, deletion records, and credential data.
API-key bindings are checked against account and user credential fences, so
normal key replacement, revocation, and user deletion invalidate old bindings.
Redis automatically removes the index when its last sorted-set member is removed.
Cleanup does not issue a separate index deletion, which could remove a newer
fill's index after lease loss. Provisioning
and key replacement clear negative lookups both with and without a user hint.
Redis connection and command timeouts are configured with
`cache.params.connect_timeout_ms` and `cache.params.command_timeout_ms`; both
default to 5000 milliseconds when omitted. These are socket timeouts, not an
overall request deadline.

```json
{
"server": {
"account_store": {
"provider": "redis_mysql",
"params": {
"mysql": {
"host": "db.example.com",
"user": "openviking",
"password": "secret",
"database": "openviking"
},
"cache": {
"provider": "redis",
"params": {
"mode": "sentinel",
"endpoints": [
"rediss://sentinel-1.example.com:26379",
"rediss://sentinel-2.example.com:26379",
"rediss://sentinel-3.example.com:26379"
],
"master_name": "openviking",
"username": "openviking",
"password_env": "OPENVIKING_REDIS_PASSWORD",
"sentinel_password_env": "OPENVIKING_SENTINEL_PASSWORD",
"key_prefix": "ov:account-auth",
"ttl_seconds": 60,
"lock_ttl_seconds": 60,
"lock_wait_seconds": 30
}
}
}
}
}
}
```

Omitting `auth_mode` (or setting it to `null`) selects `api_key` when a non-empty `root_api_key` is configured, and `dev` otherwise. `dev` is allowed only on localhost and accepts requests without authentication. An empty-string `root_api_key` is invalid.

Explicit `auth_mode: "api_key"` requires a non-empty `root_api_key`, including on localhost; without it, startup fails instead of falling back to development mode. Use the root key with the Admin API to create accounts and user/admin keys; use those tenant-bound keys for data access. `trusted` mode accepts account/user identity headers from a trusted gateway and does not require user-key provisioning first. Its root key is required for startup and every authenticated request, including on localhost. For role resolution, OIDC/LDAP setup, and gateway requirements, see [Authentication](04-authentication.md).
Expand Down
2 changes: 1 addition & 1 deletion docs/zh/api/08-admin.md
Original file line number Diff line number Diff line change
Expand Up @@ -827,7 +827,7 @@ User override 并重新继承上述默认值,请 PATCH `{"memory_policy": null
| settings | object | 否 | `null` | Account 运行时初始配置。仅 ROOT 可提交;支持 `feishu`、`github`、`acl`、`agent_evolution`、`vlm`、`query_planner`、`embedding` 和 `vectordb`。其中 `vectordb` 及 Embedding 创建期字段只能在这里设置 |

**说明:**
- 文件存储账户创建时,超过 255 个 UTF-8 字节的 `account_id` 会在写入账户注册表前返回 `INVALID_ARGUMENT`。trusted 自动注册会在请求时拒绝这类新账户,避免阻塞其他身份的注册。既有账户和用户仍可使用,user ID 和 peer ID 规则保持不变。
- 文件存储账户创建时,超过 255 个 UTF-8 字节的 `account_id` 会在写入账户注册表前返回 `INVALID_ARGUMENT`。新持久化的 user ID 在所有账户存储 Provider 中最多 255 个字符。trusted 模式会在加入自动注册队列前拒绝超长的新 user ID 和新建的超长账户,避免无效身份阻塞其他身份注册;已有的超长 File user 和 account 仍可读取。
- 在 `trusted` 模式下,只创建身份和角色,不生成个人密钥、密钥哈希或密钥索引;响应不包含 `user_key`,`seed` 不参与处理。
- 省略 `seed` 时随机生成 API Key。运行确定性示例前,将 `OV_KEY_SEED` 设为安全生成的秘密值;Go 示例还需导入 `os`。不要使用短字符串或可预测内容作为 seed。
- `settings` 在账号和目录创建前完成结构与有效 Embedding/VectorDB 联合校验。校验失败不会留下 Account、用户或配置文件。
Expand Down
93 changes: 92 additions & 1 deletion docs/zh/guides/01-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -1885,7 +1885,7 @@ ov add-resource ./docs --exclude "*.tmp"
| `port` | int | 绑定端口 | `1933` |
| `auth_mode` | str / null | 内置模式:`"dev"`、`"api_key"`、`"trusted"`、`"oidc"`、`"ldap"`。省略或设为 null 时,有非空 `root_api_key` 则推导为 `api_key`,否则为 `dev`。 | `null` |
| `root_api_key` | str | `api_key` 模式必填的 Root API Key;`trusted` 模式在所有部署中均必填,包括 localhost,不负责解析普通用户身份 | `null` |
| `account_store.provider` | str | 完整 Account Store Provider。内置 `"file"` Provider,统一持久化 Account、User、Group 和 API Key,兼容现有 AGFS JSON 注册表并维护进程内查询索引。 | `"file"` |
| `account_store.provider` | str | 完整 Account Store Provider。内置 `"file"`、`"mysql"` 和 `"redis_mysql"`;统一持久化 Account、User、Group 和 API Key。`"file"` 兼容现有 AGFS JSON 注册表,并由 Provider 自身维护进程内查询索引。`"redis_mysql"` 保持 MySQL 为权威存储,并增加共享 Redis 认证和请求身份读取缓存。 | `"file"` |
| `account_store.params` | object | Provider 启动参数。不能通过这里覆盖框架注入的 VikingFS 和 File Provider API Key Hash 配置。 | `{}` |
| `profile_enabled` | bool | 是否允许 HTTP 请求通过 `profile=1` 开启请求级 cProfile。关闭时服务端会忽略该请求参数;开启后,CLI 可以显示返回的 `profile`,而 Python HTTP client 默认只触发服务端 profile,不会把顶层 `profile` 字段自动附着到大多数 SDK 返回值上。 | `false` |
| `cors_origins` | list | CORS 允许的来源 | `["*"]` |
Expand All @@ -1900,6 +1900,97 @@ ov add-resource ./docs --exclude "*.tmp"
| `user_config_defaults.auto_commit_policy` | object | 新建 Session 未显式指定策略时使用的部署级自动 Commit 默认策略。 | `null` |
| `agent_evolution.enabled` | bool | Agent 进化的集群启动默认值,运行时可由 Account 或 Cluster Admin settings 覆盖。开启时,session commit 可按 session `memory_policy` 生成或更新 cases、trajectories 和 experiences;关闭后已有记忆仍可读取和检索。 | `false` |

MySQL Provider 默认保存高熵 API Key 的带版本 SHA-256 摘要,并通过独立摘要索引
进行固定成本查询。将 `credential_storage` 设为 `"encrypted"` 可改用 AES-GCM
可逆加密;此时必须提供 `credential_encryption_key`,其值为 base64 编码的
32 字节密钥。MySQL 不会持久化 API Key 原文。哈希模式的用户列表只展示 key
prefix;加密模式可为同一列表工作流解密展示 key。`credential_storage` 仅决定
新写入凭证的格式,读取时根据每条记录的 `material` 前缀选择解码方式。从加密模式
迁移到哈希模式时,应保留 `credential_encryption_key`,直到所有旧加密凭证完成
轮换。`aesgcm:v1` 记录不包含 key id,因此不支持直接替换该加密密钥。

安装 MySQL extra 后,通过顶层 `service_name` 配置部署名称。该值会写入每张
Account Store 表的 `service_name` 列,并在多个 OpenViking 集群共用一个数据库时
充当隔离边界。修改 `service_name` 会选择另一组逻辑 Account Store 分区。

Provider 不会创建或迁移数据库表。启动服务前,应通过现有 DBA 或部署流程手工创建
`openviking/server/account_stores/sql/init.sql` 中定义的规范化表。后续经评审的
schema 变更按顺序存放于 `openviking/server/account_stores/sql/changes/`。
五张 `ov_accounts*` 表的名称固定;多个 OpenViking 集群共用数据库时,通过不同的
顶层 `service_name` 隔离部署,不使用不同表前缀。

```json
{
"service_name": "production",
"server": {
"account_store": {
"provider": "mysql",
"params": {
"host": "db.example.com",
"user": "openviking",
"password": "secret",
"database": "openviking",
"credential_storage": "encrypted",
"credential_encryption_key": "base64-encoded-32-byte-key"
}
}
}
}
```

`redis_mysql` 需要安装 `redis-mysql` extra,并在参数中嵌套同一份 MySQL 配置。
其 `cache` 配置沿用顶层 `cache` 的 `provider/params` 结构,目前 Python
客户端支持 `standalone`、`cluster` 和 `sentinel` 三种 Redis 部署模式。
它默认以固定 60 秒 TTL 缓存 API Key 绑定、User、每个 User 的 Group ID 和删除记录。
缓存重建与同一 Account 的修改共用 Redis 租约,正常缓存命中不获取该租约。修改会先
提交 MySQL,再失效相关缓存。Redis 不可用时 MySQL 修改仍会成功,遗留缓存最多保持到
固定 TTL 到期。缓存回填同时维护 Account
级、按过期时间排序的 key 索引;每次写入已索引缓存时会清理过期成员,因此删除或重建
Account 时只会清理该 Account 当前的 User、Group、删除记录和凭据缓存。API Key 绑定会
校验 Account 和 User 的凭据 fence,所以正常的换 Key、吊销和删 User 会使旧绑定失效。
索引的最后一个有序集合成员被移除后,Redis 会自动删除空索引;清理结束时不再单独删除
索引,避免失去租约的旧请求误删新回填的索引。创建身份或换 Key 会同时清除携带和未携带
User 提示的负缓存。
Redis 连接和命令超时通过 `cache.params.connect_timeout_ms` 和
`cache.params.command_timeout_ms` 配置,未配置时均为 5000 毫秒;这不是整个请求的总超时。

```json
{
"server": {
"account_store": {
"provider": "redis_mysql",
"params": {
"mysql": {
"host": "db.example.com",
"user": "openviking",
"password": "secret",
"database": "openviking"
},
"cache": {
"provider": "redis",
"params": {
"mode": "sentinel",
"endpoints": [
"rediss://sentinel-1.example.com:26379",
"rediss://sentinel-2.example.com:26379",
"rediss://sentinel-3.example.com:26379"
],
"master_name": "openviking",
"username": "openviking",
"password_env": "OPENVIKING_REDIS_PASSWORD",
"sentinel_password_env": "OPENVIKING_SENTINEL_PASSWORD",
"key_prefix": "ov:account-auth",
"ttl_seconds": 60,
"lock_ttl_seconds": 60,
"lock_wait_seconds": 30
}
}
}
}
}
}
```

省略 `auth_mode`(或设为 `null`)时,配置了非空 `root_api_key` 则选择 `api_key`,否则选择 `dev`。`dev` 仅允许监听 localhost,不进行身份认证。`root_api_key` 不能配置为空字符串。

显式设置 `auth_mode: "api_key"` 时,包括 localhost 在内都必须提供非空 `root_api_key`;缺少该 key 会导致启动失败,不会回退到开发模式。使用 root key 调用 Admin API 创建 account 和 user/admin key,数据访问使用这些绑定租户身份的 key。`trusted` 模式接受可信网关注入的 account/user 身份头,无需预先创建 user key;其 root key 在启动和每个认证请求中均必填,包括 localhost。角色解析、OIDC/LDAP 配置与网关要求参见 [身份认证](04-authentication.md)。
Expand Down
8 changes: 8 additions & 0 deletions openviking/core/identifiers.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from typing import Optional

_VALIDATION_PATTERN = re.compile(r"^[a-zA-Z0-9_.@-]+$")
MAX_IDENTIFIER_LENGTH = 255


def validate_identifier_part(part: str, part_name: str) -> str | None:
Expand Down Expand Up @@ -51,3 +52,10 @@ def validate_account_id(account_id: str) -> str | None:
def validate_user_id(user_id: str) -> str | None:
"""Validate a user id."""
return validate_identifier_part(user_id, "user_id")


def validate_identifier_length(value: str, name: str) -> str | None:
"""Validate the persistence length of a new account-store identifier."""
if len(value) > MAX_IDENTIFIER_LENGTH:
return f"{name} must not exceed {MAX_IDENTIFIER_LENGTH} characters."
return None
Loading
Loading