v2 platform foundation (docs 01/02/03): Action Runtime + Registry + unified API

- v2 foundation: ActionResult/DevError/ActorContext/ResourceRef/Execution types
- ActionRegistry + ResourceRegistry (namespaced types, FAIL FAST on duplicates)
- ActionRuntime pipeline: validate -> authorize -> middleware -> handler ->
  output validation -> audit -> event -> execution persisted (devos_action_executions)
- Legacy bridge: v1 services registered as devops.*/core.* resources & actions (47 actions, 9 resources)
- Unified Action API: POST /api/v2/actions/:actionId, GET discovery (actions/resources)
- 10 v2 unit tests (registry fail-fast, validation, authz, failure audit, events)
This commit is contained in:
phptits
2026-09-10 21:09:44 +00:00
parent 0d6cd37579
commit b1f17cf637
30 changed files with 60110 additions and 3 deletions
@@ -0,0 +1,73 @@
/**
* # Resource Registry(docs 03 §11/§12)
* 平台级资源类型注册表:命名空间类型(core.user / devops.server / seo.website),
* 每个 Resource 声明 schema、actions、UI metadata。启动阶段 Core → Apps → Plugins 依次注册。
*/
import type { z } from "zod";
import type { ResourceRef } from "../foundation/types";
/** # 资源事件定义 **/
export interface ResourceEventDefinition {
/** # 形如 resource.created **/
type: string;
description?: string;
}
/** # 资源 UI 元数据(通用 UI 自动生成用) **/
export interface ResourceUI {
icon?: string;
listColumns?: string[];
}
/** # Resource 定义(docs 03 §5) **/
export interface ResourceDefinition<T = unknown> {
/** # 命名空间类型 <namespace>.<resource> **/
type: string;
label: string;
description?: string;
schema?: z.ZodType<T>;
/** # 该资源拥有的 Action id 列表(注册到 ActionRegistry) **/
actionIds: string[];
events?: ResourceEventDefinition[];
ui?: ResourceUI;
/** # 所有权:organization → 授权自动带组织 scope **/
ownership?: { organization: boolean };
/** # 父资源(Project → Website → Page) **/
parent?: { type: string };
}
export class ResourceRegistry {
private resources = new Map<string, ResourceDefinition<never>>();
/** # 注册:重复 type FAIL FAST(docs 03 §87) */
register(resource: ResourceDefinition<never>, registeredBy = "core"): void {
if (!resource.type.includes(".")) {
throw new Error(`[v2] resource type must be namespaced: ${resource.type}`);
}
if (this.resources.has(resource.type)) {
throw new Error(`[v2] duplicate resource type: ${resource.type}`);
}
this.resources.set(resource.type, resource);
}
get(type: string): ResourceDefinition<never> | undefined {
return this.resources.get(type);
}
has(type: string): boolean {
return this.resources.has(type);
}
list(): ResourceDefinition<never>[] {
return [...this.resources.values()].map((r) => r);
}
/** # ResourceRef → 定义(校验类型存在) */
resolveRef(ref: ResourceRef): ResourceDefinition<never> {
const def = this.resources.get(ref.type);
if (!def) throw new Error(`[v2] unknown resource type: ${ref.type}`);
return def;
}
}
export const resourceRegistry = new ResourceRegistry();