- 07 Jul, 2026 3 commits
-
-
上次把按钮从占位 toast 改成了 window.open('http://localhost:4100/appointment','_top'), 硬编码 URL 随部署上了生产 —— 线上点击会 _top 跳到用户本机 localhost 死链。 回退成原占位 toast;删除 action-urls.ts(带写死默认值的配置)。真实接入时 URL 由 宿主 actionUrls / env 下发,代码不写死。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>luoqi committed -
会话前已存在、本次一并提交:cold-import.service / sync-incremental scheduler / clinical-gap potential-treatment-gap SQL / cold-import manifest.schema / dw-lag-monitor / canonical-codes / ingestion·monitoring 文档 / jvs-dw manifest.yaml / package.json / pnpm-lock / .gitignore(next-env.d.ts)/ docs/friday-saas-mapping-temp。 非本次会话产出,随此次部署一并落地。
luoqi committed -
- 嵌入检测(window.top):隐藏 PAC 品牌/用户/登出(IdentityCluster),会话失效显示 「登录已过期+重试」而非 dev 快速登录(SessionExpired);模拟登录嵌入态不出现 - 动作插槽(新建预约等):PAC 直接 window.open 宿主链接,不 iframe 嵌、不 postMessage、 无 returnUrl;患者上下文经 query 传,回跳靠 history.back()/宿主自己的召回页 - org-tree 派生:启动预热(OnApplicationBootstrap)+ loose index-scan 改写, 冷派生 ~3.8s→~1ms,消除首个 scoped 用户登录尾延迟 - docs: overview §4/§5 补 actionUrls 打开方式/回跳、sandbox 权限、嵌入隐藏、会话失效 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
luoqi committed
-
- 03 Jul, 2026 1 commit
-
-
把 push(形态 A/C)打磨成宿主唯一接入通道,存量=按批重放的增量: 摄入一致性(消除同源分叉): - 形态 C 复用 normalizeCanonical(金额元→分 / 无时区时间补 offset),含 envelope occurredAt/updatedAt —— 与形态 A/pull 同一转,宿主用自己系统的格式发,不做转换。 - 业务事件先于患者主档到达 → dispatcher stub 自建空壳患者(对齐 form A),主档后到补全 同一行,不双人;push 跨批顺序无关。 幂等/乱序: - patient_facts 加 source_updated_at,fact-writer 按它裁决:更旧版本晚到 → stale_skipped 跳过,不旧覆新。关掉"补推快照 vs 实时推送并发交错"的乱序窗口。 体制/性能: - 限批 500(A 从 2000 对齐 C);persona 入队去抖(5min 桶去重,存量批次合并重算)。 - 同 host 并发闸(信号量,PAC_PUSH_CONCURRENCY 默认 4;幂等+裁决保证并发语义=串行)。 - 单飞锁索引收窄 WHERE direction='pull' —— 原索引把并发 push 批次拦成 P2002/500。 异常/可观测: - 整批全失败 → 拒收 30802 + SyncLog FAILED;失败率/疑似改表 → 告警。 - 未知 source → 10001(非 90000,避免宿主对永久错误无限重试)。 - push 断流监控(超 26h 无 push 告警);form A 响应透出 mappingMisses/suspectFields 供自检。 文档 channel-push 按宿主视角重写(subjectType 单一口径、金额时间统一"原样发 PAC 归一")。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
luoqi committed
-
- 02 Jul, 2026 2 commits
-
-
请求参数按 role 表那样逐字段列出(字段/类型/必填/说明),对接方更易懂。 dictionary 标注为选填(schema 是 .optional(),不传换票仍成功,仅显示原始 id)。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
luoqi committed -
执行结果回调(opt-in,宿主配 callbackUrl 才投递): - Host 加 callbackUrl + callbackSecrets(存原文,PAC 签名用);迁移加两列。 - ExecutionService.submit 提交后入 execution-callback 队列(6 次退避重试,死信可见)。 - ExecutionCallbackService:查 execution→plan→patient 拼 payload,HMAC-SHA256("{ts}.{body}") 签名(与 push 入站同配方),fetch+8s 超时,非 2xx/超时抛错交 BullMQ 重试;jobId=executionId 幂等。 - 凭据页新增「执行结果回执」区:callbackUrl 输入 + Callback Secrets(add/rotate/grace/删), suffix 用 sha256 指纹不回显原文。对接文档同步。 顺手修 push 密钥存储 bug: - create/rotate 原本存 scrypt 哈希,但 HmacVerifier 把它当原文做 HMAC key → 宿主拿明文、 PAC 拿哈希,两边 key 不一致,push 验签永远过不了。改为存原文(对齐 seed 与 verifier 预期)。 - 本地实测:轮换后用返回的明文签名 → 验签通过;错误签名 → 10106 拒绝。 修凭据页「刷新即整页重载」: - useHostAdmin.refresh 一进来就切 loading,父组件把整页换成占位、卸载 HostAdminApp, 连带把"仅显示一次"的明文 secret 弹窗刷没(来不及复制)。改 stale-while-revalidate: 已有数据不切 loading,旧数据留屏后台重取。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>luoqi committed
-
- 01 Jul, 2026 6 commits
-
-
code/accessToken/refreshToken 有效期不写清楚,接入方不知道过期后该调哪个接口; 另外"用 code 拼 URL"具体怎么拼、嵌哪条路径此前只字未提,补链接到 §5。
luoqi committed -
getStats() 并发跑 10 条查询,其中两条缺覆盖索引: - patient_transactions.count(hostId, occurredAt>=X):(patient_id, occurred_at) 索引带不动"只按时间不带 patient_id"的过滤,近乎全扫索引。线上冷执行 8.3s。 - patient_facts.count(hostId, status='active'):(hostId, tenantId) 没 status、 (patientId, status) 没 hostId,两个都覆盖不了。线上冷执行 18.9s。 三列(非二列):tenant-guard 扩展对未带 tenantId 的查询自动注入 tenantId 过滤, 二列索引配上这条自动追加的谓词后 planner 直接弃用索引退回全表扫,三列才稳定命中 index-only scan。 两条索引都用 CREATE INDEX CONCURRENTLY(表已 379万/371万行,避免锁表阻塞摄入写入)。 线上手动建索引 + 验证已完成(index-only scan,查询从 8~19s 降到 <1s;端到端 GET /admin/host/self 28s → 0.9s);IF NOT EXISTS 保证下次 `prisma migrate deploy` 在 pac-migrate 里重跑时是空操作,只补 `_prisma_migrations` 记录。
luoqi committed -
tenant-group 重构后每请求都派生 org 树(全扫 patient_transactions 数十万行)解析用户 scope, 冷缓存时登录长时间卡(首次慢、之后正常)。修: - 空 orgScope(全集团用户,最常见)直接短路返回不限,不派生树(400ms→50ms) - 非空 scope 仍需树:getTree 加单飞(并发 miss 共享一次,防惊群)+ TTL 10min→6h(org 数据日更) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
luoqi committed -
fix(docs): auth-login 链接指向真实 endpoint 页 —— /docs/api/auth 是 tag 目录无 index(404),改 AuthController_exchangeToken Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
luoqi committed -
committed pac.json server 改回 localhost:3101(本地开箱即对); pac-docs Dockerfile build 期用 DOCS_API_URL 覆盖 servers;compose.prod 传生产域名。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
luoqi committed
-
- 30 Jun, 2026 4 commits
-
-
build-document 加 .addServer(env);重新生成 pac.json,servers=https://pac.jarvismedical.asia(paths 已含 /pac/v1)。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
luoqi committed -
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
luoqi committed -
资源引用走 /docs/_next/*,避免与 pac-web 的 /_next 撞;仅生产生效。 nginx 侧加 /docs/、/docs/_next/(rewrite)、/api/search、/og/docs/、/llms 几条 location 兜到 3102。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
luoqi committed -
文档(apps/pac-docs,首次纳入仓库): - 接入/运维/参考全面重写,脱敏、对齐现有代码 - 运维拆为 部署/数据摄入/监控/故障排查;新增「设计系统·DESIGN.md」(三层 token + oklch) - 过时文档归档 → docs/_archive;ADR → docs/adr 代码: - perf(prisma): PrismaService 连接池按 PAC_COHORT_CONCURRENCY 自动推导 connection_limit(免手调) - chore(prisma): schema 注释更正(payloadHash 不参与 source_event_id 合成) - chore: docker-compose 增 pac-docs 服务 - 含 feat/tenant-group-p0 分支在途 WIP(pac-service / packages/types) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
luoqi committed
-
- 28 Jun, 2026 2 commits
-
-
并发重算(--concurrency>1)时发现:缓存空/过期瞬间,N 个 recompute 同时调 getMonetaryQuantiles → 同时发 N 份全租户聚合 $queryRaw(370万 facts 实测单跑 4.1s) → 打爆 4 核 DB + 连接池 → 进程卡死(stat=S,0 吞吐)。串行版无事因缓存命中只跑 1 次; 且缓存 30min 过期,长批(~1.6h)半路会再次惊群。 修:single-flight —— 缓存 miss 时把在飞 Promise 存 map,并发调用复用同一个查询, 只跑 1 份。等待者 await JS promise(不占 DB 连接)。根治,也利好增量 cohort 路径。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
luoqi committed -
实测推翻旧注记"persona 重算 CPU-bound、异步并发不提速":每患者 recompute 含 ~15 次 串行 DB 往返(2 次全量 fact 读 + 多次 findFirst),实为 **I/O-bound**。单进程分块 Promise.all 并发可重叠这些 DB 等待 —— 本地 500 患者 17s(串行)→ 4s(并发16),~4.5x, concurrency 8-16 后 plateau(DB 成瓶颈)。正确性验证:500/500 各 1 active persona、 0 重复版本、0 空特征(不同患者独立写 + 同患者不并发,无竞争)。 - --concurrency=N:分块并发(默认 1 向后兼容) - --shard=i/k:多进程分片(按患者序号取模,吃多核;DB 先饱和时用) - 大批(>500)只打进度行不逐条刷屏;orderBy id 稳定序供分片 全量重算 131689 患者:7h(串行)→ ~1.6h(并发12)。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
luoqi committed
-
- 27 Jun, 2026 22 commits
-
-
后端: - plan.recomputeByPatient:patient 守卫补 source_unit(与其它 by-id 守卫对齐;防跨品牌触发重算 + 存在性探测) - persona.getByVersion:删(无 scope 的 findUnique by-id,死代码,属之前修过的泄漏模式) - cold-import:改"sourceUnit:''(过渡)"陷阱注释为真实语义(从 manifest 解析,勿退回 '') - auth.allClinics:去"恰好两品牌"硬编码,改 Object.values(MOCK_PRESETS) 遍历;refresh/mcp 陈旧注释(clinicIds→orgScope) 前端: - auth-store:setTokens 同 sub 沿用上次 clinicIds 占位,消除 silent refresh 时诊所筛选闪空(M2 竞态) - visibleClinics(user) helper:clinicIds 空(集团级=不限)→ 回退字典全部诊所,修集团登录后诊所筛选空白(M1); 4 处(plans-list/patient-picker 筛选 + plans/plan-detail 计数)统一用它 两端 tsc 0 错。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
luoqi committed -
agent 审查(3 个独立都挖到)的真·安全洞:expandOrgScope 对树里找不到的 ref 兜底成 "当裸 clinicId、sourceUnits 留空"。而瑞尔/瑞泰共用 tenantId,sourceUnits=[] 被下游当 "不限品牌" → 跨品牌看全部患者(含真号 reveal)。触发:退化树(无数据)、新诊所未进树、 10min 缓存陈旧。 - org-scope:探测 branded(树有非空 source_unit);品牌制租户 + 非空 scope 却没解析出 任何品牌 → 塞 DENY_SOURCE_UNIT 哨兵(患者查询返回空),不退化成不限。命中集团根仍合法不限。 单命名空间/退化树(无品牌)→ 维持 [] 不限(正确)。回传 unresolved 供刷新重试。 - org-tree:展开有 unresolved → 强制重派生树一次再展开(避免误拒刚摄入的新诊所合法用户); forceRefresh 30s 防抖,挡 bogus ref 刷库。 单测:品牌ref/诊所ref正常、集团根→不限、未知ref→DENY、混合(含已解析品牌)→限该品牌不DENY、 单命名空间未知ref→不限。活体三级 + REST 无回归。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
luoqi committed -
之前 org-tree 复用 jvs-dw-preset 的硬编码 clinic UUID 建树 —— 把通用隔离路径绑死到 mock 演示数据 + 写死 id(换环境/换 DW 即废)。改为从摄入数据派生: - OrgTreeService(注入 Prisma):$queryRaw 从 patient_transactions.clinic_id JOIN patients.source_unit 派生「品牌→诊所」,按 (hostId,tenantId) 缓存(10min TTL); 无数据→退化树。对任何 host/环境自适应,零硬编码 id。 - AuthModule 导出 OrgTreeService;tenant-scope 拦截器(from+mergeMap 异步展开,ALS 不变) 与 mcp-auth 注入它,每请求 expandScope。 - jvs-dw-preset 退回**仅 mockLogin 用**(dev,env 门控):集团/品牌 slug 是稳定逻辑名; clinic UUID/中文名仅演示展示,无数据源,换环境降级成 id —— 隔离仍由 DB 派生树保证正确。 验证(DB 派生树):诊所级石琳 sourceUnits=['瑞尔']/2诊所、品牌瑞尔/3、瑞泰/2、集团不限、 REST /plans 仅命中本诊所 —— 与原硬编码结果一致。tsc 0 错。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
luoqi committed -
JWT 是 host 也能解码的凭证,原先装展开后的 sourceUnits/clinicIds(PAC 内部 schema 词汇), 与"host 只发 org_scope"的契约不一致、泄漏内部表示。改为 JWT 只装 orgScope(与契约同词汇), 过滤维留到每请求展开: - AccessTokenPayload / RefreshPayload:sourceUnits/clinicIds → orgScope;issueTokens 不再展开,直接签 - org-tree.ts(纯函数 expandScope:按 tenantId 选 host org 树展开)+ jvs-dw-preset.ts(树数据,与 mock 共用) - tenant-scope.interceptor(REST)/ mcp-auth(MCP)每请求 expandScope → scope.{sourceUnits,clinicIds} - 前端展不开 orgScope → 新增 GET /auth/session 返回展开后的 clinicIds/sourceUnits + role/permissions/dictionary; auth-store 加 loadSession(登录/F5 后异步补进 user),组件读 user.clinicIds 不变 - weixin-aibot 自签 token 同步改 orgScope;契约文档 §2/§3 同步 验证:JWT 解码只含 orgScope;/auth/session 展开正确;REST /plans 诊所过滤生效(石琳仅本诊所); 集团级 orgScope=[grp]→clinicIds=[]/sourceUnits=[] 不限。两端 tsc 0 错。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>luoqi committed -
原文案把数据权限说得像跟着角色("看自己的/看本团队所有任务"其实是 view_own/view_all 功能权限,却像在描述数据范围)。点透两轴(正交): - 顶部加一句:① 角色=能做什么(功能);② 选谁/哪级=能看哪些诊所品牌(数据权限,跟角色无关) - ① 标题 "登录权限(角色)" → "角色 · 能做什么(功能权限)";角色 desc 改成纯功能口径 - 集团按钮 "集团·X 身份" → "数据范围 · 集团级 …… 角色沿用上方「X」"(数据范围与角色解耦) - ② "选客服" → "数据范围 · 选客服 = 该客服的诊所(诊所级)+ 上方角色" Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>luoqi committed -
第一性:换了 org-scope 数据权限后,clinicIds/sourceUnits 是 expandOrgScope 的**展开产物** (PAC 内部过滤维),不该出现在对外契约入参里 —— host 只该传"用户可达的 org 节点"。 - TokenExchangeUser:删 clinicIds + sourceUnits,orgScope 转必填(任意层级:集团/区域/品牌/诊所) 诊所级即传诊所 id;没品牌概念的 host 同理。clinicIds/sourceUnits 仅留在内部 JWT AccessTokenPayload。 - issueTokens:永远走 orgScope 展开;非 jvs-dw host 用退化树(根=tenantId,无子)→ orgScope 里诊所 id 当裸诊所透传、sourceUnits 留空、根 id=不限。删显式 fallback 分支。 - auth-login-contract.md:org_scope 从"P2 可选增强"翻正为唯一契约入口;派生表/示例/实现现状同步 - 活体三级复验:集团 []/0(不限);品牌 ["瑞尔"]/3;诊所 ["瑞尔"]/2 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
luoqi committed -
第一性:模拟登录 = 模拟 host 调换票,本应产出契约同形 user 上下文(核心 orgScope) 并走与 exchangeToken **同一条**展开+签发。之前 mockLogin 把 expand+sign 抄了一遍,已飘: - 抽出 private issueTokens(host, user):orgScope→expandOrgScope→sourceUnits+clinicIds→签 access/refresh - exchangeToken 与 mockLogin 都委托它;mockLogin 只负责"preset → 契约 user(算 orgScope)" - 删 exchangeToken 的 clinicIds 空→['default'] 兜底:空=不限(下游 plan 过滤 length>0 才加), 兜底会把集团级误锁成不存在的诊所 → 啥都看不到(mockLogin 本就没这兜底,统一后契约路径一并修) 活体验证三级:集团 sourceUnits=[]/clinics=0(不限);品牌 ["瑞尔"]/3;诊所 ["瑞尔"]/2;瑞泰 ["瑞泰"]/2 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
luoqi committed -
之前 OrgTree 是写死的 3 层形状 { groupId, brands:{clinics[]} },塞不进区域层。 改成递归 OrgNode(children 无限嵌套):集团→多级区域→品牌→诊所→…均可。 expand 逻辑不变 —— 过滤永远只落 sourceUnits/clinicIds 两维,中间层只是分组, 展开任意节点 = 走子树收集这两类叶子属性。 - OrgNode 递归类型 + buildTree(通用) ;buildOrgTree(3 层糖)签名/tree.groupId 不变 - sourceUnit 命名空间**向下继承**:scope 到单诊所时带上祖先品牌, 否则 sourceUnits 退化成空(不限品牌)= 越权看全品牌患者(测试抓出,已修) - 扁平索引按 id **聚合**(数组),同一品牌跨多个区域时全收,不被 Map 覆盖丢诊所 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>luoqi committed -
- org-scope:删 JVS_DW_ORG_TREE hardcode,改 buildOrgTree(从配置/数据派生); 实现对所有 host 通用,树数据 3 层本就在数据里(yaml 只为区域等数据外的层) - auth:jvsDwOrgTree() 从 MOCK_PRESETS 派生;exchangeToken + mockLogin 都走 expandOrgScope - mockLogin:数据范围统一 org-scope —— 诊所级(选客服)/品牌级(仅品牌)/集团级(orgLevel=group, sourceUnits=[] 看全部品牌全部诊所);MockLoginRequest 加 orgLevel - 前端:加"以集团身份登录"按钮(跨品牌,验证数据权限最上一层) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
luoqi committed -
数据权限 = 用户的 org 范围(任意层级:集团/品牌/诊所)。expandOrgScope() 把 org 节点展开成 sourceUnits + clinicIds(已有过滤层不变): 诊所 ref → 该诊所 + 所属品牌;品牌 ref → 该品牌 + 其下全部诊所; 集团 ref / 空 → 不限。多 ref 取并集。 - org-scope.ts:jvs-dw 内存树(ruier-grp→瑞尔/瑞泰→诊所)+ expandOrgScope - 契约 exchangeToken 接 orgScope(优先于显式 sourceUnits/clinicIds)→ 展开 - TokenExchangeUserSchema 加 orgScope 验证:诊所/品牌/集团/跨品牌四级展开均正确。通用态树放 manifest yaml。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
luoqi committed -
① source_event_id 品牌盲 → 数据丢失(HIGH):synthesizer 的幂等键 ${ctx}:${resource}:${subjectId}:${updatedAt} 不含品牌,两品牌同 subjectId+ updatedAt 撞键被去重跳过 → 丢 transaction/fact。并进 source_unit(re-import 仍幂等)。 ② persona by-id 跨品牌泄漏(HIGH):/patients/:id/persona 的 getCurrent 无 scope 过滤 (Persona 非 source_unit 模型,纵深防御只注 tenantId 不注品牌)。加经 patient 关系的 sourceUnit 过滤;controller + 2 处 MCP 调用传 scope。 ③ MCP by-id 泄漏(HIGH):MCP @Public 无 ALS 上下文,纵深防御不兜; assertPatientInScope / activeRecallPlan 只按 host+tenant,补 sourceUnit 过滤。 ④ exchangeToken 真 host SSO 丢 sourceUnits(latent):TokenExchangeUserSchema 加 source_units + exchangeToken 透传(否则真 host 登录 = 不限品牌)。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>luoqi committed -
host→PAC 登录输入规范:host_id + group_id + user{id,name,role} + source_units(品牌数据范围,空=不限)+ clinic_ids;PAC 派生 permissions/ dictionary,signAccessToken 签发新契约 token。mock 与真 host 共用 signAccessToken + 同一 token 形状,差别仅输入来源。含隔离保证映射。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>luoqi committed -
- tenant-context.ts:请求级 AsyncLocalStorage(tenantId/sourceUnits) - tenant-guard.extension.ts:$extends 对 14 张 scoped 表的列表/聚合读查询自动注入 tenantId(+ source_unit 模型注品牌);findUnique 不注(破坏唯一键,by-id 走显式守卫); 无 ALS 上下文(系统任务)不注;$queryRaw 不经扩展(召回引擎自带隔离闸)。env PAC_TENANT_GUARD=warn 可切观察模式 - prisma.service:构造器 return this.$extends(...) 透明接入(services 不改) - tenant-scope.interceptor:handler 在 tenantAls.run 内执行(subscribe 包裹) 验证(已删一次性 CLI):注错 tenant→0、限品牌→该品牌数、系统任务不注、findUnique 不破。 关键:Prisma 惰性查询,await 须在 als.run 内 — interceptor 的 handler 正满足。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
luoqi committed -
ruier/ruitai 注明为**品牌(source_unit)**,同属集团租户 ruier-grp; 类型 TenantSlug→BrandSlug。功能不变(slug 经后端 MOCK_PRESETS 映射为 tenantId=ruier-grp + sourceUnit)。正式 SSO 对外契约(host_id+group_id+ org_scope)待 host 接入时实现。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
luoqi committed -
- pipeline-dispatcher:加 identityNamespaceField,patient upsert/index/patientIdResolver 全按 (source_unit,external_id) 复合键(pull/push/reconcile 多命名空间 host 也正确; 单命名空间默认 '' 不变) - reconcile 守卫:除 distinct tenant 外也查 distinct source_unit —— 合集团后 jvs-dw 只剩 1 tenant 但 2 source_unit,旧守卫拦不住会撞号;现多命名空间一律转 cold-import Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
luoqi committed -
患者详情(reveal/timeline)、plan 详情/分配/回收、plan-aggregate 详情: by-id 取数也按 patient.sourceUnit 圈品牌(findUnique→findFirst + patient 过滤, 命中不到→现有 NotFound 守卫接住)。sourceUnits 空=不限(集团级角色)。 至此 §4.4 数据范围在患者粒度的品牌隔离全覆盖(搜索/列表/by-id)。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
luoqi committed -
- token/scope 加 sourceUnits(用户所属品牌;空=不限,集团级角色) - 数据范围在患者粒度按品牌圈:患者搜索 + plan 列表(patient.sourceUnit,覆盖召回池) - auth mock 预设 tenantId→ruier-grp + sourceUnit;weixin fallback→ruier-grp - mcp/timeline/weixin 手动 scope 补 sourceUnits 至此 P0 代码完整:3 表 source_unit + 摄入 + 集团 slug + 品牌隔离查询。 本地重摄已验证:external_id 261067 → 王辰(瑞尔)/王文彦(瑞泰)两条独立行, facts source_unit 全填,tenant=ruier-grp。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
luoqi committed -
- schema+migration:patient_facts (host,tenant,source_unit,subject_id,version)、 patient_return_visits (host,tenant,source_unit,external_id) - fact-writer:writeDraft/bulkWrite 的版本链 findFirst + 史表查 + liveLatest map 全按 (source_unit, subject_id) 复合键隔离;create 带 source_unit (集团内 subject_id 品牌级撞 631,不隔离会取错品牌版本=改坏 fact 历史) - parser-pipeline:按 patientId 查患者 source_unit(批内一次取),注入 PrismaService - cold-import:return_visit upsert 带 source_unit - 删旧原地迁移脚本 — 改用"清空重摄"(本地/服务器均可),无原地改 fact 版本链风险 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
luoqi committed -
实施期 pre-check:patient_facts.subject_id 跨品牌撞 631、patient_return_visits .external_id 撞 611,均品牌级,合 tenant 会撞键。故 source_unit 必须加在 patients + patient_facts + patient_return_visits 三张表;fact-writer 版本链 findFirst 也须按 source_unit 过滤。transactions(撞0)、relations(UUID)免。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
luoqi committed -
- manifest.schema: 加 identity_namespace_field - tenant-resolver: 加 buildSourceUnitResolver(从 identity_namespace_field 解析) - cold-import: source_unit 从 rawSource 解析,串入 patient upsert / stub / patientIndex(按 (source_unit,external_id) 复合键防撞号)/ patientIdResolver - manifest.yaml: 删写死 tenant_map UUID,改 tenant_id: ruier-grp(集团 slug)+ identity_namespace_field: brand - 加数据迁移脚本(未执行)
⚠ ️ 前置审发现 patient_facts.subject_id(撞631)、patient_return_visits.external_id (撞611)也是品牌级,合 tenant 会撞键 — 这俩也需 source_unit。迁移待补这两表后再跑。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>luoqi committed -
- patients.source_unit(通用命名空间,NOT NULL 默认 ''):各宿主"在哪层发患者号" 不同,jvs-dw=品牌;'' 非 null 防唯一索引 NULL-distinct 失效 - 唯一键 (host,tenant,external_id) → (host,tenant,source_unit,external_id); 存量 source_unit='' 常量,新键与旧键唯一性等价,迁移安全(95k 患者完整) - 3 处患者 upsert + 2 CLI 改新键(过渡用 sourceUnit:'';stub 与真主档同 '' 不双行) - patientReturnVisit 的 (host,tenant,external_id) 键未动(只改 patients) 下一步:source_unit 真填值(assembler sourceUnit:brand)+ tenant→集团 slug remap。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
luoqi committed -
澄清:无独立隔离策略,一切由数据范围控制;现状隔离=代码 where(tenant_id), 非 RLS/非 schema。brand 从 tenant_id 槽搬到 source_unit 槽后,品牌隔离的 where 判别列随之 tenant_id→source_unit。数据范围两粒度:plan 按诊所、 患者按品牌(source_unit),同源于 org 位置。默认本品牌(复刻现状),P1 可配置。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
luoqi committed
-