deepspace 0.27.1 → 0.28.1

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/dist/server.d.ts CHANGED
@@ -828,6 +828,20 @@ interface AppRoleEnv {
828
828
  DEEPSPACE_APP_ID: string;
829
829
  OWNER_USER_ID: string;
830
830
  }
831
+ interface AppMembership {
832
+ /** True when the caller's row exists in the app's canonical users collection. */
833
+ member: boolean;
834
+ role: Role;
835
+ }
836
+ /**
837
+ * Resolve a user's membership and role from the app's canonical users
838
+ * collection in one read. This is the single definition of "is this user in
839
+ * the app" — gate new surfaces with it rather than re-deriving membership
840
+ * from another tool call. Returns null when the read itself failed (room
841
+ * unreachable, aborted): callers must treat that as "could not verify", never
842
+ * as "not a member".
843
+ */
844
+ declare function resolveAppMembership(env: AppRoleEnv, userId: string, signal?: AbortSignal): Promise<AppMembership | null>;
831
845
  /** Resolve a user's current role from the app's canonical users collection. */
832
846
  declare function resolveAppRole(env: AppRoleEnv, userId: string): Promise<Role>;
833
847
 
@@ -3096,8 +3110,10 @@ declare function handleDelete(ctx: RecordContext, ws: WebSocket, attachment: Con
3096
3110
  * that have already been authorized at the app level.
3097
3111
  * @param systemUpdate - When true, also skip system-managed field stripping.
3098
3112
  * Used for server-initiated updates to system fields (e.g. user profile sync).
3113
+ * @param updateOnly - When true, an absent recordId is refused instead of
3114
+ * created. `records.update`'s mode: updates never create.
3099
3115
  */
3100
- declare function putRecord(ctx: RecordContext, collection: string, recordId: string, data: Record<string, unknown>, userId: string, userRole: string, skipUserRbac?: boolean, systemUpdate?: boolean): ToolResult;
3116
+ declare function putRecord(ctx: RecordContext, collection: string, recordId: string, data: Record<string, unknown>, userId: string, userRole: string, skipUserRbac?: boolean, systemUpdate?: boolean, updateOnly?: boolean): ToolResult;
3101
3117
  /**
3102
3118
  * Delete a record. Returns ToolResult instead of sending WS messages.
3103
3119
  * Performs RBAC check and broadcasts the deletion.
@@ -3352,6 +3368,12 @@ interface VerifyOutcome {
3352
3368
  */
3353
3369
  declare function verifyJwt(config: JwtVerifierConfig, token: string | null | undefined): Promise<VerifyOutcome>;
3354
3370
 
3371
+ declare const AGENT_TOKEN_SCOPE = "agent";
3372
+ /** A separate issuer keeps agent credentials invalid at ordinary platform APIs. */
3373
+ declare function agentTokenIssuer(issuer: string): string;
3374
+ /** Verify an agent credential for the exact origin currently receiving it. */
3375
+ declare function verifyAgentToken(config: Omit<JwtVerifierConfig, 'audience'>, token: string | null | undefined, targetOrigin: string): Promise<VerifyOutcome>;
3376
+
3355
3377
  /**
3356
3378
  * HMAC primitives used by the per-app identity tokens (APP_IDENTITY_TOKEN)
3357
3379
  * minted at deploy time and verified on every /internal/* call into the
@@ -4632,4 +4654,4 @@ declare class RefundError extends Error {
4632
4654
  constructor(message: string, status: number);
4633
4655
  }
4634
4656
 
4635
- export { ACCOUNT_STORAGE_LIMIT_BYTES, AI_CHATS_SCHEMA, AI_MESSAGES_SCHEMA, ALLOWED_BINDING_TYPES, APP_ID_RE, APP_NAME_RULES, AUTO_PROVISIONABLE_TYPES, AUTO_PROVISION_SENTINEL, type ActionContext, type ActionHandler, type ActionResult, type ActionTools, type ApiWorkerEnv, type AppNameResolution, type AppNameValidation, type AuthWorkerEnv, BASE_USERS_SCHEMA, BUILT_IN_TOOLS, BaseRoom, type BindingJsonValue, CHANNELS_SCHEMA, CHANNEL_MEMBERS_SCHEMA, COST_RATES, CancelSubscriptionError, type CancelSubscriptionOpts, type CancelSubscriptionResult, CanvasRoom, type CanvasShape, type ChatContextConfig, type ChatMessageRow, type ChatRow, type ChatTurn, type CollectionSchema, type ColumnDefinition, type ColumnInterpretation, type ComposioToolsOptions, type CronContext, type CronExecution, CronRoom, type CronRoomConfig, type CronTask, type CustomBinding, type CustomBindingManifest, DEFAULT_CONTEXT_CONFIG, DEFAULT_DELETE_WHERE_LIMIT, DEFAULT_DO_MANIFEST, DEFAULT_QUERY_LIMIT, DEPLOYED_APPS_DEFAULTS, type DOBindings, type DOManifest, type DOManifestEntry, type DebugApiContext, type DeepSpaceAIEnv, type DeepSpaceAIOptions, type DeepSpaceAgentDiagnosticContext, DeepSpaceAgentModelError, DeepSpaceAgentProfileError, type DeepSpaceAgentStream, type DeepSpaceAgentStreamOptions, type DeepSpaceAuth, type DeepSpaceAuthConfig, type DoMigrationDirective, type DoMigrationPlan, type ExistingDOBinding, type GetActionData, type Job, type JobContext, JobRoom, type JobRoomConfig, type JobStatus, type JwtClaims, type JwtVerifierConfig, type KnowledgeAddOptions, type KnowledgeAddResult, type KnowledgeClient, type KnowledgeEnv, KnowledgeError, type KnowledgeItem, type KnowledgeListOptions, type KnowledgeListResult, type KnowledgeSearchChunk, type KnowledgeSearchOptions, type KnowledgeSearchResult, type KnowledgeStatus, MAX_APP_FILE_BYTES, MAX_DELETE_WHERE_LIMIT, MAX_DEPLOY_ASSET_FILE_BYTES, MAX_ENV_VAR_BYTES, MAX_UPLOAD_PARTS, MAX_UPLOAD_REQUEST_BYTES, MESSAGES_SCHEMA, type MutateActionData, type PermissionContext, type PermissionLevel, type PlanInfo, type PlatformWorkerEnv, type PrefixResult, type PresencePeer, PresenceRoom, type QueryActionData, REACTIONS_SCHEMA, READ_RECEIPTS_SCHEMA, REGISTRY_INSTANCE, RESERVED_BINDING_NAMES, RESOURCE_ID_RE, type RecordContext, RecordRoom, type RecordRoomConfig, RefundError, type RefundOpts, type RefundResult, RegistryClientError, type ResolvedColumn, type RolePermissions, type RunMigrationsResult, STRICT_APP_ID_RE, SYSTEM_ASSIGNED_COLUMNS, SYSTEM_COLLECTION_SCHEMAS, SYSTEM_MAINTAINED_COLUMNS, SYSTEM_MANAGED_COLUMNS, SchemaRegistry, type ScopeContext, type ScopedKnowledgeClient, type ScopedR2Auth, type ScopedR2Config, type ScopedR2Handler, type ScreenshotEnv, type ScreenshotResult, type StorageAdmission, type StoredRecord, SubscriptionAuthError, type SubscriptionContext, type SubscriptionRead, SubscriptionRequiredError, type SubscriptionStatus, type Summarizer, TEST_ACCOUNT_EMAIL_SUFFIX, type TokenDebugInfo, type ToolResult, type ToolSchema, type ToolsApiContext, UPLOAD_PART_BYTES, USERS_COLUMNS, type User, type UserAttachment, type UserContext, type ValidationError, type VerifiedAuth, type VerifyOutcome, type VerifyResult, type Viewport, type YjsContext, YjsRoom, apiWorkerFetch, appendMessage, applyAiToolDefaults, applySlidingWindow, armCronRoom, authWorkerFetch, bindingManifestFromOutputConfig, broadcastChange, broadcastUserList, broadcastYjsUpdate, buildCronContext, buildTableSelect, buildUiParts, callRegistry, canCreate, canDelete, canRead, canUpdate, cancelSubscription, capToolResultSize, captureScreenshot, checkFieldPermissions, checkUnclaimedOwnerTransition, coerceValue, collectionTableName, columnId, composioTools, computeDoMigration, computeHmacHex, createChat, createDeepSpaceAI, createDeepSpaceAuth, createScopedR2Handler, dataToColumnValues, decodeJwtPayload, deepSpaceAgentErrorSummary, deleteChatCascade, deleteRecord, enqueueJob, epochSeconds, executeQuery, folderFilter, formatBytes, getAllUsers, getChat, getOrCreateYjsDoc, getRecord, getRolePermissions, getSubscription, getUser, getYjsDocKey, handleApiRequest, handleDelete, handlePut, handleSetRole, handleSubscribe, handleToolsRequest, handleUnsubscribe, handleUserList, handleUserUpdate, handleYjsBinaryMessage, handleYjsJoin, handleYjsLeave, isAutoProvision, isFilesVerbPath, isOwner, isTestAccountClaims, isTestAccountEmail, isTestAccountTier, knowledge, lintSchema, lintSchemaSet, lintSchemas, loadMessages, makeDefaultSummarizer, matchOrigin, meterAi, meterUsage, meterVectorize, mintAppId, mintUlid, noopPermissionContext, normalizeKnowledgeFolder, parseQuotaLimit, platformCorsPatterns, platformWorkerFetch, prepareMessagesWithCompaction, priceBindingUsageEvent, putRecord, readRecord, recordMatchesSubscription, refundInvoice, registerUser, registryErrorJson, registryStub, requireSubscription, resolveAppName, resolveAppRole, resolveColumn, rowToData, runMigrations, saveYjsDoc, storageLimitForTier, storageQuotaMessage, streamDeepSpaceAgent, timingSafeEqualHex, timingSafeEqualStrings, totalChars, truncateOldToolResults, turnsToCoreMessages, unwrapToolOutput, updateChat, validateAppName, validateBindingManifest, validateDoManifest, verifyJwt };
4657
+ export { ACCOUNT_STORAGE_LIMIT_BYTES, AGENT_TOKEN_SCOPE, AI_CHATS_SCHEMA, AI_MESSAGES_SCHEMA, ALLOWED_BINDING_TYPES, APP_ID_RE, APP_NAME_RULES, AUTO_PROVISIONABLE_TYPES, AUTO_PROVISION_SENTINEL, type ActionContext, type ActionHandler, type ActionResult, type ActionTools, type ApiWorkerEnv, type AppMembership, type AppNameResolution, type AppNameValidation, type AuthWorkerEnv, BASE_USERS_SCHEMA, BUILT_IN_TOOLS, BaseRoom, type BindingJsonValue, CHANNELS_SCHEMA, CHANNEL_MEMBERS_SCHEMA, COST_RATES, CancelSubscriptionError, type CancelSubscriptionOpts, type CancelSubscriptionResult, CanvasRoom, type CanvasShape, type ChatContextConfig, type ChatMessageRow, type ChatRow, type ChatTurn, type CollectionSchema, type ColumnDefinition, type ColumnInterpretation, type ComposioToolsOptions, type CronContext, type CronExecution, CronRoom, type CronRoomConfig, type CronTask, type CustomBinding, type CustomBindingManifest, DEFAULT_CONTEXT_CONFIG, DEFAULT_DELETE_WHERE_LIMIT, DEFAULT_DO_MANIFEST, DEFAULT_QUERY_LIMIT, DEPLOYED_APPS_DEFAULTS, type DOBindings, type DOManifest, type DOManifestEntry, type DebugApiContext, type DeepSpaceAIEnv, type DeepSpaceAIOptions, type DeepSpaceAgentDiagnosticContext, DeepSpaceAgentModelError, DeepSpaceAgentProfileError, type DeepSpaceAgentStream, type DeepSpaceAgentStreamOptions, type DeepSpaceAuth, type DeepSpaceAuthConfig, type DoMigrationDirective, type DoMigrationPlan, type ExistingDOBinding, type GetActionData, type Job, type JobContext, JobRoom, type JobRoomConfig, type JobStatus, type JwtClaims, type JwtVerifierConfig, type KnowledgeAddOptions, type KnowledgeAddResult, type KnowledgeClient, type KnowledgeEnv, KnowledgeError, type KnowledgeItem, type KnowledgeListOptions, type KnowledgeListResult, type KnowledgeSearchChunk, type KnowledgeSearchOptions, type KnowledgeSearchResult, type KnowledgeStatus, MAX_APP_FILE_BYTES, MAX_DELETE_WHERE_LIMIT, MAX_DEPLOY_ASSET_FILE_BYTES, MAX_ENV_VAR_BYTES, MAX_UPLOAD_PARTS, MAX_UPLOAD_REQUEST_BYTES, MESSAGES_SCHEMA, type MutateActionData, type PermissionContext, type PermissionLevel, type PlanInfo, type PlatformWorkerEnv, type PrefixResult, type PresencePeer, PresenceRoom, type QueryActionData, REACTIONS_SCHEMA, READ_RECEIPTS_SCHEMA, REGISTRY_INSTANCE, RESERVED_BINDING_NAMES, RESOURCE_ID_RE, type RecordContext, RecordRoom, type RecordRoomConfig, RefundError, type RefundOpts, type RefundResult, RegistryClientError, type ResolvedColumn, type RolePermissions, type RunMigrationsResult, STRICT_APP_ID_RE, SYSTEM_ASSIGNED_COLUMNS, SYSTEM_COLLECTION_SCHEMAS, SYSTEM_MAINTAINED_COLUMNS, SYSTEM_MANAGED_COLUMNS, SchemaRegistry, type ScopeContext, type ScopedKnowledgeClient, type ScopedR2Auth, type ScopedR2Config, type ScopedR2Handler, type ScreenshotEnv, type ScreenshotResult, type StorageAdmission, type StoredRecord, SubscriptionAuthError, type SubscriptionContext, type SubscriptionRead, SubscriptionRequiredError, type SubscriptionStatus, type Summarizer, TEST_ACCOUNT_EMAIL_SUFFIX, type TokenDebugInfo, type ToolResult, type ToolSchema, type ToolsApiContext, UPLOAD_PART_BYTES, USERS_COLUMNS, type User, type UserAttachment, type UserContext, type ValidationError, type VerifiedAuth, type VerifyOutcome, type VerifyResult, type Viewport, type YjsContext, YjsRoom, agentTokenIssuer, apiWorkerFetch, appendMessage, applyAiToolDefaults, applySlidingWindow, armCronRoom, authWorkerFetch, bindingManifestFromOutputConfig, broadcastChange, broadcastUserList, broadcastYjsUpdate, buildCronContext, buildTableSelect, buildUiParts, callRegistry, canCreate, canDelete, canRead, canUpdate, cancelSubscription, capToolResultSize, captureScreenshot, checkFieldPermissions, checkUnclaimedOwnerTransition, coerceValue, collectionTableName, columnId, composioTools, computeDoMigration, computeHmacHex, createChat, createDeepSpaceAI, createDeepSpaceAuth, createScopedR2Handler, dataToColumnValues, decodeJwtPayload, deepSpaceAgentErrorSummary, deleteChatCascade, deleteRecord, enqueueJob, epochSeconds, executeQuery, folderFilter, formatBytes, getAllUsers, getChat, getOrCreateYjsDoc, getRecord, getRolePermissions, getSubscription, getUser, getYjsDocKey, handleApiRequest, handleDelete, handlePut, handleSetRole, handleSubscribe, handleToolsRequest, handleUnsubscribe, handleUserList, handleUserUpdate, handleYjsBinaryMessage, handleYjsJoin, handleYjsLeave, isAutoProvision, isFilesVerbPath, isOwner, isTestAccountClaims, isTestAccountEmail, isTestAccountTier, knowledge, lintSchema, lintSchemaSet, lintSchemas, loadMessages, makeDefaultSummarizer, matchOrigin, meterAi, meterUsage, meterVectorize, mintAppId, mintUlid, noopPermissionContext, normalizeKnowledgeFolder, parseQuotaLimit, platformCorsPatterns, platformWorkerFetch, prepareMessagesWithCompaction, priceBindingUsageEvent, putRecord, readRecord, recordMatchesSubscription, refundInvoice, registerUser, registryErrorJson, registryStub, requireSubscription, resolveAppMembership, resolveAppName, resolveAppRole, resolveColumn, rowToData, runMigrations, saveYjsDoc, storageLimitForTier, storageQuotaMessage, streamDeepSpaceAgent, timingSafeEqualHex, timingSafeEqualStrings, totalChars, truncateOldToolResults, turnsToCoreMessages, unwrapToolOutput, updateChat, validateAppName, validateBindingManifest, validateDoManifest, verifyAgentToken, verifyJwt };
package/dist/server.js CHANGED
@@ -408,10 +408,10 @@ async function writeQuotaSummary(bucket, key, previousEtag, summary) {
408
408
  });
409
409
  return object ? { etag: object.etag, summary } : null;
410
410
  }
411
- async function ensureQuotaSummary(bucket, storage, prefixes, forceMeasure = false, excludedMarkerKey) {
411
+ async function ensureQuotaSummary(bucket, storage, prefixes, measure = "reuse", excludedMarkerKey) {
412
412
  for (let attempt = 0; attempt < 4; attempt++) {
413
413
  const current = await readQuotaSummary(bucket, storage.quotaKey);
414
- if (!forceMeasure && current.summary && accountPrefixesEqual(current.summary.prefixes, prefixes)) {
414
+ if (measure !== "force" && current.summary && accountPrefixesEqual(current.summary.prefixes, prefixes) && !(measure === "repairDirty" && current.summary.dirty && Date.now() - current.summary.measuredAt >= QUOTA_REPAIR_COOLDOWN_MS)) {
415
415
  return current;
416
416
  }
417
417
  const usedBytes = await measuredAllocationUsage(bucket, storage, prefixes, excludedMarkerKey);
@@ -599,7 +599,7 @@ async function reserveQuotaSummary(bucket, storage, prefixes, initial, chargeByt
599
599
  bucket,
600
600
  storage,
601
601
  prefixes,
602
- false,
602
+ "reuse",
603
603
  markerKey ?? void 0
604
604
  );
605
605
  if (!ensured) {
@@ -624,7 +624,7 @@ async function reserveQuotaSummary(bucket, storage, prefixes, initial, chargeByt
624
624
  bucket,
625
625
  storage,
626
626
  prefixes,
627
- true,
627
+ "force",
628
628
  markerKey ?? void 0
629
629
  );
630
630
  if (!refreshed?.summary) {
@@ -717,7 +717,7 @@ async function storageUsage(bucket, storage) {
717
717
  try {
718
718
  const prefixes = await allocationPrefixes(storage);
719
719
  if (prefixes === null) return null;
720
- const current = await ensureQuotaSummary(bucket, storage, prefixes);
720
+ const current = await ensureQuotaSummary(bucket, storage, prefixes, "repairDirty");
721
721
  return current?.summary?.usedBytes ?? null;
722
722
  } catch (error) {
723
723
  console.error("[files:quota] usage:", error);
@@ -1676,8 +1676,8 @@ function isWriterRole(role) {
1676
1676
  }
1677
1677
 
1678
1678
  // src/server/utils/app-role.ts
1679
- async function resolveAppRole(env, userId) {
1680
- if (userId === env.OWNER_USER_ID) return "admin";
1679
+ async function resolveAppMembership(env, userId, signal) {
1680
+ if (userId === env.OWNER_USER_ID) return { member: true, role: "admin" };
1681
1681
  const stub = env.RECORD_ROOMS.get(env.RECORD_ROOMS.idFromName(`app:${env.DEEPSPACE_APP_ID}`));
1682
1682
  try {
1683
1683
  const res = await stub.fetch(
@@ -1691,16 +1691,21 @@ async function resolveAppRole(env, userId) {
1691
1691
  body: JSON.stringify({
1692
1692
  tool: "records.get",
1693
1693
  params: { collection: "users", recordId: userId }
1694
- })
1694
+ }),
1695
+ signal
1695
1696
  })
1696
1697
  );
1697
1698
  const json = await res.json();
1698
- const role = json.data?.record?.data?.role;
1699
- return json.success && isWriterRole(role) ? role : "viewer";
1699
+ if (!json.success || !json.data?.record) return { member: false, role: "viewer" };
1700
+ const role = json.data.record.data?.role;
1701
+ return { member: true, role: isWriterRole(role) ? role : "viewer" };
1700
1702
  } catch {
1701
- return "viewer";
1703
+ return null;
1702
1704
  }
1703
1705
  }
1706
+ async function resolveAppRole(env, userId) {
1707
+ return (await resolveAppMembership(env, userId))?.role ?? "viewer";
1708
+ }
1704
1709
 
1705
1710
  // src/server/utils/ai.ts
1706
1711
  import { createAnthropic } from "@ai-sdk/anthropic";
@@ -2248,6 +2253,9 @@ function locateTrimmableArray(result) {
2248
2253
  }
2249
2254
  return void 0;
2250
2255
  }
2256
+ function utf8ByteLength(text) {
2257
+ return new TextEncoder().encode(text).byteLength;
2258
+ }
2251
2259
  function capToolResultSize(result, byteCap) {
2252
2260
  let serialized;
2253
2261
  try {
@@ -2256,7 +2264,7 @@ function capToolResultSize(result, byteCap) {
2256
2264
  return { success: false, error: "Tool result could not be serialized." };
2257
2265
  }
2258
2266
  if (typeof serialized !== "string") return result;
2259
- if (serialized.length <= byteCap) return result;
2267
+ if (utf8ByteLength(serialized) <= byteCap) return result;
2260
2268
  if (result && typeof result === "object") {
2261
2269
  const located = locateTrimmableArray(result);
2262
2270
  if (located) {
@@ -2265,7 +2273,7 @@ function capToolResultSize(result, byteCap) {
2265
2273
  const build = (k) => setPath(result, path, items.slice(0, k), { truncated: true, returned: k, total });
2266
2274
  const fits = (k) => {
2267
2275
  try {
2268
- return JSON.stringify(build(k)).length <= byteCap;
2276
+ return utf8ByteLength(JSON.stringify(build(k))) <= byteCap;
2269
2277
  } catch {
2270
2278
  return false;
2271
2279
  }
@@ -2285,7 +2293,7 @@ function capToolResultSize(result, byteCap) {
2285
2293
  return {
2286
2294
  success: false,
2287
2295
  truncated: true,
2288
- error: `Tool result exceeded ${byteCap} bytes (was ${serialized.length}). Retry with a narrower query (e.g. add a \`where\` filter, reduce \`limit\`, or call records.get for a single record).`,
2296
+ error: `Tool result exceeded ${byteCap} bytes (was ${utf8ByteLength(serialized)}). Retry with a narrower query (e.g. add a \`where\` filter, reduce \`limit\`, or call records.get for a single record).`,
2289
2297
  preview: serialized.slice(0, 2e3)
2290
2298
  };
2291
2299
  }
@@ -2437,10 +2445,15 @@ async function prepareMessagesWithCompaction(messages, config, options) {
2437
2445
  newSummary: { text, throughId }
2438
2446
  };
2439
2447
  } catch (err) {
2440
- console.error("prepareMessagesWithCompaction: summarizer failed, falling back to sliding window", err);
2448
+ console.error(
2449
+ "prepareMessagesWithCompaction: summarizer failed, falling back to sliding window",
2450
+ err
2451
+ );
2441
2452
  }
2442
2453
  } else {
2443
- console.warn("prepareMessagesWithCompaction: messages lack ids; cannot anchor a summary, falling back to sliding window");
2454
+ console.warn(
2455
+ "prepareMessagesWithCompaction: messages lack ids; cannot anchor a summary, falling back to sliding window"
2456
+ );
2444
2457
  }
2445
2458
  return {
2446
2459
  messages: applySlidingWindow(working, config.contextBudget, config.minKept)
@@ -3876,7 +3889,7 @@ function handleDelete(ctx, ws, attachment, payload) {
3876
3889
  ctx.send(ws, serverBuild.error(result.error));
3877
3890
  }
3878
3891
  }
3879
- function putRecord(ctx, collection, recordId, data, userId, userRole, skipUserRbac = false, systemUpdate = false) {
3892
+ function putRecord(ctx, collection, recordId, data, userId, userRole, skipUserRbac = false, systemUpdate = false, updateOnly = false) {
3880
3893
  const resolved = resolveCollection(ctx, collection);
3881
3894
  if (!resolved.ok) return { success: false, error: resolved.error };
3882
3895
  const schema = resolved.schema;
@@ -3894,6 +3907,12 @@ function putRecord(ctx, collection, recordId, data, userId, userRole, skipUserRb
3894
3907
  }
3895
3908
  const existing = getRecord(ctx.sql, collection, recordId, schema);
3896
3909
  const isUpdate = existing !== null;
3910
+ if (updateOnly && !isUpdate) {
3911
+ return {
3912
+ success: false,
3913
+ error: `records.update: no record "${recordId}" in ${collection} \u2014 updates never create; use records.create.`
3914
+ };
3915
+ }
3897
3916
  const mergedData = isUpdate ? { ...existing.data, ...data } : { ...data };
3898
3917
  for (const col of columns) {
3899
3918
  if (col.readonly) continue;
@@ -5052,7 +5071,7 @@ async function executeTool(ctx, toolName, params, userId, userRole, skipUserRbac
5052
5071
  if (!collection) return { success: false, error: "Missing required param: collection" };
5053
5072
  if (!recordId) return { success: false, error: "Missing required param: recordId" };
5054
5073
  if (!data) return { success: false, error: "Missing required param: data" };
5055
- return putRecord(ctx, collection, recordId, data, userId, userRole, skipUserRbac);
5074
+ return putRecord(ctx, collection, recordId, data, userId, userRole, skipUserRbac, false, true);
5056
5075
  }
5057
5076
  case "records.delete": {
5058
5077
  const collection = params.collection;
@@ -8312,6 +8331,24 @@ async function verifyJwt(config, token) {
8312
8331
  }
8313
8332
  }
8314
8333
 
8334
+ // src/server/auth/agentToken.ts
8335
+ var AGENT_TOKEN_SCOPE = "agent";
8336
+ function agentTokenIssuer(issuer) {
8337
+ return `${issuer.replace(/\/+$/, "")}/agent`;
8338
+ }
8339
+ async function verifyAgentToken(config, token, targetOrigin) {
8340
+ const outcome = await verifyJwt(
8341
+ {
8342
+ ...config,
8343
+ issuer: agentTokenIssuer(config.issuer),
8344
+ audience: targetOrigin
8345
+ },
8346
+ token
8347
+ );
8348
+ if (!outcome.result || outcome.result.claims.scope === AGENT_TOKEN_SCOPE) return outcome;
8349
+ return { ...outcome, result: null, error: new Error("Invalid agent token scope") };
8350
+ }
8351
+
8315
8352
  // src/server/auth/internalAuth.ts
8316
8353
  var nodeCryptoPromise = null;
8317
8354
  async function loadNodeCrypto() {
@@ -8641,6 +8678,7 @@ var RefundError = class extends Error {
8641
8678
  };
8642
8679
  export {
8643
8680
  ACCOUNT_STORAGE_LIMIT_BYTES,
8681
+ AGENT_TOKEN_SCOPE,
8644
8682
  AI_CHATS_SCHEMA,
8645
8683
  AI_MESSAGES_SCHEMA,
8646
8684
  ALLOWED_BINDING_TYPES,
@@ -8694,6 +8732,7 @@ export {
8694
8732
  UPLOAD_PART_BYTES,
8695
8733
  USERS_COLUMNS,
8696
8734
  YjsRoom,
8735
+ agentTokenIssuer,
8697
8736
  apiWorkerFetch,
8698
8737
  appendMessage,
8699
8738
  applyAiToolDefaults,
@@ -8790,6 +8829,7 @@ export {
8790
8829
  registryErrorJson,
8791
8830
  registryStub,
8792
8831
  requireSubscription,
8832
+ resolveAppMembership,
8793
8833
  resolveAppName,
8794
8834
  resolveAppRole,
8795
8835
  resolveColumn,
@@ -8809,6 +8849,7 @@ export {
8809
8849
  validateAppName,
8810
8850
  validateBindingManifest,
8811
8851
  validateDoManifest,
8852
+ verifyAgentToken,
8812
8853
  verifyJwt
8813
8854
  };
8814
8855
  //# sourceMappingURL=server.js.map