From ec0f6be4e83fd08706ad4a11d14524f76f48876c Mon Sep 17 00:00:00 2001 From: jlin53882 Date: Sat, 25 Apr 2026 01:16:56 +0800 Subject: [PATCH 1/5] fix: isOwnedByAgent fail-closed for malformed itemKind (#448) --- src/reflection-store.ts | Bin 21129 -> 45086 bytes test/isOwnedByAgent.test.mjs | 65 +++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 test/isOwnedByAgent.test.mjs diff --git a/src/reflection-store.ts b/src/reflection-store.ts index 38da5ce791e7693e568bd3870ef8b94650f15454..577a6b36c6da236a7e6189059667150a0b733ef0 100644 GIT binary patch literal 45086 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 literal 21129 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 diff --git a/test/isOwnedByAgent.test.mjs b/test/isOwnedByAgent.test.mjs new file mode 100644 index 00000000..b23e4c4b --- /dev/null +++ b/test/isOwnedByAgent.test.mjs @@ -0,0 +1,65 @@ +// isOwnedByAgent unit tests — Issue #448 fix verification +import { describe, it } from "node:test"; +import assert from "node:assert"; + +// From reflection-store.ts: isOwnedByAgent function for isolated testing +function isOwnedByAgent(metadata, agentId) { + const owner = typeof metadata.agentId === "string" ? metadata.agentId.trim() : ""; + + const itemKind = metadata.itemKind; + + // derived: no main fallback, empty owner -> completely invisible + if (itemKind === "derived") { + if (!owner) return false; + return owner === agentId; + } + + // invariant / legacy / mapped: maintain original main fallback + if (!owner) return true; + return owner === agentId || owner === "main"; +} + +describe("isOwnedByAgent — derived ownership fix (Issue #448)", () => { + describe("itemKind === 'derived'", () => { + it("main's derived -> main visible", () => { + assert.strictEqual(isOwnedByAgent({ itemKind: "derived", agentId: "main" }, "main"), true); + }); + it("main's derived -> sub-agent invisible (core bug fix)", () => { + assert.strictEqual(isOwnedByAgent({ itemKind: "derived", agentId: "main" }, "sub-agent-A"), false); + }); + it("agent-x's derived -> agent-x visible", () => { + assert.strictEqual(isOwnedByAgent({ itemKind: "derived", agentId: "agent-x" }, "agent-x"), true); + }); + it("agent-x's derived -> agent-y invisible", () => { + assert.strictEqual(isOwnedByAgent({ itemKind: "derived", agentId: "agent-x" }, "agent-y"), false); + }); + it("derived + empty owner -> completely invisible (guard)", () => { + assert.strictEqual(isOwnedByAgent({ itemKind: "derived", agentId: "" }, "main"), false); + assert.strictEqual(isOwnedByAgent({ itemKind: "derived", agentId: "" }, "sub-agent"), false); + }); + }); + + describe("itemKind === 'invariant' (maintain fallback)", () => { + it("main's invariant -> sub-agent visible", () => { + assert.strictEqual(isOwnedByAgent({ itemKind: "invariant", agentId: "main" }, "sub-agent-A"), true); + }); + it("agent-x's invariant -> agent-x visible", () => { + assert.strictEqual(isOwnedByAgent({ itemKind: "invariant", agentId: "agent-x" }, "agent-x"), true); + }); + it("agent-x's invariant -> agent-y invisible", () => { + assert.strictEqual(isOwnedByAgent({ itemKind: "invariant", agentId: "agent-x" }, "agent-y"), false); + }); + }); + + describe("legacy / mapped (no itemKind, maintain fallback)", () => { + it("main legacy -> sub-agent visible", () => { + assert.strictEqual(isOwnedByAgent({ agentId: "main" }, "sub-agent-A"), true); + }); + it("agent-x legacy -> agent-x visible", () => { + assert.strictEqual(isOwnedByAgent({ agentId: "agent-x" }, "agent-x"), true); + }); + it("agent-x legacy -> agent-y invisible", () => { + assert.strictEqual(isOwnedByAgent({ agentId: "agent-x" }, "agent-y"), false); + }); + }); +}); \ No newline at end of file From 7249252fa709542e8e32dda687d9da05d3cbc1af Mon Sep 17 00:00:00 2001 From: jlin53882 Date: Mon, 27 Apr 2026 00:49:31 +0800 Subject: [PATCH 2/5] fix(register): move _registeredApis.add after successful _initPluginState Resolves reviewer Must-Fix #1 from PR #522: - _registeredApis.add(api) was called BEFORE _initPluginState. If _initPluginState threw, the api was already in the WeakSet, permanently blocking any retry from the same api instance. - Fix: wrap _initPluginState in try-catch; add api to WeakSet only after successful init. If init fails, api stays out of WeakSet, allowing subsequent register() calls with the same api to retry. Fixes CortexReach/memory-lancedb-pro#448 --- index.ts | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/index.ts b/index.ts index 25b2012f..28829715 100644 --- a/index.ts +++ b/index.ts @@ -1899,7 +1899,6 @@ const memoryLanceDBProPlugin = { api.logger.debug?.("memory-lancedb-pro: register() called again — skipping re-init (idempotent)"); return; } - _registeredApis.add(api); // Parse and validate configuration // ======================================================================== @@ -1908,8 +1907,22 @@ const memoryLanceDBProPlugin = { // the same singleton via destructuring. This prevents: // - Memory heap growth from repeated resource creation (~9 calls/process) // - Accumulated session Maps being lost on re-registration + // + // IMPORTANT: _registeredApis.add(api) is called AFTER successful init. + // This ensures that if _initPluginState throws, the api is NOT in the + // WeakSet, allowing a subsequent register() call with the same api to retry. + // (The old placement — before init — caused permanent breakage on init failure.) // ======================================================================== - if (!_singletonState) { _singletonState = _initPluginState(api); } + let singleton: typeof _singletonState; + try { + if (!_singletonState) { _singletonState = _initPluginState(api); } + singleton = _singletonState; + } catch (err) { + api.logger.error(`memory-lancedb-pro: _initPluginState failed — ${String(err)}`); + throw err; + } + _registeredApis.add(api); + const { config, resolvedDbPath, @@ -1930,7 +1943,7 @@ const memoryLanceDBProPlugin = { autoCaptureSeenTextCount, autoCapturePendingIngressTexts, autoCaptureRecentTexts, - } = _singletonState; + } = singleton; async function sleep(ms: number): Promise { From 0c8ee9b173b61042f4cd6f67e906473895f5e71b Mon Sep 17 00:00:00 2001 From: jlin53882 Date: Mon, 27 Apr 2026 01:25:37 +0800 Subject: [PATCH 3/5] fix: re-encode reflection-store.ts as UTF-8 (UTF-16 corruption from ec0f6be) --- src/reflection-store.ts | Bin 45086 -> 22414 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/src/reflection-store.ts b/src/reflection-store.ts index 577a6b36c6da236a7e6189059667150a0b733ef0..eb16189d795539e2750ce006f7b4ec1c2747953b 100644 GIT binary patch literal 22414 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 bJUig1BVX1$b)Js;bEW7wL$1mz0>1tq$?vKd literal 45086 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 From 33d0f55695668258cb3f51c9fec2d9b5b3de436b Mon Sep 17 00:00:00 2001 From: jlin53882 Date: Tue, 28 Apr 2026 00:36:30 +0800 Subject: [PATCH 4/5] fix: normalize isOwnedByAgent encoding + wire test to production source - Issue 1: split 'if' from comment line + add missing fallback block - Issue 2: test now imports from ../src/reflection-store.ts (no local copy) - Issue 3: normalize all CR to LF (UTF-8 + LF) Fixes reviewer Must-Fix from PR #522 --- src/reflection-store.ts | 36 ++++++---- test/isOwnedByAgent.test.mjs | 133 ++++++++++++++++++----------------- 2 files changed, 90 insertions(+), 79 deletions(-) diff --git a/src/reflection-store.ts b/src/reflection-store.ts index eb16189d..56a928a4 100644 --- a/src/reflection-store.ts +++ b/src/reflection-store.ts @@ -426,20 +426,28 @@ function isReflectionMetadataType(type: unknown): boolean { return type === "memory-reflection-item" || type === "memory-reflection"; } -function isOwnedByAgent(metadata: Record, agentId: string): boolean { - const owner = typeof metadata.agentId === "string" ? metadata.agentId.trim() : ""; - - // itemKind ?芸??冽 memory-reflection-item 憿? - // legacy (memory-reflection) ??mapped (memory-reflection-mapped) ?賣???itemKind - // ?迨 undefined !== "derived"嚗?韏啣??祉? main fallback嚗雁?摰對? - const itemKind = metadata.itemKind; - - // 憒???derived ?嚗emory-reflection-item嚗?銝? main fallback嚗? // 銝?derived 銝?閮梁征??owner嚗征??owner ??derived ???其??航?嚗甇X援瞍? - // itemKind 敹???string type嚗???航炊?脣 derived ?嚗ull/undefined/number 蝑?韏?legacy fallback嚗? if (typeof itemKind === "string" && itemKind === "derived") { - if (!owner) return false; - return owner === agentId; - } - +export function isOwnedByAgent(metadata: Record, agentId: string): boolean { + const owner = typeof metadata.agentId === "string" ? metadata.agentId.trim() : ""; + + const itemKind = metadata.itemKind; + + // itemKind 只存在於 memory-reflection-item(derived | invariant) + // legacy (memory-reflection) 和 mapped (memory-reflection-mapped) 沒有 itemKind + // 因此 undefined !== "derived",會走 main fallback(維護相容性) + + // 若是 derived 項目(memory-reflection-item):不做 main fallback, + // 且 derived 不允許空白 owner(空白 owner 的 derived 應完全不可見,防止洩漏) + // itemKind 必須是 string type,否則會錯誤進入 derived 分支 + // (null/undefined/number 等非 string 值應走 legacy fallback) + if (typeof itemKind === "string" && itemKind === "derived") { + if (!owner) return false; + return owner === agentId; + } + + // Invariant / legacy / mapped:允許空的 owner 通行,維護舊的 main fallback + if (!owner) return true; + return owner === agentId || owner === "main"; +} function toStringArray(value: unknown): string[] { if (!Array.isArray(value)) return []; diff --git a/test/isOwnedByAgent.test.mjs b/test/isOwnedByAgent.test.mjs index b23e4c4b..70c5146c 100644 --- a/test/isOwnedByAgent.test.mjs +++ b/test/isOwnedByAgent.test.mjs @@ -1,65 +1,68 @@ -// isOwnedByAgent unit tests — Issue #448 fix verification -import { describe, it } from "node:test"; -import assert from "node:assert"; - -// From reflection-store.ts: isOwnedByAgent function for isolated testing -function isOwnedByAgent(metadata, agentId) { - const owner = typeof metadata.agentId === "string" ? metadata.agentId.trim() : ""; - - const itemKind = metadata.itemKind; - - // derived: no main fallback, empty owner -> completely invisible - if (itemKind === "derived") { - if (!owner) return false; - return owner === agentId; - } - - // invariant / legacy / mapped: maintain original main fallback - if (!owner) return true; - return owner === agentId || owner === "main"; -} - -describe("isOwnedByAgent — derived ownership fix (Issue #448)", () => { - describe("itemKind === 'derived'", () => { - it("main's derived -> main visible", () => { - assert.strictEqual(isOwnedByAgent({ itemKind: "derived", agentId: "main" }, "main"), true); - }); - it("main's derived -> sub-agent invisible (core bug fix)", () => { - assert.strictEqual(isOwnedByAgent({ itemKind: "derived", agentId: "main" }, "sub-agent-A"), false); - }); - it("agent-x's derived -> agent-x visible", () => { - assert.strictEqual(isOwnedByAgent({ itemKind: "derived", agentId: "agent-x" }, "agent-x"), true); - }); - it("agent-x's derived -> agent-y invisible", () => { - assert.strictEqual(isOwnedByAgent({ itemKind: "derived", agentId: "agent-x" }, "agent-y"), false); - }); - it("derived + empty owner -> completely invisible (guard)", () => { - assert.strictEqual(isOwnedByAgent({ itemKind: "derived", agentId: "" }, "main"), false); - assert.strictEqual(isOwnedByAgent({ itemKind: "derived", agentId: "" }, "sub-agent"), false); - }); - }); - - describe("itemKind === 'invariant' (maintain fallback)", () => { - it("main's invariant -> sub-agent visible", () => { - assert.strictEqual(isOwnedByAgent({ itemKind: "invariant", agentId: "main" }, "sub-agent-A"), true); - }); - it("agent-x's invariant -> agent-x visible", () => { - assert.strictEqual(isOwnedByAgent({ itemKind: "invariant", agentId: "agent-x" }, "agent-x"), true); - }); - it("agent-x's invariant -> agent-y invisible", () => { - assert.strictEqual(isOwnedByAgent({ itemKind: "invariant", agentId: "agent-x" }, "agent-y"), false); - }); - }); - - describe("legacy / mapped (no itemKind, maintain fallback)", () => { - it("main legacy -> sub-agent visible", () => { - assert.strictEqual(isOwnedByAgent({ agentId: "main" }, "sub-agent-A"), true); - }); - it("agent-x legacy -> agent-x visible", () => { - assert.strictEqual(isOwnedByAgent({ agentId: "agent-x" }, "agent-x"), true); - }); - it("agent-x legacy -> agent-y invisible", () => { - assert.strictEqual(isOwnedByAgent({ agentId: "agent-x" }, "agent-y"), false); - }); - }); -}); \ No newline at end of file +// isOwnedByAgent unit tests — Issue #448 fix verification +import { describe, it } from "node:test"; +import assert from "node:assert"; + +// Import from production source — NOT a local copy +import { isOwnedByAgent } from "../src/reflection-store.ts"; + +describe("isOwnedByAgent — derived ownership fix (Issue #448)", () => { + describe("itemKind === 'derived' (memory-reflection-item)", () => { + it("main's derived -> main visible", () => { + assert.strictEqual(isOwnedByAgent({ itemKind: "derived", agentId: "main" }, "main"), true); + }); + it("main's derived -> sub-agent invisible (core bug fix)", () => { + assert.strictEqual(isOwnedByAgent({ itemKind: "derived", agentId: "main" }, "sub-agent-A"), false); + }); + it("agent-x's derived -> agent-x visible", () => { + assert.strictEqual(isOwnedByAgent({ itemKind: "derived", agentId: "agent-x" }, "agent-x"), true); + }); + it("agent-x's derived -> agent-y invisible", () => { + assert.strictEqual(isOwnedByAgent({ itemKind: "derived", agentId: "agent-x" }, "agent-y"), false); + }); + it("derived + empty owner -> completely invisible (guard)", () => { + assert.strictEqual(isOwnedByAgent({ itemKind: "derived", agentId: "" }, "main"), false); + assert.strictEqual(isOwnedByAgent({ itemKind: "derived", agentId: "" }, "sub-agent"), false); + }); + }); + + describe("itemKind === 'invariant' (maintain fallback)", () => { + it("main's invariant -> sub-agent visible", () => { + assert.strictEqual(isOwnedByAgent({ itemKind: "invariant", agentId: "main" }, "sub-agent-A"), true); + }); + it("agent-x's invariant -> agent-x visible", () => { + assert.strictEqual(isOwnedByAgent({ itemKind: "invariant", agentId: "agent-x" }, "agent-x"), true); + }); + it("agent-x's invariant -> agent-y invisible", () => { + assert.strictEqual(isOwnedByAgent({ itemKind: "invariant", agentId: "agent-x" }, "agent-y"), false); + }); + }); + + describe("legacy / mapped (no itemKind — maintain fallback)", () => { + it("main legacy -> sub-agent visible", () => { + assert.strictEqual(isOwnedByAgent({ agentId: "main" }, "sub-agent-A"), true); + }); + it("agent-x legacy -> agent-x visible", () => { + assert.strictEqual(isOwnedByAgent({ agentId: "agent-x" }, "agent-x"), true); + }); + it("agent-x legacy -> agent-y invisible", () => { + assert.strictEqual(isOwnedByAgent({ agentId: "agent-x" }, "agent-y"), false); + }); + }); + + describe("malformed itemKind (fail-closed)", () => { + // When itemKind is not a string, it cannot be "derived" (typeof guard) + // Falls through to fallback: empty owner -> true, owner-main -> true + it("itemKind = null falls through to fallback (main -> sub visible)", () => { + assert.strictEqual(isOwnedByAgent({ itemKind: null, agentId: "main" }, "sub-agent-A"), true); + }); + it("itemKind = undefined falls through to fallback (main -> sub visible)", () => { + assert.strictEqual(isOwnedByAgent({ itemKind: undefined, agentId: "main" }, "sub-agent-A"), true); + }); + it("itemKind = number falls through to fallback (main -> sub visible)", () => { + assert.strictEqual(isOwnedByAgent({ itemKind: 42, agentId: "main" }, "sub-agent-A"), true); + }); + it("itemKind = non-derived string falls through to fallback (main -> sub visible)", () => { + assert.strictEqual(isOwnedByAgent({ itemKind: "weird-kind", agentId: "main" }, "sub-agent-A"), true); + }); + }); +}); From b02f15c080d8e1766a7704943805a55eac2fcfe1 Mon Sep 17 00:00:00 2001 From: Hermes Agent Date: Tue, 28 Apr 2026 12:43:53 +0800 Subject: [PATCH 5/5] fix: block main derived bleed via legacy fallback in buildDerivedCandidates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Layer 1 of 2 — blocks the read-path leak without changing write behavior. Problem: buildDerivedCandidates() falls back to legacyRows when itemCandidates is empty. combined-legacy rows (written by buildLegacyCombinedPayload) carry metadata.derived but have no itemKind, so isOwnedByAgent accepts owner==='main' for any sub-agent — causing context bleed. Fix: before the legacy flatMap, filter out rows where: - derived content exists (hasDerivedContent) - AND owner is 'main' (owner === 'main' → reject) - AND owner is empty (empty owner with derived → reject) - AND owner does not match querying agentId Pure legacy invariants (no derived) are unaffected. Added: buildDerivedCandidates-legacy-fallback.test.mjs with 7 cases covering all fallback paths including the main→sub-agent bleed (case A). Review: https://github.com/CortexReach/memory-lancedb-pro/pull/522#pullrequestreview-4185627472 --- src/reflection-store.ts | 70 +++++++----- ...DerivedCandidates-legacy-fallback.test.mjs | 106 ++++++++++++++++++ 2 files changed, 148 insertions(+), 28 deletions(-) create mode 100644 test/buildDerivedCandidates-legacy-fallback.test.mjs diff --git a/src/reflection-store.ts b/src/reflection-store.ts index 56a928a4..d51a268b 100644 --- a/src/reflection-store.ts +++ b/src/reflection-store.ts @@ -253,7 +253,7 @@ export function loadAgentReflectionSlicesFromEntries(params: LoadReflectionSlice const legacyRows = reflectionRows.filter(({ metadata }) => metadata.type === "memory-reflection"); const invariantCandidates = buildInvariantCandidates(itemRows, legacyRows); - const derivedCandidates = buildDerivedCandidates(itemRows, legacyRows); + const derivedCandidates = buildDerivedCandidates(itemRows, legacyRows, params.agentId); const invariants = rankReflectionLines(invariantCandidates, { now, @@ -322,33 +322,47 @@ function buildInvariantCandidates( }); } -function buildDerivedCandidates( - itemRows: Array<{ entry: MemoryEntry; metadata: Record }>, - legacyRows: Array<{ entry: MemoryEntry; metadata: Record }> -): WeightedLineCandidate[] { - const itemCandidates = itemRows - .filter(({ metadata }) => metadata.itemKind === "derived") - .flatMap(({ entry, metadata }) => { - const lines = sanitizeReflectionSliceLines([entry.text]); - const safeLines = sanitizeInjectableReflectionLines([entry.text]); - if (safeLines.length === 0) return []; - - const defaults = getReflectionItemDecayDefaults("derived"); - const timestamp = metadataTimestamp(metadata, entry.timestamp); - return safeLines.map((line) => ({ - line, - timestamp, - midpointDays: readPositiveNumber(metadata.decayMidpointDays, defaults.midpointDays), - k: readPositiveNumber(metadata.decayK, defaults.k), - baseWeight: readPositiveNumber(metadata.baseWeight, defaults.baseWeight), - quality: readClampedNumber(metadata.quality, defaults.quality, 0.2, 1), - usedFallback: metadata.usedFallback === true, - })); - }); - - if (itemCandidates.length > 0) return itemCandidates; - - return legacyRows.flatMap(({ entry, metadata }) => { +function buildDerivedCandidates( + itemRows: Array<{ entry: MemoryEntry; metadata: Record }>, + legacyRows: Array<{ entry: MemoryEntry; metadata: Record }>, + agentId: string +): WeightedLineCandidate[] { + const itemCandidates = itemRows + .filter(({ metadata }) => metadata.itemKind === "derived") + .flatMap(({ entry, metadata }) => { + const lines = sanitizeReflectionSliceLines([entry.text]); + const safeLines = sanitizeInjectableReflectionLines([entry.text]); + if (safeLines.length === 0) return []; + + const defaults = getReflectionItemDecayDefaults("derived"); + const timestamp = metadataTimestamp(metadata, entry.timestamp); + return safeLines.map((line) => ({ + line, + timestamp, + midpointDays: readPositiveNumber(metadata.decayMidpointDays, defaults.midpointDays), + k: readPositiveNumber(metadata.decayK, defaults.k), + baseWeight: readPositiveNumber(metadata.baseWeight, defaults.baseWeight), + quality: readClampedNumber(metadata.quality, defaults.quality, 0.2, 1), + usedFallback: metadata.usedFallback === true, + })); + }); + + if (itemCandidates.length > 0) return itemCandidates; + + // ★ 修復:legacy fallback 中,有 derived 內容的 row(來自 combined-legacy), + // 如果 owner 是 "main",則對 sub-agent 不可見,防止 context bleed。 + // 純 legacy invariant(無 derived)不受影響,正常可見。 + return legacyRows + .filter(({ metadata }) => { + const derived = metadata.derived; + const hasDerivedContent = Array.isArray(derived) && derived.length > 0; + if (!hasDerivedContent) return true; // 無 derived → 正常 legacy invariant + const owner = typeof metadata.agentId === "string" ? metadata.agentId.trim() : ""; + if (!owner) return false; // 有 derived 但無 owner → 不可見 + if (owner === "main") return false; // ★ main 的 derived 不外流 + return owner === agentId; // 其他 agent 的 derived → 限本人 + }) + .flatMap(({ entry, metadata }) => { const timestamp = metadataTimestamp(metadata, entry.timestamp); const lines = sanitizeInjectableReflectionLines(toStringArray(metadata.derived)); if (lines.length === 0) return []; diff --git a/test/buildDerivedCandidates-legacy-fallback.test.mjs b/test/buildDerivedCandidates-legacy-fallback.test.mjs new file mode 100644 index 00000000..9f7b3321 --- /dev/null +++ b/test/buildDerivedCandidates-legacy-fallback.test.mjs @@ -0,0 +1,106 @@ +// Regression test:combined-legacy fallback 不應讓 sub-agent 看到 main derived +// 對應 PR: https://github.com/CortexReach/memory-lancedb-pro/pull/522 +// Review: https://github.com/CortexReach/memory-lancedb-pro/pull/522#pullrequestreview-4185627472 +// +// 問題:buildDerivedCandidates() 在 itemCandidates 為空時, +// fallback 到 legacyRows。此時 combined-legacy row(無 itemKind, +// 但有 metadata.derived)被視為 legacy,isOwnedByAgent 接受 owner="main", +// 導致 sub-agent 可以看到 main 的 derived 內容。 +// +// 修復:在 legacy fallback 前,先過濾掉 owner="main" 且有 derived 內容的 legacy row。 + +import { describe, it } from "node:test"; +import assert from "node:assert"; +import { buildDerivedCandidates } from "../src/reflection-store.ts"; + +// 輔助:模擬 legacy row(type="memory-reflection",來自 buildLegacyCombinedPayload) +function makeLegacyRow(agentId, derived = []) { + return { + entry: { text: "dummy legacy", timestamp: Date.now() }, + metadata: { type: "memory-reflection", agentId, derived }, + }; +} + +// 輔助:模擬 item-derived row(type="memory-reflection-item") +function makeItemRow(agentId, itemKind = "derived") { + return { + entry: { text: "dummy item", timestamp: Date.now() }, + metadata: { type: "memory-reflection-item", agentId, itemKind }, + }; +} + +describe("buildDerivedCandidates — combined-legacy fallback ownership (regression)", () => { + describe("itemCandidates 為空,fallback 到 legacy rows", () => { + it("main 的 derived legacy row → sub-agent 不可見(核心修復)", () => { + const itemRows = []; + const legacyRows = [makeLegacyRow("main", ["some derived content"])]; + const candidates = buildDerivedCandidates(itemRows, legacyRows, "sub-agent-A"); + assert.strictEqual( + candidates.length, + 0, + "sub-agent 不該看到 main 的 derived fallback" + ); + }); + + it("main 的 derived legacy row → main 自己可見", () => { + const itemRows = []; + const legacyRows = [makeLegacyRow("main", ["main's derived content"])]; + const candidates = buildDerivedCandidates(itemRows, legacyRows, "main"); + assert.ok( + candidates.length > 0, + "main 應看到自己的 derived fallback" + ); + }); + + it("agent-x 的 derived legacy row → agent-x 自己可見", () => { + const itemRows = []; + const legacyRows = [makeLegacyRow("agent-x", ["x's derived"])]; + const candidates = buildDerivedCandidates(itemRows, legacyRows, "agent-x"); + assert.ok(candidates.length > 0, "agent-x 應看到自己的 derived fallback"); + }); + + it("agent-x 的 derived legacy row → agent-y 不可見", () => { + const itemRows = []; + const legacyRows = [makeLegacyRow("agent-x", ["x's derived"])]; + const candidates = buildDerivedCandidates(itemRows, legacyRows, "agent-y"); + assert.strictEqual( + candidates.length, + 0, + "agent-y 不該看到 agent-x 的 derived fallback" + ); + }); + + it("純 legacy invariant(無 derived)→ sub-agent 可見(不應被阻擋)", () => { + const itemRows = []; + const legacyRows = [makeLegacyRow("main", [])]; // 無 derived + const candidates = buildDerivedCandidates(itemRows, legacyRows, "sub-agent-A"); + assert.ok( + candidates.length > 0, + "sub-agent 應看到純 legacy invariant(無 derived 不應阻擋)" + ); + }); + + it("有 item-derived candidates 時,不走 legacy fallback(item path 優先)", () => { + // item-derived row(main 寫的)會被 isOwnedByAgent 擋掉, + // 所以傳入 buildDerivedCandidates 時 itemRows 已是過濾後的狀態(不含 main 的 derived) + const itemRows = [makeItemRow("sub-agent-B", "derived")]; + const legacyRows = [makeLegacyRow("main", ["should be ignored"])]; + const candidates = buildDerivedCandidates(itemRows, legacyRows, "sub-agent-A"); + assert.ok( + candidates.length === 0, + "item-derived 已被隔離,sub-agent-A 不應看到任何 derived" + ); + }); + + it("有 derived 但 owner 為空字串 → 不可見(防禦性)", () => { + const itemRows = []; + const legacyRows = [makeLegacyRow("", ["some content"])]; + const candidates = buildDerivedCandidates(itemRows, legacyRows, "sub-agent-A"); + assert.strictEqual( + candidates.length, + 0, + "owner 為空的有 derived legacy row 不應對任何 agent 可見" + ); + }); + }); +});