@cat-factory/server 0.252.0 → 0.253.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.
- package/dist/agents/harnessContract.d.ts +56 -0
- package/dist/agents/harnessContract.d.ts.map +1 -0
- package/dist/agents/harnessContract.js +86 -0
- package/dist/agents/harnessContract.js.map +1 -0
- package/dist/agents/jobBody.d.ts.map +1 -1
- package/dist/agents/jobBody.js +1 -15
- package/dist/agents/jobBody.js.map +1 -1
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +6 -0
- package/dist/app.js.map +1 -1
- package/dist/events/InAppNotificationChannel.d.ts +5 -0
- package/dist/events/InAppNotificationChannel.d.ts.map +1 -1
- package/dist/events/InAppNotificationChannel.js +5 -0
- package/dist/events/InAppNotificationChannel.js.map +1 -1
- package/dist/github/ProviderRoutingGitHubClient.d.ts +15 -68
- package/dist/github/ProviderRoutingGitHubClient.d.ts.map +1 -1
- package/dist/github/ProviderRoutingGitHubClient.js +150 -142
- package/dist/github/ProviderRoutingGitHubClient.js.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -2
- package/dist/index.js.map +1 -1
- package/dist/modules/notifications/NotificationRelayController.d.ts.map +1 -1
- package/dist/modules/notifications/NotificationRelayController.js +14 -1
- package/dist/modules/notifications/NotificationRelayController.js.map +1 -1
- package/dist/modules/notifications/NotificationSettingsController.d.ts +17 -0
- package/dist/modules/notifications/NotificationSettingsController.d.ts.map +1 -0
- package/dist/modules/notifications/NotificationSettingsController.js +46 -0
- package/dist/modules/notifications/NotificationSettingsController.js.map +1 -0
- package/dist/notifications/machineNotifications.d.ts +13 -4
- package/dist/notifications/machineNotifications.d.ts.map +1 -1
- package/dist/notifications/machineNotifications.js +4 -3
- package/dist/notifications/machineNotifications.js.map +1 -1
- package/dist/notifications/notificationDelivery.d.ts +36 -0
- package/dist/notifications/notificationDelivery.d.ts.map +1 -0
- package/dist/notifications/notificationDelivery.js +43 -0
- package/dist/notifications/notificationDelivery.js.map +1 -0
- package/dist/persistence/binaryArtifactStore.d.ts +21 -3
- package/dist/persistence/binaryArtifactStore.d.ts.map +1 -1
- package/dist/persistence/binaryArtifactStore.js +106 -3
- package/dist/persistence/binaryArtifactStore.js.map +1 -1
- package/dist/persistence/rpc-allowlist.d.ts.map +1 -1
- package/dist/persistence/rpc-allowlist.js +8 -0
- package/dist/persistence/rpc-allowlist.js.map +1 -1
- package/package.json +7 -7
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { getNotificationSettingsContract, updateNotificationSettingsContract, } from '@cat-factory/contracts';
|
|
2
|
+
import { buildHonoRoute } from '@toad-contracts/hono';
|
|
3
|
+
import { Hono } from 'hono';
|
|
4
|
+
import { requireCapability } from '../../http/guards.js';
|
|
5
|
+
import { param } from '../../http/params.js';
|
|
6
|
+
import { mountWorkspacePermission } from '../../http/workspaceAccess.js';
|
|
7
|
+
/**
|
|
8
|
+
* Resolve the notification manager, or refuse with a 503 naming what isn't wired. It has
|
|
9
|
+
* its OWN accessor rather than borrowing the notifications module's: the routing store is
|
|
10
|
+
* optional within a wired notifications module, so a message about notifications being
|
|
11
|
+
* unconfigured would name a subsystem the operator has already wired.
|
|
12
|
+
*/
|
|
13
|
+
function requireNotificationSettings(c) {
|
|
14
|
+
return requireCapability(c.get('container').notifications?.settingsService, 'The notification manager is not configured');
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* The notification manager: which notification types this board delivers on which channel.
|
|
18
|
+
*
|
|
19
|
+
* Its own controller rather than routes on {@link notificationController}, because the tiers
|
|
20
|
+
* differ: acting on a card is a MEMBER's everyday work, while re-routing what the whole board
|
|
21
|
+
* is told is workspace configuration. A controller gates one permission over its own prefixes,
|
|
22
|
+
* so mixing the two would either under-gate the settings or lock members out of their inbox.
|
|
23
|
+
*
|
|
24
|
+
* The READ is left at the member tier on purpose: the settings surface renders the resolved
|
|
25
|
+
* routing, and it carries no credential or endpoint — only which types go where.
|
|
26
|
+
*
|
|
27
|
+
* Mounted under `/workspaces/:workspaceId`.
|
|
28
|
+
*/
|
|
29
|
+
export function notificationSettingsController() {
|
|
30
|
+
const app = new Hono();
|
|
31
|
+
mountWorkspacePermission(app, 'settings.manage', ['/notification-settings']);
|
|
32
|
+
buildHonoRoute(app, getNotificationSettingsContract, async (c) => {
|
|
33
|
+
const settings = requireNotificationSettings(c);
|
|
34
|
+
return c.json(await settings.get(param(c, 'workspaceId')), 200);
|
|
35
|
+
});
|
|
36
|
+
// A full replace of the override matrix, like the Slack routing write: the settings surface
|
|
37
|
+
// always sends the whole thing, and a merge would make "turn this cell back to its default"
|
|
38
|
+
// unexpressible.
|
|
39
|
+
buildHonoRoute(app, updateNotificationSettingsContract, async (c) => {
|
|
40
|
+
const settings = requireNotificationSettings(c);
|
|
41
|
+
const { matrix } = c.req.valid('json');
|
|
42
|
+
return c.json(await settings.update(param(c, 'workspaceId'), matrix), 200);
|
|
43
|
+
});
|
|
44
|
+
return app;
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=NotificationSettingsController.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NotificationSettingsController.js","sourceRoot":"","sources":["../../../src/modules/notifications/NotificationSettingsController.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,+BAA+B,EAC/B,kCAAkC,GACnC,MAAM,wBAAwB,CAAA;AAE/B,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AACrD,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAG3B,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAA;AACxD,OAAO,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAA;AAC5C,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAA;AAExE;;;;;GAKG;AACH,SAAS,2BAA2B,CAAmB,CAAa;IAClE,OAAO,iBAAiB,CACtB,CAAC,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,aAAa,EAAE,eAAe,EACjD,4CAA4C,CAC7C,CAAA;AACH,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,8BAA8B;IAC5C,MAAM,GAAG,GAAG,IAAI,IAAI,EAAU,CAAA;IAC9B,wBAAwB,CAAC,GAAG,EAAE,iBAAiB,EAAE,CAAC,wBAAwB,CAAC,CAAC,CAAA;IAE5E,cAAc,CAAC,GAAG,EAAE,+BAA+B,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;QAC/D,MAAM,QAAQ,GAAG,2BAA2B,CAAC,CAAC,CAAC,CAAA;QAC/C,OAAO,CAAC,CAAC,IAAI,CAAC,MAAM,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,aAAa,CAAC,CAAC,EAAE,GAAG,CAAC,CAAA;IACjE,CAAC,CAAC,CAAA;IAEF,4FAA4F;IAC5F,4FAA4F;IAC5F,iBAAiB;IACjB,cAAc,CAAC,GAAG,EAAE,kCAAkC,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;QAClE,MAAM,QAAQ,GAAG,2BAA2B,CAAC,CAAC,CAAC,CAAA;QAC/C,MAAM,EAAE,MAAM,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAA;QACtC,OAAO,CAAC,CAAC,IAAI,CAAC,MAAM,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,aAAa,CAAC,EAAE,MAAM,CAAC,EAAE,GAAG,CAAC,CAAA;IAC5E,CAAC,CAAC,CAAA;IAEF,OAAO,GAAG,CAAA;AACZ,CAAC"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Notification, NotificationChannel } from '@cat-factory/kernel';
|
|
1
|
+
import type { Notification, NotificationChannel, NotificationDeliveryReason } from '@cat-factory/kernel';
|
|
2
2
|
/**
|
|
3
3
|
* The request a mothership-mode node posts to `POST /internal/notifications/deliver`.
|
|
4
4
|
*
|
|
@@ -6,10 +6,19 @@ import type { Notification, NotificationChannel } from '@cat-factory/kernel';
|
|
|
6
6
|
* from its own store and delivers THAT. A compromised node token can therefore only ask the
|
|
7
7
|
* mothership to re-deliver a notification that already exists in an account it can already reach —
|
|
8
8
|
* it can never inject forged text into the org's Slack channel.
|
|
9
|
+
*
|
|
10
|
+
* The one thing the row does NOT carry is `reason`: which lifecycle edge this delegation is for.
|
|
11
|
+
* The mothership cannot re-derive it, because the two edges that decide whether its alert
|
|
12
|
+
* transports fire are indistinguishable in the persisted row (an escalation and a fresh raise are
|
|
13
|
+
* both `open`). Left off the wire, every dismissal a laptop made would reach the org's Slack as a
|
|
14
|
+
* fresh "Decision needed". So it is REQUIRED, and a body without it is refused rather than
|
|
15
|
+
* defaulted: guessing `raised` mails a decision already made, guessing `settled` silences the
|
|
16
|
+
* alert the delegation exists to send, and a node one build behind is better off failing loudly.
|
|
9
17
|
*/
|
|
10
18
|
export interface DelegatedNotificationRequest {
|
|
11
19
|
workspaceId: string;
|
|
12
20
|
notificationId: string;
|
|
21
|
+
reason: NotificationDeliveryReason;
|
|
13
22
|
}
|
|
14
23
|
/** The client half: asks a mothership to deliver one of its own notification rows. */
|
|
15
24
|
export interface MachineNotificationClient {
|
|
@@ -18,7 +27,7 @@ export interface MachineNotificationClient {
|
|
|
18
27
|
* channels. Rejects on a transport/HTTP failure so the caller can surface it; delivery itself is
|
|
19
28
|
* best-effort on the mothership side (the persisted row is the source of truth).
|
|
20
29
|
*/
|
|
21
|
-
deliver(workspaceId: string, notificationId: string): Promise<void>;
|
|
30
|
+
deliver(workspaceId: string, notificationId: string, reason: NotificationDeliveryReason): Promise<void>;
|
|
22
31
|
}
|
|
23
32
|
export interface HttpMachineNotificationClientOptions {
|
|
24
33
|
baseUrl: string;
|
|
@@ -40,7 +49,7 @@ export interface HttpMachineNotificationClientOptions {
|
|
|
40
49
|
export declare class HttpMachineNotificationClient implements MachineNotificationClient {
|
|
41
50
|
private readonly opts;
|
|
42
51
|
constructor(opts: HttpMachineNotificationClientOptions);
|
|
43
|
-
deliver(workspaceId: string, notificationId: string): Promise<void>;
|
|
52
|
+
deliver(workspaceId: string, notificationId: string, reason: NotificationDeliveryReason): Promise<void>;
|
|
44
53
|
}
|
|
45
54
|
export interface RemoteNotificationChannelOptions {
|
|
46
55
|
client: MachineNotificationClient;
|
|
@@ -65,6 +74,6 @@ export interface RemoteNotificationChannelOptions {
|
|
|
65
74
|
export declare class RemoteNotificationChannel implements NotificationChannel {
|
|
66
75
|
private readonly opts;
|
|
67
76
|
constructor(opts: RemoteNotificationChannelOptions);
|
|
68
|
-
deliver(workspaceId: string, notification: Notification): Promise<void>;
|
|
77
|
+
deliver(workspaceId: string, notification: Notification, reason: NotificationDeliveryReason): Promise<void>;
|
|
69
78
|
}
|
|
70
79
|
//# sourceMappingURL=machineNotifications.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"machineNotifications.d.ts","sourceRoot":"","sources":["../../src/notifications/machineNotifications.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"machineNotifications.d.ts","sourceRoot":"","sources":["../../src/notifications/machineNotifications.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,YAAY,EACZ,mBAAmB,EACnB,0BAA0B,EAC3B,MAAM,qBAAqB,CAAA;AAwB5B;;;;;;;;;;;;;;;GAeG;AACH,MAAM,WAAW,4BAA4B;IAC3C,WAAW,EAAE,MAAM,CAAA;IACnB,cAAc,EAAE,MAAM,CAAA;IACtB,MAAM,EAAE,0BAA0B,CAAA;CACnC;AAED,sFAAsF;AACtF,MAAM,WAAW,yBAAyB;IACxC;;;;OAIG;IACH,OAAO,CACL,WAAW,EAAE,MAAM,EACnB,cAAc,EAAE,MAAM,EACtB,MAAM,EAAE,0BAA0B,GACjC,OAAO,CAAC,IAAI,CAAC,CAAA;CACjB;AAED,MAAM,WAAW,oCAAoC;IACnD,OAAO,EAAE,MAAM,CAAA;IACf,6FAA6F;IAC7F,KAAK,EAAE,MAAM,GAAG,CAAC,MAAM,MAAM,GAAG,IAAI,CAAC,CAAA;IACrC,SAAS,CAAC,EAAE,OAAO,KAAK,CAAA;IACxB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAKD;;;;;GAKG;AACH,qBAAa,6BAA8B,YAAW,yBAAyB;IACjE,OAAO,CAAC,QAAQ,CAAC,IAAI;IAAjC,YAA6B,IAAI,EAAE,oCAAoC,EAAI;IAErE,OAAO,CACX,WAAW,EAAE,MAAM,EACnB,cAAc,EAAE,MAAM,EACtB,MAAM,EAAE,0BAA0B,GACjC,OAAO,CAAC,IAAI,CAAC,CAsBf;CACF;AAED,MAAM,WAAW,gCAAgC;IAC/C,MAAM,EAAE,yBAAyB,CAAA;IACjC;;;;OAIG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,cAAc,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAA;CACzF;AAED;;;;;;;GAOG;AACH,qBAAa,yBAA0B,YAAW,mBAAmB;IACvD,OAAO,CAAC,QAAQ,CAAC,IAAI;IAAjC,YAA6B,IAAI,EAAE,gCAAgC,EAAI;IAEjE,OAAO,CACX,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,YAAY,EAC1B,MAAM,EAAE,0BAA0B,GACjC,OAAO,CAAC,IAAI,CAAC,CAMf;CACF"}
|
|
@@ -11,7 +11,7 @@ export class HttpMachineNotificationClient {
|
|
|
11
11
|
constructor(opts) {
|
|
12
12
|
this.opts = opts;
|
|
13
13
|
}
|
|
14
|
-
async deliver(workspaceId, notificationId) {
|
|
14
|
+
async deliver(workspaceId, notificationId, reason) {
|
|
15
15
|
const token = typeof this.opts.token === 'function' ? this.opts.token() : this.opts.token;
|
|
16
16
|
// No token yet (a node booted before the mothership login): there is nothing to delegate to,
|
|
17
17
|
// and a guaranteed-403 round-trip per notification is pure waste. The row is still persisted.
|
|
@@ -24,6 +24,7 @@ export class HttpMachineNotificationClient {
|
|
|
24
24
|
body: JSON.stringify({
|
|
25
25
|
workspaceId,
|
|
26
26
|
notificationId,
|
|
27
|
+
reason,
|
|
27
28
|
}),
|
|
28
29
|
signal: AbortSignal.timeout(this.opts.timeoutMs ?? DEFAULT_TIMEOUT_MS),
|
|
29
30
|
});
|
|
@@ -45,9 +46,9 @@ export class RemoteNotificationChannel {
|
|
|
45
46
|
constructor(opts) {
|
|
46
47
|
this.opts = opts;
|
|
47
48
|
}
|
|
48
|
-
async deliver(workspaceId, notification) {
|
|
49
|
+
async deliver(workspaceId, notification, reason) {
|
|
49
50
|
try {
|
|
50
|
-
await this.opts.client.deliver(workspaceId, notification.id);
|
|
51
|
+
await this.opts.client.deliver(workspaceId, notification.id, reason);
|
|
51
52
|
}
|
|
52
53
|
catch (error) {
|
|
53
54
|
this.opts.onError?.(error, { workspaceId, notificationId: notification.id });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"machineNotifications.js","sourceRoot":"","sources":["../../src/notifications/machineNotifications.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"machineNotifications.js","sourceRoot":"","sources":["../../src/notifications/machineNotifications.ts"],"names":[],"mappings":"AA4EA,4FAA4F;AAC5F,MAAM,kBAAkB,GAAG,MAAM,CAAA;AAEjC;;;;;GAKG;AACH,MAAM,OAAO,6BAA6B;IACX,IAAI;IAAjC,YAA6B,IAA0C;oBAA1C,IAAI;IAAyC,CAAC;IAE3E,KAAK,CAAC,OAAO,CACX,WAAmB,EACnB,cAAsB,EACtB,MAAkC;QAElC,MAAM,KAAK,GAAG,OAAO,IAAI,CAAC,IAAI,CAAC,KAAK,KAAK,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAA;QACzF,6FAA6F;QAC7F,8FAA8F;QAC9F,IAAI,CAAC,KAAK;YAAE,OAAM;QAClB,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,IAAI,KAAK,CAAA;QAC9C,MAAM,GAAG,GAAG,MAAM,SAAS,CACzB,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,iCAAiC,EACxE;YACE,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,aAAa,EAAE,UAAU,KAAK,EAAE,EAAE;YACjF,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;gBACnB,WAAW;gBACX,cAAc;gBACd,MAAM;aACgC,CAAC;YACzC,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,IAAI,kBAAkB,CAAC;SACvE,CACF,CAAA;QACD,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACZ,MAAM,IAAI,KAAK,CAAC,qDAAqD,GAAG,CAAC,MAAM,EAAE,CAAC,CAAA;QACpF,CAAC;IACH,CAAC;CACF;AAYD;;;;;;;GAOG;AACH,MAAM,OAAO,yBAAyB;IACP,IAAI;IAAjC,YAA6B,IAAsC;oBAAtC,IAAI;IAAqC,CAAC;IAEvE,KAAK,CAAC,OAAO,CACX,WAAmB,EACnB,YAA0B,EAC1B,MAAkC;QAElC,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,WAAW,EAAE,YAAY,CAAC,EAAE,EAAE,MAAM,CAAC,CAAA;QACtE,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE,WAAW,EAAE,cAAc,EAAE,YAAY,CAAC,EAAE,EAAE,CAAC,CAAA;QAC9E,CAAC;IACH,CAAC;CACF"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Clock, EmailSender, MembershipRepository, NotificationChannel, NotificationDeliveryChannel, NotificationSettingsRepository, UserRepository, WorkspaceMemberRepository, WorkspaceRepository } from '@cat-factory/kernel';
|
|
2
|
+
import { NotificationSettingsService } from '@cat-factory/orchestration';
|
|
3
|
+
export interface NotificationDeliveryInput {
|
|
4
|
+
/** The manager's store. Absent ⇒ no routing store; see {@link buildNotificationDelivery}. */
|
|
5
|
+
notificationSettingsRepository: NotificationSettingsRepository;
|
|
6
|
+
clock: Clock;
|
|
7
|
+
workspaceRepository: WorkspaceRepository;
|
|
8
|
+
membershipRepository: MembershipRepository;
|
|
9
|
+
workspaceMemberRepository: WorkspaceMemberRepository;
|
|
10
|
+
userRepository: UserRepository;
|
|
11
|
+
/**
|
|
12
|
+
* The account's configured transactional sender, when the deployment wired the per-account
|
|
13
|
+
* email module (`EmailConnectionService.resolveSender`). Absent ⇒ no email channel is built
|
|
14
|
+
* at all, the standard opt-in pass-through.
|
|
15
|
+
*/
|
|
16
|
+
resolveEmailSender?: (accountId: string) => Promise<EmailSender | null>;
|
|
17
|
+
/** The deployment's public SPA base URL, for the "open in cat-factory" deep link. */
|
|
18
|
+
appBaseUrl?: string;
|
|
19
|
+
}
|
|
20
|
+
export interface NotificationDeliverySupport {
|
|
21
|
+
/** The manager: hand it to `CoreDependencies` so the settings API writes the same rows. */
|
|
22
|
+
settingsService: NotificationSettingsService;
|
|
23
|
+
/** Wrap a channel the manager owns, so its deliveries obey the workspace's routing. */
|
|
24
|
+
route(channel: NotificationDeliveryChannel, inner: NotificationChannel): NotificationChannel;
|
|
25
|
+
/** The email transport, or null when the deployment wired no per-account email module. */
|
|
26
|
+
emailChannel: NotificationChannel | null;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Build the notification manager, the per-channel routing gate, and the email channel.
|
|
30
|
+
*
|
|
31
|
+
* The email channel is EXTERNAL in the mothership sense (it sends through the account's
|
|
32
|
+
* sealed provider key), so a facade composes it into its external channel set beside Slack
|
|
33
|
+
* and the webhook, not into the in-app half.
|
|
34
|
+
*/
|
|
35
|
+
export declare function buildNotificationDelivery(input: NotificationDeliveryInput): NotificationDeliverySupport;
|
|
36
|
+
//# sourceMappingURL=notificationDelivery.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notificationDelivery.d.ts","sourceRoot":"","sources":["../../src/notifications/notificationDelivery.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,KAAK,EACL,WAAW,EACX,oBAAoB,EACpB,mBAAmB,EACnB,2BAA2B,EAC3B,8BAA8B,EAC9B,cAAc,EACd,yBAAyB,EACzB,mBAAmB,EACpB,MAAM,qBAAqB,CAAA;AAE5B,OAAO,EAAE,2BAA2B,EAAE,MAAM,4BAA4B,CAAA;AAkBxE,MAAM,WAAW,yBAAyB;IACxC,6FAA6F;IAC7F,8BAA8B,EAAE,8BAA8B,CAAA;IAC9D,KAAK,EAAE,KAAK,CAAA;IACZ,mBAAmB,EAAE,mBAAmB,CAAA;IACxC,oBAAoB,EAAE,oBAAoB,CAAA;IAC1C,yBAAyB,EAAE,yBAAyB,CAAA;IACpD,cAAc,EAAE,cAAc,CAAA;IAC9B;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAA;IACvE,qFAAqF;IACrF,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,WAAW,2BAA2B;IAC1C,2FAA2F;IAC3F,eAAe,EAAE,2BAA2B,CAAA;IAC5C,uFAAuF;IACvF,KAAK,CAAC,OAAO,EAAE,2BAA2B,EAAE,KAAK,EAAE,mBAAmB,GAAG,mBAAmB,CAAA;IAC5F,0FAA0F;IAC1F,YAAY,EAAE,mBAAmB,GAAG,IAAI,CAAA;CACzC;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,yBAAyB,GAC/B,2BAA2B,CA0C7B"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { EmailNotificationChannel } from '@cat-factory/integrations';
|
|
2
|
+
import { RoutedNotificationChannel } from '@cat-factory/kernel';
|
|
3
|
+
import { NotificationSettingsService } from '@cat-factory/orchestration';
|
|
4
|
+
import { logger } from '../observability/logger.js';
|
|
5
|
+
/**
|
|
6
|
+
* Build the notification manager, the per-channel routing gate, and the email channel.
|
|
7
|
+
*
|
|
8
|
+
* The email channel is EXTERNAL in the mothership sense (it sends through the account's
|
|
9
|
+
* sealed provider key), so a facade composes it into its external channel set beside Slack
|
|
10
|
+
* and the webhook, not into the in-app half.
|
|
11
|
+
*/
|
|
12
|
+
export function buildNotificationDelivery(input) {
|
|
13
|
+
const settingsService = new NotificationSettingsService({
|
|
14
|
+
notificationSettingsRepository: input.notificationSettingsRepository,
|
|
15
|
+
clock: input.clock,
|
|
16
|
+
});
|
|
17
|
+
const route = (channel, inner) => new RoutedNotificationChannel(channel, settingsService, inner, (error, ctx) =>
|
|
18
|
+
// The gate fell back to the shipped default rather than dropping the notification; say
|
|
19
|
+
// so, because a settings store that cannot be read is an outage in its own right.
|
|
20
|
+
logger.warn('notification routing lookup failed; using the default route', {
|
|
21
|
+
err: error instanceof Error ? error.message : String(error),
|
|
22
|
+
...ctx,
|
|
23
|
+
}));
|
|
24
|
+
const resolveSender = input.resolveEmailSender;
|
|
25
|
+
const emailChannel = resolveSender
|
|
26
|
+
? route('email', new EmailNotificationChannel({
|
|
27
|
+
workspaceRepository: input.workspaceRepository,
|
|
28
|
+
membershipRepository: input.membershipRepository,
|
|
29
|
+
workspaceMemberRepository: input.workspaceMemberRepository,
|
|
30
|
+
userRepository: input.userRepository,
|
|
31
|
+
resolveSender,
|
|
32
|
+
...(input.appBaseUrl ? { appBaseUrl: input.appBaseUrl } : {}),
|
|
33
|
+
// Best-effort delivery still surfaces failures (a rejected API key, a bounced
|
|
34
|
+
// address) through the structured logger so a broken sender is diagnosable.
|
|
35
|
+
onError: (error, ctx) => logger.warn('email notification delivery failed', {
|
|
36
|
+
err: error instanceof Error ? error.message : String(error),
|
|
37
|
+
...ctx,
|
|
38
|
+
}),
|
|
39
|
+
}))
|
|
40
|
+
: null;
|
|
41
|
+
return { settingsService, route, emailChannel };
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=notificationDelivery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notificationDelivery.js","sourceRoot":"","sources":["../../src/notifications/notificationDelivery.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAA;AAYpE,OAAO,EAAE,yBAAyB,EAAE,MAAM,qBAAqB,CAAA;AAC/D,OAAO,EAAE,2BAA2B,EAAE,MAAM,4BAA4B,CAAA;AACxE,OAAO,EAAE,MAAM,EAAE,MAAM,4BAA4B,CAAA;AA4CnD;;;;;;GAMG;AACH,MAAM,UAAU,yBAAyB,CACvC,KAAgC;IAEhC,MAAM,eAAe,GAAG,IAAI,2BAA2B,CAAC;QACtD,8BAA8B,EAAE,KAAK,CAAC,8BAA8B;QACpE,KAAK,EAAE,KAAK,CAAC,KAAK;KACnB,CAAC,CAAA;IAEF,MAAM,KAAK,GAAG,CACZ,OAAoC,EACpC,KAA0B,EACL,EAAE,CACvB,IAAI,yBAAyB,CAAC,OAAO,EAAE,eAAe,EAAE,KAAK,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;IAC5E,uFAAuF;IACvF,kFAAkF;IAClF,MAAM,CAAC,IAAI,CAAC,6DAA6D,EAAE;QACzE,GAAG,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;QAC3D,GAAG,GAAG;KACP,CAAC,CACH,CAAA;IAEH,MAAM,aAAa,GAAG,KAAK,CAAC,kBAAkB,CAAA;IAC9C,MAAM,YAAY,GAAG,aAAa;QAChC,CAAC,CAAC,KAAK,CACH,OAAO,EACP,IAAI,wBAAwB,CAAC;YAC3B,mBAAmB,EAAE,KAAK,CAAC,mBAAmB;YAC9C,oBAAoB,EAAE,KAAK,CAAC,oBAAoB;YAChD,yBAAyB,EAAE,KAAK,CAAC,yBAAyB;YAC1D,cAAc,EAAE,KAAK,CAAC,cAAc;YACpC,aAAa;YACb,GAAG,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,KAAK,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC7D,8EAA8E;YAC9E,4EAA4E;YAC5E,OAAO,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE,CACtB,MAAM,CAAC,IAAI,CAAC,oCAAoC,EAAE;gBAChD,GAAG,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;gBAC3D,GAAG,GAAG;aACP,CAAC;SACL,CAAC,CACH;QACH,CAAC,CAAC,IAAI,CAAA;IAER,OAAO,EAAE,eAAe,EAAE,KAAK,EAAE,YAAY,EAAE,CAAA;AACjD,CAAC"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { ContentStorageBackend, ContentStorageConfig, ContentStorageFsConfig, ContentStorageS3Config, S3CredentialsSecret } from '@cat-factory/contracts';
|
|
2
|
-
import type { BinaryArtifactMetadataStore, BinaryArtifactStorageKind, BinaryBlobBackend, Clock, IdGenerator, Logger, ResolveBinaryArtifactStore } from '@cat-factory/kernel';
|
|
1
|
+
import type { ContentStorageBackend, ContentStorageCapability, ContentStorageConfig, ContentStorageFsConfig, ContentStorageS3Config, S3CredentialsSecret } from '@cat-factory/contracts';
|
|
2
|
+
import type { BinaryArtifactMetadataStore, BinaryArtifactStorageKind, BinaryBlobBackend, BinaryStoreRegistry, Clock, IdGenerator, Logger, ResolveBinaryArtifactStore } from '@cat-factory/kernel';
|
|
3
3
|
/** The non-secret + decrypted content-storage settings the resolver needs (structural). */
|
|
4
4
|
export interface ContentStorageSettingsResolver {
|
|
5
5
|
resolve(accountId: string): Promise<{
|
|
@@ -19,9 +19,20 @@ export interface BuildBlobBackendOptions {
|
|
|
19
19
|
/**
|
|
20
20
|
* Build the blob backend a runtime can serve for `kind`, or `null` when the runtime does
|
|
21
21
|
* not support it (e.g. `fs` on Cloudflare, `r2` on Node) — `null` ⇒ storage unavailable.
|
|
22
|
-
* `kind` is never `'off'`
|
|
22
|
+
* `kind` is never `'off'` and never `'custom'`: the resolver short-circuits the first and serves
|
|
23
|
+
* the second from the app-owned {@link BinaryStoreRegistry}, which is runtime-neutral, so
|
|
24
|
+
* neither facade's factory has to learn about deployment-registered stores at all.
|
|
23
25
|
*/
|
|
24
26
|
export type BuildBlobBackend = (kind: BinaryArtifactStorageKind, opts: BuildBlobBackendOptions) => BinaryBlobBackend | null;
|
|
27
|
+
/**
|
|
28
|
+
* Fold the deployment's registered stores into a runtime's declared content-storage capability:
|
|
29
|
+
* the `custom` option appears in the picker exactly when at least one store is registered.
|
|
30
|
+
*
|
|
31
|
+
* Shared rather than done per facade because it is the answer to one question ("what may an
|
|
32
|
+
* account select here"), and a facade that assembled it itself is a facade that can offer
|
|
33
|
+
* `custom` with nothing behind it, or register stores nothing offers.
|
|
34
|
+
*/
|
|
35
|
+
export declare function withRegisteredBinaryStores(capability: ContentStorageCapability, registry?: BinaryStoreRegistry): ContentStorageCapability;
|
|
25
36
|
export interface MakeResolveBinaryArtifactStoreDeps {
|
|
26
37
|
/**
|
|
27
38
|
* Resolves an account's decrypted content-storage settings (the AccountSettingsService).
|
|
@@ -37,6 +48,13 @@ export interface MakeResolveBinaryArtifactStoreDeps {
|
|
|
37
48
|
clock: Clock;
|
|
38
49
|
/** Builds the blob backend for a resolved backend kind; `null` ⇒ unsupported on this runtime. */
|
|
39
50
|
buildBlobBackend: BuildBlobBackend;
|
|
51
|
+
/**
|
|
52
|
+
* The deployment's own binary artifact stores, registered in code. Consulted only when an
|
|
53
|
+
* account selects `backend: 'custom'`; absent ⇒ this deployment registers none, and such an
|
|
54
|
+
* account resolves to no storage (named in the log, since the alternative is an account that
|
|
55
|
+
* reads as configured and silently retains nothing).
|
|
56
|
+
*/
|
|
57
|
+
binaryStoreRegistry?: BinaryStoreRegistry;
|
|
40
58
|
/** Backend used when an account has no content-storage config (the runtime default). */
|
|
41
59
|
defaultBackend: ContentStorageBackend;
|
|
42
60
|
/** Optional structural logger, forwarded to the composed store to surface partial reclaims. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"binaryArtifactStore.d.ts","sourceRoot":"","sources":["../../src/persistence/binaryArtifactStore.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,qBAAqB,EACrB,oBAAoB,
|
|
1
|
+
{"version":3,"file":"binaryArtifactStore.d.ts","sourceRoot":"","sources":["../../src/persistence/binaryArtifactStore.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,qBAAqB,EACrB,wBAAwB,EACxB,oBAAoB,EAEpB,sBAAsB,EACtB,sBAAsB,EACtB,mBAAmB,EACpB,MAAM,wBAAwB,CAAA;AAC/B,OAAO,KAAK,EACV,2BAA2B,EAC3B,yBAAyB,EAEzB,iBAAiB,EACjB,mBAAmB,EACnB,KAAK,EACL,WAAW,EACX,MAAM,EACN,0BAA0B,EAC3B,MAAM,qBAAqB,CAAA;AAW5B,2FAA2F;AAC3F,MAAM,WAAW,8BAA8B;IAC7C,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC;QAClC,MAAM,EAAE;YAAE,cAAc,CAAC,EAAE,oBAAoB,CAAA;SAAE,CAAA;QACjD,aAAa,CAAC,EAAE,mBAAmB,CAAA;KACpC,CAAC,CAAA;CACH;AAED,+FAA+F;AAC/F,MAAM,WAAW,uBAAuB;IACtC,EAAE,CAAC,EAAE,sBAAsB,CAAA;IAC3B,EAAE,CAAC,EAAE,sBAAsB,CAAA;IAC3B,8EAA8E;IAC9E,aAAa,CAAC,EAAE,mBAAmB,CAAA;CACpC;AAED;;;;;;GAMG;AACH,MAAM,MAAM,gBAAgB,GAAG,CAC7B,IAAI,EAAE,yBAAyB,EAC/B,IAAI,EAAE,uBAAuB,KAC1B,iBAAiB,GAAG,IAAI,CAAA;AAE7B;;;;;;;GAOG;AACH,wBAAgB,0BAA0B,CACxC,UAAU,EAAE,wBAAwB,EACpC,QAAQ,CAAC,EAAE,mBAAmB,GAC7B,wBAAwB,CAS1B;AAED,MAAM,WAAW,kCAAkC;IACjD;;;;OAIG;IACH,eAAe,CAAC,EAAE,8BAA8B,CAAA;IAChD,gFAAgF;IAChF,SAAS,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,CAAA;IACtE,6FAA6F;IAC7F,QAAQ,EAAE,2BAA2B,CAAA;IACrC,WAAW,EAAE,WAAW,CAAA;IACxB,KAAK,EAAE,KAAK,CAAA;IACZ,iGAAiG;IACjG,gBAAgB,EAAE,gBAAgB,CAAA;IAClC;;;;;OAKG;IACH,mBAAmB,CAAC,EAAE,mBAAmB,CAAA;IACzC,wFAAwF;IACxF,cAAc,EAAE,qBAAqB,CAAA;IACrC,+FAA+F;IAC/F,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AA6GD;;;;;;;;;GASG;AACH,wBAAgB,8BAA8B,CAC5C,IAAI,EAAE,kCAAkC,GACvC,0BAA0B,CAkE5B"}
|
|
@@ -1,4 +1,22 @@
|
|
|
1
1
|
import { createBinaryArtifactStore } from '@cat-factory/kernel';
|
|
2
|
+
/**
|
|
3
|
+
* Fold the deployment's registered stores into a runtime's declared content-storage capability:
|
|
4
|
+
* the `custom` option appears in the picker exactly when at least one store is registered.
|
|
5
|
+
*
|
|
6
|
+
* Shared rather than done per facade because it is the answer to one question ("what may an
|
|
7
|
+
* account select here"), and a facade that assembled it itself is a facade that can offer
|
|
8
|
+
* `custom` with nothing behind it, or register stores nothing offers.
|
|
9
|
+
*/
|
|
10
|
+
export function withRegisteredBinaryStores(capability, registry) {
|
|
11
|
+
const customStores = registry?.views() ?? [];
|
|
12
|
+
return {
|
|
13
|
+
...capability,
|
|
14
|
+
supportedBackends: customStores.length
|
|
15
|
+
? [...capability.supportedBackends, 'custom']
|
|
16
|
+
: capability.supportedBackends,
|
|
17
|
+
customStores,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
2
20
|
/**
|
|
3
21
|
* A stable, non-reversible fingerprint of the decrypted S3 credentials, folded into the
|
|
4
22
|
* cache signature so ROTATING the keys (their presence is unchanged, but the values differ)
|
|
@@ -17,6 +35,71 @@ function credentialFingerprint(creds) {
|
|
|
17
35
|
}
|
|
18
36
|
return (hash >>> 0).toString(16);
|
|
19
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* Build the blob backend for an account that selected a DEPLOYMENT-REGISTERED store.
|
|
40
|
+
*
|
|
41
|
+
* A failure here does NOT share the built-in backends' silence: an unregistered id is a
|
|
42
|
+
* deployment whose build no longer carries a store its accounts still point at, and nothing else
|
|
43
|
+
* in the system would ever say so. The account's settings page shows exactly what was saved, and
|
|
44
|
+
* the artifacts simply stop being retained. On a MOTHERSHIP deployment this line is the whole
|
|
45
|
+
* signal that the retention sweep is reclaiming nothing, since the process that sweeps is not the
|
|
46
|
+
* process that wrote the bytes.
|
|
47
|
+
*
|
|
48
|
+
* It is said ONCE per account per config. The remembered answers get that from the resolver's
|
|
49
|
+
* cache; a store that DECLINED cannot be remembered, so it carries its own guard: {@link announced}
|
|
50
|
+
* holds the `account::store` pairs already reported, and a later success clears the pair so a
|
|
51
|
+
* store that breaks a second time is reported a second time.
|
|
52
|
+
*/
|
|
53
|
+
function buildRegisteredStore(deps, accountId, custom, announced) {
|
|
54
|
+
const storeId = custom?.storeId;
|
|
55
|
+
if (!storeId) {
|
|
56
|
+
// Only reachable through a config written outside the settings API, which refuses this shape.
|
|
57
|
+
deps.logger?.warn('content storage: custom backend selected with no store named', { accountId });
|
|
58
|
+
return { blob: null, remember: true };
|
|
59
|
+
}
|
|
60
|
+
const definition = deps.binaryStoreRegistry?.get(storeId);
|
|
61
|
+
if (!definition) {
|
|
62
|
+
deps.logger?.warn('content storage: no binary store registered under the configured id', {
|
|
63
|
+
accountId,
|
|
64
|
+
storeId,
|
|
65
|
+
registered: deps.binaryStoreRegistry?.ids() ?? [],
|
|
66
|
+
});
|
|
67
|
+
return { blob: null, remember: true };
|
|
68
|
+
}
|
|
69
|
+
const declineKey = `${accountId ?? '__default__'}::${storeId}`;
|
|
70
|
+
const blob = definition.create({
|
|
71
|
+
accountId,
|
|
72
|
+
...(deps.logger ? { logger: deps.logger } : {}),
|
|
73
|
+
});
|
|
74
|
+
if (!blob) {
|
|
75
|
+
// The store's own "not right now". It said so deliberately, so this is a lower-severity line
|
|
76
|
+
// than the two above and still not silent.
|
|
77
|
+
if (!announced.has(declineKey)) {
|
|
78
|
+
announced.add(declineKey);
|
|
79
|
+
deps.logger?.info('content storage: registered binary store declined to build', {
|
|
80
|
+
accountId,
|
|
81
|
+
storeId,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
return { blob: null, remember: false };
|
|
85
|
+
}
|
|
86
|
+
announced.delete(declineKey);
|
|
87
|
+
// The persisted `storage` value is the REGISTERED id, not whatever the implementation declares.
|
|
88
|
+
// The column's only job is to say which store to ask for these bytes, and the registry is the
|
|
89
|
+
// only party that knows the answer: an implementation reused under two registrations (one bucket
|
|
90
|
+
// per region, say) declares one `kind` for both, and one stamping `s3` would make its rows read
|
|
91
|
+
// as the platform's own S3 backend's. Wrapped rather than spread so a class instance keeps its
|
|
92
|
+
// `this`.
|
|
93
|
+
return {
|
|
94
|
+
blob: {
|
|
95
|
+
kind: definition.id,
|
|
96
|
+
put: (key, bytes, contentType) => blob.put(key, bytes, contentType),
|
|
97
|
+
get: (key) => blob.get(key),
|
|
98
|
+
delete: (key) => blob.delete(key),
|
|
99
|
+
},
|
|
100
|
+
remember: true,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
20
103
|
/**
|
|
21
104
|
* Compose a {@link ResolveBinaryArtifactStore}: workspace → owning account → that account's
|
|
22
105
|
* configured backend (or the runtime default when unconfigured) → a composed
|
|
@@ -28,12 +111,19 @@ function credentialFingerprint(creds) {
|
|
|
28
111
|
* requests until the config actually changes.
|
|
29
112
|
*/
|
|
30
113
|
export function makeResolveBinaryArtifactStore(deps) {
|
|
114
|
+
// `store: null` is a CACHED REFUSAL, not an empty slot: an account whose configured backend
|
|
115
|
+
// this build cannot serve gets the same answer every time until its config changes, so
|
|
116
|
+
// re-deriving it per resolve only repeats the work and the log line that named the problem.
|
|
117
|
+
// Only a `settled` refusal is remembered (see {@link BlobResolution}).
|
|
31
118
|
const cache = new Map();
|
|
119
|
+
// `account::store` pairs whose store has already reported that it cannot build right now.
|
|
120
|
+
const announcedDeclines = new Set();
|
|
32
121
|
return async (workspaceId) => {
|
|
33
122
|
const accountId = (await deps.accountOf(workspaceId)) ?? null;
|
|
34
123
|
let backend = deps.defaultBackend;
|
|
35
124
|
let fs;
|
|
36
125
|
let s3;
|
|
126
|
+
let custom;
|
|
37
127
|
let s3Credentials;
|
|
38
128
|
if (accountId && deps.accountSettings) {
|
|
39
129
|
const resolved = await deps.accountSettings.resolve(accountId);
|
|
@@ -42,6 +132,7 @@ export function makeResolveBinaryArtifactStore(deps) {
|
|
|
42
132
|
backend = cs.backend;
|
|
43
133
|
fs = cs.fs;
|
|
44
134
|
s3 = cs.s3;
|
|
135
|
+
custom = cs.custom;
|
|
45
136
|
s3Credentials = resolved.s3Credentials;
|
|
46
137
|
}
|
|
47
138
|
}
|
|
@@ -52,17 +143,29 @@ export function makeResolveBinaryArtifactStore(deps) {
|
|
|
52
143
|
backend,
|
|
53
144
|
fs,
|
|
54
145
|
s3,
|
|
146
|
+
// The store id joins the signature for the same reason the S3 fingerprint does: switching
|
|
147
|
+
// an account between two registered stores changes nothing else here, and a cached store
|
|
148
|
+
// would keep writing into the one it was pointed at when it was built.
|
|
149
|
+
custom,
|
|
55
150
|
creds: credentialFingerprint(s3Credentials),
|
|
56
151
|
});
|
|
57
152
|
const cached = cache.get(cacheKey);
|
|
58
153
|
if (cached && cached.signature === signature)
|
|
59
154
|
return cached.store;
|
|
60
|
-
const
|
|
61
|
-
|
|
155
|
+
const resolution = backend === 'custom'
|
|
156
|
+
? buildRegisteredStore(deps, accountId, custom, announcedDeclines)
|
|
157
|
+
: // A runtime's own factory is a pure function of the kind plus this account's connection
|
|
158
|
+
// settings, both of which are in the signature, so its `null` ("`fs` on Cloudflare",
|
|
159
|
+
// "`s3` with no connection") cannot change without the signature changing with it.
|
|
160
|
+
{ blob: deps.buildBlobBackend(backend, { fs, s3, s3Credentials }), remember: true };
|
|
161
|
+
if (!resolution.blob) {
|
|
162
|
+
if (resolution.remember)
|
|
163
|
+
cache.set(cacheKey, { signature, store: null });
|
|
62
164
|
return null;
|
|
165
|
+
}
|
|
63
166
|
const store = createBinaryArtifactStore({
|
|
64
167
|
metadata: deps.metadata,
|
|
65
|
-
blob,
|
|
168
|
+
blob: resolution.blob,
|
|
66
169
|
idGenerator: deps.idGenerator,
|
|
67
170
|
clock: deps.clock,
|
|
68
171
|
...(deps.logger ? { logger: deps.logger } : {}),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"binaryArtifactStore.js","sourceRoot":"","sources":["../../src/persistence/binaryArtifactStore.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"binaryArtifactStore.js","sourceRoot":"","sources":["../../src/persistence/binaryArtifactStore.ts"],"names":[],"mappings":"AAoBA,OAAO,EAAE,yBAAyB,EAAE,MAAM,qBAAqB,CAAA;AAsC/D;;;;;;;GAOG;AACH,MAAM,UAAU,0BAA0B,CACxC,UAAoC,EACpC,QAA8B;IAE9B,MAAM,YAAY,GAAG,QAAQ,EAAE,KAAK,EAAE,IAAI,EAAE,CAAA;IAC5C,OAAO;QACL,GAAG,UAAU;QACb,iBAAiB,EAAE,YAAY,CAAC,MAAM;YACpC,CAAC,CAAC,CAAC,GAAG,UAAU,CAAC,iBAAiB,EAAE,QAAQ,CAAC;YAC7C,CAAC,CAAC,UAAU,CAAC,iBAAiB;QAChC,YAAY;KACb,CAAA;AACH,CAAC;AA8BD;;;;;;GAMG;AACH,SAAS,qBAAqB,CAAC,KAA2B;IACxD,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAA;IACvB,MAAM,QAAQ,GAAG,GAAG,KAAK,CAAC,WAAW,IAAI,KAAK,CAAC,eAAe,EAAE,CAAA;IAChE,IAAI,IAAI,GAAG,UAAU,CAAA;IACrB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACzC,IAAI,IAAI,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,CAAA;QAC9B,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,UAAU,CAAC,CAAA;IACpC,CAAC;IACD,OAAO,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAA;AAClC,CAAC;AAoBD;;;;;;;;;;;;;;GAcG;AACH,SAAS,oBAAoB,CAC3B,IAAwC,EACxC,SAAwB,EACxB,MAA8C,EAC9C,SAAsB;IAEtB,MAAM,OAAO,GAAG,MAAM,EAAE,OAAO,CAAA;IAC/B,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,8FAA8F;QAC9F,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,8DAA8D,EAAE,EAAE,SAAS,EAAE,CAAC,CAAA;QAChG,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAA;IACvC,CAAC;IACD,MAAM,UAAU,GAAG,IAAI,CAAC,mBAAmB,EAAE,GAAG,CAAC,OAAO,CAAC,CAAA;IACzD,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,qEAAqE,EAAE;YACvF,SAAS;YACT,OAAO;YACP,UAAU,EAAE,IAAI,CAAC,mBAAmB,EAAE,GAAG,EAAE,IAAI,EAAE;SAClD,CAAC,CAAA;QACF,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAA;IACvC,CAAC;IACD,MAAM,UAAU,GAAG,GAAG,SAAS,IAAI,aAAa,KAAK,OAAO,EAAE,CAAA;IAC9D,MAAM,IAAI,GAAG,UAAU,CAAC,MAAM,CAAC;QAC7B,SAAS;QACT,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAChD,CAAC,CAAA;IACF,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,6FAA6F;QAC7F,2CAA2C;QAC3C,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC;YAC/B,SAAS,CAAC,GAAG,CAAC,UAAU,CAAC,CAAA;YACzB,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,4DAA4D,EAAE;gBAC9E,SAAS;gBACT,OAAO;aACR,CAAC,CAAA;QACJ,CAAC;QACD,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAA;IACxC,CAAC;IACD,SAAS,CAAC,MAAM,CAAC,UAAU,CAAC,CAAA;IAC5B,gGAAgG;IAChG,8FAA8F;IAC9F,iGAAiG;IACjG,gGAAgG;IAChG,+FAA+F;IAC/F,UAAU;IACV,OAAO;QACL,IAAI,EAAE;YACJ,IAAI,EAAE,UAAU,CAAC,EAAE;YACnB,GAAG,EAAE,CAAC,GAAG,EAAE,KAAK,EAAE,WAAW,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,EAAE,WAAW,CAAC;YACnE,GAAG,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;YAC3B,MAAM,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC;SAClC;QACD,QAAQ,EAAE,IAAI;KACf,CAAA;AACH,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,8BAA8B,CAC5C,IAAwC;IAExC,4FAA4F;IAC5F,uFAAuF;IACvF,4FAA4F;IAC5F,uEAAuE;IACvE,MAAM,KAAK,GAAG,IAAI,GAAG,EAAoE,CAAA;IACzF,0FAA0F;IAC1F,MAAM,iBAAiB,GAAG,IAAI,GAAG,EAAU,CAAA;IAE3C,OAAO,KAAK,EAAE,WAAW,EAAE,EAAE;QAC3B,MAAM,SAAS,GAAG,CAAC,MAAM,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC,IAAI,IAAI,CAAA;QAE7D,IAAI,OAAO,GAA0B,IAAI,CAAC,cAAc,CAAA;QACxD,IAAI,EAAsC,CAAA;QAC1C,IAAI,EAAsC,CAAA;QAC1C,IAAI,MAA8C,CAAA;QAClD,IAAI,aAA8C,CAAA;QAClD,IAAI,SAAS,IAAI,IAAI,CAAC,eAAe,EAAE,CAAC;YACtC,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,eAAe,CAAC,OAAO,CAAC,SAAS,CAAC,CAAA;YAC9D,MAAM,EAAE,GAAG,QAAQ,CAAC,MAAM,CAAC,cAAc,CAAA;YACzC,IAAI,EAAE,EAAE,CAAC;gBACP,OAAO,GAAG,EAAE,CAAC,OAAO,CAAA;gBACpB,EAAE,GAAG,EAAE,CAAC,EAAE,CAAA;gBACV,EAAE,GAAG,EAAE,CAAC,EAAE,CAAA;gBACV,MAAM,GAAG,EAAE,CAAC,MAAM,CAAA;gBAClB,aAAa,GAAG,QAAQ,CAAC,aAAa,CAAA;YACxC,CAAC;QACH,CAAC;QAED,IAAI,OAAO,KAAK,KAAK;YAAE,OAAO,IAAI,CAAA;QAElC,MAAM,QAAQ,GAAG,SAAS,IAAI,aAAa,CAAA;QAC3C,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC;YAC/B,OAAO;YACP,EAAE;YACF,EAAE;YACF,0FAA0F;YAC1F,yFAAyF;YACzF,uEAAuE;YACvE,MAAM;YACN,KAAK,EAAE,qBAAqB,CAAC,aAAa,CAAC;SAC5C,CAAC,CAAA;QACF,MAAM,MAAM,GAAG,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;QAClC,IAAI,MAAM,IAAI,MAAM,CAAC,SAAS,KAAK,SAAS;YAAE,OAAO,MAAM,CAAC,KAAK,CAAA;QAEjE,MAAM,UAAU,GACd,OAAO,KAAK,QAAQ;YAClB,CAAC,CAAC,oBAAoB,CAAC,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,iBAAiB,CAAC;YAClE,CAAC,CAAC,wFAAwF;gBACxF,qFAAqF;gBACrF,mFAAmF;gBACnF,EAAE,IAAI,EAAE,IAAI,CAAC,gBAAgB,CAAC,OAAO,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,aAAa,EAAE,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAA;QACzF,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC;YACrB,IAAI,UAAU,CAAC,QAAQ;gBAAE,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAA;YACxE,OAAO,IAAI,CAAA;QACb,CAAC;QACD,MAAM,KAAK,GAAG,yBAAyB,CAAC;YACtC,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,IAAI,EAAE,UAAU,CAAC,IAAI;YACrB,WAAW,EAAE,IAAI,CAAC,WAAW;YAC7B,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAChD,CAAC,CAAA;QACF,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAA;QACzC,OAAO,KAAK,CAAA;IACd,CAAC,CAAA;AACH,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rpc-allowlist.d.ts","sourceRoot":"","sources":["../../src/persistence/rpc-allowlist.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,UAAU,CAAA;AAgBtD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,eAAO,MAAM,0BAA0B,EAAE,
|
|
1
|
+
{"version":3,"file":"rpc-allowlist.d.ts","sourceRoot":"","sources":["../../src/persistence/rpc-allowlist.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,UAAU,CAAA;AAgBtD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,eAAO,MAAM,0BAA0B,EAAE,sBAkxCxC,CAAA;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,oCAAoC,YAC/C,yBAAyB,EACzB,gCAAgC,EAChC,4BAA4B,EAC5B,yBAAyB,EACzB,2BAA2B,EAC3B,kCAAkC,CAC1B,CAAA;AAEV,yEAAyE;AACzE,MAAM,MAAM,+BAA+B,GAAG,CAAC,OAAO,oCAAoC,CAAC,CAAC,MAAM,CAAC,CAAA"}
|
|
@@ -1328,6 +1328,14 @@ export const REMOTE_PERSISTENCE_METHODS = {
|
|
|
1328
1328
|
getByWorkspace: { scope: { kind: 'workspace', arg: 0 } },
|
|
1329
1329
|
upsert: { scope: { kind: 'workspaceField', arg: 0 } },
|
|
1330
1330
|
},
|
|
1331
|
+
// Per-workspace notification routing (which types this board delivers on which channel). No
|
|
1332
|
+
// secrets, and the same shape as `slackSettingsRepository` above: `getByWorkspace` takes the
|
|
1333
|
+
// workspaceId as arg0 (the `workspace` rule), the record-based `upsert(record)` binds on the
|
|
1334
|
+
// record's `workspaceId` FIELD (the `workspaceField` rule).
|
|
1335
|
+
notificationSettingsRepository: {
|
|
1336
|
+
getByWorkspace: { scope: { kind: 'workspace', arg: 0 } },
|
|
1337
|
+
upsert: { scope: { kind: 'workspaceField', arg: 0 } },
|
|
1338
|
+
},
|
|
1331
1339
|
// Per-account GitHub-user → Slack-member mapping (for @-mentions). No secrets. Both methods take
|
|
1332
1340
|
// the accountId as arg0 positionally (`upsert(accountId, entries, at)` — a positional accountId,
|
|
1333
1341
|
// not a record), so the `account` rule binds both.
|