Files
john b1577a16e9
Memind CI / Test, build, and release guards (push) Failing after 12m3s
fix: harden release gate and page delivery
2026-07-26 14:32:01 +08:00

75 lines
2.5 KiB
JavaScript

const TARGETS = new Set(['stable', 'candidate']);
function csvSet(value) {
return new Set(
String(value ?? '')
.split(',')
.map((item) => item.trim())
.filter(Boolean),
);
}
function normalizeIdentity(identity = {}) {
identity = identity ?? {};
return {
id: String(identity.id ?? '').trim(),
username: String(identity.username ?? '').trim().toLowerCase(),
wechatUserId: String(identity.wechatUserId ?? identity.wechat_user_id ?? '').trim(),
};
}
/**
* Build a fail-closed, immutable-identity canary policy. Display names are
* intentionally not accepted: nicknames are not unique enough for production
* routing (there may be multiple users named “唐”).
*/
export function createCanaryPolicy({
userIds = [],
usernames = [],
wechatUserIds = [],
candidateTarget = 'candidate',
} = {}) {
if (!TARGETS.has(candidateTarget) || candidateTarget === 'stable') {
throw new Error('candidateTarget must be candidate');
}
const policy = {
userIds: userIds instanceof Set ? new Set(userIds) : csvSet(userIds),
usernames: usernames instanceof Set
? new Set([...usernames].map((item) => String(item).trim().toLowerCase()))
: new Set([...csvSet(usernames)].map((item) => item.toLowerCase())),
wechatUserIds: wechatUserIds instanceof Set ? new Set(wechatUserIds) : csvSet(wechatUserIds),
candidateTarget,
};
if (!policy.userIds.size && !policy.usernames.size && !policy.wechatUserIds.size) {
throw new Error('canary policy must contain at least one immutable user selector');
}
return Object.freeze(policy);
}
export function canaryPolicyFromEnv(env = process.env) {
return createCanaryPolicy({
userIds: csvSet(env.MEMIND_RELEASE_CANARY_USER_IDS),
usernames: csvSet(env.MEMIND_RELEASE_CANARY_USERNAMES),
wechatUserIds: csvSet(env.MEMIND_RELEASE_CANARY_WECHAT_USER_IDS),
});
}
export function resolveCanaryTarget(identity, policy) {
if (!policy || typeof policy !== 'object') return 'stable';
const normalized = normalizeIdentity(identity);
const matched = policy.userIds?.has(normalized.id)
|| policy.usernames?.has(normalized.username)
|| policy.wechatUserIds?.has(normalized.wechatUserId);
return matched ? policy.candidateTarget : 'stable';
}
export function describeCanaryMatch(identity, policy) {
const normalized = normalizeIdentity(identity);
const target = resolveCanaryTarget(normalized, policy);
return {
target,
identity: normalized,
matched: target === 'candidate',
};
}