Friend 项目 INV-DATA-1 数据面连续性不变式:生产家族身份、路由权威矩阵与多层守护机制解析
【免费下载链接】FriendAI that sees your screen, listens to your conversations and tells you what to do项目地址: https://gitcode.com/GitHub_Trending/fr/Friend
导读
本文围绕 Friend(Omi)仓库中已锁定(locked)的 INV-DATA-1 生产家族客户数据面连续性不变式 展开,系统讲解"生产家族(production-family)"产物如何在不同平台、不同发布通道之间共享同一套客户身份与数据面:移动端(Stable / Beta / 内部 / alpha / TestFlight / Play Internal)、macOS 端 Stable 与 Beta,都必须使用生产 Firebase Auth 与 Firestore(based-hardware),保持同一 UID、录音、会话、集成与同步状态。读完本文,你将掌握该不变式的权威矩阵、唯一允许的服务面(serving-plane)拆分、local_prod开发者例外、外部预览的 fail-closed 机制,以及从启动路由校验到 CI 静态守卫、再到生产 Firebase 探针的整套守护测试体系,并能在提交涉及路由相关路径的 PR 时正确引用INV-DATA-1。
一、不变式核心:一个客户身份,一个数据面
Statement(不变式声明):任何生产家族产物都只能保留一个规范的客户身份/数据面(customer identity/data plane)。具体而言:
- 移动端:Stable、Beta、内部构建、alpha、TestFlight、Play Internal 通道发布的包,全部使用生产 Firebase Auth 与 Firestore(项目
based-hardware),并保留相同的 UID、录音(recordings)、会话(conversations)、集成(integrations)与同步状态(sync state)。 - macOS 端:Stable 的身份是
com.omi.computer-macos;Beta 是可独立安装的com.omi.computer-macos.beta狗粮(dogfood)身份。Beta 拥有独立的本地存储是刻意设计的隔离(拥有自己的 UserDefaults 域、TCC 授权、Keychain ACL 与单实例锁,可同时与 Stable 并行运行),但它不是独立的云账户或独立的 Firestore 宇宙。
这一设计意味着:发布通道(release channel)只能控制资格(eligibility)、灰度曝光(rollout exposure)、诊断(diagnostics)与功能可用性(feature availability),绝不允许通过通道切换不同的账户或客户数据宇宙。TestFlight 检测、Android 的 dart define、更新通道偏好、启动环境或打包进产物里的环境文件,都不是把生产家族包重定向到其他环境的授权依据。
二、路由权威矩阵:谁是唯一的"权威"
文档以一张矩阵形式给出了生产家族路由的唯一权威,这也是排查任何"路由漂移"问题的第一参照表:
| Surface | Production-family authority |
|---|---|
| Flutter API | https://api.omi.me/ |
| macOS Stable Python / desktop API | https://api.omi.me//https://desktop-backend-hhibjajaja-uc.a.run.app/ |
| macOS Beta Python / desktop API | https://api.omiapi.com//https://desktop-backend-dt5lrfkkoa-uc.a.run.app/ |
| macOS Beta OAuth API | https://api.omi.me/ |
| macOS production identities | Stable:com.omi.computer-macos; Beta:com.omi.computer-macos.beta |
| macOS Firebase/Firestore config | the shipped production customer project (based-hardware) |
从表中可以提炼出两个关键结论:
- 服务面(serving plane)只允许这一个拆分:Stable 固定在生产 Python 与桌面 API,Beta 固定在开发 Python(
api.omiapi.com)与开发桌面后端(desktop-backend-dt5lrfkkoa)——但 Beta 的OAuth 权威仍然是生产api.omi.me。除此之外,不得再选择任何其他 Firebase 项目、账户宇宙或任意端点。 - 数据面(data plane)不允许任何拆分:无论是 Stable 还是 Beta,Firebase/Firestore 一律指向生产项目
based-hardware。
这一"服务面可拆、数据面不可拆"的不对称设计,是理解整个守护体系的关键:Beta 的隔离是存储与身份维度上的本地隔离 + 服务路由维度上的固定开发端点,但云端的用户数据永远在生产宇宙内。
三、平台实现:移动端 profile 体系与启动路由校验
3.1 四个受支持的 profile
移动端的数据/信任面(trust plane)在 app/lib/env/environment_profile.dart 中以枚举形式显式建模,每个 profile 在构建时被选定:
| Profile | 默认 API Base URL | Firebase 项目 | Auth 回调 scheme | Firebase Auth 模拟器 | 允许生产数据 |
|---|---|---|---|---|---|
local_dev | http://127.0.0.1:8000/ | demo-omi-local | omi-dev | 是 | 否 |
local_prod | http://127.0.0.1:8000/ | based-hardware | omi | 否 | 是 |
mobile_beta | https://api.omiapi.com/ | based-hardware | omi-beta | 否 | 是 |
production | https://api.omi.me/ | based-hardware | omi | 否 | 是 |
其中:
local_dev面向本地模拟器,不允许访问生产数据(allowsProductionData: false);mobile_beta是唯一把生产 Firebase 身份与开发服务面显式配对的正规 profile;local_prod则是文档强调的仅限 debug 的开发者工作流例外:用显式的OMI_APP_PROFILE=local_proddart-define 把生产 Firebase 身份与开发者自选的本地后端端点配对。
profile 的选择入口在 app/lib/env/environment_profile.dart#L56-L69 的AppEnvironmentProfile.forFlavor:读取String.fromEnvironment('OMI_APP_PROFILE'),为空时按 flavor 回退(prod环境 →production,否则 →local_dev),非空时必须命中local_dev/mobile_beta/production之一,否则抛StateError。
3.2 启动路由校验:网络服务启动前的硬闸门
app/lib/startup_routing.dart 提供validateApplicationStartupRouting,在main中任何网络化服务启动前执行(env_test.dart#L254-L261 以静态接线测试钉死了"该校验必须先于ServiceManager.init()"的调用顺序)。其底层 Env.validateStartupRouting 按 profile 分支处理:
local_dev:只接受回环或私网端点,判定逻辑_isLocalDevelopmentApi覆盖localhost、host.docker.internal、::1、10/8、172.16/12、192.168/16,并特意放行 RFC 6598 的100.64.0.0/10(CGNAT,Tailscale 分配的网段)——注释说明这是物理设备访问开发者本机 harness 的唯一路径,同时严格拒绝100.63.x与100.128.x等网段边缘外的地址,防止"放行 100.x"退化成放行任意公网地址;local_prod:在 release 构建中直接抛StateError('Profile local_prod is only available in debug builds.')——这是"任何已发布产物都无法选中它"的实现保证;debug 构建下要求合法的 http(s) 端点;production/mobile_beta:要求归一化后的 API Base URL 与 profile 默认值精确相等(mobile_beta为https://api.omiapi.com/),否则抛错。
配套的 profile 配对约束validateProfilePairing与 Firebase 项目校验validateFirebaseProject(要求实际初始化的项目 ID 必须等于 profile 的firebaseProjectId,即based-hardware或demo-omi-local)进一步收紧了防线。
3.3 OAuth 固定在生产身份面
值得单独强调 Env.authApiBaseUrlForProfile:当 profile 为mobile_beta时,OAuth/认证 API 无条件返回生产https://api.omi.me/。这正是文档矩阵中"Beta OAuth 权威仍是生产"的移动端实现,并由 env_test.dart#L68-L73 用断言钉死——即使服务面指向api.omiapi.com,认证仍留在生产身份面。
四、macOS 实现:身份即路由,环境变量无法越权
4.1 AppBuild:bundle 身份是路由的第一判据
macOS 侧的路由决策不依赖环境变量,而是以 bundle 身份为锚。在 desktop/macos/Desktop/Sources/AppBuild.swift 中:
productionBundleIdentifier = "com.omi.computer-macos",betaProductionBundleIdentifier = "com.omi.computer-macos.beta",二者组成productionFamilyBundleIdentifiers;- Beta 的独立 bundle id 带来独立的 UserDefaults 域、TCC 授权、Keychain ACL 与单实例锁,从而可与 Stable 并行共存(注释明确要求与
DesktopStorageIdentity.betaProductionBundleIdentifier保持同步,并有单元测试断言); - 更新通道(Sparkle)是身份绑定的:
currentUpdateChannel对 Beta 恒为"beta"、对 Stable 恒为"stable"——"Beta 永久是 beta 通道客户端,Stable.app 永远不会消费 beta 通道",防止残留的update_channel设置把用户拖入针对生产 API 的更新; mayRunLegacyStableAppCleanup只允许 Stable 身份执行旧包清理,避免 Beta 或开发包误杀用户正在运行的 Stable 应用;manualDownloadURL对 Beta 强制携带identity=beta参数,保证 Beta 应用永远下载回自己的身份,而不是 Stable 应用。
4.2 DesktopBackendEnvironment:固定端点的 fail-closed 决策链
desktop/macos/Desktop/Sources/DesktopBackendEnvironment.swift 定义了四个固定端点常量,路由决策由shouldUseDevelopmentBackends触发:
- 外部预览(bundle id 前缀
com.omi.preview.):必须通过签名元数据显式选择后端;缺失或畸形元数据时externalPreviewBackend返回nil,路由fail closed 到生产,绝不继承本地开发默认值; - Beta(
com.omi.computer-macos.beta):无条件返回开发服务面(api.omiapi.com/desktop-backend-dt5lrfkkoa-uc.a.run.app),同时shouldUseProductionAuth恒为 true,保证 Auth/Firebase/Firestore 仍在生产; - 其他非生产家族身份:默认开发后端,且只有这类身份才允许环境变量(
OMI_PYTHON_API_URL/OMI_DESKTOP_API_URL/OMI_AUTH_API_URL)覆盖; - 生产家族身份(Stable / Beta):
shouldUseProductionAuth为 true 时pythonBaseURL、authBaseURL、rustBackendURL全部锁定生产常量——启动环境或打包配置不能切换其客户数据面;applyReleaseChannelDefaults也只为缺失的 URL 补齐默认值,不会覆盖既有权威。
注意authBaseURL的注释:桌面 Apple 登录使用共享 Services ID,注册的 web 回调在api.omi.me,因此 Beta 绝不能把 OAuth 继承到开发数据后端主机——这与文档"Beta 必须忽略OMI_AUTH_API_URL"的要求完全一致。
五、外部预览:保留身份 + 签名元数据,畸形即关闭
文档对外部预览(external preview)的约束是:不允许外部预览使用生产家族身份发布。外部预览需要:
- 一个保留的预览身份(
com.omi.preview.前缀,见 AppBuild.swift); - 显式选择其许可数据面的签名元数据(
OMIExternalPreview标记 +OMIExternalPreviewBackend值,取值production或development); - 畸形元数据 fail closed 到生产面。
源码中的三重保障可以印证这一设计:AppBuild.isExternalPreviewBundleIdentifier用"保留前缀 + 非空后缀"界定预览身份(即使打包漏写标记,身份本身仍受限);AppBuild.Configuration.hasValidExternalPreviewConfiguration要求"是预览 ⇒ 必须有标记且后端非空";而DesktopBackendEnvironment.shouldUseDevelopmentBackends中预览分支只对externalPreviewBackend == .development返回 true,其余一律落到生产。
六、MUST NOT:五条不可逾越的红线
原文档的 MUST NOT 清单必须原样继承,这是评审任何涉及路由 PR 的判据:
- 不得通过 build define、CI 变量、运行时偏好、更新通道、进程环境或打包
.env值,把 Stable、移动端或其他生产家族产物路由到开发、staging、beta API 或任意端点——Beta 的两个固定开发服务权威与仅限 debug 的local_prod开发者 profile 是仅有的例外; - 不得把 Beta 的 OAuth、Firebase Auth、Firebase API-key 绑定或 Firestore 路由到开发项目或端点——Beta 必须忽略
OMI_AUTH_API_URL; - 不得把
OMI_BETA_RELEASE_RING、STAGING_API_URL、api-beta.omi.me或等价的 beta/staging 选择器当作生产家族路由机制; - 不得以生产家族身份发布外部预览(预览需保留身份 + 显式选择数据面的签名元数据,畸形元数据 fail closed 到生产面);
- 不得把受保护的权威或 Firebase/Firestore 项目当作发布流水线的附带工作随意更改。
七、刻意迁移例外:迁移不是 beta 灰度
文档强调:客户数据面迁移不是 beta 灰度。一次真正的数据面迁移,必须同时满足:
- 在 PR 中显式引用
INV-DATA-1; - 通过架构评审与产品评审;
- 提供身份/数据连续性证据(identity/data continuity evidence);
- 有回滚计划(rollback plan);
- 在发布前提供产物级断言,锁定新的不可变权威(immutable authority)。
而"Beta 的固定服务面拆分"本身不是客户数据面迁移。独立的开发/测试 app 身份与测试凭据可以使用非生产服务,但不得复用生产家族身份——这条边界把"开发测试"与"生产狗粮"严格区分开来。
八、守护测试体系:从单元测试到生产探针的四层防线
原文档列出的守护测试构成了"启动即校验 → 桌面路由单测 → 外部预览单测 → CI 静态守卫 → 生产探针"的多层防线,逐层对应如下源码:
第一层:移动端启动路由单元测试 app/test/unit/env_test.dart
- 生产启动接受
https://api.omi.me/(TestFlight 与 Android 两分支); mobile_beta接受开发服务面但要求生产身份配对;- 生产启动拒绝
api-beta.omi.me、api.omi.dev、staging.example.test、任意端点(见 L133-L146); local_dev放行全部私网与 CGNAT 范围、拒绝公网端点及 CGNAT 边缘之外(L148-L192);local_prod在 debug 下接受回环/私网/隧道端点,在 release 构建下必抛错,畸形端点必抛错(L194-L231);- 静态接线测试钉死
main中启动路由校验先于ServiceManager.init()(L254-L261),并验证 Firebase 项目校验挂在ensureFirebaseApp()的所有路径上。
第二层:macOS 桌面路由测试
- APIClientRoutingTests.swift:验证 Stable 保持生产路由;Beta 在污染值(contaminated values)存在时仍只解析固定的开发服务端点与生产 Auth;
- ExternalPreviewBuildTests.swift:预览身份必须有签名后端元数据且fail closed。
第三层:CI 静态守卫 .github/scripts/check-mobile-production-routing.py
该脚本以"任何生产家族客户端一旦离开其数据面即失败"为目标,检查:
codemagic.yaml中 7 个生产工作流(ios-internal-auto、android-internal-auto、ios-prod-testflight、android-prod-internal、ios-prod-patch、android-prod-patch、macos-prod-appstore)必须且只能各含恰好一条API_BASE_URL=https://api.omi.me/的不可变赋值;桌面工作流omi-desktop-swift-release必须恰好一条BUNDLE_ID=com.omi.computer-macos、一条OMI_PYTHON_API_URL=https://api.omi.me、一条OMI_DESKTOP_API_URL=https://desktop-backend-hhibjajaja-uc.a.run.app/;- 保护路径(
codemagic.yaml、app/lib/env/dev_env.dart、app/lib/env/prod_env.dart、app/lib/main.dart、app/lib/utils/environment_detector.dart、DesktopBackendEnvironment.swift)中不得出现OMI_BETA_RELEASE_RING、api-beta.omi.me、STAGING_API_URL等遗留路由令牌; AppBuild.swift中出现的 macOS 生产 bundle 身份只允许com.omi.computer-macos与com.omi.computer-macos.beta两个受认可值(SANCTIONED_MACOS_PRODUCTION_BUNDLE_IDENTIFIERS),任何新增的"发散身份"都会被拒绝;- 退役的 GKE desktop-backend 图表(
backend/charts、desktop/macos/charts中带desktop-api.omi.me/desktop-backend标记的清单)与 GKE 部署工作流不得回归(生产桌面后端已迁移到 Cloud Run)。
其变异契约测试 test_check_mobile_production_routing.py 覆盖缺失、冲突、staging、任意与遗留赋值的各类突变场景,确保守卫本身不被绕过。
第四层:生产 Firebase 探针 backend/scripts/probe_beta_uid_continuity.py
这是文档强调的非人工(non-human)生产探针:它以based-hardware生产项目为目标,创建一个有界的哨兵记录(sentinel,PROBE_UID+uuid4随机标记),通过生产写入;然后通过Beta 的固定开发 Python 端点(https://api.omiapi.com/)读取同一条记录来证明 UID 连续性;最后在finally块中通过生产删除该记录。只有这条探针链路通过,Beta 才有资格被晋升(qualification can promote Beta)。探针同时校验生产 Firebase JWT 声明(validate_production_firebase_claims),任何一步失败都会以ContinuityProbeError终止。
文档还提醒:签名后的移动端与桌面产物冒烟(artifact smoke)仍是发布证据,静态 CI 守卫只是"绊线"(tripwires),不能替代产物级验证。
九、受保护路径清单与 PR 规则
文档给出了变更即触发评审的路径 globs,本文保持完整:
codemagic.yaml- app/lib/env/env.dart
- app/lib/main.dart
- app/lib/startup_routing.dart
- app/lib/utils/environment_detector.dart
app/lib/firebase_options*.dartapp/android/**/google-services.jsonapp/ios/**/GoogleService-Info.plist- desktop/macos/Desktop/Sources/AppBuild.swift
- desktop/macos/Desktop/Sources/DesktopBackendEnvironment.swift
desktop/macos/Desktop/Sources/GoogleService-Info*.plistbackend/charts/desktop-backend/**(已退役:此图表不得回归).github/workflows/gcp_*.yml(已退役:任何 GKE desktop-backend 部署权威不得回归).github/workflows/desktop_backend_*.yml- .github/scripts/check-mobile-production-routing.py
- .github/scripts/test_check_mobile_production_routing.py
backend/docs/runbooks/desktop-backend-cloud-run-ownership.md
PR 规则:任何改动上述路径的 PR,必须在 PR 描述中写明INV-DATA-1,并明确声明该改动是"保持既有权威"还是"显式迁移例外"。这条规则把不变式从文档层面落到了日常工程协作流程。
十、小结
INV-DATA-1 的核心可以用一句话概括:服务面可以且只允许有一条固定拆分(Beta → 开发 Python/桌面端点),数据面在任何情况下都只有一条(生产based-hardware),身份(bundle id / UID / Firebase 项目)是路由的最终判据,环境变量、更新通道与打包配置均无越权资格。Friend 仓库通过移动端 profile 枚举 + 启动硬校验、macOS 身份绑定路由 + fail-closed 决策链、CI 静态守卫(含变异契约测试)与生产 Firebase 哨兵探针这四层防线,把这个不变式变成了可在每次发布前机器验证的工程事实。对于任何需要改动上述路径的开发者和评审者,本文提供的权威矩阵、MUST NOT 红线、迁移例外条件与守护测试清单,可以作为直接的引用与检查依据。
【免费下载链接】FriendAI that sees your screen, listens to your conversations and tells you what to do项目地址: https://gitcode.com/GitHub_Trending/fr/Friend
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考