diff --git a/docs/系统架构/新版设计/模块设计/B04-知识方法.md b/docs/系统架构/新版设计/模块设计/B04-知识方法.md index 5b77e48..7833216 100644 --- a/docs/系统架构/新版设计/模块设计/B04-知识方法.md +++ b/docs/系统架构/新版设计/模块设计/B04-知识方法.md @@ -53,7 +53,7 @@ - 方法的实际生效状态只在本模块维护。 - 启用命令的“启用凭据”必须指向B10导出的具体目标证明;旧“验证对象”消费记录不能替代效果。当前仅接写手用途证明,方法状态与enablement消费审计在S01同事务写入;证明绑定来源、版本、材料、资源、策略与期限。状态依赖使用真正的FOR UPDATE当前行锁,禁止仅查版本却声称持有指针锁。 - 由新证明启用的方法只开放generation用途,其他用途明确拒绝;既有绑定仍固定原版本,不因新版本启用而默换。证明来源停止、过期或当前投影不同后阻止新消费,旧交付和启用记录保留。历史启用的迁移边界继续保留,不把旧消费记录补造为新证明。 -- 模型消费登记须同时核对 S02 当前尝试的授权派发回执、完整输入/响应证据、有效交付状态及已登记消费处理器;派发与输入证据的请求哈希相同,任务、作者、用途、调用、用户输入哈希必须一致。单有自报引用或没有派发授权的旧调用不能登记;已有历史记录可回查,不补造授权。生产消费入口只写生产消费表,其他用途返回 `USAGE_SCOPE_UNSUPPORTED`,不得借此入口将隔离评测结果写入生产。 +- 模型消费登记须同时核对 S02 当前尝试的授权派发回执、完整输入/响应证据、有效交付状态及已登记消费处理器;派发与输入证据的请求哈希相同,任务、作者、用途、调用、用户输入哈希必须一致。单有自报引用或没有派发授权的旧调用不能登记;已有历史记录可回查,不补造授权。生产消费入口只写生产消费表,其他用途返回 `USAGE_SCOPE_UNSUPPORTED`,不得借此入口将隔离评测结果写入生产。只接受已实现的真实消费步骤,流程白名单随实现登记:generation×生成正文、planning×生成规划、review×完整审校、review×审校修订(入选参考在修订候选真实交付后登记);新流程接入须先扩白名单,不默认放行。 - 实际模型输入须携带 `上下文快照ID` 与 `装配材料`;由 B09 核对同任务快照、派发时的快照指纹、入选版本、来源绑定和逐键片段,不以当前方法重新渲染的文字替代历史输入。用于校验的模型输入仅瞬时传递,不写入消费表或延长原文保留。 - 任务与尝试仍有效时,合法派发并取得有效完整交付的消费按历史事实登记;交付后、登记前的方法停用不阻止该次登记。停用继续阻止新派发与正式采纳,任务取消或旧尝试迟到不能绕过 S02 交付有效性。 - 消费记录说明有效模型交付时确有方法输入,不证明整条任务完成、方法有效或版本已测。后续步骤失败不擦除已经发生的消费;没有有效模型交付的任务不能补写消费。 diff --git a/docs/系统架构/新版设计/模块设计/B06-审校修订.md b/docs/系统架构/新版设计/模块设计/B06-审校修订.md index eb3ff19..09d66b3 100644 --- a/docs/系统架构/新版设计/模块设计/B06-审校修订.md +++ b/docs/系统架构/新版设计/模块设计/B06-审校修订.md @@ -179,7 +179,9 @@ HTTP的`revision-tasks`及CLI的`审校 模型修订/修订报告`共用请求 参考改写不新建流程,扩展选段写作授权(mode=rewrite)的既有受控模型修订链。作者挑选参考发生在流程发起之前:检索候选→偏好重排→作者挑选参考方法版本(可不挑)并可点踩;挑选与点踩经 B07 参考挑选反馈落账,选中的 method_version_id 清单作为授权的一部分进入冻结输入。流程内无暂停点,冻结语义不变;人挑是授权构造的输入,不是流程内交互。 -冻结修订步骤在既有"修订目标+声音"之外补充两类材料绑定:作者活跃偏好的偏好材料(与生成正文冻结回放同合同)与选中参考方法的原文材料(可选材料语义,受字节预算约束,超预算记省略原因不截断)。两类绑定均入快照;任一来源漂移则快照当前失效,沿用既有依赖复检。 +冻结修订步骤在既有“修订目标+声音”之外补充两类材料绑定:作者活跃偏好的偏好材料(与生成正文冻结回放同合同)与选中参考方法的原文材料(可选材料语义,受字节预算约束,超预算记省略原因不截断)。两类绑定均入快照;任一来源漂移则快照当前失效,沿用既有依赖复检。 + +入选参考在修订候选真实交付后登记方法消费(kind=review,与完整审校同合同:核对派发回执与快照入选依据,发送即事实,交付后停用不抹掉已发送;blocked 交付不登记、不产候选)。只有装配入选的参考登记;未入选参考未进入模型输入,不登记。消费由该登记进入 B04 消费台账,构成 B09 偏好重排的强信号路;与 B07 参考挑选的弱信号分列,不混算。 工作台交互合同:候选默认高亮重排前三,支持键盘多选;每张候选卡显示推荐理由(双路得分分项+历史选用次数),使挑选是知情的。点踩与选用在发起改写时一并落账为 B07 参考挑选反馈,记录的是作者最终选择状态;发起前点踩只是本地状态,作者未发起即离开不留痕。跳过不设显式控件:不挑参考直接发起即等价跳过,不阻塞流程。落账失败不阻断改写,但对作者可见。 diff --git a/src/muse/上下文/任务范围.py b/src/muse/上下文/任务范围.py index ab6ffae..4d8d068 100644 --- a/src/muse/上下文/任务范围.py +++ b/src/muse/上下文/任务范围.py @@ -105,8 +105,11 @@ def _核对材料片段(行: dict, 快照ID: str, 方法版本ID: str, 材料哈 ) 实际 = 按键读取材料(装配) 版本绑定 = {条["source_id"]: 条 for 条 in 行["source_bindings"]} - for 条 in 行["frozen_scope"].get("methods", []): - 绑 = 版本绑定.get(条["binding_id"]) + # 方法节与参考节同样校验:方法节经作品绑定关联,参考节无绑定,以版本身份直接关联。 + 冻结条目 = [*行["frozen_scope"].get("methods", []), *行["frozen_scope"].get("references", [])] + for 条 in 冻结条目: + 绑定键 = 条.get("binding_id", 条["version_id"]) + 绑 = 版本绑定.get(绑定键) if 条["version_id"] != 方法版本ID or 条["材料键"] not in 实际 or 绑 is None: continue if ( diff --git a/src/muse/知识方法/消费记录.py b/src/muse/知识方法/消费记录.py index 289a0fb..3ad41af 100644 --- a/src/muse/知识方法/消费记录.py +++ b/src/muse/知识方法/消费记录.py @@ -66,6 +66,7 @@ def 记录消费( ("generation", "生成正文", "ctx.write"), ("planning", "生成规划", "plan.generate"), ("review", "完整审校", "review.literary"), + ("review", "审校修订", "revision.generate"), }: raise 方法错误("USAGE_FLOW_UNSUPPORTED", "消费类别与已实现的模型流程不一致") 输入哈希 = hashlib.sha256(模型输入.encode("utf-8")).hexdigest() diff --git a/src/muse/编排/审校修订.py b/src/muse/编排/审校修订.py index 389920b..ae9d9ad 100644 --- a/src/muse/编排/审校修订.py +++ b/src/muse/编排/审校修订.py @@ -209,7 +209,17 @@ def 登记审校修订(登记, app, 计价): if 条["材料键"] in 组装.入选来源: bindings.append(参考绑定(条)) snap = 上下文.冻结上下文(ctx.任务.任务ID, 组装, tuple(bindings)) - return 步骤结果({"snapshot_id": snap.快照ID}, 检查点={"snapshot_id": snap.快照ID}) + # 只把装配入选的参考写入检查点:供真实交付后登记方法消费(kind=review)。 + # 未入选参考未进入模型输入,不登记消费;旧任务检查点无该键,向后兼容为空。 + 入选参考 = [ + {"version_id": 条["version_id"], "材料哈希": 条["材料哈希"]} + for 条 in 参考材料 + if 条["材料键"] in 组装.入选来源 + ] + return 步骤结果( + {"snapshot_id": snap.快照ID}, + 检查点={"snapshot_id": snap.快照ID, "参考材料": 入选参考}, + ) def 模型修订(ctx): # 业务结果已提交但步骤回执丢失时,直接补回原结果,不重发模型。 @@ -281,6 +291,26 @@ def 登记审校修订(登记, app, 计价): ) ) with 库.连接() as 连, 连.transaction(): + # 入选参考与候选保存同事务登记消费(kind=review):与完整审校同合同, + # 发送即事实;步骤恢复时 existing 提前返回,同键重复登记亦幂等。 + from muse.知识方法.接口 import 消费输入, 记录消费 + + 冻结 = next(s for s in ctx.任务.步骤 if s["step_id"] == "冻结修订")["checkpoint"] + for r in 冻结.get("参考材料", []): + 记录消费( + 连, + ctx.任务.作者, + 消费输入( + r["version_id"], + "review", + snap_id, + r["材料哈希"], + "model-call:" + str(delivery.调用ID), + ctx.任务.任务ID, + ), + 模型输入=request.用户输入, + 执行用途=库.用途, + ) result = app.要求审校().保存模型修订( 连, app.任务运行, diff --git a/tests/用例清单.json b/tests/用例清单.json index a0fab2e..386e52d 100644 --- a/tests/用例清单.json +++ b/tests/用例清单.json @@ -27677,6 +27677,74 @@ "数据库" ] }, + { + "case_id": "NC-w32-a10023", + "environment": "隔离PG与合成HTTP提供方", + "given": "带入选参考的选段改写授权且修订候选真实交付", + "when": "流程完成后查询方法消费台账", + "then": [ + "入选参考按 kind=review 登记消费,关联快照与材料哈希", + "未入选或未携带参考时不登记", + "消费计数进入 B09 强信号统计" + ], + "contract": "docs/系统架构/新版设计/模块设计/B06-审校修订.md#参考改写选段改写链路扩展", + "file": "tests/集成/test_参考改写.py", + "symbol": "test_入选参考交付后登记消费__a10023", + "parameter_ids": [], + "node_ids": [ + "tests/集成/test_参考改写.py::test_入选参考交付后登记消费__a10023" + ], + "fixtures": [ + "request", + "tmp_path", + "tmp_path_factory", + "内置种子方案", + "内置结构测试库", + "参考环境", + "数据库底座", + "测试资源接缝", + "源码资源", + "生成环境", + "离线防护", + "隔离数据库URL" + ], + "markers": [ + "数据库" + ] + }, + { + "case_id": "NC-w32-a10024", + "environment": "隔离PG与合成HTTP提供方", + "given": "不带参考版本的选段改写授权且候选真实交付", + "when": "流程完成后查询方法消费台账", + "then": [ + "无参考即无新增方法消费记录" + ], + "contract": "docs/系统架构/新版设计/模块设计/B06-审校修订.md#参考改写选段改写链路扩展", + "file": "tests/集成/test_参考改写.py", + "symbol": "test_无参考修订不登记消费__a10024", + "parameter_ids": [], + "node_ids": [ + "tests/集成/test_参考改写.py::test_无参考修订不登记消费__a10024" + ], + "fixtures": [ + "request", + "tmp_path", + "tmp_path_factory", + "内置种子方案", + "内置结构测试库", + "参考环境", + "数据库底座", + "测试资源接缝", + "源码资源", + "生成环境", + "离线防护", + "隔离数据库URL" + ], + "markers": [ + "数据库" + ] + }, { "case_id": "NC-work-current-binding", "environment": "隔离 PostgreSQL;结构升级为明确的已完成状态夹具,不代替作者升级旅程", diff --git a/tests/集成/test_参考改写.py b/tests/集成/test_参考改写.py index a4e55ac..52ea335 100644 --- a/tests/集成/test_参考改写.py +++ b/tests/集成/test_参考改写.py @@ -181,3 +181,90 @@ def test_参考版本漂移使快照失效__a10012(参考环境): 快照 = env["装配"].要求上下文().读取快照(快照ID) assert 快照.失效说明 and "参考方法" in 快照.失效说明 assert "需重新授权" in 快照.失效说明 + + +@pytest.mark.case_id( + "NC-w32-a10023", + environment="隔离PG与合成HTTP提供方", + given="带入选参考的选段改写授权且修订候选真实交付", + when="流程完成后查询方法消费台账", + then=[ + "入选参考按 kind=review 登记消费,关联快照与材料哈希", + "未入选或未携带参考时不登记", + "消费计数进入 B09 强信号统计", + ], + contract="docs/系统架构/新版设计/模块设计/B06-审校修订.md#参考改写选段改写链路扩展", +) +def test_入选参考交付后登记消费__a10023(参考环境): + env = 参考环境 + tid = 修订测试.发起(env, command="ref-revision-usage") + env["剧本"].append( + { + "类型": "文本", + "文本": { + "edits": [ + { + "selection_id": env["选段"].selection_id, + "action": "replace", + "replacement": "刀锋擦过装甲,反震顺着掌心爬上来。", + "reason": "合成参考改写", + } + ] + }, + } + ) + task = 修订测试.跑(env, tid) + assert task.状态 == 任务状态.已完成 + with env["装配"].要求数据库().连接(只读=True) as 连: + rows = 连.execute( + "SELECT kind, context_ref, fragment_hash FROM muse_method_usage " + "WHERE task_id=%s AND method_version_id=%s", + (tid, env["参考版本"]), + ).fetchall() + assert len(rows) == 1 + assert rows[0][0] == "review" + assert rows[0][1] == _快照ID(task) + # 材料哈希与快照入选的一致性由记录消费内部核对方法消费材料保证,不匹配即拒登。 + assert len(rows[0][2]) == 64 + from muse.知识方法.接口 import 读取消费统计 + + 作者ID = env["作者"].作者 + with env["装配"].要求数据库().连接(只读=True) as 连: + 统计 = 读取消费统计(连, 作者ID) + assert 统计[env["参考版本"]] >= 1 + + +@pytest.mark.case_id( + "NC-w32-a10024", + environment="隔离PG与合成HTTP提供方", + given="不带参考版本的选段改写授权且候选真实交付", + when="流程完成后查询方法消费台账", + then=["无参考即无新增方法消费记录"], + contract="docs/系统架构/新版设计/模块设计/B06-审校修订.md#参考改写选段改写链路扩展", +) +def test_无参考修订不登记消费__a10024(参考环境): + env = 参考环境 + env["授权"] = replace(env["授权"], references=()) + tid = 修订测试.发起(env, command="ref-revision-nousage") + env["剧本"].append( + { + "类型": "文本", + "文本": { + "edits": [ + { + "selection_id": env["选段"].selection_id, + "action": "replace", + "replacement": "无参考改写。", + "reason": "合成", + } + ] + }, + } + ) + task = 修订测试.跑(env, tid) + assert task.状态 == 任务状态.已完成 + with env["装配"].要求数据库().连接(只读=True) as 连: + count = 连.execute( + "SELECT COUNT(*) FROM muse_method_usage WHERE task_id=%s", (tid,) + ).fetchone()[0] + assert count == 0