feat(memory): resolve gateway profile principals

This commit is contained in:
Galin Iliev
2026-08-14 05:31:18 -07:00
parent 8b407e815f
commit 30dba2b78d
2 changed files with 56 additions and 0 deletions
+26
View File
@@ -12,6 +12,7 @@ import {
recheckMemoryIdentityBinding,
recheckMemoryIdentityBindingRecipient,
resolveMemoryIdentityBindingFromAdmission,
resolveMemoryPrincipalForUserProfile,
} from "./memory-identity.js";
import {
closeOpenClawStateDatabaseForTest,
@@ -162,6 +163,31 @@ describe("memory identity binding", () => {
).toEqual({ kind: "unbound" });
});
it("resolves only the active memory principal for the current Gateway profile head", () => {
const { env, profileId } = fixture();
expect(
resolveMemoryPrincipalForUserProfile({ userProfileId: profileId, options: { env } }),
).toBeUndefined();
const binding = adminLinkAdmittedMemoryIdentity({
admission: admitted("telegram", "default", "sender-profile-principal"),
authenticatedOperatorProfileId: profileId,
authenticatedOperatorScopes: ["operator.admin"],
options: { env },
});
expect(
resolveMemoryPrincipalForUserProfile({ userProfileId: profileId, options: { env } }),
).toEqual({
principalId: binding.principalId,
kind: "user",
revision: expect.any(String),
});
expect(
resolveMemoryPrincipalForUserProfile({ userProfileId: "unknown-profile", options: { env } }),
).toBeUndefined();
});
it("fails closed when a damaged shared database contains conflicting active bindings", () => {
const { env, profileId } = fixture();
const binding = adminLinkAdmittedMemoryIdentity({
+30
View File
@@ -266,6 +266,36 @@ export function recheckMemoryOperationalPrincipal(params: {
return row ? toPrincipal(row) : undefined;
}
/**
* Resolves the Gateway-authenticated profile to its active memory principal.
* Control-plane callers must derive this here instead of accepting a principal
* identifier from RPC or CLI input.
*/
export function resolveMemoryPrincipalForUserProfile(params: {
userProfileId: string;
options?: OpenClawStateDatabaseOptions;
}): MemoryPrincipal | undefined {
const options = params.options ?? {};
const userProfileId = resolveUserProfileId(
requireText(params.userProfileId, "userProfileId"),
options,
);
if (!userProfileId) {
return undefined;
}
ensureMemoryIdentitySchema(options);
const row = openOpenClawStateDatabase(options)
.db.prepare(
`SELECT principal_id, principal_kind, revision
FROM memory_principals
WHERE user_profile_id = ? AND principal_kind = 'user' AND state = 'active'`,
)
.get(userProfileId) as
| { principal_id: string; principal_kind: string; revision: string }
| undefined;
return row ? toPrincipal(row) : undefined;
}
/**
* The only binding writer. Pairing admission supplies a one-use opaque proof;
* a separately authenticated Gateway profile supplies the operator identity.