import { matchedWorkspaceLeaseIds, missingActiveWorkspaceLeases, workspacePathWithin } from "../workspace-state/enforcement.js";
import { assessToolHookCapability } from "./hook-capability.js";
const defaultSafeCoordinationTools = new Set([
    "seed_orchestrator_next",
    "seed_orchestrator_record_decision",
    "seed_orchestrator_tick",
    "seed_team_inbox",
    "seed_team_meeting_close",
    "seed_team_meeting_create",
    "seed_team_meeting_post",
    "seed_team_meeting_status",
    "seed_team_member_request",
    "seed_team_ack",
    "seed_team_broadcast",
    "seed_team_status",
    "seed_team_roster",
    "seed_team_conversation",
    "seed_agent_thread_status",
    "seed_agent_thread_trace",
    "seed_message_queue",
    "seed_message_read",
    "seed_message_deliver",
    "seed_team_send",
    "seed_team_task_list",
    "seed_session_info",
]);
export function evaluateMemberToolUse(input) {
    const capability = assessToolHookCapability({ toolName: input.toolName, args: input.args });
    const toolName = capability.tool.normalized_tool_name;
    const allowedTools = normalizedSet(input.memberToolPolicy?.allowed_tools);
    const deniedTools = normalizedSet(input.memberToolPolicy?.denied_tools);
    const safeCoordinationTools = normalizedSet(input.safeCoordinationTools ?? [...defaultSafeCoordinationTools]);
    const isSafeCoordinationTool = safeCoordinationTools.has(toolName);
    const explicitlyAllowed = allowedTools.has(toolName);
    if (deniedTools.has(toolName)) {
        return blocked("tool_denied", "tool is explicitly denied by member contract", capability);
    }
    if (capability.tool.kind === "seed_delegation" && !input.allowRecursiveDelegation) {
        return blocked("recursive_delegation_denied", "recursive delegation/orchestration tools are reserved for Lab-Representative unless explicitly allowed", capability);
    }
    if (capability.tool.kind === "seed_command_exec") {
        return blocked("seed_command_exec_reserved", "SEED command execution tools are reserved for Lab-Representative stateful verification/background capture", capability);
    }
    if (allowedTools.size > 0 && !explicitlyAllowed && !isSafeCoordinationTool) {
        return blocked("tool_not_allowed", "tool is not in member allowed_tools", capability);
    }
    const skillDecision = evaluateSkillPolicy(input.args, capability, input.memberSkillPolicy);
    if (skillDecision)
        return skillDecision;
    const mcpDecision = evaluateMcpPolicy(capability, input.memberMcpPolicy);
    if (mcpDecision)
        return mcpDecision;
    switch (capability.tool.kind) {
        case "read_only":
            if (capability.paths.length > 0) {
                return enforceMemberReadPathsOnly(capability, input.memberReadPaths ?? []);
            }
            return allowed("read-only native tool has no write effect", capability);
        case "seed_coordination":
            return allowed("SEED coordination tool is allowed by member policy", capability);
        case "seed_evidence_capture":
            return allowed("SEED evidence capture tool is allowed by member policy", capability);
        case "seed_delegation":
            return allowed("SEED delegation tool is explicitly allowed by member policy", capability);
        case "seed_workspace":
            return evaluateWorkspaceToolUse(capability, input.args, input.memberReadPaths ?? [], input.memberWritePaths ?? []);
        case "write_file":
            return evaluateWriteEffect(capability, input);
        case "shell":
            if (capability.file_effect === "none" || capability.file_effect === "read") {
                return allowed("shell command was classified as read-only", capability);
            }
            if (capability.file_effect === "write" || capability.file_effect === "append") {
                return evaluateWriteEffect(capability, input);
            }
            return blocked("shell_effect_unknown", "shell command may have side effects but exposes no enforceable write target", capability);
        case "mcp_or_external":
            return blocked("mcp_uninspectable", "MCP/external tool side effects are not inspectable by SEED policy", capability);
        case "unknown":
            return blocked("unknown_tool", "unknown tool is blocked for ordinary members unless explicitly mapped by policy", capability);
    }
}
function evaluateWorkspaceToolUse(capability, args, memberReadPaths, memberWritePaths) {
    const mode = recordValue(args).mode;
    const isWriteRequest = mode === "write" || mode === "append";
    const isReadRequest = mode === "read";
    if (isReadRequest && capability.paths.length > 0) {
        return enforceMemberReadPathsOnly(capability, memberReadPaths);
    }
    if (!isWriteRequest) {
        return allowed("workspace tool does not request a write/append lease", capability);
    }
    if (capability.paths.length === 0) {
        return blocked("write_path_unknown", "write/append workspace request has no target path", capability);
    }
    return enforceMemberWritePathsOnly(capability, memberWritePaths);
}
function enforceMemberReadPathsOnly(capability, memberReadPaths) {
    if (!memberReadPaths.length) {
        return blocked("member_has_no_read_paths", "member contract has no read_paths", capability);
    }
    const outside = capability.paths.filter((entry) => !memberReadPaths.some((allowedPath) => workspacePathWithin(entry.path, allowedPath)));
    if (outside.length > 0) {
        return blocked("outside_member_read_paths", `requested read path is outside member read_paths: ${outside.map((entry) => entry.path).join(", ")}`, capability);
    }
    return allowed("requested read paths are inside member read_paths", capability);
}
function evaluateWriteEffect(capability, input) {
    if (!capability.enforceable || capability.paths.length === 0 || capability.paths.some((path) => path.access === "unknown")) {
        return blocked("write_path_unknown", "write target path is not inspectable by SEED policy", capability);
    }
    const writePathDecision = enforceMemberWritePathsOnly(capability, input.memberWritePaths ?? []);
    if (!writePathDecision.allowed)
        return writePathDecision;
    const missing = missingActiveWorkspaceLeases({
        paths: capability.paths,
        leases: input.activeLeases ?? [],
        owner: input.memberName,
    });
    if (missing.length > 0) {
        return blocked("missing_active_lease", `no active write/append lease covers: ${missing.map((path) => path.path).join(", ")}`, capability);
    }
    return allowed("write target is inside member write_paths and covered by active lease", capability, matchedWorkspaceLeaseIds({ paths: capability.paths, leases: input.activeLeases ?? [], owner: input.memberName }));
}
function enforceMemberWritePathsOnly(capability, memberWritePaths) {
    if (!memberWritePaths.length) {
        return blocked("member_has_no_write_paths", "member contract has no write_paths", capability);
    }
    const outside = capability.paths.filter((entry) => !memberWritePaths.some((allowedPath) => workspacePathWithin(entry.path, allowedPath)));
    if (outside.length > 0) {
        return blocked("outside_member_write_paths", `requested write path is outside member write_paths: ${outside.map((entry) => entry.path).join(", ")}`, capability);
    }
    return allowed("requested write paths are inside member write_paths", capability);
}
function evaluateMcpPolicy(capability, policy) {
    if (capability.tool.kind !== "mcp_or_external")
        return undefined;
    const server = mcpServerName(capability.tool.normalized_tool_name);
    const denied = normalizedSet(policy?.denied_mcps);
    const allowedMcps = normalizedSet(policy?.allowed_mcps);
    if (server && denied.has(server)) {
        return blocked("mcp_denied", `MCP server is explicitly denied by member policy: ${server}`, capability);
    }
    if (allowedMcps.size > 0 && (!server || !allowedMcps.has(server))) {
        return blocked("mcp_not_allowed", server ? `MCP server is not in member allowed_mcps: ${server}` : "MCP server cannot be determined", capability);
    }
    return blocked("mcp_uninspectable", "MCP/external tool side effects are not inspectable by SEED policy", capability);
}
function evaluateSkillPolicy(args, capability, policy) {
    if (capability.tool.normalized_tool_name !== "seed_skill")
        return undefined;
    const skillName = stringValue(recordValue(args).name);
    if (!skillName)
        return undefined;
    const normalizedSkill = normalizeToken(skillName);
    const denied = normalizedSet(policy?.denied_skills);
    const allowedSkills = normalizedSet(policy?.allowed_skills);
    if (denied.has(normalizedSkill)) {
        return blocked("skill_denied", `skill is explicitly denied by member policy: ${skillName}`, capability);
    }
    if (allowedSkills.size > 0 && !allowedSkills.has(normalizedSkill)) {
        return blocked("skill_not_allowed", `skill is not in member allowed_skills: ${skillName}`, capability);
    }
    return undefined;
}
function allowed(reason, capability, matchedLeaseIdsValue = []) {
    return {
        allowed: true,
        code: "allowed",
        reason,
        capability,
        extracted_paths: capability.paths,
        matched_lease_ids: matchedLeaseIdsValue,
    };
}
function blocked(code, reason, capability) {
    return {
        allowed: false,
        code,
        reason,
        capability,
        extracted_paths: capability.paths,
        matched_lease_ids: [],
    };
}
function mcpServerName(toolName) {
    const parts = toolName.split("__");
    return parts.length >= 3 && parts[0] === "mcp" ? parts[1] : undefined;
}
function normalizedSet(values) {
    const result = new Set();
    for (const value of values ?? []) {
        const normalized = normalizeToken(value);
        if (!normalized)
            continue;
        result.add(normalized);
        if (normalized.startsWith("opencode:"))
            result.add(normalized.slice("opencode:".length));
    }
    return result;
}
function normalizeToken(value) {
    return value.trim().toLowerCase();
}
function recordValue(input) {
    return typeof input === "object" && input !== null && !Array.isArray(input) ? input : {};
}
function stringValue(input) {
    return typeof input === "string" && input.trim().length > 0 ? input.trim() : undefined;
}
//# sourceMappingURL=member-policy.js.map