Skip to content

Commit 9a488e5

Browse files
author
Yaron (Personal Assistant)
committed
feat: add document delete support (API, DB, SKILL, delete-document.ss)
1 parent 2da6e90 commit 9a488e5

4 files changed

Lines changed: 133 additions & 0 deletions

File tree

SKILL.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ Bundle shape (decoded JSON):
5656
| `list-documents.ss` | Return decrypted latest snapshot per doc |
5757
| `get-document.ss` | Return one decrypted latest snapshot |
5858
| `search-documents.ss` | Return all decrypted latest snapshots |
59+
| `delete-document.ss` | Delete a single document by ID |
5960

6061
### Script signatures
6162

@@ -77,6 +78,9 @@ getDocument(documentId: string, agentdocsIdentity: string)
7778
7879
searchDocuments(agentdocsIdentity: string)
7980
-> { documents: [{ documentId, kind, title, content, documentKey }] }
81+
82+
deleteDocument(documentId: string, agentdocsIdentity: string)
83+
-> { success: boolean, deleted: number }
8084
```
8185

8286
## How agents should modify documents

api/db.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,42 @@ export async function deleteAllDocuments(
394394
return { deleted: docIds.length };
395395
}
396396

397+
export async function deleteDocument(
398+
documentId: string,
399+
identityId: string,
400+
): Promise<{ success: boolean; deleted: number } | null> {
401+
const doc = await getDocumentForIdentity(documentId, identityId);
402+
if (!doc) return null;
403+
404+
const steps: unknown[] = [];
405+
406+
// Query all access grants for this document
407+
const grantQuery = await query({
408+
accessGrants: {
409+
$: { where: { "document.id": documentId } },
410+
},
411+
});
412+
const grants = (grantQuery.accessGrants || []) as any[];
413+
for (const g of grants) {
414+
if (g.id) steps.push(["delete", "accessGrants", g.id, {}]);
415+
}
416+
417+
// Query all edits for this document
418+
const editQuery = await query({
419+
edits: { $: { where: { "document.id": documentId } } },
420+
});
421+
const edits = (editQuery.edits || []) as any[];
422+
for (const e of edits) {
423+
if (e.id) steps.push(["delete", "edits", e.id, {}]);
424+
}
425+
426+
// Delete the document itself
427+
steps.push(["delete", "documents", documentId, {}]);
428+
429+
if (steps.length > 0) await transact(steps);
430+
return { success: true, deleted: 1 };
431+
}
432+
397433
export async function getIdentity(
398434
identityId: string,
399435
): Promise<Record<string, unknown> | null> {

api/routes/documents.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
createAccessGrant,
55
createDocument,
66
deleteAllDocuments,
7+
deleteDocument,
78
getDocumentEdits,
89
getDocumentForIdentity,
910
getDocumentsForIdentity,
@@ -84,6 +85,18 @@ documentsRouter.delete("/", async (c) => {
8485
return c.json(result);
8586
});
8687

88+
// Delete a single document by ID
89+
documentsRouter.delete("/:id", async (c) => {
90+
const identityId = c.get("identityId") as string;
91+
const documentId = c.req.param("id");
92+
const result = await deleteDocument(documentId, identityId);
93+
if (!result) return c.json({ error: "not found or not authorized" }, 404);
94+
95+
fireWebhooks("document", documentId, "document.deleted", identityId, {});
96+
97+
return c.json(result);
98+
});
99+
87100
// Add an edit to a document
88101
documentsRouter.post("/:id/edits", async (c) => {
89102
const identityId = c.get("identityId") as string;

scripts/delete-document.ss

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// delete-document.ss
2+
// Deletes a single document by id.
3+
// Returns { success: boolean, deleted: number }.
4+
//
5+
// Parameters:
6+
// agentdocsIdentity -- base64url-encoded identity bundle (regular input)
7+
//
8+
// Permission surface:
9+
// hosts: agentdocs-api.uriva.deno.net
10+
// env: timestamp
11+
12+
loadIdentity = (bundleBase64url: string): {
13+
id: string,
14+
signingPrivateKey: string,
15+
signingPublicKey: string,
16+
encryptionPrivateKey: string,
17+
encryptionPublicKey: string
18+
} => {
19+
decoded = base64urlDecode({ encoded: bundleBase64url })
20+
parsed = jsonParse({ text: decoded.text })
21+
bundle = parsed.value
22+
signPub = ed25519PublicFromPrivate({ privateKey: bundle.signing.privateKey })
23+
encPub = x25519PublicFromPrivate({ privateKey: bundle.encryption.privateKey })
24+
return {
25+
id: bundle.id,
26+
signingPrivateKey: bundle.signing.privateKey,
27+
signingPublicKey: signPub.publicKey,
28+
encryptionPrivateKey: bundle.encryption.privateKey,
29+
encryptionPublicKey: encPub.publicKey
30+
}
31+
}
32+
33+
buildAuthSignature = (
34+
method: string,
35+
path: string,
36+
timestampStr: string,
37+
body: string,
38+
signingPrivateKey: string
39+
): { signature: string } => {
40+
h = sha256({ data: body })
41+
msg = stringConcat({ parts: [method, "\n", path, "\n", timestampStr, "\n", h.hash] })
42+
return ed25519Sign({ data: msg.result, privateKey: signingPrivateKey })
43+
}
44+
45+
signedDelete = (
46+
path: string,
47+
identityId: string,
48+
signingPrivateKey: string
49+
): { status: number, body: string } => {
50+
t = timestamp()
51+
tsStr = jsonStringify({ value: t.timestamp })
52+
sig = buildAuthSignature("DELETE", path, tsStr.text, "", signingPrivateKey)
53+
return httpRequest({
54+
host: "agentdocs-api.uriva.deno.net",
55+
method: "DELETE",
56+
path: path,
57+
headers: {
58+
"x-identity-id": identityId,
59+
"x-timestamp": tsStr.text,
60+
"x-signature": sig.signature
61+
}
62+
})
63+
}
64+
65+
deleteDocument = (documentId: string, agentdocsIdentity: string): {
66+
success: boolean,
67+
deleted: number
68+
} => {
69+
identity = loadIdentity(agentdocsIdentity)
70+
71+
docPath = stringConcat({ parts: ["/api/documents/", documentId] })
72+
docRes = signedDelete(docPath.result, identity.id, identity.signingPrivateKey)
73+
docParsed = jsonParse({ text: docRes.body })
74+
data = docParsed.value
75+
76+
return {
77+
success: data.success,
78+
deleted: data.deleted
79+
}
80+
}

0 commit comments

Comments
 (0)