Files
memind/scripts/goose-v149-canary.mjs
T
john 1d165bc6e3 feat(goose): complete v1.49 phase3 closeout gates and context fusion plan
Expand Goose v1.49 smoke coverage (memory chat, portal resume, page e2e,
multiturn provider), add canary memory policy lock, refresh baselines, and
ignore one-off evidence artifacts. Document headroom-based context runtime
fusion plan; include auth, scheduled-task, and wechat intent fixes on branch.

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

249 lines
7.6 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 { 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);
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();
}