@joshuaswarren/openclaw-engram 9.1.28 → 9.1.29

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/index.js CHANGED
@@ -7549,7 +7549,8 @@ var GraphDashboardServer = class {
7549
7549
 
7550
7550
  // src/access-http.ts
7551
7551
  import { createServer as createServer3 } from "http";
7552
- import { timingSafeEqual as timingSafeEqual2 } from "crypto";
7552
+ import { randomUUID as randomUUID2, timingSafeEqual as timingSafeEqual2 } from "crypto";
7553
+ import { AsyncLocalStorage } from "async_hooks";
7553
7554
  import { existsSync } from "fs";
7554
7555
  import { readFile as readFile15 } from "fs/promises";
7555
7556
  import path22 from "path";
@@ -8041,6 +8042,151 @@ ${body}`;
8041
8042
  }
8042
8043
  };
8043
8044
 
8045
+ // src/access-schema.ts
8046
+ import { z as z2 } from "zod";
8047
+ function formatZodError(error) {
8048
+ return {
8049
+ error: "request validation failed",
8050
+ code: "validation_error",
8051
+ details: error.issues.map((issue) => ({
8052
+ field: issue.path.join(".") || "(root)",
8053
+ message: issue.message
8054
+ }))
8055
+ };
8056
+ }
8057
+ var namespaceSchema = z2.string().trim().max(256).optional();
8058
+ var sessionKeySchema = z2.string().trim().min(1).max(512).optional();
8059
+ var idempotencyKeySchema = z2.string().trim().min(1).max(256).optional();
8060
+ var dryRunSchema = z2.boolean().optional();
8061
+ var schemaVersionSchema = z2.number().int().optional();
8062
+ var recallRequestSchema = z2.object({
8063
+ query: z2.string().min(1, "query is required"),
8064
+ sessionKey: sessionKeySchema,
8065
+ namespace: namespaceSchema,
8066
+ topK: z2.number().int().min(0).max(200).optional(),
8067
+ mode: z2.enum(["auto", "no_recall", "minimal", "full", "graph_mode"]).optional(),
8068
+ includeDebug: z2.boolean().optional()
8069
+ });
8070
+ var recallExplainRequestSchema = z2.object({
8071
+ sessionKey: sessionKeySchema,
8072
+ namespace: namespaceSchema
8073
+ });
8074
+ var messageSchema = z2.object({
8075
+ role: z2.enum(["user", "assistant"]),
8076
+ content: z2.string().min(1, "message content must be non-empty")
8077
+ });
8078
+ var observeRequestSchema = z2.object({
8079
+ sessionKey: z2.string().trim().min(1, "sessionKey is required").max(512),
8080
+ messages: z2.array(messageSchema).min(1, "messages must be a non-empty array"),
8081
+ namespace: namespaceSchema,
8082
+ skipExtraction: z2.boolean().optional()
8083
+ });
8084
+ var writeContentSchema = z2.string().min(1, "content is required").max(5e4);
8085
+ var categorySchema = z2.enum([
8086
+ "fact",
8087
+ "preference",
8088
+ "correction",
8089
+ "entity",
8090
+ "decision",
8091
+ "relationship",
8092
+ "principle",
8093
+ "commitment",
8094
+ "moment",
8095
+ "skill",
8096
+ "rule"
8097
+ ]).optional();
8098
+ var confidenceSchema = z2.number().min(0).max(1).optional();
8099
+ var tagsSchema = z2.array(z2.string().max(256)).max(50).optional();
8100
+ var entityRefSchema = z2.string().trim().max(512).optional();
8101
+ var ttlSchema = z2.string().trim().max(128).optional();
8102
+ var sourceReasonSchema = z2.string().trim().max(2e3).optional();
8103
+ var memoryStoreRequestSchema = z2.object({
8104
+ schemaVersion: schemaVersionSchema,
8105
+ idempotencyKey: idempotencyKeySchema,
8106
+ dryRun: dryRunSchema,
8107
+ sessionKey: sessionKeySchema,
8108
+ content: writeContentSchema,
8109
+ category: categorySchema,
8110
+ confidence: confidenceSchema,
8111
+ namespace: namespaceSchema,
8112
+ tags: tagsSchema,
8113
+ entityRef: entityRefSchema,
8114
+ ttl: ttlSchema,
8115
+ sourceReason: sourceReasonSchema
8116
+ });
8117
+ var suggestionSubmitRequestSchema = memoryStoreRequestSchema;
8118
+ var reviewDispositionRequestSchema = z2.object({
8119
+ memoryId: z2.string().trim().min(1, "memoryId is required"),
8120
+ status: z2.enum([
8121
+ "active",
8122
+ "pending_review",
8123
+ "quarantined",
8124
+ "rejected",
8125
+ "superseded",
8126
+ "archived"
8127
+ ]),
8128
+ reasonCode: z2.string().trim().min(1, "reasonCode is required"),
8129
+ namespace: namespaceSchema
8130
+ });
8131
+ var trustZonePromoteRequestSchema = z2.object({
8132
+ recordId: z2.string().trim().min(1, "recordId is required"),
8133
+ targetZone: z2.enum(["working", "trusted"], {
8134
+ errorMap: () => ({ message: "targetZone must be 'working' or 'trusted'" })
8135
+ }),
8136
+ promotionReason: z2.string().trim().min(1, "promotionReason is required"),
8137
+ recordedAt: z2.string().trim().optional(),
8138
+ summary: z2.string().trim().max(5e3).optional(),
8139
+ dryRun: dryRunSchema,
8140
+ namespace: namespaceSchema
8141
+ });
8142
+ var trustZoneDemoSeedRequestSchema = z2.object({
8143
+ scenario: z2.string().trim().max(256).optional(),
8144
+ recordedAt: z2.string().trim().optional(),
8145
+ dryRun: dryRunSchema,
8146
+ namespace: namespaceSchema
8147
+ });
8148
+ var lcmSearchRequestSchema = z2.object({
8149
+ query: z2.string().min(1, "query is required"),
8150
+ sessionKey: sessionKeySchema,
8151
+ namespace: namespaceSchema,
8152
+ limit: z2.number().int().min(1).max(100).optional()
8153
+ });
8154
+ var daySummaryRequestSchema = z2.object({
8155
+ memories: z2.string().max(1e5).optional(),
8156
+ sessionKey: sessionKeySchema,
8157
+ namespace: namespaceSchema
8158
+ });
8159
+ var schemas = {
8160
+ recall: recallRequestSchema,
8161
+ recallExplain: recallExplainRequestSchema,
8162
+ observe: observeRequestSchema,
8163
+ memoryStore: memoryStoreRequestSchema,
8164
+ suggestionSubmit: suggestionSubmitRequestSchema,
8165
+ reviewDisposition: reviewDispositionRequestSchema,
8166
+ trustZonePromote: trustZonePromoteRequestSchema,
8167
+ trustZoneDemoSeed: trustZoneDemoSeedRequestSchema,
8168
+ lcmSearch: lcmSearchRequestSchema,
8169
+ daySummary: daySummaryRequestSchema
8170
+ };
8171
+ function validateRequest(schemaName, body) {
8172
+ const schema = schemas[schemaName];
8173
+ if (!schema) {
8174
+ return {
8175
+ success: false,
8176
+ error: {
8177
+ error: `unknown schema: ${schemaName}`,
8178
+ code: "validation_error",
8179
+ details: []
8180
+ }
8181
+ };
8182
+ }
8183
+ const result = schema.safeParse(body);
8184
+ if (result.success) {
8185
+ return { success: true, data: result.data };
8186
+ }
8187
+ return { success: false, error: formatZodError(result.error) };
8188
+ }
8189
+
8044
8190
  // src/access-http.ts
8045
8191
  function resolveDefaultAdminConsolePublicDir() {
8046
8192
  const candidates = [
@@ -8050,15 +8196,20 @@ function resolveDefaultAdminConsolePublicDir() {
8050
8196
  return candidates.find((candidate) => existsSync(candidate)) ?? candidates[0];
8051
8197
  }
8052
8198
  var defaultAdminConsolePublicDir = resolveDefaultAdminConsolePublicDir();
8199
+ var correlationIdStore = new AsyncLocalStorage();
8053
8200
  var WRITE_RATE_LIMIT_WINDOW_MS = 6e4;
8054
8201
  var WRITE_RATE_LIMIT_MAX_REQUESTS = 30;
8055
8202
  var TRUST_ZONE_RECORD_KINDS = ["memory", "artifact", "state", "trajectory", "external"];
8056
8203
  var TRUST_ZONE_SOURCE_CLASSES = ["tool_output", "web_content", "subagent_trace", "system_memory", "user_input", "manual"];
8057
8204
  var HttpError = class extends Error {
8058
- constructor(status, message) {
8205
+ constructor(status, message, code, details) {
8059
8206
  super(message);
8060
8207
  this.status = status;
8208
+ this.code = code ?? `http_${status}`;
8209
+ this.details = details;
8061
8210
  }
8211
+ code;
8212
+ details;
8062
8213
  };
8063
8214
  function hostToUrlAuthority2(host) {
8064
8215
  if (host.includes(":") && !host.startsWith("[") && !host.endsWith("]")) {
@@ -8071,21 +8222,21 @@ function parseTrustZoneKindFilter(raw) {
8071
8222
  if (TRUST_ZONE_RECORD_KINDS.includes(raw)) {
8072
8223
  return raw;
8073
8224
  }
8074
- throw new HttpError(400, `kind must be one of ${TRUST_ZONE_RECORD_KINDS.join("|")}`);
8225
+ throw new HttpError(400, `kind must be one of ${TRUST_ZONE_RECORD_KINDS.join("|")}`, "invalid_kind_filter");
8075
8226
  }
8076
8227
  function parseTrustZoneSourceClassFilter(raw) {
8077
8228
  if (raw === null) return void 0;
8078
8229
  if (TRUST_ZONE_SOURCE_CLASSES.includes(raw)) {
8079
8230
  return raw;
8080
8231
  }
8081
- throw new HttpError(400, `sourceClass must be one of ${TRUST_ZONE_SOURCE_CLASSES.join("|")}`);
8232
+ throw new HttpError(400, `sourceClass must be one of ${TRUST_ZONE_SOURCE_CLASSES.join("|")}`, "invalid_source_class_filter");
8082
8233
  }
8083
8234
  function parseTrustZoneFilter(raw) {
8084
8235
  if (raw === null) return void 0;
8085
8236
  if (isTrustZoneName(raw)) {
8086
8237
  return raw;
8087
8238
  }
8088
- throw new HttpError(400, "zone must be one of quarantine|working|trusted");
8239
+ throw new HttpError(400, "zone must be one of quarantine|working|trusted", "invalid_zone_filter");
8089
8240
  }
8090
8241
  var EngramAccessHttpServer = class {
8091
8242
  service;
@@ -8119,21 +8270,26 @@ var EngramAccessHttpServer = class {
8119
8270
  }
8120
8271
  if (this.server) return this.status();
8121
8272
  const server = createServer3((req, res) => {
8122
- void this.handle(req, res).catch((err) => {
8123
- log.debug(`engram access HTTP request failed: ${err}`);
8124
- if (err instanceof HttpError) {
8125
- this.respondJson(res, err.status, { error: err.message });
8126
- return;
8127
- }
8128
- if (err instanceof EngramAccessInputError) {
8129
- this.respondJson(res, 400, { error: err.message });
8130
- return;
8131
- }
8132
- if (res.headersSent) {
8133
- res.destroy(err);
8134
- return;
8135
- }
8136
- this.respondJson(res, 500, { error: "internal_error" });
8273
+ const correlationId = randomUUID2();
8274
+ correlationIdStore.run(correlationId, () => {
8275
+ void this.handle(req, res, correlationId).catch((err) => {
8276
+ log.debug(`engram access HTTP request failed [${correlationId}]: ${err}`);
8277
+ if (err instanceof HttpError) {
8278
+ const payload = { error: err.message, code: err.code };
8279
+ if (err.details) payload.details = err.details;
8280
+ this.respondJson(res, err.status, payload);
8281
+ return;
8282
+ }
8283
+ if (err instanceof EngramAccessInputError) {
8284
+ this.respondJson(res, 400, { error: err.message, code: "input_error" });
8285
+ return;
8286
+ }
8287
+ if (res.headersSent) {
8288
+ res.destroy(err);
8289
+ return;
8290
+ }
8291
+ this.respondJson(res, 500, { error: "internal_error", code: "internal_error" });
8292
+ });
8137
8293
  });
8138
8294
  });
8139
8295
  try {
@@ -8189,18 +8345,20 @@ var EngramAccessHttpServer = class {
8189
8345
  }
8190
8346
  return this.authenticatedPrincipal;
8191
8347
  }
8192
- async handle(req, res) {
8348
+ async handle(req, res, correlationId) {
8193
8349
  const parsed = new URL3(req.url ?? "/", `http://${hostToUrlAuthority2(this.host)}`);
8194
8350
  const pathname = parsed.pathname;
8195
8351
  if (this.adminConsoleEnabled && await this.handleAdminConsole(req, res, pathname)) {
8196
8352
  return;
8197
8353
  }
8198
8354
  if (!this.isAuthorized(req)) {
8355
+ const body = JSON.stringify({ error: "unauthorized", code: "unauthorized" });
8199
8356
  res.writeHead(401, {
8200
8357
  "content-type": "application/json; charset=utf-8",
8201
- "www-authenticate": "Bearer"
8358
+ "www-authenticate": "Bearer",
8359
+ "x-request-id": correlationId
8202
8360
  });
8203
- res.end(JSON.stringify({ error: "unauthorized" }));
8361
+ res.end(body);
8204
8362
  return;
8205
8363
  }
8206
8364
  if (req.method === "POST" && pathname === "/mcp") {
@@ -8212,34 +8370,34 @@ var EngramAccessHttpServer = class {
8212
8370
  return;
8213
8371
  }
8214
8372
  if (req.method === "POST" && pathname === "/engram/v1/recall") {
8215
- const body = await this.readJsonBody(req);
8373
+ const body = await this.readValidatedBody(req, "recall");
8216
8374
  const response = await this.service.recall({
8217
- query: typeof body.query === "string" ? body.query : "",
8218
- sessionKey: typeof body.sessionKey === "string" ? body.sessionKey : void 0,
8219
- namespace: typeof body.namespace === "string" ? body.namespace : void 0,
8220
- topK: typeof body.topK === "number" ? body.topK : void 0,
8221
- mode: typeof body.mode === "string" ? body.mode : void 0,
8375
+ query: body.query ?? "",
8376
+ sessionKey: body.sessionKey,
8377
+ namespace: body.namespace,
8378
+ topK: body.topK,
8379
+ mode: body.mode,
8222
8380
  includeDebug: body.includeDebug === true
8223
8381
  });
8224
8382
  this.respondJson(res, 200, response);
8225
8383
  return;
8226
8384
  }
8227
8385
  if (req.method === "POST" && pathname === "/engram/v1/recall/explain") {
8228
- const body = await this.readJsonBody(req);
8386
+ const body = await this.readValidatedBody(req, "recallExplain");
8229
8387
  const response = await this.service.recallExplain({
8230
- sessionKey: typeof body.sessionKey === "string" ? body.sessionKey : void 0,
8231
- namespace: typeof body.namespace === "string" ? body.namespace : void 0
8388
+ sessionKey: body.sessionKey,
8389
+ namespace: body.namespace
8232
8390
  });
8233
8391
  this.respondJson(res, 200, response);
8234
8392
  return;
8235
8393
  }
8236
8394
  if (req.method === "POST" && pathname === "/engram/v1/observe") {
8237
- const body = await this.readJsonBody(req);
8395
+ const body = await this.readValidatedBody(req, "observe");
8238
8396
  this.ensureWriteRateLimitAvailable();
8239
8397
  const response = await this.service.observe({
8240
- sessionKey: typeof body.sessionKey === "string" ? body.sessionKey : "",
8241
- messages: Array.isArray(body.messages) ? body.messages : [],
8242
- namespace: typeof body.namespace === "string" ? body.namespace : void 0,
8398
+ sessionKey: body.sessionKey,
8399
+ messages: body.messages,
8400
+ namespace: body.namespace,
8243
8401
  authenticatedPrincipal: this.resolveRequestPrincipal(req),
8244
8402
  skipExtraction: body.skipExtraction === true
8245
8403
  });
@@ -8248,13 +8406,13 @@ var EngramAccessHttpServer = class {
8248
8406
  return;
8249
8407
  }
8250
8408
  if (req.method === "POST" && pathname === "/engram/v1/lcm/search") {
8251
- const body = await this.readJsonBody(req);
8409
+ const body = await this.readValidatedBody(req, "lcmSearch");
8252
8410
  const response = await this.service.lcmSearch({
8253
- query: typeof body.query === "string" ? body.query : "",
8254
- sessionKey: typeof body.sessionKey === "string" ? body.sessionKey : void 0,
8255
- namespace: typeof body.namespace === "string" ? body.namespace : void 0,
8411
+ query: body.query,
8412
+ sessionKey: body.sessionKey,
8413
+ namespace: body.namespace,
8256
8414
  authenticatedPrincipal: this.resolveRequestPrincipal(req),
8257
- limit: typeof body.limit === "number" ? body.limit : void 0
8415
+ limit: body.limit
8258
8416
  });
8259
8417
  this.respondJson(res, 200, response);
8260
8418
  return;
@@ -8264,21 +8422,21 @@ var EngramAccessHttpServer = class {
8264
8422
  return;
8265
8423
  }
8266
8424
  if (req.method === "POST" && pathname === "/engram/v1/memories") {
8267
- const body = await this.readJsonBody(req);
8425
+ const body = await this.readValidatedBody(req, "memoryStore");
8268
8426
  const request = {
8269
- schemaVersion: typeof body.schemaVersion === "number" ? body.schemaVersion : void 0,
8270
- idempotencyKey: typeof body.idempotencyKey === "string" ? body.idempotencyKey : void 0,
8427
+ schemaVersion: body.schemaVersion,
8428
+ idempotencyKey: body.idempotencyKey,
8271
8429
  dryRun: body.dryRun === true,
8272
- sessionKey: typeof body.sessionKey === "string" ? body.sessionKey : void 0,
8430
+ sessionKey: body.sessionKey,
8273
8431
  authenticatedPrincipal: this.resolveRequestPrincipal(req),
8274
- content: typeof body.content === "string" ? body.content : "",
8275
- category: typeof body.category === "string" ? body.category : void 0,
8276
- confidence: typeof body.confidence === "number" ? body.confidence : void 0,
8277
- namespace: typeof body.namespace === "string" ? body.namespace : void 0,
8278
- tags: Array.isArray(body.tags) ? body.tags.filter((tag) => typeof tag === "string") : void 0,
8279
- entityRef: typeof body.entityRef === "string" ? body.entityRef : void 0,
8280
- ttl: typeof body.ttl === "string" ? body.ttl : void 0,
8281
- sourceReason: typeof body.sourceReason === "string" ? body.sourceReason : void 0
8432
+ content: body.content,
8433
+ category: body.category,
8434
+ confidence: body.confidence,
8435
+ namespace: body.namespace,
8436
+ tags: body.tags,
8437
+ entityRef: body.entityRef,
8438
+ ttl: body.ttl,
8439
+ sourceReason: body.sourceReason
8282
8440
  };
8283
8441
  const idempotencyStatus = await this.service.peekMemoryStoreIdempotency(request);
8284
8442
  if (idempotencyStatus === "miss" && request.dryRun !== true) {
@@ -8292,21 +8450,21 @@ var EngramAccessHttpServer = class {
8292
8450
  return;
8293
8451
  }
8294
8452
  if (req.method === "POST" && pathname === "/engram/v1/suggestions") {
8295
- const body = await this.readJsonBody(req);
8453
+ const body = await this.readValidatedBody(req, "suggestionSubmit");
8296
8454
  const request = {
8297
- schemaVersion: typeof body.schemaVersion === "number" ? body.schemaVersion : void 0,
8298
- idempotencyKey: typeof body.idempotencyKey === "string" ? body.idempotencyKey : void 0,
8455
+ schemaVersion: body.schemaVersion,
8456
+ idempotencyKey: body.idempotencyKey,
8299
8457
  dryRun: body.dryRun === true,
8300
- sessionKey: typeof body.sessionKey === "string" ? body.sessionKey : void 0,
8458
+ sessionKey: body.sessionKey,
8301
8459
  authenticatedPrincipal: this.resolveRequestPrincipal(req),
8302
- content: typeof body.content === "string" ? body.content : "",
8303
- category: typeof body.category === "string" ? body.category : void 0,
8304
- confidence: typeof body.confidence === "number" ? body.confidence : void 0,
8305
- namespace: typeof body.namespace === "string" ? body.namespace : void 0,
8306
- tags: Array.isArray(body.tags) ? body.tags.filter((tag) => typeof tag === "string") : void 0,
8307
- entityRef: typeof body.entityRef === "string" ? body.entityRef : void 0,
8308
- ttl: typeof body.ttl === "string" ? body.ttl : void 0,
8309
- sourceReason: typeof body.sourceReason === "string" ? body.sourceReason : void 0
8460
+ content: body.content,
8461
+ category: body.category,
8462
+ confidence: body.confidence,
8463
+ namespace: body.namespace,
8464
+ tags: body.tags,
8465
+ entityRef: body.entityRef,
8466
+ ttl: body.ttl,
8467
+ sourceReason: body.sourceReason
8310
8468
  };
8311
8469
  const idempotencyStatus = await this.service.peekSuggestionSubmitIdempotency(request);
8312
8470
  if (idempotencyStatus === "miss" && request.dryRun !== true) {
@@ -8415,17 +8573,13 @@ var EngramAccessHttpServer = class {
8415
8573
  return;
8416
8574
  }
8417
8575
  if (req.method === "POST" && pathname === "/engram/v1/review-disposition") {
8418
- const body = await this.readJsonBody(req);
8419
- const status = typeof body.status === "string" ? body.status : "";
8420
- if (status !== "active" && status !== "pending_review" && status !== "quarantined" && status !== "rejected" && status !== "superseded" && status !== "archived") {
8421
- throw new HttpError(400, "invalid_review_status");
8422
- }
8576
+ const body = await this.readValidatedBody(req, "reviewDisposition");
8423
8577
  this.ensureWriteRateLimitAvailable();
8424
8578
  const response = await this.service.reviewDisposition({
8425
- memoryId: typeof body.memoryId === "string" ? body.memoryId : "",
8426
- status,
8427
- reasonCode: typeof body.reasonCode === "string" ? body.reasonCode : "",
8428
- namespace: typeof body.namespace === "string" ? body.namespace : void 0,
8579
+ memoryId: body.memoryId,
8580
+ status: body.status,
8581
+ reasonCode: body.reasonCode,
8582
+ namespace: body.namespace,
8429
8583
  authenticatedPrincipal: this.resolveRequestPrincipal(req)
8430
8584
  });
8431
8585
  if (this.shouldCountWriteRateLimit(response)) {
@@ -8435,31 +8589,19 @@ var EngramAccessHttpServer = class {
8435
8589
  return;
8436
8590
  }
8437
8591
  if (req.method === "POST" && pathname === "/engram/v1/trust-zones/promote") {
8438
- const body = await this.readJsonBody(req);
8439
- const recordId = typeof body.recordId === "string" ? body.recordId.trim() : "";
8440
- const targetZone = typeof body.targetZone === "string" ? body.targetZone.trim() : "";
8441
- const promotionReason = typeof body.promotionReason === "string" ? body.promotionReason.trim() : "";
8592
+ const body = await this.readValidatedBody(req, "trustZonePromote");
8442
8593
  const dryRun = body.dryRun === true;
8443
- if (!recordId) {
8444
- throw new HttpError(400, "recordId is required");
8445
- }
8446
- if (!isTrustZoneName(targetZone)) {
8447
- throw new HttpError(400, "invalid_trust_zone_target");
8448
- }
8449
- if (!promotionReason) {
8450
- throw new HttpError(400, "promotionReason is required");
8451
- }
8452
8594
  if (!dryRun) {
8453
8595
  this.ensureWriteRateLimitAvailable();
8454
8596
  }
8455
8597
  const response = await this.service.trustZonePromote({
8456
- recordId,
8457
- targetZone,
8458
- promotionReason,
8459
- recordedAt: typeof body.recordedAt === "string" ? body.recordedAt : void 0,
8460
- summary: typeof body.summary === "string" ? body.summary : void 0,
8598
+ recordId: body.recordId,
8599
+ targetZone: body.targetZone,
8600
+ promotionReason: body.promotionReason,
8601
+ recordedAt: body.recordedAt,
8602
+ summary: body.summary,
8461
8603
  dryRun,
8462
- namespace: typeof body.namespace === "string" ? body.namespace : void 0,
8604
+ namespace: body.namespace,
8463
8605
  authenticatedPrincipal: this.resolveRequestPrincipal(req)
8464
8606
  });
8465
8607
  if (this.shouldCountWriteRateLimit(response)) {
@@ -8469,16 +8611,16 @@ var EngramAccessHttpServer = class {
8469
8611
  return;
8470
8612
  }
8471
8613
  if (req.method === "POST" && pathname === "/engram/v1/trust-zones/demo-seed") {
8472
- const body = await this.readJsonBody(req);
8614
+ const body = await this.readValidatedBody(req, "trustZoneDemoSeed");
8473
8615
  const dryRun = body.dryRun === true;
8474
8616
  if (!dryRun) {
8475
8617
  this.ensureWriteRateLimitAvailable();
8476
8618
  }
8477
8619
  const response = await this.service.trustZoneDemoSeed({
8478
- scenario: typeof body.scenario === "string" ? body.scenario : void 0,
8479
- recordedAt: typeof body.recordedAt === "string" ? body.recordedAt : void 0,
8620
+ scenario: body.scenario,
8621
+ recordedAt: body.recordedAt,
8480
8622
  dryRun,
8481
- namespace: typeof body.namespace === "string" ? body.namespace : void 0,
8623
+ namespace: body.namespace,
8482
8624
  authenticatedPrincipal: this.resolveRequestPrincipal(req)
8483
8625
  });
8484
8626
  if (this.shouldCountWriteRateLimit(response)) {
@@ -8487,7 +8629,7 @@ var EngramAccessHttpServer = class {
8487
8629
  this.respondJson(res, response.dryRun ? 200 : 201, response);
8488
8630
  return;
8489
8631
  }
8490
- this.respondJson(res, 404, { error: "not_found" });
8632
+ this.respondJson(res, 404, { error: "not_found", code: "not_found" });
8491
8633
  }
8492
8634
  async handleMcpRequest(req, res) {
8493
8635
  const body = await this.readJsonBody(req);
@@ -8519,6 +8661,10 @@ var EngramAccessHttpServer = class {
8519
8661
  res.statusCode = status;
8520
8662
  res.setHeader("content-type", "application/json; charset=utf-8");
8521
8663
  res.setHeader("content-length", String(Buffer.byteLength(body)));
8664
+ const cid = correlationIdStore.getStore();
8665
+ if (cid) {
8666
+ res.setHeader("x-request-id", cid);
8667
+ }
8522
8668
  res.end(body);
8523
8669
  }
8524
8670
  async handleAdminConsole(req, res, pathname) {
@@ -8551,7 +8697,7 @@ var EngramAccessHttpServer = class {
8551
8697
  const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
8552
8698
  total += buffer.length;
8553
8699
  if (total > this.maxBodyBytes) {
8554
- throw new HttpError(413, "request_body_too_large");
8700
+ throw new HttpError(413, "request_body_too_large", "request_body_too_large");
8555
8701
  }
8556
8702
  chunks.push(buffer);
8557
8703
  }
@@ -8562,13 +8708,21 @@ var EngramAccessHttpServer = class {
8562
8708
  try {
8563
8709
  parsed = JSON.parse(raw);
8564
8710
  } catch {
8565
- throw new HttpError(400, "invalid_json");
8711
+ throw new HttpError(400, "invalid_json", "invalid_json");
8566
8712
  }
8567
8713
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
8568
- throw new HttpError(400, "invalid_json_object");
8714
+ throw new HttpError(400, "invalid_json_object", "invalid_json_object");
8569
8715
  }
8570
8716
  return parsed;
8571
8717
  }
8718
+ async readValidatedBody(req, schemaName) {
8719
+ const raw = await this.readJsonBody(req);
8720
+ const result = validateRequest(schemaName, raw);
8721
+ if (!result.success) {
8722
+ throw new HttpError(400, result.error.error, "validation_error", result.error.details);
8723
+ }
8724
+ return result.data;
8725
+ }
8572
8726
  isAuthorized(req) {
8573
8727
  if (!this.authToken) return false;
8574
8728
  const raw = req.headers.authorization;
@@ -8605,7 +8759,7 @@ var EngramAccessHttpServer = class {
8605
8759
  this.writeRequestTimestamps.shift();
8606
8760
  }
8607
8761
  if (this.writeRequestTimestamps.length >= WRITE_RATE_LIMIT_MAX_REQUESTS) {
8608
- throw new HttpError(429, "write_rate_limited");
8762
+ throw new HttpError(429, "write_rate_limited", "write_rate_limited");
8609
8763
  }
8610
8764
  }
8611
8765
  recordWriteRateLimitHit() {