import fs from 'node:fs/promises' import { createHash } from 'node:crypto' import { buildHubLauncherAdminPlan } from './adapters/hub-launcher-admin.mjs' import { buildOpsProductPlan } from './adapters/ops-product-api.mjs' import { resolveAssistantAction } from './assistant-action-resolver.mjs' import { validateCatalog } from './validate.mjs' const WRITE_METHODS = new Set(['PATCH', 'POST', 'PUT']) const HUB_LAUNCHER_ADMIN_ADAPTER_ID = 'adapter.hub.launcher_admin_api' const OPS_PRODUCT_API_ADAPTER_ID = 'adapter.ops.product_api' const ASSISTANT_GATEWAY_NAME = 'ontology-core' const CONFIRMATION_ENVELOPE_VERSION = 'assistant-write-confirmation-v1' const ALLOWED_ROUTES = [ { method: 'GET', pattern: /^\/api\/admin\/control-plane$/ }, { method: 'PATCH', pattern: /^\/api\/admin\/users\/[^/?#]+\/profile$/ }, { method: 'PATCH', pattern: /^\/api\/admin\/memberships\/[^/?#]+$/ }, ] const OPS_ALLOWED_ROUTES = [ { method: 'GET', pattern: /^\/api\/v1\/tools\/issues$/ }, { method: 'POST', pattern: /^\/api\/v1\/tools\/issues$/ }, { method: 'POST', pattern: /^\/api\/v1\/tools\/issues\/[^/?#]+\/comments$/ }, ] function headerValue(headers, key) { const wanted = key.toLowerCase() for (const [candidateKey, value] of Object.entries(headers || {})) { if (candidateKey.toLowerCase() === wanted) return value } return null } function normalizeMethod(method) { return String(method || '').trim().toUpperCase() } function isAllowedRoute(method, path) { return ALLOWED_ROUTES.some((route) => route.method === method && route.pattern.test(path)) } function isAllowedOpsRoute(method, path) { return OPS_ALLOWED_ROUTES.some((route) => route.method === method && route.pattern.test(path)) } function appendQuery(url, query = {}) { for (const [key, value] of Object.entries(query || {})) { if (value === null || value === undefined || value === '') continue url.searchParams.set(key, String(value)) } } function buildRequestUrl(baseUrl, request) { if (!baseUrl) throw new Error('baseUrl is required for network execution') const url = new URL(request.path, baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`) appendQuery(url, request.query) return url } function readResponseHeader(headers, key) { if (!headers) return '' if (typeof headers.get === 'function') return headers.get(key) || '' return headerValue(headers, key) || '' } function normalizeHeaderString(value) { return String(value || '').trim() } function stableJson(value) { if (Array.isArray(value)) { return `[${value.map((item) => stableJson(item)).join(',')}]` } if (value && typeof value === 'object') { return `{${Object.keys(value) .sort() .map((key) => `${JSON.stringify(key)}:${stableJson(value[key])}`) .join(',')}}` } return JSON.stringify(value) } function confirmationDigest(payload) { return createHash('sha256').update(stableJson(payload)).digest('hex').slice(0, 24) } function buildWriteConfirmationEnvelope(plan) { const request = plan?.request || {} const method = normalizeMethod(request.method) if (!WRITE_METHODS.has(method)) return null const action = plan?.actionDecision?.action || {} const actor = plan?.gatewayActor || {} const payload = { version: CONFIRMATION_ENVELOPE_VERSION, actionId: action.id || null, app: action.app || null, adapterId: plan?.adapterId || null, actor: { userId: actor.userId || null, email: actor.email || null, subject: actor.subject || null, }, request: { method, path: String(request.path || ''), body: request.body ?? null, idempotencyKey: headerValue(request.headers, 'Idempotency-Key') || null, }, } return { ...payload, token: confirmationDigest(payload), riskLevel: action.riskLevel || null, confirmationMode: action.confirmationMode || null, expectedEffect: action.summary || request.summary || null, summary: request.summary || null, } } function readConfirmationToken(input = {}, options = {}) { return normalizeHeaderString( options.confirmationToken || input.confirmationToken || input.confirmation?.token || input.confirmationEnvelope?.token, ) } function verifyWriteConfirmationEnvelope(input, options, envelope) { if (!envelope) return { ok: true } const providedToken = readConfirmationToken(input, options) if (!providedToken) { return { ok: false, reason: 'write_confirmation_envelope_missing', } } if (providedToken !== envelope.token) { return { ok: false, reason: 'write_confirmation_envelope_mismatch', } } return { ok: true } } function buildBearerHeader(token) { const value = normalizeHeaderString(token) if (!value) return '' return /^Bearer\s+/i.test(value) ? value : `Bearer ${value}` } function buildHubLauncherGatewayHeaders(plan, options = {}) { if (plan?.adapterId !== HUB_LAUNCHER_ADMIN_ADAPTER_ID) { return { ok: true, headers: {} } } const token = normalizeHeaderString(options.launcherInternalToken || options.internalAccessToken) if (!token) { return { ok: false, reason: 'launcher_internal_token_missing', } } const actor = options.gatewayActor || plan.gatewayActor || {} const actorUserId = normalizeHeaderString(actor.userId) const actorEmail = normalizeHeaderString(actor.email) const actorSubject = normalizeHeaderString(actor.subject) if (!actorUserId && !actorEmail && !actorSubject) { return { ok: false, reason: 'assistant_gateway_actor_missing', } } return { ok: true, headers: { Authorization: buildBearerHeader(token), 'X-NODEDC-Assistant-Gateway': ASSISTANT_GATEWAY_NAME, ...(actorUserId ? { 'X-NODEDC-Assistant-Actor-User-Id': actorUserId } : {}), ...(actorEmail ? { 'X-NODEDC-Assistant-Actor-Email': actorEmail } : {}), ...(actorSubject ? { 'X-NODEDC-Assistant-Actor-Subject': actorSubject } : {}), }, } } function endpointOrigin(value) { try { return new URL(value).origin } catch { return '' } } function firstArray(...values) { for (const value of values) { if (Array.isArray(value)) return value } return [] } function entitlementOpsGrant(payload) { const grants = payload?.appGrants || payload?.grant || payload?.appGrant || payload?.entitlement || payload?.entitlements if (grants?.ops && typeof grants.ops === 'object') return grants.ops if (payload?.ops && typeof payload.ops === 'object') return payload.ops return null } function authorizationFromOpsGrant(grant) { const servers = firstArray(grant?.mcpServers, grant?.mcp_servers) for (const server of servers) { const headers = server?.httpHeaders || server?.headers || {} const authorization = normalizeHeaderString(headerValue(headers, 'Authorization')) if (authorization) return authorization } return '' } function omitEmptyObject(value = {}) { const out = {} for (const [key, raw] of Object.entries(value || {})) { const text = normalizeHeaderString(raw) if (text) out[key] = text } return out } async function resolveOpsAuthorization(plan, options = {}) { const explicit = buildBearerHeader(options.opsRunToken || options.opsGatewayToken) if (explicit) return { ok: true, authorization: explicit } const entitlementUrl = normalizeHeaderString(options.opsEntitlementUrl) const entitlementAuthorization = normalizeHeaderString( options.opsEntitlementAuthorization || buildBearerHeader(options.opsEntitlementToken), ) if (!entitlementUrl || !entitlementAuthorization) { return { ok: false, reason: 'ops_entitlement_not_configured', } } const actor = options.gatewayActor || plan.gatewayActor || {} const opsContext = plan.opsContext || {} const fetchImpl = options.fetchImpl || globalThis.fetch if (typeof fetchImpl !== 'function') { throw new Error('No fetch implementation available') } const owner = omitEmptyObject({ userId: actor.userId, email: actor.email, ownerKey: actor.subject, }) const ops = omitEmptyObject({ workspaceSlug: opsContext.workspaceSlug, projectId: opsContext.projectId, }) const response = await fetchImpl(entitlementUrl, { method: 'POST', headers: { Accept: 'application/json', 'Content-Type': 'application/json', Authorization: entitlementAuthorization, }, body: JSON.stringify({ schemaVersion: 'ai-workspace.entitlement-request.v1', appId: 'ops', owner, runContext: { ...(Object.keys(ops).length ? { ops } : {}), ...omitEmptyObject({ opsWorkspaceSlug: opsContext.workspaceSlug, opsProjectId: opsContext.projectId, }), }, activeContext: {}, requestedAt: new Date().toISOString(), }), }) const payload = await readResponsePayload(response) if (!response.ok || payload?.ok === false) { return { ok: false, reason: payload?.error || payload?.message || `ops_entitlement_http_${response.status}`, } } const grant = entitlementOpsGrant(payload) const authorization = authorizationFromOpsGrant(grant) if (!authorization) { return { ok: false, reason: grant?.status || 'ops_entitlement_token_missing', grantStatus: grant?.status || null, grantContext: grant?.context || null, } } return { ok: true, authorization } } async function buildGatewayHeaders(plan, options = {}) { if (plan?.adapterId === HUB_LAUNCHER_ADMIN_ADAPTER_ID) { return buildHubLauncherGatewayHeaders(plan, options) } if (plan?.adapterId === OPS_PRODUCT_API_ADAPTER_ID) { const resolved = await resolveOpsAuthorization(plan, options) if (!resolved.ok) return resolved return { ok: true, headers: { Authorization: resolved.authorization, Accept: 'application/json', }, } } return { ok: true, headers: {} } } async function readResponsePayload(response) { const raw = typeof response.text === 'function' ? await response.text() : '' const contentType = readResponseHeader(response.headers, 'content-type') if (!raw) return null if (contentType.includes('application/json')) { try { return JSON.parse(raw) } catch { return raw } } return raw } function planActionId(plan) { return String(plan?.actionDecision?.action?.id || '').trim() } function dataArray(payload, key) { const value = payload?.data?.[key] return Array.isArray(value) ? value : [] } function isPendingLikeStatus(value) { const status = String(value || '').trim().toLowerCase() return ['new', 'pending', 'requested', 'open', 'waiting', 'submitted'].includes(status) } function compactAccessRequest(item = {}) { return { id: item.id || null, email: item.email || null, name: [item.lastName, item.firstName, item.middleName].filter(Boolean).join(' ').trim() || null, company: item.company || null, phone: item.phone || null, status: item.status || null, targetClientId: item.targetClientId || null, role: item.role || null, createdAt: item.createdAt || null, updatedAt: item.updatedAt || null, } } function compactInvite(item = {}) { return { id: item.id || null, email: item.email || item.inviteeEmail || null, status: item.status || null, clientId: item.clientId || item.targetClientId || null, role: item.role || null, createdAt: item.createdAt || null, updatedAt: item.updatedAt || null, } } function compactUser(item = {}) { return { id: item.id || null, name: item.name || null, email: item.email || null, globalStatus: item.globalStatus || null, } } function compactMembership(item = {}) { return { id: item.id || null, clientId: item.clientId || null, userId: item.userId || null, role: item.role || null, status: item.status || null, coreAssistantRole: item.coreAssistantRole || null, } } function firstIssueArray(payload) { return firstArray( payload?.issues, payload?.items, payload?.results, payload?.data?.issues, payload?.data?.items, payload?.data?.results, Array.isArray(payload?.data) ? payload.data : null, ) } function firstObject(...values) { for (const value of values) { if (value && typeof value === 'object' && !Array.isArray(value)) return value } return null } function issueTimestamp(item = {}) { const value = item.updatedAt || item.updated_at || item.createdAt || item.created_at || item.created || item.updated const time = Date.parse(value) return Number.isFinite(time) ? time : 0 } function compactIssue(item = {}) { const state = item.state && typeof item.state === 'object' ? item.state : null const project = item.project && typeof item.project === 'object' ? item.project : null return { id: item.id || item.issue_id || null, identifier: item.identifier || item.sequence_id || item.issueIdentifier || item.issue_identifier || null, title: item.title || item.name || item.subject || null, description: item.description || null, state: state?.name || item.stateName || item.state_name || item.state || null, priority: item.priority || null, projectId: item.project_id || item.projectId || project?.id || null, projectName: item.project_name || item.projectName || project?.name || null, createdAt: item.createdAt || item.created_at || item.created || null, updatedAt: item.updatedAt || item.updated_at || item.updated || null, url: item.url || item.web_url || item.webUrl || null, } } function compactComment(item = {}) { return { id: item.id || item.comment_id || item.commentId || null, issueId: item.issue_id || item.issueId || item.issue?.id || null, body: item.body || item.comment || item.text || null, createdAt: item.createdAt || item.created_at || item.created || null, updatedAt: item.updatedAt || item.updated_at || item.updated || null, } } function normalizeOpsPayload(plan, payload) { if (!payload || typeof payload !== 'object') return payload const actionId = planActionId(plan) if (actionId === 'ops.card.create') { const issue = firstObject( payload.issue, payload.card, payload.data?.issue, payload.data?.card, payload.data, ) return { card: issue ? compactIssue(issue) : null, source: 'ops-agent-gateway', context: plan.opsContext || null, rawOk: payload.ok ?? null, } } if (actionId === 'ops.card.add_comment') { const comment = firstObject( payload.comment, payload.issueComment, payload.data?.comment, payload.data?.issueComment, payload.data, ) return { comment: comment ? compactComment(comment) : null, source: 'ops-agent-gateway', context: plan.opsContext || null, rawOk: payload.ok ?? null, } } if (actionId !== 'ops.card.list_recent') return payload const limit = Number(plan?.opsContext?.limit || 5) const issues = firstIssueArray(payload) .slice() .sort((a, b) => issueTimestamp(b) - issueTimestamp(a)) .slice(0, Math.max(1, Math.min(20, Number.isFinite(limit) ? limit : 5))) .map(compactIssue) return { counts: { cards: issues.length, }, latestCard: issues[0] || null, cards: issues, source: 'ops-agent-gateway', context: plan.opsContext || null, } } function normalizeExecutionPayload(plan, payload) { if (plan?.adapterId === OPS_PRODUCT_API_ADAPTER_ID) { return normalizeOpsPayload(plan, payload) } return normalizeControlPlanePayload(plan, payload) } function normalizeControlPlanePayload(plan, payload) { if (!payload || typeof payload !== 'object') return payload const actionId = planActionId(plan) const counts = payload.counts && typeof payload.counts === 'object' ? payload.counts : {} const accessRequests = dataArray(payload, 'accessRequests') const pendingAccessRequests = accessRequests.filter((item) => isPendingLikeStatus(item?.status)) const engineRequests = dataArray(payload, 'engineWorkflowAccessRequests') const pendingEngineRequests = engineRequests.filter((item) => isPendingLikeStatus(item?.status)) const invites = dataArray(payload, 'invites') const pendingInvites = invites.filter((item) => isPendingLikeStatus(item?.status)) if (actionId === 'hub.access_request.list_pending') { return { actor: payload.actor || null, counts: { accessRequests: counts.accessRequests ?? accessRequests.length, engineWorkflowAccessRequests: counts.engineWorkflowAccessRequests ?? engineRequests.length, }, summary: { accessRequests: accessRequests.length, pendingAccessRequests: pendingAccessRequests.length, engineWorkflowAccessRequests: engineRequests.length, pendingEngineWorkflowAccessRequests: pendingEngineRequests.length, newAccessRequests: pendingAccessRequests.map((item) => item.email).filter(Boolean), }, accessRequests: pendingAccessRequests.map(compactAccessRequest), engineWorkflowAccessRequests: pendingEngineRequests.map(compactAccessRequest), } } if (actionId === 'hub.invite.list_pending') { return { actor: payload.actor || null, counts: { invites: counts.invites ?? invites.length, }, summary: { invites: invites.length, pendingInvites: pendingInvites.length, pendingInviteEmails: pendingInvites.map((item) => item.email || item.inviteeEmail).filter(Boolean), }, invites: pendingInvites.map(compactInvite), } } if (actionId === 'hub.user.read_admin_summary') { return { actor: payload.actor || null, counts, summary: { users: counts.users ?? dataArray(payload, 'users').length, memberships: counts.memberships ?? dataArray(payload, 'memberships').length, services: counts.services ?? dataArray(payload, 'services').length, grants: counts.grants ?? dataArray(payload, 'grants').length, invites: counts.invites ?? invites.length, accessRequests: counts.accessRequests ?? accessRequests.length, pendingAccessRequests: pendingAccessRequests.length, newAccessRequests: pendingAccessRequests.map((item) => item.email).filter(Boolean), }, users: dataArray(payload, 'users').map(compactUser), memberships: dataArray(payload, 'memberships').map(compactMembership), accessRequests: pendingAccessRequests.map(compactAccessRequest), } } return payload } export function validateExecutionPlan(plan) { const errors = [] const warnings = [] const request = plan?.request || {} const method = normalizeMethod(request.method) const path = String(request.path || '') const adapterId = plan?.adapterId || plan?.actionDecision?.action?.adapterId || '' if (!plan?.ok) { errors.push('plan_not_ok') } if (!method) { errors.push('request_method_missing') } if (method === 'DELETE') { errors.push('delete_method_forbidden') } if (adapterId === OPS_PRODUCT_API_ADAPTER_ID) { if (!path.startsWith('/api/v1/tools/')) { errors.push('request_path_not_ops_tools_api') } if (!['GET', 'POST'].includes(method)) { errors.push(`method_not_allowed:${method || 'empty'}`) } if (method && path && !isAllowedOpsRoute(method, path)) { errors.push(`route_not_allowlisted:${method} ${path}`) } if (method === 'GET' && !request.query?.project_id) { errors.push('ops_project_id_missing') } if (method === 'POST' && !request.body?.project_id) { errors.push('ops_project_id_missing') } } else { if (!path.startsWith('/api/admin/')) { errors.push('request_path_not_admin_api') } if (!['GET', 'PATCH'].includes(method)) { errors.push(`method_not_allowed:${method || 'empty'}`) } if (method && path && !isAllowedRoute(method, path)) { errors.push(`route_not_allowlisted:${method} ${path}`) } } if (WRITE_METHODS.has(method) && !headerValue(request.headers, 'Idempotency-Key')) { errors.push('write_requires_idempotency_key') } if (WRITE_METHODS.has(method) && plan?.safety?.confirmationAlreadyChecked !== true) { errors.push('write_requires_prior_confirmation_check') } if (plan?.actionDecision?.action?.riskLevel === 'destructive') { errors.push('destructive_action_forbidden') } if (plan?.actionDecision?.decision === 'forbidden') { errors.push('forbidden_action_decision') } if (plan?.safety?.noDeleteRouteMapped !== true) { warnings.push('no_delete_route_mapping_not_asserted') } if (plan?.safety?.sourceGuardedRoutesOnly !== true) { warnings.push('source_guarded_routes_only_not_asserted') } return { ok: errors.length === 0, errors, warnings, method, path, } } async function executeHttpPlan(plan, options) { const request = plan.request const method = normalizeMethod(request.method) const url = buildRequestUrl(options.baseUrl, request) const headers = { ...(request.headers || {}), ...(options.headers || {}), } const init = { method, headers, } if (method !== 'GET' && request.body !== undefined) { if (!headerValue(headers, 'Content-Type')) { headers['Content-Type'] = 'application/json' } if (!headerValue(headers, 'Accept')) { headers.Accept = 'application/json' } init.body = JSON.stringify(request.body) } const fetchImpl = options.fetchImpl || globalThis.fetch if (typeof fetchImpl !== 'function') { throw new Error('No fetch implementation available') } const response = await fetchImpl(url, init) const payload = normalizeExecutionPayload(plan, await readResponsePayload(response)) return { ok: Boolean(response.ok), status: response.status, statusText: response.statusText || '', url: String(url), method, payload, } } async function buildAssistantExecutionPlan(input = {}, options = {}) { const decision = options.decision || await resolveAssistantAction(input, options) const adapterId = decision?.action?.adapterId if (adapterId === OPS_PRODUCT_API_ADAPTER_ID) { return buildOpsProductPlan(input, { ...options, decision }) } return buildHubLauncherAdminPlan(input, { ...options, decision }) } function executionBaseUrlForPlan(plan, options = {}) { if (plan?.adapterId === OPS_PRODUCT_API_ADAPTER_ID) { return normalizeHeaderString(options.opsGatewayBaseUrl) || endpointOrigin(options.opsEntitlementUrl) } return normalizeHeaderString(options.baseUrl) } export async function executeAssistantAction(input = {}, options = {}) { const mode = options.mode || 'dry-run' const plan = options.plan || await buildAssistantExecutionPlan(input, options) const safety = validateExecutionPlan(plan) const confirmationEnvelope = buildWriteConfirmationEnvelope(plan) if (!safety.ok) { return { ok: false, decision: 'execution_blocked', reason: 'execution_safety_check_failed', safety, confirmationEnvelope, plan, } } if (mode !== 'execute') { return { ok: true, decision: 'dry_run', reason: 'network_execution_not_requested', safety, confirmationEnvelope, plan, result: null, } } if (!options.allowNetworkExecution) { return { ok: false, decision: 'execution_blocked', reason: 'network_execution_not_enabled', safety, confirmationEnvelope, plan, } } if ((plan.dryRunOnly || plan.adapterStatus !== 'implemented') && !options.allowPlannedAdapterExecution) { return { ok: false, decision: 'execution_blocked', reason: 'adapter_not_marked_implemented', safety, confirmationEnvelope, plan, } } const baseUrl = executionBaseUrlForPlan(plan, options) if (!baseUrl) { return { ok: false, decision: 'execution_blocked', reason: plan?.adapterId === OPS_PRODUCT_API_ADAPTER_ID ? 'ops_gateway_base_url_missing' : 'base_url_missing', safety, confirmationEnvelope, plan, } } const confirmationCheck = verifyWriteConfirmationEnvelope(input, options, confirmationEnvelope) if (!confirmationCheck.ok) { return { ok: false, decision: 'execution_blocked', reason: confirmationCheck.reason, safety, confirmationEnvelope, plan, } } const gatewayHeaders = await buildGatewayHeaders(plan, options) if (!gatewayHeaders.ok) { return { ok: false, decision: 'execution_blocked', reason: gatewayHeaders.reason, safety, confirmationEnvelope, plan, } } const result = await executeHttpPlan(plan, { ...options, baseUrl, headers: { ...(options.headers || {}), ...gatewayHeaders.headers, }, }) return { ok: result.ok, decision: result.ok ? 'executed' : 'execution_failed', safety, confirmationEnvelope, plan, result, } } async function readInputJson() { const inputIndex = process.argv.indexOf('--input-json') if (inputIndex >= 0) { const inputPath = process.argv[inputIndex + 1] if (!inputPath) throw new Error('--input-json requires a path') return JSON.parse(await fs.readFile(inputPath, 'utf8')) } const inlineIndex = process.argv.indexOf('--input') if (inlineIndex >= 0) { return JSON.parse(process.argv[inlineIndex + 1] || '{}') } return null } function readArg(name) { const index = process.argv.indexOf(name) if (index === -1) return null return process.argv[index + 1] || null } function assertSmoke(condition, message) { if (!condition) throw new Error(message) } async function runSmoke() { const validation = await validateCatalog() assertSmoke(validation.ok, 'catalog must validate before executor smoke') const blockInput = { actionId: 'hub.user.block', assistantRole: 'admin', membershipRole: 'client_admin', actorUserId: 'user_root', targetUserId: 'user_pupa', confirmed: true, idempotencyKey: 'smoke:execute:block:user_pupa', } const dryRun = await executeAssistantAction(blockInput) const executeWithoutConfirmation = await executeAssistantAction(blockInput, { mode: 'execute', allowNetworkExecution: true, baseUrl: 'http://launcher.local.test', }) const confirmedBlockInput = { ...blockInput, confirmationToken: dryRun.confirmationEnvelope?.token, } const executeBlocked = await executeAssistantAction(confirmedBlockInput, { mode: 'execute', allowNetworkExecution: true, baseUrl: 'http://launcher.local.test', }) const mockCalls = [] const mockFetch = async (url, init) => { mockCalls.push({ url: String(url), init }) return { ok: true, status: 200, statusText: 'OK', headers: { get: () => 'application/json' }, text: async () => JSON.stringify({ ok: true, source: 'mock-launcher' }), } } const mockExecuted = await executeAssistantAction(confirmedBlockInput, { mode: 'execute', allowNetworkExecution: true, baseUrl: 'http://launcher.local.test', launcherInternalToken: 'smoke-token', fetchImpl: mockFetch, }) const deleteBlocked = await executeAssistantAction({ actionId: 'hub.user.delete', assistantRole: 'admin', membershipRole: 'client_admin', actorUserId: 'user_root', targetUserId: 'user_pupa', confirmed: true, }, { mode: 'execute', allowNetworkExecution: true, allowPlannedAdapterExecution: true, baseUrl: 'http://launcher.local.test', fetchImpl: mockFetch, }) const badDeletePlanSafety = validateExecutionPlan({ ok: true, adapterStatus: 'implemented', dryRunOnly: false, safety: { noDeleteRouteMapped: false, sourceGuardedRoutesOnly: false, confirmationAlreadyChecked: true, }, actionDecision: { decision: 'allowed', action: { riskLevel: 'privileged' }, }, request: { method: 'DELETE', path: '/api/admin/users/user_pupa', headers: { 'Idempotency-Key': 'bad-delete' }, }, }) assertSmoke(dryRun.decision === 'dry_run', 'default executor mode must be dry_run') assertSmoke(Boolean(dryRun.confirmationEnvelope?.token), 'dry-run write should return confirmation envelope token') assertSmoke(executeWithoutConfirmation.reason === 'write_confirmation_envelope_missing', 'write execution must require confirmation envelope token') assertSmoke(executeBlocked.reason === 'launcher_internal_token_missing', 'confirmed implemented gateway adapter must require an internal token') assertSmoke(mockExecuted.decision === 'executed', 'mock execution should execute with gateway auth headers') assertSmoke(mockCalls.length === 1, 'mock execution should issue exactly one request') assertSmoke(mockCalls[0].init.method === 'PATCH', 'mock execution should use PATCH') assertSmoke(mockCalls[0].url === 'http://launcher.local.test/api/admin/users/user_pupa/profile', 'mock execution should hit profile route') assertSmoke(mockCalls[0].init.headers.Authorization === 'Bearer smoke-token', 'mock execution should send internal bearer token') assertSmoke(mockCalls[0].init.headers['X-NODEDC-Assistant-Gateway'] === ASSISTANT_GATEWAY_NAME, 'mock execution should send assistant gateway header') assertSmoke(mockCalls[0].init.headers['X-NODEDC-Assistant-Actor-User-Id'] === 'user_root', 'mock execution should send actor user id') assertSmoke(deleteBlocked.decision === 'execution_blocked', 'delete action must block before network execution') assertSmoke(!badDeletePlanSafety.ok && badDeletePlanSafety.errors.includes('delete_method_forbidden'), 'DELETE plan must fail safety check') return { ok: true, validation: validation.counts, cases: { dryRun, executeWithoutConfirmation, executeBlocked, mockExecuted, deleteBlocked, badDeletePlanSafety, }, } } if (import.meta.url === `file://${process.argv[1]}`) { if (process.argv.includes('--smoke')) { console.log(JSON.stringify(await runSmoke(), null, 2)) } else { const input = await readInputJson() if (!input) throw new Error('Use --smoke, --input , or --input-json ') const allowPlannedAdapterExecution = process.argv.includes('--allow-planned-adapter-execution') && process.env.NDC_ASSISTANT_EXECUTOR_UNSAFE_DEV === '1' const output = await executeAssistantAction(input, { mode: process.argv.includes('--execute') ? 'execute' : 'dry-run', allowNetworkExecution: process.argv.includes('--execute'), allowPlannedAdapterExecution, baseUrl: readArg('--base-url') || process.env.NDC_LAUNCHER_BASE_URL || null, confirmationToken: readArg('--confirmation-token') || null, launcherInternalToken: readArg('--launcher-internal-token') || readArg('--internal-token') || process.env.NDC_LAUNCHER_INTERNAL_ACCESS_TOKEN || process.env.NODEDC_INTERNAL_ACCESS_TOKEN || process.env.NODEDC_PLATFORM_SERVICE_TOKEN || null, }) console.log(JSON.stringify(output, null, 2)) } }