@pellux/goodvibes-daemon-sdk 0.30.3 → 0.33.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/README.md +2 -2
- package/dist/api-router.d.ts +4 -2
- package/dist/api-router.d.ts.map +1 -1
- package/dist/api-router.js +18 -10
- package/dist/artifact-upload.d.ts +9 -9
- package/dist/artifact-upload.d.ts.map +1 -1
- package/dist/artifact-upload.js +27 -19
- package/dist/auth-helpers.d.ts +9 -0
- package/dist/auth-helpers.d.ts.map +1 -0
- package/dist/auth-helpers.js +10 -0
- package/dist/automation.js +10 -10
- package/dist/channel-route-types.d.ts +22 -23
- package/dist/channel-route-types.d.ts.map +1 -1
- package/dist/channel-routes.d.ts.map +1 -1
- package/dist/channel-routes.js +37 -42
- package/dist/context.d.ts +27 -27
- package/dist/context.d.ts.map +1 -1
- package/dist/control-routes.d.ts +12 -13
- package/dist/control-routes.d.ts.map +1 -1
- package/dist/control-routes.js +55 -26
- package/dist/error-response.d.ts +10 -3
- package/dist/error-response.d.ts.map +1 -1
- package/dist/error-response.js +102 -11
- package/dist/http-policy.d.ts +3 -4
- package/dist/http-policy.d.ts.map +1 -1
- package/dist/http-policy.js +2 -1
- package/dist/index.d.ts +11 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/integration-routes.d.ts +1 -1
- package/dist/integration-routes.d.ts.map +1 -1
- package/dist/integration-routes.js +72 -33
- package/dist/knowledge-refinement-routes.d.ts.map +1 -1
- package/dist/knowledge-refinement-routes.js +22 -15
- package/dist/knowledge-route-types.d.ts +16 -15
- package/dist/knowledge-route-types.d.ts.map +1 -1
- package/dist/knowledge-routes.d.ts.map +1 -1
- package/dist/knowledge-routes.js +144 -89
- package/dist/media-route-types.d.ts +2 -1
- package/dist/media-route-types.d.ts.map +1 -1
- package/dist/media-routes.d.ts.map +1 -1
- package/dist/media-routes.js +119 -55
- package/dist/operator.d.ts +16 -0
- package/dist/operator.d.ts.map +1 -1
- package/dist/operator.js +105 -61
- package/dist/otlp-protobuf.d.ts.map +1 -1
- package/dist/otlp-protobuf.js +28 -10
- package/dist/remote-routes.d.ts +9 -3
- package/dist/remote-routes.d.ts.map +1 -1
- package/dist/remote-routes.js +259 -163
- package/dist/route-helpers.d.ts +13 -2
- package/dist/route-helpers.d.ts.map +1 -1
- package/dist/route-helpers.js +38 -1
- package/dist/runtime-automation-routes.d.ts.map +1 -1
- package/dist/runtime-automation-routes.js +88 -54
- package/dist/runtime-route-types.d.ts +87 -93
- package/dist/runtime-route-types.d.ts.map +1 -1
- package/dist/runtime-session-routes.d.ts +6 -4
- package/dist/runtime-session-routes.d.ts.map +1 -1
- package/dist/runtime-session-routes.js +132 -88
- package/dist/sessions.js +3 -3
- package/dist/system-route-types.d.ts +25 -24
- package/dist/system-route-types.d.ts.map +1 -1
- package/dist/system-routes.d.ts +1 -1
- package/dist/system-routes.d.ts.map +1 -1
- package/dist/system-routes.js +126 -92
- package/dist/tasks.d.ts.map +1 -1
- package/dist/tasks.js +2 -0
- package/dist/telemetry-routes.d.ts +14 -14
- package/dist/telemetry-routes.d.ts.map +1 -1
- package/dist/telemetry-routes.js +54 -29
- package/package.json +5 -4
package/dist/system-routes.js
CHANGED
|
@@ -1,5 +1,40 @@
|
|
|
1
|
+
import { createRouteBodySchema, createRouteBodySchemaRegistry, readBoundedBodyInteger, readOptionalStringField, } from './route-helpers.js';
|
|
1
2
|
import { jsonErrorResponse } from './error-response.js';
|
|
2
|
-
|
|
3
|
+
import { withAdmin } from './auth-helpers.js';
|
|
4
|
+
const WATCHER_KIND_VALUES = ['filesystem', 'webhook', 'socket', 'integration', 'polling'];
|
|
5
|
+
const WATCHER_SOURCE_KIND_MAP = {
|
|
6
|
+
webhook: 'webhook',
|
|
7
|
+
file: 'filesystem',
|
|
8
|
+
stream: 'socket',
|
|
9
|
+
api: 'integration',
|
|
10
|
+
};
|
|
11
|
+
const WATCHER_SOURCE_RECORD_KIND_MAP = {
|
|
12
|
+
webhook: 'webhook',
|
|
13
|
+
file: 'hook',
|
|
14
|
+
stream: 'hook',
|
|
15
|
+
api: 'hook',
|
|
16
|
+
};
|
|
17
|
+
const DEFAULT_WATCHER_INTERVAL_MS = 60_000;
|
|
18
|
+
const MIN_WATCHER_INTERVAL_MS = 1_000;
|
|
19
|
+
const MAX_WATCHER_INTERVAL_MS = 86_400_000;
|
|
20
|
+
const systemBodySchemas = createRouteBodySchemaRegistry({
|
|
21
|
+
routeBindingCreate: createRouteBodySchema('POST /api/system/route-bindings', (body) => {
|
|
22
|
+
const surfaceKind = readOptionalStringField(body, 'surfaceKind');
|
|
23
|
+
const kind = readOptionalStringField(body, 'kind');
|
|
24
|
+
const surfaceId = readOptionalStringField(body, 'surfaceId');
|
|
25
|
+
const externalId = readOptionalStringField(body, 'externalId');
|
|
26
|
+
if (!surfaceKind || !kind || !surfaceId || !externalId) {
|
|
27
|
+
return jsonErrorResponse({ error: 'Missing required route binding fields' }, { status: 400 });
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
surfaceKind: surfaceKind,
|
|
31
|
+
kind: kind,
|
|
32
|
+
surfaceId,
|
|
33
|
+
externalId,
|
|
34
|
+
};
|
|
35
|
+
}),
|
|
36
|
+
});
|
|
37
|
+
export function createDaemonSystemRouteHandlers(context) {
|
|
3
38
|
return {
|
|
4
39
|
getWatchers: () => Response.json({ watchers: context.watcherRegistry.list() }),
|
|
5
40
|
postWatcher: (req) => {
|
|
@@ -14,20 +49,20 @@ export function createDaemonSystemRouteHandlers(context, request) {
|
|
|
14
49
|
return admin;
|
|
15
50
|
return handleUpdateWatcher(context, watcherId, req);
|
|
16
51
|
},
|
|
17
|
-
watcherAction: (watcherId, action) => {
|
|
18
|
-
const admin = context.requireAdmin(
|
|
52
|
+
watcherAction: (watcherId, action, req) => {
|
|
53
|
+
const admin = context.requireAdmin(req);
|
|
19
54
|
if (admin)
|
|
20
55
|
return admin;
|
|
21
|
-
return handleWatcherAction(context, watcherId, action);
|
|
56
|
+
return handleWatcherAction(context, watcherId, action, req);
|
|
22
57
|
},
|
|
23
|
-
deleteWatcher: (watcherId) => {
|
|
24
|
-
const admin = context.requireAdmin(
|
|
58
|
+
deleteWatcher: (watcherId, req) => {
|
|
59
|
+
const admin = context.requireAdmin(req);
|
|
25
60
|
if (admin)
|
|
26
61
|
return admin;
|
|
27
62
|
const removed = context.watcherRegistry.removeWatcher(watcherId);
|
|
28
63
|
return removed
|
|
29
64
|
? Response.json({ removed: true, id: watcherId })
|
|
30
|
-
:
|
|
65
|
+
: jsonErrorResponse({ error: 'Unknown watcher' }, { status: 404 });
|
|
31
66
|
},
|
|
32
67
|
getServiceStatus: () => Response.json({
|
|
33
68
|
...context.platformServiceManager.status(),
|
|
@@ -37,37 +72,42 @@ export function createDaemonSystemRouteHandlers(context, request) {
|
|
|
37
72
|
outbound: context.inspectOutboundTls(),
|
|
38
73
|
},
|
|
39
74
|
}),
|
|
40
|
-
installService: () => {
|
|
41
|
-
const admin = context.requireAdmin(
|
|
75
|
+
installService: (req) => {
|
|
76
|
+
const admin = context.requireAdmin(req);
|
|
42
77
|
if (admin)
|
|
43
78
|
return admin;
|
|
44
79
|
return Response.json(context.platformServiceManager.install());
|
|
45
80
|
},
|
|
46
|
-
startService: () => {
|
|
47
|
-
const admin = context.requireAdmin(
|
|
81
|
+
startService: (req) => {
|
|
82
|
+
const admin = context.requireAdmin(req);
|
|
48
83
|
if (admin)
|
|
49
84
|
return admin;
|
|
50
85
|
return Response.json(context.platformServiceManager.start());
|
|
51
86
|
},
|
|
52
|
-
stopService: () => {
|
|
53
|
-
const admin = context.requireAdmin(
|
|
87
|
+
stopService: (req) => {
|
|
88
|
+
const admin = context.requireAdmin(req);
|
|
54
89
|
if (admin)
|
|
55
90
|
return admin;
|
|
56
91
|
return Response.json(context.platformServiceManager.stop());
|
|
57
92
|
},
|
|
58
|
-
restartService: () => {
|
|
59
|
-
const admin = context.requireAdmin(
|
|
93
|
+
restartService: (req) => {
|
|
94
|
+
const admin = context.requireAdmin(req);
|
|
60
95
|
if (admin)
|
|
61
96
|
return admin;
|
|
62
97
|
return Response.json(context.platformServiceManager.restart());
|
|
63
98
|
},
|
|
64
|
-
uninstallService: () => {
|
|
65
|
-
const admin = context.requireAdmin(
|
|
99
|
+
uninstallService: (req) => {
|
|
100
|
+
const admin = context.requireAdmin(req);
|
|
66
101
|
if (admin)
|
|
67
102
|
return admin;
|
|
68
103
|
return Response.json(context.platformServiceManager.uninstall());
|
|
69
104
|
},
|
|
70
|
-
getRouteBindings: () =>
|
|
105
|
+
getRouteBindings: (req) => {
|
|
106
|
+
const admin = context.requireAdmin(req);
|
|
107
|
+
if (admin)
|
|
108
|
+
return admin;
|
|
109
|
+
return Response.json({ bindings: context.routeBindings.listBindings() });
|
|
110
|
+
},
|
|
71
111
|
postRouteBinding: async (req) => {
|
|
72
112
|
const admin = context.requireAdmin(req);
|
|
73
113
|
if (admin)
|
|
@@ -75,19 +115,15 @@ export function createDaemonSystemRouteHandlers(context, request) {
|
|
|
75
115
|
const body = await context.parseJsonBody(req);
|
|
76
116
|
if (body instanceof Response)
|
|
77
117
|
return body;
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
const externalId = typeof body.externalId === 'string' ? body.externalId : '';
|
|
82
|
-
if (!surfaceKind || !kind || !surfaceId || !externalId) {
|
|
83
|
-
return Response.json({ error: 'Missing required route binding fields' }, { status: 400 });
|
|
84
|
-
}
|
|
118
|
+
const input = systemBodySchemas.routeBindingCreate.parse(body);
|
|
119
|
+
if (input instanceof Response)
|
|
120
|
+
return input;
|
|
85
121
|
const binding = await context.routeBindings.upsertBinding({
|
|
86
122
|
id: typeof body.id === 'string' ? body.id : undefined,
|
|
87
|
-
kind: kind,
|
|
88
|
-
surfaceKind: surfaceKind,
|
|
89
|
-
surfaceId,
|
|
90
|
-
externalId,
|
|
123
|
+
kind: input.kind,
|
|
124
|
+
surfaceKind: input.surfaceKind,
|
|
125
|
+
surfaceId: input.surfaceId,
|
|
126
|
+
externalId: input.externalId,
|
|
91
127
|
sessionPolicy: typeof body.sessionPolicy === 'string' ? body.sessionPolicy : undefined,
|
|
92
128
|
threadPolicy: typeof body.threadPolicy === 'string' ? body.threadPolicy : undefined,
|
|
93
129
|
deliveryGuarantee: typeof body.deliveryGuarantee === 'string' ? body.deliveryGuarantee : undefined,
|
|
@@ -122,26 +158,26 @@ export function createDaemonSystemRouteHandlers(context, request) {
|
|
|
122
158
|
});
|
|
123
159
|
return updated
|
|
124
160
|
? Response.json(updated)
|
|
125
|
-
:
|
|
161
|
+
: jsonErrorResponse({ error: 'Unknown route binding' }, { status: 404 });
|
|
126
162
|
},
|
|
127
|
-
deleteRouteBinding: async (bindingId) => {
|
|
128
|
-
const admin = context.requireAdmin(
|
|
163
|
+
deleteRouteBinding: async (bindingId, req) => {
|
|
164
|
+
const admin = context.requireAdmin(req);
|
|
129
165
|
if (admin)
|
|
130
166
|
return admin;
|
|
131
167
|
const removed = await context.routeBindings.removeBinding(bindingId);
|
|
132
168
|
return removed
|
|
133
169
|
? Response.json({ removed: true, id: bindingId })
|
|
134
|
-
:
|
|
170
|
+
: jsonErrorResponse({ error: 'Unknown route binding' }, { status: 404 });
|
|
135
171
|
},
|
|
136
172
|
getApprovals: () => {
|
|
137
173
|
if (!context.integrationHelpers) {
|
|
138
|
-
return
|
|
174
|
+
return jsonErrorResponse({ error: 'Integration helper service unavailable' }, { status: 503 });
|
|
139
175
|
}
|
|
140
176
|
return Response.json(context.integrationHelpers.getApprovalSnapshot());
|
|
141
177
|
},
|
|
142
178
|
approvalAction: (approvalId, action, req) => handleApprovalAction(context, approvalId, action, req),
|
|
143
|
-
getConfig: () => {
|
|
144
|
-
const admin = context.requireAdmin(
|
|
179
|
+
getConfig: (req) => {
|
|
180
|
+
const admin = context.requireAdmin(req);
|
|
145
181
|
if (admin)
|
|
146
182
|
return admin;
|
|
147
183
|
return Response.json(context.configManager.getAll());
|
|
@@ -155,28 +191,13 @@ export function createDaemonSystemRouteHandlers(context, request) {
|
|
|
155
191
|
return payload;
|
|
156
192
|
const { key, value } = payload;
|
|
157
193
|
if (!key || typeof key !== 'string') {
|
|
158
|
-
return
|
|
194
|
+
return jsonErrorResponse({ error: 'Missing or invalid key' }, { status: 400 });
|
|
159
195
|
}
|
|
160
|
-
// Special-case: runtime.workingDir is handled by the workspace swap manager.
|
|
161
196
|
if (key === 'runtime.workingDir') {
|
|
162
|
-
|
|
163
|
-
return Response.json({ error: 'Workspace swapping is not available in this daemon configuration.' }, { status: 400 });
|
|
164
|
-
}
|
|
165
|
-
if (typeof value !== 'string' || !value.trim()) {
|
|
166
|
-
return Response.json({ error: 'runtime.workingDir value must be a non-empty string path.', code: 'INVALID_PATH' }, { status: 400 });
|
|
167
|
-
}
|
|
168
|
-
const result = await context.swapManager.requestSwap(value);
|
|
169
|
-
if (result.ok) {
|
|
170
|
-
return Response.json({ success: true, key, value: result.current, previous: result.previous });
|
|
171
|
-
}
|
|
172
|
-
if (result.code === 'WORKSPACE_BUSY') {
|
|
173
|
-
return Response.json({ error: result.reason, code: 'WORKSPACE_BUSY', retryAfter: result.retryAfter }, { status: 409 });
|
|
174
|
-
}
|
|
175
|
-
// INVALID_PATH
|
|
176
|
-
return Response.json({ error: result.reason, code: 'INVALID_PATH' }, { status: 400 });
|
|
197
|
+
return handleWorkingDirectoryConfig(context, key, value);
|
|
177
198
|
}
|
|
178
199
|
if (!context.isValidConfigKey(key)) {
|
|
179
|
-
return
|
|
200
|
+
return jsonErrorResponse({ error: 'Invalid config key' }, { status: 400 });
|
|
180
201
|
}
|
|
181
202
|
try {
|
|
182
203
|
context.configManager.setDynamic(key, value);
|
|
@@ -196,24 +217,16 @@ async function handleRegisterWatcher(context, req) {
|
|
|
196
217
|
const id = typeof body.id === 'string' && body.id.trim().length > 0
|
|
197
218
|
? body.id.trim()
|
|
198
219
|
: label
|
|
199
|
-
?
|
|
220
|
+
? (() => {
|
|
221
|
+
const slug = label.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, '');
|
|
222
|
+
// If the label consists entirely of special chars the slug will be empty.
|
|
223
|
+
return slug ? `watcher-${slug}` : `watcher-${Date.now()}`;
|
|
224
|
+
})()
|
|
200
225
|
: `watcher-${Date.now()}`;
|
|
201
|
-
const kind =
|
|
202
|
-
|
|
203
|
-
: typeof body.sourceKind === 'string'
|
|
204
|
-
? body.sourceKind === 'webhook'
|
|
205
|
-
? 'webhook'
|
|
206
|
-
: body.sourceKind === 'file'
|
|
207
|
-
? 'filesystem'
|
|
208
|
-
: body.sourceKind === 'stream'
|
|
209
|
-
? 'socket'
|
|
210
|
-
: body.sourceKind === 'api'
|
|
211
|
-
? 'integration'
|
|
212
|
-
: 'polling'
|
|
213
|
-
: 'polling';
|
|
214
|
-
const intervalMs = Number(body.intervalMs ?? context.configManager.get('watchers.pollIntervalMs') ?? 60_000);
|
|
226
|
+
const kind = readWatcherKind(body.kind, body.sourceKind, 'polling');
|
|
227
|
+
const intervalMs = readWatcherIntervalMs(body.intervalMs, context.configManager.get('watchers.pollIntervalMs'));
|
|
215
228
|
if (!label) {
|
|
216
|
-
return
|
|
229
|
+
return jsonErrorResponse({ error: 'Missing watcher label' }, { status: 400 });
|
|
217
230
|
}
|
|
218
231
|
const metadata = typeof body.metadata === 'object' && body.metadata !== null
|
|
219
232
|
? body.metadata
|
|
@@ -234,11 +247,7 @@ async function handleRegisterWatcher(context, req) {
|
|
|
234
247
|
source: {
|
|
235
248
|
id: typeof body.sourceId === 'string' && body.sourceId.trim() ? body.sourceId.trim() : `source:${id}`,
|
|
236
249
|
kind: typeof body.sourceKind === 'string'
|
|
237
|
-
? body.sourceKind
|
|
238
|
-
? 'webhook'
|
|
239
|
-
: body.sourceKind === 'file' || body.sourceKind === 'stream' || body.sourceKind === 'api'
|
|
240
|
-
? 'hook'
|
|
241
|
-
: 'watcher'
|
|
250
|
+
? WATCHER_SOURCE_RECORD_KIND_MAP[body.sourceKind] ?? 'watcher'
|
|
242
251
|
: 'watcher',
|
|
243
252
|
label,
|
|
244
253
|
enabled: true,
|
|
@@ -252,27 +261,52 @@ async function handleRegisterWatcher(context, req) {
|
|
|
252
261
|
});
|
|
253
262
|
return Response.json(record, { status: 201 });
|
|
254
263
|
}
|
|
264
|
+
async function handleWorkingDirectoryConfig(context, key, value) {
|
|
265
|
+
if (!context.swapManager) {
|
|
266
|
+
return jsonErrorResponse({ error: 'Workspace swapping is not available in this daemon configuration.' }, { status: 400 });
|
|
267
|
+
}
|
|
268
|
+
if (typeof value !== 'string' || !value.trim()) {
|
|
269
|
+
return jsonErrorResponse({ error: 'runtime.workingDir value must be a non-empty string path.', code: 'INVALID_PATH' }, { status: 400 });
|
|
270
|
+
}
|
|
271
|
+
const result = await context.swapManager.requestSwap(value);
|
|
272
|
+
if (result.ok) {
|
|
273
|
+
return Response.json({ success: true, key, value: result.current, previous: result.previous });
|
|
274
|
+
}
|
|
275
|
+
return jsonErrorResponse({
|
|
276
|
+
error: result.reason,
|
|
277
|
+
code: result.code,
|
|
278
|
+
...(result.code === 'WORKSPACE_BUSY' ? { retryAfter: result.retryAfter } : {}),
|
|
279
|
+
}, { status: result.code === 'WORKSPACE_BUSY' ? 409 : 400 });
|
|
280
|
+
}
|
|
281
|
+
function readWatcherKind(kind, sourceKind, fallback) {
|
|
282
|
+
if (typeof kind === 'string' && WATCHER_KIND_VALUES.includes(kind)) {
|
|
283
|
+
return kind;
|
|
284
|
+
}
|
|
285
|
+
return typeof sourceKind === 'string'
|
|
286
|
+
? WATCHER_SOURCE_KIND_MAP[sourceKind] ?? fallback
|
|
287
|
+
: fallback;
|
|
288
|
+
}
|
|
289
|
+
function readWatcherIntervalMs(value, fallbackValue) {
|
|
290
|
+
const fallback = typeof fallbackValue === 'number' && Number.isFinite(fallbackValue)
|
|
291
|
+
? fallbackValue
|
|
292
|
+
: DEFAULT_WATCHER_INTERVAL_MS;
|
|
293
|
+
return readBoundedBodyInteger(value, fallback, MAX_WATCHER_INTERVAL_MS, MIN_WATCHER_INTERVAL_MS);
|
|
294
|
+
}
|
|
255
295
|
async function handleUpdateWatcher(context, watcherId, req) {
|
|
256
296
|
const body = await context.parseJsonBody(req);
|
|
257
297
|
if (body instanceof Response)
|
|
258
298
|
return body;
|
|
259
299
|
const current = context.watcherRegistry.getWatcher(watcherId);
|
|
260
300
|
if (!current) {
|
|
261
|
-
return
|
|
301
|
+
return jsonErrorResponse({ error: 'Unknown watcher' }, { status: 404 });
|
|
262
302
|
}
|
|
263
303
|
const nextSourceKind = typeof body.sourceKind === 'string'
|
|
264
|
-
? body.sourceKind
|
|
265
|
-
? 'webhook'
|
|
266
|
-
: body.sourceKind === 'file' || body.sourceKind === 'stream' || body.sourceKind === 'api'
|
|
267
|
-
? 'hook'
|
|
268
|
-
: 'watcher'
|
|
304
|
+
? WATCHER_SOURCE_RECORD_KIND_MAP[body.sourceKind] ?? 'watcher'
|
|
269
305
|
: current.source.kind;
|
|
270
306
|
const updated = context.watcherRegistry.registerWatcher({
|
|
271
307
|
id: watcherId,
|
|
272
308
|
label: typeof body.label === 'string' ? body.label : current.label,
|
|
273
|
-
kind:
|
|
274
|
-
? body.kind
|
|
275
|
-
: current.kind,
|
|
309
|
+
kind: readWatcherKind(body.kind, undefined, current.kind),
|
|
276
310
|
source: {
|
|
277
311
|
...current.source,
|
|
278
312
|
...(typeof body.source === 'object' && body.source !== null ? body.source : {}),
|
|
@@ -292,28 +326,28 @@ async function handleUpdateWatcher(context, watcherId, req) {
|
|
|
292
326
|
},
|
|
293
327
|
updatedAt: Date.now(),
|
|
294
328
|
},
|
|
295
|
-
intervalMs:
|
|
329
|
+
intervalMs: body.intervalMs !== undefined ? readWatcherIntervalMs(body.intervalMs, current.intervalMs) : (current.intervalMs ?? DEFAULT_WATCHER_INTERVAL_MS),
|
|
296
330
|
metadata: typeof body.metadata === 'object' && body.metadata !== null ? body.metadata : current.metadata,
|
|
297
331
|
});
|
|
298
332
|
return Response.json(updated);
|
|
299
333
|
}
|
|
300
|
-
async function handleWatcherAction(context, watcherId, action) {
|
|
334
|
+
async function handleWatcherAction(context, watcherId, action, _req) {
|
|
301
335
|
if (action === 'start') {
|
|
302
336
|
const watcher = context.watcherRegistry.startWatcher(watcherId);
|
|
303
337
|
return watcher
|
|
304
338
|
? Response.json(watcher)
|
|
305
|
-
:
|
|
339
|
+
: jsonErrorResponse({ error: 'Unknown watcher' }, { status: 404 });
|
|
306
340
|
}
|
|
307
341
|
if (action === 'stop') {
|
|
308
342
|
const watcher = context.watcherRegistry.stopWatcher(watcherId, 'operator-stop');
|
|
309
343
|
return watcher
|
|
310
344
|
? Response.json(watcher)
|
|
311
|
-
:
|
|
345
|
+
: jsonErrorResponse({ error: 'Unknown watcher' }, { status: 404 });
|
|
312
346
|
}
|
|
313
347
|
const watcher = await context.watcherRegistry.runWatcherNow(watcherId);
|
|
314
348
|
return watcher
|
|
315
349
|
? Response.json(watcher)
|
|
316
|
-
:
|
|
350
|
+
: jsonErrorResponse({ error: 'Unknown watcher' }, { status: 404 });
|
|
317
351
|
}
|
|
318
352
|
async function handleApprovalAction(context, approvalId, action, req) {
|
|
319
353
|
const body = await context.parseOptionalJsonBody(req);
|
|
@@ -324,13 +358,13 @@ async function handleApprovalAction(context, approvalId, action, req) {
|
|
|
324
358
|
const approval = await context.approvalBroker.claimApproval(approvalId, actor, 'web', note);
|
|
325
359
|
return approval
|
|
326
360
|
? context.recordApiResponse(req, `/api/approvals/${approvalId}/${action}`, Response.json({ approval }))
|
|
327
|
-
: context.recordApiResponse(req, `/api/approvals/${approvalId}/${action}`,
|
|
361
|
+
: context.recordApiResponse(req, `/api/approvals/${approvalId}/${action}`, jsonErrorResponse({ error: 'Unknown approval' }, { status: 404 }));
|
|
328
362
|
}
|
|
329
363
|
if (action === 'cancel') {
|
|
330
364
|
const approval = await context.approvalBroker.cancelApproval(approvalId, actor, 'web', note);
|
|
331
365
|
return approval
|
|
332
366
|
? context.recordApiResponse(req, `/api/approvals/${approvalId}/${action}`, Response.json({ approval }))
|
|
333
|
-
: context.recordApiResponse(req, `/api/approvals/${approvalId}/${action}`,
|
|
367
|
+
: context.recordApiResponse(req, `/api/approvals/${approvalId}/${action}`, jsonErrorResponse({ error: 'Unknown approval' }, { status: 404 }));
|
|
334
368
|
}
|
|
335
369
|
const approval = await context.approvalBroker.resolveApproval(approvalId, {
|
|
336
370
|
approved: action === 'approve',
|
|
@@ -341,5 +375,5 @@ async function handleApprovalAction(context, approvalId, action, req) {
|
|
|
341
375
|
});
|
|
342
376
|
return approval
|
|
343
377
|
? context.recordApiResponse(req, `/api/approvals/${approvalId}/${action}`, Response.json({ approval }))
|
|
344
|
-
: context.recordApiResponse(req, `/api/approvals/${approvalId}/${action}`,
|
|
378
|
+
: context.recordApiResponse(req, `/api/approvals/${approvalId}/${action}`, jsonErrorResponse({ error: 'Unknown approval' }, { status: 404 }));
|
|
345
379
|
}
|
package/dist/tasks.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tasks.d.ts","sourceRoot":"","sources":["../src/tasks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAC;AAE5D,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,OAAO,EACZ,QAAQ,EAAE,uBAAuB,GAChC,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"tasks.d.ts","sourceRoot":"","sources":["../src/tasks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAC;AAE5D,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,OAAO,EACZ,QAAQ,EAAE,uBAAuB,GAChC,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAuB1B"}
|
package/dist/tasks.js
CHANGED
|
@@ -4,6 +4,8 @@ export async function dispatchTaskRoutes(req, handlers) {
|
|
|
4
4
|
const method = req.method;
|
|
5
5
|
if (pathname === '/task' && method === 'POST')
|
|
6
6
|
return handlers.postTask(req);
|
|
7
|
+
// GET /task is intentionally left to extension routers that expose
|
|
8
|
+
// surface-specific task status pages.
|
|
7
9
|
if (pathname === '/task' && method === 'GET')
|
|
8
10
|
return null;
|
|
9
11
|
if (pathname === '/api/tasks' && method === 'GET')
|
|
@@ -4,19 +4,19 @@ import type { RuntimeEventDomain } from '@pellux/goodvibes-contracts';
|
|
|
4
4
|
type TelemetrySeverity = 'debug' | 'info' | 'warn' | 'error';
|
|
5
5
|
type TelemetryViewMode = 'safe' | 'raw';
|
|
6
6
|
interface TelemetryFilter {
|
|
7
|
-
readonly limit?: number;
|
|
8
|
-
readonly since?: number;
|
|
9
|
-
readonly until?: number;
|
|
10
|
-
readonly domains?: readonly RuntimeEventDomain[];
|
|
11
|
-
readonly eventTypes?: readonly string[];
|
|
12
|
-
readonly severity?: TelemetrySeverity;
|
|
13
|
-
readonly traceId?: string;
|
|
14
|
-
readonly sessionId?: string;
|
|
15
|
-
readonly turnId?: string;
|
|
16
|
-
readonly agentId?: string;
|
|
17
|
-
readonly taskId?: string;
|
|
18
|
-
readonly cursor?: string;
|
|
19
|
-
readonly view?: TelemetryViewMode;
|
|
7
|
+
readonly limit?: number | undefined;
|
|
8
|
+
readonly since?: number | undefined;
|
|
9
|
+
readonly until?: number | undefined;
|
|
10
|
+
readonly domains?: readonly RuntimeEventDomain[] | undefined;
|
|
11
|
+
readonly eventTypes?: readonly string[] | undefined;
|
|
12
|
+
readonly severity?: TelemetrySeverity | undefined;
|
|
13
|
+
readonly traceId?: string | undefined;
|
|
14
|
+
readonly sessionId?: string | undefined;
|
|
15
|
+
readonly turnId?: string | undefined;
|
|
16
|
+
readonly agentId?: string | undefined;
|
|
17
|
+
readonly taskId?: string | undefined;
|
|
18
|
+
readonly cursor?: string | undefined;
|
|
19
|
+
readonly view?: TelemetryViewMode | undefined;
|
|
20
20
|
}
|
|
21
21
|
interface TelemetryApiLike {
|
|
22
22
|
getSnapshot(filter: TelemetryFilter, view: TelemetryViewMode, rawAccessible: boolean): {
|
|
@@ -56,7 +56,7 @@ interface TelemetryRouteContext {
|
|
|
56
56
|
* In production `DaemonHttpRouter` this is the `TelemetryApiService` instance
|
|
57
57
|
* which stores ingested records in its bounded event buffer (default 500
|
|
58
58
|
* records) and makes them observable via GET /api/v1/telemetry/events.
|
|
59
|
-
* Pass `null` only in test
|
|
59
|
+
* Pass `null` only in test fixtures where ingestion is intentionally
|
|
60
60
|
* a no-op — the receivers still return 200 to keep OTLP exporters happy but
|
|
61
61
|
* discard the payload.
|
|
62
62
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"telemetry-routes.d.ts","sourceRoot":"","sources":["../src/telemetry-routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"telemetry-routes.d.ts","sourceRoot":"","sources":["../src/telemetry-routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,cAAc,CAAC;AAEjE,OAAO,EAAyB,KAAK,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAEtF,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AAItE,KAAK,iBAAiB,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;AAC7D,KAAK,iBAAiB,GAAG,MAAM,GAAG,KAAK,CAAC;AAExC,UAAU,eAAe;IACvB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,kBAAkB,EAAE,GAAG,SAAS,CAAC;IAC7D,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;IACpD,QAAQ,CAAC,QAAQ,CAAC,EAAE,iBAAiB,GAAG,SAAS,CAAC;IAClD,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACtC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACxC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACrC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACtC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACrC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACrC,QAAQ,CAAC,IAAI,CAAC,EAAE,iBAAiB,GAAG,SAAS,CAAC;CAC/C;AAED,UAAU,gBAAgB;IACxB,WAAW,CAAC,MAAM,EAAE,eAAe,EAAE,IAAI,EAAE,iBAAiB,EAAE,aAAa,EAAE,OAAO,GAAG;QACrF,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;QAC7B,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAAC;QACjC,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;QAChC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;QAC1B,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;QACjC,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;KAC9B,CAAC;IACF,aAAa,CAAC,MAAM,EAAE,eAAe,EAAE,IAAI,EAAE,iBAAiB,EAAE,aAAa,EAAE,OAAO,GAAG,OAAO,CAAC;IACjG,aAAa,CAAC,MAAM,EAAE,eAAe,EAAE,IAAI,EAAE,iBAAiB,EAAE,aAAa,EAAE,OAAO,GAAG,OAAO,CAAC;IACjG,YAAY,CAAC,MAAM,EAAE,eAAe,EAAE,IAAI,EAAE,iBAAiB,EAAE,aAAa,EAAE,OAAO,GAAG,OAAO,CAAC;IAChG,YAAY,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,eAAe,EAAE,IAAI,EAAE,iBAAiB,EAAE,aAAa,EAAE,OAAO,GAAG,QAAQ,CAAC;IAC/G,sBAAsB,CAAC,MAAM,EAAE,eAAe,EAAE,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC;IAClF,oBAAoB,CAAC,MAAM,EAAE,eAAe,EAAE,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC;IAChF,uBAAuB,IAAI,OAAO,CAAC;CACpC;AAED;;;;GAIG;AACH,UAAU,mBAAmB;IAC3B,2EAA2E;IAC3E,UAAU,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IACnD,4EAA4E;IAC5E,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IACrD,qFAAqF;IACrF,aAAa,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CACvD;AAED,UAAU,qBAAqB;IAC7B,QAAQ,CAAC,YAAY,EAAE,gBAAgB,GAAG,IAAI,CAAC;IAC/C,QAAQ,CAAC,6BAA6B,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,sBAAsB,GAAG,IAAI,CAAC;IACxF;;;;;;;;OAQG;IACH,QAAQ,CAAC,UAAU,EAAE,mBAAmB,GAAG,IAAI,CAAC;CACjD;AAyQD,wBAAgB,kCAAkC,CAChD,OAAO,EAAE,qBAAqB,GAC7B,4BAA4B,CA4H9B"}
|
package/dist/telemetry-routes.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import { jsonErrorResponse } from './error-response.js';
|
|
1
2
|
import { buildMissingScopeBody } from './http-policy.js';
|
|
2
3
|
import { decodeOtlpProtobuf } from './otlp-protobuf.js';
|
|
3
4
|
import { DaemonErrorCategory } from '@pellux/goodvibes-errors';
|
|
4
|
-
import { readBoundedPositiveInteger, readOptionalBoundedInteger } from './route-helpers.js';
|
|
5
|
+
import { hasAnyScope, readBoundedPositiveInteger, readOptionalBoundedInteger } from './route-helpers.js';
|
|
5
6
|
const MAX_TELEMETRY_TIMESTAMP_MS = Date.UTC(2100, 0, 1);
|
|
6
7
|
function parseLimit(value) {
|
|
7
8
|
if (value === null || value.trim().length === 0)
|
|
@@ -65,6 +66,7 @@ const OTLP_PARTIAL_SUCCESS_KEYS = {
|
|
|
65
66
|
traces: 'partialSuccess',
|
|
66
67
|
metrics: 'partialSuccess',
|
|
67
68
|
};
|
|
69
|
+
const OTLP_INGEST_SCOPES = ['ingest:telemetry', 'write:telemetry'];
|
|
68
70
|
/**
|
|
69
71
|
* Validate and parse an OTLP HTTP ingest request body.
|
|
70
72
|
* Returns a parsed JSON Record on success, or a Response (error) on failure.
|
|
@@ -73,20 +75,29 @@ const OTLP_PARTIAL_SUCCESS_KEYS = {
|
|
|
73
75
|
* requests for logs, traces, and metrics.
|
|
74
76
|
*/
|
|
75
77
|
async function parseOtlpBody(req, kind) {
|
|
76
|
-
const contentType = (req.headers.get('content-type') ?? '').toLowerCase().split(';')[0].trim();
|
|
78
|
+
const contentType = ((req.headers.get('content-type') ?? '').toLowerCase().split(';')[0] ?? '').trim();
|
|
77
79
|
const acceptsJson = contentType === OTLP_JSON_CONTENT_TYPE;
|
|
78
80
|
const acceptsProtobuf = OTLP_PROTOBUF_CONTENT_TYPES.has(contentType);
|
|
79
81
|
if (!acceptsJson && !acceptsProtobuf) {
|
|
80
|
-
return
|
|
82
|
+
return jsonErrorResponse({
|
|
81
83
|
error: `Unsupported Content-Type '${contentType}' for OTLP ingest`,
|
|
82
84
|
code: 'UNSUPPORTED_MEDIA_TYPE',
|
|
83
85
|
category: DaemonErrorCategory.BAD_REQUEST,
|
|
84
86
|
hint: `Use '${OTLP_JSON_CONTENT_TYPE}' or 'application/x-protobuf'.`,
|
|
85
87
|
}, { status: 415 });
|
|
86
88
|
}
|
|
89
|
+
// reject oversized requests before buffering the body into memory.
|
|
90
|
+
const contentLength = Number(req.headers.get('content-length'));
|
|
91
|
+
if (!Number.isNaN(contentLength) && contentLength > OTLP_INGEST_MAX_BODY_BYTES) {
|
|
92
|
+
return jsonErrorResponse({
|
|
93
|
+
error: `OTLP ingest payload too large (Content-Length: ${contentLength} > ${OTLP_INGEST_MAX_BODY_BYTES} bytes)`,
|
|
94
|
+
code: 'PAYLOAD_TOO_LARGE',
|
|
95
|
+
category: DaemonErrorCategory.BAD_REQUEST,
|
|
96
|
+
}, { status: 413 });
|
|
97
|
+
}
|
|
87
98
|
const raw = await req.arrayBuffer();
|
|
88
99
|
if (raw.byteLength > OTLP_INGEST_MAX_BODY_BYTES) {
|
|
89
|
-
return
|
|
100
|
+
return jsonErrorResponse({
|
|
90
101
|
error: `OTLP ingest payload too large (${raw.byteLength} > ${OTLP_INGEST_MAX_BODY_BYTES} bytes)`,
|
|
91
102
|
code: 'PAYLOAD_TOO_LARGE',
|
|
92
103
|
category: DaemonErrorCategory.BAD_REQUEST,
|
|
@@ -96,22 +107,20 @@ async function parseOtlpBody(req, kind) {
|
|
|
96
107
|
try {
|
|
97
108
|
return decodeOtlpProtobuf(kind, new Uint8Array(raw));
|
|
98
109
|
}
|
|
99
|
-
catch
|
|
100
|
-
|
|
101
|
-
return Response.json({ error: 'OTLP ingest body is not valid protobuf', code: 'INVALID_PAYLOAD', category: DaemonErrorCategory.BAD_REQUEST }, { status: 400 });
|
|
110
|
+
catch {
|
|
111
|
+
return jsonErrorResponse({ error: 'OTLP ingest body is not valid protobuf', code: 'INVALID_PAYLOAD', category: DaemonErrorCategory.BAD_REQUEST }, { status: 400 });
|
|
102
112
|
}
|
|
103
113
|
}
|
|
104
114
|
try {
|
|
105
115
|
const text = new TextDecoder().decode(raw);
|
|
106
116
|
const parsed = JSON.parse(text);
|
|
107
117
|
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
|
|
108
|
-
return
|
|
118
|
+
return jsonErrorResponse({ error: 'OTLP ingest body must be a JSON object', code: 'INVALID_PAYLOAD', category: DaemonErrorCategory.BAD_REQUEST }, { status: 400 });
|
|
109
119
|
}
|
|
110
120
|
return parsed;
|
|
111
121
|
}
|
|
112
|
-
catch
|
|
113
|
-
|
|
114
|
-
return Response.json({ error: 'OTLP ingest body is not valid JSON', code: 'INVALID_PAYLOAD', category: DaemonErrorCategory.BAD_REQUEST }, { status: 400 });
|
|
122
|
+
catch {
|
|
123
|
+
return jsonErrorResponse({ error: 'OTLP ingest body is not valid JSON', code: 'INVALID_PAYLOAD', category: DaemonErrorCategory.BAD_REQUEST }, { status: 400 });
|
|
115
124
|
}
|
|
116
125
|
}
|
|
117
126
|
function otlpIngestSuccess(kind) {
|
|
@@ -119,8 +128,27 @@ function otlpIngestSuccess(kind) {
|
|
|
119
128
|
// partialSuccess omitted when empty (all records accepted).
|
|
120
129
|
return Response.json({ [OTLP_PARTIAL_SUCCESS_KEYS[kind]]: {} });
|
|
121
130
|
}
|
|
131
|
+
function authenticateTelemetryIngest(context, req) {
|
|
132
|
+
const principal = context.resolveAuthenticatedPrincipal(req);
|
|
133
|
+
if (!principal) {
|
|
134
|
+
return jsonErrorResponse({ error: 'Authentication required for OTLP ingest', code: 'AUTH_REQUIRED', category: DaemonErrorCategory.AUTHENTICATION, status: 401 }, { status: 401 });
|
|
135
|
+
}
|
|
136
|
+
if (!principal.admin && !hasAnyScope(principal.scopes, OTLP_INGEST_SCOPES)) {
|
|
137
|
+
return jsonErrorResponse({
|
|
138
|
+
error: `Missing required telemetry ingest scope: ${OTLP_INGEST_SCOPES.join(' or ')}`,
|
|
139
|
+
code: 'MISSING_SCOPE',
|
|
140
|
+
category: DaemonErrorCategory.AUTHORIZATION,
|
|
141
|
+
source: 'permission',
|
|
142
|
+
recoverable: false,
|
|
143
|
+
requiredScopes: [...OTLP_INGEST_SCOPES],
|
|
144
|
+
grantedScopes: [...principal.scopes],
|
|
145
|
+
status: 403,
|
|
146
|
+
}, { status: 403 });
|
|
147
|
+
}
|
|
148
|
+
return principal;
|
|
149
|
+
}
|
|
122
150
|
function unavailable() {
|
|
123
|
-
return
|
|
151
|
+
return jsonErrorResponse({
|
|
124
152
|
error: 'Telemetry API unavailable',
|
|
125
153
|
code: 'TELEMETRY_UNAVAILABLE',
|
|
126
154
|
category: DaemonErrorCategory.SERVICE,
|
|
@@ -131,7 +159,7 @@ function unavailable() {
|
|
|
131
159
|
}, { status: 503 });
|
|
132
160
|
}
|
|
133
161
|
function invalidCursor(error) {
|
|
134
|
-
return
|
|
162
|
+
return jsonErrorResponse({
|
|
135
163
|
error: error instanceof Error ? error.message : 'Invalid telemetry cursor',
|
|
136
164
|
code: 'INVALID_CURSOR',
|
|
137
165
|
category: DaemonErrorCategory.BAD_REQUEST,
|
|
@@ -144,7 +172,7 @@ function invalidCursor(error) {
|
|
|
144
172
|
function authenticateTelemetryRequest(context, req, requestedView) {
|
|
145
173
|
const principal = context.resolveAuthenticatedPrincipal(req);
|
|
146
174
|
if (!principal) {
|
|
147
|
-
return
|
|
175
|
+
return jsonErrorResponse({
|
|
148
176
|
error: 'Authentication required for telemetry access',
|
|
149
177
|
code: 'AUTH_REQUIRED',
|
|
150
178
|
category: DaemonErrorCategory.AUTHENTICATION,
|
|
@@ -156,7 +184,7 @@ function authenticateTelemetryRequest(context, req, requestedView) {
|
|
|
156
184
|
}
|
|
157
185
|
const missingRead = buildMissingScopeBody('telemetry access', ['read:telemetry'], principal.scopes);
|
|
158
186
|
if (missingRead) {
|
|
159
|
-
return
|
|
187
|
+
return jsonErrorResponse({
|
|
160
188
|
error: missingRead.error,
|
|
161
189
|
code: 'MISSING_SCOPE',
|
|
162
190
|
category: DaemonErrorCategory.AUTHORIZATION,
|
|
@@ -164,12 +192,12 @@ function authenticateTelemetryRequest(context, req, requestedView) {
|
|
|
164
192
|
recoverable: false,
|
|
165
193
|
hint: 'Use a token or session with the read:telemetry scope, or elevate to an admin/shared-token session.',
|
|
166
194
|
status: 403,
|
|
167
|
-
detail:
|
|
195
|
+
detail: missingRead,
|
|
168
196
|
}, { status: 403 });
|
|
169
197
|
}
|
|
170
198
|
const rawAccessible = principal.admin || principal.scopes.includes('read:telemetry-sensitive');
|
|
171
199
|
if (requestedView === 'raw' && !rawAccessible) {
|
|
172
|
-
return
|
|
200
|
+
return jsonErrorResponse({
|
|
173
201
|
error: 'Raw telemetry view requires elevated telemetry scope',
|
|
174
202
|
code: 'MISSING_SCOPE',
|
|
175
203
|
category: DaemonErrorCategory.AUTHORIZATION,
|
|
@@ -307,10 +335,9 @@ export function createDaemonTelemetryRouteHandlers(context) {
|
|
|
307
335
|
// OTLP POST ingest receivers
|
|
308
336
|
// -------------------------------------------------------------------------
|
|
309
337
|
postTelemetryOtlpLogs: async (req) => {
|
|
310
|
-
const auth = context
|
|
311
|
-
if (
|
|
312
|
-
return
|
|
313
|
-
}
|
|
338
|
+
const auth = authenticateTelemetryIngest(context, req);
|
|
339
|
+
if (auth instanceof Response)
|
|
340
|
+
return auth;
|
|
314
341
|
const bodyOrErr = await parseOtlpBody(req, 'logs');
|
|
315
342
|
if (bodyOrErr instanceof Response)
|
|
316
343
|
return bodyOrErr;
|
|
@@ -318,10 +345,9 @@ export function createDaemonTelemetryRouteHandlers(context) {
|
|
|
318
345
|
return otlpIngestSuccess('logs');
|
|
319
346
|
},
|
|
320
347
|
postTelemetryOtlpTraces: async (req) => {
|
|
321
|
-
const auth = context
|
|
322
|
-
if (
|
|
323
|
-
return
|
|
324
|
-
}
|
|
348
|
+
const auth = authenticateTelemetryIngest(context, req);
|
|
349
|
+
if (auth instanceof Response)
|
|
350
|
+
return auth;
|
|
325
351
|
const bodyOrErr = await parseOtlpBody(req, 'traces');
|
|
326
352
|
if (bodyOrErr instanceof Response)
|
|
327
353
|
return bodyOrErr;
|
|
@@ -329,10 +355,9 @@ export function createDaemonTelemetryRouteHandlers(context) {
|
|
|
329
355
|
return otlpIngestSuccess('traces');
|
|
330
356
|
},
|
|
331
357
|
postTelemetryOtlpMetrics: async (req) => {
|
|
332
|
-
const auth = context
|
|
333
|
-
if (
|
|
334
|
-
return
|
|
335
|
-
}
|
|
358
|
+
const auth = authenticateTelemetryIngest(context, req);
|
|
359
|
+
if (auth instanceof Response)
|
|
360
|
+
return auth;
|
|
336
361
|
const bodyOrErr = await parseOtlpBody(req, 'metrics');
|
|
337
362
|
if (bodyOrErr instanceof Response)
|
|
338
363
|
return bodyOrErr;
|