fix(knowledge_fs): add capability grant ID to durable deletion handlers

This commit is contained in:
FFXN 2026-08-13 15:18:51 +08:00
parent 8d263af94a
commit f938be656b
2 changed files with 15 additions and 1 deletions

View File

@ -13,6 +13,7 @@ const SPACE_ID = "018f0d60-7a49-7cc2-9c1b-5b36f18f2c42";
const SOURCE_ID = "018f0d60-7a49-7cc2-9c1b-5b36f18f2c43";
const DOCUMENT_ID = "018f0d60-7a49-7cc2-9c1b-5b36f18f2c44";
const JOB_ID = "018f0d60-7a49-7cc2-9c1b-5b36f18f2c45";
const CAPABILITY_GRANT_ID = "018f0d60-7a49-7cc2-9c1b-5b36f18f2c46";
const NOW = "2026-07-14T12:00:00.000Z";
describe("durable deletion handlers", () => {
@ -116,6 +117,7 @@ describe("durable deletion handlers", () => {
);
expect(service.requestDocumentDeletion).not.toHaveBeenCalled();
await expect(bulkOperations.get({ id: JOB_ID, tenantId: "tenant-1" })).resolves.toMatchObject({
capabilityGrantId: CAPABILITY_GRANT_ID,
items: [expect.objectContaining({ deletionJobId: JOB_ID, documentId: DOCUMENT_ID })],
type: "document_delete",
});
@ -145,6 +147,7 @@ describe("durable deletion handlers", () => {
}),
);
await expect(bulkOperations.get({ id: JOB_ID, tenantId: "tenant-1" })).resolves.toMatchObject({
capabilityGrantId: CAPABILITY_GRANT_ID,
items: [expect.objectContaining({ deletionJobId: JOB_ID, documentId: DOCUMENT_ID })],
type: "document_delete",
});
@ -204,6 +207,12 @@ function testApp(
const app = createKnowledgeGatewayApp();
app.use("*", async (context, next) => {
context.set("callerKind", callerKind);
if (callerKind === "interactive") {
context.set("capabilityV2Grant", {
contentScopeIds: [],
grantId: CAPABILITY_GRANT_ID,
} as never);
}
context.set("subject", {
scopes: ["knowledge-spaces:read", "knowledge-spaces:write"],
subjectId: "owner-1",

View File

@ -194,6 +194,7 @@ export function registerDurableDeletionHandlers({
knowledgeSpaceId: params.id,
requestedBySubjectId: subject.subjectId,
tenantId: subject.tenantId,
...(principal.capability ? { capabilityGrantId: principal.capability.grantId } : {}),
});
context.header("Location", `/knowledge-spaces/${params.id}/background-tasks`);
return context.json(accepted, 202);
@ -238,8 +239,9 @@ export function registerDurableDeletionHandlers({
const body = context.req.valid("json") as DeleteLogicalDocumentBody;
const headers = context.req.valid("header") as DurableDeletionIdempotencyHeaders;
try {
const principal = deletionPrincipal(context);
const accepted = await service.requestLogicalDocumentDeletion({
...deletionPrincipal(context),
...principal,
documentId: params.documentId,
expectedRevision: body.expectedRevision,
idempotencyKey: headers["idempotency-key"],
@ -253,6 +255,7 @@ export function registerDurableDeletionHandlers({
knowledgeSpaceId: params.id,
requestedBySubjectId: subject.subjectId,
tenantId: subject.tenantId,
...(principal.capability ? { capabilityGrantId: principal.capability.grantId } : {}),
});
context.header("Location", accepted.statusUrl);
return context.json(accepted, 202);
@ -301,6 +304,7 @@ export function registerDurableDeletionHandlers({
}
async function ensureLogicalDeletionBackgroundTask(input: {
readonly capabilityGrantId?: string | undefined;
readonly bulkOperations: BulkOperationRepository;
readonly id: string;
readonly items: readonly {
@ -315,6 +319,7 @@ async function ensureLogicalDeletionBackgroundTask(input: {
const existing = await input.bulkOperations.get({ id: input.id, tenantId: input.tenantId });
if (existing) return;
await input.bulkOperations.create({
...(input.capabilityGrantId ? { capabilityGrantId: input.capabilityGrantId } : {}),
id: input.id,
items: input.items.map((item) => ({ ...item, status: "queued" as const })),
knowledgeSpaceId: input.knowledgeSpaceId,