{"record":{"id":"cf7b0e8ca39b9cb4","repo":"mastra-ai/mastra","slug":"knowledge-ceiling-cannot-be-lowered-from-current","errorCode":null,"errorMessage":"Knowledge ceiling cannot be lowered from ${currentMaxScope} to ${maxScope}","messagePattern":"Knowledge ceiling cannot be lowered from (.+?) to (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/core/src/storage/domains/knowledge/base.ts","lineNumber":376,"sourceCode":"}\n\nexport function assertKnowledgeScopeWithinCeiling(scope: KnowledgeScope, maxScope?: KnowledgeScopeLevel): void {\n  if (!maxScope) return;\n  const reservedLevels = scope\n    .map(entry => SCOPE_ORDER[entry.slice(0, entry.indexOf(':')) as KnowledgeScopeLevel])\n    .filter((value): value is number => value !== undefined);\n  const narrowestLevel = reservedLevels.length > 0 ? Math.max(...reservedLevels) : Number.MAX_SAFE_INTEGER;\n  if (narrowestLevel < SCOPE_ORDER[maxScope]) {\n    throw new Error(`Knowledge scope exceeds ${maxScope} ceiling`);\n  }\n}\n\nexport function assertKnowledgeCeilingRaised(\n  currentMaxScope: KnowledgeScopeLevel | undefined,\n  maxScope: KnowledgeScopeLevel | undefined,\n): void {\n  if (currentMaxScope && maxScope && SCOPE_ORDER[maxScope] > SCOPE_ORDER[currentMaxScope]) {\n    throw new Error(`Knowledge ceiling cannot be lowered from ${currentMaxScope} to ${maxScope}`);\n  }\n}\n\nexport function parseKnowledgeWikilinks(text: string): string[] {\n  const names: string[] = [];\n  const seen = new Set<string>();\n  let contentStart = -1;\n\n  for (let index = 0; index < text.length - 1; index++) {\n    const pair = text.slice(index, index + 2);\n    if (pair === '[[') {\n      contentStart = index + 2;\n      index++;\n      continue;\n    }\n    if (pair !== ']]' || contentStart < 0) continue;\n\n    const name = text.slice(contentStart, index).trim();","sourceCodeStart":358,"sourceCodeEnd":394,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/packages/core/src/storage/domains/knowledge/base.ts#L358-L394","documentation":"Ceilings are monotonic: once a knowledge record's maxScope is set, assertKnowledgeCeilingRaised (used by raiseKnowledgeCeiling) only permits keeping or widening it. Since levels order org=0 < resource=1 < thread=2, a proposed maxScope with a higher order value means narrowing the ceiling, which is rejected to preserve previously-granted visibility guarantees.","triggerScenarios":"raiseKnowledgeCeiling({ id, maxScope: 'org' }) on a record whose current maxScope is 'resource' or 'thread'; passing a stale record snapshot's maxScope (older, narrower) back to the API; any attempt to tighten the ceiling after initially granting a wider one.","commonSituations":"Round-tripping a fetched record and accidentally writing back its old maxScope; admin tooling trying to revoke thread-level access (unsupported — must be handled by deletion/rescope of content instead); new ceiling set to the record's own creation level, which equals current, fine — but smaller than current throws.","solutions":["Only call raiseKnowledgeCeiling with the same level or a WIDER level (thread -> resource -> org direction).","Fetch the current record first and skip the call if the desired maxScope equals the current one.","If access must be reduced, rescope the record's scope or remove/restore records rather than lowering the ceiling.","Fix stale-snapshot bugs: always read the latest record state before issuing ceiling updates."],"exampleFix":"// before\nawait storage.raiseKnowledgeCeiling({ id, maxScope: 'org' }); // current is 'thread'\n// after\nconst rec = await storage.getKnowledge({ id });\nif (rec?.maxScope !== 'org') {\n  // narrowing unsupported; rescope or delete instead\n}","handlingStrategy":"try-catch","validationCode":"const ORDER = { org: 0, resource: 1, thread: 2 } as const;\nfunction isCeilingRaise(current?: keyof typeof ORDER, next?: keyof typeof ORDER): boolean {\n  if (!current || !next) return true;\n  return ORDER[next] <= ORDER[current]; // equal or wider is allowed\n}","typeGuard":null,"tryCatchPattern":"try {\n  await storage.raiseKnowledgeCeiling({ id, maxScope });\n} catch (e) {\n  if (e instanceof Error && e.message.startsWith('Knowledge ceiling cannot be lowered')) {\n    const rec = await storage.getKnowledge({ id });\n    // treat as no-op or handle reduction via rescope/delete; never lower the ceiling\n  } else throw e;\n}","preventionTips":["Fetch the record and compare maxScope before calling raiseKnowledgeCeiling.","Skip the call when the desired ceiling equals the current one.","Never write a stale maxScope back from an old record snapshot.","Reduce access via rescopeKnowledge or record removal, not ceiling lowering."],"tags":["knowledge","ceiling","monotonic-state","validation"],"backgroundTag":"invalid-state-transition","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}