353 lines
11 KiB
Bash
Executable File
353 lines
11 KiB
Bash
Executable File
#!/bin/sh
|
|
set -eu
|
|
|
|
# Compare the committed candidate's complete Schema contract independently with
|
|
# the Git-owned PR merge-base (or previous main SHA on push) and the latest
|
|
# reachable stable GA. 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)"
|
|
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> [--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 'error: unknown argument: %s\n' "$1" >&2
|
|
usage
|
|
exit 2
|
|
;;
|
|
esac
|
|
done
|
|
|
|
[ -n "$BASE_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 'error: Schema authority is not available locally: %s\n' "$BASE_REF" >&2
|
|
exit 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"
|
|
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() {
|
|
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
|
|
trap 'exit 129' HUP
|
|
trap 'exit 130' INT
|
|
trap 'exit 143' TERM
|
|
|
|
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"
|
|
STABLE_BIN="$TMP_ROOT/stable-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"
|
|
STABLE_RAW="$TMP_ROOT/stable-schema.json"
|
|
STABLE_BASELINE="$TMP_ROOT/stable-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 "$STABLE_WORKTREE"
|
|
go build -o "$STABLE_BIN" ./cmd
|
|
)
|
|
(
|
|
cd "$CANDIDATE_WORKTREE"
|
|
go build -o "$CANDIDATE_BIN" ./cmd
|
|
)
|
|
|
|
mkdir -p "$TMP_ROOT/base-home" "$TMP_ROOT/stable-home" "$TMP_ROOT/candidate-home"
|
|
HOME="$TMP_ROOT/base-home" DWS_LANG=zh \
|
|
"$BASE_BIN" schema --all --format json >"$BASE_RAW"
|
|
HOME="$TMP_ROOT/stable-home" DWS_LANG=zh \
|
|
"$STABLE_BIN" schema --all --format json >"$STABLE_RAW"
|
|
HOME="$TMP_ROOT/candidate-home" DWS_LANG=zh \
|
|
"$CANDIDATE_BIN" schema --all --format json >"$CANDIDATE_RAW"
|
|
|
|
"$CHECKER" --normalize "$BASE_RAW" >"$BASELINE"
|
|
"$CHECKER" --normalize "$STABLE_RAW" >"$STABLE_BASELINE"
|
|
|
|
check_schema_contract() {
|
|
historical_kind="$1"
|
|
historical_ref="$2"
|
|
historical_baseline="$3"
|
|
shift 3
|
|
printf 'checking complete Schema contract against %s %s\n' \
|
|
"$historical_kind" "$historical_ref"
|
|
"$CHECKER" --check "$historical_baseline" --current "$CANDIDATE_RAW" "$@"
|
|
}
|
|
|
|
if [ "$USE_MIGRATION_GOVERNANCE" = false ]; then
|
|
check_schema_contract "PR merge-base" "$BASE_REF" "$BASELINE"
|
|
check_schema_contract "stable" "$STABLE_REF" "$STABLE_BASELINE"
|
|
exit 0
|
|
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"
|
|
|
|
check_schema_contract "PR merge-base" "$BASE_REF" "$BASELINE" \
|
|
--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"
|
|
|
|
check_schema_contract "stable" "$STABLE_REF" "$STABLE_BASELINE" \
|
|
--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"
|