Commit 0ad9af5975cd38c5fef1e5333c6d78fd93149551
1 parent
54470be3
refactor(db): 删掉 system_agents.source_kind;legacy_path 留下,写清退出条件
28-followups §3.1。**审计结论与那条待办原来的写法不一样,两列要分开处理。**
## source_kind:删
与 origin 一一映射(global_agent↔builtin / company_hired↔created),
实查生产库 76 行零例外。全仓唯一读侧是 migrate() 里那条 origin 回填,
而那是给「origin 列出现之前写下的行」用的一次性迁移。
顺序:**先回填、再删列**,两步都用 hasColumn 守着(幂等)。反过来做不报错 ——
库能开、测试能过,只是那几行 origin 悄悄是错的。守卫
source-kind-drop-migration.test.js 走存量库重启那条路,专门钉这个顺序,
反向验过:弄反立刻红。
写侧散在 3 处生产 INSERT + 35 个测试文件。批量改的时候正则误伤了 4 处
(吃掉了 JS 默认参数、位置占位符、ON CONFLICT 的赋值行),逐个修回并复查了 diff ——
**这种跨几十个文件的机械改动,靠「全量跑一遍」兜底比靠正则写得对更可靠**。
## legacy_path:不能删,它还没死
原待办说「所有读取点都已改成从 id 推导」——那半句对新行成立,对存量行不成立:
· 新行 legacy_path 恒等于 system/agents/{id}(确实冗余);
· **存量行里存着批次 4 之前的公司侧路径**,而 graph-seed-service 判「悬空行 / 孤儿行」
靠的正是这个路径指向的目录还在不在。从 id 推导出来的路径永远指向 system/agents/,
那条判据会整个失效。
实查生产库:76 行里还有 2 行是老路径(org-planner / org-coder 指着已不存在的
data/companies/mycompany/agents/)—— 正是这条判据要认出来的那种行。
退出条件写进了 graph-db.js 的建表注释(不只留在文档里):
库里不再有 legacy_path <> 'system/agents/' || id 的行。
## 顺带记一条:getDb() 会就地迁移生产库
删列那次,生产库的列是被一条**查状态用的**一行 node -e 删掉的 ——
服务当时没重启,人也没打算迁移。结果是对的(幂等;与备份逐行核对 76 行一致、
origin 全部按映射回填正确),但「读一下状态」不该顺手改 schema 这件事值得知道。
闸门只挡测试进程,故意不挡普通脚本(挡了服务自己也起不来)。
注释里给了只读的正确姿势。
全量:140 文件 / 1299 测试全绿。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Showing
40 changed files
with
268 additions
and
109 deletions
docs/todo/system-design/28-followups.md
| @@ -54,10 +54,44 @@ | @@ -54,10 +54,44 @@ | ||
| 54 | 54 | ||
| 55 | ## 三、未实施的工程项 | 55 | ## 三、未实施的工程项 |
| 56 | 56 | ||
| 57 | -### 3.1 `source_kind` / `legacy_path` 删列(验收 2 后半) | ||
| 58 | -两列仍在库里。`origin` 已经是真相来源,`legacy_path` 的所有**读**取点都已改成从 id 推导路径 | ||
| 59 | -(唯二的例外是 `graph-seed-service` 里判断「悬空行」时拿它做存在性检查,那是正当用法)。 | ||
| 60 | -删列前需确认没有遗漏的读取方。 | 57 | +### 3.1 ~~`source_kind`~~ 已删 / `legacy_path` **不能删**(验收 2 后半) |
| 58 | + | ||
| 59 | +**审计结论与本条原来的写法不一样,两列要分开处理。** | ||
| 60 | + | ||
| 61 | +**`source_kind` ✅ 已删(2026-08-20)**:它与 `origin` 是一一映射 | ||
| 62 | +(`global_agent`↔`builtin` / `company_hired`↔`created`),实查生产库 76 行零例外。 | ||
| 63 | +全仓唯一读侧是 `graph-db.migrate()` 里那条 origin 回填,而那是给「`origin` 列出现之前写下的行」 | ||
| 64 | +用的一次性迁移。所以顺序是**先回填、再删列**,两步都用 `hasColumn` 守着(幂等)。 | ||
| 65 | +写侧散在 3 处生产 INSERT + 35 个测试文件,一并清掉。 | ||
| 66 | +守卫 `source-kind-drop-migration.test.js` 走存量库重启那条路,**专门钉住顺序** —— | ||
| 67 | +反过来做不会报错,只是那几行 origin 悄悄是错的(反向验过,弄反立刻红)。 | ||
| 68 | + | ||
| 69 | +**`legacy_path` ❌ 不能删,它还没死**。原文说「所有读取点都已改成从 id 推导」—— | ||
| 70 | +那半句对新行成立,对存量行不成立: | ||
| 71 | + | ||
| 72 | +- 新写入的行 `legacy_path` 恒等于 `system/agents/{id}`(= 可由 id 推导,确实冗余); | ||
| 73 | +- **存量行里存着批次 4 之前的公司侧路径**(`data/companies/{cid}/agents/{role}`), | ||
| 74 | + 而 `graph-seed-service` 判「悬空行 / 孤儿行」正是靠**这个路径指向的目录还在不在**。 | ||
| 75 | + 从 id 推导出来的路径永远指向 `system/agents/`,那条判据会整个失效。 | ||
| 76 | + | ||
| 77 | +实查生产库(2026-08-20):76 行里**还有 2 行**是老路径(`org-planner` / `org-coder` | ||
| 78 | +指着已经不存在的 `data/companies/mycompany/agents/`)—— 正是这条判据要认出来的那种行。 | ||
| 79 | + | ||
| 80 | +> **删它的退出条件**(写在 `graph-db.js` 建表注释里,别只留在这份文档): | ||
| 81 | +> 库里不再有 `legacy_path <> 'system/agents/' || id` 的行。 | ||
| 82 | +> 到那一天它才真的零信息量。 | ||
| 83 | + | ||
| 84 | +### 3.1b 顺带记一条:`getDb()` 会就地迁移生产库 | ||
| 85 | + | ||
| 86 | +`graph-db.getDb()` 内部调 `migrate()`。**任何 require 本模块并调 `getDb()` 的进程都会改生产库的 schema**, | ||
| 87 | +包括一条随手敲的 `node -e "...getDb()..."`。 | ||
| 88 | + | ||
| 89 | +删 `source_kind` 那次,生产库的列就是被一条**查状态用的**一行脚本删掉的 —— | ||
| 90 | +服务当时根本没重启,人也没打算做迁移。结果是对的(迁移幂等,76 行逐行核对与备份一致、 | ||
| 91 | +origin 全部按映射回填正确),但「读一下状态」不该顺手改 schema 这件事本身值得知道。 | ||
| 92 | + | ||
| 93 | +`assertNotProdDbInTests` 那道闸门**只挡测试进程**,故意不挡普通脚本(挡了服务自己也起不来)。 | ||
| 94 | +要只读地看一眼,用 `new Database('data/graph.sqlite', {readonly: true})`,别走 `getDb()`。 | ||
| 61 | 95 | ||
| 62 | ### 3.2 任务态清理策略(§5.6) | 96 | ### 3.2 任务态清理策略(§5.6) |
| 63 | 只有设计,没有实现。任务目录 `system/agents/{aid}/{cid}/{taskId}/` 在源码树里无限增长。 | 97 | 只有设计,没有实现。任务目录 `system/agents/{aid}/{cid}/{taskId}/` 在源码树里无限增长。 |
server/services/graph-db.js
| @@ -143,10 +143,20 @@ function migrate(database) { | @@ -143,10 +143,20 @@ function migrate(database) { | ||
| 143 | // 回填放在 if 外面,且写成「只改需要改的行」:ALTER 与 UPDATE 是两条语句, | 143 | // 回填放在 if 外面,且写成「只改需要改的行」:ALTER 与 UPDATE 是两条语句, |
| 144 | // 中间挂掉的话 company_hired 行会停在默认的 'builtin' 上,下次 migrate 必须能补上。 | 144 | // 中间挂掉的话 company_hired 行会停在默认的 'builtin' 上,下次 migrate 必须能补上。 |
| 145 | // WHERE 里的 origin <> 'created' 让第二遍变成零行更新(幂等)。 | 145 | // WHERE 里的 origin <> 'created' 让第二遍变成零行更新(幂等)。 |
| 146 | - database.exec(` | ||
| 147 | - UPDATE system_agents SET origin = 'created' | ||
| 148 | - WHERE source_kind = 'company_hired' AND origin <> 'created' | ||
| 149 | - `); | 146 | + // |
| 147 | + // **顺序不能反**:先把 source_kind 的信息回填进 origin,再删列。反过来做的话, | ||
| 148 | + // 「origin 列出现之前写下的行」永远回不到正确的 origin —— 那正是这条回填存在的理由。 | ||
| 149 | + // 两步都用 hasColumn 守着,所以删过之后第二遍是零操作(幂等)。 | ||
| 150 | + if (hasColumn(database, 'system_agents', 'source_kind')) { | ||
| 151 | + database.exec(` | ||
| 152 | + UPDATE system_agents SET origin = 'created' | ||
| 153 | + WHERE source_kind = 'company_hired' AND origin <> 'created' | ||
| 154 | + `); | ||
| 155 | + // 28-followups §3.1:source_kind 与 origin 一一映射,零信息量,读侧只有上面那一条回填。 | ||
| 156 | + // 删列会让所有还带着它的 INSERT 报错,所以这一步必须跑在**任何写入之前** —— | ||
| 157 | + // migrate() 由 getDb() 在建表之后立刻调用,正是那个位置。 | ||
| 158 | + database.exec(`ALTER TABLE system_agents DROP COLUMN source_kind`); | ||
| 159 | + } | ||
| 150 | 160 | ||
| 151 | // 7. 28 批次 4:company_agents 的存量回填。 | 161 | // 7. 28 批次 4:company_agents 的存量回填。 |
| 152 | backfillCompanyAgents(database); | 162 | backfillCompanyAgents(database); |
| @@ -229,6 +239,18 @@ function createEdgesTable(database) { | @@ -229,6 +239,18 @@ function createEdgesTable(database) { | ||
| 229 | * | 239 | * |
| 230 | * 需要读真库的测试显式写 `process.env.GRAPH_DB_PATH = <真库路径>` 表明意图即可。 | 240 | * 需要读真库的测试显式写 `process.env.GRAPH_DB_PATH = <真库路径>` 表明意图即可。 |
| 231 | */ | 241 | */ |
| 242 | +/** | ||
| 243 | + * ⚠️ **`getDb()` 会跑 `migrate()`** —— 也就是说,**任何 require 本模块并调 getDb() 的进程 | ||
| 244 | + * 都会就地修改生产库的 schema**,包括一条随手敲的 `node -e "...getDb()..."`。 | ||
| 245 | + * | ||
| 246 | + * 这不是假想:2026-08-20 删 `source_kind` 那次,生产库的列是被一条**查状态用的** | ||
| 247 | + * 一行脚本删掉的 —— 服务当时根本没重启,人也没打算迁移。结果是对的(迁移本身幂等、 | ||
| 248 | + * 76 行逐行核对无误),但「读一下状态」不该顺手改 schema 这件事本身值得知道。 | ||
| 249 | + * | ||
| 250 | + * 下面这道闸门**只挡测试进程**(避免污染共享数据),故意不挡普通脚本 —— | ||
| 251 | + * 挡了的话服务自己也起不来。要只读地看一眼,用 | ||
| 252 | + * `new (require('better-sqlite3'))('data/graph.sqlite', {readonly:true})`,别走 getDb()。 | ||
| 253 | + */ | ||
| 232 | function assertNotProdDbInTests() { | 254 | function assertNotProdDbInTests() { |
| 233 | if (!process.env.VITEST) return; | 255 | if (!process.env.VITEST) return; |
| 234 | if (process.env.GRAPH_DB_PATH) return; | 256 | if (process.env.GRAPH_DB_PATH) return; |
| @@ -258,10 +280,19 @@ function getDb() { | @@ -258,10 +280,19 @@ function getDb() { | ||
| 258 | name TEXT NOT NULL, | 280 | name TEXT NOT NULL, |
| 259 | scope TEXT NOT NULL CHECK (scope IN ('public','company')), | 281 | scope TEXT NOT NULL CHECK (scope IN ('public','company')), |
| 260 | owner_company_id TEXT, | 282 | owner_company_id TEXT, |
| 261 | - -- source_kind / legacy_path 是 28 批次 1 之后的**遗留列**:读侧已全部改走 | ||
| 262 | - -- origin + 「路径恒为 system/agents/{id}」的推导,这两列只为存量库和还没改完的 | ||
| 263 | - -- 批次(company_agents 表落地在批次 4)留一版兼容,写入侧仍按 origin 一一映射维护。 | ||
| 264 | - source_kind TEXT NOT NULL CHECK (source_kind IN ('global_agent','template','company_hired')), | 283 | + -- legacy_path 是 28 批次 1 之后的遗留列,但**还没死**(28-followups §3.1)。 |
| 284 | + -- 新写入的行它恒等于 system/agents/{id}(= 可由 id 推导,冗余), | ||
| 285 | + -- 但存量行里存着批次 4 之前的公司侧路径(data/companies/{cid}/agents/{role}), | ||
| 286 | + -- 而 graph-seed-service 判「悬空行 / 孤儿行」正是靠「这个路径指向的目录还在不在」。 | ||
| 287 | + -- 删它之前的退出条件:库里不再有 legacy_path <> 'system/agents/' || id 的行。 | ||
| 288 | + -- (2026-08-20 实查生产库:76 行里还有 2 行不满足 —— org-planner / org-coder | ||
| 289 | + -- 指着已经不存在的 data/companies/mycompany/agents/。所以这一版还不能删。) | ||
| 290 | + -- | ||
| 291 | + -- source_kind 已于 2026-08-20 删除:它与 origin 一一映射 | ||
| 292 | + -- (global_agent 对 builtin / company_hired 对 created),实查生产库 76 行零例外, | ||
| 293 | + -- 唯一读侧是 migrate() 里那条 origin 回填,而那是给「origin 列出现之前的行」用的一次性迁移。 | ||
| 294 | + -- 注意:本段刻意不用反引号 —— 这里在 JS 模板字符串里面,反引号会把它提前截断 | ||
| 295 | + -- (踩过两次:一次在这张表,一次在 work-graph-db 的 DDL)。 | ||
| 265 | legacy_path TEXT NOT NULL, | 296 | legacy_path TEXT NOT NULL, |
| 266 | status TEXT NOT NULL DEFAULT 'active', | 297 | status TEXT NOT NULL DEFAULT 'active', |
| 267 | benchmark_score REAL, | 298 | benchmark_score REAL, |
server/services/graph-seed-service.js
| @@ -372,7 +372,7 @@ function reconcileDanglingRows() { | @@ -372,7 +372,7 @@ function reconcileDanglingRows() { | ||
| 372 | * 返回本体路径,等于在公司毫不知情的情况下换掉它的 agent.md。这个死角由批次 4/5 | 372 | * 返回本体路径,等于在公司毫不知情的情况下换掉它的 agent.md。这个死角由批次 4/5 |
| 373 | * (把私有拷贝毕业成本体)根治,不在这里硬来。 | 373 | * (把私有拷贝毕业成本体)根治,不在这里硬来。 |
| 374 | * - 那行是**悬空的**(isStaleCreatedRow)→ 就地转正成 builtin:scope / owner_company_id / | 374 | * - 那行是**悬空的**(isStaleCreatedRow)→ 就地转正成 builtin:scope / owner_company_id / |
| 375 | - * origin / source_kind / legacy_path 全部改写。原来的「一律 skip」让污染不可逆: | 375 | + * origin / legacy_path 全部改写。原来的「一律 skip」让污染不可逆: |
| 376 | * 公司目录删了、行还占着本体 id,本体永远补不回来。转正而不是删了重建, | 376 | * 公司目录删了、行还占着本体 id,本体永远补不回来。转正而不是删了重建, |
| 377 | * 是为了让还引着这行的 org_graph_nodes 外键继续有效。 | 377 | * 是为了让还引着这行的 org_graph_nodes 外键继续有效。 |
| 378 | * | 378 | * |
| @@ -404,17 +404,16 @@ function seedAgents() { | @@ -404,17 +404,16 @@ function seedAgents() { | ||
| 404 | // status 也要取:ON CONFLICT 分支要拿它判「这行已经毕业了,别被 config.json 推回去」 | 404 | // status 也要取:ON CONFLICT 分支要拿它判「这行已经毕业了,别被 config.json 推回去」 |
| 405 | const findExisting = db.prepare(`SELECT id, origin, legacy_path, status FROM system_agents WHERE id = ?`); | 405 | const findExisting = db.prepare(`SELECT id, origin, legacy_path, status FROM system_agents WHERE id = ?`); |
| 406 | 406 | ||
| 407 | - // ON CONFLICT 分支必须连 scope/owner_company_id/origin/source_kind 一起改写,接管才算完成: | 407 | + // ON CONFLICT 分支必须连 scope/owner_company_id/origin 一起改写,接管才算完成: |
| 408 | // 只改 legacy_path 会留下一行「legacy_path 指向 system/agents/ 却仍标着 created」的半吊子记录。 | 408 | // 只改 legacy_path 会留下一行「legacy_path 指向 system/agents/ 却仍标着 created」的半吊子记录。 |
| 409 | // 对本来就是 builtin 的行这几列是原值写原值。 | 409 | // 对本来就是 builtin 的行这几列是原值写原值。 |
| 410 | const upsert = db.prepare(` | 410 | const upsert = db.prepare(` |
| 411 | - INSERT INTO system_agents (id, name, scope, owner_company_id, source_kind, origin, legacy_path, status, benchmark_score, created_at, updated_at) | ||
| 412 | - VALUES (@id, @name, 'public', NULL, 'global_agent', 'builtin', @legacy_path, @status, @benchmark_score, @created_at, @updated_at) | 411 | + INSERT INTO system_agents (id, name, scope, owner_company_id, origin, legacy_path, status, benchmark_score, created_at, updated_at) |
| 412 | + VALUES (@id, @name, 'public', NULL, 'builtin', @legacy_path, @status, @benchmark_score, @created_at, @updated_at) | ||
| 413 | ON CONFLICT(id) DO UPDATE SET | 413 | ON CONFLICT(id) DO UPDATE SET |
| 414 | name = excluded.name, | 414 | name = excluded.name, |
| 415 | scope = excluded.scope, | 415 | scope = excluded.scope, |
| 416 | owner_company_id = excluded.owner_company_id, | 416 | owner_company_id = excluded.owner_company_id, |
| 417 | - source_kind = excluded.source_kind, | ||
| 418 | origin = excluded.origin, | 417 | origin = excluded.origin, |
| 419 | legacy_path = excluded.legacy_path, | 418 | legacy_path = excluded.legacy_path, |
| 420 | status = excluded.status, | 419 | status = excluded.status, |
| @@ -523,8 +522,8 @@ function seedCompanyAgents() { | @@ -523,8 +522,8 @@ function seedCompanyAgents() { | ||
| 523 | 522 | ||
| 524 | const findAgent = db.prepare(`SELECT id FROM system_agents WHERE id = ?`); | 523 | const findAgent = db.prepare(`SELECT id FROM system_agents WHERE id = ?`); |
| 525 | const insertAgent = db.prepare(` | 524 | const insertAgent = db.prepare(` |
| 526 | - INSERT INTO system_agents (id, name, scope, owner_company_id, source_kind, origin, legacy_path, status, benchmark_score, created_at, updated_at) | ||
| 527 | - VALUES (@id, @name, 'company', @owner_company_id, 'company_hired', 'created', @legacy_path, 'active', NULL, @created_at, @updated_at) | 525 | + INSERT INTO system_agents (id, name, scope, owner_company_id, origin, legacy_path, status, benchmark_score, created_at, updated_at) |
| 526 | + VALUES (@id, @name, 'company', @owner_company_id, 'created', @legacy_path, 'active', NULL, @created_at, @updated_at) | ||
| 528 | ON CONFLICT(id) DO NOTHING | 527 | ON CONFLICT(id) DO NOTHING |
| 529 | `); | 528 | `); |
| 530 | const findNode = db.prepare(` | 529 | const findNode = db.prepare(` |
server/services/graph-service.js
| @@ -85,8 +85,8 @@ function ensureSystemAgentRow(companyId, systemAgentId, roleName, opts = {}) { | @@ -85,8 +85,8 @@ function ensureSystemAgentRow(companyId, systemAgentId, roleName, opts = {}) { | ||
| 85 | 85 | ||
| 86 | const now = new Date().toISOString(); | 86 | const now = new Date().toISOString(); |
| 87 | db.prepare(` | 87 | db.prepare(` |
| 88 | - INSERT INTO system_agents (id, name, scope, owner_company_id, source_kind, origin, legacy_path, status, benchmark_score, created_at, updated_at) | ||
| 89 | - VALUES (@id, @name, 'company', @owner_company_id, 'company_hired', 'created', @legacy_path, @status, NULL, @created_at, @updated_at) | 88 | + INSERT INTO system_agents (id, name, scope, owner_company_id, origin, legacy_path, status, benchmark_score, created_at, updated_at) |
| 89 | + VALUES (@id, @name, 'company', @owner_company_id, 'created', @legacy_path, @status, NULL, @created_at, @updated_at) | ||
| 90 | `).run({ | 90 | `).run({ |
| 91 | id: systemAgentId, | 91 | id: systemAgentId, |
| 92 | name: systemAgentId, | 92 | name: systemAgentId, |
server/tests/integration/company-create-mainline.test.js
| @@ -85,8 +85,8 @@ describe('28 终验 · 「新建公司」主线(真本体)', () => { | @@ -85,8 +85,8 @@ describe('28 终验 · 「新建公司」主线(真本体)', () => { | ||
| 85 | const now = new Date().toISOString(); | 85 | const now = new Date().toISOString(); |
| 86 | const ins = db().prepare(` | 86 | const ins = db().prepare(` |
| 87 | INSERT OR REPLACE INTO system_agents | 87 | INSERT OR REPLACE INTO system_agents |
| 88 | - (id, name, scope, owner_company_id, source_kind, origin, legacy_path, status, created_at, updated_at) | ||
| 89 | - VALUES (?, ?, 'public', NULL, 'global_agent', 'builtin', ?, ?, ?, ?) | 88 | + (id, name, scope, owner_company_id, origin, legacy_path, status, created_at, updated_at) |
| 89 | + VALUES (?, ?, 'public', NULL, 'builtin', ?, ?, ?, ?) | ||
| 90 | `); | 90 | `); |
| 91 | for (const id of REAL_BODIES) { | 91 | for (const id of REAL_BODIES) { |
| 92 | const cfg = JSON.parse(fs.readFileSync(path.join(locator.bodyDir(id), 'config.json'), 'utf-8')); | 92 | const cfg = JSON.parse(fs.readFileSync(path.join(locator.bodyDir(id), 'config.json'), 'utf-8')); |
server/tests/integration/graph-agent-p1-acceptance.test.js
| @@ -80,8 +80,8 @@ describe('本体共享 + 公司态下沉 — 端到端验收(28 §八 4/5)', | @@ -80,8 +80,8 @@ describe('本体共享 + 公司态下沉 — 端到端验收(28 §八 4/5)', | ||
| 80 | // 镜像生产种子(seedAgents:两个 id 都是 origin='builtin' 的本体行) | 80 | // 镜像生产种子(seedAgents:两个 id 都是 origin='builtin' 的本体行) |
| 81 | for (const id of ['qa-judge', COLLISION_ROLE]) { | 81 | for (const id of ['qa-judge', COLLISION_ROLE]) { |
| 82 | db.prepare(` | 82 | db.prepare(` |
| 83 | - INSERT INTO system_agents (id, name, scope, owner_company_id, source_kind, origin, legacy_path, status, benchmark_score, created_at, updated_at) | ||
| 84 | - VALUES (?, ?, 'public', NULL, 'global_agent', 'builtin', ?, 'active', NULL, ?, ?) | 83 | + INSERT INTO system_agents (id, name, scope, owner_company_id, origin, legacy_path, status, benchmark_score, created_at, updated_at) |
| 84 | + VALUES (?, ?, 'public', NULL, 'builtin', ?, 'active', NULL, ?, ?) | ||
| 85 | `).run(id, id, `system/agents/${id}`, now, now); | 85 | `).run(id, id, `system/agents/${id}`, now, now); |
| 86 | } | 86 | } |
| 87 | 87 |
server/tests/integration/graph-agent-p1-hire.test.js
| @@ -62,8 +62,8 @@ describe('company-service.hire() — 引用本体 + 公司态下沉', () => { | @@ -62,8 +62,8 @@ describe('company-service.hire() — 引用本体 + 公司态下沉', () => { | ||
| 62 | const db = graphDb.getDb(); | 62 | const db = graphDb.getDb(); |
| 63 | const now = new Date().toISOString(); | 63 | const now = new Date().toISOString(); |
| 64 | db.prepare(` | 64 | db.prepare(` |
| 65 | - INSERT INTO system_agents (id, name, scope, owner_company_id, source_kind, origin, legacy_path, status, benchmark_score, created_at, updated_at) | ||
| 66 | - VALUES (?, ?, 'public', NULL, 'global_agent', 'builtin', ?, 'active', NULL, ?, ?) | 65 | + INSERT INTO system_agents (id, name, scope, owner_company_id, origin, legacy_path, status, benchmark_score, created_at, updated_at) |
| 66 | + VALUES (?, ?, 'public', NULL, 'builtin', ?, 'active', NULL, ?, ?) | ||
| 67 | `).run('qa-judge', 'qa-judge', 'system/agents/qa-judge', now, now); | 67 | `).run('qa-judge', 'qa-judge', 'system/agents/qa-judge', now, now); |
| 68 | 68 | ||
| 69 | // 固定 id 的代价:上一次跑要是崩在中途,公司目录会留在盘上,而 create() 撞见已存在的 | 69 | // 固定 id 的代价:上一次跑要是崩在中途,公司目录会留在盘上,而 create() 撞见已存在的 |
server/tests/unit/agent-body-draft-gate.test.js
| @@ -102,8 +102,8 @@ describe('新造本体的 draft 闸门(28 批次 5)', () => { | @@ -102,8 +102,8 @@ describe('新造本体的 draft 闸门(28 批次 5)', () => { | ||
| 102 | const now = new Date().toISOString(); | 102 | const now = new Date().toISOString(); |
| 103 | graphDb.getDb().prepare(` | 103 | graphDb.getDb().prepare(` |
| 104 | INSERT OR REPLACE INTO system_agents | 104 | INSERT OR REPLACE INTO system_agents |
| 105 | - (id, name, scope, owner_company_id, source_kind, origin, legacy_path, status, created_at, updated_at) | ||
| 106 | - VALUES (?, ?, 'public', NULL, 'global_agent', 'builtin', ?, ?, ?, ?) | 105 | + (id, name, scope, owner_company_id, origin, legacy_path, status, created_at, updated_at) |
| 106 | + VALUES (?, ?, 'public', NULL, 'builtin', ?, ?, ?, ?) | ||
| 107 | `).run(id, id, `system/agents/${id}`, cfg.status || 'active', now, now); | 107 | `).run(id, id, `system/agents/${id}`, cfg.status || 'active', now, now); |
| 108 | }; | 108 | }; |
| 109 | 109 | ||
| @@ -216,8 +216,8 @@ describe('新造本体的 draft 闸门(28 批次 5)', () => { | @@ -216,8 +216,8 @@ describe('新造本体的 draft 闸门(28 批次 5)', () => { | ||
| 216 | for (const status of ['enabled', 'idle', 'ready', 'trained', 'active']) { | 216 | for (const status of ['enabled', 'idle', 'ready', 'trained', 'active']) { |
| 217 | const id = `qa-judge`; | 217 | const id = `qa-judge`; |
| 218 | db.prepare(`INSERT OR REPLACE INTO system_agents | 218 | db.prepare(`INSERT OR REPLACE INTO system_agents |
| 219 | - (id,name,scope,owner_company_id,source_kind,origin,legacy_path,status,benchmark_score,created_at,updated_at) | ||
| 220 | - VALUES (?,?,'public',NULL,'global_agent','builtin',?, ?,NULL,?,?)`) | 219 | + (id,name,scope,owner_company_id,origin,legacy_path,status,benchmark_score,created_at,updated_at) |
| 220 | + VALUES (?,?,'public',NULL,'builtin',?, ?,NULL,?,?)`) | ||
| 221 | .run(id, id, `system/agents/${id}`, status, now, now); | 221 | .run(id, id, `system/agents/${id}`, status, now, now); |
| 222 | db.prepare(`DELETE FROM company_agents WHERE role_name = ?`).run(id); | 222 | db.prepare(`DELETE FROM company_agents WHERE role_name = ?`).run(id); |
| 223 | 223 |
server/tests/unit/agent-transcript.test.js
| @@ -98,8 +98,8 @@ describe('agent-transcript:会话原文读取', () => { | @@ -98,8 +98,8 @@ describe('agent-transcript:会话原文读取', () => { | ||
| 98 | const now = new Date().toISOString(); | 98 | const now = new Date().toISOString(); |
| 99 | db.prepare(` | 99 | db.prepare(` |
| 100 | INSERT OR IGNORE INTO system_agents | 100 | INSERT OR IGNORE INTO system_agents |
| 101 | - (id, name, scope, owner_company_id, source_kind, legacy_path, status, created_at, updated_at) | ||
| 102 | - VALUES (?, ?, 'public', NULL, 'global_agent', ?, 'active', ?, ?) | 101 | + (id, name, scope, owner_company_id, legacy_path, status, created_at, updated_at) |
| 102 | + VALUES (?, ?, 'public', NULL, ?, 'active', ?, ?) | ||
| 103 | `).run(ROLE, ROLE, `system/agents/${ROLE}`, now, now); | 103 | `).run(ROLE, ROLE, `system/agents/${ROLE}`, now, now); |
| 104 | 104 | ||
| 105 | // 雇佣关系:listRoles 的角色清单来自这张表(28 批次 4-19),没有这行就一个角色都列不出来 | 105 | // 雇佣关系:listRoles 的角色清单来自这张表(28 批次 4-19),没有这行就一个角色都列不出来 |
server/tests/unit/base-graduation-gate.test.js
| @@ -62,8 +62,8 @@ describe('_base 骨架不算定义已填满(哨兵闸门)', () => { | @@ -62,8 +62,8 @@ describe('_base 骨架不算定义已填满(哨兵闸门)', () => { | ||
| 62 | const now = new Date().toISOString(); | 62 | const now = new Date().toISOString(); |
| 63 | graphDb.getDb().prepare(` | 63 | graphDb.getDb().prepare(` |
| 64 | INSERT OR REPLACE INTO system_agents | 64 | INSERT OR REPLACE INTO system_agents |
| 65 | - (id, name, scope, owner_company_id, source_kind, origin, legacy_path, status, created_at, updated_at) | ||
| 66 | - VALUES (?, ?, 'public', NULL, 'global_agent', 'builtin', ?, ?, ?, ?) | 65 | + (id, name, scope, owner_company_id, origin, legacy_path, status, created_at, updated_at) |
| 66 | + VALUES (?, ?, 'public', NULL, 'builtin', ?, ?, ?, ?) | ||
| 67 | `).run(id, id, `system/agents/${id}`, cfg.status || 'active', now, now); | 67 | `).run(id, id, `system/agents/${id}`, cfg.status || 'active', now, now); |
| 68 | }; | 68 | }; |
| 69 | const agentRow = (id) => | 69 | const agentRow = (id) => |
server/tests/unit/boundary-guard-new-body.test.js
| @@ -88,8 +88,8 @@ describe('边界守卫 · 新建 agent 本体不判越界(28 §九)', () => | @@ -88,8 +88,8 @@ describe('边界守卫 · 新建 agent 本体不判越界(28 §九)', () => | ||
| 88 | const ts = new Date().toISOString(); | 88 | const ts = new Date().toISOString(); |
| 89 | wdb.getDb().prepare(` | 89 | wdb.getDb().prepare(` |
| 90 | INSERT OR IGNORE INTO system_agents | 90 | INSERT OR IGNORE INTO system_agents |
| 91 | - (id, name, scope, owner_company_id, source_kind, legacy_path, status, created_at, updated_at) | ||
| 92 | - VALUES ('hr', 'hr', 'public', NULL, 'global_agent', 'system/agents/hr', 'active', ?, ?) | 91 | + (id, name, scope, owner_company_id, legacy_path, status, created_at, updated_at) |
| 92 | + VALUES ('hr', 'hr', 'public', NULL, 'system/agents/hr', 'active', ?, ?) | ||
| 93 | `).run(ts, ts); | 93 | `).run(ts, ts); |
| 94 | const g = wdb.createWorkGraph({ companyId, nodes: ['hr'], edges: [] }); | 94 | const g = wdb.createWorkGraph({ companyId, nodes: ['hr'], edges: [] }); |
| 95 | expect(g.errors || []).toEqual([]); | 95 | expect(g.errors || []).toEqual([]); |
server/tests/unit/company-hire-org-layer.test.js
| @@ -100,8 +100,8 @@ describe('hire() 的 org / work 分层(27-P3B W7)', () => { | @@ -100,8 +100,8 @@ describe('hire() 的 org / work 分层(27-P3B W7)', () => { | ||
| 100 | const now = new Date().toISOString(); | 100 | const now = new Date().toISOString(); |
| 101 | for (const id of TEMPLATES) { | 101 | for (const id of TEMPLATES) { |
| 102 | db.prepare(` | 102 | db.prepare(` |
| 103 | - INSERT INTO system_agents (id, name, scope, owner_company_id, source_kind, legacy_path, status, benchmark_score, created_at, updated_at) | ||
| 104 | - VALUES (?, ?, 'public', NULL, 'template', ?, 'active', NULL, ?, ?) | 103 | + INSERT INTO system_agents (id, name, scope, owner_company_id, legacy_path, status, benchmark_score, created_at, updated_at) |
| 104 | + VALUES (?, ?, 'public', NULL, ?, 'active', NULL, ?, ?) | ||
| 105 | `).run(id, id, `system/agents/${id}`, now, now); | 105 | `).run(id, id, `system/agents/${id}`, now, now); |
| 106 | } | 106 | } |
| 107 | 107 |
server/tests/unit/goal-task-acceptance.test.js
| @@ -180,8 +180,8 @@ describe('27-P3C 批 1 验收(§八 4 / 5 / 6 / 10 / 11 / 12b)', () => { | @@ -180,8 +180,8 @@ describe('27-P3C 批 1 验收(§八 4 / 5 / 6 / 10 / 11 / 12b)', () => { | ||
| 180 | const now = new Date().toISOString(); | 180 | const now = new Date().toISOString(); |
| 181 | db.prepare(` | 181 | db.prepare(` |
| 182 | INSERT OR IGNORE INTO system_agents | 182 | INSERT OR IGNORE INTO system_agents |
| 183 | - (id, name, scope, owner_company_id, source_kind, legacy_path, status, created_at, updated_at) | ||
| 184 | - VALUES ('acc-worker', 'acc-worker', 'public', NULL, 'global_agent', 'system/agents/acc-worker', 'active', ?, ?) | 183 | + (id, name, scope, owner_company_id, legacy_path, status, created_at, updated_at) |
| 184 | + VALUES ('acc-worker', 'acc-worker', 'public', NULL, 'system/agents/acc-worker', 'active', ?, ?) | ||
| 185 | `).run(now, now); | 185 | `).run(now, now); |
| 186 | const g = wdb.createWorkGraph({ companyId: cid, nodes: ['acc-worker'], edges: [] }); | 186 | const g = wdb.createWorkGraph({ companyId: cid, nodes: ['acc-worker'], edges: [] }); |
| 187 | expect(g.errors || []).toEqual([]); | 187 | expect(g.errors || []).toEqual([]); |
server/tests/unit/graph-boundary.test.js
| @@ -60,8 +60,8 @@ describe('graph-engine 项目根与边界守卫', () => { | @@ -60,8 +60,8 @@ describe('graph-engine 项目根与边界守卫', () => { | ||
| 60 | const ts = new Date().toISOString(); | 60 | const ts = new Date().toISOString(); |
| 61 | const ins = db.prepare(` | 61 | const ins = db.prepare(` |
| 62 | INSERT OR IGNORE INTO system_agents | 62 | INSERT OR IGNORE INTO system_agents |
| 63 | - (id, name, scope, owner_company_id, source_kind, legacy_path, status, created_at, updated_at) | ||
| 64 | - VALUES (?, ?, 'public', NULL, 'global_agent', ?, 'active', ?, ?) | 63 | + (id, name, scope, owner_company_id, legacy_path, status, created_at, updated_at) |
| 64 | + VALUES (?, ?, 'public', NULL, ?, 'active', ?, ?) | ||
| 65 | `); | 65 | `); |
| 66 | for (const id of new Set(ids)) ins.run(id, id, `system/agents/${id}`, ts, ts); | 66 | for (const id of new Set(ids)) ins.run(id, id, `system/agents/${id}`, ts, ts); |
| 67 | } | 67 | } |
server/tests/unit/graph-cross-layer-address.test.js
| @@ -82,8 +82,8 @@ describe('R-17.1 跨层地址 · 两半的必填性(W10 事实核实)', () = | @@ -82,8 +82,8 @@ describe('R-17.1 跨层地址 · 两半的必填性(W10 事实核实)', () = | ||
| 82 | const ts = new Date().toISOString(); | 82 | const ts = new Date().toISOString(); |
| 83 | const ins = db.prepare(` | 83 | const ins = db.prepare(` |
| 84 | INSERT OR IGNORE INTO system_agents | 84 | INSERT OR IGNORE INTO system_agents |
| 85 | - (id, name, scope, owner_company_id, source_kind, legacy_path, status, created_at, updated_at) | ||
| 86 | - VALUES (?, ?, 'public', NULL, 'global_agent', ?, 'active', ?, ?) | 85 | + (id, name, scope, owner_company_id, legacy_path, status, created_at, updated_at) |
| 86 | + VALUES (?, ?, 'public', NULL, ?, 'active', ?, ?) | ||
| 87 | `); | 87 | `); |
| 88 | for (const id of new Set(ids)) ins.run(id, id, `system/agents/${id}`, ts, ts); | 88 | for (const id of new Set(ids)) ins.run(id, id, `system/agents/${id}`, ts, ts); |
| 89 | } | 89 | } |
server/tests/unit/graph-db.test.js
| @@ -40,9 +40,9 @@ describe('graph-db', () => { | @@ -40,9 +40,9 @@ describe('graph-db', () => { | ||
| 40 | const d = graphDb.getDb(); | 40 | const d = graphDb.getDb(); |
| 41 | const now = new Date().toISOString(); | 41 | const now = new Date().toISOString(); |
| 42 | d.prepare(` | 42 | d.prepare(` |
| 43 | - INSERT INTO system_agents (id, name, scope, owner_company_id, source_kind, legacy_path, status, benchmark_score, created_at, updated_at) | ||
| 44 | - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) | ||
| 45 | - `).run('test-agent-env-check', 'Test Agent', 'public', null, 'global_agent', 'system/agents/test-agent-env-check', 'active', null, now, now); | 43 | + INSERT INTO system_agents (id, name, scope, owner_company_id, legacy_path, status, benchmark_score, created_at, updated_at) |
| 44 | + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) | ||
| 45 | + `).run('test-agent-env-check', 'Test Agent', 'public', null, 'system/agents/test-agent-env-check', 'active', null, now, now); | ||
| 46 | 46 | ||
| 47 | // The row must have landed in the overridden file, not the default one. | 47 | // The row must have landed in the overridden file, not the default one. |
| 48 | expect(fs.existsSync(TEST_DB_PATH)).toBe(true); | 48 | expect(fs.existsSync(TEST_DB_PATH)).toBe(true); |
server/tests/unit/graph-edge-contract-role.test.js
| @@ -95,8 +95,8 @@ describe('R-19.A2 契约 role 落库 + PATCH 通道的 data-contract 校验', () | @@ -95,8 +95,8 @@ describe('R-19.A2 契约 role 落库 + PATCH 通道的 data-contract 校验', () | ||
| 95 | const ts = new Date().toISOString(); | 95 | const ts = new Date().toISOString(); |
| 96 | const ins = db.prepare(` | 96 | const ins = db.prepare(` |
| 97 | INSERT OR IGNORE INTO system_agents | 97 | INSERT OR IGNORE INTO system_agents |
| 98 | - (id, name, scope, owner_company_id, source_kind, legacy_path, status, created_at, updated_at) | ||
| 99 | - VALUES (?, ?, 'public', NULL, 'global_agent', ?, 'active', ?, ?) | 98 | + (id, name, scope, owner_company_id, legacy_path, status, created_at, updated_at) |
| 99 | + VALUES (?, ?, 'public', NULL, ?, 'active', ?, ?) | ||
| 100 | `); | 100 | `); |
| 101 | for (const id of ['brd-planning', 'prd-planning', 'sdd-planning']) { | 101 | for (const id of ['brd-planning', 'prd-planning', 'sdd-planning']) { |
| 102 | ins.run(id, id, `system/agents/${id}`, ts, ts); | 102 | ins.run(id, id, `system/agents/${id}`, ts, ts); |
server/tests/unit/graph-edge-contracts.test.js
| @@ -67,8 +67,8 @@ describe('R-19.A 边上的数据契约:声明 → 落库 → 读回', () => { | @@ -67,8 +67,8 @@ describe('R-19.A 边上的数据契约:声明 → 落库 → 读回', () => { | ||
| 67 | const ts = new Date().toISOString(); | 67 | const ts = new Date().toISOString(); |
| 68 | const ins = db.prepare(` | 68 | const ins = db.prepare(` |
| 69 | INSERT OR IGNORE INTO system_agents | 69 | INSERT OR IGNORE INTO system_agents |
| 70 | - (id, name, scope, owner_company_id, source_kind, legacy_path, status, created_at, updated_at) | ||
| 71 | - VALUES (?, ?, 'public', NULL, 'global_agent', ?, 'active', ?, ?) | 70 | + (id, name, scope, owner_company_id, legacy_path, status, created_at, updated_at) |
| 71 | + VALUES (?, ?, 'public', NULL, ?, 'active', ?, ?) | ||
| 72 | `); | 72 | `); |
| 73 | for (const id of new Set(ids)) ins.run(id, id, `system/agents/${id}`, ts, ts); | 73 | for (const id of new Set(ids)) ins.run(id, id, `system/agents/${id}`, ts, ts); |
| 74 | } | 74 | } |
server/tests/unit/graph-engine-fail-reason.test.js
| @@ -92,8 +92,8 @@ describe('引擎自判失败的 fail_reason 与失败上下文(W12 补完 / W9 | @@ -92,8 +92,8 @@ describe('引擎自判失败的 fail_reason 与失败上下文(W12 补完 / W9 | ||
| 92 | const ts = new Date().toISOString(); | 92 | const ts = new Date().toISOString(); |
| 93 | const ins = db.prepare(` | 93 | const ins = db.prepare(` |
| 94 | INSERT OR IGNORE INTO system_agents | 94 | INSERT OR IGNORE INTO system_agents |
| 95 | - (id, name, scope, owner_company_id, source_kind, legacy_path, status, created_at, updated_at) | ||
| 96 | - VALUES (?, ?, 'public', NULL, 'global_agent', ?, 'active', ?, ?) | 95 | + (id, name, scope, owner_company_id, legacy_path, status, created_at, updated_at) |
| 96 | + VALUES (?, ?, 'public', NULL, ?, 'active', ?, ?) | ||
| 97 | `); | 97 | `); |
| 98 | for (const id of new Set(ids)) ins.run(id, id, `system/agents/${id}`, ts, ts); | 98 | for (const id of new Set(ids)) ins.run(id, id, `system/agents/${id}`, ts, ts); |
| 99 | } | 99 | } |
server/tests/unit/graph-engine-node-error.test.js
| @@ -82,8 +82,8 @@ describe('graph-engine 节点失败留痕(W12)', () => { | @@ -82,8 +82,8 @@ describe('graph-engine 节点失败留痕(W12)', () => { | ||
| 82 | const now = new Date().toISOString(); | 82 | const now = new Date().toISOString(); |
| 83 | const ins = db.prepare(` | 83 | const ins = db.prepare(` |
| 84 | INSERT OR IGNORE INTO system_agents | 84 | INSERT OR IGNORE INTO system_agents |
| 85 | - (id, name, scope, owner_company_id, source_kind, legacy_path, status, created_at, updated_at) | ||
| 86 | - VALUES (?, ?, 'public', NULL, 'global_agent', ?, 'active', ?, ?) | 85 | + (id, name, scope, owner_company_id, legacy_path, status, created_at, updated_at) |
| 86 | + VALUES (?, ?, 'public', NULL, ?, 'active', ?, ?) | ||
| 87 | `); | 87 | `); |
| 88 | for (const id of new Set(ids)) ins.run(id, id, `system/agents/${id}`, now, now); | 88 | for (const id of new Set(ids)) ins.run(id, id, `system/agents/${id}`, now, now); |
| 89 | } | 89 | } |
server/tests/unit/graph-engine-revive-parent.test.js
| @@ -85,8 +85,8 @@ describe('子 run 转终态回写父节点', () => { | @@ -85,8 +85,8 @@ describe('子 run 转终态回写父节点', () => { | ||
| 85 | const now = new Date().toISOString(); | 85 | const now = new Date().toISOString(); |
| 86 | const ins = db.prepare(` | 86 | const ins = db.prepare(` |
| 87 | INSERT OR IGNORE INTO system_agents | 87 | INSERT OR IGNORE INTO system_agents |
| 88 | - (id, name, scope, owner_company_id, source_kind, legacy_path, status, created_at, updated_at) | ||
| 89 | - VALUES (?, ?, 'public', NULL, 'global_agent', ?, 'active', ?, ?) | 88 | + (id, name, scope, owner_company_id, legacy_path, status, created_at, updated_at) |
| 89 | + VALUES (?, ?, 'public', NULL, ?, 'active', ?, ?) | ||
| 90 | `); | 90 | `); |
| 91 | for (const id of new Set(ids)) ins.run(id, id, `system/agents/${id}`, now, now); | 91 | for (const id of new Set(ids)) ins.run(id, id, `system/agents/${id}`, now, now); |
| 92 | } | 92 | } |
server/tests/unit/graph-engine.test.js
| @@ -98,8 +98,8 @@ describe('graph-engine', () => { | @@ -98,8 +98,8 @@ describe('graph-engine', () => { | ||
| 98 | const now = new Date().toISOString(); | 98 | const now = new Date().toISOString(); |
| 99 | const ins = db.prepare(` | 99 | const ins = db.prepare(` |
| 100 | INSERT OR IGNORE INTO system_agents | 100 | INSERT OR IGNORE INTO system_agents |
| 101 | - (id, name, scope, owner_company_id, source_kind, legacy_path, status, created_at, updated_at) | ||
| 102 | - VALUES (?, ?, 'public', NULL, 'global_agent', ?, 'active', ?, ?) | 101 | + (id, name, scope, owner_company_id, legacy_path, status, created_at, updated_at) |
| 102 | + VALUES (?, ?, 'public', NULL, ?, 'active', ?, ?) | ||
| 103 | `); | 103 | `); |
| 104 | for (const id of new Set(ids)) ins.run(id, id, `system/agents/${id}`, now, now); | 104 | for (const id of new Set(ids)) ins.run(id, id, `system/agents/${id}`, now, now); |
| 105 | } | 105 | } |
server/tests/unit/graph-files-index.test.js
| @@ -75,8 +75,8 @@ describe('R-19 产物索引:graph_files / graph_edge_contracts', () => { | @@ -75,8 +75,8 @@ describe('R-19 产物索引:graph_files / graph_edge_contracts', () => { | ||
| 75 | const ts = new Date().toISOString(); | 75 | const ts = new Date().toISOString(); |
| 76 | const ins = db.prepare(` | 76 | const ins = db.prepare(` |
| 77 | INSERT OR IGNORE INTO system_agents | 77 | INSERT OR IGNORE INTO system_agents |
| 78 | - (id, name, scope, owner_company_id, source_kind, legacy_path, status, created_at, updated_at) | ||
| 79 | - VALUES (?, ?, 'public', NULL, 'global_agent', ?, 'active', ?, ?) | 78 | + (id, name, scope, owner_company_id, legacy_path, status, created_at, updated_at) |
| 79 | + VALUES (?, ?, 'public', NULL, ?, 'active', ?, ?) | ||
| 80 | `); | 80 | `); |
| 81 | for (const id of new Set(ids)) ins.run(id, id, `system/agents/${id}`, ts, ts); | 81 | for (const id of new Set(ids)) ins.run(id, id, `system/agents/${id}`, ts, ts); |
| 82 | } | 82 | } |
server/tests/unit/graph-real-chain-smoke.test.js
| @@ -75,8 +75,8 @@ describe('图执行真实链路 smokeï¼ˆé›¶æ³¨å…¥ï¼Œåªæ¢æŽ‰çœŸ CLI åè¿›ç¨‹ï¼ | @@ -75,8 +75,8 @@ describe('图执行真实链路 smokeï¼ˆé›¶æ³¨å…¥ï¼Œåªæ¢æŽ‰çœŸ CLI åè¿›ç¨‹ï¼ | ||
| 75 | const db = wdb.getDb(); | 75 | const db = wdb.getDb(); |
| 76 | const now = new Date().toISOString(); | 76 | const now = new Date().toISOString(); |
| 77 | db.prepare(`INSERT OR IGNORE INTO system_agents | 77 | db.prepare(`INSERT OR IGNORE INTO system_agents |
| 78 | - (id, name, scope, owner_company_id, source_kind, legacy_path, status, created_at, updated_at) | ||
| 79 | - VALUES (?, ?, 'public', NULL, 'global_agent', ?, 'active', ?, ?)`) | 78 | + (id, name, scope, owner_company_id, legacy_path, status, created_at, updated_at) |
| 79 | + VALUES (?, ?, 'public', NULL, ?, 'active', ?, ?)`) | ||
| 80 | .run(AGENT, AGENT, `system/agents/${AGENT}`, now, now); | 80 | .run(AGENT, AGENT, `system/agents/${AGENT}`, now, now); |
| 81 | 81 | ||
| 82 | const g = wdb.createWorkGraph({ companyId: COMPANY, nodes: [{ nodeName: AGENT, systemAgentId: AGENT }], edges: [] }); | 82 | const g = wdb.createWorkGraph({ companyId: COMPANY, nodes: [{ nodeName: AGENT, systemAgentId: AGENT }], edges: [] }); |
| @@ -142,14 +142,14 @@ describe('图执行真实链路 smokeï¼ˆé›¶æ³¨å…¥ï¼Œåªæ¢æŽ‰çœŸ CLI åè¿›ç¨‹ï¼ | @@ -142,14 +142,14 @@ describe('图执行真实链路 smokeï¼ˆé›¶æ³¨å…¥ï¼Œåªæ¢æŽ‰çœŸ CLI åè¿›ç¨‹ï¼ | ||
| 142 | const db = wdb.getDb(); | 142 | const db = wdb.getDb(); |
| 143 | const now = new Date().toISOString(); | 143 | const now = new Date().toISOString(); |
| 144 | db.prepare(`INSERT OR IGNORE INTO system_agents | 144 | db.prepare(`INSERT OR IGNORE INTO system_agents |
| 145 | - (id, name, scope, owner_company_id, source_kind, legacy_path, status, created_at, updated_at) | ||
| 146 | - VALUES (?, ?, 'public', NULL, 'global_agent', ?, 'active', ?, ?)`) | 145 | + (id, name, scope, owner_company_id, legacy_path, status, created_at, updated_at) |
| 146 | + VALUES (?, ?, 'public', NULL, ?, 'active', ?, ?)`) | ||
| 147 | .run(PLANNER, PLANNER, `system/agents/${PLANNER}`, now, now); | 147 | .run(PLANNER, PLANNER, `system/agents/${PLANNER}`, now, now); |
| 148 | // subgraph èŠ‚ç‚¹è‡ªå·±ä¹Ÿè¦æœ‰ system_agents 行(work_graph_nodes.system_agent_id 是外键)。 | 148 | // subgraph èŠ‚ç‚¹è‡ªå·±ä¹Ÿè¦æœ‰ system_agents 行(work_graph_nodes.system_agent_id 是外键)。 |
| 149 | // å®ƒåªæ˜¯ã€Œå“ªä¸ª org è§’è‰²æ‹¥æœ‰è¿™å¼ å图ã€çš„åå—,ä¸ä¼šè¢«çœŸçš„调用 —— 调的是 PLANNER。 | 149 | // å®ƒåªæ˜¯ã€Œå“ªä¸ª org è§’è‰²æ‹¥æœ‰è¿™å¼ å图ã€çš„åå—,ä¸ä¼šè¢«çœŸçš„调用 —— 调的是 PLANNER。 |
| 150 | db.prepare(`INSERT OR IGNORE INTO system_agents | 150 | db.prepare(`INSERT OR IGNORE INTO system_agents |
| 151 | - (id, name, scope, owner_company_id, source_kind, legacy_path, status, created_at, updated_at) | ||
| 152 | - VALUES (?, ?, 'public', NULL, 'global_agent', ?, 'active', ?, ?)`) | 151 | + (id, name, scope, owner_company_id, legacy_path, status, created_at, updated_at) |
| 152 | + VALUES (?, ?, 'public', NULL, ?, 'active', ?, ?)`) | ||
| 153 | .run(WRAPPER, WRAPPER, `system/agents/${WRAPPER}`, now, now); | 153 | .run(WRAPPER, WRAPPER, `system/agents/${WRAPPER}`, now, now); |
| 154 | db.prepare(`INSERT OR IGNORE INTO company_agents (company_id, system_agent_id, role_name, status, hired_at) | 154 | db.prepare(`INSERT OR IGNORE INTO company_agents (company_id, system_agent_id, role_name, status, hired_at) |
| 155 | VALUES (?, ?, ?, 'active', ?)`).run(COMPANY, PLANNER, PLANNER, now); | 155 | VALUES (?, ?, ?, 'active', ?)`).run(COMPANY, PLANNER, PLANNER, now); |
| @@ -213,8 +213,8 @@ describe('图执行真实链路 smokeï¼ˆé›¶æ³¨å…¥ï¼Œåªæ¢æŽ‰çœŸ CLI åè¿›ç¨‹ï¼ | @@ -213,8 +213,8 @@ describe('图执行真实链路 smokeï¼ˆé›¶æ³¨å…¥ï¼Œåªæ¢æŽ‰çœŸ CLI åè¿›ç¨‹ï¼ | ||
| 213 | const db = wdb.getDb(); | 213 | const db = wdb.getDb(); |
| 214 | const now = new Date().toISOString(); | 214 | const now = new Date().toISOString(); |
| 215 | db.prepare(`INSERT OR IGNORE INTO system_agents | 215 | db.prepare(`INSERT OR IGNORE INTO system_agents |
| 216 | - (id, name, scope, owner_company_id, source_kind, legacy_path, status, created_at, updated_at) | ||
| 217 | - VALUES (?, ?, 'public', NULL, 'global_agent', ?, 'active', ?, ?)`) | 216 | + (id, name, scope, owner_company_id, legacy_path, status, created_at, updated_at) |
| 217 | + VALUES (?, ?, 'public', NULL, ?, 'active', ?, ?)`) | ||
| 218 | .run(VALIDATOR, VALIDATOR, `system/agents/${VALIDATOR}`, now, now); | 218 | .run(VALIDATOR, VALIDATOR, `system/agents/${VALIDATOR}`, now, now); |
| 219 | 219 | ||
| 220 | const g = wdb.createWorkGraph({ | 220 | const g = wdb.createWorkGraph({ |
server/tests/unit/graph-run-route-io.test.js
| @@ -53,8 +53,8 @@ describe('GET /api/graph-runs/:id — R-19 输入输出三字段', () => { | @@ -53,8 +53,8 @@ describe('GET /api/graph-runs/:id — R-19 输入输出三字段', () => { | ||
| 53 | const ts = new Date().toISOString(); | 53 | const ts = new Date().toISOString(); |
| 54 | const ins = db.prepare(` | 54 | const ins = db.prepare(` |
| 55 | INSERT OR IGNORE INTO system_agents | 55 | INSERT OR IGNORE INTO system_agents |
| 56 | - (id, name, scope, owner_company_id, source_kind, legacy_path, status, created_at, updated_at) | ||
| 57 | - VALUES (?, ?, 'public', NULL, 'global_agent', ?, 'active', ?, ?) | 56 | + (id, name, scope, owner_company_id, legacy_path, status, created_at, updated_at) |
| 57 | + VALUES (?, ?, 'public', NULL, ?, 'active', ?, ?) | ||
| 58 | `); | 58 | `); |
| 59 | for (const id of ['brd-planning', 'prd-planning']) ins.run(id, id, `system/agents/${id}`, ts, ts); | 59 | for (const id of ['brd-planning', 'prd-planning']) ins.run(id, id, `system/agents/${id}`, ts, ts); |
| 60 | 60 |
server/tests/unit/graph-run-route-slot-busy.test.js
| @@ -60,8 +60,8 @@ describe('POST /api/graph-runs/... 撞上坑满 → 409(不是 500)', () => | @@ -60,8 +60,8 @@ describe('POST /api/graph-runs/... 撞上坑满 → 409(不是 500)', () => | ||
| 60 | const now = new Date().toISOString(); | 60 | const now = new Date().toISOString(); |
| 61 | const insAgent = db.prepare(` | 61 | const insAgent = db.prepare(` |
| 62 | INSERT OR IGNORE INTO system_agents | 62 | INSERT OR IGNORE INTO system_agents |
| 63 | - (id, name, scope, owner_company_id, source_kind, legacy_path, status, created_at, updated_at) | ||
| 64 | - VALUES (?, ?, 'public', NULL, 'global_agent', ?, 'active', ?, ?) | 63 | + (id, name, scope, owner_company_id, legacy_path, status, created_at, updated_at) |
| 64 | + VALUES (?, ?, 'public', NULL, ?, 'active', ?, ?) | ||
| 65 | `); | 65 | `); |
| 66 | for (const id of ['solo', 'solo2']) insAgent.run(id, id, `system/agents/${id}`, now, now); | 66 | for (const id of ['solo', 'solo2']) insAgent.run(id, id, `system/agents/${id}`, now, now); |
| 67 | 67 |
server/tests/unit/graph-run-slot-recovery.test.js
| @@ -98,8 +98,8 @@ describe('恢复路径与执行坑', () => { | @@ -98,8 +98,8 @@ describe('恢复路径与执行坑', () => { | ||
| 98 | const now = new Date().toISOString(); | 98 | const now = new Date().toISOString(); |
| 99 | const ins = wdb.getDb().prepare(` | 99 | const ins = wdb.getDb().prepare(` |
| 100 | INSERT OR IGNORE INTO system_agents | 100 | INSERT OR IGNORE INTO system_agents |
| 101 | - (id, name, scope, owner_company_id, source_kind, legacy_path, status, created_at, updated_at) | ||
| 102 | - VALUES (?, ?, 'public', NULL, 'global_agent', ?, 'active', ?, ?) | 101 | + (id, name, scope, owner_company_id, legacy_path, status, created_at, updated_at) |
| 102 | + VALUES (?, ?, 'public', NULL, ?, 'active', ?, ?) | ||
| 103 | `); | 103 | `); |
| 104 | for (const id of new Set(ids)) ins.run(id, id, `system/agents/${id}`, now, now); | 104 | for (const id of new Set(ids)) ins.run(id, id, `system/agents/${id}`, now, now); |
| 105 | } | 105 | } |
server/tests/unit/graph-seed-reconcile.test.js
| @@ -63,11 +63,11 @@ describe('graph-seed 对账在存量形态上退出码为 0', () => { | @@ -63,11 +63,11 @@ describe('graph-seed 对账在存量形态上退出码为 0', () => { | ||
| 63 | const db = graphDb.getDb(); | 63 | const db = graphDb.getDb(); |
| 64 | const now = new Date().toISOString(); | 64 | const now = new Date().toISOString(); |
| 65 | const insert = db.prepare(` | 65 | const insert = db.prepare(` |
| 66 | - INSERT INTO system_agents (id, name, scope, owner_company_id, source_kind, origin, legacy_path, status, benchmark_score, created_at, updated_at) | ||
| 67 | - VALUES (@id, @name, @scope, @owner, @source_kind, @origin, @legacy_path, 'active', NULL, @now, @now) | 66 | + INSERT INTO system_agents (id, name, scope, owner_company_id, origin, legacy_path, status, benchmark_score, created_at, updated_at) |
| 67 | + VALUES (@id, @name, @scope, @owner, @origin, @legacy_path, 'active', NULL, @now, @now) | ||
| 68 | `); | 68 | `); |
| 69 | - insert.run({ id: 'base', name: 'base', scope: 'public', owner: null, source_kind: 'global_agent', origin: 'builtin', legacy_path: 'system/templates/base', now }); | ||
| 70 | - insert.run({ id: 'graduated', name: 'graduated', scope: 'company', owner: 'live-co', source_kind: 'company_hired', origin: 'created', legacy_path: 'system/agents/graduated', now }); | 69 | + insert.run({ id: 'base', name: 'base', scope: 'public', owner: null, origin: 'builtin', legacy_path: 'system/templates/base', now }); |
| 70 | + insert.run({ id: 'graduated', name: 'graduated', scope: 'company', owner: 'live-co', origin: 'created', legacy_path: 'system/agents/graduated', now }); | ||
| 71 | 71 | ||
| 72 | // ── 幽灵公司的名册行:目录(连同 company.json)已经不在,company_agents 那行还在 ── | 72 | // ── 幽灵公司的名册行:目录(连同 company.json)已经不在,company_agents 那行还在 ── |
| 73 | // 这张表批次 4-19 之后是**唯一被读的名册**,heartbeat-service.start() 拿它挂心跳时 | 73 | // 这张表批次 4-19 之后是**唯一被读的名册**,heartbeat-service.start() 拿它挂心跳时 |
server/tests/unit/graph-seed-service.test.js
| @@ -148,8 +148,8 @@ describe('graph-seed-service (hermetic fixture root)', () => { | @@ -148,8 +148,8 @@ describe('graph-seed-service (hermetic fixture root)', () => { | ||
| 148 | function insertHired(id, ownerCompanyId, legacyPath) { | 148 | function insertHired(id, ownerCompanyId, legacyPath) { |
| 149 | const now = new Date().toISOString(); | 149 | const now = new Date().toISOString(); |
| 150 | graphDb.getDb().prepare(` | 150 | graphDb.getDb().prepare(` |
| 151 | - INSERT INTO system_agents (id, name, scope, owner_company_id, source_kind, origin, legacy_path, status, benchmark_score, created_at, updated_at) | ||
| 152 | - VALUES (?, ?, 'company', ?, 'company_hired', 'created', ?, 'active', NULL, ?, ?) | 151 | + INSERT INTO system_agents (id, name, scope, owner_company_id, origin, legacy_path, status, benchmark_score, created_at, updated_at) |
| 152 | + VALUES (?, ?, 'company', ?, 'created', ?, 'active', NULL, ?, ?) | ||
| 153 | `).run(id, id, ownerCompanyId, legacyPath, now, now); | 153 | `).run(id, id, ownerCompanyId, legacyPath, now, now); |
| 154 | } | 154 | } |
| 155 | 155 | ||
| @@ -429,9 +429,9 @@ describe('graph-seed-service verifyCounts (real repo root)', () => { | @@ -429,9 +429,9 @@ describe('graph-seed-service verifyCounts (real repo root)', () => { | ||
| 429 | const db = graphDb.getDb(); | 429 | const db = graphDb.getDb(); |
| 430 | const now = new Date().toISOString(); | 430 | const now = new Date().toISOString(); |
| 431 | db.prepare(` | 431 | db.prepare(` |
| 432 | - INSERT INTO system_agents (id, name, scope, owner_company_id, source_kind, legacy_path, status, benchmark_score, created_at, updated_at) | ||
| 433 | - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) | ||
| 434 | - `).run('bogus-agent-no-fs-backing', 'Bogus', 'public', null, 'global_agent', 'system/agents/bogus-agent-no-fs-backing', 'active', null, now, now); | 432 | + INSERT INTO system_agents (id, name, scope, owner_company_id, legacy_path, status, benchmark_score, created_at, updated_at) |
| 433 | + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) | ||
| 434 | + `).run('bogus-agent-no-fs-backing', 'Bogus', 'public', null, 'system/agents/bogus-agent-no-fs-backing', 'active', null, now, now); | ||
| 435 | 435 | ||
| 436 | const v = seedService.verifyCounts(); | 436 | const v = seedService.verifyCounts(); |
| 437 | expect(v.ok).toBe(false); | 437 | expect(v.ok).toBe(false); |
server/tests/unit/graph-service.test.js
| @@ -35,13 +35,13 @@ describe('graph-service', () => { | @@ -35,13 +35,13 @@ describe('graph-service', () => { | ||
| 35 | db.exec('DELETE FROM org_graph_nodes; DELETE FROM system_agents;'); | 35 | db.exec('DELETE FROM org_graph_nodes; DELETE FROM system_agents;'); |
| 36 | }); | 36 | }); |
| 37 | 37 | ||
| 38 | - function insertSystemAgent({ id, origin = 'builtin', source_kind = 'global_agent', legacy_path = `system/agents/${id}`, scope = 'public', owner_company_id = null }) { | 38 | + function insertSystemAgent({ id, origin = 'builtin', legacy_path = `system/agents/${id}`, scope = 'public', owner_company_id = null }) { |
| 39 | const db = graphDb.getDb(); | 39 | const db = graphDb.getDb(); |
| 40 | const now = new Date().toISOString(); | 40 | const now = new Date().toISOString(); |
| 41 | db.prepare(` | 41 | db.prepare(` |
| 42 | - INSERT INTO system_agents (id, name, scope, owner_company_id, source_kind, origin, legacy_path, status, benchmark_score, created_at, updated_at) | ||
| 43 | - VALUES (?, ?, ?, ?, ?, ?, ?, 'active', NULL, ?, ?) | ||
| 44 | - `).run(id, id, scope, owner_company_id, source_kind, origin, legacy_path, now, now); | 42 | + INSERT INTO system_agents (id, name, scope, owner_company_id, origin, legacy_path, status, benchmark_score, created_at, updated_at) |
| 43 | + VALUES (?, ?, ?, ?, ?, ?, 'active', NULL, ?, ?) | ||
| 44 | + `).run(id, id, scope, owner_company_id, origin, legacy_path, now, now); | ||
| 45 | } | 45 | } |
| 46 | 46 | ||
| 47 | describe('lookupSharedAgentMd', () => { | 47 | describe('lookupSharedAgentMd', () => { |
server/tests/unit/heartbeat-company-scoped.test.js
| @@ -85,8 +85,8 @@ describe('心跳设置按公司隔离(28 遗留项)', () => { | @@ -85,8 +85,8 @@ describe('心跳设置按公司隔离(28 遗留项)', () => { | ||
| 85 | const db = graphDb.getDb(); | 85 | const db = graphDb.getDb(); |
| 86 | const now = new Date().toISOString(); | 86 | const now = new Date().toISOString(); |
| 87 | db.prepare(` | 87 | db.prepare(` |
| 88 | - INSERT OR REPLACE INTO system_agents (id, name, scope, owner_company_id, source_kind, legacy_path, status, benchmark_score, created_at, updated_at) | ||
| 89 | - VALUES (?, ?, 'public', NULL, 'template', ?, 'active', NULL, ?, ?) | 88 | + INSERT OR REPLACE INTO system_agents (id, name, scope, owner_company_id, legacy_path, status, benchmark_score, created_at, updated_at) |
| 89 | + VALUES (?, ?, 'public', NULL, ?, 'active', NULL, ?, ?) | ||
| 90 | `).run(BODY_ID, BODY_ID, `system/agents/${BODY_ID}`, now, now); | 90 | `).run(BODY_ID, BODY_ID, `system/agents/${BODY_ID}`, now, now); |
| 91 | for (const cid of [CO_A, CO_B]) { | 91 | for (const cid of [CO_A, CO_B]) { |
| 92 | db.prepare(`INSERT OR REPLACE INTO company_agents (company_id, system_agent_id, role_name, status, hired_at) | 92 | db.prepare(`INSERT OR REPLACE INTO company_agents (company_id, system_agent_id, role_name, status, hired_at) |
server/tests/unit/org-layer-contracts.test.js
| @@ -122,8 +122,8 @@ describe('R-19 org 层数据契约(读回方 = buildPlanningPrompt)', () => | @@ -122,8 +122,8 @@ describe('R-19 org 层数据契约(读回方 = buildPlanningPrompt)', () => | ||
| 122 | const ts = new Date().toISOString(); | 122 | const ts = new Date().toISOString(); |
| 123 | const ins = db.prepare(` | 123 | const ins = db.prepare(` |
| 124 | INSERT OR IGNORE INTO system_agents | 124 | INSERT OR IGNORE INTO system_agents |
| 125 | - (id, name, scope, owner_company_id, source_kind, legacy_path, status, created_at, updated_at) | ||
| 126 | - VALUES (?, ?, 'public', NULL, 'global_agent', ?, 'active', ?, ?) | 125 | + (id, name, scope, owner_company_id, legacy_path, status, created_at, updated_at) |
| 126 | + VALUES (?, ?, 'public', NULL, ?, 'active', ?, ?) | ||
| 127 | `); | 127 | `); |
| 128 | for (const id of new Set(ids)) ins.run(id, id, `system/agents/${id}`, ts, ts); | 128 | for (const id of new Set(ids)) ins.run(id, id, `system/agents/${id}`, ts, ts); |
| 129 | } | 129 | } |
server/tests/unit/p3c-recovery-blind-spots.test.js
| @@ -110,8 +110,8 @@ describe('批 3 恢复路径的盲区', () => { | @@ -110,8 +110,8 @@ describe('批 3 恢复路径的盲区', () => { | ||
| 110 | const now = new Date().toISOString(); | 110 | const now = new Date().toISOString(); |
| 111 | const ins = wdb.getDb().prepare(` | 111 | const ins = wdb.getDb().prepare(` |
| 112 | INSERT OR IGNORE INTO system_agents | 112 | INSERT OR IGNORE INTO system_agents |
| 113 | - (id, name, scope, owner_company_id, source_kind, legacy_path, status, created_at, updated_at) | ||
| 114 | - VALUES (?, ?, 'public', NULL, 'global_agent', ?, 'active', ?, ?) | 113 | + (id, name, scope, owner_company_id, legacy_path, status, created_at, updated_at) |
| 114 | + VALUES (?, ?, 'public', NULL, ?, 'active', ?, ?) | ||
| 115 | `); | 115 | `); |
| 116 | for (const id of new Set(ids)) ins.run(id, id, `system/agents/${id}`, now, now); | 116 | for (const id of new Set(ids)) ins.run(id, id, `system/agents/${id}`, now, now); |
| 117 | } | 117 | } |
server/tests/unit/p3c-recovery-http.test.js
| @@ -94,8 +94,8 @@ describe('恢复动作的 HTTP 结局:退得回队列 → 2xx,退不回 → | @@ -94,8 +94,8 @@ describe('恢复动作的 HTTP 结局:退得回队列 → 2xx,退不回 → | ||
| 94 | const now = new Date().toISOString(); | 94 | const now = new Date().toISOString(); |
| 95 | const ins = wdb.getDb().prepare(` | 95 | const ins = wdb.getDb().prepare(` |
| 96 | INSERT OR IGNORE INTO system_agents | 96 | INSERT OR IGNORE INTO system_agents |
| 97 | - (id, name, scope, owner_company_id, source_kind, legacy_path, status, created_at, updated_at) | ||
| 98 | - VALUES (?, ?, 'public', NULL, 'global_agent', ?, 'active', ?, ?) | 97 | + (id, name, scope, owner_company_id, legacy_path, status, created_at, updated_at) |
| 98 | + VALUES (?, ?, 'public', NULL, ?, 'active', ?, ?) | ||
| 99 | `); | 99 | `); |
| 100 | for (const id of new Set(ids)) ins.run(id, id, `system/agents/${id}`, now, now); | 100 | for (const id of new Set(ids)) ins.run(id, id, `system/agents/${id}`, now, now); |
| 101 | } | 101 | } |
server/tests/unit/prompt-write-boundary.test.js
| @@ -93,8 +93,8 @@ describe('提示词的交付物归宿:graph 节点 vs agent-chat 两条链路' | @@ -93,8 +93,8 @@ describe('提示词的交付物归宿:graph 节点 vs agent-chat 两条链路' | ||
| 93 | const ts = new Date().toISOString(); | 93 | const ts = new Date().toISOString(); |
| 94 | db.prepare(` | 94 | db.prepare(` |
| 95 | INSERT OR IGNORE INTO system_agents | 95 | INSERT OR IGNORE INTO system_agents |
| 96 | - (id, name, scope, owner_company_id, source_kind, legacy_path, status, created_at, updated_at) | ||
| 97 | - VALUES (?, ?, 'public', NULL, 'global_agent', ?, 'active', ?, ?) | 96 | + (id, name, scope, owner_company_id, legacy_path, status, created_at, updated_at) |
| 97 | + VALUES (?, ?, 'public', NULL, ?, 'active', ?, ?) | ||
| 98 | `).run(AGENT_ID, AGENT_ID, `system/agents/${AGENT_ID}`, ts, ts); | 98 | `).run(AGENT_ID, AGENT_ID, `system/agents/${AGENT_ID}`, ts, ts); |
| 99 | 99 | ||
| 100 | const g = wdb.createWorkGraph({ | 100 | const g = wdb.createWorkGraph({ |
server/tests/unit/source-kind-drop-migration.test.js
0 → 100644
| 1 | +/** | ||
| 2 | + * 28-followups §3.1 —— `system_agents.source_kind` 删列的**存量库路径**。 | ||
| 3 | + * | ||
| 4 | + * 单独钉这一条的理由与 tasks-executor-migration 一样:新建库那条路由单测天然覆盖 | ||
| 5 | + * (建表块里已经没有这一列),「存量库 + 重启」这条必须另外安排人走一遍。 | ||
| 6 | + * | ||
| 7 | + * ── 要守的三件事 ────────────────────────────────────────────────────── | ||
| 8 | + * ① **顺序**:先把 source_kind 的信息回填进 origin,再删列。反过来做, | ||
| 9 | + * 「origin 列出现之前写下的行」永远回不到正确的 origin —— 那正是那条回填存在的理由。 | ||
| 10 | + * 这一条最容易在重构里被弄反,而且弄反之后**不报错**:库能开、测试能过, | ||
| 11 | + * 只有那几行 origin 悄悄是错的。 | ||
| 12 | + * ② **幂等**:删过之后第二次开库不能抛(`no such column: source_kind`)。 | ||
| 13 | + * ③ **位置**:删列必须跑在任何写入之前。migrate() 由 getDb() 在建表之后立刻调用, | ||
| 14 | + * 正是那个位置;挪到别处的话,带着 source_kind 的 INSERT 会先撞 NOT NULL。 | ||
| 15 | + * | ||
| 16 | + * ── 为什么 legacy_path 不一起删 ──────────────────────────────────────── | ||
| 17 | + * 它**还没死**:新行里它恒等于 system/agents/{id}(冗余),但存量行里存着批次 4 之前的 | ||
| 18 | + * 公司侧路径,而 graph-seed-service 判「悬空行 / 孤儿行」靠的就是它指向的目录还在不在。 | ||
| 19 | + * 实查生产库(2026-08-20)76 行里有 2 行仍是老路径。退出条件写在 graph-db.js 的建表注释里。 | ||
| 20 | + */ | ||
| 21 | +import { describe, it, expect } from 'vitest'; | ||
| 22 | +import { createRequire } from 'module'; | ||
| 23 | +import fs from 'fs'; | ||
| 24 | +import os from 'os'; | ||
| 25 | +import path from 'path'; | ||
| 26 | +const require_ = createRequire(import.meta.url); | ||
| 27 | + | ||
| 28 | +/** 用独立 DB 文件跑一遍 getDb()(写法照抄 tasks-executor-migration.test.js) */ | ||
| 29 | +function freshDb(dbPath) { | ||
| 30 | + process.env.GRAPH_DB_PATH = dbPath; | ||
| 31 | + for (const m of ['../../services/graph-db', '../../services/work-graph-db']) { | ||
| 32 | + delete require_.cache[require_.resolve(m)]; | ||
| 33 | + } | ||
| 34 | + const graphDb = require_('../../services/graph-db'); | ||
| 35 | + graphDb.getDb(); | ||
| 36 | + return graphDb; | ||
| 37 | +} | ||
| 38 | + | ||
| 39 | +const cols = (db) => db.pragma('table_info(system_agents)').map((c) => c.name); | ||
| 40 | + | ||
| 41 | +describe('system_agents.source_kind 删列', () => { | ||
| 42 | + it('存量库重启:先回填 origin 再删列,且不炸', () => { | ||
| 43 | + const dbPath = path.join(fs.mkdtempSync(path.join(os.tmpdir(), 'sk-drop-')), 'graph.sqlite'); | ||
| 44 | + const graphDb = freshDb(dbPath); | ||
| 45 | + expect(cols(graphDb.getDb())).not.toContain('source_kind'); // 新建库本来就没有 | ||
| 46 | + graphDb.getDb().close(); | ||
| 47 | + | ||
| 48 | + // 退回删列之前的形态:把列加回来,并造两行「origin 列出现之前」的记录 —— | ||
| 49 | + // 它们的 origin 是加列时的默认值 'builtin',真相只存在 source_kind 里。 | ||
| 50 | + const Database = require_('better-sqlite3'); | ||
| 51 | + const raw = new Database(dbPath); | ||
| 52 | + raw.exec(`ALTER TABLE system_agents ADD COLUMN source_kind TEXT NOT NULL DEFAULT 'global_agent'`); | ||
| 53 | + const now = new Date().toISOString(); | ||
| 54 | + const ins = raw.prepare(`INSERT INTO system_agents | ||
| 55 | + (id, name, scope, owner_company_id, legacy_path, status, created_at, updated_at, origin, source_kind) | ||
| 56 | + VALUES (?, ?, ?, ?, ?, 'active', ?, ?, 'builtin', ?)`); | ||
| 57 | + ins.run('legacy-hired', 'legacy-hired', 'company', 'co-x', 'system/agents/legacy-hired', now, now, 'company_hired'); | ||
| 58 | + ins.run('legacy-global', 'legacy-global', 'public', null, 'system/agents/legacy-global', now, now, 'global_agent'); | ||
| 59 | + expect(raw.pragma('table_info(system_agents)').map((c) => c.name)).toContain('source_kind'); | ||
| 60 | + raw.close(); | ||
| 61 | + | ||
| 62 | + // 重开进程 = 服务重启 | ||
| 63 | + const graphDb2 = freshDb(dbPath); | ||
| 64 | + const db2 = graphDb2.getDb(); | ||
| 65 | + | ||
| 66 | + expect(cols(db2)).not.toContain('source_kind'); // ② 列没了 | ||
| 67 | + // ① 顺序:company_hired 那行的 origin 必须已经被回填成 created。 | ||
| 68 | + // 先删后填的话这里会停在 'builtin' —— 而且不会有任何报错。 | ||
| 69 | + const get = (id) => db2.prepare('SELECT origin FROM system_agents WHERE id = ?').get(id); | ||
| 70 | + expect(get('legacy-hired').origin).toBe('created'); | ||
| 71 | + expect(get('legacy-global').origin).toBe('builtin'); // 对照:不该被一起改写 | ||
| 72 | + db2.close(); | ||
| 73 | + | ||
| 74 | + // ③ 幂等:再重启一次,不抛,结果不变 | ||
| 75 | + const graphDb3 = freshDb(dbPath); | ||
| 76 | + expect(cols(graphDb3.getDb())).not.toContain('source_kind'); | ||
| 77 | + expect(graphDb3.getDb().prepare('SELECT origin FROM system_agents WHERE id = ?').get('legacy-hired').origin).toBe('created'); | ||
| 78 | + | ||
| 79 | + fs.rmSync(path.dirname(dbPath), { recursive: true, force: true }); | ||
| 80 | + delete process.env.GRAPH_DB_PATH; | ||
| 81 | + }); | ||
| 82 | + | ||
| 83 | + it('legacy_path 仍在,且删它的退出条件写在了建表注释里(别顺手一起删)', () => { | ||
| 84 | + const dbPath = path.join(fs.mkdtempSync(path.join(os.tmpdir(), 'sk-drop2-')), 'graph.sqlite'); | ||
| 85 | + const graphDb = freshDb(dbPath); | ||
| 86 | + expect(cols(graphDb.getDb())).toContain('legacy_path'); | ||
| 87 | + graphDb.getDb().close(); | ||
| 88 | + fs.rmSync(path.dirname(dbPath), { recursive: true, force: true }); | ||
| 89 | + delete process.env.GRAPH_DB_PATH; | ||
| 90 | + | ||
| 91 | + // 退出条件必须留在源码里可被读到 —— 否则下一个人只会看到一列「没人读的冗余字段」 | ||
| 92 | + const src = fs.readFileSync(path.join(__dirname, '../../services/graph-db.js'), 'utf-8'); | ||
| 93 | + expect(src).toContain("legacy_path <> 'system/agents/' || id"); | ||
| 94 | + }); | ||
| 95 | +}); |
server/tests/unit/task-blocked-reason.test.js
| @@ -74,8 +74,8 @@ describe('blocked task 的原因回写', () => { | @@ -74,8 +74,8 @@ describe('blocked task 的原因回写', () => { | ||
| 74 | 74 | ||
| 75 | db.prepare(` | 75 | db.prepare(` |
| 76 | INSERT OR IGNORE INTO system_agents | 76 | INSERT OR IGNORE INTO system_agents |
| 77 | - (id, name, scope, owner_company_id, source_kind, legacy_path, status, created_at, updated_at) | ||
| 78 | - VALUES ('sdd-planning', 'sdd-planning', 'public', NULL, 'global_agent', 'system/agents/sdd-planning', 'active', ?, ?) | 77 | + (id, name, scope, owner_company_id, legacy_path, status, created_at, updated_at) |
| 78 | + VALUES ('sdd-planning', 'sdd-planning', 'public', NULL, 'system/agents/sdd-planning', 'active', ?, ?) | ||
| 79 | `).run(now, now); | 79 | `).run(now, now); |
| 80 | 80 | ||
| 81 | const graph = wdb.createWorkGraph({ companyId, nodes: ['sdd-planning'], edges: [] }); | 81 | const graph = wdb.createWorkGraph({ companyId, nodes: ['sdd-planning'], edges: [] }); |
server/tests/unit/task-queue-acceptance.test.js
| @@ -130,8 +130,8 @@ describe('批 3 排队 · §八 验收', () => { | @@ -130,8 +130,8 @@ describe('批 3 排队 · §八 验收', () => { | ||
| 130 | const now = new Date().toISOString(); | 130 | const now = new Date().toISOString(); |
| 131 | const ins = wdb.getDb().prepare(` | 131 | const ins = wdb.getDb().prepare(` |
| 132 | INSERT OR IGNORE INTO system_agents | 132 | INSERT OR IGNORE INTO system_agents |
| 133 | - (id, name, scope, owner_company_id, source_kind, legacy_path, status, created_at, updated_at) | ||
| 134 | - VALUES (?, ?, 'public', NULL, 'global_agent', ?, 'active', ?, ?) | 133 | + (id, name, scope, owner_company_id, legacy_path, status, created_at, updated_at) |
| 134 | + VALUES (?, ?, 'public', NULL, ?, 'active', ?, ?) | ||
| 135 | `); | 135 | `); |
| 136 | for (const id of new Set(ids)) ins.run(id, id, `system/agents/${id}`, now, now); | 136 | for (const id of new Set(ids)) ins.run(id, id, `system/agents/${id}`, now, now); |
| 137 | } | 137 | } |
server/tests/unit/task-scheduler.test.js
| @@ -112,8 +112,8 @@ describe('task-scheduler', () => { | @@ -112,8 +112,8 @@ describe('task-scheduler', () => { | ||
| 112 | const now = new Date().toISOString(); | 112 | const now = new Date().toISOString(); |
| 113 | const ins = wdb.getDb().prepare(` | 113 | const ins = wdb.getDb().prepare(` |
| 114 | INSERT OR IGNORE INTO system_agents | 114 | INSERT OR IGNORE INTO system_agents |
| 115 | - (id, name, scope, owner_company_id, source_kind, legacy_path, status, created_at, updated_at) | ||
| 116 | - VALUES (?, ?, 'public', NULL, 'global_agent', ?, 'active', ?, ?) | 115 | + (id, name, scope, owner_company_id, legacy_path, status, created_at, updated_at) |
| 116 | + VALUES (?, ?, 'public', NULL, ?, 'active', ?, ?) | ||
| 117 | `); | 117 | `); |
| 118 | for (const id of new Set(ids)) ins.run(id, id, `system/agents/${id}`, now, now); | 118 | for (const id of new Set(ids)) ins.run(id, id, `system/agents/${id}`, now, now); |
| 119 | } | 119 | } |
server/tests/unit/task-slots-parallel.test.js
| @@ -71,8 +71,8 @@ try { | @@ -71,8 +71,8 @@ try { | ||
| 71 | const db = wdb.getDb(); | 71 | const db = wdb.getDb(); |
| 72 | const now = new Date().toISOString(); | 72 | const now = new Date().toISOString(); |
| 73 | db.prepare( | 73 | db.prepare( |
| 74 | - "INSERT OR IGNORE INTO system_agents (id,name,scope,owner_company_id,source_kind,legacy_path,status,created_at,updated_at)" | ||
| 75 | - + " VALUES ('solo','solo','public',NULL,'global_agent','system/agents/solo','active',?,?)" | 74 | + "INSERT OR IGNORE INTO system_agents (id,name,scope,owner_company_id,legacy_path,status,created_at,updated_at)" |
| 75 | + + " VALUES ('solo','solo','public',NULL,'system/agents/solo','active',?,?)" | ||
| 76 | ).run(now, now); | 76 | ).run(now, now); |
| 77 | const g = wdb.createWorkGraph({ companyId: cid, nodes: ['solo'], edges: [] }); | 77 | const g = wdb.createWorkGraph({ companyId: cid, nodes: ['solo'], edges: [] }); |
| 78 | if ((g.errors || []).length) throw new Error(JSON.stringify(g.errors)); | 78 | if ((g.errors || []).length) throw new Error(JSON.stringify(g.errors)); |
| @@ -186,8 +186,8 @@ describe('批 4 · 公司级执行位(max_parallel_tasks)', () => { | @@ -186,8 +186,8 @@ describe('批 4 · 公司级执行位(max_parallel_tasks)', () => { | ||
| 186 | const now = new Date().toISOString(); | 186 | const now = new Date().toISOString(); |
| 187 | const ins = wdb.getDb().prepare(` | 187 | const ins = wdb.getDb().prepare(` |
| 188 | INSERT OR IGNORE INTO system_agents | 188 | INSERT OR IGNORE INTO system_agents |
| 189 | - (id, name, scope, owner_company_id, source_kind, legacy_path, status, created_at, updated_at) | ||
| 190 | - VALUES (?, ?, 'public', NULL, 'global_agent', ?, 'active', ?, ?) | 189 | + (id, name, scope, owner_company_id, legacy_path, status, created_at, updated_at) |
| 190 | + VALUES (?, ?, 'public', NULL, ?, 'active', ?, ?) | ||
| 191 | `); | 191 | `); |
| 192 | for (const id of new Set(ids)) ins.run(id, id, `system/agents/${id}`, now, now); | 192 | for (const id of new Set(ids)) ins.run(id, id, `system/agents/${id}`, now, now); |
| 193 | } | 193 | } |