diff --git a/.github/workflows/conformance-seed.yml b/.github/workflows/conformance-seed.yml new file mode 100644 index 0000000..7b84895 --- /dev/null +++ b/.github/workflows/conformance-seed.yml @@ -0,0 +1,109 @@ +name: conformance-seed +# conformance 语料库种子+门禁元治理首跑(IR-0006 W6-M1 / 卡 #423 / AC-1b+1c) +# +# 全链:拉 30-50 张已完成卡(state:done type:card)→ 回放三元组语料 +# (初始快照+目标+密封验收,机械校验)→ 四列元治理评审(声明门禁 vs +# 全仓 job 清单对账)→ 胜出实践晋级首跑(append-only hash 链账本)→ +# 语料+评审+晋级账本落 archive 仓 conformance/(PR 面)。 +on: + workflow_dispatch: {} + +permissions: {} + +jobs: + seed: + runs-on: ubuntu-latest + timeout-minutes: 20 + permissions: + contents: read + env: + CARD: Cloudbird-Software/.github#423 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - name: 回放采集(30-50 张已完成卡——三元组语料) + env: + GH_TOKEN: ${{ secrets.GOVERNANCE_TOKEN }} + run: | + set -euo pipefail + mkdir -p comments + # 已完成卡:type:card+state:done closed——取最近 50 张 + gh api -X GET "search/issues" -f q="repo:Cloudbird-Software/.github is:issue is:closed label:type:card label:state:done" -f per_page=50 -f sort=created -f order=desc \ + --jq '[.items[] | {number, title, created_at, closed_at, labels: [.labels[].name], body}]' > cards.json + N=$(jq length cards.json) + echo "拉到已完成卡 $N 张" + for num in $(jq -r '.[].number' cards.json); do + gh api "repos/Cloudbird-Software/.github/issues/${num}/comments" --paginate \ + --jq '[.[] | {body}]' > "comments/${num}.json" || echo "[]" > "comments/${num}.json" + done + python3 governance/conformance-corpus.py harvest \ + --cards-file cards.json --comments-dir comments --out corpus.jsonl + # 机械校验(fail-closed:结构红或 <30 条=本 job 红) + python3 governance/conformance-corpus.py validate --corpus corpus.jsonl --min 30 + - name: 四列元治理评审(声明门禁 vs 全仓 job 清单对账) + run: | + set -euo pipefail + python3 governance/metagov.py review \ + --policy governance/policy/metrics.yaml \ + --workflows-dir .github/workflows --out gate-review.json + - name: 胜出实践晋级首跑(append-only hash 链,AC-1c) + run: | + set -euo pipefail + cat > rec1.json <<'EOF' + {"practice": "fail-closed 双层验证(写入侧宽松+验证侧严格)", + "goal": "错误事件进不了账本主链;生成器缺陷在 PR 面早暴露", + "evidence": ["run 33263613945(eval-wave 落账步 verify_evidence 红拦截 ts 缺字段)", + "PR Cloudbird-Software/.github#460(缺陷修复留痕)"], + "promoted_by": "metagov-review-bot"} + EOF + cat > rec2.json <<'EOF' + {"practice": "同 harness 同语料只换被优化物(eval 归因隔离)", + "goal": "optimization 波次指标差异只归因优化本体——非劣性裁决的前提", + "evidence": ["run 33263909046(基线/候选同装置评测+GREEN 裁决)", + "PR Cloudbird-Software/CI-Workflows#132(eval harness 落位)"], + "promoted_by": "metagov-review-bot"} + EOF + python3 governance/metagov.py promote --registry promotions.jsonl --record rec1.json + python3 governance/metagov.py promote --registry promotions.jsonl --record rec2.json + python3 governance/metagov.py verify --registry promotions.jsonl + - name: 语料+评审+晋级账本落 archive conformance/(PR 面) + env: + GH_TOKEN: ${{ secrets.GOVERNANCE_TOKEN }} + RUN_ID: ${{ github.run_id }} + run: | + set -euo pipefail + git clone --depth 5 "https://x-access-token:${GH_TOKEN}@github.com/Cloudbird-Software/archive.git" arch + mkdir -p arch/conformance + cp corpus.jsonl arch/conformance/cards.jsonl + cp gate-review.json arch/conformance/gate-review.json + cp promotions.jsonl arch/conformance/promotions.jsonl + cat > arch/conformance/index.yaml < --comments-dir --out corpus.jsonl + (issues.json = gh api 拉的卡 issue 数组;comments-dir/.json = + 该卡评论数组——离线 fixture 同形状,CI 在线拉取) + validate --corpus corpus.jsonl [--min N](N=最低条数,缺省 30) +退出码:0=绿 | 1=结构红 | 2=infra。 +""" +from __future__ import annotations + +import hashlib +import json +import re +import sys +from pathlib import Path + +SCHEMA = "conformance-corpus/v1" +AC_RE = re.compile(r"\b(AC-[0-9]+[a-z]?)\b") +PARENT_RE = re.compile(r"父意图[::]\s*#(\d+)") +TASK_RE = re.compile(r"##\s*任务\s*\n(.*?)(?=\n##|\Z)", re.S) +ACSEC_RE = re.compile(r"##\s*AC[^\n]*\n(.*?)(?=\n##|\Z)", re.S) +# 收口评论惯用语四种形态:state:done(T8 机器语)/ 收口 / T8 谓词 / 验收完成 +DONE_COMMENT_RE = re.compile(r"state:done|收口|T8|验收完成") +HEX64 = re.compile(r"^[0-9a-f]{64}$") + + +def die2(msg: str) -> None: + print(f"FATAL conformance-corpus: {msg}", file=sys.stderr) + sys.exit(2) + + +def sha256_text(s: str) -> str: + return hashlib.sha256(s.encode("utf-8")).hexdigest() + + +def extract_task(body: str) -> str: + m = TASK_RE.search(body or "") + return (m.group(1) if m else (body or "")).strip() + + +def build_entry(issue: dict, comments: list) -> dict: + body = issue.get("body") or "" + done_comments = [c for c in comments + if DONE_COMMENT_RE.search(c.get("body") or "")] + seal = sha256_text(done_comments[-1]["body"]) if done_comments else "" + labels = [l["name"] if isinstance(l, dict) else l for l in issue.get("labels", [])] + parent = PARENT_RE.search(body) + acs = sorted(set(AC_RE.findall(body))) + ac_m = ACSEC_RE.search(body) + ac_sec = ac_m.group(1).strip() if ac_m else "" + ac_count = sum(1 for ln in ac_sec.splitlines() if ln.strip().startswith(("-", "*"))) + entry = { + "schema": SCHEMA, + "card": f"Cloudbird-Software/.github#{issue['number']}", + "ir": f"Cloudbird-Software/.github#{parent.group(1)}" if parent else None, + "triple": { + "initial_snapshot": { + "created_at": issue.get("created_at"), + "title": issue.get("title"), + "body_sha256": sha256_text(body), + }, + "goal": { + "task_sha256": sha256_text(extract_task(body)), + "ac_ids": acs, + "ac_section_sha256": sha256_text(ac_sec), + "ac_count": ac_count, + "labels_final": sorted(labels), + }, + "sealed_acceptance": { + "closed_at": issue.get("closed_at"), + "done_comment_sha256": seal, + "done_comment_sha8": seal[:8], + }, + }, + } + return entry + + +def validate_entry(e: dict) -> str | None: + if e.get("schema") != SCHEMA: + return "schema 非 conformance-corpus/v1" + t = e.get("triple") + if not isinstance(t, dict) or set(t) != {"initial_snapshot", "goal", "sealed_acceptance"}: + return "triple 须且仅含三元组三键" + snap, goal, seal = t["initial_snapshot"], t["goal"], t["sealed_acceptance"] + if not snap.get("created_at") or not str(snap.get("title") or "").strip(): + return "initial_snapshot 缺 created_at/title" + if not HEX64.match(str(snap.get("body_sha256"))): + return "initial_snapshot.body_sha256 非 64hex" + if not HEX64.match(str(goal.get("task_sha256"))): + return "goal.task_sha256 非 64hex" + # 验收判据双形态:AC id 列表(新形态)或 AC 节非空(旧卡朴素 bullet 形态) + if not goal.get("ac_ids") and not (goal.get("ac_count", 0) >= 1 + and HEX64.match(str(goal.get("ac_section_sha256")))): + return "goal 无验收判据(ac_ids 空且 AC 节空=不可回放)" + if not seal.get("closed_at"): + return "sealed_acceptance.closed_at 缺" + dg = str(seal.get("done_comment_sha256")) + if not HEX64.match(dg) or seal.get("done_comment_sha8") != dg[:8]: + return "sealed_acceptance 密封 digest 形状非法(须 64hex+sha8 一致)" + return None + + +def main() -> int: + if len(sys.argv) < 2: + print(__doc__) + return 2 + cmd = sys.argv[1] + + if cmd == "harvest": + cards_f = sys.argv[sys.argv.index("--cards-file") + 1] + cdir = Path(sys.argv[sys.argv.index("--comments-dir") + 1]) + out_f = sys.argv[sys.argv.index("--out") + 1] + try: + cards = json.loads(Path(cards_f).read_text(encoding="utf-8")) + except (OSError, json.JSONDecodeError) as e: + die2(f"cards-file 不可读: {e}") + if not isinstance(cards, list) or not cards: + die2("cards-file 须为非空数组") + entries, bad = [], 0 + for it in cards: + num = it.get("number") + cpath = cdir / f"{num}.json" + try: + comments = json.loads(cpath.read_text(encoding="utf-8")) + except (OSError, json.JSONDecodeError): + comments = [] + e = build_entry(it, comments) + err = validate_entry(e) + if err: + bad += 1 + print(f"SKIP #{num}: {err}", file=sys.stderr) + continue + entries.append(e) + Path(out_f).write_text( + "".join(json.dumps(e, ensure_ascii=False, separators=(",", ":")) + "\n" for e in entries), + encoding="utf-8") + print(f"OK 语料 {len(entries)} 条落盘 {out_f}(跳过 {bad} 条结构非法)") + return 0 if entries else 1 + + if cmd == "validate": + corpus_f = sys.argv[sys.argv.index("--corpus") + 1] + min_n = 30 + if "--min" in sys.argv: + min_n = int(sys.argv[sys.argv.index("--min") + 1]) + try: + lines = [ln for ln in Path(corpus_f).read_text(encoding="utf-8").splitlines() if ln.strip()] + except OSError as e: + die2(f"corpus 不可读: {e}") + errs = 0 + for i, ln in enumerate(lines, 1): + try: + e = json.loads(ln) + except json.JSONDecodeError as ex: + print(f"REJECT 第 {i} 行 JSON 非法: {ex}") + errs += 1 + continue + err = validate_entry(e) + if err: + print(f"REJECT 第 {i} 行({e.get('card', '?')}): {err}") + errs += 1 + if len(lines) < min_n: + print(f"REJECT 语料条数 {len(lines)} < 最低 {min_n}(AC-1b:30-50 张已完成卡)") + errs += 1 + if errs: + return 1 + print(f"OK conformance 语料结构绿({len(lines)} 条三元组可机械校验)") + return 0 + + print(__doc__) + return 2 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/governance/metagov.py b/governance/metagov.py new file mode 100644 index 0000000..97e2aa4 --- /dev/null +++ b/governance/metagov.py @@ -0,0 +1,185 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +"""metagov.py —— 门禁元治理四列指标评审+胜出实践晋级(IR-0006 W6-M1 / AC-1c) + +四列(policy/metrics.yaml gate_metagovernance.four_columns 声明): + gate 门禁锚(:——须真实存在于 .github/workflows/*.yml, + 声明与实现漂移=红,机械对账全仓 job 清单) + judge 判定语义(mechanical=机械谓词 / pending=声明位未落——ADR-0073 + 决策 7:缺数据不渲染成好数据) + data_source 真源(文件/账本路径) + red_line 红线记录(fail-closed 生效锚——负向断言在位=红线可执法, + 带 run 引用=红线已活体触发过) + +胜出实践晋级(promote):append-only 账本 conformance/promotions.jsonl +(hash 链,公式同 write_evidence——改历史必断链,verify 随时巡检)。 +记录字段:practice/goal/evidence(≥1 条引用)/promoted_by/ts—— +胜出=有证据支撑的实践胜出(评审产出),晋级=进政策/流程的留痕。 + +子命令: + review --policy metrics.yaml --workflows-dir <.github/workflows> --out review.json + promote --registry promotions.jsonl --record rec.json + verify --registry promotions.jsonl +退出码:0=绿 | 1=红(漂移/结构/断链)| 2=infra。 +""" +from __future__ import annotations + +import datetime +import hashlib +import json +import sys +from pathlib import Path + +import yaml + +FOUR_COLUMNS = ["gate", "judge", "data_source", "red_line"] +JUDGES = {"mechanical", "pending"} + + +def die2(msg: str) -> None: + print(f"FATAL metagov: {msg}", file=sys.stderr) + sys.exit(2) + + +def canon(obj) -> str: + return json.dumps(obj, ensure_ascii=False, sort_keys=True, separators=(",", ":")) + + +def content_hash(rec: dict) -> str: + return hashlib.sha256(canon({k: v for k, v in rec.items() if k != "hash"}).encode("utf-8")).hexdigest() + + +def now_utc() -> str: + return datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ") + + +def load_policy(path: str) -> dict: + try: + p = yaml.safe_load(Path(path).read_text(encoding="utf-8")) + except (OSError, yaml.YAMLError) as e: + die2(f"metrics.yaml 不可读: {e}") + mg = (p or {}).get("gate_metagovernance") + if not isinstance(mg, dict): + die2("metrics.yaml 缺 gate_metagovernance 节(W6-M1 metrics 扩展)") + if mg.get("four_columns") != FOUR_COLUMNS: + die2(f"gate_metagovernance.four_columns 须为 {FOUR_COLUMNS}") + gates = mg.get("gates") + if not isinstance(gates, list) or not gates: + die2("gate_metagovernance.gates 缺失或为空") + for g in gates: + if set(g) != set(FOUR_COLUMNS): + die2(f"门禁行须且仅含四列 {FOUR_COLUMNS}: {sorted(g)}") + if g["judge"] not in JUDGES: + die2(f"gate {g['gate']}: judge 须 mechanical|pending") + if not str(g["data_source"] or "").strip() or not str(g["red_line"] or "").strip(): + die2(f"gate {g['gate']}: data_source/red_line 不得为空") + return mg + + +def main() -> int: + if len(sys.argv) < 2: + print(__doc__) + return 2 + cmd = sys.argv[1] + + if cmd == "review": + pol = sys.argv[sys.argv.index("--policy") + 1] + wdir = Path(sys.argv[sys.argv.index("--workflows-dir") + 1]) + out = sys.argv[sys.argv.index("--out") + 1] + mg = load_policy(pol) + # 全仓 job 清单(:)——机械对账面 + anchors = set() + for wf in sorted(wdir.glob("*.yml")): + try: + jobs = yaml.safe_load(wf.read_text(encoding="utf-8"))["jobs"] + except (yaml.YAMLError, KeyError, TypeError): + die2(f"workflow 不可解析: {wf.name}") + for j in jobs: + anchors.add(f"{wf.stem}:{j}") + ghosts = [g["gate"] for g in mg["gates"] if g["gate"] not in anchors] + if ghosts: + print(f"REJECT 声明门禁不在 workflows job 清单(漂移): {ghosts}") + return 1 + review = { + "schema": "gate-metagovernance-review/v1", + "generated_at": now_utc(), + "four_columns": FOUR_COLUMNS, + "gates": mg["gates"], + "workflow_jobs_total": len(anchors), + "declared_total": len(mg["gates"]), + } + Path(out).write_text(json.dumps(review, ensure_ascii=False, indent=1), encoding="utf-8") + pend = sum(1 for g in mg["gates"] if g["judge"] == "pending") + print(f"OK 四列评审产出 {out}({len(mg['gates'])} 门禁全对账在册/" + f"全仓 {len(anchors)} job;pending {pend} 诚实不造数)") + return 0 + + if cmd == "promote": + reg = sys.argv[sys.argv.index("--registry") + 1] + rec_f = sys.argv[sys.argv.index("--record") + 1] + try: + rec = json.loads(Path(rec_f).read_text(encoding="utf-8")) + except (OSError, json.JSONDecodeError) as e: + die2(f"record 不可读: {e}") + if {"practice", "goal", "evidence", "promoted_by"} - set(rec): + die2("晋级记录缺必填(practice/goal/evidence/promoted_by)") + if not isinstance(rec.get("evidence"), list) or not rec["evidence"]: + die2("evidence 须为非空数组(胜出=有证据——空证据晋级=自封)") + if any(k in rec for k in ("seq", "prev_hash", "hash")): + die2("链字段由本工具独占计算,记录不得自带") + lines = [] + rp = Path(reg) + if rp.exists(): + lines = [ln for ln in rp.read_text(encoding="utf-8").splitlines() if ln.strip()] + # 追加前先验既有链(append-only 的前提=旧链完好) + rc = _verify_lines(lines) + if rc: + return rc + rec["ts"] = rec.get("ts") or now_utc() + rec["seq"] = len(lines) + 1 + rec["prev_hash"] = json.loads(lines[-1])["hash"] if lines else None + rec["hash"] = content_hash(rec) + with open(reg, "a", encoding="utf-8") as f: + f.write(json.dumps(rec, ensure_ascii=False, separators=(",", ":")) + "\n") + print(f"OK 晋级记录 #{rec['seq']} 已追加(hash 尾={rec['hash'][-12:]})") + return 0 + + if cmd == "verify": + reg = sys.argv[sys.argv.index("--registry") + 1] + try: + lines = [ln for ln in Path(reg).read_text(encoding="utf-8").splitlines() if ln.strip()] + except OSError as e: + die2(f"registry 不可读: {e}") + return _verify_lines(lines) + + print(__doc__) + return 2 + + +def _verify_lines(lines: list) -> int: + prev = None + for i, ln in enumerate(lines, 1): + try: + rec = json.loads(ln) + except json.JSONDecodeError as e: + print(f"REJECT 第 {i} 行 JSON 非法: {e}") + return 1 + if rec.get("seq") != i: + print(f"REJECT 第 {i} 行 seq={rec.get('seq')}(应 {i})") + return 1 + if rec.get("prev_hash") != prev: + print(f"REJECT 第 {i} 行 prev_hash 断链") + return 1 + if content_hash(rec) != rec.get("hash"): + print(f"REJECT 第 {i} 行 hash 不符(改历史必断链)") + return 1 + if not rec.get("evidence"): + print(f"REJECT 第 {i} 行 evidence 空") + return 1 + prev = rec["hash"] + print(f"OK 晋级账本链完整({len(lines)} 条)") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/governance/policy/metrics.yaml b/governance/policy/metrics.yaml index accf407..b688ca7 100644 --- a/governance/policy/metrics.yaml +++ b/governance/policy/metrics.yaml @@ -88,3 +88,48 @@ board: # factory-floor 板字段完善(AC-3 / 宪法 §12):谓词状态数据源=W5-C2 硬谓词信任门 # (ADR-0071,进行中)——未落前板字段存在但值恒为 pending 标注(字段占位≠造数) predicate_status_pending: "pending(W5-C2)" + +gate_metagovernance: + # 门禁元治理四列指标(IR-0006 W6-M1 / AC-1c——宪法 §4A 阈值唯一来源延伸): + # 每个门禁一行四列;judge=pending 诚实不造数(ADR-0073 决策 7)。 + # 声明的 gate=: 须真实存在于 .github/workflows/*.yml——评审时 + # 机械对账全仓 job 清单,漂移=红(governance/metagov.py review)。 + # red_line 列=该门禁 fail-closed 生效锚(负向断言在位=红线可执法; + # 带 run 引用=红线已活体触发过——不造数)。 + four_columns: [gate, judge, data_source, red_line] + review_cadence: wave-exit + gates: + - gate: gate:gate + judge: mechanical + data_source: governance/tests/*(make gates-pr 治理自测发现面) + red_line: "负向断言族在位(test-attest-bind 三负向/test-eval-gate 边界红/test-metagov 断链红)" + - gate: eval-wave:eval-wave + judge: mechanical + data_source: governance/eval-gate.py + policy/eval-gates.yaml(δ/ratio/污染阈值,INV-01) + red_line: "run 33263613945(ts 缺字段被账本链验拦截红——fail-closed 生效活体)" + - gate: env-drift:drift + judge: mechanical + data_source: governance/env-drift.py + policy/env-drift.yaml(scope 检测面) + red_line: "run 33257903305(dev-self ingress 漂移→exit 1→issue #453 自动开)" + - gate: attest-drill:drill + judge: mechanical + data_source: governance/attest-trace.sh(payload↔attestation 双锚+git archive 重建) + red_line: "run 33261139414(RUN_ID unbound 红——绑定步绿/回溯步红分层,无默认绿)" + - gate: feishu-drill:drill + judge: mechanical + data_source: governance/feishu-sync.py(INV-05 label 唯一真源+漂移纠正) + red_line: "run 33254361404(人工违规改表被下轮投影纠正+drift 告警入账本)" + - gate: governance-drift:drift-check + judge: mechanical + data_source: governance/GOVERNANCE.yaml + expected-state.json(期望态对账) + red_line: "GM-1 模式三消费者实证(governance/env/镜像 tag 后续)" + - gate: post-merge-verify:smoke + judge: pending + data_source: post-merge-verify.yml(合并后冒烟——样本待首红,通道在位) + red_line: "pending(fail-closed 通道在位:smoke 红→revert;首红样本尚未发生,不造数)" + practice_promotion: + # 胜出实践晋级机制(AC-1c):append-only 账本 archive conformance/promotions.jsonl + # (hash 链,改历史必断链——metagov.py promote/verify)。晋级=有证据支撑的 + # 实践进政策/流程的留痕;evidence 须非空(空证据晋级=自封)。 + registry: Cloudbird-Software/archive:conformance/promotions.jsonl + chain: sha256(canonical JSON 去hash字段)——公式同 write_evidence diff --git a/governance/tests/test-metagov.sh b/governance/tests/test-metagov.sh new file mode 100644 index 0000000..8022e84 --- /dev/null +++ b/governance/tests/test-metagov.sh @@ -0,0 +1,117 @@ +#!/usr/bin/env bash +# test-metagov.sh —— W6-M1(#423)conformance 语料三元组+四列元治理+晋级账本自测 +# +# 离线自足:fixture 语料/policy/工作流清单全部临时生成;真实面(metrics.yaml +# 四列声明 vs 本仓 workflows job 清单)机械对账断言。 +set -uo pipefail +DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +PASS=0; FAIL=0 +ok() { PASS=$((PASS+1)); echo "PASS $1"; } +bad() { FAIL=$((FAIL+1)); echo "FAIL $1"; } + +TMP=$(mktemp -d); trap 'rm -rf "$TMP"' EXIT +CORPUS="$DIR/governance/conformance-corpus.py" +METAGOV="$DIR/governance/metagov.py" + +# ---- fixture:已完成卡 issue+评论(三元组原料) ---- +D="$TMP/c" +mkdir -p "$D" +cat > "$D/cards.json" <<'EOF' +[{"number": 9001, "title": "T: 演示卡甲", "created_at": "2026-08-01T00:00:00Z", + "closed_at": "2026-08-02T00:00:00Z", "labels": [{"name": "type:card"}, {"name": "state:done"}], + "body": "> 父意图: #402\n\n## 任务\ndemo task A\n\n## AC\n- AC-1b: demo"}, + {"number": 9002, "title": "T: 演示卡乙", "created_at": "2026-08-01T00:00:00Z", + "closed_at": "2026-08-02T00:00:00Z", "labels": [{"name": "type:card"}, {"name": "state:done"}], + "body": "> 父意图: #402\n\n## 任务\ndemo task B\n\n## AC\n- AC-1b: demo\n- AC-1c: demo"}] +EOF +cat > "$D/9001.json" <<'EOF' +[{"body": "state:done(T8)——PR #1 合并+实测全绿"}] +EOF +: > "$D/9002.json" # 无 done 评论 → 密封验收缺=跳过 + +python3 "$CORPUS" harvest --cards-file "$D/cards.json" --comments-dir "$D" --out "$TMP/corpus.jsonl" >/dev/null 2>&1 +[[ $? -eq 0 ]] && ok "harvest 绿(甲卡三元组落盘)" || bad "harvest 红" +N=$(grep -c . "$TMP/corpus.jsonl") +[[ $N -eq 1 ]] && ok "无 done 评论卡被跳过(乙卡=密封验收缺=不可回放)" || bad "跳过语义坏(n=$N)" + +# 三元组结构机械校验 +python3 "$CORPUS" validate --corpus "$TMP/corpus.jsonl" --min 1 >/dev/null 2>&1 +[[ $? -eq 0 ]] && ok "validate 绿(三元组结构可机械校验,AC-1b)" || bad "validate 红" + +# 负向:三元组残缺 → 红(三条逐一) +jq -c '.triple.initial_snapshot.body_sha256="zz"' "$TMP/corpus.jsonl" > "$TMP/bad1.jsonl" +jq -c 'del(.triple.goal.ac_ids, .triple.goal.ac_section_sha256, .triple.goal.ac_count)' "$TMP/corpus.jsonl" > "$TMP/bad2.jsonl" +jq -c '.triple.sealed_acceptance.done_comment_sha8="mismatch"' "$TMP/corpus.jsonl" > "$TMP/bad3.jsonl" +for f in bad1 bad2 bad3; do + python3 "$CORPUS" validate --corpus "$TMP/$f.jsonl" --min 1 >/dev/null 2>&1 + [[ $? -eq 1 ]] || bad "$f 漏检" +done +ok "三元组残缺三形态 → 红(digest 形状/AC 空/密封不一致)" + +# 最低条数执法:--min 30 而 1 条 → 红 +python3 "$CORPUS" validate --corpus "$TMP/corpus.jsonl" --min 30 >/dev/null 2>&1 +[[ $? -eq 1 ]] && ok "低于最低条数 → 红(30-50 卡下限执法)" || bad "条数下限漏检" + +# ---- 四列元治理评审 ---- +# 真实面:metrics.yaml 四列声明 vs 本仓 workflows job 清单(机械对账) +python3 "$METAGOV" review --policy "$DIR/governance/policy/metrics.yaml" \ + --workflows-dir "$DIR/.github/workflows" --out "$TMP/review.json" >/dev/null 2>&1 +[[ $? -eq 0 ]] && ok "四列评审绿(声明门禁全对账在册,AC-1c)" || bad "四列评审红" +jq -e '.four_columns==["gate","judge","data_source","red_line"] and (.gates|length>=7) + and (.gates[]|select(.gate=="eval-wave:eval-wave")|.judge=="mechanical")' \ + "$TMP/review.json" >/dev/null && ok "评审产物四列结构+门禁行数(含 eval-wave 锚)" || bad "评审产物结构坏" + +# 负向:声明幽灵门禁 → 红(漂移执法) +P="$DIR/governance/policy/metrics.yaml" python3 - "$TMP" <<'PY' && ok "幽灵门禁声明构造(追加 ghost:job)" || bad "fixture 构造失败" +import os, sys, yaml +tmp = sys.argv[1] +p = yaml.safe_load(open(os.environ["P"], encoding="utf-8")) +p["gate_metagovernance"]["gates"].append( + {"gate": "ghost:job", "judge": "mechanical", "data_source": "x", "red_line": "y"}) +open(f"{tmp}/pbad.yaml", "w").write(yaml.safe_dump(p, allow_unicode=True)) +sys.exit(0) +PY +python3 "$METAGOV" review --policy "$TMP/pbad.yaml" \ + --workflows-dir "$DIR/.github/workflows" --out "$TMP/r2.json" >/dev/null 2>&1 +[[ $? -eq 1 ]] && ok "幽灵门禁 → 红" || bad "幽灵门禁漏检" + +# ---- 晋级账本(append-only+hash 链) ---- +REG="$TMP/promotions.jsonl" +cat > "$TMP/rec.json" <<'EOF' +{"practice": "fail-closed 双层验证(write 宽松+verify 严格)", + "goal": "错误事件进不了账本主链,生成器缺陷早暴露", + "evidence": ["run 33263613945", "PR Cloudbird-Software/.github#460"], + "promoted_by": "metagov-review-bot"} +EOF +python3 "$METAGOV" promote --registry "$REG" --record "$TMP/rec.json" >/dev/null 2>&1 +[[ $? -eq 0 ]] && ok "晋级追加绿(evidence 非空+链字段独占计算)" || bad "晋级追加红" + +# 记录自带链字段 → 拒(infra) +cat > "$TMP/rec-chain.json" <<'EOF' +{"practice": "x", "goal": "y", "evidence": ["z"], "promoted_by": "b", "hash": "pretyped"} +EOF +python3 "$METAGOV" promote --registry "$REG" --record "$TMP/rec-chain.json" >/dev/null 2>&1 +[[ $? -eq 2 ]] && ok "自带链字段 → 拒(写入器独占,防伪造链)" || bad "链字段伪造未拒" + +# 空证据 → 拒(自封防御) +echo '{"practice":"x","goal":"y","evidence":[],"promoted_by":"b"}' > "$TMP/rec-empty.json" +python3 "$METAGOV" promote --registry "$REG" --record "$TMP/rec-empty.json" >/dev/null 2>&1 +[[ $? -eq 2 ]] && ok "空证据晋级 → 拒(胜出须有证据——自封防御)" || bad "空证据未拒" + +# 追加第二条 → 链续接+verify 绿;改历史 → 断链红 +echo '{"practice":"p2","goal":"g2","evidence":["e2"],"promoted_by":"b"}' > "$TMP/rec2.json" +python3 "$METAGOV" promote --registry "$REG" --record "$TMP/rec2.json" >/dev/null 2>&1 +python3 "$METAGOV" verify --registry "$REG" >/dev/null 2>&1 +[[ $? -eq 0 ]] && ok "两记录链完整(verify 绿)" || bad "链验红" +python3 - "$REG" <<'PY' +import json, sys +lines = open(sys.argv[1]).read().splitlines() +rec = json.loads(lines[0]); rec["practice"] = "tampered" +open(sys.argv[1], "w").write(json.dumps(rec, ensure_ascii=False, separators=(",", ":")) + "\n" + lines[1]) +PY +python3 "$METAGOV" verify --registry "$REG" >/dev/null 2>&1 +[[ $? -eq 1 ]] && ok "改历史 → 断链红(append-only 执法)" || bad "篡改漏检" + +echo "----------------------------------------" +echo "test-metagov: $([[ $FAIL -eq 0 ]] && echo PASS || echo "FAIL($FAIL)")" +exit $([[ $FAIL -eq 0 ]] && echo 0 || echo 1)