diff --git a/src/main/prompt-builder/api-docs/approvals.en.md b/src/main/prompt-builder/api-docs/approvals.en.md index bc74059c..0fc392bb 100644 --- a/src/main/prompt-builder/api-docs/approvals.en.md +++ b/src/main/prompt-builder/api-docs/approvals.en.md @@ -1,7 +1,7 @@ ### Request an Approval (when you need human/executive permission) POST /api/companies/{{companyId}}/approvals Body: { - "type": "secret_access|hire_agent|approve_ceo_strategy|dangerous_command", + "type": "{{approvalTypes}}", "relatedTaskId": "{taskId — what task is this for}", "requestedByAgentId": "{{agentId}}", "payloadSummary": "Clear description of what you need and why", @@ -10,10 +10,10 @@ Body: { "workProductIds": ["{workProductUuid}"] } Approval types: - - secret_access: Need API keys, credentials, or account access - - hire_agent: Proposing a new team member - - approve_ceo_strategy: CEO submitting org strategy for Board review - - dangerous_command: Irreversible action that needs human sign-off +{{approvalTypeList}} + +Co-Founder mandatory review types: +{{cofounderMandatoryApprovalTypes}} Attaching artifacts: - `documentIds` — UUIDs of existing company documents. The board inspector diff --git a/src/main/prompt-builder/api-section.ts b/src/main/prompt-builder/api-section.ts index baedb628..ca68218b 100644 --- a/src/main/prompt-builder/api-section.ts +++ b/src/main/prompt-builder/api-section.ts @@ -1,5 +1,7 @@ import type { PromptContext } from "./index"; import { buildSocialSection } from "./api-endpoints"; +import { approvalTypeEnum, type ApprovalType } from "@shared/types"; +import { COFOUNDER_MANDATORY_APPROVAL_TYPES } from "../execution-policy"; import headerEn from "./api-docs/header.en.md?raw"; import agentControlEn from "./api-docs/agent-control.en.md?raw"; @@ -60,6 +62,40 @@ const SECTIONS_BY_ROLE: Record = { ], }; +const APPROVAL_TYPE_DESCRIPTIONS: Record = { + hire_agent: "Propose a new team member", + approve_ceo_strategy: "CEO submits org strategy for Board review", + dangerous_command: "Irreversible command or destructive operation", + secret_access: "Need API keys, credentials, or account access", + budget_request: "Request budget for planned spend", + promotion: "Propose role or seniority advancement", + termination: "Propose ending an agent's role", + vendor_approval: "Approve vendor, SaaS, or service purchase", + document_review: "Request review of an existing document", + production_deploy: "Approve deployment to production", + incident_escalation: "Escalate a critical incident for human decision", + social_post: "Approve a social media post before publishing", + deliverable_review: "Approve a completed deliverable or spec", + budget_override: "Override a budget gate for urgent work", + plan_review: "Review a plan before execution", + scope_review: "Review proposed scope before work proceeds", + draft_review: "Review a drafted document or artifact", + strategy_review: "Review strategy, project brief, or proposal", + exception_review: "Review an exception to normal policy", +}; + +function renderApprovalTypeList(): string { + return approvalTypeEnum.options + .map((type) => ` - ${type}: ${APPROVAL_TYPE_DESCRIPTIONS[type]}`) + .join("\n"); +} + +function renderCofounderMandatoryApprovalTypes(): string { + return COFOUNDER_MANDATORY_APPROVAL_TYPES + .map((type) => ` - ${type}`) + .join("\n"); +} + function render(template: string, ctx: PromptContext): string { const connectorIds = ctx.allConnectors && ctx.allConnectors.length > 0 ? ctx.allConnectors.map((c) => c.id).join("|") @@ -69,7 +105,10 @@ function render(template: string, ctx: PromptContext): string { .replace(/\{\{companyId\}\}/g, ctx.agent.companyId) .replace(/\{\{agentId\}\}/g, ctx.agent.id) .replace(/\{\{runId\}\}/g, ctx.runId) - .replace(/\{\{connectorIds\}\}/g, connectorIds); + .replace(/\{\{connectorIds\}\}/g, connectorIds) + .replace(/\{\{approvalTypes\}\}/g, approvalTypeEnum.options.join("|")) + .replace(/\{\{approvalTypeList\}\}/g, renderApprovalTypeList()) + .replace(/\{\{cofounderMandatoryApprovalTypes\}\}/g, renderCofounderMandatoryApprovalTypes()); } export function buildAPISection(ctx: PromptContext, audience: ApiAudience): string { diff --git a/tests/eval/approval-api-docs-contract.test.ts b/tests/eval/approval-api-docs-contract.test.ts new file mode 100644 index 00000000..2973a9a0 --- /dev/null +++ b/tests/eval/approval-api-docs-contract.test.ts @@ -0,0 +1,23 @@ +import { describe, expect, it } from "vitest"; +import { approvalTypeEnum } from "../../src/shared/types/enums"; +import { COFOUNDER_MANDATORY_APPROVAL_TYPES } from "../../src/main/execution-policy"; +import { EVAL_SCENARIO_MATRIX, buildMatrixPrompt } from "./scenario-contract.js"; + +describe("approval API prompt contract", () => { + it("renders every schema approval type and every Co-Founder mandatory review type", () => { + const managerScenario = EVAL_SCENARIO_MATRIX.find((entry) => entry.audience === "manager"); + if (!managerScenario) { + throw new Error("Expected at least one manager eval scenario."); + } + + const prompt = buildMatrixPrompt(managerScenario); + + for (const approvalType of approvalTypeEnum.options) { + expect(prompt).toContain(approvalType); + } + expect(prompt).toContain("Co-Founder mandatory review types:"); + for (const approvalType of COFOUNDER_MANDATORY_APPROVAL_TYPES) { + expect(prompt).toContain(`- ${approvalType}`); + } + }); +});