@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.
Files changed (150) hide show
  1. package/LICENSE +7 -0
  2. package/README.md +238 -0
  3. package/dist/bin/codex-orchestrator.js +507 -0
  4. package/dist/orchestrator/src/agents/builder.js +16 -0
  5. package/dist/orchestrator/src/agents/index.js +4 -0
  6. package/dist/orchestrator/src/agents/planner.js +17 -0
  7. package/dist/orchestrator/src/agents/reviewer.js +13 -0
  8. package/dist/orchestrator/src/agents/tester.js +13 -0
  9. package/dist/orchestrator/src/cli/adapters/CommandBuilder.js +20 -0
  10. package/dist/orchestrator/src/cli/adapters/CommandPlanner.js +164 -0
  11. package/dist/orchestrator/src/cli/adapters/CommandReviewer.js +32 -0
  12. package/dist/orchestrator/src/cli/adapters/CommandTester.js +33 -0
  13. package/dist/orchestrator/src/cli/adapters/index.js +4 -0
  14. package/dist/orchestrator/src/cli/config/userConfig.js +28 -0
  15. package/dist/orchestrator/src/cli/doctor.js +48 -0
  16. package/dist/orchestrator/src/cli/events/runEvents.js +84 -0
  17. package/dist/orchestrator/src/cli/exec/command.js +56 -0
  18. package/dist/orchestrator/src/cli/exec/context.js +108 -0
  19. package/dist/orchestrator/src/cli/exec/experience.js +77 -0
  20. package/dist/orchestrator/src/cli/exec/finalization.js +140 -0
  21. package/dist/orchestrator/src/cli/exec/learning.js +62 -0
  22. package/dist/orchestrator/src/cli/exec/stageRunner.js +71 -0
  23. package/dist/orchestrator/src/cli/exec/summary.js +109 -0
  24. package/dist/orchestrator/src/cli/exec/telemetry.js +18 -0
  25. package/dist/orchestrator/src/cli/exec/tfgrpo.js +200 -0
  26. package/dist/orchestrator/src/cli/exec/tfgrpoArtifacts.js +19 -0
  27. package/dist/orchestrator/src/cli/exec/types.js +1 -0
  28. package/dist/orchestrator/src/cli/init.js +64 -0
  29. package/dist/orchestrator/src/cli/mcp.js +124 -0
  30. package/dist/orchestrator/src/cli/metrics/metricsAggregator.js +404 -0
  31. package/dist/orchestrator/src/cli/metrics/metricsRecorder.js +138 -0
  32. package/dist/orchestrator/src/cli/orchestrator.js +554 -0
  33. package/dist/orchestrator/src/cli/pipelines/defaultDiagnostics.js +32 -0
  34. package/dist/orchestrator/src/cli/pipelines/designReference.js +72 -0
  35. package/dist/orchestrator/src/cli/pipelines/hiFiDesignToolkit.js +71 -0
  36. package/dist/orchestrator/src/cli/pipelines/index.js +34 -0
  37. package/dist/orchestrator/src/cli/run/environment.js +24 -0
  38. package/dist/orchestrator/src/cli/run/manifest.js +367 -0
  39. package/dist/orchestrator/src/cli/run/manifestPersister.js +88 -0
  40. package/dist/orchestrator/src/cli/run/runPaths.js +30 -0
  41. package/dist/orchestrator/src/cli/selfCheck.js +12 -0
  42. package/dist/orchestrator/src/cli/services/commandRunner.js +420 -0
  43. package/dist/orchestrator/src/cli/services/controlPlaneService.js +107 -0
  44. package/dist/orchestrator/src/cli/services/execRuntime.js +69 -0
  45. package/dist/orchestrator/src/cli/services/pipelineResolver.js +47 -0
  46. package/dist/orchestrator/src/cli/services/runPreparation.js +82 -0
  47. package/dist/orchestrator/src/cli/services/runSummaryWriter.js +35 -0
  48. package/dist/orchestrator/src/cli/services/schedulerService.js +42 -0
  49. package/dist/orchestrator/src/cli/tasks/taskMetadata.js +19 -0
  50. package/dist/orchestrator/src/cli/telemetry/schema.js +8 -0
  51. package/dist/orchestrator/src/cli/types.js +1 -0
  52. package/dist/orchestrator/src/cli/ui/HudApp.js +112 -0
  53. package/dist/orchestrator/src/cli/ui/controller.js +26 -0
  54. package/dist/orchestrator/src/cli/ui/store.js +240 -0
  55. package/dist/orchestrator/src/cli/utils/enforcementMode.js +12 -0
  56. package/dist/orchestrator/src/cli/utils/fs.js +8 -0
  57. package/dist/orchestrator/src/cli/utils/interactive.js +25 -0
  58. package/dist/orchestrator/src/cli/utils/jsonlWriter.js +10 -0
  59. package/dist/orchestrator/src/cli/utils/optionalDeps.js +30 -0
  60. package/dist/orchestrator/src/cli/utils/packageInfo.js +25 -0
  61. package/dist/orchestrator/src/cli/utils/planFormatter.js +49 -0
  62. package/dist/orchestrator/src/cli/utils/runId.js +7 -0
  63. package/dist/orchestrator/src/cli/utils/specGuardRunner.js +26 -0
  64. package/dist/orchestrator/src/cli/utils/strings.js +8 -0
  65. package/dist/orchestrator/src/cli/utils/time.js +6 -0
  66. package/dist/orchestrator/src/control-plane/drift-reporter.js +109 -0
  67. package/dist/orchestrator/src/control-plane/index.js +3 -0
  68. package/dist/orchestrator/src/control-plane/request-builder.js +217 -0
  69. package/dist/orchestrator/src/control-plane/types.js +1 -0
  70. package/dist/orchestrator/src/control-plane/validator.js +50 -0
  71. package/dist/orchestrator/src/credentials/CredentialBroker.js +1 -0
  72. package/dist/orchestrator/src/events/EventBus.js +25 -0
  73. package/dist/orchestrator/src/learning/crystalizer.js +108 -0
  74. package/dist/orchestrator/src/learning/harvester.js +146 -0
  75. package/dist/orchestrator/src/learning/manifest.js +56 -0
  76. package/dist/orchestrator/src/learning/runner.js +177 -0
  77. package/dist/orchestrator/src/learning/validator.js +164 -0
  78. package/dist/orchestrator/src/logger.js +20 -0
  79. package/dist/orchestrator/src/manager.js +388 -0
  80. package/dist/orchestrator/src/persistence/ArtifactStager.js +95 -0
  81. package/dist/orchestrator/src/persistence/ExperienceStore.js +210 -0
  82. package/dist/orchestrator/src/persistence/PersistenceCoordinator.js +65 -0
  83. package/dist/orchestrator/src/persistence/RunManifestWriter.js +23 -0
  84. package/dist/orchestrator/src/persistence/TaskStateStore.js +172 -0
  85. package/dist/orchestrator/src/persistence/identifierGuards.js +1 -0
  86. package/dist/orchestrator/src/persistence/lockFile.js +26 -0
  87. package/dist/orchestrator/src/persistence/sanitizeIdentifier.js +26 -0
  88. package/dist/orchestrator/src/persistence/sanitizeRunId.js +8 -0
  89. package/dist/orchestrator/src/persistence/sanitizeTaskId.js +8 -0
  90. package/dist/orchestrator/src/persistence/writeAtomicFile.js +4 -0
  91. package/dist/orchestrator/src/privacy/guard.js +111 -0
  92. package/dist/orchestrator/src/scheduler/index.js +1 -0
  93. package/dist/orchestrator/src/scheduler/plan.js +171 -0
  94. package/dist/orchestrator/src/scheduler/types.js +1 -0
  95. package/dist/orchestrator/src/sync/CloudRunsClient.js +1 -0
  96. package/dist/orchestrator/src/sync/CloudRunsHttpClient.js +82 -0
  97. package/dist/orchestrator/src/sync/CloudSyncWorker.js +206 -0
  98. package/dist/orchestrator/src/sync/createCloudSyncWorker.js +15 -0
  99. package/dist/orchestrator/src/types.js +1 -0
  100. package/dist/orchestrator/src/utils/atomicWrite.js +15 -0
  101. package/dist/orchestrator/src/utils/errorMessage.js +14 -0
  102. package/dist/orchestrator/src/utils/executionMode.js +69 -0
  103. package/dist/packages/control-plane-schemas/src/index.js +1 -0
  104. package/dist/packages/control-plane-schemas/src/run-request.js +548 -0
  105. package/dist/packages/orchestrator/src/exec/handle-service.js +203 -0
  106. package/dist/packages/orchestrator/src/exec/session-manager.js +147 -0
  107. package/dist/packages/orchestrator/src/exec/unified-exec.js +432 -0
  108. package/dist/packages/orchestrator/src/index.js +3 -0
  109. package/dist/packages/orchestrator/src/instructions/loader.js +101 -0
  110. package/dist/packages/orchestrator/src/instructions/promptPacks.js +151 -0
  111. package/dist/packages/orchestrator/src/notifications/index.js +74 -0
  112. package/dist/packages/orchestrator/src/telemetry/otel-exporter.js +142 -0
  113. package/dist/packages/orchestrator/src/tool-orchestrator.js +161 -0
  114. package/dist/packages/sdk-node/src/orchestrator.js +195 -0
  115. package/dist/packages/shared/config/designConfig.js +495 -0
  116. package/dist/packages/shared/config/env.js +37 -0
  117. package/dist/packages/shared/config/index.js +2 -0
  118. package/dist/packages/shared/design-artifacts/writer.js +221 -0
  119. package/dist/packages/shared/events/serializer.js +84 -0
  120. package/dist/packages/shared/events/types.js +1 -0
  121. package/dist/packages/shared/manifest/artifactUtils.js +36 -0
  122. package/dist/packages/shared/manifest/designArtifacts.js +665 -0
  123. package/dist/packages/shared/manifest/fileIO.js +29 -0
  124. package/dist/packages/shared/manifest/toolRuns.js +78 -0
  125. package/dist/packages/shared/manifest/toolkitArtifacts.js +223 -0
  126. package/dist/packages/shared/manifest/types.js +5 -0
  127. package/dist/packages/shared/manifest/validator.js +73 -0
  128. package/dist/packages/shared/manifest/writer.js +2 -0
  129. package/dist/packages/shared/streams/stdio.js +112 -0
  130. package/dist/scripts/design/pipeline/advanced-assets.js +466 -0
  131. package/dist/scripts/design/pipeline/componentize.js +74 -0
  132. package/dist/scripts/design/pipeline/context.js +34 -0
  133. package/dist/scripts/design/pipeline/extract.js +249 -0
  134. package/dist/scripts/design/pipeline/optionalDeps.js +107 -0
  135. package/dist/scripts/design/pipeline/prepare.js +46 -0
  136. package/dist/scripts/design/pipeline/reference.js +94 -0
  137. package/dist/scripts/design/pipeline/state.js +206 -0
  138. package/dist/scripts/design/pipeline/toolkit/common.js +94 -0
  139. package/dist/scripts/design/pipeline/toolkit/extract.js +258 -0
  140. package/dist/scripts/design/pipeline/toolkit/publish.js +202 -0
  141. package/dist/scripts/design/pipeline/toolkit/publishActions.js +12 -0
  142. package/dist/scripts/design/pipeline/toolkit/reference.js +846 -0
  143. package/dist/scripts/design/pipeline/toolkit/snapshot.js +882 -0
  144. package/dist/scripts/design/pipeline/toolkit/tokens.js +456 -0
  145. package/dist/scripts/design/pipeline/visual-regression.js +137 -0
  146. package/dist/scripts/design/pipeline/write-artifacts.js +61 -0
  147. package/package.json +97 -0
  148. package/schemas/manifest.json +1064 -0
  149. package/templates/README.md +12 -0
  150. 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
+ }