@vibes.diy/api-svc 14.1.21 → 14.1.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/create-handler.d.ts +2 -1
- package/create-handler.js +1 -0
- package/create-handler.js.map +1 -1
- package/index.d.ts +2 -0
- package/index.js +2 -0
- package/index.js.map +1 -1
- package/intern/agent-belt.js +15 -4
- package/intern/agent-belt.js.map +1 -1
- package/intern/agent-build-tools.js +5 -2
- package/intern/agent-build-tools.js.map +1 -1
- package/intern/agent-debug-tools.d.ts +33 -0
- package/intern/agent-debug-tools.js +676 -0
- package/intern/agent-debug-tools.js.map +1 -0
- package/intern/agent-deferred-access.d.ts +3 -0
- package/intern/agent-deferred-access.js +45 -1
- package/intern/agent-deferred-access.js.map +1 -1
- package/intern/agent-file-tools.js +8 -2
- package/intern/agent-file-tools.js.map +1 -1
- package/intern/agent-instruction.d.ts +1 -1
- package/intern/agent-instruction.js +2 -2
- package/intern/agent-instruction.js.map +1 -1
- package/intern/agent-session-instruction.d.ts +2 -2
- package/intern/agent-session-instruction.js +15 -2
- package/intern/agent-session-instruction.js.map +1 -1
- package/intern/agent-session-runtime.d.ts +1 -0
- package/intern/agent-session-runtime.js +38 -6
- package/intern/agent-session-runtime.js.map +1 -1
- package/intern/attempt-backend-fetch.js +7 -1
- package/intern/attempt-backend-fetch.js.map +1 -1
- package/intern/backend-do-addr.d.ts +2 -0
- package/intern/backend-do-addr.js +2 -0
- package/intern/backend-do-addr.js.map +1 -1
- package/intern/backend-do-control-plane.d.ts +18 -0
- package/intern/backend-do-control-plane.js +90 -1
- package/intern/backend-do-control-plane.js.map +1 -1
- package/intern/mint-decision.d.ts +8 -0
- package/intern/mint-decision.js +15 -3
- package/intern/mint-decision.js.map +1 -1
- package/intern/resolve-backend-session-user.d.ts +8 -0
- package/intern/resolve-backend-session-user.js +10 -0
- package/intern/resolve-backend-session-user.js.map +1 -1
- package/package.json +11 -11
- package/public/app-documents-broadcast.d.ts +2 -0
- package/public/app-documents-broadcast.js +7 -0
- package/public/app-documents-broadcast.js.map +1 -1
- package/public/app-documents-read-eventos.d.ts +52 -1
- package/public/app-documents-read-eventos.js +250 -222
- package/public/app-documents-read-eventos.js.map +1 -1
- package/public/app-documents-write-eventos.d.ts +42 -0
- package/public/app-documents-write-eventos.js +334 -334
- package/public/app-documents-write-eventos.js.map +1 -1
- package/public/app-logs.d.ts +14 -2
- package/public/app-logs.js +60 -45
- package/public/app-logs.js.map +1 -1
- package/public/app-setting-items.d.ts +4 -0
- package/public/app-setting-items.js +6 -3
- package/public/app-setting-items.js.map +1 -1
- package/public/app-settings-sql-upsert.d.ts +4 -0
- package/public/app-settings-sql-upsert.js +1 -1
- package/public/app-settings-sql-upsert.js.map +1 -1
- package/public/prompt-chat-section.js +1 -0
- package/public/prompt-chat-section.js.map +1 -1
- package/types.d.ts +2 -1
- package/types.js.map +1 -1
|
@@ -10,6 +10,7 @@ import { resolveAccessBinding, resolveAccessFnSource } from "./access-binding-re
|
|
|
10
10
|
import { extractContribution, newSeededReduce } from "./grant-reduce.js";
|
|
11
11
|
import { resolveDeveloperHandles } from "./developer-grants.js";
|
|
12
12
|
import { clientWsSend, connectionAdminMode } from "./app-documents-shared.js";
|
|
13
|
+
import { addressesDirectChannel } from "./dm-lane.js";
|
|
13
14
|
import { normalizeChannels } from "./normalize-channels.js";
|
|
14
15
|
import { audienceChannelsOf, hasInheritChannel, inheritChannelOf, INHERIT_CAPABILITY_CHANNEL, isReservedChannelId, stripForeignAudience, } from "./audience-channels.js";
|
|
15
16
|
import { emitBackendOnChange } from "../intern/emit-backend-onchange.js";
|
|
@@ -21,7 +22,7 @@ import { orThrow, syncChannelIndex } from "./access-fn-channel-index.js";
|
|
|
21
22
|
import { resolveWriteAclVerdict, resolveWriterContext } from "./write-front-door.js";
|
|
22
23
|
import { resolveActingHandle } from "./resolve-active-handle.js";
|
|
23
24
|
import { purgeRootHtmlCacheSameColo } from "../intern/swr-purge.js";
|
|
24
|
-
import { notifyDocChangedFanOut, notifyViewerGrantsChangedFanOut, emitCommentPostedEvent } from "./app-documents-broadcast.js";
|
|
25
|
+
import { notifyDocChangedFanOut, notifyViewerGrantsChangedFanOut, emitCommentPostedEvent, } from "./app-documents-broadcast.js";
|
|
25
26
|
import { notifyViewerGrantDeltaIfChanged } from "./app-documents-grant-delta.js";
|
|
26
27
|
import { persistPutRevision } from "./app-documents-put-persist.js";
|
|
27
28
|
import { validateFilesUploads } from "./validate-files-uploads.js";
|
|
@@ -78,6 +79,193 @@ export async function deriveAccessChannels(vctx, p, afbRow) {
|
|
|
78
79
|
const sanitized = stripForeignAudience(invokeResult, p.userContext?.userHandle ?? null);
|
|
79
80
|
return [...realChannels, ...audienceChannelsOf(sanitized), ...inheritChannelOf(sanitized)];
|
|
80
81
|
}
|
|
82
|
+
export async function putDocForCaller(vctx, args) {
|
|
83
|
+
if (addressesDirectChannel(args.ownerHandle))
|
|
84
|
+
return { kind: "denied", message: "Access denied" };
|
|
85
|
+
const userId = args.userId;
|
|
86
|
+
const adminRequested = args.adminMode;
|
|
87
|
+
if (args.actingHandle !== undefined && userId !== null) {
|
|
88
|
+
const acting = await resolveActingHandle(vctx, userId, args.actingHandle);
|
|
89
|
+
if (!acting.ok)
|
|
90
|
+
return { kind: "denied", message: "Acting handle not bound" };
|
|
91
|
+
}
|
|
92
|
+
const aclVerdict = await resolveWriteAclVerdict(vctx, {
|
|
93
|
+
ownerHandle: args.ownerHandle,
|
|
94
|
+
appSlug: args.appSlug,
|
|
95
|
+
dbName: args.dbName,
|
|
96
|
+
userId,
|
|
97
|
+
adminRequested,
|
|
98
|
+
});
|
|
99
|
+
if (!aclVerdict.aclWriteAllowed)
|
|
100
|
+
return { kind: "denied", message: "Access denied" };
|
|
101
|
+
const docAccessOverride = aclVerdict.adminActive;
|
|
102
|
+
const filesCheck = await validateFilesUploads(vctx, args.doc, args.ownerHandle, args.appSlug);
|
|
103
|
+
if (!filesCheck.ok)
|
|
104
|
+
return { kind: "denied", message: `Invalid file reference: ${filesCheck.reason}` };
|
|
105
|
+
let accessResult;
|
|
106
|
+
let grantsReduceBefore;
|
|
107
|
+
const afbRow = await resolveAccessBinding(vctx, args.ownerHandle, args.appSlug, args.dbName);
|
|
108
|
+
if (!userId && !afbRow?.accessFnCid)
|
|
109
|
+
return { kind: "denied", message: "Access denied" };
|
|
110
|
+
const docId = args.docId ?? vctx.sthis.timeOrderedNextId().str;
|
|
111
|
+
let headRow;
|
|
112
|
+
if (args.docId) {
|
|
113
|
+
const tDocs = vctx.sql.tables.appDocuments;
|
|
114
|
+
headRow = await vctx.sql.db
|
|
115
|
+
.select({ data: tDocs.data, deleted: tDocs.deleted })
|
|
116
|
+
.from(tDocs)
|
|
117
|
+
.where(and(eq(tDocs.ownerHandle, args.ownerHandle), eq(tDocs.appSlug, args.appSlug), eq(tDocs.dbName, args.dbName), eq(tDocs.docId, args.docId)))
|
|
118
|
+
.orderBy(desc(tDocs.seq))
|
|
119
|
+
.limit(1)
|
|
120
|
+
.then((r) => r[0]);
|
|
121
|
+
}
|
|
122
|
+
if (afbRow?.accessFnCid && !vctx.invokeAccessFn)
|
|
123
|
+
return { kind: "denied", message: "Access function unavailable" };
|
|
124
|
+
if (afbRow?.accessFnCid && vctx.invokeAccessFn) {
|
|
125
|
+
const { userContext, unboundActingHandle } = await resolveWriterContext(vctx, {
|
|
126
|
+
userId,
|
|
127
|
+
ownerHandle: args.ownerHandle,
|
|
128
|
+
...(args.actingHandle !== undefined ? { actingHandle: args.actingHandle } : {}),
|
|
129
|
+
});
|
|
130
|
+
if (unboundActingHandle)
|
|
131
|
+
return { kind: "denied", message: "Acting handle not bound" };
|
|
132
|
+
const oldDoc = headRow?.data ?? null;
|
|
133
|
+
const adminActive = docAccessOverride;
|
|
134
|
+
const gate = await runPutAccessGate(vctx, {
|
|
135
|
+
ownerHandle: args.ownerHandle,
|
|
136
|
+
appSlug: args.appSlug,
|
|
137
|
+
dbName: args.dbName,
|
|
138
|
+
fnCid: afbRow.accessFnCid,
|
|
139
|
+
accessFnAssetUri: afbRow.accessFnAssetUri,
|
|
140
|
+
accessFnDbName: afbRow.dbName,
|
|
141
|
+
docId,
|
|
142
|
+
doc: args.doc,
|
|
143
|
+
oldDoc,
|
|
144
|
+
oldDocDeleted: headRow?.deleted === 1,
|
|
145
|
+
userContext,
|
|
146
|
+
adminMode: adminActive,
|
|
147
|
+
});
|
|
148
|
+
if (gate.kind === "deny") {
|
|
149
|
+
return { kind: "denied", message: gate.message, ...(gate.code === undefined ? {} : { code: gate.code }) };
|
|
150
|
+
}
|
|
151
|
+
accessResult = gate.descriptor;
|
|
152
|
+
grantsReduceBefore = gate.grantsReduceBefore;
|
|
153
|
+
}
|
|
154
|
+
const now = new Date().toISOString();
|
|
155
|
+
const dbName = args.dbName;
|
|
156
|
+
const oldFeedChannels = afbRow?.accessFnCid
|
|
157
|
+
? await loadDocChannels(vctx, { ownerHandle: args.ownerHandle, appSlug: args.appSlug, dbName, docId })
|
|
158
|
+
: [];
|
|
159
|
+
let persisted;
|
|
160
|
+
try {
|
|
161
|
+
persisted = await persistPutRevision(vctx, {
|
|
162
|
+
ownerHandle: args.ownerHandle,
|
|
163
|
+
appSlug: args.appSlug,
|
|
164
|
+
dbName,
|
|
165
|
+
docId,
|
|
166
|
+
doc: args.doc,
|
|
167
|
+
userId,
|
|
168
|
+
now,
|
|
169
|
+
accessResult,
|
|
170
|
+
accessFnCid: afbRow?.accessFnCid,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
catch (err) {
|
|
174
|
+
if (err instanceof SeqConflictError) {
|
|
175
|
+
return {
|
|
176
|
+
kind: "denied",
|
|
177
|
+
code: "conflict",
|
|
178
|
+
message: `write conflict: head is at seq ${err.currentHeadSeq}, retry the write`,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
throw err;
|
|
182
|
+
}
|
|
183
|
+
const { alloc, storeAccessOutput, grantUpsertErr, noopRefreshedStaleOutput } = persisted;
|
|
184
|
+
const notifyGrantDeltaIfChanged = () => {
|
|
185
|
+
if (!storeAccessOutput || !grantsReduceBefore)
|
|
186
|
+
return;
|
|
187
|
+
notifyViewerGrantDeltaIfChanged(vctx, {
|
|
188
|
+
ownerHandle: args.ownerHandle,
|
|
189
|
+
appSlug: args.appSlug,
|
|
190
|
+
docId,
|
|
191
|
+
descriptor: storeAccessOutput.descriptor,
|
|
192
|
+
outputHasGrants: storeAccessOutput.outputHasGrants,
|
|
193
|
+
grantsReduceBefore,
|
|
194
|
+
senderConnId: args.senderConnId ?? "",
|
|
195
|
+
});
|
|
196
|
+
};
|
|
197
|
+
if (!alloc.inserted) {
|
|
198
|
+
if (grantUpsertErr) {
|
|
199
|
+
console.warn("AccessFnOutputs stale-row refresh on no-op failed:", grantUpsertErr);
|
|
200
|
+
}
|
|
201
|
+
else if (noopRefreshedStaleOutput) {
|
|
202
|
+
notifyGrantDeltaIfChanged();
|
|
203
|
+
}
|
|
204
|
+
return { kind: "ok", docId };
|
|
205
|
+
}
|
|
206
|
+
const nextSeq = alloc.seq;
|
|
207
|
+
await bestEffortChannelChange({
|
|
208
|
+
db: vctx.sql.db,
|
|
209
|
+
flavour: vctx.sql.flavour,
|
|
210
|
+
ownerHandle: args.ownerHandle,
|
|
211
|
+
appSlug: args.appSlug,
|
|
212
|
+
dbName,
|
|
213
|
+
docId,
|
|
214
|
+
seq: nextSeq,
|
|
215
|
+
oldChannels: oldFeedChannels,
|
|
216
|
+
newChannels: channelSetOfDescriptor(accessResult),
|
|
217
|
+
alwaysPut: true,
|
|
218
|
+
created: now,
|
|
219
|
+
});
|
|
220
|
+
await emitBackendOnChange(vctx, {
|
|
221
|
+
ownerHandle: args.ownerHandle,
|
|
222
|
+
appSlug: args.appSlug,
|
|
223
|
+
dbName,
|
|
224
|
+
docId,
|
|
225
|
+
seq: nextSeq,
|
|
226
|
+
deleted: false,
|
|
227
|
+
doc: args.doc,
|
|
228
|
+
originDepth: 0,
|
|
229
|
+
writerUserId: userId,
|
|
230
|
+
});
|
|
231
|
+
await emitCommentPostedEvent(vctx, {
|
|
232
|
+
ownerHandle: args.ownerHandle,
|
|
233
|
+
appSlug: args.appSlug,
|
|
234
|
+
dbName,
|
|
235
|
+
docId,
|
|
236
|
+
userId,
|
|
237
|
+
email: args.email,
|
|
238
|
+
nextSeq,
|
|
239
|
+
now,
|
|
240
|
+
});
|
|
241
|
+
const putFanoutChannels = [
|
|
242
|
+
...normalizeChannels(accessResult?.channels ?? []),
|
|
243
|
+
...(accessResult === undefined ? [] : audienceChannelsOf(accessResult)),
|
|
244
|
+
...(accessResult === undefined ? [] : inheritChannelOf(accessResult)),
|
|
245
|
+
];
|
|
246
|
+
if (hasInheritChannel(putFanoutChannels))
|
|
247
|
+
putFanoutChannels.push(INHERIT_CAPABILITY_CHANNEL);
|
|
248
|
+
await (args.fanOutDocChanged ?? notifyDocChangedFanOut)(vctx, {
|
|
249
|
+
ownerHandle: args.ownerHandle,
|
|
250
|
+
appSlug: args.appSlug,
|
|
251
|
+
dbName,
|
|
252
|
+
docId,
|
|
253
|
+
channels: putFanoutChannels,
|
|
254
|
+
senderConnId: args.senderConnId ?? "",
|
|
255
|
+
});
|
|
256
|
+
if (storeAccessOutput) {
|
|
257
|
+
if (grantUpsertErr) {
|
|
258
|
+
console.error("AccessFnOutputs upsert failed:", grantUpsertErr);
|
|
259
|
+
if (storeAccessOutput.outputHasGrants === 1) {
|
|
260
|
+
return { kind: "denied", message: "grant storage failed — retry the write" };
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
else {
|
|
264
|
+
notifyGrantDeltaIfChanged();
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
return { kind: "ok", docId };
|
|
268
|
+
}
|
|
81
269
|
export const putDocEvento = {
|
|
82
270
|
hash: "put-doc",
|
|
83
271
|
validate: unwrapMsgBase(async (msg) => {
|
|
@@ -90,236 +278,167 @@ export const putDocEvento = {
|
|
|
90
278
|
handle: optAuth(async (ctx) => {
|
|
91
279
|
const req = ctx.validated.payload;
|
|
92
280
|
const vctx = ctx.ctx.getOrThrow("vibesApiCtx");
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
if (req.actingHandle !== undefined && userId !== null) {
|
|
96
|
-
const acting = await resolveActingHandle(vctx, userId, req.actingHandle);
|
|
97
|
-
if (!acting.ok) {
|
|
98
|
-
await ctx.send.send(ctx, {
|
|
99
|
-
type: "vibes.diy.res-error",
|
|
100
|
-
error: { message: "Acting handle not bound" },
|
|
101
|
-
});
|
|
102
|
-
return Result.Ok(EventoResult.Continue);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
const aclVerdict = await resolveWriteAclVerdict(vctx, {
|
|
281
|
+
const answer = await putDocForCaller(vctx, {
|
|
282
|
+
userId: req._auth?.verifiedAuth.claims.userId ?? null,
|
|
106
283
|
ownerHandle: req.ownerHandle,
|
|
107
284
|
appSlug: req.appSlug,
|
|
108
285
|
dbName: req.dbName,
|
|
109
|
-
|
|
110
|
-
|
|
286
|
+
...(req.docId === undefined ? {} : { docId: req.docId }),
|
|
287
|
+
doc: req.doc,
|
|
288
|
+
adminMode: connectionAdminMode(ctx) || req.adminMode === true,
|
|
289
|
+
...(req.actingHandle === undefined ? {} : { actingHandle: req.actingHandle }),
|
|
290
|
+
...(req._auth?.verifiedAuth.claims.params.email === undefined ? {} : { email: req._auth.verifiedAuth.claims.params.email }),
|
|
291
|
+
senderConnId: clientWsSend(ctx).connId,
|
|
111
292
|
});
|
|
112
|
-
if (
|
|
113
|
-
await ctx.send.send(ctx, { type: "vibes.diy.res-error", error: { message: "Access denied" } });
|
|
114
|
-
return Result.Ok(EventoResult.Continue);
|
|
115
|
-
}
|
|
116
|
-
const docAccessOverride = aclVerdict.adminActive;
|
|
117
|
-
const filesCheck = await validateFilesUploads(vctx, req.doc, req.ownerHandle, req.appSlug);
|
|
118
|
-
if (!filesCheck.ok) {
|
|
119
|
-
await ctx.send.send(ctx, {
|
|
120
|
-
type: "vibes.diy.res-error",
|
|
121
|
-
error: { message: `Invalid file reference: ${filesCheck.reason}` },
|
|
122
|
-
});
|
|
123
|
-
return Result.Ok(EventoResult.Continue);
|
|
124
|
-
}
|
|
125
|
-
let accessResult;
|
|
126
|
-
let grantsReduceBefore;
|
|
127
|
-
const afbRow = await resolveAccessBinding(vctx, req.ownerHandle, req.appSlug, req.dbName);
|
|
128
|
-
if (!userId && !afbRow?.accessFnCid) {
|
|
293
|
+
if (answer.kind === "denied") {
|
|
129
294
|
await ctx.send.send(ctx, {
|
|
130
295
|
type: "vibes.diy.res-error",
|
|
131
|
-
error: { message:
|
|
296
|
+
error: { message: answer.message, ...(answer.code === undefined ? {} : { code: answer.code }) },
|
|
132
297
|
});
|
|
133
298
|
return Result.Ok(EventoResult.Continue);
|
|
134
299
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
}
|
|
154
|
-
if (
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
300
|
+
await ctx.send.send(ctx, {
|
|
301
|
+
type: "vibes.diy.res-put-doc",
|
|
302
|
+
status: "ok",
|
|
303
|
+
id: answer.docId,
|
|
304
|
+
});
|
|
305
|
+
return Result.Ok(EventoResult.Continue);
|
|
306
|
+
}),
|
|
307
|
+
};
|
|
308
|
+
export async function deleteDocForCaller(vctx, args) {
|
|
309
|
+
if (addressesDirectChannel(args.ownerHandle))
|
|
310
|
+
return { kind: "denied", message: "Access denied" };
|
|
311
|
+
const userId = args.userId;
|
|
312
|
+
let pinnedWriter;
|
|
313
|
+
if (args.actingHandle !== undefined) {
|
|
314
|
+
const writer = await resolveWriterContext(vctx, {
|
|
315
|
+
userId,
|
|
316
|
+
ownerHandle: args.ownerHandle,
|
|
317
|
+
actingHandle: args.actingHandle,
|
|
318
|
+
});
|
|
319
|
+
if (writer.unboundActingHandle)
|
|
320
|
+
return { kind: "denied", message: "Acting handle not bound" };
|
|
321
|
+
pinnedWriter = writer.userContext;
|
|
322
|
+
}
|
|
323
|
+
const now = new Date().toISOString();
|
|
324
|
+
const t = vctx.sql.tables.appDocuments;
|
|
325
|
+
const dbName = args.dbName;
|
|
326
|
+
const afbRow = await resolveAccessBinding(vctx, args.ownerHandle, args.appSlug, dbName);
|
|
327
|
+
const effectiveFnCid = afbRow?.accessFnCid;
|
|
328
|
+
const delGate = await runDeleteAccessGate(vctx, {
|
|
329
|
+
ownerHandle: args.ownerHandle,
|
|
330
|
+
appSlug: args.appSlug,
|
|
331
|
+
dbName: args.dbName,
|
|
332
|
+
docId: args.docId,
|
|
333
|
+
userId,
|
|
334
|
+
adminMode: args.adminMode,
|
|
335
|
+
...(pinnedWriter !== undefined ? { userContext: pinnedWriter } : {}),
|
|
336
|
+
lane: "interactive",
|
|
337
|
+
});
|
|
338
|
+
if (delGate.kind === "deny")
|
|
339
|
+
return { kind: "denied", message: delGate.message };
|
|
340
|
+
const tOutputs = vctx.sql.tables.accessFnOutputs;
|
|
341
|
+
let deletedDocHadGrants = false;
|
|
342
|
+
let channels = [];
|
|
343
|
+
const deleteOutputSidecar = async (_seq, exec) => {
|
|
344
|
+
const outRow = await exec
|
|
345
|
+
.select({ output: tOutputs.output, hasGrants: tOutputs.hasGrants, fnCid: tOutputs.fnCid })
|
|
346
|
+
.from(tOutputs)
|
|
347
|
+
.where(and(eq(tOutputs.ownerHandle, args.ownerHandle), eq(tOutputs.appSlug, args.appSlug), eq(tOutputs.dbName, dbName), eq(tOutputs.docId, args.docId)))
|
|
348
|
+
.limit(1)
|
|
349
|
+
.then((r) => r[0]);
|
|
350
|
+
const outputIsLive = effectiveFnCid !== undefined && outRow !== undefined;
|
|
351
|
+
if (outputIsLive) {
|
|
352
|
+
deletedDocHadGrants = outRow.hasGrants === 1;
|
|
353
|
+
if (outRow.output) {
|
|
354
|
+
try {
|
|
355
|
+
const parsed = JSON.parse(outRow.output);
|
|
356
|
+
channels = [...normalizeChannels(parsed.channels ?? []), ...audienceChannelsOf(parsed), ...inheritChannelOf(parsed)];
|
|
357
|
+
if (hasInheritChannel(channels))
|
|
358
|
+
channels.push(INHERIT_CAPABILITY_CHANNEL);
|
|
359
|
+
}
|
|
360
|
+
catch (e) {
|
|
361
|
+
console.error("deleteDoc stored-channels parse error:", e);
|
|
362
|
+
}
|
|
189
363
|
}
|
|
190
|
-
accessResult = gate.descriptor;
|
|
191
|
-
grantsReduceBefore = gate.grantsReduceBefore;
|
|
192
364
|
}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
365
|
+
await exec
|
|
366
|
+
.delete(tOutputs)
|
|
367
|
+
.where(and(eq(tOutputs.ownerHandle, args.ownerHandle), eq(tOutputs.appSlug, args.appSlug), eq(tOutputs.dbName, dbName), eq(tOutputs.docId, args.docId)));
|
|
368
|
+
orThrow(await syncChannelIndex(exec, vctx.sql.tables, { ownerHandle: args.ownerHandle, appSlug: args.appSlug, dbName, docId: args.docId }, null), "frontend delete");
|
|
369
|
+
};
|
|
370
|
+
let delAlloc;
|
|
371
|
+
try {
|
|
372
|
+
delAlloc = await allocateAndInsertRevision({
|
|
373
|
+
db: vctx.sql.db,
|
|
374
|
+
flavour: vctx.sql.flavour,
|
|
375
|
+
table: t,
|
|
376
|
+
row: {
|
|
377
|
+
ownerHandle: args.ownerHandle,
|
|
378
|
+
appSlug: args.appSlug,
|
|
203
379
|
dbName,
|
|
204
|
-
docId,
|
|
205
|
-
doc: req.doc,
|
|
380
|
+
docId: args.docId,
|
|
206
381
|
userId,
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
}
|
|
222
|
-
const { alloc, storeAccessOutput, grantUpsertErr, noopRefreshedStaleOutput } = persisted;
|
|
223
|
-
const notifyGrantDeltaIfChanged = () => {
|
|
224
|
-
if (!storeAccessOutput || !grantsReduceBefore)
|
|
225
|
-
return;
|
|
226
|
-
notifyViewerGrantDeltaIfChanged(vctx, {
|
|
227
|
-
ownerHandle: req.ownerHandle,
|
|
228
|
-
appSlug: req.appSlug,
|
|
229
|
-
docId,
|
|
230
|
-
descriptor: storeAccessOutput.descriptor,
|
|
231
|
-
outputHasGrants: storeAccessOutput.outputHasGrants,
|
|
232
|
-
grantsReduceBefore,
|
|
233
|
-
senderConnId: clientWsSend(ctx).connId,
|
|
234
|
-
});
|
|
235
|
-
};
|
|
236
|
-
if (!alloc.inserted) {
|
|
237
|
-
if (grantUpsertErr) {
|
|
238
|
-
console.warn("AccessFnOutputs stale-row refresh on no-op failed:", grantUpsertErr);
|
|
239
|
-
}
|
|
240
|
-
else if (noopRefreshedStaleOutput) {
|
|
241
|
-
notifyGrantDeltaIfChanged();
|
|
242
|
-
}
|
|
243
|
-
await ctx.send.send(ctx, {
|
|
244
|
-
type: "vibes.diy.res-put-doc",
|
|
245
|
-
status: "ok",
|
|
246
|
-
id: docId,
|
|
247
|
-
});
|
|
248
|
-
return Result.Ok(EventoResult.Continue);
|
|
382
|
+
data: {},
|
|
383
|
+
deleted: 1,
|
|
384
|
+
created: now,
|
|
385
|
+
},
|
|
386
|
+
sidecar: deleteOutputSidecar,
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
catch (err) {
|
|
390
|
+
if (err instanceof SeqConflictError) {
|
|
391
|
+
return {
|
|
392
|
+
kind: "denied",
|
|
393
|
+
code: "conflict",
|
|
394
|
+
message: `write conflict: head is at seq ${err.currentHeadSeq}, retry the delete`,
|
|
395
|
+
};
|
|
249
396
|
}
|
|
250
|
-
|
|
251
|
-
|
|
397
|
+
throw err;
|
|
398
|
+
}
|
|
399
|
+
if (delAlloc.inserted) {
|
|
400
|
+
await bestEffortAppendDelete({
|
|
252
401
|
db: vctx.sql.db,
|
|
253
402
|
flavour: vctx.sql.flavour,
|
|
254
|
-
ownerHandle:
|
|
255
|
-
appSlug:
|
|
403
|
+
ownerHandle: args.ownerHandle,
|
|
404
|
+
appSlug: args.appSlug,
|
|
256
405
|
dbName,
|
|
257
|
-
docId,
|
|
258
|
-
seq:
|
|
259
|
-
oldChannels: oldFeedChannels,
|
|
260
|
-
newChannels: channelSetOfDescriptor(accessResult),
|
|
261
|
-
alwaysPut: true,
|
|
406
|
+
docId: args.docId,
|
|
407
|
+
seq: delAlloc.seq,
|
|
262
408
|
created: now,
|
|
263
409
|
});
|
|
410
|
+
}
|
|
411
|
+
if (delAlloc.inserted) {
|
|
264
412
|
await emitBackendOnChange(vctx, {
|
|
265
|
-
ownerHandle:
|
|
266
|
-
appSlug:
|
|
413
|
+
ownerHandle: args.ownerHandle,
|
|
414
|
+
appSlug: args.appSlug,
|
|
267
415
|
dbName,
|
|
268
|
-
docId,
|
|
269
|
-
seq:
|
|
270
|
-
deleted:
|
|
271
|
-
doc:
|
|
416
|
+
docId: args.docId,
|
|
417
|
+
seq: delAlloc.seq,
|
|
418
|
+
deleted: true,
|
|
419
|
+
doc: {},
|
|
272
420
|
originDepth: 0,
|
|
273
421
|
writerUserId: userId,
|
|
274
422
|
});
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
];
|
|
290
|
-
if (hasInheritChannel(putFanoutChannels))
|
|
291
|
-
putFanoutChannels.push(INHERIT_CAPABILITY_CHANNEL);
|
|
292
|
-
notifyDocChangedFanOut(vctx, {
|
|
293
|
-
ownerHandle: req.ownerHandle,
|
|
294
|
-
appSlug: req.appSlug,
|
|
295
|
-
dbName,
|
|
296
|
-
docId,
|
|
297
|
-
channels: putFanoutChannels,
|
|
298
|
-
senderConnId: clientWsSend(ctx).connId,
|
|
299
|
-
});
|
|
300
|
-
if (storeAccessOutput) {
|
|
301
|
-
if (grantUpsertErr) {
|
|
302
|
-
console.error("AccessFnOutputs upsert failed:", grantUpsertErr);
|
|
303
|
-
if (storeAccessOutput.outputHasGrants === 1) {
|
|
304
|
-
await ctx.send.send(ctx, {
|
|
305
|
-
type: "vibes.diy.res-error",
|
|
306
|
-
error: { message: "grant storage failed — retry the write" },
|
|
307
|
-
});
|
|
308
|
-
return Result.Ok(EventoResult.Continue);
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
else {
|
|
312
|
-
notifyGrantDeltaIfChanged();
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
await ctx.send.send(ctx, {
|
|
316
|
-
type: "vibes.diy.res-put-doc",
|
|
317
|
-
status: "ok",
|
|
318
|
-
id: docId,
|
|
423
|
+
}
|
|
424
|
+
await (args.fanOutDocChanged ?? notifyDocChangedFanOut)(vctx, {
|
|
425
|
+
ownerHandle: args.ownerHandle,
|
|
426
|
+
appSlug: args.appSlug,
|
|
427
|
+
dbName,
|
|
428
|
+
docId: args.docId,
|
|
429
|
+
channels,
|
|
430
|
+
senderConnId: args.senderConnId ?? "",
|
|
431
|
+
});
|
|
432
|
+
if (deletedDocHadGrants) {
|
|
433
|
+
notifyViewerGrantsChangedFanOut(vctx, {
|
|
434
|
+
ownerHandle: args.ownerHandle,
|
|
435
|
+
appSlug: args.appSlug,
|
|
436
|
+
senderConnId: args.senderConnId ?? "",
|
|
319
437
|
});
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
};
|
|
438
|
+
}
|
|
439
|
+
purgeRootHtmlCacheSameColo(vctx, args.ownerHandle, args.appSlug);
|
|
440
|
+
return { kind: "ok", docId: args.docId };
|
|
441
|
+
}
|
|
323
442
|
export const deleteDocEvento = {
|
|
324
443
|
hash: "delete-doc",
|
|
325
444
|
validate: unwrapMsgBase(async (msg) => {
|
|
@@ -332,146 +451,27 @@ export const deleteDocEvento = {
|
|
|
332
451
|
handle: checkAuth(async (ctx) => {
|
|
333
452
|
const req = ctx.validated.payload;
|
|
334
453
|
const vctx = ctx.ctx.getOrThrow("vibesApiCtx");
|
|
335
|
-
const
|
|
336
|
-
|
|
337
|
-
if (req.actingHandle !== undefined) {
|
|
338
|
-
const writer = await resolveWriterContext(vctx, {
|
|
339
|
-
userId,
|
|
340
|
-
ownerHandle: req.ownerHandle,
|
|
341
|
-
actingHandle: req.actingHandle,
|
|
342
|
-
});
|
|
343
|
-
if (writer.unboundActingHandle) {
|
|
344
|
-
await ctx.send.send(ctx, {
|
|
345
|
-
type: "vibes.diy.res-error",
|
|
346
|
-
error: { message: "Acting handle not bound" },
|
|
347
|
-
});
|
|
348
|
-
return Result.Ok(EventoResult.Continue);
|
|
349
|
-
}
|
|
350
|
-
pinnedWriter = writer.userContext;
|
|
351
|
-
}
|
|
352
|
-
const now = new Date().toISOString();
|
|
353
|
-
const t = vctx.sql.tables.appDocuments;
|
|
354
|
-
const dbName = req.dbName;
|
|
355
|
-
const afbRow = await resolveAccessBinding(vctx, req.ownerHandle, req.appSlug, dbName);
|
|
356
|
-
const effectiveFnCid = afbRow?.accessFnCid;
|
|
357
|
-
const delGate = await runDeleteAccessGate(vctx, {
|
|
454
|
+
const answer = await deleteDocForCaller(vctx, {
|
|
455
|
+
userId: req._auth.verifiedAuth.claims.userId,
|
|
358
456
|
ownerHandle: req.ownerHandle,
|
|
359
457
|
appSlug: req.appSlug,
|
|
360
458
|
dbName: req.dbName,
|
|
361
459
|
docId: req.docId,
|
|
362
|
-
userId,
|
|
363
460
|
adminMode: connectionAdminMode(ctx) || req.adminMode === true,
|
|
364
|
-
...(
|
|
365
|
-
lane: "interactive",
|
|
366
|
-
});
|
|
367
|
-
if (delGate.kind === "deny") {
|
|
368
|
-
await ctx.send.send(ctx, { type: "vibes.diy.res-error", error: { message: delGate.message } });
|
|
369
|
-
return Result.Ok(EventoResult.Continue);
|
|
370
|
-
}
|
|
371
|
-
const tOutputs = vctx.sql.tables.accessFnOutputs;
|
|
372
|
-
let deletedDocHadGrants = false;
|
|
373
|
-
let channels = [];
|
|
374
|
-
const deleteOutputSidecar = async (_seq, exec) => {
|
|
375
|
-
const outRow = await exec
|
|
376
|
-
.select({ output: tOutputs.output, hasGrants: tOutputs.hasGrants, fnCid: tOutputs.fnCid })
|
|
377
|
-
.from(tOutputs)
|
|
378
|
-
.where(and(eq(tOutputs.ownerHandle, req.ownerHandle), eq(tOutputs.appSlug, req.appSlug), eq(tOutputs.dbName, dbName), eq(tOutputs.docId, req.docId)))
|
|
379
|
-
.limit(1)
|
|
380
|
-
.then((r) => r[0]);
|
|
381
|
-
const outputIsLive = effectiveFnCid !== undefined && outRow !== undefined;
|
|
382
|
-
if (outputIsLive) {
|
|
383
|
-
deletedDocHadGrants = outRow.hasGrants === 1;
|
|
384
|
-
if (outRow.output) {
|
|
385
|
-
try {
|
|
386
|
-
const parsed = JSON.parse(outRow.output);
|
|
387
|
-
channels = [...normalizeChannels(parsed.channels ?? []), ...audienceChannelsOf(parsed), ...inheritChannelOf(parsed)];
|
|
388
|
-
if (hasInheritChannel(channels))
|
|
389
|
-
channels.push(INHERIT_CAPABILITY_CHANNEL);
|
|
390
|
-
}
|
|
391
|
-
catch (e) {
|
|
392
|
-
console.error("deleteDoc stored-channels parse error:", e);
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
await exec
|
|
397
|
-
.delete(tOutputs)
|
|
398
|
-
.where(and(eq(tOutputs.ownerHandle, req.ownerHandle), eq(tOutputs.appSlug, req.appSlug), eq(tOutputs.dbName, dbName), eq(tOutputs.docId, req.docId)));
|
|
399
|
-
orThrow(await syncChannelIndex(exec, vctx.sql.tables, { ownerHandle: req.ownerHandle, appSlug: req.appSlug, dbName, docId: req.docId }, null), "frontend delete");
|
|
400
|
-
};
|
|
401
|
-
let delAlloc;
|
|
402
|
-
try {
|
|
403
|
-
delAlloc = await allocateAndInsertRevision({
|
|
404
|
-
db: vctx.sql.db,
|
|
405
|
-
flavour: vctx.sql.flavour,
|
|
406
|
-
table: t,
|
|
407
|
-
row: {
|
|
408
|
-
ownerHandle: req.ownerHandle,
|
|
409
|
-
appSlug: req.appSlug,
|
|
410
|
-
dbName,
|
|
411
|
-
docId: req.docId,
|
|
412
|
-
userId: req._auth.verifiedAuth.claims.userId,
|
|
413
|
-
data: {},
|
|
414
|
-
deleted: 1,
|
|
415
|
-
created: now,
|
|
416
|
-
},
|
|
417
|
-
sidecar: deleteOutputSidecar,
|
|
418
|
-
});
|
|
419
|
-
}
|
|
420
|
-
catch (err) {
|
|
421
|
-
if (err instanceof SeqConflictError) {
|
|
422
|
-
await ctx.send.send(ctx, {
|
|
423
|
-
type: "vibes.diy.res-error",
|
|
424
|
-
error: { code: "conflict", message: `write conflict: head is at seq ${err.currentHeadSeq}, retry the delete` },
|
|
425
|
-
});
|
|
426
|
-
return Result.Ok(EventoResult.Continue);
|
|
427
|
-
}
|
|
428
|
-
throw err;
|
|
429
|
-
}
|
|
430
|
-
if (delAlloc.inserted) {
|
|
431
|
-
await bestEffortAppendDelete({
|
|
432
|
-
db: vctx.sql.db,
|
|
433
|
-
flavour: vctx.sql.flavour,
|
|
434
|
-
ownerHandle: req.ownerHandle,
|
|
435
|
-
appSlug: req.appSlug,
|
|
436
|
-
dbName,
|
|
437
|
-
docId: req.docId,
|
|
438
|
-
seq: delAlloc.seq,
|
|
439
|
-
created: now,
|
|
440
|
-
});
|
|
441
|
-
}
|
|
442
|
-
if (delAlloc.inserted) {
|
|
443
|
-
await emitBackendOnChange(vctx, {
|
|
444
|
-
ownerHandle: req.ownerHandle,
|
|
445
|
-
appSlug: req.appSlug,
|
|
446
|
-
dbName,
|
|
447
|
-
docId: req.docId,
|
|
448
|
-
seq: delAlloc.seq,
|
|
449
|
-
deleted: true,
|
|
450
|
-
doc: {},
|
|
451
|
-
originDepth: 0,
|
|
452
|
-
writerUserId: req._auth.verifiedAuth.claims.userId,
|
|
453
|
-
});
|
|
454
|
-
}
|
|
455
|
-
notifyDocChangedFanOut(vctx, {
|
|
456
|
-
ownerHandle: req.ownerHandle,
|
|
457
|
-
appSlug: req.appSlug,
|
|
458
|
-
dbName,
|
|
459
|
-
docId: req.docId,
|
|
460
|
-
channels,
|
|
461
|
+
...(req.actingHandle === undefined ? {} : { actingHandle: req.actingHandle }),
|
|
461
462
|
senderConnId: clientWsSend(ctx).connId,
|
|
462
463
|
});
|
|
463
|
-
if (
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
senderConnId: clientWsSend(ctx).connId,
|
|
464
|
+
if (answer.kind === "denied") {
|
|
465
|
+
await ctx.send.send(ctx, {
|
|
466
|
+
type: "vibes.diy.res-error",
|
|
467
|
+
error: { message: answer.message, ...(answer.code === undefined ? {} : { code: answer.code }) },
|
|
468
468
|
});
|
|
469
|
+
return Result.Ok(EventoResult.Continue);
|
|
469
470
|
}
|
|
470
|
-
purgeRootHtmlCacheSameColo(vctx, req.ownerHandle, req.appSlug);
|
|
471
471
|
await ctx.send.send(ctx, {
|
|
472
472
|
type: "vibes.diy.res-delete-doc",
|
|
473
473
|
status: "ok",
|
|
474
|
-
id:
|
|
474
|
+
id: answer.docId,
|
|
475
475
|
});
|
|
476
476
|
return Result.Ok(EventoResult.Continue);
|
|
477
477
|
}),
|