diff --git a/docs/cli-interface-flag-migrations.md b/docs/cli-interface-flag-migrations.md index 81477cd5..b349d95c 100644 --- a/docs/cli-interface-flag-migrations.md +++ b/docs/cli-interface-flag-migrations.md @@ -76,7 +76,7 @@ candidate 自增的 pending 记录、其他命令的同名参数、参数概念 首次消费 pending command 回执时,merge-base 的 normalized Schema 必须真实发布中间参数, 并逐跳验证参数签名和 constraints;command 回执合入为 consumed 后,中间 Schema 已从 main 消失,此时保留的两份 consumed 回执可继续对 stable 做受限重放,直到 stable 也达到 after -并按 lifecycle 清理。两种阶段都拒绝残留 predecessor/intermediate、字段漂移、环、分叉、 +并让回执转为惰性记录或由独立 PR 清理。两种阶段都拒绝残留 predecessor/intermediate、字段漂移、环、分叉、 target 碰撞或 primary path/tool identity 不唯一;positionals 不在该组合授权面内。 `replacement_constant` 不是清单自报即可成立的例外。after 阶段的 Interface Snapshot @@ -97,7 +97,7 @@ replacement 必须保留 source 已发布的 dry-run 能力:历史 `dry_run` 两种迁移都要求旧 argv 继续可执行。删除旧命令、删除旧 flag、把 legacy 改成 non-runnable、改变未登记的历史参数、改变 interface / safety,或只完成部分 before → after 转换都会 fail closed。命令别名会先规范到 reference 的 canonical path,但清单本身仍只能记录精确 canonical 命令,不能用 alias 或前缀扩大授权。 -跨命令清单复用下文同一套 `pending → consumed → cleanup` 生命周期。治理 PR 只能新增 `pending` 且产品 surface 必须仍是 before;后续产品 PR 才能一次性切到 after 并改为 `consumed`。candidate 新增的 pending 记录不能批准自己的改动。 +跨命令清单复用下文同一套 `pending → consumed → inert/cleanup` 生命周期。治理 PR 只能新增 `pending` 且产品 surface 必须仍是 before;后续产品 PR 才能一次性切到 after 并改为 `consumed`。candidate 新增的 pending 记录不能批准自己的改动。 当前首批 pending 记录覆盖 `chat topic` 收口:`chat group create --thread` 拆到 `chat topic create`,以及 `chat message list-topic-replies` / `forward-topic` 迁到对应的 `chat topic` 命令。前一条完整登记 `name` / `type` / `users` 的同名承接,以及 `thread` → `convThreadEnabled=true` 的常量承接。产品 PR 消费这些记录时只能把三条 `state` 改为 `consumed`,不得改写其 before、after、Schema mapping、constant 或 reason。 @@ -110,9 +110,9 @@ replacement 必须保留 source 已发布的 dry-run 能力:历史 `dry_run` | 1. 治理审批 | 新增 `state: pending` 的精确记录;不得在同一个 PR 修改产品 surface | candidate 和 merge-base 都与记录中的 `before` 完全一致;该记录不改变 stable 的判断 | | 2. 产品迁移 | merge-base 已拥有 `pending` 后,按记录一次性切到精确 `after`,并把记录改为 `state: consumed` | legacy 仍存在但由 visible 变 hidden,且声明 `alias_of`;canonical 的 requiredness 与 legacy 迁移前完全一致 | | 3. 保留回执 | 产品 PR 合入后,如果 stable 仍是 `before`,继续保留 `consumed` | merge-base 或 stable 仍有任一份尚未达到 `after` | -| 4. 单独清理 | 当 merge-base 和 stable 都已经是 `after`,在后续 PR 删除该记录 | 两份参考快照均精确匹配 `after`;继续保留过期回执会被门禁拒绝 | +| 4. 惰性保留或清理 | 当 merge-base 和 stable 都已经是 `after`,该记录不再提供任何授权;后续 PR 可以原样保留或删除 | 两份参考快照均精确匹配 `after`;保留时仍必须是不可改写的 `consumed`,接口偏离 `after` 继续失败 | -因此,新增 `pending` 和修改产品 surface 不能发生在同一个 PR;candidate 自己新增的记录不能 self-approve。迁移也不能部分执行:legacy、canonical、`alias_of` 或状态只要有一项不匹配,门禁即失败。 +因此,新增 `pending` 和修改产品 surface 不能发生在同一个 PR;candidate 自己新增的记录不能 self-approve。迁移也不能部分执行:legacy、canonical、`alias_of` 或状态只要有一项不匹配,门禁即失败。stable 发布只会让已经追平的 `consumed` 回执变成无授权效果的审计记录,不会在没有代码变更时让后续业务 PR 失去合规性;清理仍可作为独立的账本压缩动作,但不再是下一个 PR 的强制前置条件。 下面只是清单结构示例,不代表已审批命令;实际字段必须从 Interface Snapshot 核对: diff --git a/internal/interfacesnapshot/command_migrations.go b/internal/interfacesnapshot/command_migrations.go index 053db474..62205fba 100644 --- a/internal/interfacesnapshot/command_migrations.go +++ b/internal/interfacesnapshot/command_migrations.go @@ -479,8 +479,8 @@ func evaluateCommandMigrationLifecycle( } } if allAfter { - if exists { - return nil, fmt.Errorf("consumed command migration %s is stale after all references reached the after state", approved.displayKey()) + if exists && proposed.State != CommandMigrationConsumed { + return nil, fmt.Errorf("candidate changed consumed command migration %s back to pending", approved.displayKey()) } continue } diff --git a/internal/interfacesnapshot/command_migrations_test.go b/internal/interfacesnapshot/command_migrations_test.go index 9585afa1..a7d5965e 100644 --- a/internal/interfacesnapshot/command_migrations_test.go +++ b/internal/interfacesnapshot/command_migrations_test.go @@ -383,9 +383,9 @@ func TestCrossPlatformCoverageCommandMigrationLifecycleEdges(t *testing.T) { {"after pending receipt", after, map[string]Snapshot{"main": before, "stable": before}, pending, pending, "without marking it consumed"}, {"partial", partial, map[string]Snapshot{"main": before, "stable": before}, pending, consumed, "partially applied"}, {"consumed drift", before, map[string]Snapshot{"main": after, "stable": before}, consumed, consumed, "drifted from consumed"}, - {"stale receipt", after, map[string]Snapshot{"main": after, "stable": after}, consumed, consumed, "stale after all references"}, {"early cleanup", after, map[string]Snapshot{"main": after, "stable": before}, consumed, empty, "must retain consumed"}, {"consumed back to pending", after, map[string]Snapshot{"main": after, "stable": before}, consumed, pending, "must retain consumed"}, + {"inert consumed back to pending", after, map[string]Snapshot{"main": after, "stable": after}, consumed, pending, "back to pending"}, {"candidate added consumed", after, map[string]Snapshot{"main": before, "stable": before}, empty, consumed, "must start pending"}, {"candidate base mismatch", before, map[string]Snapshot{"main": after, "stable": before}, empty, pending, "does not match"}, } { @@ -402,6 +402,9 @@ func TestCrossPlatformCoverageCommandMigrationLifecycleEdges(t *testing.T) { if got, err := AuthorizeCommandMigrations(after, map[string]Snapshot{"main": after, "stable": after}, consumed, empty); err != nil || len(got) != 0 { t.Fatalf("cleaned stale receipt=%#v, %v", got, err) } + if got, err := AuthorizeCommandMigrations(after, map[string]Snapshot{"main": after, "stable": after}, consumed, consumed); err != nil || len(got) != 0 { + t.Fatalf("retained inert receipt=%#v, %v", got, err) + } if got, err := AuthorizeCommandMigrations(before, map[string]Snapshot{"main": before, "stable": before}, empty, pending); err != nil || len(got) != 0 { t.Fatalf("candidate pending plan=%#v, %v", got, err) } diff --git a/internal/interfacesnapshot/compare_test.go b/internal/interfacesnapshot/compare_test.go index e73aa074..ee362dee 100644 --- a/internal/interfacesnapshot/compare_test.go +++ b/internal/interfacesnapshot/compare_test.go @@ -889,7 +889,7 @@ func TestCrossPlatformCoverageCompareAllWithFlagMigrationsRetainsConsumedReceipt } } -func TestCrossPlatformCoverageCompareAllWithFlagMigrationsRequiresCleanupAfterAllReferencesCatchUp(t *testing.T) { +func TestCrossPlatformCoverageCompareAllWithFlagMigrationsAllowsRetentionOrCleanupAfterAllReferencesCatchUp(t *testing.T) { after := testFlagMigrationSnapshot(true, true) consumed := testFlagMigrationManifest(FlagMigrationConsumed) references := map[string]Snapshot{ @@ -897,11 +897,15 @@ func TestCrossPlatformCoverageCompareAllWithFlagMigrationsRequiresCleanupAfterAl "stable": after, } - if report, err := CompareAllWithFlagMigrations(after, references, consumed, consumed); err == nil { - t.Fatalf("stale consumed receipt was accepted after every reference caught up: %#v", report) + report, err := CompareAllWithFlagMigrations(after, references, consumed, consumed) + if err != nil { + t.Fatalf("inert consumed receipt was rejected after every reference caught up: %v", err) + } + if !report.Compatible { + t.Fatalf("inert consumed receipt changed compatibility: %#v", report.Comparisons) } - report, err := CompareAllWithFlagMigrations(after, references, consumed, testEmptyFlagMigrationManifest()) + report, err = CompareAllWithFlagMigrations(after, references, consumed, testEmptyFlagMigrationManifest()) if err != nil { t.Fatalf("cleanup of stale consumed receipt was rejected: %v", err) } diff --git a/internal/interfacesnapshot/migrations.go b/internal/interfacesnapshot/migrations.go index a16395f8..d1e7f3d5 100644 --- a/internal/interfacesnapshot/migrations.go +++ b/internal/interfacesnapshot/migrations.go @@ -579,8 +579,8 @@ func evaluateFlagMigrationLifecycle( } } if allReferencesAfter { - if exists { - return nil, fmt.Errorf("consumed flag migration %s is stale after all references reached the after state", approved.displayKey()) + if exists && proposed.State != FlagMigrationConsumed { + return nil, fmt.Errorf("candidate changed consumed flag migration %s back to pending", approved.displayKey()) } continue } diff --git a/internal/interfacesnapshot/migrations_coverage_test.go b/internal/interfacesnapshot/migrations_coverage_test.go index fa4b239f..90f8064a 100644 --- a/internal/interfacesnapshot/migrations_coverage_test.go +++ b/internal/interfacesnapshot/migrations_coverage_test.go @@ -602,12 +602,12 @@ func TestCrossPlatformCoverageCompareAllWithFlagMigrationsLifecycleErrors(t *tes wantErr: "back to pending", }, { - name: "consumed receipt becomes stale", + name: "inert consumed receipt cannot revert to pending", current: after, references: map[string]Snapshot{"merge-base": after, "stable": after}, authority: consumed, - candidate: consumed, - wantErr: "is stale after all references reached the after state", + candidate: pending, + wantErr: "back to pending", }, { name: "candidate-added receipt starts pending", @@ -857,15 +857,15 @@ func TestCrossPlatformCoverageOptionalFlagMigrationLifecycleRemainsHostile(t *te } }) - t.Run("consumed receipt remains stale after every reference converges", func(t *testing.T) { - _, err := CompareAllWithFlagMigrations( + t.Run("consumed receipt becomes inert after every reference converges", func(t *testing.T) { + report, err := CompareAllWithFlagMigrations( after, map[string]Snapshot{"merge-base": after, "stable": after}, consumed, consumed, ) - if err == nil || !strings.Contains(err.Error(), "stale after all references reached the after state") { - t.Fatalf("stale optional migration error = %v", err) + if err != nil || !report.Compatible { + t.Fatalf("inert optional migration = (%#v, %v), want compatible", report, err) } }) } diff --git a/scripts/policy/interface-migrations/approved-flag-migrations-v1.json b/scripts/policy/interface-migrations/approved-flag-migrations-v1.json index c1c596fa..c1474dd0 100644 --- a/scripts/policy/interface-migrations/approved-flag-migrations-v1.json +++ b/scripts/policy/interface-migrations/approved-flag-migrations-v1.json @@ -1,39 +1,6 @@ { "version": 1, "migrations": [ - { - "command": "dws chat group audit-join-validation", - "legacy": { - "name": "group", - "before": { - "present": true, - "type": "string", - "scope": "local", - "required": true - }, - "after": { - "present": true, - "type": "string", - "scope": "local", - "hidden": true, - "alias_of": "conversation-id" - } - }, - "canonical": { - "name": "conversation-id", - "before": { - "present": false - }, - "after": { - "present": true, - "type": "string", - "scope": "local", - "required": true - } - }, - "state": "consumed", - "reason": "保留旧 argv 兼容性,并将 IM ID 规范 flag 设为唯一可见入口" - }, { "command": "dws chat group bots", "legacy": { @@ -67,1098 +34,6 @@ "state": "pending", "reason": "保留旧 argv 兼容性,并将 IM ID 规范 flag 设为唯一可见入口" }, - { - "command": "dws chat group dismiss", - "legacy": { - "name": "group", - "before": { - "present": true, - "type": "string", - "scope": "local", - "required": true - }, - "after": { - "present": true, - "type": "string", - "scope": "local", - "hidden": true, - "alias_of": "conversation-id" - } - }, - "canonical": { - "name": "conversation-id", - "before": { - "present": false - }, - "after": { - "present": true, - "type": "string", - "scope": "local", - "required": true - } - }, - "state": "consumed", - "reason": "保留旧 argv 兼容性,并将 IM ID 规范 flag 设为唯一可见入口" - }, - { - "command": "dws chat group invite-url", - "legacy": { - "name": "group", - "before": { - "present": true, - "type": "string", - "scope": "local", - "required": true - }, - "after": { - "present": true, - "type": "string", - "scope": "local", - "hidden": true, - "alias_of": "conversation-id" - } - }, - "canonical": { - "name": "conversation-id", - "before": { - "present": false - }, - "after": { - "present": true, - "type": "string", - "scope": "local", - "required": true - } - }, - "state": "consumed", - "reason": "保留旧 argv 兼容性,并将 IM ID 规范 flag 设为唯一可见入口" - }, - { - "command": "dws chat group notice create", - "legacy": { - "name": "group", - "before": { - "present": true, - "type": "string", - "scope": "local", - "required": true - }, - "after": { - "present": true, - "type": "string", - "scope": "local", - "hidden": true, - "alias_of": "conversation-id" - } - }, - "canonical": { - "name": "conversation-id", - "before": { - "present": false - }, - "after": { - "present": true, - "type": "string", - "scope": "local", - "required": true - } - }, - "state": "consumed", - "reason": "保留旧 argv 兼容性,并将 IM ID 规范 flag 设为唯一可见入口" - }, - { - "command": "dws chat group notice edit", - "legacy": { - "name": "group", - "before": { - "present": true, - "type": "string", - "scope": "local", - "required": true - }, - "after": { - "present": true, - "type": "string", - "scope": "local", - "hidden": true, - "alias_of": "conversation-id" - } - }, - "canonical": { - "name": "conversation-id", - "before": { - "present": false - }, - "after": { - "present": true, - "type": "string", - "scope": "local", - "required": true - } - }, - "state": "consumed", - "reason": "保留旧 argv 兼容性,并将 IM ID 规范 flag 设为唯一可见入口" - }, - { - "command": "dws chat group notice get", - "legacy": { - "name": "group", - "before": { - "present": true, - "type": "string", - "scope": "local", - "required": true - }, - "after": { - "present": true, - "type": "string", - "scope": "local", - "hidden": true, - "alias_of": "conversation-id" - } - }, - "canonical": { - "name": "conversation-id", - "before": { - "present": false - }, - "after": { - "present": true, - "type": "string", - "scope": "local", - "required": true - } - }, - "state": "consumed", - "reason": "保留旧 argv 兼容性,并将 IM ID 规范 flag 设为唯一可见入口" - }, - { - "command": "dws chat group notice list", - "legacy": { - "name": "group", - "before": { - "present": true, - "type": "string", - "scope": "local", - "required": true - }, - "after": { - "present": true, - "type": "string", - "scope": "local", - "hidden": true, - "alias_of": "conversation-id" - } - }, - "canonical": { - "name": "conversation-id", - "before": { - "present": false - }, - "after": { - "present": true, - "type": "string", - "scope": "local", - "required": true - } - }, - "state": "consumed", - "reason": "保留旧 argv 兼容性,并将 IM ID 规范 flag 设为唯一可见入口" - }, - { - "command": "dws chat group quit", - "legacy": { - "name": "group", - "before": { - "present": true, - "type": "string", - "scope": "local", - "required": true - }, - "after": { - "present": true, - "type": "string", - "scope": "local", - "hidden": true, - "alias_of": "conversation-id" - } - }, - "canonical": { - "name": "conversation-id", - "before": { - "present": false - }, - "after": { - "present": true, - "type": "string", - "scope": "local", - "required": true - } - }, - "state": "consumed", - "reason": "保留旧 argv 兼容性,并将 IM ID 规范 flag 设为唯一可见入口" - }, - { - "command": "dws chat group set-admin", - "legacy": { - "name": "group", - "before": { - "present": true, - "type": "string", - "scope": "local", - "required": true - }, - "after": { - "present": true, - "type": "string", - "scope": "local", - "hidden": true, - "alias_of": "conversation-id" - } - }, - "canonical": { - "name": "conversation-id", - "before": { - "present": false - }, - "after": { - "present": true, - "type": "string", - "scope": "local", - "required": true - } - }, - "state": "consumed", - "reason": "保留旧 argv 兼容性,并将 IM ID 规范 flag 设为唯一可见入口" - }, - { - "command": "dws chat group set-history", - "legacy": { - "name": "group", - "before": { - "present": true, - "type": "string", - "scope": "local", - "required": true - }, - "after": { - "present": true, - "type": "string", - "scope": "local", - "hidden": true, - "alias_of": "conversation-id" - } - }, - "canonical": { - "name": "conversation-id", - "before": { - "present": false - }, - "after": { - "present": true, - "type": "string", - "scope": "local", - "required": true - } - }, - "state": "consumed", - "reason": "保留旧 argv 兼容性,并将 IM ID 规范 flag 设为唯一可见入口" - }, - { - "command": "dws chat group transfer-owner", - "legacy": { - "name": "group", - "before": { - "present": true, - "type": "string", - "scope": "local", - "required": true - }, - "after": { - "present": true, - "type": "string", - "scope": "local", - "hidden": true, - "alias_of": "conversation-id" - } - }, - "canonical": { - "name": "conversation-id", - "before": { - "present": false - }, - "after": { - "present": true, - "type": "string", - "scope": "local", - "required": true - } - }, - "state": "consumed", - "reason": "保留旧 argv 兼容性,并将 IM ID 规范 flag 设为唯一可见入口" - }, - { - "command": "dws chat group update-alias", - "legacy": { - "name": "group", - "before": { - "present": true, - "type": "string", - "scope": "local", - "required": true - }, - "after": { - "present": true, - "type": "string", - "scope": "local", - "hidden": true, - "alias_of": "conversation-id" - } - }, - "canonical": { - "name": "conversation-id", - "before": { - "present": false - }, - "after": { - "present": true, - "type": "string", - "scope": "local", - "required": true - } - }, - "state": "consumed", - "reason": "保留旧 argv 兼容性,并将 IM ID 规范 flag 设为唯一可见入口" - }, - { - "command": "dws chat group update-icon", - "legacy": { - "name": "group", - "before": { - "present": true, - "type": "string", - "scope": "local", - "required": true - }, - "after": { - "present": true, - "type": "string", - "scope": "local", - "hidden": true, - "alias_of": "conversation-id" - } - }, - "canonical": { - "name": "conversation-id", - "before": { - "present": false - }, - "after": { - "present": true, - "type": "string", - "scope": "local", - "required": true - } - }, - "state": "consumed", - "reason": "保留旧 argv 兼容性,并将 IM ID 规范 flag 设为唯一可见入口" - }, - { - "command": "dws chat group update-nick", - "legacy": { - "name": "group", - "before": { - "present": true, - "type": "string", - "scope": "local", - "required": true - }, - "after": { - "present": true, - "type": "string", - "scope": "local", - "hidden": true, - "alias_of": "conversation-id" - } - }, - "canonical": { - "name": "conversation-id", - "before": { - "present": false - }, - "after": { - "present": true, - "type": "string", - "scope": "local", - "required": true - } - }, - "state": "consumed", - "reason": "保留旧 argv 兼容性,并将 IM ID 规范 flag 设为唯一可见入口" - }, - { - "command": "dws chat group update-settings", - "legacy": { - "name": "group", - "before": { - "present": true, - "type": "string", - "scope": "local", - "required": true - }, - "after": { - "present": true, - "type": "string", - "scope": "local", - "hidden": true, - "alias_of": "conversation-id" - } - }, - "canonical": { - "name": "conversation-id", - "before": { - "present": false - }, - "after": { - "present": true, - "type": "string", - "scope": "local", - "required": true - } - }, - "state": "consumed", - "reason": "保留旧 argv 兼容性,并将 IM ID 规范 flag 设为唯一可见入口" - }, - { - "command": "dws chat group upgrade-to-external", - "legacy": { - "name": "group", - "before": { - "present": true, - "type": "string", - "scope": "local", - "required": true - }, - "after": { - "present": true, - "type": "string", - "scope": "local", - "hidden": true, - "alias_of": "conversation-id" - } - }, - "canonical": { - "name": "conversation-id", - "before": { - "present": false - }, - "after": { - "present": true, - "type": "string", - "scope": "local", - "required": true - } - }, - "state": "consumed", - "reason": "保留旧 argv 兼容性,并将 IM ID 规范 flag 设为唯一可见入口" - }, - { - "command": "dws chat group-role add", - "legacy": { - "name": "group", - "before": { - "present": true, - "type": "string", - "scope": "local", - "required": true - }, - "after": { - "present": true, - "type": "string", - "scope": "local", - "hidden": true, - "alias_of": "conversation-id" - } - }, - "canonical": { - "name": "conversation-id", - "before": { - "present": false - }, - "after": { - "present": true, - "type": "string", - "scope": "local", - "required": true - } - }, - "state": "consumed", - "reason": "保留旧 argv 兼容性,并将 IM ID 规范 flag 设为唯一可见入口" - }, - { - "command": "dws chat group-role list", - "legacy": { - "name": "group", - "before": { - "present": true, - "type": "string", - "scope": "local", - "required": true - }, - "after": { - "present": true, - "type": "string", - "scope": "local", - "hidden": true, - "alias_of": "conversation-id" - } - }, - "canonical": { - "name": "conversation-id", - "before": { - "present": false - }, - "after": { - "present": true, - "type": "string", - "scope": "local", - "required": true - } - }, - "state": "consumed", - "reason": "保留旧 argv 兼容性,并将 IM ID 规范 flag 设为唯一可见入口" - }, - { - "command": "dws chat group-role query-user", - "legacy": { - "name": "group", - "before": { - "present": true, - "type": "string", - "scope": "local", - "required": true - }, - "after": { - "present": true, - "type": "string", - "scope": "local", - "hidden": true, - "alias_of": "conversation-id" - } - }, - "canonical": { - "name": "conversation-id", - "before": { - "present": false - }, - "after": { - "present": true, - "type": "string", - "scope": "local", - "required": true - } - }, - "state": "consumed", - "reason": "保留旧 argv 兼容性,并将 IM ID 规范 flag 设为唯一可见入口" - }, - { - "command": "dws chat group-role remove-user", - "legacy": { - "name": "group", - "before": { - "present": true, - "type": "string", - "scope": "local", - "required": true - }, - "after": { - "present": true, - "type": "string", - "scope": "local", - "hidden": true, - "alias_of": "conversation-id" - } - }, - "canonical": { - "name": "conversation-id", - "before": { - "present": false - }, - "after": { - "present": true, - "type": "string", - "scope": "local", - "required": true - } - }, - "state": "consumed", - "reason": "保留旧 argv 兼容性,并将 IM ID 规范 flag 设为唯一可见入口" - }, - { - "command": "dws chat group-role remove", - "legacy": { - "name": "group", - "before": { - "present": true, - "type": "string", - "scope": "local", - "required": true - }, - "after": { - "present": true, - "type": "string", - "scope": "local", - "hidden": true, - "alias_of": "conversation-id" - } - }, - "canonical": { - "name": "conversation-id", - "before": { - "present": false - }, - "after": { - "present": true, - "type": "string", - "scope": "local", - "required": true - } - }, - "state": "consumed", - "reason": "保留旧 argv 兼容性,并将 IM ID 规范 flag 设为唯一可见入口" - }, - { - "command": "dws chat group-role set-user", - "legacy": { - "name": "group", - "before": { - "present": true, - "type": "string", - "scope": "local", - "required": true - }, - "after": { - "present": true, - "type": "string", - "scope": "local", - "hidden": true, - "alias_of": "conversation-id" - } - }, - "canonical": { - "name": "conversation-id", - "before": { - "present": false - }, - "after": { - "present": true, - "type": "string", - "scope": "local", - "required": true - } - }, - "state": "consumed", - "reason": "保留旧 argv 兼容性,并将 IM ID 规范 flag 设为唯一可见入口" - }, - { - "command": "dws chat group-role set-user", - "legacy": { - "name": "role-ids", - "before": { - "present": true, - "type": "string", - "scope": "local", - "required": true - }, - "after": { - "present": true, - "type": "string", - "scope": "local", - "hidden": true, - "alias_of": "role-id" - } - }, - "canonical": { - "name": "role-id", - "before": { - "present": false - }, - "after": { - "present": true, - "type": "string", - "scope": "local", - "required": true - } - }, - "state": "consumed", - "reason": "保留旧 argv 兼容性,并将单值群身份 flag 设为唯一可见入口" - }, - { - "command": "dws chat group-role update", - "legacy": { - "name": "group", - "before": { - "present": true, - "type": "string", - "scope": "local", - "required": true - }, - "after": { - "present": true, - "type": "string", - "scope": "local", - "hidden": true, - "alias_of": "conversation-id" - } - }, - "canonical": { - "name": "conversation-id", - "before": { - "present": false - }, - "after": { - "present": true, - "type": "string", - "scope": "local", - "required": true - } - }, - "state": "consumed", - "reason": "保留旧 argv 兼容性,并将 IM ID 规范 flag 设为唯一可见入口" - }, - { - "command": "dws chat message edit", - "legacy": { - "name": "msg-id", - "before": { - "present": true, - "type": "string", - "scope": "local", - "required": true - }, - "after": { - "present": true, - "type": "string", - "scope": "local", - "hidden": true, - "alias_of": "message-id" - } - }, - "canonical": { - "name": "message-id", - "before": { - "present": false - }, - "after": { - "present": true, - "type": "string", - "scope": "local", - "required": true - } - }, - "state": "consumed", - "reason": "保留旧 argv 兼容性,并将 IM ID 规范 flag 设为唯一可见入口" - }, - { - "command": "dws chat message forward", - "legacy": { - "name": "msg-id", - "before": { - "present": true, - "type": "string", - "scope": "local", - "required": true - }, - "after": { - "present": true, - "type": "string", - "scope": "local", - "hidden": true, - "alias_of": "message-id" - } - }, - "canonical": { - "name": "message-id", - "before": { - "present": false - }, - "after": { - "present": true, - "type": "string", - "scope": "local", - "required": true - } - }, - "state": "consumed", - "reason": "保留旧 argv 兼容性,并将 IM ID 规范 flag 设为唯一可见入口" - }, - { - "command": "dws chat message list-topic-replies", - "legacy": { - "name": "group", - "before": { - "present": true, - "type": "string", - "scope": "local", - "required": true - }, - "after": { - "present": true, - "type": "string", - "scope": "local", - "hidden": true, - "alias_of": "conversation-id" - } - }, - "canonical": { - "name": "conversation-id", - "before": { - "present": true, - "type": "string", - "scope": "local", - "hidden": true - }, - "after": { - "present": true, - "type": "string", - "scope": "local", - "required": true - } - }, - "state": "consumed", - "reason": "保留旧 argv 兼容性,并将 IM ID 规范 flag 设为唯一可见入口" - }, - { - "command": "dws chat message recall", - "legacy": { - "name": "msg-id", - "before": { - "present": true, - "type": "string", - "scope": "local", - "required": true - }, - "after": { - "present": true, - "type": "string", - "scope": "local", - "hidden": true, - "alias_of": "message-id" - } - }, - "canonical": { - "name": "message-id", - "before": { - "present": false - }, - "after": { - "present": true, - "type": "string", - "scope": "local", - "required": true - } - }, - "state": "consumed", - "reason": "保留旧 argv 兼容性,并将 IM ID 规范 flag 设为唯一可见入口" - }, - { - "command": "dws chat message send", - "legacy": { - "name": "uuid", - "before": { - "present": true, - "type": "string", - "scope": "local", - "required": false - }, - "after": { - "present": true, - "type": "string", - "scope": "local", - "hidden": true, - "alias_of": "idempotency-key" - } - }, - "canonical": { - "name": "idempotency-key", - "before": { - "present": false - }, - "after": { - "present": true, - "type": "string", - "scope": "local", - "required": false - } - }, - "state": "consumed", - "reason": "保留旧 argv 兼容性,并将幂等参数规范 flag 设为唯一可见入口" - }, - { - "command": "dws chat message set-pin-msg", - "legacy": { - "name": "msg-id", - "before": { - "present": true, - "type": "string", - "scope": "local", - "required": true - }, - "after": { - "present": true, - "type": "string", - "scope": "local", - "hidden": true, - "alias_of": "message-id" - } - }, - "canonical": { - "name": "message-id", - "before": { - "present": false - }, - "after": { - "present": true, - "type": "string", - "scope": "local", - "required": true - } - }, - "state": "consumed", - "reason": "保留旧 argv 兼容性,并将 IM ID 规范 flag 设为唯一可见入口" - }, - { - "command": "dws chat message set-top-msg", - "legacy": { - "name": "msg-id", - "before": { - "present": true, - "type": "string", - "scope": "local", - "required": true - }, - "after": { - "present": true, - "type": "string", - "scope": "local", - "hidden": true, - "alias_of": "message-id" - } - }, - "canonical": { - "name": "message-id", - "before": { - "present": false - }, - "after": { - "present": true, - "type": "string", - "scope": "local", - "required": true - } - }, - "state": "consumed", - "reason": "保留旧 argv 兼容性,并将 IM ID 规范 flag 设为唯一可见入口" - }, - { - "command": "dws chat message unset-pin-msg", - "legacy": { - "name": "msg-id", - "before": { - "present": true, - "type": "string", - "scope": "local", - "required": true - }, - "after": { - "present": true, - "type": "string", - "scope": "local", - "hidden": true, - "alias_of": "message-id" - } - }, - "canonical": { - "name": "message-id", - "before": { - "present": false - }, - "after": { - "present": true, - "type": "string", - "scope": "local", - "required": true - } - }, - "state": "consumed", - "reason": "保留旧 argv 兼容性,并将 IM ID 规范 flag 设为唯一可见入口" - }, - { - "command": "dws chat message unset-top-msg", - "legacy": { - "name": "msg-id", - "before": { - "present": true, - "type": "string", - "scope": "local", - "required": true - }, - "after": { - "present": true, - "type": "string", - "scope": "local", - "hidden": true, - "alias_of": "message-id" - } - }, - "canonical": { - "name": "message-id", - "before": { - "present": false - }, - "after": { - "present": true, - "type": "string", - "scope": "local", - "required": true - } - }, - "state": "consumed", - "reason": "保留旧 argv 兼容性,并将 IM ID 规范 flag 设为唯一可见入口" - }, - { - "command": "dws chat message update-text-emotion", - "legacy": { - "name": "msg-id", - "before": { - "present": true, - "type": "string", - "scope": "local", - "required": true - }, - "after": { - "present": true, - "type": "string", - "scope": "local", - "hidden": true, - "alias_of": "message-id" - } - }, - "canonical": { - "name": "message-id", - "before": { - "present": false - }, - "after": { - "present": true, - "type": "string", - "scope": "local", - "required": true - } - }, - "state": "consumed", - "reason": "保留旧 argv 兼容性,并将 IM ID 规范 flag 设为唯一可见入口" - }, { "command": "dws ding message send-by-message", "legacy": { @@ -1192,216 +67,6 @@ "state": "pending", "reason": "保留旧 argv 兼容性,并将 IM ID 规范 flag 设为唯一可见入口" }, - { - "command": "dws aisearch", - "legacy": { - "name": "keyword", - "before": {"present": true, "type": "string", "shorthand": "w", "scope": "local"}, - "after": {"present": true, "type": "string", "hidden": true, "shorthand": "w", "scope": "local", "alias_of": "query"} - }, - "canonical": { - "name": "query", - "before": {"present": true, "type": "string", "hidden": true, "scope": "local"}, - "after": {"present": true, "type": "string", "scope": "local"} - }, - "state": "consumed", - "reason": "Preserve legacy argv compatibility while making the canonical flag the only visible parameter." - }, - { - "command": "dws aisearch person", - "legacy": { - "name": "keyword", - "before": {"present": true, "type": "string", "shorthand": "w", "scope": "local"}, - "after": {"present": true, "type": "string", "hidden": true, "shorthand": "w", "scope": "local", "alias_of": "query"} - }, - "canonical": { - "name": "query", - "before": {"present": true, "type": "string", "hidden": true, "scope": "local"}, - "after": {"present": true, "type": "string", "scope": "local"} - }, - "state": "consumed", - "reason": "Preserve legacy argv compatibility while making the canonical flag the only visible parameter." - }, - { - "command": "dws chat +broadcast", - "legacy": { - "name": "text", - "before": {"present": true, "type": "string", "scope": "local"}, - "after": {"present": true, "type": "string", "hidden": true, "scope": "local", "alias_of": "content"} - }, - "canonical": { - "name": "content", - "before": {"present": false}, - "after": {"present": true, "type": "string", "scope": "local"} - }, - "state": "consumed", - "reason": "Preserve legacy argv compatibility while making the canonical flag the only visible parameter." - }, - { - "command": "dws chat +dm", - "legacy": { - "name": "text", - "before": {"present": true, "type": "string", "scope": "local"}, - "after": {"present": true, "type": "string", "hidden": true, "scope": "local", "alias_of": "content"} - }, - "canonical": { - "name": "content", - "before": {"present": false}, - "after": {"present": true, "type": "string", "scope": "local"} - }, - "state": "consumed", - "reason": "Preserve legacy argv compatibility while making the canonical flag the only visible parameter." - }, - { - "command": "dws chat +messages-batch-send-by-bot", - "legacy": { - "name": "text", - "before": {"present": true, "type": "string", "scope": "local"}, - "after": {"present": true, "type": "string", "hidden": true, "scope": "local", "alias_of": "content"} - }, - "canonical": { - "name": "content", - "before": {"present": false}, - "after": {"present": true, "type": "string", "scope": "local"} - }, - "state": "consumed", - "reason": "Preserve legacy argv compatibility while making the canonical flag the only visible parameter." - }, - { - "command": "dws chat +messages-reply", - "legacy": { - "name": "conversation-id", - "before": {"present": true, "type": "string", "scope": "local"}, - "after": {"present": true, "type": "string", "hidden": true, "scope": "local", "alias_of": "group"} - }, - "canonical": { - "name": "group", - "before": {"present": false}, - "after": {"present": true, "type": "string", "scope": "local"} - }, - "state": "consumed", - "reason": "Preserve legacy argv compatibility while making the canonical flag the only visible parameter." - }, - { - "command": "dws chat +messages-reply", - "legacy": { - "name": "text", - "before": {"present": true, "type": "string", "scope": "local"}, - "after": {"present": true, "type": "string", "hidden": true, "scope": "local", "alias_of": "content"} - }, - "canonical": { - "name": "content", - "before": {"present": false}, - "after": {"present": true, "type": "string", "scope": "local"} - }, - "state": "consumed", - "reason": "Preserve legacy argv compatibility while making the canonical flag the only visible parameter." - }, - { - "command": "dws chat +messages-send-by-bot", - "legacy": { - "name": "text", - "before": {"present": true, "type": "string", "scope": "local"}, - "after": {"present": true, "type": "string", "hidden": true, "scope": "local", "alias_of": "content"} - }, - "canonical": { - "name": "content", - "before": {"present": false}, - "after": {"present": true, "type": "string", "scope": "local"} - }, - "state": "consumed", - "reason": "Preserve legacy argv compatibility while making the canonical flag the only visible parameter." - }, - { - "command": "dws chat +messages-send-by-webhook", - "legacy": { - "name": "text", - "before": {"present": true, "type": "string", "scope": "local"}, - "after": {"present": true, "type": "string", "hidden": true, "scope": "local", "alias_of": "content"} - }, - "canonical": { - "name": "content", - "before": {"present": false}, - "after": {"present": true, "type": "string", "scope": "local"} - }, - "state": "consumed", - "reason": "Preserve legacy argv compatibility while making the canonical flag the only visible parameter." - }, - { - "command": "dws chat +send-to-group", - "legacy": { - "name": "text", - "before": {"present": true, "type": "string", "scope": "local"}, - "after": {"present": true, "type": "string", "hidden": true, "scope": "local", "alias_of": "content"} - }, - "canonical": { - "name": "content", - "before": {"present": false}, - "after": {"present": true, "type": "string", "scope": "local"} - }, - "state": "consumed", - "reason": "Preserve legacy argv compatibility while making the canonical flag the only visible parameter." - }, - { - "command": "dws chat message reply", - "legacy": { - "name": "conversation-id", - "before": {"present": true, "type": "string", "required": true, "scope": "local"}, - "after": {"present": true, "type": "string", "required": false, "hidden": true, "scope": "local", "alias_of": "group"} - }, - "canonical": { - "name": "group", - "before": {"present": false}, - "after": {"present": true, "type": "string", "required": true, "scope": "local"} - }, - "state": "consumed", - "reason": "Preserve legacy argv compatibility while making the canonical flag the only visible parameter." - }, - { - "command": "dws chat message reply", - "legacy": { - "name": "text", - "before": {"present": true, "type": "string", "required": true, "scope": "local"}, - "after": {"present": true, "type": "string", "required": false, "hidden": true, "scope": "local", "alias_of": "content"} - }, - "canonical": { - "name": "content", - "before": {"present": false}, - "after": {"present": true, "type": "string", "required": true, "scope": "local"} - }, - "state": "consumed", - "reason": "Preserve legacy argv compatibility while making the canonical flag the only visible parameter." - }, - { - "command": "dws chat message send", - "legacy": { - "name": "file-path", - "before": {"present": true, "type": "string", "scope": "local"}, - "after": {"present": true, "type": "string", "hidden": true, "scope": "local", "alias_of": "file"} - }, - "canonical": { - "name": "file", - "before": {"present": false}, - "after": {"present": true, "type": "string", "scope": "local"} - }, - "state": "consumed", - "reason": "Preserve legacy argv compatibility while making the canonical flag the only visible parameter." - }, - { - "command": "dws chat message send", - "legacy": { - "name": "text", - "before": {"present": true, "type": "string", "scope": "local"}, - "after": {"present": true, "type": "string", "hidden": true, "scope": "local", "alias_of": "content"} - }, - "canonical": { - "name": "content", - "before": {"present": true, "type": "string", "hidden": true, "scope": "local"}, - "after": {"present": true, "type": "string", "scope": "local"} - }, - "state": "consumed", - "reason": "Preserve legacy argv compatibility while making the canonical flag the only visible parameter." - }, { "command": "dws chat message send-by-bot", "legacy": { @@ -1431,81 +96,6 @@ }, "state": "pending", "reason": "Preserve legacy argv compatibility while making the canonical flag the only visible parameter." - }, - { - "command": "dws chat message send-by-webhook", - "legacy": { - "name": "text", - "before": {"present": true, "type": "string", "required": true, "scope": "local"}, - "after": {"present": true, "type": "string", "required": false, "hidden": true, "scope": "local", "alias_of": "content"} - }, - "canonical": { - "name": "content", - "before": {"present": false}, - "after": {"present": true, "type": "string", "required": true, "scope": "local"} - }, - "state": "consumed", - "reason": "Preserve legacy argv compatibility while making the canonical flag the only visible parameter." - }, - { - "command": "dws doc +doc-append", - "legacy": { - "name": "text", - "before": {"present": true, "type": "string", "scope": "local"}, - "after": {"present": true, "type": "string", "hidden": true, "scope": "local", "alias_of": "content"} - }, - "canonical": { - "name": "content", - "before": {"present": false}, - "after": {"present": true, "type": "string", "scope": "local"} - }, - "state": "consumed", - "reason": "Preserve legacy argv compatibility while making the canonical flag the only visible parameter." - }, - { - "command": "dws doc block insert", - "legacy": { - "name": "text", - "before": {"present": true, "type": "string", "scope": "local"}, - "after": {"present": true, "type": "string", "hidden": true, "scope": "local", "alias_of": "content"} - }, - "canonical": { - "name": "content", - "before": {"present": false}, - "after": {"present": true, "type": "string", "scope": "local"} - }, - "state": "consumed", - "reason": "Preserve legacy argv compatibility while making the canonical flag the only visible parameter." - }, - { - "command": "dws doc block update", - "legacy": { - "name": "text", - "before": {"present": true, "type": "string", "scope": "local"}, - "after": {"present": true, "type": "string", "hidden": true, "scope": "local", "alias_of": "content"} - }, - "canonical": { - "name": "content", - "before": {"present": false}, - "after": {"present": true, "type": "string", "scope": "local"} - }, - "state": "consumed", - "reason": "Preserve legacy argv compatibility while making the canonical flag the only visible parameter." - }, - { - "command": "dws todo task add-attachment", - "legacy": { - "name": "file-path", - "before": {"present": true, "type": "string", "scope": "local"}, - "after": {"present": true, "type": "string", "hidden": true, "scope": "local", "alias_of": "file"} - }, - "canonical": { - "name": "file", - "before": {"present": false}, - "after": {"present": true, "type": "string", "scope": "local"} - }, - "state": "consumed", - "reason": "Preserve legacy argv compatibility while making the canonical flag the only visible parameter." } ] } diff --git a/scripts/policy/schema-compat/main_test.go b/scripts/policy/schema-compat/main_test.go index c5eaa912..d1067f0e 100644 --- a/scripts/policy/schema-compat/main_test.go +++ b/scripts/policy/schema-compat/main_test.go @@ -1739,11 +1739,12 @@ func TestCrossPlatformCoverageSchemaConsumedReceiptIsNoOpForAfterBaseline(t *tes t.Fatalf("consumed receipt did not preserve compatibility: %v", failures) } - // Once stable also reaches after, retaining the consumed receipt is stale; - // deleting it yields no authorization and the already-after Schema passes. + // Once stable also reaches after, retaining or deleting the consumed receipt + // yields no authorization and the already-after Schema passes. writeInterfaceSnapshotFile(t, stableSnapshotPath, schemaFlagMigrationSnapshot(true)) - if _, err := authorizeSchemaFlagMigrations(approvedPath, candidatePath, currentSnapshotPath, baseSnapshotPath, stableSnapshotPath); err == nil || !strings.Contains(err.Error(), "stale after all references reached the after state") { - t.Fatalf("stale consumed receipt error = %v", err) + migrations, err = authorizeSchemaFlagMigrations(approvedPath, candidatePath, currentSnapshotPath, baseSnapshotPath, stableSnapshotPath) + if err != nil || len(migrations) != 0 { + t.Fatalf("retained consumed receipt authorizations = %#v, %v", migrations, err) } emptyManifest := interfacesnapshot.FlagMigrationManifest{ Version: interfacesnapshot.FlagMigrationManifestVersion,