先看结论与判断条件
- 本地推理与云端调用是不同数据流,原先允许设备内处理的数据不自动获得网络上传授权。
- 只有 allowlisted 的本地失败原因可以进入 fallback 评估,输出超差、数据受限或授权缺失不能被包装成普通不可用。
- 上传清单按字段分类和最小化生成,原始媒体、检索文档、诊断日志与用户问题分别判断,不发送整个本地上下文。
- 云端令牌要限制 issuer、audience、scope、主体和有效期;发送者约束降低重放风险,但不替代服务端业务授权。
- 用户选择必须与当前数据类别、云服务目的和会话 generation 绑定,旧同意或另一账号选择不得复用。
- fallback 前后使用同一输出 schema 与业务终态,云端结果也要经过内容、权限、缓存和页面生命周期校验。
本地与云端是两条独立数据路径
端侧推理通常让输入停留在设备内,云端推理则需要通过网络把字段发送给外部服务。两条路径的接收方、处理位置、保留策略、第三方参与和网络风险不同。产品即使承诺“优先本地”,也不能在本地失败时默认上传,因为用户对本地功能的使用不等于同意云端处理。
架构图要为 localPath 和 cloudPath 分别列出 dataCategories、processors、purpose、retentionClass、authRequirement 和 outputPolicy。fallback 是从一条路径切换到另一条路径的安全决策,不是异常捕获中的备用函数调用。决策通过前,不创建网络请求、不获取云令牌,也不把本地上下文序列化到待发队列。
本地路径也可能使用硬件 delegate 或 CPU 回退,但它们仍在设备内数据域。delegate 不可用时优先尝试经验证的本地 CPU 路径;只有本地能力确实不可满足且业务允许云端时,才进入云 fallback。初始化成功不证明本地结果正确,输出一致性失败应按策略拒绝,而不是自动把敏感输入上传。
| 边界项 | 本地推理 | 云端推理 | fallback 要求 |
|---|---|---|---|
| 处理位置 | 设备内运行时 | 远端服务 | 重新授权 |
| 数据传输 | 通常不上传输入 | 通过网络发送字段 | 最小化清单 |
| 身份令牌 | 可能不需要云 token | 需要受众与 scope | 重新获取或验证 |
| 保留策略 | 本地缓存规则 | 服务端与第三方规则 | 显示目的和期限 |
| 失败终态 | 本地降级或不可用 | 网络、授权与服务错误 | 不得静默切换 |
先判断本地失败是否允许进入 fallback
本地失败原因要使用稳定枚举,如 delegate_unavailable、model_not_supported、resource_pressure、model_integrity_failed、output_validation_failed 和 user_cancelled。只有产品与安全团队批准的 allowlist 可以进入云评估。模型完整性失败、输出验证失败和用户取消通常不应被改写成网络请求,因为它们可能代表候选异常或明确停止。
失败事件绑定 candidateId、modelBundleId、deviceProfile、generation 和 localTerminal。日志文字不参与分支,避免某个 SDK 改文案后错误启用云端。连续失败还要受重试预算和熔断控制,不能在本地、云端之间循环;同一 requestId 最多有一个 active path,旧路径迟到结果按 generation 拒绝。
云端不可达、授权拒绝或用户拒绝上传时,终态回到 cloud_not_authorized、cloud_unavailable 或 local_only_failed。UI 明确告诉用户当前功能没有完成,不伪装成空答案。若业务允许稍后重试,队列只保存授权范围内的最小字段和过期时间,不保存完整本地模型上下文。
| localReason | 是否进入评估 | 下一步 | 禁止行为 |
|---|---|---|---|
| delegate_unavailable | 是 | 先验证 CPU 路径 | 直接上传 |
| model_not_supported | 按业务策略 | 提示或云评估 | 伪装硬件成功 |
| resource_pressure | 按当前授权 | 释放资源或云评估 | 无限循环 |
| model_integrity_failed | 否 | 阻止候选并诊断 | 发送同一模型上下文 |
| output_validation_failed | 通常否 | 保持失败并核查 | 用云结果掩盖本地错误 |
| user_cancelled | 否 | 终止请求 | 后台继续上传 |
上传字段按类别、目的和最小化重新生成
fallback 决策读取 dataInventory,而不是复用本地模型输入对象。用户问题、图片、音频、检索片段、设备诊断、账号标识和模型中间状态分别分类。云端完成任务需要哪些字段,就从 allowlist 构造新的 payload;不需要的 EXIF、路径、日志、缓存键和本地标签全部排除。
高敏字段可能禁止云端处理,或要求当次明确选择。用户选择记录 consentId、accountId、dataCategories、purpose、cloudProviderId、policyVersion、generation 和 expiresAt。换账号、用途、云服务或字段集合后重新确认,不能用“用户曾开启智能功能”覆盖所有未来上传。撤销后新请求立即拒绝,队列中的未发送 payload 失效。
数据最小化还要覆盖上下文历史和 RAG。云模型通常不需要整个聊天数据库,只需要当前请求、保留策略和有限引用;检索片段先核对来源权限是否允许离开设备。若删除关键证据后无法回答,终态是 insufficient_authorized_data,不应为追求答案完整度扩大上传。
| 字段类别 | 默认动作 | 允许条件 | 回执 |
|---|---|---|---|
| 当前用户问题 | 按目的最小化 | 明确云任务需要 | 字段摘要 |
| 原始媒体 | 默认拒绝 | 当次选择与必要性 | 类别和大小 |
| RAG 片段 | 默认设备内 | 来源允许云处理 | citationId |
| 账号标识 | 使用最小主体 | 服务端授权需要 | subject 类别 |
| 设备诊断 | 不随任务上传 | 独立诊断选择 | diagnosticPolicy |
| 本地日志与路径 | 拒绝 | 无 | 拒绝原因 |
令牌 audience、scope 与发送者约束分别检查
云请求使用的 access token 必须面向目标资源服务器,audience 与 cloudProviderId 匹配,scope 只包含当前推理动作所需能力,subject 与当前账号一致,并检查 issuer、时间与撤销状态。不能把登录令牌、另一个 API 的 token 或长期通用 token 直接附加到 AI 请求。
OAuth 安全部署还要防止令牌重放、授权码注入、不安全重定向和旧式 grant。移动端只持有完成业务所需的短期凭据,服务端重新执行数据与动作授权,不因为 token 语法有效就接受全部字段。令牌错误与数据政策拒绝分开返回,避免客户端不断刷新 token 绕过政策失败。
DPoP 一类发送者约束可以把 token 与客户端持有的密钥关联,降低被盗令牌在其他发送者处重放的风险。它不证明设备可信,不确认用户同意,也不替代 audience、scope、短有效期与服务端授权。decision receipt 分别记录 senderConstraintRequired、proofPresent 和 proofValidated,不能用一个 attested 布尔值代替全部条件。
| 检查项 | 要回答的问题 | 失败终态 | 不能替代 |
|---|---|---|---|
| issuer | 谁签发 token | token_invalid | 业务授权 |
| audience | 发给哪个资源服务器 | wrong_audience | 用户选择 |
| scope | 允许什么动作 | scope_insufficient | 字段最小化 |
| subject | 代表哪个账号 | subject_mismatch | 当前会话代际 |
| expiry | 是否仍在时限内 | token_expired | 撤销检查 |
| sender proof | 是否绑定发送者 | proof_required | 设备可信与同意 |
用户可见选择与会话 generation 同步
fallback 提示要发生在上传之前,说明本地处理失败、计划使用云端、发送的数据类别和可选动作。用户选择 local_only、allow_once 或 allow_policy_scope,应用把选择绑定当前 requestId 与 generation。页面销毁、切换账号或新请求开始后,旧选择不能被迟到回调复用。
选择界面不是唯一控制。服务端收到请求后仍核对 consentId、主体、数据类别、purpose 和过期时间,避免被修改客户端绕过。客户端只发送 consent reference,不把 UI 文案当作授权证明。若服务端政策版本更新导致选择无效,返回 consent_refresh_required,由新页面重新呈现。
流式云响应也绑定 generation。用户取消或撤销同意后,客户端停止读取、标记终态并拒绝后续片段写入缓存与 UI;服务端按能力取消或停止后续处理。不能仅隐藏加载动画,后台仍继续发送下一批上下文或把迟到答案写回新会话。
输出、缓存和商店声明都要反映真实路径
云端与本地可能使用不同模型,但面向 App 的 output schema 和 policyVersion 应一致,或通过显式版本适配器转换。云结果同样执行内容类型、引用、工具候选和敏感字段校验;不能因为来自服务端就直接渲染或执行。localResult 和 cloudResult 的缓存键包含 path、modelBundleId、accountId 与 generation。
隐私与 Data safety 声明要覆盖应用和第三方 SDK 实际收集、共享的数据。若产品新增云 fallback,数据清单、用户说明、服务提供方和保留政策需要同步评审。商店声明是透明度要求,不会自动执行技术隔离;payload allowlist、服务端授权、日志脱敏与撤销清理仍要单独实现。
发布证据保存 localReason、fallbackDecision、payloadCategories、consent reference、token 约束、服务端授权、outputValidation 和 finalTerminal。原始 token、证明材料和用户数据不进入普通报告。安全开发记录还要绑定构建、配置、第三方服务和变更审查,使一次云路径启用能够被复核。
| 阶段 | 关键字段 | 成功条件 | 失败终态 |
|---|---|---|---|
| 本地执行 | reason、candidate、generation | allowlisted reason | local_blocked |
| 数据决策 | categories、purpose | 全部允许且最小化 | data_not_authorized |
| 用户选择 | consentId、account、expiry | 当前请求有效 | consent_required |
| 令牌检查 | aud、scope、subject、proof | 全部匹配 | token_rejected |
| 云处理 | provider、schemaVersion | 服务端授权通过 | cloud_rejected |
| 输出交付 | validation、generation | 当前会话且合格 | output_blocked |
用 fallback 决策门禁检查数据与授权条件
决策文件可以只包含枚举和摘要:localReason、allowedReasons、requestedCategories、allowedCategories、consent、tokenClaims、expectedAudience、requiredScopes、senderProof 和 generation。检查器不发送网络请求、不存真实 token,也不包含用户内容,只验证切换前的确定性条件。
下面的 Python 代码从命令行读取决策。输入缺失时直接非零退出;随后拒绝不允许的本地原因、越界数据类别、同意代际或用途不匹配、audience 错误、scope 缺失和发送者证明未验证。所有条件通过只返回 eligible,实际服务端仍需重复授权。
项目实现还要校验 issuer、subject、时限、撤销与 payload schema,并将服务端结果绑定同一 decisionId。静态门禁通过不能证明 token 安全、设备可信或云端已合规处理,只证明这份脱敏决策记录没有明显缺项。
import json
import sys
from pathlib import Path
def stop(message):
print(f"cloud fallback rejected: {message}", file=sys.stderr)
raise SystemExit(2)
def load_decision(path_text):
path = Path(path_text)
if not path.is_file():
print("cloud fallback rejected: decision file is missing", file=sys.stderr)
raise SystemExit(2)
try:
return json.loads(path.read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError) as exc:
stop(f"cannot parse decision: {exc}")
def validate(data):
reason = data.get("localReason")
if reason not in set(data.get("allowedReasons", [])):
stop("local failure reason is not eligible")
requested = set(data.get("requestedCategories", []))
allowed = set(data.get("allowedCategories", []))
if not requested or not requested.issubset(allowed):
stop("requested data categories are not authorized")
consent = data.get("consent", {})
if consent.get("generation") != data.get("generation"):
stop("consent belongs to another conversation generation")
if consent.get("purpose") != data.get("purpose"):
stop("consent purpose does not match the cloud request")
if not requested.issubset(set(consent.get("dataCategories", []))):
stop("consent does not cover every requested category")
claims = data.get("tokenClaims", {})
if claims.get("audience") != data.get("expectedAudience"):
stop("token audience does not match the cloud service")
required_scopes = set(data.get("requiredScopes", []))
if not required_scopes.issubset(set(claims.get("scopes", []))):
stop("token scope is insufficient")
if data.get("senderConstraintRequired") and not data.get("senderProofValidated"):
stop("sender proof is required but not validated")
print("cloud fallback decision is eligible for server reauthorization")
if __name__ == "__main__":
if len(sys.argv) != 2:
stop("usage: validate_fallback.py decision.json")
validate(load_decision(sys.argv[1]))发布门禁同时验证本地失败、云授权和撤销
发布前固定 App、模型、fallbackPolicy、数据清单、云服务和输出 schema。矩阵覆盖本地成功、不支持、资源不足、完整性失败、输出失败、用户取消、无同意、同意过期、wrong audience、scope 缺失、proof 缺失、服务端拒绝、云超时、用户撤销和迟到流式片段。
加固、模型、delegate、云 SDK、OAuth 配置、数据字段、商店声明或服务提供方变化后,重新生成候选身份并重跑关键矩阵。旧同意和 token 测试不能替新配置签字。任何上传前门禁缺失都阻止发布,不用“只在少数设备降级”降低要求。
站内的端侧 delegate 一致性文章可继续检查本地失败是否真实;准备评估商业 App 的端云 AI 数据路径与加固范围时,可通过页面行动按钮进入御盾中央平台提交候选包、数据清单和授权流程。提交只启动评估,数据与权限结论仍以同一候选回执为准。
- 本地与云端分别建立数据流和输出契约。
- 只有 allowlisted 本地失败原因进入云评估。
- payload 从字段 allowlist 重新生成,不复用完整本地上下文。
- 用户选择绑定账号、用途、类别、服务和 generation。
- token audience、scope、subject 与发送者约束分别验证。
- 撤销、取消、迟到响应和服务端拒绝都有明确终态。
事实依据与适用边界
以下内容区分官方事实、本文工程判断和不能外推的范围,避免把设计建议写成未经验证的产品结论。
| 本文判断 | 事实或工程依据 | 适用限制 |
|---|---|---|
| delegate 不支持时应保留本地 CPU 路径,而不是直接上传。 | LiteRT Android Java API 说明检查 delegate 可用性并保留 CPU 执行路径。 | delegate 或 CPU 初始化成功不证明输出一致或业务可接受。 |
| 端侧模型与 LoRA 路径受具体模型和后端条件限制。 | Google AI Edge LLM Inference for Android 说明基础模型、LoRA 与 GPU 适用条件。 | 该维护期 API 的约束不能外推为所有端侧运行时标准。 |
| 云授权部署需要限制令牌重放、授权码注入与不安全流程。 | RFC 9700 OAuth Security BCP 提供 OAuth 安全部署建议。 | 安全 BCP 不决定具体 AI 服务的数据类别和商业权限。 |
| 发送者约束可降低被盗 access token 的重放风险。 | RFC 9449 OAuth DPoP 定义 token 与客户端密钥的证明机制。 | DPoP 不证明设备可信,也不替代短期令牌、用户选择和服务端授权。 |
| 数据声明要反映应用与第三方 SDK 的实际收集和共享。 | Declare Android app data use 说明 Data safety 的申报责任。 | 商店声明不是技术隔离、授权或日志脱敏控制。 |
| 端云 fallback 变更要保留来源、构建、验证和供应链证据。 | NIST SP 800-218 SSDF 提供安全开发与变更治理实践。 | 组织级框架不定义具体云服务或证明候选通过。 |
| 数据类别、同意、audience、scope 或 proof 任一不满足时不得上传。 | 工程判断:跨越本地到云端数据边界需要全部前置条件同时成立。 | 服务端仍需独立执行主体、用途和数据授权,客户端门禁不是最终授权。 |
| 当前不能声称目标 App 的端云降级已经符合授权边界。 | 项目证据尚未接入;需要候选、数据清单、同意、token 与服务端回执。 | 文章提供方法和门禁代码,不替代真实合规与运行验收。 |
工程常见问题
本地模型不可用时自动调用云端是不是合理降级?
只有在数据类别、用户选择、token 与服务端授权全部通过时才合理。否则应保持本地失败或让用户明确选择,不能静默上传。
用户开启过 AI 功能是否等于同意所有云端上传?
不等于。同意要绑定账号、用途、服务提供方、数据类别、政策版本和有效期,路径或字段变化后重新确认。
有效 access token 能否证明请求有权发送这些数据?
不能。还要核对 issuer、audience、scope、subject、时限和撤销,服务端再对用途与字段执行业务授权。
使用 DPoP 后是否可以省略设备和用户授权检查?
不可以。DPoP 主要约束 token 发送者并降低重放,它不证明设备可信,不代表用户同意,也不决定数据范围。
云 fallback 加入后只更新隐私声明够吗?
不够。声明用于透明度,技术上还要实现 payload allowlist、token 边界、服务端授权、日志脱敏、撤销清理和输出校验。
App 加固后为什么要重测端云降级?
加固可能影响本地模型、网络、token、序列化、缓存和生命周期。候选变化后,旧授权与路径回执不能证明新包行为。