#!/usr/bin/env node /** * Content-free policy evaluation for model-selected Slack answer delivery. * Fixture validation is offline. Provider trials require --live, never call * Slack, never print model output, and permit egress only to the selected host. */ import assert from 'node:assert/strict'; import { createHash } from 'node:crypto'; import { readFile } from 'node:fs/promises'; import { resolveModel } from '@flue/runtime/internal'; import { getApiProvider } from '@earendil-works/pi-ai/compat'; import { resolveRuntimeModel } from '../src/config/runtime-model.ts'; import { resolveOpenAiAuthMethod, saveOpenAiAuthMethod } from '../src/config/openai-auth.ts'; import { SqliteSettingsStore } from '../src/config/settings-store.ts'; import { activateModelCatalog } from '../src/model-catalog/catalog.ts'; import { parseModelCatalogBytes } from '../src/model-catalog/schema.ts'; import { SLACK_STREAM_ANSWER_ACKNOWLEDGEMENT, SLACK_STREAM_ANSWER_INSTRUCTION, SLACK_STREAM_ANSWER_TOOL_DESCRIPTION, SLACK_STREAM_ANSWER_TOOL_NAME, } from '../src/slack/presentation-intent.ts'; import { assessSlackStreamingDeclaration, evaluateSlackStreamingPolicy, parseSlackStreamingPolicyFixtureSet, } from '../src/slack/streaming-policy-evaluation.ts'; const args = parseArgs(process.argv.slice(2)); if (args.has('--help')) { console.log('Offline fixture check: npm run verify:slack-streaming-policy'); console.log( 'Live evaluation: npm run verify:slack-streaming-policy:live -- --live ' + '--lane --model ' + '[--trials <1-10>] [--state-db ] [--catalog-file ]', ); console.log('Live mode consumes quota, never calls Slack, and never prints model output.'); process.exit(0); } const fixtureFile = new URL('../fixtures/slack-streaming-policy.json', import.meta.url); const fixtureSet = parseSlackStreamingPolicyFixtureSet( JSON.parse(await readFile(fixtureFile, 'utf8')), ); if (args.has('--validate-fixtures')) { console.log(JSON.stringify({ ok: true, fixtureCount: fixtureSet.fixtures.length, expectations: countBy(fixtureSet.fixtures, (fixture) => fixture.expectation), categories: countBy(fixtureSet.fixtures, (fixture) => fixture.category), })); process.exit(0); } assert.equal(args.has('--live'), true, 'refusing model traffic without the explicit --live flag'); const lane = String(args.get('--lane') ?? ''); assert.ok( lane === 'subscription' || lane === 'openai-api-key' || lane === 'anthropic-api-key', '--lane must be subscription, openai-api-key, or anthropic-api-key', ); const modelId = String(args.get('--model') ?? ''); assert.match(modelId, /^[a-z0-9][a-z0-9._-]{0,127}$/, '--model must be a safe model id'); const trialsPerFixture = Number(args.get('--trials') ?? 1); assert.ok( Number.isSafeInteger(trialsPerFixture) && trialsPerFixture >= 1 && trialsPerFixture <= 10, '--trials must be an integer from 1 to 10', ); const provider = lane === 'anthropic-api-key' ? 'anthropic' : 'openai'; const canonicalModel = `${provider}/${modelId}`; const expectedHost = lane === 'subscription' ? 'chatgpt.com' : `api.${provider}.com`; const catalogFile = args.get('--catalog-file'); const hostedCatalog = typeof catalogFile === 'string' ? await readHostedCatalog(catalogFile) : undefined; const statePath = lane === 'subscription' ? String(args.get('--state-db') ?? '') : ':memory:'; if (lane === 'subscription') { assert.ok(statePath, '--state-db is required for a subscription evaluation'); } else { const keyName = provider === 'openai' ? 'OPENAI_API_KEY' : 'ANTHROPIC_API_KEY'; assert.ok(process.env[keyName], `${keyName} is required`); if (provider === 'openai') { assert.equal(process.env.OPENAI_BASE_URL ?? '', '', 'OPENAI_BASE_URL must be unset'); } else { assert.equal(process.env.ANTHROPIC_BASE_URL ?? '', '', 'ANTHROPIC_BASE_URL must be unset'); } } const nativeFetch = globalThis.fetch; const destinations = []; globalThis.fetch = async (input, init) => { const url = new URL( typeof input === 'string' ? input : input instanceof URL ? input.href : input.url, ); destinations.push(url.hostname); if (url.hostname !== expectedHost && !(lane === 'subscription' && url.hostname === 'auth.openai.com')) { throw new Error(`Slack streaming policy evaluation blocked unexpected host ${url.hostname}.`); } return nativeFetch(input, init); }; const settings = new SqliteSettingsStore(statePath); const priorOpenAiMethod = provider === 'openai' ? await resolveOpenAiAuthMethod(settings) : undefined; try { if (provider === 'openai') { await saveOpenAiAuthMethod(settings, lane === 'subscription' ? 'subscription' : 'api_key'); } const route = await resolveRuntimeModel('slack_streaming_policy_evaluation', canonicalModel, { settings, ...(hostedCatalog ? { loadCatalog: async () => { const activation = activateModelCatalog(hostedCatalog); return { status: activation.status === 'restart_required' ? 'restart_required' : 'activated', revision: activation.snapshot.revision, }; }, } : {}), }); const model = resolveModel(route.model); const api = getApiProvider(model.api); assert.ok(api, `API handler ${model.api} must be registered`); const options = { apiKey: lane === 'subscription' ? 'chickpea-boundary-managed' : process.env[provider === 'openai' ? 'OPENAI_API_KEY' : 'ANTHROPIC_API_KEY'], maxTokens: 2_000, }; const trials = []; let ordinal = 0; for (let repetition = 0; repetition < trialsPerFixture; repetition += 1) { for (const fixture of fixtureSet.fixtures) { const needsControl = fixture.expectation === 'clear_positive'; let offered; let controlProviderReadyMs; // Alternate pair order to reduce a simple warm-cache/order bias. if (needsControl && ordinal % 2 === 0) { controlProviderReadyMs = await runControl(api, model, options, fixture.prompt); offered = await runOffered(api, model, options, fixture.prompt); } else { offered = await runOffered(api, model, options, fixture.prompt); if (needsControl) { controlProviderReadyMs = await runControl(api, model, options, fixture.prompt); } } trials.push({ fixtureId: fixture.id, expectation: fixture.expectation, category: fixture.category, ...offered, ...(controlProviderReadyMs === undefined ? {} : { controlProviderReadyMs }), }); ordinal += 1; } } const report = evaluateSlackStreamingPolicy(trials); assert.ok(destinations.includes(expectedHost), `model traffic must reach ${expectedHost}`); assert.equal( destinations.every((host) => host === expectedHost || (lane === 'subscription' && host === 'auth.openai.com') ), true, ); console.log(JSON.stringify({ ok: report.pass, lane, canonicalModel, runtimeModel: route.model, trialsPerFixture, report, })); if (!report.pass) process.exitCode = 1; } finally { globalThis.fetch = nativeFetch; if (priorOpenAiMethod) await saveOpenAiAuthMethod(settings, priorOpenAiMethod); settings.close(); } async function runOffered(api, model, options, prompt) { const startedAt = performance.now(); const userMessage = { role: 'user', content: prompt, timestamp: Date.now() }; const first = await consumeTurn(api, model, { systemPrompt: `You are Chickpea, a helpful Slack agent. ${SLACK_STREAM_ANSWER_INSTRUCTION}`, messages: [userMessage], tools: [{ name: SLACK_STREAM_ANSWER_TOOL_NAME, description: SLACK_STREAM_ANSWER_TOOL_DESCRIPTION, parameters: { type: 'object', properties: {}, required: [], additionalProperties: false }, }], }, options); const toolCalls = first.message.content.filter((part) => part.type === 'toolCall'); const declaration = assessSlackStreamingDeclaration(toolCalls); const { declared, declarationShapeValid } = declaration; if (!declared) { return { declared: false, declarationBeforeText: true, declarationShapeValid: true, contaminationDetected: containsDeliveryContamination(first.text), offeredProviderReadyMs: first.completedAt - startedAt, }; } if (!declarationShapeValid || !declaration.declarationId) { return { declared: true, declarationBeforeText: first.text.length === 0, declarationShapeValid: false, contaminationDetected: containsDeliveryContamination(first.text), offeredProviderReadyMs: (first.firstTextAt ?? first.completedAt) - startedAt, declarationMs: (first.declarationAt ?? first.completedAt) - startedAt, }; } const continuation = await consumeTurn(api, model, { systemPrompt: `You are Chickpea, a helpful Slack agent. ${SLACK_STREAM_ANSWER_INSTRUCTION}`, messages: [ userMessage, first.message, { role: 'toolResult', toolCallId: declaration.declarationId, toolName: SLACK_STREAM_ANSWER_TOOL_NAME, content: [{ type: 'text', text: SLACK_STREAM_ANSWER_ACKNOWLEDGEMENT }], isError: false, timestamp: Date.now(), }, ], }, options); return { declared: true, declarationBeforeText: first.text.length === 0, declarationShapeValid, contaminationDetected: containsDeliveryContamination(continuation.text), offeredProviderReadyMs: (continuation.firstTextAt ?? continuation.completedAt) - startedAt, declarationMs: (first.declarationAt ?? first.completedAt) - startedAt, }; } async function runControl(api, model, options, prompt) { const startedAt = performance.now(); const turn = await consumeTurn(api, model, { systemPrompt: 'You are Chickpea, a helpful Slack agent. Answer the user directly.', messages: [{ role: 'user', content: prompt, timestamp: Date.now() }], }, options); // Terminal delivery is provider-ready only after the full model turn completes. return turn.completedAt - startedAt; } async function consumeTurn(api, model, context, options) { const stream = api.stream(model, context, options); let firstTextAt; let declarationAt; let message; for await (const event of stream) { if (event.type === 'text_delta' && event.delta && firstTextAt === undefined) { firstTextAt = performance.now(); } if (event.type === 'toolcall_end' && event.toolCall.name === SLACK_STREAM_ANSWER_TOOL_NAME && declarationAt === undefined) { declarationAt = performance.now(); } if (event.type === 'done') message = event.message; if (event.type === 'error') { throw new Error(event.error.errorMessage ?? 'Model evaluation turn failed.'); } } assert.ok(message, 'Model evaluation turn ended without a result'); return { message, text: message.content .filter((part) => part.type === 'text') .map((part) => part.text) .join(''), firstTextAt, declarationAt, completedAt: performance.now(), }; } function containsDeliveryContamination(text) { return /stream_answer|delivery preference noted|(?:I am|I'm|Slack is) streaming (?:this|the) answer/i .test(text); } function countBy(values, keyFor) { const result = {}; for (const value of values) { const key = keyFor(value); result[key] = (result[key] ?? 0) + 1; } return result; } function parseArgs(values) { const result = new Map(); for (let index = 0; index < values.length; index += 1) { const value = values[index]; if (!value?.startsWith('--')) continue; const next = values[index + 1]; if (next && !next.startsWith('--')) { result.set(value, next); index += 1; } else { result.set(value, true); } } return result; } async function readHostedCatalog(file) { const bytes = new Uint8Array(await readFile(file)); return { document: parseModelCatalogBytes(bytes), sha256: createHash('sha256').update(bytes).digest('hex'), }; }