@codemem/server 0.30.0 → 0.31.0

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.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,KAAK,eAAe,EAAiB,OAAO,EAAE,MAAM,eAAe,CAAC;AAE1F,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,OAAO,EAEN,KAAK,0BAA0B,EAC/B,MAAM,yBAAyB,CAAC;AAQjC,OAAO,EAAE,OAAO,EAAE,CAAC;AAKnB,uFAAuF;AACvF,wBAAgB,QAAQ,IAAI,WAAW,CAKtC;AAED,mDAAmD;AACnD,wBAAgB,UAAU,IAAI,IAAI,CAGjC;AAED;;;GAGG;AACH,MAAM,WAAW,UAAU;IAC1B,YAAY,CAAC,EAAE,MAAM,WAAW,CAAC;IACjC,OAAO,CAAC,EAAE,eAAe,GAAG,IAAI,CAAC;IACjC,QAAQ,CAAC,EAAE,cAAc,GAAG,IAAI,CAAC;IACjC,oBAAoB,CAAC,EAAE;QACtB,OAAO,CAAC,EAAE,0BAA0B,CAAC;QACrC,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,wBAAwB,CAAC,EAAE,MAAM,CAAC;QAClC,4BAA4B,CAAC,EAAE,MAAM,CAAC;KACtC,CAAC;IACF,oBAAoB,CAAC,EAAE,MAAM;QAC5B,KAAK,EACF,UAAU,GACV,SAAS,GACT,UAAU,GACV,OAAO,GACP,UAAU,GACV,iBAAiB,GACjB,iBAAiB,GACjB,IAAI,CAAC;QACR,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KACvB,GAAG,IAAI,CAAC;CACT;AAED,wBAAgB,SAAS,CAAC,IAAI,CAAC,EAAE,UAAU,8EAuD1C;AAED;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,IAAI,CAAC,EAAE,UAAU,8EA8B9C"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,KAAK,eAAe,EAAiB,OAAO,EAAE,MAAM,eAAe,CAAC;AAE1F,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,OAAO,EAEN,KAAK,0BAA0B,EAC/B,MAAM,yBAAyB,CAAC;AAQjC,OAAO,EAAE,OAAO,EAAE,CAAC;AAKnB,uFAAuF;AACvF,wBAAgB,QAAQ,IAAI,WAAW,CAKtC;AAED,mDAAmD;AACnD,wBAAgB,UAAU,IAAI,IAAI,CAGjC;AAED;;;GAGG;AACH,MAAM,WAAW,UAAU;IAC1B,YAAY,CAAC,EAAE,MAAM,WAAW,CAAC;IACjC,OAAO,CAAC,EAAE,eAAe,GAAG,IAAI,CAAC;IACjC,QAAQ,CAAC,EAAE,cAAc,GAAG,IAAI,CAAC;IACjC,oBAAoB,CAAC,EAAE;QACtB,OAAO,CAAC,EAAE,0BAA0B,CAAC;QACrC,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,wBAAwB,CAAC,EAAE,MAAM,CAAC;QAClC,4BAA4B,CAAC,EAAE,MAAM,CAAC;KACtC,CAAC;IACF,oBAAoB,CAAC,EAAE,MAAM;QAC5B,KAAK,EACF,UAAU,GACV,SAAS,GACT,UAAU,GACV,OAAO,GACP,UAAU,GACV,iBAAiB,GACjB,iBAAiB,GACjB,IAAI,CAAC;QACR,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KACvB,GAAG,IAAI,CAAC;CACT;AAED,wBAAgB,SAAS,CAAC,IAAI,CAAC,EAAE,UAAU,8EA6D1C;AAED;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,IAAI,CAAC,EAAE,UAAU,8EA8B9C"}
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { existsSync, readFileSync } from "node:fs";
2
2
  import { dirname, join } from "node:path";
3
- import { CODEMEM_CONFIG_ENV_OVERRIDES, DEFAULT_TIME_WINDOW_S, LEGACY_SHARED_REVIEW_SCOPE_ID, LOCAL_DEFAULT_SCOPE_ID, LOCAL_SYNC_CAPABILITY, MemoryStore, SYNC_SCOPE_QUERY_PARAM, VERSION, VERSION as VERSION$1, addSyncScopeToBoundary, analyzeProjectScopeMappingChangeGuardrails, applyReplicationOps, buildBaseUrl, buildFilterClausesWithContext, buildRawEventEnvelopeFromHook, canonicalMemoryKind, cleanupNonces, coordinatorArchiveGroupAction, coordinatorCreateGroupAction, coordinatorCreateInviteAction, coordinatorCreateScopeAction, coordinatorDisableDeviceAction, coordinatorEnableDeviceAction, coordinatorGrantScopeMembershipAction, coordinatorImportInviteAction, coordinatorListBootstrapGrantsAction, coordinatorListDevicesAction, coordinatorListGroupsAction, coordinatorListJoinRequestsAction, coordinatorListScopeMembershipsAction, coordinatorListScopesAction, coordinatorRemoveDeviceAction, coordinatorRenameDeviceAction, coordinatorRenameGroupAction, coordinatorReviewJoinRequestAction, coordinatorRevokeBootstrapGrantAction, coordinatorRevokeScopeMembershipAction, coordinatorStatusSnapshot, coordinatorUnarchiveGroupAction, coordinatorUpdateScopeAction, createCoordinatorReciprocalApproval, deleteProjectScopeSettingsMapping, ensureDeviceIdentity, extractReplicationOps, filterReplicationOpsForSyncWithStatus, fingerprintPublicKey, fromJson, getCodememConfigPath, getCodememEnvOverrides, getCoordinatorGroupPreference, getSemanticIndexDiagnostics, getSyncResetState, isSummaryLikeMemory, listCoordinatorJoinRequests, listInboundScopeRejections, listMaintenanceJobs, listObserverProviderOptions, listProjectScopeCandidates, listProjectScopeSettingsMappings, listSharingDomainSettingsScopes, loadMemorySnapshotPageForPeer, loadReplicationOpsForPeer, lookupCoordinatorPeers, mergeAddresses, negotiateSyncCapability, normalizeSyncCapability, parseStrictInteger, parseSyncScopeRequest, personalScopeGrantStatusForPeer, probeAvailableCredentials, readCodememConfigFile, readCoordinatorSyncConfig, recordNonce, rejectInboundScopeFailures, requestJson, resolveDbPath, runSyncPass, schema, stripJsonComments, stripPrivateObj, stripTrailingCommas, summarizeInboundScopeRejections, syncScopeResetRequiredPayload, updatePeerAddresses, upsertCoordinatorGroupPreference, upsertProjectScopeSettingsMapping, verifySignature, writeCodememConfigFile } from "@codemem/core";
3
+ import { CODEMEM_CONFIG_ENV_OVERRIDES, DEFAULT_TIME_WINDOW_S, LEGACY_SHARED_REVIEW_SCOPE_ID, LOCAL_DEFAULT_SCOPE_ID, LOCAL_SYNC_CAPABILITY, MemoryStore, SYNC_SCOPE_QUERY_PARAM, VERSION, VERSION as VERSION$1, addSyncScopeToBoundary, analyzeProjectScopeMappingChangeGuardrails, applyReplicationOps, buildBaseUrl, buildFilterClausesWithContext, buildRawEventEnvelopeFromHook, canonicalMemoryKind, canonicalWorkspaceIdentity, cleanupNonces, coordinatorArchiveGroupAction, coordinatorCreateGroupAction, coordinatorCreateInviteAction, coordinatorCreateScopeAction, coordinatorDisableDeviceAction, coordinatorEnableDeviceAction, coordinatorGrantScopeMembershipAction, coordinatorImportInviteAction, coordinatorListBootstrapGrantsAction, coordinatorListDevicesAction, coordinatorListGroupsAction, coordinatorListJoinRequestsAction, coordinatorListScopeMembershipsAction, coordinatorListScopesAction, coordinatorRemoveDeviceAction, coordinatorRenameDeviceAction, coordinatorRenameGroupAction, coordinatorReviewJoinRequestAction, coordinatorRevokeBootstrapGrantAction, coordinatorRevokeScopeMembershipAction, coordinatorStatusSnapshot, coordinatorUnarchiveGroupAction, coordinatorUpdateScopeAction, createCoordinatorReciprocalApproval, deleteProjectScopeSettingsMapping, ensureDeviceIdentity, extractReplicationOps, filterReplicationOpsForSyncWithStatus, fingerprintPublicKey, fromJson, getCodememConfigPath, getCodememEnvOverrides, getCoordinatorGroupPreference, getSemanticIndexDiagnostics, getSyncResetState, isSummaryLikeMemory, listCoordinatorJoinRequests, listInboundScopeRejections, listMaintenanceJobs, listObserverProviderOptions, listProjectScopeCandidates, listProjectScopeInventory, listProjectScopeSettingsMappings, listSharingDomainSettingsScopes, loadMemorySnapshotPageForPeer, loadReplicationOpsForPeer, lookupCoordinatorPeers, mergeAddresses, negotiateSyncCapability, normalizeSyncCapability, parseStrictInteger, parseSyncScopeRequest, personalScopeGrantStatusForPeer, probeAvailableCredentials, readCodememConfigFile, readCoordinatorSyncConfig, reassignProjectScopeInventoryProject, recordNonce, rejectInboundScopeFailures, requestJson, resolveDbPath, runSyncPass, schema, stripJsonComments, stripPrivateObj, stripTrailingCommas, summarizeInboundScopeRejections, syncScopeResetRequiredPayload, updatePeerAddresses, upsertCoordinatorGroupPreference, upsertProjectScopeSettingsMapping, verifySignature, writeCodememConfigFile } from "@codemem/core";
4
4
  import { serveStatic } from "@hono/node-server/serve-static";
5
5
  import { Hono } from "hono";
6
6
  import { createMiddleware } from "hono/factory";
@@ -2461,11 +2461,153 @@ function legacySharedReviewSummary(store) {
2461
2461
  AND active = 1
2462
2462
  AND deleted_at IS NULL`).get(LEGACY_SHARED_REVIEW_SCOPE_ID);
2463
2463
  const memoryCount = Number(row?.memory_count ?? 0);
2464
+ const candidatesByIdentity = new Map(listProjectScopeCandidates(store.db, { limit: null }).map((candidate) => [candidate.workspace_identity, candidate]));
2465
+ const groupRows = store.db.prepare(`SELECT s.project,
2466
+ s.cwd,
2467
+ s.git_remote,
2468
+ s.git_branch,
2469
+ m.workspace_id,
2470
+ COUNT(*) AS memory_count,
2471
+ MAX(m.updated_at) AS last_updated_at
2472
+ FROM memory_items m
2473
+ LEFT JOIN sessions s ON s.id = m.session_id
2474
+ WHERE m.scope_id = ?
2475
+ AND m.active = 1
2476
+ AND m.deleted_at IS NULL
2477
+ GROUP BY s.project, s.cwd, s.git_remote, s.git_branch, m.workspace_id
2478
+ ORDER BY memory_count DESC, last_updated_at DESC`).all(LEGACY_SHARED_REVIEW_SCOPE_ID);
2479
+ const groupsByIdentity = /* @__PURE__ */ new Map();
2480
+ for (const group of groupRows) {
2481
+ const identity = canonicalWorkspaceIdentity({
2482
+ cwd: group.cwd,
2483
+ gitBranch: group.git_branch,
2484
+ gitRemote: group.git_remote,
2485
+ project: group.project,
2486
+ workspaceId: group.workspace_id
2487
+ });
2488
+ const candidate = candidatesByIdentity.get(identity.value);
2489
+ const resolvedScope = candidate?.resolved_scope_id ?? null;
2490
+ const suggestedScope = resolvedScope && resolvedScope !== LOCAL_DEFAULT_SCOPE_ID && resolvedScope !== LEGACY_SHARED_REVIEW_SCOPE_ID ? resolvedScope : candidate?.suggested_scope_id;
2491
+ const existing = groupsByIdentity.get(identity.value);
2492
+ const memoryCount = Number(group.memory_count ?? 0);
2493
+ const lastUpdatedAt = group.last_updated_at ?? null;
2494
+ if (existing) {
2495
+ existing.memory_count += memoryCount;
2496
+ if (lastUpdatedAt && (!existing.last_updated_at || lastUpdatedAt > existing.last_updated_at)) existing.last_updated_at = lastUpdatedAt;
2497
+ continue;
2498
+ }
2499
+ groupsByIdentity.set(identity.value, {
2500
+ workspace_identity: identity.value,
2501
+ identity_source: identity.source,
2502
+ display_project: identity.displayProject ?? group.project ?? group.cwd ?? identity.value,
2503
+ memory_count: memoryCount,
2504
+ last_updated_at: lastUpdatedAt,
2505
+ suggested_scope_id: suggestedScope ?? null,
2506
+ suggestion_reason: suggestedScope ? candidate?.suggestion_reason ?? "Existing project mapping can be reviewed as a destination, but legacy data is not promoted automatically." : null
2507
+ });
2508
+ }
2509
+ const totalGroupCount = groupsByIdentity.size;
2510
+ const groups = [...groupsByIdentity.values()].sort((left, right) => right.memory_count - left.memory_count || (right.last_updated_at ?? "").localeCompare(left.last_updated_at ?? "") || left.workspace_identity.localeCompare(right.workspace_identity)).slice(0, 20);
2511
+ const targetScopes = listSharingDomainSettingsScopes(store.db).filter((scope) => scope.scope_id !== LOCAL_DEFAULT_SCOPE_ID && scope.scope_id !== LEGACY_SHARED_REVIEW_SCOPE_ID).map((scope) => ({
2512
+ authority_type: scope.authority_type,
2513
+ label: scope.label || scope.scope_id,
2514
+ scope_id: scope.scope_id
2515
+ }));
2464
2516
  return {
2465
2517
  scope_id: LEGACY_SHARED_REVIEW_SCOPE_ID,
2466
2518
  memory_count: memoryCount,
2467
2519
  has_data: memoryCount > 0,
2468
- last_updated_at: row?.last_updated_at ?? null
2520
+ last_updated_at: row?.last_updated_at ?? null,
2521
+ groups,
2522
+ total_group_count: totalGroupCount,
2523
+ target_scopes: targetScopes
2524
+ };
2525
+ }
2526
+ function assertLocalDeviceScopeMembership(store, scopeId) {
2527
+ if (scopeId === LOCAL_DEFAULT_SCOPE_ID) return;
2528
+ if (!store.db.prepare(`SELECT 1
2529
+ FROM scope_memberships sm
2530
+ JOIN replication_scopes rs ON rs.scope_id = sm.scope_id
2531
+ WHERE sm.scope_id = ?
2532
+ AND sm.device_id = ?
2533
+ AND sm.status = 'active'
2534
+ AND rs.status = 'active'
2535
+ AND sm.membership_epoch >= rs.membership_epoch
2536
+ LIMIT 1`).get(scopeId, store.deviceId)) throw new Error(`local device is not a member of Sharing domain ${scopeId}`);
2537
+ }
2538
+ function legacySharedReviewRowsForWorkspace(store, workspaceIdentity) {
2539
+ return store.db.prepare(`SELECT m.id,
2540
+ m.active,
2541
+ m.deleted_at,
2542
+ m.scope_id,
2543
+ s.project,
2544
+ s.cwd,
2545
+ s.git_remote,
2546
+ s.git_branch,
2547
+ m.workspace_id,
2548
+ m.actor_id,
2549
+ m.origin_device_id,
2550
+ m.metadata_json
2551
+ FROM memory_items m
2552
+ LEFT JOIN sessions s ON s.id = m.session_id
2553
+ WHERE m.scope_id = ?
2554
+ AND m.active = 1
2555
+ AND m.deleted_at IS NULL`).all(LEGACY_SHARED_REVIEW_SCOPE_ID).filter((row) => {
2556
+ return canonicalWorkspaceIdentity({
2557
+ cwd: row.cwd,
2558
+ gitBranch: row.git_branch,
2559
+ gitRemote: row.git_remote,
2560
+ project: row.project,
2561
+ workspaceId: row.workspace_id
2562
+ }).value === workspaceIdentity;
2563
+ });
2564
+ }
2565
+ function reassignableLegacySharedReviewRows(store, rows) {
2566
+ const reassignableRows = rows.filter((row) => store.memoryOwnedBySelf(row));
2567
+ if (reassignableRows.length === 0) throw new Error("legacy shared review group has no locally owned memories to reassign");
2568
+ const currentRows = store.db.prepare(`SELECT id, scope_id, active, deleted_at
2569
+ FROM memory_items
2570
+ WHERE id IN (${reassignableRows.map(() => "?").join(",")})`).all(...reassignableRows.map((row) => row.id));
2571
+ const currentById = new Map(currentRows.map((row) => [Number(row.id), row]));
2572
+ for (const row of reassignableRows) {
2573
+ const current = currentById.get(row.id);
2574
+ if (current?.scope_id !== LEGACY_SHARED_REVIEW_SCOPE_ID || current.active !== 1 || current.deleted_at) throw new Error("legacy shared review group changed before reassignment; refresh and try again");
2575
+ }
2576
+ return reassignableRows;
2577
+ }
2578
+ function legacySharedReviewReassignmentPreview(store, input) {
2579
+ if (!input.workspaceIdentity.trim()) throw new Error("workspace_identity must be a non-empty string");
2580
+ if (!input.scopeId.trim()) throw new Error("scope_id must be a non-empty string");
2581
+ if (input.scopeId === LOCAL_DEFAULT_SCOPE_ID) throw new Error("local-default is not a valid target for legacy shared review reassignment");
2582
+ if (input.scopeId === LEGACY_SHARED_REVIEW_SCOPE_ID) throw new Error("legacy-shared-review is a review bucket, not an assignable Sharing domain");
2583
+ const targetScope = listSharingDomainSettingsScopes(store.db).find((scope) => scope.scope_id === input.scopeId);
2584
+ if (!targetScope) throw new Error(`scope_id ${input.scopeId} is not an active Sharing domain`);
2585
+ assertLocalDeviceScopeMembership(store, input.scopeId);
2586
+ const rows = legacySharedReviewRowsForWorkspace(store, input.workspaceIdentity);
2587
+ if (rows.length === 0) throw new Error("legacy shared review group not found");
2588
+ const reassignableRows = reassignableLegacySharedReviewRows(store, rows);
2589
+ const peerDeviceIds = [...new Set(rows.map((row) => String(row.origin_device_id ?? "").trim()).filter((deviceId) => deviceId && deviceId !== store.deviceId))].sort();
2590
+ return {
2591
+ affected_peer_device_count: peerDeviceIds.length,
2592
+ affected_peer_device_ids: peerDeviceIds.slice(0, 5),
2593
+ memory_count: rows.length,
2594
+ reassignable_memory_count: reassignableRows.length,
2595
+ scope_id: input.scopeId,
2596
+ skipped_memory_count: rows.length - reassignableRows.length,
2597
+ target_scope_label: targetScope.label || targetScope.scope_id,
2598
+ warning: "This changes future sync authorization for reassignable local memories. It does not erase data already copied to peers under legacy-shared-review.",
2599
+ workspace_identity: input.workspaceIdentity
2600
+ };
2601
+ }
2602
+ function reassignLegacySharedReviewGroup(store, input) {
2603
+ const preview = legacySharedReviewReassignmentPreview(store, input);
2604
+ const rows = reassignableLegacySharedReviewRows(store, legacySharedReviewRowsForWorkspace(store, input.workspaceIdentity));
2605
+ for (const row of rows) store.reassignMemoryScope(row.id, input.scopeId);
2606
+ return {
2607
+ ok: true,
2608
+ ...preview,
2609
+ reassigned_memory_count: rows.length,
2610
+ legacy_shared_review: legacySharedReviewSummary(store)
2469
2611
  };
2470
2612
  }
2471
2613
  var SYNC_DIRECTION_WINDOW_SECONDS = 1440 * 60;
@@ -3162,6 +3304,65 @@ function syncRoutes(getStore, getSyncRuntimeStatus) {
3162
3304
  local_default_scope_id: LOCAL_DEFAULT_SCOPE_ID
3163
3305
  });
3164
3306
  });
3307
+ app.get("/api/sync/projects", (c) => {
3308
+ const store = getStore();
3309
+ const limit = Math.max(1, queryInt(c.req.query("limit"), 50));
3310
+ const offset = Math.max(0, queryInt(c.req.query("offset"), 0));
3311
+ return c.json(listProjectScopeInventory(store.db, {
3312
+ identitySource: c.req.query("identity_source"),
3313
+ limit,
3314
+ offset,
3315
+ query: c.req.query("q"),
3316
+ scopeId: c.req.query("scope_id"),
3317
+ status: c.req.query("status")
3318
+ }));
3319
+ });
3320
+ app.post("/api/sync/projects/reassign-project", async (c) => {
3321
+ const store = getStore();
3322
+ const body = await parseViewerJsonBody(c);
3323
+ if (!body) return c.json({ error: "invalid json" }, 400);
3324
+ try {
3325
+ const workspaceIdentity = optionalViewerStrictString(body, "workspace_identity") ?? "";
3326
+ const project = optionalViewerStrictString(body, "project") ?? "";
3327
+ const result = reassignProjectScopeInventoryProject(store.db, {
3328
+ project,
3329
+ workspaceIdentity
3330
+ });
3331
+ return c.json({
3332
+ ok: true,
3333
+ ...result
3334
+ });
3335
+ } catch (error) {
3336
+ const message = error instanceof Error ? error.message : String(error);
3337
+ return c.json({ error: message }, message.includes("not found") ? 404 : 400);
3338
+ }
3339
+ });
3340
+ app.post("/api/sync/legacy-shared-review/reassign", async (c) => {
3341
+ const store = getStore();
3342
+ const body = await parseViewerJsonBody(c);
3343
+ if (!body) return c.json({ error: "invalid json" }, 400);
3344
+ try {
3345
+ const workspaceIdentity = optionalViewerStrictString(body, "workspace_identity") ?? "";
3346
+ const scopeId = optionalViewerStrictString(body, "scope_id") ?? "";
3347
+ const confirmedOldCopies = body.confirmed_old_copies === true;
3348
+ const preview = legacySharedReviewReassignmentPreview(store, {
3349
+ scopeId,
3350
+ workspaceIdentity
3351
+ });
3352
+ if (!confirmedOldCopies) return c.json({
3353
+ error: "legacy_review_confirmation_required",
3354
+ message: preview.warning,
3355
+ preview
3356
+ }, 409);
3357
+ return c.json(reassignLegacySharedReviewGroup(store, {
3358
+ scopeId,
3359
+ workspaceIdentity
3360
+ }));
3361
+ } catch (error) {
3362
+ const message = error instanceof Error ? error.message : String(error);
3363
+ return c.json({ error: message }, message.includes("not found") ? 404 : 400);
3364
+ }
3365
+ });
3165
3366
  app.put("/api/sync/sharing-domains/project-mappings", async (c) => {
3166
3367
  const store = getStore();
3167
3368
  const body = await parseViewerJsonBody(c);
@@ -3185,9 +3386,9 @@ function syncRoutes(getStore, getSyncRuntimeStatus) {
3185
3386
  source
3186
3387
  };
3187
3388
  const analysis = analyzeProjectScopeMappingChangeGuardrails(store.db, mappingInput);
3188
- if (analysis.requested_workspace_identity?.startsWith("unmapped:") && scopeId !== LOCAL_DEFAULT_SCOPE_ID) return c.json({
3389
+ if (analysis.requested_workspace_identity?.startsWith("unmapped:")) return c.json({
3189
3390
  error: "unmapped_project_local_only",
3190
- message: "Unmapped projects need a stable path, git remote, or workspace id before assigning a non-local Sharing domain.",
3391
+ message: "Unmapped projects need a stable path, git remote, or workspace id before assigning a Sharing domain.",
3191
3392
  guardrail_warnings: analysis.warnings
3192
3393
  }, 400);
3193
3394
  const missingConfirmations = missingGuardrailConfirmations(analysis.warnings, confirmedGuardrailTokens);
@@ -4706,6 +4907,10 @@ function createApp(opts) {
4706
4907
  app.route("/", rawEventsRoutes(storeFactory, sweeper));
4707
4908
  app.route("/", syncRoutes(storeFactory, getSyncRuntimeStatus));
4708
4909
  const staticRoot = process.env.CODEMEM_VIEWER_STATIC_DIR ?? join(import.meta.dirname ?? ".", "../static");
4910
+ app.use("/assets/*", async (c, next) => {
4911
+ c.header("Cache-Control", "no-cache");
4912
+ await next();
4913
+ });
4709
4914
  app.use("/assets/*", serveStatic({
4710
4915
  root: staticRoot,
4711
4916
  rewriteRequestPath: (path) => path.replace(/^\/assets/, "")
@@ -4715,6 +4920,7 @@ function createApp(opts) {
4715
4920
  const indexHtml = readFileSync(indexPath, "utf-8");
4716
4921
  app.get("*", (c) => {
4717
4922
  if (c.req.path.startsWith("/api/")) return c.json({ error: "not found" }, 404);
4923
+ c.header("Cache-Control", "no-store");
4718
4924
  return c.html(indexHtml);
4719
4925
  });
4720
4926
  return app;