{"record":{"id":"81b3fd6720e47b25","repo":"affaan-m/ECC","slug":"memory-roots-must-include-a-trusted-boundary-polic","errorCode":null,"errorMessage":"Memory roots must include a trusted boundary policy.","messagePattern":"Memory roots must include a trusted boundary policy\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/lib/memory-vault.js","lineNumber":99,"sourceCode":"        ? realpathNearestExisting(projectVault)\n        : projectRoot,\n      team: env.ECC_MEMORY_PROJECT_ROOT\n        ? realpathNearestExisting(projectVault)\n        : projectRoot,\n      user: env.ECC_MEMORY_USER_ROOT\n        ? realpathNearestExisting(userVault)\n        : homeDir,\n    }),\n    enumerable: false,\n    configurable: false,\n    writable: false,\n  });\n  return Object.freeze(roots);\n}\n\nfunction assertMemoryRootSafe(roots, scope) {\n  if (!roots || typeof roots !== 'object' || Array.isArray(roots)) {\n    throw new Error('Memory roots must include a trusted boundary policy.');\n  }\n  const root = roots[scope];\n  if (typeof root !== 'string' || root.length === 0) {\n    throw new Error(`No memory root is configured for scope \"${scope}\".`);\n  }\n  const boundary = roots[VAULT_ROOT_BOUNDARIES]?.[scope];\n  if (typeof boundary !== 'string' || boundary.length === 0) {\n    throw new Error(`No trusted boundary policy is configured for memory scope \"${scope}\".`);\n  }\n  assertWithinTrustedRoot(root, boundary, 'access memory through a symlink');\n  if (fs.existsSync(root) && fs.lstatSync(root).isSymbolicLink()) {\n    throw new Error(`Refusing to access memory through symlink root: ${root}`);\n  }\n  return root;\n}\n\nfunction assertMemoryDirectorySafe(directory, root) {\n  assertWithinTrustedRoot(directory, root, 'access memory directory');","sourceCodeStart":81,"sourceCodeEnd":117,"githubUrl":"https://github.com/affaan-m/ECC/blob/01e15490f04e29cfefe3896951f43db46994d8ee/scripts/lib/memory-vault.js#L81-L117","documentation":"Thrown by assertMemoryRootSafe() as its opening guard: the roots argument must be a non-null, non-array object. roots is expected to be the frozen object produced by resolveVaultRoots() — a plain object with 'project', 'team', and 'user' string keys plus a hidden Symbol-keyed boundary map. Passing anything else is a programming error in the caller.","triggerScenarios":"Calling assertMemoryRootSafe(null, scope), assertMemoryRootSafe(undefined, scope) when options.roots was not set, assertMemoryRootSafe([], scope) when a list of roots was passed by mistake, or a wrapper that built its own roots object without using resolveVaultRoots() and passed a string path instead.","commonSituations":"Custom wrapper that wanted to override roots but passed a single string. Refactor that changed the shape passed to initializeVault/readMemoryFiles/saveMemory. Test stub that constructed roots manually and forgot the Symbol boundary property.","solutions":["Always obtain roots via resolveVaultRoots(options) — never build the object by hand.","If you must override, call resolveVaultRoots({ ECC_MEMORY_PROJECT_ROOT: '/path' }) with env-style options.","Pass options through to saveMemory()/searchMemories()/readMemoryFiles() and let them resolve roots themselves.","Add an assertion in your wrapper: if (!options.roots) options.roots = resolveVaultRoots(options);"],"exampleFix":"// before\nsaveMemory(input, { roots: '/custom/path' }); // string, not a roots object\n\n// after\nconst roots = resolveVaultRoots({ ECC_MEMORY_PROJECT_ROOT: '/custom/path' });\nsaveMemory(input, { roots });","handlingStrategy":"type-guard","validationCode":"if (!options.roots) options.roots = resolveVaultRoots(options);\nif (!options.roots || typeof options.roots !== 'object' || Array.isArray(options.roots)) {\n  throw new TypeError('options.roots must be a resolveVaultRoots() result');\n}","typeGuard":"function isRootsObject(value): value is Record<'project'|'team'|'user', string> {\n  return value !== null && typeof value === 'object' && !Array.isArray(value)\n    && typeof value.project === 'string'\n    && typeof value.team === 'string'\n    && typeof value.user === 'string';\n}","tryCatchPattern":null,"preventionTips":["Always source roots from resolveVaultRoots() — never hand-build the object.","In tests, call resolveVaultRoots({ cwd: tempDir }) to get a valid fixture.","Pass options through to the library APIs and let them resolve roots themselves."],"tags":["validation","memory-vault","roots","type-check"],"backgroundTag":null,"analyzedSha":"01e15490f04e29cfefe3896951f43db46994d8ee","analyzedAt":"2026-08-13T00:31:08.655Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}