Files
ci-workflows/tools/ci/source-policy.sh
T
zcode_mulm b3b4e2b391 est/ci-workflows v1:公司级 reusable 模板(checkout/node-quality/oci-build-push-verify)与 tools/ci 上移
迁移自 mu-ref/est-ci-reusable(模板三修复版:est-bundle CA、verify 解释器
无关、build-arg 无内嵌引号、revision-arg-name、install+quality 单容器、
verdaccio add-host)+ Est-Infra 的 source-policy/REGISTER/ci-stats 上移;
全部去项目专名。架构依据 coordination runbooks/ci-repo-architecture.md。
2026-08-27 12:07:51 +08:00

106 lines
5.1 KiB
Bash
Executable File

# Canonical source-selection policy for est CI — the SINGLE implementation.
# Canonical home: est/ci-workflows tools/ci/source-policy.sh (est 公司级唯一实现;
# 项目仓经调用或同步使用,不得各自 fork)。
#
# Profiles:
# host — helper-container installs that can reach the local pull-through
# cache (npm.cache.est) and trust the est CA bundle.
# hermetic — in-Dockerfile installs with direct internet only (https only,
# no local cache: build containers cannot resolve or trust it).
#
# Integrity properties (unchanged from the CNB-era contract):
# - candidates are a fixed, policy-ordered allowlist;
# - a candidate is only used after a COMPLETENESS probe: registry metadata
# must serve exact pinned versions+integrity for the probe packages AND
# the heaviest lockfile tarballs (including platform variants) must exist;
# - speed never reorders candidates (a fast-but-incomplete mirror must not
# win — this exact bug shipped npmmirror's missing platform tarballs);
# - probes retry with bounded backoff; consumers fall back per policy order.
#
# NOTE (2026-08-26, cache-first posture): with the tn verdaccio pull-through
# cache live (npm.cache.est:4873), the host profile resolves locally first and
# probes are a FALLBACK only — see coordination runbook
# runbooks/build-download-source-selection.md (cache-first revision).
#
# Outputs:
# policy_npm_candidates <profile> [lockfile]
# -> newline-separated ordered valid registry URLs on stdout.
# also persisted to ${POLICY_STATE_DIR:-$TMPDIR}/npm-candidates for later
# stages (build.sh reuses the same validated order).
POLICY_NPM_HOST_ORDER='https://npm.cache.est:4873 https://registry.npmjs.org https://mirrors.cloud.tencent.com/npm https://registry.npmmirror.com'
POLICY_NPM_HERMETIC_ORDER='https://mirrors.cloud.tencent.com/npm https://registry.npmjs.org'
POLICY_PROBE_ATTEMPTS=2
POLICY_PROBE_BACKOFF_S=10
# Probe packages: exact version+integrity must match (same anchors the
# frozen toolchain uses).
POLICY_PROBE_PACKAGES='pnpm@11.0.0:sha512-W9GHUA5JzGw9iR2XO0MsArhEpetyCRcskKUXo+9PV57Vwj1Am2meap3EGP97KxiQ5j9tdPHT/EmEjzd3nInITA== tsx@4.23.0:sha512-eUdUIaCr963q2h5u3+QwvYp0+eqPvn+egeqZUm0hwERCqqx1E3kK5ehbGCvqSE5MQAULr67ww0cA3jKc3YkM1w=='
policy_probe_registry() {
# $1 registry, $2 lockfile (optional). Prints "ok" or fails silently.
node - "$1" "$2" <<'NODE'
const [registry, lockfile] = process.argv.slice(2);
const probes = [
['pnpm', '11.0.0', 'sha512-W9GHUA5JzGw9iR2XO0MsArhEpetyCRcskKUXo+9PV57Vwj1Am2meap3EGP97KxiQ5j9tdPHT/EmEjzd3nInITA=='],
['tsx', '4.23.0', 'sha512-eUdUIaCr963q2h5u3+QwvYp0+eqPvn+egeqZUm0hwERCqqx1E3kK5ehbGCvqSE5MQAULr67ww0cA3jKc3YkM1w=='],
];
// Heaviest lockfile tarballs (incl. platform variants) — the completeness
// signal that metadata-only probes cannot see.
let heavy = [
'@embedded-postgres/linux-x64/-/linux-x64-18.4.0-beta.17.tgz',
'@embedded-postgres/darwin-arm64/-/darwin-arm64-18.4.0-beta.17.tgz',
];
try {
const lock = require('node:fs').readFileSync(lockfile, 'utf8');
const sizes = [];
for (const m of lock.matchAll(/"resolution":\s*\{[^}]*\}[^}]*"size":\s*(\d+)/g)) sizes.push(Number(m[1]));
} catch { /* lockfile optional; anchors above stay */ }
const targets = [];
for (const [name, version, integrity] of probes) targets.push(`${registry}/${name}/${version}|meta|${version}|${integrity}`);
for (const t of heavy) targets.push(`${registry}/${t}|tar`);
(async () => {
for (const target of targets) {
const [url, kind, version, integrity] = target.split('|');
const r = await fetch(url, { redirect: 'follow', signal: AbortSignal.timeout(15000) });
if (kind === 'meta') {
if (!r.ok) process.exit(1);
const body = await r.json();
if (body?.version !== version || body?.dist?.integrity !== integrity) process.exit(1);
} else {
await r.body?.cancel();
if (r.status !== 200) process.exit(1);
}
}
process.stdout.write('ok');
})().catch(() => process.exit(1));
NODE
}
policy_npm_candidates() {
profile="${1:-host}"
lockfile="${2:-}"
case "$profile" in
host) order="$POLICY_NPM_HOST_ORDER" ;;
hermetic) order="$POLICY_NPM_HERMETIC_ORDER" ;;
*) printf '' ; return 1 ;;
esac
valid=""
for registry in $order; do
attempt=1
while [ "$attempt" -le "$POLICY_PROBE_ATTEMPTS" ]; do
if [ "$(policy_probe_registry "$registry" "$lockfile" 2>/dev/null)" = "ok" ]; then
valid="${valid}${registry}\n"
printf '%s\n' "policy_probe kind=npm host=$(printf '%s' "$registry" | sed 's#https://##;s#/$##') profile=$profile status=accepted attempt=$attempt" >&2
break
fi
printf '%s\n' "policy_probe kind=npm host=$(printf '%s' "$registry" | sed 's#https://##;s#/$##') profile=$profile status=failed attempt=$attempt" >&2
attempt=$((attempt + 1))
[ "$attempt" -le "$POLICY_PROBE_ATTEMPTS" ] && sleep "$POLICY_PROBE_BACKOFF_S"
done
done
if [ -z "$valid" ]; then
printf '%s\n' "policy_probe kind=npm status=failed all-candidates-rejected" >&2
return 1
fi
printf '%b' "$valid" | tee "${POLICY_STATE_DIR:-${TMPDIR:-/tmp}}/npm-candidates" 2>/dev/null || printf '%b' "$valid"
}