merge: B8 门挂载——四契约门挂 pre-commit(.githooks 五门条件触发)+ Gitea Actions contract-gates.yml + rubric↔skill 双写对账门(rubric-sync-gate.py)+ check_registry 真挂载 + prompt治理档口径对齐(Δ5 执行面)·负向演示×4 全红转绿·live-fire 实证
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
commit
685a21a74c
@ -81,6 +81,7 @@
|
||||
|---|---|
|
||||
| `docs-gate.py` / `docs-gate.sh` | 文档治理机器门(engineering-conventions §10.7):七检——品牌不变量 / canonical 唯一 / 死链 / 入口卫生 / 留痕隔离 / 设计档申报 / 计划谱系(`上级:` 单指针) |
|
||||
| `plan-tree.py` | 计划谱系树查询视图(engineering-conventions §10.8):沿 frontmatter `上级:` 拼树打印,回答「某计划在上线计划的哪个位置 / 某线推进到哪」 |
|
||||
| `rubric-sync-gate.py` | 品类 rubric fixture ↔ skill 双写对账门(Δ5「无校验器不算契约」执行面):以 `cheap-worker/fixtures/genre-rubrics/<genre>.json` 为单源,校验各品类 skill 的内联副本名目一致 / 纯指针可达,漂移 exit 1;已挂 pre-commit + Gitea Actions |
|
||||
| `doc-organizer.{sh,-analyze.mjs}` + `doc-organizer-state.json` | doc-organizer 底层:检测(scan/health/stale/ledger)+ 可逆归档 + 记时 + 三轴分析 Workflow + 增量窗口锚点 |
|
||||
|
||||
---
|
||||
|
||||
259
.agents/tools/rubric-sync-gate.py
Executable file
259
.agents/tools/rubric-sync-gate.py
Executable file
@ -0,0 +1,259 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""rubric-sync-gate.py —— 品类 rubric fixture ↔ 品类 skill 双写对账机器门。
|
||||
|
||||
背景:每个品类的丰富度 rubric(喂 LLM 验证 agent 的评分尺)以
|
||||
`cheap-worker/fixtures/genre-rubrics/<genre>.json` 为**单一数据源**。部分品类的
|
||||
设计 skill(`.agents/skills/<genre>-game-design.md`)出于人读需要,在 §10/§11 里
|
||||
留了一份内联条目副本;另一些已收敛成"只留一个指针指向 fixture、不再抄表"。两种形态
|
||||
都有漂移风险:内联副本会与 fixture 各改一侧而失同步,纯指针会指向一个被改名/删除的
|
||||
fixture。本门把这条"改一处必须同改另一处 / 指针必须真实可达"的纪律编译成可被 CI 与
|
||||
pre-commit 消费的退出码。
|
||||
|
||||
判据(逐品类):
|
||||
1. 通用底座:fixture 文件必须存在、能解析、items 非空且每条有 name —— 否则硬失败。
|
||||
2. 指针完好(所有品类):skill 正文必须引用到本品类 fixture 路径
|
||||
`genre-rubrics/<genre>.json`,且该文件真实存在 —— 否则硬失败。
|
||||
3. 内联对账(仅带内联条目副本的品类):
|
||||
逐条按序比对 fixture 的 name 与 skill 内联表/清单抽出的条目名。
|
||||
· 规范化后逐字相等 → 一致;
|
||||
· 规范化后 fixture 名是 skill 名的子序列 → 判为"同条目·措辞装饰"(容忍,
|
||||
打印提示不失败)——skill 是人读自检清单,允许在 canonical 名上加描述性修饰
|
||||
(如"风险回报取舍"写成"风险-回报取舍真实");
|
||||
· 否则 → 判为真实漂移(改名/替换),硬失败并打印差异;
|
||||
条目数不一致(增删条目)→ 硬失败。
|
||||
|
||||
规范化 = 只保留中日韩统一表意文字(去标点/空格/ASCII/加减号等),以吸收人读排版差异、
|
||||
只在"名目"层面对账。
|
||||
|
||||
零依赖:仅标准库(json/re/sys/pathlib),与同仓 docs-gate.py / check_registry.py /
|
||||
play-loop/validate.py 同栈同风格。
|
||||
|
||||
用法:
|
||||
python3 .agents/tools/rubric-sync-gate.py # 对本仓
|
||||
python3 .agents/tools/rubric-sync-gate.py --root DIR # 对指定仓根(供负向演示跑副本)
|
||||
# exit 0 = 全部对齐;exit 1 = 有真实漂移/指针断裂/fixture 损坏
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# 本文件 = <root>/.agents/tools/rubric-sync-gate.py → 仓根上溯两级
|
||||
DEFAULT_ROOT = Path(__file__).resolve().parents[2]
|
||||
|
||||
# 品类配置(逐一考察真实状态后编码,2026-07-03):
|
||||
# mode=inline → skill 在指定小节留了内联条目表/清单,需逐条对账;
|
||||
# mode=pointer → skill 已收敛为纯指针,只校验指针路径真实存在。
|
||||
# section_anchor:内联小节标题前缀(用于把抽取范围钉在该节内,不误采别处的表/清单)。
|
||||
# row_re:该节内"条目行"的识别正则(表行 / 编号清单行)。
|
||||
GENRES = {
|
||||
"puzzle": {
|
||||
"skill": ".agents/skills/puzzle-game-design.md",
|
||||
"mode": "inline",
|
||||
"section_anchor": "## 10.",
|
||||
"row_re": r"^\|\s*P\d", # §10 品类扩展表:| P1 | L2 | 谜题规则递进 | …
|
||||
},
|
||||
"trpg": {
|
||||
"skill": ".agents/skills/trpg-game-design.md",
|
||||
"mode": "inline",
|
||||
"section_anchor": "## 10.",
|
||||
"row_re": r"^\d+\.\s+\*\*", # §10 自检编号清单:1. **[L2] 掷骰过程可见**:…
|
||||
},
|
||||
"heritage": {
|
||||
"skill": ".agents/skills/heritage-game-design.md",
|
||||
"mode": "inline",
|
||||
"section_anchor": "## 11.",
|
||||
"row_re": r"^\|\s*H\d", # §11 品类 rubric 表:| H1 | L2 | **工序链成立**:… |
|
||||
},
|
||||
"narrative": {
|
||||
"skill": ".agents/skills/narrative-game-design.md",
|
||||
"mode": "pointer", # §10 已迁出为单源指针,不再维护表格副本
|
||||
},
|
||||
"sim-business": {
|
||||
"skill": ".agents/skills/sim-business-game-design.md",
|
||||
"mode": "pointer", # §10 只有通用自检 + 指向 fixture 的指针,无品类表副本
|
||||
},
|
||||
}
|
||||
|
||||
_IDEOGRAPH = re.compile(r"[一-鿿]")
|
||||
|
||||
|
||||
def _norm(name: str) -> str:
|
||||
"""规范化:只留中日韩表意文字,吸收标点/空格/ASCII/加减号等人读排版差异。"""
|
||||
return "".join(_IDEOGRAPH.findall(name or ""))
|
||||
|
||||
|
||||
def _is_subsequence(needle: str, haystack: str) -> bool:
|
||||
"""needle 的字符是否按序(可不连续)出现在 haystack 中。"""
|
||||
it = iter(haystack)
|
||||
return all(ch in it for ch in needle)
|
||||
|
||||
|
||||
def _fixture_path(root: Path, genre: str) -> Path:
|
||||
return root / "cheap-worker" / "fixtures" / "genre-rubrics" / f"{genre}.json"
|
||||
|
||||
|
||||
def _load_fixture_names(root: Path, genre: str):
|
||||
"""读 fixture,返回 (names 列表, 错误串)。错误串非空表示通用底座判据未过。"""
|
||||
fp = _fixture_path(root, genre)
|
||||
if not fp.exists():
|
||||
return None, f"fixture 缺失:{fp}"
|
||||
try:
|
||||
data = json.loads(fp.read_text(encoding="utf-8"))
|
||||
except Exception as e: # noqa: BLE001 —— 解析失败即 fixture 损坏,如实报
|
||||
return None, f"fixture JSON 解析失败:{e}"
|
||||
items = data.get("items")
|
||||
if not isinstance(items, list) or not items:
|
||||
return None, "fixture 缺 items 或为空"
|
||||
names = []
|
||||
for i, it in enumerate(items):
|
||||
nm = (it or {}).get("name")
|
||||
if not nm:
|
||||
return None, f"fixture items[{i}] 缺 name"
|
||||
names.append(nm)
|
||||
return names, None
|
||||
|
||||
|
||||
def _slice_section(lines, anchor: str):
|
||||
"""取 anchor 小节的行区间:从标题行到下一条 `---` 或下一个 `## ` 标题之前。"""
|
||||
start = None
|
||||
for i, ln in enumerate(lines):
|
||||
if ln.startswith(anchor):
|
||||
start = i
|
||||
break
|
||||
if start is None:
|
||||
return None
|
||||
end = len(lines)
|
||||
for j in range(start + 1, len(lines)):
|
||||
s = lines[j].strip()
|
||||
if s == "---" or lines[j].startswith("## "):
|
||||
end = j
|
||||
break
|
||||
return lines[start:end]
|
||||
|
||||
|
||||
def _extract_inline_name(row: str) -> str:
|
||||
"""从一条内联条目行抽出品类条目名。统一处理三种形态:
|
||||
· 表行(含 `|`):取第 3 个单元格(条目/判据列);
|
||||
· 编号清单行:取 `N. ` 之后的正文;
|
||||
再:剥 markdown 粗体 `**`、去开头的 `[Lx]` 层标、截到首个中文冒号/英文冒号前。
|
||||
"""
|
||||
if "|" in row:
|
||||
cells = row.split("|")
|
||||
frag = cells[3] if len(cells) > 3 else row
|
||||
else:
|
||||
frag = re.sub(r"^\d+\.\s+", "", row)
|
||||
frag = frag.replace("**", "")
|
||||
frag = re.sub(r"^\s*\[L\d\]\s*", "", frag) # 去 trpg 的 [L2] 层标前缀
|
||||
frag = re.split(r"[::]", frag, maxsplit=1)[0]
|
||||
return frag.strip()
|
||||
|
||||
|
||||
def _skill_inline_names(root: Path, cfg: dict):
|
||||
"""按配置从 skill 指定小节抽出内联条目名列表,返回 (names, 错误串)。"""
|
||||
sp = root / cfg["skill"]
|
||||
if not sp.exists():
|
||||
return None, f"skill 缺失:{sp}"
|
||||
lines = sp.read_text(encoding="utf-8").splitlines()
|
||||
section = _slice_section(lines, cfg["section_anchor"])
|
||||
if section is None:
|
||||
return None, f"skill 找不到小节 {cfg['section_anchor']}"
|
||||
row_re = re.compile(cfg["row_re"])
|
||||
names = [_extract_inline_name(ln) for ln in section if row_re.match(ln)]
|
||||
return names, None
|
||||
|
||||
|
||||
def _skill_has_pointer(root: Path, genre: str, cfg: dict):
|
||||
"""校验 skill 正文含指向本品类 fixture 的指针,返回 (bool, 错误串)。"""
|
||||
sp = root / cfg["skill"]
|
||||
if not sp.exists():
|
||||
return False, f"skill 缺失:{sp}"
|
||||
text = sp.read_text(encoding="utf-8")
|
||||
if f"genre-rubrics/{genre}.json" not in text:
|
||||
return False, f"skill 未引用 fixture 指针 genre-rubrics/{genre}.json"
|
||||
return True, None
|
||||
|
||||
|
||||
def check_genre(root: Path, genre: str, cfg: dict):
|
||||
"""对账单个品类,返回 (ok, 明细行列表)。ok=False 表示硬失败。"""
|
||||
detail = []
|
||||
|
||||
# 判据 1:通用底座 —— fixture 完好
|
||||
fx_names, err = _load_fixture_names(root, genre)
|
||||
if err:
|
||||
return False, [f" ✗ {err}"]
|
||||
|
||||
# 判据 2:指针完好(所有品类)
|
||||
ptr_ok, ptr_err = _skill_has_pointer(root, genre, cfg)
|
||||
if not ptr_ok:
|
||||
return False, [f" ✗ {ptr_err}"]
|
||||
detail.append(f" ✓ 指针可达:skill 引用 genre-rubrics/{genre}.json,fixture {len(fx_names)} 条")
|
||||
|
||||
if cfg["mode"] == "pointer":
|
||||
detail.append(" · 形态=纯指针(skill 不维护副本),只校验指针 —— 通过")
|
||||
return True, detail
|
||||
|
||||
# 判据 3:内联对账
|
||||
sk_names, err = _skill_inline_names(root, cfg)
|
||||
if err:
|
||||
return False, detail + [f" ✗ {err}"]
|
||||
|
||||
if len(sk_names) != len(fx_names):
|
||||
return False, detail + [
|
||||
f" ✗ 条目数漂移:fixture {len(fx_names)} 条 vs skill 内联 {len(sk_names)} 条",
|
||||
f" fixture:{fx_names}",
|
||||
f" skill :{sk_names}",
|
||||
]
|
||||
|
||||
drift = []
|
||||
paraphrase = []
|
||||
for i, (fn, sn) in enumerate(zip(fx_names, sk_names)):
|
||||
fnn, snn = _norm(fn), _norm(sn)
|
||||
if fnn == snn:
|
||||
continue
|
||||
if _is_subsequence(fnn, snn):
|
||||
paraphrase.append(f" · 第{i + 1}条 同条目·措辞装饰:fixture「{fn}」→ skill「{sn}」")
|
||||
else:
|
||||
drift.append(f" · 第{i + 1}条 真实漂移:fixture「{fn}」 ≠ skill「{sn}」")
|
||||
|
||||
if drift:
|
||||
return False, detail + [f" ✗ 内联副本与 fixture 名目漂移({len(drift)} 条):"] + drift + paraphrase
|
||||
if paraphrase:
|
||||
detail.append(f" ✓ 内联副本 {len(fx_names)} 条名目一致({len(paraphrase)} 条为措辞装饰、非漂移):")
|
||||
detail.extend(paraphrase)
|
||||
else:
|
||||
detail.append(f" ✓ 内联副本 {len(fx_names)} 条名目逐字一致")
|
||||
return True, detail
|
||||
|
||||
|
||||
def main(argv) -> int:
|
||||
root = DEFAULT_ROOT
|
||||
if len(argv) >= 3 and argv[1] == "--root":
|
||||
root = Path(argv[2]).resolve()
|
||||
|
||||
print("品类 rubric fixture ↔ skill 双写对账门")
|
||||
print("=" * 60)
|
||||
all_ok = True
|
||||
for genre, cfg in GENRES.items():
|
||||
ok, detail = check_genre(root, genre, cfg)
|
||||
status = "PASS" if ok else "FAIL"
|
||||
icon = "✔" if ok else "✗"
|
||||
print(f"\n[{status}] {genre} {icon} ({cfg['mode']})")
|
||||
for d in detail:
|
||||
print(d)
|
||||
if not ok:
|
||||
all_ok = False
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
if all_ok:
|
||||
print("对账通过:所有品类 fixture 单源与 skill 副本/指针一致。")
|
||||
return 0
|
||||
print("对账失败:见上 ✗ —— 改一处必须同改另一处(fixture 为单源),或修复断裂指针。")
|
||||
return 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main(sys.argv))
|
||||
35
.gitea/workflows/contract-gates.yml
Normal file
35
.gitea/workflows/contract-gates.yml
Normal file
@ -0,0 +1,35 @@
|
||||
# 契约机器门 · 服务端兜底(Gitea Actions,与 GitHub Actions 语法兼容)
|
||||
# pre-commit 是自愿门(--no-verify / 新 clone 未配 hooksPath 可绕),本工作流是不可绕的服务端门。
|
||||
# 与 docs-gate.yml 平级、各管一摊:docs-gate 管文档治理七检,本工作流管四类契约门。
|
||||
# 生效前提:Gitea 实例已配 act_runner;未配时本文件静默不跑,不影响开发。
|
||||
# CI 侧全量真跑(不做 pre-commit 的路径条件触发):判例库四段尽量装齐 esbuild + pytest 后全跑,
|
||||
# 装不齐时 run.mjs 自带零依赖降级、诚实 SKIP 缺依赖段,不假绿。
|
||||
name: contract-gates
|
||||
on:
|
||||
push:
|
||||
branches: ['**']
|
||||
pull_request:
|
||||
jobs:
|
||||
contract-gates:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
# 判例库 python-gates 段需 pytest;check-undef 段需 esbuild(随 node_modules)。
|
||||
# 尽力预装让四段全量真跑;装不上则相应段 run.mjs 会诚实 SKIP。
|
||||
- name: 预装门依赖(尽力)
|
||||
run: |
|
||||
pip3 install --quiet pytest || true
|
||||
npm ci --prefix game-runtime/tools/amodel-gen || true
|
||||
|
||||
- name: 门金标判例库全量回归
|
||||
run: node contracts/gate-fixtures/run.mjs
|
||||
|
||||
- name: play-loop 契约正负样本套件
|
||||
run: python3 contracts/play-loop/validate.py --suite
|
||||
|
||||
- name: 品类 rubric fixture↔skill 双写对账
|
||||
run: python3 .agents/tools/rubric-sync-gate.py
|
||||
|
||||
- name: Prompt Registry 版本一致性
|
||||
run: python3 contracts/prompts/check_registry.py
|
||||
@ -1,9 +1,40 @@
|
||||
#!/usr/bin/env bash
|
||||
# 文档治理门 pre-commit(激活一次:git config core.hooksPath .githooks)
|
||||
# 仅当提交含 md / AGENTS.md 变更时跑 docs-gate(<5s);紧急绕过:git commit --no-verify(服务端与 wave-close 门仍会兜住)。
|
||||
if git diff --cached --name-only | grep -qE '\.md$'; then
|
||||
bash "$(git rev-parse --show-toplevel)/.agents/tools/docs-gate.sh" || {
|
||||
echo '✘ docs-gate 未过,提交被拦(明细见上;紧急绕过 --no-verify)'
|
||||
exit 1
|
||||
}
|
||||
# 治理机器门 pre-commit(激活一次:git config core.hooksPath .githooks)
|
||||
# 各门按暂存文件路径条件触发(判据没动的提交不跑对应门,省时间);
|
||||
# 紧急绕过:git commit --no-verify(服务端 Gitea Actions 门仍会兜住)。
|
||||
set -uo pipefail
|
||||
|
||||
STAGED="$(git diff --cached --name-only)"
|
||||
ROOT="$(git rev-parse --show-toplevel)"
|
||||
fail=0
|
||||
|
||||
# ── 门 1:文档治理七检 —— 触发 = 任一 .md 变更 ──
|
||||
if printf '%s\n' "$STAGED" | grep -qE '\.md$'; then
|
||||
bash "$ROOT/.agents/tools/docs-gate.sh" || { echo '✘ docs-gate 七检未过'; fail=1; }
|
||||
fi
|
||||
|
||||
# ── 门 2:门金标判例库全量回归 —— 触发 = 触及任一验收门判据或判例库自身 ──
|
||||
# 判据文件改动前必须先让本回归全绿(误杀/漏放判例不得回退),口径见 contracts/gate-fixtures/README.md。
|
||||
if printf '%s\n' "$STAGED" | grep -qE '(play\.cdp\.cjs|amodel-gen/tools\.mjs|check-undef\.mjs|gate_judge\.py|cheap_gates\.py|^contracts/gate-fixtures/)'; then
|
||||
node "$ROOT/contracts/gate-fixtures/run.mjs" || { echo '✘ 门金标判例库回归未过'; fail=1; }
|
||||
fi
|
||||
|
||||
# ── 门 3:play-loop 契约校验(PlaySpec C5 / VerdictFeedback C6)—— 触发 = 触及 play-loop 契约 ──
|
||||
if printf '%s\n' "$STAGED" | grep -qE '^contracts/play-loop/'; then
|
||||
python3 "$ROOT/contracts/play-loop/validate.py" --suite || { echo '✘ play-loop 契约正负样本套件未过'; fail=1; }
|
||||
fi
|
||||
|
||||
# ── 门 4:品类 rubric fixture ↔ skill 双写对账 —— 触发 = 触及品类 rubric fixture 或品类 skill ──
|
||||
if printf '%s\n' "$STAGED" | grep -qE '(cheap-worker/fixtures/genre-rubrics/|\.agents/skills/(trpg|heritage|puzzle|narrative|sim-business)-game-design\.md)'; then
|
||||
python3 "$ROOT/.agents/tools/rubric-sync-gate.py" || { echo '✘ rubric 双写对账未过'; fail=1; }
|
||||
fi
|
||||
|
||||
# ── 门 5:Prompt Registry 版本一致性 —— 触发 = 触及 prompts 契约(registry.yaml 或任一 prompt .md) ──
|
||||
if printf '%s\n' "$STAGED" | grep -qE '^contracts/prompts/'; then
|
||||
python3 "$ROOT/contracts/prompts/check_registry.py" || { echo '✘ Prompt Registry 版本一致性未过'; fail=1; }
|
||||
fi
|
||||
|
||||
if [ "$fail" -ne 0 ]; then
|
||||
echo '提交被拦(明细见上;紧急绕过 git commit --no-verify,服务端门仍兜)'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@ -32,8 +32,8 @@ python3 contracts/prompts/check_registry.py
|
||||
# exit 0 = 全部对齐;exit 1 = 有漂移(打印具体 id + 两侧 version)
|
||||
```
|
||||
|
||||
**接入 CI / pre-commit(推荐):**
|
||||
- CI:在流水线加步骤 `python3 contracts/prompts/check_registry.py`(无三方依赖,标准库即可跑)。
|
||||
- pre-commit:在 `.pre-commit-config.yaml` 加 hook,`entry: python3 contracts/prompts/check_registry.py`,`pass_filenames: false`。
|
||||
**已挂载(2026-07-03):**
|
||||
- pre-commit:`.githooks/pre-commit` 在本次暂存触及 `contracts/prompts/` 时跑本脚本,非零即拦(紧急绕过 `git commit --no-verify`,服务端门仍兜)。
|
||||
- CI:`.gitea/workflows/contract-gates.yml`(与 docs-gate 平级的服务端门)每次 push / PR 全量跑,不可绕。
|
||||
|
||||
触发时机:任何 `.md` 或 `registry.yaml` 变更都应跑一次;CI 全量跑。
|
||||
触发时机:任何 prompt `.md` 或 `registry.yaml` 变更(pre-commit 按路径条件触发);CI 每次全量跑。
|
||||
|
||||
@ -133,7 +133,7 @@ guardrails: [injection-detect, schema-validate] # 护栏:注入检测 / schema
|
||||
|
||||
frontmatter 里几个字段值得点名:**owner**(归属工位)规定了"谁能改这条 prompt"——本项目把工位编号为 WS1~WS5(WorkStation,工位),改一条 prompt 要经它的 owner 批准,杜绝无主乱改;**tier**(梯队)区分 prompt 的重要性,tier1 是 MVP 生产主线必须治理的,tier2/3 是更长期的增强轨;**constraints**(硬约束块)是产物必须守住的红线(如包体大小、零网络请求),**guardrails**(护栏)是运行时挂的自动检查(如注入检测、schema 校验)。**engine**(目标引擎)在设计期是区分注入载体的必填枚举,但在现行**单引擎**(new-api / SAA)语境下已退化为假设占位值(真实文件里写作 `engine: newapi-chat` 并自注"枚举未定"),不再是有效的区分维度——保留它只为与真实 frontmatter 一致。另注:示例里的 `input_schema` / `output_schema` 指向真实契约文件(`../../agent-loop/game-design.schema.json` 等),因为 §3 目录里那个 `_schemas/` 目录尚未落地(见上文目录树标注)。
|
||||
|
||||
`registry.yaml` 是这棵树的总索引,逐条登记每个 prompt 的 id、版本、owner、绑定的 schema 和 eval 目录。它和文件系统的实际内容必须**严格一致**——这份索引↔文件的对账由 §4 四道闸 CI 负责,在合入前挡下"配置漂移"(加载器自身只逐模板自检自禁用、不复制此对账逻辑,见 §5/§7)。
|
||||
`registry.yaml` 是这棵树的总索引,逐条登记每个 prompt 的 id、版本、owner、绑定的 schema 和 eval 目录。它和文件系统的实际内容必须**严格一致**——这份索引↔文件的 version 对账由 `contracts/prompts/check_registry.py` 负责(2026-07-03 已挂 `.githooks/pre-commit` 与 Gitea Actions 服务端门,零依赖、退出码可拦提交;它与 §4 四道闸质量闸各管一摊、互不等待——四道闸尚在接线,version 对账已先行上门),在合入前挡下"配置漂移"(加载器自身只逐模板自检自禁用、不复制此对账逻辑,见 §5/§7)。
|
||||
|
||||
---
|
||||
|
||||
@ -189,7 +189,7 @@ boolean validate(PromptTemplate) # 校验 frontmatter 与 sche
|
||||
|
||||
**热取分 read / write 两路(§6.8 双评审修正,2026-06-24)**:运行时热取只解决"新版怎么到达运行时",**不等于绕过治理**。read path——运行期只拉**已批准版本**(按 production label/version),缓存+回落保证可用;write path——改一条 prompt、调一个生成门阈值,仍走四道闸的 version bump + eval + 灰度 + 人工批准(GitOps 审计留痕),**不 DB 热改、不绕审批**。只有运营降级开关/配额数值这类不影响生成质量与安全的配置才 DB 即时热改。坏版本经 label 秒回退,并记录其生效期间产出的 game_id 以便追溯。这条把 §1"prompt 是影响安全的第 8 契约"与"热取便利"调和:便利在 read,纪律在 write。
|
||||
|
||||
> 这里不存在"数据库只读镜像",也没有"运行时回源 git checkout":那是早期设计期契约里设想的载体,工程落地改走了更简单的构建期快照路。`registry.yaml` 与文件是否一致的对账是 §4 四道闸 CI 的职责,加载器不复制治理逻辑。
|
||||
> 这里不存在"数据库只读镜像",也没有"运行时回源 git checkout":那是早期设计期契约里设想的载体,工程落地改走了更简单的构建期快照路。`registry.yaml` 与文件的 version 对账由 `check_registry.py` 承担(2026-07-03 已挂 pre-commit + Gitea Actions),加载器不复制治理逻辑。
|
||||
|
||||
第二组契约值得专门点名,因为它把"prompt 治理"和"产物质量"接到了一起——这就是 **T-AGC-09 测试脚本原子**(原子 = 生成流水线里一个不可再分的处理单元;T-AGC-09 是它的工序编号)。**注意它当前是设计/规划态契约,尚未实现**(game-cloud 内暂无对应实现);其规划接口形态如下:
|
||||
|
||||
@ -225,7 +225,7 @@ HITL 在整条链路里的位置很明确:**它永远站在机器闸之后**。
|
||||
| frontmatter / schema 形态非法 | 加载器启动逐模板自检不过 → 整体 `ready=false` **自禁用**(执行器不带病认领任务),并由 CI 报出具体字段;**不抛异常、不崩进程** |
|
||||
| eval 跑不通(模型限流 / 超时) | CI 标记 skip + 通知人工兜底审,**不阻塞紧急修复** |
|
||||
| 改 prompt 未升 version | CI 卡死:version 未变 → 拒绝合入(即 §4 的闸 0) |
|
||||
| registry.yaml 与文件不一致 | 由 CI 四道闸对账拦截(加载器不复制此治理逻辑,见 §5);合入前挡下漂移 |
|
||||
| registry.yaml 与文件 version 不一致 | 由 `check_registry.py` 对账拦截(2026-07-03 已挂 pre-commit + Gitea Actions;加载器不复制此治理逻辑,见 §5);合入前挡下漂移 |
|
||||
|
||||
回滚同样是分层设计的,**整体回滚成本极低**,因为 Registry 是一个**叠加层**——它叠在原有调用方式之上,而不是替换掉:
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user