import { nowIso } from '../fsx.js';
export const MCP_0_134_POLICY_SCHEMA = 'sks.mcp-0.134-policy.v1';
const DESTRUCTIVE_TOOL_RE = /(?:^|[^A-Za-z0-9])(write|delete|remove|rm|mutate|update|insert|create|drop|truncate|reset|publish|deploy|apply|patch|commit|push)(?:$|[^A-Za-z0-9])/i;
const DESTRUCTIVE_WORDS = [
    'write',
    'delete',
    'remove',
    'rm',
    'mutate',
    'update',
    'insert',
    'create',
    'drop',
    'truncate',
    'reset',
    'publish',
    'deploy',
    'apply',
    'patch',
    'commit',
    'push'
];
export function classifyMcpToolForConcurrency(tool) {
    const annotations = tool?.annotations || tool?.inputSchema?.annotations || tool?.schema?.annotations || {};
    const readOnlyHint = annotations.readOnlyHint === true;
    const name = String(tool?.name || tool?.id || '');
    const schemaText = safeJson(tool?.inputSchema || tool?.schema || {});
    const destructive = hasDestructiveSignal(name) ||
        hasDestructiveSignal(schemaText) ||
        annotations.destructiveHint === true;
    return {
        name,
        read_only_hint: readOnlyHint,
        concurrency: readOnlyHint && !destructive ? 'candidate_parallel_readonly' : 'serial_required',
        advisory_only: true,
        warnings: [
            ...(readOnlyHint ? ['readOnlyHint_is_advisory_not_authoritative'] : ['readOnlyHint_missing']),
            ...(destructive ? ['destructive_name_or_schema_blocks_parallel_mcp'] : [])
        ]
    };
}
export function compactMcpToolSchema(schema, maxBytes = 8192) {
    const text = safeJson(schema);
    const hasDollarDefs = Boolean(schema?.$defs);
    const hasDefinitions = Boolean(schema?.definitions);
    const refs = collectRefs(schema);
    if (Buffer.byteLength(text, 'utf8') <= maxBytes) {
        return {
            schema,
            compacted: false,
            original_bytes: Buffer.byteLength(text, 'utf8'),
            preserved_ref_defs: refs.length > 0 || hasDollarDefs || hasDefinitions,
            refs
        };
    }
    const preservedDefs = {};
    if (hasDollarDefs)
        preservedDefs.$defs = schema.$defs;
    if (hasDefinitions)
        preservedDefs.definitions = schema.definitions;
    return {
        schema: {
            type: schema?.type || 'object',
            description: String(schema?.description || '').slice(0, 512),
            properties: Object.fromEntries(Object.entries(schema?.properties || {}).slice(0, 50)),
            ...preservedDefs,
            ...(refs.length ? { refs } : {})
        },
        compacted: true,
        original_bytes: Buffer.byteLength(text, 'utf8'),
        preserved_ref_defs: refs.length > 0 || hasDollarDefs || hasDefinitions,
        refs
    };
}
export async function proveMcpReadOnlyRuntimeScheduler(input = {}) {
    const readOnlyTools = input.readOnlyTools || [
        { name: 'docs_search_a', annotations: { readOnlyHint: true }, inputSchema: { type: 'object' }, durationMs: 25 },
        { name: 'docs_search_b', annotations: { readOnlyHint: true }, inputSchema: { type: 'object' }, durationMs: 25 },
        { name: 'docs_lookup_c', annotations: { readOnlyHint: true }, inputSchema: { type: 'object' }, durationMs: 25 }
    ];
    const writeTools = input.writeTools || [
        { name: 'docs_update', annotations: { readOnlyHint: false }, inputSchema: { type: 'object' }, durationMs: 5 },
        { name: 'delete_docs', annotations: { readOnlyHint: true }, inputSchema: { type: 'object', properties: { destructiveOperation: { const: 'delete' } } }, durationMs: 5 }
    ];
    const readOnlyCandidates = readOnlyTools.filter((tool) => classifyMcpToolForConcurrency(tool).concurrency === 'candidate_parallel_readonly');
    const readOnlySerial = readOnlyTools.filter((tool) => classifyMcpToolForConcurrency(tool).concurrency !== 'candidate_parallel_readonly');
    const readOnlyRows = await Promise.all(readOnlyCandidates.map((tool) => runMcpRuntimeFixture(tool, 'parallel_readonly_batch', 'readonly-parallel-001')));
    const writeRows = [];
    for (const tool of [...readOnlySerial, ...writeTools])
        writeRows.push(await runMcpRuntimeFixture(tool, 'serial_required', `serial-${String(writeRows.length + 1).padStart(3, '0')}`));
    const tools = [...readOnlyRows, ...writeRows];
    const overlapEvidence = collectOverlapEvidence(readOnlyRows);
    const readOnlyParallel = readOnlyRows.length < 2 || overlapEvidence.length > 0;
    const writeSerial = collectOverlapEvidence(writeRows).length === 0;
    const destructiveFalsePositiveBlocked = writeRows.some((row) => row.name === 'delete_docs' && row.concurrency === 'serial_required');
    const blockers = [
        ...(readOnlyTools.length >= 3 ? [] : ['mcp_readonly_tool_count_below_3']),
        ...(readOnlyParallel ? [] : ['mcp_readonly_tools_serialized_without_reason']),
        ...(overlapEvidence.every((edge) => {
            const a = readOnlyRows.find((row) => row.name === edge.a);
            const b = readOnlyRows.find((row) => row.name === edge.b);
            return a?.concurrency === 'candidate_parallel_readonly' && b?.concurrency === 'candidate_parallel_readonly';
        }) ? [] : ['mcp_overlap_contains_non_readonly_candidate']),
        ...(writeSerial ? [] : ['mcp_write_tools_ran_concurrently_without_permission']),
        ...(destructiveFalsePositiveBlocked ? [] : ['mcp_destructive_readonly_hint_false_positive_not_blocked'])
    ];
    return {
        schema: 'sks.mcp-readonly-runtime-scheduler-proof.v1',
        generated_at: nowIso(),
        ok: blockers.length === 0,
        read_only_parallel: readOnlyParallel,
        write_serial: writeSerial,
        destructive_false_positive_blocked: destructiveFalsePositiveBlocked,
        overlap_evidence: overlapEvidence,
        tools,
        blockers
    };
}
export function detectMcp0134PolicyFromConfig(sources = []) {
    const servers = sources.flatMap((source) => parseServers(source.text));
    const streamableWithoutOAuth = servers.filter((server) => isStreamableHttp(server.transport) && !server.oauth_configured);
    return {
        schema: MCP_0_134_POLICY_SCHEMA,
        generated_at: nowIso(),
        ok: true,
        servers,
        per_server_environment_supported: servers.some((server) => server.environment_keys.length > 0),
        streamable_http_oauth_supported: servers.some((server) => server.oauth_configured && isStreamableHttp(server.transport)),
        streamable_http_servers_detected: servers.filter((server) => isStreamableHttp(server.transport)).map((server) => server.name),
        warnings: [
            ...(servers.length ? [] : ['mcp_servers_not_configured']),
            ...streamableWithoutOAuth.map((server) => `streamable_http_oauth_not_configured:${server.name}`)
        ]
    };
}
async function runMcpRuntimeFixture(tool, scheduledMode, batchId) {
    const classification = classifyMcpToolForConcurrency(tool);
    const started = Date.now();
    await new Promise((resolve) => setTimeout(resolve, Math.max(0, tool.durationMs || 0)));
    const ended = Date.now();
    return {
        name: classification.name,
        concurrency: classification.concurrency,
        scheduled_mode: scheduledMode,
        batch_id: batchId,
        started_at_ms: started,
        ended_at_ms: ended,
        duration_ms: ended - started
    };
}
function collectOverlapEvidence(rows) {
    const overlaps = [];
    for (let i = 0; i < rows.length; i += 1) {
        for (let j = i + 1; j < rows.length; j += 1) {
            const a = rows[i];
            const b = rows[j];
            if (!a || !b)
                continue;
            const overlap = Math.min(a.ended_at_ms, b.ended_at_ms) - Math.max(a.started_at_ms, b.started_at_ms);
            if (overlap > 0)
                overlaps.push({ a: a.name, b: b.name, overlap_ms: overlap });
        }
    }
    return overlaps;
}
function parseServers(text) {
    const trimmed = String(text || '').trim();
    if (!trimmed)
        return [];
    if (trimmed.startsWith('{')) {
        try {
            const parsed = JSON.parse(trimmed);
            const servers = parsed.mcp_servers || parsed.mcpServers || {};
            return Object.entries(servers).map(([name, value]) => ({
                name,
                environment_keys: Object.keys(value?.env || value?.environment || {}),
                transport: value?.transport || value?.type || null,
                oauth_configured: isConfiguredObject(value?.oauth) || isConfiguredObject(value?.auth?.oauth)
            }));
        }
        catch {
            return [];
        }
    }
    const servers = new Map();
    for (const section of tomlSections(text)) {
        if (!section.parts.length)
            continue;
        const name = unquote(section.parts[0] || '');
        if (!name)
            continue;
        const server = ensureServer(servers, name);
        const subtable = section.parts.slice(1).map((part) => unquote(part)).join('.');
        if (subtable === 'env' || subtable === 'environment') {
            server.environment_keys.push(...assignmentKeys(section.body));
            continue;
        }
        if (subtable === 'oauth' || subtable === 'auth.oauth' || subtable === 'authorization') {
            server.oauth_configured = section.body.trim().length > 0 || server.oauth_configured;
            continue;
        }
        server.environment_keys.push(...[...section.body.matchAll(/^\s*(?:env|environment)\.([A-Za-z0-9_]+)\s*=/gm)].map((entry) => String(entry[1] || '')));
        const transport = section.body.match(/^\s*(?:transport|type)\s*=\s*["']?([^"'\n]+)["']?/m)?.[1]?.trim();
        if (transport)
            server.transport = transport;
        if (/^\s*(?:oauth|authorization)\s*=\s*(?:true|["'][^"']+["']|\{)/im.test(section.body))
            server.oauth_configured = true;
    }
    return [...servers.values()].map((server) => ({
        ...server,
        environment_keys: [...new Set(server.environment_keys)].sort()
    }));
}
function hasDestructiveSignal(value) {
    if (DESTRUCTIVE_TOOL_RE.test(` ${value} `))
        return true;
    const normalized = String(value || '')
        .replace(/([a-z0-9])([A-Z])/g, '$1_$2')
        .replace(/[.:/\\-]+/g, '_')
        .toLowerCase();
    return DESTRUCTIVE_WORDS.some((word) => new RegExp(`(?:^|[^a-z0-9])${word}(?:$|[^a-z0-9])`).test(normalized));
}
function isStreamableHttp(transport) {
    return /streamable.*http|http.*streamable/i.test(String(transport || ''));
}
function isConfiguredObject(value) {
    if (value === true || typeof value === 'string')
        return true;
    return Boolean(value && typeof value === 'object' && Object.keys(value).length > 0);
}
function ensureServer(servers, name) {
    let server = servers.get(name);
    if (!server) {
        server = { name, environment_keys: [], transport: null, oauth_configured: false };
        servers.set(name, server);
    }
    return server;
}
function tomlSections(text) {
    const sections = [];
    const tableRe = /^\s*\[(?:mcp_servers|mcpServers)\.([^\]]+)\]\s*$/gm;
    let match;
    while ((match = tableRe.exec(text)) !== null) {
        const start = match.index + match[0].length;
        const next = text.slice(start).search(/^\s*\[/m);
        const body = next >= 0 ? text.slice(start, start + next) : text.slice(start);
        sections.push({ parts: splitTomlPath(match[1] || ''), body });
    }
    return sections;
}
function splitTomlPath(value) {
    const parts = [];
    let current = '';
    let quote = null;
    for (const char of String(value || '')) {
        if ((char === '"' || char === "'") && !quote) {
            quote = char;
            continue;
        }
        if (char === quote) {
            quote = null;
            continue;
        }
        if (char === '.' && !quote) {
            parts.push(current.trim());
            current = '';
            continue;
        }
        current += char;
    }
    if (current.trim())
        parts.push(current.trim());
    return parts;
}
function assignmentKeys(body) {
    return [...String(body || '').matchAll(/^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=/gm)]
        .map((entry) => String(entry[1] || ''))
        .filter((key) => !['transport', 'type', 'oauth', 'authorization'].includes(key));
}
function collectRefs(value, refs = new Set()) {
    if (!value || typeof value !== 'object')
        return [...refs];
    if (typeof value.$ref === 'string')
        refs.add(value.$ref);
    for (const child of Object.values(value))
        collectRefs(child, refs);
    return [...refs].sort();
}
function safeJson(value) {
    try {
        return JSON.stringify(value || {});
    }
    catch {
        return '{}';
    }
}
function unquote(value) {
    return String(value || '').trim().replace(/^["']|["']$/g, '');
}
//# sourceMappingURL=mcp-0-134-policy.js.map