75 lines
2.5 KiB
JavaScript
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',
|
|
};
|
|
}
|