@mastra/factory 0.0.0 → 0.1.0-alpha.3
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/CHANGELOG.md +23 -0
- package/LICENSE.md +30 -0
- package/dist/auth.d.ts +181 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +370 -0
- package/dist/auth.js.map +1 -0
- package/dist/capabilities/connection.d.ts +9 -0
- package/dist/capabilities/connection.d.ts.map +1 -0
- package/dist/capabilities/connection.js +1 -0
- package/dist/capabilities/connection.js.map +1 -0
- package/dist/capabilities/intake.d.ts +93 -0
- package/dist/capabilities/intake.d.ts.map +1 -0
- package/dist/capabilities/intake.js +1 -0
- package/dist/capabilities/intake.js.map +1 -0
- package/dist/capabilities/version-control.d.ts +239 -0
- package/dist/capabilities/version-control.d.ts.map +1 -0
- package/dist/capabilities/version-control.js +1 -0
- package/dist/capabilities/version-control.js.map +1 -0
- package/dist/factory.d.ts +145 -0
- package/dist/factory.d.ts.map +1 -0
- package/dist/factory.js +38245 -0
- package/dist/factory.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +38245 -0
- package/dist/index.js.map +1 -0
- package/dist/integrations/base.d.ts +197 -0
- package/dist/integrations/base.d.ts.map +1 -0
- package/dist/integrations/base.js +1 -0
- package/dist/integrations/base.js.map +1 -0
- package/dist/integrations/github/config.d.ts +60 -0
- package/dist/integrations/github/config.d.ts.map +1 -0
- package/dist/integrations/github/config.js +28 -0
- package/dist/integrations/github/config.js.map +1 -0
- package/dist/integrations/github/factory-session.d.ts +12 -0
- package/dist/integrations/github/factory-session.d.ts.map +1 -0
- package/dist/integrations/github/factory-session.js +41 -0
- package/dist/integrations/github/factory-session.js.map +1 -0
- package/dist/integrations/github/integration.d.ts +191 -0
- package/dist/integrations/github/integration.d.ts.map +1 -0
- package/dist/integrations/github/integration.js +3001 -0
- package/dist/integrations/github/integration.js.map +1 -0
- package/dist/integrations/github/issue-triage.d.ts +27 -0
- package/dist/integrations/github/issue-triage.d.ts.map +1 -0
- package/dist/integrations/github/issue-triage.js +82 -0
- package/dist/integrations/github/issue-triage.js.map +1 -0
- package/dist/integrations/github/project-lock.d.ts +78 -0
- package/dist/integrations/github/project-lock.d.ts.map +1 -0
- package/dist/integrations/github/project-lock.js +70 -0
- package/dist/integrations/github/project-lock.js.map +1 -0
- package/dist/integrations/github/provenance.d.ts +27 -0
- package/dist/integrations/github/provenance.d.ts.map +1 -0
- package/dist/integrations/github/provenance.js +104 -0
- package/dist/integrations/github/provenance.js.map +1 -0
- package/dist/integrations/github/routes.d.ts +72 -0
- package/dist/integrations/github/routes.d.ts.map +1 -0
- package/dist/integrations/github/routes.js +1834 -0
- package/dist/integrations/github/routes.js.map +1 -0
- package/dist/integrations/github/sandbox.d.ts +268 -0
- package/dist/integrations/github/sandbox.d.ts.map +1 -0
- package/dist/integrations/github/sandbox.js +381 -0
- package/dist/integrations/github/sandbox.js.map +1 -0
- package/dist/integrations/github/session-subscriptions.d.ts +23 -0
- package/dist/integrations/github/session-subscriptions.d.ts.map +1 -0
- package/dist/integrations/github/session-subscriptions.js +190 -0
- package/dist/integrations/github/session-subscriptions.js.map +1 -0
- package/dist/integrations/github/subscriptions.d.ts +54 -0
- package/dist/integrations/github/subscriptions.d.ts.map +1 -0
- package/dist/integrations/github/subscriptions.js +70 -0
- package/dist/integrations/github/subscriptions.js.map +1 -0
- package/dist/integrations/github/webhook.d.ts +87 -0
- package/dist/integrations/github/webhook.d.ts.map +1 -0
- package/dist/integrations/github/webhook.js +381 -0
- package/dist/integrations/github/webhook.js.map +1 -0
- package/dist/integrations/linear/agent-tools.d.ts +34 -0
- package/dist/integrations/linear/agent-tools.d.ts.map +1 -0
- package/dist/integrations/linear/agent-tools.js +108 -0
- package/dist/integrations/linear/agent-tools.js.map +1 -0
- package/dist/integrations/linear/integration.d.ts +233 -0
- package/dist/integrations/linear/integration.d.ts.map +1 -0
- package/dist/integrations/linear/integration.js +962 -0
- package/dist/integrations/linear/integration.js.map +1 -0
- package/dist/integrations/linear/routes.d.ts +61 -0
- package/dist/integrations/linear/routes.d.ts.map +1 -0
- package/dist/integrations/linear/routes.js +242 -0
- package/dist/integrations/linear/routes.js.map +1 -0
- package/dist/integrations/linear/storage.d.ts +65 -0
- package/dist/integrations/linear/storage.d.ts.map +1 -0
- package/dist/integrations/linear/storage.js +1 -0
- package/dist/integrations/linear/storage.js.map +1 -0
- package/dist/integrations/platform/api-client.d.ts +24 -0
- package/dist/integrations/platform/api-client.d.ts.map +1 -0
- package/dist/integrations/platform/api-client.js +153 -0
- package/dist/integrations/platform/api-client.js.map +1 -0
- package/dist/integrations/platform/github/event-worker.d.ts +44 -0
- package/dist/integrations/platform/github/event-worker.d.ts.map +1 -0
- package/dist/integrations/platform/github/event-worker.js +585 -0
- package/dist/integrations/platform/github/event-worker.js.map +1 -0
- package/dist/integrations/platform/github/integration.d.ts +36 -0
- package/dist/integrations/platform/github/integration.d.ts.map +1 -0
- package/dist/integrations/platform/github/integration.js +3253 -0
- package/dist/integrations/platform/github/integration.js.map +1 -0
- package/dist/integrations/platform/linear/integration.d.ts +46 -0
- package/dist/integrations/platform/linear/integration.d.ts.map +1 -0
- package/dist/integrations/platform/linear/integration.js +891 -0
- package/dist/integrations/platform/linear/integration.js.map +1 -0
- package/dist/integrations/workos/integration.d.ts +39 -0
- package/dist/integrations/workos/integration.d.ts.map +1 -0
- package/dist/integrations/workos/integration.js +96 -0
- package/dist/integrations/workos/integration.js.map +1 -0
- package/dist/routes/config.d.ts +220 -0
- package/dist/routes/config.d.ts.map +1 -0
- package/dist/routes/config.js +770 -0
- package/dist/routes/config.js.map +1 -0
- package/dist/routes/fs.d.ts +105 -0
- package/dist/routes/fs.d.ts.map +1 -0
- package/dist/routes/fs.js +294 -0
- package/dist/routes/fs.js.map +1 -0
- package/dist/routes/intake.d.ts +23 -0
- package/dist/routes/intake.d.ts.map +1 -0
- package/dist/routes/intake.js +179 -0
- package/dist/routes/intake.js.map +1 -0
- package/dist/routes/oauth.d.ts +43 -0
- package/dist/routes/oauth.d.ts.map +1 -0
- package/dist/routes/oauth.js +25058 -0
- package/dist/routes/oauth.js.map +1 -0
- package/dist/routes/projects.d.ts +19 -0
- package/dist/routes/projects.d.ts.map +1 -0
- package/dist/routes/projects.js +395 -0
- package/dist/routes/projects.js.map +1 -0
- package/dist/routes/provider-credentials.d.ts +81 -0
- package/dist/routes/provider-credentials.d.ts.map +1 -0
- package/dist/routes/provider-credentials.js +68 -0
- package/dist/routes/provider-credentials.js.map +1 -0
- package/dist/routes/route.d.ts +48 -0
- package/dist/routes/route.d.ts.map +1 -0
- package/dist/routes/route.js +11 -0
- package/dist/routes/route.js.map +1 -0
- package/dist/routes/skills.d.ts +30 -0
- package/dist/routes/skills.d.ts.map +1 -0
- package/dist/routes/skills.js +181 -0
- package/dist/routes/skills.js.map +1 -0
- package/dist/routes/surface.d.ts +83 -0
- package/dist/routes/surface.d.ts.map +1 -0
- package/dist/routes/surface.js +28519 -0
- package/dist/routes/surface.js.map +1 -0
- package/dist/routes/tenant-credentials.d.ts +74 -0
- package/dist/routes/tenant-credentials.d.ts.map +1 -0
- package/dist/routes/tenant-credentials.js +181 -0
- package/dist/routes/tenant-credentials.js.map +1 -0
- package/dist/routes/work-items.d.ts +40 -0
- package/dist/routes/work-items.d.ts.map +1 -0
- package/dist/routes/work-items.js +902 -0
- package/dist/routes/work-items.js.map +1 -0
- package/dist/rules/binding-context.d.ts +5 -0
- package/dist/rules/binding-context.d.ts.map +1 -0
- package/dist/rules/binding-context.js +40 -0
- package/dist/rules/binding-context.js.map +1 -0
- package/dist/rules/defaults.d.ts +9 -0
- package/dist/rules/defaults.d.ts.map +1 -0
- package/dist/rules/defaults.js +334 -0
- package/dist/rules/defaults.js.map +1 -0
- package/dist/rules/dispatcher.d.ts +40 -0
- package/dist/rules/dispatcher.d.ts.map +1 -0
- package/dist/rules/dispatcher.js +695 -0
- package/dist/rules/dispatcher.js.map +1 -0
- package/dist/rules/github-service.d.ts +29 -0
- package/dist/rules/github-service.d.ts.map +1 -0
- package/dist/rules/github-service.js +474 -0
- package/dist/rules/github-service.js.map +1 -0
- package/dist/rules/index.d.ts +7 -0
- package/dist/rules/index.d.ts.map +1 -0
- package/dist/rules/index.js +583 -0
- package/dist/rules/index.js.map +1 -0
- package/dist/rules/linear-service.d.ts +27 -0
- package/dist/rules/linear-service.d.ts.map +1 -0
- package/dist/rules/linear-service.js +343 -0
- package/dist/rules/linear-service.js.map +1 -0
- package/dist/rules/processor.d.ts +54 -0
- package/dist/rules/processor.d.ts.map +1 -0
- package/dist/rules/processor.js +629 -0
- package/dist/rules/processor.js.map +1 -0
- package/dist/rules/resolve.d.ts +17 -0
- package/dist/rules/resolve.d.ts.map +1 -0
- package/dist/rules/resolve.js +27 -0
- package/dist/rules/resolve.js.map +1 -0
- package/dist/rules/start-coordinator.d.ts +58 -0
- package/dist/rules/start-coordinator.d.ts.map +1 -0
- package/dist/rules/start-coordinator.js +136 -0
- package/dist/rules/start-coordinator.js.map +1 -0
- package/dist/rules/tools.d.ts +10 -0
- package/dist/rules/tools.d.ts.map +1 -0
- package/dist/rules/tools.js +93 -0
- package/dist/rules/tools.js.map +1 -0
- package/dist/rules/transition-service.d.ts +32 -0
- package/dist/rules/transition-service.d.ts.map +1 -0
- package/dist/rules/transition-service.js +474 -0
- package/dist/rules/transition-service.js.map +1 -0
- package/dist/rules/types.d.ts +225 -0
- package/dist/rules/types.d.ts.map +1 -0
- package/dist/rules/types.js +33 -0
- package/dist/rules/types.js.map +1 -0
- package/dist/rules/validation.d.ts +11 -0
- package/dist/rules/validation.d.ts.map +1 -0
- package/dist/rules/validation.js +310 -0
- package/dist/rules/validation.js.map +1 -0
- package/dist/sandbox/fleet.d.ts +208 -0
- package/dist/sandbox/fleet.d.ts.map +1 -0
- package/dist/sandbox/fleet.js +259 -0
- package/dist/sandbox/fleet.js.map +1 -0
- package/dist/sandbox/reattach.d.ts +3 -0
- package/dist/sandbox/reattach.d.ts.map +1 -0
- package/dist/sandbox/reattach.js +9 -0
- package/dist/sandbox/reattach.js.map +1 -0
- package/dist/server-error.d.ts +12 -0
- package/dist/server-error.d.ts.map +1 -0
- package/dist/server-error.js +20 -0
- package/dist/server-error.js.map +1 -0
- package/dist/skills/service.d.ts +48 -0
- package/dist/skills/service.d.ts.map +1 -0
- package/dist/skills/service.js +45 -0
- package/dist/skills/service.js.map +1 -0
- package/dist/spa-static.d.ts +34 -0
- package/dist/spa-static.d.ts.map +1 -0
- package/dist/spa-static.js +73 -0
- package/dist/spa-static.js.map +1 -0
- package/dist/state-signing.d.ts +48 -0
- package/dist/state-signing.d.ts.map +1 -0
- package/dist/state-signing.js +48 -0
- package/dist/state-signing.js.map +1 -0
- package/dist/storage/domains/audit/agent-audit.d.ts +27 -0
- package/dist/storage/domains/audit/agent-audit.d.ts.map +1 -0
- package/dist/storage/domains/audit/agent-audit.js +65 -0
- package/dist/storage/domains/audit/agent-audit.js.map +1 -0
- package/dist/storage/domains/audit/base.d.ts +140 -0
- package/dist/storage/domains/audit/base.d.ts.map +1 -0
- package/dist/storage/domains/audit/base.js +127 -0
- package/dist/storage/domains/audit/base.js.map +1 -0
- package/dist/storage/domains/audit/domain.d.ts +69 -0
- package/dist/storage/domains/audit/domain.d.ts.map +1 -0
- package/dist/storage/domains/audit/domain.js +165 -0
- package/dist/storage/domains/audit/domain.js.map +1 -0
- package/dist/storage/domains/credentials/base.d.ts +134 -0
- package/dist/storage/domains/credentials/base.d.ts.map +1 -0
- package/dist/storage/domains/credentials/base.js +257 -0
- package/dist/storage/domains/credentials/base.js.map +1 -0
- package/dist/storage/domains/intake/base.d.ts +38 -0
- package/dist/storage/domains/intake/base.d.ts.map +1 -0
- package/dist/storage/domains/intake/base.js +62 -0
- package/dist/storage/domains/intake/base.js.map +1 -0
- package/dist/storage/domains/integrations/base.d.ts +126 -0
- package/dist/storage/domains/integrations/base.d.ts.map +1 -0
- package/dist/storage/domains/integrations/base.js +252 -0
- package/dist/storage/domains/integrations/base.js.map +1 -0
- package/dist/storage/domains/model-packs/base.d.ts +49 -0
- package/dist/storage/domains/model-packs/base.d.ts.map +1 -0
- package/dist/storage/domains/model-packs/base.js +95 -0
- package/dist/storage/domains/model-packs/base.js.map +1 -0
- package/dist/storage/domains/projects/base.d.ts +55 -0
- package/dist/storage/domains/projects/base.d.ts.map +1 -0
- package/dist/storage/domains/projects/base.js +99 -0
- package/dist/storage/domains/projects/base.js.map +1 -0
- package/dist/storage/domains/queue-health/base.d.ts +52 -0
- package/dist/storage/domains/queue-health/base.d.ts.map +1 -0
- package/dist/storage/domains/queue-health/base.js +87 -0
- package/dist/storage/domains/queue-health/base.js.map +1 -0
- package/dist/storage/domains/source-control/base.d.ts +288 -0
- package/dist/storage/domains/source-control/base.d.ts.map +1 -0
- package/dist/storage/domains/source-control/base.js +722 -0
- package/dist/storage/domains/source-control/base.js.map +1 -0
- package/dist/storage/domains/source-control/inmemory.d.ts +158 -0
- package/dist/storage/domains/source-control/inmemory.d.ts.map +1 -0
- package/dist/storage/domains/source-control/inmemory.js +348 -0
- package/dist/storage/domains/source-control/inmemory.js.map +1 -0
- package/dist/storage/domains/work-items/base.d.ts +412 -0
- package/dist/storage/domains/work-items/base.d.ts.map +1 -0
- package/dist/storage/domains/work-items/base.js +1174 -0
- package/dist/storage/domains/work-items/base.js.map +1 -0
- package/dist/storage/domains/work-items/metrics.d.ts +101 -0
- package/dist/storage/domains/work-items/metrics.d.ts.map +1 -0
- package/dist/storage/domains/work-items/metrics.js +203 -0
- package/dist/storage/domains/work-items/metrics.js.map +1 -0
- package/dist/workspace.d.ts +20 -0
- package/dist/workspace.d.ts.map +1 -0
- package/dist/workspace.js +364 -0
- package/dist/workspace.js.map +1 -0
- package/package.json +19 -19
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-tenant credential store for model resolution (deployed mode).
|
|
3
|
+
*
|
|
4
|
+
* The SDK's `resolveModel` asks the registered `CredentialStoreProvider` for a
|
|
5
|
+
* store synchronously, so this module keeps a small per-tenant **snapshot** of
|
|
6
|
+
* resolved credentials (user rows over org rows) hydrated from the
|
|
7
|
+
* `model-credentials` domain. The snapshot serves the gateway's synchronous
|
|
8
|
+
* path-selection reads (`get` / `getStoredApiKey`); the fetch-time
|
|
9
|
+
* `getApiKey` is authoritative — it re-resolves against the domain and
|
|
10
|
+
* refreshes expired OAuth tokens under the domain's row lock, so a slightly
|
|
11
|
+
* stale snapshot can never send an expired token upstream.
|
|
12
|
+
*
|
|
13
|
+
* Snapshots are primed per request by `createTenantCredentialPrimer` (mounted
|
|
14
|
+
* after the web auth gate) so the first model call of a request already sees
|
|
15
|
+
* the caller's credentials. This store explicitly disables the SDK's
|
|
16
|
+
* environment fallback so server-shell credentials never leak into tenants.
|
|
17
|
+
*/
|
|
18
|
+
import type { CredentialTenant as SdkCredentialTenant } from '@mastra/code-sdk/agents/credential-resolver';
|
|
19
|
+
import type { AuthCredential, CredentialStore } from '@mastra/code-sdk/auth/types';
|
|
20
|
+
import type { MiddlewareHandler } from 'hono';
|
|
21
|
+
import type { ModelCredentialsStorage } from '../storage/domains/credentials/base.js';
|
|
22
|
+
import type { RouteAuth } from './route.js';
|
|
23
|
+
export declare class TenantCredentialStore implements CredentialStore {
|
|
24
|
+
#private;
|
|
25
|
+
readonly allowEnvironmentFallback = false;
|
|
26
|
+
constructor(orgId: string, userId: string, credentials: ModelCredentialsStorage | undefined);
|
|
27
|
+
/** Hydrate the snapshot when stale; coalesces concurrent callers. */
|
|
28
|
+
ensureFresh(now?: number): Promise<void>;
|
|
29
|
+
/** Sync by contract; kicks a background re-hydrate when the snapshot is stale. */
|
|
30
|
+
reload(): void;
|
|
31
|
+
get(provider: string): AuthCredential | undefined;
|
|
32
|
+
getStoredApiKey(provider: string): string | undefined;
|
|
33
|
+
/**
|
|
34
|
+
* Authoritative fetch-time resolution: re-reads the domain (user > org) and
|
|
35
|
+
* refreshes expired OAuth tokens under the domain's row lock. Mirrors
|
|
36
|
+
* `AuthStorage.getApiKey` semantics: `undefined` on missing credential or
|
|
37
|
+
* failed refresh (caller surfaces a re-login error).
|
|
38
|
+
*/
|
|
39
|
+
getApiKey(provider: string): Promise<string | undefined>;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Register the web tenant credential store provider with the SDK. Called by
|
|
43
|
+
* the factory after storage init with the `model-credentials` domain handle;
|
|
44
|
+
* from then on `resolveModel` uses per-tenant credentials and the SDK skips
|
|
45
|
+
* the `loadStoredApiKeysIntoEnv` env side-channel.
|
|
46
|
+
*/
|
|
47
|
+
export declare function registerTenantCredentialResolver(credentials: ModelCredentialsStorage): void;
|
|
48
|
+
/** Test hook: clear registration and cached tenant snapshots. */
|
|
49
|
+
export declare function resetTenantCredentialResolverForTests(): void;
|
|
50
|
+
/**
|
|
51
|
+
* Drop cached snapshots after a credential write so the change is visible to
|
|
52
|
+
* the next model call immediately instead of after the snapshot TTL. An org
|
|
53
|
+
* write affects every member's resolved view, so all stores under the org are
|
|
54
|
+
* invalidated; a user write only drops that user's store.
|
|
55
|
+
*/
|
|
56
|
+
export declare function invalidateTenantCredentialSnapshots(tenant: {
|
|
57
|
+
orgId: string;
|
|
58
|
+
userId?: string;
|
|
59
|
+
}): void;
|
|
60
|
+
/**
|
|
61
|
+
* Middleware mounted after the web auth gate: primes the caller's credential
|
|
62
|
+
* snapshot so the request's first model call sees their credentials without an
|
|
63
|
+
* async seam in model resolution. Cheap when fresh (TTL check), best-effort
|
|
64
|
+
* when not — a failed hydrate falls back to env vars, never blocks a request.
|
|
65
|
+
*/
|
|
66
|
+
export declare function primeTenantCredentials({ tenant, credentials, }: {
|
|
67
|
+
tenant: SdkCredentialTenant;
|
|
68
|
+
credentials: ModelCredentialsStorage;
|
|
69
|
+
}): Promise<void>;
|
|
70
|
+
export declare function createTenantCredentialPrimer({ auth, credentials, }: {
|
|
71
|
+
auth: RouteAuth;
|
|
72
|
+
credentials: ModelCredentialsStorage;
|
|
73
|
+
}): MiddlewareHandler;
|
|
74
|
+
//# sourceMappingURL=tenant-credentials.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tenant-credentials.d.ts","sourceRoot":"","sources":["../../src/routes/tenant-credentials.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,gBAAgB,IAAI,mBAAmB,EAAE,MAAM,6CAA6C,CAAC;AAG3G,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AACnF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAG9C,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,wCAAwC,CAAC;AAEtF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAQ5C,qBAAa,qBAAsB,YAAW,eAAe;;IAC3D,QAAQ,CAAC,wBAAwB,SAAS;gBAQ9B,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,uBAAuB,GAAG,SAAS;IAM3F,qEAAqE;IAC/D,WAAW,CAAC,GAAG,SAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAwBlD,kFAAkF;IAClF,MAAM,IAAI,IAAI;IAMd,GAAG,CAAC,QAAQ,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS;IAIjD,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAKrD;;;;;OAKG;IACG,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;CA+C/D;AAmBD;;;;;GAKG;AACH,wBAAgB,gCAAgC,CAAC,WAAW,EAAE,uBAAuB,GAAG,IAAI,CAE3F;AAED,iEAAiE;AACjE,wBAAgB,qCAAqC,IAAI,IAAI,CAG5D;AAED;;;;;GAKG;AACH,wBAAgB,mCAAmC,CAAC,MAAM,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAQpG;AAED;;;;;GAKG;AACH,wBAAsB,sBAAsB,CAAC,EAC3C,MAAM,EACN,WAAW,GACZ,EAAE;IACD,MAAM,EAAE,mBAAmB,CAAC;IAC5B,WAAW,EAAE,uBAAuB,CAAC;CACtC,GAAG,OAAO,CAAC,IAAI,CAAC,CAEhB;AAED,wBAAgB,4BAA4B,CAAC,EAC3C,IAAI,EACJ,WAAW,GACZ,EAAE;IACD,IAAI,EAAE,SAAS,CAAC;IAChB,WAAW,EAAE,uBAAuB,CAAC;CACtC,GAAG,iBAAiB,CAYpB"}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
// src/routes/tenant-credentials.ts
|
|
2
|
+
import { setCredentialStoreProvider } from "@mastra/code-sdk/agents/credential-resolver";
|
|
3
|
+
import { getOAuthProvider } from "@mastra/code-sdk/auth/storage";
|
|
4
|
+
|
|
5
|
+
// src/storage/domains/credentials/base.ts
|
|
6
|
+
import { FactoryStorageDomain, UniqueViolationError } from "@mastra/core/storage";
|
|
7
|
+
function isOAuthCredentialExpired(credential, now = Date.now()) {
|
|
8
|
+
return now >= credential.expires;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// src/routes/provider-credentials.ts
|
|
12
|
+
async function getTenantCredentialsStorage(credentials) {
|
|
13
|
+
if (!credentials) return void 0;
|
|
14
|
+
try {
|
|
15
|
+
await credentials.ensureReady();
|
|
16
|
+
} catch {
|
|
17
|
+
return void 0;
|
|
18
|
+
}
|
|
19
|
+
return credentials;
|
|
20
|
+
}
|
|
21
|
+
function tenantOrgId(tenant) {
|
|
22
|
+
return tenant.orgId ?? `user:${tenant.userId}`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// src/routes/tenant-credentials.ts
|
|
26
|
+
var SNAPSHOT_TTL_MS = 15e3;
|
|
27
|
+
var MAX_CACHED_TENANTS = 1e3;
|
|
28
|
+
var TenantCredentialStore = class {
|
|
29
|
+
allowEnvironmentFallback = false;
|
|
30
|
+
#orgId;
|
|
31
|
+
#userId;
|
|
32
|
+
#credentials;
|
|
33
|
+
#snapshot = /* @__PURE__ */ new Map();
|
|
34
|
+
#fetchedAt = 0;
|
|
35
|
+
#hydrating;
|
|
36
|
+
constructor(orgId, userId, credentials) {
|
|
37
|
+
this.#orgId = orgId;
|
|
38
|
+
this.#userId = userId;
|
|
39
|
+
this.#credentials = credentials;
|
|
40
|
+
}
|
|
41
|
+
/** Hydrate the snapshot when stale; coalesces concurrent callers. */
|
|
42
|
+
async ensureFresh(now = Date.now()) {
|
|
43
|
+
if (now - this.#fetchedAt < SNAPSHOT_TTL_MS) return;
|
|
44
|
+
this.#hydrating ??= this.#hydrate().finally(() => {
|
|
45
|
+
this.#hydrating = void 0;
|
|
46
|
+
});
|
|
47
|
+
await this.#hydrating;
|
|
48
|
+
}
|
|
49
|
+
async #hydrate() {
|
|
50
|
+
const storage = await getTenantCredentialsStorage(this.#credentials);
|
|
51
|
+
if (!storage) return;
|
|
52
|
+
const records = await storage.listCredentials(this.#orgId, this.#userId);
|
|
53
|
+
const next = /* @__PURE__ */ new Map();
|
|
54
|
+
for (const record of records.filter((r) => r.scope === "org")) {
|
|
55
|
+
next.set(record.provider, record.credential);
|
|
56
|
+
}
|
|
57
|
+
for (const record of records.filter((r) => r.scope === "user")) {
|
|
58
|
+
next.set(record.provider, record.credential);
|
|
59
|
+
}
|
|
60
|
+
this.#snapshot = next;
|
|
61
|
+
this.#fetchedAt = Date.now();
|
|
62
|
+
}
|
|
63
|
+
/** Sync by contract; kicks a background re-hydrate when the snapshot is stale. */
|
|
64
|
+
reload() {
|
|
65
|
+
if (Date.now() - this.#fetchedAt >= SNAPSHOT_TTL_MS) {
|
|
66
|
+
void this.ensureFresh().catch(() => {
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
get(provider) {
|
|
71
|
+
return this.#snapshot.get(provider);
|
|
72
|
+
}
|
|
73
|
+
getStoredApiKey(provider) {
|
|
74
|
+
const cred = this.#snapshot.get(provider);
|
|
75
|
+
return cred?.type === "api_key" ? cred.key : void 0;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Authoritative fetch-time resolution: re-reads the domain (user > org) and
|
|
79
|
+
* refreshes expired OAuth tokens under the domain's row lock. Mirrors
|
|
80
|
+
* `AuthStorage.getApiKey` semantics: `undefined` on missing credential or
|
|
81
|
+
* failed refresh (caller surfaces a re-login error).
|
|
82
|
+
*/
|
|
83
|
+
async getApiKey(provider) {
|
|
84
|
+
const storage = await getTenantCredentialsStorage(this.#credentials);
|
|
85
|
+
if (!storage) {
|
|
86
|
+
const cred = this.#snapshot.get(provider);
|
|
87
|
+
if (cred?.type === "api_key") return cred.key;
|
|
88
|
+
if (cred?.type === "oauth" && !isOAuthCredentialExpired(cred)) {
|
|
89
|
+
return getOAuthProvider(provider)?.getApiKey(cred);
|
|
90
|
+
}
|
|
91
|
+
return void 0;
|
|
92
|
+
}
|
|
93
|
+
const resolved = await storage.resolveCredential(this.#orgId, this.#userId, provider);
|
|
94
|
+
if (!resolved) {
|
|
95
|
+
this.#snapshot.delete(provider);
|
|
96
|
+
return void 0;
|
|
97
|
+
}
|
|
98
|
+
this.#snapshot.set(provider, resolved.credential);
|
|
99
|
+
if (resolved.credential.type === "api_key") {
|
|
100
|
+
return resolved.credential.key;
|
|
101
|
+
}
|
|
102
|
+
const oauthProvider = getOAuthProvider(provider);
|
|
103
|
+
if (!oauthProvider) return void 0;
|
|
104
|
+
if (!isOAuthCredentialExpired(resolved.credential)) {
|
|
105
|
+
return oauthProvider.getApiKey(resolved.credential);
|
|
106
|
+
}
|
|
107
|
+
const rowTenant = resolved.scope === "user" ? { orgId: this.#orgId, userId: this.#userId } : { orgId: this.#orgId };
|
|
108
|
+
try {
|
|
109
|
+
const refreshed = await storage.refreshOAuth(rowTenant, provider, async (current) => ({
|
|
110
|
+
type: "oauth",
|
|
111
|
+
...await oauthProvider.refreshToken(current)
|
|
112
|
+
}));
|
|
113
|
+
if (!refreshed) return void 0;
|
|
114
|
+
this.#snapshot.set(provider, refreshed);
|
|
115
|
+
return oauthProvider.getApiKey(refreshed);
|
|
116
|
+
} catch {
|
|
117
|
+
return void 0;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
var tenantStores = /* @__PURE__ */ new Map();
|
|
122
|
+
function storeFor(tenant, credentials) {
|
|
123
|
+
const orgId = tenantOrgId(tenant);
|
|
124
|
+
const key = `${orgId}\0${tenant.userId}`;
|
|
125
|
+
let store = tenantStores.get(key);
|
|
126
|
+
if (!store) {
|
|
127
|
+
if (tenantStores.size >= MAX_CACHED_TENANTS) {
|
|
128
|
+
const oldest = tenantStores.keys().next().value;
|
|
129
|
+
if (oldest !== void 0) tenantStores.delete(oldest);
|
|
130
|
+
}
|
|
131
|
+
store = new TenantCredentialStore(orgId, tenant.userId, credentials);
|
|
132
|
+
tenantStores.set(key, store);
|
|
133
|
+
}
|
|
134
|
+
return store;
|
|
135
|
+
}
|
|
136
|
+
function registerTenantCredentialResolver(credentials) {
|
|
137
|
+
setCredentialStoreProvider((tenant) => storeFor(tenant, credentials));
|
|
138
|
+
}
|
|
139
|
+
function resetTenantCredentialResolverForTests() {
|
|
140
|
+
setCredentialStoreProvider(void 0);
|
|
141
|
+
tenantStores.clear();
|
|
142
|
+
}
|
|
143
|
+
function invalidateTenantCredentialSnapshots(tenant) {
|
|
144
|
+
if (tenant.userId) {
|
|
145
|
+
tenantStores.delete(`${tenant.orgId}\0${tenant.userId}`);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
for (const key of tenantStores.keys()) {
|
|
149
|
+
if (key.startsWith(`${tenant.orgId}\0`)) tenantStores.delete(key);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
async function primeTenantCredentials({
|
|
153
|
+
tenant,
|
|
154
|
+
credentials
|
|
155
|
+
}) {
|
|
156
|
+
await storeFor(tenant, credentials).ensureFresh();
|
|
157
|
+
}
|
|
158
|
+
function createTenantCredentialPrimer({
|
|
159
|
+
auth,
|
|
160
|
+
credentials
|
|
161
|
+
}) {
|
|
162
|
+
return async (c, next) => {
|
|
163
|
+
const tenant = auth.tenant(c);
|
|
164
|
+
if (tenant) {
|
|
165
|
+
try {
|
|
166
|
+
await storeFor(tenant, credentials).ensureFresh();
|
|
167
|
+
} catch {
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
await next();
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
export {
|
|
174
|
+
TenantCredentialStore,
|
|
175
|
+
createTenantCredentialPrimer,
|
|
176
|
+
invalidateTenantCredentialSnapshots,
|
|
177
|
+
primeTenantCredentials,
|
|
178
|
+
registerTenantCredentialResolver,
|
|
179
|
+
resetTenantCredentialResolverForTests
|
|
180
|
+
};
|
|
181
|
+
//# sourceMappingURL=tenant-credentials.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/routes/tenant-credentials.ts","../../src/storage/domains/credentials/base.ts","../../src/routes/provider-credentials.ts"],"sourcesContent":["/**\n * Per-tenant credential store for model resolution (deployed mode).\n *\n * The SDK's `resolveModel` asks the registered `CredentialStoreProvider` for a\n * store synchronously, so this module keeps a small per-tenant **snapshot** of\n * resolved credentials (user rows over org rows) hydrated from the\n * `model-credentials` domain. The snapshot serves the gateway's synchronous\n * path-selection reads (`get` / `getStoredApiKey`); the fetch-time\n * `getApiKey` is authoritative — it re-resolves against the domain and\n * refreshes expired OAuth tokens under the domain's row lock, so a slightly\n * stale snapshot can never send an expired token upstream.\n *\n * Snapshots are primed per request by `createTenantCredentialPrimer` (mounted\n * after the web auth gate) so the first model call of a request already sees\n * the caller's credentials. This store explicitly disables the SDK's\n * environment fallback so server-shell credentials never leak into tenants.\n */\n\nimport type { CredentialTenant as SdkCredentialTenant } from '@mastra/code-sdk/agents/credential-resolver';\nimport { setCredentialStoreProvider } from '@mastra/code-sdk/agents/credential-resolver';\nimport { getOAuthProvider } from '@mastra/code-sdk/auth/storage';\nimport type { AuthCredential, CredentialStore } from '@mastra/code-sdk/auth/types';\nimport type { MiddlewareHandler } from 'hono';\n\nimport { isOAuthCredentialExpired } from '../storage/domains/credentials/base.js';\nimport type { ModelCredentialsStorage } from '../storage/domains/credentials/base.js';\nimport { getTenantCredentialsStorage, tenantOrgId } from './provider-credentials.js';\nimport type { RouteAuth } from './route.js';\n\n/** How long a hydrated snapshot is considered fresh. */\nconst SNAPSHOT_TTL_MS = 15_000;\n\n/** Cap on cached tenant stores; oldest-inserted evicted beyond this. */\nconst MAX_CACHED_TENANTS = 1000;\n\nexport class TenantCredentialStore implements CredentialStore {\n readonly allowEnvironmentFallback = false;\n readonly #orgId: string;\n readonly #userId: string;\n readonly #credentials: ModelCredentialsStorage | undefined;\n #snapshot = new Map<string, AuthCredential>();\n #fetchedAt = 0;\n #hydrating: Promise<void> | undefined;\n\n constructor(orgId: string, userId: string, credentials: ModelCredentialsStorage | undefined) {\n this.#orgId = orgId;\n this.#userId = userId;\n this.#credentials = credentials;\n }\n\n /** Hydrate the snapshot when stale; coalesces concurrent callers. */\n async ensureFresh(now = Date.now()): Promise<void> {\n if (now - this.#fetchedAt < SNAPSHOT_TTL_MS) return;\n this.#hydrating ??= this.#hydrate().finally(() => {\n this.#hydrating = undefined;\n });\n await this.#hydrating;\n }\n\n async #hydrate(): Promise<void> {\n const storage = await getTenantCredentialsStorage(this.#credentials);\n if (!storage) return; // Keep the last tenant-scoped snapshot.\n const records = await storage.listCredentials(this.#orgId, this.#userId);\n const next = new Map<string, AuthCredential>();\n // Org rows first so user rows overwrite them (user > org precedence).\n for (const record of records.filter(r => r.scope === 'org')) {\n next.set(record.provider, record.credential);\n }\n for (const record of records.filter(r => r.scope === 'user')) {\n next.set(record.provider, record.credential);\n }\n this.#snapshot = next;\n this.#fetchedAt = Date.now();\n }\n\n /** Sync by contract; kicks a background re-hydrate when the snapshot is stale. */\n reload(): void {\n if (Date.now() - this.#fetchedAt >= SNAPSHOT_TTL_MS) {\n void this.ensureFresh().catch(() => {});\n }\n }\n\n get(provider: string): AuthCredential | undefined {\n return this.#snapshot.get(provider);\n }\n\n getStoredApiKey(provider: string): string | undefined {\n const cred = this.#snapshot.get(provider);\n return cred?.type === 'api_key' ? cred.key : undefined;\n }\n\n /**\n * Authoritative fetch-time resolution: re-reads the domain (user > org) and\n * refreshes expired OAuth tokens under the domain's row lock. Mirrors\n * `AuthStorage.getApiKey` semantics: `undefined` on missing credential or\n * failed refresh (caller surfaces a re-login error).\n */\n async getApiKey(provider: string): Promise<string | undefined> {\n const storage = await getTenantCredentialsStorage(this.#credentials);\n if (!storage) {\n // Domain unavailable: best effort from the snapshot; expired OAuth\n // tokens cannot be refreshed without the domain's lock.\n const cred = this.#snapshot.get(provider);\n if (cred?.type === 'api_key') return cred.key;\n if (cred?.type === 'oauth' && !isOAuthCredentialExpired(cred)) {\n return getOAuthProvider(provider)?.getApiKey(cred);\n }\n return undefined;\n }\n\n const resolved = await storage.resolveCredential(this.#orgId, this.#userId, provider);\n if (!resolved) {\n this.#snapshot.delete(provider);\n return undefined;\n }\n this.#snapshot.set(provider, resolved.credential);\n\n if (resolved.credential.type === 'api_key') {\n return resolved.credential.key;\n }\n\n const oauthProvider = getOAuthProvider(provider);\n if (!oauthProvider) return undefined;\n\n if (!isOAuthCredentialExpired(resolved.credential)) {\n return oauthProvider.getApiKey(resolved.credential);\n }\n\n // Refresh at the scope the credential actually lives at (OAuth rows are\n // user-scoped by policy, but resolve defensively from the record).\n const rowTenant = resolved.scope === 'user' ? { orgId: this.#orgId, userId: this.#userId } : { orgId: this.#orgId };\n try {\n const refreshed = await storage.refreshOAuth(rowTenant, provider, async current => ({\n type: 'oauth' as const,\n ...(await oauthProvider.refreshToken(current)),\n }));\n if (!refreshed) return undefined;\n this.#snapshot.set(provider, refreshed);\n return oauthProvider.getApiKey(refreshed);\n } catch {\n // Refresh failed — user needs to re-login (same posture as AuthStorage).\n return undefined;\n }\n }\n}\n\nconst tenantStores = new Map<string, TenantCredentialStore>();\n\nfunction storeFor(tenant: SdkCredentialTenant, credentials: ModelCredentialsStorage): TenantCredentialStore {\n const orgId = tenantOrgId(tenant);\n const key = `${orgId}\\u0000${tenant.userId}`;\n let store = tenantStores.get(key);\n if (!store) {\n if (tenantStores.size >= MAX_CACHED_TENANTS) {\n const oldest = tenantStores.keys().next().value;\n if (oldest !== undefined) tenantStores.delete(oldest);\n }\n store = new TenantCredentialStore(orgId, tenant.userId, credentials);\n tenantStores.set(key, store);\n }\n return store;\n}\n\n/**\n * Register the web tenant credential store provider with the SDK. Called by\n * the factory after storage init with the `model-credentials` domain handle;\n * from then on `resolveModel` uses per-tenant credentials and the SDK skips\n * the `loadStoredApiKeysIntoEnv` env side-channel.\n */\nexport function registerTenantCredentialResolver(credentials: ModelCredentialsStorage): void {\n setCredentialStoreProvider(tenant => storeFor(tenant, credentials));\n}\n\n/** Test hook: clear registration and cached tenant snapshots. */\nexport function resetTenantCredentialResolverForTests(): void {\n setCredentialStoreProvider(undefined);\n tenantStores.clear();\n}\n\n/**\n * Drop cached snapshots after a credential write so the change is visible to\n * the next model call immediately instead of after the snapshot TTL. An org\n * write affects every member's resolved view, so all stores under the org are\n * invalidated; a user write only drops that user's store.\n */\nexport function invalidateTenantCredentialSnapshots(tenant: { orgId: string; userId?: string }): void {\n if (tenant.userId) {\n tenantStores.delete(`${tenant.orgId}\\u0000${tenant.userId}`);\n return;\n }\n for (const key of tenantStores.keys()) {\n if (key.startsWith(`${tenant.orgId}\\u0000`)) tenantStores.delete(key);\n }\n}\n\n/**\n * Middleware mounted after the web auth gate: primes the caller's credential\n * snapshot so the request's first model call sees their credentials without an\n * async seam in model resolution. Cheap when fresh (TTL check), best-effort\n * when not — a failed hydrate falls back to env vars, never blocks a request.\n */\nexport async function primeTenantCredentials({\n tenant,\n credentials,\n}: {\n tenant: SdkCredentialTenant;\n credentials: ModelCredentialsStorage;\n}): Promise<void> {\n await storeFor(tenant, credentials).ensureFresh();\n}\n\nexport function createTenantCredentialPrimer({\n auth,\n credentials,\n}: {\n auth: RouteAuth;\n credentials: ModelCredentialsStorage;\n}): MiddlewareHandler {\n return async (c, next) => {\n const tenant = auth.tenant(c);\n if (tenant) {\n try {\n await storeFor(tenant, credentials).ensureFresh();\n } catch {\n // Fail open: model calls fall back to env credentials.\n }\n }\n await next();\n };\n}\n","/**\n * Model provider credentials domain — tenant-scoped OAuth tokens and API keys\n * for model providers (Anthropic/Claude Max, OpenAI Codex, GitHub Copilot,\n * xAI, plus plain API-key providers).\n *\n * Tenancy is two-level:\n * - **user-scoped** rows (`userId` present): personal plan OAuth tokens and\n * personal API keys. OAuth plan tokens are ONLY ever user-scoped — they are\n * personal subscriptions, and sharing one would bill/rate-limit a single\n * account for the whole org.\n * - **org-scoped** rows (`userId` absent): shared API keys set by an org\n * admin, inherited by every member.\n *\n * Resolution order at model-call time is user > org (the caller layers server\n * env vars underneath as a final fallback).\n *\n * Secrets posture: credentials are stored server-side only and never returned\n * to the client (same as `linear_connections.access_token`). Encryption at\n * rest is out of scope, matching the existing posture.\n *\n * The domain also owns `oauth_login_sessions`: pending web login flows\n * (paste-code PKCE verifiers, device-code poll state) persisted server-side so\n * any replica can complete or poll a flow started on another.\n */\n\nimport type { AuthCredential, OAuthCredential } from '@mastra/code-sdk/auth/types';\nimport { FactoryStorageDomain, UniqueViolationError } from '@mastra/core/storage';\nimport type { CollectionSchema, CollectionWhere, FactoryStorageOps } from '@mastra/core/storage';\n\n/** Owning tenant of a credential row. `userId` absent = org-scoped row. */\nexport interface CredentialTenant {\n orgId: string;\n userId?: string;\n}\n\n/** Which tenancy level a credential row lives at. */\nexport type CredentialScope = 'user' | 'org';\n\n/** One stored credential, annotated with its scope. */\nexport interface CredentialRecord {\n provider: string;\n scope: CredentialScope;\n credential: AuthCredential;\n updatedAt: Date;\n}\n\n/** Result of per-caller resolution: the winning credential and its scope. */\nexport interface ResolvedCredential {\n provider: string;\n scope: CredentialScope;\n credential: AuthCredential;\n}\n\n/** Kind of pending web login flow a session row tracks. */\nexport type LoginSessionKind = 'paste-code' | 'device-code';\n\n/** One pending OAuth login flow, persisted so any replica can continue it. */\nexport interface LoginSessionRow {\n sessionId: string;\n orgId: string;\n userId: string;\n provider: string;\n kind: LoginSessionKind;\n /** Serialized flow state (PKCE verifier, device-code pending state, ...). */\n pending: Record<string, unknown>;\n expiresAt: Date;\n /** Earliest time the next upstream poll is allowed (device-code flows). */\n nextPollAt: Date | null;\n createdAt: Date;\n}\n\nexport interface CreateLoginSessionInput {\n sessionId: string;\n orgId: string;\n userId: string;\n provider: string;\n kind: LoginSessionKind;\n pending: Record<string, unknown>;\n expiresAt: Date;\n nextPollAt?: Date | null;\n}\n\n/** Mirror of `AuthStorage.getApiKey()`'s expiry check. */\nexport function isOAuthCredentialExpired(credential: OAuthCredential, now = Date.now()): boolean {\n return now >= credential.expires;\n}\n\n/** OAuth plan credentials are personal and must always have an owning user. */\nexport function assertCredentialScope(tenant: CredentialTenant, credential: AuthCredential): void {\n if (credential.type === 'oauth' && !tenant.userId) {\n throw new Error('OAuth credentials must be user-scoped');\n }\n}\n\nexport const MODEL_CREDENTIALS_SCHEMA: CollectionSchema = {\n name: 'model_provider_credentials',\n columns: {\n id: { type: 'uuid-pk' },\n org_id: { type: 'text' },\n user_id: { type: 'text', nullable: true },\n provider: { type: 'text' },\n type: { type: 'text' },\n data: { type: 'json' },\n created_at: { type: 'timestamp' },\n updated_at: { type: 'timestamp' },\n },\n uniqueIndexes: [\n // Scope is encoded in `user_id` nullability (NULL = org-scoped shared\n // row), enforced by two partial unique indexes.\n {\n name: 'model_provider_credentials_user_unique',\n columns: ['org_id', 'user_id', 'provider'],\n whereNotNull: 'user_id',\n },\n { name: 'model_provider_credentials_org_unique', columns: ['org_id', 'provider'], whereNull: 'user_id' },\n ],\n};\n\nexport const OAUTH_LOGIN_SESSIONS_SCHEMA: CollectionSchema = {\n name: 'oauth_login_sessions',\n columns: {\n session_id: { type: 'text', primaryKey: true },\n org_id: { type: 'text' },\n user_id: { type: 'text' },\n provider: { type: 'text' },\n kind: { type: 'text' },\n pending: { type: 'json' },\n expires_at: { type: 'timestamp' },\n next_poll_at: { type: 'timestamp', nullable: true },\n created_at: { type: 'timestamp' },\n },\n};\n\n/** Column shape of one `model_provider_credentials` row as returned by ops. */\ninterface CredentialDbRow extends Record<string, unknown> {\n id: string;\n provider: string;\n user_id: string | null;\n data: AuthCredential;\n updated_at: Date;\n}\n\n/** Column shape of one `oauth_login_sessions` row as returned by ops. */\ninterface LoginSessionDbRow extends Record<string, unknown> {\n session_id: string;\n org_id: string;\n user_id: string;\n provider: string;\n kind: LoginSessionKind;\n pending: Record<string, unknown>;\n expires_at: Date;\n next_poll_at: Date | null;\n created_at: Date;\n}\n\nfunction toSessionRow(db: LoginSessionDbRow): LoginSessionRow {\n return {\n sessionId: db.session_id,\n orgId: db.org_id,\n userId: db.user_id,\n provider: db.provider,\n kind: db.kind,\n pending: db.pending,\n expiresAt: db.expires_at,\n nextPollAt: db.next_poll_at,\n createdAt: db.created_at,\n };\n}\n\n/** Filter selecting exactly the tenant's row for a provider (`null` = org row). */\nfunction tenantWhere(tenant: CredentialTenant, provider: string): CollectionWhere {\n return { org_id: tenant.orgId, provider, user_id: tenant.userId ?? null };\n}\n\n/**\n * Model-credentials storage, written once against the generic\n * `FactoryStorageOps` surface. `refreshOAuth()` and `claimLoginSession()`\n * ride `updateAtomic` so concurrent replicas serialize instead of\n * invalidating each other's rotating tokens / double-claiming a flow.\n */\nexport class ModelCredentialsStorage extends FactoryStorageDomain {\n constructor() {\n super('model-credentials');\n }\n\n async init(): Promise<void> {\n await this.ensureCollections([MODEL_CREDENTIALS_SCHEMA, OAUTH_LOGIN_SESSIONS_SCHEMA]);\n }\n\n async dangerouslyClearAll(): Promise<void> {\n await this.ops.deleteMany('oauth_login_sessions', {});\n await this.ops.deleteMany('model_provider_credentials', {});\n }\n\n get #db(): FactoryStorageOps {\n return this.ops;\n }\n\n /** Read the tenant's credential for a provider at exactly that scope. */\n async getCredential(tenant: CredentialTenant, provider: string): Promise<AuthCredential | undefined> {\n const row = await this.#db.findOne<CredentialDbRow>('model_provider_credentials', tenantWhere(tenant, provider));\n return row?.data;\n }\n\n /** Upsert the tenant's credential (`created_at` is preserved on update). */\n async setCredential(tenant: CredentialTenant, provider: string, credential: AuthCredential): Promise<void> {\n assertCredentialScope(tenant, credential);\n const where = tenantWhere(tenant, provider);\n const update = async () =>\n this.#db.updateMany('model_provider_credentials', where, {\n type: credential.type,\n data: credential,\n updated_at: new Date(),\n });\n\n let lastError: unknown;\n for (let attempt = 0; attempt < 2; attempt++) {\n if ((await update()) > 0) return;\n\n const now = new Date();\n try {\n await this.#db.insertOne<CredentialDbRow>('model_provider_credentials', {\n org_id: tenant.orgId,\n user_id: tenant.userId ?? null,\n provider,\n type: credential.type,\n data: credential,\n created_at: now,\n updated_at: now,\n });\n return;\n } catch (error) {\n if (!(error instanceof UniqueViolationError)) throw error;\n lastError = error;\n }\n }\n throw lastError;\n }\n\n /** Delete the tenant's credential at exactly that scope. True when a row was removed. */\n async removeCredential(tenant: CredentialTenant, provider: string): Promise<boolean> {\n const deleted = await this.#db.deleteMany('model_provider_credentials', tenantWhere(tenant, provider));\n return deleted > 0;\n }\n\n /** All credentials visible to a user: their own rows plus the org's shared rows. */\n async listCredentials(orgId: string, userId: string): Promise<CredentialRecord[]> {\n // SQL `IN` never matches NULL, so the org rows need their own query.\n const [userRows, orgRows] = await Promise.all([\n this.#db.findMany<CredentialDbRow>('model_provider_credentials', { org_id: orgId, user_id: userId }),\n this.#db.findMany<CredentialDbRow>('model_provider_credentials', { org_id: orgId, user_id: null }),\n ]);\n return [\n ...userRows.map(row => ({\n provider: row.provider,\n scope: 'user' as const,\n credential: row.data,\n updatedAt: row.updated_at,\n })),\n ...orgRows.map(row => ({\n provider: row.provider,\n scope: 'org' as const,\n credential: row.data,\n updatedAt: row.updated_at,\n })),\n ];\n }\n\n /**\n * Resolve the credential a caller should use for a provider: the user's own\n * row wins over the org's shared row.\n */\n async resolveCredential(orgId: string, userId: string, provider: string): Promise<ResolvedCredential | undefined> {\n const userRow = await this.#db.findOne<CredentialDbRow>('model_provider_credentials', {\n org_id: orgId,\n provider,\n user_id: userId,\n });\n if (userRow) return { provider: userRow.provider, scope: 'user', credential: userRow.data };\n const orgRow = await this.#db.findOne<CredentialDbRow>('model_provider_credentials', {\n org_id: orgId,\n provider,\n user_id: null,\n });\n if (orgRow) return { provider: orgRow.provider, scope: 'org', credential: orgRow.data };\n return undefined;\n }\n\n /**\n * Refresh the tenant's OAuth credential under the backend's atomic\n * read-modify-write so concurrent replicas don't invalidate each other's\n * rotating refresh tokens. The expiry is re-checked under the lock —\n * another replica may have refreshed already, in which case `refreshFn` is\n * skipped and the fresh credential is returned. Returns `undefined` when no\n * OAuth row exists for the tenant.\n */\n async refreshOAuth(\n tenant: CredentialTenant,\n provider: string,\n refreshFn: (current: OAuthCredential) => Promise<OAuthCredential>,\n ): Promise<OAuthCredential | undefined> {\n let result: OAuthCredential | undefined;\n await this.#db.updateAtomic<CredentialDbRow>(\n 'model_provider_credentials',\n tenantWhere(tenant, provider),\n async row => {\n if (row.data.type !== 'oauth') return null;\n const current = row.data;\n // Re-check under the lock: another replica may have refreshed while we waited.\n if (!isOAuthCredentialExpired(current)) {\n result = current;\n return null;\n }\n const next = await refreshFn(current);\n result = next;\n return { data: next, updated_at: new Date() };\n },\n );\n return result;\n }\n\n /** Persist a pending login flow started by a web route. */\n async createLoginSession(input: CreateLoginSessionInput): Promise<LoginSessionRow> {\n const inserted = await this.#db.insertOne<LoginSessionDbRow>('oauth_login_sessions', {\n session_id: input.sessionId,\n org_id: input.orgId,\n user_id: input.userId,\n provider: input.provider,\n kind: input.kind,\n pending: input.pending,\n expires_at: input.expiresAt,\n next_poll_at: input.nextPollAt ?? null,\n created_at: new Date(),\n });\n return toSessionRow(inserted);\n }\n\n /**\n * Read a pending login flow. An expired session is deleted on read (TTL\n * cleanup) and reported as absent.\n */\n async getLoginSession(sessionId: string): Promise<LoginSessionRow | undefined> {\n const row = await this.#db.findOne<LoginSessionDbRow>('oauth_login_sessions', { session_id: sessionId });\n if (!row) return undefined;\n if (row.expires_at.getTime() <= Date.now()) {\n await this.#db.deleteMany('oauth_login_sessions', { session_id: sessionId });\n return undefined;\n }\n return toSessionRow(row);\n }\n\n /**\n * Atomically claim a due login session for one upstream completion/poll attempt.\n * Returns undefined when the session is missing, expired, owned by another\n * tenant/provider, or already claimed by a concurrent request.\n */\n async claimLoginSession(\n sessionId: string,\n owner: Pick<LoginSessionRow, 'orgId' | 'userId' | 'provider' | 'kind'>,\n ): Promise<LoginSessionRow | undefined> {\n const now = Date.now();\n let claimed = false;\n const updated = await this.#db.updateAtomic<LoginSessionDbRow>(\n 'oauth_login_sessions',\n { session_id: sessionId },\n row => {\n if (\n row.org_id !== owner.orgId ||\n row.user_id !== owner.userId ||\n row.provider !== owner.provider ||\n row.kind !== owner.kind\n ) {\n return null;\n }\n if (row.expires_at.getTime() <= now) return null;\n if (row.next_poll_at && row.next_poll_at.getTime() > now) return null;\n claimed = true;\n // Park the next poll at expiry so a concurrent claim loses until the\n // claimer either touches the session forward or deletes it.\n return { next_poll_at: row.expires_at };\n },\n );\n return claimed && updated ? toSessionRow(updated) : undefined;\n }\n\n /** Update a pending flow's serialized state and/or next allowed poll time. */\n async touchLoginSession(\n sessionId: string,\n updates: { pending?: Record<string, unknown>; nextPollAt?: Date | null },\n ): Promise<void> {\n const set: Record<string, unknown> = {};\n if (updates.pending !== undefined) set.pending = updates.pending;\n if (updates.nextPollAt !== undefined) set.next_poll_at = updates.nextPollAt;\n if (Object.keys(set).length === 0) return;\n await this.#db.updateMany('oauth_login_sessions', { session_id: sessionId }, set);\n }\n\n /** Remove a pending login flow (completed, cancelled, or failed). */\n async deleteLoginSession(sessionId: string): Promise<void> {\n await this.#db.deleteMany('oauth_login_sessions', { session_id: sessionId });\n }\n}\n","/**\n * Per-request model-provider credential context.\n *\n * The provider config/OAuth routes serve two deployment shapes:\n *\n * - **Local** (no web auth adapter): credentials live in the server-global\n * file-backed `AuthStorage` (`auth.json`) — unchanged TUI behavior.\n * - **Tenant** (auth adapter active): credentials live in the\n * `model-credentials` factory storage domain, scoped per `(orgId, userId)`\n * with optional org-wide rows (`userId` absent).\n *\n * `resolveCredentialContext` decides which shape a request is in: an\n * authenticated user always gets the tenant path; an unauthenticated request\n * is rejected when auth is enabled and falls back to local otherwise.\n */\n\nimport type { Context } from 'hono';\n\nimport type {\n CredentialRecord,\n LoginSessionKind,\n ModelCredentialsStorage,\n} from '../storage/domains/credentials/base.js';\nimport type { RouteAuth } from './route.js';\n\n/**\n * OAuth credentials are stored under the auth provider id, which differs from\n * the catalog provider id for OpenAI (stored as `openai-codex`). Tenant rows\n * use the same keying so one lookup serves both OAuth tokens and API keys.\n */\nexport function getAuthProviderId(provider: string): string {\n return provider === 'openai' ? 'openai-codex' : provider;\n}\n\n/**\n * Providers that support a browser-driven OAuth sign-in flow, keyed by\n * *catalog* provider id (what the routes and settings UI use). All flows need\n * no inbound connection to the server: Anthropic uses paste-code PKCE, the\n * rest use RFC 8628-style device codes.\n */\nexport const WEB_OAUTH_FLOW_KINDS: Readonly<Record<string, LoginSessionKind>> = {\n anthropic: 'paste-code',\n openai: 'device-code',\n 'github-copilot': 'device-code',\n xai: 'device-code',\n};\n\nexport type CredentialContext =\n { mode: 'local' } | { mode: 'tenant'; storage: ModelCredentialsStorage; orgId: string; userId: string };\n\n/**\n * The tenant credentials domain, when registered and ready. `undefined` means\n * the factory never ran (no handle threaded in) or the domain's init failed\n * (fail-soft — callers report the feature unavailable instead of crashing).\n */\nexport async function getTenantCredentialsStorage(\n credentials: ModelCredentialsStorage | undefined,\n): Promise<ModelCredentialsStorage | undefined> {\n if (!credentials) return undefined;\n try {\n await credentials.ensureReady();\n } catch {\n return undefined;\n }\n return credentials;\n}\n\n/**\n * Org key for tenant rows. Personal accounts get an org bootstrapped by the\n * auth adapter (`ensureOrg`); if one still isn't present, scope rows under a\n * per-user synthetic org so credentials never become server-global.\n */\nexport function tenantOrgId(tenant: { orgId?: string; userId: string }): string {\n return tenant.orgId ?? `user:${tenant.userId}`;\n}\n\n/**\n * Resolve the credential context for a request, or a ready-to-return error\n * response. Mutating credential routes call this and hard-fail (401/503)\n * when the tenant path is required but unavailable.\n */\nexport async function resolveCredentialContext({\n c,\n auth,\n credentials,\n}: {\n c: Context;\n auth: RouteAuth;\n /** Tenant credential domain handle; absent in local (no-DB) mode. */\n credentials?: ModelCredentialsStorage;\n}): Promise<CredentialContext | { response: Response }> {\n await auth.ensureUser(c);\n const tenant = auth.tenant(c);\n if (!tenant) {\n // When an auth provider is active, credential operations always require a\n // signed-in caller — otherwise one anonymous request could write keys for\n // everyone. Without a provider this is a single-user local server.\n if (auth.enabled()) return { response: c.json({ error: 'unauthorized' }, 401) };\n return { mode: 'local' };\n }\n const storage = await getTenantCredentialsStorage(credentials);\n if (!storage) {\n return {\n response: c.json(\n {\n error: 'credentials_unavailable',\n message: 'Tenant credential storage is unavailable — the app database is not configured or failed to start.',\n },\n 503,\n ),\n };\n }\n return { mode: 'tenant', storage, orgId: tenantOrgId(tenant), userId: tenant.userId };\n}\n\n/**\n * List the caller's tenant credential records for provider listing, or\n * `undefined` in local mode (caller should fall back to `AuthStorage`).\n * Read-only and degradation-friendly: an authenticated caller with the domain\n * unavailable gets an empty list (sources show `env`/`none`) instead of a 503\n * so the settings page still renders.\n */\nexport async function listTenantCredentialsForRequest({\n c,\n auth,\n credentials,\n}: {\n c: Context;\n auth: RouteAuth;\n /** Tenant credential domain handle; absent in local (no-DB) mode. */\n credentials?: ModelCredentialsStorage;\n}): Promise<CredentialRecord[] | undefined> {\n await auth.ensureUser(c);\n const tenant = auth.tenant(c);\n if (!tenant) return auth.enabled() ? [] : undefined;\n const storage = await getTenantCredentialsStorage(credentials);\n if (!storage) return [];\n return storage.listCredentials(tenantOrgId(tenant), tenant.userId);\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mastra `apiRoutes` for Factory work items (the kanban board).
|
|
3
|
+
*
|
|
4
|
+
* Registered alongside the other `/web/*` routes behind the host auth gate.
|
|
5
|
+
* The board is org-wide: every route re-resolves the caller's `(orgId, userId)`
|
|
6
|
+
* tenant and scopes reads/writes by `orgId`, so any org member sees and moves
|
|
7
|
+
* the same cards while `created_by` / stage history record who acted.
|
|
8
|
+
*/
|
|
9
|
+
import type { ApiRoute } from '@mastra/core/server';
|
|
10
|
+
import type { FactoryStartCoordinator } from '../rules/start-coordinator.js';
|
|
11
|
+
import type { FactoryTransitionService } from '../rules/transition-service.js';
|
|
12
|
+
import type { AuditEmitter } from '../storage/domains/audit/domain.js';
|
|
13
|
+
import type { FactoryProjectsStorage } from '../storage/domains/projects/base.js';
|
|
14
|
+
import type { QueueHealthStorage } from '../storage/domains/queue-health/base.js';
|
|
15
|
+
import type { CreateWorkItemInput, UpdateWorkItemInput, WorkItemsStorage } from '../storage/domains/work-items/base.js';
|
|
16
|
+
import type { RouteDependencies } from './route.js';
|
|
17
|
+
import { Route } from './route.js';
|
|
18
|
+
export interface WorkItemRoutesDeps extends RouteDependencies {
|
|
19
|
+
audit: AuditEmitter;
|
|
20
|
+
/** Factory projects domain — validates the `:id` project belongs to the caller's org. */
|
|
21
|
+
projects: FactoryProjectsStorage;
|
|
22
|
+
/** Work-items domain backing the kanban board. */
|
|
23
|
+
workItems: WorkItemsStorage;
|
|
24
|
+
/** Per-project queue-health threshold config. */
|
|
25
|
+
queueHealth: QueueHealthStorage;
|
|
26
|
+
/** Governed stage-transition service. Stage moves 503 when absent. */
|
|
27
|
+
transitionService?: Pick<FactoryTransitionService, 'transition' | 'ruleSetVersion'>;
|
|
28
|
+
/** Coordinator that binds a Factory run before dispatching its kickoff. */
|
|
29
|
+
startCoordinator?: Pick<FactoryStartCoordinator, 'prepare'>;
|
|
30
|
+
}
|
|
31
|
+
/** Validate an untrusted create body. Unknown keys are dropped. */
|
|
32
|
+
export declare function parseCreateWorkItem(body: unknown): CreateWorkItemInput | null;
|
|
33
|
+
/** Validate an untrusted patch body. Unknown keys are dropped. */
|
|
34
|
+
export declare function parseUpdateWorkItem(body: unknown): UpdateWorkItemInput | null;
|
|
35
|
+
export declare class WorkItemRoutes extends Route<WorkItemRoutesDeps> {
|
|
36
|
+
#private;
|
|
37
|
+
/** Build the Factory work-item routes as Mastra `apiRoutes`. */
|
|
38
|
+
routes(): ApiRoute[];
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=work-items.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"work-items.d.ts","sourceRoot":"","sources":["../../src/routes/work-items.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAIpD,OAAO,KAAK,EACV,uBAAuB,EAGxB,MAAM,+BAA+B,CAAC;AAEvC,OAAO,KAAK,EAA4B,wBAAwB,EAAE,MAAM,gCAAgC,CAAC;AAGzG,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAC;AACvE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,qCAAqC,CAAC;AAClF,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yCAAyC,CAAC;AAElF,OAAO,KAAK,EACV,mBAAmB,EAInB,mBAAmB,EAKnB,gBAAgB,EACjB,MAAM,uCAAuC,CAAC;AAG/C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AACpD,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAEnC,MAAM,WAAW,kBAAmB,SAAQ,iBAAiB;IAC3D,KAAK,EAAE,YAAY,CAAC;IACpB,yFAAyF;IACzF,QAAQ,EAAE,sBAAsB,CAAC;IACjC,kDAAkD;IAClD,SAAS,EAAE,gBAAgB,CAAC;IAC5B,iDAAiD;IACjD,WAAW,EAAE,kBAAkB,CAAC;IAChC,sEAAsE;IACtE,iBAAiB,CAAC,EAAE,IAAI,CAAC,wBAAwB,EAAE,YAAY,GAAG,gBAAgB,CAAC,CAAC;IACpF,2EAA2E;IAC3E,gBAAgB,CAAC,EAAE,IAAI,CAAC,uBAAuB,EAAE,SAAS,CAAC,CAAC;CAC7D;AAqED,mEAAmE;AACnE,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,OAAO,GAAG,mBAAmB,GAAG,IAAI,CAuB7E;AAED,kEAAkE;AAClE,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,OAAO,GAAG,mBAAmB,GAAG,IAAI,CA4B7E;AAmLD,qBAAa,cAAe,SAAQ,KAAK,CAAC,kBAAkB,CAAC;;IAwG3D,gEAAgE;IAChE,MAAM,IAAI,QAAQ,EAAE;CAsWrB"}
|