Files
memind/session-broker-coverage.mjs
T
john 14a00774d9 feat(h5): web 联网能力、实时查询路由与 session Finish 对齐
- 新增 web 能力并挂载 platform/web(web_search/fetch_url)
- 实时查询强制 web skill,router fallback 与 await session Finish
- Session Broker 覆盖率/指标、stream replay 与相关单测/E2E 脚本

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-06 16:06:26 +08:00

170 lines
5.2 KiB
JavaScript

import fs from 'node:fs';
import path from 'node:path';
/**
* SESSION BROKER COVERAGE (H5 Session architecture Patch 2 / §5.7)
*
* Business code must not call userAuth session ownership helpers directly.
* Use session-broker.mjs facade via createSessionAccess / resolveSessionAccess.
*
* See docs/h5-session-architecture-20260706.md §5.7
*/
/** @typedef {{ file: string, line: number, rule: string, text: string }} SessionBrokerCoverageViolation */
export const SESSION_BROKER_FACADE_FILE = 'session-broker.mjs';
export const SESSION_BROKER_DEFINITION_FILE = 'user-auth.mjs';
/** Files allowed to call userAuth.registerAgentSession / ownsSession directly. */
export const SESSION_BROKER_DIRECT_ALLOWLIST = new Set([
SESSION_BROKER_FACADE_FILE,
SESSION_BROKER_DEFINITION_FILE,
]);
/** Patch 2 modules that must route session ownership through sessionAccess. */
export const SESSION_BROKER_PATCH2_FILES = [
'tkmind-proxy.mjs',
'agent-run-routes.mjs',
'agent-run-gateway.mjs',
'direct-chat-service.mjs',
'server.mjs',
'wechat-mp.mjs',
'mindspace-page-edit-session.mjs',
'mindspace-agent-runner.mjs',
'mindspace-conversation-package-routes.mjs',
];
export const SESSION_BROKER_FORBIDDEN_CALLS = [
{ id: 'register-agent-session', pattern: /\buserAuth\.registerAgentSession\s*\(/ },
{ id: 'owns-session', pattern: /\buserAuth\.ownsSession\s*\(/ },
{ id: 'get-session-target', pattern: /\buserAuth\.getSessionTarget\s*\(/ },
{ id: 'unregister-agent-session', pattern: /\buserAuth\.unregisterAgentSession\s*\(/ },
];
export const SESSION_BROKER_PATCH2_MARKERS = [
/\bresolveSessionAccess\b/,
/\bcreateSessionAccess\b/,
/\bsessionAccess\b/,
];
export const SESSION_BROKER_SCAN_IGNORE = [
/^scripts\//,
/^mindspace-service\//,
/^\.runtime\//,
/^node_modules\//,
/\.test\.mjs$/,
/\.test\.ts$/,
];
export const SESSION_BROKER_LINE_ALLOW = [
/SESSION BROKER COVERAGE/,
/§5\.7/,
/session-broker\.mjs/,
/createSessionAccess/,
/resolveSessionAccess/,
/sessionAccess\./,
/getSessionAccess/,
];
function relativePosix(rootDir, absolutePath) {
return path.relative(rootDir, absolutePath).split(path.sep).join('/');
}
function isIgnoredScanPath(relativePath) {
return SESSION_BROKER_SCAN_IGNORE.some((pattern) => pattern.test(relativePath));
}
function isTestFile(relativePath) {
return /\.test\.mjs$/.test(relativePath);
}
function isLineAllowed(line) {
return SESSION_BROKER_LINE_ALLOW.some((pattern) => pattern.test(line));
}
function scanDirectUserAuthCalls(relativePath, content) {
/** @type {SessionBrokerCoverageViolation[]} */
const violations = [];
if (SESSION_BROKER_DIRECT_ALLOWLIST.has(relativePath) || isTestFile(relativePath)) {
return violations;
}
const lines = content.split('\n');
for (let index = 0; index < lines.length; index += 1) {
const line = lines[index];
if (!line.trim() || line.trim().startsWith('//') || line.trim().startsWith('*')) continue;
if (isLineAllowed(line)) continue;
for (const rule of SESSION_BROKER_FORBIDDEN_CALLS) {
if (!rule.pattern.test(line)) continue;
violations.push({
file: relativePath,
line: index + 1,
rule: rule.id,
text: line.trim(),
});
}
}
return violations;
}
function checkPatch2ModuleWiring(rootDir) {
/** @type {SessionBrokerCoverageViolation[]} */
const violations = [];
for (const relativePath of SESSION_BROKER_PATCH2_FILES) {
const absolutePath = path.join(rootDir, relativePath);
if (!fs.existsSync(absolutePath)) {
violations.push({
file: relativePath,
line: 0,
rule: 'missing-patch2-file',
text: 'Patch 2 module missing from repository',
});
continue;
}
const content = fs.readFileSync(absolutePath, 'utf8');
if (!SESSION_BROKER_PATCH2_MARKERS.some((pattern) => pattern.test(content))) {
violations.push({
file: relativePath,
line: 0,
rule: 'missing-session-access-wiring',
text: 'Expected resolveSessionAccess/createSessionAccess/sessionAccess wiring',
});
}
}
return violations;
}
export function checkSessionBrokerCoverage(rootDir, { scanRepo = true } = {}) {
/** @type {SessionBrokerCoverageViolation[]} */
const violations = [...checkPatch2ModuleWiring(rootDir)];
if (!scanRepo) return violations;
const queue = [rootDir];
while (queue.length > 0) {
const current = queue.pop();
const entries = fs.readdirSync(current, { withFileTypes: true });
for (const entry of entries) {
if (entry.name === 'node_modules' || entry.name === '.git') continue;
const absolutePath = path.join(current, entry.name);
const relativePath = relativePosix(rootDir, absolutePath);
if (entry.isDirectory()) {
queue.push(absolutePath);
continue;
}
if (!entry.name.endsWith('.mjs')) continue;
if (isIgnoredScanPath(relativePath)) continue;
const content = fs.readFileSync(absolutePath, 'utf8');
violations.push(...scanDirectUserAuthCalls(relativePath, content));
}
}
return violations;
}
export function formatSessionBrokerCoverageViolations(violations) {
return violations
.map((item) => `${item.file}:${item.line} [${item.rule}] ${item.text}`)
.join('\n');
}