一次「平台顺手改了一下」
回声的客服链路 B 在周五晚高峰出现回复语气异常。值班的平台工程师发现 chat.reply 的默认提示里有一段过时的促销话术,顺手改掉并发布。周一,内容团队反馈摘要链路 A 的输出风格也变了,安全团队的抽检样本出现一批新的误判。
这次改动只花了两分钟,复盘却开了三小时。平台认为自己是在修一个明显的缺陷;业务认为平台未经评审改了自己的提示;安全团队认为自己依赖的基线被悄悄换掉了。三方的说法都对,问题是没有人能指出「这段提示到底归谁」。
这就是职责边界缺失的典型现场。它不是能力问题,也不是态度问题,而是任务没有被映射到一个明确的决策主体。边界不写清楚,每一次跨团队改动都会变成一次重新谈判。
越界有两个方向
讨论平台与业务,容易只盯着「平台管得太多」。实际越界有两个方向。
第一个方向是平台越界:平台直接改业务提示、替业务设定质量阈值、把业务 KPI 收到自己名下。短期看效率很高,长期看业务不再为结果负责,平台变成所有链路的共同背锅方。
第二个方向是业务越界:业务私改共享默认值、绕过配额直接调用、在未评审的情况下改对外契约。短期看业务跑得快,长期看其他调用方的基线被改写,平台失去限流与计量能力。
两个方向的共同点是把「共同决策」误当成「单方决定」。边界不是一条把两边隔开的线,而是一组任务到决策主体的映射。有些任务归平台,有些归业务,有些必须共同决定。
三条划分原则
第一条,谁承担后果,谁拥有决定权;如果后果由多方承担,就共同决定。提示直接决定业务输出质量,业务承担后果,所以提示内容归业务;但提示模板被多个调用方共享时,后果外溢,变更就要共同评审。
第二条,谁维护共享机制,谁定义机制的使用规则。平台维护网关、配额、评测与审计,因此平台定义配额的三层结构、借用规则与耗尽行为;业务在规则内分配自己的份额,不能绕过规则。
第三条,契约与生命周期必须共同决定。对外契约、主版本升级、租户开通、跨租户事故,这些任务同时改变多方预期,任何单方决定都会把风险转嫁给别人。
三条原则不能解决所有灰区,但能把灰区压缩到可讨论的范围。剩下的灰区用一个显式字段记录「这次为什么这么判」,而不是靠记忆。
职责划分表
把常见任务按产物分成九类,每类给出平台、业务、共同决策三列。这张表不是组织架构图,而是任务路由表;同一个团队既可能出现在平台列,也可能出现在业务列。
| 产物 | 平台负责 | 业务负责 | 共同决策 |
|---|---|---|---|
| 提示 | 版本登记、回放与回滚接口 | 编写与调优本团队提示,记录版本与评测 | 跨能力共享模板的变更评审 |
| 契约 | 契约格式、兼容性检查、目录发布流程 | 声明输入输出与失败语义,保证内容正确 | 字段变更、主版本升级与弃用窗口 |
| 配额 | 三层配额、借用规则与耗尽行为 | 在份额内分配调用方额度并监控用量 | 跨团队借用、总额调整与保供排序 |
| 版本 | 版本矩阵、弃用窗口与迁移清单 | 决定升级节奏,完成本团队链路回归 | 跨所有者主版本迁移与强制下线 |
| 模型 | 模型档案、选型基线与路由默认值 | 在允许范围内选择档位并承担质量成本 | 默认模型变更、供应商切换与区域调整 |
| 评测 | 评测集目录、污染检查与回归门禁 | 维护本链路样本与验收阈值 | 跨团队共享评测集与统一发布门禁 |
| 事故 | 事故分级、值班与影响面工具 | 处置本链路事故,提交复盘与修复证据 | 跨租户事故定级、对外通告与联合复盘 |
| 租户 | 开通、计量与级联删除能力 | 确认业务范围与验收场景 | 开通、合同配额与数据保留条款 |
| 工具 | 网关、配额、评测与审计等共享设施 | 提交需求与集成问题,不直接改实现 | 平台 SLO、重大选型与自助边界 |
这张表最容易误读的一点是:平台负责「机制」,不负责「业务结果」。平台提供配额的三层结构与借用规则,但内容团队分给主站入口多少、分给批处理入口多少,是业务决定;平台提供评测门禁,但某条链路的验收阈值定在多少,是业务决定。
反过来,业务负责「内容与节奏」,不负责「共享机制的实现」。业务可以决定本团队提示怎么写、什么时候升级版本,但不能自己改网关的限流实现,也不能绕过配额直接调用。越界的典型句式是「这次特殊,先放过」,它把例外变成了常态。
两类判定:职责归属与越界信号
表格适合人读,不适合系统执行。系统需要的是:给定一个任务描述,返回它归谁、依据是什么。下面的模块把九类产物与影响范围编码成判定函数。
判定顺序就是优先级。工具类任务直接归平台;租户生命周期直接归共同决策;改对外契约、平台级范围、跨租户影响、跨团队预算依次升级;最后,多调用方共享的契约、版本、模型、配额、评测归共同决策;剩下的单调用方任务才归业务。
越界的典型信号
职责表定义「应该怎样」,还需要一个工具识别「实际怎样」。越界往往不是一次大动作,而是一串小信号:业务绕过配额、私改共享默认值、平台直接改业务提示、平台替业务背 KPI、共同决策没有记录、在役能力没有 owner、版本与契约不一致。
把这些信号做成评分,有三个好处。第一,评审时有一致的口径,不靠谁声音大。第二,告警可以直接带上分值与信号列表,值班知道该找谁。第三,季度复盘可以看趋势,而不是只讨论最近一次事故。
下面的模块把职责判定与越界评分放在同一个文件:先返回任务归属与依据,再把越界信号折算成分值与档位。权重表达的是「越界后修复成本」,不是发生概率。
// file: src/gov/responsibility.ts
// tier: L2
// verify: node --test test/gov/scope.test.ts
export type OwnerSide = "platform" | "business" | "shared";
export type TaskArtifact =
| "prompt"
| "contract"
| "quota"
| "version"
| "model"
| "evaluation"
| "incident"
| "tenant"
| "tooling";
export type TaskScope =
| "single-caller"
| "multiple-callers"
| "single-tenant"
| "multiple-tenants"
| "platform";
export interface OwnershipTask {
id: string;
artifact: TaskArtifact;
scope: TaskScope;
/** 是否改变对外契约;改契约必须平台评审,不能由单方决定。 */
changesExternalContract: boolean;
/** 是否波及其他租户;波及即升级为共同决策。 */
affectsOtherTenants: boolean;
/** 是否需要跨团队预算或配额借用。 */
requiresCrossTeamBudget: boolean;
}
export type OwnershipBasis =
| "platform-wide"
| "cross-tenant"
| "external-contract"
| "cross-team-budget"
| "multi-caller-shared-artifact"
| "single-caller-local"
| "tenant-lifecycle";
export interface OwnershipDecision {
taskId: string;
side: OwnerSide;
artifact: TaskArtifact;
basis: readonly OwnershipBasis[];
}
const SHARED_ARTIFACTS: readonly TaskArtifact[] = [
"contract",
"version",
"model",
"quota",
"evaluation",
];
/**
* 判定顺序本身就是优先级:
* 租户生命周期与对外契约先归共同决策,再看影响范围,
* 最后才允许落到单调用方的业务自治。
*/
export function decideOwner(task: OwnershipTask): OwnershipDecision {
const basis: OwnershipBasis[] = [];
if (task.artifact === "tooling") {
basis.push("platform-wide");
return { taskId: task.id, side: "platform", artifact: task.artifact, basis };
}
if (task.artifact === "tenant") {
basis.push("tenant-lifecycle");
return { taskId: task.id, side: "shared", artifact: task.artifact, basis };
}
if (task.changesExternalContract) basis.push("external-contract");
if (task.scope === "platform") basis.push("platform-wide");
if (task.scope === "multiple-tenants") basis.push("cross-tenant");
if (task.affectsOtherTenants && !basis.includes("cross-tenant")) basis.push("cross-tenant");
if (task.requiresCrossTeamBudget) basis.push("cross-team-budget");
if (
task.scope === "multiple-callers"
&& SHARED_ARTIFACTS.includes(task.artifact)
&& !basis.includes("multi-caller-shared-artifact")
) {
basis.push("multi-caller-shared-artifact");
}
if (basis.length > 0) {
return { taskId: task.id, side: "shared", artifact: task.artifact, basis };
}
basis.push("single-caller-local");
return { taskId: task.id, side: "business", artifact: task.artifact, basis };
}
/** 批量判定,顺序与输入一致;重复 id 直接拒绝,避免报告被同名任务覆盖。 */
export function decideOwners(tasks: readonly OwnershipTask[]): OwnershipDecision[] {
const seen = new Set<string>();
for (const task of tasks) {
if (seen.has(task.id)) throw new Error(`duplicate task id: ${task.id}`);
seen.add(task.id);
}
return tasks.map((task) => decideOwner(task));
}
/**
* 职责表只分三列:平台负责、业务负责、共同决策。
* artifact 是这张表的索引,同一 artifact 在不同 scope 下可以换列。
*/
export const RESPONSIBILITY_TABLE: Record<TaskArtifact, { platform: string; business: string; shared: string }> = {
prompt: {
platform: "提供提示版本登记、回放与回滚接口",
business: "编写与调优本团队提示,记录版本与评测结果",
shared: "跨能力共享提示模板的变更评审",
},
contract: {
platform: "定义契约格式、兼容性检查与目录发布流程",
business: "声明输入输出与失败语义,承担契约内容正确性",
shared: "契约字段变更、主版本升级与弃用窗口",
},
quota: {
platform: "维护三层配额、借用规则与耗尽行为",
business: "在份额内分配调用方额度并监控用量",
shared: "跨团队借用、总额调整与旺季保供排序",
},
version: {
platform: "维护版本矩阵、弃用窗口与迁移清单",
business: "决定升级节奏,完成本团队链路回归",
shared: "跨所有者主版本迁移与强制下线",
},
model: {
platform: "维护模型档案、选型基线与路由默认值",
business: "在允许范围内选择档位并承担质量成本",
shared: "默认模型变更、供应商切换与区域调整",
},
evaluation: {
platform: "提供评测集目录、污染检查与回归门禁",
business: "维护本链路样本与验收阈值",
shared: "跨团队共享评测集与统一发布门禁",
},
incident: {
platform: "提供事故分级、值班与跨租户影响面工具",
business: "处置本链路事故,提交复盘与修复证据",
shared: "跨租户事故定级、对外通告与联合复盘",
},
tenant: {
platform: "提供租户开通、计量与级联删除能力",
business: "确认业务范围与验收场景",
shared: "租户开通、合同配额与数据保留条款",
},
tooling: {
platform: "维护网关、配额、评测与审计等共享基础设施",
business: "提交需求与集成问题,不直接改平台实现",
shared: "平台 SLO 目标、重大工具选型与自助边界",
},
};
export type BoundarySignalCode =
| "business-edits-shared-default"
| "platform-edits-business-prompt"
| "business-bypasses-quota"
| "platform-owns-business-kpi"
| "shared-decision-without-record"
| "active-capability-without-owner"
| "version-contract-mismatch";
export interface BoundarySignal {
code: BoundarySignalCode;
/** 证据位置,例如评审单号、目录条目或告警 id。 */
detail?: string;
}
export interface BoundaryAssessment {
score: number;
level: "clear" | "watch" | "violation";
signals: readonly BoundarySignalCode[];
reasons: readonly string[];
}
/**
* 权重表达的是「越界后修复成本」,不是概率。
* 绕过配额与私改共享默认值会直接破坏他人链路,权重最高。
*/
export const SIGNAL_WEIGHTS: Record<BoundarySignalCode, number> = {
"business-bypasses-quota": 40,
"business-edits-shared-default": 35,
"platform-edits-business-prompt": 30,
"version-contract-mismatch": 30,
"platform-owns-business-kpi": 25,
"active-capability-without-owner": 25,
"shared-decision-without-record": 20,
};
const SIGNAL_TEXT: Record<BoundarySignalCode, string> = {
"business-bypasses-quota": "业务绕过配额直接调用,平台失去限流与计量",
"business-edits-shared-default": "业务私改共享默认值,其他调用方的基线被改写",
"platform-edits-business-prompt": "平台直接改业务提示,业务失去质量责任主体",
"version-contract-mismatch": "版本与契约不一致,调用方无法判断兼容性",
"platform-owns-business-kpi": "平台替业务背 KPI,业务不再为结果负责",
"active-capability-without-owner": "在役能力没有责任人,事故与退役无人承接",
"shared-decision-without-record": "共同决策没有记录,事后无法追责与复现",
};
/**
* 评分只做三档判定:0 为 clear,1–29 为 watch,30 及以上为 violation。
* 同一信号重复出现只计一次,避免告警风暴把分数推满。
*/
export function assessBoundary(signals: readonly BoundarySignal[]): BoundaryAssessment {
const seen = new Set<BoundarySignalCode>();
const normalized: BoundarySignalCode[] = [];
for (const signal of signals) {
if (seen.has(signal.code)) continue;
seen.add(signal.code);
normalized.push(signal.code);
}
const ordered = normalized
.slice()
.sort((left, right) => SIGNAL_WEIGHTS[right] - SIGNAL_WEIGHTS[left] || left.localeCompare(right));
const raw = ordered.reduce((sum, code) => sum + SIGNAL_WEIGHTS[code], 0);
const score = Math.min(100, raw);
const level = score === 0 ? "clear" : score < 30 ? "watch" : "violation";
return {
score,
level,
signals: ordered,
reasons: ordered.map((code) => SIGNAL_TEXT[code]),
};
}
/** 把评分结果压成一行,便于写进评审记录或告警正文。 */
export function describeBoundary(assessment: BoundaryAssessment): string {
if (assessment.level === "clear") return "boundary=clear score=0";
return `boundary=${assessment.level} score=${assessment.score} signals=${assessment.signals.join(",")}`;
}
函数返回的 basis 是判定依据,不是解释性文字。external-contract、cross-tenant、cross-team-budget、multi-caller-shared-artifact、single-caller-local 这些代码可以进入评审记录与看板,也可以被测试直接断言。需要写给人看的说明时,再用 RESPONSIBILITY_TABLE 取对应列。
有一个容易忽略的设计:tooling 与 tenant 在函数开头就返回,不参与后续规则。工具归平台是因为它本身就是共享机制的实现;租户归共同决策是因为它同时改变合同、数据边界与计量口径。把这两类放进通用规则,会让判定结果依赖字段组合,难以解释。
示例样本上的判定结果
用 8 个任务覆盖三档归属,在示例样本上运行,结果如下。
| 任务 | 产物 / 范围 | 归属 | 依据 |
|---|---|---|---|
| 内容团队调自己的摘要提示 | prompt / 单调用方 | business | single-caller-local |
修改 chat.reply 对外契约 | contract / 单调用方,改契约 | shared | external-contract |
| 跨团队借用配额 | quota / 多调用方,跨团队 | shared | cross-team-budget、multi-caller-shared-artifact |
| 多调用方共享评测集 | evaluation / 多调用方 | shared | multi-caller-shared-artifact |
| 跨租户事故复盘 | incident / 多租户 | shared | cross-tenant |
| 维护网关与审计工具 | tooling / 平台 | platform | platform-wide |
| 新租户开通 | tenant / 单租户 | shared | tenant-lifecycle |
| 多调用方默认模型变更 | model / 多调用方 | shared | multi-caller-shared-artifact |
这张表里只有第一个任务归业务。不是因为业务能做的事少,而是因为示例特意选了七个会外溢的任务。现实中大量单调用方的提示调优、样本维护、事故处置都归业务,平台不应该介入。
还要注意「改契约」这一行:任务范围是单调用方,按影响范围本来可以归业务,但因为它改的是对外契约,仍然升级为共同决策。这说明判定顺序不是「范围优先」,而是「契约与生命周期优先」。
示例信号评分
评分有三个刻意选择。第一,同一信号重复出现只计一次,否则一次批量告警就能把分数推到 100,反而掩盖了信号种类。第二,总分封顶 100,避免权重相加失去意义。第三,三档阈值固定为 0、1–29、30 以上;只有「共同决策没有记录」一个信号时是 watch(20 分),一旦叠加绕过配额(40 分),立刻进入 violation。
| 信号组合 | 分数 | 档位 | 处置 |
|---|---|---|---|
| 无 | 0 | clear | 正常评审 |
| 共同决策无记录 | 20 | watch | 补齐记录,下次评审前完成 |
| 业务绕过配额 | 40 | violation | 立即切断旁路,补登记与计量 |
| 绕过配额 + 平台背 KPI + 无记录 | 85 | violation | 联合复盘,恢复三方责任 |
85 分这一行值得展开。三个信号分别来自业务、平台与流程,说明越界不是单方问题,而是边界整体失效。此时只修其中一项没有意义:切断旁路但平台仍背 KPI,业务会把绕过当成唯一出路;补了记录但旁路还在,记录只会变成形式。
边界判定的使用方式
这套判定最适合放在三个位置。
第一个位置是方案评审。每个跨团队方案在评审单里填任务描述,系统返回归属与依据;如果归属是 shared,评审单必须列出平台与业务双方的签字人。这样「共同决策」不再是一句口号,而是一个必填字段。
第二个位置是变更流水线。契约、配额、默认模型这类产物在发布前跑一次判定;归属为 shared 但缺少双方审批的变更直接拒绝。判定不替代人工评审,只负责把「该谁签字」变成可执行的检查。
第三个位置是季度复盘。把一季度的越界信号按团队、产物、信号类型聚合,看哪类边界最常被突破。如果某个团队反复出现「绕过配额」,问题可能在配额规则本身,而不是团队纪律。
三个位置共用一个原则:判定结果与依据必须落盘。只返回一个「shared」没有用,必须同时记录 basis、任务 id、评审单号与签字人。否则下次争论时,仍然只能靠回忆。
小结
平台与业务的边界有两类越界:平台替业务做内容决定,业务替平台改共享机制。划分原则是「谁承担后果谁决定、谁维护机制谁定规则、契约与生命周期共同决定」。九类产物的职责表把原则落到平台、业务、共同决策三列。
decideOwner 把任务映射为 platform、business 或 shared,并返回 basis;示例中 8 个任务有 7 个归共同决策或平台,只有单调用方的提示调优归业务。assessBoundary 把七类越界信号按修复成本加权,0 分为 clear、1–29 为 watch、30 以上为 violation;示例中「无记录」是 20 分,「绕过配额」是 40 分,三者叠加是 85 分。
职责边界不是组织架构图,而是一组可判定的任务路由与一份可追溯的决策记录。下一章从能力目录开始,把本节的对象与职责落到一条条可查询的条目上。
阅读导航:上一节:1.2 治理的四个对象:能力、调用方、租户、版本 · 下一节:2.1 一条能力条目的最小字段 。
常见错误与边界
- 把边界理解成部门分工。边界是任务路由,同一个团队在不同任务上可以站在不同列。
- 平台替业务写提示。短期修了缺陷,长期让业务失去质量责任主体。
- 业务私改共享默认值。其他调用方的基线被改写,事故无法定位到具体变更。
- 把「共同决策」做成「都要签字」。签字人不明确、依据不记录,评审会退化成走流程。
- 只判归属,不记依据。下次争论时仍要靠回忆,判定结果无法复现。
- 用越界评分替代纪律处分。评分用于定位机制缺口,不是给团队打分的绩效工具。
- 同一信号重复计分。批量告警会把分数推满,反而掩盖信号种类。
- 认为归业务就可以绕过平台。业务决定内容与节奏,仍要在配额、契约与审计规则内运行。
- 把示例中的 7/8 共同决策当成常态。示例特意选了外溢任务,单调用方任务大多归业务。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。