@safebrowse/daemon 0.1.3 → 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,11 +1,12 @@
1
- import { createHash, 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 { applyV4FailClosedMediation, attachCapabilitiesToPlannerInput, brokerArtifact, brokerArtifactV2, buildReplayBundle, compilePolicy, createApprovalGrantHash, evaluateAction, evaluateCapabilityUse, evaluateMemoryWrite, evaluateMemoryWriteV4, evaluateToolRequest, mintCapabilitiesForObservation, prepareToolOnboarding, prepareToolOnboardingV4, promoteMemoryRecordV4, rollbackMemoryRecordV4, sanitizeObservation, verifyToolCallback, verifyToolCallbackV4 } 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 { compileObservationInIsolation, probeParserIsolation } from "./parserIsolation.js";
8
+ import { createParserIsolationService } from "./parserIsolation.js";
9
+ const PARSER_HEALTH_REFRESH_INTERVAL_MS = 30_000;
9
10
  function hashValue(input) {
10
11
  return createHash("sha256").update(JSON.stringify(input)).digest("hex");
11
12
  }
@@ -21,6 +22,13 @@ function writeJson(response, statusCode, payload) {
21
22
  response.setHeader("content-type", "application/json; charset=utf-8");
22
23
  response.end(JSON.stringify(payload, null, 2));
23
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
+ }
24
32
  function legacyResponseMeta(route) {
25
33
  return {
26
34
  deprecated: true,
@@ -52,16 +60,34 @@ async function resolveDefaultRootDir() {
52
60
  }
53
61
  async function buildRuntimeContext(options) {
54
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";
55
67
  const policyPack = options.policyPack ??
56
68
  (await loadPolicyPackFromPaths(resolvePolicyLayerFiles(resolve(rootDir))));
57
69
  const knowledgeBase = options.knowledgeBase ?? (await loadKnowledgeBaseContext(resolve(rootDir, "knowledge_base")));
58
70
  const verifiedRegistry = options.verifiedRegistry ??
59
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));
60
76
  return {
61
77
  policy: compilePolicy(policyPack),
62
78
  knowledgeBase,
63
79
  verifiedRegistry,
64
- parserAllowlistedEgress: options.parserAllowlistedEgress ?? []
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
65
91
  };
66
92
  }
67
93
  function plusMinutes(value, minutes) {
@@ -104,10 +130,15 @@ function createSessionState(request, runtime) {
104
130
  }),
105
131
  currentStep: 0,
106
132
  createdAt,
107
- expiresAt: plusSeconds(createdAt, request.expiresInSeconds ?? 1800)
133
+ expiresAt: plusSeconds(createdAt, request.expiresInSeconds ?? 1800),
134
+ claimProfile: runtime.claimBearingReady ? secureClaimProfile(runtime) : undefined,
135
+ approvalBrokerRequired: isSecureDeploymentProfile(runtime.deploymentProfile),
136
+ legacyRoutesDisabled: isSecureDeploymentProfile(runtime.deploymentProfile)
108
137
  };
138
+ const memorySourceClasses = new Map();
109
139
  return {
110
140
  session,
141
+ observations: new Map(),
111
142
  capabilities: new Map(),
112
143
  usedCapabilities: new Set(),
113
144
  authorityReduced: false,
@@ -115,9 +146,43 @@ function createSessionState(request, runtime) {
115
146
  approvalGrants: new Map(),
116
147
  memoryRecords: new Map(),
117
148
  memorySnapshots: new Map(),
118
- onboardingSessions: 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: []
119
160
  };
120
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
+ }
121
186
  function shouldReduceAuthority(sessionState, observation) {
122
187
  if (sessionState.authorityReduced) {
123
188
  return true;
@@ -213,7 +278,223 @@ function findSessionState(sessions, sessionId) {
213
278
  }
214
279
  return sessionState;
215
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
+ }
216
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
+ }
217
498
  if (payload.surfaceType === "pdf") {
218
499
  return {
219
500
  mimeType: "application/pdf",
@@ -253,32 +534,107 @@ function buildLegacyObservationCapture(payload) {
253
534
  }
254
535
  return undefined;
255
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;
256
554
  export async function createSafeBrowseServer(options = {}) {
257
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
+ }
258
583
  const onboardingSessions = new Map();
259
584
  const sessions = new Map();
260
- return createServer(async (request, response) => {
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) => {
261
598
  if (!request.url) {
262
599
  writeJson(response, 400, { error: "missing_url" });
263
600
  return;
264
601
  }
602
+ if (request.url.startsWith("/v6/")) {
603
+ request.url = canonicalReplayRoute(request.url);
604
+ }
265
605
  try {
266
606
  if (request.method === "GET" && request.url === "/health") {
267
- const parserProbe = await probeParserIsolation();
607
+ parserProbeSnapshot = await parserIsolationService.getCachedProbe();
608
+ const claimReady = claimBearingReady(runtime, parserProbeSnapshot.probe);
609
+ runtime.claimBearingReady = claimReady;
268
610
  writeJson(response, 200, {
269
611
  status: "ok",
270
612
  profile: runtime.policy.profile,
613
+ deploymentProfile: runtime.deploymentProfile,
614
+ claimBearingReady: claimReady,
271
615
  version: runtime.policy.version,
272
616
  policyLayers: runtime.policy.layerProvenance,
617
+ legacyRoutesEnabled: !legacyRoutesDisabled(runtime),
273
618
  verifiedRegistry: runtime.verifiedRegistry
274
619
  ? {
275
620
  bundleId: runtime.verifiedRegistry.bundleId,
276
621
  version: runtime.verifiedRegistry.version,
277
622
  signatureVerified: runtime.verifiedRegistry.signatureVerified,
278
- entryCount: runtime.verifiedRegistry.entries.length
623
+ entryCount: runtime.verifiedRegistry.entries.length,
624
+ required: isSecureDeploymentProfile(runtime.deploymentProfile)
279
625
  }
280
626
  : undefined,
281
- parserIsolation: parserProbe
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
+ }
282
638
  });
283
639
  return;
284
640
  }
@@ -286,6 +642,14 @@ export async function createSafeBrowseServer(options = {}) {
286
642
  writeJson(response, 405, { error: "method_not_allowed" });
287
643
  return;
288
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
+ }
289
653
  if (request.url === "/v1/observe") {
290
654
  const payload = await readJson(request);
291
655
  writeJson(response, 200, {
@@ -392,16 +756,26 @@ export async function createSafeBrowseServer(options = {}) {
392
756
  });
393
757
  return;
394
758
  }
395
- if (request.url === "/v4/session/start") {
759
+ if (request.url === "/v6/session/start" ||
760
+ (unifiedV5InSecureV6 && request.url === "/v5/session/start")) {
396
761
  const payload = await readJson(request);
397
762
  const sessionState = createSessionState(payload, runtime);
398
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
+ });
399
773
  writeJson(response, 200, {
400
774
  session: sessionState.session
401
775
  });
402
776
  return;
403
777
  }
404
- if (request.url === "/v4/observe") {
778
+ if (request.url === "/v6/observe" || (unifiedV5InSecureV6 && request.url === "/v5/observe")) {
405
779
  const payload = await readJson(request);
406
780
  const sessionState = findSessionState(sessions, payload.sessionId);
407
781
  if (!sessionState) {
@@ -413,134 +787,332 @@ export async function createSafeBrowseServer(options = {}) {
413
787
  sessionId: payload.sessionId,
414
788
  taskId: sessionState.session.taskId
415
789
  };
416
- const observation = await compileObservationInIsolation({
790
+ const observationResult = await parserIsolationService.compileObservation({
417
791
  capture,
418
792
  workflowHash: sessionState.session.workflowHash,
419
- allowlistedEgress: runtime.parserAllowlistedEgress,
420
- runtime: {
421
- knowledgeBase: runtime.knowledgeBase
422
- }
793
+ compilerVersion: "v5"
423
794
  });
424
- const failClosedObservation = applyV4FailClosedMediation(observation.compiledObservation, observation.plannerInput, "observe");
425
- let capabilities = mintCapabilitiesForObservation(sessionState.session, observation.compiledObservation, {
426
- sourceObservationId: observation.compiledObservation.observationId
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
427
815
  });
428
- let plannerInput = failClosedObservation.plannerInput;
429
- if (shouldReduceAuthority(sessionState, observation.compiledObservation)) {
430
- plannerInput = applyAuthorityReduction(sessionState, observation.compiledObservation, plannerInput);
431
- capabilities = [];
432
- }
433
- sessionState.capabilities.clear();
816
+ sessionState.latestObservationV5 = compiledObservation;
817
+ sessionState.observationsV5.set(compiledObservation.observationId, compiledObservation);
434
818
  for (const capability of capabilities) {
435
- sessionState.capabilities.set(capability.capabilityId, capability);
819
+ sessionState.capabilitiesV5.set(capability.capabilityId, capability);
436
820
  }
437
- sessionState.latestObservation = observation.compiledObservation;
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
+ });
438
858
  writeJson(response, 200, {
439
- compiledObservation: observation.compiledObservation,
440
- observationVerdict: failClosedObservation.verdict,
441
- plannerInput: attachCapabilitiesToPlannerInput(plannerInput, capabilities)
859
+ compiledObservation,
860
+ plannerView,
861
+ capabilities: capabilityAliases,
862
+ authorityCandidates,
863
+ artifactRefs,
864
+ observationVerdict,
865
+ replayEventId: replayEvent.eventId
442
866
  });
443
867
  return;
444
868
  }
445
- if (request.url === "/v4/action/evaluate") {
869
+ if (request.url === "/v6/action/evaluate" ||
870
+ (unifiedV5InSecureV6 && request.url === "/v5/action/evaluate")) {
446
871
  const payload = await readJson(request);
872
+ const authorityId = payload.authorityId ?? payload.capabilityId;
873
+ const authorityDigest = payload.authorityDigest ?? payload.capabilityDigest;
447
874
  const sessionState = findSessionState(sessions, payload.sessionId);
448
- const capability = sessionState?.capabilities.get(payload.capabilityId);
449
- const verdict = evaluateCapabilityUse(payload, sessionState?.session, capability, {
450
- alreadyUsed: sessionState?.usedCapabilities.has(payload.capabilityId) ?? false
451
- });
452
- if (verdict.decision === "ALLOW" && sessionState && capability) {
453
- sessionState.capabilities.delete(payload.capabilityId);
454
- sessionState.usedCapabilities.add(payload.capabilityId);
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);
455
916
  sessionState.session = {
456
917
  ...sessionState.session,
457
918
  currentStep: sessionState.session.currentStep + 1
458
919
  };
459
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
+ });
460
932
  writeJson(response, 200, {
461
- verdict,
462
- executionPlan: verdict.decision === "ALLOW" && capability
463
- ? {
464
- verb: capability.kind,
465
- targetUrl: capability.targetUrl,
466
- targetOrigin: capability.targetOrigin,
467
- selector: capability.selector,
468
- derivedSinkClass: capability.derivedSinkClass,
469
- derivedSensitiveSink: capability.derivedSensitiveSink
470
- }
471
- : undefined
933
+ observationDecision,
934
+ authorityDecision,
935
+ effectDecision,
936
+ verdict: effectDecision,
937
+ executionPlan: buildV5ExecutionPlan(capability)
472
938
  });
473
939
  return;
474
940
  }
475
- if (request.url === "/v4/approval/grant") {
941
+ if (request.url === "/v6/approval/issue" ||
942
+ (unifiedV5InSecureV6 && request.url === "/v5/approval/issue")) {
476
943
  const payload = await readJson(request);
477
944
  const sessionState = findSessionState(sessions, payload.sessionId);
478
- if (!sessionState) {
479
- writeJson(response, 404, { error: "unknown_session" });
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
+ });
480
956
  return;
481
957
  }
482
- const unknownCapability = (payload.capabilityIds ?? []).find((capabilityId) => !sessionState.capabilities.has(capabilityId));
483
- if (unknownCapability) {
484
- writeJson(response, 400, {
485
- error: "unknown_capability",
486
- capabilityId: unknownCapability
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")
487
967
  });
488
968
  return;
489
969
  }
490
- if (payload.sinkClass === "connector_oauth" &&
491
- !(payload.capabilityIds ?? []).length) {
492
- writeJson(response, 400, {
493
- error: "capability_ids_required",
494
- sinkClass: payload.sinkClass
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
495
981
  });
496
982
  return;
497
983
  }
498
- const approvalGrant = issueApprovalGrant(payload, sessionState);
499
- sessionState.approvalGrants.set(approvalGrant.approvalGrantId, approvalGrant);
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
+ });
500
1013
  writeJson(response, 200, {
501
- approvalGrant
1014
+ ...issued,
1015
+ verdict: relabelClaimProfile(issued.verdict, "secure_v6")
502
1016
  });
503
1017
  return;
504
1018
  }
505
- if (request.url === "/v4/tool/prepare") {
1019
+ if (request.url === "/v6/tool/prepare" ||
1020
+ (unifiedV5InSecureV6 && request.url === "/v5/tool/prepare")) {
506
1021
  const payload = await readJson(request);
507
1022
  const sessionState = findSessionState(sessions, payload.sessionId);
508
- const approvalGrant = sessionState?.approvalGrants.get(payload.approvalGrantId);
509
- const prepared = prepareToolOnboardingV4({
510
- ...payload.request,
511
- approvalGrantId: payload.approvalGrantId
512
- }, sessionState?.session, approvalGrant, runtime);
513
- const onboardingSession = prepared.verdict.decision === "ALLOW" && payload.request.authType === "oauth" && approvalGrant
514
- ? createOnboardingSession(payload.request, runtime, approvalGrant, prepared.verifiedRegistryEntry)
1023
+ const approvalEnvelope = sessionState?.approvalEnvelopesV5.get(payload.approvalId);
1024
+ const capability = approvalEnvelope
1025
+ ? getCapabilityV5(sessionState, approvalEnvelope.capabilityId)
515
1026
  : undefined;
516
- if (sessionState && onboardingSession) {
517
- sessionState.onboardingSessions.set(onboardingSession.sessionId, onboardingSession);
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
+ };
518
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
+ });
519
1063
  writeJson(response, 200, {
520
- verdict: prepared.verdict,
521
- approvalVerdict: prepared.approvalVerdict,
522
- verifiedRegistryEntry: prepared.verifiedRegistryEntry,
523
- workflowBinding: prepared.workflowBinding,
524
- onboardingSession
1064
+ verdict: relabelClaimProfile(prepared.verdict, "secure_v6"),
1065
+ approvalEnvelope: sessionState?.approvalEnvelopesV5.get(payload.approvalId) ?? approvalEnvelope,
1066
+ verifiedRegistryEntry: verifiedEntry,
1067
+ onboardingSession: prepared.onboardingSession
525
1068
  });
526
1069
  return;
527
1070
  }
528
- if (request.url === "/v4/tool/callback/verify") {
1071
+ if (request.url === "/v6/tool/callback/verify" ||
1072
+ (unifiedV5InSecureV6 && request.url === "/v5/tool/callback/verify")) {
529
1073
  const payload = await readJson(request);
530
1074
  const sessionState = findSessionState(sessions, payload.sessionId);
531
- const approvalGrant = sessionState?.approvalGrants.get(payload.approvalGrantId);
532
- const session = sessionState?.onboardingSessions.get(payload.request.sessionId);
533
- const result = verifyToolCallbackV4(payload.request, sessionState?.session, session, approvalGrant, runtime);
534
- if (sessionState && session) {
535
- sessionState.onboardingSessions.set(payload.request.sessionId, {
536
- ...session,
537
- status: result.verdict.decision === "ALLOW" ? "used" : session.status
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"
538
1095
  });
539
1096
  }
540
- writeJson(response, 200, result);
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
+ });
541
1113
  return;
542
1114
  }
543
- if (request.url === "/v4/artifact/ingest") {
1115
+ if (request.url === "/v6/artifact/ingest") {
544
1116
  const payload = await readJson(request);
545
1117
  const sessionState = findSessionState(sessions, payload.sessionId);
546
1118
  if (!sessionState) {
@@ -552,72 +1124,1139 @@ export async function createSafeBrowseServer(options = {}) {
552
1124
  sessionId: payload.sessionId,
553
1125
  taskId: sessionState.session.taskId
554
1126
  };
555
- const observation = await compileObservationInIsolation({
1127
+ const observationResult = await parserIsolationService.compileObservation({
556
1128
  capture,
557
1129
  workflowHash: sessionState.session.workflowHash,
558
- allowlistedEgress: runtime.parserAllowlistedEgress,
559
- runtime: {
560
- knowledgeBase: runtime.knowledgeBase
561
- }
1130
+ compilerVersion: "v5"
562
1131
  });
563
- const failClosedArtifact = applyV4FailClosedMediation(observation.compiledObservation, observation.plannerInput, "artifact");
564
- const legacyArtifact = buildLegacyObservationCapture(capture);
565
- const artifactResult = legacyArtifact !== undefined ? brokerArtifact(legacyArtifact, runtime) : undefined;
566
- sessionState.latestObservation = observation.compiledObservation;
567
- const effectiveArtifactVerdict = failClosedArtifact.failClosed
568
- ? failClosedArtifact.verdict
569
- : artifactResult?.verdict;
570
- const effectiveArtifact = failClosedArtifact.failClosed && artifactResult?.artifact
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
571
1150
  ? {
572
- ...artifactResult.artifact,
573
- toolActivationPolicy: "block",
574
- approvalRequiredForFollowOn: true
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"]
575
1159
  }
576
- : artifactResult?.artifact;
577
- const plannerInput = shouldReduceAuthority(sessionState, observation.compiledObservation)
578
- ? applyAuthorityReduction(sessionState, observation.compiledObservation, failClosedArtifact.plannerInput)
579
- : failClosedArtifact.plannerInput;
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
+ });
580
1195
  writeJson(response, 200, {
581
- compiledObservation: observation.compiledObservation,
582
- plannerInput,
583
- artifactVerdict: effectiveArtifactVerdict,
584
- artifact: effectiveArtifact
1196
+ compiledObservation,
1197
+ plannerView,
1198
+ artifactRef: buildV6ArtifactRef(brokeredArtifact.artifact, authorityEligible),
1199
+ mismatchSignals: brokeredArtifact.artifact.mismatchSignals,
1200
+ artifactVerdict,
1201
+ replayEventId: replayEvent.eventId
585
1202
  });
586
1203
  return;
587
1204
  }
588
- if (request.url === "/v4/memory/write") {
1205
+ if (request.url === "/v6/memory/stage" ||
1206
+ (unifiedV5InSecureV6 && request.url === "/v5/memory/stage")) {
589
1207
  const payload = await readJson(request);
590
1208
  const sessionState = findSessionState(sessions, payload.sessionId);
591
- const result = evaluateMemoryWriteV4(payload, sessionState?.session, runtime);
1209
+ const result = stageMemoryRecordV6(payload, sessionState?.session);
1210
+ let promotionTicket;
592
1211
  if (sessionState && result.record) {
593
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
+ }
594
1232
  }
595
- writeJson(response, 200, result);
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
+ });
596
1248
  return;
597
1249
  }
598
- if (request.url === "/v4/memory/promote") {
1250
+ if (request.url === "/v6/memory/promote" ||
1251
+ (unifiedV5InSecureV6 && request.url === "/v5/memory/promote")) {
599
1252
  const payload = await readJson(request);
600
- const sessionState = findSessionState(sessions, payload.sessionId);
601
- const record = sessionState?.memoryRecords.get(payload.recordId);
602
- const approvalGrant = payload.approvalGrantId && sessionState
603
- ? sessionState.approvalGrants.get(payload.approvalGrantId)
604
- : undefined;
605
- const result = promoteMemoryRecordV4(payload, sessionState?.session, record, approvalGrant);
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
+ });
606
1267
  if (sessionState && result.promotedRecord) {
607
- if (record && result.promotedRecord.snapshotId) {
1268
+ if (snapshotState && result.promotedRecord.snapshotId) {
608
1269
  sessionState.memorySnapshots.set(result.promotedRecord.snapshotId, {
609
- ...record,
610
- tier: "trusted_durable",
611
- summaryOnly: false,
612
- snapshotId: result.promotedRecord.snapshotId,
613
- rollbackPointId: result.promotedRecord.rollbackPointId
1270
+ ...snapshotState,
1271
+ snapshotId: result.promotedRecord.snapshotId
614
1272
  });
615
1273
  }
616
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
+ };
617
1288
  }
618
- writeJson(response, 200, result);
619
- return;
620
- }
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
+ }
621
2260
  if (request.url === "/v4/memory/rollback") {
622
2261
  const payload = await readJson(request);
623
2262
  const sessionState = findSessionState(sessions, payload.sessionId);
@@ -642,12 +2281,22 @@ export async function createSafeBrowseServer(options = {}) {
642
2281
  writeJson(response, 404, { error: "not_found" });
643
2282
  }
644
2283
  catch (error) {
2284
+ logServerError(error);
645
2285
  writeJson(response, 500, {
646
- error: "server_error",
647
- message: error instanceof Error ? error.message : String(error)
2286
+ error: "server_error"
648
2287
  });
649
2288
  }
650
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;
651
2300
  }
652
2301
  export async function startSafeBrowseDaemon(options = {}) {
653
2302
  const server = await createSafeBrowseServer(options);