Initial Dever 0.0.1

This commit is contained in:
Codex committed 2026-11-03 03:32:07 +00:00
commit 81233d098c
1479 files changed
+163782

No files matched your search

@@ -0,0 +1,24 @@
# account
本领域的职责与行为见主题说明;函数签名和调用关系从源码读取。
## 说明
- [任务权限入口](doc/api-admin-jobs.md)
- [请求元数据入口](doc/api-admin-metadata.md)
- [自动资源接口](doc/api-admin-profile.md)
- [角色接口](doc/api-admin-roles.md)
- [Secret 输入验收](doc/api-admin-secret.md)
- [身份认证接口](doc/api-admin-session.md)
- [认证测试命令](doc/api.md)
- [身份任务](doc/app-jobs.md)
- [请求元数据业务](doc/app-metadata.md)
- [角色业务验收](doc/app-roles.md)
- [敏感 HTTP 输入](doc/app-secret.md)
- [会话认证验收](doc/app-session.md)
- [可信任务执行](doc/job.md)
- [自动 REST 数据](doc/model-profile.md)
- [租户身份类型](doc/model-tenant.md)
- [全局会话账号](doc/model.md)
[调用流程](link.md)由 `dever inspect . --links` 生成。
@@ -0,0 +1,5 @@
cmd seed = app.seed
cmd set_deadline = app.setDeadline
cmd revoke = app.revoke
@@ -0,0 +1,5 @@
post job_submit = app.jobSubmit
cmd job_submit = app.jobSubmit
cmd job_count = app.jobCount
@@ -0,0 +1,3 @@
public post metadata = app.metadata
public post metadata_fail = app.metadataFail
@@ -0,0 +1 @@
rest model.profile
@@ -0,0 +1,25 @@
authenticated get permissions = app.permissions
authenticated get current_permissions = app.currentPermissions
authenticated get roles = app.roles
authenticated get role = app.role
authenticated get user_roles = app.userRoles
authenticated get is_owner = app.isOwner
post save_role = app.saveRole
post grant_role = app.grantRole
post revoke_role = app.revokeRole
post disable_role = app.disableRole
post delete_role = app.deleteRole
post role_fail = app.roleFail
get protected_me = app.me
@@ -0,0 +1 @@
public post probe = app.probe
@@ -0,0 +1,11 @@
authenticated get me = app.me
authenticated post me = app.me
public get health = app.health
public post login = app.login
public post logout = app.logout
public post login_fail = app.loginFail
@@ -0,0 +1,11 @@
type Work { subject: Text }
jobSubmit(subject: Text, key: Text) (id: Id) {
id = dever.job.enqueue(job.perform, Work { subject = subject }, key)
}
jobApply(input: Work) () {
created = seed(input.subject, "from-job")
}
jobCount() (total: Int) { total = model.count() }
@@ -0,0 +1,22 @@
type Metadata {
request_id: Text
method: Text
path: Text
address: Text
header: Text?
cookie: Text?
}
metadata() (value: Metadata) {
id = dever.api.requestId()
dever.api.setHeader("X-Request-Id", id)
dever.api.setCookie("theme", "dark", dever.api.secureCookieOptions())
options = dever.api.CookieOptions { path = "/", domain = null, same_site = dever.api.SameSite.Strict, secure = true, http_only = true, max_age = 60 }
dever.api.setCookie("strict", "yes", options)
value = Metadata { request_id = id, method = dever.api.method(), path = dever.api.path(), address = dever.api.clientAddress(), header = dever.api.header("X-Test"), cookie = dever.api.cookie("theme") }
}
metadataFail() () {
value = metadata()
fail(dever.api.Error.Conflict)
}
@@ -0,0 +1,48 @@
permissions() (values: List<dever.auth.Permission>) {
values = dever.auth.permissions()
}
currentPermissions() (values: List<dever.auth.Permission>) {
values = dever.auth.currentPermissions()
}
roles() (values: List<dever.auth.Role>) {
values = dever.auth.roles()
}
role(id: Text) (value: dever.auth.Role?) {
value = dever.auth.role(id)
}
userRoles(user_id: Int) (values: List<dever.auth.Role>) {
values = dever.auth.userRoles(user_id)
}
isOwner(user_id: Int) (value: Bool) {
value = dever.auth.isOwner(user_id)
}
saveRole(id: Text, name: Text, all_permissions: Bool, permission_keys: List<Text>) () {
dever.auth.saveRole(id, name, all_permissions, permission_keys)
}
grantRole(user_id: Int, role_id: Text) () {
dever.auth.grantRole(user_id, role_id)
}
revokeRole(user_id: Int, role_id: Text) () {
dever.auth.revokeRole(user_id, role_id)
}
disableRole(role_id: Text) () {
dever.auth.disableRole(role_id)
}
deleteRole(role_id: Text) () {
dever.auth.deleteRole(role_id)
}
roleFail(id: Text) () {
dever.auth.saveRole(id, "failed", false, [])
fail(dever.api.Error.Conflict)
}
@@ -0,0 +1,6 @@
type Credentials { key: Secret }
probe(input: Credentials) (size: Int) {
digest = dever.crypto.hmacSha256(input.key, dever.bytes.fromText("fixture"))
size = length(digest)
}
@@ -0,0 +1,54 @@
type Identity {
id: Text
user_id: model.id?
tenant_id: model.tenant.id?
}
verify(claims: dever.auth.Claims) (verified: Identity) {
account = model.first({ where = subject == claims.subject and session == claims.session and active == true and expires_at > dever.time.unixMillis() })
verified = identity(account)
}
identity(account: model.Account) (verified: Identity) {
verified = Identity { id = account.subject, user_id = account.id, tenant_id = null }
}
identity(account: null) (verified: Identity) { fail(dever.api.Error.Unauthorized) }
seed(subject: Text, session: Text) (account: model.Account) {
account = model.create({ subject = subject, session = session, active = true, expires_at = dever.time.unixMillis() + 600000 })
}
setDeadline(id: model.id, expires: Int) (deadline: Int) {
account = model.update(id, { expires_at = expires })
deadline = account.expires_at
}
revoke(id: model.id) (disabled: Bool) {
account = model.update(id, { active = false })
disabled = not account.active
}
me() (profile: Profile) {
profile = Profile { id = dever.auth.id(), session = dever.auth.session(), site = dever.site.key(), user_id = dever.auth.userId(), tenant_id = dever.auth.tenantId() }
}
health() (ready: Bool) { ready = true }
type Profile { id: Text
session: Text
site: Text
user_id: model.id?
tenant_id: model.tenant.id?
}
login(subject: Text, session: Text) () {
verified = verify(dever.auth.Claims { subject = subject, session = session, tenant = null, site = dever.site.key() })
dever.auth.issueCookie(verified.id, session, null)
}
logout() () { dever.auth.clearCookie() }
loginFail(subject: Text, session: Text) () {
login(subject, session)
fail(dever.api.Error.Conflict)
}
@@ -0,0 +1,15 @@
# 任务权限入口
只有受保护入口才能以 User 身份提交任务。
### submit
权限成功后保存可信上下文。
### system
本机命令拥有明确 System 身份。
### count
本机读取业务结果。
@@ -0,0 +1,11 @@
# 请求元数据入口
公开 POST 也遵守 Cookie 写入的同源要求。
### metadata
成功时发布头与 Cookie。
### metadata_fail
失败时丢弃暂存响应字段。
@@ -0,0 +1,7 @@
# 自动资源接口
生成受保护的列表、详情、创建、替换与删除接口。
### resource
显式选择同领域 Model topic。
@@ -0,0 +1,55 @@
# 角色接口
写入仅允许受保护 API;读取允许已认证用户。
### permissions
已认证读取当前站点。
### current_permissions
已认证读取当前站点。
### roles
已认证读取当前站点。
### role
已认证读取当前站点。
### user_roles
已认证读取当前站点。
### is_owner
已认证读取当前站点。
### save_role
执行前核验精确角色权限。
### grant_role
执行前核验精确角色权限。
### revoke_role
执行前核验精确角色权限。
### disable_role
执行前核验精确角色权限。
### delete_role
执行前核验精确角色权限。
### role_fail
执行前核验精确角色权限。
### protected_me
只有持有本入口权限的可信用户才能读取。
@@ -0,0 +1,7 @@
# Secret 输入验收
验证合法敏感输入不被输出 codec 错误拒绝。
### probe
通过 JSON 输入 Secret record。
@@ -0,0 +1,27 @@
# 身份认证接口
authenticated 仅验证身份;角色授权入口仍使用独立的权限检查。
### read
未认证请求不能执行 App。
### write
Cookie 非安全方法必须进行同源检查。
### health
允许匿名访问的单个声明。
### login
登录入口验证凭据之后签发 Cookie。
### logout
匿名也能清除旧 Cookie,但不能绕过同源检查。
### login_fail
业务失败时不发布 Cookie。
@@ -0,0 +1,15 @@
# 认证测试命令
测试命令只操作自有临时数据库。
### seed
创建测试账号。
### set_deadline
调整本测试数据库的会话截止时间。
### revoke
禁用测试账号。
@@ -0,0 +1,32 @@
# 身份任务
排队身份由入口建立,payload 只包含业务名称。
## demo.account.Work
保存业务输入。
字段:`subject`:名称
## demo.account.jobSubmit
提交任务,不能用输入替代执行者。
输入:
- `subject`:名称
- `key`:幂等键
输出:`id`:任务 ID
## demo.account.jobApply
通过已有账号业务保存任务结果。
输入:`input`:业务数据
输出:无
## demo.account.jobCount
从独立命令读取已提交结果数量。
输入:无
输出:`total`:账号数量
@@ -0,0 +1,29 @@
# 请求元数据业务
读取直接请求信息,并暂存成功响应的头和 Cookie。
## demo.account.Metadata
输出不含身份令牌或敏感请求头。
字段:
- `request_id`:请求标识
- `method`:方法
- `path`:路径
- `address`:直接客户端 IP
- `header`:自定义测试头
- `cookie`:普通 Cookie
## demo.account.metadata
读取当前请求,并为成功响应设置元数据。
输入:无
输出:`value`:元数据
## demo.account.metadataFail
业务失败时必须丢弃之前暂存的元数据。
输入:无
输出:无
@@ -0,0 +1,101 @@
# 角色业务验收
通过 Dever 标准库操作当前站点角色,用真实 HTTP 验证事务和权限。
## demo.account.permissions
复用当前入口的可信上下文和数据库事务。
输入:无
输出:`values`:授权结果
## demo.account.currentPermissions
复用当前入口的可信上下文和数据库事务。
输入:无
输出:`values`:授权结果
## demo.account.roles
复用当前入口的可信上下文和数据库事务。
输入:无
输出:`values`:授权结果
## demo.account.role
复用当前入口的可信上下文和数据库事务。
输入:
- `id`:授权参数
输出:`value`:授权结果
## demo.account.userRoles
复用当前入口的可信上下文和数据库事务。
输入:
- `user_id`:授权参数
输出:`values`:授权结果
## demo.account.isOwner
复用当前入口的可信上下文和数据库事务。
输入:
- `user_id`:授权参数
输出:`value`:授权结果
## demo.account.saveRole
复用当前入口的可信上下文和数据库事务。
输入:
- `id`:授权参数
- `name`:授权参数
- `all_permissions`:授权参数
- `permission_keys`:授权参数
输出:无
## demo.account.grantRole
复用当前入口的可信上下文和数据库事务。
输入:
- `user_id`:授权参数
- `role_id`:授权参数
输出:无
## demo.account.revokeRole
复用当前入口的可信上下文和数据库事务。
输入:
- `user_id`:授权参数
- `role_id`:授权参数
输出:无
## demo.account.disableRole
复用当前入口的可信上下文和数据库事务。
输入:
- `role_id`:授权参数
输出:无
## demo.account.deleteRole
复用当前入口的可信上下文和数据库事务。
输入:
- `role_id`:授权参数
输出:无
## demo.account.roleFail
复用当前入口的可信上下文和数据库事务。
输入:
- `id`:授权参数
输出:无
@@ -0,0 +1,16 @@
# 敏感 HTTP 输入
输入 record 可以包含 Secret,但不能生成该 record 的输出序列化器。
## demo.account.Credentials
密钥只能传给密码学函数。
字段:`key`:测试密钥
## demo.account.probe
只返回摘要的长度。
输入:`input`:密钥输入
输出:`size`:摘要字节数
@@ -0,0 +1,101 @@
# 会话认证验收
Token 验证之后仍读取账号与会话状态;认证入口不能绕过此函数。
## demo.account.Identity
身份字段不携带权限,两个 ID 保持各自 Model 类型。
字段:
- `id`:身份标识
- `user_id`:用户 ID
- `tenant_id`:租户 ID
## demo.account.verify
只读取全局库和当前时钟,校验账号、会话与服务端截止时间。
输入:`claims`:已签名的声明
输出:`verified`:可信身份
## demo.account.identity
未找到有效会话时显式拒绝。
输入:`account`:匹配的账号
输出:`verified`:可信身份
## demo.account.seed
只通过测试 CMD 准备临时数据库。
输入:
- `subject`:账号标识
- `session`:会话标识
输出:`account`:新账号
## demo.account.setDeadline
仅供测试 CMD 调整截止时间,不依赖等待或修改 Token。
输入:
- `id`:账号 ID
- `expires`:新的截止时间
输出:`deadline`:保存后的截止时间
## demo.account.revoke
禁用账号后,原有 Token 也必须被 verify 拒绝。
输入:`id`:账号 ID
输出:`disabled`:是否已禁用
## demo.account.me
验证认证发生在调用业务之前。
输入:无
输出:`profile`:当前可信身份
## demo.account.health
公开接口不因浏览器旧 Cookie 无法访问。
输入:无
输出:`ready`:是否可用
## demo.account.Profile
身份数据来自隐藏上下文,业务参数不能覆盖它。
字段:
- `id`:身份标识
- `session`:会话标识
- `site`:当前站点
- `user_id`:用户 ID
- `tenant_id`:租户 ID
## demo.account.login
测试凭据匹配后暂存 Cookie;使用同一全局会话验证函数。
输入:
- `subject`:账号标识
- `session`:测试会话凭据
输出:无
## demo.account.logout
清除当前站点 Cookie,仍需同源检查。
输入:无
输出:无
## demo.account.loginFail
在暂存 Cookie 后失败,验证响应不能发布尚未提交的凭据。
输入:
- `subject`:账号标识
- `session`:测试会话凭据
输出:无
@@ -0,0 +1,14 @@
# 可信任务执行
worker 必须重新验证入队者。
### database
任务和账号共用连接。
## demo.account.job.perform
调用同领域业务。
输入:`input`:业务数据
输出:无
@@ -0,0 +1,26 @@
# 自动 REST 数据
REST 只暴露公开字段,私有备注保持数据库内部值。
## demo.account.model.profile.Status
持久化选择项使用稳定名称,显示标签不参与存储。
分支:
- `Active`:启用
- `Disabled`:禁用
## demo.account.model.profile.Profile
公开名称和可选简介,私有备注不可由请求提交。创建键用请求方法和可信身份构成,仅创建时写入;唯一约束让原生测试验证 PUT 确实保留该值。
字段:
- `private user_id: model.id`:可信记录所有者
- `name`:名称
- `bio`:简介
- `rank`:默认排序
- `tag`:允许显式清空的默认标签
- `uid`:不可由客户端写入的生成标识
- `status`:状态
- `private note: Text?`:内部备注
- `private creation_key: Text`:仅创建时写入的内部唯一键
@@ -0,0 +1,9 @@
# 租户身份类型
本例仅验证非租户身份,返回的 tenant_id 必须为空。
## demo.account.model.tenant.Tenant
保留租户 ID 的名义类型。
字段:`name`:名称
@@ -0,0 +1,13 @@
# 全局会话账号
用于原生认证验收;业务 verify 只读取 global Model。
## demo.account.model.Account
身份和会话状态由数据库决定。
字段:
- `subject`:账号标识
- `session`:会话标识
- `active`:是否允许认证
- `expires_at`:会话截止时间的 Unix 毫秒数
@@ -0,0 +1,5 @@
database default
job perform(input: app.Work) () retry(1) timeout(1000) {
app.jobApply(input)
}
@@ -0,0 +1,6 @@
global type Account {
subject: Text unique
session: Text
active: Bool
expires_at: Int
}
@@ -0,0 +1,16 @@
type Status {
Active = "启用"
Disabled = "禁用"
}
type Profile {
private owner user_id: model.id = dever.auth.userId()
name: Text(1, 32) unique replace = text.lower(input) search = text.trim(input)
bio: Text?
rank: Int default 7
tag: Text? default "automatic"
uid: Uuid generated unique
status: Status default Status.Active
private note: Text? create = dever.api.header("x-source") replace = dever.api.header("x-source")
private creation_key: Text unique create = dever.api.method() + ":" + dever.auth.id()
}
@@ -0,0 +1 @@
global type Tenant { name: Text }
@@ -0,0 +1,12 @@
# document
本领域的职责与行为见主题说明;函数签名和调用关系从源码读取。
## 说明
- [原始 JSON 资源接口](doc/api-admin-content.md)
- [原始 JSON 输出入口](doc/api-admin-encode.md)
- [原始 JSON 输出对照](doc/app.md)
- [REST 原始 JSON 字段](doc/model.md)
[调用流程](link.md)由 `dever inspect . --links` 生成。
@@ -0,0 +1 @@
rest model
@@ -0,0 +1 @@
public post encode = app.encode
@@ -0,0 +1,3 @@
encode(value: Json) (answer: Text) {
answer = dever.json.toText(dever.json.encode(value))
}
@@ -0,0 +1,7 @@
# 原始 JSON 资源接口
复用认证后的自动 REST 输入边界。
### resource
创建和替换都验证整个请求的 JSON 预算。
@@ -0,0 +1,7 @@
# 原始 JSON 输出入口
让输出对照经过应用函数的正式检查与生成。
### encode
返回输入 Json 的文本表示。
@@ -0,0 +1,12 @@
# 原始 JSON 输出对照
从真实 Dever 函数生成已有 Json 的编码路径。
## demo.document.encode
返回已经验证的 Json,不在基准中另写输出实现。
输入:
- `value`:已经验证的内容
输出:
- `answer`:JSON 原文
@@ -0,0 +1,11 @@
# REST 原始 JSON 字段
验证必填与可空 JSON 字段的输入边界。
## demo.document.model.Document
JSON 内容保留原始数字和内部空白。
字段:
- `value`:必填内容
- `extra`:可空内容
@@ -0,0 +1,4 @@
global type Document {
value: Json
extra: Json?
}