Files
dingtalk-workspace-cli/scripts/policy/check-command-compatibility.sh

442 lines
15 KiB
Bash
Executable File

#!/bin/sh
set -eu
# 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"
COMMAND_MIGRATION_MANIFEST_REL="scripts/policy/interface-migrations/approved-command-migrations-v1.json"
COMMAND_MIGRATIONS_REL="internal/interfacesnapshot/command_migrations.go"
CORECMD_BRIDGE_REL="internal/corecmd/corecmd.go"
ALIAS_CONTRACT_REL="internal/corecmd/runtimeannotate/interface_alias.go"
CONST_PARAMS_REGISTRY_REL="internal/corecmd/interface_const_params.go"
LEAF_ADAPTER_REL="internal/helpers/leaf.go"
usage() {
printf '%s\n' "usage: $0 --base-ref <ref> --stable-ref <ref> [--candidate-ref <ref>]" >&2
}
while [ "$#" -gt 0 ]; do
case "$1" in
--base-ref)
[ "$#" -ge 2 ] || { usage; exit 2; }
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
;;
*)
printf 'unknown argument: %s\n' "$1" >&2
usage
exit 2
;;
esac
done
[ -n "$BASE_REF" ] && [ -n "$STABLE_REF" ] || { usage; exit 2; }
cd "$ROOT"
. "$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
}
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="$(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"
}
trap cleanup EXIT
trap 'exit 129' HUP
trap 'exit 130' INT
trap 'exit 143' TERM
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
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" ]
}
commit_path_blob() {
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
[ "$2" = blob ] && [ "$4" = "$tree_path" ] || return 1
printf '%s\n' "$3"
}
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
}
has_complete_command_migration_governance() {
governance_commit="$1"
has_complete_migration_governance "$governance_commit" &&
commit_path_is_regular_file "$governance_commit" "$COMMAND_MIGRATIONS_REL" &&
commit_path_is_regular_file "$governance_commit" "$CONST_PARAMS_REGISTRY_REL" &&
commit_path_is_regular_file "$governance_commit" "$COMMAND_MIGRATION_MANIFEST_REL"
}
has_any_command_migration_governance_artifact() {
governance_commit="$1"
for relative_path in "$COMMAND_MIGRATIONS_REL" "$CONST_PARAMS_REGISTRY_REL" "$COMMAND_MIGRATION_MANIFEST_REL"; do
if commit_path_exists "$governance_commit" "$relative_path"; then
return 0
fi
done
return 1
}
check_candidate_const_params_source_policy() {
source_policy_failed=false
for token in attachInterfaceBoolConstParams InterfaceBoolConstParams interfaceBoolConstParamsRegistry; do
matches="$(git -C "$CANDIDATE_WORKTREE" grep -l -w -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/interface_const_params.go)
continue
;;
internal/corecmd/corecmd.go)
[ "$token" = attachInterfaceBoolConstParams ] && continue
;;
internal/interfacesnapshot/snapshot.go)
[ "$token" = InterfaceBoolConstParams ] && continue
;;
esac
if [ "$source_policy_failed" = false ]; then
printf 'candidate production source may not access framework-owned bool ConstParams registry:\n' >&2
fi
printf ' %s: protected bool ConstParams registry identifier %s\n' "$relative_path" "$token" >&2
source_policy_failed=true
done <<EOF
$matches
EOF
done
if [ "$source_policy_failed" = true ]; then
exit 2
fi
}
require_complete_candidate_command_governance() {
if ! has_complete_command_migration_governance "$CANDIDATE_COMMIT"; then
printf 'candidate must preserve the complete command migration governance artifact set at %s\n' \
"$CANDIDATE_COMMIT" >&2
exit 2
fi
}
require_base_identical_command_migration_bridges() {
base_command_manifest_blob="$(commit_path_blob "$BASE_COMMIT" "$COMMAND_MIGRATION_MANIFEST_REL")"
candidate_command_manifest_blob="$(commit_path_blob "$CANDIDATE_COMMIT" "$COMMAND_MIGRATION_MANIFEST_REL")"
[ "$base_command_manifest_blob" != "$candidate_command_manifest_blob" ] || return 0
for protected_bridge_path in "$CORECMD_BRIDGE_REL" "$CONST_PARAMS_REGISTRY_REL" "$LEAF_ADAPTER_REL"; do
base_protected_bridge_blob="$(commit_path_blob "$BASE_COMMIT" "$protected_bridge_path" || true)"
candidate_protected_bridge_blob="$(commit_path_blob "$CANDIDATE_COMMIT" "$protected_bridge_path" || true)"
if [ -z "$base_protected_bridge_blob" ] ||
[ "$candidate_protected_bridge_blob" != "$base_protected_bridge_blob" ]; then
printf 'candidate command migration manifest differs from base; protected bridge must preserve the base Git blob: %s\n' \
"$protected_bridge_path" >&2
exit 2
fi
done
}
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
}
install_authority_const_params_registry() {
source_root="$1"
worktree="$2"
[ "$source_root" = "$worktree" ] && return
mkdir -p "$worktree/$(dirname "$CONST_PARAMS_REGISTRY_REL")"
rm -f "$worktree/$CONST_PARAMS_REGISTRY_REL"
cp "$source_root/$CONST_PARAMS_REGISTRY_REL" "$worktree/$CONST_PARAMS_REGISTRY_REL"
}
EMPTY_MANIFEST="$TMP_ROOT/empty-migrations.json"
printf '%s\n' '{"version":1,"migrations":[]}' >"$EMPTY_MANIFEST"
USE_MIGRATION_GOVERNANCE=false
USE_COMMAND_MIGRATION_GOVERNANCE=false
if has_complete_command_migration_governance "$BASE_COMMIT"; then
USE_COMMAND_MIGRATION_GOVERNANCE=true
APPROVED_COMMAND_MANIFEST="$BASE_WORKTREE/$COMMAND_MIGRATION_MANIFEST_REL"
CANDIDATE_COMMAND_MANIFEST="$CANDIDATE_WORKTREE/$COMMAND_MIGRATION_MANIFEST_REL"
require_complete_candidate_command_governance
require_base_identical_command_migration_bridges
elif has_any_command_migration_governance_artifact "$BASE_COMMIT"; then
printf 'merge-base contains an incomplete command migration governance artifact set: %s\n' \
"$BASE_REF" >&2
exit 2
elif has_any_command_migration_governance_artifact "$CANDIDATE_COMMIT"; then
# Bootstrap is safe because the base-owned comparator receives no new
# authorization input and still performs the ordinary compatibility check.
require_complete_candidate_command_governance
fi
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
check_candidate_const_params_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
check_candidate_const_params_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"
if [ "$USE_COMMAND_MIGRATION_GOVERNANCE" = true ]; then
install_authority_const_params_registry "$AUTHORITY_ROOT" "$CANDIDATE_WORKTREE"
install_authority_const_params_registry "$AUTHORITY_ROOT" "$BASE_WORKTREE"
install_authority_const_params_registry "$AUTHORITY_ROOT" "$STABLE_WORKTREE"
fi
generate_snapshot() {
worktree="$1"
output="$2"
(
cd "$worktree"
go run ./cmd/interface-snapshot generate --output "$output"
)
}
CANDIDATE="$TMP_ROOT/candidate.json"
BASELINE="$TMP_ROOT/merge-base.json"
STABLE="$TMP_ROOT/stable.json"
generate_snapshot "$CANDIDATE_WORKTREE" "$CANDIDATE"
generate_snapshot "$BASE_WORKTREE" "$BASELINE"
generate_snapshot "$STABLE_WORKTREE" "$STABLE"
if [ "$USE_COMMAND_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" \
--approved-command-migrations "$APPROVED_COMMAND_MANIFEST" \
--candidate-command-migrations "$CANDIDATE_COMMAND_MANIFEST"
)
elif [ "$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