Files
EvoFirewall/scripts/ci/generate-openapi-schemas.mjs
T
Denozordec 9fa6269174
quality / changes (push) Successful in 7s
quality / commitlint (push) Skipped
quality / openapi (push) Successful in 30s
quality / web (push) Successful in 1m3s
quality / api (push) Successful in 1m15s
quality / linux-agent-smoke (push) Failing after 27s
quality / docker-check (push) Failing after 2m28s
CD / quality (push) Failing after 5m52s
CD / publish (push) Skipped
feat: harden firewall policies telemetry and operations
Implement reliable agent apply, telemetry v2, analytics retention, guarded rollouts, enrollment, labels and webhooks. Improve ReUI frontend data safety and bounded list loading, and add Docker smoke, recovery and resource checks. Document local verification and pending native acceptance.
2026-10-26 13:10:58 +07:00

54 lines
7.0 KiB
JavaScript

// 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)