From 0ad9af5975cd38c5fef1e5333c6d78fd93149551 Mon Sep 17 00:00:00 2001 From: yibo Date: Thu, 20 Aug 2026 18:34:50 +0800 Subject: [PATCH] refactor(db): 删掉 system_agents.source_kind;legacy_path 留下,写清退出条件 --- docs/todo/system-design/28-followups.md | 42 ++++++++++++++++++++++++++++++++++++++---- server/services/graph-db.js | 47 +++++++++++++++++++++++++++++++++++++++-------- server/services/graph-seed-service.js | 13 ++++++------- server/services/graph-service.js | 4 ++-- server/tests/integration/company-create-mainline.test.js | 4 ++-- server/tests/integration/graph-agent-p1-acceptance.test.js | 4 ++-- server/tests/integration/graph-agent-p1-hire.test.js | 4 ++-- server/tests/unit/agent-body-draft-gate.test.js | 8 ++++---- server/tests/unit/agent-transcript.test.js | 4 ++-- server/tests/unit/base-graduation-gate.test.js | 4 ++-- server/tests/unit/boundary-guard-new-body.test.js | 4 ++-- server/tests/unit/company-hire-org-layer.test.js | 4 ++-- server/tests/unit/goal-task-acceptance.test.js | 4 ++-- server/tests/unit/graph-boundary.test.js | 4 ++-- server/tests/unit/graph-cross-layer-address.test.js | 4 ++-- server/tests/unit/graph-db.test.js | 6 +++--- server/tests/unit/graph-edge-contract-role.test.js | 4 ++-- server/tests/unit/graph-edge-contracts.test.js | 4 ++-- server/tests/unit/graph-engine-fail-reason.test.js | 4 ++-- server/tests/unit/graph-engine-node-error.test.js | 4 ++-- server/tests/unit/graph-engine-revive-parent.test.js | 4 ++-- server/tests/unit/graph-engine.test.js | 4 ++-- server/tests/unit/graph-files-index.test.js | 4 ++-- server/tests/unit/graph-real-chain-smoke.test.js | 16 ++++++++-------- server/tests/unit/graph-run-route-io.test.js | 4 ++-- server/tests/unit/graph-run-route-slot-busy.test.js | 4 ++-- server/tests/unit/graph-run-slot-recovery.test.js | 4 ++-- server/tests/unit/graph-seed-reconcile.test.js | 8 ++++---- server/tests/unit/graph-seed-service.test.js | 10 +++++----- server/tests/unit/graph-service.test.js | 8 ++++---- server/tests/unit/heartbeat-company-scoped.test.js | 4 ++-- server/tests/unit/org-layer-contracts.test.js | 4 ++-- server/tests/unit/p3c-recovery-blind-spots.test.js | 4 ++-- server/tests/unit/p3c-recovery-http.test.js | 4 ++-- server/tests/unit/prompt-write-boundary.test.js | 4 ++-- server/tests/unit/source-kind-drop-migration.test.js | 95 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ server/tests/unit/task-blocked-reason.test.js | 4 ++-- server/tests/unit/task-queue-acceptance.test.js | 4 ++-- server/tests/unit/task-scheduler.test.js | 4 ++-- server/tests/unit/task-slots-parallel.test.js | 8 ++++---- 40 files changed, 268 insertions(+), 109 deletions(-) create mode 100644 server/tests/unit/source-kind-drop-migration.test.js diff --git a/docs/todo/system-design/28-followups.md b/docs/todo/system-design/28-followups.md index f6015eb..899a88b 100644 --- a/docs/todo/system-design/28-followups.md +++ b/docs/todo/system-design/28-followups.md @@ -54,10 +54,44 @@ ## 三、未实施的工程项 -### 3.1 `source_kind` / `legacy_path` 删列(验收 2 后半) -两列仍在库里。`origin` 已经是真相来源,`legacy_path` 的所有**读**取点都已改成从 id 推导路径 -(唯二的例外是 `graph-seed-service` 里判断「悬空行」时拿它做存在性检查,那是正当用法)。 -删列前需确认没有遗漏的读取方。 +### 3.1 ~~`source_kind`~~ 已删 / `legacy_path` **不能删**(验收 2 后半) + +**审计结论与本条原来的写法不一样,两列要分开处理。** + +**`source_kind` ✅ 已删(2026-08-20)**:它与 `origin` 是一一映射 +(`global_agent`↔`builtin` / `company_hired`↔`created`),实查生产库 76 行零例外。 +全仓唯一读侧是 `graph-db.migrate()` 里那条 origin 回填,而那是给「`origin` 列出现之前写下的行」 +用的一次性迁移。所以顺序是**先回填、再删列**,两步都用 `hasColumn` 守着(幂等)。 +写侧散在 3 处生产 INSERT + 35 个测试文件,一并清掉。 +守卫 `source-kind-drop-migration.test.js` 走存量库重启那条路,**专门钉住顺序** —— +反过来做不会报错,只是那几行 origin 悄悄是错的(反向验过,弄反立刻红)。 + +**`legacy_path` ❌ 不能删,它还没死**。原文说「所有读取点都已改成从 id 推导」—— +那半句对新行成立,对存量行不成立: + +- 新写入的行 `legacy_path` 恒等于 `system/agents/{id}`(= 可由 id 推导,确实冗余); +- **存量行里存着批次 4 之前的公司侧路径**(`data/companies/{cid}/agents/{role}`), + 而 `graph-seed-service` 判「悬空行 / 孤儿行」正是靠**这个路径指向的目录还在不在**。 + 从 id 推导出来的路径永远指向 `system/agents/`,那条判据会整个失效。 + +实查生产库(2026-08-20):76 行里**还有 2 行**是老路径(`org-planner` / `org-coder` +指着已经不存在的 `data/companies/mycompany/agents/`)—— 正是这条判据要认出来的那种行。 + +> **删它的退出条件**(写在 `graph-db.js` 建表注释里,别只留在这份文档): +> 库里不再有 `legacy_path <> 'system/agents/' || id` 的行。 +> 到那一天它才真的零信息量。 + +### 3.1b 顺带记一条:`getDb()` 会就地迁移生产库 + +`graph-db.getDb()` 内部调 `migrate()`。**任何 require 本模块并调 `getDb()` 的进程都会改生产库的 schema**, +包括一条随手敲的 `node -e "...getDb()..."`。 + +删 `source_kind` 那次,生产库的列就是被一条**查状态用的**一行脚本删掉的 —— +服务当时根本没重启,人也没打算做迁移。结果是对的(迁移幂等,76 行逐行核对与备份一致、 +origin 全部按映射回填正确),但「读一下状态」不该顺手改 schema 这件事本身值得知道。 + +`assertNotProdDbInTests` 那道闸门**只挡测试进程**,故意不挡普通脚本(挡了服务自己也起不来)。 +要只读地看一眼,用 `new Database('data/graph.sqlite', {readonly: true})`,别走 `getDb()`。 ### 3.2 任务态清理策略(§5.6) 只有设计,没有实现。任务目录 `system/agents/{aid}/{cid}/{taskId}/` 在源码树里无限增长。 diff --git a/server/services/graph-db.js b/server/services/graph-db.js index 4ac65b7..8f15f0e 100644 --- a/server/services/graph-db.js +++ b/server/services/graph-db.js @@ -143,10 +143,20 @@ function migrate(database) { // 回填放在 if 外面,且写成「只改需要改的行」:ALTER 与 UPDATE 是两条语句, // 中间挂掉的话 company_hired 行会停在默认的 'builtin' 上,下次 migrate 必须能补上。 // WHERE 里的 origin <> 'created' 让第二遍变成零行更新(幂等)。 - database.exec(` - UPDATE system_agents SET origin = 'created' - WHERE source_kind = 'company_hired' AND origin <> 'created' - `); + // + // **顺序不能反**:先把 source_kind 的信息回填进 origin,再删列。反过来做的话, + // 「origin 列出现之前写下的行」永远回不到正确的 origin —— 那正是这条回填存在的理由。 + // 两步都用 hasColumn 守着,所以删过之后第二遍是零操作(幂等)。 + if (hasColumn(database, 'system_agents', 'source_kind')) { + database.exec(` + UPDATE system_agents SET origin = 'created' + WHERE source_kind = 'company_hired' AND origin <> 'created' + `); + // 28-followups §3.1:source_kind 与 origin 一一映射,零信息量,读侧只有上面那一条回填。 + // 删列会让所有还带着它的 INSERT 报错,所以这一步必须跑在**任何写入之前** —— + // migrate() 由 getDb() 在建表之后立刻调用,正是那个位置。 + database.exec(`ALTER TABLE system_agents DROP COLUMN source_kind`); + } // 7. 28 批次 4:company_agents 的存量回填。 backfillCompanyAgents(database); @@ -229,6 +239,18 @@ function createEdgesTable(database) { * * 需要读真库的测试显式写 `process.env.GRAPH_DB_PATH = <真库路径>` 表明意图即可。 */ +/** + * ⚠️ **`getDb()` 会跑 `migrate()`** —— 也就是说,**任何 require 本模块并调 getDb() 的进程 + * 都会就地修改生产库的 schema**,包括一条随手敲的 `node -e "...getDb()..."`。 + * + * 这不是假想:2026-08-20 删 `source_kind` 那次,生产库的列是被一条**查状态用的** + * 一行脚本删掉的 —— 服务当时根本没重启,人也没打算迁移。结果是对的(迁移本身幂等、 + * 76 行逐行核对无误),但「读一下状态」不该顺手改 schema 这件事本身值得知道。 + * + * 下面这道闸门**只挡测试进程**(避免污染共享数据),故意不挡普通脚本 —— + * 挡了的话服务自己也起不来。要只读地看一眼,用 + * `new (require('better-sqlite3'))('data/graph.sqlite', {readonly:true})`,别走 getDb()。 + */ function assertNotProdDbInTests() { if (!process.env.VITEST) return; if (process.env.GRAPH_DB_PATH) return; @@ -258,10 +280,19 @@ function getDb() { name TEXT NOT NULL, scope TEXT NOT NULL CHECK (scope IN ('public','company')), owner_company_id TEXT, - -- source_kind / legacy_path 是 28 批次 1 之后的**遗留列**:读侧已全部改走 - -- origin + 「路径恒为 system/agents/{id}」的推导,这两列只为存量库和还没改完的 - -- 批次(company_agents 表落地在批次 4)留一版兼容,写入侧仍按 origin 一一映射维护。 - source_kind TEXT NOT NULL CHECK (source_kind IN ('global_agent','template','company_hired')), + -- legacy_path 是 28 批次 1 之后的遗留列,但**还没死**(28-followups §3.1)。 + -- 新写入的行它恒等于 system/agents/{id}(= 可由 id 推导,冗余), + -- 但存量行里存着批次 4 之前的公司侧路径(data/companies/{cid}/agents/{role}), + -- 而 graph-seed-service 判「悬空行 / 孤儿行」正是靠「这个路径指向的目录还在不在」。 + -- 删它之前的退出条件:库里不再有 legacy_path <> 'system/agents/' || id 的行。 + -- (2026-08-20 实查生产库:76 行里还有 2 行不满足 —— org-planner / org-coder + -- 指着已经不存在的 data/companies/mycompany/agents/。所以这一版还不能删。) + -- + -- source_kind 已于 2026-08-20 删除:它与 origin 一一映射 + -- (global_agent 对 builtin / company_hired 对 created),实查生产库 76 行零例外, + -- 唯一读侧是 migrate() 里那条 origin 回填,而那是给「origin 列出现之前的行」用的一次性迁移。 + -- 注意:本段刻意不用反引号 —— 这里在 JS 模板字符串里面,反引号会把它提前截断 + -- (踩过两次:一次在这张表,一次在 work-graph-db 的 DDL)。 legacy_path TEXT NOT NULL, status TEXT NOT NULL DEFAULT 'active', benchmark_score REAL, diff --git a/server/services/graph-seed-service.js b/server/services/graph-seed-service.js index 9aaaae0..5acd307 100644 --- a/server/services/graph-seed-service.js +++ b/server/services/graph-seed-service.js @@ -372,7 +372,7 @@ function reconcileDanglingRows() { * 返回本体路径,等于在公司毫不知情的情况下换掉它的 agent.md。这个死角由批次 4/5 * (把私有拷贝毕业成本体)根治,不在这里硬来。 * - 那行是**悬空的**(isStaleCreatedRow)→ 就地转正成 builtin:scope / owner_company_id / - * origin / source_kind / legacy_path 全部改写。原来的「一律 skip」让污染不可逆: + * origin / legacy_path 全部改写。原来的「一律 skip」让污染不可逆: * 公司目录删了、行还占着本体 id,本体永远补不回来。转正而不是删了重建, * 是为了让还引着这行的 org_graph_nodes 外键继续有效。 * @@ -404,17 +404,16 @@ function seedAgents() { // status 也要取:ON CONFLICT 分支要拿它判「这行已经毕业了,别被 config.json 推回去」 const findExisting = db.prepare(`SELECT id, origin, legacy_path, status FROM system_agents WHERE id = ?`); - // ON CONFLICT 分支必须连 scope/owner_company_id/origin/source_kind 一起改写,接管才算完成: + // ON CONFLICT 分支必须连 scope/owner_company_id/origin 一起改写,接管才算完成: // 只改 legacy_path 会留下一行「legacy_path 指向 system/agents/ 却仍标着 created」的半吊子记录。 // 对本来就是 builtin 的行这几列是原值写原值。 const upsert = db.prepare(` - INSERT INTO system_agents (id, name, scope, owner_company_id, source_kind, origin, legacy_path, status, benchmark_score, created_at, updated_at) - VALUES (@id, @name, 'public', NULL, 'global_agent', 'builtin', @legacy_path, @status, @benchmark_score, @created_at, @updated_at) + INSERT INTO system_agents (id, name, scope, owner_company_id, origin, legacy_path, status, benchmark_score, created_at, updated_at) + VALUES (@id, @name, 'public', NULL, 'builtin', @legacy_path, @status, @benchmark_score, @created_at, @updated_at) ON CONFLICT(id) DO UPDATE SET name = excluded.name, scope = excluded.scope, owner_company_id = excluded.owner_company_id, - source_kind = excluded.source_kind, origin = excluded.origin, legacy_path = excluded.legacy_path, status = excluded.status, @@ -523,8 +522,8 @@ function seedCompanyAgents() { const findAgent = db.prepare(`SELECT id FROM system_agents WHERE id = ?`); const insertAgent = db.prepare(` - INSERT INTO system_agents (id, name, scope, owner_company_id, source_kind, origin, legacy_path, status, benchmark_score, created_at, updated_at) - VALUES (@id, @name, 'company', @owner_company_id, 'company_hired', 'created', @legacy_path, 'active', NULL, @created_at, @updated_at) + INSERT INTO system_agents (id, name, scope, owner_company_id, origin, legacy_path, status, benchmark_score, created_at, updated_at) + VALUES (@id, @name, 'company', @owner_company_id, 'created', @legacy_path, 'active', NULL, @created_at, @updated_at) ON CONFLICT(id) DO NOTHING `); const findNode = db.prepare(` diff --git a/server/services/graph-service.js b/server/services/graph-service.js index 0b7c0e4..c6ecb3f 100644 --- a/server/services/graph-service.js +++ b/server/services/graph-service.js @@ -85,8 +85,8 @@ function ensureSystemAgentRow(companyId, systemAgentId, roleName, opts = {}) { const now = new Date().toISOString(); db.prepare(` - INSERT INTO system_agents (id, name, scope, owner_company_id, source_kind, origin, legacy_path, status, benchmark_score, created_at, updated_at) - VALUES (@id, @name, 'company', @owner_company_id, 'company_hired', 'created', @legacy_path, @status, NULL, @created_at, @updated_at) + INSERT INTO system_agents (id, name, scope, owner_company_id, origin, legacy_path, status, benchmark_score, created_at, updated_at) + VALUES (@id, @name, 'company', @owner_company_id, 'created', @legacy_path, @status, NULL, @created_at, @updated_at) `).run({ id: systemAgentId, name: systemAgentId, diff --git a/server/tests/integration/company-create-mainline.test.js b/server/tests/integration/company-create-mainline.test.js index 52742c0..783097b 100644 --- a/server/tests/integration/company-create-mainline.test.js +++ b/server/tests/integration/company-create-mainline.test.js @@ -85,8 +85,8 @@ describe('28 终验 · 「新建公司」主线(真本体)', () => { const now = new Date().toISOString(); const ins = db().prepare(` INSERT OR REPLACE INTO system_agents - (id, name, scope, owner_company_id, source_kind, origin, legacy_path, status, created_at, updated_at) - VALUES (?, ?, 'public', NULL, 'global_agent', 'builtin', ?, ?, ?, ?) + (id, name, scope, owner_company_id, origin, legacy_path, status, created_at, updated_at) + VALUES (?, ?, 'public', NULL, 'builtin', ?, ?, ?, ?) `); for (const id of REAL_BODIES) { const cfg = JSON.parse(fs.readFileSync(path.join(locator.bodyDir(id), 'config.json'), 'utf-8')); diff --git a/server/tests/integration/graph-agent-p1-acceptance.test.js b/server/tests/integration/graph-agent-p1-acceptance.test.js index 38d07d7..bf14e77 100644 --- a/server/tests/integration/graph-agent-p1-acceptance.test.js +++ b/server/tests/integration/graph-agent-p1-acceptance.test.js @@ -80,8 +80,8 @@ describe('本体共享 + 公司态下沉 — 端到端验收(28 §八 4/5)', // 镜像生产种子(seedAgents:两个 id 都是 origin='builtin' 的本体行) for (const id of ['qa-judge', COLLISION_ROLE]) { db.prepare(` - INSERT INTO system_agents (id, name, scope, owner_company_id, source_kind, origin, legacy_path, status, benchmark_score, created_at, updated_at) - VALUES (?, ?, 'public', NULL, 'global_agent', 'builtin', ?, 'active', NULL, ?, ?) + INSERT INTO system_agents (id, name, scope, owner_company_id, origin, legacy_path, status, benchmark_score, created_at, updated_at) + VALUES (?, ?, 'public', NULL, 'builtin', ?, 'active', NULL, ?, ?) `).run(id, id, `system/agents/${id}`, now, now); } diff --git a/server/tests/integration/graph-agent-p1-hire.test.js b/server/tests/integration/graph-agent-p1-hire.test.js index f34303b..5579fb9 100644 --- a/server/tests/integration/graph-agent-p1-hire.test.js +++ b/server/tests/integration/graph-agent-p1-hire.test.js @@ -62,8 +62,8 @@ describe('company-service.hire() — 引用本体 + 公司态下沉', () => { const db = graphDb.getDb(); const now = new Date().toISOString(); db.prepare(` - INSERT INTO system_agents (id, name, scope, owner_company_id, source_kind, origin, legacy_path, status, benchmark_score, created_at, updated_at) - VALUES (?, ?, 'public', NULL, 'global_agent', 'builtin', ?, 'active', NULL, ?, ?) + INSERT INTO system_agents (id, name, scope, owner_company_id, origin, legacy_path, status, benchmark_score, created_at, updated_at) + VALUES (?, ?, 'public', NULL, 'builtin', ?, 'active', NULL, ?, ?) `).run('qa-judge', 'qa-judge', 'system/agents/qa-judge', now, now); // 固定 id 的代价:上一次跑要是崩在中途,公司目录会留在盘上,而 create() 撞见已存在的 diff --git a/server/tests/unit/agent-body-draft-gate.test.js b/server/tests/unit/agent-body-draft-gate.test.js index 3489fc0..08c9f18 100644 --- a/server/tests/unit/agent-body-draft-gate.test.js +++ b/server/tests/unit/agent-body-draft-gate.test.js @@ -102,8 +102,8 @@ describe('新造本体的 draft 闸门(28 批次 5)', () => { const now = new Date().toISOString(); graphDb.getDb().prepare(` INSERT OR REPLACE INTO system_agents - (id, name, scope, owner_company_id, source_kind, origin, legacy_path, status, created_at, updated_at) - VALUES (?, ?, 'public', NULL, 'global_agent', 'builtin', ?, ?, ?, ?) + (id, name, scope, owner_company_id, origin, legacy_path, status, created_at, updated_at) + VALUES (?, ?, 'public', NULL, 'builtin', ?, ?, ?, ?) `).run(id, id, `system/agents/${id}`, cfg.status || 'active', now, now); }; @@ -216,8 +216,8 @@ describe('新造本体的 draft 闸门(28 批次 5)', () => { for (const status of ['enabled', 'idle', 'ready', 'trained', 'active']) { const id = `qa-judge`; db.prepare(`INSERT OR REPLACE INTO system_agents - (id,name,scope,owner_company_id,source_kind,origin,legacy_path,status,benchmark_score,created_at,updated_at) - VALUES (?,?,'public',NULL,'global_agent','builtin',?, ?,NULL,?,?)`) + (id,name,scope,owner_company_id,origin,legacy_path,status,benchmark_score,created_at,updated_at) + VALUES (?,?,'public',NULL,'builtin',?, ?,NULL,?,?)`) .run(id, id, `system/agents/${id}`, status, now, now); db.prepare(`DELETE FROM company_agents WHERE role_name = ?`).run(id); diff --git a/server/tests/unit/agent-transcript.test.js b/server/tests/unit/agent-transcript.test.js index 556933a..187f757 100644 --- a/server/tests/unit/agent-transcript.test.js +++ b/server/tests/unit/agent-transcript.test.js @@ -98,8 +98,8 @@ describe('agent-transcript:会话原文读取', () => { const now = new Date().toISOString(); db.prepare(` INSERT OR IGNORE INTO system_agents - (id, name, scope, owner_company_id, source_kind, legacy_path, status, created_at, updated_at) - VALUES (?, ?, 'public', NULL, 'global_agent', ?, 'active', ?, ?) + (id, name, scope, owner_company_id, legacy_path, status, created_at, updated_at) + VALUES (?, ?, 'public', NULL, ?, 'active', ?, ?) `).run(ROLE, ROLE, `system/agents/${ROLE}`, now, now); // 雇佣关系:listRoles 的角色清单来自这张表(28 批次 4-19),没有这行就一个角色都列不出来 diff --git a/server/tests/unit/base-graduation-gate.test.js b/server/tests/unit/base-graduation-gate.test.js index 36b674e..7be31ff 100644 --- a/server/tests/unit/base-graduation-gate.test.js +++ b/server/tests/unit/base-graduation-gate.test.js @@ -62,8 +62,8 @@ describe('_base 骨架不算定义已填满(哨兵闸门)', () => { const now = new Date().toISOString(); graphDb.getDb().prepare(` INSERT OR REPLACE INTO system_agents - (id, name, scope, owner_company_id, source_kind, origin, legacy_path, status, created_at, updated_at) - VALUES (?, ?, 'public', NULL, 'global_agent', 'builtin', ?, ?, ?, ?) + (id, name, scope, owner_company_id, origin, legacy_path, status, created_at, updated_at) + VALUES (?, ?, 'public', NULL, 'builtin', ?, ?, ?, ?) `).run(id, id, `system/agents/${id}`, cfg.status || 'active', now, now); }; const agentRow = (id) => diff --git a/server/tests/unit/boundary-guard-new-body.test.js b/server/tests/unit/boundary-guard-new-body.test.js index 200d7a8..b4e54d6 100644 --- a/server/tests/unit/boundary-guard-new-body.test.js +++ b/server/tests/unit/boundary-guard-new-body.test.js @@ -88,8 +88,8 @@ describe('边界守卫 · 新建 agent 本体不判越界(28 §九)', () => const ts = new Date().toISOString(); wdb.getDb().prepare(` INSERT OR IGNORE INTO system_agents - (id, name, scope, owner_company_id, source_kind, legacy_path, status, created_at, updated_at) - VALUES ('hr', 'hr', 'public', NULL, 'global_agent', 'system/agents/hr', 'active', ?, ?) + (id, name, scope, owner_company_id, legacy_path, status, created_at, updated_at) + VALUES ('hr', 'hr', 'public', NULL, 'system/agents/hr', 'active', ?, ?) `).run(ts, ts); const g = wdb.createWorkGraph({ companyId, nodes: ['hr'], edges: [] }); expect(g.errors || []).toEqual([]); diff --git a/server/tests/unit/company-hire-org-layer.test.js b/server/tests/unit/company-hire-org-layer.test.js index 0a26acc..cc92dc7 100644 --- a/server/tests/unit/company-hire-org-layer.test.js +++ b/server/tests/unit/company-hire-org-layer.test.js @@ -100,8 +100,8 @@ describe('hire() 的 org / work 分层(27-P3B W7)', () => { const now = new Date().toISOString(); for (const id of TEMPLATES) { db.prepare(` - INSERT INTO system_agents (id, name, scope, owner_company_id, source_kind, legacy_path, status, benchmark_score, created_at, updated_at) - VALUES (?, ?, 'public', NULL, 'template', ?, 'active', NULL, ?, ?) + INSERT INTO system_agents (id, name, scope, owner_company_id, legacy_path, status, benchmark_score, created_at, updated_at) + VALUES (?, ?, 'public', NULL, ?, 'active', NULL, ?, ?) `).run(id, id, `system/agents/${id}`, now, now); } diff --git a/server/tests/unit/goal-task-acceptance.test.js b/server/tests/unit/goal-task-acceptance.test.js index 3e5d9fc..97e5151 100644 --- a/server/tests/unit/goal-task-acceptance.test.js +++ b/server/tests/unit/goal-task-acceptance.test.js @@ -180,8 +180,8 @@ describe('27-P3C 批 1 验收(§八 4 / 5 / 6 / 10 / 11 / 12b)', () => { const now = new Date().toISOString(); db.prepare(` INSERT OR IGNORE INTO system_agents - (id, name, scope, owner_company_id, source_kind, legacy_path, status, created_at, updated_at) - VALUES ('acc-worker', 'acc-worker', 'public', NULL, 'global_agent', 'system/agents/acc-worker', 'active', ?, ?) + (id, name, scope, owner_company_id, legacy_path, status, created_at, updated_at) + VALUES ('acc-worker', 'acc-worker', 'public', NULL, 'system/agents/acc-worker', 'active', ?, ?) `).run(now, now); const g = wdb.createWorkGraph({ companyId: cid, nodes: ['acc-worker'], edges: [] }); expect(g.errors || []).toEqual([]); diff --git a/server/tests/unit/graph-boundary.test.js b/server/tests/unit/graph-boundary.test.js index 526a987..04a709e 100644 --- a/server/tests/unit/graph-boundary.test.js +++ b/server/tests/unit/graph-boundary.test.js @@ -60,8 +60,8 @@ describe('graph-engine 项目根与边界守卫', () => { const ts = new Date().toISOString(); const ins = db.prepare(` INSERT OR IGNORE INTO system_agents - (id, name, scope, owner_company_id, source_kind, legacy_path, status, created_at, updated_at) - VALUES (?, ?, 'public', NULL, 'global_agent', ?, 'active', ?, ?) + (id, name, scope, owner_company_id, legacy_path, status, created_at, updated_at) + VALUES (?, ?, 'public', NULL, ?, 'active', ?, ?) `); for (const id of new Set(ids)) ins.run(id, id, `system/agents/${id}`, ts, ts); } diff --git a/server/tests/unit/graph-cross-layer-address.test.js b/server/tests/unit/graph-cross-layer-address.test.js index 36b7d91..526243c 100644 --- a/server/tests/unit/graph-cross-layer-address.test.js +++ b/server/tests/unit/graph-cross-layer-address.test.js @@ -82,8 +82,8 @@ describe('R-17.1 跨层地址 · 两半的必填性(W10 事实核实)', () = const ts = new Date().toISOString(); const ins = db.prepare(` INSERT OR IGNORE INTO system_agents - (id, name, scope, owner_company_id, source_kind, legacy_path, status, created_at, updated_at) - VALUES (?, ?, 'public', NULL, 'global_agent', ?, 'active', ?, ?) + (id, name, scope, owner_company_id, legacy_path, status, created_at, updated_at) + VALUES (?, ?, 'public', NULL, ?, 'active', ?, ?) `); for (const id of new Set(ids)) ins.run(id, id, `system/agents/${id}`, ts, ts); } diff --git a/server/tests/unit/graph-db.test.js b/server/tests/unit/graph-db.test.js index ac52f7d..aa742f8 100644 --- a/server/tests/unit/graph-db.test.js +++ b/server/tests/unit/graph-db.test.js @@ -40,9 +40,9 @@ describe('graph-db', () => { const d = graphDb.getDb(); const now = new Date().toISOString(); d.prepare(` - INSERT INTO system_agents (id, name, scope, owner_company_id, source_kind, legacy_path, status, benchmark_score, created_at, updated_at) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - `).run('test-agent-env-check', 'Test Agent', 'public', null, 'global_agent', 'system/agents/test-agent-env-check', 'active', null, now, now); + INSERT INTO system_agents (id, name, scope, owner_company_id, legacy_path, status, benchmark_score, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) + `).run('test-agent-env-check', 'Test Agent', 'public', null, 'system/agents/test-agent-env-check', 'active', null, now, now); // The row must have landed in the overridden file, not the default one. expect(fs.existsSync(TEST_DB_PATH)).toBe(true); diff --git a/server/tests/unit/graph-edge-contract-role.test.js b/server/tests/unit/graph-edge-contract-role.test.js index 5f4afb9..be2e1ef 100644 --- a/server/tests/unit/graph-edge-contract-role.test.js +++ b/server/tests/unit/graph-edge-contract-role.test.js @@ -95,8 +95,8 @@ describe('R-19.A2 契约 role 落库 + PATCH 通道的 data-contract 校验', () const ts = new Date().toISOString(); const ins = db.prepare(` INSERT OR IGNORE INTO system_agents - (id, name, scope, owner_company_id, source_kind, legacy_path, status, created_at, updated_at) - VALUES (?, ?, 'public', NULL, 'global_agent', ?, 'active', ?, ?) + (id, name, scope, owner_company_id, legacy_path, status, created_at, updated_at) + VALUES (?, ?, 'public', NULL, ?, 'active', ?, ?) `); for (const id of ['brd-planning', 'prd-planning', 'sdd-planning']) { ins.run(id, id, `system/agents/${id}`, ts, ts); diff --git a/server/tests/unit/graph-edge-contracts.test.js b/server/tests/unit/graph-edge-contracts.test.js index 8823629..2ae40b6 100644 --- a/server/tests/unit/graph-edge-contracts.test.js +++ b/server/tests/unit/graph-edge-contracts.test.js @@ -67,8 +67,8 @@ describe('R-19.A 边上的数据契约:声明 → 落库 → 读回', () => { const ts = new Date().toISOString(); const ins = db.prepare(` INSERT OR IGNORE INTO system_agents - (id, name, scope, owner_company_id, source_kind, legacy_path, status, created_at, updated_at) - VALUES (?, ?, 'public', NULL, 'global_agent', ?, 'active', ?, ?) + (id, name, scope, owner_company_id, legacy_path, status, created_at, updated_at) + VALUES (?, ?, 'public', NULL, ?, 'active', ?, ?) `); for (const id of new Set(ids)) ins.run(id, id, `system/agents/${id}`, ts, ts); } diff --git a/server/tests/unit/graph-engine-fail-reason.test.js b/server/tests/unit/graph-engine-fail-reason.test.js index 29d2a83..5efac1d 100644 --- a/server/tests/unit/graph-engine-fail-reason.test.js +++ b/server/tests/unit/graph-engine-fail-reason.test.js @@ -92,8 +92,8 @@ describe('引擎自判失败的 fail_reason 与失败上下文(W12 补完 / W9 const ts = new Date().toISOString(); const ins = db.prepare(` INSERT OR IGNORE INTO system_agents - (id, name, scope, owner_company_id, source_kind, legacy_path, status, created_at, updated_at) - VALUES (?, ?, 'public', NULL, 'global_agent', ?, 'active', ?, ?) + (id, name, scope, owner_company_id, legacy_path, status, created_at, updated_at) + VALUES (?, ?, 'public', NULL, ?, 'active', ?, ?) `); for (const id of new Set(ids)) ins.run(id, id, `system/agents/${id}`, ts, ts); } diff --git a/server/tests/unit/graph-engine-node-error.test.js b/server/tests/unit/graph-engine-node-error.test.js index d6995fe..9b1c4bd 100644 --- a/server/tests/unit/graph-engine-node-error.test.js +++ b/server/tests/unit/graph-engine-node-error.test.js @@ -82,8 +82,8 @@ describe('graph-engine 节点失败留痕(W12)', () => { const now = new Date().toISOString(); const ins = db.prepare(` INSERT OR IGNORE INTO system_agents - (id, name, scope, owner_company_id, source_kind, legacy_path, status, created_at, updated_at) - VALUES (?, ?, 'public', NULL, 'global_agent', ?, 'active', ?, ?) + (id, name, scope, owner_company_id, legacy_path, status, created_at, updated_at) + VALUES (?, ?, 'public', NULL, ?, 'active', ?, ?) `); for (const id of new Set(ids)) ins.run(id, id, `system/agents/${id}`, now, now); } diff --git a/server/tests/unit/graph-engine-revive-parent.test.js b/server/tests/unit/graph-engine-revive-parent.test.js index 774a2e9..8912c8a 100644 --- a/server/tests/unit/graph-engine-revive-parent.test.js +++ b/server/tests/unit/graph-engine-revive-parent.test.js @@ -85,8 +85,8 @@ describe('子 run 转终态回写父节点', () => { const now = new Date().toISOString(); const ins = db.prepare(` INSERT OR IGNORE INTO system_agents - (id, name, scope, owner_company_id, source_kind, legacy_path, status, created_at, updated_at) - VALUES (?, ?, 'public', NULL, 'global_agent', ?, 'active', ?, ?) + (id, name, scope, owner_company_id, legacy_path, status, created_at, updated_at) + VALUES (?, ?, 'public', NULL, ?, 'active', ?, ?) `); for (const id of new Set(ids)) ins.run(id, id, `system/agents/${id}`, now, now); } diff --git a/server/tests/unit/graph-engine.test.js b/server/tests/unit/graph-engine.test.js index b16dea1..77467f7 100644 --- a/server/tests/unit/graph-engine.test.js +++ b/server/tests/unit/graph-engine.test.js @@ -98,8 +98,8 @@ describe('graph-engine', () => { const now = new Date().toISOString(); const ins = db.prepare(` INSERT OR IGNORE INTO system_agents - (id, name, scope, owner_company_id, source_kind, legacy_path, status, created_at, updated_at) - VALUES (?, ?, 'public', NULL, 'global_agent', ?, 'active', ?, ?) + (id, name, scope, owner_company_id, legacy_path, status, created_at, updated_at) + VALUES (?, ?, 'public', NULL, ?, 'active', ?, ?) `); for (const id of new Set(ids)) ins.run(id, id, `system/agents/${id}`, now, now); } diff --git a/server/tests/unit/graph-files-index.test.js b/server/tests/unit/graph-files-index.test.js index d93eed5..fda0ff1 100644 --- a/server/tests/unit/graph-files-index.test.js +++ b/server/tests/unit/graph-files-index.test.js @@ -75,8 +75,8 @@ describe('R-19 产物索引:graph_files / graph_edge_contracts', () => { const ts = new Date().toISOString(); const ins = db.prepare(` INSERT OR IGNORE INTO system_agents - (id, name, scope, owner_company_id, source_kind, legacy_path, status, created_at, updated_at) - VALUES (?, ?, 'public', NULL, 'global_agent', ?, 'active', ?, ?) + (id, name, scope, owner_company_id, legacy_path, status, created_at, updated_at) + VALUES (?, ?, 'public', NULL, ?, 'active', ?, ?) `); for (const id of new Set(ids)) ins.run(id, id, `system/agents/${id}`, ts, ts); } diff --git a/server/tests/unit/graph-real-chain-smoke.test.js b/server/tests/unit/graph-real-chain-smoke.test.js index 5cdf447..02bd886 100644 --- a/server/tests/unit/graph-real-chain-smoke.test.js +++ b/server/tests/unit/graph-real-chain-smoke.test.js @@ -75,8 +75,8 @@ describe('图执行真实链路 smoke(零注入,只换掉真 CLI 子进程ï¼ const db = wdb.getDb(); const now = new Date().toISOString(); db.prepare(`INSERT OR IGNORE INTO system_agents - (id, name, scope, owner_company_id, source_kind, legacy_path, status, created_at, updated_at) - VALUES (?, ?, 'public', NULL, 'global_agent', ?, 'active', ?, ?)`) + (id, name, scope, owner_company_id, legacy_path, status, created_at, updated_at) + VALUES (?, ?, 'public', NULL, ?, 'active', ?, ?)`) .run(AGENT, AGENT, `system/agents/${AGENT}`, now, now); const g = wdb.createWorkGraph({ companyId: COMPANY, nodes: [{ nodeName: AGENT, systemAgentId: AGENT }], edges: [] }); @@ -142,14 +142,14 @@ describe('图执行真实链路 smoke(零注入,只换掉真 CLI 子进程ï¼ const db = wdb.getDb(); const now = new Date().toISOString(); db.prepare(`INSERT OR IGNORE INTO system_agents - (id, name, scope, owner_company_id, source_kind, legacy_path, status, created_at, updated_at) - VALUES (?, ?, 'public', NULL, 'global_agent', ?, 'active', ?, ?)`) + (id, name, scope, owner_company_id, legacy_path, status, created_at, updated_at) + VALUES (?, ?, 'public', NULL, ?, 'active', ?, ?)`) .run(PLANNER, PLANNER, `system/agents/${PLANNER}`, now, now); // subgraph 节点自己也要有 system_agents 行(work_graph_nodes.system_agent_id 是外键)。 // 它只是「哪个 org 角色拥有这张子图」的名字,不会被真的调用 —— 调的是 PLANNER。 db.prepare(`INSERT OR IGNORE INTO system_agents - (id, name, scope, owner_company_id, source_kind, legacy_path, status, created_at, updated_at) - VALUES (?, ?, 'public', NULL, 'global_agent', ?, 'active', ?, ?)`) + (id, name, scope, owner_company_id, legacy_path, status, created_at, updated_at) + VALUES (?, ?, 'public', NULL, ?, 'active', ?, ?)`) .run(WRAPPER, WRAPPER, `system/agents/${WRAPPER}`, now, now); db.prepare(`INSERT OR IGNORE INTO company_agents (company_id, system_agent_id, role_name, status, hired_at) VALUES (?, ?, ?, 'active', ?)`).run(COMPANY, PLANNER, PLANNER, now); @@ -213,8 +213,8 @@ describe('图执行真实链路 smoke(零注入,只换掉真 CLI 子进程ï¼ const db = wdb.getDb(); const now = new Date().toISOString(); db.prepare(`INSERT OR IGNORE INTO system_agents - (id, name, scope, owner_company_id, source_kind, legacy_path, status, created_at, updated_at) - VALUES (?, ?, 'public', NULL, 'global_agent', ?, 'active', ?, ?)`) + (id, name, scope, owner_company_id, legacy_path, status, created_at, updated_at) + VALUES (?, ?, 'public', NULL, ?, 'active', ?, ?)`) .run(VALIDATOR, VALIDATOR, `system/agents/${VALIDATOR}`, now, now); const g = wdb.createWorkGraph({ diff --git a/server/tests/unit/graph-run-route-io.test.js b/server/tests/unit/graph-run-route-io.test.js index 3a6c585..e67b21d 100644 --- a/server/tests/unit/graph-run-route-io.test.js +++ b/server/tests/unit/graph-run-route-io.test.js @@ -53,8 +53,8 @@ describe('GET /api/graph-runs/:id — R-19 输入输出三字段', () => { const ts = new Date().toISOString(); const ins = db.prepare(` INSERT OR IGNORE INTO system_agents - (id, name, scope, owner_company_id, source_kind, legacy_path, status, created_at, updated_at) - VALUES (?, ?, 'public', NULL, 'global_agent', ?, 'active', ?, ?) + (id, name, scope, owner_company_id, legacy_path, status, created_at, updated_at) + VALUES (?, ?, 'public', NULL, ?, 'active', ?, ?) `); for (const id of ['brd-planning', 'prd-planning']) ins.run(id, id, `system/agents/${id}`, ts, ts); diff --git a/server/tests/unit/graph-run-route-slot-busy.test.js b/server/tests/unit/graph-run-route-slot-busy.test.js index 1cf3665..327e56a 100644 --- a/server/tests/unit/graph-run-route-slot-busy.test.js +++ b/server/tests/unit/graph-run-route-slot-busy.test.js @@ -60,8 +60,8 @@ describe('POST /api/graph-runs/... 撞上坑满 → 409(不是 500)', () => const now = new Date().toISOString(); const insAgent = db.prepare(` INSERT OR IGNORE INTO system_agents - (id, name, scope, owner_company_id, source_kind, legacy_path, status, created_at, updated_at) - VALUES (?, ?, 'public', NULL, 'global_agent', ?, 'active', ?, ?) + (id, name, scope, owner_company_id, legacy_path, status, created_at, updated_at) + VALUES (?, ?, 'public', NULL, ?, 'active', ?, ?) `); for (const id of ['solo', 'solo2']) insAgent.run(id, id, `system/agents/${id}`, now, now); diff --git a/server/tests/unit/graph-run-slot-recovery.test.js b/server/tests/unit/graph-run-slot-recovery.test.js index 64b83f7..53d6af6 100644 --- a/server/tests/unit/graph-run-slot-recovery.test.js +++ b/server/tests/unit/graph-run-slot-recovery.test.js @@ -98,8 +98,8 @@ describe('恢复路径与执行坑', () => { const now = new Date().toISOString(); const ins = wdb.getDb().prepare(` INSERT OR IGNORE INTO system_agents - (id, name, scope, owner_company_id, source_kind, legacy_path, status, created_at, updated_at) - VALUES (?, ?, 'public', NULL, 'global_agent', ?, 'active', ?, ?) + (id, name, scope, owner_company_id, legacy_path, status, created_at, updated_at) + VALUES (?, ?, 'public', NULL, ?, 'active', ?, ?) `); for (const id of new Set(ids)) ins.run(id, id, `system/agents/${id}`, now, now); } diff --git a/server/tests/unit/graph-seed-reconcile.test.js b/server/tests/unit/graph-seed-reconcile.test.js index e57714d..24378b8 100644 --- a/server/tests/unit/graph-seed-reconcile.test.js +++ b/server/tests/unit/graph-seed-reconcile.test.js @@ -63,11 +63,11 @@ describe('graph-seed 对账在存量形态上退出码为 0', () => { const db = graphDb.getDb(); const now = new Date().toISOString(); const insert = db.prepare(` - INSERT INTO system_agents (id, name, scope, owner_company_id, source_kind, origin, legacy_path, status, benchmark_score, created_at, updated_at) - VALUES (@id, @name, @scope, @owner, @source_kind, @origin, @legacy_path, 'active', NULL, @now, @now) + INSERT INTO system_agents (id, name, scope, owner_company_id, origin, legacy_path, status, benchmark_score, created_at, updated_at) + VALUES (@id, @name, @scope, @owner, @origin, @legacy_path, 'active', NULL, @now, @now) `); - insert.run({ id: 'base', name: 'base', scope: 'public', owner: null, source_kind: 'global_agent', origin: 'builtin', legacy_path: 'system/templates/base', now }); - insert.run({ id: 'graduated', name: 'graduated', scope: 'company', owner: 'live-co', source_kind: 'company_hired', origin: 'created', legacy_path: 'system/agents/graduated', now }); + insert.run({ id: 'base', name: 'base', scope: 'public', owner: null, origin: 'builtin', legacy_path: 'system/templates/base', now }); + insert.run({ id: 'graduated', name: 'graduated', scope: 'company', owner: 'live-co', origin: 'created', legacy_path: 'system/agents/graduated', now }); // ── 幽灵公司的名册行:目录(连同 company.json)已经不在,company_agents 那行还在 ── // 这张表批次 4-19 之后是**唯一被读的名册**,heartbeat-service.start() 拿它挂心跳时 diff --git a/server/tests/unit/graph-seed-service.test.js b/server/tests/unit/graph-seed-service.test.js index 30350e4..10e2863 100644 --- a/server/tests/unit/graph-seed-service.test.js +++ b/server/tests/unit/graph-seed-service.test.js @@ -148,8 +148,8 @@ describe('graph-seed-service (hermetic fixture root)', () => { function insertHired(id, ownerCompanyId, legacyPath) { const now = new Date().toISOString(); graphDb.getDb().prepare(` - INSERT INTO system_agents (id, name, scope, owner_company_id, source_kind, origin, legacy_path, status, benchmark_score, created_at, updated_at) - VALUES (?, ?, 'company', ?, 'company_hired', 'created', ?, 'active', NULL, ?, ?) + INSERT INTO system_agents (id, name, scope, owner_company_id, origin, legacy_path, status, benchmark_score, created_at, updated_at) + VALUES (?, ?, 'company', ?, 'created', ?, 'active', NULL, ?, ?) `).run(id, id, ownerCompanyId, legacyPath, now, now); } @@ -429,9 +429,9 @@ describe('graph-seed-service verifyCounts (real repo root)', () => { const db = graphDb.getDb(); const now = new Date().toISOString(); db.prepare(` - INSERT INTO system_agents (id, name, scope, owner_company_id, source_kind, legacy_path, status, benchmark_score, created_at, updated_at) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - `).run('bogus-agent-no-fs-backing', 'Bogus', 'public', null, 'global_agent', 'system/agents/bogus-agent-no-fs-backing', 'active', null, now, now); + INSERT INTO system_agents (id, name, scope, owner_company_id, legacy_path, status, benchmark_score, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) + `).run('bogus-agent-no-fs-backing', 'Bogus', 'public', null, 'system/agents/bogus-agent-no-fs-backing', 'active', null, now, now); const v = seedService.verifyCounts(); expect(v.ok).toBe(false); diff --git a/server/tests/unit/graph-service.test.js b/server/tests/unit/graph-service.test.js index 25244af..a5ba1ae 100644 --- a/server/tests/unit/graph-service.test.js +++ b/server/tests/unit/graph-service.test.js @@ -35,13 +35,13 @@ describe('graph-service', () => { db.exec('DELETE FROM org_graph_nodes; DELETE FROM system_agents;'); }); - function insertSystemAgent({ id, origin = 'builtin', source_kind = 'global_agent', legacy_path = `system/agents/${id}`, scope = 'public', owner_company_id = null }) { + function insertSystemAgent({ id, origin = 'builtin', legacy_path = `system/agents/${id}`, scope = 'public', owner_company_id = null }) { const db = graphDb.getDb(); const now = new Date().toISOString(); db.prepare(` - INSERT INTO system_agents (id, name, scope, owner_company_id, source_kind, origin, legacy_path, status, benchmark_score, created_at, updated_at) - VALUES (?, ?, ?, ?, ?, ?, ?, 'active', NULL, ?, ?) - `).run(id, id, scope, owner_company_id, source_kind, origin, legacy_path, now, now); + INSERT INTO system_agents (id, name, scope, owner_company_id, origin, legacy_path, status, benchmark_score, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, 'active', NULL, ?, ?) + `).run(id, id, scope, owner_company_id, origin, legacy_path, now, now); } describe('lookupSharedAgentMd', () => { diff --git a/server/tests/unit/heartbeat-company-scoped.test.js b/server/tests/unit/heartbeat-company-scoped.test.js index d259ec2..eed95f9 100644 --- a/server/tests/unit/heartbeat-company-scoped.test.js +++ b/server/tests/unit/heartbeat-company-scoped.test.js @@ -85,8 +85,8 @@ describe('心跳设置按公司隔离(28 遗留项)', () => { const db = graphDb.getDb(); const now = new Date().toISOString(); db.prepare(` - INSERT OR REPLACE INTO system_agents (id, name, scope, owner_company_id, source_kind, legacy_path, status, benchmark_score, created_at, updated_at) - VALUES (?, ?, 'public', NULL, 'template', ?, 'active', NULL, ?, ?) + INSERT OR REPLACE INTO system_agents (id, name, scope, owner_company_id, legacy_path, status, benchmark_score, created_at, updated_at) + VALUES (?, ?, 'public', NULL, ?, 'active', NULL, ?, ?) `).run(BODY_ID, BODY_ID, `system/agents/${BODY_ID}`, now, now); for (const cid of [CO_A, CO_B]) { db.prepare(`INSERT OR REPLACE INTO company_agents (company_id, system_agent_id, role_name, status, hired_at) diff --git a/server/tests/unit/org-layer-contracts.test.js b/server/tests/unit/org-layer-contracts.test.js index f436e8c..69ef0f8 100644 --- a/server/tests/unit/org-layer-contracts.test.js +++ b/server/tests/unit/org-layer-contracts.test.js @@ -122,8 +122,8 @@ describe('R-19 org 层数据契约(读回方 = buildPlanningPrompt)', () => const ts = new Date().toISOString(); const ins = db.prepare(` INSERT OR IGNORE INTO system_agents - (id, name, scope, owner_company_id, source_kind, legacy_path, status, created_at, updated_at) - VALUES (?, ?, 'public', NULL, 'global_agent', ?, 'active', ?, ?) + (id, name, scope, owner_company_id, legacy_path, status, created_at, updated_at) + VALUES (?, ?, 'public', NULL, ?, 'active', ?, ?) `); for (const id of new Set(ids)) ins.run(id, id, `system/agents/${id}`, ts, ts); } diff --git a/server/tests/unit/p3c-recovery-blind-spots.test.js b/server/tests/unit/p3c-recovery-blind-spots.test.js index 3e5a9de..1506037 100644 --- a/server/tests/unit/p3c-recovery-blind-spots.test.js +++ b/server/tests/unit/p3c-recovery-blind-spots.test.js @@ -110,8 +110,8 @@ describe('批 3 恢复路径的盲区', () => { const now = new Date().toISOString(); const ins = wdb.getDb().prepare(` INSERT OR IGNORE INTO system_agents - (id, name, scope, owner_company_id, source_kind, legacy_path, status, created_at, updated_at) - VALUES (?, ?, 'public', NULL, 'global_agent', ?, 'active', ?, ?) + (id, name, scope, owner_company_id, legacy_path, status, created_at, updated_at) + VALUES (?, ?, 'public', NULL, ?, 'active', ?, ?) `); for (const id of new Set(ids)) ins.run(id, id, `system/agents/${id}`, now, now); } diff --git a/server/tests/unit/p3c-recovery-http.test.js b/server/tests/unit/p3c-recovery-http.test.js index 670bdf6..12ff1a5 100644 --- a/server/tests/unit/p3c-recovery-http.test.js +++ b/server/tests/unit/p3c-recovery-http.test.js @@ -94,8 +94,8 @@ describe('恢复动作的 HTTP 结局:退得回队列 → 2xx,退不回 → const now = new Date().toISOString(); const ins = wdb.getDb().prepare(` INSERT OR IGNORE INTO system_agents - (id, name, scope, owner_company_id, source_kind, legacy_path, status, created_at, updated_at) - VALUES (?, ?, 'public', NULL, 'global_agent', ?, 'active', ?, ?) + (id, name, scope, owner_company_id, legacy_path, status, created_at, updated_at) + VALUES (?, ?, 'public', NULL, ?, 'active', ?, ?) `); for (const id of new Set(ids)) ins.run(id, id, `system/agents/${id}`, now, now); } diff --git a/server/tests/unit/prompt-write-boundary.test.js b/server/tests/unit/prompt-write-boundary.test.js index 8c468f1..9f585bc 100644 --- a/server/tests/unit/prompt-write-boundary.test.js +++ b/server/tests/unit/prompt-write-boundary.test.js @@ -93,8 +93,8 @@ describe('提示词的交付物归宿:graph 节点 vs agent-chat 两条链路' const ts = new Date().toISOString(); db.prepare(` INSERT OR IGNORE INTO system_agents - (id, name, scope, owner_company_id, source_kind, legacy_path, status, created_at, updated_at) - VALUES (?, ?, 'public', NULL, 'global_agent', ?, 'active', ?, ?) + (id, name, scope, owner_company_id, legacy_path, status, created_at, updated_at) + VALUES (?, ?, 'public', NULL, ?, 'active', ?, ?) `).run(AGENT_ID, AGENT_ID, `system/agents/${AGENT_ID}`, ts, ts); const g = wdb.createWorkGraph({ diff --git a/server/tests/unit/source-kind-drop-migration.test.js b/server/tests/unit/source-kind-drop-migration.test.js new file mode 100644 index 0000000..966db57 --- /dev/null +++ b/server/tests/unit/source-kind-drop-migration.test.js @@ -0,0 +1,95 @@ +/** + * 28-followups §3.1 —— `system_agents.source_kind` 删列的**存量库路径**。 + * + * 单独钉这一条的理由与 tasks-executor-migration 一样:新建库那条路由单测天然覆盖 + * (建表块里已经没有这一列),「存量库 + 重启」这条必须另外安排人走一遍。 + * + * ── 要守的三件事 ────────────────────────────────────────────────────── + * ① **顺序**:先把 source_kind 的信息回填进 origin,再删列。反过来做, + * 「origin 列出现之前写下的行」永远回不到正确的 origin —— 那正是那条回填存在的理由。 + * 这一条最容易在重构里被弄反,而且弄反之后**不报错**:库能开、测试能过, + * 只有那几行 origin 悄悄是错的。 + * ② **幂等**:删过之后第二次开库不能抛(`no such column: source_kind`)。 + * ③ **位置**:删列必须跑在任何写入之前。migrate() 由 getDb() 在建表之后立刻调用, + * 正是那个位置;挪到别处的话,带着 source_kind 的 INSERT 会先撞 NOT NULL。 + * + * ── 为什么 legacy_path 不一起删 ──────────────────────────────────────── + * 它**还没死**:新行里它恒等于 system/agents/{id}(冗余),但存量行里存着批次 4 之前的 + * 公司侧路径,而 graph-seed-service 判「悬空行 / 孤儿行」靠的就是它指向的目录还在不在。 + * 实查生产库(2026-08-20)76 行里有 2 行仍是老路径。退出条件写在 graph-db.js 的建表注释里。 + */ +import { describe, it, expect } from 'vitest'; +import { createRequire } from 'module'; +import fs from 'fs'; +import os from 'os'; +import path from 'path'; +const require_ = createRequire(import.meta.url); + +/** 用独立 DB 文件跑一遍 getDb()(写法照抄 tasks-executor-migration.test.js) */ +function freshDb(dbPath) { + process.env.GRAPH_DB_PATH = dbPath; + for (const m of ['../../services/graph-db', '../../services/work-graph-db']) { + delete require_.cache[require_.resolve(m)]; + } + const graphDb = require_('../../services/graph-db'); + graphDb.getDb(); + return graphDb; +} + +const cols = (db) => db.pragma('table_info(system_agents)').map((c) => c.name); + +describe('system_agents.source_kind 删列', () => { + it('存量库重启:先回填 origin 再删列,且不炸', () => { + const dbPath = path.join(fs.mkdtempSync(path.join(os.tmpdir(), 'sk-drop-')), 'graph.sqlite'); + const graphDb = freshDb(dbPath); + expect(cols(graphDb.getDb())).not.toContain('source_kind'); // 新建库本来就没有 + graphDb.getDb().close(); + + // 退回删列之前的形态:把列加回来,并造两行「origin 列出现之前」的记录 —— + // 它们的 origin 是加列时的默认值 'builtin',真相只存在 source_kind 里。 + const Database = require_('better-sqlite3'); + const raw = new Database(dbPath); + raw.exec(`ALTER TABLE system_agents ADD COLUMN source_kind TEXT NOT NULL DEFAULT 'global_agent'`); + const now = new Date().toISOString(); + const ins = raw.prepare(`INSERT INTO system_agents + (id, name, scope, owner_company_id, legacy_path, status, created_at, updated_at, origin, source_kind) + VALUES (?, ?, ?, ?, ?, 'active', ?, ?, 'builtin', ?)`); + ins.run('legacy-hired', 'legacy-hired', 'company', 'co-x', 'system/agents/legacy-hired', now, now, 'company_hired'); + ins.run('legacy-global', 'legacy-global', 'public', null, 'system/agents/legacy-global', now, now, 'global_agent'); + expect(raw.pragma('table_info(system_agents)').map((c) => c.name)).toContain('source_kind'); + raw.close(); + + // 重开进程 = 服务重启 + const graphDb2 = freshDb(dbPath); + const db2 = graphDb2.getDb(); + + expect(cols(db2)).not.toContain('source_kind'); // ② 列没了 + // ① 顺序:company_hired 那行的 origin 必须已经被回填成 created。 + // 先删后填的话这里会停在 'builtin' —— 而且不会有任何报错。 + const get = (id) => db2.prepare('SELECT origin FROM system_agents WHERE id = ?').get(id); + expect(get('legacy-hired').origin).toBe('created'); + expect(get('legacy-global').origin).toBe('builtin'); // 对照:不该被一起改写 + db2.close(); + + // ③ 幂等:再重启一次,不抛,结果不变 + const graphDb3 = freshDb(dbPath); + expect(cols(graphDb3.getDb())).not.toContain('source_kind'); + expect(graphDb3.getDb().prepare('SELECT origin FROM system_agents WHERE id = ?').get('legacy-hired').origin).toBe('created'); + + fs.rmSync(path.dirname(dbPath), { recursive: true, force: true }); + delete process.env.GRAPH_DB_PATH; + }); + + it('legacy_path 仍在,且删它的退出条件写在了建表注释里(别顺手一起删)', () => { + const dbPath = path.join(fs.mkdtempSync(path.join(os.tmpdir(), 'sk-drop2-')), 'graph.sqlite'); + const graphDb = freshDb(dbPath); + expect(cols(graphDb.getDb())).toContain('legacy_path'); + graphDb.getDb().close(); + fs.rmSync(path.dirname(dbPath), { recursive: true, force: true }); + delete process.env.GRAPH_DB_PATH; + + // 退出条件必须留在源码里可被读到 —— 否则下一个人只会看到一列「没人读的冗余字段」 + const src = fs.readFileSync(path.join(__dirname, '../../services/graph-db.js'), 'utf-8'); + expect(src).toContain("legacy_path <> 'system/agents/' || id"); + }); +}); diff --git a/server/tests/unit/task-blocked-reason.test.js b/server/tests/unit/task-blocked-reason.test.js index d48c582..34b812c 100644 --- a/server/tests/unit/task-blocked-reason.test.js +++ b/server/tests/unit/task-blocked-reason.test.js @@ -74,8 +74,8 @@ describe('blocked task 的原因回写', () => { db.prepare(` INSERT OR IGNORE INTO system_agents - (id, name, scope, owner_company_id, source_kind, legacy_path, status, created_at, updated_at) - VALUES ('sdd-planning', 'sdd-planning', 'public', NULL, 'global_agent', 'system/agents/sdd-planning', 'active', ?, ?) + (id, name, scope, owner_company_id, legacy_path, status, created_at, updated_at) + VALUES ('sdd-planning', 'sdd-planning', 'public', NULL, 'system/agents/sdd-planning', 'active', ?, ?) `).run(now, now); const graph = wdb.createWorkGraph({ companyId, nodes: ['sdd-planning'], edges: [] }); diff --git a/server/tests/unit/task-queue-acceptance.test.js b/server/tests/unit/task-queue-acceptance.test.js index 2582fbf..150f1c2 100644 --- a/server/tests/unit/task-queue-acceptance.test.js +++ b/server/tests/unit/task-queue-acceptance.test.js @@ -130,8 +130,8 @@ describe('批 3 排队 · §八 验收', () => { const now = new Date().toISOString(); const ins = wdb.getDb().prepare(` INSERT OR IGNORE INTO system_agents - (id, name, scope, owner_company_id, source_kind, legacy_path, status, created_at, updated_at) - VALUES (?, ?, 'public', NULL, 'global_agent', ?, 'active', ?, ?) + (id, name, scope, owner_company_id, legacy_path, status, created_at, updated_at) + VALUES (?, ?, 'public', NULL, ?, 'active', ?, ?) `); for (const id of new Set(ids)) ins.run(id, id, `system/agents/${id}`, now, now); } diff --git a/server/tests/unit/task-scheduler.test.js b/server/tests/unit/task-scheduler.test.js index 9c63dbf..e57981d 100644 --- a/server/tests/unit/task-scheduler.test.js +++ b/server/tests/unit/task-scheduler.test.js @@ -112,8 +112,8 @@ describe('task-scheduler', () => { const now = new Date().toISOString(); const ins = wdb.getDb().prepare(` INSERT OR IGNORE INTO system_agents - (id, name, scope, owner_company_id, source_kind, legacy_path, status, created_at, updated_at) - VALUES (?, ?, 'public', NULL, 'global_agent', ?, 'active', ?, ?) + (id, name, scope, owner_company_id, legacy_path, status, created_at, updated_at) + VALUES (?, ?, 'public', NULL, ?, 'active', ?, ?) `); for (const id of new Set(ids)) ins.run(id, id, `system/agents/${id}`, now, now); } diff --git a/server/tests/unit/task-slots-parallel.test.js b/server/tests/unit/task-slots-parallel.test.js index 046d22f..8708468 100644 --- a/server/tests/unit/task-slots-parallel.test.js +++ b/server/tests/unit/task-slots-parallel.test.js @@ -71,8 +71,8 @@ try { const db = wdb.getDb(); const now = new Date().toISOString(); db.prepare( - "INSERT OR IGNORE INTO system_agents (id,name,scope,owner_company_id,source_kind,legacy_path,status,created_at,updated_at)" - + " VALUES ('solo','solo','public',NULL,'global_agent','system/agents/solo','active',?,?)" + "INSERT OR IGNORE INTO system_agents (id,name,scope,owner_company_id,legacy_path,status,created_at,updated_at)" + + " VALUES ('solo','solo','public',NULL,'system/agents/solo','active',?,?)" ).run(now, now); const g = wdb.createWorkGraph({ companyId: cid, nodes: ['solo'], edges: [] }); if ((g.errors || []).length) throw new Error(JSON.stringify(g.errors)); @@ -186,8 +186,8 @@ describe('批 4 · 公司级执行位(max_parallel_tasks)', () => { const now = new Date().toISOString(); const ins = wdb.getDb().prepare(` INSERT OR IGNORE INTO system_agents - (id, name, scope, owner_company_id, source_kind, legacy_path, status, created_at, updated_at) - VALUES (?, ?, 'public', NULL, 'global_agent', ?, 'active', ?, ?) + (id, name, scope, owner_company_id, legacy_path, status, created_at, updated_at) + VALUES (?, ?, 'public', NULL, ?, 'active', ?, ?) `); for (const id of new Set(ids)) ins.run(id, id, `system/agents/${id}`, now, now); } -- libgit2 0.21.4