@safebrowse/daemon 0.1.2 → 0.1.4

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/dist/server.js CHANGED
@@ -1,10 +1,15 @@
1
- import { randomUUID } from "node:crypto";
2
- import { stat } from "node:fs/promises";
1
+ import { createHash, createPublicKey, randomUUID } from "node:crypto";
2
+ import { readFile, stat } from "node:fs/promises";
3
3
  import { createServer } from "node:http";
4
4
  import { resolve } from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
- import { brokerArtifact, brokerArtifactV2, buildReplayBundle, compilePolicy, evaluateAction, evaluateMemoryWrite, evaluateToolRequest, prepareToolOnboarding, sanitizeObservation, verifyToolCallback } from "@safebrowse/core";
6
+ import { applyV4FailClosedMediation, applyV5ObservationMediation, attachCapabilitiesToPlannerInput, brokerArtifact, brokerArtifactV2, buildReplayBundle, compilePolicy, createApprovalGrantHash, createApprovalIntentPayloadV5, evaluateAction, evaluateCapabilityUse, evaluateCapabilityUseV5, evaluateMemoryWrite, evaluateMemoryWriteV4, evaluateMemoryWriteV5, evaluateToolRequest, mintCapabilitiesForObservation, mintCapabilitiesForObservationV5, mintMemoryPromotionCapabilityV5, prepareToolOnboarding, prepareToolOnboardingV4, prepareToolOnboardingV5, promoteMemoryRecordV4, promoteMemoryRecordV5, promoteMemoryRecordV6, promoteStagedMemoryRecordV5, rollbackMemoryRecordV4, rollbackMemoryRecordV5, sanitizeObservation, stageMemoryRecordV6, stageMemoryRecordV5, issueApprovalEnvelopeV5, verifyApprovalIntentSignatureV5, verifyToolCallback, verifyToolCallbackV4, verifyToolCallbackV5 } from "@safebrowse/core";
7
7
  import { buildRegistryDefaults, loadKnowledgeBaseContext, loadVerifiedRegistryBundle, loadPolicyPackFromPaths, resolvePolicyLayerFiles } from "./loaders.js";
8
+ import { createParserIsolationService } from "./parserIsolation.js";
9
+ const PARSER_HEALTH_REFRESH_INTERVAL_MS = 30_000;
10
+ function hashValue(input) {
11
+ return createHash("sha256").update(JSON.stringify(input)).digest("hex");
12
+ }
8
13
  async function readJson(request) {
9
14
  const chunks = [];
10
15
  for await (const chunk of request) {
@@ -17,6 +22,24 @@ function writeJson(response, statusCode, payload) {
17
22
  response.setHeader("content-type", "application/json; charset=utf-8");
18
23
  response.end(JSON.stringify(payload, null, 2));
19
24
  }
25
+ function logServerError(error) {
26
+ if (error instanceof Error) {
27
+ console.error("SafeBrowse daemon error:", error.stack ?? error.message);
28
+ return;
29
+ }
30
+ console.error("SafeBrowse daemon error:", error);
31
+ }
32
+ function legacyResponseMeta(route) {
33
+ return {
34
+ deprecated: true,
35
+ telemetry: {
36
+ deprecated: true,
37
+ claimScope: "legacy_compatibility",
38
+ preventionClaim: false,
39
+ routeVersion: route
40
+ }
41
+ };
42
+ }
20
43
  async function fileExists(path) {
21
44
  try {
22
45
  await stat(path);
@@ -37,39 +60,187 @@ async function resolveDefaultRootDir() {
37
60
  }
38
61
  async function buildRuntimeContext(options) {
39
62
  const rootDir = options.rootDir ?? (await resolveDefaultRootDir());
63
+ const deploymentProfile = options.deploymentProfile === "secure_v6"
64
+ ? "secure_v5"
65
+ : (options.deploymentProfile ?? "development");
66
+ const secureDeployment = deploymentProfile === "secure_v5";
40
67
  const policyPack = options.policyPack ??
41
68
  (await loadPolicyPackFromPaths(resolvePolicyLayerFiles(resolve(rootDir))));
42
69
  const knowledgeBase = options.knowledgeBase ?? (await loadKnowledgeBaseContext(resolve(rootDir, "knowledge_base")));
43
70
  const verifiedRegistry = options.verifiedRegistry ??
44
71
  (await loadVerifiedRegistryBundle(buildRegistryDefaults(resolve(rootDir))).catch(() => undefined));
72
+ const approvalBrokerPublicKeyPem = options.approvalBrokerPublicKeyPem ??
73
+ (options.approvalBrokerPublicKeyPath
74
+ ? await readFile(options.approvalBrokerPublicKeyPath, "utf8").catch(() => undefined)
75
+ : await readFile(resolve(rootDir, "knowledge_base", "signing", "safebrowse_vf_ed25519_public.pem"), "utf8").catch(() => undefined));
45
76
  return {
46
77
  policy: compilePolicy(policyPack),
47
78
  knowledgeBase,
48
- verifiedRegistry
79
+ verifiedRegistry,
80
+ parserAllowlistedEgress: options.parserAllowlistedEgress ?? [],
81
+ parserIsolationMode: secureDeployment
82
+ ? "node_permission_process"
83
+ : options.parserIsolationMode ?? "scrubbed_process",
84
+ deploymentProfile,
85
+ approvalBrokerPublicKey: approvalBrokerPublicKeyPem
86
+ ? createPublicKey(approvalBrokerPublicKeyPem)
87
+ : undefined,
88
+ approvalBrokerConfigured: Boolean(approvalBrokerPublicKeyPem),
89
+ approvalBrokerMode: secureDeployment ? "external_service" : options.approvalBrokerMode ?? "signature_verification",
90
+ claimBearingReady: false
49
91
  };
50
92
  }
51
93
  function plusMinutes(value, minutes) {
52
94
  return new Date(new Date(value).getTime() + minutes * 60_000).toISOString();
53
95
  }
54
- function createOnboardingSession(request, runtime, workflowBindingId) {
55
- const callbackUri = request.oauthContext?.callbackUri ??
96
+ function plusSeconds(value, seconds) {
97
+ return new Date(new Date(value).getTime() + seconds * 1_000).toISOString();
98
+ }
99
+ function createWorkflowHash(payload) {
100
+ return hashValue({
101
+ taskId: payload.taskId,
102
+ userGoal: payload.userGoal,
103
+ phase: payload.phase ?? "",
104
+ allowedOrigins: payload.allowedOrigins ?? [],
105
+ allowedVerbs: payload.allowedVerbs ?? [],
106
+ forbiddenSinks: payload.forbiddenSinks ?? []
107
+ });
108
+ }
109
+ function createSessionState(request, runtime) {
110
+ const createdAt = new Date().toISOString();
111
+ const allowedOrigins = request.allowedOrigins ??
112
+ [...runtime.policy.readOnlyOrigins, ...runtime.policy.writableOrigins];
113
+ const allowedVerbs = request.allowedVerbs ?? [...runtime.policy.allowedActions];
114
+ const forbiddenSinks = request.forbiddenSinks ?? [];
115
+ const session = {
116
+ sessionId: randomUUID(),
117
+ taskId: request.taskId,
118
+ userGoal: request.userGoal,
119
+ phase: request.phase,
120
+ allowedOrigins,
121
+ allowedVerbs,
122
+ forbiddenSinks,
123
+ workflowHash: createWorkflowHash({
124
+ taskId: request.taskId,
125
+ userGoal: request.userGoal,
126
+ phase: request.phase,
127
+ allowedOrigins,
128
+ allowedVerbs,
129
+ forbiddenSinks
130
+ }),
131
+ currentStep: 0,
132
+ createdAt,
133
+ expiresAt: plusSeconds(createdAt, request.expiresInSeconds ?? 1800),
134
+ claimProfile: runtime.claimBearingReady ? secureClaimProfile(runtime) : undefined,
135
+ approvalBrokerRequired: isSecureDeploymentProfile(runtime.deploymentProfile),
136
+ legacyRoutesDisabled: isSecureDeploymentProfile(runtime.deploymentProfile)
137
+ };
138
+ const memorySourceClasses = new Map();
139
+ return {
140
+ session,
141
+ observations: new Map(),
142
+ capabilities: new Map(),
143
+ usedCapabilities: new Set(),
144
+ authorityReduced: false,
145
+ authorityReductionReasons: [],
146
+ approvalGrants: new Map(),
147
+ memoryRecords: new Map(),
148
+ memorySnapshots: new Map(),
149
+ onboardingSessions: new Map(),
150
+ observationsV5: new Map(),
151
+ capabilitiesV5: new Map(),
152
+ consumedCapabilitiesV5: new Map(),
153
+ usedCapabilitiesV5: new Set(),
154
+ approvalEnvelopesV5: new Map(),
155
+ onboardingSessionsV5: new Map(),
156
+ connectorHandlesV5: new Map(),
157
+ memorySourceClassesV5: memorySourceClasses,
158
+ memorySourceClassesV6: memorySourceClasses,
159
+ replayEvents: []
160
+ };
161
+ }
162
+ function secureParserIsolationSatisfied(probe) {
163
+ return (probe.mode === "node_permission_process" &&
164
+ probe.processIsolated &&
165
+ probe.egressDenied &&
166
+ probe.permissionModelEnabled &&
167
+ probe.childProcessDenied &&
168
+ probe.workerThreadsDenied &&
169
+ probe.envKeys.length === 0);
170
+ }
171
+ function isSecureDeploymentProfile(deploymentProfile) {
172
+ return deploymentProfile === "secure_v5";
173
+ }
174
+ function secureClaimProfile(runtime) {
175
+ return "secure_v5";
176
+ }
177
+ function claimBearingReady(runtime, probe) {
178
+ return (isSecureDeploymentProfile(runtime.deploymentProfile) &&
179
+ runtime.approvalBrokerConfigured &&
180
+ Boolean(runtime.approvalBrokerPublicKey) &&
181
+ runtime.approvalBrokerMode === "external_service" &&
182
+ runtime.verifiedRegistry?.signatureVerified === true &&
183
+ runtime.parserIsolationMode === "node_permission_process" &&
184
+ secureParserIsolationSatisfied(probe));
185
+ }
186
+ function shouldReduceAuthority(sessionState, observation) {
187
+ if (sessionState.authorityReduced) {
188
+ return true;
189
+ }
190
+ const hasPriorSurface = Boolean(sessionState.latestObservation);
191
+ const hasMeaningfulRisk = observation.parseStatus !== "compiled" ||
192
+ observation.riskFindings.length > 0 ||
193
+ observation.secretFindings.length > 0;
194
+ return hasPriorSurface && hasMeaningfulRisk;
195
+ }
196
+ function applyAuthorityReduction(sessionState, observation, plannerInput) {
197
+ const reasons = [
198
+ ...sessionState.authorityReductionReasons,
199
+ ...(observation.parseStatus !== "compiled"
200
+ ? [`parse_status_${observation.parseStatus}`]
201
+ : []),
202
+ ...observation.riskFindings,
203
+ ...(observation.secretFindings.length ? ["secret_redaction_boundary"] : [])
204
+ ];
205
+ sessionState.authorityReduced = true;
206
+ sessionState.authorityReductionReasons = [...new Set(reasons)];
207
+ sessionState.capabilities.clear();
208
+ return {
209
+ ...plannerInput,
210
+ candidateCapabilities: [],
211
+ riskMarkers: [
212
+ ...new Set([
213
+ ...plannerInput.riskMarkers,
214
+ "multimodal_reducer_active",
215
+ ...sessionState.authorityReductionReasons.map((reason) => `chain:${reason}`)
216
+ ])
217
+ ]
218
+ };
219
+ }
220
+ function createOnboardingSession(request, runtime, approvalGrant, verifiedRegistryEntry) {
221
+ const callbackUri = verifiedRegistryEntry?.allowedRedirectUris[0] ??
222
+ request.oauthContext?.callbackUri ??
56
223
  request.callbackUri ??
57
224
  request.oauthContext?.redirectUri ??
58
225
  request.requestedRedirectUri;
59
226
  if (!callbackUri) {
60
227
  return undefined;
61
228
  }
229
+ const callbackOrigin = verifiedRegistryEntry?.allowedCallbackOrigins[0] ?? new URL(callbackUri).origin;
62
230
  const createdAt = (runtime.now?.() ?? new Date()).toISOString();
63
231
  return {
64
232
  sessionId: randomUUID(),
65
- approvalBindingId: request.approvalBindingId ?? randomUUID(),
66
- workflowBindingId,
233
+ approvalBindingId: approvalGrant.approvalGrantId,
234
+ workflowBindingId: request.sourceObservationId,
67
235
  toolId: request.toolId,
68
- registryEntryId: request.registryEntryId ?? request.toolId,
69
- registryBundleId: request.registryBundleId ?? runtime.verifiedRegistry?.bundleId ?? "unverified-registry",
236
+ registryEntryId: verifiedRegistryEntry?.registryEntryId ?? request.registryEntryId ?? request.toolId,
237
+ registryBundleId: verifiedRegistryEntry?.bundleId ??
238
+ request.registryBundleId ??
239
+ runtime.verifiedRegistry?.bundleId ??
240
+ "unverified-registry",
70
241
  callbackUri,
71
- callbackOrigin: new URL(callbackUri).origin,
72
- requestedScopes: request.requestedScopes ?? request.oauthContext?.requestedScopes ?? [],
242
+ callbackOrigin,
243
+ requestedScopes: approvalGrant.scopes,
73
244
  state: randomUUID(),
74
245
  pkceMethod: "S256",
75
246
  createdAt,
@@ -77,29 +248,393 @@ function createOnboardingSession(request, runtime, workflowBindingId) {
77
248
  status: "prepared"
78
249
  };
79
250
  }
251
+ function issueApprovalGrant(request, sessionState) {
252
+ const issuedAt = new Date().toISOString();
253
+ const grantWithoutHash = {
254
+ approvalGrantId: randomUUID(),
255
+ sessionId: sessionState.session.sessionId,
256
+ workflowHash: sessionState.session.workflowHash,
257
+ connectorId: request.connectorId,
258
+ scopes: request.scopes ?? [],
259
+ sinkClass: request.sinkClass,
260
+ capabilityIds: request.capabilityIds ?? [],
261
+ targetOrigin: request.targetOrigin,
262
+ issuedAt,
263
+ expiresAt: plusSeconds(issuedAt, request.expiresInSeconds ?? 600)
264
+ };
265
+ return {
266
+ ...grantWithoutHash,
267
+ grantHash: createApprovalGrantHash(grantWithoutHash)
268
+ };
269
+ }
270
+ function findSessionState(sessions, sessionId) {
271
+ const sessionState = sessions.get(sessionId);
272
+ if (!sessionState) {
273
+ return undefined;
274
+ }
275
+ if (new Date(sessionState.session.expiresAt).getTime() <= Date.now()) {
276
+ sessions.delete(sessionId);
277
+ return undefined;
278
+ }
279
+ return sessionState;
280
+ }
281
+ function legacyRoutesDisabled(runtime) {
282
+ return isSecureDeploymentProfile(runtime.deploymentProfile);
283
+ }
284
+ function isLegacyRoute(url) {
285
+ return url.startsWith("/v1/") || url.startsWith("/v2/") || url.startsWith("/v4/");
286
+ }
287
+ function lookupVerifiedRegistryEntry(runtime, toolId, registryEntryId) {
288
+ return runtime.verifiedRegistry?.entries.find((entry) => entry.registryEntryId === registryEntryId ||
289
+ entry.registryEntryId === toolId ||
290
+ entry.adapterId === toolId);
291
+ }
292
+ function resolveToolManifestRegistryEntry(runtime, capture) {
293
+ return runtime.verifiedRegistry?.entries.find((entry) => {
294
+ if (capture.toolId !== entry.adapterId && capture.toolId !== entry.registryEntryId) {
295
+ return false;
296
+ }
297
+ if (capture.authType && capture.authType !== entry.authType) {
298
+ return false;
299
+ }
300
+ if (capture.callbackUri && !entry.allowedRedirectUris.includes(capture.callbackUri)) {
301
+ return false;
302
+ }
303
+ if (capture.callbackOrigin &&
304
+ !entry.allowedCallbackOrigins.includes(capture.callbackOrigin)) {
305
+ return false;
306
+ }
307
+ if (capture.requestedScopes?.length &&
308
+ capture.requestedScopes.some((scope) => !entry.allowedScopes.includes(scope))) {
309
+ return false;
310
+ }
311
+ return true;
312
+ });
313
+ }
314
+ function getCapabilityV5(sessionState, capabilityId) {
315
+ if (!sessionState || !capabilityId) {
316
+ return undefined;
317
+ }
318
+ return (sessionState.capabilitiesV5.get(capabilityId) ??
319
+ sessionState.consumedCapabilitiesV5.get(capabilityId));
320
+ }
321
+ function consumeCapabilityV5(sessionState, capability, consumedAt = new Date().toISOString()) {
322
+ const consumedCapability = {
323
+ ...capability,
324
+ consumedAt
325
+ };
326
+ sessionState.capabilitiesV5.delete(capability.capabilityId);
327
+ sessionState.consumedCapabilitiesV5.set(capability.capabilityId, consumedCapability);
328
+ sessionState.usedCapabilitiesV5.add(capability.capabilityId);
329
+ return consumedCapability;
330
+ }
331
+ function getActiveApprovalEnvelopeForCapability(sessionState, capabilityId) {
332
+ if (!sessionState || !capabilityId) {
333
+ return undefined;
334
+ }
335
+ return [...sessionState.approvalEnvelopesV5.values()].find((envelope) => envelope.capabilityId === capabilityId &&
336
+ !envelope.consumedAt &&
337
+ new Date(envelope.expiresAt).getTime() > Date.now());
338
+ }
339
+ function buildMemorySnapshotState(sessionState, record) {
340
+ const priorTrustedBaseline = [...sessionState.memoryRecords.values()]
341
+ .filter((candidate) => candidate.sessionId === record.sessionId &&
342
+ candidate.key === record.key &&
343
+ candidate.tier === "trusted_durable" &&
344
+ candidate.recordId !== record.recordId)
345
+ .sort((left, right) => right.createdAt.localeCompare(left.createdAt))[0];
346
+ return {
347
+ snapshotId: randomUUID(),
348
+ sessionId: record.sessionId,
349
+ recordId: record.recordId,
350
+ key: record.key,
351
+ createdAt: new Date().toISOString(),
352
+ baselineAbsent: !priorTrustedBaseline,
353
+ snapshotRecord: priorTrustedBaseline
354
+ ? {
355
+ ...priorTrustedBaseline
356
+ }
357
+ : undefined
358
+ };
359
+ }
360
+ function retireObservationCapabilitiesV5(sessionState) {
361
+ const retiredCapabilityIds = [];
362
+ for (const [capabilityId, capability] of sessionState.capabilitiesV5.entries()) {
363
+ if (capability.kind !== "memory_promote") {
364
+ sessionState.capabilitiesV5.delete(capabilityId);
365
+ retiredCapabilityIds.push(capabilityId);
366
+ }
367
+ }
368
+ if (!retiredCapabilityIds.length) {
369
+ return;
370
+ }
371
+ for (const [approvalId, envelope] of sessionState.approvalEnvelopesV5.entries()) {
372
+ if (retiredCapabilityIds.includes(envelope.capabilityId) && !envelope.consumedAt) {
373
+ sessionState.approvalEnvelopesV5.delete(approvalId);
374
+ }
375
+ }
376
+ }
377
+ function buildV5ExecutionPlan(capability) {
378
+ if (!capability) {
379
+ return undefined;
380
+ }
381
+ return {
382
+ verb: capability.kind,
383
+ targetUrl: capability.targetUrl,
384
+ targetOrigin: capability.targetOrigin,
385
+ selector: capability.selector,
386
+ connectorId: capability.connectorId,
387
+ callbackUri: capability.callbackUri,
388
+ callbackOrigin: capability.callbackOrigin,
389
+ memoryRecordId: capability.memoryRecordId,
390
+ derivedSinkClass: capability.derivedSinkClass,
391
+ derivedSensitiveSink: capability.derivedSensitiveSink,
392
+ semanticDigest: capability.semanticDigest
393
+ };
394
+ }
395
+ function summarizeCapabilityV5(capability) {
396
+ return {
397
+ capabilityId: capability.capabilityId,
398
+ capabilityDigest: capability.capabilityDigest,
399
+ semanticDigest: capability.semanticDigest,
400
+ title: capability.title,
401
+ kind: capability.kind,
402
+ parameterSchema: capability.parameterSchema,
403
+ expiresAt: capability.expiresAt
404
+ };
405
+ }
406
+ function summarizeAuthorityCandidate(capability) {
407
+ return {
408
+ authorityId: capability.capabilityId,
409
+ authorityDigest: capability.capabilityDigest,
410
+ capabilityId: capability.capabilityId,
411
+ capabilityDigest: capability.capabilityDigest,
412
+ semanticDigest: capability.semanticDigest,
413
+ title: capability.title,
414
+ kind: capability.kind,
415
+ parameterSchema: capability.parameterSchema,
416
+ expiresAt: capability.expiresAt
417
+ };
418
+ }
419
+ function relabelClaimProfile(verdict, claimProfile) {
420
+ return {
421
+ ...verdict,
422
+ safeConstraints: {
423
+ ...(verdict.safeConstraints ?? {}),
424
+ claim_profile: claimProfile
425
+ }
426
+ };
427
+ }
428
+ function appendReplayEvent(sessionState, event) {
429
+ const replayEvent = {
430
+ eventId: randomUUID(),
431
+ timestamp: new Date().toISOString(),
432
+ ...event
433
+ };
434
+ sessionState?.replayEvents.push(replayEvent);
435
+ return replayEvent;
436
+ }
437
+ function buildV5ObservationDecision(compiledObservation) {
438
+ if (compiledObservation.parseStatus !== "compiled") {
439
+ return {
440
+ decision: "BLOCK",
441
+ reasonCodes: [
442
+ compiledObservation.parseStatus === "partial"
443
+ ? "PARSE_STATUS_PARTIAL"
444
+ : "PARSE_STATUS_UNSUPPORTED"
445
+ ],
446
+ riskScore: 0.98,
447
+ safeConstraints: {
448
+ authority_eligible: false
449
+ },
450
+ telemetryTags: ["v5_observation", "block"]
451
+ };
452
+ }
453
+ return {
454
+ decision: compiledObservation.authorityEligible ? "ALLOW" : "REPLAN_READ_ONLY",
455
+ reasonCodes: compiledObservation.authorityEligible ? [] : ["AUTHORITY_REDUCED_TO_FACTS_ONLY"],
456
+ riskScore: compiledObservation.riskScore,
457
+ safeConstraints: {
458
+ authority_eligible: compiledObservation.authorityEligible
459
+ },
460
+ telemetryTags: [
461
+ "v5_observation",
462
+ compiledObservation.authorityEligible ? "authority_eligible" : "facts_only"
463
+ ]
464
+ };
465
+ }
466
+ function buildV6ObservationDecision(compiledObservation) {
467
+ return buildV5ObservationDecision(compiledObservation);
468
+ }
469
+ function buildObservationDecisionForClaimProfile(compiledObservation, claimProfile) {
470
+ if (!compiledObservation) {
471
+ return relabelClaimProfile({
472
+ decision: "BLOCK",
473
+ reasonCodes: ["OBSERVATION_REQUIRED"],
474
+ riskScore: 0.99,
475
+ safeConstraints: {},
476
+ telemetryTags: [`${claimProfile}_observation`, "block"]
477
+ }, claimProfile);
478
+ }
479
+ return relabelClaimProfile(buildV5ObservationDecision(compiledObservation), claimProfile);
480
+ }
481
+ function canonicalReplayRoute(route) {
482
+ return route.replace(/^\/v6\//, "/v5/");
483
+ }
484
+ function buildLegacyObservationCapture(payload) {
485
+ if (payload.surfaceType === "html") {
486
+ return {
487
+ mimeType: "text/html",
488
+ sourceOrigin: payload.url,
489
+ viewerOrigin: payload.frameUrl ?? payload.url,
490
+ renderedText: payload.visibleText,
491
+ extractedText: payload.visibleText,
492
+ metadataText: payload.metadataText,
493
+ annotations: payload.annotations,
494
+ extractionMethod: "dom",
495
+ trustSignals: payload.trustSignals
496
+ };
497
+ }
498
+ if (payload.surfaceType === "pdf") {
499
+ return {
500
+ mimeType: "application/pdf",
501
+ sourceOrigin: payload.url,
502
+ viewerOrigin: payload.frameUrl ?? payload.url,
503
+ renderedText: payload.renderedText,
504
+ extractedText: payload.extractedText,
505
+ ocrText: payload.ocrText,
506
+ annotations: payload.annotations,
507
+ metadataText: payload.metadataText,
508
+ extractionMethod: "download",
509
+ trustSignals: payload.trustSignals
510
+ };
511
+ }
512
+ if (payload.surfaceType === "image") {
513
+ return {
514
+ mimeType: "image/png",
515
+ sourceOrigin: payload.url,
516
+ viewerOrigin: payload.frameUrl ?? payload.url,
517
+ ocrText: payload.ocrText,
518
+ metadataText: payload.metadataText,
519
+ extractionMethod: "ocr",
520
+ trustSignals: payload.trustSignals
521
+ };
522
+ }
523
+ if (payload.surfaceType === "tool_manifest") {
524
+ return {
525
+ mimeType: "application/json",
526
+ surfaceKind: "tool_manifest",
527
+ sourceOrigin: payload.url,
528
+ viewerOrigin: payload.frameUrl ?? payload.url,
529
+ extractedText: payload.description,
530
+ metadataText: payload.schemaDescriptions,
531
+ extractionMethod: "api",
532
+ trustSignals: payload.trustSignals
533
+ };
534
+ }
535
+ return undefined;
536
+ }
537
+ function buildV5ArtifactRef(artifact, authorityEligible) {
538
+ return {
539
+ artifactId: artifact.artifactId,
540
+ surfaceKind: artifact.surfaceKind,
541
+ sourceOrigin: artifact.sourceOrigin,
542
+ viewerOrigin: artifact.viewerOrigin,
543
+ mismatchSignals: artifact.mismatchSignals,
544
+ metadataSignals: artifact.metadataSignals,
545
+ provenance: {
546
+ extractionMethod: artifact.extractionMethod,
547
+ lineageChain: artifact.lineageChain,
548
+ derivedTaintClass: artifact.derivedTaintClass
549
+ },
550
+ authorityEligible
551
+ };
552
+ }
553
+ const buildV6ArtifactRef = buildV5ArtifactRef;
80
554
  export async function createSafeBrowseServer(options = {}) {
81
555
  const runtime = await buildRuntimeContext(options);
556
+ const unifiedV5InSecureV6 = false;
557
+ const parserIsolationService = createParserIsolationService(runtime.parserIsolationMode, {
558
+ allowlistedEgress: runtime.parserAllowlistedEgress,
559
+ runtime: {
560
+ knowledgeBase: runtime.knowledgeBase
561
+ }
562
+ });
563
+ let parserProbeSnapshot = await parserIsolationService.refreshProbe();
564
+ runtime.claimBearingReady = claimBearingReady(runtime, parserProbeSnapshot.probe);
565
+ if (isSecureDeploymentProfile(runtime.deploymentProfile)) {
566
+ const secureProfile = runtime.deploymentProfile;
567
+ if (!runtime.verifiedRegistry?.signatureVerified) {
568
+ throw new Error(`${secureProfile} requires a signature-verified registry bundle`);
569
+ }
570
+ if (!runtime.approvalBrokerConfigured || !runtime.approvalBrokerPublicKey) {
571
+ throw new Error(`${secureProfile} requires an approval broker public key`);
572
+ }
573
+ if (runtime.approvalBrokerMode !== "external_service") {
574
+ throw new Error(`${secureProfile} requires approvalBrokerMode=external_service`);
575
+ }
576
+ if (runtime.parserIsolationMode !== "node_permission_process") {
577
+ throw new Error(`${secureProfile} requires parserIsolationMode=node_permission_process`);
578
+ }
579
+ if (!secureParserIsolationSatisfied(parserProbeSnapshot.probe)) {
580
+ throw new Error(`${secureProfile} requires a permission-constrained parser process with denied egress and scrubbed env`);
581
+ }
582
+ }
82
583
  const onboardingSessions = new Map();
83
- return createServer(async (request, response) => {
584
+ const sessions = new Map();
585
+ const parserHealthRefreshTimer = setInterval(() => {
586
+ void parserIsolationService
587
+ .refreshProbe()
588
+ .then((snapshot) => {
589
+ parserProbeSnapshot = snapshot;
590
+ runtime.claimBearingReady = claimBearingReady(runtime, snapshot.probe);
591
+ })
592
+ .catch(() => {
593
+ runtime.claimBearingReady = false;
594
+ });
595
+ }, PARSER_HEALTH_REFRESH_INTERVAL_MS);
596
+ parserHealthRefreshTimer.unref();
597
+ const server = createServer(async (request, response) => {
84
598
  if (!request.url) {
85
599
  writeJson(response, 400, { error: "missing_url" });
86
600
  return;
87
601
  }
602
+ if (request.url.startsWith("/v6/")) {
603
+ request.url = canonicalReplayRoute(request.url);
604
+ }
88
605
  try {
89
606
  if (request.method === "GET" && request.url === "/health") {
607
+ parserProbeSnapshot = await parserIsolationService.getCachedProbe();
608
+ const claimReady = claimBearingReady(runtime, parserProbeSnapshot.probe);
609
+ runtime.claimBearingReady = claimReady;
90
610
  writeJson(response, 200, {
91
611
  status: "ok",
92
612
  profile: runtime.policy.profile,
613
+ deploymentProfile: runtime.deploymentProfile,
614
+ claimBearingReady: claimReady,
93
615
  version: runtime.policy.version,
94
616
  policyLayers: runtime.policy.layerProvenance,
617
+ legacyRoutesEnabled: !legacyRoutesDisabled(runtime),
95
618
  verifiedRegistry: runtime.verifiedRegistry
96
619
  ? {
97
620
  bundleId: runtime.verifiedRegistry.bundleId,
98
621
  version: runtime.verifiedRegistry.version,
99
622
  signatureVerified: runtime.verifiedRegistry.signatureVerified,
100
- entryCount: runtime.verifiedRegistry.entries.length
623
+ entryCount: runtime.verifiedRegistry.entries.length,
624
+ required: isSecureDeploymentProfile(runtime.deploymentProfile)
101
625
  }
102
- : undefined
626
+ : undefined,
627
+ parserIsolation: {
628
+ ...parserProbeSnapshot.probe,
629
+ lastCheckedAt: parserProbeSnapshot.lastCheckedAt,
630
+ configuredMode: runtime.parserIsolationMode,
631
+ enforced: isSecureDeploymentProfile(runtime.deploymentProfile)
632
+ },
633
+ approvalBroker: {
634
+ required: isSecureDeploymentProfile(runtime.deploymentProfile),
635
+ configured: runtime.approvalBrokerConfigured,
636
+ mode: runtime.approvalBrokerMode
637
+ }
103
638
  });
104
639
  return;
105
640
  }
@@ -107,41 +642,83 @@ export async function createSafeBrowseServer(options = {}) {
107
642
  writeJson(response, 405, { error: "method_not_allowed" });
108
643
  return;
109
644
  }
645
+ if (legacyRoutesDisabled(runtime) && isLegacyRoute(request.url)) {
646
+ writeJson(response, 403, {
647
+ error: `route_disabled_in_${runtime.deploymentProfile}`,
648
+ route: request.url,
649
+ claimProfile: secureClaimProfile(runtime)
650
+ });
651
+ return;
652
+ }
110
653
  if (request.url === "/v1/observe") {
111
654
  const payload = await readJson(request);
112
- writeJson(response, 200, sanitizeObservation(payload, runtime));
655
+ writeJson(response, 200, {
656
+ ...sanitizeObservation(payload, runtime),
657
+ ...legacyResponseMeta("/v1/observe")
658
+ });
113
659
  return;
114
660
  }
115
661
  if (request.url === "/v1/action") {
116
662
  const payload = await readJson(request);
117
- writeJson(response, 200, evaluateAction(payload, runtime));
663
+ writeJson(response, 200, {
664
+ ...evaluateAction(payload, runtime),
665
+ ...legacyResponseMeta("/v1/action")
666
+ });
118
667
  return;
119
668
  }
120
669
  if (request.url === "/v1/artifact") {
121
670
  const payload = await readJson(request);
122
- writeJson(response, 200, brokerArtifact(payload, runtime));
671
+ writeJson(response, 200, {
672
+ ...brokerArtifact(payload, runtime),
673
+ ...legacyResponseMeta("/v1/artifact")
674
+ });
123
675
  return;
124
676
  }
125
677
  if (request.url === "/v1/tool") {
126
678
  const payload = await readJson(request);
127
- writeJson(response, 200, evaluateToolRequest(payload, runtime));
679
+ writeJson(response, 200, {
680
+ ...evaluateToolRequest(payload, runtime),
681
+ ...legacyResponseMeta("/v1/tool")
682
+ });
128
683
  return;
129
684
  }
130
685
  if (request.url === "/v1/memory") {
131
686
  const payload = await readJson(request);
132
- writeJson(response, 200, evaluateMemoryWrite(payload, runtime));
687
+ writeJson(response, 200, {
688
+ ...evaluateMemoryWrite(payload, runtime),
689
+ ...legacyResponseMeta("/v1/memory")
690
+ });
133
691
  return;
134
692
  }
135
693
  if (request.url === "/v1/replay") {
136
694
  const payload = await readJson(request);
137
- writeJson(response, 200, buildReplayBundle(payload.events, runtime));
695
+ writeJson(response, 200, {
696
+ ...buildReplayBundle(payload.events, runtime),
697
+ ...legacyResponseMeta("/v1/replay")
698
+ });
138
699
  return;
139
700
  }
140
701
  if (request.url === "/v2/tool/prepare") {
141
702
  const payload = await readJson(request);
142
703
  const prepared = prepareToolOnboarding(payload, runtime);
143
704
  const onboardingSession = prepared.verdict.decision === "ALLOW" && payload.authType === "oauth"
144
- ? createOnboardingSession(payload, runtime, prepared.workflowBinding?.bindingId)
705
+ ? createOnboardingSession(payload, runtime, {
706
+ approvalGrantId: payload.approvalBindingId ?? randomUUID(),
707
+ sessionId: "legacy-session",
708
+ workflowHash: hashValue(payload.sourceObservationId ?? payload.requestId),
709
+ connectorId: payload.toolId,
710
+ scopes: payload.requestedScopes ?? [],
711
+ sinkClass: "connector_oauth",
712
+ capabilityIds: payload.capabilityId ? [payload.capabilityId] : [],
713
+ targetOrigin: payload.callbackOrigin ??
714
+ payload.oauthContext?.callbackOrigin ??
715
+ payload.callbackUri ??
716
+ payload.requestedRedirectUri ??
717
+ "unknown",
718
+ issuedAt: new Date().toISOString(),
719
+ expiresAt: plusMinutes(new Date().toISOString(), 10),
720
+ grantHash: "legacy"
721
+ })
145
722
  : undefined;
146
723
  if (onboardingSession) {
147
724
  onboardingSessions.set(onboardingSession.sessionId, onboardingSession);
@@ -150,7 +727,8 @@ export async function createSafeBrowseServer(options = {}) {
150
727
  verdict: prepared.verdict,
151
728
  verifiedRegistryEntry: prepared.verifiedRegistryEntry,
152
729
  workflowBinding: prepared.workflowBinding,
153
- onboardingSession
730
+ onboardingSession,
731
+ ...legacyResponseMeta("/v2/tool/prepare")
154
732
  });
155
733
  return;
156
734
  }
@@ -164,23 +742,1561 @@ export async function createSafeBrowseServer(options = {}) {
164
742
  status: result.verdict.decision === "ALLOW" ? "used" : session.status
165
743
  });
166
744
  }
167
- writeJson(response, 200, result);
745
+ writeJson(response, 200, {
746
+ ...result,
747
+ ...legacyResponseMeta("/v2/tool/callback/verify")
748
+ });
168
749
  return;
169
750
  }
170
751
  if (request.url === "/v2/artifact") {
171
752
  const payload = await readJson(request);
172
- writeJson(response, 200, brokerArtifactV2(payload, runtime));
753
+ writeJson(response, 200, {
754
+ ...brokerArtifactV2(payload, runtime),
755
+ ...legacyResponseMeta("/v2/artifact")
756
+ });
757
+ return;
758
+ }
759
+ if (request.url === "/v6/session/start" ||
760
+ (unifiedV5InSecureV6 && request.url === "/v5/session/start")) {
761
+ const payload = await readJson(request);
762
+ const sessionState = createSessionState(payload, runtime);
763
+ sessions.set(sessionState.session.sessionId, sessionState);
764
+ appendReplayEvent(sessionState, {
765
+ kind: "verdict",
766
+ actor: "system",
767
+ payload: {
768
+ route: canonicalReplayRoute(request.url),
769
+ sessionId: sessionState.session.sessionId,
770
+ claimProfile: sessionState.session.claimProfile ?? "secure_v6"
771
+ }
772
+ });
773
+ writeJson(response, 200, {
774
+ session: sessionState.session
775
+ });
776
+ return;
777
+ }
778
+ if (request.url === "/v6/observe" || (unifiedV5InSecureV6 && request.url === "/v5/observe")) {
779
+ const payload = await readJson(request);
780
+ const sessionState = findSessionState(sessions, payload.sessionId);
781
+ if (!sessionState) {
782
+ writeJson(response, 404, { error: "unknown_session" });
783
+ return;
784
+ }
785
+ const capture = {
786
+ ...payload.capture,
787
+ sessionId: payload.sessionId,
788
+ taskId: sessionState.session.taskId
789
+ };
790
+ const observationResult = await parserIsolationService.compileObservation({
791
+ capture,
792
+ workflowHash: sessionState.session.workflowHash,
793
+ compilerVersion: "v5"
794
+ });
795
+ const compiledObservation = observationResult.compiledObservation;
796
+ const plannerView = observationResult.plannerView;
797
+ const verifiedEntry = capture.surfaceType === "tool_manifest"
798
+ ? resolveToolManifestRegistryEntry(runtime, capture)
799
+ : undefined;
800
+ const manifestHash = observationResult.toolManifestDigests?.manifestHash;
801
+ const schemaHash = observationResult.toolManifestDigests?.schemaHash;
802
+ retireObservationCapabilitiesV5(sessionState);
803
+ const capabilities = mintCapabilitiesForObservationV5(sessionState.session, compiledObservation, plannerView, {
804
+ verifiedRegistryEntry: verifiedEntry,
805
+ registryEntryId: capture.surfaceType === "tool_manifest" ? verifiedEntry?.registryEntryId : undefined,
806
+ connectorId: capture.surfaceType === "tool_manifest"
807
+ ? verifiedEntry?.adapterId ?? capture.toolId
808
+ : undefined,
809
+ requestedScopes: capture.surfaceType === "tool_manifest" ? capture.requestedScopes : undefined,
810
+ callbackUri: capture.surfaceType === "tool_manifest" ? capture.callbackUri : undefined,
811
+ callbackOrigin: capture.surfaceType === "tool_manifest" ? capture.callbackOrigin : undefined,
812
+ manifestAuthType: capture.surfaceType === "tool_manifest" ? capture.authType : undefined,
813
+ manifestHash,
814
+ schemaHash
815
+ });
816
+ sessionState.latestObservationV5 = compiledObservation;
817
+ sessionState.observationsV5.set(compiledObservation.observationId, compiledObservation);
818
+ for (const capability of capabilities) {
819
+ sessionState.capabilitiesV5.set(capability.capabilityId, capability);
820
+ }
821
+ const observationVerdict = relabelClaimProfile(buildV6ObservationDecision(compiledObservation), "secure_v6");
822
+ const artifactCapture = buildLegacyObservationCapture(capture);
823
+ const artifactRefs = artifactCapture
824
+ ? [
825
+ buildV6ArtifactRef(brokerArtifact(artifactCapture, runtime).artifact, compiledObservation.authorityEligible)
826
+ ]
827
+ : [];
828
+ const authorityCandidates = capabilities.map((capability) => ({
829
+ authorityId: capability.capabilityId,
830
+ authorityDigest: capability.capabilityDigest,
831
+ semanticDigest: capability.semanticDigest,
832
+ title: capability.title,
833
+ kind: capability.kind,
834
+ parameterSchema: capability.parameterSchema,
835
+ expiresAt: capability.expiresAt
836
+ }));
837
+ const capabilityAliases = authorityCandidates.map((candidate) => ({
838
+ capabilityId: candidate.authorityId,
839
+ capabilityDigest: candidate.authorityDigest,
840
+ semanticDigest: candidate.semanticDigest,
841
+ title: candidate.title,
842
+ kind: candidate.kind,
843
+ parameterSchema: candidate.parameterSchema,
844
+ expiresAt: candidate.expiresAt
845
+ }));
846
+ const replayEvent = appendReplayEvent(sessionState, {
847
+ kind: "observation",
848
+ actor: "sdk",
849
+ payload: {
850
+ route: canonicalReplayRoute(request.url),
851
+ observationId: compiledObservation.observationId,
852
+ surfaceType: compiledObservation.surfaceType,
853
+ authorityEligible: compiledObservation.authorityEligible,
854
+ authorityCandidateCount: capabilities.length,
855
+ verdict: observationVerdict.decision
856
+ }
857
+ });
858
+ writeJson(response, 200, {
859
+ compiledObservation,
860
+ plannerView,
861
+ capabilities: capabilityAliases,
862
+ authorityCandidates,
863
+ artifactRefs,
864
+ observationVerdict,
865
+ replayEventId: replayEvent.eventId
866
+ });
867
+ return;
868
+ }
869
+ if (request.url === "/v6/action/evaluate" ||
870
+ (unifiedV5InSecureV6 && request.url === "/v5/action/evaluate")) {
871
+ const payload = await readJson(request);
872
+ const authorityId = payload.authorityId ?? payload.capabilityId;
873
+ const authorityDigest = payload.authorityDigest ?? payload.capabilityDigest;
874
+ const sessionState = findSessionState(sessions, payload.sessionId);
875
+ const capability = authorityId ? getCapabilityV5(sessionState, authorityId) : undefined;
876
+ const authorityDecision = relabelClaimProfile(evaluateCapabilityUseV5({
877
+ sessionId: payload.sessionId,
878
+ capabilityId: authorityId ?? "",
879
+ capabilityDigest: authorityDigest ?? "",
880
+ parameters: payload.parameters
881
+ }, sessionState?.session, capability, {
882
+ alreadyUsed: authorityId
883
+ ? (sessionState?.usedCapabilitiesV5.has(authorityId) ?? false)
884
+ : false
885
+ }), "secure_v6");
886
+ const observationDecision = relabelClaimProfile(sessionState?.latestObservationV5
887
+ ? buildV6ObservationDecision(sessionState.latestObservationV5)
888
+ : {
889
+ decision: "BLOCK",
890
+ reasonCodes: ["OBSERVATION_REQUIRED"],
891
+ riskScore: 0.99,
892
+ safeConstraints: {},
893
+ telemetryTags: ["v6_observation", "block"]
894
+ }, "secure_v6");
895
+ const effectDecision = observationDecision.decision === "ALLOW" &&
896
+ authorityDecision.decision === "ALLOW"
897
+ ? authorityDecision
898
+ : relabelClaimProfile({
899
+ decision: "BLOCK",
900
+ reasonCodes: [...new Set([
901
+ ...(observationDecision.decision === "ALLOW"
902
+ ? []
903
+ : ["OBSERVATION_NOT_EFFECT_ELIGIBLE"]),
904
+ ...(authorityDecision.decision === "ALLOW"
905
+ ? []
906
+ : authorityDecision.reasonCodes)
907
+ ])],
908
+ riskScore: Math.max(observationDecision.riskScore, authorityDecision.riskScore),
909
+ safeConstraints: {
910
+ target_class: capability?.targetClass ?? "unknown"
911
+ },
912
+ telemetryTags: ["v6_action", "block"]
913
+ }, "secure_v6");
914
+ if (effectDecision.decision === "ALLOW" && sessionState && capability) {
915
+ consumeCapabilityV5(sessionState, capability);
916
+ sessionState.session = {
917
+ ...sessionState.session,
918
+ currentStep: sessionState.session.currentStep + 1
919
+ };
920
+ }
921
+ appendReplayEvent(sessionState, {
922
+ kind: "action",
923
+ actor: "sdk",
924
+ payload: {
925
+ route: canonicalReplayRoute(request.url),
926
+ authorityId: authorityId ?? null,
927
+ observationDecision: observationDecision.decision,
928
+ authorityDecision: authorityDecision.decision,
929
+ effectDecision: effectDecision.decision
930
+ }
931
+ });
932
+ writeJson(response, 200, {
933
+ observationDecision,
934
+ authorityDecision,
935
+ effectDecision,
936
+ verdict: effectDecision,
937
+ executionPlan: buildV5ExecutionPlan(capability)
938
+ });
939
+ return;
940
+ }
941
+ if (request.url === "/v6/approval/issue" ||
942
+ (unifiedV5InSecureV6 && request.url === "/v5/approval/issue")) {
943
+ const payload = await readJson(request);
944
+ const sessionState = findSessionState(sessions, payload.sessionId);
945
+ const capability = sessionState?.capabilitiesV5.get(payload.capabilityId);
946
+ if (sessionState?.usedCapabilitiesV5.has(payload.capabilityId)) {
947
+ writeJson(response, 200, {
948
+ verdict: relabelClaimProfile({
949
+ decision: "BLOCK",
950
+ reasonCodes: ["CAPABILITY_REPLAYED"],
951
+ riskScore: 0.99,
952
+ safeConstraints: {},
953
+ telemetryTags: ["approval_v6_issue", "block"]
954
+ }, "secure_v6")
955
+ });
956
+ return;
957
+ }
958
+ if (capability && payload.capabilityDigest !== capability.capabilityDigest) {
959
+ writeJson(response, 200, {
960
+ verdict: relabelClaimProfile({
961
+ decision: "BLOCK",
962
+ reasonCodes: ["CAPABILITY_DIGEST_MISMATCH"],
963
+ riskScore: 0.99,
964
+ safeConstraints: {},
965
+ telemetryTags: ["approval_v6_issue", "block"]
966
+ }, "secure_v6")
967
+ });
968
+ return;
969
+ }
970
+ const activeApproval = getActiveApprovalEnvelopeForCapability(sessionState, payload.capabilityId);
971
+ if (activeApproval) {
972
+ writeJson(response, 200, {
973
+ verdict: relabelClaimProfile({
974
+ decision: "BLOCK",
975
+ reasonCodes: ["APPROVAL_ALREADY_ISSUED_FOR_CAPABILITY"],
976
+ riskScore: 0.99,
977
+ safeConstraints: {},
978
+ telemetryTags: ["approval_v6_issue", "block"]
979
+ }, "secure_v6"),
980
+ approvalEnvelope: activeApproval
981
+ });
982
+ return;
983
+ }
984
+ const brokerPayload = sessionState && capability
985
+ ? createApprovalIntentPayloadV5({
986
+ sessionId: sessionState.session.sessionId,
987
+ workflowHash: sessionState.session.workflowHash,
988
+ capabilityId: capability.capabilityId,
989
+ capabilityDigest: capability.capabilityDigest,
990
+ expiresInSeconds: payload.expiresInSeconds
991
+ })
992
+ : "";
993
+ const brokerSignatureVerified = verifyApprovalIntentSignatureV5(brokerPayload, payload.brokerSignature, runtime.approvalBrokerPublicKey);
994
+ const issued = issueApprovalEnvelopeV5({
995
+ session: sessionState?.session,
996
+ capability,
997
+ brokerSignature: payload.brokerSignature,
998
+ brokerSignatureVerified,
999
+ expiresInSeconds: payload.expiresInSeconds
1000
+ });
1001
+ if (issued.approvalEnvelope && sessionState) {
1002
+ sessionState.approvalEnvelopesV5.set(issued.approvalEnvelope.approvalId, issued.approvalEnvelope);
1003
+ }
1004
+ appendReplayEvent(sessionState, {
1005
+ kind: "tool",
1006
+ actor: "sdk",
1007
+ payload: {
1008
+ route: canonicalReplayRoute(request.url),
1009
+ capabilityId: payload.capabilityId,
1010
+ verdict: issued.verdict.decision
1011
+ }
1012
+ });
1013
+ writeJson(response, 200, {
1014
+ ...issued,
1015
+ verdict: relabelClaimProfile(issued.verdict, "secure_v6")
1016
+ });
1017
+ return;
1018
+ }
1019
+ if (request.url === "/v6/tool/prepare" ||
1020
+ (unifiedV5InSecureV6 && request.url === "/v5/tool/prepare")) {
1021
+ const payload = await readJson(request);
1022
+ const sessionState = findSessionState(sessions, payload.sessionId);
1023
+ const approvalEnvelope = sessionState?.approvalEnvelopesV5.get(payload.approvalId);
1024
+ const capability = approvalEnvelope
1025
+ ? getCapabilityV5(sessionState, approvalEnvelope.capabilityId)
1026
+ : undefined;
1027
+ const verifiedEntry = approvalEnvelope
1028
+ ? lookupVerifiedRegistryEntry(runtime, approvalEnvelope.connectorId, approvalEnvelope.registryEntryId)
1029
+ : undefined;
1030
+ const prepared = prepareToolOnboardingV5({
1031
+ session: sessionState?.session,
1032
+ capability,
1033
+ approvalEnvelope,
1034
+ verifiedRegistryEntry: verifiedEntry
1035
+ });
1036
+ if (sessionState && prepared.onboardingSession) {
1037
+ const consumedAt = new Date().toISOString();
1038
+ if (capability) {
1039
+ consumeCapabilityV5(sessionState, capability, consumedAt);
1040
+ }
1041
+ if (approvalEnvelope) {
1042
+ sessionState.approvalEnvelopesV5.set(payload.approvalId, {
1043
+ ...approvalEnvelope,
1044
+ consumedAt,
1045
+ onboardingSessionId: prepared.onboardingSession.onboardingSessionId
1046
+ });
1047
+ }
1048
+ sessionState.onboardingSessionsV5.set(prepared.onboardingSession.onboardingSessionId, prepared.onboardingSession);
1049
+ sessionState.session = {
1050
+ ...sessionState.session,
1051
+ currentStep: sessionState.session.currentStep + 1
1052
+ };
1053
+ }
1054
+ appendReplayEvent(sessionState, {
1055
+ kind: "tool",
1056
+ actor: "sdk",
1057
+ payload: {
1058
+ route: canonicalReplayRoute(request.url),
1059
+ approvalId: payload.approvalId,
1060
+ verdict: prepared.verdict.decision
1061
+ }
1062
+ });
1063
+ writeJson(response, 200, {
1064
+ verdict: relabelClaimProfile(prepared.verdict, "secure_v6"),
1065
+ approvalEnvelope: sessionState?.approvalEnvelopesV5.get(payload.approvalId) ?? approvalEnvelope,
1066
+ verifiedRegistryEntry: verifiedEntry,
1067
+ onboardingSession: prepared.onboardingSession
1068
+ });
1069
+ return;
1070
+ }
1071
+ if (request.url === "/v6/tool/callback/verify" ||
1072
+ (unifiedV5InSecureV6 && request.url === "/v5/tool/callback/verify")) {
1073
+ const payload = await readJson(request);
1074
+ const sessionState = findSessionState(sessions, payload.sessionId);
1075
+ const approvalEnvelope = sessionState?.approvalEnvelopesV5.get(payload.approvalId);
1076
+ const capability = approvalEnvelope
1077
+ ? getCapabilityV5(sessionState, approvalEnvelope.capabilityId)
1078
+ : undefined;
1079
+ const onboardingSession = sessionState?.onboardingSessionsV5.get(payload.onboardingSessionId);
1080
+ const verifiedEntry = approvalEnvelope
1081
+ ? lookupVerifiedRegistryEntry(runtime, approvalEnvelope.connectorId, approvalEnvelope.registryEntryId)
1082
+ : undefined;
1083
+ const verified = verifyToolCallbackV5({
1084
+ session: sessionState?.session,
1085
+ capability,
1086
+ approvalEnvelope,
1087
+ onboardingSession,
1088
+ verifiedRegistryEntry: verifiedEntry,
1089
+ request: payload.request
1090
+ });
1091
+ if (sessionState && onboardingSession && verified.verdict.decision === "ALLOW") {
1092
+ sessionState.onboardingSessionsV5.set(payload.onboardingSessionId, {
1093
+ ...onboardingSession,
1094
+ status: "used"
1095
+ });
1096
+ }
1097
+ if (sessionState && verified.connectorHandle) {
1098
+ sessionState.connectorHandlesV5.set(verified.connectorHandle.handleId, verified.connectorHandle);
1099
+ }
1100
+ appendReplayEvent(sessionState, {
1101
+ kind: "tool",
1102
+ actor: "sdk",
1103
+ payload: {
1104
+ route: canonicalReplayRoute(request.url),
1105
+ onboardingSessionId: payload.onboardingSessionId,
1106
+ verdict: verified.verdict.decision
1107
+ }
1108
+ });
1109
+ writeJson(response, 200, {
1110
+ ...verified,
1111
+ verdict: relabelClaimProfile(verified.verdict, "secure_v6")
1112
+ });
1113
+ return;
1114
+ }
1115
+ if (request.url === "/v6/artifact/ingest") {
1116
+ const payload = await readJson(request);
1117
+ const sessionState = findSessionState(sessions, payload.sessionId);
1118
+ if (!sessionState) {
1119
+ writeJson(response, 404, { error: "unknown_session" });
1120
+ return;
1121
+ }
1122
+ const capture = {
1123
+ ...payload.capture,
1124
+ sessionId: payload.sessionId,
1125
+ taskId: sessionState.session.taskId
1126
+ };
1127
+ const observationResult = await parserIsolationService.compileObservation({
1128
+ capture,
1129
+ workflowHash: sessionState.session.workflowHash,
1130
+ compilerVersion: "v5"
1131
+ });
1132
+ const compiledObservation = observationResult.compiledObservation;
1133
+ const plannerView = observationResult.plannerView;
1134
+ const artifactCapture = buildLegacyObservationCapture(capture);
1135
+ const brokeredArtifact = artifactCapture
1136
+ ? brokerArtifact(artifactCapture, runtime)
1137
+ : brokerArtifact({
1138
+ mimeType: "application/octet-stream",
1139
+ surfaceKind: "document",
1140
+ sourceOrigin: capture.url,
1141
+ viewerOrigin: capture.frameUrl ?? capture.url,
1142
+ extractionMethod: "download",
1143
+ trustSignals: capture.trustSignals
1144
+ }, runtime);
1145
+ const authorityEligible = compiledObservation.parseStatus === "compiled" &&
1146
+ compiledObservation.authorityEligible &&
1147
+ !brokeredArtifact.artifact.mismatchSignals.length &&
1148
+ !brokeredArtifact.artifact.metadataSignals.length;
1149
+ const artifactVerdict = relabelClaimProfile(authorityEligible
1150
+ ? {
1151
+ decision: "ALLOW",
1152
+ reasonCodes: [],
1153
+ riskScore: Math.max(compiledObservation.riskScore, brokeredArtifact.verdict.riskScore),
1154
+ safeConstraints: {
1155
+ authority_eligible: true,
1156
+ handoff_mode: "artifact_reference"
1157
+ },
1158
+ telemetryTags: ["artifact_v6", "allow"]
1159
+ }
1160
+ : {
1161
+ decision: brokeredArtifact.verdict.decision === "QUARANTINE_ARTIFACT" ||
1162
+ brokeredArtifact.artifact.mismatchSignals.length > 0 ||
1163
+ brokeredArtifact.artifact.metadataSignals.length > 0
1164
+ ? "QUARANTINE_ARTIFACT"
1165
+ : "BLOCK",
1166
+ reasonCodes: [...new Set([
1167
+ ...(compiledObservation.parseStatus === "compiled"
1168
+ ? ["ARTIFACT_NOT_AUTHORITY_ELIGIBLE"]
1169
+ : [
1170
+ compiledObservation.parseStatus === "partial"
1171
+ ? "PARSE_STATUS_PARTIAL"
1172
+ : "PARSE_STATUS_UNSUPPORTED"
1173
+ ]),
1174
+ ...brokeredArtifact.verdict.reasonCodes
1175
+ ])],
1176
+ riskScore: Math.max(0.95, brokeredArtifact.verdict.riskScore),
1177
+ safeConstraints: {
1178
+ authority_eligible: false,
1179
+ handoff_mode: "artifact_reference"
1180
+ },
1181
+ telemetryTags: ["artifact_v6", "quarantine"]
1182
+ }, "secure_v6");
1183
+ sessionState.latestObservationV5 = compiledObservation;
1184
+ sessionState.observationsV5.set(compiledObservation.observationId, compiledObservation);
1185
+ const replayEvent = appendReplayEvent(sessionState, {
1186
+ kind: "artifact",
1187
+ actor: "sdk",
1188
+ payload: {
1189
+ route: canonicalReplayRoute(request.url),
1190
+ artifactId: brokeredArtifact.artifact.artifactId,
1191
+ surfaceKind: brokeredArtifact.artifact.surfaceKind,
1192
+ verdict: artifactVerdict.decision
1193
+ }
1194
+ });
1195
+ writeJson(response, 200, {
1196
+ compiledObservation,
1197
+ plannerView,
1198
+ artifactRef: buildV6ArtifactRef(brokeredArtifact.artifact, authorityEligible),
1199
+ mismatchSignals: brokeredArtifact.artifact.mismatchSignals,
1200
+ artifactVerdict,
1201
+ replayEventId: replayEvent.eventId
1202
+ });
1203
+ return;
1204
+ }
1205
+ if (request.url === "/v6/memory/stage" ||
1206
+ (unifiedV5InSecureV6 && request.url === "/v5/memory/stage")) {
1207
+ const payload = await readJson(request);
1208
+ const sessionState = findSessionState(sessions, payload.sessionId);
1209
+ const result = stageMemoryRecordV6(payload, sessionState?.session);
1210
+ let promotionTicket;
1211
+ if (sessionState && result.record) {
1212
+ sessionState.memoryRecords.set(result.record.recordId, result.record);
1213
+ sessionState.memorySourceClassesV6.set(result.record.recordId, payload.sourceClass);
1214
+ if (result.record.tier === "candidate_durable") {
1215
+ const capability = mintMemoryPromotionCapabilityV5(sessionState.session, {
1216
+ recordId: result.record.recordId,
1217
+ sourceDigest: result.record.sourceDigest,
1218
+ sourceObservationId: result.record.sourceObservationId,
1219
+ key: result.record.key,
1220
+ valueDigest: result.record.sourceDigest ?? hashValue(result.record.value)
1221
+ });
1222
+ sessionState.capabilitiesV5.set(capability.capabilityId, capability);
1223
+ promotionTicket = {
1224
+ ticketId: capability.capabilityId,
1225
+ ticketDigest: capability.capabilityDigest,
1226
+ semanticDigest: capability.semanticDigest,
1227
+ recordId: result.record.recordId,
1228
+ sourceClass: payload.sourceClass,
1229
+ expiresAt: capability.expiresAt
1230
+ };
1231
+ }
1232
+ }
1233
+ appendReplayEvent(sessionState, {
1234
+ kind: "memory",
1235
+ actor: "sdk",
1236
+ payload: {
1237
+ route: canonicalReplayRoute(request.url),
1238
+ recordId: result.record?.recordId ?? null,
1239
+ sourceClass: payload.sourceClass,
1240
+ verdict: result.verdict.decision
1241
+ }
1242
+ });
1243
+ writeJson(response, 200, {
1244
+ ...result,
1245
+ verdict: relabelClaimProfile(result.verdict, "secure_v6"),
1246
+ promotionTicket
1247
+ });
1248
+ return;
1249
+ }
1250
+ if (request.url === "/v6/memory/promote" ||
1251
+ (unifiedV5InSecureV6 && request.url === "/v5/memory/promote")) {
1252
+ const payload = await readJson(request);
1253
+ const normalizedPayload = {
1254
+ ...payload,
1255
+ ticketId: payload.ticketId ?? payload.capabilityId ?? "",
1256
+ ticketDigest: payload.ticketDigest ?? payload.capabilityDigest ?? ""
1257
+ };
1258
+ const sessionState = findSessionState(sessions, normalizedPayload.sessionId);
1259
+ const record = sessionState?.memoryRecords.get(normalizedPayload.recordId);
1260
+ const capability = sessionState?.capabilitiesV5.get(normalizedPayload.ticketId);
1261
+ const approvalEnvelope = sessionState?.approvalEnvelopesV5.get(normalizedPayload.approvalId);
1262
+ const snapshotState = sessionState && record ? buildMemorySnapshotState(sessionState, record) : undefined;
1263
+ const result = promoteMemoryRecordV6(normalizedPayload, sessionState?.session, record, capability, approvalEnvelope, {
1264
+ sourceClass: record ? sessionState?.memorySourceClassesV6.get(record.recordId) : undefined,
1265
+ priorTrustedRecord: snapshotState?.snapshotRecord
1266
+ });
1267
+ if (sessionState && result.promotedRecord) {
1268
+ if (snapshotState && result.promotedRecord.snapshotId) {
1269
+ sessionState.memorySnapshots.set(result.promotedRecord.snapshotId, {
1270
+ ...snapshotState,
1271
+ snapshotId: result.promotedRecord.snapshotId
1272
+ });
1273
+ }
1274
+ sessionState.memoryRecords.set(result.promotedRecord.recordId, result.promotedRecord);
1275
+ if (capability) {
1276
+ consumeCapabilityV5(sessionState, capability);
1277
+ }
1278
+ if (approvalEnvelope) {
1279
+ sessionState.approvalEnvelopesV5.set(payload.approvalId, {
1280
+ ...approvalEnvelope,
1281
+ consumedAt: new Date().toISOString()
1282
+ });
1283
+ }
1284
+ sessionState.session = {
1285
+ ...sessionState.session,
1286
+ currentStep: sessionState.session.currentStep + 1
1287
+ };
1288
+ }
1289
+ appendReplayEvent(sessionState, {
1290
+ kind: "memory",
1291
+ actor: "sdk",
1292
+ payload: {
1293
+ route: canonicalReplayRoute(request.url),
1294
+ recordId: normalizedPayload.recordId,
1295
+ verdict: result.verdict.decision
1296
+ }
1297
+ });
1298
+ writeJson(response, 200, {
1299
+ ...result,
1300
+ verdict: relabelClaimProfile(result.verdict, "secure_v6"),
1301
+ approvalEnvelope: normalizedPayload.approvalId && sessionState
1302
+ ? sessionState.approvalEnvelopesV5.get(normalizedPayload.approvalId)
1303
+ : approvalEnvelope
1304
+ });
1305
+ return;
1306
+ }
1307
+ if (request.url === "/v6/memory/rollback" ||
1308
+ (unifiedV5InSecureV6 && request.url === "/v5/memory/rollback")) {
1309
+ const payload = await readJson(request);
1310
+ const sessionState = findSessionState(sessions, payload.sessionId);
1311
+ const record = sessionState?.memoryRecords.get(payload.recordId);
1312
+ const snapshotState = sessionState?.memorySnapshots.get(payload.snapshotId);
1313
+ const result = rollbackMemoryRecordV5(payload, sessionState?.session, record, {
1314
+ snapshotRecord: snapshotState?.snapshotRecord,
1315
+ baselineAbsent: snapshotState?.baselineAbsent
1316
+ });
1317
+ if (sessionState && result.verdict.decision === "ALLOW") {
1318
+ if (result.restoredRecord) {
1319
+ sessionState.memoryRecords.set(result.restoredRecord.recordId, result.restoredRecord);
1320
+ }
1321
+ else {
1322
+ sessionState.memoryRecords.delete(payload.recordId);
1323
+ }
1324
+ }
1325
+ appendReplayEvent(sessionState, {
1326
+ kind: "memory",
1327
+ actor: "sdk",
1328
+ payload: {
1329
+ route: canonicalReplayRoute(request.url),
1330
+ recordId: payload.recordId,
1331
+ snapshotId: payload.snapshotId,
1332
+ verdict: result.verdict.decision
1333
+ }
1334
+ });
1335
+ writeJson(response, 200, {
1336
+ ...result,
1337
+ verdict: relabelClaimProfile(result.verdict, "secure_v6"),
1338
+ rollbackEvent: result.verdict.decision === "ALLOW"
1339
+ ? {
1340
+ recordId: payload.recordId,
1341
+ snapshotId: payload.snapshotId,
1342
+ appliedAt: new Date().toISOString()
1343
+ }
1344
+ : undefined
1345
+ });
1346
+ return;
1347
+ }
1348
+ if (request.url === "/v6/replay/bundle" ||
1349
+ (unifiedV5InSecureV6 && request.url === "/v5/replay/bundle")) {
1350
+ const payload = await readJson(request);
1351
+ const sessionState = findSessionState(sessions, payload.sessionId);
1352
+ if (!sessionState) {
1353
+ writeJson(response, 404, { error: "unknown_session" });
1354
+ return;
1355
+ }
1356
+ writeJson(response, 200, buildReplayBundle(sessionState.replayEvents, runtime));
1357
+ return;
1358
+ }
1359
+ if (request.url === "/v5/session/start") {
1360
+ const payload = await readJson(request);
1361
+ const sessionState = createSessionState(payload, runtime);
1362
+ sessions.set(sessionState.session.sessionId, sessionState);
1363
+ appendReplayEvent(sessionState, {
1364
+ kind: "verdict",
1365
+ actor: "system",
1366
+ payload: {
1367
+ route: request.url,
1368
+ sessionId: sessionState.session.sessionId,
1369
+ claimProfile: sessionState.session.claimProfile ?? "secure_v5"
1370
+ }
1371
+ });
1372
+ writeJson(response, 200, {
1373
+ session: sessionState.session
1374
+ });
1375
+ return;
1376
+ }
1377
+ if (request.url === "/v5/observe") {
1378
+ const payload = await readJson(request);
1379
+ const sessionState = findSessionState(sessions, payload.sessionId);
1380
+ if (!sessionState) {
1381
+ writeJson(response, 404, { error: "unknown_session" });
1382
+ return;
1383
+ }
1384
+ const capture = {
1385
+ ...payload.capture,
1386
+ sessionId: payload.sessionId,
1387
+ taskId: sessionState.session.taskId
1388
+ };
1389
+ const observationResult = await parserIsolationService.compileObservation({
1390
+ capture,
1391
+ workflowHash: sessionState.session.workflowHash,
1392
+ compilerVersion: "v5"
1393
+ });
1394
+ const compiledObservation = observationResult.compiledObservation;
1395
+ const plannerView = observationResult.plannerView;
1396
+ const mediated = applyV5ObservationMediation(compiledObservation, plannerView);
1397
+ const verifiedEntry = capture.surfaceType === "tool_manifest"
1398
+ ? resolveToolManifestRegistryEntry(runtime, capture)
1399
+ : undefined;
1400
+ const manifestHash = observationResult.toolManifestDigests?.manifestHash;
1401
+ const schemaHash = observationResult.toolManifestDigests?.schemaHash;
1402
+ retireObservationCapabilitiesV5(sessionState);
1403
+ const capabilities = mediated.failClosed
1404
+ ? []
1405
+ : mintCapabilitiesForObservationV5(sessionState.session, compiledObservation, mediated.plannerView, {
1406
+ verifiedRegistryEntry: verifiedEntry,
1407
+ registryEntryId: capture.surfaceType === "tool_manifest" ? verifiedEntry?.registryEntryId : undefined,
1408
+ connectorId: capture.surfaceType === "tool_manifest" ? verifiedEntry?.adapterId ?? capture.toolId : undefined,
1409
+ requestedScopes: capture.surfaceType === "tool_manifest" ? capture.requestedScopes : undefined,
1410
+ callbackUri: capture.surfaceType === "tool_manifest" ? capture.callbackUri : undefined,
1411
+ callbackOrigin: capture.surfaceType === "tool_manifest" ? capture.callbackOrigin : undefined,
1412
+ manifestAuthType: capture.surfaceType === "tool_manifest" ? capture.authType : undefined,
1413
+ manifestHash,
1414
+ schemaHash
1415
+ });
1416
+ sessionState.latestObservationV5 = compiledObservation;
1417
+ sessionState.observationsV5.set(compiledObservation.observationId, compiledObservation);
1418
+ for (const capability of capabilities) {
1419
+ sessionState.capabilitiesV5.set(capability.capabilityId, capability);
1420
+ }
1421
+ const artifactCapture = buildLegacyObservationCapture(capture);
1422
+ const artifactRefs = artifactCapture
1423
+ ? [
1424
+ buildV5ArtifactRef(brokerArtifact(artifactCapture, runtime).artifact, compiledObservation.authorityEligible)
1425
+ ]
1426
+ : [];
1427
+ const observationVerdict = relabelClaimProfile(buildV5ObservationDecision(compiledObservation), "secure_v5");
1428
+ const replayEvent = appendReplayEvent(sessionState, {
1429
+ kind: "observation",
1430
+ actor: "sdk",
1431
+ payload: {
1432
+ route: request.url,
1433
+ observationId: compiledObservation.observationId,
1434
+ surfaceType: compiledObservation.surfaceType,
1435
+ authorityEligible: compiledObservation.authorityEligible,
1436
+ authorityCandidateCount: capabilities.length,
1437
+ verdict: observationVerdict.decision
1438
+ }
1439
+ });
1440
+ writeJson(response, 200, {
1441
+ compiledObservation,
1442
+ plannerView: mediated.plannerView,
1443
+ capabilities: capabilities.map(summarizeCapabilityV5),
1444
+ authorityCandidates: capabilities.map(summarizeAuthorityCandidate),
1445
+ artifactRefs,
1446
+ observationVerdict,
1447
+ replayEventId: replayEvent.eventId
1448
+ });
1449
+ return;
1450
+ }
1451
+ if (request.url === "/v5/action/evaluate") {
1452
+ const payload = await readJson(request);
1453
+ const authorityId = payload.authorityId ?? payload.capabilityId;
1454
+ const authorityDigest = payload.authorityDigest ?? payload.capabilityDigest;
1455
+ const sessionState = findSessionState(sessions, payload.sessionId);
1456
+ const capability = authorityId ? getCapabilityV5(sessionState, authorityId) : undefined;
1457
+ const authorityDecision = relabelClaimProfile(evaluateCapabilityUseV5({
1458
+ sessionId: payload.sessionId,
1459
+ capabilityId: authorityId ?? "",
1460
+ capabilityDigest: authorityDigest ?? "",
1461
+ parameters: payload.parameters
1462
+ }, sessionState?.session, capability, {
1463
+ alreadyUsed: authorityId
1464
+ ? (sessionState?.usedCapabilitiesV5.has(authorityId) ?? false)
1465
+ : false
1466
+ }), "secure_v5");
1467
+ const observationDecision = buildObservationDecisionForClaimProfile(sessionState?.latestObservationV5, "secure_v5");
1468
+ const effectDecision = observationDecision.decision === "ALLOW" && authorityDecision.decision === "ALLOW"
1469
+ ? authorityDecision
1470
+ : relabelClaimProfile({
1471
+ decision: "BLOCK",
1472
+ reasonCodes: [
1473
+ ...new Set([
1474
+ ...(observationDecision.decision === "ALLOW"
1475
+ ? []
1476
+ : ["OBSERVATION_NOT_EFFECT_ELIGIBLE"]),
1477
+ ...(authorityDecision.decision === "ALLOW"
1478
+ ? []
1479
+ : authorityDecision.reasonCodes)
1480
+ ])
1481
+ ],
1482
+ riskScore: Math.max(observationDecision.riskScore, authorityDecision.riskScore),
1483
+ safeConstraints: {
1484
+ target_class: capability?.targetClass ?? "unknown"
1485
+ },
1486
+ telemetryTags: ["v5_action", "block"]
1487
+ }, "secure_v5");
1488
+ if (effectDecision.decision === "ALLOW" && sessionState && capability) {
1489
+ consumeCapabilityV5(sessionState, capability);
1490
+ sessionState.session = {
1491
+ ...sessionState.session,
1492
+ currentStep: sessionState.session.currentStep + 1
1493
+ };
1494
+ }
1495
+ appendReplayEvent(sessionState, {
1496
+ kind: "action",
1497
+ actor: "sdk",
1498
+ payload: {
1499
+ route: request.url,
1500
+ authorityId: authorityId ?? null,
1501
+ observationDecision: observationDecision.decision,
1502
+ authorityDecision: authorityDecision.decision,
1503
+ effectDecision: effectDecision.decision
1504
+ }
1505
+ });
1506
+ writeJson(response, 200, {
1507
+ observationDecision,
1508
+ authorityDecision,
1509
+ effectDecision,
1510
+ verdict: effectDecision,
1511
+ executionPlan: buildV5ExecutionPlan(capability)
1512
+ });
1513
+ return;
1514
+ }
1515
+ if (request.url === "/v5/capability/use") {
1516
+ const payload = await readJson(request);
1517
+ const sessionState = findSessionState(sessions, payload.sessionId);
1518
+ const capability = getCapabilityV5(sessionState, payload.capabilityId);
1519
+ const verdict = evaluateCapabilityUseV5(payload, sessionState?.session, capability, {
1520
+ alreadyUsed: sessionState?.usedCapabilitiesV5.has(payload.capabilityId) ?? false
1521
+ });
1522
+ if (verdict.decision === "ALLOW" && sessionState && capability) {
1523
+ consumeCapabilityV5(sessionState, capability);
1524
+ sessionState.session = {
1525
+ ...sessionState.session,
1526
+ currentStep: sessionState.session.currentStep + 1
1527
+ };
1528
+ }
1529
+ appendReplayEvent(sessionState, {
1530
+ kind: "action",
1531
+ actor: "sdk",
1532
+ payload: {
1533
+ route: request.url,
1534
+ authorityId: payload.capabilityId,
1535
+ observationDecision: buildObservationDecisionForClaimProfile(sessionState?.latestObservationV5, "secure_v5").decision,
1536
+ authorityDecision: verdict.decision,
1537
+ effectDecision: verdict.decision
1538
+ }
1539
+ });
1540
+ writeJson(response, 200, {
1541
+ verdict: relabelClaimProfile(verdict, "secure_v5"),
1542
+ executionPlan: buildV5ExecutionPlan(capability)
1543
+ });
1544
+ return;
1545
+ }
1546
+ if (request.url === "/v5/approval/issue") {
1547
+ const payload = await readJson(request);
1548
+ const sessionState = findSessionState(sessions, payload.sessionId);
1549
+ const capability = sessionState?.capabilitiesV5.get(payload.capabilityId);
1550
+ if (sessionState?.usedCapabilitiesV5.has(payload.capabilityId)) {
1551
+ writeJson(response, 200, {
1552
+ verdict: {
1553
+ decision: "BLOCK",
1554
+ reasonCodes: ["CAPABILITY_REPLAYED"],
1555
+ riskScore: 0.99,
1556
+ safeConstraints: {
1557
+ claim_profile: "secure_v5"
1558
+ },
1559
+ telemetryTags: ["approval_v5_issue", "block"]
1560
+ }
1561
+ });
1562
+ return;
1563
+ }
1564
+ if (capability && payload.capabilityDigest !== capability.capabilityDigest) {
1565
+ writeJson(response, 200, {
1566
+ verdict: {
1567
+ decision: "BLOCK",
1568
+ reasonCodes: ["CAPABILITY_DIGEST_MISMATCH"],
1569
+ riskScore: 0.99,
1570
+ safeConstraints: {
1571
+ claim_profile: "secure_v5"
1572
+ },
1573
+ telemetryTags: ["approval_v5_issue", "block"]
1574
+ }
1575
+ });
1576
+ return;
1577
+ }
1578
+ const activeApproval = getActiveApprovalEnvelopeForCapability(sessionState, payload.capabilityId);
1579
+ if (activeApproval) {
1580
+ writeJson(response, 200, {
1581
+ verdict: {
1582
+ decision: "BLOCK",
1583
+ reasonCodes: ["APPROVAL_ALREADY_ISSUED_FOR_CAPABILITY"],
1584
+ riskScore: 0.99,
1585
+ safeConstraints: {
1586
+ claim_profile: "secure_v5"
1587
+ },
1588
+ telemetryTags: ["approval_v5_issue", "block"]
1589
+ },
1590
+ approvalEnvelope: activeApproval
1591
+ });
1592
+ return;
1593
+ }
1594
+ const brokerPayload = sessionState && capability
1595
+ ? createApprovalIntentPayloadV5({
1596
+ sessionId: sessionState.session.sessionId,
1597
+ workflowHash: sessionState.session.workflowHash,
1598
+ capabilityId: capability.capabilityId,
1599
+ capabilityDigest: capability.capabilityDigest,
1600
+ expiresInSeconds: payload.expiresInSeconds
1601
+ })
1602
+ : "";
1603
+ const brokerSignatureVerified = verifyApprovalIntentSignatureV5(brokerPayload, payload.brokerSignature, runtime.approvalBrokerPublicKey);
1604
+ const issued = issueApprovalEnvelopeV5({
1605
+ session: sessionState?.session,
1606
+ capability,
1607
+ brokerSignature: payload.brokerSignature,
1608
+ brokerSignatureVerified,
1609
+ expiresInSeconds: payload.expiresInSeconds
1610
+ });
1611
+ if (issued.approvalEnvelope && sessionState) {
1612
+ sessionState.approvalEnvelopesV5.set(issued.approvalEnvelope.approvalId, issued.approvalEnvelope);
1613
+ }
1614
+ appendReplayEvent(sessionState, {
1615
+ kind: "tool",
1616
+ actor: "sdk",
1617
+ payload: {
1618
+ route: request.url,
1619
+ capabilityId: payload.capabilityId,
1620
+ verdict: issued.verdict.decision
1621
+ }
1622
+ });
1623
+ writeJson(response, 200, {
1624
+ ...issued,
1625
+ verdict: relabelClaimProfile(issued.verdict, "secure_v5")
1626
+ });
1627
+ return;
1628
+ }
1629
+ if (request.url === "/v5/tool/prepare") {
1630
+ const payload = await readJson(request);
1631
+ const sessionState = findSessionState(sessions, payload.sessionId);
1632
+ const approvalEnvelope = sessionState?.approvalEnvelopesV5.get(payload.approvalId);
1633
+ const capability = approvalEnvelope
1634
+ ? getCapabilityV5(sessionState, approvalEnvelope.capabilityId)
1635
+ : undefined;
1636
+ const verifiedEntry = approvalEnvelope
1637
+ ? lookupVerifiedRegistryEntry(runtime, approvalEnvelope.connectorId, approvalEnvelope.registryEntryId)
1638
+ : undefined;
1639
+ const prepared = prepareToolOnboardingV5({
1640
+ session: sessionState?.session,
1641
+ capability,
1642
+ approvalEnvelope,
1643
+ verifiedRegistryEntry: verifiedEntry
1644
+ });
1645
+ if (sessionState && prepared.onboardingSession) {
1646
+ const consumedAt = new Date().toISOString();
1647
+ if (capability) {
1648
+ consumeCapabilityV5(sessionState, capability, consumedAt);
1649
+ }
1650
+ if (approvalEnvelope) {
1651
+ sessionState.approvalEnvelopesV5.set(payload.approvalId, {
1652
+ ...approvalEnvelope,
1653
+ consumedAt,
1654
+ onboardingSessionId: prepared.onboardingSession.onboardingSessionId
1655
+ });
1656
+ }
1657
+ sessionState.onboardingSessionsV5.set(prepared.onboardingSession.onboardingSessionId, prepared.onboardingSession);
1658
+ sessionState.session = {
1659
+ ...sessionState.session,
1660
+ currentStep: sessionState.session.currentStep + 1
1661
+ };
1662
+ }
1663
+ appendReplayEvent(sessionState, {
1664
+ kind: "tool",
1665
+ actor: "sdk",
1666
+ payload: {
1667
+ route: request.url,
1668
+ approvalId: payload.approvalId,
1669
+ verdict: prepared.verdict.decision
1670
+ }
1671
+ });
1672
+ writeJson(response, 200, {
1673
+ verdict: relabelClaimProfile(prepared.verdict, "secure_v5"),
1674
+ approvalEnvelope: sessionState?.approvalEnvelopesV5.get(payload.approvalId) ?? approvalEnvelope,
1675
+ verifiedRegistryEntry: verifiedEntry,
1676
+ onboardingSession: prepared.onboardingSession
1677
+ });
1678
+ return;
1679
+ }
1680
+ if (request.url === "/v5/tool/callback/verify") {
1681
+ const payload = await readJson(request);
1682
+ const sessionState = findSessionState(sessions, payload.sessionId);
1683
+ const approvalEnvelope = sessionState?.approvalEnvelopesV5.get(payload.approvalId);
1684
+ const capability = approvalEnvelope
1685
+ ? getCapabilityV5(sessionState, approvalEnvelope.capabilityId)
1686
+ : undefined;
1687
+ const onboardingSession = sessionState?.onboardingSessionsV5.get(payload.onboardingSessionId);
1688
+ const verifiedEntry = approvalEnvelope
1689
+ ? lookupVerifiedRegistryEntry(runtime, approvalEnvelope.connectorId, approvalEnvelope.registryEntryId)
1690
+ : undefined;
1691
+ const verified = verifyToolCallbackV5({
1692
+ session: sessionState?.session,
1693
+ capability,
1694
+ approvalEnvelope,
1695
+ onboardingSession,
1696
+ verifiedRegistryEntry: verifiedEntry,
1697
+ request: payload.request
1698
+ });
1699
+ if (sessionState && onboardingSession && verified.verdict.decision === "ALLOW") {
1700
+ sessionState.onboardingSessionsV5.set(payload.onboardingSessionId, {
1701
+ ...onboardingSession,
1702
+ status: "used"
1703
+ });
1704
+ }
1705
+ if (sessionState && verified.connectorHandle) {
1706
+ sessionState.connectorHandlesV5.set(verified.connectorHandle.handleId, verified.connectorHandle);
1707
+ }
1708
+ appendReplayEvent(sessionState, {
1709
+ kind: "tool",
1710
+ actor: "sdk",
1711
+ payload: {
1712
+ route: request.url,
1713
+ onboardingSessionId: payload.onboardingSessionId,
1714
+ verdict: verified.verdict.decision
1715
+ }
1716
+ });
1717
+ writeJson(response, 200, {
1718
+ ...verified,
1719
+ verdict: relabelClaimProfile(verified.verdict, "secure_v5")
1720
+ });
1721
+ return;
1722
+ }
1723
+ if (request.url === "/v5/artifact/ingest") {
1724
+ const payload = await readJson(request);
1725
+ const sessionState = findSessionState(sessions, payload.sessionId);
1726
+ if (!sessionState) {
1727
+ writeJson(response, 404, { error: "unknown_session" });
1728
+ return;
1729
+ }
1730
+ const capture = {
1731
+ ...payload.capture,
1732
+ sessionId: payload.sessionId,
1733
+ taskId: sessionState.session.taskId
1734
+ };
1735
+ const observationResult = await parserIsolationService.compileObservation({
1736
+ capture,
1737
+ workflowHash: sessionState.session.workflowHash,
1738
+ compilerVersion: "v5"
1739
+ });
1740
+ const compiledObservation = observationResult.compiledObservation;
1741
+ const plannerView = observationResult.plannerView;
1742
+ const artifactCapture = buildLegacyObservationCapture(capture);
1743
+ const brokeredArtifact = artifactCapture
1744
+ ? brokerArtifact(artifactCapture, runtime)
1745
+ : brokerArtifact({
1746
+ mimeType: "application/octet-stream",
1747
+ surfaceKind: "document",
1748
+ sourceOrigin: capture.url,
1749
+ viewerOrigin: capture.frameUrl ?? capture.url,
1750
+ extractionMethod: "download",
1751
+ trustSignals: capture.trustSignals
1752
+ }, runtime);
1753
+ const artifactAuthoritative = compiledObservation.parseStatus === "compiled" &&
1754
+ compiledObservation.authorityEligible &&
1755
+ !brokeredArtifact.artifact.mismatchSignals.length &&
1756
+ !brokeredArtifact.artifact.metadataSignals.length;
1757
+ const artifactVerdict = relabelClaimProfile(artifactAuthoritative
1758
+ ? {
1759
+ decision: "ALLOW",
1760
+ reasonCodes: [],
1761
+ riskScore: Math.max(compiledObservation.riskScore, brokeredArtifact.verdict.riskScore),
1762
+ safeConstraints: {
1763
+ authority_eligible: true,
1764
+ handoff_mode: "artifact_reference"
1765
+ },
1766
+ telemetryTags: ["artifact_v5", "allow"]
1767
+ }
1768
+ : {
1769
+ decision: brokeredArtifact.verdict.decision === "QUARANTINE_ARTIFACT" ||
1770
+ brokeredArtifact.artifact.mismatchSignals.length > 0 ||
1771
+ brokeredArtifact.artifact.metadataSignals.length > 0
1772
+ ? "QUARANTINE_ARTIFACT"
1773
+ : "BLOCK",
1774
+ reasonCodes: [...new Set([
1775
+ ...(compiledObservation.parseStatus === "compiled"
1776
+ ? ["ARTIFACT_NOT_AUTHORITY_ELIGIBLE"]
1777
+ : [
1778
+ compiledObservation.parseStatus === "partial"
1779
+ ? "PARSE_STATUS_PARTIAL"
1780
+ : "PARSE_STATUS_UNSUPPORTED"
1781
+ ]),
1782
+ ...brokeredArtifact.verdict.reasonCodes
1783
+ ])],
1784
+ riskScore: Math.max(0.95, brokeredArtifact.verdict.riskScore),
1785
+ safeConstraints: {
1786
+ authority_eligible: false,
1787
+ handoff_mode: "artifact_reference"
1788
+ },
1789
+ telemetryTags: ["artifact_v5", "quarantine"]
1790
+ }, "secure_v5");
1791
+ sessionState.latestObservationV5 = compiledObservation;
1792
+ sessionState.observationsV5.set(compiledObservation.observationId, compiledObservation);
1793
+ const replayEvent = appendReplayEvent(sessionState, {
1794
+ kind: "artifact",
1795
+ actor: "sdk",
1796
+ payload: {
1797
+ route: request.url,
1798
+ artifactId: brokeredArtifact?.artifact.artifactId ?? null,
1799
+ surfaceKind: brokeredArtifact?.artifact.surfaceKind ?? capture.surfaceType,
1800
+ verdict: artifactVerdict.decision
1801
+ }
1802
+ });
1803
+ writeJson(response, 200, {
1804
+ compiledObservation,
1805
+ plannerView,
1806
+ artifactVerdict,
1807
+ artifactRef: buildV5ArtifactRef(brokeredArtifact.artifact, artifactAuthoritative),
1808
+ mismatchSignals: brokeredArtifact.artifact.mismatchSignals,
1809
+ metadataSignals: brokeredArtifact.artifact.metadataSignals,
1810
+ replayEventId: replayEvent.eventId
1811
+ });
1812
+ return;
1813
+ }
1814
+ if (request.url === "/v5/memory/stage") {
1815
+ const payload = await readJson(request);
1816
+ const sessionState = findSessionState(sessions, payload.sessionId);
1817
+ const result = stageMemoryRecordV5(payload, sessionState?.session);
1818
+ let promotionCapability;
1819
+ let promotionTicket;
1820
+ if (sessionState && result.record) {
1821
+ sessionState.memoryRecords.set(result.record.recordId, result.record);
1822
+ sessionState.memorySourceClassesV5.set(result.record.recordId, payload.sourceClass);
1823
+ if (result.record.tier === "candidate_durable") {
1824
+ promotionCapability = mintMemoryPromotionCapabilityV5(sessionState.session, {
1825
+ recordId: result.record.recordId,
1826
+ sourceDigest: result.record.sourceDigest,
1827
+ sourceObservationId: result.record.sourceObservationId,
1828
+ key: result.record.key,
1829
+ valueDigest: result.record.sourceDigest ?? hashValue(result.record.value)
1830
+ });
1831
+ sessionState.capabilitiesV5.set(promotionCapability.capabilityId, promotionCapability);
1832
+ promotionTicket = {
1833
+ ticketId: promotionCapability.capabilityId,
1834
+ ticketDigest: promotionCapability.capabilityDigest,
1835
+ semanticDigest: promotionCapability.semanticDigest,
1836
+ recordId: result.record.recordId,
1837
+ sourceClass: payload.sourceClass,
1838
+ expiresAt: promotionCapability.expiresAt
1839
+ };
1840
+ }
1841
+ }
1842
+ appendReplayEvent(sessionState, {
1843
+ kind: "memory",
1844
+ actor: "sdk",
1845
+ payload: {
1846
+ route: request.url,
1847
+ recordId: result.record?.recordId ?? null,
1848
+ sourceClass: payload.sourceClass,
1849
+ verdict: result.verdict.decision
1850
+ }
1851
+ });
1852
+ writeJson(response, 200, {
1853
+ ...result,
1854
+ verdict: relabelClaimProfile(result.verdict, "secure_v5"),
1855
+ record: result.record
1856
+ ? {
1857
+ ...result.record,
1858
+ sourceClass: payload.sourceClass
1859
+ }
1860
+ : undefined,
1861
+ promotionCapability: promotionCapability
1862
+ ? {
1863
+ capabilityId: promotionCapability.capabilityId,
1864
+ capabilityDigest: promotionCapability.capabilityDigest,
1865
+ semanticDigest: promotionCapability.semanticDigest,
1866
+ title: promotionCapability.title,
1867
+ kind: promotionCapability.kind,
1868
+ expiresAt: promotionCapability.expiresAt
1869
+ }
1870
+ : undefined,
1871
+ promotionTicket
1872
+ });
1873
+ return;
1874
+ }
1875
+ if (request.url === "/v5/memory/write") {
1876
+ const payload = await readJson(request);
1877
+ const sessionState = findSessionState(sessions, payload.sessionId);
1878
+ const result = evaluateMemoryWriteV5(payload, sessionState?.session);
1879
+ let promotionCapability;
1880
+ if (sessionState && result.record) {
1881
+ sessionState.memoryRecords.set(result.record.recordId, result.record);
1882
+ sessionState.memorySourceClassesV5.set(result.record.recordId, "user_note");
1883
+ if (result.record.tier === "candidate_durable") {
1884
+ promotionCapability = mintMemoryPromotionCapabilityV5(sessionState.session, {
1885
+ recordId: result.record.recordId,
1886
+ sourceDigest: result.record.sourceDigest,
1887
+ sourceObservationId: result.record.sourceObservationId,
1888
+ key: result.record.key,
1889
+ valueDigest: result.record.sourceDigest ?? hashValue(result.record.value)
1890
+ });
1891
+ sessionState.capabilitiesV5.set(promotionCapability.capabilityId, promotionCapability);
1892
+ }
1893
+ }
1894
+ appendReplayEvent(sessionState, {
1895
+ kind: "memory",
1896
+ actor: "sdk",
1897
+ payload: {
1898
+ route: request.url,
1899
+ recordId: result.record?.recordId ?? null,
1900
+ sourceClass: "user_note",
1901
+ verdict: result.verdict.decision
1902
+ }
1903
+ });
1904
+ writeJson(response, 200, {
1905
+ ...result,
1906
+ verdict: relabelClaimProfile(result.verdict, "secure_v5"),
1907
+ promotionCapability: promotionCapability
1908
+ ? {
1909
+ capabilityId: promotionCapability.capabilityId,
1910
+ capabilityDigest: promotionCapability.capabilityDigest,
1911
+ semanticDigest: promotionCapability.semanticDigest,
1912
+ title: promotionCapability.title,
1913
+ kind: promotionCapability.kind,
1914
+ expiresAt: promotionCapability.expiresAt
1915
+ }
1916
+ : undefined,
1917
+ promotionTicket: promotionCapability
1918
+ ? {
1919
+ ticketId: promotionCapability.capabilityId,
1920
+ ticketDigest: promotionCapability.capabilityDigest,
1921
+ semanticDigest: promotionCapability.semanticDigest,
1922
+ recordId: result.record?.recordId,
1923
+ sourceClass: "user_note",
1924
+ expiresAt: promotionCapability.expiresAt
1925
+ }
1926
+ : undefined
1927
+ });
1928
+ return;
1929
+ }
1930
+ if (request.url === "/v5/memory/promote") {
1931
+ const payload = await readJson(request);
1932
+ const normalizedPayload = {
1933
+ ...payload,
1934
+ ticketId: payload.ticketId ?? payload.capabilityId ?? "",
1935
+ ticketDigest: payload.ticketDigest ?? payload.capabilityDigest ?? ""
1936
+ };
1937
+ const sessionState = findSessionState(sessions, normalizedPayload.sessionId);
1938
+ const record = sessionState?.memoryRecords.get(normalizedPayload.recordId);
1939
+ const capability = sessionState?.capabilitiesV5.get(normalizedPayload.ticketId);
1940
+ const approvalEnvelope = sessionState?.approvalEnvelopesV5.get(normalizedPayload.approvalId);
1941
+ const snapshotState = sessionState && record ? buildMemorySnapshotState(sessionState, record) : undefined;
1942
+ const result = promoteStagedMemoryRecordV5(normalizedPayload, sessionState?.session, record, capability, approvalEnvelope, {
1943
+ sourceClass: record
1944
+ ? (sessionState?.memorySourceClassesV5.get(record.recordId) ?? "user_note")
1945
+ : undefined,
1946
+ priorTrustedRecord: snapshotState?.snapshotRecord
1947
+ });
1948
+ if (sessionState && result.promotedRecord) {
1949
+ if (snapshotState && result.promotedRecord.snapshotId) {
1950
+ sessionState.memorySnapshots.set(result.promotedRecord.snapshotId, {
1951
+ ...snapshotState,
1952
+ snapshotId: result.promotedRecord.snapshotId
1953
+ });
1954
+ }
1955
+ sessionState.memoryRecords.set(result.promotedRecord.recordId, result.promotedRecord);
1956
+ if (capability) {
1957
+ consumeCapabilityV5(sessionState, capability);
1958
+ }
1959
+ if (approvalEnvelope) {
1960
+ sessionState.approvalEnvelopesV5.set(normalizedPayload.approvalId, {
1961
+ ...approvalEnvelope,
1962
+ consumedAt: new Date().toISOString()
1963
+ });
1964
+ }
1965
+ sessionState.session = {
1966
+ ...sessionState.session,
1967
+ currentStep: sessionState.session.currentStep + 1
1968
+ };
1969
+ }
1970
+ appendReplayEvent(sessionState, {
1971
+ kind: "memory",
1972
+ actor: "sdk",
1973
+ payload: {
1974
+ route: request.url,
1975
+ recordId: normalizedPayload.recordId,
1976
+ verdict: result.verdict.decision
1977
+ }
1978
+ });
1979
+ writeJson(response, 200, {
1980
+ ...result,
1981
+ verdict: relabelClaimProfile(result.verdict, "secure_v5"),
1982
+ approvalEnvelope: normalizedPayload.approvalId && sessionState
1983
+ ? sessionState.approvalEnvelopesV5.get(normalizedPayload.approvalId)
1984
+ : approvalEnvelope
1985
+ });
1986
+ return;
1987
+ }
1988
+ if (request.url === "/v5/memory/rollback") {
1989
+ const payload = await readJson(request);
1990
+ const sessionState = findSessionState(sessions, payload.sessionId);
1991
+ const record = sessionState?.memoryRecords.get(payload.recordId);
1992
+ const snapshotState = sessionState?.memorySnapshots.get(payload.snapshotId);
1993
+ const result = rollbackMemoryRecordV5(payload, sessionState?.session, record, {
1994
+ snapshotRecord: snapshotState?.snapshotRecord,
1995
+ baselineAbsent: snapshotState?.baselineAbsent
1996
+ });
1997
+ if (sessionState && result.verdict.decision === "ALLOW") {
1998
+ if (result.restoredRecord) {
1999
+ sessionState.memoryRecords.set(result.restoredRecord.recordId, result.restoredRecord);
2000
+ }
2001
+ else {
2002
+ sessionState.memoryRecords.delete(payload.recordId);
2003
+ }
2004
+ }
2005
+ appendReplayEvent(sessionState, {
2006
+ kind: "memory",
2007
+ actor: "sdk",
2008
+ payload: {
2009
+ route: request.url,
2010
+ recordId: payload.recordId,
2011
+ snapshotId: payload.snapshotId,
2012
+ verdict: result.verdict.decision
2013
+ }
2014
+ });
2015
+ writeJson(response, 200, {
2016
+ ...result,
2017
+ verdict: relabelClaimProfile(result.verdict, "secure_v5"),
2018
+ rollbackEvent: result.verdict.decision === "ALLOW"
2019
+ ? {
2020
+ recordId: payload.recordId,
2021
+ snapshotId: payload.snapshotId,
2022
+ appliedAt: new Date().toISOString()
2023
+ }
2024
+ : undefined
2025
+ });
2026
+ return;
2027
+ }
2028
+ if (request.url === "/v5/replay/bundle") {
2029
+ const payload = await readJson(request);
2030
+ const sessionState = findSessionState(sessions, payload.sessionId);
2031
+ if (!sessionState) {
2032
+ writeJson(response, 404, { error: "unknown_session" });
2033
+ return;
2034
+ }
2035
+ writeJson(response, 200, buildReplayBundle(sessionState.replayEvents, runtime));
2036
+ return;
2037
+ }
2038
+ if (request.url === "/v4/session/start") {
2039
+ const payload = await readJson(request);
2040
+ const sessionState = createSessionState(payload, runtime);
2041
+ sessions.set(sessionState.session.sessionId, sessionState);
2042
+ writeJson(response, 200, {
2043
+ session: sessionState.session
2044
+ });
2045
+ return;
2046
+ }
2047
+ if (request.url === "/v4/observe") {
2048
+ const payload = await readJson(request);
2049
+ const sessionState = findSessionState(sessions, payload.sessionId);
2050
+ if (!sessionState) {
2051
+ writeJson(response, 404, { error: "unknown_session" });
2052
+ return;
2053
+ }
2054
+ const capture = {
2055
+ ...payload.capture,
2056
+ sessionId: payload.sessionId,
2057
+ taskId: sessionState.session.taskId
2058
+ };
2059
+ const observation = await parserIsolationService.compileObservation({
2060
+ capture,
2061
+ workflowHash: sessionState.session.workflowHash,
2062
+ compilerVersion: "v4"
2063
+ });
2064
+ const failClosedObservation = applyV4FailClosedMediation(observation.compiledObservation, observation.plannerInput, "observe");
2065
+ let capabilities = mintCapabilitiesForObservation(sessionState.session, observation.compiledObservation, {
2066
+ sourceObservationId: observation.compiledObservation.observationId
2067
+ });
2068
+ let plannerInput = failClosedObservation.plannerInput;
2069
+ if (shouldReduceAuthority(sessionState, observation.compiledObservation)) {
2070
+ plannerInput = applyAuthorityReduction(sessionState, observation.compiledObservation, plannerInput);
2071
+ capabilities = [];
2072
+ }
2073
+ sessionState.capabilities.clear();
2074
+ for (const capability of capabilities) {
2075
+ sessionState.capabilities.set(capability.capabilityId, capability);
2076
+ }
2077
+ sessionState.latestObservation = observation.compiledObservation;
2078
+ sessionState.observations.set(observation.compiledObservation.observationId, observation.compiledObservation);
2079
+ writeJson(response, 200, {
2080
+ compiledObservation: observation.compiledObservation,
2081
+ observationVerdict: failClosedObservation.verdict,
2082
+ plannerInput: attachCapabilitiesToPlannerInput(plannerInput, capabilities)
2083
+ });
2084
+ return;
2085
+ }
2086
+ if (request.url === "/v4/action/evaluate") {
2087
+ const payload = await readJson(request);
2088
+ const sessionState = findSessionState(sessions, payload.sessionId);
2089
+ const capability = sessionState?.capabilities.get(payload.capabilityId);
2090
+ const verdict = evaluateCapabilityUse(payload, sessionState?.session, capability, {
2091
+ alreadyUsed: sessionState?.usedCapabilities.has(payload.capabilityId) ?? false
2092
+ });
2093
+ if (verdict.decision === "ALLOW" && sessionState && capability) {
2094
+ sessionState.capabilities.delete(payload.capabilityId);
2095
+ sessionState.usedCapabilities.add(payload.capabilityId);
2096
+ sessionState.session = {
2097
+ ...sessionState.session,
2098
+ currentStep: sessionState.session.currentStep + 1
2099
+ };
2100
+ }
2101
+ writeJson(response, 200, {
2102
+ verdict,
2103
+ executionPlan: verdict.decision === "ALLOW" && capability
2104
+ ? {
2105
+ verb: capability.kind,
2106
+ targetUrl: capability.targetUrl,
2107
+ targetOrigin: capability.targetOrigin,
2108
+ selector: capability.selector,
2109
+ derivedSinkClass: capability.derivedSinkClass,
2110
+ derivedSensitiveSink: capability.derivedSensitiveSink
2111
+ }
2112
+ : undefined
2113
+ });
2114
+ return;
2115
+ }
2116
+ if (request.url === "/v4/approval/grant") {
2117
+ const payload = await readJson(request);
2118
+ const sessionState = findSessionState(sessions, payload.sessionId);
2119
+ if (!sessionState) {
2120
+ writeJson(response, 404, { error: "unknown_session" });
2121
+ return;
2122
+ }
2123
+ const unknownCapability = (payload.capabilityIds ?? []).find((capabilityId) => !sessionState.capabilities.has(capabilityId));
2124
+ if (unknownCapability) {
2125
+ writeJson(response, 400, {
2126
+ error: "unknown_capability",
2127
+ capabilityId: unknownCapability
2128
+ });
2129
+ return;
2130
+ }
2131
+ if (payload.sinkClass === "connector_oauth" &&
2132
+ !(payload.capabilityIds ?? []).length) {
2133
+ writeJson(response, 400, {
2134
+ error: "capability_ids_required",
2135
+ sinkClass: payload.sinkClass
2136
+ });
2137
+ return;
2138
+ }
2139
+ const approvalGrant = issueApprovalGrant(payload, sessionState);
2140
+ sessionState.approvalGrants.set(approvalGrant.approvalGrantId, approvalGrant);
2141
+ writeJson(response, 200, {
2142
+ approvalGrant
2143
+ });
2144
+ return;
2145
+ }
2146
+ if (request.url === "/v4/tool/prepare") {
2147
+ const payload = await readJson(request);
2148
+ const sessionState = findSessionState(sessions, payload.sessionId);
2149
+ const approvalGrant = sessionState?.approvalGrants.get(payload.approvalGrantId);
2150
+ const prepared = prepareToolOnboardingV4({
2151
+ ...payload.request,
2152
+ approvalGrantId: payload.approvalGrantId
2153
+ }, sessionState?.session, approvalGrant, runtime);
2154
+ const onboardingSession = prepared.verdict.decision === "ALLOW" && payload.request.authType === "oauth" && approvalGrant
2155
+ ? createOnboardingSession(payload.request, runtime, approvalGrant, prepared.verifiedRegistryEntry)
2156
+ : undefined;
2157
+ if (sessionState && onboardingSession) {
2158
+ sessionState.onboardingSessions.set(onboardingSession.sessionId, onboardingSession);
2159
+ }
2160
+ writeJson(response, 200, {
2161
+ verdict: prepared.verdict,
2162
+ approvalVerdict: prepared.approvalVerdict,
2163
+ verifiedRegistryEntry: prepared.verifiedRegistryEntry,
2164
+ workflowBinding: prepared.workflowBinding,
2165
+ onboardingSession
2166
+ });
2167
+ return;
2168
+ }
2169
+ if (request.url === "/v4/tool/callback/verify") {
2170
+ const payload = await readJson(request);
2171
+ const sessionState = findSessionState(sessions, payload.sessionId);
2172
+ const approvalGrant = sessionState?.approvalGrants.get(payload.approvalGrantId);
2173
+ const session = sessionState?.onboardingSessions.get(payload.request.sessionId);
2174
+ const result = verifyToolCallbackV4(payload.request, sessionState?.session, session, approvalGrant, runtime);
2175
+ if (sessionState && session) {
2176
+ sessionState.onboardingSessions.set(payload.request.sessionId, {
2177
+ ...session,
2178
+ status: result.verdict.decision === "ALLOW" ? "used" : session.status
2179
+ });
2180
+ }
2181
+ writeJson(response, 200, result);
2182
+ return;
2183
+ }
2184
+ if (request.url === "/v4/artifact/ingest") {
2185
+ const payload = await readJson(request);
2186
+ const sessionState = findSessionState(sessions, payload.sessionId);
2187
+ if (!sessionState) {
2188
+ writeJson(response, 404, { error: "unknown_session" });
2189
+ return;
2190
+ }
2191
+ const capture = {
2192
+ ...payload.capture,
2193
+ sessionId: payload.sessionId,
2194
+ taskId: sessionState.session.taskId
2195
+ };
2196
+ const observation = await parserIsolationService.compileObservation({
2197
+ capture,
2198
+ workflowHash: sessionState.session.workflowHash,
2199
+ compilerVersion: "v4"
2200
+ });
2201
+ const failClosedArtifact = applyV4FailClosedMediation(observation.compiledObservation, observation.plannerInput, "artifact");
2202
+ const legacyArtifact = buildLegacyObservationCapture(capture);
2203
+ const artifactResult = legacyArtifact !== undefined ? brokerArtifact(legacyArtifact, runtime) : undefined;
2204
+ sessionState.latestObservation = observation.compiledObservation;
2205
+ sessionState.observations.set(observation.compiledObservation.observationId, observation.compiledObservation);
2206
+ const effectiveArtifactVerdict = failClosedArtifact.failClosed
2207
+ ? failClosedArtifact.verdict
2208
+ : artifactResult?.verdict;
2209
+ const effectiveArtifact = failClosedArtifact.failClosed && artifactResult?.artifact
2210
+ ? {
2211
+ ...artifactResult.artifact,
2212
+ toolActivationPolicy: "block",
2213
+ approvalRequiredForFollowOn: true
2214
+ }
2215
+ : artifactResult?.artifact;
2216
+ const plannerInput = shouldReduceAuthority(sessionState, observation.compiledObservation)
2217
+ ? applyAuthorityReduction(sessionState, observation.compiledObservation, failClosedArtifact.plannerInput)
2218
+ : failClosedArtifact.plannerInput;
2219
+ writeJson(response, 200, {
2220
+ compiledObservation: observation.compiledObservation,
2221
+ plannerInput,
2222
+ artifactVerdict: effectiveArtifactVerdict,
2223
+ artifact: effectiveArtifact
2224
+ });
2225
+ return;
2226
+ }
2227
+ if (request.url === "/v4/memory/write") {
2228
+ const payload = await readJson(request);
2229
+ const sessionState = findSessionState(sessions, payload.sessionId);
2230
+ const result = evaluateMemoryWriteV4(payload, sessionState?.session, runtime);
2231
+ if (sessionState && result.record) {
2232
+ sessionState.memoryRecords.set(result.record.recordId, result.record);
2233
+ }
2234
+ writeJson(response, 200, result);
2235
+ return;
2236
+ }
2237
+ if (request.url === "/v4/memory/promote") {
2238
+ const payload = await readJson(request);
2239
+ const sessionState = findSessionState(sessions, payload.sessionId);
2240
+ const record = sessionState?.memoryRecords.get(payload.recordId);
2241
+ const approvalGrant = payload.approvalGrantId && sessionState
2242
+ ? sessionState.approvalGrants.get(payload.approvalGrantId)
2243
+ : undefined;
2244
+ const result = promoteMemoryRecordV4(payload, sessionState?.session, record, approvalGrant);
2245
+ if (sessionState && result.promotedRecord) {
2246
+ if (record && result.promotedRecord.snapshotId) {
2247
+ sessionState.memorySnapshots.set(result.promotedRecord.snapshotId, {
2248
+ ...record,
2249
+ tier: "trusted_durable",
2250
+ summaryOnly: false,
2251
+ snapshotId: result.promotedRecord.snapshotId,
2252
+ rollbackPointId: result.promotedRecord.rollbackPointId
2253
+ });
2254
+ }
2255
+ sessionState.memoryRecords.set(result.promotedRecord.recordId, result.promotedRecord);
2256
+ }
2257
+ writeJson(response, 200, result);
2258
+ return;
2259
+ }
2260
+ if (request.url === "/v4/memory/rollback") {
2261
+ const payload = await readJson(request);
2262
+ const sessionState = findSessionState(sessions, payload.sessionId);
2263
+ const record = sessionState?.memoryRecords.get(payload.recordId);
2264
+ const snapshotRecord = sessionState?.memorySnapshots.get(payload.snapshotId);
2265
+ const result = rollbackMemoryRecordV4(payload, sessionState?.session, record, snapshotRecord);
2266
+ if (sessionState && result.restoredRecord) {
2267
+ sessionState.memoryRecords.set(result.restoredRecord.recordId, result.restoredRecord);
2268
+ }
2269
+ writeJson(response, 200, {
2270
+ ...result,
2271
+ rollbackEvent: result.verdict.decision === "ALLOW"
2272
+ ? {
2273
+ recordId: payload.recordId,
2274
+ snapshotId: payload.snapshotId,
2275
+ appliedAt: new Date().toISOString()
2276
+ }
2277
+ : undefined
2278
+ });
173
2279
  return;
174
2280
  }
175
2281
  writeJson(response, 404, { error: "not_found" });
176
2282
  }
177
2283
  catch (error) {
2284
+ logServerError(error);
178
2285
  writeJson(response, 500, {
179
- error: "server_error",
180
- message: error instanceof Error ? error.message : String(error)
2286
+ error: "server_error"
181
2287
  });
182
2288
  }
183
2289
  });
2290
+ const originalClose = server.close.bind(server);
2291
+ server.close = ((callback) => {
2292
+ clearInterval(parserHealthRefreshTimer);
2293
+ return originalClose((error) => {
2294
+ void parserIsolationService.close().finally(() => {
2295
+ callback?.(error);
2296
+ });
2297
+ });
2298
+ });
2299
+ return server;
184
2300
  }
185
2301
  export async function startSafeBrowseDaemon(options = {}) {
186
2302
  const server = await createSafeBrowseServer(options);