@bevel-software/platform-core-backend 0.3.1 → 0.3.2

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.
@@ -188,6 +188,45 @@ describe('DbSecretsVaultService — dead-grant detection on refresh', () => {
188
188
  expect(stored.tokens).toBeUndefined();
189
189
  });
190
190
 
191
+ it('a SUCCESSFUL refresh notifies mutation listeners with the row user (cache repair signal)', async () => {
192
+ const { db } = makeFakeDb([
193
+ [userRow()], // resolve row lookup
194
+ undefined, // refreshed-token persist
195
+ ]);
196
+ const svc = new DbSecretsVaultService(db, ENC_KEY, undefined, userScope);
197
+ const onMutation = vi.fn();
198
+ svc.onMutation(onMutation);
199
+ vi.stubGlobal(
200
+ 'fetch',
201
+ vi.fn(async () =>
202
+ new Response(
203
+ JSON.stringify({ access_token: 'fresh-at', refresh_token: 'rt-2', expires_in: 3600 }),
204
+ { status: 200 },
205
+ ),
206
+ ),
207
+ );
208
+
209
+ await expect(svc.resolve('user-1', KEY)).resolves.toBe('fresh-at');
210
+ expect(onMutation).toHaveBeenCalledWith('user-1');
211
+ });
212
+
213
+ it('putSharedOAuthProvider notifies mutation listeners with null (shared → everyone)', async () => {
214
+ const { db } = makeFakeDb([undefined]); // provider upsert
215
+ const svc = new DbSecretsVaultService(db, ENC_KEY);
216
+ const onMutation = vi.fn();
217
+ svc.onMutation(onMutation);
218
+ await svc.putSharedOAuthProvider({
219
+ key: KEY,
220
+ provider: {
221
+ clientId: 'client-1',
222
+ authorizationUrl: 'https://idp.example.com/authorize',
223
+ tokenUrl: 'https://idp.example.com/token',
224
+ scopes: ['mcp.read'],
225
+ },
226
+ });
227
+ expect(onMutation).toHaveBeenCalledWith(null);
228
+ });
229
+
191
230
  it('a transient failure (network / 5xx) keeps the tokens and returns the stale one', async () => {
192
231
  for (const impl of [
193
232
  async () => new Response('bad gateway', { status: 502 }),
@@ -582,6 +582,10 @@ export class DbSecretsVaultService implements ISecretsVaultService {
582
582
  .update(secrets)
583
583
  .set({ valueEncrypted: this.crypto().encrypt(JSON.stringify(next)), updatedAt: new Date() })
584
584
  .where(and(eq(secrets.id, row.id), eq(secrets.valueEncrypted, row.valueEncrypted)));
585
+ // A successful refresh is a credential repair — notify so dependent caches
586
+ // (the MCP proxy's manual-failure memo) retry immediately. `row.userId` is
587
+ // null for a shared (admin-scope) row, which maps to "affects everyone".
588
+ this.notifyMutation(row.userId);
585
589
  return refreshed.access_token;
586
590
  }
587
591
 
@@ -609,6 +613,7 @@ export class DbSecretsVaultService implements ISecretsVaultService {
609
613
  targetWhere: isNull(secrets.userId),
610
614
  set: { kind: 'oauth', valueEncrypted, oauthMeta, updatedAt: new Date() },
611
615
  });
616
+ this.notifyMutation(null);
612
617
  }
613
618
 
614
619
  async getSharedOAuthProvider(key: string): Promise<OAuthProviderConfig | null> {