fix(memory): inspect selected capability in shadow mode

This commit is contained in:
Galin Iliev
2026-08-09 16:54:12 -07:00
parent d65305026a
commit 283729f718
7 changed files with 240 additions and 110 deletions
@@ -15,7 +15,6 @@ const REPO_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..
const REQUIRED_PHASE_0_PATH_IDS = [
"selected-runtime-manager-acquisition",
"selected-runtime-backend-resolution",
"selected-runtime-startup-warmup-and-sync",
"bootstrap-memory-and-user-files",
"startup-recent-memory-context",
"memory-search-tool",
@@ -63,14 +63,6 @@ export const MEMORY_AUTHORIZATION_PATH_INVENTORY = Object.freeze([
"legacy-only",
"src/plugins/memory-runtime.ts",
),
entry(
"selected-runtime-startup-warmup-and-sync",
"control",
"selected-memory-plugin",
"blocked-in-enforced-mode",
"extensions/memory-core/index.ts",
"src/gateway/server-startup-memory.ts",
),
entry(
"bootstrap-memory-and-user-files",
"egress",
+108 -39
View File
@@ -4,8 +4,9 @@ import {
LEGACY_MEMORY_AUTHORIZATION_CAPABILITIES,
MEMORY_AUTHORIZATION_CAPABILITY_NAMES,
} from "../memory-host-sdk/host/authorization.js";
import { inspectMemoryAuthorizationRuntime } from "./memory-authorization-runtime.js";
import { inspectMemoryAuthorizationCapability } from "./memory-authorization-runtime.js";
import { observeMemoryAuthorizationShadowSurface } from "./memory-authorization-shadow.js";
import { createEmptyPluginRegistry } from "./registry-empty.js";
const AUTHORIZED_METHOD_NAMES = [
"authorize",
@@ -18,12 +19,11 @@ const AUTHORIZED_METHOD_NAMES = [
"statusAuthorized",
] as const;
function createRuntime(capabilities = COMPLETE_MEMORY_AUTHORIZATION_CAPABILITIES) {
function createRuntime() {
const notCalled = vi.fn(() => {
throw new Error("runtime methods must not execute in shadow mode");
});
return {
authorization: capabilities,
authorize: notCalled,
searchAuthorized: notCalled,
readAuthorized: notCalled,
@@ -37,8 +37,6 @@ function createRuntime(capabilities = COMPLETE_MEMORY_AUTHORIZATION_CAPABILITIES
}
class PrototypeRuntime {
readonly authorization = COMPLETE_MEMORY_AUTHORIZATION_CAPABILITIES;
authorize() {
throw new Error("runtime methods must not execute in shadow mode");
}
@@ -72,10 +70,18 @@ class PrototypeRuntime {
}
}
describe("memory authorization runtime inspection", () => {
class PrototypeCapability {
readonly authorization = COMPLETE_MEMORY_AUTHORIZATION_CAPABILITIES;
readonly runtime = new PrototypeRuntime();
}
describe("memory authorization capability inspection", () => {
it("reports a complete declared surface without calling an authorized or legacy method", () => {
const runtime = createRuntime();
const inspection = inspectMemoryAuthorizationRuntime(runtime);
const inspection = inspectMemoryAuthorizationCapability({
authorization: COMPLETE_MEMORY_AUTHORIZATION_CAPABILITIES,
runtime,
});
expect(inspection).toMatchObject({
version: 1,
@@ -94,12 +100,13 @@ describe("memory authorization runtime inspection", () => {
});
it("reports all-false and incomplete declarations as nonconforming", () => {
const legacy = inspectMemoryAuthorizationRuntime(
createRuntime(LEGACY_MEMORY_AUTHORIZATION_CAPABILITIES),
);
const incomplete = inspectMemoryAuthorizationRuntime({
const legacy = inspectMemoryAuthorizationCapability({
authorization: LEGACY_MEMORY_AUTHORIZATION_CAPABILITIES,
runtime: createRuntime(),
});
const incomplete = inspectMemoryAuthorizationCapability({
authorization: { ...COMPLETE_MEMORY_AUTHORIZATION_CAPABILITIES, scopedSync: false },
...Object.fromEntries(AUTHORIZED_METHOD_NAMES.map((name) => [name, () => undefined])),
runtime: Object.fromEntries(AUTHORIZED_METHOD_NAMES.map((name) => [name, () => undefined])),
});
expect(legacy).toMatchObject({
@@ -119,16 +126,16 @@ describe("memory authorization runtime inspection", () => {
});
it("uses the SDK's exact capability-declaration rules for shadow reporting", () => {
const unexpectedCapability = inspectMemoryAuthorizationRuntime({
...createRuntime(),
const unexpectedCapability = inspectMemoryAuthorizationCapability({
authorization: { ...COMPLETE_MEMORY_AUTHORIZATION_CAPABILITIES, unexpected: true },
runtime: createRuntime(),
});
const symbolicCapability = inspectMemoryAuthorizationRuntime({
...createRuntime(),
const symbolicCapability = inspectMemoryAuthorizationCapability({
authorization: {
...COMPLETE_MEMORY_AUTHORIZATION_CAPABILITIES,
[Symbol("plugin-framework-metadata")]: true,
},
runtime: createRuntime(),
});
expect(unexpectedCapability).toMatchObject({
@@ -144,12 +151,12 @@ describe("memory authorization runtime inspection", () => {
});
it("accepts data descriptors from class prototypes and ignores unrelated symbols", () => {
const runtime = new PrototypeRuntime();
Object.defineProperty(runtime, Symbol("plugin-framework-metadata"), {
const capability = new PrototypeCapability();
Object.defineProperty(capability, Symbol("plugin-framework-metadata"), {
value: "not an authorization capability",
});
expect(inspectMemoryAuthorizationRuntime(runtime)).toMatchObject({
expect(inspectMemoryAuthorizationCapability(capability)).toMatchObject({
capabilityDeclaration: "complete",
implementedMethodCount: AUTHORIZED_METHOD_NAMES.length,
surfaceComplete: true,
@@ -160,13 +167,6 @@ describe("memory authorization runtime inspection", () => {
it("fails closed on accessor and proxy surfaces without evaluating their getters or methods", () => {
let getterCalls = 0;
const accessorRuntime = Object.create(null) as Record<string, unknown>;
Object.defineProperty(accessorRuntime, "authorization", {
enumerable: true,
get() {
getterCalls += 1;
throw new Error("must not read authorization getter");
},
});
for (const name of AUTHORIZED_METHOD_NAMES) {
Object.defineProperty(accessorRuntime, name, {
enumerable: true,
@@ -176,7 +176,31 @@ describe("memory authorization runtime inspection", () => {
},
});
}
const proxyRuntime = new Proxy(
const accessorCapability = Object.create(null) as Record<string, unknown>;
Object.defineProperty(accessorCapability, "authorization", {
enumerable: true,
value: COMPLETE_MEMORY_AUTHORIZATION_CAPABILITIES,
});
Object.defineProperty(accessorCapability, "runtime", {
enumerable: true,
value: accessorRuntime,
});
const capabilityGetter = Object.create(null) as Record<string, unknown>;
Object.defineProperty(capabilityGetter, "authorization", {
enumerable: true,
get() {
getterCalls += 1;
throw new Error("must not read authorization getter");
},
});
Object.defineProperty(capabilityGetter, "runtime", {
enumerable: true,
get() {
getterCalls += 1;
throw new Error("must not read runtime getter");
},
});
const proxyCapability = new Proxy(
{},
{
getOwnPropertyDescriptor() {
@@ -184,7 +208,7 @@ describe("memory authorization runtime inspection", () => {
},
},
);
const declarationProxyRuntime = {
const declarationProxyCapability = {
authorization: new Proxy(
{},
{
@@ -193,15 +217,24 @@ describe("memory authorization runtime inspection", () => {
},
},
),
runtime: createRuntime(),
};
const accessor = inspectMemoryAuthorizationRuntime(accessorRuntime);
const undefinedDeclaration = inspectMemoryAuthorizationRuntime({ authorization: undefined });
const proxy = inspectMemoryAuthorizationRuntime(proxyRuntime);
const declarationProxy = inspectMemoryAuthorizationRuntime(declarationProxyRuntime);
const accessor = inspectMemoryAuthorizationCapability(accessorCapability);
const getter = inspectMemoryAuthorizationCapability(capabilityGetter);
const undefinedDeclaration = inspectMemoryAuthorizationCapability({
authorization: undefined,
runtime: createRuntime(),
});
const proxy = inspectMemoryAuthorizationCapability(proxyCapability);
const declarationProxy = inspectMemoryAuthorizationCapability(declarationProxyCapability);
expect(getterCalls).toBe(0);
expect(accessor).toMatchObject({
capabilityDeclaration: "complete",
reasonCode: "backend-nonconforming",
});
expect(getter).toMatchObject({
capabilityDeclaration: "malformed",
reasonCode: "backend-nonconforming",
});
@@ -221,15 +254,17 @@ describe("memory authorization runtime inspection", () => {
});
describe("memory authorization shadow inspection", () => {
it("returns one bounded, content-free observation per selected runtime", () => {
const runtime = Object.assign(createRuntime(LEGACY_MEMORY_AUTHORIZATION_CAPABILITIES), {
it("returns one bounded, content-free observation per selected registry", () => {
const runtime = Object.assign(createRuntime(), {
content: "private content sentinel",
prompt: "private prompt sentinel",
query: "private query sentinel",
principalId: "private principal sentinel",
});
const first = observeMemoryAuthorizationShadowSurface(runtime);
const second = observeMemoryAuthorizationShadowSurface(runtime);
const capability = { authorization: LEGACY_MEMORY_AUTHORIZATION_CAPABILITIES, runtime };
const registry = createEmptyPluginRegistry();
const first = observeMemoryAuthorizationShadowSurface({ capability, registry });
const second = observeMemoryAuthorizationShadowSurface({ capability, registry });
expect(first).toMatchObject({
mode: "shadow",
@@ -240,8 +275,39 @@ describe("memory authorization shadow inspection", () => {
expect(JSON.stringify(first)).not.toMatch(/private|content|prompt|query|principal/u);
});
it("does not let a hostile proxy change a selected runtime path", () => {
const runtime = new Proxy(
it("observes runtime-less selected capabilities without invoking or creating a runtime", () => {
const observation = observeMemoryAuthorizationShadowSurface({
capability: { authorization: COMPLETE_MEMORY_AUTHORIZATION_CAPABILITIES },
registry: createEmptyPluginRegistry(),
});
expect(observation).toEqual(
expect.objectContaining({
capabilityDeclaration: "complete",
implementedMethodCount: 0,
surfaceComplete: false,
reasonCode: "backend-nonconforming",
}),
);
});
it("deduplicates by selected registry rather than a shared runtime object", () => {
const runtime = createRuntime();
const first = observeMemoryAuthorizationShadowSurface({
capability: { authorization: COMPLETE_MEMORY_AUTHORIZATION_CAPABILITIES, runtime },
registry: createEmptyPluginRegistry(),
});
const second = observeMemoryAuthorizationShadowSurface({
capability: { authorization: COMPLETE_MEMORY_AUTHORIZATION_CAPABILITIES, runtime },
registry: createEmptyPluginRegistry(),
});
expect(first).toEqual(expect.objectContaining({ surfaceComplete: true }));
expect(second).toEqual(expect.objectContaining({ surfaceComplete: true }));
});
it("does not let a hostile proxy change a selected capability path", () => {
const capability = new Proxy(
{},
{
getOwnPropertyDescriptor() {
@@ -249,7 +315,10 @@ describe("memory authorization shadow inspection", () => {
},
},
);
const observation = observeMemoryAuthorizationShadowSurface(runtime);
const observation = observeMemoryAuthorizationShadowSurface({
capability,
registry: createEmptyPluginRegistry(),
});
expect(observation).toEqual(
expect.objectContaining({ reasonCode: "backend-nonconforming", surfaceComplete: false }),
+18 -18
View File
@@ -19,7 +19,7 @@ const AUTHORIZED_MEMORY_RUNTIME_METHODS = [
type AuthorizedMemoryRuntimeMethodName = (typeof AUTHORIZED_MEMORY_RUNTIME_METHODS)[number];
type MemoryAuthorizationRuntimeInspection = Readonly<{
type MemoryAuthorizationCapabilityInspection = Readonly<{
version: 1;
capabilityDeclaration: "missing" | "malformed" | "partial" | "complete";
declaredCapabilityCount: number;
@@ -32,9 +32,9 @@ type MemoryAuthorizationRuntimeInspection = Readonly<{
reasonCode: "surface-complete" | "backend-nonconforming";
}>;
// Runtime interfaces are shallow; the bound prevents hostile prototype chains from extending a
// shadow-only inspection beyond its fixed metadata budget.
const MAXIMUM_RUNTIME_PROTOTYPE_DEPTH = 8;
// Capability and runtime interfaces are shallow; the bound prevents hostile prototype chains
// from extending a shadow-only inspection beyond its fixed metadata budget.
const MAXIMUM_INSPECTION_PROTOTYPE_DEPTH = 8;
function isObjectReference(value: unknown): value is object {
return (typeof value === "object" && value !== null) || typeof value === "function";
@@ -45,10 +45,9 @@ type DataPropertyLookup =
| { kind: "missing" | "accessor" | "unavailable" };
/**
* Reads a data descriptor without evaluating the corresponding property. Runtime interfaces may
* use class methods, so the bounded prototype walk accepts data descriptors there too. A getter
* or hostile reflection failure remains nonconforming without touching an authorized or legacy
* runtime method.
* Reads a data descriptor without evaluating the corresponding property. Capability and runtime
* interfaces may use class methods, so the bounded prototype walk accepts data descriptors there
* too. A getter or hostile reflection failure remains nonconforming without touching a method.
*/
function readDataProperty(value: unknown, key: string): DataPropertyLookup {
if (!isObjectReference(value)) {
@@ -56,7 +55,7 @@ function readDataProperty(value: unknown, key: string): DataPropertyLookup {
}
try {
let current: object | null = value;
for (let depth = 0; current && depth < MAXIMUM_RUNTIME_PROTOTYPE_DEPTH; depth += 1) {
for (let depth = 0; current && depth < MAXIMUM_INSPECTION_PROTOTYPE_DEPTH; depth += 1) {
const descriptor = Object.getOwnPropertyDescriptor(current, key);
if (descriptor) {
return "value" in descriptor
@@ -73,8 +72,8 @@ function readDataProperty(value: unknown, key: string): DataPropertyLookup {
}
/**
* The SDK validator deliberately enforces exact descriptor shape. A plugin runtime can still be
* a hostile Proxy, so shadow inspection turns any reflection failure into a nonconforming result.
* The SDK validator deliberately enforces exact descriptor shape. A plugin capability can still
* be a hostile Proxy, so shadow inspection turns any reflection failure into a nonconforming result.
*/
function inspectCapabilityDeclaration(value: unknown): {
hasWellFormedDeclaration: boolean;
@@ -99,7 +98,7 @@ function inspectCapabilityDeclaration(value: unknown): {
}
}
function freezeInspection(params: Omit<MemoryAuthorizationRuntimeInspection, "version">) {
function freezeInspection(params: Omit<MemoryAuthorizationCapabilityInspection, "version">) {
return Object.freeze({
version: 1 as const,
...params,
@@ -110,12 +109,12 @@ function freezeInspection(params: Omit<MemoryAuthorizationRuntimeInspection, "ve
/**
* Produces content-free shape metadata only. It is intentionally not an admission decision and
* does not retain, invoke, or wrap the inspected runtime.
* does not retain, invoke, or wrap the selected capability or its runtime.
*/
export function inspectMemoryAuthorizationRuntime(
runtime: unknown,
): MemoryAuthorizationRuntimeInspection {
const authorization = readDataProperty(runtime, "authorization");
export function inspectMemoryAuthorizationCapability(
capability: unknown,
): MemoryAuthorizationCapabilityInspection {
const authorization = readDataProperty(capability, "authorization");
const hasDeclaration = authorization.kind === "data";
// Use the shared contract validator so shadow reporting and enforced-mode admission agree on
// the exact declaration shape. This boundary catches reflection traps as malformed.
@@ -124,9 +123,10 @@ export function inspectMemoryAuthorizationRuntime(
);
const declaredCapabilityCount =
MEMORY_AUTHORIZATION_CAPABILITY_NAMES.length - missingCapabilities.length;
const runtime = readDataProperty(capability, "runtime");
const methods = AUTHORIZED_MEMORY_RUNTIME_METHODS.map((name) => ({
name,
property: readDataProperty(runtime, name),
property: readDataProperty(runtime.kind === "data" ? runtime.value : undefined, name),
}));
const implementedMethodCount = methods.filter(
({ property }) => property.kind === "data" && typeof property.value === "function",
+8 -14
View File
@@ -1,7 +1,8 @@
import { MEMORY_AUTHORIZATION_CONTRACT_VERSION } from "../memory-host-sdk/host/authorization.js";
import { inspectMemoryAuthorizationRuntime } from "./memory-authorization-runtime.js";
import { inspectMemoryAuthorizationCapability } from "./memory-authorization-runtime.js";
import type { PluginRegistry } from "./registry-types.js";
const inspectedRuntimes = new WeakSet<object>();
const inspectedRegistries = new WeakSet<PluginRegistry>();
type MemoryAuthorizationShadowMetadata = Readonly<{
event: "memory-authorization-backend-surface";
@@ -16,26 +17,19 @@ type MemoryAuthorizationShadowMetadata = Readonly<{
reasonCode: "surface-complete" | "backend-nonconforming";
}>;
function isObjectReference(value: unknown): value is object {
return (typeof value === "object" && value !== null) || typeof value === "function";
}
/**
* Shadow mode returns bounded surface counts once per selected runtime. Reflection failures are
* Shadow mode returns bounded surface counts once per selected registry. Reflection failures are
* nonconforming observations; logging remains with the runtime owner and cannot change selection.
*/
export function observeMemoryAuthorizationShadowSurface(
runtime: unknown,
params: Readonly<{ capability: unknown; registry: PluginRegistry }>,
): MemoryAuthorizationShadowMetadata | undefined {
if (!isObjectReference(runtime)) {
return undefined;
}
try {
if (inspectedRuntimes.has(runtime)) {
if (inspectedRegistries.has(params.registry)) {
return undefined;
}
inspectedRuntimes.add(runtime);
const inspection = inspectMemoryAuthorizationRuntime(runtime);
inspectedRegistries.add(params.registry);
const inspection = inspectMemoryAuthorizationCapability(params.capability);
const metadata = Object.freeze({
event: "memory-authorization-backend-surface" as const,
mode: "shadow" as const,
+88 -16
View File
@@ -1,17 +1,21 @@
/** Covers non-activating memory registry handles and requesting-agent workspace ownership. */
import { beforeEach, describe, expect, it, vi } from "vitest";
import {
COMPLETE_MEMORY_AUTHORIZATION_CAPABILITIES,
LEGACY_MEMORY_AUTHORIZATION_CAPABILITIES,
} from "../memory-host-sdk/host/authorization.js";
import type { MemorySearchResult } from "../memory-host-sdk/host/types.js";
import type { MemoryPluginRuntime } from "./registry-contribution-types.js";
import type { MemoryPluginCapability, MemoryPluginRuntime } from "./registry-contribution-types.js";
import { createEmptyPluginRegistry } from "./registry-empty.js";
import { getPluginRuntimeGatewayRequestScope } from "./runtime/gateway-request-scope.js";
type AuthorizeSearchHits = NonNullable<MemoryPluginRuntime["authorizeSearchHits"]>;
const mocks = vi.hoisted(() => ({
getMemoryRuntime: vi.fn(),
loadPluginRegistryHandle: vi.fn(),
logDebug: vi.fn(),
observeMemoryAuthorizationShadowSurface: vi.fn(),
requireActivePluginRegistry: vi.fn(),
resolvePluginRegistryLoadCacheKey: vi.fn((options: unknown) => JSON.stringify(options)),
resolveAgentWorkspaceDir: vi.fn(),
}));
@@ -33,9 +37,9 @@ vi.mock("./memory-authorization-shadow.js", () => ({
observeMemoryAuthorizationShadowSurface: mocks.observeMemoryAuthorizationShadowSurface,
}));
vi.mock("./memory-state.js", async (importOriginal) => {
const actual = await importOriginal<typeof import("./memory-state.js")>();
return { ...actual, getMemoryRuntime: mocks.getMemoryRuntime };
vi.mock("./runtime.js", async (importOriginal) => {
const actual = await importOriginal<typeof import("./runtime.js")>();
return { ...actual, requireActivePluginRegistry: mocks.requireActivePluginRegistry };
});
import {
@@ -62,6 +66,7 @@ function createRuntime() {
type TestRegistry<T extends MemoryPluginRuntime> = {
registry: ReturnType<typeof createEmptyPluginRegistry>;
runtime: T;
capability: MemoryPluginCapability;
};
function createRegistry(): TestRegistry<ReturnType<typeof createRuntime>>;
@@ -70,8 +75,13 @@ function createRegistry(
runtime: MemoryPluginRuntime = createRuntime(),
): TestRegistry<MemoryPluginRuntime> {
const registry = createEmptyPluginRegistry();
registry.memoryCapabilities.push({ pluginId: "memory-core", capability: { runtime } });
return { registry, runtime };
const capability = {
authorization: LEGACY_MEMORY_AUTHORIZATION_CAPABILITIES,
runtime,
} satisfies MemoryPluginCapability;
registry.plugins.push({ id: "memory-core", memorySlotSelected: true } as never);
registry.memoryCapabilities.push({ pluginId: "memory-core", capability });
return { registry, runtime, capability };
}
const memoryConfig = {
@@ -81,10 +91,10 @@ const memoryConfig = {
describe("memory runtime handles", () => {
beforeEach(() => {
resetStandaloneMemoryRegistrySlot();
mocks.getMemoryRuntime.mockReset().mockReturnValue(undefined);
mocks.loadPluginRegistryHandle.mockReset();
mocks.logDebug.mockReset();
mocks.observeMemoryAuthorizationShadowSurface.mockReset();
mocks.requireActivePluginRegistry.mockReset().mockReturnValue(createEmptyPluginRegistry());
mocks.resolvePluginRegistryLoadCacheKey.mockClear();
mocks.resolveAgentWorkspaceDir
.mockReset()
@@ -230,9 +240,9 @@ describe("memory runtime handles", () => {
expect(mocks.loadPluginRegistryHandle).not.toHaveBeenCalled();
});
it("prefers an already-registered runtime", () => {
const runtime = createRuntime();
mocks.getMemoryRuntime.mockReturnValue(runtime);
it("prefers an already-registered selected capability runtime", () => {
const { registry } = createRegistry();
mocks.requireActivePluginRegistry.mockReturnValue(registry);
expect(resolveActiveMemoryBackendConfig({ cfg: memoryConfig, agentId: "main" })).toEqual({
backend: "builtin",
@@ -240,13 +250,19 @@ describe("memory runtime handles", () => {
expect(mocks.loadPluginRegistryHandle).not.toHaveBeenCalled();
});
it("inspects direct selected-runtime acquisition through the canonical seam", () => {
const runtime = createRuntime();
mocks.getMemoryRuntime.mockReturnValue(runtime);
it("inspects direct selected capability resolution through the canonical seam", () => {
const { registry, runtime } = createRegistry();
mocks.requireActivePluginRegistry.mockReturnValue(registry);
expect(getSelectedMemoryRuntime()).toBe(runtime);
expect(mocks.observeMemoryAuthorizationShadowSurface).toHaveBeenCalledOnce();
expect(mocks.observeMemoryAuthorizationShadowSurface).toHaveBeenCalledWith(runtime);
expect(mocks.observeMemoryAuthorizationShadowSurface).toHaveBeenCalledWith({
capability: expect.objectContaining({
authorization: LEGACY_MEMORY_AUTHORIZATION_CAPABILITIES,
runtime,
}),
registry,
});
});
it("wires each legacy resolution through shadow inspection without changing legacy resolution", () => {
@@ -261,10 +277,66 @@ describe("memory runtime handles", () => {
});
expect(mocks.observeMemoryAuthorizationShadowSurface).toHaveBeenCalledTimes(2);
expect(mocks.observeMemoryAuthorizationShadowSurface).toHaveBeenCalledWith(runtime);
expect(mocks.observeMemoryAuthorizationShadowSurface).toHaveBeenCalledWith({
capability: expect.objectContaining({
authorization: LEGACY_MEMORY_AUTHORIZATION_CAPABILITIES,
runtime,
}),
registry,
});
expect(runtime.resolveMemoryBackendConfig).toHaveBeenCalledTimes(2);
});
it("observes a selected runtime-less capability without inventing a runtime", () => {
const registry = createEmptyPluginRegistry();
registry.plugins.push({ id: "memory-lancedb", memorySlotSelected: true } as never);
registry.memoryCapabilities.push({
pluginId: "memory-lancedb",
capability: { authorization: LEGACY_MEMORY_AUTHORIZATION_CAPABILITIES },
});
mocks.requireActivePluginRegistry.mockReturnValue(registry);
expect(getSelectedMemoryRuntime()).toBeUndefined();
expect(mocks.observeMemoryAuthorizationShadowSurface).toHaveBeenCalledWith({
capability: expect.objectContaining({
authorization: LEGACY_MEMORY_AUTHORIZATION_CAPABILITIES,
}),
registry,
});
});
it("observes selected authorization when it inherits a sidecar runtime", () => {
const registry = createEmptyPluginRegistry();
const runtime = createRuntime();
registry.plugins.push({ id: "memory-lancedb", memorySlotSelected: true } as never);
registry.memoryCapabilities.push(
{
pluginId: "memory-core",
capability: {
authorization: COMPLETE_MEMORY_AUTHORIZATION_CAPABILITIES,
runtime,
},
},
{
pluginId: "memory-lancedb",
capability: {
authorization: LEGACY_MEMORY_AUTHORIZATION_CAPABILITIES,
publicArtifacts: { listArtifacts: async () => [] },
},
},
);
mocks.requireActivePluginRegistry.mockReturnValue(registry);
expect(getSelectedMemoryRuntime()).toBe(runtime);
expect(mocks.observeMemoryAuthorizationShadowSurface).toHaveBeenCalledWith({
capability: expect.objectContaining({
authorization: LEGACY_MEMORY_AUTHORIZATION_CAPABILITIES,
runtime,
}),
registry,
});
});
it("keeps legacy resolution when shadow logging fails", () => {
const { registry, runtime } = createRegistry();
mocks.loadPluginRegistryHandle.mockReturnValue(registry);
+18 -14
View File
@@ -7,12 +7,12 @@ import { normalizePluginsConfig } from "./config-state.js";
import { loadPluginRegistryHandle, resolvePluginRegistryLoadCacheKey } from "./loader.js";
import { observeMemoryAuthorizationShadowSurface } from "./memory-authorization-shadow.js";
import {
getMemoryRuntime,
resolveSelectedMemoryCapabilityRegistration,
setStandaloneMemoryManagerActive,
} from "./memory-state.js";
import type { MemoryPluginRuntime } from "./registry-contribution-types.js";
import type { MemoryPluginCapability, MemoryPluginRuntime } from "./registry-contribution-types.js";
import type { PluginRegistry } from "./registry-types.js";
import { requireActivePluginRegistry } from "./runtime.js";
import { withPluginRuntimeRegistryScope } from "./runtime/gateway-request-scope.js";
type MemoryRuntime = NonNullable<
@@ -52,8 +52,11 @@ function resolveMemoryRuntimeWorkspaceDir(
return resolveUserPath(dir);
}
function resolveMemoryRuntimeFromRegistry(registry: PluginRegistry) {
return resolveSelectedMemoryCapabilityRegistration(registry)?.capability.runtime;
function resolveMemoryRuntimeFromRegistry(registry: PluginRegistry): MemoryRuntime | undefined {
const registration = resolveSelectedMemoryCapabilityRegistration(registry);
return registration
? inspectSelectedMemoryCapability({ capability: registration.capability, registry })
: undefined;
}
function listCurrentMemoryRuntimeOwners(): MemoryRuntimeOwner[] {
@@ -81,31 +84,32 @@ function withMemoryRuntimeOwner<T>(
return withPluginRuntimeRegistryScope(owner.registry, () => run(owner.runtime));
}
function inspectSelectedMemoryRuntime(runtime: MemoryRuntime): MemoryRuntime {
// The inspection has no result-path effect: it only emits bounded shadow metadata once per
// selected runtime object and deliberately tolerates malformed/plugin-hostile surfaces.
const metadata = observeMemoryAuthorizationShadowSurface(runtime);
function inspectSelectedMemoryCapability(params: {
capability: MemoryPluginCapability;
registry: PluginRegistry;
}): MemoryRuntime | undefined {
// Inspection has no result-path effect: it emits bounded shadow metadata once per selected
// registry and deliberately tolerates malformed/plugin-hostile capability surfaces.
const metadata = observeMemoryAuthorizationShadowSurface(params);
if (metadata) {
try {
log.debug("memory authorization backend surface evaluated", metadata);
} catch {
// Shadow logging must not change selected runtime resolution or a legacy result path.
// Shadow logging must not change selected capability resolution or a legacy result path.
}
}
return runtime;
return params.capability.runtime;
}
/** Reads the selected registered runtime through the canonical shadow-inspected seam. */
/** Reads the selected capability runtime through the canonical shadow-inspected seam. */
export function getSelectedMemoryRuntime(): MemoryRuntime | undefined {
const runtime = getMemoryRuntime();
return runtime ? inspectSelectedMemoryRuntime(runtime) : undefined;
return resolveMemoryRuntimeFromRegistry(requireActivePluginRegistry());
}
function toMemoryRuntimeOwner(
runtime: MemoryRuntime,
registry?: PluginRegistry,
): MemoryRuntimeOwner {
inspectSelectedMemoryRuntime(runtime);
return registry ? { runtime, registry } : { runtime };
}