// Schema definitions come from the compiled shared Zod contracts, with no new deps. import { createRequire } from 'node:module' import { readFileSync, writeFileSync } from 'node:fs' import { resolve } from 'node:path' import * as shared from '../../packages/shared/dist/index.js' const require = createRequire(resolve('packages/shared/package.json')) const { z } = require('zod') const definitions = { Agent: 'agentSchema', AgentDiagnostics: 'agentDiagnosticsSchema', AgentPolicy: 'agentPolicySchema', ApplyReport: 'applyReportBodySchema', ApplyReportV2: 'applyReportV2BodySchema', TelemetryQuality: 'telemetryQualitySchema', ApplyError: 'applyErrorSchema', TimeseriesResponse: 'timeseriesResponseSchema', UiSummary: 'uiSummarySchema', ExplainResponse: 'explainResponseSchema', CreateOverride: 'createOverrideBodySchema', IpOverride: 'ipOverrideSchema', IpList: 'ipListSchema', CreateWebhook: 'webhookBodySchema', PatchWebhook: 'webhookPatchSchema', EnrollWithCredential: 'enrollmentCredentialBodySchema', TokenRotationExchange: 'tokenRotationExchangeBodySchema', CredentialStatus: 'credentialStatusSchema', TokenRotationAck: 'tokenRotationAckSchema', AdminCredentialStatus: 'adminCredentialStatusSchema', ListImportSummary: 'listImportSummarySchema', CreatePolicySnapshot: 'createPolicySnapshotSchema', PolicySnapshotDiffRequest: 'policySnapshotDiffSchema', CreatePolicyRollout: 'createPolicyRolloutSchema', PolicyRolloutTransition: 'policyRolloutTransitionSchema', ReleasePolicyPin: 'releasePolicyPinSchema', CreateAgentLabel: 'agentLabelBodySchema', PatchAgentLabel: 'patchAgentLabelSchema', PutAgentLabels: 'putAgentLabelsSchema', PreviewLabelAssignment: 'previewLabelAssignmentSchema', ApplyLabelAssignment: 'applyLabelAssignmentSchema', } const schemas = Object.fromEntries(Object.entries(definitions).map(([name, exported]) => [name, z.toJSONSchema(shared[exported], { target: 'openapi-3.0', io: 'input', unrepresentable: 'any' })])) const schemaRef = name => ({ $ref: `#/components/schemas/${name}` }) const operation = (summary, requestSchema, responseSchema, status = '200', security = 'bearerAuth') => ({ summary, tags: ['ops'], security: [{ [security]: [] }], ...(requestSchema ? { requestBody: { required: true, content: { 'application/json': { schema: schemaRef(requestSchema) } } } } : {}), responses: { [status]: { description: summary, ...(responseSchema ? { content: { 'application/json': { schema: schemaRef(responseSchema) } } } : {}) }, '400': { description: 'Invalid request' }, '401': { description: 'Authentication required' }, '403': { description: 'Insufficient permission' }, '409': { description: 'Stale revision, conflicting operation or resource still referenced' } }, }) const idParameter = [{ name: 'id', in: 'path', required: true, schema: { type: 'string', minLength: 1, maxLength: 128 } }] const pageParameters = [{ name: 'limit', in: 'query', schema: { type: 'integer', minimum: 1, maximum: 50, default: 20 } }, { name: 'offset', in: 'query', schema: { type: 'integer', minimum: 0, maximum: 10000, default: 0 } }] const paths = { '/v1/agent/credentials': { get: operation('Agent credential status; no-store; approved current token only', null, 'CredentialStatus', '200', 'agentToken') }, '/v1/agent/token-rotation': { post: operation('Atomic token exchange; old-token retry only for exact completed exchange within 24 hours; no-store', 'TokenRotationExchange', 'TokenRotationAck', '200', 'agentToken') }, '/api/v1/agents/{id}/token-rotation': { parameters: idParameter, get: operation('Operator credential rotation status; agents read permission; no-store', null, 'AdminCredentialStatus'), post: operation('Request rotation for a supported approved client; agents write permission', null, null, '202') }, '/api/v1/policies/snapshots': { get: { ...operation('List immutable snapshot metadata; policies read permission'), parameters: pageParameters }, post: operation('Capture immutable effective policy for source agent; policies write permission', 'CreatePolicySnapshot', null, '201') }, '/api/v1/policies/snapshots/{id}': { parameters: idParameter, get: operation('Get immutable snapshot and payload; policies read permission'), delete: operation('Delete unreferenced snapshot; policies write permission') }, '/api/v1/policies/snapshots/{id}/diff': { parameters: idParameter, post: operation('Read-only bounded diff for up to 100 agents; policies write permission', 'PolicySnapshotDiffRequest') }, '/api/v1/policies/rollouts': { get: { ...operation('List rollout metadata; policies read permission'), parameters: pageParameters }, post: operation('Start explicitly selected canary rollout with generation checks; policies write permission', 'CreatePolicyRollout', null, '201') }, '/api/v1/policies/rollouts/{id}': { parameters: idParameter, get: operation('Get rollout and per-target acknowledgement; policies read permission'), delete: operation('Delete finished rollout after all pins released; policies write permission') }, '/api/v1/policies/rollouts/{id}/confirm': { parameters: idParameter, post: operation('Confirm rollout at expected version after fresh exact canary acknowledgement; policies write permission', 'PolicyRolloutTransition') }, '/api/v1/policies/rollouts/{id}/rollback': { parameters: idParameter, post: operation('Rollback to captured per-agent immutable policies; policies write permission', 'PolicyRolloutTransition') }, '/api/v1/agents/{id}/policy-pin/release': { parameters: idParameter, post: operation('Release selected rollout pin with expected generation; agents write permission', 'ReleasePolicyPin') }, '/api/v1/agent-labels': { get: { ...operation('List labels; agents read permission'), parameters: pageParameters }, post: operation('Create unique agent label; agents write permission', 'CreateAgentLabel', null, '201') }, '/api/v1/agent-labels/{id}': { parameters: idParameter, patch: operation('Update label; agents write permission', 'PatchAgentLabel'), delete: operation('Delete label without deleting assigned agents; agents write permission') }, '/api/v1/agents/{id}/labels': { parameters: idParameter, get: operation('Read agent label IDs; agents read permission'), put: operation('Replace agent labels atomically; agents write permission', 'PutAgentLabels') }, '/api/v1/agent-labels/preview-assignment': { post: operation('Preview add/replace policy-set assignment from explicit agent IDs or labels; policies read permission', 'PreviewLabelAssignment') }, '/api/v1/agent-labels/assign-policy-sets': { post: operation('Apply previously reviewed assignment with exact per-agent generations; policies write permission', 'ApplyLabelAssignment') }, } const output = JSON.stringify({ 'x-generated-by': 'scripts/ci/generate-openapi-schemas.mjs; rebuild @evofw/shared first', components: { schemas }, paths }, null, 2) + '\n' const path = resolve('docs/openapi.schemas.json') if (process.argv.includes('--check')) { if (readFileSync(path, 'utf8') !== output) throw new Error('OpenAPI schemas differ from shared contracts; rebuild shared and regenerate') } else writeFileSync(path, output)