diff --git a/governance/GOVERNANCE.yaml b/governance/GOVERNANCE.yaml index 3f225de..f3c669b 100644 --- a/governance/GOVERNANCE.yaml +++ b/governance/GOVERNANCE.yaml @@ -180,6 +180,18 @@ domains: removal_drill: "cnb-bridge/REMOVAL.md 单页清单;删除后 gate/org-gate/conductor 语义不变(ADR-0085 决策 6 断言)", seams: "GOVERNANCE 本条目 + org secrets CNB_TOKEN_ + .github cnb-dispatch/cnb-audit 两工作流(隔离审计=cnb-audit 周扫,操作性引用越界即红)"}} + evidence_ledger: + # IR-0006 / ADR-0103:证据账本统一(判定/轨迹/丢弃三层)+ 保留策略。 + # 声明先行——enforcement 载体(evidence/ 目录、checkpoint 复算脚本)随 + # W1-B1/B2 落地后升 enforced 并入 drift-check。 + measures: + - id: EL-1 + intent: "判定层账本落 archive 仓 evidence/:append-only + hash 链(ADR-0062 平移)+ 月度 checkpoint(链头 hash+当月汇总提交 git)+ 独立脚本复算整链,链断=红(fail-closed);payload 内联上限 4KB 超限拒写;每条判定记录必含 tenant 字段" + strength: advisory + - id: EL-2 + intent: "保留策略:判定层 git 永续;轨迹层(云内网 blob)按记录内保留策略字段执行,git 仅存摘要+sha256 指针;丢弃层(GitHub 事件面)transient 不承诺持久;三源原 JSONL(metering/butler/drill)只读冻结(平移不搬移,可回退)" + strength: advisory + flows: governance_change: # 治理仓(.github governance|standards、CI-Workflows workflows/)内容变更的唯一合法路径 diff --git a/specs/IR-0003/constitution.md b/specs/IR-0003/constitution.md index 2453f49..1cd6a92 100644 --- a/specs/IR-0003/constitution.md +++ b/specs/IR-0003/constitution.md @@ -7,6 +7,10 @@ > 审查通过后:本文作为 IR-0003「重订宪法」核心附件走流程,spec v4 吸收条款级变更。 > v2.1:按 owner 五问修订——§4 全量展开、§11 管家唤醒与统一入口、§12 状态可视化。 > v2.2:按 owner 业务模式陈述(2026-08-21)增补 §13 及三处小修订(§4C/§9/§10)。 +> v2.3:跨族红队清零版(签署 2026-08-21)。 +> v2.4(IR-0006 W1-A2,卡 #405):吸收治理总纲 v1.0 三组扩展条款——I3 证据账本三层、 +> I4 Wave 对象 schema、I7 云内网入图三面分离——为 §14;**§5 逐字未动**(硬谓词+shadow +> 为硬边界,ADR-0103 决策 1:risk_class 仅参数包选择器,裁决语义不参数化)。 ## 0. 一句话(经前提异议修正版) @@ -332,6 +336,52 @@ FDE 的交付动作可复用主流水线——客户定制 = 对产品仓发 IR 理由:核心生产流程未跑通前,这些都是无源之水。触发条件:任一假设被验证为真 (有真实客户/真实训练需求)时,按正常 IR 流程立项。 +## 14. 三面分离与证据账本(v2.4,IR-0006 总纲吸收) + +> 来源:治理总纲 v1.0 I3/I4/I7 三组扩展条款,经 absorption-map.md 落位吸收 +> (ADR-0103)。本节为宪法级扩展;**§5 硬谓词+shadow 不动**——risk_class +> 仅是参数包选择器(门禁集/entitlement 档/介入点),永不是裁决输入。 + +### 14a. I3 证据账本三层(判定/轨迹/丢弃) + +一切判定事件(gate 裁决/成本/审批/决策)进统一证据账本,三层分离: + +| 层 | 载体 | 纪律 | +|---|---|---| +| 判定层 | archive 仓 `evidence/`(git) | append-only + hash 链(ADR-0062 平移);月度 checkpoint(链头 hash+当月汇总提交 git);独立脚本可从任意旧 blob 复算整链,**链断=红**(fail-closed) | +| 轨迹层 | 云内网对象存储(blob) | git 侧仅存摘要+sha256 指针+保留策略字段;payload 内联上限 **4KB,超限拒写** | +| 丢弃层 | GitHub 事件面 | transient,不承诺持久,可随时消失 | + +- 每条判定记录必含 `tenant` 字段(潮玩公司共用额度的计量分离先行——分家工程另行立项)。 +- 三源(metering/butler/drill)新事件按 schema v1 双写过渡,原 JSONL 只读冻结 + (平移不搬移,可回退)。 +- 字段命名对齐 OTel gen_ai.* 语义约定;schema 落 standards/。 + +### 14b. I4 Wave 对象(schema 扩展,不新建 kind) + +**Wave ≡ card issue + wave-plan.md**(词汇归并,见 absorption-map 二)。卡模板 +扩展三个字段: + +- `budget:` 四元组(usd/tokens/wallclock/human_minutes)+ on_exceed—— + 波次级预算**超限硬停**:熔断+撤 auto-merge+开 issue(复位走 ADR-0040 流程不变)。 +- `capabilities:` allowlist 式 org secret / Vault 引用(agent 上下文零凭据不变)。 +- `evidence:` 判定记录指针(id@sha8 形态,禁引 payload)。 + +### 14c. I7 云内网入图(三面分离) + +治理体系按三面组织(ADR-0103 决策 2): + +| 面 | 载体 | 纪律 | +|---|---|---| +| 声明面 | Git(治理仓 specs/、governance/、env 定义仓) | 凡不能写进 Git 声明的就不能被输出(铁律) | +| 执行面 | 多域:GitHub Actions / 云内网(公网服务器+云电脑池+Vault+LLM 路由)/ CNB 池 | 云内网=事实生产工厂,申报入 providers.yaml(self-cloud-pool、vault 条目)+ env 定义仓(environments/*.yaml 期望态+实况上报);未申报=漂移 | +| 判定面 | 恒定 GitHub CI(gate/org-gate/conductor) | **判定锚点永不外置**:云内网池与 CNB 同为可删除层,removal 声明同 CNB 模式(删除后判定语义不变,EX-1 延伸) | + +- PM 会话凭证收敛:服务器代签 cloudbrid-agent 短令牌(单仓+短 TTL), + 个人 PAT 退出日常流程(INV 凭据纪律不变)。 +- 飞书多维表格=投影体系**第四投影**(outbound-only):label 唯一真源, + drop & rebuild 单轮保真,人工修改被下轮纠正并告警(§12 纪律延伸)。 + ## 审查指引(owner 请重点看) - §0 一句话的修正(判断不可压缩)——认吗? @@ -341,3 +391,6 @@ FDE 的交付动作可复用主流水线——客户定制 = 对产品仓发 IR - §7 反退化设计(独立复算、每周亲手一件、决策卡第三选项)——愿意承诺吗? - §11 唤醒矩阵 + 统一入口协议、§12 Project 只读投影板(修正 DECISION-04)——认吗? - §13 业务模式假设与四个推论(尤其"现在不建"清单)——认吗? +- §14(v2.4)三组扩展条款:I3 账本三层(4KB 拒写/链断=红/tenant 必有)、 + I4 Wave schema 扩展不新建 kind、I7 云内网入图(可删除层+判定锚点不外置)—— + §5 未动,认吸收边界吗? diff --git a/specs/IR-0003/suite/test_constitution.py b/specs/IR-0003/suite/test_constitution.py new file mode 100644 index 0000000..1a2fd4c --- /dev/null +++ b/specs/IR-0003/suite/test_constitution.py @@ -0,0 +1,124 @@ +#!/usr/bin/env python3 +"""IR-0003 宪法 suite——v2.4 §14 总纲吸收的结构校验(T-14 第一面)。 + +卡 #405 / PR #426:宪法吸收治理总纲 v1.0 三组扩展条款(I3/I4/I7)。 +测试锚点全为机械文本断言(无 LLM 判定——INV-01): + - §14 三小节结构(14a 证据账本三层 / 14b Wave 对象 / 14c 三面分离) + - §5 硬谓词+shadow 逐字不动(ADR-0103 决策 1 的硬边界) + - risk_class 语义约束(参数包选择器,永不是裁决输入) + - GOVERNANCE.yaml evidence_ledger 域随附(EL-1/EL-2) +""" +import re +import unittest +from pathlib import Path + +SPEC_DIR = Path(__file__).resolve().parent.parent +REPO_ROOT = SPEC_DIR.parent.parent +CONSTITUTION = SPEC_DIR / "constitution.md" +GOVERNANCE = REPO_ROOT / "governance" / "GOVERNANCE.yaml" + + +def constitution_text() -> str: + self_ = CONSTITUTION.read_text(encoding="utf-8") + return self_ + + +def section(text: str, num: str, next_prefix: str) -> str: + """提取 `## .` 到下一个 `## .` 之间的正文。""" + pat = re.compile( + r"^## " + re.escape(num) + r"\..*?(?=^## " + re.escape(next_prefix) + r"\.)", + re.S | re.M, + ) + m = pat.search(text) + assert m, f"找不到宪法 §{num}" + return m.group(0) + + +class TestConstitutionV24(unittest.TestCase): + def test_version_note_v24(self): + """版本注记含 v2.4 且声明 §5 逐字未动。""" + t = constitution_text() + self.assertIn("v2.4(IR-0006 W1-A2,卡 #405)", t) + self.assertIn("§5 逐字未动", t) + self.assertIn("ADR-0103 决策 1", t) + + def test_section14_exists_with_three_subsections(self): + """§14 存在且含 14a/14b/14c 三小节。""" + t = constitution_text() + m = re.search(r"^## 14\..*", t, re.M) + self.assertIsNotNone(m, "§14 缺失") + for sub in ("### 14a.", "### 14b.", "### 14c."): + self.assertIn(sub, t, f"{sub} 小节缺失") + + def test_14a_evidence_ledger_three_layers(self): + """14a:判定/轨迹/丢弃三层 + 4KB 上限 + tenant 必含 + 链断=红。""" + t = constitution_text() + m = re.search(r"^### 14a\..*?(?=^### 14b\.)", t, re.S | re.M) + self.assertIsNotNone(m, "14a 缺失") + body = m.group(0) + for kw in ("判定层", "轨迹层", "丢弃层", "append-only", "hash 链", "4KB,超限拒写", "tenant"): + self.assertIn(kw, body, f"14a 缺关键词: {kw}") + self.assertIn("链断=红", body) + + def test_14b_wave_object_extension(self): + """14b:Wave ≡ card issue + wave-plan.md;卡模板三字段;预算超限硬停。""" + t = constitution_text() + m = re.search(r"^### 14b\..*?(?=^### 14c\.)", t, re.S | re.M) + self.assertIsNotNone(m, "14b 缺失") + body = m.group(0) + self.assertIn("Wave ≡ card issue + wave-plan.md", body) + for field in ("budget:", "capabilities:", "evidence:"): + self.assertIn(field, body, f"14b 缺卡模板字段: {field}") + self.assertIn("超限硬停", body) + self.assertIn("id@sha8", body) + + def test_14c_three_planes(self): + """14c:声明面/执行面/判定面三面 + 判定锚点永不外置 + 飞书第四投影。""" + t = constitution_text() + m = re.search(r"^### 14c\..*?(?=^## )", t, re.S | re.M) + self.assertIsNotNone(m, "14c 缺失") + body = m.group(0) + for plane in ("声明面", "执行面", "判定面"): + self.assertIn(plane, body, f"14c 缺面: {plane}") + self.assertIn("判定锚点永不外置", body) + self.assertIn("outbound-only", body) + self.assertIn("label 唯一真源", body) + + def test_section5_hard_predicate_untouched(self): + """§5 硬谓词+shadow 核心句逐字在位(ADR-0103 决策 1 硬边界)。""" + t = constitution_text() + s5 = section(t, "5", "6") + for kw in ( + "硬谓词白名单(fail-closed)+ 常设 shadow 模式", + "缺证据=拒绝,不是中性", + "连续 ≥50 例一致且零逃逸", + "成本熔断只准降级为人签,**不准降级为少验**", + ): + self.assertIn(kw, s5, f"§5 关键句被改动: {kw}") + + def test_risk_class_is_parameter_package_only(self): + """risk_class 仅参数包选择器,禁止成为裁决输入(红线)。""" + t = constitution_text() + m = re.search(r"^## 14\..*?(?=^## )", t, re.S | re.M) + self.assertIsNotNone(m, "§14 缺失") + self.assertIn("参数包选择器", m.group(0)) + self.assertIn("永不是裁决输入", m.group(0)) + # §5 本体不引入 risk_class(裁决语义不参数化) + s5 = section(t, "5", "6") + self.assertNotIn("risk_class", s5, "§5 出现 risk_class=裁决语义被参数化(红线)") + + +class TestGovernanceEvidenceLedger(unittest.TestCase): + def test_evidence_ledger_domain_declared(self): + """GOVERNANCE.yaml 随附 evidence_ledger 域(EL-1/EL-2,advised 起步)。""" + self.assertTrue(GOVERNANCE.exists(), "GOVERNANCE.yaml 缺失") + t = GOVERNANCE.read_text(encoding="utf-8") + self.assertIn("evidence_ledger:", t) + for mid in ("- id: EL-1", "- id: EL-2"): + self.assertIn(mid, t, f"缺措施 {mid}") + self.assertIn("4KB", t) + self.assertIn("tenant", t) + + +if __name__ == "__main__": + unittest.main()