@codemem/server 0.35.2 → 0.36.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/index.js CHANGED
@@ -556,37 +556,14 @@ function queryBool(value) {
556
556
  }
557
557
  //#endregion
558
558
  //#region src/routes/memory.ts
559
- function cleanOwnershipValue(value) {
560
- if (typeof value !== "string") return null;
561
- const trimmed = value.trim();
562
- return trimmed ? trimmed : null;
563
- }
564
- function buildOwnershipContext(store) {
565
- const claimedPeerIds = new Set(store.sameActorPeerIds());
566
- return {
567
- actorId: store.actorId,
568
- claimedPeerIds,
569
- deviceId: store.deviceId,
570
- legacyActorIds: new Set(Array.from(claimedPeerIds, (peerId) => `legacy-sync:${peerId}`))
571
- };
572
- }
573
- function memoryOwnedBySelf(row, ownership, metadata = fromJson(row.metadata_json ?? null)) {
574
- const meta = metadata;
575
- const actorId = cleanOwnershipValue(row.actor_id) ?? cleanOwnershipValue(meta.actor_id);
576
- if (actorId === ownership.actorId) return true;
577
- const deviceId = cleanOwnershipValue(row.origin_device_id) ?? cleanOwnershipValue(meta.origin_device_id);
578
- if (deviceId === ownership.deviceId) return true;
579
- if (deviceId && ownership.claimedPeerIds.has(deviceId)) return true;
580
- if (actorId && ownership.legacyActorIds.has(actorId)) return true;
581
- return false;
582
- }
583
- function serializeMemoryRow(ownership, row) {
559
+ function serializeMemoryRow(ownedBySelf, row) {
584
560
  const metadata = fromJson(row.metadata_json ?? null);
561
+ const owned_by_self = ownedBySelf(row);
585
562
  return {
586
563
  ...row,
587
564
  kind: canonicalMemoryKind(row.kind ?? null, row.metadata_json),
588
565
  metadata_json: metadata,
589
- owned_by_self: memoryOwnedBySelf(row, ownership, metadata)
566
+ owned_by_self
590
567
  };
591
568
  }
592
569
  /**
@@ -642,11 +619,7 @@ function normalizeScope(raw) {
642
619
  if (value === "mine" || value === "theirs") return value;
643
620
  }
644
621
  function buildViewerMemoryFilters(store, filters) {
645
- return buildFilterClausesWithContext(filters, {
646
- actorId: store.actorId,
647
- deviceId: store.deviceId,
648
- enforceScopeVisibility: true
649
- });
622
+ return buildFilterClausesWithContext(filters, store.ownershipFilterContext());
650
623
  }
651
624
  function countVisibleMemoryRows(store, filters) {
652
625
  const filterResult = buildViewerMemoryFilters(store, filters);
@@ -729,8 +702,8 @@ function queryMemoryPage(store, options) {
729
702
  WHERE ${where}
730
703
  ORDER BY memory_items.created_at DESC
731
704
  LIMIT ? OFFSET ?`).all(...filterResult.params, options.limit + 1, options.offset);
732
- const ownership = buildOwnershipContext(store);
733
- return rows.map((row) => serializeMemoryRow(ownership, row));
705
+ const ownedBySelf = store.buildOwnershipPredicate();
706
+ return rows.map((row) => serializeMemoryRow(ownedBySelf, row));
734
707
  }
735
708
  function isSummaryLikeMemory$1(item) {
736
709
  return isSummaryLikeMemory({
@@ -1010,7 +983,7 @@ function memoryRoutes(getStore) {
1010
983
  if (!store.get(memoryId)) return c.json({ error: "memory not found" }, 404);
1011
984
  const row = drizzle(store.db, { schema }).select().from(schema.memoryItems).where(eq(schema.memoryItems.id, memoryId)).get();
1012
985
  if (!row) return c.json({ error: "memory not found" }, 404);
1013
- if (!memoryOwnedBySelf(row, buildOwnershipContext(store))) return c.json({ error: "memory not owned by this device" }, 403);
986
+ if (!store.memoryOwnedBySelf(row)) return c.json({ error: "memory not owned by this device" }, 403);
1014
987
  if (Number(row.active ?? 1) === 0 || row.deleted_at != null) return c.json({ status: "ok" });
1015
988
  try {
1016
989
  store.forget(memoryId);
@@ -1858,13 +1831,12 @@ async function maybeGrantDefaultSpaceOnJoin(opts) {
1858
1831
  const scopeId = preferences.default_space_scope_id?.trim();
1859
1832
  if (!scopeId) return null;
1860
1833
  if (scopeId !== defaultSpaceScopeIdForGroup(opts.groupId)) return null;
1861
- const defaultScope = (await coordinatorListScopesAction({
1834
+ if (((await coordinatorListScopesAction({
1862
1835
  groupId: opts.groupId,
1863
1836
  includeInactive: false,
1864
1837
  remoteUrl: opts.config.syncCoordinatorUrl || null,
1865
1838
  adminSecret: opts.config.syncCoordinatorAdminSecret || null
1866
- })).find((scope) => scope.scope_id === scopeId) ?? null;
1867
- if (!defaultScope || defaultScope.kind !== "team_default") return null;
1839
+ })).find((scope) => scope.scope_id === scopeId) ?? null)?.kind !== "team_default") return null;
1868
1840
  return await coordinatorGrantScopeMembershipAction({
1869
1841
  groupId: opts.groupId,
1870
1842
  scopeId,
@@ -3107,9 +3079,14 @@ var PEERS_QUERY = `
3107
3079
  * honor scoped-sync wire features such as explicit `scope_id` parameters
3108
3080
  * and `/v1/status` `authorized_scopes` enumeration.
3109
3081
  *
3110
- * Unsigned headers are fine here: the server is computing what it is
3111
- * willing to do, not granting access. Per-scope authorization is enforced
3112
- * separately via the membership cache when a scope_id is actually used.
3082
+ * Threat model: this header is intentionally only a best-effort capability
3083
+ * advertisement. It is not signed, so an authenticated paired peer — or a
3084
+ * bootstrap worker presenting a valid grant on `/v1/status` — can claim
3085
+ * `scoped` support and cause status to enumerate the scopes that caller is
3086
+ * already authorized to see. That is metadata disclosure inside the paired-peer
3087
+ * or bootstrap-grant trust boundary, not data authorization. Every scoped data
3088
+ * path still requires signed requests and checks membership before returning
3089
+ * scope contents.
3113
3090
  */
3114
3091
  function negotiatedSyncCapability(c) {
3115
3092
  return negotiateSyncCapability(LOCAL_SYNC_CAPABILITY, normalizeSyncCapability(c.req.header(SYNC_CAPABILITY_HEADER) ?? null));
@@ -3157,10 +3134,8 @@ function syncProtocolRoutes(getStore, opts = {}) {
3157
3134
  }
3158
3135
  if (isSyncAuthStoreBusy(auth)) return syncAuthStoreBusyResponse(c);
3159
3136
  if (!auth.ok) {
3160
- if (bootstrapAttempted) {
3161
- const grantPresent = Boolean((c.req.header("X-Codemem-Bootstrap-Grant") ?? "").trim());
3162
- console.warn(`[sync] bootstrap grant auth failed: reason=${auth.reason} grant_present=${grantPresent} path=${c.req.path}`);
3163
- }
3137
+ const grantPresent = Boolean((c.req.header("X-Codemem-Bootstrap-Grant") ?? "").trim());
3138
+ if (bootstrapAttempted && grantPresent) console.warn(`[sync] bootstrap grant auth failed: reason=${auth.reason} grant_present=${grantPresent} path=${c.req.path}`);
3164
3139
  const wireReason = bootstrapAttempted ? "bootstrap_grant_invalid" : auth.reason;
3165
3140
  return (preauthChecked ? null : rateLimitedResponse(c, c.req.path, false)) ?? c.json(unauthorizedPayload(wireReason, true), 401);
3166
3141
  }
@@ -3268,10 +3243,8 @@ function syncProtocolRoutes(getStore, opts = {}) {
3268
3243
  }
3269
3244
  if (isSyncAuthStoreBusy(auth)) return syncAuthStoreBusyResponse(c);
3270
3245
  if (!auth.ok) {
3271
- if (bootstrapAttempted) {
3272
- const grantPresent = Boolean((c.req.header("X-Codemem-Bootstrap-Grant") ?? "").trim());
3273
- console.warn(`[sync] bootstrap grant auth failed: reason=${auth.reason} grant_present=${grantPresent} path=${c.req.path}`);
3274
- }
3246
+ const grantPresent = Boolean((c.req.header("X-Codemem-Bootstrap-Grant") ?? "").trim());
3247
+ if (bootstrapAttempted && grantPresent) console.warn(`[sync] bootstrap grant auth failed: reason=${auth.reason} grant_present=${grantPresent} path=${c.req.path}`);
3275
3248
  const wireReason = bootstrapAttempted ? "bootstrap_grant_invalid" : auth.reason;
3276
3249
  return (preauthChecked ? null : rateLimitedResponse(c, c.req.path, false)) ?? c.json(unauthorizedPayload(wireReason, true), 401);
3277
3250
  }