ci: govern exact CLI flag migrations

This commit is contained in:
chichuan
2026-08-10 10:46:20 +08:00
parent 2bc4ded969
commit 46ae1c50fe
28 changed files with 6354 additions and 176 deletions
+1 -1
View File
@@ -19,7 +19,7 @@ Script groups:
- Product convenience installers: `./scripts/install-devapp.sh`, `./scripts/install-devapp.ps1`, `./scripts/install-event.sh`
- CI package plan: `./scripts/ci/test-packages.sh`
- Dev helpers: `./scripts/dev/build.sh`, `./scripts/dev/lint.sh`, `./scripts/dev/ci-local.sh`, `./scripts/dev/run-mock-e2e.sh`, `./scripts/dev/coverage.sh`
- Policy checks: `./scripts/policy/check-generated-drift.sh`, `./scripts/policy/check-command-surface.sh`, `./scripts/policy/check-command-compatibility.sh --base-ref <main-ref> --stable-ref <latest-GA-tag>`, `./scripts/policy/check-schema-catalog.sh`, `./scripts/policy/check-schema-binary.sh`, `./scripts/policy/check-open-source-assets.sh`
- Policy checks: `./scripts/policy/check-generated-drift.sh`, `./scripts/policy/check-command-surface.sh`, `./scripts/policy/check-command-compatibility.sh --base-ref <main-ref> --stable-ref <latest-GA-tag> --candidate-ref <candidate-sha>`, `./scripts/policy/check-schema-catalog.sh`, `./scripts/policy/check-schema-binary.sh`, `./scripts/policy/check-open-source-assets.sh`
- Policy runtime files: set `DWS_POLICY_TMPDIR` to override the default `.worktrees/policy-tmp` workspace
- Release entrypoint and contract: `./scripts/release/release.sh`, `./scripts/release/release-contract.sh`; operator guide: [`docs/releasing.md`](../docs/releasing.md)
- Release packaging helpers: `./scripts/release/post-goreleaser.sh`, `./scripts/release/stage-npm-package.sh`, `./scripts/release/pack-npm-package.sh`, `./scripts/release/verify-delivered-stable.sh`, `./scripts/release/verify-release-artifacts.sh`, `./scripts/release/verify-github-release-assets.sh`, `./scripts/release/verify-github-tag-authority.sh`, `./scripts/release/verify-package-managers.sh`, `./scripts/release/publish-homebrew-formula.sh`
@@ -1,15 +1,16 @@
#!/bin/sh
set -eu
# Compare the candidate command tree against a Git-owned historical source
# revision. CI invokes this for both the PR merge-base and latest stable tag; a
# fixture from the candidate branch is never used as authority.
# Compatibility decisions are centralized in check-command-compatibility.sh.
# This historical entry point remains as the Makefile/user-facing wrapper.
ROOT="$(CDPATH= cd -- "$(dirname -- "$0")/../.." && pwd)"
BASE_REF=""
STABLE_REF=""
CANDIDATE_REF="HEAD"
usage() {
printf '%s\n' "usage: $0 --base-ref <ref>" >&2
printf '%s\n' "usage: $0 --base-ref <ref> [--stable-ref <ref>] [--candidate-ref <ref>]" >&2
}
while [ "$#" -gt 0 ]; do
@@ -19,6 +20,16 @@ while [ "$#" -gt 0 ]; do
BASE_REF="$2"
shift 2
;;
--stable-ref)
[ "$#" -ge 2 ] || { usage; exit 2; }
STABLE_REF="$2"
shift 2
;;
--candidate-ref)
[ "$#" -ge 2 ] || { usage; exit 2; }
CANDIDATE_REF="$2"
shift 2
;;
-h|--help)
usage
exit 0
@@ -32,63 +43,30 @@ while [ "$#" -gt 0 ]; do
done
[ -n "$BASE_REF" ] || { usage; exit 2; }
cd "$ROOT"
. "$ROOT/scripts/policy/policy-runtime.sh"
policy_prepare_runtime "$ROOT"
. "$ROOT/scripts/release/release-lib.sh"
git rev-parse --verify --quiet "${BASE_REF}^{commit}" >/dev/null || {
printf 'error: interface authority is not available locally: %s\n' "$BASE_REF" >&2
exit 2
}
TMP_ROOT="$(policy_runtime_mktemp_dir dws-interface-authority)"
BASE_WORKTREE="$TMP_ROOT/base-worktree"
BASELINE="$TMP_ROOT/merge-base.txt"
CANDIDATE_BIN="$TMP_ROOT/interface-baseline"
CANDIDATE_HOME="$TMP_ROOT/candidate-home"
if [ -z "$STABLE_REF" ]; then
for tag in $(git tag --merged "$BASE_REF" --list 'v*' --sort=-version:refname); do
release_is_stable_version "$tag" || continue
if git rev-parse --verify --quiet "refs/tags/withdrawn/$tag" >/dev/null; then
continue
fi
STABLE_REF="$tag"
break
done
fi
if [ -z "$STABLE_REF" ]; then
printf 'error: no stable release tag is reachable from interface authority %s\n' "$BASE_REF" >&2
exit 2
fi
cleanup() {
git -C "$ROOT" worktree remove --force "$BASE_WORKTREE" >/dev/null 2>&1 || true
rm -rf "$TMP_ROOT"
}
trap cleanup EXIT
trap 'exit 129' HUP
trap 'exit 130' INT
trap 'exit 143' TERM
install_snapshot_helper() {
worktree="$1"
helper="$worktree/scripts/policy/interface-baseline"
# Use one contract format on both sides while constructing the command tree
# from the authoritative historical source revision.
rm -rf "$helper"
mkdir -p "$helper"
cp -R "$ROOT/scripts/policy/interface-baseline/." "$helper/"
}
generate_reference() {
ref="$1"
worktree="$2"
output="$3"
home="$4"
bin="$TMP_ROOT/reference-$(basename "$worktree")"
git -C "$ROOT" worktree add --detach "$worktree" "$ref" >/dev/null
install_snapshot_helper "$worktree"
mkdir -p "$home"
(
cd "$worktree"
go build -o "$bin" ./scripts/policy/interface-baseline
HOME="$home" DWS_LANG=zh "$bin" >"$output"
)
}
generate_reference "$BASE_REF" "$BASE_WORKTREE" "$BASELINE" "$TMP_ROOT/base-home"
mkdir -p "$CANDIDATE_HOME"
go build -o "$CANDIDATE_BIN" ./scripts/policy/interface-baseline
printf 'checking candidate against authoritative interface ref %s\n' "$BASE_REF"
HOME="$CANDIDATE_HOME" DWS_LANG=zh "$CANDIDATE_BIN" --check "$BASELINE"
exec "$ROOT/scripts/policy/check-command-compatibility.sh" \
--base-ref "$BASE_REF" \
--stable-ref "$STABLE_REF" \
--candidate-ref "$CANDIDATE_REF"
@@ -1,16 +1,22 @@
#!/bin/sh
set -eu
# Compare the candidate's complete Schema contract with the Git-owned PR
# merge-base (or previous main SHA on push). The candidate branch cannot
# weaken this authority by editing a checked-in fixture.
# Compare the committed candidate's complete Schema contract with the Git-owned
# PR merge-base (or previous main SHA on push). The merge-base owns the Schema
# checker and, after bootstrap, the approved flag-migration ledger. Candidate
# code is lifecycle input only and cannot replace either authority.
ROOT="$(CDPATH= cd -- "$(dirname -- "$0")/../.." && pwd)"
BIN="${DWS_BIN:-$ROOT/dws}"
BASE_REF=""
STABLE_REF=""
CANDIDATE_REF="HEAD"
SCHEMA_CHECKER_REL="scripts/policy/schema-compat/main.go"
MIGRATION_MANIFEST_REL="scripts/policy/interface-migrations/approved-flag-migrations-v1.json"
MIGRATIONS_REL="internal/interfacesnapshot/migrations.go"
ALIAS_CONTRACT_REL="internal/corecmd/runtimeannotate/interface_alias.go"
usage() {
printf '%s\n' "usage: $0 --base-ref <ref>" >&2
printf '%s\n' "usage: $0 --base-ref <ref> [--stable-ref <ref>] [--candidate-ref <ref>]" >&2
}
while [ "$#" -gt 0 ]; do
@@ -20,6 +26,16 @@ while [ "$#" -gt 0 ]; do
BASE_REF="$2"
shift 2
;;
--stable-ref)
[ "$#" -ge 2 ] || { usage; exit 2; }
STABLE_REF="$2"
shift 2
;;
--candidate-ref)
[ "$#" -ge 2 ] || { usage; exit 2; }
CANDIDATE_REF="$2"
shift 2
;;
-h|--help)
usage
exit 0
@@ -36,27 +52,62 @@ done
cd "$ROOT"
. "$ROOT/scripts/policy/policy-runtime.sh"
. "$ROOT/scripts/release/release-lib.sh"
policy_prepare_runtime "$ROOT"
git rev-parse --verify --quiet "${BASE_REF}^{commit}" >/dev/null || {
BASE_COMMIT="$(git rev-parse --verify "${BASE_REF}^{commit}")" || {
printf 'error: Schema authority is not available locally: %s\n' "$BASE_REF" >&2
exit 2
}
if [ ! -x "$BIN" ]; then
printf 'error: dws binary not found at %s (run make build first)\n' "$BIN" >&2
CANDIDATE_COMMIT="$(git rev-parse --verify "${CANDIDATE_REF}^{commit}")" || {
printf 'error: Schema candidate is not available locally: %s\n' "$CANDIDATE_REF" >&2
exit 2
}
EXPECTED_STABLE_REF=""
for tag in $(git tag --merged "$BASE_REF" --list 'v*' --sort=-version:refname); do
release_is_stable_version "$tag" || continue
if git rev-parse --verify --quiet "refs/tags/withdrawn/$tag" >/dev/null; then
continue
fi
EXPECTED_STABLE_REF="$tag"
break
done
if [ -z "$EXPECTED_STABLE_REF" ]; then
printf 'error: no stable release tag is reachable from Schema authority %s\n' "$BASE_REF" >&2
exit 2
fi
if [ -z "$STABLE_REF" ]; then
STABLE_REF="$EXPECTED_STABLE_REF"
fi
STABLE_COMMIT="$(git rev-parse --verify "${STABLE_REF}^{commit}")" || {
printf 'error: Schema stable authority is not available locally: %s\n' "$STABLE_REF" >&2
exit 2
}
if [ "$(git rev-parse "${STABLE_REF}^{commit}")" != "$(git rev-parse "${EXPECTED_STABLE_REF}^{commit}")" ]; then
printf 'error: stable ref %s is not the expected highest GA tag %s reachable from %s\n' \
"$STABLE_REF" "$EXPECTED_STABLE_REF" "$BASE_REF" >&2
exit 2
fi
TMP_ROOT="$(policy_runtime_mktemp_dir dws-schema-authority)"
BASE_WORKTREE="$TMP_ROOT/base-worktree"
BASE_BIN="$TMP_ROOT/base-dws"
BASE_RAW="$TMP_ROOT/base-schema.json"
BASELINE="$TMP_ROOT/base-contract.json"
CANDIDATE_RAW="$TMP_ROOT/candidate-schema.json"
CHECKER="$TMP_ROOT/schema-compat"
STABLE_WORKTREE="$TMP_ROOT/stable-worktree"
CANDIDATE_WORKTREE="$TMP_ROOT/candidate-worktree"
BASE_WORKTREE_ADDED=false
STABLE_WORKTREE_ADDED=false
CANDIDATE_WORKTREE_ADDED=false
cleanup() {
git -C "$ROOT" worktree remove --force "$BASE_WORKTREE" >/dev/null 2>&1 || true
if [ "$CANDIDATE_WORKTREE_ADDED" = true ]; then
git -C "$ROOT" worktree remove --force "$CANDIDATE_WORKTREE" >/dev/null 2>&1 || true
fi
if [ "$STABLE_WORKTREE_ADDED" = true ]; then
git -C "$ROOT" worktree remove --force "$STABLE_WORKTREE" >/dev/null 2>&1 || true
fi
if [ "$BASE_WORKTREE_ADDED" = true ]; then
git -C "$ROOT" worktree remove --force "$BASE_WORKTREE" >/dev/null 2>&1 || true
fi
rm -rf "$TMP_ROOT"
}
trap cleanup EXIT
@@ -64,19 +115,213 @@ trap 'exit 129' HUP
trap 'exit 130' INT
trap 'exit 143' TERM
git -C "$ROOT" worktree add --detach "$BASE_WORKTREE" "$BASE_REF" >/dev/null
git -C "$ROOT" worktree add --detach "$BASE_WORKTREE" "$BASE_COMMIT" >/dev/null
BASE_WORKTREE_ADDED=true
git -C "$ROOT" worktree add --detach "$STABLE_WORKTREE" "$STABLE_COMMIT" >/dev/null
STABLE_WORKTREE_ADDED=true
git -C "$ROOT" worktree add --detach "$CANDIDATE_WORKTREE" "$CANDIDATE_COMMIT" >/dev/null
CANDIDATE_WORKTREE_ADDED=true
commit_path_exists() {
tree_commit="$1"
tree_path="$2"
tree_entry="$(git -C "$ROOT" ls-tree "$tree_commit" -- "$tree_path")"
[ -n "$tree_entry" ]
}
commit_path_is_regular_file() {
tree_commit="$1"
tree_path="$2"
tree_entry="$(git -C "$ROOT" ls-tree "$tree_commit" -- "$tree_path")"
[ -n "$tree_entry" ] || return 1
set -- $tree_entry
[ "$#" -eq 4 ] || return 1
case "$1" in
100644|100755) ;;
*) return 1 ;;
esac
[ "$2" = blob ] && [ "$4" = "$tree_path" ]
}
has_modern_interface_helper() {
governance_commit="$1"
commit_path_is_regular_file "$governance_commit" cmd/interface-snapshot/main.go &&
commit_path_is_regular_file "$governance_commit" internal/interfacesnapshot/snapshot.go &&
commit_path_is_regular_file "$governance_commit" internal/interfacesnapshot/compare.go
}
has_complete_schema_migration_governance() {
governance_commit="$1"
commit_path_is_regular_file "$governance_commit" "$SCHEMA_CHECKER_REL" &&
has_modern_interface_helper "$governance_commit" &&
commit_path_is_regular_file "$governance_commit" "$MIGRATIONS_REL" &&
commit_path_is_regular_file "$governance_commit" "$ALIAS_CONTRACT_REL" &&
commit_path_is_regular_file "$governance_commit" "$MIGRATION_MANIFEST_REL"
}
has_any_schema_migration_governance_artifact() {
governance_commit="$1"
for relative_path in \
"$MIGRATIONS_REL" \
"$ALIAS_CONTRACT_REL" \
"$MIGRATION_MANIFEST_REL"; do
if commit_path_exists "$governance_commit" "$relative_path"; then
return 0
fi
done
return 1
}
check_candidate_alias_source_policy() {
source_policy_failed=false
for token in \
dws.compat.alias_of \
dws.compat.alias_origin \
corecmd.flag_spec_aliases.v1 \
AnnotationFlagAliasOf \
AnnotationFlagAliasOrigin \
FlagAliasOriginCorecmdV1; do
matches="$(git -C "$CANDIDATE_WORKTREE" grep -l -F -e "$token" -- '*.go' || true)"
[ -z "$matches" ] && continue
while IFS= read -r relative_path; do
[ -n "$relative_path" ] || continue
case "$relative_path" in
*_test.go)
continue
;;
internal/corecmd/corecmd.go|\
internal/corecmd/runtimeannotate/interface_alias.go|\
internal/interfacesnapshot/snapshot.go)
;;
*)
if [ "$source_policy_failed" = false ]; then
printf 'error: candidate production source may not forge framework-owned flag alias evidence:\n' >&2
fi
printf ' %s: protected alias evidence token %s\n' "$relative_path" "$token" >&2
source_policy_failed=true
;;
esac
done <<EOF
$matches
EOF
done
if [ "$source_policy_failed" = true ]; then
exit 2
fi
}
if ! commit_path_is_regular_file "$BASE_COMMIT" "$SCHEMA_CHECKER_REL"; then
printf 'error: merge-base lacks the base-owned Schema checker: %s\n' "$BASE_REF" >&2
exit 2
fi
if ! has_modern_interface_helper "$BASE_COMMIT"; then
printf 'error: merge-base lacks the modern interface snapshot helper required for Schema governance: %s\n' "$BASE_REF" >&2
exit 2
fi
if ! has_complete_schema_migration_governance "$CANDIDATE_COMMIT"; then
printf 'error: candidate must preserve the complete Schema flag migration governance artifact set at %s\n' \
"$CANDIDATE_COMMIT" >&2
exit 2
fi
check_candidate_alias_source_policy
EMPTY_MANIFEST="$TMP_ROOT/empty-migrations.json"
printf '%s\n' '{"version":1,"migrations":[]}' >"$EMPTY_MANIFEST"
USE_MIGRATION_GOVERNANCE=false
if has_complete_schema_migration_governance "$BASE_COMMIT"; then
USE_MIGRATION_GOVERNANCE=true
APPROVED_MANIFEST="$BASE_WORKTREE/$MIGRATION_MANIFEST_REL"
CANDIDATE_MANIFEST="$CANDIDATE_WORKTREE/$MIGRATION_MANIFEST_REL"
else
# One-time bootstrap for the governance PR itself. The old base checker owns
# the decision and receives no flags it does not understand. With no base
# approval authority, only the exact canonical empty candidate ledger is
# admissible.
if has_any_schema_migration_governance_artifact "$BASE_COMMIT"; then
printf 'error: merge-base contains an incomplete Schema flag migration governance artifact set: %s\n' \
"$BASE_REF" >&2
exit 2
fi
BOOTSTRAP_MANIFEST="$CANDIDATE_WORKTREE/$MIGRATION_MANIFEST_REL"
if ! cmp -s "$BOOTSTRAP_MANIFEST" "$EMPTY_MANIFEST"; then
printf 'error: initial Schema flag migration governance requires the canonical empty manifest: %s\n' \
"$BOOTSTRAP_MANIFEST" >&2
exit 2
fi
fi
BASE_BIN="$TMP_ROOT/base-dws"
CANDIDATE_BIN="$TMP_ROOT/candidate-dws"
CHECKER="$TMP_ROOT/schema-compat"
BASE_RAW="$TMP_ROOT/base-schema.json"
BASELINE="$TMP_ROOT/base-contract.json"
CANDIDATE_RAW="$TMP_ROOT/candidate-schema.json"
(
cd "$BASE_WORKTREE"
go build -o "$BASE_BIN" ./cmd
go build -o "$CHECKER" ./scripts/policy/schema-compat
)
(
cd "$CANDIDATE_WORKTREE"
go build -o "$CANDIDATE_BIN" ./cmd
)
go build -o "$CHECKER" ./scripts/policy/schema-compat
mkdir -p "$TMP_ROOT/base-home" "$TMP_ROOT/candidate-home"
HOME="$TMP_ROOT/base-home" DWS_LANG=zh \
"$BASE_BIN" schema --all --format json >"$BASE_RAW"
HOME="$TMP_ROOT/candidate-home" DWS_LANG=zh \
"$BIN" schema --all --format json >"$CANDIDATE_RAW"
"$CANDIDATE_BIN" schema --all --format json >"$CANDIDATE_RAW"
"$CHECKER" --normalize "$BASE_RAW" >"$BASELINE"
printf 'checking complete Schema contract against PR merge-base %s\n' "$BASE_REF"
"$CHECKER" --check "$BASELINE" --current "$CANDIDATE_RAW"
printf 'checking complete Schema contract against PR merge-base %s and stable %s\n' \
"$BASE_REF" "$STABLE_REF"
if [ "$USE_MIGRATION_GOVERNANCE" = false ]; then
"$CHECKER" --check "$BASELINE" --current "$CANDIDATE_RAW"
exit $?
fi
install_authority_interface_helper() {
worktree="$1"
for helper_path in cmd/interface-snapshot internal/interfacesnapshot; do
rm -rf "$worktree/$helper_path"
mkdir -p "$worktree/$helper_path"
cp -R "$BASE_WORKTREE/$helper_path/." "$worktree/$helper_path/"
done
mkdir -p "$worktree/$(dirname "$ALIAS_CONTRACT_REL")"
rm -f "$worktree/$ALIAS_CONTRACT_REL"
cp "$BASE_WORKTREE/$ALIAS_CONTRACT_REL" "$worktree/$ALIAS_CONTRACT_REL"
}
# The candidate and historical stable trees provide only the command surface.
# The merge-base owns the snapshot generator, comparator, and alias contract.
install_authority_interface_helper "$CANDIDATE_WORKTREE"
install_authority_interface_helper "$STABLE_WORKTREE"
generate_interface_snapshot() {
worktree="$1"
output="$2"
(
cd "$worktree"
go run ./cmd/interface-snapshot generate --output "$output"
)
}
CURRENT_INTERFACE_SNAPSHOT="$TMP_ROOT/candidate-interface-snapshot.json"
BASE_INTERFACE_SNAPSHOT="$TMP_ROOT/base-interface-snapshot.json"
STABLE_INTERFACE_SNAPSHOT="$TMP_ROOT/stable-interface-snapshot.json"
generate_interface_snapshot "$CANDIDATE_WORKTREE" "$CURRENT_INTERFACE_SNAPSHOT"
generate_interface_snapshot "$BASE_WORKTREE" "$BASE_INTERFACE_SNAPSHOT"
generate_interface_snapshot "$STABLE_WORKTREE" "$STABLE_INTERFACE_SNAPSHOT"
"$CHECKER" \
--check "$BASELINE" \
--current "$CANDIDATE_RAW" \
--approved-flag-migrations "$APPROVED_MANIFEST" \
--candidate-flag-migrations "$CANDIDATE_MANIFEST" \
--migration-current-snapshot "$CURRENT_INTERFACE_SNAPSHOT" \
--migration-base-snapshot "$BASE_INTERFACE_SNAPSHOT" \
--migration-stable-snapshot "$STABLE_INTERFACE_SNAPSHOT"
+228 -49
View File
@@ -1,23 +1,21 @@
#!/bin/sh
set -eu
# Compare the current Cobra command surface with two local Git revisions:
# the PR merge-base/main reference and the latest stable GA tag. The caller must
# pass the highest non-prerelease SemVer tag (excluding beta/rc tags). Tag
# governance treats that tag as proof of a successfully published GA release;
# release automation must not leave a GA tag behind when publication fails.
#
# The script never fetches refs or snapshots from the network. With checkout
# fetch-depth=0, both references are materialized in temporary worktrees. A
# reference uses its own snapshot helper when available; older revisions are
# bootstrapped with the candidate helper.
# Compare the committed candidate Cobra surface with the PR merge-base and the
# latest stable GA tag. Once the merge-base contains the migration governance
# helper, that revision owns the generator, comparator, and approved manifest.
# The candidate manifest is lifecycle input only and cannot authorize a change
# introduced by the same PR.
ROOT="$(CDPATH= cd -- "$(dirname -- "$0")/../.." && pwd)"
BASE_REF=""
STABLE_REF=""
CANDIDATE_REF="HEAD"
MIGRATION_MANIFEST_REL="scripts/policy/interface-migrations/approved-flag-migrations-v1.json"
ALIAS_CONTRACT_REL="internal/corecmd/runtimeannotate/interface_alias.go"
usage() {
printf '%s\n' "usage: $0 --base-ref <ref> --stable-ref <ref>" >&2
printf '%s\n' "usage: $0 --base-ref <ref> --stable-ref <ref> [--candidate-ref <ref>]" >&2
}
while [ "$#" -gt 0 ]; do
@@ -32,6 +30,11 @@ while [ "$#" -gt 0 ]; do
STABLE_REF="$2"
shift 2
;;
--candidate-ref)
[ "$#" -ge 2 ] || { usage; exit 2; }
CANDIDATE_REF="$2"
shift 2
;;
-h|--help)
usage
exit 0
@@ -47,20 +50,51 @@ done
[ -n "$BASE_REF" ] && [ -n "$STABLE_REF" ] || { usage; exit 2; }
cd "$ROOT"
git rev-parse --verify --quiet "${BASE_REF}^{commit}" >/dev/null || {
. "$ROOT/scripts/policy/policy-runtime.sh"
. "$ROOT/scripts/release/release-lib.sh"
policy_prepare_runtime "$ROOT"
BASE_COMMIT="$(git rev-parse --verify "${BASE_REF}^{commit}")" || {
printf 'base ref is not available locally: %s\n' "$BASE_REF" >&2
exit 2
}
git rev-parse --verify --quiet "${STABLE_REF}^{commit}" >/dev/null || {
STABLE_COMMIT="$(git rev-parse --verify "${STABLE_REF}^{commit}")" || {
printf 'stable ref is not available locally: %s\n' "$STABLE_REF" >&2
exit 2
}
CANDIDATE_COMMIT="$(git rev-parse --verify "${CANDIDATE_REF}^{commit}")" || {
printf 'candidate ref is not available locally: %s\n' "$CANDIDATE_REF" >&2
exit 2
}
EXPECTED_STABLE_REF=""
for tag in $(git tag --merged "$BASE_REF" --list 'v*' --sort=-version:refname); do
release_is_stable_version "$tag" || continue
if git rev-parse --verify --quiet "refs/tags/withdrawn/$tag" >/dev/null; then
continue
fi
EXPECTED_STABLE_REF="$tag"
break
done
if [ -z "$EXPECTED_STABLE_REF" ]; then
printf 'no stable GA tag is reachable from compatibility base %s\n' "$BASE_REF" >&2
exit 2
fi
if [ "$(git rev-parse "${STABLE_REF}^{commit}")" != "$(git rev-parse "${EXPECTED_STABLE_REF}^{commit}")" ]; then
printf 'stable ref %s is not the expected highest GA tag %s reachable from %s\n' \
"$STABLE_REF" "$EXPECTED_STABLE_REF" "$BASE_REF" >&2
exit 2
fi
TMP_ROOT="$(mktemp -d "${TMPDIR:-/tmp}/dws-command-compat.XXXXXX")"
TMP_ROOT="$(policy_runtime_mktemp_dir dws-command-compat)"
BASE_WORKTREE="$TMP_ROOT/base-worktree"
STABLE_WORKTREE="$TMP_ROOT/stable-worktree"
CANDIDATE_WORKTREE="$TMP_ROOT/candidate-worktree"
CANDIDATE_WORKTREE_ADDED=false
cleanup() {
if [ "$CANDIDATE_WORKTREE_ADDED" = true ]; then
git -C "$ROOT" worktree remove --force "$CANDIDATE_WORKTREE" >/dev/null 2>&1 || true
fi
git -C "$ROOT" worktree remove --force "$BASE_WORKTREE" >/dev/null 2>&1 || true
git -C "$ROOT" worktree remove --force "$STABLE_WORKTREE" >/dev/null 2>&1 || true
rm -rf "$TMP_ROOT"
@@ -70,38 +104,168 @@ trap 'exit 129' HUP
trap 'exit 130' INT
trap 'exit 143' TERM
ensure_snapshot_helper() {
worktree="$1"
git -C "$ROOT" worktree add --detach "$BASE_WORKTREE" "$BASE_COMMIT" >/dev/null
git -C "$ROOT" worktree add --detach "$STABLE_WORKTREE" "$STABLE_COMMIT" >/dev/null
git -C "$ROOT" worktree add --detach "$CANDIDATE_WORKTREE" "$CANDIDATE_COMMIT" >/dev/null
CANDIDATE_WORKTREE_ADDED=true
# Once a baseline contains the helper, use that revision's own capture
# rules. This makes a rule/filter change in the candidate visible as a
# snapshot_rules_changed failure instead of applying the new exclusion to
# both sides and accidentally hiding a removed command. Older revisions
# created before this helper existed are bootstrapped with the candidate
# implementation.
if [ -f "$worktree/cmd/interface-snapshot/main.go" ] && \
[ -f "$worktree/internal/interfacesnapshot/snapshot.go" ] && \
[ -f "$worktree/internal/interfacesnapshot/compare.go" ]; then
return
fi
if [ -e "$worktree/cmd/interface-snapshot" ] || \
[ -e "$worktree/internal/interfacesnapshot" ]; then
printf 'baseline contains an incomplete interface snapshot helper: %s\n' "$worktree" >&2
exit 2
fi
mkdir -p "$worktree/cmd/interface-snapshot" "$worktree/internal/interfacesnapshot"
cp -R "$ROOT/cmd/interface-snapshot/." "$worktree/cmd/interface-snapshot/"
cp -R "$ROOT/internal/interfacesnapshot/." "$worktree/internal/interfacesnapshot/"
commit_path_exists() {
tree_commit="$1"
tree_path="$2"
tree_entry="$(git -C "$ROOT" ls-tree "$tree_commit" -- "$tree_path")"
[ -n "$tree_entry" ]
}
generate_ref_snapshot() {
ref="$1"
worktree="$2"
output="$3"
commit_path_is_regular_file() {
tree_commit="$1"
tree_path="$2"
tree_entry="$(git -C "$ROOT" ls-tree "$tree_commit" -- "$tree_path")"
[ -n "$tree_entry" ] || return 1
set -- $tree_entry
[ "$#" -eq 4 ] || return 1
case "$1" in
100644|100755) ;;
*) return 1 ;;
esac
[ "$2" = blob ] && [ "$4" = "$tree_path" ]
}
git -C "$ROOT" worktree add --detach "$worktree" "$ref" >/dev/null
ensure_snapshot_helper "$worktree"
has_modern_interface_helper() {
governance_commit="$1"
commit_path_is_regular_file "$governance_commit" cmd/interface-snapshot/main.go &&
commit_path_is_regular_file "$governance_commit" internal/interfacesnapshot/snapshot.go &&
commit_path_is_regular_file "$governance_commit" internal/interfacesnapshot/compare.go
}
has_complete_migration_governance() {
governance_commit="$1"
has_modern_interface_helper "$governance_commit" &&
commit_path_is_regular_file "$governance_commit" internal/interfacesnapshot/migrations.go &&
commit_path_is_regular_file "$governance_commit" "$ALIAS_CONTRACT_REL" &&
commit_path_is_regular_file "$governance_commit" "$MIGRATION_MANIFEST_REL"
}
has_any_migration_governance_artifact() {
governance_commit="$1"
for relative_path in \
internal/interfacesnapshot/migrations.go \
"$ALIAS_CONTRACT_REL" \
"$MIGRATION_MANIFEST_REL"; do
if commit_path_exists "$governance_commit" "$relative_path"; then
return 0
fi
done
return 1
}
require_complete_candidate_governance() {
if ! has_complete_migration_governance "$CANDIDATE_COMMIT"; then
printf 'candidate must preserve the complete flag migration governance artifact set at %s\n' \
"$CANDIDATE_COMMIT" >&2
exit 2
fi
}
check_candidate_alias_source_policy() {
source_policy_failed=false
for token in \
dws.compat.alias_of \
dws.compat.alias_origin \
corecmd.flag_spec_aliases.v1 \
AnnotationFlagAliasOf \
AnnotationFlagAliasOrigin \
FlagAliasOriginCorecmdV1; do
matches="$(git -C "$CANDIDATE_WORKTREE" grep -l -F -e "$token" -- '*.go' || true)"
[ -z "$matches" ] && continue
while IFS= read -r relative_path; do
[ -n "$relative_path" ] || continue
case "$relative_path" in
*_test.go)
continue
;;
internal/corecmd/corecmd.go|\
internal/corecmd/runtimeannotate/interface_alias.go|\
internal/interfacesnapshot/snapshot.go)
;;
*)
if [ "$source_policy_failed" = false ]; then
printf 'candidate production source may not forge framework-owned flag alias evidence:\n' >&2
fi
printf ' %s: protected alias evidence token %s\n' "$relative_path" "$token" >&2
source_policy_failed=true
;;
esac
done <<EOF
$matches
EOF
done
if [ "$source_policy_failed" = true ]; then
exit 2
fi
}
install_authority_helper() {
source_root="$1"
worktree="$2"
include_alias_contract="$3"
[ "$source_root" = "$worktree" ] && return
for helper_path in cmd/interface-snapshot internal/interfacesnapshot; do
rm -rf "$worktree/$helper_path"
mkdir -p "$worktree/$helper_path"
cp -R "$source_root/$helper_path/." "$worktree/$helper_path/"
done
if [ "$include_alias_contract" = true ]; then
mkdir -p "$worktree/$(dirname "$ALIAS_CONTRACT_REL")"
rm -f "$worktree/$ALIAS_CONTRACT_REL"
cp "$source_root/$ALIAS_CONTRACT_REL" "$worktree/$ALIAS_CONTRACT_REL"
fi
}
EMPTY_MANIFEST="$TMP_ROOT/empty-migrations.json"
printf '%s\n' '{"version":1,"migrations":[]}' >"$EMPTY_MANIFEST"
USE_MIGRATION_GOVERNANCE=false
if has_complete_migration_governance "$BASE_COMMIT"; then
USE_MIGRATION_GOVERNANCE=true
AUTHORITY_ROOT="$BASE_WORKTREE"
APPROVED_MANIFEST="$BASE_WORKTREE/$MIGRATION_MANIFEST_REL"
CANDIDATE_MANIFEST="$CANDIDATE_WORKTREE/$MIGRATION_MANIFEST_REL"
require_complete_candidate_governance
check_candidate_alias_source_policy
install_authority_helper "$AUTHORITY_ROOT" "$CANDIDATE_WORKTREE" true
else
# One-time bootstrap for the governance PR itself. No base approval exists,
# so the existing base-owned modern helper performs an ordinary comparison
# and this path admits only the canonical empty ledger. The candidate's new
# comparator never participates in the bootstrap decision.
if has_any_migration_governance_artifact "$BASE_COMMIT"; then
printf 'merge-base contains an incomplete flag migration governance artifact set: %s\n' \
"$BASE_REF" >&2
exit 2
fi
if ! has_modern_interface_helper "$BASE_COMMIT"; then
printf 'merge-base lacks the modern interface snapshot helper required for governance bootstrap: %s\n' "$BASE_REF" >&2
exit 2
fi
AUTHORITY_ROOT="$BASE_WORKTREE"
require_complete_candidate_governance
check_candidate_alias_source_policy
BOOTSTRAP_MANIFEST="$CANDIDATE_WORKTREE/$MIGRATION_MANIFEST_REL"
if ! cmp -s "$BOOTSTRAP_MANIFEST" "$EMPTY_MANIFEST"; then
printf 'initial flag migration governance requires the canonical empty manifest: %s\n' "$BOOTSTRAP_MANIFEST" >&2
exit 2
fi
install_authority_helper "$AUTHORITY_ROOT" "$CANDIDATE_WORKTREE" false
fi
install_authority_helper "$AUTHORITY_ROOT" "$BASE_WORKTREE" "$USE_MIGRATION_GOVERNANCE"
install_authority_helper "$AUTHORITY_ROOT" "$STABLE_WORKTREE" "$USE_MIGRATION_GOVERNANCE"
generate_snapshot() {
worktree="$1"
output="$2"
(
cd "$worktree"
go run ./cmd/interface-snapshot generate --output "$output"
@@ -112,11 +276,26 @@ CANDIDATE="$TMP_ROOT/candidate.json"
BASELINE="$TMP_ROOT/merge-base.json"
STABLE="$TMP_ROOT/stable.json"
go run ./cmd/interface-snapshot generate --output "$CANDIDATE"
generate_ref_snapshot "$BASE_REF" "$BASE_WORKTREE" "$BASELINE"
generate_ref_snapshot "$STABLE_REF" "$STABLE_WORKTREE" "$STABLE"
generate_snapshot "$CANDIDATE_WORKTREE" "$CANDIDATE"
generate_snapshot "$BASE_WORKTREE" "$BASELINE"
generate_snapshot "$STABLE_WORKTREE" "$STABLE"
go run ./cmd/interface-snapshot compare \
--current "$CANDIDATE" \
--base "$BASELINE" \
--stable "$STABLE"
if [ "$USE_MIGRATION_GOVERNANCE" = true ]; then
(
cd "$AUTHORITY_ROOT"
go run ./cmd/interface-snapshot compare \
--current "$CANDIDATE" \
--base "$BASELINE" \
--stable "$STABLE" \
--approved-flag-migrations "$APPROVED_MANIFEST" \
--candidate-flag-migrations "$CANDIDATE_MANIFEST"
)
else
(
cd "$AUTHORITY_ROOT"
go run ./cmd/interface-snapshot compare \
--current "$CANDIDATE" \
--base "$BASELINE" \
--stable "$STABLE"
)
fi
@@ -0,0 +1,454 @@
// Copyright 2026 Alibaba Group
// Licensed under the Apache License, Version 2.0 (the "License");
package commandcompat
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"testing"
"time"
)
const authorityHelperSource = `package main
import (
"flag"
"fmt"
"os"
"example.com/dws-command-compat-authority-test/internal/interfacesnapshot"
)
func main() {
if len(os.Args) < 2 {
fmt.Fprintln(os.Stderr, "missing command")
os.Exit(2)
}
switch os.Args[1] {
case "generate":
flags := flag.NewFlagSet("generate", flag.ExitOnError)
output := flags.String("output", "", "snapshot output")
_ = flags.Parse(os.Args[2:])
if *output == "" {
fmt.Fprintln(os.Stderr, "missing --output")
os.Exit(2)
}
marker := interfacesnapshot.AuthorityMarker()
if err := os.WriteFile(*output, []byte("AUTHORITY="+marker+"\n"), 0o600); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(2)
}
fmt.Printf("BASE_HELPER_GENERATE=%s\n", marker)
case "compare":
flags := flag.NewFlagSet("compare", flag.ExitOnError)
candidateManifest := flags.String("candidate-flag-migrations", "", "candidate manifest")
current := flags.String("current", "", "current snapshot")
base := flags.String("base", "", "base snapshot")
stable := flags.String("stable", "", "stable snapshot")
_ = flags.String("approved-flag-migrations", "", "approved manifest")
_ = flags.Parse(os.Args[2:])
for _, path := range []string{*current, *base, *stable} {
content, err := os.ReadFile(path)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(2)
}
if string(content) != "AUTHORITY=BASE\n" {
fmt.Fprintf(os.Stderr, "snapshot did not use base internal helper: %s", content)
os.Exit(2)
}
}
if *candidateManifest != "" {
content, err := os.ReadFile(*candidateManifest)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(2)
}
fmt.Printf("CANDIDATE_MANIFEST=%s\n", *candidateManifest)
if string(content) != "{\"version\":1,\"migrations\":[]}\n" {
fmt.Println("UNCOMMITTED_MANIFEST_USED")
return
}
}
fmt.Fprintln(os.Stderr, "BASE_COMPARATOR_ENFORCED")
os.Exit(23)
default:
fmt.Fprintln(os.Stderr, "unknown command")
os.Exit(2)
}
}
`
const governedSnapshotSource = `package interfacesnapshot
import "example.com/dws-command-compat-authority-test/internal/corecmd/runtimeannotate"
var (
_ = runtimeannotate.AnnotationFlagAliasOf
_ = runtimeannotate.AnnotationFlagAliasOrigin
_ = runtimeannotate.FlagAliasOriginCorecmdV1
)
func AuthorityMarker() string { return "BASE" }
`
const baseSnapshotSource = `package interfacesnapshot
func AuthorityMarker() string { return "BASE" }
`
const stableSnapshotSource = `package interfacesnapshot
func AuthorityMarker() string { return "STABLE" }
`
const candidateSnapshotSource = `package interfacesnapshot
func AuthorityMarker() string { return "CANDIDATE" }
`
const aliasContractSource = `package runtimeannotate
const (
AnnotationFlagAliasOf = "dws.compat.alias_of"
AnnotationFlagAliasOrigin = "dws.compat.alias_origin"
FlagAliasOriginCorecmdV1 = "corecmd.flag_spec_aliases.v1"
)
`
const candidateNoopSource = `package main
import "fmt"
func main() {
fmt.Println("CANDIDATE_NOOP")
}
`
type authorityScenario struct {
name string
baseGovernance string
candidateMutation string
want string
wantAuthorityDecision bool
}
func TestCrossPlatformCoverageCommandCompatibilityUsesBaseOwnedAuthority(t *testing.T) {
tests := []authorityScenario{
{
name: "governed base rejects candidate helper and live manifest",
baseGovernance: "complete",
wantAuthorityDecision: true,
},
{
name: "bootstrap uses old base helper",
baseGovernance: "none",
wantAuthorityDecision: true,
},
{
name: "partial base fails closed",
baseGovernance: "partial",
want: "incomplete flag migration governance artifact set",
},
{
name: "base ledger ancestor symlink fails closed",
baseGovernance: "ancestor-symlink",
want: "incomplete flag migration governance artifact set",
},
{
name: "governed candidate cannot delete alias contract",
baseGovernance: "complete",
candidateMutation: "delete-alias-contract",
want: "must preserve the complete flag migration governance artifact set",
},
{
name: "governed candidate cannot replace helper parent with symlink",
baseGovernance: "complete",
candidateMutation: "symlink-helper-parent",
want: "must preserve the complete flag migration governance artifact set",
},
{
name: "candidate cannot forge alias evidence token",
baseGovernance: "complete",
candidateMutation: "forge-alias-token",
want: "may not forge framework-owned flag alias evidence",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
t.Parallel()
runBaseOwnedAuthorityCase(t, test)
})
}
}
func runBaseOwnedAuthorityCase(t *testing.T, test authorityScenario) {
t.Helper()
repoRoot := repositoryRoot(t)
managedRoot := filepath.Join(repoRoot, ".worktrees", "commandcompat-authority-integration")
if err := os.MkdirAll(managedRoot, 0o755); err != nil {
t.Fatalf("create managed test root: %v", err)
}
fixtureRoot, err := os.MkdirTemp(managedRoot, fmt.Sprintf("case-%d-", time.Now().UnixNano()))
if err != nil {
t.Fatalf("create managed fixture repo: %v", err)
}
t.Cleanup(func() {
if err := os.RemoveAll(fixtureRoot); err != nil {
t.Errorf("remove managed fixture repo: %v", err)
}
})
copyFile(t,
filepath.Join(repoRoot, "scripts", "policy", "check-command-compatibility.sh"),
filepath.Join(fixtureRoot, "scripts", "policy", "check-command-compatibility.sh"),
0o755,
)
copyFile(t,
filepath.Join(repoRoot, "scripts", "policy", "policy-runtime.sh"),
filepath.Join(fixtureRoot, "scripts", "policy", "policy-runtime.sh"),
0o755,
)
copyFile(t,
filepath.Join(repoRoot, "scripts", "release", "release-lib.sh"),
filepath.Join(fixtureRoot, "scripts", "release", "release-lib.sh"),
0o755,
)
writeFile(t, filepath.Join(fixtureRoot, "go.mod"), "module example.com/dws-command-compat-authority-test\n\ngo 1.23\n", 0o644)
writeFile(t, filepath.Join(fixtureRoot, "cmd", "interface-snapshot", "main.go"), authorityHelperSource, 0o644)
writeFile(t, filepath.Join(fixtureRoot, "internal", "interfacesnapshot", "snapshot.go"), stableSnapshotSource, 0o644)
writeFile(t, filepath.Join(fixtureRoot, "internal", "interfacesnapshot", "compare.go"), "package interfacesnapshot\n", 0o644)
run(t, fixtureRoot, "git", "init", "-b", "main")
run(t, fixtureRoot, "git", "config", "user.name", "Command Compatibility Test")
run(t, fixtureRoot, "git", "config", "user.email", "command-compat-test@example.invalid")
run(t, fixtureRoot, "git", "add", ".")
run(t, fixtureRoot, "git", "commit", "-m", "old stable without migration governance")
stableCommit := strings.TrimSpace(run(t, fixtureRoot, "git", "rev-parse", "HEAD"))
run(t, fixtureRoot, "git", "tag", "v1.0.0", stableCommit)
writeFile(t, filepath.Join(fixtureRoot, "BASE_MARKER"), "newer merge-base\n", 0o644)
writeFile(t, filepath.Join(fixtureRoot, "internal", "interfacesnapshot", "snapshot.go"), baseSnapshotSource, 0o644)
switch test.baseGovernance {
case "complete":
writeFile(t, filepath.Join(fixtureRoot, "internal", "interfacesnapshot", "snapshot.go"), governedSnapshotSource, 0o644)
writeFile(t, filepath.Join(fixtureRoot, "internal", "interfacesnapshot", "migrations.go"), "package interfacesnapshot\n", 0o644)
writeFile(t,
filepath.Join(fixtureRoot, "internal", "corecmd", "runtimeannotate", "interface_alias.go"),
aliasContractSource,
0o644,
)
writeFile(t,
filepath.Join(fixtureRoot, "scripts", "policy", "interface-migrations", "approved-flag-migrations-v1.json"),
"{\"version\":1,\"migrations\":[]}\n",
0o644,
)
case "partial":
writeFile(t,
filepath.Join(fixtureRoot, "scripts", "policy", "interface-migrations", "approved-flag-migrations-v1.json"),
"{\"version\":1,\"migrations\":[]}\n",
0o644,
)
case "ancestor-symlink":
writeFile(t, filepath.Join(fixtureRoot, "internal", "interfacesnapshot", "migrations.go"), "package interfacesnapshot\n", 0o644)
writeFile(t,
filepath.Join(fixtureRoot, "internal", "corecmd", "runtimeannotate", "interface_alias.go"),
aliasContractSource,
0o644,
)
writeFile(t,
filepath.Join(fixtureRoot, "scripts", "policy", "authority-ledger", "approved-flag-migrations-v1.json"),
"{\"version\":1,\"migrations\":[]}\n",
0o644,
)
if err := os.Symlink("authority-ledger", filepath.Join(fixtureRoot, "scripts", "policy", "interface-migrations")); err != nil {
t.Fatalf("symlink base ledger parent: %v", err)
}
case "none":
default:
t.Fatalf("unknown base governance state %q", test.baseGovernance)
}
run(t, fixtureRoot, "git", "add", ".")
run(t, fixtureRoot, "git", "commit", "-m", "base authority")
baseRef := strings.TrimSpace(run(t, fixtureRoot, "git", "rev-parse", "HEAD"))
if baseRef == stableCommit {
t.Fatal("fixture stable tag must predate the governed merge-base")
}
// A withdrawn higher GA tag is reserved history, not the active stable
// compatibility baseline. The checker must agree with release-contract.sh.
run(t, fixtureRoot, "git", "tag", "v1.1.0", baseRef)
run(t, fixtureRoot, "git", "update-ref", "refs/tags/withdrawn/v1.1.0", baseRef)
// 候选提交故意把生成器与比较器改成无条件成功;若脚本错误地信任候选代码,
// 整个检查会输出 CANDIDATE_NOOP 并以 0 退出。
writeFile(t, filepath.Join(fixtureRoot, "cmd", "interface-snapshot", "main.go"), candidateNoopSource, 0o644)
writeFile(t, filepath.Join(fixtureRoot, "internal", "interfacesnapshot", "snapshot.go"), candidateSnapshotSource, 0o644)
if test.baseGovernance != "complete" {
writeFile(t, filepath.Join(fixtureRoot, "internal", "interfacesnapshot", "migrations.go"), "package interfacesnapshot\n", 0o644)
writeFile(t,
filepath.Join(fixtureRoot, "internal", "corecmd", "runtimeannotate", "interface_alias.go"),
aliasContractSource,
0o644,
)
writeFile(t,
filepath.Join(fixtureRoot, "scripts", "policy", "interface-migrations", "approved-flag-migrations-v1.json"),
"{\"version\":1,\"migrations\":[]}\n",
0o644,
)
}
switch test.candidateMutation {
case "":
case "delete-alias-contract":
if err := os.Remove(filepath.Join(fixtureRoot, "internal", "corecmd", "runtimeannotate", "interface_alias.go")); err != nil {
t.Fatalf("remove candidate alias contract: %v", err)
}
case "forge-alias-token":
writeFile(t,
filepath.Join(fixtureRoot, "internal", "evil", "forged.go"),
"package evil\n\nconst forged = \"dws.compat.alias_of\"\n",
0o644,
)
case "symlink-helper-parent":
helperPath := filepath.Join(fixtureRoot, "internal", "interfacesnapshot")
alternatePath := filepath.Join(fixtureRoot, "internal", "interfacesnapshot-alt")
if err := os.Rename(helperPath, alternatePath); err != nil {
t.Fatalf("move candidate helper behind ancestor symlink: %v", err)
}
if err := os.Symlink("interfacesnapshot-alt", helperPath); err != nil {
t.Fatalf("symlink candidate helper parent: %v", err)
}
default:
t.Fatalf("unknown candidate mutation %q", test.candidateMutation)
}
run(t, fixtureRoot, "git", "add", ".")
run(t, fixtureRoot, "git", "commit", "-m", "malicious candidate comparator")
if test.baseGovernance == "complete" && test.candidateMutation == "" {
writeFile(t,
filepath.Join(fixtureRoot, "scripts", "policy", "interface-migrations", "approved-flag-migrations-v1.json"),
"{\"version\":1,\"migrations\":[{\"uncommitted\":true}]}\n",
0o644,
)
}
cmd := exec.Command(
"sh",
"scripts/policy/check-command-compatibility.sh",
"--base-ref", baseRef,
"--stable-ref", "v1.0.0",
"--candidate-ref", "HEAD",
)
cmd.Dir = fixtureRoot
cmd.Env = integrationEnvironment(fixtureRoot)
output, runErr := cmd.CombinedOutput()
got := string(output)
if runErr == nil {
t.Fatalf("compatibility script unexpectedly succeeded; output:\n%s", got)
}
if strings.Contains(got, "CANDIDATE_NOOP") {
t.Fatalf("candidate-owned no-op helper took control; output:\n%s", got)
}
if strings.Contains(got, "UNCOMMITTED_MANIFEST_USED") {
t.Fatalf("compatibility script mixed the live manifest with the committed candidate surface; output:\n%s", got)
}
if test.wantAuthorityDecision {
if count := strings.Count(got, "BASE_HELPER_GENERATE=BASE"); count != 3 {
t.Fatalf("compatibility script generated %d/3 snapshots with the base-owned internal helper; output:\n%s", count, got)
}
if !strings.Contains(got, "BASE_COMPARATOR_ENFORCED") {
t.Fatalf("compatibility script did not use the base-owned comparator; output:\n%s", got)
}
if test.baseGovernance == "complete" && !strings.Contains(got, "CANDIDATE_MANIFEST=") {
t.Fatalf("base comparator did not receive the committed candidate manifest; output:\n%s", got)
}
} else if !strings.Contains(got, test.want) {
t.Fatalf("compatibility script output missing %q:\n%s", test.want, got)
}
if !test.wantAuthorityDecision {
return
}
wrongStable := exec.Command(
"sh",
"scripts/policy/check-command-compatibility.sh",
"--base-ref", baseRef,
"--stable-ref", "HEAD",
"--candidate-ref", "HEAD",
)
wrongStable.Dir = fixtureRoot
wrongStable.Env = integrationEnvironment(fixtureRoot)
wrongOutput, wrongErr := wrongStable.CombinedOutput()
if wrongErr == nil || !strings.Contains(string(wrongOutput), "is not the expected highest GA tag") {
t.Fatalf("arbitrary stable ref was not rejected: err=%v output:\n%s", wrongErr, wrongOutput)
}
}
func repositoryRoot(t *testing.T) string {
t.Helper()
_, sourceFile, _, ok := runtime.Caller(0)
if !ok {
t.Fatal("resolve test source path")
}
root := filepath.Clean(filepath.Join(filepath.Dir(sourceFile), "..", "..", ".."))
if _, err := os.Stat(filepath.Join(root, "scripts", "policy", "check-command-compatibility.sh")); err != nil {
t.Fatalf("resolve repository root %s: %v", root, err)
}
return root
}
func copyFile(t *testing.T, source, destination string, mode os.FileMode) {
t.Helper()
content, err := os.ReadFile(source)
if err != nil {
t.Fatalf("read %s: %v", source, err)
}
writeFile(t, destination, string(content), mode)
}
func writeFile(t *testing.T, path, content string, mode os.FileMode) {
t.Helper()
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
t.Fatalf("create parent for %s: %v", path, err)
}
if err := os.WriteFile(path, []byte(content), mode); err != nil {
t.Fatalf("write %s: %v", path, err)
}
}
func run(t *testing.T, dir, name string, args ...string) string {
t.Helper()
cmd := exec.Command(name, args...)
cmd.Dir = dir
output, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("%s %s failed: %v\n%s", name, strings.Join(args, " "), err, output)
}
return string(output)
}
func integrationEnvironment(fixtureRoot string) []string {
overrides := map[string]string{
"DWS_POLICY_TMPDIR": "",
"GOCACHE": filepath.Join(fixtureRoot, ".cache", "go-build"),
"GOMODCACHE": filepath.Join(fixtureRoot, ".cache", "go-mod"),
"GOTMPDIR": "",
"GOTOOLCHAIN": "local",
"GOWORK": "off",
}
environment := make([]string, 0, len(os.Environ())+len(overrides))
for _, item := range os.Environ() {
key, _, _ := strings.Cut(item, "=")
if _, replaced := overrides[key]; !replaced {
environment = append(environment, item)
}
}
for key, value := range overrides {
environment = append(environment, key+"="+value)
}
return environment
}
@@ -0,0 +1 @@
{"version":1,"migrations":[]}
+427
View File
@@ -14,6 +14,8 @@ import (
"os"
"sort"
"strings"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/interfacesnapshot"
)
const schemaContractVersion = 3
@@ -82,12 +84,19 @@ func main() {
func run(args []string, stdout, stderr io.Writer) int {
var normalizePath, checkPath, mergePath, currentPath string
var approvedFlagMigrationsPath, candidateFlagMigrationsPath string
var migrationCurrentSnapshotPath, migrationBaseSnapshotPath, migrationStableSnapshotPath string
flags := flag.NewFlagSet("schema-compat", flag.ContinueOnError)
flags.SetOutput(stderr)
flags.StringVar(&normalizePath, "normalize", "", "normalize a raw complete Schema response")
flags.StringVar(&checkPath, "check", "", "check against a normalized historical baseline")
flags.StringVar(&mergePath, "merge", "", "merge additions into a normalized historical baseline")
flags.StringVar(&currentPath, "current", "", "raw current complete Schema response")
flags.StringVar(&approvedFlagMigrationsPath, "approved-flag-migrations", "", "base-owned approved flag migration manifest")
flags.StringVar(&candidateFlagMigrationsPath, "candidate-flag-migrations", "", "detached candidate flag migration manifest")
flags.StringVar(&migrationCurrentSnapshotPath, "migration-current-snapshot", "", "current interface snapshot used for migration authorization")
flags.StringVar(&migrationBaseSnapshotPath, "migration-base-snapshot", "", "merge-base interface snapshot used for migration authorization")
flags.StringVar(&migrationStableSnapshotPath, "migration-stable-snapshot", "", "stable interface snapshot used for migration authorization")
if err := flags.Parse(args); err != nil {
return 2
}
@@ -102,6 +111,27 @@ func run(args []string, stdout, stderr io.Writer) int {
fmt.Fprintln(stderr, "exactly one of --normalize, --check, or --merge is required")
return 2
}
migrationInputs := []string{
approvedFlagMigrationsPath,
candidateFlagMigrationsPath,
migrationCurrentSnapshotPath,
migrationBaseSnapshotPath,
migrationStableSnapshotPath,
}
migrationInputCount := 0
for _, path := range migrationInputs {
if path != "" {
migrationInputCount++
}
}
if migrationInputCount != 0 && migrationInputCount != len(migrationInputs) {
fmt.Fprintln(stderr, "Schema flag migration authorization requires all five migration inputs")
return 2
}
if migrationInputCount != 0 && checkPath == "" {
fmt.Fprintln(stderr, "Schema flag migration authorization is only valid with --check")
return 2
}
if normalizePath != "" {
currentPath = normalizePath
@@ -128,6 +158,24 @@ func run(args []string, stdout, stderr io.Writer) int {
fmt.Fprintf(stderr, "read schema baseline: %v\n", err)
return 2
}
if migrationInputCount != 0 {
migrations, err := authorizeSchemaFlagMigrations(
approvedFlagMigrationsPath,
candidateFlagMigrationsPath,
migrationCurrentSnapshotPath,
migrationBaseSnapshotPath,
migrationStableSnapshotPath,
)
if err != nil {
fmt.Fprintf(stderr, "authorize Schema flag migrations: %v\n", err)
return 2
}
baseline, err = normalizeSchemaFlagMigrations(baseline, current, migrations)
if err != nil {
fmt.Fprintf(stderr, "normalize approved Schema flag migrations: %v\n", err)
return 2
}
}
failures := checkCompatibility(baseline, current)
if len(failures) > 0 {
fmt.Fprintln(stderr, "Schema backwards-compatibility check failed:")
@@ -159,6 +207,57 @@ func run(args []string, stdout, stderr io.Writer) int {
return 0
}
func authorizeSchemaFlagMigrations(
approvedManifestPath string,
candidateManifestPath string,
currentSnapshotPath string,
baseSnapshotPath string,
stableSnapshotPath string,
) ([]interfacesnapshot.FlagMigration, error) {
approved, err := readFlagMigrationManifestFile(approvedManifestPath)
if err != nil {
return nil, fmt.Errorf("read approved flag migrations: %w", err)
}
candidate, err := readFlagMigrationManifestFile(candidateManifestPath)
if err != nil {
return nil, fmt.Errorf("read candidate flag migrations: %w", err)
}
current, err := readInterfaceSnapshotFile(currentSnapshotPath)
if err != nil {
return nil, fmt.Errorf("read migration current snapshot: %w", err)
}
base, err := readInterfaceSnapshotFile(baseSnapshotPath)
if err != nil {
return nil, fmt.Errorf("read migration base snapshot: %w", err)
}
stable, err := readInterfaceSnapshotFile(stableSnapshotPath)
if err != nil {
return nil, fmt.Errorf("read migration stable snapshot: %w", err)
}
return interfacesnapshot.AuthorizeFlagMigrations(
current,
map[string]interfacesnapshot.Snapshot{"merge-base": base, "stable": stable},
approved,
candidate,
)
}
func readFlagMigrationManifestFile(path string) (interfacesnapshot.FlagMigrationManifest, error) {
data, err := os.ReadFile(path)
if err != nil {
return interfacesnapshot.FlagMigrationManifest{}, err
}
return interfacesnapshot.ReadFlagMigrationManifest(bytes.NewReader(data))
}
func readInterfaceSnapshotFile(path string) (interfacesnapshot.Snapshot, error) {
data, err := os.ReadFile(path)
if err != nil {
return interfacesnapshot.Snapshot{}, err
}
return interfacesnapshot.Read(bytes.NewReader(data))
}
func normalizeRawFile(path string) (schemaContract, error) {
data, err := os.ReadFile(path)
if err != nil {
@@ -919,6 +1018,334 @@ func cloneContract(source schemaContract) schemaContract {
return cloned
}
type schemaToolRef struct {
productID string
toolID string
}
// normalizeSchemaFlagMigrations projects only an already-authorized CLI flag
// rename onto a cloned historical Schema contract. The ordinary compatibility
// checker still makes the final decision; this adapter never drops findings by
// matching their rendered text.
func normalizeSchemaFlagMigrations(
baseline schemaContract,
current schemaContract,
migrations []interfacesnapshot.FlagMigration,
) (schemaContract, error) {
normalized := cloneContract(baseline)
renamesByTool := map[schemaToolRef]map[string]string{}
for _, migration := range migrations {
primaryPath := strings.TrimPrefix(migration.Command, "dws ")
matches := schemaToolsByPrimaryPath(baseline, primaryPath)
if len(matches) == 0 {
// A reviewed CLI-only command has no Schema compatibility surface.
continue
}
if len(matches) != 1 {
return schemaContract{}, fmt.Errorf(
"approved flag migration %q matches %d historical Schema tools",
migration.Command,
len(matches),
)
}
ref := matches[0]
oldTool := baseline.Products[ref.productID].Tools[ref.toolID]
newProduct, productExists := current.Products[ref.productID]
newTool, toolExists := newProduct.Tools[ref.toolID]
if !productExists || !toolExists || newTool.PrimaryCLIPath != primaryPath {
// Preserve the original baseline so the ordinary checker reports the
// missing tool or changed primary_cli_path.
continue
}
oldLegacy, legacyExisted := oldTool.Parameters[migration.Legacy.Name]
oldCanonical, canonicalExisted := oldTool.Parameters[migration.Canonical.Name]
if !legacyExisted && !canonicalExisted {
// A CLI migration is not evidence that an unrelated Schema surface
// published either name. In particular, it must not authorize a
// constraints rewrite with no historical parameter to rename.
continue
}
if _, exists := newTool.Parameters[migration.Legacy.Name]; exists {
return schemaContract{}, fmt.Errorf(
"approved flag migration %q still publishes legacy Schema parameter %q",
migration.Command,
migration.Legacy.Name,
)
}
newCanonical, exists := newTool.Parameters[migration.Canonical.Name]
if !exists {
return schemaContract{}, fmt.Errorf(
"approved flag migration %q does not publish canonical Schema parameter %q",
migration.Command,
migration.Canonical.Name,
)
}
if migration.Canonical.After.Required && (!newCanonical.Required || !newCanonical.CLIRequired) {
return schemaContract{}, fmt.Errorf(
"approved flag migration %q canonical Schema parameter %q must be required and cli_required",
migration.Command,
migration.Canonical.Name,
)
}
if !legacyExisted {
// A consumed receipt may be evaluated after the merge-base Schema has
// already reached the canonical-only state. In that case the ordinary
// checker needs no projection. More importantly, a CLI migration is not
// authority to promote a canonical-only historical Schema parameter from
// optional to required: without legacy Schema evidence there is no rename.
continue
}
normalizedProduct := normalized.Products[ref.productID]
normalizedTool := normalizedProduct.Tools[ref.toolID]
if err := validateRenamedSchemaParameter(migration, oldLegacy, newCanonical); err != nil {
return schemaContract{}, err
}
delete(normalizedTool.Parameters, migration.Legacy.Name)
if canonicalExisted {
if err := validateRenamedSchemaParameter(migration, oldCanonical, newCanonical); err != nil {
return schemaContract{}, err
}
normalizedCanonical := normalizedTool.Parameters[migration.Canonical.Name]
normalizedCanonical.Required = newCanonical.Required
normalizedCanonical.CLIRequired = newCanonical.CLIRequired
normalizedTool.Parameters[migration.Canonical.Name] = normalizedCanonical
} else {
normalizedTool.Parameters[migration.Canonical.Name] = newCanonical
}
normalizedProduct.Tools[ref.toolID] = normalizedTool
normalized.Products[ref.productID] = normalizedProduct
if renamesByTool[ref] == nil {
renamesByTool[ref] = map[string]string{}
}
if existing, ok := renamesByTool[ref][migration.Legacy.Name]; ok && existing != migration.Canonical.Name {
return schemaContract{}, fmt.Errorf(
"approved flag migration %q maps Schema parameter %q to both %q and %q",
migration.Command,
migration.Legacy.Name,
existing,
migration.Canonical.Name,
)
}
renamesByTool[ref][migration.Legacy.Name] = migration.Canonical.Name
}
for ref, renames := range renamesByTool {
oldTool := baseline.Products[ref.productID].Tools[ref.toolID]
newTool := current.Products[ref.productID].Tools[ref.toolID]
if oldTool.Constraints == newTool.Constraints {
continue
}
oldConstraints, oldOK := canonicalizeMigratedConstraints(oldTool.Constraints, renames)
newConstraints, newOK := canonicalizeMigratedConstraints(newTool.Constraints, nil)
if !oldOK || !newOK || oldConstraints != newConstraints {
// Keep the historical constraints unchanged. The ordinary checker
// will report every non-rename constraint change.
continue
}
product := normalized.Products[ref.productID]
tool := product.Tools[ref.toolID]
tool.Constraints = newTool.Constraints
product.Tools[ref.toolID] = tool
normalized.Products[ref.productID] = product
}
return normalized, nil
}
func schemaToolsByPrimaryPath(contract schemaContract, primaryPath string) []schemaToolRef {
var matches []schemaToolRef
for productID, product := range contract.Products {
for toolID, tool := range product.Tools {
if tool.PrimaryCLIPath == primaryPath {
matches = append(matches, schemaToolRef{productID: productID, toolID: toolID})
}
}
}
sort.Slice(matches, func(i, j int) bool {
if matches[i].productID != matches[j].productID {
return matches[i].productID < matches[j].productID
}
return matches[i].toolID < matches[j].toolID
})
return matches
}
func validateRenamedSchemaParameter(
migration interfacesnapshot.FlagMigration,
oldParameter parameterSchema,
newParameter parameterSchema,
) error {
if oldParameter.Type != newParameter.Type ||
oldParameter.Property != newParameter.Property ||
oldParameter.InterfaceType != newParameter.InterfaceType ||
oldParameter.RequiredWhen != newParameter.RequiredWhen ||
oldParameter.Default != newParameter.Default ||
oldParameter.InterfaceDefault != newParameter.InterfaceDefault ||
oldParameter.Format != newParameter.Format ||
!equalStringSlices(oldParameter.Enum, newParameter.Enum) {
return fmt.Errorf(
"approved flag migration %q Schema parameter %q -> %q changed a non-migration field",
migration.Command,
migration.Legacy.Name,
migration.Canonical.Name,
)
}
if oldParameter.Required && !newParameter.Required {
return fmt.Errorf(
"approved flag migration %q Schema parameter %q -> %q became optional",
migration.Command,
migration.Legacy.Name,
migration.Canonical.Name,
)
}
if oldParameter.CLIRequired && !newParameter.CLIRequired {
return fmt.Errorf(
"approved flag migration %q Schema parameter %q -> %q stopped being cli_required",
migration.Command,
migration.Legacy.Name,
migration.Canonical.Name,
)
}
return nil
}
func equalStringSlices(left, right []string) bool {
if len(left) != len(right) {
return false
}
for index := range left {
if left[index] != right[index] {
return false
}
}
return true
}
func canonicalizeMigratedConstraints(raw string, renames map[string]string) (string, bool) {
groups, ok := parseMigrationConstraintsStrict(raw)
if !ok {
return "", false
}
normalized := map[string][][]string{}
for _, kind := range []string{"mutually_exclusive", "require_one_of", "require_together"} {
for _, group := range groups[kind] {
members := make([]string, 0, len(group))
for _, member := range group {
if replacement := renames[member]; replacement != "" {
member = replacement
}
members = append(members, member)
}
sort.Strings(members)
members = compactConstraintStrings(members)
normalized[kind] = append(normalized[kind], members)
}
sort.Slice(normalized[kind], func(i, j int) bool {
return strings.Join(normalized[kind][i], "\x00") < strings.Join(normalized[kind][j], "\x00")
})
normalized[kind] = compactConstraintGroups(normalized[kind])
if len(normalized[kind]) == 0 {
delete(normalized, kind)
}
}
if len(normalized) == 0 {
return "", true
}
encoded, err := json.Marshal(normalized)
return string(encoded), err == nil
}
func parseMigrationConstraintsStrict(raw string) (map[string][][]string, bool) {
trimmed := strings.TrimSpace(raw)
if trimmed == "" {
return map[string][][]string{}, true
}
decoder := json.NewDecoder(strings.NewReader(trimmed))
opening, err := decoder.Token()
if err != nil || opening != json.Delim('{') {
return nil, false
}
groups := map[string][][]string{}
for decoder.More() {
fieldToken, err := decoder.Token()
if err != nil {
return nil, false
}
// encoding/json guarantees that an object member name is a string token.
field := fieldToken.(string)
switch field {
case "mutually_exclusive", "require_one_of", "require_together":
default:
return nil, false
}
if _, duplicate := groups[field]; duplicate {
return nil, false
}
var encodedGroups json.RawMessage
if err := decoder.Decode(&encodedGroups); err != nil {
return nil, false
}
encodedGroups = bytes.TrimSpace(encodedGroups)
if len(encodedGroups) == 0 || encodedGroups[0] != '[' {
return nil, false
}
var fieldGroups [][]string
if err := json.Unmarshal(encodedGroups, &fieldGroups); err != nil {
return nil, false
}
for _, group := range fieldGroups {
if len(group) == 0 {
return nil, false
}
for _, member := range group {
if member == "" || member != strings.TrimSpace(member) {
return nil, false
}
}
}
groups[field] = fieldGroups
}
closing, err := decoder.Token()
if err != nil || closing != json.Delim('}') {
return nil, false
}
if err := decoder.Decode(&struct{}{}); err != io.EOF {
return nil, false
}
return groups, true
}
func compactConstraintStrings(values []string) []string {
if len(values) < 2 {
return values
}
out := values[:1]
for _, value := range values[1:] {
if value != out[len(out)-1] {
out = append(out, value)
}
}
return out
}
func compactConstraintGroups(groups [][]string) [][]string {
if len(groups) < 2 {
return groups
}
out := groups[:1]
for _, group := range groups[1:] {
if !equalStringSlices(group, out[len(out)-1]) {
out = append(out, group)
}
}
return out
}
func writeContract(w io.Writer, contract schemaContract) error {
contract.Version = schemaContractVersion
if contract.Products == nil {
+805
View File
@@ -9,8 +9,12 @@ import (
"errors"
"os"
"path/filepath"
"reflect"
"sort"
"strings"
"testing"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/interfacesnapshot"
)
const completeSchemaJSON = `{
@@ -931,3 +935,804 @@ func TestCrossPlatformCoverageSchemaCompatInterfaceRefRedirect(t *testing.T) {
}
})
}
func TestCrossPlatformCoverageSchemaFlagMigrationNormalizesExactRename(t *testing.T) {
baseline := schemaFlagMigrationContract(false)
current := schemaFlagMigrationContract(true)
normalized, err := normalizeSchemaFlagMigrations(baseline, current, schemaFlagMigrationAuthorizations())
if err != nil {
t.Fatal(err)
}
if failures := checkCompatibility(normalized, current); len(failures) != 0 {
t.Fatalf("exact flag migration should pass after normalization: %v", failures)
}
tool := normalized.Products["chat"].Tools["chat.edit_message"]
for _, legacy := range []string{"group", "id", "msg-id"} {
if _, exists := tool.Parameters[legacy]; exists {
t.Fatalf("normalized baseline retained legacy parameter %q", legacy)
}
}
if canonical := tool.Parameters["conversation-id"]; !canonical.Required || !canonical.CLIRequired {
t.Fatalf("canonical required transition was not normalized: %#v", canonical)
}
if tool.Constraints != current.Products["chat"].Tools["chat.edit_message"].Constraints {
t.Fatalf("constraints were not normalized: %s", tool.Constraints)
}
}
func TestCrossPlatformCoverageSchemaFlagMigrationRejectsSemanticDrift(t *testing.T) {
tests := []struct {
name string
want string
mutate func(*parameterSchema)
}{
{name: "required decline", want: "must be required and cli_required", mutate: func(parameter *parameterSchema) {
parameter.Required = false
}},
{name: "cli required decline", want: "must be required and cli_required", mutate: func(parameter *parameterSchema) {
parameter.CLIRequired = false
}},
{name: "type", want: "non-migration field", mutate: func(parameter *parameterSchema) { parameter.Type = `"integer"` }},
{name: "property", want: "non-migration field", mutate: func(parameter *parameterSchema) { parameter.Property = "other" }},
{name: "interface type", want: "non-migration field", mutate: func(parameter *parameterSchema) { parameter.InterfaceType = "integer" }},
{name: "required when", want: "non-migration field", mutate: func(parameter *parameterSchema) { parameter.RequiredWhen = "mode=other" }},
{name: "default", want: "non-migration field", mutate: func(parameter *parameterSchema) { parameter.Default = `"other"` }},
{name: "interface default", want: "non-migration field", mutate: func(parameter *parameterSchema) { parameter.InterfaceDefault = `"other"` }},
{name: "format", want: "non-migration field", mutate: func(parameter *parameterSchema) { parameter.Format = "uri" }},
{name: "enum", want: "non-migration field", mutate: func(parameter *parameterSchema) { parameter.Enum = []string{"a"} }},
{name: "enum same length", want: "non-migration field", mutate: func(parameter *parameterSchema) { parameter.Enum = []string{"a", "c"} }},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
current := schemaFlagMigrationContract(true)
product := current.Products["chat"]
tool := product.Tools["chat.edit_message"]
canonical := tool.Parameters["message-id"]
test.mutate(&canonical)
tool.Parameters["message-id"] = canonical
product.Tools["chat.edit_message"] = tool
current.Products["chat"] = product
_, err := normalizeSchemaFlagMigrations(
schemaFlagMigrationContract(false),
current,
schemaFlagMigrationAuthorizations(),
)
if err == nil || !strings.Contains(err.Error(), test.want) {
t.Fatalf("normalizeSchemaFlagMigrations() error = %v, want %q", err, test.want)
}
})
}
}
func TestCrossPlatformCoverageSchemaFlagMigrationAdapterBranches(t *testing.T) {
baseline := schemaFlagMigrationContract(false)
current := schemaFlagMigrationContract(true)
noConstraintsBaseline := cloneContract(baseline)
product := noConstraintsBaseline.Products["chat"]
tool := product.Tools["chat.edit_message"]
tool.Constraints = ""
product.Tools["chat.edit_message"] = tool
noConstraintsBaseline.Products["chat"] = product
noConstraintsCurrent := cloneContract(current)
product = noConstraintsCurrent.Products["chat"]
tool = product.Tools["chat.edit_message"]
tool.Constraints = ""
product.Tools["chat.edit_message"] = tool
noConstraintsCurrent.Products["chat"] = product
if _, err := normalizeSchemaFlagMigrations(noConstraintsBaseline, noConstraintsCurrent, schemaFlagMigrationAuthorizations()); err != nil {
t.Fatalf("unchanged empty constraints: %v", err)
}
cliOnly := schemaFlagMigrationAuthorizations()[:1]
cliOnly[0].Command = "dws chat cli-only"
normalized, err := normalizeSchemaFlagMigrations(baseline, current, cliOnly)
if err != nil || !reflect.DeepEqual(normalized, baseline) {
t.Fatalf("CLI-only migration = %#v, %v", normalized, err)
}
duplicate := cloneContract(baseline)
product = duplicate.Products["chat"]
product.Tools["chat.another"] = product.Tools["chat.edit_message"]
duplicate.Products["chat"] = product
duplicate.Products["chat2"] = productSchema{Tools: map[string]toolSchema{
"chat.third": product.Tools["chat.edit_message"],
}}
if matches := schemaToolsByPrimaryPath(duplicate, "chat message edit"); len(matches) != 3 || matches[0].productID != "chat" || matches[0].toolID != "chat.another" {
t.Fatalf("sorted duplicate Schema matches = %#v", matches)
}
if _, err := normalizeSchemaFlagMigrations(duplicate, current, schemaFlagMigrationAuthorizations()[:1]); err == nil || !strings.Contains(err.Error(), "matches 3") {
t.Fatalf("duplicate Schema path error = %v", err)
}
missingCurrent := cloneContract(current)
delete(missingCurrent.Products, "chat")
normalized, err = normalizeSchemaFlagMigrations(baseline, missingCurrent, schemaFlagMigrationAuthorizations())
if err != nil {
t.Fatal(err)
}
if failures := checkCompatibility(normalized, missingCurrent); len(failures) == 0 {
t.Fatal("missing candidate product was normalized away")
}
canonicalOnly := schemaFlagMigrationAuthorizations()[0]
canonicalOnly.Legacy.Name = "legacy-not-published-in-schema"
driftedCanonical := cloneContract(current)
product = driftedCanonical.Products["chat"]
tool = product.Tools["chat.edit_message"]
canonical := tool.Parameters["conversation-id"]
canonical.Property = "different"
tool.Parameters["conversation-id"] = canonical
product.Tools["chat.edit_message"] = tool
driftedCanonical.Products["chat"] = product
normalized, err = normalizeSchemaFlagMigrations(baseline, driftedCanonical, []interfacesnapshot.FlagMigration{canonicalOnly})
if err != nil {
t.Fatal(err)
}
if failures := strings.Join(checkCompatibility(normalized, driftedCanonical), "\n"); !strings.Contains(failures, "changed property") {
t.Fatalf("canonical-only Schema drift was hidden: %s", failures)
}
canonicalOptional := schemaFlagMigrationContract(true)
product = canonicalOptional.Products["chat"]
tool = product.Tools["chat.edit_message"]
canonical = tool.Parameters["conversation-id"]
canonical.Required = false
canonical.CLIRequired = false
tool.Parameters["conversation-id"] = canonical
product.Tools["chat.edit_message"] = tool
canonicalOptional.Products["chat"] = product
normalized, err = normalizeSchemaFlagMigrations(
canonicalOptional,
schemaFlagMigrationContract(true),
[]interfacesnapshot.FlagMigration{canonicalOnly},
)
if err != nil {
t.Fatal(err)
}
if failures := strings.Join(checkCompatibility(normalized, schemaFlagMigrationContract(true)), "\n"); !strings.Contains(failures, "newly required") || !strings.Contains(failures, "newly cli_required") {
t.Fatalf("canonical-only required promotion was hidden: %s", failures)
}
canonicalConflictBaseline := schemaFlagMigrationContract(false)
product = canonicalConflictBaseline.Products["chat"]
tool = product.Tools["chat.edit_message"]
canonical = tool.Parameters["conversation-id"]
canonical.Property = "historicalCanonicalProperty"
tool.Parameters["conversation-id"] = canonical
product.Tools["chat.edit_message"] = tool
canonicalConflictBaseline.Products["chat"] = product
if _, err := normalizeSchemaFlagMigrations(
canonicalConflictBaseline,
schemaFlagMigrationContract(true),
schemaFlagMigrationAuthorizations()[:1],
); err == nil || !strings.Contains(err.Error(), "non-migration field") {
t.Fatalf("historical canonical drift error = %v", err)
}
conflicting := schemaFlagMigrationAuthorizations()[0]
conflicting.Canonical.Name = "other-conversation-id"
conflicting.Legacy.After.AliasOf = conflicting.Canonical.Name
product = current.Products["chat"]
tool = product.Tools["chat.edit_message"]
tool.Parameters[conflicting.Canonical.Name] = tool.Parameters["conversation-id"]
product.Tools["chat.edit_message"] = tool
current.Products["chat"] = product
if _, err := normalizeSchemaFlagMigrations(baseline, current, []interfacesnapshot.FlagMigration{
schemaFlagMigrationAuthorizations()[0],
conflicting,
}); err == nil || !strings.Contains(err.Error(), "maps Schema parameter") {
t.Fatalf("conflicting Schema mapping error = %v", err)
}
old := parameterSchema{Required: true, CLIRequired: true}
if err := validateRenamedSchemaParameter(schemaFlagMigrationAuthorizations()[0], old, parameterSchema{CLIRequired: true}); err == nil || !strings.Contains(err.Error(), "became optional") {
t.Fatalf("direct required decline error = %v", err)
}
if err := validateRenamedSchemaParameter(schemaFlagMigrationAuthorizations()[0], old, parameterSchema{Required: true}); err == nil || !strings.Contains(err.Error(), "stopped being cli_required") {
t.Fatalf("direct cli_required decline error = %v", err)
}
}
func TestCrossPlatformCoverageSchemaFlagMigrationRejectsPartialAndUnrelatedChanges(t *testing.T) {
baseline := schemaFlagMigrationContract(false)
migrations := schemaFlagMigrationAuthorizations()
current := schemaFlagMigrationContract(true)
product := current.Products["chat"]
tool := product.Tools["chat.edit_message"]
delete(tool.Parameters, "message-id")
product.Tools["chat.edit_message"] = tool
current.Products["chat"] = product
if _, err := normalizeSchemaFlagMigrations(baseline, current, migrations); err == nil || !strings.Contains(err.Error(), "does not publish canonical") {
t.Fatalf("missing canonical error = %v", err)
}
current = schemaFlagMigrationContract(true)
product = current.Products["chat"]
tool = product.Tools["chat.edit_message"]
tool.Parameters["msg-id"] = schemaFlagMigrationContract(false).Products["chat"].Tools["chat.edit_message"].Parameters["msg-id"]
product.Tools["chat.edit_message"] = tool
current.Products["chat"] = product
if _, err := normalizeSchemaFlagMigrations(baseline, current, migrations); err == nil || !strings.Contains(err.Error(), "still publishes legacy") {
t.Fatalf("published legacy error = %v", err)
}
current = schemaFlagMigrationContract(true)
product = current.Products["chat"]
tool = product.Tools["chat.edit_message"]
delete(tool.Parameters, "unrelated")
product.Tools["chat.edit_message"] = tool
current.Products["chat"] = product
normalized, err := normalizeSchemaFlagMigrations(baseline, current, migrations)
if err != nil {
t.Fatal(err)
}
if failures := strings.Join(checkCompatibility(normalized, current), "\n"); !strings.Contains(failures, `lost parameter "unrelated"`) {
t.Fatalf("unrelated parameter removal was hidden: %s", failures)
}
current = schemaFlagMigrationContract(true)
product = current.Products["chat"]
tool = product.Tools["chat.edit_message"]
tool.Constraints = `{"require_one_of":[["conversation-id"]],"require_together":[["conversation-id","unrelated"]]}`
product.Tools["chat.edit_message"] = tool
current.Products["chat"] = product
normalized, err = normalizeSchemaFlagMigrations(baseline, current, migrations)
if err != nil {
t.Fatal(err)
}
if failures := strings.Join(checkCompatibility(normalized, current), "\n"); !strings.Contains(failures, "changed constraints") {
t.Fatalf("unrelated constraint change was hidden: %s", failures)
}
// A CLI ledger entry alone cannot authorize a Schema constraint rewrite
// when the historical Schema published neither the legacy nor canonical
// parameter involved in that entry.
noSchemaEvidence := schemaFlagMigrationContract(false)
product = noSchemaEvidence.Products["chat"]
tool = product.Tools["chat.edit_message"]
for _, name := range []string{"conversation-id", "group", "id", "msg-id"} {
delete(tool.Parameters, name)
}
product.Tools["chat.edit_message"] = tool
noSchemaEvidence.Products["chat"] = product
normalized, err = normalizeSchemaFlagMigrations(noSchemaEvidence, schemaFlagMigrationContract(true), migrations)
if err != nil {
t.Fatal(err)
}
if failures := strings.Join(checkCompatibility(normalized, schemaFlagMigrationContract(true)), "\n"); !strings.Contains(failures, "changed constraints") {
t.Fatalf("constraint rewrite without Schema parameter evidence was hidden: %s", failures)
}
// A baseline that already contains only the canonical parameter may receive
// a required promotion, but that is not evidence that a stray legacy name in
// constraints belongs to the migration.
canonicalOnly := schemaFlagMigrationContract(true)
product = canonicalOnly.Products["chat"]
tool = product.Tools["chat.edit_message"]
tool.Constraints = `{"require_one_of":[["group"]]}`
product.Tools["chat.edit_message"] = tool
canonicalOnly.Products["chat"] = product
normalized, err = normalizeSchemaFlagMigrations(canonicalOnly, schemaFlagMigrationContract(true), migrations)
if err != nil {
t.Fatal(err)
}
if failures := strings.Join(checkCompatibility(normalized, schemaFlagMigrationContract(true)), "\n"); !strings.Contains(failures, "changed constraints") {
t.Fatalf("canonical-only baseline authorized a legacy constraint rewrite: %s", failures)
}
}
func TestCrossPlatformCoverageSchemaFlagMigrationConstraintsFailClosed(t *testing.T) {
for _, constraints := range []string{
`{`,
`{"`,
`{"require_one_of":`,
`{"unknown_kind":[["conversation-id"]]}`,
`{"require_one_of":"conversation-id"}`,
`{"require_one_of":["conversation-id"]}`,
`{"require_one_of":null}`,
`{"require_one_of":[[]]}`,
`{"require_one_of":[[" conversation-id"]]}`,
`{"require_one_of":[["conversation-id"]],"require_one_of":[["conversation-id"]]}`,
`{"require_one_of":[]`,
`{} {}`,
`[]`,
} {
current := schemaFlagMigrationContract(true)
product := current.Products["chat"]
tool := product.Tools["chat.edit_message"]
tool.Constraints = constraints
product.Tools["chat.edit_message"] = tool
current.Products["chat"] = product
normalized, err := normalizeSchemaFlagMigrations(
schemaFlagMigrationContract(false),
current,
schemaFlagMigrationAuthorizations(),
)
if err != nil {
t.Fatal(err)
}
if failures := strings.Join(checkCompatibility(normalized, current), "\n"); !strings.Contains(failures, "changed constraints") {
t.Fatalf("malformed constraints %s were authorized: %s", constraints, failures)
}
}
if encoded, ok := canonicalizeMigratedConstraints("", nil); !ok || encoded != "" {
t.Fatalf("empty migrated constraints = %q, %v", encoded, ok)
}
encoded, ok := canonicalizeMigratedConstraints(
`{"require_one_of":[["b"],["a"],["a"],["c","c"]]}`,
nil,
)
if !ok || encoded != `{"require_one_of":[["a"],["b"],["c"]]}` {
t.Fatalf("canonical migrated constraint groups = %q, %v", encoded, ok)
}
}
func TestCrossPlatformCoverageSchemaMigrationCLIRequiresCompleteCheckInputs(t *testing.T) {
allInputs := []string{
"--approved-flag-migrations", "approved.json",
"--candidate-flag-migrations", "candidate.json",
"--migration-current-snapshot", "current.json",
"--migration-base-snapshot", "base.json",
"--migration-stable-snapshot", "stable.json",
}
var stdout, stderr bytes.Buffer
if code := run([]string{"--check", "baseline.json", "--current", "schema.json", "--approved-flag-migrations", "approved.json"}, &stdout, &stderr); code != 2 || !strings.Contains(stderr.String(), "all five") {
t.Fatalf("partial migration inputs code=%d stderr=%q", code, stderr.String())
}
for _, mode := range []string{"--normalize", "--merge"} {
stderr.Reset()
args := append([]string{mode, "schema.json"}, allInputs...)
if code := run(args, &stdout, &stderr); code != 2 || !strings.Contains(stderr.String(), "only valid with --check") {
t.Fatalf("%s migration inputs code=%d stderr=%q", mode, code, stderr.String())
}
}
}
func TestCrossPlatformCoverageSchemaMigrationLifecycleAndRun(t *testing.T) {
directory := t.TempDir()
baselinePath := filepath.Join(directory, "schema-baseline.json")
currentSchemaPath := filepath.Join(directory, "schema-current.json")
writeSchemaContractFile(t, baselinePath, schemaFlagMigrationContract(false))
writeRawSchemaContractFile(t, currentSchemaPath, schemaFlagMigrationContract(true))
approvedPath := filepath.Join(directory, "approved.json")
candidatePath := filepath.Join(directory, "candidate.json")
currentSnapshotPath := filepath.Join(directory, "interface-current.json")
baseSnapshotPath := filepath.Join(directory, "interface-base.json")
stableSnapshotPath := filepath.Join(directory, "interface-stable.json")
writeFlagMigrationManifestFile(t, approvedPath, schemaFlagMigrationManifest(interfacesnapshot.FlagMigrationPending))
writeFlagMigrationManifestFile(t, candidatePath, schemaFlagMigrationManifest(interfacesnapshot.FlagMigrationConsumed))
writeInterfaceSnapshotFile(t, currentSnapshotPath, schemaFlagMigrationSnapshot(true))
writeInterfaceSnapshotFile(t, baseSnapshotPath, schemaFlagMigrationSnapshot(false))
writeInterfaceSnapshotFile(t, stableSnapshotPath, schemaFlagMigrationSnapshot(false))
args := []string{
"--check", baselinePath,
"--current", currentSchemaPath,
"--approved-flag-migrations", approvedPath,
"--candidate-flag-migrations", candidatePath,
"--migration-current-snapshot", currentSnapshotPath,
"--migration-base-snapshot", baseSnapshotPath,
"--migration-stable-snapshot", stableSnapshotPath,
}
var stdout, stderr bytes.Buffer
if code := run(args, &stdout, &stderr); code != 0 {
t.Fatalf("governed Schema check code=%d stderr=%s", code, stderr.String())
}
if !strings.Contains(stdout.String(), "compatibility check: ok") {
t.Fatalf("governed Schema check output = %q", stdout.String())
}
badArgs := append([]string(nil), args...)
badArgs[5] = filepath.Join(directory, "missing-approved.json")
stderr.Reset()
if code := run(badArgs, &stdout, &stderr); code != 2 || !strings.Contains(stderr.String(), "authorize Schema flag migrations") {
t.Fatalf("authorization error code=%d stderr=%q", code, stderr.String())
}
legacyCurrent := schemaFlagMigrationContract(true)
product := legacyCurrent.Products["chat"]
tool := product.Tools["chat.edit_message"]
tool.Parameters["msg-id"] = schemaFlagMigrationContract(false).Products["chat"].Tools["chat.edit_message"].Parameters["msg-id"]
product.Tools["chat.edit_message"] = tool
legacyCurrent.Products["chat"] = product
writeRawSchemaContractFile(t, currentSchemaPath, legacyCurrent)
stderr.Reset()
if code := run(args, &stdout, &stderr); code != 2 || !strings.Contains(stderr.String(), "normalize approved Schema flag migrations") {
t.Fatalf("normalization error code=%d stderr=%q", code, stderr.String())
}
// Candidate-owned pending records cannot approve their own after-state.
writeFlagMigrationManifestFile(t, approvedPath, interfacesnapshot.FlagMigrationManifest{
Version: interfacesnapshot.FlagMigrationManifestVersion,
Migrations: []interfacesnapshot.FlagMigration{},
})
writeFlagMigrationManifestFile(t, candidatePath, schemaFlagMigrationManifest(interfacesnapshot.FlagMigrationPending))
if _, err := authorizeSchemaFlagMigrations(approvedPath, candidatePath, currentSnapshotPath, baseSnapshotPath, stableSnapshotPath); err == nil || !strings.Contains(err.Error(), "cannot authorize its own interface change") {
t.Fatalf("self-approval error = %v", err)
}
// An incomplete after-state fails lifecycle authorization before Schema is normalized.
writeFlagMigrationManifestFile(t, approvedPath, schemaFlagMigrationManifest(interfacesnapshot.FlagMigrationPending))
writeFlagMigrationManifestFile(t, candidatePath, schemaFlagMigrationManifest(interfacesnapshot.FlagMigrationConsumed))
partial := schemaFlagMigrationSnapshot(true)
partial.Commands[0].LocalFlags = partial.Commands[0].LocalFlags[:len(partial.Commands[0].LocalFlags)-1]
writeInterfaceSnapshotFile(t, currentSnapshotPath, partial)
if _, err := authorizeSchemaFlagMigrations(approvedPath, candidatePath, currentSnapshotPath, baseSnapshotPath, stableSnapshotPath); err == nil || !strings.Contains(err.Error(), "partially applied") {
t.Fatalf("partial migration error = %v", err)
}
}
func TestCrossPlatformCoverageSchemaMigrationFileErrors(t *testing.T) {
directory := t.TempDir()
approvedPath := filepath.Join(directory, "approved.json")
candidatePath := filepath.Join(directory, "candidate.json")
currentPath := filepath.Join(directory, "current.json")
basePath := filepath.Join(directory, "base.json")
stablePath := filepath.Join(directory, "stable.json")
writeFlagMigrationManifestFile(t, approvedPath, schemaFlagMigrationManifest(interfacesnapshot.FlagMigrationPending))
writeFlagMigrationManifestFile(t, candidatePath, schemaFlagMigrationManifest(interfacesnapshot.FlagMigrationConsumed))
writeInterfaceSnapshotFile(t, currentPath, schemaFlagMigrationSnapshot(true))
writeInterfaceSnapshotFile(t, basePath, schemaFlagMigrationSnapshot(false))
writeInterfaceSnapshotFile(t, stablePath, schemaFlagMigrationSnapshot(false))
paths := []string{approvedPath, candidatePath, currentPath, basePath, stablePath}
wants := []string{
"read approved flag migrations",
"read candidate flag migrations",
"read migration current snapshot",
"read migration base snapshot",
"read migration stable snapshot",
}
for index, want := range wants {
t.Run(want, func(t *testing.T) {
invalid := append([]string(nil), paths...)
invalid[index] = filepath.Join(directory, "missing.json")
_, err := authorizeSchemaFlagMigrations(invalid[0], invalid[1], invalid[2], invalid[3], invalid[4])
if err == nil || !strings.Contains(err.Error(), want) {
t.Fatalf("authorizeSchemaFlagMigrations() error = %v, want %q", err, want)
}
})
}
}
func TestCrossPlatformCoverageSchemaConsumedReceiptIsNoOpForAfterBaseline(t *testing.T) {
directory := t.TempDir()
approvedPath := filepath.Join(directory, "approved.json")
candidatePath := filepath.Join(directory, "candidate.json")
currentSnapshotPath := filepath.Join(directory, "current.json")
baseSnapshotPath := filepath.Join(directory, "base.json")
stableSnapshotPath := filepath.Join(directory, "stable.json")
writeFlagMigrationManifestFile(t, approvedPath, schemaFlagMigrationManifest(interfacesnapshot.FlagMigrationConsumed))
writeFlagMigrationManifestFile(t, candidatePath, schemaFlagMigrationManifest(interfacesnapshot.FlagMigrationConsumed))
writeInterfaceSnapshotFile(t, currentSnapshotPath, schemaFlagMigrationSnapshot(true))
writeInterfaceSnapshotFile(t, baseSnapshotPath, schemaFlagMigrationSnapshot(true))
writeInterfaceSnapshotFile(t, stableSnapshotPath, schemaFlagMigrationSnapshot(false))
migrations, err := authorizeSchemaFlagMigrations(approvedPath, candidatePath, currentSnapshotPath, baseSnapshotPath, stableSnapshotPath)
if err != nil {
t.Fatal(err)
}
baseline := schemaFlagMigrationContract(true)
normalized, err := normalizeSchemaFlagMigrations(baseline, schemaFlagMigrationContract(true), migrations)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(normalized, baseline) {
t.Fatalf("consumed receipt changed an already-after Schema baseline: %#v", normalized)
}
if failures := checkCompatibility(normalized, schemaFlagMigrationContract(true)); len(failures) != 0 {
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.
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)
}
emptyManifest := interfacesnapshot.FlagMigrationManifest{
Version: interfacesnapshot.FlagMigrationManifestVersion,
Migrations: []interfacesnapshot.FlagMigration{},
}
writeFlagMigrationManifestFile(t, candidatePath, emptyManifest)
migrations, err = authorizeSchemaFlagMigrations(approvedPath, candidatePath, currentSnapshotPath, baseSnapshotPath, stableSnapshotPath)
if err != nil || len(migrations) != 0 {
t.Fatalf("cleaned consumed receipt authorizations = %#v, %v", migrations, err)
}
baselinePath := filepath.Join(directory, "schema-baseline.json")
currentSchemaPath := filepath.Join(directory, "schema-current.json")
writeSchemaContractFile(t, baselinePath, baseline)
writeRawSchemaContractFile(t, currentSchemaPath, schemaFlagMigrationContract(true))
args := []string{
"--check", baselinePath,
"--current", currentSchemaPath,
"--approved-flag-migrations", approvedPath,
"--candidate-flag-migrations", candidatePath,
"--migration-current-snapshot", currentSnapshotPath,
"--migration-base-snapshot", baseSnapshotPath,
"--migration-stable-snapshot", stableSnapshotPath,
}
var stdout, stderr bytes.Buffer
if code := run(args, &stdout, &stderr); code != 0 {
t.Fatalf("cleaned consumed receipt check code=%d stderr=%q", code, stderr.String())
}
}
func schemaFlagMigrationContract(after bool) schemaContract {
legacyMessage := parameterSchema{
Type: `"string"`,
Property: "openMessageId",
InterfaceType: "string",
Required: true,
CLIRequired: true,
Default: `"default-message"`,
InterfaceDefault: `"default-message"`,
Format: "id",
Enum: []string{"a", "b"},
}
conversation := parameterSchema{
Type: `"string"`,
Property: "openConversationId",
InterfaceType: "string",
}
parameters := map[string]parameterSchema{
"conversation-id": conversation,
"unrelated": {Type: `"string"`, Property: "unrelated"},
}
constraints := `{"require_one_of":[["conversation-id","group","id"]]}`
if after {
conversation.Required = true
conversation.CLIRequired = true
parameters["conversation-id"] = conversation
parameters["message-id"] = legacyMessage
constraints = `{"require_one_of":[["conversation-id"]]}`
} else {
parameters["group"] = conversation
parameters["id"] = conversation
parameters["msg-id"] = legacyMessage
}
return schemaContract{Version: schemaContractVersion, Products: map[string]productSchema{
"chat": {Tools: map[string]toolSchema{
"chat.edit_message": {
PrimaryCLIPath: "chat message edit",
InterfaceMode: "mcp",
Availability: "available",
Parameters: parameters,
Constraints: constraints,
Effect: "write",
Risk: "medium",
Confirmation: "not_required",
Idempotency: "unknown",
},
}},
}}
}
func schemaFlagMigrationAuthorizations() []interfacesnapshot.FlagMigration {
conversationBefore := interfacesnapshot.FlagMigrationState{
Present: true,
Type: "string",
Scope: "local",
}
conversationAfter := conversationBefore
conversationAfter.Required = true
messageBefore := interfacesnapshot.FlagMigrationState{
Present: true,
Type: "string",
Required: true,
Scope: "local",
}
messageAfter := messageBefore
return []interfacesnapshot.FlagMigration{
{
Command: "dws chat message edit",
Legacy: interfacesnapshot.FlagMigrationSide{
Name: "group",
Before: conversationBefore,
After: interfacesnapshot.FlagMigrationState{
Present: true, Type: "string", Hidden: true, Scope: "local", AliasOf: "conversation-id",
},
},
Canonical: interfacesnapshot.FlagMigrationSide{
Name: "conversation-id",
Before: conversationBefore,
After: conversationAfter,
},
},
{
Command: "dws chat message edit",
Legacy: interfacesnapshot.FlagMigrationSide{
Name: "id",
Before: conversationBefore,
After: interfacesnapshot.FlagMigrationState{
Present: true, Type: "string", Hidden: true, Scope: "local", AliasOf: "conversation-id",
},
},
Canonical: interfacesnapshot.FlagMigrationSide{
Name: "conversation-id",
Before: conversationBefore,
After: conversationAfter,
},
},
{
Command: "dws chat message edit",
Legacy: interfacesnapshot.FlagMigrationSide{
Name: "msg-id",
Before: messageBefore,
After: interfacesnapshot.FlagMigrationState{
Present: true, Type: "string", Hidden: true, Scope: "local", AliasOf: "message-id",
},
},
Canonical: interfacesnapshot.FlagMigrationSide{
Name: "message-id",
Before: interfacesnapshot.FlagMigrationState{},
After: messageAfter,
},
},
}
}
func schemaFlagMigrationManifest(state string) interfacesnapshot.FlagMigrationManifest {
migrations := schemaFlagMigrationAuthorizations()
for index := range migrations {
migrations[index].State = state
migrations[index].Reason = "canonicalize historical public flag names"
}
return interfacesnapshot.FlagMigrationManifest{
Version: interfacesnapshot.FlagMigrationManifestVersion,
Migrations: migrations,
}
}
func schemaFlagMigrationSnapshot(after bool) interfacesnapshot.Snapshot {
states := map[string]interfacesnapshot.FlagMigrationState{}
for _, migration := range schemaFlagMigrationAuthorizations() {
if after {
states[migration.Legacy.Name] = migration.Legacy.After
states[migration.Canonical.Name] = migration.Canonical.After
} else {
states[migration.Legacy.Name] = migration.Legacy.Before
states[migration.Canonical.Name] = migration.Canonical.Before
}
}
names := make([]string, 0, len(states))
for name, state := range states {
if state.Present {
names = append(names, name)
}
}
sort.Strings(names)
flags := make([]interfacesnapshot.Flag, 0, len(names))
for _, name := range names {
state := states[name]
flags = append(flags, interfacesnapshot.Flag{
Name: name,
Type: state.Type,
Required: state.Required,
Hidden: state.Hidden,
Shorthand: state.Shorthand,
NoOpt: state.NoOpt,
AliasOf: state.AliasOf,
})
}
return interfacesnapshot.Snapshot{
SchemaVersion: interfacesnapshot.SchemaVersion,
Rules: interfacesnapshot.Rules{
ExcludedCommandSubtrees: []string{},
ExcludedFlags: []string{},
},
Commands: []interfacesnapshot.Command{{
Path: "dws chat message edit",
Runnable: true,
Aliases: []string{},
LocalFlags: flags,
}},
}
}
func writeFlagMigrationManifestFile(t *testing.T, path string, manifest interfacesnapshot.FlagMigrationManifest) {
t.Helper()
data, err := json.MarshalIndent(manifest, "", " ")
if err != nil {
t.Fatal(err)
}
if err := os.WriteFile(path, append(data, '\n'), 0o600); err != nil {
t.Fatal(err)
}
}
func writeInterfaceSnapshotFile(t *testing.T, path string, snapshot interfacesnapshot.Snapshot) {
t.Helper()
var encoded bytes.Buffer
if err := interfacesnapshot.Write(&encoded, snapshot); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(path, encoded.Bytes(), 0o600); err != nil {
t.Fatal(err)
}
}
func writeSchemaContractFile(t *testing.T, path string, contract schemaContract) {
t.Helper()
var encoded bytes.Buffer
if err := writeContract(&encoded, contract); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(path, encoded.Bytes(), 0o600); err != nil {
t.Fatal(err)
}
}
func writeRawSchemaContractFile(t *testing.T, path string, contract schemaContract) {
t.Helper()
products := make([]map[string]any, 0, len(contract.Products))
for productID, product := range contract.Products {
tools := make([]map[string]any, 0, len(product.Tools))
for toolID, tool := range product.Tools {
parameters := map[string]any{}
for name, parameter := range tool.Parameters {
raw := map[string]any{
"type": json.RawMessage(parameter.Type),
"required": parameter.Required,
"cli_required": parameter.CLIRequired,
"property": parameter.Property,
"interface_type": parameter.InterfaceType,
"required_when": parameter.RequiredWhen,
"format": parameter.Format,
"enum": parameter.Enum,
"field_provenance": map[string]any{},
}
if parameter.Default != "" {
raw["default"] = json.RawMessage(parameter.Default)
}
if parameter.InterfaceDefault != "" {
raw["interface_default"] = json.RawMessage(parameter.InterfaceDefault)
}
parameters[name] = raw
}
rawTool := map[string]any{
"canonical_path": toolID,
"primary_cli_path": tool.PrimaryCLIPath,
"interface_mode": tool.InterfaceMode,
"availability": tool.Availability,
"parameters": parameters,
"effect": tool.Effect,
"risk": tool.Risk,
"confirmation": tool.Confirmation,
"idempotency": tool.Idempotency,
"field_provenance": map[string]any{},
}
if tool.InterfaceRef != "" {
rawTool["interface_ref"] = json.RawMessage(tool.InterfaceRef)
}
if tool.Constraints != "" {
rawTool["constraints"] = json.RawMessage(tool.Constraints)
}
if len(tool.Positionals) > 0 {
rawTool["positionals"] = tool.Positionals
}
if tool.DryRun != "" {
rawTool["dry_run"] = json.RawMessage(tool.DryRun)
}
tools = append(tools, rawTool)
}
products = append(products, map[string]any{"id": productID, "tools": tools})
}
payload := map[string]any{"kind": "schema", "products": products}
data, err := json.Marshal(payload)
if err != nil {
t.Fatal(err)
}
if err := os.WriteFile(path, data, 0o600); err != nil {
t.Fatal(err)
}
}
@@ -0,0 +1,568 @@
// Copyright 2026 Alibaba Group
// Licensed under the Apache License, Version 2.0 (the "License");
package schemacompat
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"testing"
"time"
)
const governedSchemaCheckerSource = `package main
import (
"flag"
"fmt"
"os"
"strings"
)
func main() {
normalize := flag.String("normalize", "", "raw schema")
check := flag.String("check", "", "base schema")
current := flag.String("current", "", "candidate schema")
approved := flag.String("approved-flag-migrations", "", "base ledger")
candidate := flag.String("candidate-flag-migrations", "", "candidate ledger")
currentSnapshot := flag.String("migration-current-snapshot", "", "candidate interface snapshot")
baseSnapshot := flag.String("migration-base-snapshot", "", "base interface snapshot")
stableSnapshot := flag.String("migration-stable-snapshot", "", "stable interface snapshot")
flag.Parse()
if *normalize != "" {
data, err := os.ReadFile(*normalize)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(2)
}
fmt.Print(string(data))
return
}
if *check == "" || *current == "" {
fmt.Fprintln(os.Stderr, "missing check inputs")
os.Exit(2)
}
currentData, err := os.ReadFile(*current)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(2)
}
if !strings.Contains(string(currentData), "COMMITTED_CANDIDATE") {
fmt.Fprintf(os.Stderr, "candidate schema did not come from detached commit: %s\n", currentData)
os.Exit(2)
}
paths := []string{*approved, *candidate, *currentSnapshot, *baseSnapshot, *stableSnapshot}
for _, path := range paths {
if path == "" {
fmt.Fprintln(os.Stderr, "governed checker did not receive all migration inputs")
os.Exit(2)
}
}
for _, fixture := range []struct {
path string
want string
}{
{*approved, "{\"version\":1,\"migrations\":[{\"authority\":\"BASE\"}]}\n"},
{*candidate, "{\"version\":1,\"migrations\":[{\"candidate\":\"COMMITTED\"}]}\n"},
{*currentSnapshot, "COMMITTED_CANDIDATE"},
{*baseSnapshot, "BASE_AUTHORITY"},
{*stableSnapshot, "STABLE_RELEASE"},
} {
data, err := os.ReadFile(fixture.path)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(2)
}
if !strings.Contains(string(data), fixture.want) {
fmt.Fprintf(os.Stderr, "migration input %s does not contain %q: %s\n", fixture.path, fixture.want, data)
os.Exit(2)
}
}
for _, path := range []string{*currentSnapshot, *baseSnapshot, *stableSnapshot} {
data, err := os.ReadFile(path)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(2)
}
if !strings.Contains(string(data), "INTERFACE_HELPER=BASE") {
fmt.Fprintf(os.Stderr, "interface snapshot did not use base internal helper: %s\n", data)
os.Exit(2)
}
}
fmt.Fprintln(os.Stderr, "BASE_SCHEMA_CHECKER_ENFORCED")
os.Exit(23)
}
`
const bootstrapSchemaCheckerSource = `package main
import (
"flag"
"fmt"
"os"
"strings"
)
func main() {
normalize := flag.String("normalize", "", "raw schema")
check := flag.String("check", "", "base schema")
current := flag.String("current", "", "candidate schema")
flag.Parse()
if *normalize != "" {
data, err := os.ReadFile(*normalize)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(2)
}
fmt.Print(string(data))
return
}
if *check == "" || *current == "" {
fmt.Fprintln(os.Stderr, "missing check inputs")
os.Exit(2)
}
data, err := os.ReadFile(*current)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(2)
}
if !strings.Contains(string(data), "COMMITTED_CANDIDATE") {
fmt.Fprintf(os.Stderr, "candidate schema did not come from detached commit: %s\n", data)
os.Exit(2)
}
fmt.Fprintln(os.Stderr, "OLD_BASE_SCHEMA_CHECKER_ENFORCED")
os.Exit(23)
}
`
const authorityInterfaceHelperSource = `package main
import (
"flag"
"fmt"
"os"
"example.com/dws-schema-compat-authority-test/internal/interfacesnapshot"
)
func main() {
if len(os.Args) < 2 || os.Args[1] != "generate" {
fmt.Fprintln(os.Stderr, "expected generate")
os.Exit(2)
}
flags := flag.NewFlagSet("generate", flag.ExitOnError)
output := flags.String("output", "", "snapshot output")
_ = flags.Parse(os.Args[2:])
marker, err := os.ReadFile("SNAPSHOT_MARKER")
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(2)
}
marker = append(marker, []byte("INTERFACE_HELPER="+interfacesnapshot.AuthorityMarker()+"\n")...)
if err := os.WriteFile(*output, marker, 0o600); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(2)
}
fmt.Printf("BASE_INTERFACE_HELPER_GENERATE=%s\n", interfacesnapshot.AuthorityMarker())
}
`
const schemaGovernedSnapshotSource = `package interfacesnapshot
import "example.com/dws-schema-compat-authority-test/internal/corecmd/runtimeannotate"
var (
_ = runtimeannotate.AnnotationFlagAliasOf
_ = runtimeannotate.AnnotationFlagAliasOrigin
_ = runtimeannotate.FlagAliasOriginCorecmdV1
)
func AuthorityMarker() string { return "BASE" }
`
const schemaBaseSnapshotSource = `package interfacesnapshot
func AuthorityMarker() string { return "BASE" }
`
const schemaStableSnapshotSource = `package interfacesnapshot
func AuthorityMarker() string { return "STABLE" }
`
const schemaCandidateSnapshotSource = `package interfacesnapshot
func AuthorityMarker() string { return "CANDIDATE" }
`
const schemaAliasContractSource = `package runtimeannotate
const (
AnnotationFlagAliasOf = "dws.compat.alias_of"
AnnotationFlagAliasOrigin = "dws.compat.alias_origin"
FlagAliasOriginCorecmdV1 = "corecmd.flag_spec_aliases.v1"
)
`
const candidateNoopSource = `package main
import "fmt"
func main() {
fmt.Println("CANDIDATE_NOOP")
}
`
func schemaBinarySource(marker string) string {
return fmt.Sprintf(`package main
import "fmt"
func main() {
fmt.Println(%q)
}
`, `{"kind":"schema","marker":"`+marker+`"}`)
}
type authorityScenario struct {
name string
baseGovernance string
candidateMutation string
want string
authorityMarker string
}
func TestCrossPlatformCoverageSchemaCompatibilityUsesBaseOwnedAuthority(t *testing.T) {
tests := []authorityScenario{
{
name: "governed base rejects candidate checker and live workspace",
baseGovernance: "complete",
authorityMarker: "BASE_SCHEMA_CHECKER_ENFORCED",
},
{
name: "bootstrap uses old base checker and committed canonical empty ledger",
baseGovernance: "none",
authorityMarker: "OLD_BASE_SCHEMA_CHECKER_ENFORCED",
},
{
name: "partial base fails closed",
baseGovernance: "partial",
want: "incomplete Schema flag migration governance artifact set",
},
{
name: "base ledger ancestor symlink fails closed",
baseGovernance: "ancestor-symlink",
want: "incomplete Schema flag migration governance artifact set",
},
{
name: "governed candidate must preserve schema checker",
baseGovernance: "complete",
candidateMutation: "delete-checker",
want: "must preserve the complete Schema flag migration governance artifact set",
},
{
name: "governed candidate must preserve migrations implementation",
baseGovernance: "complete",
candidateMutation: "delete-migrations",
want: "must preserve the complete Schema flag migration governance artifact set",
},
{
name: "governed candidate ledger must be a regular file",
baseGovernance: "complete",
candidateMutation: "symlink-ledger",
want: "must preserve the complete Schema flag migration governance artifact set",
},
{
name: "governed candidate helper parent must be a Git tree",
baseGovernance: "complete",
candidateMutation: "symlink-helper-parent",
want: "must preserve the complete Schema flag migration governance artifact set",
},
{
name: "candidate cannot forge alias evidence token",
baseGovernance: "complete",
candidateMutation: "forge-alias-token",
want: "may not forge framework-owned flag alias evidence",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
t.Parallel()
runSchemaAuthorityCase(t, test)
})
}
}
func runSchemaAuthorityCase(t *testing.T, test authorityScenario) {
t.Helper()
repoRoot := schemaRepositoryRoot(t)
managedRoot := filepath.Join(repoRoot, ".worktrees", "schemacompat-authority-integration")
if err := os.MkdirAll(managedRoot, 0o755); err != nil {
t.Fatalf("create managed test root: %v", err)
}
fixtureRoot, err := os.MkdirTemp(managedRoot, fmt.Sprintf("case-%d-", time.Now().UnixNano()))
if err != nil {
t.Fatalf("create managed fixture repo: %v", err)
}
t.Cleanup(func() {
if err := os.RemoveAll(fixtureRoot); err != nil {
t.Errorf("remove managed fixture repo: %v", err)
}
})
schemaCopyFile(t,
filepath.Join(repoRoot, "scripts", "policy", "check-authoritative-schema-compatibility.sh"),
filepath.Join(fixtureRoot, "scripts", "policy", "check-authoritative-schema-compatibility.sh"),
0o755,
)
schemaCopyFile(t,
filepath.Join(repoRoot, "scripts", "policy", "policy-runtime.sh"),
filepath.Join(fixtureRoot, "scripts", "policy", "policy-runtime.sh"),
0o755,
)
schemaCopyFile(t,
filepath.Join(repoRoot, "scripts", "release", "release-lib.sh"),
filepath.Join(fixtureRoot, "scripts", "release", "release-lib.sh"),
0o755,
)
schemaWriteFile(t, filepath.Join(fixtureRoot, "go.mod"), "module example.com/dws-schema-compat-authority-test\n\ngo 1.23\n", 0o644)
schemaWriteFile(t, filepath.Join(fixtureRoot, "cmd", "main.go"), schemaBinarySource("STABLE_RELEASE"), 0o644)
schemaWriteFile(t, filepath.Join(fixtureRoot, "cmd", "interface-snapshot", "main.go"), candidateNoopSource, 0o644)
schemaWriteFile(t, filepath.Join(fixtureRoot, "internal", "interfacesnapshot", "snapshot.go"), schemaStableSnapshotSource, 0o644)
schemaWriteFile(t, filepath.Join(fixtureRoot, "internal", "interfacesnapshot", "compare.go"), "package interfacesnapshot\n", 0o644)
schemaWriteFile(t, filepath.Join(fixtureRoot, "scripts", "policy", "schema-compat", "main.go"), bootstrapSchemaCheckerSource, 0o644)
schemaWriteFile(t, filepath.Join(fixtureRoot, "SNAPSHOT_MARKER"), "STABLE_RELEASE\n", 0o644)
schemaRun(t, fixtureRoot, "git", "init", "-b", "main")
schemaRun(t, fixtureRoot, "git", "config", "user.name", "Schema Compatibility Test")
schemaRun(t, fixtureRoot, "git", "config", "user.email", "schema-compat-test@example.invalid")
schemaRun(t, fixtureRoot, "git", "add", ".")
schemaRun(t, fixtureRoot, "git", "commit", "-m", "stable release")
stableCommit := strings.TrimSpace(schemaRun(t, fixtureRoot, "git", "rev-parse", "HEAD"))
schemaRun(t, fixtureRoot, "git", "tag", "v1.0.0", stableCommit)
schemaWriteFile(t, filepath.Join(fixtureRoot, "cmd", "main.go"), schemaBinarySource("BASE_AUTHORITY"), 0o644)
schemaWriteFile(t, filepath.Join(fixtureRoot, "cmd", "interface-snapshot", "main.go"), authorityInterfaceHelperSource, 0o644)
schemaWriteFile(t, filepath.Join(fixtureRoot, "SNAPSHOT_MARKER"), "BASE_AUTHORITY\n", 0o644)
schemaWriteFile(t, filepath.Join(fixtureRoot, "internal", "interfacesnapshot", "snapshot.go"), schemaBaseSnapshotSource, 0o644)
switch test.baseGovernance {
case "complete":
schemaWriteFile(t, filepath.Join(fixtureRoot, "scripts", "policy", "schema-compat", "main.go"), governedSchemaCheckerSource, 0o644)
schemaWriteFile(t, filepath.Join(fixtureRoot, "internal", "interfacesnapshot", "snapshot.go"), schemaGovernedSnapshotSource, 0o644)
schemaWriteFile(t, filepath.Join(fixtureRoot, "internal", "interfacesnapshot", "migrations.go"), "package interfacesnapshot\n", 0o644)
schemaWriteFile(t, filepath.Join(fixtureRoot, "internal", "corecmd", "runtimeannotate", "interface_alias.go"), schemaAliasContractSource, 0o644)
schemaWriteFile(t, filepath.Join(fixtureRoot, "scripts", "policy", "interface-migrations", "approved-flag-migrations-v1.json"), "{\"version\":1,\"migrations\":[{\"authority\":\"BASE\"}]}\n", 0o644)
case "partial":
schemaWriteFile(t, filepath.Join(fixtureRoot, "scripts", "policy", "interface-migrations", "approved-flag-migrations-v1.json"), "{\"version\":1,\"migrations\":[]}\n", 0o644)
case "ancestor-symlink":
schemaWriteFile(t, filepath.Join(fixtureRoot, "scripts", "policy", "schema-compat", "main.go"), governedSchemaCheckerSource, 0o644)
schemaWriteFile(t, filepath.Join(fixtureRoot, "internal", "interfacesnapshot", "migrations.go"), "package interfacesnapshot\n", 0o644)
schemaWriteFile(t, filepath.Join(fixtureRoot, "internal", "corecmd", "runtimeannotate", "interface_alias.go"), "package runtimeannotate\n", 0o644)
schemaWriteFile(t, filepath.Join(fixtureRoot, "scripts", "policy", "authority-ledger", "approved-flag-migrations-v1.json"), "{\"version\":1,\"migrations\":[]}\n", 0o644)
if err := os.Symlink("authority-ledger", filepath.Join(fixtureRoot, "scripts", "policy", "interface-migrations")); err != nil {
t.Fatalf("symlink base Schema ledger parent: %v", err)
}
case "none":
default:
t.Fatalf("unknown base governance state %q", test.baseGovernance)
}
schemaRun(t, fixtureRoot, "git", "add", ".")
schemaRun(t, fixtureRoot, "git", "commit", "-m", "base authority")
baseRef := strings.TrimSpace(schemaRun(t, fixtureRoot, "git", "rev-parse", "HEAD"))
schemaRun(t, fixtureRoot, "git", "tag", "v1.1.0", baseRef)
schemaRun(t, fixtureRoot, "git", "update-ref", "refs/tags/withdrawn/v1.1.0", baseRef)
if test.baseGovernance == "ancestor-symlink" {
ledgerPath := filepath.Join(fixtureRoot, "scripts", "policy", "interface-migrations")
if err := os.Remove(ledgerPath); err != nil {
t.Fatalf("remove inherited base ledger parent symlink: %v", err)
}
if err := os.Rename(
filepath.Join(fixtureRoot, "scripts", "policy", "authority-ledger"),
ledgerPath,
); err != nil {
t.Fatalf("restore regular candidate ledger tree: %v", err)
}
}
schemaWriteFile(t, filepath.Join(fixtureRoot, "cmd", "main.go"), schemaBinarySource("COMMITTED_CANDIDATE"), 0o644)
schemaWriteFile(t, filepath.Join(fixtureRoot, "cmd", "interface-snapshot", "main.go"), candidateNoopSource, 0o644)
schemaWriteFile(t, filepath.Join(fixtureRoot, "scripts", "policy", "schema-compat", "main.go"), candidateNoopSource, 0o644)
schemaWriteFile(t, filepath.Join(fixtureRoot, "internal", "interfacesnapshot", "snapshot.go"), schemaCandidateSnapshotSource, 0o644)
schemaWriteFile(t, filepath.Join(fixtureRoot, "internal", "interfacesnapshot", "migrations.go"), "package interfacesnapshot\n", 0o644)
schemaWriteFile(t, filepath.Join(fixtureRoot, "internal", "corecmd", "runtimeannotate", "interface_alias.go"), "package runtimeannotate\n", 0o644)
candidateLedger := "{\"version\":1,\"migrations\":[]}\n"
if test.baseGovernance == "complete" {
candidateLedger = "{\"version\":1,\"migrations\":[{\"candidate\":\"COMMITTED\"}]}\n"
}
schemaWriteFile(t, filepath.Join(fixtureRoot, "scripts", "policy", "interface-migrations", "approved-flag-migrations-v1.json"), candidateLedger, 0o644)
schemaWriteFile(t, filepath.Join(fixtureRoot, "SNAPSHOT_MARKER"), "COMMITTED_CANDIDATE\n", 0o644)
switch test.candidateMutation {
case "":
case "delete-checker":
if err := os.Remove(filepath.Join(fixtureRoot, "scripts", "policy", "schema-compat", "main.go")); err != nil {
t.Fatalf("remove candidate schema checker: %v", err)
}
case "delete-migrations":
if err := os.Remove(filepath.Join(fixtureRoot, "internal", "interfacesnapshot", "migrations.go")); err != nil {
t.Fatalf("remove candidate migrations implementation: %v", err)
}
case "symlink-ledger":
ledger := filepath.Join(fixtureRoot, "scripts", "policy", "interface-migrations", "approved-flag-migrations-v1.json")
if err := os.Remove(ledger); err != nil {
t.Fatalf("remove candidate ledger: %v", err)
}
if err := os.Symlink("../../../CANONICAL_EMPTY", ledger); err != nil {
t.Fatalf("symlink candidate ledger: %v", err)
}
schemaWriteFile(t, filepath.Join(fixtureRoot, "CANONICAL_EMPTY"), "{\"version\":1,\"migrations\":[]}\n", 0o644)
case "forge-alias-token":
schemaWriteFile(t,
filepath.Join(fixtureRoot, "internal", "evil", "forged.go"),
"package evil\n\nconst forged = \"dws.compat.alias_of\"\n",
0o644,
)
case "symlink-helper-parent":
helperPath := filepath.Join(fixtureRoot, "internal", "interfacesnapshot")
alternatePath := filepath.Join(fixtureRoot, "internal", "interfacesnapshot-alt")
if err := os.Rename(helperPath, alternatePath); err != nil {
t.Fatalf("move candidate Schema helper behind ancestor symlink: %v", err)
}
if err := os.Symlink("interfacesnapshot-alt", helperPath); err != nil {
t.Fatalf("symlink candidate Schema helper parent: %v", err)
}
default:
t.Fatalf("unknown candidate mutation %q", test.candidateMutation)
}
schemaRun(t, fixtureRoot, "git", "add", ".")
schemaRun(t, fixtureRoot, "git", "commit", "-m", "candidate revision")
candidateRef := strings.TrimSpace(schemaRun(t, fixtureRoot, "git", "rev-parse", "HEAD"))
// 让调用者工作区前进到另一提交,并放入可自批的二进制与 ledger;显式
// candidate ref 必须隔离这些内容。
schemaWriteFile(t, filepath.Join(fixtureRoot, "cmd", "main.go"), schemaBinarySource("LIVE_WORKTREE_BINARY"), 0o644)
liveLedger := filepath.Join(fixtureRoot, "scripts", "policy", "interface-migrations", "approved-flag-migrations-v1.json")
if err := os.Remove(liveLedger); err != nil && !os.IsNotExist(err) {
t.Fatalf("remove selected candidate ledger from live revision: %v", err)
}
schemaWriteFile(t, liveLedger, "{\"version\":1,\"migrations\":[{\"live\":true}]}\n", 0o644)
schemaRun(t, fixtureRoot, "git", "add", ".")
schemaRun(t, fixtureRoot, "git", "commit", "-m", "unselected live revision")
schemaWriteFile(t, filepath.Join(fixtureRoot, "dws"), "#!/bin/sh\nprintf '%s\\n' 'LIVE_WORKTREE_BINARY'\n", 0o755)
cmd := exec.Command(
"sh",
"scripts/policy/check-authoritative-schema-compatibility.sh",
"--base-ref", baseRef,
"--stable-ref", "v1.0.0",
"--candidate-ref", candidateRef,
)
cmd.Dir = fixtureRoot
cmd.Env = schemaIntegrationEnvironment(fixtureRoot)
output, runErr := cmd.CombinedOutput()
got := string(output)
if runErr == nil {
t.Fatalf("Schema compatibility script unexpectedly succeeded; output:\n%s", got)
}
for _, forbidden := range []string{"CANDIDATE_NOOP", "LIVE_WORKTREE_BINARY"} {
if strings.Contains(got, forbidden) {
t.Fatalf("candidate-controlled authority %q took effect; output:\n%s", forbidden, got)
}
}
if test.authorityMarker != "" {
if !strings.Contains(got, test.authorityMarker) {
t.Fatalf("Schema compatibility script did not use base authority %q; output:\n%s", test.authorityMarker, got)
}
if test.baseGovernance == "complete" && strings.Count(got, "BASE_INTERFACE_HELPER_GENERATE=BASE") != 3 {
t.Fatalf("governed Schema check did not generate three base-owned interface snapshots; output:\n%s", got)
}
wrongStable := exec.Command(
"sh",
"scripts/policy/check-authoritative-schema-compatibility.sh",
"--base-ref", baseRef,
"--stable-ref", "HEAD",
"--candidate-ref", candidateRef,
)
wrongStable.Dir = fixtureRoot
wrongStable.Env = schemaIntegrationEnvironment(fixtureRoot)
wrongOutput, wrongErr := wrongStable.CombinedOutput()
if wrongErr == nil || !strings.Contains(string(wrongOutput), "is not the expected highest GA tag") {
t.Fatalf("arbitrary Schema stable ref was not rejected: err=%v output:\n%s", wrongErr, wrongOutput)
}
return
}
if !strings.Contains(got, test.want) {
t.Fatalf("Schema compatibility script output missing %q:\n%s", test.want, got)
}
}
func schemaRepositoryRoot(t *testing.T) string {
t.Helper()
_, sourceFile, _, ok := runtime.Caller(0)
if !ok {
t.Fatal("resolve test source path")
}
root := filepath.Clean(filepath.Join(filepath.Dir(sourceFile), "..", "..", ".."))
if _, err := os.Stat(filepath.Join(root, "scripts", "policy", "check-authoritative-schema-compatibility.sh")); err != nil {
t.Fatalf("resolve repository root %s: %v", root, err)
}
return root
}
func schemaCopyFile(t *testing.T, source, destination string, mode os.FileMode) {
t.Helper()
content, err := os.ReadFile(source)
if err != nil {
t.Fatalf("read %s: %v", source, err)
}
schemaWriteFile(t, destination, string(content), mode)
}
func schemaWriteFile(t *testing.T, path, content string, mode os.FileMode) {
t.Helper()
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
t.Fatalf("create parent for %s: %v", path, err)
}
if err := os.WriteFile(path, []byte(content), mode); err != nil {
t.Fatalf("write %s: %v", path, err)
}
}
func schemaRun(t *testing.T, dir, name string, args ...string) string {
t.Helper()
cmd := exec.Command(name, args...)
cmd.Dir = dir
output, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("%s %s failed: %v\n%s", name, strings.Join(args, " "), err, output)
}
return string(output)
}
func schemaIntegrationEnvironment(fixtureRoot string) []string {
overrides := map[string]string{
"DWS_BIN": filepath.Join(fixtureRoot, "dws"),
"DWS_POLICY_TMPDIR": "",
"GOCACHE": filepath.Join(fixtureRoot, ".cache", "go-build"),
"GOMODCACHE": filepath.Join(fixtureRoot, ".cache", "go-mod"),
"GOTMPDIR": "",
"GOTOOLCHAIN": "local",
"GOWORK": "off",
}
environment := make([]string, 0, len(os.Environ())+len(overrides))
for _, item := range os.Environ() {
key, _, _ := strings.Cut(item, "=")
if _, replaced := overrides[key]; !replaced {
environment = append(environment, item)
}
}
for key, value := range overrides {
environment = append(environment, key+"="+value)
}
return environment
}