服务目录 Scorecard:标准、证据时效与改进行动
发布评审会上,某生产服务的安全 Scorecard 是满分,负责人因此申请跳过人工复核。发布后才发现依赖扫描器已经连续多轮认证失败,门户仍在展示最后一次成功结果;那次结果来自旧分支,扫描时间早于当前制品,且规则只检查“漏洞数量字段是否存在”。一个绿色分数同时掩盖了证据过期、采集失败和对象不匹配。
采购页面上的“支持 Scorecards”不能回答这些风险。Port 是云原生 SaaS,Scorecards 位于完整平台能力中;公开套餐把实体数、座席和自动化执行量作为容量边界,细粒度 dynamic permissions 从 Standard 起提供,Private Link、IP allowlist、SCIM 和更高 SLA 位于 Enterprise,官方明确不提供传统 on-prem 安装,只为 Enterprise 提供专属租户等灵活部署选项。Cortex 同样是报价制商业产品,没有可固定到文章里的公开稳定版本或统一标价;Scorecards 可通过 UI、API 与 GitOps 管理,Initiatives、报表、集成、SSO 和自定义角色的实际授权以租户合同为准。选型时要把产品更新日志、API 弃用期、数据导出和退出协助写进合同,不能把持续交付 SaaS 当成可永久锁版的本地软件。Port 的套餐与部署说明、Cortex 的报价入口和Scorecard 创建说明给出了这些商业边界。
Scorecard 不是把目录字段拼成排行榜。它是一条带来源和时间语义的判断管线:采集器生成不可变证据,规范化层校验实体、环境与制品身份,规则在明确窗口内评估,聚合器保留 fresh、stale、unknown、error 等状态,豁免系统改变适用性而不篡改事实,改进流程再把失败变成 owner 可完成的行动。任何层只剩布尔值,最终分数都会显得比事实更确定。
先启用一条最小证据链
托管门户通常在服务实体或 Blueprint 上创建 Scorecard,选择实体过滤器、层级或积分,再添加读取实体属性或第三方集成的规则。最小试验只接一个仓库、一个 CI 流水线和一个试验服务:创建“默认分支近期构建成功”规则,手动触发一次成功构建,再撤销集成凭证观察状态。若撤销后仍永久显示通过,说明产品默认结果不具备团队需要的错误与时效语义,必须在进入实体属性前增加证据适配层。
自托管 Backstage 没有一个能够替代所有质量平台的统一 Scorecard 内核,常见做法是安装受信插件,或由内部后端读取目录与证据存储,再以插件页面展示。启用顺序应是:先运行 Catalog 和身份系统;为证据 API 建只读服务身份;建立规则与证据表;实现后端授权;最后挂 UI。插件必须锁定版本、检查维护者与许可证,并在试验环境验证数据库迁移和卸载路径。不要把未知插件直接赋予跨组织 Git、云和安全平台的高权令牌。
这里的许可边界与 SaaS 不同:Backstage 核心采用 Apache-2.0,但社区 Scorecard 插件、规则包和集成适配器各有自己的许可证与维护节奏,不能由核心许可证代替审查。部署时为插件建立 SBOM、来源摘要和数据库迁移清单,记录它读哪些目录实体、向哪些后端发请求、是否把上游数据传到第三方。插件停止维护或使用不稳定导出时先影子替换,逐实体比较规则结果,再移除路由、数据库对象和凭证;直接卸载 UI 不会自动删除证据副本或 webhook。
无论产品形态,第一条规则都需要下面六个可观察对象:
standard:为什么要做、适用哪些实体、由谁维护。ruleVersion:可执行条件的不可变版本。evidence:来源、目标、观测值、采集时刻、接收时刻和完整性。
evaluation:使用哪份证据、得到何种状态、原因码是什么。exemption:规则是否暂不适用、谁批准、何时失效。action:失败后由谁在何处修复、如何证明关闭。
启用成功的证据不是页面出现圆环,而是能够用实体完整引用查询一次评估,看到证据来源与年龄,故意断开采集后从 fresh 转到 error 或随后转 stale,恢复采集后自动回到新证据,且每次转换都有审计事件。
规则要表达风险,不要只检查字段存在
“有 README”“有 owner”“有 SLO”适合发现接入缺口,不足以证明工程能力。README 可能是空文件,owner 可能指向已删除组,SLO 可能没有数据源。规则应从风险句开始:当生产服务不可用时,能否找到当前责任方;发布制品是否经过对应提交的测试;告警是否来自实际接收流量的监控;高危漏洞结果是否针对正在运行的制品。
每条规则建议包含以下字段:
id: production-build-provenance
version: 4
title: Production artifact has verified build provenance
appliesTo:
kind: Component
lifecycle: production
type: service
owner: group:default/platform-quality
severity: critical
evidenceType: ci.build-provenance
subjectMatch:
entityRef: required
artifactDigest: required
window:
mode: event-relative
maxAgeAfterDeployment: PT2H
evaluation:
passWhen:
status: succeeded
branch: default
signatureVerified: true
onMissing: unknown
onCollectorError: error
remediation:
runbook: https://example.com/docs/build-provenance
verifyBy: collect-new-evidenceid + version 形成规则身份,修改条件必须升版本,历史结果保留旧版本;appliesTo 决定分母;severity 决定阻断与通知,而不是随意加权;evidenceType 绑定契约;subjectMatch 防止拿别的分支或制品的证据;window 定义时间语义;onMissing 与 onCollectorError 禁止隐式通过;remediation 给修复入口和关闭方式。
规则应尽量原子化。一条规则同时检查 owner、值班和运行手册,失败后无法知道修什么,部分通过也难以表达。标准可以由多条原子规则组成,层级用于表示能力递进:基础层先建立身份和 owner,生产层再要求告警、SLO、恢复与供应链证据。关键风险采用硬门槛,不应被大量低价值规则的积分抵消。
Port 的官方 Scorecard 模型以实体属性上的条件评估规则,并明确指出文件内容要先由集成或自动化提取为属性;这提醒我们 Scorecard 只会判断它实际读到的数据,不会自动证明上游提取可靠。可在 Port Scorecard concepts 核对目标租户支持的条件、层级和结果模型。
证据契约必须绑定来源、对象和版本
证据不是一个 true。最少要保存 evidenceId、类型、来源、主体、环境、观测值、采集时刻、接收时刻、采集器版本、原始记录摘要和完整性状态。对于制品相关规则,主体必须含不可变 digest;对于仓库规则,主体至少含仓库 ID、分支和 commit;对于运行时规则,主体含环境和工作负载 UID。
{
"evidenceId": "ev_01H_EXAMPLE",
"type": "ci.build-provenance",
"source": "ci:your-org/checkout-api",
"subject": {
"entityRef": "component:commerce/checkout-api",
"commit": "0123456789abcdef",
"artifactDigest": "sha256:example",
"environment": "production"
},
"observedAt": "T0",
"receivedAt": "T0+5m",
"collector": { "name": "ci-evidence-adapter", "version": "3" },
"value": {
"status": "succeeded",
"branch": "default",
"signatureVerified": true
},
"integrity": { "schemaValid": true, "signatureValid": true },
"rawRef": "evidence://ci/run/example"
}这里使用相对时间表示语义,实际存储应采用带时区的机器时间并统一到 UTC。observedAt 是事实发生时间,receivedAt 是门户收到时间,两者差值能发现积压;不能用数据库更新时间替代观测时间,否则反复同步旧结果会让它看起来永远新鲜。rawRef 指向受权限保护的原始证据,门户只存摘要;删除原始记录前要考虑审计保留。
来源权威同样按证据类型定义。代码扫描结果来自扫描器,部署制品来自发布系统,SLO 燃尽来自监控平台,owner 来自目录关系。手工上传截图只能作为临时补充,不能覆盖机器证据。若两个扫描器检查不同范围,应保留两条规则或先规范化覆盖集合,不能简单取更绿的一条。
采集器应使用最小只读凭证,通过批量 API、webhook 或导出任务获取数据。写入证据存储前验证 schema、主体映射、时间合理性和签名;解析失败写 error 事件,不覆盖最后成功证据。原始响应可能含漏洞细节、仓库地址、人员身份或令牌片段,应在适配器边界脱敏,Scorecard 不应成为敏感结果的第二份全文数据库。
四态证据和规则结果必须分开
fresh、stale、unknown、error 描述证据可用性;pass、fail 描述规则判断。把两组状态混成一个枚举,会出现“stale 到底是通过还是失败”的争论。建议评估结果同时保存 evidenceState 与 ruleOutcome:
| 证据状态 | 含义 | 可否执行规则 | 默认展示 |
|---|---|---|---|
fresh | 完整、主体匹配且在窗口内 | 可以 | pass 或 fail |
stale | 最近成功证据完整但已超窗 | 可计算历史结果,不可声称当前合规 | stale-pass 或 stale-fail |
unknown | 从未取得证据、主体无法映射或适用性待确认 | 不应猜测 | unknown |
error | 采集、认证、解析或规则执行失败 | 不应沿用为当前判断 | error,可附最后成功结果 |
unknown 不是 fail。新服务尚未完成第一次扫描,与明确扫描出高危漏洞的风险不同;但在发布门禁中,两者都可以阻止继续,只是修复路径不同。error 也不是 unknown:前者表明系统本应知道但处理失败,平台 owner 要修;后者常由服务 owner 补元数据或触发首轮采集。
stale-pass 不能计入当前通过率。可以展示“最后已知通过”,但聚合时从 fresh 分子排除,并单独展示 stale 分母。对关键规则,超过窗口立即失去放行资格;对建议规则,可以给宽限窗口并告警。产品若只支持 pass/fail,可在进入产品属性前派生 evidence_state、is_fresh 和 effective_pass = is_fresh && raw_pass,至少不让过期结果保持绿色。
错误恢复应保存两条指针:latestAttempt 和 latestSuccessfulEvidence。最新尝试认证失败时,页面显示 error,同时允许查看旧成功证据及其年龄;恢复后新成功证据前移指针。绝不能用失败记录覆盖旧证据后丢失诊断,也不能只保留旧证据隐藏失败。
时间窗口要跟风险变化速度匹配
固定“最近若干天”不是万能答案。证据窗口应按事实变化方式设计:仓库设置是状态型证据,收到 webhook 后立即变化,并用周期全量校准;构建和部署是事件型证据,要绑定具体 commit 或 digest;运行健康是连续信号,需要滑动窗口和覆盖率;恢复演练是周期事件,窗口可更长但要在到期前提醒。
常见窗口有四类:
绝对年龄窗口:now - observedAt <= maxAge,适合依赖扫描、备份验证等周期证据。事件相对窗口:证据必须晚于目标部署,并匹配该制品,适合构建、签名和测试。滑动聚合窗口:在最近一段运行区间计算可用性、错误率或告警覆盖。
状态变更窗口:只要上游配置发生变化就立即失效,适合分支保护与 owner 关系。
窗口还要处理时钟偏差、延迟到达与乱序。采集器收到未来时间应隔离;迟到证据可补历史趋势,但若主体已经换成新 digest,不能恢复当前合规;规则重算时使用证据的事件时间,不是任务执行时间。窗口配置改变必须升规则版本或记录策略版本,否则历史趋势会像数据突然变差。
同一标准对不同实体可有不同窗口,但差异必须来自风险分类,例如互联网生产服务与内部试验库,而不是由团队自行挑最容易通过的数字。分类由目录的生命周期、数据等级和暴露面决定,并纳入评审。窗口越短,采集调用、存储写入和告警噪声越高;窗口越长,错误绿色持续越久。应结合上游 SLA、风险容忍和修复值班能力取舍。
本地正反实验:过期和采集错误不能变绿
下面的 Node.js 模型不依赖第三方包。保存为 scorecard-model.mjs 后执行 node scorecard-model.mjs,它在同一固定评估时刻验证四种证据状态,并对比一个只看 value 的错误算法。固定时刻让输出可重复,不受运行机器时间影响。
const NOW = 10_000;
const MAX_AGE = 1_000;
const samples = [
{ id: 'fresh-pass', observedAt: 9_500, receivedAt: 9_600, value: true, integrity: 'valid' },
{ id: 'stale-pass', observedAt: 7_000, receivedAt: 9_900, value: true, integrity: 'valid' },
{ id: 'unknown', value: null, integrity: 'missing' },
{ id: 'collector-error', observedAt: 9_800, value: true, integrity: 'collector-error' },
];
function evaluate(evidence) {
if (evidence.integrity === 'collector-error') {
return { evidenceState: 'error', ruleOutcome: 'not-evaluated', reason: 'collector-error' };
}
if (evidence.integrity !== 'valid' || evidence.observedAt === undefined) {
return { evidenceState: 'unknown', ruleOutcome: 'not-evaluated', reason: 'missing-evidence' };
}
const age = NOW - evidence.observedAt;
if (age > MAX_AGE) {
return { evidenceState: 'stale', ruleOutcome: evidence.value ? 'historical-pass' : 'historical-fail', age };
}
return { evidenceState: 'fresh', ruleOutcome: evidence.value ? 'pass' : 'fail', age };
}
const results = samples.map(sample => ({ id: sample.id, ...evaluate(sample) }));
const badAlgorithmPassed = samples.filter(sample => sample.value === true).map(sample => sample.id);
console.log(JSON.stringify({ results, badAlgorithmPassed }, null, 2));
if (results[0].ruleOutcome !== 'pass') throw new Error('fresh evidence should pass');
if (results[1].evidenceState !== 'stale') throw new Error('stale evidence was hidden');
if (results[2].evidenceState !== 'unknown') throw new Error('missing evidence was misclassified');
if (results[3].evidenceState !== 'error') throw new Error('collector error was hidden');
if (badAlgorithmPassed.length !== 3) throw new Error('negative model changed unexpectedly');预期 fresh-pass 为 fresh/pass,stale-pass 为 stale/historical-pass,缺失项为 unknown/not-evaluated,采集失败为 error/not-evaluated。错误算法却把 fresh、stale 和 collector-error 三项都算作通过,因为它只看旧 value: true。这正是生产门户最危险的假绿色。
接入真实项目时,把 NOW 替换为评估任务注入的时钟,把 MAX_AGE 放入版本化规则,将 integrity 拆为 schema、签名、主体映射和采集状态。每次评估保存输入证据 ID 与规则版本;重放同一输入必须得到同一结果。这个本地模型证明状态分支,不代表任何 SaaS 租户、生产集群或第三方扫描器已经连通。
豁免改变适用性,不改变原始事实
豁免有两种合理语义:规则对某类实体永久不适用;实体暂时无法满足规则并获限时风险接受。前者应优先写进 appliesTo 或实体分类,避免每个对象单独申请;后者需要申请人、批准人、理由、补偿控制、关联风险、开始与失效条件、复核人。
豁免不能把 fail 改成 pass。原始评估仍保存 fail,聚合层显示 exempted 并从适用分母中排除或按组织政策单独统计。Cortex 的 rule exemptions 允许任意用户申请,只有 Admin 或拥有 Configure Scorecard exemptions 权限的角色能够批准、拒绝或撤销;Scorecard 还能开启自动批准,管理员自行申请也会自动通过。这些产品默认值意味着关键规则上线前必须关闭自动批准,并用独立批准角色消除自批路径,不能只在流程文档里要求职责分离。
限时豁免到期必须自动恢复规则,提前通知 owner 和批准人。永久豁免每个治理周期复核,因为实体生命周期、暴露面和平台能力会变化。自动批准只适合低风险、可验证分类;关键安全与恢复规则不应让实体 owner 自批。申请和批准使用不同权限,撤销记录不可删除。
聚合报表同时显示 applicable、passed、failed、stale、unknown、error、exempted。如果只显示“排除豁免后的通过率”,团队可以通过大量申请豁免改善数字。高层趋势应展示豁免数量、年龄、原因分布和即将到期量。
趋势必须能区分改善与口径变化
一个分数从 70 升到 85,可能是服务修复,也可能是失败实体被退役、规则窗口放宽、分母过滤改变或采集器坏掉。趋势事件至少要保存规则版本、实体集合版本、证据状态分布、豁免集合和计算策略。规则升级时新旧版本影子并跑,分别展示,不要无痕覆盖历史。
推荐观察四类趋势:
结果趋势:fresh pass/fail 的数量和比例。数据质量趋势:stale、unknown、error 的数量、年龄和来源。行动趋势:从失败到认领、修复、复验的周期。
口径趋势:规则、窗口、过滤器、权重和豁免策略变更。
分母必须固定解释。按实体看通过率时,标出新加入、退役和被排除实体;按规则看时,标出规则新增、升级与删除。团队比较应先按风险、生命周期和服务规模分层,不能拿试验库与高流量生产服务直接排名。排名会驱动局部优化,成熟度层级更适合表达“具备哪些能力”。
Cortex 的 Scorecard 评估页面提供规则进度、趋势、豁免和改进行动入口,官方 evaluate guide 可用于核对目标租户报表字段;但团队仍要确认报表如何处理 stale、error 和分母变化,产品有趋势图不等于数据口径天然可信。
反游戏化要从设计上减少捷径
人会优化被测量的目标。规则检查 README 存在,就会出现空 README;检查告警链接,就会出现指向无接收人的占位规则;检查漏洞数,就可能通过关闭扫描器保持旧零值。反游戏化不是指责团队,而是让规则尽量验证结果与证据链。
可采用以下设计:
检查 owner 时解析有效 Group、活跃成员和接管渠道,而非字符串非空。检查构建时绑定当前生产 digest,而非任意一次成功流水线。检查 SLO 时验证指标有近期样本、窗口完整且关联生产流量。
检查恢复能力时读取最近一次演练结果和恢复点,而非存在运行手册链接。检查安全时要求采集器健康、扫描范围匹配和结果 fresh。对关键规则采用不可抵消门槛,避免十个文档分数盖过一个高危漏洞。
采集器也要防作弊。服务团队不应直接写最终 Scorecard 属性;它们只能提交可验证事实,由受控适配器规范化。Webhook 校验签名与重放标识,API 拉取使用只读身份,CI 证据绑定工作流身份与制品证明。手工证据标为 manual,保留审批和较短窗口。
定期抽样比无限加规则有效。抽取一批绿色实体,从门户结果追到原始证据、当前制品和运行系统;再抽取 error、unknown 与长期豁免,确认有 owner 和行动。若绿色无法重放,立即降低规则信任等级,而不是继续用它阻断发布。
改进行动必须由失败证据直接生成
失败如果只出现在平台首页,很快会成为背景噪声。每条关键规则要定义行动模板:标题含实体和规则,正文带原因码、证据年龄、当前值、期望值、修复手册、复验方式和截止策略;owner 来自目录关系;平台故障则分派给采集器 owner。
行动状态可采用 open -> acknowledged -> fixing -> verifying -> closed。关闭只能由新的 fresh 证据或批准豁免触发,不能由用户手工点绿。证据恢复但规则仍 fail 时回到 fixing;采集器 error 时进入 verifying 并通知平台团队。重复评估使用 (entityRef, ruleId, ruleVersion) 幂等键更新同一行动,避免每轮创建新工单。
不是所有失败都要建工单。关键门槛立即建行动并可能阻断;普通标准进入团队待办;实验规则只观察。通知按状态转换触发,不按每次评估触发:fresh-pass 变 fresh-fail、fresh 变 stale、首次 error、豁免将到期才需要通知。恢复通知同样重要,避免工单保持错误状态。
改进计划应聚焦有限规则和明确时间箱。Cortex 将 Scorecard 与 Initiative 连接,用目标、责任和截止推动失败规则;Port、OpsLevel 等产品也有各自的成熟度与行动机制。产品功能名称会演进,落地时始终检查三个不变量:目标规则版本是否固定,参与实体集合是否可解释,完成是否由新证据证明。
排障从状态来源而不是总分开始
总分异常时,先分辨规则逻辑、证据采集、身份映射、窗口还是豁免。下面的顺序能减少盲目重跑:
| 现象 | 第一证据 | 常见原因 | 修复与再验证 |
|---|---|---|---|
| 扫描已成功但仍 unknown | subject 与实体映射 | 仓库改名、namespace 不同、digest 缺失 | 修复外部键,重放同一证据 |
| 页面一直 fresh | observedAt 与 receivedAt | 用同步时间冒充观测时间 | 改事件时间,等待窗口转 stale |
| 大量实体同时 error | collector latestAttempt | Token 到期、配额、API 变更 | 换短期凭证,先小批量恢复 |
| 规则升级后全体下降 | ruleVersion 与影子结果 | 条件或分母变化 | 并列新旧口径,不回写历史 |
| 豁免到期仍不评估 | exemption 审计与缓存 | 过期任务失败、缓存未失效 | 撤销缓存并触发重评 |
| 团队得分升高但风险未降 | 状态分布与分母 | 删除失败实体、增加低价值规则 | 固定队列和关键门槛重算 |
采集器恢复时不要立刻全量轰击上游。按来源限流,优先关键生产实体,使用 checkpoint 继续,成功后再扩大并发。重试要区分认证、限流、临时网络、永久解析错误;无界重试只会增加成本并推迟 error 暴露。
规则引擎排障需要保存可重放输入。给定规则版本、证据集合、评估时刻和豁免集合,应得到相同结果。若插件只保存最终分数,至少在外部证据层保留这些引用。重放环境使用脱敏样本,不能把真实漏洞详情或人员数据复制到开发机。
权限与敏感数据要沿证据链收紧
Scorecard 管理权限至少分为:查看汇总、查看受限证据、创建草稿规则、发布规则、管理采集器、申请豁免、批准豁免、触发重评和导出审计。规则发布可能影响发布门禁,不能与普通页面编辑共用权限。采集器服务身份只读上游、只写指定证据类型;规则引擎只读证据、只写评估;UI 不能持有上游 Token。
产品原生角色不一定自动满足这组职责分离。Port 当前以受保护的 Scorecard、Rule、Rule Result blueprints 表达模型,规则结果由平台生成且不能直接创建、删除或修改;但拥有 Scorecard blueprint register 权限的用户可以在其他 blueprint 上创建 Scorecard 并连带创建规则,授权评审必须把这条隐含能力算进去。Cortex 则把 Edit Scorecards、View Scorecards、Configure Scorecard exemptions 等权限分开。正向实验用规则编辑者发布一条草稿、采集器写一份合成证据、审批者批准豁免,三者审计主体应不同;反向实验让任一身份越权执行另外两种动作,预期为拒绝且规则、证据、豁免版本均不变化。结束后删除合成实体与草稿,撤销临时角色绑定,并从审计导出确认没有残留授权。
安全扫描、成本、事故和人员值班证据可能高度敏感。汇总页面只显示风险级别和修复入口,原始细节留在源系统并执行其权限。证据存储对主体标识做最小化,禁止存源码片段、Secret、完整日志和个人绩效标签。日志记录 evidence ID 与原因码,不记录原始响应。
团队级 Scorecard 不应用于个人排名。owner 关系用于路由责任,不是绩效归因;跨团队比较必须说明风险与服务组合。数据保留按审计和隐私要求制定:当前评估、趋势聚合、原始证据和访问日志采用不同保留期。删除实体时仍需保留不可识别的趋势或审计墓碑,并撤销导出缓存。
托管平台接入前核对 SSO、细粒度权限、审计导出、数据驻留、加密、支持访问、备份、删除和退出 API。真实凭证放入受管 Secret 系统,通过短期身份注入;试验结束撤销安装与 Token,并确认 webhook、缓存、导出和自动化任务都被清理。
容量与成本不能靠降低可信度节省
Scorecard 的主要成本项是上游 API 调用、采集计算、证据存储、规则评估、历史趋势、通知与人工修复。近似计算量是 适用实体数 × 规则数 × 评估频率,但共享证据能显著降低扇出:一次构建证明可以被多个规则读取,不应每条规则各调一次 CI API。
采集层按证据类型批量同步并缓存原始摘要,规则层做本地纯计算。Webhook 提供低延迟变化,周期全量修复漏事件;ETag 和游标减少配额。评估只在实体、规则、证据或豁免变化时增量执行,再用低频全量重算校验。历史存储保存状态转换而非每轮相同快照,趋势需要时按日或按治理周期聚合。
成本优化不能把 error 隐藏为旧 pass,也不能无限延长窗口。应先减少重复拉取、无变化写入、低价值规则和通知风暴。规则价值可用“发现的真实问题、行动完成率、平均修复时间、误报率、每次评估成本”衡量。长期无人查看且不产生行动的规则应下线,而不是继续消耗配额。
托管产品还可能按实体、座席、自动化执行、集成或高级报表计费;自托管则承担数据库、队列、计算、备份和升级。试点时记录每千实体每轮采集调用、证据字节、评估耗时和工单量,用实际基线估算。一个拥有数百条规则却没有修复能力的组织,只是购买了更昂贵的红色仪表盘。
接进项目时让 CI 产事实而不是产分数
项目流水线不应调用门户管理 API 把 score=100 写进去。CI 掌握的是一次提交、构建、测试和制品签名的事实,Scorecard 掌握的是跨来源规则。流水线只发布不可变证据,规则引擎再决定它是否适用于当前生产制品。这样修改规则不需要重跑历史构建,也不会让仓库管理员直接伪造最终分数。
一个可落地的 CI 接入分为四步。构建开始时记录仓库不可变 ID、commit 和工作流身份;产物生成后计算 digest;测试与扫描步骤分别输出机器可读摘要;发布步骤把环境、部署 ID 和 digest 发给证据适配器。适配器验证工作流的 OIDC 身份或 webhook 签名,将各摘要写成独立 evidence,再由 artifactDigest 关联。不能使用分支名或镜像 tag 作为唯一关联,因为它们都可移动。
下面是一段产品中立的证据提交请求。端点只接受 CI 身份,调用方不能传 pass、Scorecard 名称或豁免字段:
curl --fail-with-body \
--request POST 'https://example.com/evidence/v1/builds' \
--header "Authorization: Bearer <short-lived-token>" \
--header 'Content-Type: application/json' \
--data '{
"repositoryId": "repo:your-org/checkout-api",
"commit": "0123456789abcdef",
"artifactDigest": "sha256:example",
"workflowRef": "build-and-test@default",
"result": "succeeded",
"signatureVerified": true
}'预期响应是 202 Accepted 与 evidence ID,表示已接收待规范化事实,不表示规则通过。重复提交相同 (source, workflowRunId, artifactDigest) 应返回同一 ID;主体缺失返回 422;签名或身份不符返回 401/403;队列超载返回可重试状态并带退避建议。CI 只对网络和限流做有界重试,不因门户暂时不可用而重跑昂贵测试。证据提交最终失败时,构建可以成功,但对应发布门禁应看到 unknown 或 error,不能继承旧绿色。
应用仓库还需要一个规则适用性文件,但不应复制全组织规则。它只声明分类事实,例如生命周期、数据等级、互联网暴露和豁免申请引用;中心标准根据分类选择规则。仓库 PR 同时运行 schema 校验和预览评估,告诉作者哪些规则将新增或失效。预览不写生产结果,也不允许用 PR 修改中心规则版本。
发布系统在真正部署前查询当前 digest 的关键门槛,而不是读取实体总分。响应应列出每条关键规则的 evidenceState、ruleOutcome、证据年龄、豁免状态和原因码。门禁策略可以拒绝 fresh-fail、stale、unknown、error,却给出不同处置:业务失败由服务团队修,采集错误由平台团队修,紧急放行走受审计的风险接受。总分接口只适合浏览与趋势,不能承担安全授权。
本地开发不应要求工程师持有生产证据写权限。开发环境使用内存或临时 SQLite 存储、固定假身份和合成实体;测试结束删除数据库。共享测试环境按 namespace 隔离证据,防止相同仓库和 digest 污染生产。CI 日志不回显短期令牌和完整证据响应,失败时只打印 evidence ID、状态码和脱敏原因。
聚合结果要保留不可抵消的风险
一个实体有几十条规则时,需要摘要,但摘要不能损坏语义。最简单的百分比 passed / total 至少要明确 total 只含 applicable 且 fresh 的已评估规则;stale、unknown、error 和 exempted 各自进入旁路计数。把它们放进分母当失败会混淆数据质量与工程风险,把它们排除后又不展示则会形成虚高。因此页面应同时显示“当前可评估覆盖率”和“已评估规则通过率”。
可评估覆盖率可以表达为:freshEvaluated / applicable。通过率表达为:freshPassed / freshEvaluated。假设十条适用规则中六条 fresh,其中五条通过,两条 stale、一条 unknown、一条 error,则覆盖率是六成,通过率是六分之五;不能只显示后者的高比例。被批准豁免的规则从 applicable 排除,但在豁免计数中可见。没有适用规则的实体显示 not-applicable,不能显示满分。
严重度不宜直接变成任意权重。关键规则采用门槛:只要任意一条 fresh-fail,最高成熟度就被封顶;stale、unknown 或 error 则进入“不可证明”状态。建议规则可以计分,但权重由标准版本固定,变更需影子比较。层级模型要满足单调性:通过更高层规则不能跳过低层必备能力。Port 官方模型就是按层级逐级满足的示例之一,但各产品的计算方式不同,迁移时必须重算而不是搬运颜色。
团队和系统聚合也不能简单平均实体百分比。一个拥有一个试验库的团队和维护数十个关键服务的团队权重不同;按规则次数加权又会让规则多的实体主导。更稳妥的看法是分层展示:关键生产实体的门槛状态、各风险域覆盖率、状态分布和改进周期。确需一个组织摘要时,先按风险等级分组,再按固定实体集合计算,并公开分母。
标准之间可能重复消费同一证据。例如构建来源同时支持供应链、发布和可追溯性规则。共享证据不等于重复加分,治理团队要画出“风险 -> 规则 -> 证据”映射,识别一份事实支撑多个相关规则造成的表面繁荣。若所有绿色都依赖同一采集器,这个采集器是集中风险,必须有自己的健康规则和 SLO。
证据平台也需要独立 SLO
门户可访问、规则任务按时启动,都不能证明 Scorecard 可用。证据链至少监控采集成功、规范化延迟、身份映射、评估延迟、状态转换和行动投递。SLO 目标按关键性分层,关键生产门槛优先于建议型文档规则。
采集新鲜度衡量上游事实发生到 evidence 可查询的延迟;评估延迟衡量 evidence 接收后到实体结果更新的延迟;映射成功率衡量证据主体能解析到唯一实体和制品的比例;规则错误率统计执行超时、解析异常和非确定结果;状态老化队列统计即将 stale 和已经 stale 的证据;通知投递率统计状态转换到 owner 收到行动的成功率。
告警必须区分局部与系统性故障。单实体 unknown 通知服务 owner;某来源 error 比例突增通知集成 owner;全部来源评估延迟上升通知平台值班;多个来源同时失败则检查身份、网络、队列和数据库。告警事件携带 source、ruleVersion、最后成功时间、积压量和影响实体数,不附敏感原始证据。
容量保护要保证失败可见。队列达到上限时,优先处理关键实体与状态转换,低优先级全量重算延后;拒绝的新任务进入明确 error 或 backlog 指标,不能静默丢弃。每个来源设置并发、速率和超时预算,防止一个慢扫描器占满 worker。规则引擎使用确定性超时,超时结果为 error;不能在超时后返回缓存 pass 而不标 stale。
恢复演练至少模拟四条链。撤销上游 Token,确认 latestAttempt 变 error 且旧证据继续按年龄老化;暂停 worker,确认 backlog 与评估延迟告警;提交无法映射的 digest,确认 unknown 而不是绑定最近制品;恢复后确认新证据推进、工单更新且没有重复通知。演练数据使用合成实体,清理时撤销临时凭证并删除队列消息与证据。
证据平台的可用性目标不应高于所有上游能共同提供的事实。对于无法实时获取的系统,门禁可以要求发布事件主动附带证明,而不是高频轮询。若平台故障会阻断全部发布,需要设计受控应急模式:只允许特定批准人对特定 digest、环境和短窗口放行,记录当时缺失状态,恢复后自动补评;绝不能提供全局“忽略 Scorecard”开关。
从试点到长期采用要分开扩展规则和实体
一次性导入全部服务和规则会让团队同时面对身份错误、证据空洞与大量红色,无法判断平台是否可信。试点先选少量不同类型实体:一个活跃生产服务、一个内部工具、一个库和一个待退役组件。规则只选 owner 可解析、当前制品构建证明、关键告警入口三类,每条都能找到原始证据和修复人。
第一阶段只观察数据质量,不设门禁。目标是让 fresh 覆盖稳定,unknown 与 error 有明确 owner,窗口经过真实变化验证。第二阶段发布建议性标准,把失败转成行动并测量误报、修复周期和通知噪声。第三阶段只将少数关键规则接入发布门禁,同时提供应急审批与恢复后补评。第四阶段才扩展风险域和实体集合。
每次扩展只改变一个维度:增加实体但保持规则集,或增加规则但固定实体队列。这样趋势异常可以归因。新规则必须提供失败样本、成功样本、unknown/error 样本和成本估算;新来源必须通过凭证轮换、限流、数据删除和退出演练。达不到条件的规则留在草稿或影子模式。
采用率不能用登录次数衡量。有效指标是有多少活跃实体具备可解析 owner,有多少关键规则拥有 fresh 证据,失败行动被认领和关闭的比例,豁免是否按时复核,以及团队能否从结果追到原始事实。页面访问量高但 error 长期无人处理,说明门户是展示层而非治理系统。
标准需要淘汰机制。风险已由平台默认能力消除时,规则可以退役;证据来源不再维护、误报高或长期不产生行动时,应重写或删除。退役规则先停止新行动,保留历史结果和版本说明,再从当前聚合中移除。不能通过删除不利规则美化趋势,口径变化必须在时间线上可见。
退出某个托管产品时,先导出规则版本、实体过滤器、原始证据引用、状态历史、豁免和行动映射,在替代引擎中并行重算。比较的不是总分颜色,而是每个实体、每条规则的状态与原因码。替代系统达到差异预算后,冻结旧系统写入,迁移剩余事件,撤销集成,再按保留策略删除租户数据。缺少规则和证据可移植性时,平台迁移成本会远高于页面重建。
规则升级、回滚与清理
规则修改采用不可变版本。新版本先在影子模式运行:不影响门禁,只比较新旧结果、状态分布、误报和成本;确认后切换当前版本,保留回滚指针。回滚恢复旧规则版本与聚合口径,但不删除新版本产生的证据和审计。若新规则触发了外部工单,回滚时应标记为策略撤回,而不是假装从未发生。
采集器升级同样需要影子比较。新旧适配器对同一批原始响应生成证据,比较主体映射、观测时间、值和完整性;超过差异预算就停止切换。数据库迁移先备份规则、证据索引、豁免、行动和游标,并验证恢复。插件卸载前导出规则与审计,移除后端路由和权限,撤销上游凭证,清理 webhook 与队列。
本地试验清理顺序是:停止评估任务,停止采集器,删除试验规则和证据,清除趋势与行动,撤销 Token,最后删除试验实体。共享环境不能直接清空数据库,应按 test-* namespace 或实验标签删除,并先预览数量。清理后查询试验实体应返回不存在,采集器不再调用上游,Secret 系统显示凭证已撤销。
长期治理需要规则 owner、证据 owner 和行动 owner 三种责任。标准团队维护风险意图和规则版本;平台团队维护采集、状态机、权限与容量;服务团队处理失败并维护适用元数据。治理会议不讨论“为什么不是满分”,而应看 stale/error 是否下降、关键失败是否关闭、豁免是否到期、规则是否产生真实改进,以及评估成本是否与风险价值匹配。
一个可信的 Scorecard 最终能回答:这条规则为什么适用于该实体,哪份证据针对哪个制品,证据现在是否新鲜,采集是否健康,规则哪个版本得出什么结果,豁免是否改变适用性,失败由谁在何处修复,以及新证据怎样证明行动已经完成。只要其中任意一问需要凭页面颜色猜测,总分就还没有资格进入发布决策。
