拒绝无审批决定的 user_revision 提交
plan.submit_gdd 新版本仅在 session 已有 revise/reject lastDecisionRef 时接受 user_revision 首次 collecting 伪造 user_revision 改为拒绝且不落 GDD 同步 Fast GDD 技术方案与 decision-log
This commit is contained in:
@@ -1189,6 +1189,33 @@ fn validate_current_session_cas(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// `user_revision` 只证明审批意见,不证明澄清。首次 collecting、澄清续跑和提交前
|
||||
/// 质量返工的 session 都没有 revise/reject `lastDecisionRef`;合法续跑会把该引用
|
||||
/// 带到新的 collecting successor 上。replay 不走这里。
|
||||
fn validate_user_revision_requires_approval_decision(
|
||||
session: &PlanSessionV1,
|
||||
input: &PlanSubmitGddInputV1,
|
||||
) -> Result<(), PlanningStorageError> {
|
||||
if !input
|
||||
.decisions
|
||||
.iter()
|
||||
.any(|decision| decision.answer_source == "user_revision")
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
if session
|
||||
.last_decision_ref
|
||||
.as_ref()
|
||||
.is_some_and(|reference| matches!(reference.action.as_str(), "revise" | "reject"))
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
Err(submit_error(
|
||||
"PLAN_INVALID_REQUEST",
|
||||
"user_revision 只能用于当前 session 已有 revise/reject 审批决定的续跑提交",
|
||||
))
|
||||
}
|
||||
|
||||
fn validate_durable_child_binding(
|
||||
root: &std::path::Path,
|
||||
context: &PlanSubmitGddRuntimeContext,
|
||||
@@ -1765,6 +1792,7 @@ pub(crate) fn execute_plan_submit_gdd(
|
||||
};
|
||||
validate_plan_session(current_session)?;
|
||||
validate_current_session_cas(current_session, context)?;
|
||||
validate_user_revision_requires_approval_decision(current_session, input)?;
|
||||
let version = chain
|
||||
.last()
|
||||
.map(|latest| latest.version.saturating_add(1))
|
||||
@@ -3303,7 +3331,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn submit_allows_user_revision_decisions_outside_the_previous_session_snapshot() {
|
||||
fn submit_rejects_user_revision_without_revise_or_reject_decision() {
|
||||
let (root, context, mut input) = submit_fixture();
|
||||
input.decisions.push(PlanSubmitDecision {
|
||||
id: "invented-confirmation".to_string(),
|
||||
@@ -3314,13 +3342,11 @@ mod tests {
|
||||
answer_summary: "用户在审批意见中明确提出".to_string(),
|
||||
});
|
||||
|
||||
execute_plan_submit_gdd(&root, &context, &input)
|
||||
.expect("a user revision may add a decision to the new snapshot");
|
||||
let chain = read_plan_gdd_chain(&root).expect("read chain");
|
||||
assert_eq!(
|
||||
chain[0].decisions.last().unwrap().answer_source,
|
||||
"user_revision"
|
||||
);
|
||||
let error = execute_plan_submit_gdd(&root, &context, &input)
|
||||
.expect_err("first collecting submit cannot forge user_revision");
|
||||
assert_eq!(error.code(), "PLAN_INVALID_REQUEST");
|
||||
assert!(error.to_string().contains("revise/reject"));
|
||||
assert!(!root.join(".agent/planning/gdd.v1.json").exists());
|
||||
cleanup_fixture(root);
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,14 @@
|
||||
|
||||
---
|
||||
|
||||
## 2026-08-27 `plan.submit_gdd` 拒绝无审批决定的 `user_revision`
|
||||
|
||||
- 背景:结构校验允许 `round=0 + user_revision + confirmed`,提交闸原先只做结构、身份和 Session CAS。Provider 可在首次 collecting、澄清续跑或提交前质量返工里把未确认项标成用户审批修改,审批卡显示「已确认」。
|
||||
- 决策:新版本 create 时,payload 含 `user_revision` 则当前 session 的 `lastDecisionRef.action` 必须是 `revise` 或 `reject`;否则 `PLAN_INVALID_REQUEST`。同 `submissionId` replay 不重判。不恢复 session 前缀逐项相等,不把 `user_revision` 与审批意见正文对齐,也不在这次处理 `round≥1` 的 `user_option` 伪造。
|
||||
- 影响范围:`planning_submit.rs` 提交闸;Fast GDD 技术方案第 5.1 / 8.2 / 12 节。
|
||||
- 验证方式:首次 collecting 带 invented-confirmation 必须拒绝且不落 GDD;reject continuation 再交 `user_revision` 的 v2 仍成功。
|
||||
- 关联文档:`docs/technical/【技术方案】立项策划Agent(Fast GDD)-2026-08-10.md`。
|
||||
|
||||
## 2026-08-27 SpacetimeDB 工具链统一升级到 2.8.3
|
||||
|
||||
- 背景:SpacetimeDB 2.8.0 引入 TypeScript submodule 与调度延迟观测,2.8.1 修复 v1 WebSocket 订阅移除死锁、TypeScript SDK `array<u8>` 读缓存别名和 Rust string 默认值支持,2.8.2 修复 table accessor 改名自动迁移,2.8.3 修复 scheduled function 从实际执行时间重排导致的长期漂移。仓库若继续锁定 2.7.0,会保留这些已知运行时与 SDK 问题。
|
||||
|
||||
@@ -402,7 +402,7 @@ Runtime 注入并强校验以下精确结构:
|
||||
- **轮次计数与上限**:本轮是第几轮由委派链上的 `clarification_round` 派生值决定(沿 `repair_of_delegation_id` 上溯推断,见第 23.5 节),上限 3;不再由 session 自行累加 `roundsUsed`。session 仍是 decisions 数组与 GDD 草稿内容的权威,但**不再是轮次状态机的权威**。
|
||||
|
||||
> **随之而来的合同影响 —— 2026-08-13 已全部收口。** 第 3 节注册表的 `plan-decision-checkpoint.v1` 与 request kind、第 8.6 节 `plan-session.v1` 的 `activeQuestion` / `roundsUsed` / `supersededCheckpointHandoffs`、第 9 节的 checkpoint domain 与 `supersededCheckpointProviderRequestIds`、第 12 节的 checkpoint stale 状态机、第 14 节与 activeQuestion 相关的恢复行,均已随本节重写一并删除或改写;第 9.1 节 golden vector 已按新 identity 重新生成(3857 bytes,`a59856de7e…`)。
|
||||
- 用户明确输入优先于 Agent 默认;默认建议必须标为 `default_pending`,手感、节奏、镜头、可读性或重玩差异等需要验证的结论标为 `prototype_pending`。审批阶段的用户修改意见使用 `answerSource=user_revision`、`round=0`。
|
||||
- 用户明确输入优先于 Agent 默认;默认建议必须标为 `default_pending`,手感、节奏、镜头、可读性或重玩差异等需要验证的结论标为 `prototype_pending`。审批阶段的用户修改意见使用 `answerSource=user_revision`、`round=0`;Runtime 仅在 session 带 revise/reject 的 `lastDecisionRef` 时接受该来源。
|
||||
- session revision 1 由 Runtime 先写入固定 `initial-request` 决定:topic=`初始需求`、state=`confirmed`、answerSource=`user_freeform`、round=0、answerSummary 精确等于规范化后的 1~400 scalar 初始用户需求。Provider 不能改写或省略这条来源记录;超过上限的初始输入先要求用户收束,不能截断。
|
||||
|
||||
### 5.2 决策卡
|
||||
@@ -663,7 +663,7 @@ Provider 只能提交设计内容,不能提交或覆盖任何 Runtime 身份
|
||||
|
||||
该 input 及所有嵌套类型都使用 `deny_unknown_fields`;`game.platformFacts`、任意 `basis`、`projectId/gddId/version/submissionId/approvalRequestId`、action/run/session identity、时间与任何 fingerprint 一旦出现在 Provider input 中即返回 `PLAN_INVALID_REQUEST`。Runtime 在发出本轮 Provider request 前把当前 `sessionRevision/sessionFingerprint` 绑定进内部执行上下文,在项目锁内验证该 CAS 后,才把 project、GDD、版本、durable action、source/profile、session/run、时间、固定 `platformFacts`、全部 `basis:null` 与 fingerprint 注入 `plan-gdd.v1`。字段数量和文本限制按第 8.3 节对应 durable 字段执行。
|
||||
|
||||
input 是当前 GDD 的完整快照,不要求与 source session 的 `decisionsSummary` 和 `prototypeValidationItems` 逐项相等。审批修订可用 `user_revision + round=0` 修改、删除或新增决定;未涉及内容由 Agent 以当前 GDD 为基线保持不变。Runtime 仍校验决定结构、原型项双射、`initial-request` 首项、身份和 CAS。唯一固定的 `confirmed + user_freeform + round=0` 是 Runtime 创建的 `initial-request`。
|
||||
input 是当前 GDD 的完整快照,不要求与 source session 的 `decisionsSummary` 和 `prototypeValidationItems` 逐项相等。审批修订可用 `user_revision + round=0` 修改、删除或新增决定;未涉及内容由 Agent 以当前 GDD 为基线保持不变。Runtime 仍校验决定结构、原型项双射、`initial-request` 首项、身份和 CAS。payload 出现 `answerSource=user_revision` 时,当前 session 的 `lastDecisionRef.action` 必须是 `revise` 或 `reject`;首次提交、澄清续跑和普通质量返工返回 `PLAN_INVALID_REQUEST`。该闸只作用于新版本 create,同 `submissionId` replay 不重判。唯一固定的 `confirmed + user_freeform + round=0` 是 Runtime 创建的 `initial-request`。
|
||||
|
||||
### 8.3 `plan-gdd.v1`
|
||||
|
||||
@@ -1161,7 +1161,7 @@ GDD handler 只能从已验证 batch binding 复制 `sourceSessionRevision/sourc
|
||||
main loop 不能把 submit 当成普通 action dispatch:在 durable action identity 建立后、生成普通 command ID 或进入 action executor 前,必须进入 `plan.submit_gdd` 专用分支。该分支重验 exact plan identity,执行下列提交与投影。**(2026-08-14 按 M1B-2 实现边界收口)** 本包只负责校验、定版、写不可变 GDD、重建 index、渲染 `game/fast_gdd.md`、安装 session successor 并终止策划子 run;**不创建 `.agent/planning/pending.json` / `gdd-approval` planning pending,不创建审批卡,也不把 Supervisor 或策划子 run 投影为审批等待**。`gdd-approval` pending 与 Supervisor 等待态属于 `M1C-1`,还要受第 13.0 节 `M1C-2a` 验收取证门约束。原 submit 在进入专用分支前已经建立的 generic `game-creator-pending-action.v5` standalone pending 与 `game-creator-provider-action-batch.v4` action batch 必须原样保留,作为后续 receipt/terminal observation 的同 action 恢复锚点;GDD create 成功不等于该 action 已 observed。
|
||||
|
||||
1. 解析第 8.2 节 strict input;在项目锁内重读 project identity、策划子 run 与委派根身份、Provider request 所绑定的 session CAS、canonical GDD 链及原 submit 的 generic v5 standalone pending / v4 batch anchors。不信任 Provider payload 中不存在也不允许出现的版本、时间、平台事实或身份;M1B-2 不读取或创建尚未实现的 approval receipt / planning pending。
|
||||
2. 验证文本上限、轮次、决定状态和 prototype item 一一对应;`decisions` 按本次完整 GDD 快照校验,不与旧 session 内容逐项比较。`round=0` 的非首项决定只能是 `answerSource=default`(默认建议)或 `answerSource=user_revision`(审批修改),分别对应允许的状态集合。Runtime 注入固定 platformFacts 和所有 `basis:null`,以当前 durable actionId/裸 action fingerprint 作为 submission identity。
|
||||
2. 验证文本上限、轮次、决定状态和 prototype item 一一对应;`decisions` 按本次完整 GDD 快照校验,不与旧 session 内容逐项比较。`round=0` 的非首项决定只能是 `answerSource=default`(默认建议)或 `answerSource=user_revision`(审批修改),分别对应允许的状态集合。`user_revision` 还要求当前 session 已有 `lastDecisionRef.action ∈ {revise, reject}`;没有该引用时不得把未确认项标成审批修改。Runtime 注入固定 platformFacts 和所有 `basis:null`,以当前 durable actionId/裸 action fingerprint 作为 submission identity。
|
||||
3. M1B-2 尚无 receipt writer:只要已有任一 GDD,新的不同 submissionId 就返回 `PLAN_PENDING_GDD_EXISTS`;同 submissionId 只允许按历史 binding replay。`M1C-1` 接入有效 approve/revise/reject receipt 后,才把边界扩为“最新版本已有 receipt 才允许下一版本”。
|
||||
4. 当前 M1B-2 的首次版本固定为 1;未来版本仍只能取最后一个连续有效版本加一,范围 1~128,不允许缺号或扫描任意文件补号。
|
||||
5. 新提交由 Runtime 生成并冻结 `approvalRequestId/createdAtUtc`,填充全部 durable identity、source session binding 和时间,计算 GDD fingerprint,以第 10.1 节算法 create-only 发布 `gdd.v{N}.json`。同 submissionId replay 必须先找到并严格读取既有 GDD,复用其中 Runtime 生成的版本、request/time 与 identity 后再比较,不能用新时间制造假冲突。
|
||||
|
||||
Reference in New Issue
Block a user