diff --git a/src/functions/slots.ts b/src/functions/slots.ts index 47b49d496..d1d49e999 100644 --- a/src/functions/slots.ts +++ b/src/functions/slots.ts @@ -159,7 +159,21 @@ async function seedDefaults(kv: StateKV): Promise { for (const tmpl of DEFAULT_SLOTS) { const target = scopeKv(tmpl.scope); const existing = await kv.get(target, tmpl.label); - if (existing) continue; + if (existing) { + if (existing.pinned !== tmpl.pinned) { + await kv.set(target, tmpl.label, { + ...existing, + pinned: tmpl.pinned, + updatedAt: ts, + }); + await recordAudit(kv, "slot_seed_reconcile", "seedDefaults", [tmpl.label], { + scope: tmpl.scope, + fromPinned: existing.pinned, + toPinned: tmpl.pinned, + }); + } + continue; + } const slot: MemorySlot = { ...tmpl, createdAt: ts, diff --git a/src/types.ts b/src/types.ts index 6797dfaf9..e6506c045 100644 --- a/src/types.ts +++ b/src/types.ts @@ -608,7 +608,8 @@ export interface AuditEntry { | "slot_replace" | "slot_create" | "slot_delete" - | "slot_reflect"; + | "slot_reflect" + | "slot_seed_reconcile"; userId?: string; functionId: string; targetIds: string[]; diff --git a/test/slots.test.ts b/test/slots.test.ts index 70da2aed1..9c9e7ad15 100644 --- a/test/slots.test.ts +++ b/test/slots.test.ts @@ -255,3 +255,59 @@ describe("slots — reflect", () => { expect(patterns.slot.content).toMatch(/errors: 2/); }); }); + + +describe("slots — seedDefaults reconciles pinned on existing default-label slots", () => { + function wireWith(kv: ReturnType) { + const handlers: Record) => Promise>> = {}; + const sdk = { + registerFunction: vi.fn((id: string, cb) => { + handlers[id] = cb; + }), + } as unknown as import("iii-sdk").ISdk; + registerSlotsFunctions(sdk, kv as never); + return handlers; + } + + it("re-pins a default-label slot that was stored with pinned:false, preserving content", async () => { + const kv = mockKV(); + await kv.set(KV.globalSlots, "persona", { + label: "persona", + content: "senior engineer persona", + sizeLimit: 1000, + description: "old description", + pinned: false, + readOnly: false, + scope: "global", + createdAt: "2026-01-01T00:00:00.000Z", + updatedAt: "2026-01-01T00:00:00.000Z", + }); + wireWith(kv); + await waitForSeed(kv); + const persona = (await kv.get(KV.globalSlots, "persona")) as { + pinned: boolean; + content: string; + }; + expect(persona.pinned).toBe(true); + expect(persona.content).toBe("senior engineer persona"); + }); + + it("leaves a non-default custom slot untouched", async () => { + const kv = mockKV(); + await kv.set(KV.slots, "my_custom", { + label: "my_custom", + content: "custom", + sizeLimit: 2000, + description: "", + pinned: false, + readOnly: false, + scope: "project", + createdAt: "2026-01-01T00:00:00.000Z", + updatedAt: "2026-01-01T00:00:00.000Z", + }); + wireWith(kv); + await waitForSeed(kv); + const custom = (await kv.get(KV.slots, "my_custom")) as { pinned: boolean }; + expect(custom.pinned).toBe(false); + }); +}); \ No newline at end of file