Files
memind/scripts/goose-v149-canary.mjs
T
john 67220a14ea fix(goose-v149): block unattended real LLM smokes by default
Prevent phase2/phase3/all reruns from burning DashScope tokens unless
GOOSE_V149_ALLOW_REAL_LLM=1 is explicitly set with human approval.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-09 22:08:39 +08:00

251 lines
7.7 KiB
JavaScript

/**
* Local Goose v1.49 canary routing helpers (loopback only).
* Production hosts are refused; see docs/goose-v149-canary-runbook.md.
*/
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { createCanaryPolicy, resolveCanaryTarget } from '../release-gate/canary-routing.mjs';
import { applyRealLlmSkipDefaults } from './goose-v149-real-llm-gate.mjs';
import { loadMemindEnvFiles } from './memind-runtime-profile.mjs';
const memindRoot = path.join(path.dirname(fileURLToPath(import.meta.url)), '..');
function loadEnvFile(filePath) {
if (!fs.existsSync(filePath)) return;
for (const line of fs.readFileSync(filePath, 'utf8').split('\n')) {
const trimmed = line.trim();
if (!trimmed || trimmed.startsWith('#')) continue;
const eq = trimmed.indexOf('=');
if (eq < 0) continue;
const key = trimmed.slice(0, eq).trim();
let value = trimmed.slice(eq + 1).trim();
if (
(value.startsWith('"') && value.endsWith('"'))
|| (value.startsWith("'") && value.endsWith("'"))
) {
value = value.slice(1, -1);
}
if (!(key in process.env)) process.env[key] = value;
}
}
loadEnvFile(path.join(memindRoot, '.env.local'));
export const GOOSE_V149_CANARY_BEGIN = '# GOOSE_V149_CANARY_BEGIN';
export const GOOSE_V149_CANARY_END = '# GOOSE_V149_CANARY_END';
/** Force-apply GOOSE_V149 canary block from .env.local (overrides .env). */
export function applyGooseV149CanaryBlockEnv(env = process.env, rootDir = memindRoot) {
const envFile = path.join(rootDir, '.env.local');
if (!fs.existsSync(envFile)) return;
const lines = fs.readFileSync(envFile, 'utf8').split('\n');
let inBlock = false;
for (const line of lines) {
const trimmed = line.trim();
if (trimmed === GOOSE_V149_CANARY_BEGIN) {
inBlock = true;
continue;
}
if (trimmed === GOOSE_V149_CANARY_END) break;
if (!inBlock || !trimmed || trimmed.startsWith('#')) continue;
const eq = trimmed.indexOf('=');
if (eq < 0) continue;
const key = trimmed.slice(0, eq).trim();
let value = trimmed.slice(eq + 1).trim();
if (
(value.startsWith('"') && value.endsWith('"'))
|| (value.startsWith("'") && value.endsWith("'"))
) {
value = value.slice(1, -1);
}
env[key] = value;
}
}
function applyLocalScenarioCredentials(env) {
if (!env.JOHN_PASSWORD && !env.H5_ACCESS_PASSWORD && !env.MEMIND_PASSWORD) {
env.JOHN_PASSWORD = '981122tj';
}
if (!env.RELEASE_GATE_SCENARIO_USERNAME) {
env.RELEASE_GATE_SCENARIO_USERNAME = 'john';
}
return env;
}
/** Load Memind env files then force-apply the GOOSE_V149 canary block (legacy memory, loopback targets). */
export function prepareGooseV149CheckEnv(env = process.env, rootDir = memindRoot) {
loadMemindEnvFiles(rootDir, env);
applyGooseV149CanaryBlockEnv(env, rootDir);
applyLocalScenarioCredentials(env);
applyRealLlmSkipDefaults(env);
const canary = resolveGooseApiTargetsFromEnv(env);
if (canary?.mode && canary.mode !== 'off' && env.GOOSE_V149_MEMORY_DRIFT_REFRESH_AFTER_SMOKE == null) {
env.GOOSE_V149_MEMORY_DRIFT_REFRESH_AFTER_SMOKE = '1';
}
return env;
}
export const GOOSE_CANARY_MODES = new Set(['off', 'all', 'users']);
const BLOCKED_HOSTS = new Set([
'58.38.22.103',
'120.26.184.105',
'103.tkmind.cn',
'105.tkmind.cn',
]);
export const DEFAULT_GOOSE_STABLE_TARGET = 'https://127.0.0.1:18006';
export const DEFAULT_GOOSE_V149_TARGET = 'https://127.0.0.1:18049';
function csvSet(value) {
return new Set(
String(value ?? '')
.split(',')
.map((item) => item.trim())
.filter(Boolean),
);
}
export function parseGooseCanaryMode(env = process.env) {
const mode = String(env.TKMIND_GOOSE_CANARY ?? 'off').trim().toLowerCase();
if (!GOOSE_CANARY_MODES.has(mode)) {
throw new Error(
`Unsupported TKMIND_GOOSE_CANARY "${mode}". Expected: ${[...GOOSE_CANARY_MODES].join(', ')}`,
);
}
return mode;
}
export function assertLoopbackGooseTarget(rawTarget, label = 'goose target') {
let parsed;
try {
parsed = new URL(String(rawTarget ?? '').trim());
} catch {
throw new Error(`${label} must be a valid URL`);
}
if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:') {
throw new Error(`${label} must use http or https`);
}
const host = parsed.hostname.toLowerCase();
if (BLOCKED_HOSTS.has(host)) {
throw new Error(`${label} refuses production host ${host}`);
}
if (!['127.0.0.1', 'localhost', '::1'].includes(host)) {
throw new Error(`${label} must be loopback (127.0.0.1 / localhost / ::1)`);
}
parsed.pathname = parsed.pathname.replace(/\/+$/, '') || '';
parsed.search = '';
parsed.hash = '';
return parsed.toString().replace(/\/+$/, '');
}
export function gooseCanaryPolicyFromEnv(env = process.env) {
const userIds = csvSet(env.TKMIND_GOOSE_CANARY_USER_IDS);
const usernames = csvSet(env.TKMIND_GOOSE_CANARY_USERNAMES);
const wechatUserIds = csvSet(env.TKMIND_GOOSE_CANARY_WECHAT_USER_IDS);
if (!userIds.size && !usernames.size && !wechatUserIds.size) {
return null;
}
return createCanaryPolicy({ userIds, usernames, wechatUserIds, candidateTarget: 'candidate' });
}
/**
* Resolve Portal → goosed API targets for local v1.49 canary.
* Returns null when canary is off (caller keeps legacy TKMIND_API_TARGET parsing).
*/
export function resolveGooseApiTargetsFromEnv(env = process.env) {
const mode = parseGooseCanaryMode(env);
if (mode === 'off') return null;
const stable = assertLoopbackGooseTarget(
env.TKMIND_API_TARGET_STABLE ?? DEFAULT_GOOSE_STABLE_TARGET,
'TKMIND_API_TARGET_STABLE',
);
const v149 = assertLoopbackGooseTarget(
env.TKMIND_API_TARGET_V149 ?? DEFAULT_GOOSE_V149_TARGET,
'TKMIND_API_TARGET_V149',
);
if (mode === 'all') {
return Object.freeze({
mode,
stable,
v149,
primary: v149,
targets: [v149],
});
}
const policy = gooseCanaryPolicyFromEnv(env);
if (!policy) {
throw new Error(
'TKMIND_GOOSE_CANARY=users requires TKMIND_GOOSE_CANARY_USER_IDS and/or '
+ 'TKMIND_GOOSE_CANARY_USERNAMES / TKMIND_GOOSE_CANARY_WECHAT_USER_IDS',
);
}
return Object.freeze({
mode,
stable,
v149,
primary: stable,
targets: [stable, v149],
policy,
});
}
export function resolveGooseTargetForIdentity(identity, config) {
if (!config || config.mode === 'off') {
return config?.stable ?? DEFAULT_GOOSE_STABLE_TARGET;
}
if (config.mode === 'all') return config.v149;
const targetKey = resolveCanaryTarget(identity, config.policy);
return targetKey === 'candidate' ? config.v149 : config.stable;
}
export function describeGooseCanaryConfig(env = process.env) {
const config = resolveGooseApiTargetsFromEnv(env);
if (!config) {
return {
enabled: false,
mode: 'off',
stable: assertLoopbackGooseTarget(
env.TKMIND_API_TARGET ?? DEFAULT_GOOSE_STABLE_TARGET,
'TKMIND_API_TARGET',
),
v149: assertLoopbackGooseTarget(
env.TKMIND_API_TARGET_V149 ?? DEFAULT_GOOSE_V149_TARGET,
'TKMIND_API_TARGET_V149',
),
};
}
return {
enabled: true,
mode: config.mode,
stable: config.stable,
v149: config.v149,
primary: config.primary,
targets: [...config.targets],
policySelectors: config.policy
? {
userIds: [...config.policy.userIds],
usernames: [...config.policy.usernames],
wechatUserIds: [...config.policy.wechatUserIds],
}
: null,
};
}
function printCliStatus() {
const summary = describeGooseCanaryConfig(process.env);
console.log(JSON.stringify(summary, null, 2));
}
const modulePath = fileURLToPath(import.meta.url);
const invokedPath = process.argv[1] ? path.resolve(process.argv[1]) : '';
if (invokedPath && invokedPath === modulePath) {
printCliStatus();
}