{"record":{"id":"e6482d6bfb3c7046","repo":"JuliusBrussee/caveman","slug":"assembly-slot-json-stringify-slot-id-has-unkno","errorCode":null,"errorMessage":"assembly slot ${JSON.stringify(slot.id)} has unknown stability ${JSON.stringify(slot.stability)}","messagePattern":"assembly slot (.+?) has unknown stability (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/sdk/typescript/src/index.ts","lineNumber":1831,"sourceCode":"function assemble(cave: Cave, options: AssembleOptions): AssemblyResult {\n  const provider = options.provider.trim().toLowerCase();\n  const emitCacheHints = options.emitCacheHints ?? \"gateway\";\n  if (!options.model || !options.sessionId) throw new Error(\"assemble requires model and sessionId\");\n  if (![\"gateway\", \"self\", \"none\"].includes(emitCacheHints)) {\n    throw new Error(`unknown emitCacheHints value ${JSON.stringify(emitCacheHints)}`);\n  }\n\n  const ids = new Set<string>();\n  let ledger = assemblyHashLedgers.get(cave);\n  if (!ledger) {\n    ledger = new Map<string, string>();\n    assemblyHashLedgers.set(cave, ledger);\n  }\n  const normalized = options.slots.map((slot) => {\n    if (!slot.id || ids.has(slot.id)) throw new Error(`assembly slot id must be non-empty and unique: ${JSON.stringify(slot.id)}`);\n    ids.add(slot.id);\n    if (![\"stable\", \"session\", \"volatile\"].includes(slot.stability)) {\n      throw new Error(`assembly slot ${JSON.stringify(slot.id)} has unknown stability ${JSON.stringify(slot.stability)}`);\n    }\n    let contentJSON: string;\n    try {\n      contentJSON = canonicalJSON(slot.content);\n    } catch {\n      throw new Error(`assembly slot ${JSON.stringify(slot.id)} content is not JSON-serializable`);\n    }\n    const contentHash = sha256Hex(contentJSON);\n    if (slot.stability !== \"volatile\") {\n      const key = `${options.sessionId}\\0${slot.id}`;\n      const previous = ledger.get(key);\n      if (previous !== undefined && previous !== contentHash) throw new AssemblyStabilityError(slot.id);\n      ledger.set(key, contentHash);\n    }\n    return { ...slot, content: JSON.parse(contentJSON) as unknown };\n  });\n\n  const ordered = ([\"stable\", \"session\", \"volatile\"] as const)","sourceCodeStart":1813,"sourceCodeEnd":1849,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/sdk/typescript/src/index.ts#L1813-L1849","documentation":"Each assembly slot's `stability` must be one of the declared lifetimes \"stable\", \"session\", or \"volatile\"; it drives how content hashes are tracked across requests in the per-session ledger. assemble() throws this Error when a slot's stability string is not in that set.","triggerScenarios":"Passing `stability` values like \"permanent\", \"stable \" (trailing space), \"Stable\", or `undefined` — possible when stability is computed from config, or from a JS caller constructing slots without the field.","commonSituations":"Custom abstractions over the SDK mapping their own stability vocabulary to the SDK's three tiers and missing the mapping; JSON-driven slot definitions with free-form stability strings; typos like \"volatile\" vs \"volitile\".","solutions":["Use exactly \"stable\", \"session\", or \"volatile\" for each slot's stability.","Map your app's stability vocabulary explicitly (e.g. `immortal -> \"stable\"`) instead of passing your own strings through.","Trim/lowercase values sourced from config before assigning them.","Type slot literals as AssemblySlot so the stability union is enforced at compile time."],"exampleFix":"// before\nslots.push({ id, stability: cfg.ttl === Infinity ? \"forever\" : \"session\", content });\n\n// after\nconst stability = cfg.ttl === Infinity ? \"stable\" : cfg.ttl === 0 ? \"volatile\" : \"session\";\nslots.push({ id, stability, content });","handlingStrategy":"type-guard","validationCode":"const STABILITIES = new Set([\"stable\", \"session\", \"volatile\"]);\nfunction validateStabilities(slots) {\n  for (const s of slots) if (!STABILITIES.has(s.stability)) throw new Error(`slot ${s.id}: bad stability ${s.stability}`);\n}","typeGuard":"function isAssemblyStability(v) {\n  return v === \"stable\" || v === \"session\" || v === \"volatile\";\n}","tryCatchPattern":"try {\n  return cave.assemble(opts);\n} catch (e) {\n  const m = e.message.match(/has unknown stability (\".*\")/);\n  if (m) {\n    console.error(`Fix slot stability value ${m[1]} to stable|session|volatile`);\n  }\n  throw e;\n}","preventionTips":["Annotate slot literals as AssemblySlot[] so the stability union is checked at compile time.","Map your app's TTL vocabulary to the three SDK tiers in one place.","Never pass raw config strings as stability without normalization."],"tags":["validation","enum","assembly","slots"],"backgroundTag":"invalid-enum-value","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}