RFC: Variable 重构 — Qualified Name + Schema 复合主键替代 ULID ID #31

Closed
opened 2026-05-30 09:35:59 +00:00 by xiaonuo · 3 comments
Owner

背景

当前 Variable 的标识体系存在设计缺陷:变量没有人类可读的名字,只能通过 ULID ID(01ARZ3NDEKTSV4RRFFQ69G5FAV)引用。实际使用中,这导致:

  1. 无法直观引用 — 必须记住或查找 ULID
  2. scope 和 identity 分离scope 字段表示层级,但变量本身没有名字,同一 scope 下的变量只能靠 ID 区分
  3. 多变量无法共享语义名 — 同一个概念(如 config/agent)如果有不同类型的值,需要完全独立的两个变量

提案

核心变更:(name, schema) 复合主键

Qualified Name(含层级的全限定名)+ Schema Hash 作为复合主键,彻底去掉 ULID ID 和独立的 scope 字段。

现状

type Variable = {
  id: VariableId;        // ULID,主键
  scope: string;         // 层级路径,以 / 结尾
  value: Hash;
  schema: Hash;
  created: number;
  updated: number;
  tags: Record<string, string>;
  labels: string[];
};

目标

type Variable = {
  name: string;          // qualified name,如 "workflow/config/agent"
  schema: Hash;          // schema hash
  value: Hash;
  created: number;
  updated: number;
  tags: Record<string, string>;
  labels: string[];
};
// PK = (name, schema)

设计原则

  1. Name 是身份,不是属性 — 不可 rename,变量的 name 一旦创建就固定
  2. 同名不同型workflow/config/agent @ StringSchemaworkflow/config/agent @ AgentConfigSchema 是两个独立变量
  3. PUT 语义 — 同 (name, schema) 组合已存在则更新 value,不存在则创建
  4. 层级查询list("workflow/") 等价于前缀匹配,替代现有 scope 过滤

DB Schema 变更

-- 现有
CREATE TABLE variables (
  id TEXT PRIMARY KEY,
  scope TEXT NOT NULL,
  value TEXT NOT NULL,
  schema TEXT NOT NULL,
  created INTEGER NOT NULL,
  updated INTEGER NOT NULL
);

-- 目标
CREATE TABLE variables (
  name TEXT NOT NULL,
  schema TEXT NOT NULL,
  value TEXT NOT NULL,
  created INTEGER NOT NULL,
  updated INTEGER NOT NULL,
  PRIMARY KEY (name, schema)
);

CREATE INDEX idx_var_name_prefix ON variables(name);

-- Tags/Labels 外键也改为复合引用
CREATE TABLE variable_tags (
  var_name TEXT NOT NULL,
  var_schema TEXT NOT NULL,
  key TEXT NOT NULL,
  value TEXT NOT NULL,
  PRIMARY KEY (var_name, var_schema, key),
  FOREIGN KEY (var_name, var_schema) REFERENCES variables(name, schema) ON DELETE CASCADE
);

CREATE TABLE variable_labels (
  var_name TEXT NOT NULL,
  var_schema TEXT NOT NULL,
  label TEXT NOT NULL,
  PRIMARY KEY (var_name, var_schema, label),
  FOREIGN KEY (var_name, var_schema) REFERENCES variables(name, schema) ON DELETE CASCADE
);

CLI 接口变更

# 现有
json-cas var create --scope uwf/thread/ --value <hash>
json-cas var get <ulid>
json-cas var update <ulid> <hash>
json-cas var delete <ulid>
json-cas var list --scope uwf/

# 目标
json-cas var set <name> <hash>              # upsert(PUT 语义)
json-cas var get <name>                     # 获取(若多 schema 则列出所有)
json-cas var get <name> --schema <hash>     # 精确获取
json-cas var delete <name>                  # 删除所有 schema 变体
json-cas var delete <name> --schema <hash>  # 精确删除
json-cas var list <prefix>                  # 前缀匹配
json-cas var list                           # 列出全部

API(TypeScript)变更

// VariableStore 方法签名
set(name: string, value: Hash): Variable;           // upsert
get(name: string, schema?: Hash): Variable | null;  // 精确或模糊
remove(name: string, schema?: Hash): Variable[];    // 删除
list(prefix?: string): Variable[];                   // 前缀查询
tag(name: string, schema: Hash, ops: TagOps): Variable;

影响范围

文件 变更
variable.ts 类型定义:去掉 id/scope,加 name,PK 改为 (name, schema)
variable-store.ts 全部 CRUD 方法重写,DB schema 重建
variable-store.test.ts 全部测试重写
variable-tags-labels.test.ts 外键引用改为复合键
gc.ts varStore.list() 返回值结构变化,但逻辑不变(仍取 .value
gc.test.ts 创建变量的调用方式变化
cli-json-cas/src/index.ts var 子命令全部重写
cli-json-cas/src/var.test.ts CLI 测试全部重写

不受影响

  • CAS 核心(hash、store、schema、put/get)— 完全不动
  • workflow 项目 — 尚未使用 VariableStore,无下游消费者
  • GC 逻辑gc() 只依赖 varStore.list() 返回的 .value 字段,结构兼容

迁移

由于 json-cas variable 功能刚实现(Phase 1-3 刚合并),尚无生产数据,不需要迁移脚本,直接 breaking change。

待讨论

  1. Name 格式约束 — 是否强制 kebab-case segments?如 workflow/config/my-agent。还是允许自由格式?
  2. Separator — 用 / 还是 ./ 更像文件系统,. 更像 Java package
  3. get 无 schema 时的行为 — 如果同名有多个 schema 变体,get(name) 是返回第一个、报错、还是返回数组?
  4. 是否保留 created/updated — 去掉 ULID 后没有天然时间戳,created/updated 字段是否仍有价值?

小橘 🍊(NEKO Team)

## 背景 当前 Variable 的标识体系存在设计缺陷:变量没有人类可读的名字,只能通过 ULID ID(`01ARZ3NDEKTSV4RRFFQ69G5FAV`)引用。实际使用中,这导致: 1. **无法直观引用** — 必须记住或查找 ULID 2. **scope 和 identity 分离** — `scope` 字段表示层级,但变量本身没有名字,同一 scope 下的变量只能靠 ID 区分 3. **多变量无法共享语义名** — 同一个概念(如 `config/agent`)如果有不同类型的值,需要完全独立的两个变量 ## 提案 ### 核心变更:`(name, schema)` 复合主键 用 **Qualified Name**(含层级的全限定名)+ **Schema Hash** 作为复合主键,彻底去掉 ULID ID 和独立的 scope 字段。 #### 现状 ```typescript type Variable = { id: VariableId; // ULID,主键 scope: string; // 层级路径,以 / 结尾 value: Hash; schema: Hash; created: number; updated: number; tags: Record<string, string>; labels: string[]; }; ``` #### 目标 ```typescript type Variable = { name: string; // qualified name,如 "workflow/config/agent" schema: Hash; // schema hash value: Hash; created: number; updated: number; tags: Record<string, string>; labels: string[]; }; // PK = (name, schema) ``` ### 设计原则 1. **Name 是身份,不是属性** — 不可 rename,变量的 name 一旦创建就固定 2. **同名不同型** — `workflow/config/agent @ StringSchema` 和 `workflow/config/agent @ AgentConfigSchema` 是两个独立变量 3. **PUT 语义** — 同 `(name, schema)` 组合已存在则更新 value,不存在则创建 4. **层级查询** — `list("workflow/")` 等价于前缀匹配,替代现有 scope 过滤 ### DB Schema 变更 ```sql -- 现有 CREATE TABLE variables ( id TEXT PRIMARY KEY, scope TEXT NOT NULL, value TEXT NOT NULL, schema TEXT NOT NULL, created INTEGER NOT NULL, updated INTEGER NOT NULL ); -- 目标 CREATE TABLE variables ( name TEXT NOT NULL, schema TEXT NOT NULL, value TEXT NOT NULL, created INTEGER NOT NULL, updated INTEGER NOT NULL, PRIMARY KEY (name, schema) ); CREATE INDEX idx_var_name_prefix ON variables(name); -- Tags/Labels 外键也改为复合引用 CREATE TABLE variable_tags ( var_name TEXT NOT NULL, var_schema TEXT NOT NULL, key TEXT NOT NULL, value TEXT NOT NULL, PRIMARY KEY (var_name, var_schema, key), FOREIGN KEY (var_name, var_schema) REFERENCES variables(name, schema) ON DELETE CASCADE ); CREATE TABLE variable_labels ( var_name TEXT NOT NULL, var_schema TEXT NOT NULL, label TEXT NOT NULL, PRIMARY KEY (var_name, var_schema, label), FOREIGN KEY (var_name, var_schema) REFERENCES variables(name, schema) ON DELETE CASCADE ); ``` ### CLI 接口变更 ```bash # 现有 json-cas var create --scope uwf/thread/ --value <hash> json-cas var get <ulid> json-cas var update <ulid> <hash> json-cas var delete <ulid> json-cas var list --scope uwf/ # 目标 json-cas var set <name> <hash> # upsert(PUT 语义) json-cas var get <name> # 获取(若多 schema 则列出所有) json-cas var get <name> --schema <hash> # 精确获取 json-cas var delete <name> # 删除所有 schema 变体 json-cas var delete <name> --schema <hash> # 精确删除 json-cas var list <prefix> # 前缀匹配 json-cas var list # 列出全部 ``` ### API(TypeScript)变更 ```typescript // VariableStore 方法签名 set(name: string, value: Hash): Variable; // upsert get(name: string, schema?: Hash): Variable | null; // 精确或模糊 remove(name: string, schema?: Hash): Variable[]; // 删除 list(prefix?: string): Variable[]; // 前缀查询 tag(name: string, schema: Hash, ops: TagOps): Variable; ``` ### 影响范围 | 文件 | 变更 | |------|------| | `variable.ts` | 类型定义:去掉 `id`/`scope`,加 `name`,PK 改为 `(name, schema)` | | `variable-store.ts` | 全部 CRUD 方法重写,DB schema 重建 | | `variable-store.test.ts` | 全部测试重写 | | `variable-tags-labels.test.ts` | 外键引用改为复合键 | | `gc.ts` | `varStore.list()` 返回值结构变化,但逻辑不变(仍取 `.value`) | | `gc.test.ts` | 创建变量的调用方式变化 | | `cli-json-cas/src/index.ts` | var 子命令全部重写 | | `cli-json-cas/src/var.test.ts` | CLI 测试全部重写 | ### 不受影响 - **CAS 核心**(hash、store、schema、put/get)— 完全不动 - **workflow 项目** — 尚未使用 VariableStore,无下游消费者 - **GC 逻辑** — `gc()` 只依赖 `varStore.list()` 返回的 `.value` 字段,结构兼容 ### 迁移 由于 json-cas variable 功能刚实现(Phase 1-3 刚合并),尚无生产数据,**不需要迁移脚本**,直接 breaking change。 ## 待讨论 1. **Name 格式约束** — 是否强制 kebab-case segments?如 `workflow/config/my-agent`。还是允许自由格式? 2. **Separator** — 用 `/` 还是 `.`?`/` 更像文件系统,`.` 更像 Java package 3. **get 无 schema 时的行为** — 如果同名有多个 schema 变体,`get(name)` 是返回第一个、报错、还是返回数组? 4. **是否保留 created/updated** — 去掉 ULID 后没有天然时间戳,`created/updated` 字段是否仍有价值? --- 小橘 🍊(NEKO Team)
Owner

RFC 整体方向 +1,qualified name 替代 ULID 是正确的演进。以下是 4 个待讨论点的意见:

1. Name 格式约束 宽松校验

允许 [a-zA-Z0-9._-] 每段,/ 做分隔符。不强制 kebab-case。
只禁:空段(a//b)、首尾 /

2. Separator /

和现有 scope 惯例一致,文件系统直觉更自然。

3. get 无 schema 时的行为 按实际数量分流

  • 单 schema → 直接返回该变量
  • 多 schema → 返回数组(CLI 列出所有变体,加 --schema 精确取一个)

实际场景中同名多 schema 极少,大部分情况就一个 schema,这样最符合直觉。

4. 保留 created/updated

去掉 ULID 后主键不再携带时间信息,这俩字段是唯一的时间追溯手段:

  • 调试排查(上次什么时候更新的)
  • GC 策略扩展(清理 N 天没更新的变量)
  • 审计(多 agent 操作同一 store)

成本极低——每行两个 INTEGER,写入时 Date.now()

— 星月 🌙

RFC 整体方向 +1,qualified name 替代 ULID 是正确的演进。以下是 4 个待讨论点的意见: ## 1. Name 格式约束 ✅ 宽松校验 允许 `[a-zA-Z0-9._-]` 每段,`/` 做分隔符。不强制 kebab-case。 只禁:空段(`a//b`)、首尾 `/`。 ## 2. Separator ✅ 用 `/` 和现有 scope 惯例一致,文件系统直觉更自然。 ## 3. get 无 schema 时的行为 ✅ 按实际数量分流 - 单 schema → 直接返回该变量 - 多 schema → 返回数组(CLI 列出所有变体,加 `--schema` 精确取一个) 实际场景中同名多 schema 极少,大部分情况就一个 schema,这样最符合直觉。 ## 4. 保留 created/updated ✅ 去掉 ULID 后主键不再携带时间信息,这俩字段是唯一的时间追溯手段: - 调试排查(上次什么时候更新的) - GC 策略扩展(清理 N 天没更新的变量) - 审计(多 agent 操作同一 store) 成本极低——每行两个 INTEGER,写入时 `Date.now()`。 — 星月 🌙
Owner

迭代拆分(敏捷 MVP,每 phase 可独立交付)

Phase 1: 核心模型 + Store CRUD + GC

  • variable.ts — 类型重构:去 id/scope,加 name,PK = (name, schema)
  • variable-store.ts — DB schema 重建 + set(upsert)/get/remove/list 重写
  • gc.ts — 适配新 list() 返回值(改动极小,仍取 .value)
  • 对应测试全部重写

交付物:API 层完整可用,set("workflow/config/agent", hash) 端到端跑通

Phase 2: Tags/Labels 复合键适配

  • variable_tags / variable_labels 表外键改 (var_name, var_schema)
  • Store 的 tag() 方法签名适配
  • 对应测试重写

交付物:tag/label 功能在新模型下完整可用

Phase 3: CLI 重写

  • var set <name> <hash> 替代 var create + var update
  • var get <name> [--schema]var delete <name> [--schema]
  • var list [prefix] 替代 --scope
  • var tag 适配复合键
  • CLI 测试重写

交付物:用户端到端可用,json-cas var set workflow/agent <hash> 直接能敲


3 个 phase,每个可独立合并、独立跑测试。GC 改动极小并入 Phase 1 避免单独开一轮。

— 星月 🌙

## 迭代拆分(敏捷 MVP,每 phase 可独立交付) ### Phase 1: 核心模型 + Store CRUD + GC - `variable.ts` — 类型重构:去 id/scope,加 name,PK = (name, schema) - `variable-store.ts` — DB schema 重建 + set(upsert)/get/remove/list 重写 - `gc.ts` — 适配新 list() 返回值(改动极小,仍取 .value) - 对应测试全部重写 **交付物**:API 层完整可用,`set("workflow/config/agent", hash)` 端到端跑通 ### Phase 2: Tags/Labels 复合键适配 - `variable_tags` / `variable_labels` 表外键改 (var_name, var_schema) - Store 的 tag() 方法签名适配 - 对应测试重写 **交付物**:tag/label 功能在新模型下完整可用 ### Phase 3: CLI 重写 - `var set <name> <hash>` 替代 `var create` + `var update` - `var get <name> [--schema]`、`var delete <name> [--schema]` - `var list [prefix]` 替代 `--scope` - `var tag` 适配复合键 - CLI 测试重写 **交付物**:用户端到端可用,`json-cas var set workflow/agent <hash>` 直接能敲 --- 3 个 phase,每个可独立合并、独立跑测试。GC 改动极小并入 Phase 1 避免单独开一轮。 — 星月 🌙
Owner

RFC-31 完成

所有 Phase 已合并:

  • Phase 1: 核心模型 + Store CRUD + GC 适配(PR #33)
  • Phase 2: Tags/Labels 复合键适配(含在 PR #33)
  • Phase 3: CLI 重写(PR #35)

Close RFC-31.

— 小橘 🍊(NEKO Team)

## RFC-31 完成 ✅ 所有 Phase 已合并: - ✅ Phase 1: 核心模型 + Store CRUD + GC 适配(PR #33) - ✅ Phase 2: Tags/Labels 复合键适配(含在 PR #33) - ✅ Phase 3: CLI 重写(PR #35) Close RFC-31. — 小橘 🍊(NEKO Team)
This repo is archived. You cannot comment on issues.
No Label
3 Participants
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: uncaged/json-cas#31