-
company-service.js:company.json 加 ownerId(无 SQL 表,纯 JSON 字段);create() 接收 ownerId;list({ user }) 按传入用户过滤,不传就不过滤(company-heartbeat-service.js 等 系统内部巡检要看全部公司);新增 canAccessCompany(company, user) 作为全仓库唯一一份 归属判据(admin 不过滤,判负一律 404 不是 403,不暴露"这个 id 存在只是不是你的")。 company.js 用 router.param('companyId', ...) 统一收口(约 50 条路由,含 okrPath() 系列 动态路由)。graph-run.js 的两个挂载点参数形状不一样,设计阶段的描述不准确,实现时才 发现:companyRouter 唯一路由是 /:companyId/graph-runs 直接查;主 router 全部路由是 /:id/...,:id 指的是 run 不是公司,改成先查 run 的 company_id 再判断,run 不存在或 公司不是你的统一回"run 不存在"。devloop.js 用 :pid 反查 company_id;POST /projects 单独校验 body 里的 company_id(放在基本字段校验之后,避免归属检查打乱输入校验的错误 优先级);GET /projects 按归属过滤列表。 okr.js 最终没有按原计划接入 companyId 归属校验——实现前查证发现 OkrDashboard.vue 挂载的 /okr 页面全站没有任何导航入口指向它,是个孤儿页面,给访问不到的页面建"选公司" 前端交互不划算,改成 requireAdmin(跟 dream.js 同款),先堵住直接拼 URL 看到跨公司 聚合数据的口子。真正在用的公司级 OKR(company.js 的 /:id/okr)已随 company.js 一起隔离。 存量数据迁移:scripts/backfill-company-owner.js(npm run backfill:company-owner), 两家存量公司已划给种子账号,幂等验证过。 8 个既有测试文件用的公司 id 都是从没真正建过的字符串标签,挂锁后集体 404——新增共享 helper server/tests/helpers/ensure-test-company.js 修复;两处原有的"跳过已有 company.json 的目录,那是真公司"防御逻辑,判据从"有没有 company.json"改成"ownerId 是不是 -1 这个测试专用标记",否则新出现的测试夹具会被误判成真公司永远不清理。 验证:真实 curl 走过(admin 看到全部存量公司、新用户看到空列表、新用户拼 URL 访问 别人的公司 404、新建公司 ownerId 精确落在创建者、admin 和创建者都看得到其他人看不到); 全量 vitest 1427 passed,3 个无关既有失败不受影响。 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
-
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>
-
队列与调度(§五.1): - 下达任务不再因「公司已有 active root run」吃 409,改为建 queued run + queued task 排队;出队走 updateRunStatus 的 CAS + BEGIN IMMEDIATE 准入事务,触发挂在 transitionRun 这个唯一漏斗上,60s sweep 兜底。 - 依赖已不可能满足的排队任务就地推导成 blocked,且每次 tick 重算不落死,上游重跑 成功后能原路解开。 恢复路径(批 3 复核 F1/F2):三条恢复转换的 from 全在活跃集之外,撞坑满时退回队列而不是 永远躺在 blocked;退不回去的裸 run 干净拒绝,不留半截状态。 控制台三处可见性修复 —— 症状全是纯视觉、不报错、git status 干净: 1. 顶栏墙钟恒 `now - started_at`,没有终点。一条昨晚就 blocked 的 run 第二天还在涨到 「16h 58min」,而那是页面上唯一的时间信号 —— 「已经停了等人处置」被显示成了 「还在跑再等等」,这两件事需要的动作相反。终点改取 run.updated_at,并标「已停」。 2. 越界日志只报「留下 N 处改动」,文件清单躺在 timeline.ndjson 里翻不到。判罚就是按 这份清单下的,而人要判断「这真是 agent 写的吗」唯一的依据就是路径(并行的开发会话 改宿主仓库会被算到当时在跑的节点头上,那是误判,看一眼就知道)。改为列出 entries。 3. run 收敛成 blocked 时只把 task 写成 blocked、不写原因,任务列表那一行只剩一个橙色 的「挂起」,必须逐层点进 org 图 → work 图 → 节点详情才知道发生了什么。改为把 「卡在哪个节点 + 什么 fail_reason + 判词摘要」写进 tasks.blocked_reason。 原因绝不带 UPSTREAM_BLOCK_PREFIX —— 带了会被调度器当成依赖推导出的 blocked 自动 推回 queued,而这类 blocked 本来就等着人处置(守卫在 task-blocked-reason.test.js)。 测试:126 文件 / 1201 用例全绿。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>