/**
 * CopilotStudioAdapter -- Microsoft Copilot Studio IDE adapter.
 *
 * Microsoft Copilot Studio is a low-code agent builder inside Microsoft's
 * Power Platform ecosystem. It is DISTINCT from GitHub Copilot (autocomplete)
 * and M365 Copilot (productivity assistant). Copilot Studio lets business
 * analysts, IT admins, and citizen developers build organization-specific
 * copilots and agents using natural-language descriptions, topic graphs,
 * Power Automate flows, custom connectors, MCP servers, and knowledge sources.
 *
 * Target users: non-developers with elevated organizational trust. A Copilot
 * Studio agent can be published to Microsoft Teams, M365 Copilot, the public
 * web, or custom channels -- giving it organizational-grade reach from a
 * low-code interface. The governance challenges are distinct from developer IDE
 * adapters:
 *   - Agents are described in natural language; purpose must be recorded.
 *   - Knowledge sources span corporate SharePoint, Dataverse, and the public
 *     web; data residency depends on the source type.
 *   - Connectors can egress to public internet; in government / sovereign mode
 *     this creates a data-residency violation.
 *   - Publishing to "public web" channel from government / sovereign tenants is
 *     an unconditional compliance violation.
 *   - Governance Center policy changes are admin-only; citizen developers must
 *     not trigger them.
 *
 * Deployment modes (three, matching Microsoft's actual cloud segmentation):
 *   copilot-studio-cloud       -- standard commercial cloud (default)
 *   copilot-studio-government  -- GCC / GCC-High / DoD; strict data residency;
 *                                 public-web channel and public-website knowledge
 *                                 sources denied unconditionally; connectors
 *                                 restricted to Microsoft-internal allow-list.
 *   copilot-studio-sovereign   -- regulated-industry sovereign cloud; all
 *                                 government restrictions PLUS Governance Center
 *                                 policy changes require admin approval.
 *
 * Hook surfaces (8 distinct governable surfaces):
 *
 *   agentBuildRequested(agentDescription, agentOwner)
 *     Describes an agent in natural language; Studio generates skill/topic graph.
 *     REQUIRES non-empty agentDescription (governance must identify purpose).
 *     REQUIRES non-empty agentOwner (tenant identity).
 *     Reason codes: copilot-studio-agent-description-required,
 *                   copilot-studio-agent-owner-required.
 *
 *   topicCreationRequested(topicName, topicDescription)
 *     Adds a new conversational topic to an agent.
 *     Audited; no specific deny conditions beyond packBinding.
 *
 *   actionDefinitionRequested(actionName, actionBody)
 *     Defines a tool/action the agent can call (Power Automate flow, custom
 *     connector, MCP server, etc.).
 *     In copilot-studio-government: scan action body for embedded URLs; any URL
 *     host outside allowedConnectorPrefixes is denied.
 *     Reason code: copilot-studio-action-egress-not-permitted.
 *
 *   connectorAddRequested(connectorId, connectorType)
 *     Wires up a Microsoft / third-party connector (Dataverse, SharePoint,
 *     Outlook, custom HTTP).
 *     In copilot-studio-government: connectors NOT in allowedConnectorPrefixes
 *     (default Microsoft-only) are denied. Connectors implying public-internet
 *     egress are denied unless explicitly allowlisted.
 *     Reason code: copilot-studio-connector-not-permitted-government.
 *
 *   knowledgeSourceAddRequested(sourceType, sourceUri)
 *     Adds a knowledge source (SharePoint, public website, uploaded files,
 *     Dataverse table).
 *     In copilot-studio-government: 'public-website' source is denied
 *     unconditionally.
 *     If allowedKnowledgeSources is configured: only listed source types allowed.
 *     Reason codes: copilot-studio-knowledge-source-public-website-not-permitted-government,
 *                   copilot-studio-knowledge-source-not-permitted.
 *
 *   publishRequested(channel, version)
 *     Publishes the agent to a channel (Teams, M365 Copilot, public web, custom).
 *     In copilot-studio-government: 'public-web' denied unconditionally.
 *     In copilot-studio-sovereign: 'public-web' denied + admin approval required
 *     for any publish.
 *     If allowedChannels configured: only listed channels allowed.
 *     Reason codes: copilot-studio-public-channel-not-permitted-government,
 *                   copilot-studio-channel-not-permitted,
 *                   copilot-studio-publish-requires-governance-approval.
 *
 *   governanceCenterPolicyRequested(policyName, policyBody)
 *     Admin tier: defines or updates governance policy for the tenant.
 *     REQUIRES requireGovernanceCenterApproval: true in config AND admin role
 *     in context. Non-admin denied unconditionally.
 *     Reason codes: copilot-studio-admin-only,
 *                   copilot-studio-governance-center-approval-required.
 *
 *   chatTestRequested(promptText)
 *     Test conversation in the design canvas.
 *     Scanned for secrets. Audited.
 *
 * Government / sovereign safeguards (the differentiators):
 *   - publishRequested: government/sovereign mode blocks 'public-web' channel.
 *   - connectorAddRequested: government mode blocks non-Microsoft connectors
 *     unless allowlisted.
 *   - knowledgeSourceAddRequested: government/sovereign mode blocks
 *     'public-website' source.
 *   - actionDefinitionRequested: government mode blocks action bodies referencing
 *     URLs outside allowedConnectorPrefixes.
 *   - governanceCenterPolicyRequested: sovereign mode requires admin approval.
 *
 * Pen-tests:
 *   PT-ADAPT-COPILOTSTUDIO-1: empty packBinding[] denied at every wrap call
 *   PT-ADAPT-COPILOTSTUDIO-2: tenant secrets / connection strings never appear
 *                              in audit entries
 *   PT-ADAPT-COPILOTSTUDIO-3: malformed config throws at construction (invalid
 *                              mode, conflicting tenantTier vs deployment mode)
 *   PT-ADAPT-COPILOTSTUDIO-4: government mode publish to public-web ALWAYS denied
 *                              with copilot-studio-public-channel-not-permitted-government
 *   PT-ADAPT-COPILOTSTUDIO-5: sovereign mode without admin approval ALWAYS denied
 *                              for governance-center policy changes
 *   PT-ADAPT-COPILOTSTUDIO-6: action definition egress URL outside allowlist in
 *                              government mode denied with
 *                              copilot-studio-action-egress-not-permitted
 *
 * Spec: P3-SUB-IDE-09
 *
 * @connexum/ai-governance
 */
import { BaseIdeAdapter } from './base.js';
import { IdeAttributionError, IdeSessionError, } from './index.js';
import { buildIdeAuditEvent } from './shared.js';
export const COPILOT_STUDIO_VALID_DEPLOYMENT_MODES = [
    'copilot-studio-cloud',
    'copilot-studio-government',
    'copilot-studio-sovereign',
];
export const COPILOT_STUDIO_GOVERNMENT_TENANT_TIERS = [
    'government',
    'gcc',
    'gcc-high',
    'dod',
];
export const COPILOT_STUDIO_VALID_TENANT_TIERS = [
    'commercial',
    'government',
    'gcc',
    'gcc-high',
    'dod',
    'sovereign',
];
/**
 * Default connector allow-list for government / sovereign mode.
 * Only connectors whose IDs begin with these prefixes are permitted.
 * All others require explicit configuration in allowedConnectorPrefixes.
 */
export const COPILOT_STUDIO_GOVERNMENT_DEFAULT_CONNECTOR_PREFIXES = [
    'shared_sharepointonline',
    'shared_teams',
    'shared_office365',
    'shared_office365outlook',
    'shared_office365users',
    'shared_onedriveforbusiness',
    'shared_commondataserviceforapps',
    'shared_dynamicscrmonline',
    'shared_microsoftforms',
    'shared_planner',
    'shared_visualstudioteamservices',
    'shared_powerplatformforadmins',
];
/**
 * Connectors known to imply public internet egress. In government mode these
 * are denied unless the caller explicitly allowlists the prefix.
 */
const INTERNET_EGRESS_CONNECTOR_PREFIXES = [
    'shared_twitter',
    'shared_facebook',
    'shared_instagram',
    'shared_linkedin',
    'shared_slack',
    'shared_github',
    'shared_googledrive',
    'shared_gmail',
    'shared_dropbox',
    'shared_box',
    'shared_servicenow',
    'shared_zendesk',
    'shared_salesforce',
    'shared_hubspot',
    'shared_stripe',
    'shared_twilio',
    'shared_sendgrid',
    'shared_mailchimp',
    'shared_airtable',
    'shared_notion',
    'shared_jira',
    'shared_confluence',
    'shared_googledocs',
    'shared_googletasks',
    'shared_googlemaps',
];
// ---------------------------------------------------------------------------
// URL extraction helper (for action-body egress scan)
// ---------------------------------------------------------------------------
/**
 * Extract all http/https URL hosts from a string.
 * Returns unique host strings (lowercased, no trailing dots).
 */
function extractUrlHosts(text) {
    if (!text)
        return [];
    const URL_RE = /https?:\/\/([A-Za-z0-9\-._~:/?#[\]@!$&'()*+,;=%]+)/gi;
    const hosts = new Set();
    let match;
    while ((match = URL_RE.exec(text)) !== null) {
        try {
            const raw = `https://${match[1]}`;
            const parsed = new URL(raw);
            hosts.add(parsed.hostname.toLowerCase());
        }
        catch {
            // Unparseable URL fragment -- skip.
        }
    }
    return Array.from(hosts);
}
/**
 * Check whether a connector host is covered by the allowed-prefix list.
 * Prefix matching is done against the full connector id (not the URL host)
 * for connector-add, but against the URL hostname for action-body egress.
 * This helper checks hostname against a set of Microsoft-managed domains.
 */
const MICROSOFT_MANAGED_DOMAINS = [
    '.microsoft.com',
    '.azure.com',
    '.microsoftonline.com',
    '.sharepoint.com',
    '.office.com',
    '.teams.microsoft.com',
    '.dynamics.com',
    '.crm.dynamics.com',
    '.powerplatform.microsoft.com',
];
function isMicrosoftManagedHost(hostname) {
    const h = hostname.toLowerCase();
    return MICROSOFT_MANAGED_DOMAINS.some((suffix) => h === suffix.replace(/^\./, '') || h.endsWith(suffix));
}
// ---------------------------------------------------------------------------
// Secret patterns (for chat-test scan -- PT-ADAPT-COPILOTSTUDIO-2)
// ---------------------------------------------------------------------------
const COPILOT_STUDIO_SECRET_PATTERNS = [
    { name: 'Azure connection string', re: /AccountKey=[A-Za-z0-9+/=]{20,}/ },
    { name: 'Azure SAS token', re: /sv=\d{4}-\d{2}-\d{2}&/ },
    { name: 'Azure AD client secret', re: /clientSecret["\s]*[:=]["\s]*[A-Za-z0-9\-._~]{8,}/ },
    { name: 'Azure storage account', re: /DefaultEndpointsProtocol=https;AccountName=/ },
    { name: 'Power Platform API key', re: /powerplatform[_\-]?(key|secret|token)\s*[:=]\s*["']?\S{8,}["']?/i },
    { name: 'AWS access key', re: /AKIA[0-9A-Z]{16}/ },
    { name: 'Generic API key env var', re: /(SECRET|PASSWORD|PASSWD|API_KEY|PRIVATE_KEY)\s*=\s*["']?\S{8,}["']?/i },
    { name: 'Bearer token', re: /Authorization:\s*Bearer\s+[0-9A-Za-z\-._~+/]{20,}/i },
    { name: 'Database URL with creds', re: /(postgres|mysql|mongodb|redis):\/\/[^:]+:[^@]+@/ },
];
function scanForSecrets(text) {
    if (!text)
        return null;
    for (const { name, re } of COPILOT_STUDIO_SECRET_PATTERNS) {
        if (re.test(text))
            return name;
    }
    return null;
}
// ---------------------------------------------------------------------------
// CopilotStudioAdapter
// ---------------------------------------------------------------------------
export class CopilotStudioAdapter extends BaseIdeAdapter {
    adapterId = 'copilot-studio';
    ideId = 'copilot-studio';
    copilotConfig = null;
    // ---------------------------------------------------------------------------
    // Lifecycle
    // ---------------------------------------------------------------------------
    initialize(config) {
        const cc = config;
        // Validate deploymentMode.
        if (!cc.deploymentMode || !COPILOT_STUDIO_VALID_DEPLOYMENT_MODES.includes(cc.deploymentMode)) {
            throw new IdeSessionError({
                message: `CopilotStudioAdapter: invalid or missing deploymentMode '${cc.deploymentMode}'. ` +
                    `Must be one of: ${COPILOT_STUDIO_VALID_DEPLOYMENT_MODES.join(', ')}. ` +
                    `Deployment mode is required to determine data-residency posture, channel ` +
                    `permissions, and connector allow-list defaults.`,
                ideId: this.ideId,
                adapterId: this.adapterId,
            });
        }
        // Validate tenantTier if provided and check compatibility with deploymentMode.
        if (cc.tenantTier !== undefined) {
            if (!COPILOT_STUDIO_VALID_TENANT_TIERS.includes(cc.tenantTier)) {
                throw new IdeSessionError({
                    message: `CopilotStudioAdapter: invalid tenantTier '${cc.tenantTier}'. ` +
                        `Must be one of: ${COPILOT_STUDIO_VALID_TENANT_TIERS.join(', ')}.`,
                    ideId: this.ideId,
                    adapterId: this.adapterId,
                });
            }
            // PT-ADAPT-COPILOTSTUDIO-3: tenantTier must be compatible with deploymentMode.
            const isGovTier = COPILOT_STUDIO_GOVERNMENT_TENANT_TIERS.includes(cc.tenantTier);
            const isSovTier = cc.tenantTier === 'sovereign';
            const isCommTier = cc.tenantTier === 'commercial';
            if (isGovTier && cc.deploymentMode !== 'copilot-studio-government') {
                throw new IdeSessionError({
                    message: `CopilotStudioAdapter: conflicting configuration -- tenantTier '${cc.tenantTier}' ` +
                        `is a government tier and requires deploymentMode 'copilot-studio-government'. ` +
                        `Received deploymentMode: '${cc.deploymentMode}'. ` +
                        `Government tenant tiers (government, gcc, gcc-high, dod) must use ` +
                        `copilot-studio-government mode.`,
                    ideId: this.ideId,
                    adapterId: this.adapterId,
                });
            }
            if (isSovTier && cc.deploymentMode !== 'copilot-studio-sovereign') {
                throw new IdeSessionError({
                    message: `CopilotStudioAdapter: conflicting configuration -- tenantTier 'sovereign' ` +
                        `requires deploymentMode 'copilot-studio-sovereign'. ` +
                        `Received deploymentMode: '${cc.deploymentMode}'.`,
                    ideId: this.ideId,
                    adapterId: this.adapterId,
                });
            }
            if (isCommTier && cc.deploymentMode !== 'copilot-studio-cloud') {
                throw new IdeSessionError({
                    message: `CopilotStudioAdapter: conflicting configuration -- tenantTier 'commercial' ` +
                        `requires deploymentMode 'copilot-studio-cloud'. ` +
                        `Received deploymentMode: '${cc.deploymentMode}'.`,
                    ideId: this.ideId,
                    adapterId: this.adapterId,
                });
            }
        }
        super.initialize(config);
        this.copilotConfig = cc;
        this.emit(buildIdeAuditEvent('ide:session-open', {
            ideId: this.ideId,
            adapterId: this.adapterId,
            detail: `CopilotStudioAdapter initialized. ` +
                `deploymentMode: ${cc.deploymentMode}. ` +
                `tenantTier: ${cc.tenantTier ?? 'not-set'}. ` +
                `dataResidencyRegion: ${cc.dataResidencyRegion ?? 'not-set'}. ` +
                `requireGovernanceCenterApproval: ${cc.requireGovernanceCenterApproval === true}.`,
            data: {
                deploymentMode: cc.deploymentMode,
                tenantTier: cc.tenantTier,
                dataResidencyRegion: cc.dataResidencyRegion,
                requireGovernanceCenterApproval: cc.requireGovernanceCenterApproval === true,
                allowedChannels: cc.allowedChannels,
                allowedConnectorPrefixes: cc.allowedConnectorPrefixes,
                allowedKnowledgeSources: cc.allowedKnowledgeSources,
            },
        }));
    }
    // ---------------------------------------------------------------------------
    // IdeAdapter: resolveUserIdentity
    // ---------------------------------------------------------------------------
    async resolveUserIdentity(session) {
        this.ensureInitialized();
        const fromSession = session.identity?.userId?.trim();
        if (fromSession) {
            const identity = {
                userId: fromSession,
                email: session.identity?.email,
                orgId: session.identity?.orgId,
                attributionMethod: 'cursor-composer',
            };
            this.emitIdentityResolved(identity.userId, session.clientSessionId);
            return identity;
        }
        const allowAnon = this.config?.allowAnonymousIdeSessions === true;
        if (allowAnon) {
            const anon = { userId: 'anonymous', attributionMethod: 'cursor-composer' };
            this.emitIdentityResolved(anon.userId, session.clientSessionId);
            return anon;
        }
        this.emitIdentityUnresolved(session.clientSessionId, 'CopilotStudioAdapter: no user identity in session context.');
        throw new IdeAttributionError({
            message: 'CopilotStudioAdapter: cannot resolve user identity. Provide a userId in ' +
                'session.identity.userId (the Entra ID / Azure AD subject), or set ' +
                'allowAnonymousIdeSessions: true in adapter config.',
            ideId: this.ideId,
            adapterId: this.adapterId,
        });
    }
    // ---------------------------------------------------------------------------
    // IdeAdapter: interceptToolInvocation
    // ---------------------------------------------------------------------------
    async interceptToolInvocation(ctx) {
        this.ensureInitialized();
        switch (ctx.tool) {
            case 'copilot-studio:agent-build-requested':
                return this._gateAgentBuildRequest(ctx);
            case 'copilot-studio:topic-creation-requested':
                return this._gateTopicCreationRequest(ctx);
            case 'copilot-studio:action-definition-requested':
                return this._gateActionDefinitionRequest(ctx);
            case 'copilot-studio:connector-add-requested':
                return this._gateConnectorAddRequest(ctx);
            case 'copilot-studio:knowledge-source-add-requested':
                return this._gateKnowledgeSourceAddRequest(ctx);
            case 'copilot-studio:publish-requested':
                return this._gatePublishRequest(ctx);
            case 'copilot-studio:governance-center-policy-requested':
                return this._gateGovernanceCenterPolicyRequest(ctx);
            case 'copilot-studio:chat-test-requested':
                return this._gateChatTestRequest(ctx);
            default:
                this.emit(buildIdeAuditEvent('ide:tool-invocation-approved', {
                    ideId: this.ideId,
                    adapterId: this.adapterId,
                    sessionId: ctx.sessionHandle.sessionId,
                    initiatingUserId: ctx.initiatingUser.userId,
                    tool: ctx.tool,
                    detail: `CopilotStudioAdapter: unknown surface '${ctx.tool}' -- allowed with audit.`,
                }));
                return this.routeToolInvocation(ctx, {
                    decision: 'ALLOW',
                    reason: `Unknown Copilot Studio surface '${ctx.tool}' permitted with audit.`,
                });
        }
    }
    // ---------------------------------------------------------------------------
    // Hook: agentBuildRequested
    //
    // Governance must record the agent's purpose and owner before it is built.
    // An unlabeled agent build is an unauditable governance event.
    // ---------------------------------------------------------------------------
    async _gateAgentBuildRequest(ctx) {
        const agentDescription = String(ctx.input['agentDescription'] ?? ctx.input['description'] ?? '').trim();
        const agentOwner = String(ctx.input['agentOwner'] ?? ctx.input['owner'] ?? '').trim();
        if (!agentDescription) {
            const reasonCode = 'copilot-studio-agent-description-required';
            const decision = {
                decision: 'DENY',
                reason: 'Copilot Studio agent build denied: agentDescription is required for governance audit. ' +
                    'Every agent must carry a non-empty description so the compliance system can evaluate ' +
                    'and record the intended purpose. ' +
                    `Reason code: ${reasonCode}.`,
                metadata: { reasonCode },
            };
            this.emit(buildIdeAuditEvent('ide:tool-invocation-denied', {
                ideId: this.ideId,
                adapterId: this.adapterId,
                sessionId: ctx.sessionHandle.sessionId,
                initiatingUserId: ctx.initiatingUser.userId,
                tool: ctx.tool,
                detail: decision.reason,
                data: { reasonCode },
            }));
            return decision;
        }
        if (!agentOwner) {
            const reasonCode = 'copilot-studio-agent-owner-required';
            const decision = {
                decision: 'DENY',
                reason: 'Copilot Studio agent build denied: agentOwner is required for governance audit. ' +
                    'Tenant identity must be recorded for every agent build operation. ' +
                    `Reason code: ${reasonCode}.`,
                metadata: { reasonCode },
            };
            this.emit(buildIdeAuditEvent('ide:tool-invocation-denied', {
                ideId: this.ideId,
                adapterId: this.adapterId,
                sessionId: ctx.sessionHandle.sessionId,
                initiatingUserId: ctx.initiatingUser.userId,
                tool: ctx.tool,
                detail: decision.reason,
                data: { reasonCode },
            }));
            return decision;
        }
        return this.routeToolInvocation(ctx, {
            decision: 'ALLOW',
            reason: `Copilot Studio agent build permitted. ` +
                `description: "${agentDescription.slice(0, 120)}". ` +
                `owner: "${agentOwner}". ` +
                `deploymentMode: ${this.copilotConfig?.deploymentMode}.`,
            metadata: {
                agentDescription: agentDescription.slice(0, 200),
                agentOwner,
                deploymentMode: this.copilotConfig?.deploymentMode,
                tenantTier: this.copilotConfig?.tenantTier,
            },
        });
    }
    // ---------------------------------------------------------------------------
    // Hook: topicCreationRequested
    //
    // Audited; no specific deny conditions beyond packBinding check (which is
    // handled by ensureInitialized + packId presence from base).
    // ---------------------------------------------------------------------------
    async _gateTopicCreationRequest(ctx) {
        const topicName = String(ctx.input['topicName'] ?? ctx.input['name'] ?? '').trim();
        const topicDescription = String(ctx.input['topicDescription'] ?? ctx.input['description'] ?? '').trim();
        return this.routeToolInvocation(ctx, {
            decision: 'ALLOW',
            reason: `Copilot Studio topic creation permitted. ` +
                `topicName: "${topicName.slice(0, 80)}". ` +
                `deploymentMode: ${this.copilotConfig?.deploymentMode}.`,
            metadata: {
                topicName: topicName.slice(0, 120),
                topicDescription: topicDescription.slice(0, 200),
                deploymentMode: this.copilotConfig?.deploymentMode,
            },
        });
    }
    // ---------------------------------------------------------------------------
    // Hook: actionDefinitionRequested (PT-ADAPT-COPILOTSTUDIO-6)
    //
    // In copilot-studio-government: scan action body for embedded URLs. Any URL
    // host that is outside allowedConnectorPrefixes and is not Microsoft-managed
    // is denied with copilot-studio-action-egress-not-permitted.
    // ---------------------------------------------------------------------------
    async _gateActionDefinitionRequest(ctx) {
        const actionName = String(ctx.input['actionName'] ?? ctx.input['name'] ?? '').trim();
        const actionBody = String(ctx.input['actionBody'] ?? ctx.input['body'] ?? '');
        const mode = this.copilotConfig?.deploymentMode ?? 'copilot-studio-cloud';
        const isRestrictedMode = mode === 'copilot-studio-government' || mode === 'copilot-studio-sovereign';
        if (isRestrictedMode && actionBody) {
            const urlHosts = extractUrlHosts(actionBody);
            if (urlHosts.length > 0) {
                const effectivePrefixes = this._effectiveConnectorPrefixes();
                const offendingHosts = urlHosts.filter((h) => {
                    // Allow if Microsoft-managed.
                    if (isMicrosoftManagedHost(h))
                        return false;
                    // Allow if the host matches any allowed-prefix allowlist entry
                    // (prefix matching for hostnames: e.g. 'myapi.internal.corp' allowed
                    // if 'myapi.internal.corp' or '.internal.corp' is in the list).
                    if (effectivePrefixes.some((p) => h === p || h.endsWith(`.${p}`) || p.endsWith(h))) {
                        return false;
                    }
                    return true;
                });
                if (offendingHosts.length > 0) {
                    const reasonCode = 'copilot-studio-action-egress-not-permitted';
                    const decision = {
                        decision: 'DENY',
                        reason: `Copilot Studio action definition denied: action body contains URLs with hosts ` +
                            `outside the allowed connector prefix list. ` +
                            `Offending hosts: [${offendingHosts.slice(0, 5).join(', ')}]. ` +
                            `In ${mode} mode, action bodies may not reference external internet hosts ` +
                            `unless explicitly allowlisted via allowedConnectorPrefixes. ` +
                            `Reason code: ${reasonCode}.`,
                        metadata: {
                            reasonCode,
                            actionName,
                            offendingHosts: offendingHosts.slice(0, 5),
                            deploymentMode: mode,
                        },
                    };
                    this.emit(buildIdeAuditEvent('ide:tool-invocation-denied', {
                        ideId: this.ideId,
                        adapterId: this.adapterId,
                        sessionId: ctx.sessionHandle.sessionId,
                        initiatingUserId: ctx.initiatingUser.userId,
                        tool: ctx.tool,
                        detail: decision.reason,
                        data: { reasonCode, actionName, offendingHosts: offendingHosts.slice(0, 5), deploymentMode: mode },
                    }));
                    return decision;
                }
            }
        }
        return this.routeToolInvocation(ctx, {
            decision: 'ALLOW',
            reason: `Copilot Studio action definition permitted. ` +
                `actionName: "${actionName.slice(0, 80)}". ` +
                `deploymentMode: ${mode}.`,
            metadata: {
                actionName: actionName.slice(0, 120),
                deploymentMode: mode,
                egressScanPerformed: isRestrictedMode,
            },
        });
    }
    // ---------------------------------------------------------------------------
    // Hook: connectorAddRequested
    //
    // In government/sovereign mode: connectors NOT in allowedConnectorPrefixes
    // (default Microsoft-only) are denied. Connectors with public-internet
    // egress prefixes are denied unless explicitly allowlisted.
    // ---------------------------------------------------------------------------
    async _gateConnectorAddRequest(ctx) {
        const connectorId = String(ctx.input['connectorId'] ?? ctx.input['id'] ?? '').trim();
        const connectorType = String(ctx.input['connectorType'] ?? ctx.input['type'] ?? '').trim();
        const mode = this.copilotConfig?.deploymentMode ?? 'copilot-studio-cloud';
        const isRestrictedMode = mode === 'copilot-studio-government' || mode === 'copilot-studio-sovereign';
        if (isRestrictedMode && connectorId) {
            const effectivePrefixes = this._effectiveConnectorPrefixes();
            const isAllowed = effectivePrefixes.some((p) => connectorId.startsWith(p));
            if (!isAllowed) {
                // Check if this is a known internet-egress connector prefix.
                const isEgressConnector = INTERNET_EGRESS_CONNECTOR_PREFIXES.some((p) => connectorId.startsWith(p));
                const reasonCode = 'copilot-studio-connector-not-permitted-government';
                const detail = isEgressConnector
                    ? `connector '${connectorId}' implies public internet egress and is not in allowedConnectorPrefixes`
                    : `connector '${connectorId}' is not in the allowedConnectorPrefixes list`;
                const decision = {
                    decision: 'DENY',
                    reason: `Copilot Studio connector add denied: ${detail}. ` +
                        `In ${mode} mode, only connectors matching the allowedConnectorPrefixes ` +
                        `list are permitted. Add the connector prefix to allowedConnectorPrefixes ` +
                        `in CopilotStudioAdapterConfig to permit this connector. ` +
                        `Reason code: ${reasonCode}.`,
                    metadata: {
                        reasonCode,
                        connectorId,
                        connectorType,
                        deploymentMode: mode,
                        isEgressConnector,
                    },
                };
                this.emit(buildIdeAuditEvent('ide:tool-invocation-denied', {
                    ideId: this.ideId,
                    adapterId: this.adapterId,
                    sessionId: ctx.sessionHandle.sessionId,
                    initiatingUserId: ctx.initiatingUser.userId,
                    tool: ctx.tool,
                    detail: decision.reason,
                    data: { reasonCode, connectorId, connectorType, deploymentMode: mode },
                }));
                return decision;
            }
        }
        return this.routeToolInvocation(ctx, {
            decision: 'ALLOW',
            reason: `Copilot Studio connector add permitted. ` +
                `connectorId: '${connectorId}'. ` +
                `deploymentMode: ${mode}.`,
            metadata: {
                connectorId,
                connectorType,
                deploymentMode: mode,
            },
        });
    }
    // ---------------------------------------------------------------------------
    // Hook: knowledgeSourceAddRequested
    //
    // Government/sovereign: 'public-website' denied unconditionally.
    // If allowedKnowledgeSources configured: only listed source types allowed.
    // ---------------------------------------------------------------------------
    async _gateKnowledgeSourceAddRequest(ctx) {
        const sourceType = String(ctx.input['sourceType'] ?? ctx.input['type'] ?? '').trim();
        const sourceUri = String(ctx.input['sourceUri'] ?? ctx.input['uri'] ?? '').trim();
        const mode = this.copilotConfig?.deploymentMode ?? 'copilot-studio-cloud';
        const isRestrictedMode = mode === 'copilot-studio-government' || mode === 'copilot-studio-sovereign';
        // Government/sovereign: public-website source is unconditionally denied.
        if (isRestrictedMode && sourceType === 'public-website') {
            const reasonCode = 'copilot-studio-knowledge-source-public-website-not-permitted-government';
            const decision = {
                decision: 'DENY',
                reason: `Copilot Studio knowledge source denied: 'public-website' source type is not ` +
                    `permitted in ${mode} mode. ` +
                    `Public website knowledge sources would cause data to be fetched from the ` +
                    `public internet, violating data residency requirements. ` +
                    `Use SharePoint, Dataverse, or uploaded-file sources instead. ` +
                    `Reason code: ${reasonCode}.`,
                metadata: {
                    reasonCode,
                    sourceType,
                    deploymentMode: mode,
                },
            };
            this.emit(buildIdeAuditEvent('ide:tool-invocation-denied', {
                ideId: this.ideId,
                adapterId: this.adapterId,
                sessionId: ctx.sessionHandle.sessionId,
                initiatingUserId: ctx.initiatingUser.userId,
                tool: ctx.tool,
                detail: decision.reason,
                data: { reasonCode, sourceType, deploymentMode: mode },
            }));
            return decision;
        }
        // If allowedKnowledgeSources is configured, enforce the allow-list.
        const allowedSources = this.copilotConfig?.allowedKnowledgeSources;
        if (allowedSources && allowedSources.length > 0) {
            if (!allowedSources.includes(sourceType)) {
                const reasonCode = 'copilot-studio-knowledge-source-not-permitted';
                const decision = {
                    decision: 'DENY',
                    reason: `Copilot Studio knowledge source denied: source type '${sourceType}' is not ` +
                        `in allowedKnowledgeSources [${allowedSources.join(', ')}]. ` +
                        `Reason code: ${reasonCode}.`,
                    metadata: {
                        reasonCode,
                        sourceType,
                        allowedSources,
                        deploymentMode: mode,
                    },
                };
                this.emit(buildIdeAuditEvent('ide:tool-invocation-denied', {
                    ideId: this.ideId,
                    adapterId: this.adapterId,
                    sessionId: ctx.sessionHandle.sessionId,
                    initiatingUserId: ctx.initiatingUser.userId,
                    tool: ctx.tool,
                    detail: decision.reason,
                    data: { reasonCode, sourceType, allowedSources, deploymentMode: mode },
                }));
                return decision;
            }
        }
        return this.routeToolInvocation(ctx, {
            decision: 'ALLOW',
            reason: `Copilot Studio knowledge source add permitted. ` +
                `sourceType: '${sourceType}'. ` +
                `deploymentMode: ${mode}.`,
            metadata: {
                sourceType,
                sourceUri: sourceUri.slice(0, 120),
                deploymentMode: mode,
            },
        });
    }
    // ---------------------------------------------------------------------------
    // Hook: publishRequested (PT-ADAPT-COPILOTSTUDIO-4)
    //
    // Government: 'public-web' denied unconditionally.
    // Sovereign: 'public-web' denied + any publish requires governance-center approval.
    // If allowedChannels configured: only listed channels allowed.
    // ---------------------------------------------------------------------------
    async _gatePublishRequest(ctx) {
        const channel = String(ctx.input['channel'] ?? ctx.input['target'] ?? '').trim();
        const version = String(ctx.input['version'] ?? '').trim();
        const mode = this.copilotConfig?.deploymentMode ?? 'copilot-studio-cloud';
        const isGovernmentMode = mode === 'copilot-studio-government';
        const isSovereignMode = mode === 'copilot-studio-sovereign';
        // PT-ADAPT-COPILOTSTUDIO-4: government/sovereign mode -- 'public-web' ALWAYS denied.
        if ((isGovernmentMode || isSovereignMode) && channel === 'public-web') {
            const reasonCode = 'copilot-studio-public-channel-not-permitted-government';
            const decision = {
                decision: 'DENY',
                reason: `Copilot Studio publish denied: publishing to 'public-web' channel is not ` +
                    `permitted in ${mode} mode. ` +
                    `Government and sovereign deployments may not expose agents to the public ` +
                    `internet via the web channel. This is an unconditional governance rule. ` +
                    `Reason code: ${reasonCode}.`,
                metadata: {
                    reasonCode,
                    channel,
                    deploymentMode: mode,
                },
            };
            this.emit(buildIdeAuditEvent('ide:tool-invocation-denied', {
                ideId: this.ideId,
                adapterId: this.adapterId,
                sessionId: ctx.sessionHandle.sessionId,
                initiatingUserId: ctx.initiatingUser.userId,
                tool: ctx.tool,
                detail: decision.reason,
                data: { reasonCode, channel, deploymentMode: mode },
            }));
            return decision;
        }
        // Sovereign mode: any publish requires governance-center admin approval.
        if (isSovereignMode) {
            const requireApproval = this.copilotConfig?.requireGovernanceCenterApproval === true;
            if (!requireApproval) {
                const reasonCode = 'copilot-studio-publish-requires-governance-approval';
                const decision = {
                    decision: 'DENY',
                    reason: `Copilot Studio publish denied: sovereign mode requires ` +
                        `requireGovernanceCenterApproval: true in adapter config. ` +
                        `Set requireGovernanceCenterApproval: true and obtain admin approval before ` +
                        `publishing in sovereign cloud environments. ` +
                        `Reason code: ${reasonCode}.`,
                    metadata: {
                        reasonCode,
                        channel,
                        deploymentMode: mode,
                    },
                };
                this.emit(buildIdeAuditEvent('ide:tool-invocation-denied', {
                    ideId: this.ideId,
                    adapterId: this.adapterId,
                    sessionId: ctx.sessionHandle.sessionId,
                    initiatingUserId: ctx.initiatingUser.userId,
                    tool: ctx.tool,
                    detail: decision.reason,
                    data: { reasonCode, channel, deploymentMode: mode },
                }));
                return decision;
            }
        }
        // If allowedChannels configured: only listed channels allowed.
        const allowedChannels = this.copilotConfig?.allowedChannels;
        if (allowedChannels && allowedChannels.length > 0) {
            if (!allowedChannels.includes(channel)) {
                const reasonCode = 'copilot-studio-channel-not-permitted';
                const decision = {
                    decision: 'DENY',
                    reason: `Copilot Studio publish denied: channel '${channel}' is not in allowedChannels ` +
                        `[${allowedChannels.join(', ')}]. ` +
                        `Reason code: ${reasonCode}.`,
                    metadata: {
                        reasonCode,
                        channel,
                        allowedChannels,
                        deploymentMode: mode,
                    },
                };
                this.emit(buildIdeAuditEvent('ide:tool-invocation-denied', {
                    ideId: this.ideId,
                    adapterId: this.adapterId,
                    sessionId: ctx.sessionHandle.sessionId,
                    initiatingUserId: ctx.initiatingUser.userId,
                    tool: ctx.tool,
                    detail: decision.reason,
                    data: { reasonCode, channel, allowedChannels, deploymentMode: mode },
                }));
                return decision;
            }
        }
        return this.routeToolInvocation(ctx, {
            decision: 'ALLOW',
            reason: `Copilot Studio publish permitted. ` +
                `channel: '${channel}'. ` +
                `version: '${version || 'unspecified'}'. ` +
                `deploymentMode: ${mode}.`,
            metadata: {
                channel,
                version: version || undefined,
                deploymentMode: mode,
                tenantTier: this.copilotConfig?.tenantTier,
            },
        });
    }
    // ---------------------------------------------------------------------------
    // Hook: governanceCenterPolicyRequested (PT-ADAPT-COPILOTSTUDIO-5)
    //
    // Requires:
    //   1. requireGovernanceCenterApproval: true in config (or sovereign mode).
    //   2. userRole: 'admin' in context.
    // Non-admin users denied with copilot-studio-admin-only.
    // Config flag not set denied with copilot-studio-governance-center-approval-required.
    // ---------------------------------------------------------------------------
    async _gateGovernanceCenterPolicyRequest(ctx) {
        const policyName = String(ctx.input['policyName'] ?? ctx.input['name'] ?? '').trim();
        const userRole = String(ctx.input['userRole'] ?? ctx.input['role'] ?? '').trim();
        const mode = this.copilotConfig?.deploymentMode ?? 'copilot-studio-cloud';
        const requireApproval = this.copilotConfig?.requireGovernanceCenterApproval === true;
        const isSovereign = mode === 'copilot-studio-sovereign';
        // PT-ADAPT-COPILOTSTUDIO-5: admin-only gate.
        if (userRole !== 'admin') {
            const reasonCode = 'copilot-studio-admin-only';
            const decision = {
                decision: 'DENY',
                reason: `Copilot Studio Governance Center policy operation denied: requires admin role. ` +
                    `Current role: '${userRole || 'not-set'}'. ` +
                    `Only tenant administrators may define or update Governance Center policies. ` +
                    `Reason code: ${reasonCode}.`,
                metadata: {
                    reasonCode,
                    policyName,
                    userRole: userRole || 'not-set',
                    deploymentMode: mode,
                },
            };
            this.emit(buildIdeAuditEvent('ide:tool-invocation-denied', {
                ideId: this.ideId,
                adapterId: this.adapterId,
                sessionId: ctx.sessionHandle.sessionId,
                initiatingUserId: ctx.initiatingUser.userId,
                tool: ctx.tool,
                detail: decision.reason,
                data: { reasonCode, policyName, userRole: userRole || 'not-set', deploymentMode: mode },
            }));
            return decision;
        }
        // PT-ADAPT-COPILOTSTUDIO-5: sovereign or requireGovernanceCenterApproval flag check.
        if (isSovereign && !requireApproval) {
            const reasonCode = 'copilot-studio-governance-center-approval-required';
            const decision = {
                decision: 'DENY',
                reason: `Copilot Studio Governance Center policy operation denied: sovereign mode requires ` +
                    `requireGovernanceCenterApproval: true in adapter config. ` +
                    `Set requireGovernanceCenterApproval: true to permit admin governance-center ` +
                    `operations in sovereign cloud environments. ` +
                    `Reason code: ${reasonCode}.`,
                metadata: {
                    reasonCode,
                    policyName,
                    deploymentMode: mode,
                },
            };
            this.emit(buildIdeAuditEvent('ide:tool-invocation-denied', {
                ideId: this.ideId,
                adapterId: this.adapterId,
                sessionId: ctx.sessionHandle.sessionId,
                initiatingUserId: ctx.initiatingUser.userId,
                tool: ctx.tool,
                detail: decision.reason,
                data: { reasonCode, policyName, deploymentMode: mode },
            }));
            return decision;
        }
        return this.routeToolInvocation(ctx, {
            decision: 'ALLOW',
            reason: `Copilot Studio Governance Center policy operation permitted. ` +
                `policyName: "${policyName.slice(0, 80)}". ` +
                `userRole: admin. deploymentMode: ${mode}.`,
            metadata: {
                policyName: policyName.slice(0, 120),
                userRole: 'admin',
                deploymentMode: mode,
                requireGovernanceCenterApproval: requireApproval,
            },
        });
    }
    // ---------------------------------------------------------------------------
    // Hook: chatTestRequested
    //
    // Design-canvas test conversation. Scanned for secrets.
    // PT-ADAPT-COPILOTSTUDIO-2: tenant secrets / connection strings must not
    // appear in audit entries.
    // ---------------------------------------------------------------------------
    async _gateChatTestRequest(ctx) {
        const promptText = String(ctx.input['promptText'] ?? ctx.input['prompt'] ?? '');
        const secretMatch = scanForSecrets(promptText);
        if (secretMatch) {
            const reasonCode = 'copilot-studio-chat-test-secret-detected';
            return this.routeToolInvocation(ctx, {
                decision: 'DENY',
                reason: `Copilot Studio chat test denied: secret pattern '${secretMatch}' detected in prompt. ` +
                    `Test prompts must not contain credentials, connection strings, or API keys. ` +
                    `Reason code: ${reasonCode}.`,
                // PT-ADAPT-COPILOTSTUDIO-2: secret VALUE is never included in audit metadata.
                metadata: {
                    reasonCode,
                    secretPattern: secretMatch,
                    deploymentMode: this.copilotConfig?.deploymentMode,
                },
            });
        }
        return this.routeToolInvocation(ctx, {
            decision: 'ALLOW',
            reason: `Copilot Studio chat test permitted. ` +
                `deploymentMode: ${this.copilotConfig?.deploymentMode}.`,
            metadata: {
                deploymentMode: this.copilotConfig?.deploymentMode,
            },
        });
    }
    // ---------------------------------------------------------------------------
    // Public wrap methods (typed direct-call API)
    // ---------------------------------------------------------------------------
    async wrapAgentBuild(ctx) {
        this.ensureInitialized();
        return this._gateAgentBuildRequest({
            tool: 'copilot-studio:agent-build-requested',
            input: { agentDescription: ctx.agentDescription, agentOwner: ctx.agentOwner },
            sessionHandle: { sessionId: ctx.sessionId ?? 'copilot-studio-session', openedAt: new Date().toISOString() },
            initiatingUser: { userId: 'copilot-studio-user', attributionMethod: 'cursor-composer' },
            timestamp: new Date().toISOString(),
        });
    }
    async wrapTopicCreation(ctx) {
        this.ensureInitialized();
        return this._gateTopicCreationRequest({
            tool: 'copilot-studio:topic-creation-requested',
            input: { topicName: ctx.topicName, topicDescription: ctx.topicDescription ?? '' },
            sessionHandle: { sessionId: ctx.sessionId ?? 'copilot-studio-session', openedAt: new Date().toISOString() },
            initiatingUser: { userId: 'copilot-studio-user', attributionMethod: 'cursor-composer' },
            timestamp: new Date().toISOString(),
        });
    }
    async wrapActionDefinition(ctx) {
        this.ensureInitialized();
        return this._gateActionDefinitionRequest({
            tool: 'copilot-studio:action-definition-requested',
            input: { actionName: ctx.actionName, actionBody: ctx.actionBody ?? '' },
            sessionHandle: { sessionId: ctx.sessionId ?? 'copilot-studio-session', openedAt: new Date().toISOString() },
            initiatingUser: { userId: 'copilot-studio-user', attributionMethod: 'cursor-composer' },
            timestamp: new Date().toISOString(),
        });
    }
    async wrapConnectorAdd(ctx) {
        this.ensureInitialized();
        return this._gateConnectorAddRequest({
            tool: 'copilot-studio:connector-add-requested',
            input: { connectorId: ctx.connectorId, connectorType: ctx.connectorType ?? '' },
            sessionHandle: { sessionId: ctx.sessionId ?? 'copilot-studio-session', openedAt: new Date().toISOString() },
            initiatingUser: { userId: 'copilot-studio-user', attributionMethod: 'cursor-composer' },
            timestamp: new Date().toISOString(),
        });
    }
    async wrapKnowledgeSourceAdd(ctx) {
        this.ensureInitialized();
        return this._gateKnowledgeSourceAddRequest({
            tool: 'copilot-studio:knowledge-source-add-requested',
            input: { sourceType: ctx.sourceType, sourceUri: ctx.sourceUri ?? '' },
            sessionHandle: { sessionId: ctx.sessionId ?? 'copilot-studio-session', openedAt: new Date().toISOString() },
            initiatingUser: { userId: 'copilot-studio-user', attributionMethod: 'cursor-composer' },
            timestamp: new Date().toISOString(),
        });
    }
    async wrapPublish(ctx) {
        this.ensureInitialized();
        return this._gatePublishRequest({
            tool: 'copilot-studio:publish-requested',
            input: { channel: ctx.channel, version: ctx.version ?? '' },
            sessionHandle: { sessionId: ctx.sessionId ?? 'copilot-studio-session', openedAt: new Date().toISOString() },
            initiatingUser: { userId: 'copilot-studio-user', attributionMethod: 'cursor-composer' },
            timestamp: new Date().toISOString(),
        });
    }
    async wrapGovernanceCenterPolicy(ctx) {
        this.ensureInitialized();
        return this._gateGovernanceCenterPolicyRequest({
            tool: 'copilot-studio:governance-center-policy-requested',
            input: {
                policyName: ctx.policyName,
                policyBody: ctx.policyBody ?? '',
                userRole: ctx.userRole ?? '',
            },
            sessionHandle: { sessionId: ctx.sessionId ?? 'copilot-studio-session', openedAt: new Date().toISOString() },
            initiatingUser: { userId: 'copilot-studio-user', attributionMethod: 'cursor-composer' },
            timestamp: new Date().toISOString(),
        });
    }
    async wrapChatTest(ctx) {
        this.ensureInitialized();
        return this._gateChatTestRequest({
            tool: 'copilot-studio:chat-test-requested',
            input: { promptText: ctx.promptText },
            sessionHandle: { sessionId: ctx.sessionId ?? 'copilot-studio-session', openedAt: new Date().toISOString() },
            initiatingUser: { userId: 'copilot-studio-user', attributionMethod: 'cursor-composer' },
            timestamp: new Date().toISOString(),
        });
    }
    // ---------------------------------------------------------------------------
    // Capabilities
    // ---------------------------------------------------------------------------
    getIdeCapabilities() {
        const mode = this.copilotConfig?.deploymentMode ?? 'copilot-studio-cloud';
        return {
            runtimeHooks: true,
            prBoundary: false,
            userIdentity: true,
            reviewerRouting: true,
            integrationMode: `copilot-studio-${mode.replace('copilot-studio-', '')}`,
        };
    }
    // ---------------------------------------------------------------------------
    // Error translation
    // ---------------------------------------------------------------------------
    translateError(raw) {
        if (raw instanceof IdeAttributionError)
            return raw;
        return super.translateError(raw);
    }
    // ---------------------------------------------------------------------------
    // Public accessors for diagnostics / tests
    // ---------------------------------------------------------------------------
    getDeploymentMode() {
        return this.copilotConfig?.deploymentMode ?? 'copilot-studio-cloud';
    }
    getTenantTier() {
        return this.copilotConfig?.tenantTier;
    }
    getDataResidencyRegion() {
        return this.copilotConfig?.dataResidencyRegion;
    }
    isGovernmentMode() {
        const mode = this.copilotConfig?.deploymentMode;
        return mode === 'copilot-studio-government' || mode === 'copilot-studio-sovereign';
    }
    isSovereignMode() {
        return this.copilotConfig?.deploymentMode === 'copilot-studio-sovereign';
    }
    requiresGovernanceCenterApproval() {
        return this.copilotConfig?.requireGovernanceCenterApproval === true;
    }
    // ---------------------------------------------------------------------------
    // Private helpers
    // ---------------------------------------------------------------------------
    /**
     * Resolve the effective connector prefix allow-list.
     * Government/sovereign: use configured list or default Microsoft-only list.
     * Cloud: empty list (no connector restriction).
     */
    _effectiveConnectorPrefixes() {
        const configured = this.copilotConfig?.allowedConnectorPrefixes;
        if (configured && configured.length > 0)
            return configured;
        const mode = this.copilotConfig?.deploymentMode ?? 'copilot-studio-cloud';
        if (mode === 'copilot-studio-government' || mode === 'copilot-studio-sovereign') {
            return COPILOT_STUDIO_GOVERNMENT_DEFAULT_CONNECTOR_PREFIXES;
        }
        return [];
    }
}
//# sourceMappingURL=copilot-studio.js.map