@remnic/core 1.1.17 → 1.1.18

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.
Files changed (96) hide show
  1. package/dist/access-cli.js +16 -16
  2. package/dist/access-http.d.ts +2 -1
  3. package/dist/access-http.js +10 -10
  4. package/dist/access-mcp.d.ts +1 -1
  5. package/dist/access-mcp.js +9 -9
  6. package/dist/access-schema.d.ts +23 -3
  7. package/dist/access-schema.js +6 -1
  8. package/dist/{access-service-DZXc7qwR.d.ts → access-service-DT9L2DW4.d.ts} +14 -2
  9. package/dist/access-service.d.ts +1 -1
  10. package/dist/access-service.js +7 -7
  11. package/dist/briefing.js +4 -4
  12. package/dist/causal-consolidation.js +5 -5
  13. package/dist/{chunk-NOHC2L57.js → chunk-2IRT26RZ.js} +2 -2
  14. package/dist/{chunk-CDQNR7SV.js → chunk-2XX25T6U.js} +15 -15
  15. package/dist/{chunk-66H2DZYB.js → chunk-4J3BTQRB.js} +13 -1
  16. package/dist/chunk-4J3BTQRB.js.map +1 -0
  17. package/dist/{chunk-MS3ULOZF.js → chunk-5IQC4OG6.js} +2 -2
  18. package/dist/{chunk-ZPXYWTN5.js → chunk-5ML4TH3E.js} +4 -4
  19. package/dist/{chunk-V7WH7DEM.js → chunk-6ORWKANA.js} +2 -2
  20. package/dist/{chunk-IOAY54RF.js → chunk-7Q2P774N.js} +11 -11
  21. package/dist/{chunk-JFEH2LZM.js → chunk-CN4P6SVA.js} +2 -2
  22. package/dist/{chunk-OGROP7ZN.js → chunk-FFU4GMST.js} +5 -5
  23. package/dist/{chunk-C7DGCHJE.js → chunk-FSODDMR2.js} +2 -2
  24. package/dist/{chunk-AAX3SUM3.js → chunk-GGCJ253V.js} +11 -11
  25. package/dist/{chunk-HMZYQPT5.js → chunk-J4VRI4BH.js} +97 -4
  26. package/dist/chunk-J4VRI4BH.js.map +1 -0
  27. package/dist/{chunk-IG5VGHYB.js → chunk-KSFBM6TV.js} +2 -2
  28. package/dist/{chunk-M3AA636B.js → chunk-NOQ74SJN.js} +2 -2
  29. package/dist/{chunk-GCR4JFKK.js → chunk-P7FRFY6S.js} +38 -4
  30. package/dist/chunk-P7FRFY6S.js.map +1 -0
  31. package/dist/{chunk-QLKBF3TI.js → chunk-QOHBYVZG.js} +2 -2
  32. package/dist/{chunk-OJRKZLZ4.js → chunk-SGIXDVSF.js} +2 -2
  33. package/dist/{chunk-BYACCC5C.js → chunk-SPEVF47M.js} +4 -4
  34. package/dist/{chunk-YCVWX2NF.js → chunk-SZKCBLS5.js} +2 -2
  35. package/dist/{chunk-BEB4GUU5.js → chunk-TLM762GT.js} +2 -2
  36. package/dist/{chunk-65HQPW6O.js → chunk-TOFUTKQN.js} +2 -2
  37. package/dist/{chunk-VLQWOGYM.js → chunk-TUQXQOGR.js} +34 -9
  38. package/dist/chunk-TUQXQOGR.js.map +1 -0
  39. package/dist/{chunk-G7JBLD65.js → chunk-YO3AZEE5.js} +3 -3
  40. package/dist/{cli-kVwab1_L.d.ts → cli-BN0CkYzI.d.ts} +1 -1
  41. package/dist/cli.d.ts +2 -2
  42. package/dist/cli.js +19 -19
  43. package/dist/compounding/engine.js +4 -4
  44. package/dist/connectors/codex-materialize-runner.js +4 -4
  45. package/dist/connectors/index.js +4 -4
  46. package/dist/entity-retrieval.js +4 -4
  47. package/dist/index.d.ts +4 -4
  48. package/dist/index.js +50 -46
  49. package/dist/index.js.map +1 -1
  50. package/dist/maintenance/memory-governance.js +4 -4
  51. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +4 -4
  52. package/dist/maintenance/rebuild-memory-projection.js +5 -5
  53. package/dist/mcp-memory-inspector-app.d.ts +1 -1
  54. package/dist/namespaces/migrate.js +5 -5
  55. package/dist/namespaces/storage.js +4 -4
  56. package/dist/offline-sync.d.ts +16 -1
  57. package/dist/offline-sync.js +8 -2
  58. package/dist/operator-toolkit.js +7 -7
  59. package/dist/orchestrator.js +12 -12
  60. package/dist/secure-store/index.js +17 -17
  61. package/dist/semantic-consolidation.js +5 -5
  62. package/dist/semantic-rule-promotion.js +4 -4
  63. package/dist/semantic-rule-verifier.js +4 -4
  64. package/dist/storage.js +3 -3
  65. package/dist/verified-recall.js +4 -4
  66. package/package.json +1 -1
  67. package/src/access-http.test.ts +90 -0
  68. package/src/access-http.ts +47 -0
  69. package/src/access-schema.ts +13 -0
  70. package/src/access-service.ts +48 -0
  71. package/src/index.ts +3 -0
  72. package/src/offline-sync.test.ts +32 -0
  73. package/src/offline-sync.ts +114 -0
  74. package/dist/chunk-66H2DZYB.js.map +0 -1
  75. package/dist/chunk-GCR4JFKK.js.map +0 -1
  76. package/dist/chunk-HMZYQPT5.js.map +0 -1
  77. package/dist/chunk-VLQWOGYM.js.map +0 -1
  78. /package/dist/{chunk-NOHC2L57.js.map → chunk-2IRT26RZ.js.map} +0 -0
  79. /package/dist/{chunk-CDQNR7SV.js.map → chunk-2XX25T6U.js.map} +0 -0
  80. /package/dist/{chunk-MS3ULOZF.js.map → chunk-5IQC4OG6.js.map} +0 -0
  81. /package/dist/{chunk-ZPXYWTN5.js.map → chunk-5ML4TH3E.js.map} +0 -0
  82. /package/dist/{chunk-V7WH7DEM.js.map → chunk-6ORWKANA.js.map} +0 -0
  83. /package/dist/{chunk-IOAY54RF.js.map → chunk-7Q2P774N.js.map} +0 -0
  84. /package/dist/{chunk-JFEH2LZM.js.map → chunk-CN4P6SVA.js.map} +0 -0
  85. /package/dist/{chunk-OGROP7ZN.js.map → chunk-FFU4GMST.js.map} +0 -0
  86. /package/dist/{chunk-C7DGCHJE.js.map → chunk-FSODDMR2.js.map} +0 -0
  87. /package/dist/{chunk-AAX3SUM3.js.map → chunk-GGCJ253V.js.map} +0 -0
  88. /package/dist/{chunk-IG5VGHYB.js.map → chunk-KSFBM6TV.js.map} +0 -0
  89. /package/dist/{chunk-M3AA636B.js.map → chunk-NOQ74SJN.js.map} +0 -0
  90. /package/dist/{chunk-QLKBF3TI.js.map → chunk-QOHBYVZG.js.map} +0 -0
  91. /package/dist/{chunk-OJRKZLZ4.js.map → chunk-SGIXDVSF.js.map} +0 -0
  92. /package/dist/{chunk-BYACCC5C.js.map → chunk-SPEVF47M.js.map} +0 -0
  93. /package/dist/{chunk-YCVWX2NF.js.map → chunk-SZKCBLS5.js.map} +0 -0
  94. /package/dist/{chunk-BEB4GUU5.js.map → chunk-TLM762GT.js.map} +0 -0
  95. /package/dist/{chunk-65HQPW6O.js.map → chunk-TOFUTKQN.js.map} +0 -0
  96. /package/dist/{chunk-G7JBLD65.js.map → chunk-YO3AZEE5.js.map} +0 -0
@@ -641,6 +641,34 @@ export class EngramAccessHttpServer {
641
641
  return;
642
642
  }
643
643
 
644
+ if (
645
+ req.method === "POST" &&
646
+ (
647
+ pathname === "/engram/v1/offline-sync/file-content" ||
648
+ pathname === "/remnic/v1/offline-sync/file-content"
649
+ )
650
+ ) {
651
+ const body = await this.readValidatedBody(req, "offlineSyncFileContent");
652
+ const result = await this.service.offlineSyncFileContent({
653
+ namespace: this.resolveNamespace(req, body.namespace),
654
+ principal: this.resolveRequestPrincipal(req),
655
+ includeTranscripts: body.includeTranscripts,
656
+ path: body.path,
657
+ offset: body.offset,
658
+ length: body.length,
659
+ });
660
+ this.respondBinary(res, 200, result.content, {
661
+ "x-remnic-namespace": encodeURIComponent(result.namespace),
662
+ "x-remnic-file-path": encodeURIComponent(result.path),
663
+ "x-remnic-file-bytes": String(result.bytes),
664
+ "x-remnic-file-mtime-ms": String(result.mtimeMs),
665
+ "x-remnic-chunk-offset": String(result.offset),
666
+ "x-remnic-chunk-bytes": String(result.chunkBytes),
667
+ ...(result.sha256 ? { "x-remnic-file-sha256": result.sha256 } : {}),
668
+ });
669
+ return;
670
+ }
671
+
644
672
  if (
645
673
  req.method === "POST" &&
646
674
  (pathname === "/engram/v1/offline-sync/apply" || pathname === "/remnic/v1/offline-sync/apply")
@@ -1803,6 +1831,25 @@ export class EngramAccessHttpServer {
1803
1831
  res.end(body);
1804
1832
  }
1805
1833
 
1834
+ private respondBinary(
1835
+ res: ServerResponse,
1836
+ status: number,
1837
+ body: Buffer,
1838
+ headers: Record<string, string> = {},
1839
+ ): void {
1840
+ res.statusCode = status;
1841
+ res.setHeader("content-type", "application/octet-stream");
1842
+ res.setHeader("content-length", String(body.length));
1843
+ for (const [key, value] of Object.entries(headers)) {
1844
+ res.setHeader(key, value);
1845
+ }
1846
+ const cid = correlationIdStore.getStore();
1847
+ if (cid) {
1848
+ res.setHeader("x-request-id", cid);
1849
+ }
1850
+ res.end(body);
1851
+ }
1852
+
1806
1853
  private async handleAdminConsole(
1807
1854
  req: IncomingMessage,
1808
1855
  res: ServerResponse,
@@ -10,6 +10,7 @@ import {
10
10
  } from "./action-confidence.js";
11
11
  import { isValidCapsuleSince } from "./transfer/capsule-export.js";
12
12
  import { CAPSULE_ID_PATTERN } from "./transfer/types.js";
13
+ import { OFFLINE_SYNC_FILE_CONTENT_MAX_CHUNK_BYTES } from "./offline-sync.js";
13
14
 
14
15
  // ---------------------------------------------------------------------------
15
16
  // Error formatting
@@ -386,6 +387,14 @@ export const offlineSyncFilesRequestSchema = z.object({
386
387
  .max(5000, "paths must contain 5000 or fewer entries"),
387
388
  });
388
389
 
390
+ export const offlineSyncFileContentRequestSchema = z.object({
391
+ namespace: namespaceSchema,
392
+ includeTranscripts: z.boolean().optional(),
393
+ path: z.string().trim().min(1, "path must be non-empty").max(4096),
394
+ offset: z.number().int().min(0).optional(),
395
+ length: z.number().int().min(1).max(OFFLINE_SYNC_FILE_CONTENT_MAX_CHUNK_BYTES).optional(),
396
+ });
397
+
389
398
  // ---------------------------------------------------------------------------
390
399
  // Action confidence
391
400
  // ---------------------------------------------------------------------------
@@ -453,6 +462,7 @@ export type CapsuleImportRequest = z.infer<typeof capsuleImportRequestSchema>;
453
462
  export type CapsuleListRequest = z.infer<typeof capsuleListRequestSchema>;
454
463
  export type OfflineSyncApplyRequest = z.infer<typeof offlineSyncApplyRequestSchema>;
455
464
  export type OfflineSyncFilesRequest = z.infer<typeof offlineSyncFilesRequestSchema>;
465
+ export type OfflineSyncFileContentRequest = z.infer<typeof offlineSyncFileContentRequestSchema>;
456
466
  export type ActionConfidenceRequest = z.infer<typeof actionConfidenceRequestSchema>;
457
467
 
458
468
  // ---------------------------------------------------------------------------
@@ -477,6 +487,7 @@ export type SchemaName =
477
487
  | "capsuleImport"
478
488
  | "capsuleList"
479
489
  | "offlineSyncFiles"
490
+ | "offlineSyncFileContent"
480
491
  | "offlineSyncApply"
481
492
  | "actionConfidence";
482
493
 
@@ -498,6 +509,7 @@ export type SchemaTypeFor<N extends SchemaName> =
498
509
  : N extends "capsuleImport" ? CapsuleImportRequest
499
510
  : N extends "capsuleList" ? CapsuleListRequest
500
511
  : N extends "offlineSyncFiles" ? OfflineSyncFilesRequest
512
+ : N extends "offlineSyncFileContent" ? OfflineSyncFileContentRequest
501
513
  : N extends "offlineSyncApply" ? OfflineSyncApplyRequest
502
514
  : N extends "actionConfidence" ? ActionConfidenceRequest
503
515
  : never;
@@ -520,6 +532,7 @@ const schemas: Record<SchemaName, z.ZodTypeAny> = {
520
532
  capsuleImport: capsuleImportRequestSchema,
521
533
  capsuleList: capsuleListRequestSchema,
522
534
  offlineSyncFiles: offlineSyncFilesRequestSchema,
535
+ offlineSyncFileContent: offlineSyncFileContentRequestSchema,
523
536
  offlineSyncApply: offlineSyncApplyRequestSchema,
524
537
  actionConfidence: actionConfidenceRequestSchema,
525
538
  };
@@ -139,7 +139,9 @@ import {
139
139
  applyOfflineSyncChangeset,
140
140
  buildOfflineSyncSnapshot,
141
141
  buildOfflineSyncSnapshotForPaths,
142
+ readOfflineSyncFileContentChunk,
142
143
  type OfflineSyncApplyChangesetResult,
144
+ type OfflineSyncFileContentChunk,
143
145
  type OfflineSyncSnapshot,
144
146
  } from "./offline-sync.js";
145
147
  import {
@@ -624,6 +626,15 @@ export interface EngramAccessOfflineSyncFilesRequest {
624
626
  paths: string[];
625
627
  }
626
628
 
629
+ export interface EngramAccessOfflineSyncFileContentRequest {
630
+ namespace?: string;
631
+ principal?: string;
632
+ includeTranscripts?: boolean;
633
+ path: string;
634
+ offset?: number;
635
+ length?: number;
636
+ }
637
+
627
638
  export interface EngramAccessOfflineSyncApplyRequest {
628
639
  namespace?: string;
629
640
  principal?: string;
@@ -638,6 +649,10 @@ export interface EngramAccessOfflineSyncFilesResponse extends OfflineSyncSnapsho
638
649
  namespace: string;
639
650
  }
640
651
 
652
+ export interface EngramAccessOfflineSyncFileContentResponse extends OfflineSyncFileContentChunk {
653
+ namespace: string;
654
+ }
655
+
641
656
  export interface EngramAccessOfflineSyncApplyResponse extends OfflineSyncApplyChangesetResult {
642
657
  namespace: string;
643
658
  }
@@ -5612,6 +5627,39 @@ export class EngramAccessService {
5612
5627
  }
5613
5628
  }
5614
5629
 
5630
+ async offlineSyncFileContent(
5631
+ options: EngramAccessOfflineSyncFileContentRequest,
5632
+ ): Promise<EngramAccessOfflineSyncFileContentResponse> {
5633
+ const resolvedNamespace = this.resolveReadableNamespace(options.namespace, options.principal);
5634
+ const storage = await this.orchestrator.getStorage(resolvedNamespace);
5635
+ try {
5636
+ const chunk = await readOfflineSyncFileContentChunk({
5637
+ root: storage.dir,
5638
+ path: options.path,
5639
+ offset: options.offset,
5640
+ length: options.length,
5641
+ includeTranscripts: options.includeTranscripts !== false,
5642
+ readFile: async ({ filePath }) => storage.readOfflineSyncFile(filePath),
5643
+ });
5644
+ return {
5645
+ namespace: resolvedNamespace,
5646
+ ...chunk,
5647
+ };
5648
+ } catch (error) {
5649
+ const message = error instanceof Error ? error.message : String(error);
5650
+ if (
5651
+ message.startsWith("path:") ||
5652
+ message.startsWith("offset ") ||
5653
+ message.startsWith("offset must ") ||
5654
+ message.startsWith("length ") ||
5655
+ message.startsWith("offline sync file content ")
5656
+ ) {
5657
+ throw new EngramAccessInputError(message);
5658
+ }
5659
+ throw error;
5660
+ }
5661
+ }
5662
+
5615
5663
  async offlineSyncApply(
5616
5664
  options: EngramAccessOfflineSyncApplyRequest,
5617
5665
  ): Promise<EngramAccessOfflineSyncApplyResponse> {
package/src/index.ts CHANGED
@@ -680,6 +680,7 @@ export {
680
680
 
681
681
  export {
682
682
  OFFLINE_SYNC_CHANGESET_FORMAT,
683
+ OFFLINE_SYNC_FILE_CONTENT_MAX_CHUNK_BYTES,
683
684
  OFFLINE_SYNC_SNAPSHOT_FORMAT,
684
685
  OFFLINE_SYNC_STATE_VERSION,
685
686
  applyOfflineSyncChangeset,
@@ -692,6 +693,7 @@ export {
692
693
  normalizeOfflineSyncChangeset,
693
694
  normalizeOfflineSyncSnapshot,
694
695
  offlineSyncStateFromSnapshot,
696
+ readOfflineSyncFileContentChunk,
695
697
  readOfflineSyncState,
696
698
  summarizeOfflineSyncChangeset,
697
699
  writeOfflineSyncState,
@@ -701,6 +703,7 @@ export {
701
703
  type OfflineSyncChangeset,
702
704
  type OfflineSyncConflict,
703
705
  type OfflineSyncFileRecord,
706
+ type OfflineSyncFileContentChunk,
704
707
  type OfflineSyncFileState,
705
708
  type OfflineSyncFileTarget,
706
709
  type OfflineSyncFileWriteTarget,
@@ -11,6 +11,7 @@ import {
11
11
  buildOfflineSyncChangeset,
12
12
  buildOfflineSyncSnapshot,
13
13
  buildOfflineSyncSnapshotForPaths,
14
+ readOfflineSyncFileContentChunk,
14
15
  } from "./offline-sync.js";
15
16
  import { isEncryptedFile } from "./secure-store/secure-fs.js";
16
17
  import { StorageManager } from "./storage.js";
@@ -117,6 +118,37 @@ test("offline sync excludes live LCM sqlite artifacts without deleting existing
117
118
  }
118
119
  });
119
120
 
121
+ test("offline sync reads bounded file content chunks with metadata", async () => {
122
+ const root = await tempDir("remnic-offline-file-content");
123
+ try {
124
+ await write(root, "state/memory-lifecycle-ledger.jsonl", "alpha\nbeta\ngamma\n");
125
+
126
+ const chunk = await readOfflineSyncFileContentChunk({
127
+ root,
128
+ path: "state/memory-lifecycle-ledger.jsonl",
129
+ offset: 6,
130
+ length: 5,
131
+ });
132
+
133
+ assert.equal(chunk.path, "state/memory-lifecycle-ledger.jsonl");
134
+ assert.equal(chunk.offset, 6);
135
+ assert.equal(chunk.chunkBytes, 5);
136
+ assert.equal(chunk.content.toString("utf-8"), "beta\n");
137
+ assert.equal(chunk.bytes, Buffer.byteLength("alpha\nbeta\ngamma\n"));
138
+
139
+ await assert.rejects(
140
+ () =>
141
+ readOfflineSyncFileContentChunk({
142
+ root,
143
+ path: "state/lcm.sqlite",
144
+ }),
145
+ /offline sync file content path is excluded: state\/lcm\.sqlite/,
146
+ );
147
+ } finally {
148
+ await rm(root, { recursive: true, force: true });
149
+ }
150
+ });
151
+
120
152
  test("offline changeset pushes local edits when the remote is still at the shared base", async () => {
121
153
  const remote = await tempDir("remnic-offline-remote");
122
154
  const local = await tempDir("remnic-offline-local");
@@ -2,6 +2,7 @@ import { createHash, randomUUID } from "node:crypto";
2
2
  import {
3
3
  lstat,
4
4
  mkdir,
5
+ open,
5
6
  readdir,
6
7
  readFile,
7
8
  rename,
@@ -13,6 +14,7 @@ import path from "node:path";
13
14
  import {
14
15
  DEFAULT_TRANSFER_EXCLUDE_DIRS,
15
16
  } from "./transfer/exclusions.js";
17
+ import { isEncryptedFile, MAGIC_HEADER_SIZE } from "./secure-store/secure-fs.js";
16
18
  import {
17
19
  prepareSafeArchiveRoot,
18
20
  resolveSafeArchiveTarget,
@@ -25,6 +27,7 @@ import { parseFlexibleIsoTimestamp } from "./utils/iso-timestamp.js";
25
27
  export const OFFLINE_SYNC_SNAPSHOT_FORMAT = "remnic.offline-sync.snapshot.v1";
26
28
  export const OFFLINE_SYNC_CHANGESET_FORMAT = "remnic.offline-sync.changeset.v1";
27
29
  export const OFFLINE_SYNC_STATE_VERSION = 1;
30
+ export const OFFLINE_SYNC_FILE_CONTENT_MAX_CHUNK_BYTES = 64 * 1024 * 1024;
28
31
 
29
32
  export interface OfflineSyncFileState {
30
33
  path: string;
@@ -127,6 +130,13 @@ export interface OfflineSyncFileWriteTarget extends OfflineSyncFileTarget {
127
130
  content: Buffer;
128
131
  }
129
132
 
133
+ export interface OfflineSyncFileContentChunk extends Omit<OfflineSyncFileState, "sha256"> {
134
+ sha256?: string;
135
+ offset: number;
136
+ chunkBytes: number;
137
+ content: Buffer;
138
+ }
139
+
130
140
  interface OfflineSyncFileRecordOptions {
131
141
  root: SafeArchiveRoot;
132
142
  relPath: string;
@@ -435,6 +445,35 @@ async function readOfflineSyncFileRecord(
435
445
  };
436
446
  }
437
447
 
448
+ async function fileIsSecureStoreEncrypted(filePath: string): Promise<boolean> {
449
+ const handle = await open(filePath, "r");
450
+ try {
451
+ const header = Buffer.alloc(MAGIC_HEADER_SIZE);
452
+ const { bytesRead } = await handle.read(header, 0, header.length, 0);
453
+ return bytesRead >= MAGIC_HEADER_SIZE && isEncryptedFile(header);
454
+ } finally {
455
+ await handle.close();
456
+ }
457
+ }
458
+
459
+ async function readPlainFileContentChunk(options: {
460
+ filePath: string;
461
+ offset: number;
462
+ length: number;
463
+ bytes: number;
464
+ }): Promise<Buffer> {
465
+ const chunkBytes = Math.min(options.length, options.bytes - options.offset);
466
+ const chunk = Buffer.alloc(chunkBytes);
467
+ if (chunkBytes === 0) return chunk;
468
+ const handle = await open(options.filePath, "r");
469
+ try {
470
+ const { bytesRead } = await handle.read(chunk, 0, chunk.length, options.offset);
471
+ return bytesRead === chunk.length ? chunk : chunk.subarray(0, bytesRead);
472
+ } finally {
473
+ await handle.close();
474
+ }
475
+ }
476
+
438
477
  export async function buildOfflineSyncSnapshot(options: {
439
478
  root: string;
440
479
  sourceId: string;
@@ -530,6 +569,81 @@ export async function buildOfflineSyncSnapshotForPaths(options: {
530
569
  };
531
570
  }
532
571
 
572
+ export async function readOfflineSyncFileContentChunk(options: {
573
+ root: string;
574
+ path: string;
575
+ offset?: number;
576
+ length?: number;
577
+ includeTranscripts?: boolean;
578
+ readFile?: (target: OfflineSyncFileTarget) => Promise<Buffer>;
579
+ }): Promise<OfflineSyncFileContentChunk> {
580
+ const rootAbs = path.resolve(options.root);
581
+ const root = await prepareSafeArchiveRoot(rootAbs, "readOfflineSyncFileContentChunk", "root");
582
+ const includeTranscripts = options.includeTranscripts !== false;
583
+ const relPath = normalizeRelativePath(options.path, "path");
584
+ if (shouldExcludeRelPath(relPath, includeTranscripts)) {
585
+ throw new Error(`offline sync file content path is excluded: ${relPath}`);
586
+ }
587
+ const offset = options.offset === undefined
588
+ ? 0
589
+ : assertNonNegativeInteger(options.offset, "offset");
590
+ const requestedLength = options.length === undefined
591
+ ? OFFLINE_SYNC_FILE_CONTENT_MAX_CHUNK_BYTES
592
+ : assertNonNegativeInteger(options.length, "length");
593
+ if (requestedLength < 1 || requestedLength > OFFLINE_SYNC_FILE_CONTENT_MAX_CHUNK_BYTES) {
594
+ throw new Error(
595
+ `length must be an integer from 1 to ${OFFLINE_SYNC_FILE_CONTENT_MAX_CHUNK_BYTES}`,
596
+ );
597
+ }
598
+ const filePath = await resolveSafeArchiveTarget(root, relPath);
599
+ const st = await lstat(filePath).catch((error: unknown) => {
600
+ if ((error as NodeJS.ErrnoException).code === "ENOENT") return null;
601
+ throw error;
602
+ });
603
+ if (!st || st.isSymbolicLink() || !st.isFile()) {
604
+ throw new Error(`offline sync file content path not found: ${relPath}`);
605
+ }
606
+ const encrypted = await fileIsSecureStoreEncrypted(filePath);
607
+ if (!encrypted) {
608
+ if (offset > st.size) {
609
+ throw new Error(`offset must be <= file size for ${relPath}`);
610
+ }
611
+ const chunk = await readPlainFileContentChunk({
612
+ filePath,
613
+ offset,
614
+ length: requestedLength,
615
+ bytes: st.size,
616
+ });
617
+ return {
618
+ path: relPath,
619
+ bytes: st.size,
620
+ mtimeMs: st.mtimeMs,
621
+ offset,
622
+ chunkBytes: chunk.length,
623
+ content: chunk,
624
+ };
625
+ }
626
+ if (!options.readFile) {
627
+ throw new Error(`offline sync file content requires a secure-store read hook: ${relPath}`);
628
+ }
629
+ const content = await options.readFile({ root: root.abs, path: relPath, filePath });
630
+ if (offset > content.length) {
631
+ throw new Error(`offset must be <= file size for ${relPath}`);
632
+ }
633
+ const digest = sha256Buffer(content);
634
+ const end = Math.min(content.length, offset + requestedLength);
635
+ const chunk = content.subarray(offset, end);
636
+ return {
637
+ path: relPath,
638
+ sha256: digest.sha256,
639
+ bytes: digest.bytes,
640
+ mtimeMs: st.mtimeMs,
641
+ offset,
642
+ chunkBytes: chunk.length,
643
+ content: Buffer.from(chunk),
644
+ };
645
+ }
646
+
533
647
  export async function buildOfflineSyncChangeset(options: {
534
648
  root: string;
535
649
  sourceId: string;
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/access-schema.ts"],"sourcesContent":["// Request/response schema validation for the Remnic HTTP API.\n// Uses zod for runtime validation — returns structured 400 errors with\n// field-level detail so consumers get clear feedback on malformed requests.\n\nimport { z } from \"zod\";\nimport {\n ACTION_CONFIDENCE_CONTEXT_READINESS,\n ACTION_CONFIDENCE_RISK_CATEGORIES,\n ACTION_CONFIDENCE_RULE_KINDS,\n} from \"./action-confidence.js\";\nimport { isValidCapsuleSince } from \"./transfer/capsule-export.js\";\nimport { CAPSULE_ID_PATTERN } from \"./transfer/types.js\";\n\n// ---------------------------------------------------------------------------\n// Error formatting\n// ---------------------------------------------------------------------------\n\nexport interface SchemaValidationError {\n error: string;\n code: \"validation_error\";\n details: Array<{ field: string; message: string }>;\n}\n\nexport function formatZodError(error: z.ZodError): SchemaValidationError {\n return {\n error: \"request validation failed\",\n code: \"validation_error\",\n details: error.issues.map((issue) => ({\n field: issue.path.join(\".\") || \"(root)\",\n message: issue.message,\n })),\n };\n}\n\n// ---------------------------------------------------------------------------\n// Shared fields\n// ---------------------------------------------------------------------------\n\nconst namespaceSchema = z.string().trim().max(256).optional();\nconst sessionKeySchema = z.string().trim().min(1).max(512).optional();\nconst idempotencyKeySchema = z.string().trim().min(1).max(256).optional();\nconst dryRunSchema = z.boolean().optional();\nconst schemaVersionSchema = z.number().int().optional();\n\n// ---------------------------------------------------------------------------\n// Recall\n// ---------------------------------------------------------------------------\n\n/**\n * Coding-agent context (issue #569). Optional payload that connectors may\n * ship with a recall request so the project/branch namespace overlay\n * applies to that recall. All fields are validated per CLAUDE.md #51 —\n * empty-string projectId / rootPath is rejected, not silently accepted.\n */\nexport const codingContextSchema = z\n .object({\n projectId: z.string().trim().min(1, \"codingContext.projectId is required\").max(128),\n branch: z.string().trim().max(256).nullable(),\n rootPath: z.string().trim().min(1, \"codingContext.rootPath is required\").max(1024),\n defaultBranch: z.string().trim().max(256).nullable(),\n })\n .nullable();\n\n/**\n * Recall disclosure depth (issue #677). Mirrors the `RecallDisclosure`\n * type in `types.ts` — keep these in sync. Default-application happens\n * inside `EngramAccessService.recall()`; the schema only accepts/rejects.\n * Invalid values throw a structured 400 instead of silently defaulting,\n * per CLAUDE.md rule 51.\n */\nexport const recallDisclosureSchema = z.enum([\"chunk\", \"section\", \"raw\"]);\n\n/**\n * Tag-match semantics (issue #689). `any` (default when `tags` is provided\n * and `tagMatch` is omitted) admits a result when it carries at least one\n * of the filter tags. `all` requires every filter tag to be present.\n * Schema rejects unknown values up front — never silently defaults\n * (CLAUDE.md rule 51).\n */\nexport const tagMatchSchema = z.enum([\"any\", \"all\"]);\n\nexport const recallRequestSchema = z.object({\n query: z.string().min(1, \"query is required\"),\n sessionKey: sessionKeySchema,\n namespace: namespaceSchema,\n topK: z.number().int().min(0).max(200).optional(),\n mode: z.enum([\"auto\", \"no_recall\", \"minimal\", \"full\", \"graph_mode\"]).optional(),\n includeDebug: z.boolean().optional(),\n idempotencyKey: idempotencyKeySchema,\n disclosure: recallDisclosureSchema.optional(),\n codingContext: codingContextSchema.optional(),\n /** Working directory for auto git-context resolution (issue #569). */\n cwd: z.string().trim().min(1, \"cwd must be non-empty when provided\").max(2048).optional(),\n /**\n * Arbitrary project tag for non-git-based project scoping (issue #569).\n * Creates a coding context with `projectId: \"tag:<projectTag>\"`.\n */\n projectTag: z.string().trim().min(1, \"projectTag must be non-empty when provided\").max(256).optional(),\n /**\n * Historical recall pin (issue #680). ISO 8601 timestamp. The\n * schema only enforces the basic shape; the access service runs\n * `Date.parse` and emits a structured 400 on malformed input\n * (CLAUDE.md rule 51).\n */\n asOf: z.string().trim().min(1, \"asOf must be a non-empty ISO 8601 timestamp\").max(64).optional(),\n /**\n * Free-form recall tag filter (issue #689). When provided, recall results\n * whose frontmatter `tags` do not match the filter are removed before the\n * response is returned. Comparison is case-sensitive exact match.\n */\n tags: z.array(z.string().trim().min(1).max(256)).max(50).optional(),\n /**\n * Match mode for `tags` (issue #689). Defaults to `\"any\"` when `tags` is\n * provided and `tagMatch` is omitted. Ignored when `tags` is absent.\n */\n tagMatch: tagMatchSchema.optional(),\n /**\n * Include graph edges below `graphTraversalConfidenceFloor` for diagnostic\n * recall traversal (issue #681). Defaults to false.\n */\n includeLowConfidence: z.boolean().optional(),\n});\n\nexport const recallExplainRequestSchema = z.object({\n sessionKey: sessionKeySchema,\n namespace: namespaceSchema,\n});\n\n/**\n * Standalone \"set coding context\" request. Used by the HTTP endpoint\n * `POST /engram/v1/coding-context` and the MCP `remnic.set_coding_context`\n * tool (PR 7). `codingContext: null` clears the attached context.\n */\nexport const setCodingContextRequestSchema = z.object({\n sessionKey: z.string().trim().min(1, \"sessionKey is required\").max(512),\n codingContext: codingContextSchema,\n});\n\n// ---------------------------------------------------------------------------\n// Observe\n// ---------------------------------------------------------------------------\n\nconst messageSchema = z.object({\n role: z.enum([\"user\", \"assistant\"]),\n content: z.string().min(1, \"message content must be non-empty\"),\n sourceFormat: z\n .enum([\"openai\", \"anthropic\", \"openclaw\", \"pi\", \"lossless-claw\", \"remnic\"])\n .nullable()\n .optional(),\n rawContent: z.unknown().nullable().optional(),\n parts: z\n .array(\n z.object({\n ordinal: z.number().int().min(0).nullable().optional(),\n kind: z.enum([\n \"text\",\n \"tool_call\",\n \"tool_result\",\n \"patch\",\n \"file_read\",\n \"file_write\",\n \"step_start\",\n \"step_finish\",\n \"snapshot\",\n \"retry\",\n ]),\n payload: z.record(z.string(), z.unknown()),\n toolName: z.string().nullable().optional(),\n tool_name: z.string().nullable().optional(),\n filePath: z.string().nullable().optional(),\n file_path: z.string().nullable().optional(),\n createdAt: z.string().nullable().optional(),\n created_at: z.string().nullable().optional(),\n }),\n )\n .nullable()\n .optional(),\n});\n\nexport const observeRequestSchema = z.object({\n sessionKey: z.string().trim().min(1, \"sessionKey is required\").max(512),\n messages: z.array(messageSchema).min(1, \"messages must be a non-empty array\"),\n namespace: namespaceSchema,\n skipExtraction: z.boolean().optional(),\n /** Working directory for auto git-context resolution (issue #569). */\n cwd: z.string().trim().min(1, \"cwd must be non-empty when provided\").max(2048).optional(),\n /**\n * Arbitrary project tag for non-git-based project scoping (issue #569).\n * Creates a coding context with `projectId: \"tag:<projectTag>\"`.\n */\n projectTag: z.string().trim().min(1, \"projectTag must be non-empty when provided\").max(256).optional(),\n});\n\n// ---------------------------------------------------------------------------\n// Memory store / suggestion submit\n// ---------------------------------------------------------------------------\n\nconst writeContentSchema = z.string().min(1, \"content is required\").max(50000);\nconst categorySchema = z\n .enum([\n \"fact\", \"preference\", \"correction\", \"entity\", \"decision\",\n \"relationship\", \"principle\", \"commitment\", \"moment\", \"skill\", \"rule\", \"procedure\",\n \"reasoning_trace\",\n ])\n .optional();\nconst confidenceSchema = z.number().min(0).max(1).optional();\nconst tagsSchema = z.array(z.string().max(256)).max(50).optional();\nconst entityRefSchema = z.string().trim().max(512).optional();\nconst ttlSchema = z.string().trim().max(128).optional();\nconst sourceReasonSchema = z.string().trim().max(2000).optional();\n\nexport const memoryStoreRequestSchema = z.object({\n schemaVersion: schemaVersionSchema,\n idempotencyKey: idempotencyKeySchema,\n dryRun: dryRunSchema,\n sessionKey: sessionKeySchema,\n content: writeContentSchema,\n category: categorySchema,\n confidence: confidenceSchema,\n namespace: namespaceSchema,\n tags: tagsSchema,\n entityRef: entityRefSchema,\n ttl: ttlSchema,\n sourceReason: sourceReasonSchema,\n});\n\nexport const suggestionSubmitRequestSchema = memoryStoreRequestSchema;\n\n// ---------------------------------------------------------------------------\n// Review disposition\n// ---------------------------------------------------------------------------\n\nexport const reviewDispositionRequestSchema = z.object({\n memoryId: z.string().trim().min(1, \"memoryId is required\"),\n status: z.enum([\n \"active\", \"pending_review\", \"quarantined\", \"rejected\", \"superseded\", \"archived\",\n ]),\n reasonCode: z.string().trim().min(1, \"reasonCode is required\"),\n namespace: namespaceSchema,\n});\n\n// ---------------------------------------------------------------------------\n// Trust-zone promote\n// ---------------------------------------------------------------------------\n\nexport const trustZonePromoteRequestSchema = z.object({\n recordId: z.string().trim().min(1, \"recordId is required\"),\n targetZone: z.enum([\"working\", \"trusted\"], {\n errorMap: () => ({ message: \"targetZone must be 'working' or 'trusted'\" }),\n }),\n promotionReason: z.string().trim().min(1, \"promotionReason is required\"),\n recordedAt: z.string().trim().optional(),\n summary: z.string().trim().max(5000).optional(),\n dryRun: dryRunSchema,\n namespace: namespaceSchema,\n});\n\n// ---------------------------------------------------------------------------\n// Trust-zone demo-seed\n// ---------------------------------------------------------------------------\n\nexport const trustZoneDemoSeedRequestSchema = z.object({\n scenario: z.string().trim().max(256).optional(),\n recordedAt: z.string().trim().optional(),\n dryRun: dryRunSchema,\n namespace: namespaceSchema,\n});\n\n// ---------------------------------------------------------------------------\n// LCM search\n// ---------------------------------------------------------------------------\n\nexport const lcmSearchRequestSchema = z.object({\n query: z.string().min(1, \"query is required\"),\n sessionKey: sessionKeySchema,\n namespace: namespaceSchema,\n limit: z.number().int().min(1).max(100).optional(),\n});\n\nexport const lcmCompactionFlushRequestSchema = z.object({\n sessionKey: z.string().trim().min(1, \"sessionKey is required\").max(512),\n namespace: namespaceSchema,\n});\n\nexport const lcmCompactionRecordRequestSchema = z.object({\n sessionKey: z.string().trim().min(1, \"sessionKey is required\").max(512),\n namespace: namespaceSchema,\n tokensBefore: z.number().int().min(0, \"tokensBefore must be a non-negative integer\"),\n tokensAfter: z.number().int().min(0, \"tokensAfter must be a non-negative integer\"),\n});\n\n// ---------------------------------------------------------------------------\n// Day summary\n// ---------------------------------------------------------------------------\n\nexport const daySummaryRequestSchema = z.object({\n memories: z.string().max(100000).optional(),\n sessionKey: sessionKeySchema,\n namespace: namespaceSchema,\n});\n\n// ---------------------------------------------------------------------------\n// Capsule export\n// ---------------------------------------------------------------------------\n\nconst capsuleTopLevelSegmentSchema = z\n .string()\n .trim()\n .min(1)\n .max(128)\n .refine(\n (value) => !value.includes(\"/\") && !value.includes(\"\\\\\"),\n \"must be a top-level directory name without path separators\",\n );\n\nconst capsulePeerIdSchema = z\n .string()\n .trim()\n .min(1)\n .max(256)\n .refine(\n (value) => value !== \".\" && value !== \"..\" && !value.includes(\"/\") && !value.includes(\"\\\\\"),\n \"must be a plain peer id without path separators\",\n );\n\nconst capsuleIsoSinceSchema = z\n .string()\n .trim()\n .min(1, \"since must be a non-empty ISO 8601 timestamp\")\n .max(128)\n .refine(\n isValidCapsuleSince,\n \"since must be a valid ISO 8601 timestamp with no calendar overflow\",\n );\n\nexport const capsuleExportRequestSchema = z\n .object({\n name: z\n .string()\n .trim()\n .min(1, \"name is required\")\n .max(64, \"name must be 64 characters or fewer\")\n .regex(\n CAPSULE_ID_PATTERN,\n \"name must be alphanumeric with single dashes (no spaces, no leading/trailing dashes)\",\n ),\n namespace: namespaceSchema,\n since: capsuleIsoSinceSchema.optional(),\n includeKinds: z.array(capsuleTopLevelSegmentSchema).max(50).optional(),\n peerIds: z.array(capsulePeerIdSchema).max(100).optional(),\n includeTranscripts: z.boolean().optional(),\n encrypt: z.boolean().optional(),\n });\n\nexport const capsuleImportRequestSchema = z\n .object({\n archivePath: z.string().trim().min(1, \"archivePath is required\").max(4096),\n namespace: namespaceSchema,\n mode: z.enum([\"skip\", \"overwrite\", \"fork\"]).optional(),\n });\n\nexport const capsuleListRequestSchema = z\n .object({\n namespace: namespaceSchema,\n });\n\n// ---------------------------------------------------------------------------\n// Offline sync\n// ---------------------------------------------------------------------------\n\nexport const offlineSyncApplyRequestSchema = z\n .object({\n namespace: namespaceSchema,\n changeset: z.unknown(),\n })\n .refine((value) => value.changeset !== undefined && value.changeset !== null, {\n message: \"changeset is required\",\n path: [\"changeset\"],\n });\n\nexport const offlineSyncFilesRequestSchema = z.object({\n namespace: namespaceSchema,\n includeTranscripts: z.boolean().optional(),\n paths: z\n .array(z.string().trim().min(1, \"path must be non-empty\").max(4096))\n .max(5000, \"paths must contain 5000 or fewer entries\"),\n});\n\n// ---------------------------------------------------------------------------\n// Action confidence\n// ---------------------------------------------------------------------------\n\nconst nullableOptional = <T extends z.ZodTypeAny>(schema: T) =>\n schema.optional().nullable().transform((value) => value ?? undefined);\n\nconst actionConfidenceRuleSchema = z\n .object({\n kind: z.enum(ACTION_CONFIDENCE_RULE_KINDS),\n description: nullableOptional(z.string().trim().min(1).max(2000)),\n matched: nullableOptional(z.boolean()),\n })\n .strict();\n\nconst actionConfidenceMemorySchema = z\n .object({\n source: nullableOptional(z.string().trim().min(1).max(256)),\n created: nullableOptional(z.string().trim().min(1).max(128)),\n updated: nullableOptional(z.string().trim().min(1).max(128)),\n scope: nullableOptional(z.string().trim().min(1).max(512)),\n userContextScopes: nullableOptional(z.array(z.string().trim().min(1).max(128)).max(50)),\n retrievalReason: nullableOptional(z.string().trim().min(1).max(2000)),\n confidence: nullableOptional(z.number().min(0).max(1)),\n stale: nullableOptional(z.boolean()),\n corrected: nullableOptional(z.boolean()),\n correctionState: nullableOptional(z.enum([\"none\", \"correction\", \"superseded\", \"disputed\", \"forgotten\"])),\n safeToUse: nullableOptional(z.boolean()),\n safety: nullableOptional(z.enum([\"safe\", \"requires-review\", \"blocked\"])),\n safetyReasons: nullableOptional(z.array(z.string().trim().min(1).max(1000)).max(50)),\n })\n .strict();\n\nexport const actionConfidenceRequestSchema = z\n .object({\n intendedAction: nullableOptional(z.string().trim().min(1).max(1000)),\n confidence: nullableOptional(z.number().min(0).max(1)),\n risk: nullableOptional(z.enum(ACTION_CONFIDENCE_RISK_CATEGORIES)),\n contextReadiness: nullableOptional(z.enum(ACTION_CONFIDENCE_CONTEXT_READINESS)),\n currentContextScopes: nullableOptional(z.array(z.string().trim().min(1).max(128)).max(50)),\n userRules: nullableOptional(z.array(actionConfidenceRuleSchema).max(100)),\n retrievedMemories: nullableOptional(z.array(actionConfidenceMemorySchema).max(200)),\n })\n .strict();\n\n// ---------------------------------------------------------------------------\n// Inferred types\n// ---------------------------------------------------------------------------\n\nexport type RecallRequest = z.infer<typeof recallRequestSchema>;\nexport type RecallExplainRequest = z.infer<typeof recallExplainRequestSchema>;\nexport type SetCodingContextRequest = z.infer<typeof setCodingContextRequestSchema>;\nexport type ObserveRequest = z.infer<typeof observeRequestSchema>;\nexport type MemoryStoreRequest = z.infer<typeof memoryStoreRequestSchema>;\nexport type SuggestionSubmitRequest = z.infer<typeof suggestionSubmitRequestSchema>;\nexport type ReviewDispositionRequest = z.infer<typeof reviewDispositionRequestSchema>;\nexport type TrustZonePromoteRequest = z.infer<typeof trustZonePromoteRequestSchema>;\nexport type TrustZoneDemoSeedRequest = z.infer<typeof trustZoneDemoSeedRequestSchema>;\nexport type LcmSearchRequest = z.infer<typeof lcmSearchRequestSchema>;\nexport type LcmCompactionFlushRequest = z.infer<typeof lcmCompactionFlushRequestSchema>;\nexport type LcmCompactionRecordRequest = z.infer<typeof lcmCompactionRecordRequestSchema>;\nexport type DaySummaryRequest = z.infer<typeof daySummaryRequestSchema>;\nexport type CapsuleExportRequest = z.infer<typeof capsuleExportRequestSchema>;\nexport type CapsuleImportRequest = z.infer<typeof capsuleImportRequestSchema>;\nexport type CapsuleListRequest = z.infer<typeof capsuleListRequestSchema>;\nexport type OfflineSyncApplyRequest = z.infer<typeof offlineSyncApplyRequestSchema>;\nexport type OfflineSyncFilesRequest = z.infer<typeof offlineSyncFilesRequestSchema>;\nexport type ActionConfidenceRequest = z.infer<typeof actionConfidenceRequestSchema>;\n\n// ---------------------------------------------------------------------------\n// Validation helper\n// ---------------------------------------------------------------------------\n\nexport type SchemaName =\n | \"recall\"\n | \"recallExplain\"\n | \"setCodingContext\"\n | \"observe\"\n | \"memoryStore\"\n | \"suggestionSubmit\"\n | \"reviewDisposition\"\n | \"trustZonePromote\"\n | \"trustZoneDemoSeed\"\n | \"lcmSearch\"\n | \"lcmCompactionFlush\"\n | \"lcmCompactionRecord\"\n | \"daySummary\"\n | \"capsuleExport\"\n | \"capsuleImport\"\n | \"capsuleList\"\n | \"offlineSyncFiles\"\n | \"offlineSyncApply\"\n | \"actionConfidence\";\n\nexport type SchemaTypeFor<N extends SchemaName> =\n N extends \"recall\" ? RecallRequest\n : N extends \"recallExplain\" ? RecallExplainRequest\n : N extends \"setCodingContext\" ? SetCodingContextRequest\n : N extends \"observe\" ? ObserveRequest\n : N extends \"memoryStore\" ? MemoryStoreRequest\n : N extends \"suggestionSubmit\" ? SuggestionSubmitRequest\n : N extends \"reviewDisposition\" ? ReviewDispositionRequest\n : N extends \"trustZonePromote\" ? TrustZonePromoteRequest\n : N extends \"trustZoneDemoSeed\" ? TrustZoneDemoSeedRequest\n : N extends \"lcmSearch\" ? LcmSearchRequest\n : N extends \"lcmCompactionFlush\" ? LcmCompactionFlushRequest\n : N extends \"lcmCompactionRecord\" ? LcmCompactionRecordRequest\n : N extends \"daySummary\" ? DaySummaryRequest\n : N extends \"capsuleExport\" ? CapsuleExportRequest\n : N extends \"capsuleImport\" ? CapsuleImportRequest\n : N extends \"capsuleList\" ? CapsuleListRequest\n : N extends \"offlineSyncFiles\" ? OfflineSyncFilesRequest\n : N extends \"offlineSyncApply\" ? OfflineSyncApplyRequest\n : N extends \"actionConfidence\" ? ActionConfidenceRequest\n : never;\n\nconst schemas: Record<SchemaName, z.ZodTypeAny> = {\n recall: recallRequestSchema,\n recallExplain: recallExplainRequestSchema,\n setCodingContext: setCodingContextRequestSchema,\n observe: observeRequestSchema,\n memoryStore: memoryStoreRequestSchema,\n suggestionSubmit: suggestionSubmitRequestSchema,\n reviewDisposition: reviewDispositionRequestSchema,\n trustZonePromote: trustZonePromoteRequestSchema,\n trustZoneDemoSeed: trustZoneDemoSeedRequestSchema,\n lcmSearch: lcmSearchRequestSchema,\n lcmCompactionFlush: lcmCompactionFlushRequestSchema,\n lcmCompactionRecord: lcmCompactionRecordRequestSchema,\n daySummary: daySummaryRequestSchema,\n capsuleExport: capsuleExportRequestSchema,\n capsuleImport: capsuleImportRequestSchema,\n capsuleList: capsuleListRequestSchema,\n offlineSyncFiles: offlineSyncFilesRequestSchema,\n offlineSyncApply: offlineSyncApplyRequestSchema,\n actionConfidence: actionConfidenceRequestSchema,\n};\n\n/**\n * Validate a request body against the named schema.\n * Returns `{ success: true, data }` on pass or\n * `{ success: false, error }` on failure with field-level detail.\n */\nexport function validateRequest<T = unknown>(\n schemaName: SchemaName,\n body: unknown,\n): { success: true; data: T } | { success: false; error: SchemaValidationError } {\n const schema = schemas[schemaName];\n if (!schema) {\n return {\n success: false,\n error: {\n error: `unknown schema: ${schemaName}`,\n code: \"validation_error\",\n details: [],\n },\n };\n }\n const result = schema.safeParse(body);\n if (result.success) {\n return { success: true, data: result.data as T };\n }\n return { success: false, error: formatZodError(result.error) };\n}\n"],"mappings":"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","names":[]}