Merge branch 'main' into feat/drive-sync-family
This commit is contained in:
@@ -122,7 +122,7 @@ scripts/policy/interface-migrations/approved-flag-migrations-v1.json
|
||||
一条 base-owned、状态正确且前后快照精确匹配的记录,只会从普通兼容报告中移除以下两类预期 finding:
|
||||
|
||||
1. legacy flag 的 `flag_became_hidden`(visible → hidden);
|
||||
2. required legacy 被新增的 required canonical 替代时产生的 `required_flag_added`。已有 canonical 的 requiredness 不允许借 rename 改变。
|
||||
2. required legacy 被新增的 required canonical 替代时产生的 `required_flag_added`;如果 canonical 在 before 阶段只是 hidden 占位符,则允许它在转为公开拼写时继承 legacy 的 requiredness。已有的 visible canonical 不允许借 rename 改变 requiredness。
|
||||
|
||||
以下变化仍按普通兼容规则阻塞,不能被迁移记录掩盖:
|
||||
|
||||
|
||||
@@ -326,8 +326,9 @@ func (m FlagMigration) validate() error {
|
||||
}
|
||||
// Requiredness belongs to the one logical parameter. The hidden legacy
|
||||
// spelling must not remain independently required, while the canonical
|
||||
// spelling inherits the exact before-state contract. If the canonical flag
|
||||
// already existed, a rename receipt cannot authorize changing it either.
|
||||
// spelling inherits the exact before-state contract. An already-visible
|
||||
// canonical flag cannot change requiredness; an existing hidden canonical
|
||||
// placeholder may inherit it when promoted to the public spelling.
|
||||
if m.Legacy.After.Required {
|
||||
return fmt.Errorf("legacy compatibility alias must not remain independently required after migration")
|
||||
}
|
||||
@@ -336,7 +337,9 @@ func (m FlagMigration) validate() error {
|
||||
"flag requiredness must be preserved from legacy before to canonical after",
|
||||
)
|
||||
}
|
||||
if m.Canonical.Before.Present && m.Canonical.Before.Required != m.Canonical.After.Required {
|
||||
if m.Canonical.Before.Present &&
|
||||
!m.Canonical.Before.Hidden &&
|
||||
m.Canonical.Before.Required != m.Canonical.After.Required {
|
||||
return fmt.Errorf("canonical flag requiredness must remain unchanged when already present")
|
||||
}
|
||||
if m.Legacy.After.AliasOf != m.Canonical.Name {
|
||||
@@ -690,6 +693,13 @@ func flagMigrationAuthorizesChange(
|
||||
change.Kind == "required_flag_added" {
|
||||
return true
|
||||
}
|
||||
if migration.Canonical.Before.Present &&
|
||||
migration.Canonical.Before.Hidden &&
|
||||
!migration.Canonical.Before.Required &&
|
||||
migration.Canonical.After.Required &&
|
||||
change.Kind == "flag_became_required" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -792,6 +792,35 @@ func TestCrossPlatformCoverageCompareAllWithFlagMigrationsInheritedAndOptionalCa
|
||||
t.Fatalf("existing optional canonical migration = (%#v, %v), want compatible", report, err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("hidden canonical inherits requiredness when promoted", func(t *testing.T) {
|
||||
pending := coverageManifest(FlagMigrationPending)
|
||||
pending.Migrations[0].Canonical.Before = FlagMigrationState{
|
||||
Present: true,
|
||||
Type: "string",
|
||||
Hidden: true,
|
||||
Scope: "local",
|
||||
}
|
||||
consumed := pending
|
||||
consumed.Migrations = append([]FlagMigration(nil), pending.Migrations...)
|
||||
consumed.Migrations[0].State = FlagMigrationConsumed
|
||||
before := coverageMigrationSnapshot(pending.Migrations[0], false, false)
|
||||
after := coverageMigrationSnapshot(pending.Migrations[0], true, false)
|
||||
|
||||
ordinary := Compare(after, before, "merge-base")
|
||||
if !hasFlagChange(ordinary.Blocking, "flag_became_required", pending.Migrations[0].Command, pending.Migrations[0].Canonical.Name) {
|
||||
t.Fatalf("fixture did not change canonical requiredness: %#v", ordinary.Blocking)
|
||||
}
|
||||
report, err := CompareAllWithFlagMigrations(
|
||||
after,
|
||||
map[string]Snapshot{"merge-base": before, "stable": before},
|
||||
pending,
|
||||
consumed,
|
||||
)
|
||||
if err != nil || !report.Compatible {
|
||||
t.Fatalf("hidden canonical promotion = (%#v, %v), want compatible", report, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestCrossPlatformCoverageOptionalFlagMigrationLifecycleRemainsHostile(t *testing.T) {
|
||||
|
||||
@@ -5,6 +5,7 @@ package interfacesnapshot
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -52,6 +53,27 @@ func optionalFlagMigrationManifestJSON() string {
|
||||
)
|
||||
}
|
||||
|
||||
func hiddenCanonicalFlagMigrationManifestJSON() string {
|
||||
return strings.Replace(
|
||||
validFlagMigrationManifestJSON,
|
||||
`"before": {"present": false}`,
|
||||
`"before": {"present": true, "type": "string", "hidden": true, "scope": "local"}`,
|
||||
1,
|
||||
)
|
||||
}
|
||||
|
||||
func TestApprovedFlagMigrationManifestRemainsValid(t *testing.T) {
|
||||
manifest, err := os.Open("../../scripts/policy/interface-migrations/approved-flag-migrations-v1.json")
|
||||
if err != nil {
|
||||
t.Fatalf("open approved flag migration manifest: %v", err)
|
||||
}
|
||||
defer manifest.Close()
|
||||
|
||||
if _, err := ReadFlagMigrationManifest(manifest); err != nil {
|
||||
t.Fatalf("approved flag migration manifest is invalid: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCrossPlatformCoverageReadFlagMigrationManifestRejectsUnknownFields(t *testing.T) {
|
||||
_, err := ReadFlagMigrationManifest(strings.NewReader(`{
|
||||
"version": 1,
|
||||
@@ -128,6 +150,9 @@ func TestCrossPlatformCoverageReadFlagMigrationManifestValidatesExactEntries(t *
|
||||
if _, err := ReadFlagMigrationManifest(strings.NewReader(optionalFlagMigrationManifestJSON())); err != nil {
|
||||
t.Fatalf("ReadFlagMigrationManifest(optional rename) error = %v", err)
|
||||
}
|
||||
if _, err := ReadFlagMigrationManifest(strings.NewReader(hiddenCanonicalFlagMigrationManifestJSON())); err != nil {
|
||||
t.Fatalf("ReadFlagMigrationManifest(hidden canonical promotion) error = %v", err)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
|
||||
Reference in New Issue
Block a user