@kbediako/codex-orchestrator 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +7 -0
- package/README.md +238 -0
- package/dist/bin/codex-orchestrator.js +507 -0
- package/dist/orchestrator/src/agents/builder.js +16 -0
- package/dist/orchestrator/src/agents/index.js +4 -0
- package/dist/orchestrator/src/agents/planner.js +17 -0
- package/dist/orchestrator/src/agents/reviewer.js +13 -0
- package/dist/orchestrator/src/agents/tester.js +13 -0
- package/dist/orchestrator/src/cli/adapters/CommandBuilder.js +20 -0
- package/dist/orchestrator/src/cli/adapters/CommandPlanner.js +164 -0
- package/dist/orchestrator/src/cli/adapters/CommandReviewer.js +32 -0
- package/dist/orchestrator/src/cli/adapters/CommandTester.js +33 -0
- package/dist/orchestrator/src/cli/adapters/index.js +4 -0
- package/dist/orchestrator/src/cli/config/userConfig.js +28 -0
- package/dist/orchestrator/src/cli/doctor.js +48 -0
- package/dist/orchestrator/src/cli/events/runEvents.js +84 -0
- package/dist/orchestrator/src/cli/exec/command.js +56 -0
- package/dist/orchestrator/src/cli/exec/context.js +108 -0
- package/dist/orchestrator/src/cli/exec/experience.js +77 -0
- package/dist/orchestrator/src/cli/exec/finalization.js +140 -0
- package/dist/orchestrator/src/cli/exec/learning.js +62 -0
- package/dist/orchestrator/src/cli/exec/stageRunner.js +71 -0
- package/dist/orchestrator/src/cli/exec/summary.js +109 -0
- package/dist/orchestrator/src/cli/exec/telemetry.js +18 -0
- package/dist/orchestrator/src/cli/exec/tfgrpo.js +200 -0
- package/dist/orchestrator/src/cli/exec/tfgrpoArtifacts.js +19 -0
- package/dist/orchestrator/src/cli/exec/types.js +1 -0
- package/dist/orchestrator/src/cli/init.js +64 -0
- package/dist/orchestrator/src/cli/mcp.js +124 -0
- package/dist/orchestrator/src/cli/metrics/metricsAggregator.js +404 -0
- package/dist/orchestrator/src/cli/metrics/metricsRecorder.js +138 -0
- package/dist/orchestrator/src/cli/orchestrator.js +554 -0
- package/dist/orchestrator/src/cli/pipelines/defaultDiagnostics.js +32 -0
- package/dist/orchestrator/src/cli/pipelines/designReference.js +72 -0
- package/dist/orchestrator/src/cli/pipelines/hiFiDesignToolkit.js +71 -0
- package/dist/orchestrator/src/cli/pipelines/index.js +34 -0
- package/dist/orchestrator/src/cli/run/environment.js +24 -0
- package/dist/orchestrator/src/cli/run/manifest.js +367 -0
- package/dist/orchestrator/src/cli/run/manifestPersister.js +88 -0
- package/dist/orchestrator/src/cli/run/runPaths.js +30 -0
- package/dist/orchestrator/src/cli/selfCheck.js +12 -0
- package/dist/orchestrator/src/cli/services/commandRunner.js +420 -0
- package/dist/orchestrator/src/cli/services/controlPlaneService.js +107 -0
- package/dist/orchestrator/src/cli/services/execRuntime.js +69 -0
- package/dist/orchestrator/src/cli/services/pipelineResolver.js +47 -0
- package/dist/orchestrator/src/cli/services/runPreparation.js +82 -0
- package/dist/orchestrator/src/cli/services/runSummaryWriter.js +35 -0
- package/dist/orchestrator/src/cli/services/schedulerService.js +42 -0
- package/dist/orchestrator/src/cli/tasks/taskMetadata.js +19 -0
- package/dist/orchestrator/src/cli/telemetry/schema.js +8 -0
- package/dist/orchestrator/src/cli/types.js +1 -0
- package/dist/orchestrator/src/cli/ui/HudApp.js +112 -0
- package/dist/orchestrator/src/cli/ui/controller.js +26 -0
- package/dist/orchestrator/src/cli/ui/store.js +240 -0
- package/dist/orchestrator/src/cli/utils/enforcementMode.js +12 -0
- package/dist/orchestrator/src/cli/utils/fs.js +8 -0
- package/dist/orchestrator/src/cli/utils/interactive.js +25 -0
- package/dist/orchestrator/src/cli/utils/jsonlWriter.js +10 -0
- package/dist/orchestrator/src/cli/utils/optionalDeps.js +30 -0
- package/dist/orchestrator/src/cli/utils/packageInfo.js +25 -0
- package/dist/orchestrator/src/cli/utils/planFormatter.js +49 -0
- package/dist/orchestrator/src/cli/utils/runId.js +7 -0
- package/dist/orchestrator/src/cli/utils/specGuardRunner.js +26 -0
- package/dist/orchestrator/src/cli/utils/strings.js +8 -0
- package/dist/orchestrator/src/cli/utils/time.js +6 -0
- package/dist/orchestrator/src/control-plane/drift-reporter.js +109 -0
- package/dist/orchestrator/src/control-plane/index.js +3 -0
- package/dist/orchestrator/src/control-plane/request-builder.js +217 -0
- package/dist/orchestrator/src/control-plane/types.js +1 -0
- package/dist/orchestrator/src/control-plane/validator.js +50 -0
- package/dist/orchestrator/src/credentials/CredentialBroker.js +1 -0
- package/dist/orchestrator/src/events/EventBus.js +25 -0
- package/dist/orchestrator/src/learning/crystalizer.js +108 -0
- package/dist/orchestrator/src/learning/harvester.js +146 -0
- package/dist/orchestrator/src/learning/manifest.js +56 -0
- package/dist/orchestrator/src/learning/runner.js +177 -0
- package/dist/orchestrator/src/learning/validator.js +164 -0
- package/dist/orchestrator/src/logger.js +20 -0
- package/dist/orchestrator/src/manager.js +388 -0
- package/dist/orchestrator/src/persistence/ArtifactStager.js +95 -0
- package/dist/orchestrator/src/persistence/ExperienceStore.js +210 -0
- package/dist/orchestrator/src/persistence/PersistenceCoordinator.js +65 -0
- package/dist/orchestrator/src/persistence/RunManifestWriter.js +23 -0
- package/dist/orchestrator/src/persistence/TaskStateStore.js +172 -0
- package/dist/orchestrator/src/persistence/identifierGuards.js +1 -0
- package/dist/orchestrator/src/persistence/lockFile.js +26 -0
- package/dist/orchestrator/src/persistence/sanitizeIdentifier.js +26 -0
- package/dist/orchestrator/src/persistence/sanitizeRunId.js +8 -0
- package/dist/orchestrator/src/persistence/sanitizeTaskId.js +8 -0
- package/dist/orchestrator/src/persistence/writeAtomicFile.js +4 -0
- package/dist/orchestrator/src/privacy/guard.js +111 -0
- package/dist/orchestrator/src/scheduler/index.js +1 -0
- package/dist/orchestrator/src/scheduler/plan.js +171 -0
- package/dist/orchestrator/src/scheduler/types.js +1 -0
- package/dist/orchestrator/src/sync/CloudRunsClient.js +1 -0
- package/dist/orchestrator/src/sync/CloudRunsHttpClient.js +82 -0
- package/dist/orchestrator/src/sync/CloudSyncWorker.js +206 -0
- package/dist/orchestrator/src/sync/createCloudSyncWorker.js +15 -0
- package/dist/orchestrator/src/types.js +1 -0
- package/dist/orchestrator/src/utils/atomicWrite.js +15 -0
- package/dist/orchestrator/src/utils/errorMessage.js +14 -0
- package/dist/orchestrator/src/utils/executionMode.js +69 -0
- package/dist/packages/control-plane-schemas/src/index.js +1 -0
- package/dist/packages/control-plane-schemas/src/run-request.js +548 -0
- package/dist/packages/orchestrator/src/exec/handle-service.js +203 -0
- package/dist/packages/orchestrator/src/exec/session-manager.js +147 -0
- package/dist/packages/orchestrator/src/exec/unified-exec.js +432 -0
- package/dist/packages/orchestrator/src/index.js +3 -0
- package/dist/packages/orchestrator/src/instructions/loader.js +101 -0
- package/dist/packages/orchestrator/src/instructions/promptPacks.js +151 -0
- package/dist/packages/orchestrator/src/notifications/index.js +74 -0
- package/dist/packages/orchestrator/src/telemetry/otel-exporter.js +142 -0
- package/dist/packages/orchestrator/src/tool-orchestrator.js +161 -0
- package/dist/packages/sdk-node/src/orchestrator.js +195 -0
- package/dist/packages/shared/config/designConfig.js +495 -0
- package/dist/packages/shared/config/env.js +37 -0
- package/dist/packages/shared/config/index.js +2 -0
- package/dist/packages/shared/design-artifacts/writer.js +221 -0
- package/dist/packages/shared/events/serializer.js +84 -0
- package/dist/packages/shared/events/types.js +1 -0
- package/dist/packages/shared/manifest/artifactUtils.js +36 -0
- package/dist/packages/shared/manifest/designArtifacts.js +665 -0
- package/dist/packages/shared/manifest/fileIO.js +29 -0
- package/dist/packages/shared/manifest/toolRuns.js +78 -0
- package/dist/packages/shared/manifest/toolkitArtifacts.js +223 -0
- package/dist/packages/shared/manifest/types.js +5 -0
- package/dist/packages/shared/manifest/validator.js +73 -0
- package/dist/packages/shared/manifest/writer.js +2 -0
- package/dist/packages/shared/streams/stdio.js +112 -0
- package/dist/scripts/design/pipeline/advanced-assets.js +466 -0
- package/dist/scripts/design/pipeline/componentize.js +74 -0
- package/dist/scripts/design/pipeline/context.js +34 -0
- package/dist/scripts/design/pipeline/extract.js +249 -0
- package/dist/scripts/design/pipeline/optionalDeps.js +107 -0
- package/dist/scripts/design/pipeline/prepare.js +46 -0
- package/dist/scripts/design/pipeline/reference.js +94 -0
- package/dist/scripts/design/pipeline/state.js +206 -0
- package/dist/scripts/design/pipeline/toolkit/common.js +94 -0
- package/dist/scripts/design/pipeline/toolkit/extract.js +258 -0
- package/dist/scripts/design/pipeline/toolkit/publish.js +202 -0
- package/dist/scripts/design/pipeline/toolkit/publishActions.js +12 -0
- package/dist/scripts/design/pipeline/toolkit/reference.js +846 -0
- package/dist/scripts/design/pipeline/toolkit/snapshot.js +882 -0
- package/dist/scripts/design/pipeline/toolkit/tokens.js +456 -0
- package/dist/scripts/design/pipeline/visual-regression.js +137 -0
- package/dist/scripts/design/pipeline/write-artifacts.js +61 -0
- package/package.json +97 -0
- package/schemas/manifest.json +1064 -0
- package/templates/README.md +12 -0
- package/templates/codex/mcp-client.json +8 -0
|
@@ -0,0 +1,548 @@
|
|
|
1
|
+
export const CONTROL_PLANE_RUN_REQUEST_SCHEMA = 'codex.control-plane.run-request';
|
|
2
|
+
export const CONTROL_PLANE_RUN_REQUEST_VERSION = '2.0.0';
|
|
3
|
+
const PRIVACY_LEVELS = new Set([
|
|
4
|
+
'standard',
|
|
5
|
+
'restricted',
|
|
6
|
+
'sensitive'
|
|
7
|
+
]);
|
|
8
|
+
const STAGE_KINDS = new Set(['command', 'subpipeline']);
|
|
9
|
+
const SCHEDULE_STRATEGIES = new Set(['auto', 'manual']);
|
|
10
|
+
export function validateRunRequestV2(payload) {
|
|
11
|
+
const errors = [];
|
|
12
|
+
if (!isPlainObject(payload)) {
|
|
13
|
+
errors.push({
|
|
14
|
+
path: '$',
|
|
15
|
+
message: 'Run request must be an object.',
|
|
16
|
+
value: payload
|
|
17
|
+
});
|
|
18
|
+
return { valid: false, errors };
|
|
19
|
+
}
|
|
20
|
+
const schema = requireString(payload, 'schema', '$.schema', errors, { minLength: 1 });
|
|
21
|
+
if (schema && schema !== CONTROL_PLANE_RUN_REQUEST_SCHEMA) {
|
|
22
|
+
errors.push({
|
|
23
|
+
path: '$.schema',
|
|
24
|
+
message: `Expected schema to equal ${CONTROL_PLANE_RUN_REQUEST_SCHEMA}.`,
|
|
25
|
+
value: schema,
|
|
26
|
+
expected: CONTROL_PLANE_RUN_REQUEST_SCHEMA
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
const version = requireString(payload, 'version', '$.version', errors, { minLength: 1 });
|
|
30
|
+
if (version && version !== CONTROL_PLANE_RUN_REQUEST_VERSION) {
|
|
31
|
+
errors.push({
|
|
32
|
+
path: '$.version',
|
|
33
|
+
message: `Expected version to equal ${CONTROL_PLANE_RUN_REQUEST_VERSION}.`,
|
|
34
|
+
value: version,
|
|
35
|
+
expected: CONTROL_PLANE_RUN_REQUEST_VERSION
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
const requestId = requireString(payload, 'requestId', '$.requestId', errors, { minLength: 1 });
|
|
39
|
+
const task = validateTask(payload, errors);
|
|
40
|
+
const pipeline = validatePipeline(payload, errors);
|
|
41
|
+
const schedule = validateSchedule(payload, errors);
|
|
42
|
+
const streaming = validateStreaming(payload, errors);
|
|
43
|
+
const constraints = validateConstraints(payload, errors);
|
|
44
|
+
const metrics = validateMetrics(payload, errors);
|
|
45
|
+
const requestedAt = requireString(payload, 'requestedAt', '$.requestedAt', errors, {
|
|
46
|
+
minLength: 1
|
|
47
|
+
});
|
|
48
|
+
if (requestedAt && Number.isNaN(Date.parse(requestedAt))) {
|
|
49
|
+
errors.push({
|
|
50
|
+
path: '$.requestedAt',
|
|
51
|
+
message: 'requestedAt must be an ISO 8601 timestamp.',
|
|
52
|
+
value: requestedAt
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
const requestedBy = validateRequestedBy(payload, errors);
|
|
56
|
+
const metadata = optionalRecord(payload, 'metadata', '$.metadata', errors);
|
|
57
|
+
if (errors.length > 0) {
|
|
58
|
+
return { valid: false, errors };
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
valid: true,
|
|
62
|
+
value: {
|
|
63
|
+
schema: CONTROL_PLANE_RUN_REQUEST_SCHEMA,
|
|
64
|
+
version: CONTROL_PLANE_RUN_REQUEST_VERSION,
|
|
65
|
+
requestId: requestId,
|
|
66
|
+
task: task,
|
|
67
|
+
pipeline: pipeline,
|
|
68
|
+
schedule: schedule,
|
|
69
|
+
streaming: streaming,
|
|
70
|
+
constraints: constraints,
|
|
71
|
+
metrics: metrics,
|
|
72
|
+
requestedAt: requestedAt,
|
|
73
|
+
requestedBy: requestedBy,
|
|
74
|
+
metadata: metadata ?? undefined
|
|
75
|
+
},
|
|
76
|
+
errors: []
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
function validateTask(payload, errors) {
|
|
80
|
+
const path = '$.task';
|
|
81
|
+
const value = requireRecord(payload, 'task', path, errors);
|
|
82
|
+
if (!value) {
|
|
83
|
+
return undefined;
|
|
84
|
+
}
|
|
85
|
+
const id = requireString(value, 'id', `${path}.id`, errors, { minLength: 1 });
|
|
86
|
+
const title = requireString(value, 'title', `${path}.title`, errors, { minLength: 1 });
|
|
87
|
+
const slug = optionalString(value, 'slug', `${path}.slug`, errors);
|
|
88
|
+
const description = optionalString(value, 'description', `${path}.description`, errors);
|
|
89
|
+
const metadata = optionalRecord(value, 'metadata', `${path}.metadata`, errors);
|
|
90
|
+
const tags = optionalStringArray(value, 'tags', `${path}.tags`, errors, { minLength: 1 });
|
|
91
|
+
if (!id || !title) {
|
|
92
|
+
return undefined;
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
id,
|
|
96
|
+
title,
|
|
97
|
+
slug: slug ?? undefined,
|
|
98
|
+
description: description ?? undefined,
|
|
99
|
+
metadata: metadata ?? undefined,
|
|
100
|
+
tags: tags ?? undefined
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
function validatePipeline(payload, errors) {
|
|
104
|
+
const path = '$.pipeline';
|
|
105
|
+
const value = requireRecord(payload, 'pipeline', path, errors);
|
|
106
|
+
if (!value) {
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
const id = requireString(value, 'id', `${path}.id`, errors, { minLength: 1 });
|
|
110
|
+
const version = requireString(value, 'version', `${path}.version`, errors, { minLength: 1 });
|
|
111
|
+
const title = requireString(value, 'title', `${path}.title`, errors, { minLength: 1 });
|
|
112
|
+
const capabilities = requireStringArray(value, 'capabilities', `${path}.capabilities`, errors, {
|
|
113
|
+
minLength: 1,
|
|
114
|
+
allowEmpty: false
|
|
115
|
+
});
|
|
116
|
+
const stagesValue = requireArray(value, 'stages', `${path}.stages`, errors, {
|
|
117
|
+
minLength: 1
|
|
118
|
+
});
|
|
119
|
+
const stages = [];
|
|
120
|
+
if (stagesValue) {
|
|
121
|
+
stagesValue.forEach((stagePayload, index) => {
|
|
122
|
+
const stagePath = `${path}.stages[${index}]`;
|
|
123
|
+
if (!isPlainObject(stagePayload)) {
|
|
124
|
+
errors.push({
|
|
125
|
+
path: stagePath,
|
|
126
|
+
message: 'Stage entry must be an object.',
|
|
127
|
+
value: stagePayload
|
|
128
|
+
});
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
const stageId = requireString(stagePayload, 'id', `${stagePath}.id`, errors, { minLength: 1 });
|
|
132
|
+
const kind = requireString(stagePayload, 'kind', `${stagePath}.kind`, errors, {
|
|
133
|
+
minLength: 1
|
|
134
|
+
});
|
|
135
|
+
if (kind && !STAGE_KINDS.has(kind)) {
|
|
136
|
+
errors.push({
|
|
137
|
+
path: `${stagePath}.kind`,
|
|
138
|
+
message: "Stage kind must be 'command' or 'subpipeline'.",
|
|
139
|
+
value: kind
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
const titleValue = requireString(stagePayload, 'title', `${stagePath}.title`, errors, {
|
|
143
|
+
minLength: 1
|
|
144
|
+
});
|
|
145
|
+
const optional = optionalBoolean(stagePayload, 'optional', `${stagePath}.optional`, errors);
|
|
146
|
+
const stageCapabilities = optionalStringArray(stagePayload, 'capabilities', `${stagePath}.capabilities`, errors, { minLength: 1 });
|
|
147
|
+
if (stageId && titleValue && kind && STAGE_KINDS.has(kind)) {
|
|
148
|
+
stages.push({
|
|
149
|
+
id: stageId,
|
|
150
|
+
title: titleValue,
|
|
151
|
+
kind: kind,
|
|
152
|
+
optional: optional ?? undefined,
|
|
153
|
+
capabilities: stageCapabilities ?? undefined
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
if (!id || !version || !title || !capabilities || stages.length === 0) {
|
|
159
|
+
return undefined;
|
|
160
|
+
}
|
|
161
|
+
return {
|
|
162
|
+
id,
|
|
163
|
+
version,
|
|
164
|
+
title,
|
|
165
|
+
capabilities,
|
|
166
|
+
stages
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
function validateSchedule(payload, errors) {
|
|
170
|
+
const path = '$.schedule';
|
|
171
|
+
const value = requireRecord(payload, 'schedule', path, errors);
|
|
172
|
+
if (!value) {
|
|
173
|
+
return undefined;
|
|
174
|
+
}
|
|
175
|
+
const strategyValue = requireString(value, 'strategy', `${path}.strategy`, errors, {
|
|
176
|
+
minLength: 1
|
|
177
|
+
});
|
|
178
|
+
if (strategyValue && !SCHEDULE_STRATEGIES.has(strategyValue)) {
|
|
179
|
+
errors.push({
|
|
180
|
+
path: `${path}.strategy`,
|
|
181
|
+
message: "strategy must be 'auto' or 'manual'.",
|
|
182
|
+
value: strategyValue
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
const minInstances = requireInteger(value, 'minInstances', `${path}.minInstances`, errors, {
|
|
186
|
+
min: 1
|
|
187
|
+
});
|
|
188
|
+
const maxInstances = requireInteger(value, 'maxInstances', `${path}.maxInstances`, errors, {
|
|
189
|
+
min: 1
|
|
190
|
+
});
|
|
191
|
+
if (typeof minInstances === 'number' &&
|
|
192
|
+
typeof maxInstances === 'number' &&
|
|
193
|
+
maxInstances < minInstances) {
|
|
194
|
+
errors.push({
|
|
195
|
+
path: path,
|
|
196
|
+
message: 'maxInstances must be greater than or equal to minInstances.',
|
|
197
|
+
value: { minInstances, maxInstances }
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
const fanOutPayload = requireArray(value, 'fanOut', `${path}.fanOut`, errors, { minLength: 1 });
|
|
201
|
+
const fanOut = [];
|
|
202
|
+
if (fanOutPayload) {
|
|
203
|
+
fanOutPayload.forEach((entry, index) => {
|
|
204
|
+
const entryPath = `${path}.fanOut[${index}]`;
|
|
205
|
+
if (!isPlainObject(entry)) {
|
|
206
|
+
errors.push({
|
|
207
|
+
path: entryPath,
|
|
208
|
+
message: 'fanOut entries must be objects.',
|
|
209
|
+
value: entry
|
|
210
|
+
});
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
const capability = requireString(entry, 'capability', `${entryPath}.capability`, errors, {
|
|
214
|
+
minLength: 1
|
|
215
|
+
});
|
|
216
|
+
const weight = requireNumber(entry, 'weight', `${entryPath}.weight`, errors, {
|
|
217
|
+
min: 0,
|
|
218
|
+
exclusiveMin: true
|
|
219
|
+
});
|
|
220
|
+
const maxConcurrency = optionalInteger(entry, 'maxConcurrency', `${entryPath}.maxConcurrency`, errors, { min: 1 });
|
|
221
|
+
if (capability && typeof weight === 'number') {
|
|
222
|
+
fanOut.push({
|
|
223
|
+
capability,
|
|
224
|
+
weight,
|
|
225
|
+
maxConcurrency: maxConcurrency ?? undefined
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
const recoveryPayload = requireRecord(value, 'recovery', `${path}.recovery`, errors);
|
|
231
|
+
let recovery;
|
|
232
|
+
if (recoveryPayload) {
|
|
233
|
+
const heartbeatIntervalSeconds = requireInteger(recoveryPayload, 'heartbeatIntervalSeconds', `${path}.recovery.heartbeatIntervalSeconds`, errors, { min: 1 });
|
|
234
|
+
const missingHeartbeatTimeoutSeconds = requireInteger(recoveryPayload, 'missingHeartbeatTimeoutSeconds', `${path}.recovery.missingHeartbeatTimeoutSeconds`, errors, { min: 1 });
|
|
235
|
+
const maxRetries = requireInteger(recoveryPayload, 'maxRetries', `${path}.recovery.maxRetries`, errors, { min: 0 });
|
|
236
|
+
if (typeof heartbeatIntervalSeconds === 'number' &&
|
|
237
|
+
typeof missingHeartbeatTimeoutSeconds === 'number' &&
|
|
238
|
+
typeof maxRetries === 'number') {
|
|
239
|
+
recovery = {
|
|
240
|
+
heartbeatIntervalSeconds,
|
|
241
|
+
missingHeartbeatTimeoutSeconds,
|
|
242
|
+
maxRetries
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
if (!strategyValue ||
|
|
247
|
+
typeof minInstances !== 'number' ||
|
|
248
|
+
typeof maxInstances !== 'number' ||
|
|
249
|
+
fanOut.length === 0 ||
|
|
250
|
+
!recovery) {
|
|
251
|
+
return undefined;
|
|
252
|
+
}
|
|
253
|
+
return {
|
|
254
|
+
strategy: strategyValue,
|
|
255
|
+
minInstances,
|
|
256
|
+
maxInstances,
|
|
257
|
+
fanOut,
|
|
258
|
+
recovery
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
function validateStreaming(payload, errors) {
|
|
262
|
+
const path = '$.streaming';
|
|
263
|
+
const value = requireRecord(payload, 'streaming', path, errors);
|
|
264
|
+
if (!value) {
|
|
265
|
+
return undefined;
|
|
266
|
+
}
|
|
267
|
+
const handles = requireBoolean(value, 'handles', `${path}.handles`, errors);
|
|
268
|
+
const resumeSupported = requireBoolean(value, 'resumeSupported', `${path}.resumeSupported`, errors);
|
|
269
|
+
const observersPayload = requireRecord(value, 'observers', `${path}.observers`, errors);
|
|
270
|
+
let observers;
|
|
271
|
+
if (observersPayload) {
|
|
272
|
+
const maxSubscribers = requireInteger(observersPayload, 'maxSubscribers', `${path}.observers.maxSubscribers`, errors, { min: 1 });
|
|
273
|
+
const defaultBackpressureMs = requireInteger(observersPayload, 'defaultBackpressureMs', `${path}.observers.defaultBackpressureMs`, errors, { min: 0 });
|
|
274
|
+
if (typeof maxSubscribers === 'number' && typeof defaultBackpressureMs === 'number') {
|
|
275
|
+
observers = { maxSubscribers, defaultBackpressureMs };
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
if (typeof handles !== 'boolean' || typeof resumeSupported !== 'boolean' || !observers) {
|
|
279
|
+
return undefined;
|
|
280
|
+
}
|
|
281
|
+
return {
|
|
282
|
+
handles,
|
|
283
|
+
resumeSupported,
|
|
284
|
+
observers
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
function validateConstraints(payload, errors) {
|
|
288
|
+
const path = '$.constraints';
|
|
289
|
+
const value = requireRecord(payload, 'constraints', path, errors);
|
|
290
|
+
if (!value) {
|
|
291
|
+
return undefined;
|
|
292
|
+
}
|
|
293
|
+
const privacyLevel = requireString(value, 'privacyLevel', `${path}.privacyLevel`, errors, {
|
|
294
|
+
minLength: 1
|
|
295
|
+
});
|
|
296
|
+
if (privacyLevel && !PRIVACY_LEVELS.has(privacyLevel)) {
|
|
297
|
+
errors.push({
|
|
298
|
+
path: `${path}.privacyLevel`,
|
|
299
|
+
message: "privacyLevel must be 'standard', 'restricted', or 'sensitive'.",
|
|
300
|
+
value: privacyLevel
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
const dataResidency = optionalString(value, 'dataResidency', `${path}.dataResidency`, errors);
|
|
304
|
+
const policyVersion = requireString(value, 'policyVersion', `${path}.policyVersion`, errors, {
|
|
305
|
+
minLength: 1
|
|
306
|
+
});
|
|
307
|
+
if (!privacyLevel || !policyVersion) {
|
|
308
|
+
return undefined;
|
|
309
|
+
}
|
|
310
|
+
return {
|
|
311
|
+
privacyLevel: privacyLevel,
|
|
312
|
+
dataResidency: dataResidency ?? undefined,
|
|
313
|
+
policyVersion
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
function validateMetrics(payload, errors) {
|
|
317
|
+
const path = '$.metrics';
|
|
318
|
+
const value = requireRecord(payload, 'metrics', path, errors);
|
|
319
|
+
if (!value) {
|
|
320
|
+
return undefined;
|
|
321
|
+
}
|
|
322
|
+
const emitIntervalSeconds = requireInteger(value, 'emitIntervalSeconds', `${path}.emitIntervalSeconds`, errors, { min: 1 });
|
|
323
|
+
const requiredDimensions = requireStringArray(value, 'requiredDimensions', `${path}.requiredDimensions`, errors, { minLength: 1, allowEmpty: false });
|
|
324
|
+
if (typeof emitIntervalSeconds !== 'number' || !requiredDimensions) {
|
|
325
|
+
return undefined;
|
|
326
|
+
}
|
|
327
|
+
return {
|
|
328
|
+
emitIntervalSeconds,
|
|
329
|
+
requiredDimensions
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
function validateRequestedBy(payload, errors) {
|
|
333
|
+
const path = '$.requestedBy';
|
|
334
|
+
const value = requireRecord(payload, 'requestedBy', path, errors);
|
|
335
|
+
if (!value) {
|
|
336
|
+
return undefined;
|
|
337
|
+
}
|
|
338
|
+
const actorId = requireString(value, 'actorId', `${path}.actorId`, errors, { minLength: 1 });
|
|
339
|
+
const channel = requireString(value, 'channel', `${path}.channel`, errors, { minLength: 1 });
|
|
340
|
+
const name = optionalString(value, 'name', `${path}.name`, errors);
|
|
341
|
+
if (!actorId || !channel) {
|
|
342
|
+
return undefined;
|
|
343
|
+
}
|
|
344
|
+
return {
|
|
345
|
+
actorId,
|
|
346
|
+
channel,
|
|
347
|
+
name: name ?? undefined
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
function requireString(record, key, path, errors, options = {}) {
|
|
351
|
+
const value = record[key];
|
|
352
|
+
if (typeof value !== 'string') {
|
|
353
|
+
errors.push({ path, message: 'Expected a string.', value });
|
|
354
|
+
return undefined;
|
|
355
|
+
}
|
|
356
|
+
if (typeof options.minLength === 'number' && value.length < options.minLength) {
|
|
357
|
+
errors.push({
|
|
358
|
+
path,
|
|
359
|
+
message: `String must be at least ${options.minLength} characters.`,
|
|
360
|
+
value
|
|
361
|
+
});
|
|
362
|
+
return undefined;
|
|
363
|
+
}
|
|
364
|
+
return value;
|
|
365
|
+
}
|
|
366
|
+
function optionalString(record, key, path, errors) {
|
|
367
|
+
if (!(key in record)) {
|
|
368
|
+
return null;
|
|
369
|
+
}
|
|
370
|
+
const value = record[key];
|
|
371
|
+
if (value === null) {
|
|
372
|
+
return null;
|
|
373
|
+
}
|
|
374
|
+
if (typeof value === 'string') {
|
|
375
|
+
return value;
|
|
376
|
+
}
|
|
377
|
+
errors.push({ path, message: 'Expected a string.', value });
|
|
378
|
+
return null;
|
|
379
|
+
}
|
|
380
|
+
function requireNumber(record, key, path, errors, options = {}) {
|
|
381
|
+
const value = record[key];
|
|
382
|
+
if (typeof value !== 'number' || Number.isNaN(value)) {
|
|
383
|
+
errors.push({ path, message: 'Expected a number.', value });
|
|
384
|
+
return undefined;
|
|
385
|
+
}
|
|
386
|
+
if (typeof options.min === 'number') {
|
|
387
|
+
const compare = options.exclusiveMin ? value <= options.min : value < options.min;
|
|
388
|
+
if (compare) {
|
|
389
|
+
errors.push({
|
|
390
|
+
path,
|
|
391
|
+
message: options.exclusiveMin
|
|
392
|
+
? `Number must be greater than ${options.min}.`
|
|
393
|
+
: `Number must be greater than or equal to ${options.min}.`,
|
|
394
|
+
value
|
|
395
|
+
});
|
|
396
|
+
return undefined;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
return value;
|
|
400
|
+
}
|
|
401
|
+
function requireInteger(record, key, path, errors, options = {}) {
|
|
402
|
+
const value = requireNumber(record, key, path, errors, {
|
|
403
|
+
min: options.min,
|
|
404
|
+
exclusiveMin: false
|
|
405
|
+
});
|
|
406
|
+
if (typeof value === 'number' && !Number.isInteger(value)) {
|
|
407
|
+
errors.push({ path, message: 'Number must be an integer.', value });
|
|
408
|
+
return undefined;
|
|
409
|
+
}
|
|
410
|
+
return value;
|
|
411
|
+
}
|
|
412
|
+
function optionalInteger(record, key, path, errors, options = {}) {
|
|
413
|
+
if (!(key in record)) {
|
|
414
|
+
return null;
|
|
415
|
+
}
|
|
416
|
+
const value = record[key];
|
|
417
|
+
if (value === null) {
|
|
418
|
+
return null;
|
|
419
|
+
}
|
|
420
|
+
if (typeof value !== 'number' || Number.isNaN(value)) {
|
|
421
|
+
errors.push({ path, message: 'Expected a number.', value });
|
|
422
|
+
return null;
|
|
423
|
+
}
|
|
424
|
+
if (!Number.isInteger(value)) {
|
|
425
|
+
errors.push({ path, message: 'Number must be an integer.', value });
|
|
426
|
+
return null;
|
|
427
|
+
}
|
|
428
|
+
if (typeof options.min === 'number' && value < options.min) {
|
|
429
|
+
errors.push({
|
|
430
|
+
path,
|
|
431
|
+
message: `Number must be greater than or equal to ${options.min}.`,
|
|
432
|
+
value
|
|
433
|
+
});
|
|
434
|
+
return null;
|
|
435
|
+
}
|
|
436
|
+
return value;
|
|
437
|
+
}
|
|
438
|
+
function requireBoolean(record, key, path, errors) {
|
|
439
|
+
const value = record[key];
|
|
440
|
+
if (typeof value !== 'boolean') {
|
|
441
|
+
errors.push({ path, message: 'Expected a boolean.', value });
|
|
442
|
+
return undefined;
|
|
443
|
+
}
|
|
444
|
+
return value;
|
|
445
|
+
}
|
|
446
|
+
function optionalBoolean(record, key, path, errors) {
|
|
447
|
+
if (!(key in record)) {
|
|
448
|
+
return null;
|
|
449
|
+
}
|
|
450
|
+
const value = record[key];
|
|
451
|
+
if (value === null) {
|
|
452
|
+
return null;
|
|
453
|
+
}
|
|
454
|
+
if (typeof value !== 'boolean') {
|
|
455
|
+
errors.push({ path, message: 'Expected a boolean.', value });
|
|
456
|
+
return null;
|
|
457
|
+
}
|
|
458
|
+
return value;
|
|
459
|
+
}
|
|
460
|
+
function requireRecord(record, key, path, errors) {
|
|
461
|
+
const value = record[key];
|
|
462
|
+
if (!isPlainObject(value)) {
|
|
463
|
+
errors.push({ path, message: 'Expected an object.', value });
|
|
464
|
+
return undefined;
|
|
465
|
+
}
|
|
466
|
+
return value;
|
|
467
|
+
}
|
|
468
|
+
function optionalRecord(record, key, path, errors) {
|
|
469
|
+
if (!(key in record)) {
|
|
470
|
+
return null;
|
|
471
|
+
}
|
|
472
|
+
const value = record[key];
|
|
473
|
+
if (value === null) {
|
|
474
|
+
return null;
|
|
475
|
+
}
|
|
476
|
+
if (!isPlainObject(value)) {
|
|
477
|
+
errors.push({ path, message: 'Expected an object.', value });
|
|
478
|
+
return null;
|
|
479
|
+
}
|
|
480
|
+
return value;
|
|
481
|
+
}
|
|
482
|
+
function requireArray(record, key, path, errors, options = {}) {
|
|
483
|
+
const value = record[key];
|
|
484
|
+
if (!Array.isArray(value)) {
|
|
485
|
+
errors.push({ path, message: 'Expected an array.', value });
|
|
486
|
+
return undefined;
|
|
487
|
+
}
|
|
488
|
+
if (typeof options.minLength === 'number' && value.length < options.minLength) {
|
|
489
|
+
errors.push({
|
|
490
|
+
path,
|
|
491
|
+
message: `Array must contain at least ${options.minLength} item(s).`,
|
|
492
|
+
value
|
|
493
|
+
});
|
|
494
|
+
return undefined;
|
|
495
|
+
}
|
|
496
|
+
return value;
|
|
497
|
+
}
|
|
498
|
+
function optionalStringArray(record, key, path, errors, options = {}) {
|
|
499
|
+
if (!(key in record)) {
|
|
500
|
+
return null;
|
|
501
|
+
}
|
|
502
|
+
const value = record[key];
|
|
503
|
+
if (value === null) {
|
|
504
|
+
return null;
|
|
505
|
+
}
|
|
506
|
+
if (!Array.isArray(value)) {
|
|
507
|
+
errors.push({ path, message: 'Expected an array.', value });
|
|
508
|
+
return null;
|
|
509
|
+
}
|
|
510
|
+
const strings = [];
|
|
511
|
+
value.forEach((entry, index) => {
|
|
512
|
+
if (typeof entry !== 'string') {
|
|
513
|
+
errors.push({
|
|
514
|
+
path: `${path}[${index}]`,
|
|
515
|
+
message: 'Expected a string.',
|
|
516
|
+
value: entry
|
|
517
|
+
});
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
if (typeof options.minLength === 'number' && entry.length < options.minLength) {
|
|
521
|
+
errors.push({
|
|
522
|
+
path: `${path}[${index}]`,
|
|
523
|
+
message: `String must be at least ${options.minLength} characters.`,
|
|
524
|
+
value: entry
|
|
525
|
+
});
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
strings.push(entry);
|
|
529
|
+
});
|
|
530
|
+
return strings;
|
|
531
|
+
}
|
|
532
|
+
function requireStringArray(record, key, path, errors, options = {}) {
|
|
533
|
+
const value = optionalStringArray(record, key, path, errors, options);
|
|
534
|
+
if (!value) {
|
|
535
|
+
if (!(key in record)) {
|
|
536
|
+
errors.push({ path, message: 'Expected an array.', value: record[key] });
|
|
537
|
+
}
|
|
538
|
+
return undefined;
|
|
539
|
+
}
|
|
540
|
+
if (options.allowEmpty === false && value.length === 0) {
|
|
541
|
+
errors.push({ path, message: 'Array must not be empty.', value });
|
|
542
|
+
return undefined;
|
|
543
|
+
}
|
|
544
|
+
return value;
|
|
545
|
+
}
|
|
546
|
+
function isPlainObject(value) {
|
|
547
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
548
|
+
}
|