@safebrowse/daemon 0.1.3 → 0.1.5

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,13 @@
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";
7
- import { buildRegistryDefaults, loadKnowledgeBaseContext, loadVerifiedRegistryBundle, loadPolicyPackFromPaths, resolvePolicyLayerFiles } from "./loaders.js";
8
- import { compileObservationInIsolation, probeParserIsolation } from "./parserIsolation.js";
6
+ import { applyModelGuardAssessment, applyV6ObservationMediation, buildReplayBundle, buildModelGuardObservationRequest, compilePolicy, computeToolManifestHash, computeToolSchemaHash, createApprovalIntentPayloadV6, evaluateCapabilityUseV6, extractAttachmentGraphV6, issueApprovalEnvelopeV6, mintCapabilitiesForObservationV6, mintMemoryPromotionCapabilityV6, prepareToolOnboardingV6, promoteMemoryRecordV6, rollbackMemoryRecordV6, stageMemoryRecordV6, tightenAuthoritiesWithModelGuard, verifyApprovalIntentSignatureV6, verifyToolCallbackV6 } from "@safebrowse/core";
7
+ import { buildRegistryDefaults, loadKnowledgeBaseContext, loadPolicyPackFromPaths, loadVerifiedRegistryBundle, resolvePolicyLayerFiles } from "./loaders.js";
8
+ import { createModelGuardClient } from "./modelGuard.js";
9
+ import { createParserIsolationService } from "./parserIsolation.js";
10
+ const PARSER_HEALTH_REFRESH_INTERVAL_MS = 30_000;
9
11
  function hashValue(input) {
10
12
  return createHash("sha256").update(JSON.stringify(input)).digest("hex");
11
13
  }
@@ -21,16 +23,12 @@ function writeJson(response, statusCode, payload) {
21
23
  response.setHeader("content-type", "application/json; charset=utf-8");
22
24
  response.end(JSON.stringify(payload, null, 2));
23
25
  }
24
- function legacyResponseMeta(route) {
25
- return {
26
- deprecated: true,
27
- telemetry: {
28
- deprecated: true,
29
- claimScope: "legacy_compatibility",
30
- preventionClaim: false,
31
- routeVersion: route
32
- }
33
- };
26
+ function logServerError(error) {
27
+ if (error instanceof Error) {
28
+ console.error("SafeBrowse daemon error:", error.stack ?? error.message);
29
+ return;
30
+ }
31
+ console.error("SafeBrowse daemon error:", error);
34
32
  }
35
33
  async function fileExists(path) {
36
34
  try {
@@ -52,21 +50,36 @@ async function resolveDefaultRootDir() {
52
50
  }
53
51
  async function buildRuntimeContext(options) {
54
52
  const rootDir = options.rootDir ?? (await resolveDefaultRootDir());
53
+ const deploymentProfile = options.deploymentProfile ?? "development";
54
+ const secureDeployment = deploymentProfile === "secure_v6";
55
55
  const policyPack = options.policyPack ??
56
56
  (await loadPolicyPackFromPaths(resolvePolicyLayerFiles(resolve(rootDir))));
57
57
  const knowledgeBase = options.knowledgeBase ?? (await loadKnowledgeBaseContext(resolve(rootDir, "knowledge_base")));
58
58
  const verifiedRegistry = options.verifiedRegistry ??
59
59
  (await loadVerifiedRegistryBundle(buildRegistryDefaults(resolve(rootDir))).catch(() => undefined));
60
+ const approvalBrokerPublicKeyPem = options.approvalBrokerPublicKeyPem ??
61
+ (options.approvalBrokerPublicKeyPath
62
+ ? await readFile(options.approvalBrokerPublicKeyPath, "utf8").catch(() => undefined)
63
+ : await readFile(resolve(rootDir, "knowledge_base", "signing", "safebrowse_vf_ed25519_public.pem"), "utf8").catch(() => undefined));
60
64
  return {
61
65
  policy: compilePolicy(policyPack),
62
66
  knowledgeBase,
63
67
  verifiedRegistry,
64
- parserAllowlistedEgress: options.parserAllowlistedEgress ?? []
68
+ parserAllowlistedEgress: options.parserAllowlistedEgress ?? [],
69
+ parserIsolationMode: secureDeployment
70
+ ? "node_permission_process"
71
+ : options.parserIsolationMode ?? "scrubbed_process",
72
+ deploymentProfile,
73
+ approvalBrokerPublicKey: approvalBrokerPublicKeyPem
74
+ ? createPublicKey(approvalBrokerPublicKeyPem)
75
+ : undefined,
76
+ approvalBrokerConfigured: Boolean(approvalBrokerPublicKeyPem),
77
+ approvalBrokerMode: secureDeployment ? "external_service" : options.approvalBrokerMode ?? "signature_verification",
78
+ modelGuardBaseUrl: options.modelGuardBaseUrl?.trim() || undefined,
79
+ modelGuardTimeoutMs: options.modelGuardTimeoutMs ?? 2_500,
80
+ modelGuardEnforcementMode: options.modelGuardEnforcementMode ?? "off"
65
81
  };
66
82
  }
67
- function plusMinutes(value, minutes) {
68
- return new Date(new Date(value).getTime() + minutes * 60_000).toISOString();
69
- }
70
83
  function plusSeconds(value, seconds) {
71
84
  return new Date(new Date(value).getTime() + seconds * 1_000).toISOString();
72
85
  }
@@ -75,333 +88,312 @@ function createWorkflowHash(payload) {
75
88
  taskId: payload.taskId,
76
89
  userGoal: payload.userGoal,
77
90
  phase: payload.phase ?? "",
91
+ taskPurposeClass: payload.taskPurposeClass ?? "",
92
+ taskPhase: payload.taskPhase ?? "",
78
93
  allowedOrigins: payload.allowedOrigins ?? [],
79
94
  allowedVerbs: payload.allowedVerbs ?? [],
80
- forbiddenSinks: payload.forbiddenSinks ?? []
95
+ forbiddenSinks: payload.forbiddenSinks ?? [],
96
+ allowedPathClasses: payload.allowedPathClasses ?? [],
97
+ approvalRequiredPathClasses: payload.approvalRequiredPathClasses ?? []
81
98
  });
82
99
  }
83
- function createSessionState(request, runtime) {
100
+ function secureParserIsolationSatisfied(probe) {
101
+ return (probe.mode === "node_permission_process" &&
102
+ probe.processIsolated &&
103
+ probe.egressDenied &&
104
+ probe.permissionModelEnabled &&
105
+ probe.childProcessDenied &&
106
+ probe.workerThreadsDenied &&
107
+ probe.envKeys.length === 0);
108
+ }
109
+ function claimBearingReady(runtime, probe) {
110
+ return (runtime.deploymentProfile === "secure_v6" &&
111
+ runtime.approvalBrokerConfigured &&
112
+ Boolean(runtime.approvalBrokerPublicKey) &&
113
+ runtime.approvalBrokerMode === "external_service" &&
114
+ runtime.verifiedRegistry?.signatureVerified === true &&
115
+ runtime.parserIsolationMode === "node_permission_process" &&
116
+ secureParserIsolationSatisfied(probe));
117
+ }
118
+ function createSessionState(request, runtime, secureReady) {
84
119
  const createdAt = new Date().toISOString();
85
- const allowedOrigins = request.allowedOrigins ??
86
- [...runtime.policy.readOnlyOrigins, ...runtime.policy.writableOrigins];
120
+ const allowedOrigins = request.allowedOrigins ?? [...runtime.policy.readOnlyOrigins, ...runtime.policy.writableOrigins];
87
121
  const allowedVerbs = request.allowedVerbs ?? [...runtime.policy.allowedActions];
88
- const forbiddenSinks = request.forbiddenSinks ?? [];
89
122
  const session = {
90
123
  sessionId: randomUUID(),
91
124
  taskId: request.taskId,
92
125
  userGoal: request.userGoal,
93
126
  phase: request.phase,
127
+ taskPurposeClass: request.taskPurposeClass,
128
+ taskPhase: request.taskPhase,
94
129
  allowedOrigins,
95
130
  allowedVerbs,
96
- forbiddenSinks,
131
+ forbiddenSinks: request.forbiddenSinks ?? [],
132
+ allowedPathClasses: request.allowedPathClasses,
133
+ approvalRequiredPathClasses: request.approvalRequiredPathClasses,
97
134
  workflowHash: createWorkflowHash({
98
- taskId: request.taskId,
99
- userGoal: request.userGoal,
100
- phase: request.phase,
135
+ ...request,
101
136
  allowedOrigins,
102
- allowedVerbs,
103
- forbiddenSinks
137
+ allowedVerbs
104
138
  }),
105
139
  currentStep: 0,
106
140
  createdAt,
107
- expiresAt: plusSeconds(createdAt, request.expiresInSeconds ?? 1800)
141
+ expiresAt: plusSeconds(createdAt, request.expiresInSeconds ?? 1800),
142
+ claimProfile: secureReady ? "secure_v6" : undefined,
143
+ approvalBrokerRequired: runtime.deploymentProfile === "secure_v6",
144
+ legacyRoutesDisabled: true
108
145
  };
109
146
  return {
110
147
  session,
111
- capabilities: new Map(),
112
- usedCapabilities: new Set(),
113
- authorityReduced: false,
114
- authorityReductionReasons: [],
115
- approvalGrants: new Map(),
148
+ observations: new Map(),
149
+ authorities: new Map(),
150
+ usedAuthorities: new Set(),
151
+ approvalEnvelopes: new Map(),
152
+ onboardingSessions: new Map(),
153
+ connectorHandles: new Map(),
116
154
  memoryRecords: new Map(),
155
+ memorySourceClasses: new Map(),
117
156
  memorySnapshots: new Map(),
118
- onboardingSessions: new Map()
157
+ replayEvents: []
119
158
  };
120
159
  }
121
- function shouldReduceAuthority(sessionState, observation) {
122
- if (sessionState.authorityReduced) {
123
- return true;
160
+ function findSessionState(sessions, sessionId) {
161
+ const sessionState = sessions.get(sessionId);
162
+ if (!sessionState) {
163
+ return undefined;
164
+ }
165
+ if (new Date(sessionState.session.expiresAt).getTime() <= Date.now()) {
166
+ sessions.delete(sessionId);
167
+ return undefined;
124
168
  }
125
- const hasPriorSurface = Boolean(sessionState.latestObservation);
126
- const hasMeaningfulRisk = observation.parseStatus !== "compiled" ||
127
- observation.riskFindings.length > 0 ||
128
- observation.secretFindings.length > 0;
129
- return hasPriorSurface && hasMeaningfulRisk;
169
+ return sessionState;
130
170
  }
131
- function applyAuthorityReduction(sessionState, observation, plannerInput) {
132
- const reasons = [
133
- ...sessionState.authorityReductionReasons,
134
- ...(observation.parseStatus !== "compiled"
135
- ? [`parse_status_${observation.parseStatus}`]
136
- : []),
137
- ...observation.riskFindings,
138
- ...(observation.secretFindings.length ? ["secret_redaction_boundary"] : [])
139
- ];
140
- sessionState.authorityReduced = true;
141
- sessionState.authorityReductionReasons = [...new Set(reasons)];
142
- sessionState.capabilities.clear();
171
+ function appendReplayEvent(sessionState, event) {
172
+ if (!sessionState) {
173
+ return undefined;
174
+ }
175
+ const replayEvent = {
176
+ eventId: randomUUID(),
177
+ timestamp: new Date().toISOString(),
178
+ ...event
179
+ };
180
+ sessionState.replayEvents.push(replayEvent);
181
+ return replayEvent.eventId;
182
+ }
183
+ function authorityCandidateFromDescriptor(authority) {
143
184
  return {
144
- ...plannerInput,
145
- candidateCapabilities: [],
146
- riskMarkers: [
147
- ...new Set([
148
- ...plannerInput.riskMarkers,
149
- "multimodal_reducer_active",
150
- ...sessionState.authorityReductionReasons.map((reason) => `chain:${reason}`)
151
- ])
152
- ]
185
+ authorityId: authority.capabilityId,
186
+ authorityDigest: authority.capabilityDigest,
187
+ semanticDigest: authority.semanticDigest,
188
+ title: authority.title,
189
+ kind: authority.kind,
190
+ operationClass: authority.operationClass,
191
+ targetPathClass: authority.targetPathClass,
192
+ requiresApproval: authority.requiresApproval,
193
+ evidenceSpanIds: authority.evidenceSpanIds,
194
+ providerId: authority.providerId,
195
+ operationId: authority.operationId,
196
+ method: authority.method,
197
+ pathTemplate: authority.pathTemplate,
198
+ mailboxId: authority.mailboxId,
199
+ accountId: authority.accountId,
200
+ messageId: authority.messageId,
201
+ threadId: authority.threadId,
202
+ recipientSetHash: authority.recipientSetHash,
203
+ subjectHash: authority.subjectHash,
204
+ bodyDigest: authority.bodyDigest,
205
+ attachmentDigestSet: authority.attachmentDigestSet,
206
+ parameterSchema: authority.parameterSchema,
207
+ expiresAt: authority.expiresAt
153
208
  };
154
209
  }
155
- function createOnboardingSession(request, runtime, approvalGrant, verifiedRegistryEntry) {
156
- const callbackUri = verifiedRegistryEntry?.allowedRedirectUris[0] ??
157
- request.oauthContext?.callbackUri ??
158
- request.callbackUri ??
159
- request.oauthContext?.redirectUri ??
160
- request.requestedRedirectUri;
161
- if (!callbackUri) {
162
- return undefined;
163
- }
164
- const callbackOrigin = verifiedRegistryEntry?.allowedCallbackOrigins[0] ?? new URL(callbackUri).origin;
165
- const createdAt = (runtime.now?.() ?? new Date()).toISOString();
210
+ function consumeAuthority(sessionState, authority) {
211
+ sessionState.authorities.set(authority.capabilityId, {
212
+ ...authority,
213
+ consumedAt: new Date().toISOString()
214
+ });
215
+ sessionState.usedAuthorities.add(authority.capabilityId);
216
+ }
217
+ function lookupVerifiedRegistryEntry(runtime, toolId, registryEntryId) {
218
+ return runtime.verifiedRegistry?.entries.find((entry) => entry.registryEntryId === registryEntryId ||
219
+ entry.registryEntryId === toolId ||
220
+ entry.adapterId === toolId);
221
+ }
222
+ function lookupVerifiedApiProviderEntry(runtime, providerId) {
223
+ return runtime.verifiedRegistry?.apiProviders?.find((entry) => entry.providerId === providerId);
224
+ }
225
+ function buildArtifactRef(capture, observation) {
226
+ const extractionMethod = capture.surfaceType === "html"
227
+ ? "dom"
228
+ : capture.surfaceType === "email_message"
229
+ ? "mime"
230
+ : capture.surfaceType === "docx" ||
231
+ capture.surfaceType === "xlsx" ||
232
+ capture.surfaceType === "pptx"
233
+ ? "ooxml"
234
+ : capture.surfaceType === "attachment_bundle"
235
+ ? "extractor"
236
+ : capture.surfaceType === "tool_manifest" || capture.surfaceType === "memory_candidate"
237
+ ? "api"
238
+ : capture.surfaceType === "external_api_response"
239
+ ? "api"
240
+ : capture.surfaceType === "image"
241
+ ? "ocr"
242
+ : "download";
243
+ const surfaceKind = capture.surfaceType === "tool_manifest"
244
+ ? "tool_manifest"
245
+ : capture.surfaceType === "memory_candidate"
246
+ ? "memory"
247
+ : capture.surfaceType;
166
248
  return {
167
- sessionId: randomUUID(),
168
- approvalBindingId: approvalGrant.approvalGrantId,
169
- workflowBindingId: request.sourceObservationId,
170
- toolId: request.toolId,
171
- registryEntryId: verifiedRegistryEntry?.registryEntryId ?? request.registryEntryId ?? request.toolId,
172
- registryBundleId: verifiedRegistryEntry?.bundleId ??
173
- request.registryBundleId ??
174
- runtime.verifiedRegistry?.bundleId ??
175
- "unverified-registry",
176
- callbackUri,
177
- callbackOrigin,
178
- requestedScopes: approvalGrant.scopes,
179
- state: randomUUID(),
180
- pkceMethod: "S256",
181
- createdAt,
182
- expiresAt: plusMinutes(createdAt, 5),
183
- status: "prepared"
249
+ artifactId: observation.observationId,
250
+ surfaceKind,
251
+ sourceOrigin: observation.sourceOrigin,
252
+ viewerOrigin: observation.frameOrigin,
253
+ mismatchSignals: observation.parseStatus === "compiled" ? [] : [`parse_status_${observation.parseStatus}`],
254
+ metadataSignals: observation.policyFindings.map((finding) => finding.code),
255
+ provenance: {
256
+ extractionMethod,
257
+ lineageChain: [],
258
+ derivedTaintClass: "tainted"
259
+ },
260
+ authorityEligible: observation.authorityEligible
184
261
  };
185
262
  }
186
- function issueApprovalGrant(request, sessionState) {
187
- const issuedAt = new Date().toISOString();
188
- const grantWithoutHash = {
189
- approvalGrantId: randomUUID(),
263
+ function buildMemorySnapshotState(sessionState, record) {
264
+ const priorTrustedRecord = [...sessionState.memoryRecords.values()]
265
+ .filter((candidate) => candidate.recordId !== record.recordId &&
266
+ candidate.key === record.key &&
267
+ candidate.tier === "trusted_durable")
268
+ .sort((left, right) => right.createdAt.localeCompare(left.createdAt))[0];
269
+ return {
270
+ snapshotId: "",
190
271
  sessionId: sessionState.session.sessionId,
191
- workflowHash: sessionState.session.workflowHash,
192
- connectorId: request.connectorId,
193
- scopes: request.scopes ?? [],
194
- sinkClass: request.sinkClass,
195
- capabilityIds: request.capabilityIds ?? [],
196
- targetOrigin: request.targetOrigin,
197
- issuedAt,
198
- expiresAt: plusSeconds(issuedAt, request.expiresInSeconds ?? 600)
272
+ recordId: record.recordId,
273
+ key: record.key,
274
+ createdAt: new Date().toISOString(),
275
+ baselineAbsent: !priorTrustedRecord,
276
+ snapshotRecord: priorTrustedRecord
199
277
  };
278
+ }
279
+ function retiredRouteResponse(route) {
200
280
  return {
201
- ...grantWithoutHash,
202
- grantHash: createApprovalGrantHash(grantWithoutHash)
281
+ error: "route_retired_use_v6",
282
+ route,
283
+ replacementPrefix: "/v6",
284
+ deprecated: true
203
285
  };
204
286
  }
205
- function findSessionState(sessions, sessionId) {
206
- const sessionState = sessions.get(sessionId);
207
- if (!sessionState) {
208
- return undefined;
209
- }
210
- if (new Date(sessionState.session.expiresAt).getTime() <= Date.now()) {
211
- sessions.delete(sessionId);
212
- return undefined;
213
- }
214
- return sessionState;
215
- }
216
- function buildLegacyObservationCapture(payload) {
217
- if (payload.surfaceType === "pdf") {
218
- return {
219
- mimeType: "application/pdf",
220
- sourceOrigin: payload.url,
221
- viewerOrigin: payload.frameUrl ?? payload.url,
222
- renderedText: payload.renderedText,
223
- extractedText: payload.extractedText,
224
- ocrText: payload.ocrText,
225
- annotations: payload.annotations,
226
- metadataText: payload.metadataText,
227
- extractionMethod: "download",
228
- trustSignals: payload.trustSignals
229
- };
230
- }
231
- if (payload.surfaceType === "image") {
232
- return {
233
- mimeType: "image/png",
234
- sourceOrigin: payload.url,
235
- viewerOrigin: payload.frameUrl ?? payload.url,
236
- ocrText: payload.ocrText,
237
- metadataText: payload.metadataText,
238
- extractionMethod: "ocr",
239
- trustSignals: payload.trustSignals
240
- };
241
- }
242
- if (payload.surfaceType === "tool_manifest") {
243
- return {
244
- mimeType: "application/json",
245
- surfaceKind: "tool_manifest",
246
- sourceOrigin: payload.url,
247
- viewerOrigin: payload.frameUrl ?? payload.url,
248
- extractedText: payload.description,
249
- metadataText: payload.schemaDescriptions,
250
- extractionMethod: "api",
251
- trustSignals: payload.trustSignals
252
- };
253
- }
254
- return undefined;
287
+ function isRetiredRoute(url) {
288
+ return (url.startsWith("/v1/") ||
289
+ url.startsWith("/v2/") ||
290
+ url.startsWith("/v4/") ||
291
+ url.startsWith("/v5/"));
255
292
  }
256
293
  export async function createSafeBrowseServer(options = {}) {
257
294
  const runtime = await buildRuntimeContext(options);
258
- const onboardingSessions = new Map();
295
+ const modelGuardClient = createModelGuardClient({
296
+ baseUrl: runtime.modelGuardBaseUrl,
297
+ timeoutMs: runtime.modelGuardTimeoutMs,
298
+ enforcementMode: runtime.modelGuardEnforcementMode
299
+ });
300
+ const parserIsolationService = createParserIsolationService(runtime.parserIsolationMode, {
301
+ allowlistedEgress: runtime.parserAllowlistedEgress,
302
+ runtime
303
+ });
304
+ let parserProbeSnapshot = await parserIsolationService.refreshProbe();
305
+ let modelGuardHealthSnapshot = await modelGuardClient.refreshHealth();
306
+ const secureReady = claimBearingReady(runtime, parserProbeSnapshot.probe);
259
307
  const sessions = new Map();
260
- return createServer(async (request, response) => {
261
- if (!request.url) {
262
- writeJson(response, 400, { error: "missing_url" });
263
- return;
264
- }
308
+ const parserHealthRefreshTimer = setInterval(() => {
309
+ void parserIsolationService
310
+ .refreshProbe()
311
+ .then((snapshot) => {
312
+ parserProbeSnapshot = snapshot;
313
+ })
314
+ .catch(() => undefined);
315
+ void modelGuardClient
316
+ .refreshHealth()
317
+ .then((snapshot) => {
318
+ modelGuardHealthSnapshot = snapshot;
319
+ })
320
+ .catch(() => undefined);
321
+ }, PARSER_HEALTH_REFRESH_INTERVAL_MS);
322
+ parserHealthRefreshTimer.unref?.();
323
+ const server = createServer(async (request, response) => {
265
324
  try {
266
- if (request.method === "GET" && request.url === "/health") {
267
- const parserProbe = await probeParserIsolation();
325
+ const requestUrl = request.url ?? "/";
326
+ if (request.method === "GET" && requestUrl === "/health") {
268
327
  writeJson(response, 200, {
269
328
  status: "ok",
270
- profile: runtime.policy.profile,
271
- version: runtime.policy.version,
272
- policyLayers: runtime.policy.layerProvenance,
329
+ deploymentProfile: runtime.deploymentProfile,
330
+ claimBearingReady: claimBearingReady(runtime, parserProbeSnapshot.probe),
331
+ legacyRoutesEnabled: false,
332
+ approvalBroker: {
333
+ configured: runtime.approvalBrokerConfigured,
334
+ mode: runtime.approvalBrokerMode
335
+ },
336
+ parserIsolation: {
337
+ configuredMode: runtime.parserIsolationMode,
338
+ lastCheckedAt: parserProbeSnapshot.lastCheckedAt,
339
+ ...parserProbeSnapshot.probe
340
+ },
273
341
  verifiedRegistry: runtime.verifiedRegistry
274
342
  ? {
275
343
  bundleId: runtime.verifiedRegistry.bundleId,
276
344
  version: runtime.verifiedRegistry.version,
345
+ signer: runtime.verifiedRegistry.signer,
277
346
  signatureVerified: runtime.verifiedRegistry.signatureVerified,
278
- entryCount: runtime.verifiedRegistry.entries.length
347
+ apiProviderCount: runtime.verifiedRegistry.apiProviders?.length ?? 0,
348
+ extractorProfileCount: runtime.verifiedRegistry.extractorProfiles?.length ?? 0
279
349
  }
280
350
  : undefined,
281
- parserIsolation: parserProbe
282
- });
283
- return;
284
- }
285
- if (request.method !== "POST") {
286
- writeJson(response, 405, { error: "method_not_allowed" });
287
- return;
288
- }
289
- if (request.url === "/v1/observe") {
290
- const payload = await readJson(request);
291
- writeJson(response, 200, {
292
- ...sanitizeObservation(payload, runtime),
293
- ...legacyResponseMeta("/v1/observe")
294
- });
295
- return;
296
- }
297
- if (request.url === "/v1/action") {
298
- const payload = await readJson(request);
299
- writeJson(response, 200, {
300
- ...evaluateAction(payload, runtime),
301
- ...legacyResponseMeta("/v1/action")
302
- });
303
- return;
304
- }
305
- if (request.url === "/v1/artifact") {
306
- const payload = await readJson(request);
307
- writeJson(response, 200, {
308
- ...brokerArtifact(payload, runtime),
309
- ...legacyResponseMeta("/v1/artifact")
310
- });
311
- return;
312
- }
313
- if (request.url === "/v1/tool") {
314
- const payload = await readJson(request);
315
- writeJson(response, 200, {
316
- ...evaluateToolRequest(payload, runtime),
317
- ...legacyResponseMeta("/v1/tool")
318
- });
319
- return;
320
- }
321
- if (request.url === "/v1/memory") {
322
- const payload = await readJson(request);
323
- writeJson(response, 200, {
324
- ...evaluateMemoryWrite(payload, runtime),
325
- ...legacyResponseMeta("/v1/memory")
326
- });
327
- return;
328
- }
329
- if (request.url === "/v1/replay") {
330
- const payload = await readJson(request);
331
- writeJson(response, 200, {
332
- ...buildReplayBundle(payload.events, runtime),
333
- ...legacyResponseMeta("/v1/replay")
334
- });
335
- return;
336
- }
337
- if (request.url === "/v2/tool/prepare") {
338
- const payload = await readJson(request);
339
- const prepared = prepareToolOnboarding(payload, runtime);
340
- const onboardingSession = prepared.verdict.decision === "ALLOW" && payload.authType === "oauth"
341
- ? createOnboardingSession(payload, runtime, {
342
- approvalGrantId: payload.approvalBindingId ?? randomUUID(),
343
- sessionId: "legacy-session",
344
- workflowHash: hashValue(payload.sourceObservationId ?? payload.requestId),
345
- connectorId: payload.toolId,
346
- scopes: payload.requestedScopes ?? [],
347
- sinkClass: "connector_oauth",
348
- capabilityIds: payload.capabilityId ? [payload.capabilityId] : [],
349
- targetOrigin: payload.callbackOrigin ??
350
- payload.oauthContext?.callbackOrigin ??
351
- payload.callbackUri ??
352
- payload.requestedRedirectUri ??
353
- "unknown",
354
- issuedAt: new Date().toISOString(),
355
- expiresAt: plusMinutes(new Date().toISOString(), 10),
356
- grantHash: "legacy"
357
- })
358
- : undefined;
359
- if (onboardingSession) {
360
- onboardingSessions.set(onboardingSession.sessionId, onboardingSession);
361
- }
362
- writeJson(response, 200, {
363
- verdict: prepared.verdict,
364
- verifiedRegistryEntry: prepared.verifiedRegistryEntry,
365
- workflowBinding: prepared.workflowBinding,
366
- onboardingSession,
367
- ...legacyResponseMeta("/v2/tool/prepare")
351
+ policyLayers: runtime.policy.layerProvenance,
352
+ captureAttestation: {
353
+ htmlDom: true,
354
+ required: true
355
+ },
356
+ modelGuard: {
357
+ configured: modelGuardHealthSnapshot.configured,
358
+ ready: modelGuardHealthSnapshot.ready,
359
+ runtimeMode: modelGuardHealthSnapshot.runtimeMode,
360
+ enforcementMode: modelGuardHealthSnapshot.enforcementMode,
361
+ bundleVersion: modelGuardHealthSnapshot.bundleVersion,
362
+ featureSchemaVersion: modelGuardHealthSnapshot.featureSchemaVersion,
363
+ bundleDigest: modelGuardHealthSnapshot.bundleDigest,
364
+ componentDigests: modelGuardHealthSnapshot.componentDigests,
365
+ validationError: modelGuardHealthSnapshot.validationError
366
+ }
368
367
  });
369
368
  return;
370
369
  }
371
- if (request.url === "/v2/tool/callback/verify") {
372
- const payload = await readJson(request);
373
- const session = onboardingSessions.get(payload.sessionId);
374
- const result = verifyToolCallback(payload, session, runtime);
375
- if (session) {
376
- onboardingSessions.set(payload.sessionId, {
377
- ...session,
378
- status: result.verdict.decision === "ALLOW" ? "used" : session.status
379
- });
380
- }
381
- writeJson(response, 200, {
382
- ...result,
383
- ...legacyResponseMeta("/v2/tool/callback/verify")
384
- });
370
+ if (isRetiredRoute(requestUrl)) {
371
+ writeJson(response, 410, retiredRouteResponse(requestUrl));
385
372
  return;
386
373
  }
387
- if (request.url === "/v2/artifact") {
388
- const payload = await readJson(request);
389
- writeJson(response, 200, {
390
- ...brokerArtifactV2(payload, runtime),
391
- ...legacyResponseMeta("/v2/artifact")
392
- });
374
+ if (request.method !== "POST" || !requestUrl.startsWith("/v6/")) {
375
+ writeJson(response, 404, { error: "not_found" });
393
376
  return;
394
377
  }
395
- if (request.url === "/v4/session/start") {
378
+ if (requestUrl === "/v6/session/start") {
396
379
  const payload = await readJson(request);
397
- const sessionState = createSessionState(payload, runtime);
380
+ const sessionState = createSessionState(payload, runtime, secureReady);
398
381
  sessions.set(sessionState.session.sessionId, sessionState);
382
+ appendReplayEvent(sessionState, {
383
+ kind: "observation",
384
+ actor: "sdk",
385
+ payload: {
386
+ route: requestUrl,
387
+ sessionId: sessionState.session.sessionId,
388
+ taskId: sessionState.session.taskId
389
+ }
390
+ });
399
391
  writeJson(response, 200, {
400
392
  session: sessionState.session
401
393
  });
402
394
  return;
403
395
  }
404
- if (request.url === "/v4/observe") {
396
+ if (requestUrl === "/v6/observe") {
405
397
  const payload = await readJson(request);
406
398
  const sessionState = findSessionState(sessions, payload.sessionId);
407
399
  if (!sessionState) {
@@ -413,134 +405,346 @@ export async function createSafeBrowseServer(options = {}) {
413
405
  sessionId: payload.sessionId,
414
406
  taskId: sessionState.session.taskId
415
407
  };
416
- const observation = await compileObservationInIsolation({
408
+ const parsed = await parserIsolationService.compileObservation({
417
409
  capture,
418
410
  workflowHash: sessionState.session.workflowHash,
419
- allowlistedEgress: runtime.parserAllowlistedEgress,
420
- runtime: {
421
- knowledgeBase: runtime.knowledgeBase
422
- }
411
+ runtime,
412
+ compilerVersion: "v6"
423
413
  });
424
- const failClosedObservation = applyV4FailClosedMediation(observation.compiledObservation, observation.plannerInput, "observe");
425
- let capabilities = mintCapabilitiesForObservation(sessionState.session, observation.compiledObservation, {
426
- sourceObservationId: observation.compiledObservation.observationId
427
- });
428
- let plannerInput = failClosedObservation.plannerInput;
429
- if (shouldReduceAuthority(sessionState, observation.compiledObservation)) {
430
- plannerInput = applyAuthorityReduction(sessionState, observation.compiledObservation, plannerInput);
431
- capabilities = [];
414
+ let compiledObservation = parsed.compiledObservation;
415
+ let plannerView = parsed.plannerView;
416
+ let mediated = applyV6ObservationMediation(compiledObservation, plannerView);
417
+ const verifiedRegistryEntry = capture.surfaceType === "tool_manifest"
418
+ ? lookupVerifiedRegistryEntry(runtime, capture.toolId, capture.toolId)
419
+ : undefined;
420
+ const verifiedApiProviderEntry = "providerId" in capture && capture.providerId
421
+ ? lookupVerifiedApiProviderEntry(runtime, capture.providerId)
422
+ : undefined;
423
+ const manifestHash = capture.surfaceType === "tool_manifest"
424
+ ? parsed.toolManifestDigests?.manifestHash ??
425
+ computeToolManifestHash({
426
+ toolId: capture.toolId,
427
+ description: capture.description,
428
+ authType: capture.authType,
429
+ requestedScopes: capture.requestedScopes,
430
+ callbackUri: capture.callbackUri
431
+ })
432
+ : undefined;
433
+ const schemaHash = capture.surfaceType === "tool_manifest"
434
+ ? parsed.toolManifestDigests?.schemaHash ??
435
+ computeToolSchemaHash(capture.schemaDescriptions)
436
+ : undefined;
437
+ if (modelGuardClient.configured &&
438
+ modelGuardClient.enforcementMode !== "off" &&
439
+ mediated.verdict.decision === "ALLOW") {
440
+ try {
441
+ const scored = await modelGuardClient.scoreObservation(buildModelGuardObservationRequest(sessionState.session, compiledObservation, plannerView));
442
+ if (modelGuardClient.enforcementMode === "shadow") {
443
+ compiledObservation = {
444
+ ...compiledObservation,
445
+ modelAssessment: scored.assessment
446
+ };
447
+ }
448
+ else {
449
+ const applied = applyModelGuardAssessment(compiledObservation, plannerView, mediated.verdict, scored.assessment);
450
+ compiledObservation = applied.compiledObservation;
451
+ plannerView = applied.plannerView;
452
+ mediated = {
453
+ plannerView,
454
+ verdict: applied.verdict,
455
+ failClosed: false
456
+ };
457
+ }
458
+ }
459
+ catch {
460
+ if (modelGuardClient.enforcementMode === "tighten") {
461
+ compiledObservation = {
462
+ ...compiledObservation,
463
+ authorityEligible: false
464
+ };
465
+ plannerView = {
466
+ ...plannerView,
467
+ visibleExcerpt: "",
468
+ riskMarkers: [...new Set([...plannerView.riskMarkers, "model_guard_unavailable"])]
469
+ };
470
+ mediated = {
471
+ plannerView,
472
+ verdict: {
473
+ decision: "REPLAN_READ_ONLY",
474
+ reasonCodes: ["MODEL_GUARD_UNAVAILABLE"],
475
+ riskScore: Math.max(0.6, compiledObservation.riskScore),
476
+ safeConstraints: {
477
+ claim_profile: "secure_v6",
478
+ authority_eligible: false
479
+ },
480
+ telemetryTags: ["v6_observation", "model_guard_unavailable"]
481
+ },
482
+ failClosed: false
483
+ };
484
+ }
485
+ }
432
486
  }
433
- sessionState.capabilities.clear();
434
- for (const capability of capabilities) {
435
- sessionState.capabilities.set(capability.capabilityId, capability);
487
+ const authorities = mediated.verdict.decision === "BLOCK"
488
+ ? []
489
+ : mintCapabilitiesForObservationV6(sessionState.session, compiledObservation, plannerView, {
490
+ policy: runtime.policy,
491
+ ...(capture.surfaceType === "tool_manifest" && verifiedRegistryEntry
492
+ ? {
493
+ verifiedRegistryEntry,
494
+ registryEntryId: capture.toolId,
495
+ connectorId: capture.toolId,
496
+ requestedScopes: capture.requestedScopes,
497
+ callbackUri: capture.callbackUri,
498
+ callbackOrigin: capture.callbackOrigin,
499
+ manifestAuthType: capture.authType,
500
+ manifestHash,
501
+ schemaHash
502
+ }
503
+ : verifiedApiProviderEntry
504
+ ? {
505
+ verifiedApiProviderEntry
506
+ }
507
+ : {})
508
+ });
509
+ const tightenedAuthorities = tightenAuthoritiesWithModelGuard(authorities, modelGuardClient.enforcementMode === "tighten"
510
+ ? compiledObservation.modelAssessment
511
+ : undefined);
512
+ for (const authority of tightenedAuthorities) {
513
+ sessionState.authorities.set(authority.capabilityId, authority);
436
514
  }
437
- sessionState.latestObservation = observation.compiledObservation;
515
+ sessionState.latestObservation = compiledObservation;
516
+ sessionState.latestObservationVerdict = mediated.verdict;
517
+ sessionState.observations.set(compiledObservation.observationId, compiledObservation);
518
+ const replayEventId = appendReplayEvent(sessionState, {
519
+ kind: "observation",
520
+ actor: "sdk",
521
+ payload: {
522
+ route: requestUrl,
523
+ observationId: compiledObservation.observationId,
524
+ parseStatus: compiledObservation.parseStatus,
525
+ authorityCount: tightenedAuthorities.length,
526
+ decision: mediated.verdict.decision,
527
+ surfaceType: capture.surfaceType,
528
+ providerId: "providerId" in capture ? capture.providerId ?? null : null,
529
+ extractorIds: "extractionAttestation" in capture && capture.extractionAttestation
530
+ ? [capture.extractionAttestation.extractorId]
531
+ : "extractionAttestations" in capture
532
+ ? capture.extractionAttestations?.map((entry) => entry.extractorId) ?? []
533
+ : [],
534
+ modelAssessment: compiledObservation.modelAssessment
535
+ ? {
536
+ bundleVersion: compiledObservation.modelAssessment.bundleVersion,
537
+ calibratedDecisionLabel: compiledObservation.modelAssessment.calibratedDecisionLabel,
538
+ coarseReasonCodes: compiledObservation.modelAssessment.coarseReasonCodes,
539
+ evidenceChunkIds: compiledObservation.modelAssessment.evidenceChunkIds
540
+ }
541
+ : null
542
+ }
543
+ });
438
544
  writeJson(response, 200, {
439
- compiledObservation: observation.compiledObservation,
440
- observationVerdict: failClosedObservation.verdict,
441
- plannerInput: attachCapabilitiesToPlannerInput(plannerInput, capabilities)
545
+ compiledObservation,
546
+ plannerView,
547
+ authorityCandidates: tightenedAuthorities.map(authorityCandidateFromDescriptor),
548
+ capabilities: tightenedAuthorities.map(authorityCandidateFromDescriptor),
549
+ artifactRefs: [],
550
+ observationVerdict: mediated.verdict,
551
+ replayEventId: replayEventId ?? randomUUID()
442
552
  });
443
553
  return;
444
554
  }
445
- if (request.url === "/v4/action/evaluate") {
555
+ if (requestUrl === "/v6/action/evaluate") {
446
556
  const payload = await readJson(request);
447
557
  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
558
+ const authority = sessionState?.authorities.get(payload.authorityId);
559
+ const approvalEnvelope = payload.approvalId && sessionState
560
+ ? sessionState.approvalEnvelopes.get(payload.approvalId)
561
+ : undefined;
562
+ const authorityDecision = evaluateCapabilityUseV6(payload, sessionState?.session, authority, {
563
+ alreadyUsed: sessionState?.usedAuthorities.has(payload.authorityId) ?? false,
564
+ approvalEnvelope
451
565
  });
452
- if (verdict.decision === "ALLOW" && sessionState && capability) {
453
- sessionState.capabilities.delete(payload.capabilityId);
454
- sessionState.usedCapabilities.add(payload.capabilityId);
566
+ if (sessionState && authority && authorityDecision.decision === "ALLOW") {
567
+ consumeAuthority(sessionState, authority);
568
+ if (approvalEnvelope) {
569
+ sessionState.approvalEnvelopes.set(approvalEnvelope.approvalId, {
570
+ ...approvalEnvelope,
571
+ consumedAt: new Date().toISOString()
572
+ });
573
+ }
455
574
  sessionState.session = {
456
575
  ...sessionState.session,
457
576
  currentStep: sessionState.session.currentStep + 1
458
577
  };
459
578
  }
579
+ appendReplayEvent(sessionState, {
580
+ kind: "action",
581
+ actor: "sdk",
582
+ payload: {
583
+ route: requestUrl,
584
+ authorityId: payload.authorityId,
585
+ decision: authorityDecision.decision,
586
+ operationClass: authority?.operationClass ?? null,
587
+ providerId: authority?.providerId ?? null,
588
+ operationId: authority?.operationId ?? null
589
+ }
590
+ });
460
591
  writeJson(response, 200, {
461
- verdict,
462
- executionPlan: verdict.decision === "ALLOW" && capability
592
+ observationDecision: sessionState?.latestObservationVerdict ?? {
593
+ decision: "ALLOW",
594
+ reasonCodes: [],
595
+ riskScore: 0
596
+ },
597
+ authorityDecision,
598
+ effectDecision: authorityDecision,
599
+ executionPlan: authorityDecision.decision === "ALLOW" && authority
463
600
  ? {
464
- verb: capability.kind,
465
- targetUrl: capability.targetUrl,
466
- targetOrigin: capability.targetOrigin,
467
- selector: capability.selector,
468
- derivedSinkClass: capability.derivedSinkClass,
469
- derivedSensitiveSink: capability.derivedSensitiveSink
601
+ verb: authority.kind,
602
+ operationClass: authority.operationClass,
603
+ targetUrl: authority.targetUrl,
604
+ targetOrigin: authority.targetOrigin,
605
+ selector: authority.selector,
606
+ targetPathClass: authority.targetPathClass,
607
+ derivedSinkClass: authority.derivedSinkClass,
608
+ derivedSensitiveSink: authority.derivedSensitiveSink,
609
+ providerId: authority.providerId,
610
+ operationId: authority.operationId,
611
+ method: authority.method,
612
+ pathTemplate: authority.pathTemplate,
613
+ mailboxId: authority.mailboxId,
614
+ accountId: authority.accountId,
615
+ messageId: authority.messageId,
616
+ threadId: authority.threadId,
617
+ recipientSetHash: authority.recipientSetHash,
618
+ subjectHash: authority.subjectHash,
619
+ bodyDigest: authority.bodyDigest,
620
+ attachmentDigestSet: authority.attachmentDigestSet
470
621
  }
471
622
  : undefined
472
623
  });
473
624
  return;
474
625
  }
475
- if (request.url === "/v4/approval/grant") {
626
+ if (requestUrl === "/v6/approval/issue") {
476
627
  const payload = await readJson(request);
477
628
  const sessionState = findSessionState(sessions, payload.sessionId);
478
- if (!sessionState) {
479
- writeJson(response, 404, { error: "unknown_session" });
480
- return;
481
- }
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
629
+ const authorityId = payload.authorityId ?? payload.capabilityId ?? "";
630
+ const authorityDigest = payload.authorityDigest ?? payload.capabilityDigest ?? "";
631
+ const authority = sessionState?.authorities.get(authorityId);
632
+ if (!sessionState || !authority) {
633
+ writeJson(response, 404, {
634
+ error: !sessionState ? "unknown_session" : "unknown_authority"
487
635
  });
488
636
  return;
489
637
  }
490
- if (payload.sinkClass === "connector_oauth" &&
491
- !(payload.capabilityIds ?? []).length) {
492
- writeJson(response, 400, {
493
- error: "capability_ids_required",
494
- sinkClass: payload.sinkClass
495
- });
496
- return;
638
+ const signatureValid = authority.capabilityDigest === authorityDigest &&
639
+ verifyApprovalIntentSignatureV6(createApprovalIntentPayloadV6({
640
+ sessionId: sessionState.session.sessionId,
641
+ workflowHash: sessionState.session.workflowHash,
642
+ capabilityId: authority.capabilityId,
643
+ capabilityDigest: authority.capabilityDigest,
644
+ expiresInSeconds: payload.expiresInSeconds
645
+ }), payload.brokerSignature, runtime.approvalBrokerPublicKey);
646
+ const issued = issueApprovalEnvelopeV6({
647
+ session: sessionState.session,
648
+ capability: authority,
649
+ brokerSignature: payload.brokerSignature,
650
+ brokerSignatureVerified: signatureValid,
651
+ expiresInSeconds: payload.expiresInSeconds
652
+ });
653
+ if (issued.approvalEnvelope) {
654
+ sessionState.approvalEnvelopes.set(issued.approvalEnvelope.approvalId, issued.approvalEnvelope);
497
655
  }
498
- const approvalGrant = issueApprovalGrant(payload, sessionState);
499
- sessionState.approvalGrants.set(approvalGrant.approvalGrantId, approvalGrant);
500
- writeJson(response, 200, {
501
- approvalGrant
656
+ appendReplayEvent(sessionState, {
657
+ kind: "tool",
658
+ actor: "sdk",
659
+ payload: {
660
+ route: requestUrl,
661
+ authorityId,
662
+ decision: issued.verdict.decision
663
+ }
502
664
  });
665
+ writeJson(response, 200, issued);
503
666
  return;
504
667
  }
505
- if (request.url === "/v4/tool/prepare") {
668
+ if (requestUrl === "/v6/tool/prepare") {
506
669
  const payload = await readJson(request);
507
670
  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)
671
+ const approvalEnvelope = sessionState?.approvalEnvelopes.get(payload.approvalId);
672
+ const authority = approvalEnvelope && sessionState
673
+ ? sessionState.authorities.get(approvalEnvelope.capabilityId)
674
+ : undefined;
675
+ const verifiedRegistryEntry = authority?.registryEntryId
676
+ ? lookupVerifiedRegistryEntry(runtime, authority.connectorId, authority.registryEntryId)
515
677
  : undefined;
516
- if (sessionState && onboardingSession) {
517
- sessionState.onboardingSessions.set(onboardingSession.sessionId, onboardingSession);
678
+ const prepared = prepareToolOnboardingV6({
679
+ session: sessionState?.session,
680
+ capability: authority,
681
+ approvalEnvelope,
682
+ verifiedRegistryEntry
683
+ });
684
+ if (sessionState && approvalEnvelope && authority && prepared.onboardingSession) {
685
+ sessionState.onboardingSessions.set(prepared.onboardingSession.onboardingSessionId, prepared.onboardingSession);
686
+ sessionState.approvalEnvelopes.set(approvalEnvelope.approvalId, {
687
+ ...approvalEnvelope,
688
+ consumedAt: new Date().toISOString(),
689
+ onboardingSessionId: prepared.onboardingSession.onboardingSessionId
690
+ });
691
+ consumeAuthority(sessionState, authority);
692
+ sessionState.session = {
693
+ ...sessionState.session,
694
+ currentStep: sessionState.session.currentStep + 1
695
+ };
518
696
  }
519
- writeJson(response, 200, {
520
- verdict: prepared.verdict,
521
- approvalVerdict: prepared.approvalVerdict,
522
- verifiedRegistryEntry: prepared.verifiedRegistryEntry,
523
- workflowBinding: prepared.workflowBinding,
524
- onboardingSession
697
+ appendReplayEvent(sessionState, {
698
+ kind: "tool",
699
+ actor: "sdk",
700
+ payload: {
701
+ route: requestUrl,
702
+ approvalId: payload.approvalId,
703
+ decision: prepared.verdict.decision
704
+ }
525
705
  });
706
+ writeJson(response, 200, prepared);
526
707
  return;
527
708
  }
528
- if (request.url === "/v4/tool/callback/verify") {
709
+ if (requestUrl === "/v6/tool/callback/verify") {
529
710
  const payload = await readJson(request);
530
711
  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
712
+ const approvalEnvelope = sessionState?.approvalEnvelopes.get(payload.approvalId);
713
+ const onboardingSession = sessionState?.onboardingSessions.get(payload.onboardingSessionId);
714
+ const authority = approvalEnvelope && sessionState
715
+ ? sessionState.authorities.get(approvalEnvelope.capabilityId)
716
+ : undefined;
717
+ const verifiedRegistryEntry = authority?.registryEntryId
718
+ ? lookupVerifiedRegistryEntry(runtime, authority.connectorId, authority.registryEntryId)
719
+ : undefined;
720
+ const verified = verifyToolCallbackV6({
721
+ session: sessionState?.session,
722
+ capability: authority,
723
+ approvalEnvelope,
724
+ onboardingSession,
725
+ verifiedRegistryEntry,
726
+ request: payload.request
727
+ });
728
+ if (sessionState && onboardingSession && verified.connectorHandle) {
729
+ sessionState.onboardingSessions.set(onboardingSession.onboardingSessionId, {
730
+ ...onboardingSession,
731
+ status: "used"
538
732
  });
733
+ sessionState.connectorHandles.set(verified.connectorHandle.handleId, verified.connectorHandle);
539
734
  }
540
- writeJson(response, 200, result);
735
+ appendReplayEvent(sessionState, {
736
+ kind: "tool",
737
+ actor: "sdk",
738
+ payload: {
739
+ route: requestUrl,
740
+ onboardingSessionId: payload.onboardingSessionId,
741
+ decision: verified.verdict.decision
742
+ }
743
+ });
744
+ writeJson(response, 200, verified);
541
745
  return;
542
746
  }
543
- if (request.url === "/v4/artifact/ingest") {
747
+ if (requestUrl === "/v6/artifact/ingest") {
544
748
  const payload = await readJson(request);
545
749
  const sessionState = findSessionState(sessions, payload.sessionId);
546
750
  if (!sessionState) {
@@ -552,81 +756,194 @@ export async function createSafeBrowseServer(options = {}) {
552
756
  sessionId: payload.sessionId,
553
757
  taskId: sessionState.session.taskId
554
758
  };
555
- const observation = await compileObservationInIsolation({
759
+ const parsed = await parserIsolationService.compileObservation({
556
760
  capture,
557
761
  workflowHash: sessionState.session.workflowHash,
558
- allowlistedEgress: runtime.parserAllowlistedEgress,
559
- runtime: {
560
- knowledgeBase: runtime.knowledgeBase
762
+ runtime,
763
+ compilerVersion: "v6"
764
+ });
765
+ const mediated = applyV6ObservationMediation(parsed.compiledObservation, parsed.plannerView);
766
+ const artifactRef = buildArtifactRef(capture, parsed.compiledObservation);
767
+ sessionState.latestObservation = parsed.compiledObservation;
768
+ sessionState.latestObservationVerdict = mediated.verdict;
769
+ sessionState.observations.set(parsed.compiledObservation.observationId, parsed.compiledObservation);
770
+ const replayEventId = appendReplayEvent(sessionState, {
771
+ kind: "artifact",
772
+ actor: "sdk",
773
+ payload: {
774
+ route: requestUrl,
775
+ observationId: parsed.compiledObservation.observationId,
776
+ decision: mediated.verdict.decision,
777
+ surfaceType: capture.surfaceType,
778
+ providerId: "providerId" in capture ? capture.providerId ?? null : null,
779
+ extractorIds: "extractionAttestation" in capture && capture.extractionAttestation
780
+ ? [capture.extractionAttestation.extractorId]
781
+ : []
561
782
  }
562
783
  });
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
571
- ? {
572
- ...artifactResult.artifact,
573
- toolActivationPolicy: "block",
574
- approvalRequiredForFollowOn: true
784
+ writeJson(response, 200, {
785
+ compiledObservation: parsed.compiledObservation,
786
+ plannerView: mediated.plannerView,
787
+ artifactRef,
788
+ mismatchSignals: artifactRef.mismatchSignals,
789
+ artifactVerdict: mediated.verdict,
790
+ replayEventId: replayEventId ?? randomUUID()
791
+ });
792
+ return;
793
+ }
794
+ if (requestUrl === "/v6/artifact/extract") {
795
+ const payload = await readJson(request);
796
+ const sessionState = findSessionState(sessions, payload.sessionId);
797
+ if (!sessionState) {
798
+ writeJson(response, 404, { error: "unknown_session" });
799
+ return;
800
+ }
801
+ const result = extractAttachmentGraphV6({
802
+ ...payload.capture,
803
+ sessionId: payload.sessionId,
804
+ taskId: sessionState.session.taskId
805
+ }, runtime);
806
+ const replayEventId = appendReplayEvent(sessionState, {
807
+ kind: "artifact",
808
+ actor: "sdk",
809
+ payload: {
810
+ route: requestUrl,
811
+ rootNodeCount: result.artifactGraph.rootNodeIds.length,
812
+ blockedChildren: result.blockedChildren.length,
813
+ unsupportedChildren: result.unsupportedChildren.length,
814
+ extractorIds: result.extractionAttestations.map((entry) => entry.extractorId),
815
+ decision: result.artifactVerdict.decision
575
816
  }
576
- : artifactResult?.artifact;
577
- const plannerInput = shouldReduceAuthority(sessionState, observation.compiledObservation)
578
- ? applyAuthorityReduction(sessionState, observation.compiledObservation, failClosedArtifact.plannerInput)
579
- : failClosedArtifact.plannerInput;
817
+ });
580
818
  writeJson(response, 200, {
581
- compiledObservation: observation.compiledObservation,
582
- plannerInput,
583
- artifactVerdict: effectiveArtifactVerdict,
584
- artifact: effectiveArtifact
819
+ ...result,
820
+ replayEventId: replayEventId ?? randomUUID()
585
821
  });
586
822
  return;
587
823
  }
588
- if (request.url === "/v4/memory/write") {
824
+ if (requestUrl === "/v6/memory/stage") {
589
825
  const payload = await readJson(request);
590
826
  const sessionState = findSessionState(sessions, payload.sessionId);
591
- const result = evaluateMemoryWriteV4(payload, sessionState?.session, runtime);
827
+ const result = stageMemoryRecordV6(payload, sessionState?.session);
828
+ let promotionAuthority;
592
829
  if (sessionState && result.record) {
593
830
  sessionState.memoryRecords.set(result.record.recordId, result.record);
831
+ sessionState.memorySourceClasses.set(result.record.recordId, payload.sourceClass);
832
+ promotionAuthority = mintMemoryPromotionCapabilityV6(sessionState.session, {
833
+ recordId: result.record.recordId,
834
+ sourceDigest: result.record.sourceDigest,
835
+ sourceObservationId: result.record.sourceObservationId,
836
+ key: result.record.key,
837
+ valueDigest: result.record.sourceDigest ?? hashValue(result.record.value)
838
+ });
839
+ sessionState.authorities.set(promotionAuthority.capabilityId, promotionAuthority);
594
840
  }
595
- writeJson(response, 200, result);
841
+ appendReplayEvent(sessionState, {
842
+ kind: "memory",
843
+ actor: "sdk",
844
+ payload: {
845
+ route: requestUrl,
846
+ recordId: result.record?.recordId ?? null,
847
+ decision: result.verdict.decision
848
+ }
849
+ });
850
+ writeJson(response, 200, {
851
+ ...result,
852
+ promotionAuthority: promotionAuthority
853
+ ? authorityCandidateFromDescriptor(promotionAuthority)
854
+ : undefined,
855
+ promotionTicket: promotionAuthority
856
+ ? {
857
+ ticketId: promotionAuthority.capabilityId,
858
+ ticketDigest: promotionAuthority.capabilityDigest,
859
+ semanticDigest: promotionAuthority.semanticDigest,
860
+ recordId: result.record?.recordId,
861
+ sourceClass: payload.sourceClass,
862
+ expiresAt: promotionAuthority.expiresAt
863
+ }
864
+ : undefined
865
+ });
596
866
  return;
597
867
  }
598
- if (request.url === "/v4/memory/promote") {
868
+ if (requestUrl === "/v6/memory/promote") {
599
869
  const payload = await readJson(request);
600
870
  const sessionState = findSessionState(sessions, payload.sessionId);
601
871
  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);
872
+ const authority = sessionState?.authorities.get(payload.ticketId);
873
+ const approvalEnvelope = sessionState?.approvalEnvelopes.get(payload.approvalId);
874
+ const snapshotState = sessionState && record ? buildMemorySnapshotState(sessionState, record) : undefined;
875
+ const result = promoteMemoryRecordV6(payload, sessionState?.session, record, authority, approvalEnvelope, {
876
+ sourceClass: record
877
+ ? sessionState?.memorySourceClasses.get(record.recordId)
878
+ : undefined,
879
+ priorTrustedRecord: snapshotState?.snapshotRecord
880
+ });
606
881
  if (sessionState && result.promotedRecord) {
607
- if (record && result.promotedRecord.snapshotId) {
882
+ if (snapshotState && result.promotedRecord.snapshotId) {
608
883
  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
884
+ ...snapshotState,
885
+ snapshotId: result.promotedRecord.snapshotId
614
886
  });
615
887
  }
616
888
  sessionState.memoryRecords.set(result.promotedRecord.recordId, result.promotedRecord);
889
+ if (authority) {
890
+ consumeAuthority(sessionState, authority);
891
+ }
892
+ if (approvalEnvelope) {
893
+ sessionState.approvalEnvelopes.set(payload.approvalId, {
894
+ ...approvalEnvelope,
895
+ consumedAt: new Date().toISOString()
896
+ });
897
+ }
898
+ sessionState.session = {
899
+ ...sessionState.session,
900
+ currentStep: sessionState.session.currentStep + 1
901
+ };
617
902
  }
618
- writeJson(response, 200, result);
903
+ appendReplayEvent(sessionState, {
904
+ kind: "memory",
905
+ actor: "sdk",
906
+ payload: {
907
+ route: requestUrl,
908
+ recordId: payload.recordId,
909
+ decision: result.verdict.decision
910
+ }
911
+ });
912
+ writeJson(response, 200, {
913
+ ...result,
914
+ approvalEnvelope: payload.approvalId && sessionState
915
+ ? sessionState.approvalEnvelopes.get(payload.approvalId)
916
+ : approvalEnvelope
917
+ });
619
918
  return;
620
919
  }
621
- if (request.url === "/v4/memory/rollback") {
920
+ if (requestUrl === "/v6/memory/rollback") {
622
921
  const payload = await readJson(request);
623
922
  const sessionState = findSessionState(sessions, payload.sessionId);
624
923
  const record = sessionState?.memoryRecords.get(payload.recordId);
625
- const snapshotRecord = sessionState?.memorySnapshots.get(payload.snapshotId);
626
- const result = rollbackMemoryRecordV4(payload, sessionState?.session, record, snapshotRecord);
627
- if (sessionState && result.restoredRecord) {
628
- sessionState.memoryRecords.set(result.restoredRecord.recordId, result.restoredRecord);
924
+ const snapshotState = sessionState?.memorySnapshots.get(payload.snapshotId);
925
+ const result = rollbackMemoryRecordV6(payload, sessionState?.session, record, {
926
+ snapshotRecord: snapshotState?.snapshotRecord,
927
+ baselineAbsent: snapshotState?.baselineAbsent
928
+ });
929
+ if (sessionState && result.verdict.decision === "ALLOW") {
930
+ if (result.restoredRecord) {
931
+ sessionState.memoryRecords.set(result.restoredRecord.recordId, result.restoredRecord);
932
+ }
933
+ else {
934
+ sessionState.memoryRecords.delete(payload.recordId);
935
+ }
629
936
  }
937
+ appendReplayEvent(sessionState, {
938
+ kind: "memory",
939
+ actor: "sdk",
940
+ payload: {
941
+ route: requestUrl,
942
+ recordId: payload.recordId,
943
+ snapshotId: payload.snapshotId,
944
+ decision: result.verdict.decision
945
+ }
946
+ });
630
947
  writeJson(response, 200, {
631
948
  ...result,
632
949
  rollbackEvent: result.verdict.decision === "ALLOW"
@@ -639,15 +956,35 @@ export async function createSafeBrowseServer(options = {}) {
639
956
  });
640
957
  return;
641
958
  }
959
+ if (requestUrl === "/v6/replay/bundle") {
960
+ const payload = await readJson(request);
961
+ const sessionState = findSessionState(sessions, payload.sessionId);
962
+ if (!sessionState) {
963
+ writeJson(response, 404, { error: "unknown_session" });
964
+ return;
965
+ }
966
+ writeJson(response, 200, buildReplayBundle(sessionState.replayEvents, runtime));
967
+ return;
968
+ }
642
969
  writeJson(response, 404, { error: "not_found" });
643
970
  }
644
971
  catch (error) {
972
+ logServerError(error);
645
973
  writeJson(response, 500, {
646
- error: "server_error",
647
- message: error instanceof Error ? error.message : String(error)
974
+ error: "server_error"
648
975
  });
649
976
  }
650
977
  });
978
+ const originalClose = server.close.bind(server);
979
+ server.close = ((callback) => {
980
+ clearInterval(parserHealthRefreshTimer);
981
+ return originalClose((error) => {
982
+ void Promise.allSettled([parserIsolationService.close(), modelGuardClient.close()]).finally(() => {
983
+ callback?.(error);
984
+ });
985
+ });
986
+ });
987
+ return server;
651
988
  }
652
989
  export async function startSafeBrowseDaemon(options = {}) {
653
990
  const server = await createSafeBrowseServer(options);