diff --git a/cloud/audit_scorecard.py b/cloud/audit_scorecard.py index 656e709..1e35e3b 100644 --- a/cloud/audit_scorecard.py +++ b/cloud/audit_scorecard.py @@ -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(): diff --git a/cloud/server.py b/cloud/server.py index cc9ccdf..a5beb18 100644 --- a/cloud/server.py +++ b/cloud/server.py @@ -8592,6 +8592,9 @@ pid 须为整数、address 须为字符串,否则 400(注册表里的 证据传给 cloud/audit_scorecard.py 的 build_scorecard(), 端点本身绝不从测试文件、摘要文档、PR 描述里拼凑分数。 阈值仍为测试默认值(``test_default_unapproved``),未拍板。 + 响应同时带 ``code`` / ``production`` / ``product`` 三个子对象 + (三张表拆分,见 cloud/audit_scorecard.py;示例证据下三表 + 均为 not_pass,产品表因无真实产品轮证据而恒 not_pass)。 """ try: from cloud.audit_scorecard import ( @@ -8608,6 +8611,26 @@ pid 须为整数、address 须为字符串,否则 400(注册表里的 "evidence_as_of": "2026-10-31", "evidence_kind": "historical_example_snapshot", "not_current_system_score": True, + # 三张表兜底形状:模块不可用时三表均为 not_pass。 + "code": {"dimensions": [], "average_score": None, + "average_is_not_pass_criterion": True, + "overall": "not_pass", "overall_pass": False, + "missing_items": ["audit_scorecard_module_unavailable"], + "untested_items": []}, + "production": {"dimensions": [], "average_score": None, + "average_is_not_pass_criterion": True, + "overall": "not_pass", "overall_pass": False, + "missing_items": ["audit_scorecard_module_unavailable"], + "untested_items": [], + "user_side_items": [], + "user_side_pending": [], + "user_side_blocked": False}, + "product": {"dimensions": [], "average_score": None, + "average_is_not_pass_criterion": True, + "overall": "not_pass", "overall_pass": False, + "missing_items": ["audit_scorecard_module_unavailable"], + "untested_items": [], + "product_gate_passed": False}, } self._send_json(200, report) diff --git a/cloud/tests/test_scorecard_mech_tables.py b/cloud/tests/test_scorecard_mech_tables.py new file mode 100644 index 0000000..6200c79 --- /dev/null +++ b/cloud/tests/test_scorecard_mech_tables.py @@ -0,0 +1,294 @@ +"""v7 机制第 2 项:三张表拆分定向测试。 + +维分配(与 cloud/audit_scorecard.py 的注释一致): +- 代码表:delivery_quality、gate_audit、arch_contract、test_coverage + (代码证据,不含生产项)。 +- 生产表:security_ledger、cost_quota、ops_scale(生产证据)。 +- 产品表:全部 7 维 + 真实产品轮门禁;无真实产品轮证据时恒 not_pass, + 这只是评分卡输入结构拆分,不代表任何产品功能已落地。 + +钉住的规则: +- 代码表 overall 独立:生产证据缺失、B/C 用户侧项 pending, + 都不拉低代码表。 +- 生产表缺项不拉低代码表;生产表 overall 独立,平均分不作依据。 +- 三张表各自 average_is_not_pass_criterion=True。 +- 阈值默认值一律 test_default_unapproved,未拍板。 +- 端点 GET /audit-scorecard 返回的示例卡带 code/production/product + 三个子对象(端点已接线,非冒充)。 +""" + +import http.client +import json +import os +import sys +import tempfile +import threading +import unittest +from pathlib import Path + +try: + from cloud.audit_scorecard import ( + CODE_DIMENSION_IDS, DEFAULT_THRESHOLDS, PRODUCTION_DIMENSION_IDS, + PRODUCT_DIMENSION_IDS, build_code_scorecard, + build_product_scorecard, build_production_scorecard, build_scorecard, + example_scorecard_as_of_2026_10_31) + from cloud.auth import AuthStore + from cloud.server import create_server +except ImportError: + sys.path.insert(0, str(Path(__file__).resolve().parents[2])) + from cloud.audit_scorecard import ( + CODE_DIMENSION_IDS, DEFAULT_THRESHOLDS, PRODUCTION_DIMENSION_IDS, + PRODUCT_DIMENSION_IDS, build_code_scorecard, + build_product_scorecard, build_production_scorecard, build_scorecard, + example_scorecard_as_of_2026_10_31) + from cloud.auth import AuthStore + from cloud.server import create_server + + +def _code_evidence(): + return { + "delivery_quality": { + "first_pass_rate": 0.96, "defect_rate": 0.01, + "rework_rate": 0.04, "first_pass_known": True, + "defect_known": True, "rework_known": True, + "attempts_present": True, "evidence_kind": "real", + }, + "gate_audit": { + "hard_gate_enforced": True, "gate_items_passed": 5, + "gate_items_total": 5, "merge_blocked_verified": True, + "loop_end_to_end_verified": True, + "sampling_misjudgment_rate": 0.0, + }, + "arch_contract": { + "contract_frozen": True, "arch_violations": 0, + "directory_violations": 0, "import_violations": 0, + "stack_changed_without_approval": False, + }, + "test_coverage": { + "prd_coverage_rate": 1.0, "cross_matrix_coverage_rate": 1.0, + "coverage_pct": 0.92, "cross_matrix_kind": "real", + "real_pg": True, "real_service": True, + "agent_six_complete": True, + "lint_items": 0, "mypy_items": 0, "bandit_high": 0, + }, + } + + +def _production_evidence(): + return { + "security_ledger": { + "ledger_verify_ok": True, "ledger_coverage_rate": 1.0, + "branch_protection": "protected", + "shell_whitelist_pass_rate": 1.0, "anchor_check_recent": True, + }, + "cost_quota": { + "cost_records_complete": True, "unpriced_as_zero": False, + "per_creator_present": True, "quota_cutoff_verified": True, + }, + "ops_scale": { + "ci_green_rate": 1.0, "deploy_success_rate": 1.0, + "rollback_drill_done": True, "ha_status": "second_host", + "parallel_scale_proven": 40, + }, + } + + +def _full_evidence(): + ev = {} + ev.update(_code_evidence()) + ev.update(_production_evidence()) + return ev + + +def _product_evidence(): + return { + "real_product_round": True, + "real_app_installed": True, + "real_user_flow_verified": True, + "product_round_as_of": "2026-11-02", + } + + +class SplitTablesTestCase(unittest.TestCase): + def test_dimension_allocation_is_pinned(self): + self.assertEqual(CODE_DIMENSION_IDS, [ + "delivery_quality", "gate_audit", "arch_contract", + "test_coverage"]) + self.assertEqual(PRODUCTION_DIMENSION_IDS, [ + "security_ledger", "cost_quota", "ops_scale"]) + self.assertEqual(PRODUCT_DIMENSION_IDS, [ + "delivery_quality", "gate_audit", "arch_contract", + "test_coverage", "security_ledger", "cost_quota", "ops_scale"]) + self.assertFalse(set(CODE_DIMENSION_IDS) + & set(PRODUCTION_DIMENSION_IDS)) + + def test_code_table_passes_on_code_evidence_alone(self): + table = build_code_scorecard(_code_evidence()) + self.assertEqual(table["overall"], "pass") + self.assertTrue(table["overall_pass"]) + self.assertEqual([d["id"] for d in table["dimensions"]], + CODE_DIMENSION_IDS) + self.assertTrue(table["average_is_not_pass_criterion"]) + self.assertEqual(table["thresholds_source"], + "test_default_unapproved") + for dim in table["dimensions"]: + self.assertGreaterEqual(dim["score"], 9.0) + + def test_bc_all_pending_code_passes_production_not_pass(self): + # 核心验收:B/C 全 pending 时代码表仍可 pass、生产表 not_pass。 + # user_side_items 在此只是占位证据,不依赖第 7 项的代码。 + user_side = [{"id": "B", "status": "pending"}, + {"id": "C", "status": "pending"}] + code = build_code_scorecard(_full_evidence()) + prod = build_production_scorecard( + _full_evidence(), user_side_items=user_side) + self.assertEqual(code["overall"], "pass") + self.assertEqual(prod["overall"], "not_pass") + self.assertFalse(prod["overall_pass"]) + self.assertTrue(prod["user_side_blocked"]) + self.assertEqual([p["id"] for p in prod["user_side_pending"]], + ["B", "C"]) + self.assertTrue(any("B" in m for m in prod["missing_items"])) + self.assertTrue(any("C" in m for m in prod["missing_items"])) + # 挡住 overall 但不篡改维分数:生产维本身仍全 >=9。 + for dim in prod["dimensions"]: + self.assertGreaterEqual(dim["score"], 9.0) + + def test_bc_pending_in_dict_and_string_forms_also_blocks(self): + prod = build_production_scorecard( + _full_evidence(), user_side_items={"B": "pending"}) + self.assertEqual(prod["overall"], "not_pass") + prod2 = build_production_scorecard( + _full_evidence(), user_side_items=["B"]) + self.assertEqual(prod2["overall"], "not_pass") + + def test_production_missing_evidence_does_not_lower_code_table(self): + # 生产证据直接缺失(不传 user_side_items 也一样):生产表 + # not_pass,代码表仍 pass——缺项不跨表拉低。 + code = build_code_scorecard(_code_evidence()) + prod = build_production_scorecard(_code_evidence()) + self.assertEqual(code["overall"], "pass") + self.assertEqual(prod["overall"], "not_pass") + for dim in prod["dimensions"]: + self.assertIsNone(dim["score"]) + self.assertEqual(dim["status"], "unknown") + + def test_production_passes_when_evidence_full_and_user_side_done(self): + prod = build_production_scorecard( + _full_evidence(), + user_side_items=[{"id": "B", "status": "done"}, + {"id": "C", "status": "approved"}]) + self.assertEqual(prod["overall"], "pass") + self.assertFalse(prod["user_side_blocked"]) + self.assertEqual(prod["user_side_pending"], []) + + def test_product_table_without_real_round_is_always_not_pass(self): + # 无真实产品轮证据:即使代码+生产证据全满,产品表也恒 not_pass, + # 全部维 None——绝不把代码/生产分数冒充成产品分数。 + table = build_product_scorecard(_full_evidence()) + self.assertEqual(table["overall"], "not_pass") + self.assertFalse(table["product_gate_passed"]) + for dim in table["dimensions"]: + self.assertIsNone(dim["score"]) + self.assertEqual(dim["status"], "unknown") + self.assertTrue(table["product_round_missing_fields"]) + + def test_product_table_with_real_round_scores_same_dimensions(self): + table = build_product_scorecard(_full_evidence(), + product_evidence=_product_evidence()) + self.assertTrue(table["product_gate_passed"]) + self.assertEqual(table["overall"], "pass") + self.assertEqual(len(table["dimensions"]), 7) + for dim in table["dimensions"]: + self.assertGreaterEqual(dim["score"], 9.0) + + def test_product_average_91_with_one_four_still_fails(self): + # 产品表平均分规则:6 维 10 分、1 维 4 分,平均 9.1,仍 not_pass。 + ev = _full_evidence() + ev["gate_audit"]["hard_gate_enforced"] = False + table = build_product_scorecard(ev, + product_evidence=_product_evidence()) + self.assertEqual(table["average_score"], 9.1) + self.assertTrue(table["average_is_not_pass_criterion"]) + self.assertEqual(table["overall"], "not_pass") + + def test_build_scorecard_carries_three_subobjects(self): + report = build_scorecard( + _full_evidence(), + user_side_items=[{"id": "B", "status": "pending"}, + {"id": "C", "status": "pending"}]) + # 旧字段语义不变:7 维全满时旧 overall 仍 pass。 + self.assertEqual(report["overall"], "pass") + self.assertEqual(len(report["dimensions"]), 7) + self.assertEqual(report["code"]["overall"], "pass") + self.assertEqual(report["production"]["overall"], "not_pass") + # 产品表无真实产品轮证据,恒 not_pass。 + self.assertEqual(report["product"]["overall"], "not_pass") + self.assertEqual([d["id"] for d in report["code"]["dimensions"]], + CODE_DIMENSION_IDS) + self.assertEqual( + [d["id"] for d in report["production"]["dimensions"]], + PRODUCTION_DIMENSION_IDS) + + def test_example_card_carries_three_subobjects_all_not_pass(self): + report = example_scorecard_as_of_2026_10_31() + self.assertEqual(report["overall"], "not_pass") + self.assertEqual(report["code"]["overall"], "not_pass") + self.assertEqual(report["production"]["overall"], "not_pass") + self.assertEqual(report["product"]["overall"], "not_pass") + self.assertFalse(report["product"]["product_gate_passed"]) + + def test_threshold_defaults_still_unapproved_in_all_tables(self): + for entry in DEFAULT_THRESHOLDS.values(): + self.assertEqual(entry["source"], "test_default_unapproved") + for table in (build_code_scorecard(_full_evidence()), + build_production_scorecard(_full_evidence()), + build_product_scorecard(_full_evidence(), + product_evidence=_product_evidence())): + self.assertEqual(table["thresholds_source"], + "test_default_unapproved") + + +class SplitTablesEndpointTestCase(unittest.TestCase): + def setUp(self): + self.tmp = tempfile.TemporaryDirectory() + self.db_path = os.path.join(self.tmp.name, "control.db") + self.auth = AuthStore(self.db_path) + self.owner_id, self.owner_key = self.auth.create_user("owner") + self.server = create_server(self.db_path, "127.0.0.1", 0) + self.port = self.server.server_address[1] + self.thread = threading.Thread( + target=self.server.serve_forever, daemon=True) + self.thread.start() + + def tearDown(self): + self.server.shutdown() + self.server.server_close() + self.thread.join(timeout=5) + try: + self.auth.conn.close() + except Exception: + pass + self.tmp.cleanup() + + def test_endpoint_returns_three_subobjects(self): + conn = http.client.HTTPConnection("127.0.0.1", self.port, timeout=10) + try: + conn.request("GET", "/audit-scorecard", + headers={"X-API-Key": self.owner_key}) + resp = conn.getresponse() + report = json.loads(resp.read().decode("utf-8")) + finally: + conn.close() + self.assertEqual(resp.status, 200) + self.assertEqual(report["overall"], "not_pass") + for key in ("code", "production", "product"): + self.assertIn(key, report) + self.assertEqual(report[key]["overall"], "not_pass") + self.assertEqual(len(report["code"]["dimensions"]), 4) + self.assertEqual(len(report["production"]["dimensions"]), 3) + self.assertEqual(len(report["product"]["dimensions"]), 7) + + +if __name__ == "__main__": + unittest.main()