v7 mech-2: split scorecard into code/production/product tables

(cherry picked from commit 67b7f5e42d001b655aef3e04e1d3c1d98166b98f)

Reconcile-port: handler_admin.py hunks redirected into cloud/server.py _audit_scorecard (target keeps 6-mixin split; scorecard module itself applied cleanly).
This commit is contained in:
host committed 2026-11-03 02:49:27 +00:00
1 parent 19fe4fc7e1
commit f8a6a9d258
3 files changed
+526 -33

No files matched your search

+209 -33
View File
@@ -67,6 +67,16 @@
每个维的证据字段见各 _score_* 函数的 docstring。
调用方必须带来源、时间、检查名;本模块不自动从测试文件、
摘要文档、PR 描述里拼凑分数。
三张表拆分(v7 机制第 2 项,见 _SCORERS 后的注释):
build_code_scorecard:代码证据 4 维,不含生产项;
build_production_scorecard:生产证据 3 维,缺项不拉低代码表,
可选 user_side_items 占位证据(pending 即挡住生产表 overall);
build_product_scorecard:全部 7 维 + 真实产品轮门禁,无真实
产品轮证据时恒 not_pass;这只是评分卡输入结构拆分,
不代表任何产品功能已落地。build_scorecard 的返回 dict
同时带 code/production/product 三个子对象,GET /audit-scorecard
端点返回的示例卡因此也带这三个子对象(端点已接线)。
"""
from __future__ import annotations
@@ -605,46 +615,79 @@ def verify_signature(evidence_bytes, signature, verifier=None):
return {"verified": False, "reason": "verifier_rejected"}
def build_scorecard(evidence, thresholds=None, rubric_version=None,
baseline_head=None, evidence_rows=None):
"""算出 7 维评分卡。evidence 缺键按空证据处理(该维记 None)。
# ---------------------------------------------------------------------------
# 三张表拆分(v7 机制第 2 项;仅评分卡输入结构拆分,不代表产品功能落地)
#
# 维分配(写死,不按调用方喜好重分;理由见各表注释):
# - 代码表 CODE_DIMENSION_IDS:delivery_quality、gate_audit、
# arch_contract、test_coverage。证据全部来自代码侧:交付指标、
# 门禁/审计、架构契约、测试覆盖——本地/代码审计即可验证,
# 不需要生产部署、真实 App、第二主机或用户侧拍板。
# - 生产表 PRODUCTION_DIMENSION_IDS:security_ledger、cost_quota、
# ops_scale。证据只在生产侧存在:分支保护与账本可信、
# 成本/配额真实记录与硬切断、CI/部署/回滚/HA/规模。
# 生产证据缺失时该表维记 None、总体 not_pass,绝不拉低代码表。
# - 产品表 PRODUCT_DIMENSION_IDS:沿用全部 7 维,但加一道真实
# 产品轮门禁(见 build_product_scorecard)。产品不是第 8 维,
# 也不是新指标:产品表 = “在真实产品轮里复测同一 7 维”。
# 没有真实产品轮证据时,产品表全部维记 None、总体 not_pass,
# 绝不把代码表/生产表的分数冒充成产品分数,也绝不声称
# 产品功能已落地。
#
# 独立性规则(与 build_scorecard 的旧规则一致,各表各自独立):
# - 每张表各自 overall 独立:本表全部维 >=9 才 pass,任一维
# <9 或 None 即 not_pass;average_score 只作参考,绝不作达标依据。
# - 代码表不含生产项:生产证据缺失、B/C 用户侧项 pending,
# 都不影响代码表的 overall(生产表才被挡住)。
# - 生产表与第 7 项 user_side_items 的配合是可选参数,不依赖
# 第 7 项的代码:调用方不传 user_side_items 时只按生产证据计分;
# 传了且其中有 pending 项时,生产表 overall 被强制 not_pass
# (维分数照常展示,不篡改),并在 missing_items 里逐项列出。
# - 阈值:三张表共用 DEFAULT_THRESHOLDS,默认值一律
# source="test_default_unapproved",未拍板;生产阈值须用户
# 拍板后由调用方显式传入 user_approved 阈值。
# ---------------------------------------------------------------------------
rubric_version:调用方显式传入的标尺版本号(见
cloud/scorecard_rubric.py)。本函数不查库、不定版本:
未传入时卡片记 rubric_version=None,表示“未锚定到版本化
标尺”,分数仍按 thresholds(或默认值)算出,但不具备
跨标尺可比性。计分规则、7 维定义、达标规则一字未改。
CODE_DIMENSION_IDS = [
"delivery_quality",
"gate_audit",
"arch_contract",
"test_coverage",
]
baseline_head:调用方显式传入的当前 main 头(机器字段,不是
docstring 里的文字说明)。不传入或传入空串时归一化为 None,
并在 missing_items 末尾记 baseline_head_missing;本函数绝不
编造、绝不探测版本控制状态。baseline 缺失本身不改写 overall
语义(overall 仍按 7 维算),漂移与否由 check_baseline_stale
单独判定,调用方不得只看 overall 就把旧分当现分用。
PRODUCTION_DIMENSION_IDS = [
"security_ledger",
"cost_quota",
"ops_scale",
]
evidence_rows(v7 机制第 4 项):调用方显式传入的证据行
列表(每行 dict),本函数只据此算 evidence_ledger_head 与
evidence_row_ids(见 compute_evidence_ledger),不写库、
不验签、不改变 overall。无证据行时 head 为 None、
row_ids 为空列表,不得编造。
"""
PRODUCT_DIMENSION_IDS = list(DIMENSION_IDS)
USER_SIDE_DONE_STATUSES = {
"done", "approved", "complete", "resolved", "user_approved",
}
PRODUCT_ROUND_REQUIRED_FIELDS = (
"real_product_round",
"real_app_installed",
"real_user_flow_verified",
"product_round_as_of",
)
def _build_table(dim_ids, evidence, th):
"""按给定维子集算一张表(纯函数;overall 与平均分规则同旧卡)。"""
evidence = evidence or {}
th = thresholds or {}
if baseline_head is not None and baseline_head == "":
baseline_head = None
ledger = compute_evidence_ledger(evidence_rows)
dimensions = []
missing_all, untested_all = [], []
for dim_id, _name in DIMENSIONS:
for dim_id in dim_ids:
result = _SCORERS[dim_id](evidence.get(dim_id) or {}, th)
dimensions.append(result)
missing_all.extend("%s: %s" % (dim_id, m) for m in result["missing_items"])
untested_all.extend("%s: %s" % (dim_id, u) for u in result["untested_items"])
if baseline_head is None:
missing_all.append("baseline_head_missing")
scores = [d["score"] for d in dimensions if d["score"] is not None]
average = _round1(sum(scores) / len(scores)) if scores else None
# 总体规则钉死:平均分绝不能作为达标依据。
# 钉死:平均分绝不能作为达标依据(各表各自独立判断)。
overall_pass = all(d["score"] is not None and d["score"] >= PASS_SCORE
for d in dimensions)
return {
@@ -654,14 +697,10 @@ def build_scorecard(evidence, thresholds=None, rubric_version=None,
"overall": "pass" if overall_pass else "not_pass",
"overall_pass": overall_pass,
"pass_score": PASS_SCORE,
"baseline_head": baseline_head,
"missing_items": missing_all,
"untested_items": untested_all,
"thresholds_source": thresholds_source(th),
"thresholds": _effective_thresholds(th),
"rubric_version": rubric_version,
"evidence_ledger_head": ledger["head"],
"evidence_row_ids": ledger["row_ids"],
}
@@ -711,7 +750,144 @@ def check_baseline_stale(scorecard, current_main_head):
"reason": reason,
"usable_as_pass_basis": not stale,
"overall_usable_as_pass_basis": not stale,
"overall_must_not_be_used_as_pass_basis": stale, }
"overall_must_not_be_used_as_pass_basis": stale,
}
def build_code_scorecard(evidence, thresholds=None):
"""代码表:仅 delivery_quality、gate_audit、arch_contract、test_coverage。
不含任何生产项;生产证据缺失、用户侧 B/C 项 pending 都不影响
本表 overall。本表全部 4 维 >=9 才 pass。
"""
return _build_table(CODE_DIMENSION_IDS, evidence, thresholds or {})
def _normalize_user_side_items(user_side_items):
"""把 user_side_items 占位证据归一化为 [{"id":..., "status":...}]。
接受:None(= 未提供,不臆断 pending)、dict{id: status}、
list[dict{id, status}]、list[str](字符串只有 id、无状态,
按 pending 计——状态未知绝不算 done)。
"""
if user_side_items is None:
return []
if isinstance(user_side_items, dict):
return [{"id": k, "status": v} for k, v in user_side_items.items()]
normalized = []
for item in user_side_items:
if isinstance(item, dict):
normalized.append({"id": item.get("id"),
"status": item.get("status")})
else:
normalized.append({"id": item, "status": "pending"})
return normalized
def build_production_scorecard(evidence, thresholds=None, user_side_items=None):
"""生产表:仅 security_ledger、cost_quota、ops_scale。
user_side_items 为可选占位证据(不依赖第 7 项的代码):
不传时只按生产证据计分;传了且有 pending 项时,维分数照常
展示,但 overall 被强制 not_pass,并在 missing_items 里逐项
列出 pending 项。生产证据缺失本身已使本表 not_pass。
"""
table = _build_table(PRODUCTION_DIMENSION_IDS, evidence, thresholds or {})
items = _normalize_user_side_items(user_side_items)
pending = [it for it in items
if it.get("status") not in USER_SIDE_DONE_STATUSES]
table["user_side_items"] = items
table["user_side_pending"] = pending
table["user_side_blocked"] = bool(pending)
if pending:
table["missing_items"] = list(table["missing_items"]) + [
"user_side_items_pending: %s" % it.get("id") for it in pending]
table["overall"] = "not_pass"
table["overall_pass"] = False
return table
def build_product_scorecard(evidence, thresholds=None, product_evidence=None):
"""产品表:全部 7 维,但须先过真实产品轮门禁。
product_evidence 须同时满足:real_product_round、real_app_installed、
real_user_flow_verified 均为真,且 product_round_as_of 非空。
门禁不过时,全部维记 None(unknown)、总体 not_pass,绝不把
代码表/生产表分数冒充成产品分数;本函数不声称任何产品功能已落地。
门禁通过时,7 维按与 build_scorecard 相同的 scorer 计分。
"""
product_evidence = product_evidence or {}
missing_fields = [f for f in PRODUCT_ROUND_REQUIRED_FIELDS
if not product_evidence.get(f)]
if missing_fields:
dimensions = []
missing_all = []
for dim_id in PRODUCT_DIMENSION_IDS:
missing = ["product_round_evidence_missing: %s" % f
for f in missing_fields]
dimensions.append(_result(dim_id, None, [], missing, []))
missing_all.extend("%s: %s" % (dim_id, m) for m in missing)
return {
"dimensions": dimensions,
"average_score": None,
"average_is_not_pass_criterion": True,
"overall": "not_pass",
"overall_pass": False,
"pass_score": PASS_SCORE,
"missing_items": missing_all,
"untested_items": [],
"thresholds_source": _thresholds_source(thresholds or {}),
"product_gate_passed": False,
"product_round_missing_fields": missing_fields,
}
table = _build_table(PRODUCT_DIMENSION_IDS, evidence, thresholds or {})
table["product_gate_passed"] = True
table["product_round"] = {
"real_product_round": True,
"real_app_installed": True,
"real_user_flow_verified": True,
"product_round_as_of": product_evidence.get("product_round_as_of"),
}
return table
def build_scorecard(evidence, thresholds=None, rubric_version=None,
baseline_head=None, evidence_rows=None,
user_side_items=None, product_evidence=None):
"""算出 7 维评分卡。evidence 缺键按空证据处理(该维记 None)。
兼容:旧返回字段(dimensions/average_score/overall 等)语义不变,
仅新增 code/production/product 三个子对象(见三张表拆分注释):
code=build_code_scorecard,production=build_production_scorecard
(user_side_items 可选占位),product=build_product_scorecard
(无真实产品轮证据时恒为 not_pass)。新增键不改变旧字段的值。
另新增 rubric_version(见 cloud/scorecard_rubric.py)、
baseline_head(机器字段,见 check_baseline_stale)、
evidence_ledger_head 与 evidence_row_ids(见
compute_evidence_ledger)三个机制字段;无证据行时头为 None。
"""
evidence = evidence or {}
th = thresholds or {}
if baseline_head is not None and baseline_head == "":
baseline_head = None
ledger = compute_evidence_ledger(evidence_rows)
report = _build_table(DIMENSION_IDS, evidence, th)
if baseline_head is None:
report["missing_items"] = list(report["missing_items"]) + [
"baseline_head_missing"]
report["baseline_head"] = baseline_head
report["rubric_version"] = rubric_version
report["evidence_ledger_head"] = ledger["head"]
report["evidence_row_ids"] = ledger["row_ids"]
report["thresholds"] = _effective_thresholds(th)
report["code"] = build_code_scorecard(evidence, thresholds)
report["production"] = build_production_scorecard(
evidence, thresholds, user_side_items)
report["product"] = build_product_scorecard(
evidence, thresholds, product_evidence)
return report
def example_scorecard_as_of_2026_10_31():