@cmmd-center/forge 0.13.131 → 0.13.133
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/bin.cjs +453 -734
- package/dist/bin.mjs +450 -734
- package/dist/client/assets/{CanvasPanel-BBU-hosR.js → CanvasPanel-BZ55yFCm.js} +2 -2
- package/dist/client/assets/{DiffPanel-CsjpjcNO.js → DiffPanel-RwcR62h1.js} +2 -2
- package/dist/client/assets/{DiffPanel.logic-D0tJmmDH.js → DiffPanel.logic-p0v4whSE.js} +2 -2
- package/dist/client/assets/{DiffWorkerPoolProvider-BaYfOG54.js → DiffWorkerPoolProvider-DuQCCe4H.js} +2 -2
- package/dist/client/assets/{PullRequestCodePanel-FsC6jwvA.js → PullRequestCodePanel-dQ0P1BYv.js} +2 -2
- package/dist/client/assets/{ThemeEditorPanel-DHyAZtjN.js → ThemeEditorPanel-Bgrpa0cR.js} +2 -2
- package/dist/client/assets/{Virtualizer-Bt_R5UQD.js → Virtualizer-B7qA3afH.js} +2 -2
- package/dist/client/assets/{WorkerPoolContext-DSayjMnB.js → WorkerPoolContext-DJIHDFVZ.js} +44 -44
- package/dist/client/assets/cmmdAuthRedirect-B_Ad6N4c.js +3 -0
- package/dist/client/assets/{cmmdForgeRuntimeListResource-DFLs7Ho_.js → cmmdForgeRuntimeListResource-uAVdZfT3.js} +4 -4
- package/dist/client/assets/{currentForgeWorkspaceStore-B5FQv1gc.js → currentForgeWorkspaceStore-DgPGQMqz.js} +2 -2
- package/dist/client/assets/{dialog-mwxYKYq-.js → dialog-DMVibvkf.js} +2 -2
- package/dist/client/assets/index-BSqrLyPh.js +301 -0
- package/dist/client/assets/{popover-BMHhFoom.js → popover-DZpHoNco.js} +2 -2
- package/dist/client/assets/{scroll-area-9mmf9iy-.js → scroll-area-dh4WduWB.js} +2 -2
- package/dist/client/assets/{sidebarV2CustomSnoozeDialog-vLvqIwRU.js → sidebarV2CustomSnoozeDialog-DsUlSvAq.js} +2 -2
- package/dist/client/assets/src-DpAPOFgC.js +3 -0
- package/dist/client/assets/{switch-DpGoyzXB.js → switch-Bt0PG_99.js} +2 -2
- package/dist/client/assets/{themePalette-BII_gGLK.js → themePalette-rKk-Jh5K.js} +2 -2
- package/dist/client/assets/{toggle-CiTk7ztA.js → toggle-CM3FO84q.js} +2 -2
- package/dist/client/assets/{toggle-group-B0GQb1vL.js → toggle-group-sUSKwOOz.js} +2 -2
- package/dist/client/assets/{tooltip-DlvXFVEM.js → tooltip-DHkQTarv.js} +2 -2
- package/dist/client/assets/{utils-BWD4ATf-.js → utils-B9FLzOgF.js} +2 -2
- package/dist/client/index.html +18 -18
- package/dist/forge-build.json +1 -1
- package/package.json +1 -1
- package/dist/client/assets/cmmdAuthRedirect-B2FNK6pk.js +0 -3
- package/dist/client/assets/index-BFu1LcEj.js +0 -301
- package/dist/client/assets/src-DICOHE3E.js +0 -3
package/dist/bin.cjs
CHANGED
|
@@ -20670,6 +20670,43 @@ var FilesystemBrowseError = class extends require_Schema$1.TaggedErrorClass()("F
|
|
|
20670
20670
|
}
|
|
20671
20671
|
};
|
|
20672
20672
|
//#endregion
|
|
20673
|
+
//#region ../../packages/contracts/src/workspaceDirectory.ts
|
|
20674
|
+
const WORKSPACE_DIRECTORY_PATH_MAX_LENGTH = 512;
|
|
20675
|
+
const WorkspaceDirectoryEntryKind = require_Schema$1.Literals(["file", "directory"]);
|
|
20676
|
+
/**
|
|
20677
|
+
* One immediate child of a listed workspace directory.
|
|
20678
|
+
*
|
|
20679
|
+
* `hasChildren` is what lets the Files tree draw an expander only on
|
|
20680
|
+
* directories that open onto something, without listing the whole workspace
|
|
20681
|
+
* up front: a directory entry is one stat-sized readdir, not a recursive walk.
|
|
20682
|
+
*/
|
|
20683
|
+
const WorkspaceDirectoryEntry = require_Schema$1.Struct({
|
|
20684
|
+
name: TrimmedNonEmptyString,
|
|
20685
|
+
kind: WorkspaceDirectoryEntryKind,
|
|
20686
|
+
hasChildren: require_Schema$1.Boolean
|
|
20687
|
+
});
|
|
20688
|
+
const WorkspaceListDirectoryInput = require_Schema$1.Struct({
|
|
20689
|
+
cwd: TrimmedNonEmptyString,
|
|
20690
|
+
relativePath: TrimmedString.check(require_Schema$1.isMaxLength(WORKSPACE_DIRECTORY_PATH_MAX_LENGTH))
|
|
20691
|
+
});
|
|
20692
|
+
const WorkspaceListDirectoryResult = require_Schema$1.Struct({ entries: require_Schema$1.ArraySchema(WorkspaceDirectoryEntry) });
|
|
20693
|
+
const WorkspaceListDirectoryFailure = require_Schema$1.Literals([
|
|
20694
|
+
"workspace_root_invalid",
|
|
20695
|
+
"path_outside_workspace",
|
|
20696
|
+
"read_directory_failed"
|
|
20697
|
+
]);
|
|
20698
|
+
var WorkspaceListDirectoryError = class extends require_Schema$1.TaggedErrorClass()("WorkspaceListDirectoryError", {
|
|
20699
|
+
cwd: require_Schema$1.String,
|
|
20700
|
+
relativePath: require_Schema$1.String,
|
|
20701
|
+
failure: WorkspaceListDirectoryFailure,
|
|
20702
|
+
detail: require_Schema$1.String,
|
|
20703
|
+
cause: require_Schema$1.optional(require_Schema$1.Defect)
|
|
20704
|
+
}) {
|
|
20705
|
+
get message() {
|
|
20706
|
+
return this.detail;
|
|
20707
|
+
}
|
|
20708
|
+
};
|
|
20709
|
+
//#endregion
|
|
20673
20710
|
//#region ../../packages/contracts/src/terminal.ts
|
|
20674
20711
|
const DEFAULT_TERMINAL_ID = "default";
|
|
20675
20712
|
const TrimmedNonEmptyStringSchema$3 = TrimmedNonEmptyString;
|
|
@@ -26292,6 +26329,7 @@ const WS_METHODS = {
|
|
|
26292
26329
|
projectsWriteFile: "projects.writeFile",
|
|
26293
26330
|
projectCheckoutDeletionPreview: "projectCheckoutDeletion.preview",
|
|
26294
26331
|
projectCheckoutDeletionExecute: "projectCheckoutDeletion.execute",
|
|
26332
|
+
workspaceListDirectory: "workspace.listDirectory",
|
|
26295
26333
|
workspaceReadFile: "workspace.readFile",
|
|
26296
26334
|
workspaceBeginWriteFile: "workspace.beginWriteFile",
|
|
26297
26335
|
workspaceWriteFileChunk: "workspace.writeFileChunk",
|
|
@@ -26603,6 +26641,17 @@ const WsWorkspaceCancelWriteFileRpc = make$29(WS_METHODS.workspaceCancelWriteFil
|
|
|
26603
26641
|
success: BulkTransferCancelResult,
|
|
26604
26642
|
error: BulkTransferError
|
|
26605
26643
|
});
|
|
26644
|
+
/**
|
|
26645
|
+
* One workspace directory's immediate children, for the right panel's Files
|
|
26646
|
+
* tree. Read-scoped and confined to the workspace root on the server; the tree
|
|
26647
|
+
* expands one directory at a time through this method rather than listing the
|
|
26648
|
+
* whole workspace up front.
|
|
26649
|
+
*/
|
|
26650
|
+
const WsWorkspaceListDirectoryRpc = make$29(WS_METHODS.workspaceListDirectory, {
|
|
26651
|
+
payload: WorkspaceListDirectoryInput,
|
|
26652
|
+
success: WorkspaceListDirectoryResult,
|
|
26653
|
+
error: WorkspaceListDirectoryError
|
|
26654
|
+
});
|
|
26606
26655
|
const WsShellOpenInEditorRpc = make$29(WS_METHODS.shellOpenInEditor, {
|
|
26607
26656
|
payload: OpenInEditorInput,
|
|
26608
26657
|
error: OpenError$1
|
|
@@ -27026,7 +27075,7 @@ const WsRpcGroup = make$28(WsServerGetConfigRpc, WsServerRefreshProvidersRpc, Ws
|
|
|
27026
27075
|
payload: UsageSummaryInput,
|
|
27027
27076
|
success: UsageSummary,
|
|
27028
27077
|
error: UsageReadError
|
|
27029
|
-
}), WsPullRequestsListRpc, WsPullRequestsListStatsRpc, WsPullRequestsDetailRpc, WsPullRequestsActivityRpc, WsPullRequestsThreadCommentsRpc, WsPullRequestsDiffFileContentsRpc, WsPullRequestsRunActionRpc, WsPullRequestsUpdateRpc, WsPullRequestsCommentRpc, WsPullRequestsUpdateCommentRpc, WsPullRequestsSubmitReviewRpc, WsPullRequestsReplyToThreadRpc, WsPullRequestsSetThreadResolutionRpc, WsPullRequestsSetReactionRpc, WsPullRequestsInvalidateRpc, WsPullRequestsReviewerCandidatesRpc, WsPullRequestsRequestReviewersRpc, WsProjectsSearchContentsRpc, WsProjectsSearchEntriesRpc, WsProjectsWriteFileRpc, WsProjectCheckoutDeletionPreviewRpc, WsProjectCheckoutDeletionExecuteRpc, WsWorkspaceReadFileRpc, WsWorkspaceBeginWriteFileRpc, WsWorkspaceWriteFileChunkRpc, WsWorkspaceCommitWriteFileRpc, WsWorkspaceCancelWriteFileRpc, WsShellOpenInEditorRpc, WsSubscribeGitStatusRpc, WsGitPullRpc, WsGitRefreshStatusRpc, WsGitRunStackedActionRpc, WsGitResolvePullRequestRpc, WsGitPreparePullRequestThreadRpc, WsGitListBranchesRpc, WsVcsListRefsRpc, WsVcsRefreshStatusRpc, WsSubscribeVcsStatusRpc, WsVcsPullRpc, WsVcsCreateWorktreeRpc, WsVcsRemoveWorktreeRpc, WsVcsCreateRefRpc, WsVcsSwitchRefRpc, WsVcsInitRpc, WsGitCreateWorktreeRpc, WsGitRemoveWorktreeRpc, WsGitCreateBranchRpc, WsGitCheckoutRpc, WsGitInitRpc, WsFilesystemBrowseRpc, WsProjectsReadFileRpc, WsReviewGetDiffPreviewRpc, WsCloudListRuntimesRpc, WsTerminalOpenRpc, WsTerminalAttachRpc, WsTerminalWriteRpc, WsTerminalResizeRpc, WsTerminalSignalRpc, WsTerminalClearRpc, WsTerminalRestartRpc, WsTerminalCloseRpc, WsBrowserOpenSessionRpc, WsBrowserListSessionsRpc, WsBrowserCloseSessionRpc, WsBrowserGetSessionRpc, WsBrowserCommandRpc, WsBrowserClearBrowsingDataRpc, WsBrowserAttachDisplayRpc, WsBrowserAddDisplayIceCandidateRpc, WsComputerInputRpc, WsDevTunnelRegisterRpc, WsDevTunnelUnregisterRpc, WsDevTunnelListRpc, WsThreadDevRuntimeGetRpc, WsThreadDevRuntimeAllocatePortRpc, WsThreadDevRuntimeReleaseRpc, WsWorkspaceEnvironmentPreflightRpc, WsWorkspaceEnsureScratchRpc, WsSubscribeTerminalEventsRpc, WsSubscribeTerminalMetadataRpc, WsProjectsListEntriesRpc, WsServerDiscoverSourceControlRpc, WsServerReportClientActivityRpc, WsSubscribeBrowserEventsRpc, WsSubscribeServerConfigRpc, WsSubscribeServerLifecycleRpc, WsSubscribeAuthAccessRpc, WsSubscribeWorkspaceReadinessRpc, WsOrchestrationDispatchCommandRpc, WsOrchestrationGetTurnDiffRpc, WsOrchestrationGetFullThreadDiffRpc, WsOrchestrationStreamTurnDiffRpc, WsOrchestrationStreamFullThreadDiffRpc, WsOrchestrationReplayEventsRpc, WsOrchestrationGetArchivedShellSnapshotRpc, WsOrchestrationSubscribeShellRpc, WsOrchestrationSubscribeThreadRpc, make$29(WS_METHODS.assetsCreateUrl, {
|
|
27078
|
+
}), WsPullRequestsListRpc, WsPullRequestsListStatsRpc, WsPullRequestsDetailRpc, WsPullRequestsActivityRpc, WsPullRequestsThreadCommentsRpc, WsPullRequestsDiffFileContentsRpc, WsPullRequestsRunActionRpc, WsPullRequestsUpdateRpc, WsPullRequestsCommentRpc, WsPullRequestsUpdateCommentRpc, WsPullRequestsSubmitReviewRpc, WsPullRequestsReplyToThreadRpc, WsPullRequestsSetThreadResolutionRpc, WsPullRequestsSetReactionRpc, WsPullRequestsInvalidateRpc, WsPullRequestsReviewerCandidatesRpc, WsPullRequestsRequestReviewersRpc, WsProjectsSearchContentsRpc, WsProjectsSearchEntriesRpc, WsProjectsWriteFileRpc, WsProjectCheckoutDeletionPreviewRpc, WsProjectCheckoutDeletionExecuteRpc, WsWorkspaceReadFileRpc, WsWorkspaceListDirectoryRpc, WsWorkspaceBeginWriteFileRpc, WsWorkspaceWriteFileChunkRpc, WsWorkspaceCommitWriteFileRpc, WsWorkspaceCancelWriteFileRpc, WsShellOpenInEditorRpc, WsSubscribeGitStatusRpc, WsGitPullRpc, WsGitRefreshStatusRpc, WsGitRunStackedActionRpc, WsGitResolvePullRequestRpc, WsGitPreparePullRequestThreadRpc, WsGitListBranchesRpc, WsVcsListRefsRpc, WsVcsRefreshStatusRpc, WsSubscribeVcsStatusRpc, WsVcsPullRpc, WsVcsCreateWorktreeRpc, WsVcsRemoveWorktreeRpc, WsVcsCreateRefRpc, WsVcsSwitchRefRpc, WsVcsInitRpc, WsGitCreateWorktreeRpc, WsGitRemoveWorktreeRpc, WsGitCreateBranchRpc, WsGitCheckoutRpc, WsGitInitRpc, WsFilesystemBrowseRpc, WsProjectsReadFileRpc, WsReviewGetDiffPreviewRpc, WsCloudListRuntimesRpc, WsTerminalOpenRpc, WsTerminalAttachRpc, WsTerminalWriteRpc, WsTerminalResizeRpc, WsTerminalSignalRpc, WsTerminalClearRpc, WsTerminalRestartRpc, WsTerminalCloseRpc, WsBrowserOpenSessionRpc, WsBrowserListSessionsRpc, WsBrowserCloseSessionRpc, WsBrowserGetSessionRpc, WsBrowserCommandRpc, WsBrowserClearBrowsingDataRpc, WsBrowserAttachDisplayRpc, WsBrowserAddDisplayIceCandidateRpc, WsComputerInputRpc, WsDevTunnelRegisterRpc, WsDevTunnelUnregisterRpc, WsDevTunnelListRpc, WsThreadDevRuntimeGetRpc, WsThreadDevRuntimeAllocatePortRpc, WsThreadDevRuntimeReleaseRpc, WsWorkspaceEnvironmentPreflightRpc, WsWorkspaceEnsureScratchRpc, WsSubscribeTerminalEventsRpc, WsSubscribeTerminalMetadataRpc, WsProjectsListEntriesRpc, WsServerDiscoverSourceControlRpc, WsServerReportClientActivityRpc, WsSubscribeBrowserEventsRpc, WsSubscribeServerConfigRpc, WsSubscribeServerLifecycleRpc, WsSubscribeAuthAccessRpc, WsSubscribeWorkspaceReadinessRpc, WsOrchestrationDispatchCommandRpc, WsOrchestrationGetTurnDiffRpc, WsOrchestrationGetFullThreadDiffRpc, WsOrchestrationStreamTurnDiffRpc, WsOrchestrationStreamFullThreadDiffRpc, WsOrchestrationReplayEventsRpc, WsOrchestrationGetArchivedShellSnapshotRpc, WsOrchestrationSubscribeShellRpc, WsOrchestrationSubscribeThreadRpc, make$29(WS_METHODS.assetsCreateUrl, {
|
|
27030
27079
|
payload: AssetsRpcSchemas.createUrl.input,
|
|
27031
27080
|
success: AssetsRpcSchemas.createUrl.output,
|
|
27032
27081
|
error: AssetAccessError
|
|
@@ -31776,6 +31825,11 @@ const RELAY_METHOD_POLICY_BASES = [
|
|
|
31776
31825
|
requiredScope: "workspace.read",
|
|
31777
31826
|
deliveryClass: "read"
|
|
31778
31827
|
},
|
|
31828
|
+
{
|
|
31829
|
+
method: WS_METHODS.workspaceListDirectory,
|
|
31830
|
+
requiredScope: "workspace.read",
|
|
31831
|
+
deliveryClass: "read"
|
|
31832
|
+
},
|
|
31779
31833
|
{
|
|
31780
31834
|
method: WS_METHODS.workspaceReadFile,
|
|
31781
31835
|
requiredScope: "workspace.read",
|
|
@@ -66353,7 +66407,7 @@ function normalizeNumberish(value) {
|
|
|
66353
66407
|
}
|
|
66354
66408
|
//#endregion
|
|
66355
66409
|
//#region package.json
|
|
66356
|
-
var version$1 = "0.13.
|
|
66410
|
+
var version$1 = "0.13.133";
|
|
66357
66411
|
//#endregion
|
|
66358
66412
|
//#region src/sentry.ts
|
|
66359
66413
|
const SERVER_APP_NAME = "forge-server";
|
|
@@ -67312,9 +67366,7 @@ const runtimeReplicationOutboxOldestEventAge = require_Schema$1.gauge("t3_runtim
|
|
|
67312
67366
|
const runtimeReplicationPublicationAttemptsTotal = require_Schema$1.counter("t3_runtime_replication_publication_attempts_total", { description: "Total canonical runtime replication publication attempts." });
|
|
67313
67367
|
const runtimeReplicationPublicationRetriesTotal = require_Schema$1.counter("t3_runtime_replication_publication_retries_total", { description: "Total runtime replication retries scheduled after recoverable failures." });
|
|
67314
67368
|
const runtimeReplicationPublicationLatency = require_Schema$1.timer("t3_runtime_replication_publication_latency", { description: "Time from durable enqueue to canonical CMMD acknowledgement." });
|
|
67315
|
-
const
|
|
67316
|
-
const cmmdTokenMintTotal = require_Schema$1.counter("t3_cmmd_token_mint_total", { description: "CMMD short-lived forge-token mints through the mint lane (forge#3518), by outcome (attempted | succeeded). The legacy rotation ladder keeps reporting t3_cmmd_credential_refresh_total, so the two lanes stay separable in every dashboard." });
|
|
67317
|
-
const cmmdRotationFenceAppliedTotal = require_Schema$1.counter("t3_cmmd_rotation_fence_applied_total", { description: "Sessions Forge refused to rotate CMMD credentials for, because the rotation fence applied." });
|
|
67369
|
+
const cmmdTokenMintTotal = require_Schema$1.counter("t3_cmmd_token_mint_total", { description: "CMMD short-lived forge-token mints, by outcome (attempted | succeeded | failed). The mint is the only CMMD JWT refresh story since forge#3521 deleted the legacy rotation ladder." });
|
|
67318
67370
|
const cmmdCredentialRowDeletedTotal = require_Schema$1.counter("t3_cmmd_credential_row_deleted_total", { description: "command_access_tokens rows actually deleted by clearUnrecoverableCmmdAuthForSession, by reason." });
|
|
67319
67371
|
const metricAttributes = (attributes) => Object.entries(compactMetricAttributes(attributes));
|
|
67320
67372
|
const increment = (metric, attributes, amount = 1) => require_Schema$1.update(require_Schema$1.withAttributes(metric, metricAttributes(attributes)), amount);
|
|
@@ -67395,12 +67447,10 @@ const isPersistenceError = (u) => require_Schema$1.is(PersistenceSqlError)(u) ||
|
|
|
67395
67447
|
const cache = /* @__PURE__ */ new Map();
|
|
67396
67448
|
const jwtCache = /* @__PURE__ */ new Map();
|
|
67397
67449
|
const jwtExpiryCache = /* @__PURE__ */ new Map();
|
|
67398
|
-
const refreshTokenCache = /* @__PURE__ */ new Map();
|
|
67399
67450
|
const identityCache = /* @__PURE__ */ new Map();
|
|
67400
67451
|
const sessionGrantCache = /* @__PURE__ */ new Map();
|
|
67401
67452
|
const ACCESS_TOKEN_SCOPE = "cmmd-access-token";
|
|
67402
67453
|
const JWT_TOKEN_SCOPE = "cmmd-jwt-token";
|
|
67403
|
-
const REFRESH_TOKEN_SCOPE = "cmmd-refresh-token";
|
|
67404
67454
|
const IDENTITY_SCOPE = "cmmd-user-identity";
|
|
67405
67455
|
const SESSION_GRANT_SCOPE = "cmmd-session-grant";
|
|
67406
67456
|
const CommandAccessTokenRow = require_Schema$1.Struct({
|
|
@@ -67408,27 +67458,15 @@ const CommandAccessTokenRow = require_Schema$1.Struct({
|
|
|
67408
67458
|
accessToken: require_Schema$1.String,
|
|
67409
67459
|
jwtToken: require_Schema$1.NullOr(require_Schema$1.String),
|
|
67410
67460
|
jwtExpiresAt: require_Schema$1.NullOr(require_Schema$1.String),
|
|
67411
|
-
refreshToken: require_Schema$1.NullOr(require_Schema$1.String),
|
|
67412
67461
|
cmmdUserIdentity: require_Schema$1.NullOr(require_Schema$1.String),
|
|
67413
67462
|
sessionGrant: require_Schema$1.NullOr(require_Schema$1.String)
|
|
67414
67463
|
});
|
|
67415
67464
|
const SubjectCommandAccessTokenRow = require_Schema$1.Struct({ accessToken: require_Schema$1.String });
|
|
67416
|
-
const SubjectJwtRow = require_Schema$1.Struct({
|
|
67417
|
-
accessToken: require_Schema$1.String,
|
|
67418
|
-
jwtToken: require_Schema$1.NullOr(require_Schema$1.String),
|
|
67419
|
-
jwtExpiresAt: require_Schema$1.NullOr(require_Schema$1.String),
|
|
67420
|
-
refreshToken: require_Schema$1.NullOr(require_Schema$1.String),
|
|
67421
|
-
sessionId: require_Schema$1.String
|
|
67422
|
-
});
|
|
67423
|
-
const SubjectRefreshTokenRow = require_Schema$1.Struct({
|
|
67424
|
-
refreshToken: require_Schema$1.String,
|
|
67425
|
-
sessionId: require_Schema$1.String
|
|
67426
|
-
});
|
|
67427
67465
|
const OwnerCommandAuthBundleRow = require_Schema$1.Struct({
|
|
67428
67466
|
accessToken: require_Schema$1.String,
|
|
67429
67467
|
jwtToken: require_Schema$1.NullOr(require_Schema$1.String),
|
|
67430
67468
|
jwtExpiresAt: require_Schema$1.NullOr(require_Schema$1.String),
|
|
67431
|
-
|
|
67469
|
+
sessionGrant: require_Schema$1.NullOr(require_Schema$1.String),
|
|
67432
67470
|
sessionId: require_Schema$1.String,
|
|
67433
67471
|
cmmdUserIdentity: require_Schema$1.String
|
|
67434
67472
|
});
|
|
@@ -67473,8 +67511,6 @@ function decryptNullable(scope, value) {
|
|
|
67473
67511
|
}
|
|
67474
67512
|
const decodeTokenRows = require_Schema$1.decodeUnknownEffect(require_Schema$1.ArraySchema(CommandAccessTokenRow));
|
|
67475
67513
|
const decodeSubjectTokenRows = require_Schema$1.decodeUnknownEffect(require_Schema$1.ArraySchema(SubjectCommandAccessTokenRow));
|
|
67476
|
-
const decodeSubjectJwtRows = require_Schema$1.decodeUnknownEffect(require_Schema$1.ArraySchema(SubjectJwtRow));
|
|
67477
|
-
require_Schema$1.decodeUnknownEffect(require_Schema$1.ArraySchema(SubjectRefreshTokenRow));
|
|
67478
67514
|
const decodeOwnerCommandAuthBundleRows = require_Schema$1.decodeUnknownEffect(require_Schema$1.ArraySchema(OwnerCommandAuthBundleRow));
|
|
67479
67515
|
const decodeOwnerCommandAuthSessionRows = require_Schema$1.decodeUnknownEffect(require_Schema$1.ArraySchema(OwnerCommandAuthSessionRow));
|
|
67480
67516
|
const decodeCloudOwnerSessionRows = require_Schema$1.decodeUnknownEffect(require_Schema$1.ArraySchema(CloudOwnerSessionRow));
|
|
@@ -67500,9 +67536,9 @@ function extractJwtExpiresAt(jwtToken) {
|
|
|
67500
67536
|
* Comparing against `exp` exactly leaves a window where Forge sends a token it
|
|
67501
67537
|
* believes is good and CMMD has already stopped accepting it: the clocks differ by a
|
|
67502
67538
|
* little, the request spends time in flight, and the answer comes back 401 on a
|
|
67503
|
-
* session
|
|
67504
|
-
*
|
|
67505
|
-
*
|
|
67539
|
+
* session whose grant could have minted a good replacement. Two minutes is far
|
|
67540
|
+
* inside the JWT's own lifetime, so this costs one early mint and removes the
|
|
67541
|
+
* race.
|
|
67506
67542
|
*/
|
|
67507
67543
|
const JWT_EXPIRY_SKEW_MS = 2 * 6e4;
|
|
67508
67544
|
/**
|
|
@@ -67528,7 +67564,6 @@ const hydrateCommandAccessTokenStore = require_Schema$1.gen(function* () {
|
|
|
67528
67564
|
access_token AS "accessToken",
|
|
67529
67565
|
jwt_token AS "jwtToken",
|
|
67530
67566
|
jwt_expires_at AS "jwtExpiresAt",
|
|
67531
|
-
refresh_token AS "refreshToken",
|
|
67532
67567
|
cmmd_user_identity AS "cmmdUserIdentity",
|
|
67533
67568
|
session_grant AS "sessionGrant"
|
|
67534
67569
|
FROM command_access_tokens
|
|
@@ -67536,7 +67571,6 @@ const hydrateCommandAccessTokenStore = require_Schema$1.gen(function* () {
|
|
|
67536
67571
|
cache.clear();
|
|
67537
67572
|
jwtCache.clear();
|
|
67538
67573
|
jwtExpiryCache.clear();
|
|
67539
|
-
refreshTokenCache.clear();
|
|
67540
67574
|
identityCache.clear();
|
|
67541
67575
|
sessionGrantCache.clear();
|
|
67542
67576
|
yield* require_Schema$1.try_({
|
|
@@ -67547,8 +67581,6 @@ const hydrateCommandAccessTokenStore = require_Schema$1.gen(function* () {
|
|
|
67547
67581
|
const jwtToken = decryptNullable(JWT_TOKEN_SCOPE, row.jwtToken);
|
|
67548
67582
|
if (jwtToken) jwtCache.set(sessionId, jwtToken);
|
|
67549
67583
|
if (row.jwtExpiresAt) jwtExpiryCache.set(sessionId, row.jwtExpiresAt);
|
|
67550
|
-
const refreshToken = decryptNullable(REFRESH_TOKEN_SCOPE, row.refreshToken);
|
|
67551
|
-
if (refreshToken) refreshTokenCache.set(sessionId, refreshToken);
|
|
67552
67584
|
const identity = decodeCmmdUserIdentity(decryptNullable(IDENTITY_SCOPE, row.cmmdUserIdentity));
|
|
67553
67585
|
if (identity) identityCache.set(sessionId, identity);
|
|
67554
67586
|
const sessionGrant = decryptNullable(SESSION_GRANT_SCOPE, row.sessionGrant);
|
|
@@ -67655,19 +67687,11 @@ function readCommandJwtForSession(sessionId) {
|
|
|
67655
67687
|
return jwtCache.get(sessionId) ?? null;
|
|
67656
67688
|
}
|
|
67657
67689
|
/**
|
|
67658
|
-
* Read the CMMD OAuth refresh token for a session. Sync; hits the in-memory
|
|
67659
|
-
* cache only. Used to rotate a REST JWT after CMMD rejects a locally cached JWT
|
|
67660
|
-
* that still appears unexpired.
|
|
67661
|
-
*/
|
|
67662
|
-
function readCommandRefreshTokenForSession(sessionId) {
|
|
67663
|
-
return refreshTokenCache.get(sessionId) ?? null;
|
|
67664
|
-
}
|
|
67665
|
-
/**
|
|
67666
67690
|
* The durable CMMD session grant for a session, if one was issued.
|
|
67667
67691
|
*
|
|
67668
|
-
*
|
|
67669
|
-
*
|
|
67670
|
-
*
|
|
67692
|
+
* The grant is NON-rotating: minting from it does not invalidate it, so it
|
|
67693
|
+
* stays valid until CMMD expires or revokes it. That is what makes it a safe
|
|
67694
|
+
* long-lived credential: the short-lived JWT it mints can always be replaced.
|
|
67671
67695
|
*/
|
|
67672
67696
|
function readCommandSessionGrantForSession(sessionId) {
|
|
67673
67697
|
return sessionGrantCache.get(sessionId) ?? null;
|
|
@@ -67717,77 +67741,26 @@ const setCmmdUserIdentityForSession = (sessionId, identity) => require_Schema$1.
|
|
|
67717
67741
|
* `jwt_expires_at` so callers can detect when the token has expired.
|
|
67718
67742
|
* Updates only the JWT columns; the PAT row must already exist
|
|
67719
67743
|
* (call `setCommandAccessTokenForSession` first in the OAuth callback).
|
|
67744
|
+
*
|
|
67745
|
+
* The JWT is short-lived by contract (CMMD clamps the mint TTL to one hour);
|
|
67746
|
+
* replacing one is the resolver's mint, so no sibling credential rides along.
|
|
67720
67747
|
*/
|
|
67721
|
-
const setCommandJwtForSession = (sessionId, jwtToken
|
|
67748
|
+
const setCommandJwtForSession = (sessionId, jwtToken) => require_Schema$1.gen(function* () {
|
|
67722
67749
|
const token = jwtToken.trim();
|
|
67723
67750
|
if (token.length === 0) return;
|
|
67724
67751
|
const expiresAt = extractJwtExpiresAt(token);
|
|
67725
|
-
const refreshToken = opts.refreshToken?.trim();
|
|
67726
67752
|
const storedToken = encodeSensitiveValue(JWT_TOKEN_SCOPE, token);
|
|
67727
|
-
|
|
67728
|
-
|
|
67729
|
-
|
|
67730
|
-
|
|
67731
|
-
|
|
67732
|
-
|
|
67733
|
-
|
|
67734
|
-
|
|
67735
|
-
refresh_token = ${storedRefreshToken},
|
|
67736
|
-
updated_at = ${now}
|
|
67737
|
-
WHERE session_id = ${sessionId}
|
|
67738
|
-
`.pipe(require_Schema$1.mapError(toPersistenceSqlError("CommandAccessTokenStore.setJwt:query")));
|
|
67739
|
-
else yield* sql`
|
|
67740
|
-
UPDATE command_access_tokens
|
|
67741
|
-
SET
|
|
67742
|
-
jwt_token = ${storedToken},
|
|
67743
|
-
jwt_expires_at = ${expiresAt},
|
|
67744
|
-
updated_at = ${now}
|
|
67745
|
-
WHERE session_id = ${sessionId}
|
|
67746
|
-
`.pipe(require_Schema$1.mapError(toPersistenceSqlError("CommandAccessTokenStore.setJwt:query")));
|
|
67753
|
+
yield* (yield* require_SqlError.SqlClient)`
|
|
67754
|
+
UPDATE command_access_tokens
|
|
67755
|
+
SET
|
|
67756
|
+
jwt_token = ${storedToken},
|
|
67757
|
+
jwt_expires_at = ${expiresAt},
|
|
67758
|
+
updated_at = ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
67759
|
+
WHERE session_id = ${sessionId}
|
|
67760
|
+
`.pipe(require_Schema$1.mapError(toPersistenceSqlError("CommandAccessTokenStore.setJwt:query")));
|
|
67747
67761
|
jwtCache.set(sessionId, token);
|
|
67748
67762
|
if (expiresAt) jwtExpiryCache.set(sessionId, expiresAt);
|
|
67749
67763
|
else jwtExpiryCache.delete(sessionId);
|
|
67750
|
-
if (refreshToken) refreshTokenCache.set(sessionId, refreshToken);
|
|
67751
|
-
});
|
|
67752
|
-
/**
|
|
67753
|
-
* Look up the most recent non-expired JWT for an auth subject across active
|
|
67754
|
-
* owner sessions. Used as a continuity fallback when the current session ID
|
|
67755
|
-
* changed (e.g. after re-authentication) but the user's CMMD JWT should still
|
|
67756
|
-
* be usable.
|
|
67757
|
-
*
|
|
67758
|
-
* Also returns the sessionId that owns the JWT so callers can promote it to
|
|
67759
|
-
* the current session, avoiding repeated cross-session lookups.
|
|
67760
|
-
*/
|
|
67761
|
-
const readMostRecentJwtForSubject = (subject) => require_Schema$1.gen(function* () {
|
|
67762
|
-
const row = (yield* decodeSubjectJwtRows(yield* (yield* require_SqlError.SqlClient)`
|
|
67763
|
-
SELECT
|
|
67764
|
-
cat.access_token AS "accessToken",
|
|
67765
|
-
cat.jwt_token AS "jwtToken",
|
|
67766
|
-
cat.jwt_expires_at AS "jwtExpiresAt",
|
|
67767
|
-
cat.refresh_token AS "refreshToken",
|
|
67768
|
-
cat.session_id AS "sessionId"
|
|
67769
|
-
FROM command_access_tokens AS cat
|
|
67770
|
-
JOIN auth_sessions AS s ON s.session_id = cat.session_id
|
|
67771
|
-
WHERE s.subject = ${subject}
|
|
67772
|
-
AND s.role = 'owner'
|
|
67773
|
-
AND s.revoked_at IS NULL
|
|
67774
|
-
AND cat.jwt_token IS NOT NULL
|
|
67775
|
-
AND cat.jwt_expires_at IS NOT NULL
|
|
67776
|
-
AND cat.jwt_expires_at > ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
67777
|
-
ORDER BY s.issued_at DESC
|
|
67778
|
-
LIMIT 1
|
|
67779
|
-
`.pipe(require_Schema$1.mapError(toPersistenceSqlError("CommandAccessTokenStore.readMostRecentJwtBySubject:query")))).pipe(require_Schema$1.mapError(toPersistenceDecodeError("CommandAccessTokenStore.readMostRecentJwtBySubject:decodeRows"))))[0];
|
|
67780
|
-
if (!row?.jwtToken) return null;
|
|
67781
|
-
const jwtToken = decodeSensitiveValue(JWT_TOKEN_SCOPE, row.jwtToken);
|
|
67782
|
-
const refreshToken = decryptNullable(REFRESH_TOKEN_SCOPE, row.refreshToken);
|
|
67783
|
-
const accessToken = decodeSensitiveValue(ACCESS_TOKEN_SCOPE, row.accessToken);
|
|
67784
|
-
return {
|
|
67785
|
-
jwt: jwtToken,
|
|
67786
|
-
sessionId: row.sessionId,
|
|
67787
|
-
expiresAt: row.jwtExpiresAt ?? null,
|
|
67788
|
-
refreshToken,
|
|
67789
|
-
accessToken
|
|
67790
|
-
};
|
|
67791
67764
|
});
|
|
67792
67765
|
/**
|
|
67793
67766
|
* Look up the latest CMMD PAT for an auth subject across active owner sessions.
|
|
@@ -67826,7 +67799,7 @@ const readMostRecentCommandAccessTokenForSubject = (subject) => require_Schema$1
|
|
|
67826
67799
|
* `desktop-bootstrap` session that minted them, and nothing later rewrites that
|
|
67827
67800
|
* subject. So the clause matched no row on any machine, promotion always
|
|
67828
67801
|
* returned null, and a phone whose short-lived JWT had expired could not reach
|
|
67829
|
-
* the
|
|
67802
|
+
* the credentials sitting on its own previous session. It surfaced as "This
|
|
67830
67803
|
* session is no longer accepted. Sign in again." about a day after signing in,
|
|
67831
67804
|
* with nothing in the log, because the ladder never got as far as a call that
|
|
67832
67805
|
* could fail out loud.
|
|
@@ -67887,6 +67860,9 @@ const readMostRecentOwnerCommandAuthSession = require_Schema$1.gen(function* ()
|
|
|
67887
67860
|
* a CMMD user subject. The CMMD token is still account-owned, so the trusted
|
|
67888
67861
|
* local owner may inherit the most recent active owner token with an identity.
|
|
67889
67862
|
* Client pairings must not call this.
|
|
67863
|
+
*
|
|
67864
|
+
* The copy includes the session grant: the promoted session can then mint its
|
|
67865
|
+
* own short-lived JWT, which since forge#3521 is the only refresh story.
|
|
67890
67866
|
*/
|
|
67891
67867
|
const promoteMostRecentOwnerCommandAuthToSession = (sessionId) => require_Schema$1.gen(function* () {
|
|
67892
67868
|
const sql = yield* require_SqlError.SqlClient;
|
|
@@ -67895,7 +67871,7 @@ const promoteMostRecentOwnerCommandAuthToSession = (sessionId) => require_Schema
|
|
|
67895
67871
|
cat.access_token AS "accessToken",
|
|
67896
67872
|
cat.jwt_token AS "jwtToken",
|
|
67897
67873
|
cat.jwt_expires_at AS "jwtExpiresAt",
|
|
67898
|
-
cat.
|
|
67874
|
+
cat.session_grant AS "sessionGrant",
|
|
67899
67875
|
cat.session_id AS "sessionId",
|
|
67900
67876
|
cat.cmmd_user_identity AS "cmmdUserIdentity"
|
|
67901
67877
|
FROM command_access_tokens AS cat
|
|
@@ -67907,7 +67883,7 @@ const promoteMostRecentOwnerCommandAuthToSession = (sessionId) => require_Schema
|
|
|
67907
67883
|
if (!row) return null;
|
|
67908
67884
|
const accessToken = decodeSensitiveValue(ACCESS_TOKEN_SCOPE, row.accessToken);
|
|
67909
67885
|
const jwtToken = decryptNullable(JWT_TOKEN_SCOPE, row.jwtToken);
|
|
67910
|
-
const
|
|
67886
|
+
const sessionGrant = decryptNullable(SESSION_GRANT_SCOPE, row.sessionGrant);
|
|
67911
67887
|
const identity = decodeCmmdUserIdentity(decodeSensitiveValue(IDENTITY_SCOPE, row.cmmdUserIdentity));
|
|
67912
67888
|
if (!identity) return null;
|
|
67913
67889
|
const updateNow = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -67917,7 +67893,7 @@ const promoteMostRecentOwnerCommandAuthToSession = (sessionId) => require_Schema
|
|
|
67917
67893
|
access_token,
|
|
67918
67894
|
jwt_token,
|
|
67919
67895
|
jwt_expires_at,
|
|
67920
|
-
|
|
67896
|
+
session_grant,
|
|
67921
67897
|
cmmd_user_identity,
|
|
67922
67898
|
created_at,
|
|
67923
67899
|
updated_at
|
|
@@ -67927,7 +67903,7 @@ const promoteMostRecentOwnerCommandAuthToSession = (sessionId) => require_Schema
|
|
|
67927
67903
|
${row.accessToken},
|
|
67928
67904
|
${row.jwtToken},
|
|
67929
67905
|
${row.jwtExpiresAt},
|
|
67930
|
-
${row.
|
|
67906
|
+
${row.sessionGrant},
|
|
67931
67907
|
${row.cmmdUserIdentity},
|
|
67932
67908
|
${updateNow},
|
|
67933
67909
|
${updateNow}
|
|
@@ -67936,7 +67912,7 @@ const promoteMostRecentOwnerCommandAuthToSession = (sessionId) => require_Schema
|
|
|
67936
67912
|
access_token = excluded.access_token,
|
|
67937
67913
|
jwt_token = excluded.jwt_token,
|
|
67938
67914
|
jwt_expires_at = excluded.jwt_expires_at,
|
|
67939
|
-
|
|
67915
|
+
session_grant = excluded.session_grant,
|
|
67940
67916
|
cmmd_user_identity = excluded.cmmd_user_identity,
|
|
67941
67917
|
updated_at = excluded.updated_at
|
|
67942
67918
|
`.pipe(require_Schema$1.mapError(toPersistenceSqlError("CommandAccessTokenStore.promoteLatestOwnerAuth:upsert")));
|
|
@@ -67945,14 +67921,14 @@ const promoteMostRecentOwnerCommandAuthToSession = (sessionId) => require_Schema
|
|
|
67945
67921
|
else jwtCache.delete(sessionId);
|
|
67946
67922
|
if (row.jwtExpiresAt) jwtExpiryCache.set(sessionId, row.jwtExpiresAt);
|
|
67947
67923
|
else jwtExpiryCache.delete(sessionId);
|
|
67948
|
-
if (
|
|
67949
|
-
else
|
|
67924
|
+
if (sessionGrant) sessionGrantCache.set(sessionId, sessionGrant);
|
|
67925
|
+
else sessionGrantCache.delete(sessionId);
|
|
67950
67926
|
identityCache.set(sessionId, identity);
|
|
67951
67927
|
return {
|
|
67952
67928
|
accessToken,
|
|
67953
67929
|
jwtToken,
|
|
67954
67930
|
jwtExpiresAt: row.jwtExpiresAt ?? null,
|
|
67955
|
-
|
|
67931
|
+
sessionGrant,
|
|
67956
67932
|
sourceSessionId: row.sessionId,
|
|
67957
67933
|
cmmdUserIdentity: identity
|
|
67958
67934
|
};
|
|
@@ -67997,11 +67973,11 @@ const clearCommandAccessTokenForSession = (sessionId) => require_Schema$1.gen(fu
|
|
|
67997
67973
|
`.pipe(require_Schema$1.mapError(toPersistenceSqlError("CommandAccessTokenStore.clear:query")));
|
|
67998
67974
|
forgetCommandAccessTokenCachesForSession(sessionId);
|
|
67999
67975
|
});
|
|
67976
|
+
/** Evict a session's credentials, JWT expiry, identity, and grant from memory without changing storage. */
|
|
68000
67977
|
function forgetCommandAccessTokenCachesForSession(sessionId) {
|
|
68001
67978
|
cache.delete(sessionId);
|
|
68002
67979
|
jwtCache.delete(sessionId);
|
|
68003
67980
|
jwtExpiryCache.delete(sessionId);
|
|
68004
|
-
refreshTokenCache.delete(sessionId);
|
|
68005
67981
|
identityCache.delete(sessionId);
|
|
68006
67982
|
sessionGrantCache.delete(sessionId);
|
|
68007
67983
|
}
|
|
@@ -68033,14 +68009,14 @@ const scheduleExpiredForgeCloudOwnerCredentialMaintenance = require_Schema$1.sle
|
|
|
68033
68009
|
* is dead too, so the promotion sources are cleared with it — otherwise the
|
|
68034
68010
|
* next promotion resurrects the same dead bundle in a loop.
|
|
68035
68011
|
* - **Every other session** owns its credentials outright: each OAuth exchange
|
|
68036
|
-
* mints
|
|
68037
|
-
*
|
|
68012
|
+
* mints its own PAT, JWT and session grant. One session's dead credential
|
|
68013
|
+
* says nothing about a
|
|
68038
68014
|
* sibling browser or device, so the clear stays local to that session.
|
|
68039
68015
|
*
|
|
68040
68016
|
* Clearing every active owner row unconditionally (the pre-CMMD-1871 behaviour)
|
|
68041
68017
|
* turned a single expired tab into a sign-out on every browser and device.
|
|
68042
68018
|
*
|
|
68043
|
-
* Destroys
|
|
68019
|
+
* Destroys durable credentials (the PAT, the grant), so every call is logged with a
|
|
68044
68020
|
* `reason` naming the caller's own decision (never the credential itself) and
|
|
68045
68021
|
* the actual row count deleted, at info level so it shows up without raising
|
|
68046
68022
|
* the server's log level. `reason` is optional only so a caller that has not
|
|
@@ -68081,65 +68057,10 @@ const clearUnrecoverableCmmdAuthForSession = (sessionId, reason) => require_Sche
|
|
|
68081
68057
|
}
|
|
68082
68058
|
});
|
|
68083
68059
|
/**
|
|
68084
|
-
* Retire refresh credentials copied into historical desktop bearer sessions.
|
|
68085
|
-
* Electron owns rotation for native desktop OAuth; Forge Local keeps only the
|
|
68086
|
-
* derived access authority needed by the current bearer session.
|
|
68087
|
-
*
|
|
68088
|
-
* Scoped to the bootstrapping client, not to the whole subject. The phone and
|
|
68089
|
-
* Electron sign in as the same `user:<id>`, so a subject-wide sweep let one
|
|
68090
|
-
* desktop launch null out a phone's refresh token and end its session a day
|
|
68091
|
-
* later. Two clauses keep that from happening:
|
|
68092
|
-
*
|
|
68093
|
-
* - `client_device_type = 'desktop'`, which now excludes phones outright,
|
|
68094
|
-
* because the mobile app declares itself at bootstrap.
|
|
68095
|
-
* - the same recorded `client_user_agent` as the bootstrap doing the
|
|
68096
|
-
* retirement, which is the only thing that separates a phone row written
|
|
68097
|
-
* before that stamp existed from a real Electron row. Both are stamped
|
|
68098
|
-
* `desktop`; neither carries any other mark. An empty or unmatched user agent
|
|
68099
|
-
* retires nothing, which is the direction that keeps a live credential.
|
|
68100
|
-
*
|
|
68101
|
-
* Electron re-bootstraps under a stable user agent, so its own historical rows
|
|
68102
|
-
* still match and still get swept.
|
|
68103
|
-
*/
|
|
68104
|
-
const clearDesktopBearerRefreshTokensForSubject = (input) => require_Schema$1.gen(function* () {
|
|
68105
|
-
const normalizedSubject = input.subject.trim();
|
|
68106
|
-
const normalizedUserAgent = input.clientUserAgent?.trim() ?? "";
|
|
68107
|
-
if (!normalizedSubject || !normalizedUserAgent) return;
|
|
68108
|
-
const rows = yield* (yield* require_SqlError.SqlClient)`
|
|
68109
|
-
UPDATE command_access_tokens
|
|
68110
|
-
SET
|
|
68111
|
-
refresh_token = NULL,
|
|
68112
|
-
updated_at = ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
68113
|
-
WHERE session_id IN (
|
|
68114
|
-
SELECT session_id
|
|
68115
|
-
FROM auth_sessions
|
|
68116
|
-
WHERE subject = ${normalizedSubject}
|
|
68117
|
-
AND role = 'owner'
|
|
68118
|
-
AND method = 'bearer-session-token'
|
|
68119
|
-
AND client_device_type = 'desktop'
|
|
68120
|
-
AND client_user_agent = ${normalizedUserAgent}
|
|
68121
|
-
)
|
|
68122
|
-
RETURNING session_id AS "sessionId"
|
|
68123
|
-
`.pipe(require_Schema$1.mapError(toPersistenceSqlError("CommandAccessTokenStore.clearDesktopBearerRefresh:query")));
|
|
68124
|
-
for (const row of rows) refreshTokenCache.delete(row.sessionId);
|
|
68125
|
-
});
|
|
68126
|
-
const isDesktopBearerAuthSession = (sessionId) => require_Schema$1.gen(function* () {
|
|
68127
|
-
const rows = yield* (yield* require_SqlError.SqlClient)`
|
|
68128
|
-
SELECT
|
|
68129
|
-
method,
|
|
68130
|
-
client_device_type AS "clientDeviceType"
|
|
68131
|
-
FROM auth_sessions
|
|
68132
|
-
WHERE session_id = ${sessionId}
|
|
68133
|
-
LIMIT 1
|
|
68134
|
-
`.pipe(require_Schema$1.mapError(toPersistenceSqlError("CommandAccessTokenStore.isDesktopBearerSession:query")));
|
|
68135
|
-
return rows[0]?.method === "bearer-session-token" && rows[0].clientDeviceType === "desktop";
|
|
68136
|
-
});
|
|
68137
|
-
/**
|
|
68138
68060
|
* The device type a session's bearer bootstrap declared, for log lines only.
|
|
68139
68061
|
*
|
|
68140
|
-
*
|
|
68141
|
-
*
|
|
68142
|
-
* what made the request, not whether Forge may rotate for it. Never fails:
|
|
68062
|
+
* Returns the raw column: a log line wants to say what made the request.
|
|
68063
|
+
* Never fails:
|
|
68143
68064
|
* an unreadable or missing row logs as `"unknown"` rather than losing the
|
|
68144
68065
|
* decision-point log line it is attached to.
|
|
68145
68066
|
*/
|
|
@@ -68192,6 +68113,7 @@ function artifactError(message) {
|
|
|
68192
68113
|
cause
|
|
68193
68114
|
});
|
|
68194
68115
|
}
|
|
68116
|
+
/** Store credentials under the asserted Cloud owner session and retain its durable mint grant. */
|
|
68195
68117
|
function persistCloudOwnerArtifacts(input) {
|
|
68196
68118
|
return require_Schema$1.gen(function* () {
|
|
68197
68119
|
const sql = yield* require_SqlError.SqlClient;
|
|
@@ -68199,7 +68121,7 @@ function persistCloudOwnerArtifacts(input) {
|
|
|
68199
68121
|
yield* setCommandAccessTokenForSession(input.assertion.sessionId, input.accessToken).pipe(require_Schema$1.mapError(artifactError("Failed to persist CMMD access token.")));
|
|
68200
68122
|
yield* setCmmdUserIdentityForSession(input.assertion.sessionId, input.identity).pipe(require_Schema$1.mapError(artifactError("Failed to persist CMMD user identity.")));
|
|
68201
68123
|
if (input.sessionGrant) yield* setCommandSessionGrantForSession(input.assertion.sessionId, input.sessionGrant, { expiresAt: input.sessionGrantExpiresAt ?? null }).pipe(require_Schema$1.mapError(artifactError("Failed to persist CMMD session grant.")));
|
|
68202
|
-
if (input.jwtToken) yield* setCommandJwtForSession(input.assertion.sessionId, input.jwtToken
|
|
68124
|
+
if (input.jwtToken) yield* setCommandJwtForSession(input.assertion.sessionId, input.jwtToken).pipe(require_Schema$1.mapError(artifactError("Failed to persist CMMD JWT.")));
|
|
68203
68125
|
});
|
|
68204
68126
|
yield* sql.withTransaction(transaction).pipe(require_Schema$1.mapError((cause) => cause instanceof AuthError ? cause : new AuthError({
|
|
68205
68127
|
message: "Failed to persist Forge Cloud owner credentials.",
|
|
@@ -68337,7 +68259,6 @@ const authSessionRenewRouteLayer = add$1("POST", "/api/auth/session/renew", requ
|
|
|
68337
68259
|
accessToken: patToStore,
|
|
68338
68260
|
identity: cmmdIdentity,
|
|
68339
68261
|
jwtToken: credentials.jwtToStore,
|
|
68340
|
-
refreshToken: credentials.refreshToken,
|
|
68341
68262
|
sessionGrant,
|
|
68342
68263
|
sessionGrantExpiresAt: void 0
|
|
68343
68264
|
});
|
|
@@ -68414,7 +68335,6 @@ const PairingCredentialRequestHeaders = require_Schema$1.Struct({
|
|
|
68414
68335
|
const CommandBootstrapInput = require_Schema$1.Struct({
|
|
68415
68336
|
accessToken: require_Schema$1.String,
|
|
68416
68337
|
mcpToken: require_Schema$1.optionalKey(require_Schema$1.String),
|
|
68417
|
-
refreshToken: require_Schema$1.optionalKey(require_Schema$1.String),
|
|
68418
68338
|
userId: require_Schema$1.optionalKey(require_Schema$1.Number),
|
|
68419
68339
|
displayName: require_Schema$1.optionalKey(require_Schema$1.String),
|
|
68420
68340
|
user: require_Schema$1.optionalKey(require_Schema$1.Unknown),
|
|
@@ -68648,6 +68568,7 @@ function validateCommandBootstrapPayload(payload) {
|
|
|
68648
68568
|
};
|
|
68649
68569
|
});
|
|
68650
68570
|
}
|
|
68571
|
+
/** Store the bootstrap PAT, optional JWT, and identity; report persistence failures as auth errors. */
|
|
68651
68572
|
function persistCommandBootstrapSessionArtifacts(input) {
|
|
68652
68573
|
return require_Schema$1.gen(function* () {
|
|
68653
68574
|
const token = input.payload.accessToken.trim();
|
|
@@ -68662,14 +68583,11 @@ function persistCommandBootstrapSessionArtifacts(input) {
|
|
|
68662
68583
|
status: 500,
|
|
68663
68584
|
cause
|
|
68664
68585
|
})));
|
|
68665
|
-
if (isCmmdJwtToken(token)) {
|
|
68666
|
-
|
|
68667
|
-
|
|
68668
|
-
|
|
68669
|
-
|
|
68670
|
-
cause
|
|
68671
|
-
})));
|
|
68672
|
-
}
|
|
68586
|
+
if (isCmmdJwtToken(token)) yield* setCommandJwtForSession(input.sessionId, token).pipe(require_Schema$1.mapError((cause) => new AuthError({
|
|
68587
|
+
message: "Failed to persist CMMD JWT.",
|
|
68588
|
+
status: 500,
|
|
68589
|
+
cause
|
|
68590
|
+
})));
|
|
68673
68591
|
const cmmdIdentity = input.cmmdIdentity ?? normalizeCmmdUserIdentity(input.payload.user);
|
|
68674
68592
|
if (cmmdIdentity) yield* setCmmdUserIdentityForSession(input.sessionId, cmmdIdentity).pipe(require_Schema$1.mapError((cause) => new AuthError({
|
|
68675
68593
|
message: "Failed to persist CMMD user identity.",
|
|
@@ -68771,7 +68689,6 @@ function resolveCmmdTokenBundleCredentials(tokenResult) {
|
|
|
68771
68689
|
userId: cmmdIdentity?.id,
|
|
68772
68690
|
accessToken,
|
|
68773
68691
|
mcpToken,
|
|
68774
|
-
refreshToken: readString("refresh_token"),
|
|
68775
68692
|
sessionGrant: readString("session_grant"),
|
|
68776
68693
|
sessionGrantExpiresAt: readString("session_grant_expires_at"),
|
|
68777
68694
|
forgeOwnerAssertion: readString("forge_owner_assertion"),
|
|
@@ -68793,7 +68710,6 @@ function describeCmmdTokenResponseForLog(input) {
|
|
|
68793
68710
|
accessTokenType: input.tokenObj ? typeof input.tokenObj["access_token"] : "n/a",
|
|
68794
68711
|
hasAccessToken: input.accessToken !== void 0,
|
|
68795
68712
|
hasMcpToken: input.mcpToken !== void 0,
|
|
68796
|
-
hasRefreshToken: input.refreshToken !== void 0,
|
|
68797
68713
|
expiresInType: input.tokenObj ? typeof input.tokenObj["expires_in"] : "n/a",
|
|
68798
68714
|
isJwt: input.isJwt,
|
|
68799
68715
|
userKeys: userObj ? Object.keys(userObj) : [],
|
|
@@ -69021,19 +68937,6 @@ const authCommandBearerBootstrapRouteLayer = add$1("POST", "/api/auth/bootstrap/
|
|
|
69021
68937
|
payload,
|
|
69022
68938
|
cmmdIdentity: validated.cmmdIdentity
|
|
69023
68939
|
});
|
|
69024
|
-
if (deviceType === "desktop" && !payload.refreshToken?.trim()) yield* clearDesktopBearerRefreshTokensForSubject({
|
|
69025
|
-
subject: sessionSubject,
|
|
69026
|
-
clientUserAgent: request.headers["user-agent"]
|
|
69027
|
-
}).pipe(require_Schema$1.mapError((cause) => new AuthError({
|
|
69028
|
-
message: "Failed to retire legacy desktop refresh credentials.",
|
|
69029
|
-
status: 500,
|
|
69030
|
-
cause
|
|
69031
|
-
})), require_Schema$1.tapError(() => require_Schema$1.logWarning("CMMD bearer bootstrap failed.", {
|
|
69032
|
-
sessionId: issued.sessionId,
|
|
69033
|
-
deviceType,
|
|
69034
|
-
outcome: "failed",
|
|
69035
|
-
reason: "legacy-desktop-refresh-retirement-failed"
|
|
69036
|
-
})));
|
|
69037
68940
|
yield* require_Schema$1.logInfo("CMMD bearer bootstrap succeeded.", {
|
|
69038
68941
|
sessionId: issued.sessionId,
|
|
69039
68942
|
deviceType,
|
|
@@ -69131,7 +69034,6 @@ function completeCloudOwnerPasswordLogin(input) {
|
|
|
69131
69034
|
accessToken: patToStore,
|
|
69132
69035
|
identity: cmmdIdentity,
|
|
69133
69036
|
jwtToken: credentials.jwtToStore,
|
|
69134
|
-
refreshToken: credentials.refreshToken,
|
|
69135
69037
|
sessionGrant: credentials.sessionGrant,
|
|
69136
69038
|
sessionGrantExpiresAt: credentials.sessionGrantExpiresAt
|
|
69137
69039
|
});
|
|
@@ -69214,6 +69116,10 @@ const authWebLoginRouteLayer = add$1("GET", "/api/auth/login", require_Schema$1.
|
|
|
69214
69116
|
transientCookie
|
|
69215
69117
|
]]));
|
|
69216
69118
|
}));
|
|
69119
|
+
/**
|
|
69120
|
+
* Persist the local OAuth PAT and identity, failing if either write fails.
|
|
69121
|
+
* Capture and ignore failures while storing the optional mint grant and JWT.
|
|
69122
|
+
*/
|
|
69217
69123
|
function persistLocalOAuthArtifacts(input) {
|
|
69218
69124
|
return require_Schema$1.gen(function* () {
|
|
69219
69125
|
yield* setCommandAccessTokenForSession(input.sessionId, input.accessToken).pipe(require_Schema$1.mapError((cause) => new AuthError({
|
|
@@ -69230,7 +69136,7 @@ function persistLocalOAuthArtifacts(input) {
|
|
|
69230
69136
|
route: "oauth-callback",
|
|
69231
69137
|
step: "grant-store"
|
|
69232
69138
|
}))), require_Schema$1.ignore({ log: true }));
|
|
69233
|
-
if (input.jwtToken) yield* setCommandJwtForSession(input.sessionId, input.jwtToken
|
|
69139
|
+
if (input.jwtToken) yield* setCommandJwtForSession(input.sessionId, input.jwtToken).pipe(require_Schema$1.tapError((error) => require_Schema$1.sync(() => captureServerError(error, {
|
|
69234
69140
|
route: "oauth-callback",
|
|
69235
69141
|
step: "jwt-store"
|
|
69236
69142
|
}))), require_Schema$1.ignore({ log: true }));
|
|
@@ -69284,13 +69190,12 @@ const authWebCallbackRouteLayer = add$1("GET", "/api/auth/callback", require_Sch
|
|
|
69284
69190
|
cause
|
|
69285
69191
|
})
|
|
69286
69192
|
}), "Failed to exchange authorization code with CMMD.");
|
|
69287
|
-
const { tokenObj, cmmdIdentity, userId, accessToken, mcpToken,
|
|
69193
|
+
const { tokenObj, cmmdIdentity, userId, accessToken, mcpToken, sessionGrant, sessionGrantExpiresAt, forgeOwnerAssertion, isJwt, patToStore, jwtToStore } = yield* resolveCmmdTokenBundleCredentials(tokenResult);
|
|
69288
69194
|
const tokenResponseLogDetails = describeCmmdTokenResponseForLog({
|
|
69289
69195
|
tokenObj,
|
|
69290
69196
|
tokenResult,
|
|
69291
69197
|
accessToken,
|
|
69292
69198
|
mcpToken,
|
|
69293
|
-
refreshToken,
|
|
69294
69199
|
isJwt,
|
|
69295
69200
|
userId,
|
|
69296
69201
|
patToStore,
|
|
@@ -69343,7 +69248,6 @@ const authWebCallbackRouteLayer = add$1("GET", "/api/auth/callback", require_Sch
|
|
|
69343
69248
|
accessToken: patToStore,
|
|
69344
69249
|
identity: cmmdIdentity,
|
|
69345
69250
|
jwtToken: jwtToStore,
|
|
69346
|
-
refreshToken,
|
|
69347
69251
|
sessionGrant,
|
|
69348
69252
|
sessionGrantExpiresAt
|
|
69349
69253
|
};
|
|
@@ -71361,51 +71265,83 @@ function isCmmdUserAccessJwt(token) {
|
|
|
71361
71265
|
}
|
|
71362
71266
|
//#endregion
|
|
71363
71267
|
//#region src/command/cmmdJwt.ts
|
|
71364
|
-
function parseCmmdOAuthRefreshResponse(value) {
|
|
71365
|
-
if (!value || typeof value !== "object") return null;
|
|
71366
|
-
const record = value;
|
|
71367
|
-
const accessToken = record["access_token"];
|
|
71368
|
-
if (typeof accessToken !== "string" || accessToken.trim().length === 0) return null;
|
|
71369
|
-
const refreshToken = record["refresh_token"];
|
|
71370
|
-
return {
|
|
71371
|
-
accessToken: accessToken.trim(),
|
|
71372
|
-
refreshToken: typeof refreshToken === "string" && refreshToken.trim().length > 0 ? refreshToken.trim() : null
|
|
71373
|
-
};
|
|
71374
|
-
}
|
|
71375
71268
|
/**
|
|
71376
|
-
* Read
|
|
71377
|
-
*
|
|
71378
|
-
|
|
71269
|
+
* Read a minted JWT with finite issuance and expiry times and a lifetime of
|
|
71270
|
+
* at most one hour. Reject expired tokens and tokens issued in the future.
|
|
71271
|
+
* This checks the mint contract only; CMMD verifies signatures on use.
|
|
71272
|
+
*/
|
|
71273
|
+
/** Machine-client tokens live at most one hour; anything longer is a CMMD
|
|
71274
|
+
* contract violation that must not silently extend forge sessions. */
|
|
71275
|
+
const MAX_MACHINE_TOKEN_LIFETIME_SECONDS = 3600;
|
|
71276
|
+
/**
|
|
71277
|
+
* The minted access token's lifetime in seconds, read from its JWT `exp` and
|
|
71278
|
+
* `iat` claims without verifying the signature (the signature is checked on
|
|
71279
|
+
* every use at the guard; here the claims only measure the lifetime). `iat`
|
|
71280
|
+
* falls back to the current time when absent. Null when the payload cannot be
|
|
71281
|
+
* read, which is itself a contract violation.
|
|
71282
|
+
*/
|
|
71283
|
+
function readJwtLifetimeSeconds(accessToken) {
|
|
71284
|
+
const claims = decodeJwtPayload(accessToken);
|
|
71285
|
+
const expiresAt = claims?.["exp"];
|
|
71286
|
+
const issuedAtRaw = claims?.["iat"];
|
|
71287
|
+
if (typeof expiresAt !== "number" || !Number.isFinite(expiresAt)) return null;
|
|
71288
|
+
const issuedAt = typeof issuedAtRaw === "number" && Number.isFinite(issuedAtRaw) ? issuedAtRaw : Math.floor(Date.now() / 1e3);
|
|
71289
|
+
const now = Date.now() / 1e3;
|
|
71290
|
+
if (expiresAt <= now || issuedAt > now) return null;
|
|
71291
|
+
return expiresAt - issuedAt;
|
|
71292
|
+
}
|
|
71293
|
+
/** True when the minted token's lifetime honors the one-hour contract. A
|
|
71294
|
+
* refusal is logged here because the reason is mint-contract specific and the
|
|
71295
|
+
* caller only wants the token-or-null answer. */
|
|
71296
|
+
function mintLifetimeWithinContract(accessToken) {
|
|
71297
|
+
const lifetime = readJwtLifetimeSeconds(accessToken);
|
|
71298
|
+
if (lifetime !== null && lifetime <= MAX_MACHINE_TOKEN_LIFETIME_SECONDS) return true;
|
|
71299
|
+
console.warn(`[cmmd-auth] CMMD minted a forge token outside the one-hour contract (lifetime=${lifetime === null ? "unreadable" : `${lifetime}s`}); refusing to persist it`);
|
|
71300
|
+
return false;
|
|
71301
|
+
}
|
|
71379
71302
|
function parseCmmdTokenMintResponse(value) {
|
|
71380
71303
|
if (!value || typeof value !== "object") return null;
|
|
71381
71304
|
const accessToken = value["access_token"];
|
|
71382
71305
|
if (typeof accessToken !== "string" || accessToken.trim().length === 0) return null;
|
|
71383
|
-
|
|
71306
|
+
const trimmed = accessToken.trim();
|
|
71307
|
+
if (!mintLifetimeWithinContract(trimmed)) return null;
|
|
71308
|
+
return { accessToken: trimmed };
|
|
71384
71309
|
}
|
|
71385
71310
|
/**
|
|
71386
|
-
* How long the mint may take before the
|
|
71311
|
+
* How long the mint may take before the caller gives up on it. Minting is one
|
|
71387
71312
|
* grant lookup and one signature on CMMD's side; anything slower is treated
|
|
71388
|
-
* like a failure and the
|
|
71313
|
+
* like a failure and the resolution returns null.
|
|
71389
71314
|
*/
|
|
71390
71315
|
const MINT_TIMEOUT_MS = 1e4;
|
|
71391
71316
|
/**
|
|
71317
|
+
* The OAuth error codes that are evidence the grant itself is dead, and the
|
|
71318
|
+
* only mint refusals allowed to feed the credential-rejection ledger.
|
|
71319
|
+
*
|
|
71320
|
+
* This set is grant-specific on purpose. Per RFC 6749, `invalid_grant` names
|
|
71321
|
+
* the grant. `invalid_client` names the client (CMMD did not accept
|
|
71322
|
+
* `cmmd-forge-web`) and `invalid_request` names the request shape (a missing or
|
|
71323
|
+
* unsupported parameter, for example after a CMMD contract change); both would
|
|
71324
|
+
* fire for every session at once, so a single misconfiguration or
|
|
71325
|
+
* deploy-ordering mistake could delete every stored credential. Those codes
|
|
71326
|
+
* stay logged refusals that resolve null and leave the row in place. If CMMD's
|
|
71327
|
+
* contract ever makes one of them grant-specific, document that here before
|
|
71328
|
+
* widening this set.
|
|
71329
|
+
*/
|
|
71330
|
+
const REJECTED_OAUTH_ERROR_CODES = new Set(["invalid_grant"]);
|
|
71331
|
+
/**
|
|
71392
71332
|
* Mint a short-lived forge-scoped access token from a durable session grant
|
|
71393
|
-
* (forge#3518 expand phase).
|
|
71333
|
+
* (forge#3518 expand; the only lane since the forge#3521 contract phase).
|
|
71394
71334
|
*
|
|
71395
|
-
* The grant is non-rotating and redeemable on demand, so this is the
|
|
71396
|
-
*
|
|
71397
|
-
*
|
|
71398
|
-
*
|
|
71399
|
-
*
|
|
71335
|
+
* The grant is non-rotating and redeemable on demand, so this is the entire
|
|
71336
|
+
* refresh story: call it again when the short-lived token expires. CMMD answers
|
|
71337
|
+
* with `access_token` only (no refresh token, no MCP PAT, nothing durable), so
|
|
71338
|
+
* a session living on this lane never accumulates long-lived credential state
|
|
71339
|
+
* beyond the grant it already holds.
|
|
71400
71340
|
*
|
|
71401
71341
|
* Returns null for every failure because the resolver wants a value, not a
|
|
71402
|
-
* thrown error; the failure modes are logged by status and OAuth error code
|
|
71403
|
-
* the same way `refreshCmmdJwt` logs them. A rejection here is never fed to
|
|
71404
|
-
* the credential-rejection ledger: the ledger exists to prove a session's
|
|
71405
|
-
* *rotation* credential is dead, and the grant dying says nothing about the
|
|
71406
|
-
* refresh token the legacy fallback still owns.
|
|
71342
|
+
* thrown error; the failure modes are logged by status and OAuth error code.
|
|
71407
71343
|
*/
|
|
71408
|
-
function mintCmmdShortLivedToken(grantToken) {
|
|
71344
|
+
function mintCmmdShortLivedToken(grantToken, observer) {
|
|
71409
71345
|
return fetch(new URL("/api/mcp-server/oauth/token", resolveCommandAppUrl()).toString(), {
|
|
71410
71346
|
method: "POST",
|
|
71411
71347
|
headers: { "Content-Type": "application/json" },
|
|
@@ -71418,6 +71354,7 @@ function mintCmmdShortLivedToken(grantToken) {
|
|
|
71418
71354
|
}).then(async (response) => {
|
|
71419
71355
|
if (!response.ok) {
|
|
71420
71356
|
const errorCode = await readOAuthErrorCode(response);
|
|
71357
|
+
if (errorCode !== null && REJECTED_OAUTH_ERROR_CODES.has(errorCode)) observer?.onCredentialRejected?.();
|
|
71421
71358
|
console.warn(`[cmmd-auth] CMMD refused a forge token mint with ${response.status}${errorCode === null ? "" : ` (${errorCode})`}`);
|
|
71422
71359
|
return null;
|
|
71423
71360
|
}
|
|
@@ -71430,103 +71367,149 @@ function mintCmmdShortLivedToken(grantToken) {
|
|
|
71430
71367
|
});
|
|
71431
71368
|
}
|
|
71432
71369
|
/**
|
|
71433
|
-
* The
|
|
71434
|
-
*
|
|
71435
|
-
*
|
|
71370
|
+
* The OAuth `error` code from a refusal, lowercased, or null when there is none.
|
|
71371
|
+
*
|
|
71372
|
+
* Best effort by design: a body that is not JSON, or is already consumed, must
|
|
71373
|
+
* not turn a logged failure into an unlogged one. Null also reads correctly as
|
|
71374
|
+
* "no evidence the grant is dead", which is the safe answer for a caller
|
|
71375
|
+
* deciding whether to keep the session's stored credential.
|
|
71436
71376
|
*/
|
|
71437
|
-
function
|
|
71438
|
-
|
|
71439
|
-
|
|
71440
|
-
|
|
71441
|
-
|
|
71442
|
-
|
|
71443
|
-
}
|
|
71377
|
+
async function readOAuthErrorCode(response) {
|
|
71378
|
+
try {
|
|
71379
|
+
const code = (await response.json())["error"];
|
|
71380
|
+
return typeof code === "string" && code.trim().length > 0 ? code.trim().toLowerCase() : null;
|
|
71381
|
+
} catch {
|
|
71382
|
+
return null;
|
|
71383
|
+
}
|
|
71444
71384
|
}
|
|
71385
|
+
//#endregion
|
|
71386
|
+
//#region src/cmmdConnection/cmmdCredentialRejection.ts
|
|
71445
71387
|
/**
|
|
71446
|
-
*
|
|
71447
|
-
*
|
|
71448
|
-
* The grant is CMMD's designed recovery path for exactly the case a rotating
|
|
71449
|
-
* refresh token cannot cover: the refresh token is single-use, so one lost or
|
|
71450
|
-
* raced rotation strands the session with no way back. The grant is
|
|
71451
|
-
* NON-rotating — the same token redeems repeatedly until CMMD expires or
|
|
71452
|
-
* revokes it — so redemption is safe to retry and cannot race itself.
|
|
71388
|
+
* How long a recorded rejection stays usable.
|
|
71453
71389
|
*
|
|
71454
|
-
*
|
|
71455
|
-
*
|
|
71390
|
+
* Long enough to span the resolver ladder and the response that follows it,
|
|
71391
|
+
* short enough that it cannot outlive the credential it describes.
|
|
71456
71392
|
*/
|
|
71457
|
-
|
|
71458
|
-
|
|
71459
|
-
|
|
71460
|
-
|
|
71461
|
-
|
|
71462
|
-
|
|
71463
|
-
|
|
71464
|
-
|
|
71465
|
-
|
|
71466
|
-
|
|
71467
|
-
|
|
71468
|
-
|
|
71469
|
-
|
|
71470
|
-
|
|
71471
|
-
|
|
71472
|
-
|
|
71393
|
+
const REJECTION_TTL_MS = 6e4;
|
|
71394
|
+
const rejectedAt = /* @__PURE__ */ new Map();
|
|
71395
|
+
/** Record that CMMD refused this session's credential outright. */
|
|
71396
|
+
function noteCmmdCredentialRejected(sessionId) {
|
|
71397
|
+
rejectedAt.set(sessionId, Date.now());
|
|
71398
|
+
}
|
|
71399
|
+
/** Whether CMMD refused this session's credential recently enough to act on. */
|
|
71400
|
+
function hasCmmdCredentialRejection(sessionId) {
|
|
71401
|
+
const observedAt = rejectedAt.get(sessionId);
|
|
71402
|
+
if (observedAt === void 0) return false;
|
|
71403
|
+
if (Date.now() - observedAt <= REJECTION_TTL_MS) return true;
|
|
71404
|
+
rejectedAt.delete(sessionId);
|
|
71405
|
+
return false;
|
|
71406
|
+
}
|
|
71407
|
+
/** Drop a recorded rejection, once it has been acted on or superseded. */
|
|
71408
|
+
function forgetCmmdCredentialRejection(sessionId) {
|
|
71409
|
+
rejectedAt.delete(sessionId);
|
|
71473
71410
|
}
|
|
71474
|
-
const REJECTED_OAUTH_ERROR_CODES = new Set([
|
|
71475
|
-
"invalid_grant",
|
|
71476
|
-
"invalid_client",
|
|
71477
|
-
"invalid_request"
|
|
71478
|
-
]);
|
|
71479
71411
|
/**
|
|
71480
|
-
*
|
|
71412
|
+
* Clear a session's CMMD credentials, but only if CMMD rejected them.
|
|
71481
71413
|
*
|
|
71482
|
-
*
|
|
71483
|
-
*
|
|
71484
|
-
*
|
|
71485
|
-
* phone that had silently stopped accepting its session cost an afternoon
|
|
71486
|
-
* partly because a failed rotation here was indistinguishable from one that
|
|
71487
|
-
* never ran. The token itself is never logged; the status and the OAuth error
|
|
71488
|
-
* code are what identify the failure, and `invalid_grant` in particular means
|
|
71489
|
-
* the token was already spent, which is a different fix from a 500.
|
|
71414
|
+
* This is the only door the 401 path may use. A resolver that returned null
|
|
71415
|
+
* without a recorded rejection is a transient failure and leaves the stored
|
|
71416
|
+
* credential exactly where it is, so the next request can mint from it again.
|
|
71490
71417
|
*/
|
|
71491
|
-
function
|
|
71492
|
-
|
|
71493
|
-
|
|
71494
|
-
|
|
71495
|
-
|
|
71496
|
-
|
|
71497
|
-
|
|
71498
|
-
|
|
71499
|
-
|
|
71500
|
-
|
|
71501
|
-
|
|
71502
|
-
|
|
71503
|
-
|
|
71504
|
-
|
|
71505
|
-
|
|
71506
|
-
|
|
71507
|
-
|
|
71508
|
-
|
|
71509
|
-
|
|
71510
|
-
|
|
71511
|
-
console.warn(`[cmmd-auth] CMMD JWT rotation could not be completed: ${String(cause)}`);
|
|
71512
|
-
return null;
|
|
71418
|
+
const clearCmmdAuthAfterCmmdRejection = (sessionId) => require_Schema$1.gen(function* () {
|
|
71419
|
+
if (!hasCmmdCredentialRejection(sessionId)) return;
|
|
71420
|
+
yield* clearUnrecoverableCmmdAuthForSession(sessionId, "cmmd-rejected-credential");
|
|
71421
|
+
forgetCmmdCredentialRejection(sessionId);
|
|
71422
|
+
});
|
|
71423
|
+
//#endregion
|
|
71424
|
+
//#region src/command/cmmdJwtResolver.ts
|
|
71425
|
+
function usableCmmdUserJwt(token) {
|
|
71426
|
+
return isCmmdUserAccessJwt(token) ? token : null;
|
|
71427
|
+
}
|
|
71428
|
+
/** Log a resolver decision with session context and a fixed reason, excluding credential values. */
|
|
71429
|
+
function logCmmdJwtRung(context, rung, outcome, reason) {
|
|
71430
|
+
return (outcome === "failed" ? require_Schema$1.logWarning : require_Schema$1.logInfo)("CMMD JWT resolver rung.", {
|
|
71431
|
+
sessionId: context.sessionId,
|
|
71432
|
+
subject: context.subject,
|
|
71433
|
+
deviceType: context.deviceType,
|
|
71434
|
+
caller: context.caller,
|
|
71435
|
+
rung,
|
|
71436
|
+
outcome,
|
|
71437
|
+
reason
|
|
71513
71438
|
});
|
|
71514
71439
|
}
|
|
71440
|
+
/** In-flight mints, keyed by session. */
|
|
71441
|
+
const inFlightMints = /* @__PURE__ */ new Map();
|
|
71515
71442
|
/**
|
|
71516
|
-
*
|
|
71443
|
+
* Collapse concurrent mints for one session into a single upstream call. A
|
|
71444
|
+
* mint cannot race itself into `invalid_grant` (the grant is non-rotating),
|
|
71445
|
+
* so this is call-rate hygiene, not correctness: two tabs observing the same
|
|
71446
|
+
* expiry must not both hit CMMD when one mint would do.
|
|
71447
|
+
*/
|
|
71448
|
+
function mintOnceForSession(sessionId, grantToken) {
|
|
71449
|
+
return require_Schema$1.promise(() => {
|
|
71450
|
+
const existing = inFlightMints.get(sessionId);
|
|
71451
|
+
if (existing) return existing;
|
|
71452
|
+
const pending = mintCmmdShortLivedToken(grantToken, { onCredentialRejected: () => noteCmmdCredentialRejected(sessionId) }).catch(() => null).finally(() => {
|
|
71453
|
+
inFlightMints.delete(sessionId);
|
|
71454
|
+
});
|
|
71455
|
+
inFlightMints.set(sessionId, pending);
|
|
71456
|
+
return pending;
|
|
71457
|
+
});
|
|
71458
|
+
}
|
|
71459
|
+
/**
|
|
71460
|
+
* Resolve the CMMD REST JWT for a Forge session.
|
|
71517
71461
|
*
|
|
71518
|
-
*
|
|
71519
|
-
*
|
|
71520
|
-
*
|
|
71521
|
-
*
|
|
71462
|
+
* Forge session auth is long-lived compared with the short-lived CMMD JWT. When
|
|
71463
|
+
* the session JWT expires, mint a fresh short-lived token from the session's
|
|
71464
|
+
* durable, non-rotating grant: CMMD answers with one forge-scoped access token
|
|
71465
|
+
* (TTL at most one hour) and nothing else, and minting is the entire refresh
|
|
71466
|
+
* story (forge#3521 contract phase). There is no rotation to fence, no
|
|
71467
|
+
* single-use token to single-flight, and no sibling credential to borrow.
|
|
71468
|
+
*
|
|
71469
|
+
* Fail-closed: never returns a non-user / malformed Bearer. Callers must map
|
|
71470
|
+
* null to CMMD_AUTH_REQUIRED and must not call CMMD upstream with a bad token.
|
|
71522
71471
|
*/
|
|
71523
|
-
|
|
71524
|
-
|
|
71525
|
-
|
|
71526
|
-
|
|
71527
|
-
|
|
71528
|
-
|
|
71472
|
+
function resolveCmmdJwtForSession({ sessionId, subject, logPrefix = "cmmd-auth", forceRefresh = false, staleAccessToken = null }) {
|
|
71473
|
+
const cached = usableCmmdUserJwt(readCommandJwtForSession(sessionId));
|
|
71474
|
+
if (cached && !isJwtExpiredForSession(sessionId)) {
|
|
71475
|
+
if (!forceRefresh) return require_Schema$1.succeed(cached);
|
|
71476
|
+
if (staleAccessToken && cached !== staleAccessToken) return require_Schema$1.succeed(cached).pipe(require_Schema$1.tap(() => require_Schema$1.gen(function* () {
|
|
71477
|
+
const deviceType = yield* readClientDeviceTypeForSession(sessionId);
|
|
71478
|
+
yield* logCmmdJwtRung({
|
|
71479
|
+
sessionId,
|
|
71480
|
+
subject: subject?.trim() || null,
|
|
71481
|
+
deviceType,
|
|
71482
|
+
caller: logPrefix
|
|
71483
|
+
}, "cache", "skipped", "forced refresh skipped: cache already holds a token another caller minted");
|
|
71484
|
+
})));
|
|
71529
71485
|
}
|
|
71486
|
+
const normalizedSubject = subject?.trim() || null;
|
|
71487
|
+
if (!normalizedSubject && !forceRefresh && readCommandSessionGrantForSession(sessionId) === null) return require_Schema$1.succeed(null);
|
|
71488
|
+
return require_Schema$1.gen(function* () {
|
|
71489
|
+
const context = {
|
|
71490
|
+
sessionId,
|
|
71491
|
+
subject: normalizedSubject,
|
|
71492
|
+
deviceType: yield* readClientDeviceTypeForSession(sessionId),
|
|
71493
|
+
caller: logPrefix
|
|
71494
|
+
};
|
|
71495
|
+
const grantToken = readCommandSessionGrantForSession(sessionId);
|
|
71496
|
+
if (!grantToken) {
|
|
71497
|
+
yield* logCmmdJwtRung(context, "mint", "skipped", "no session grant stored for this session");
|
|
71498
|
+
return null;
|
|
71499
|
+
}
|
|
71500
|
+
yield* logCmmdJwtRung(context, "mint", "attempted", "minting a short-lived forge-scoped token from this session's grant");
|
|
71501
|
+
yield* require_Schema$1.update(require_Schema$1.withAttributes(cmmdTokenMintTotal, metricAttributes({ outcome: "attempted" })), 1);
|
|
71502
|
+
const accessToken = usableCmmdUserJwt((yield* mintOnceForSession(sessionId, grantToken))?.accessToken);
|
|
71503
|
+
if (!accessToken) {
|
|
71504
|
+
yield* require_Schema$1.update(require_Schema$1.withAttributes(cmmdTokenMintTotal, metricAttributes({ outcome: "failed" })), 1);
|
|
71505
|
+
yield* logCmmdJwtRung(context, "mint", "failed", "CMMD token mint did not return a usable user access JWT");
|
|
71506
|
+
return null;
|
|
71507
|
+
}
|
|
71508
|
+
yield* setCommandJwtForSession(sessionId, accessToken).pipe(require_Schema$1.ignore({ log: true }));
|
|
71509
|
+
yield* require_Schema$1.update(require_Schema$1.withAttributes(cmmdTokenMintTotal, metricAttributes({ outcome: "succeeded" })), 1);
|
|
71510
|
+
yield* logCmmdJwtRung(context, "mint", "succeeded", "CMMD minted a short-lived forge-scoped token");
|
|
71511
|
+
return accessToken;
|
|
71512
|
+
});
|
|
71530
71513
|
}
|
|
71531
71514
|
//#endregion
|
|
71532
71515
|
//#region ../../packages/shared/src/cmmdSessionPop.ts
|
|
@@ -71644,10 +71627,6 @@ function resolveCmmdSessionPopSigner() {
|
|
|
71644
71627
|
cachedSigner ??= createServerSigner();
|
|
71645
71628
|
return cachedSigner;
|
|
71646
71629
|
}
|
|
71647
|
-
/** The `pop_public_key` value to present at issuance, or null while disabled. */
|
|
71648
|
-
function cmmdSessionPopPublicKey() {
|
|
71649
|
-
return resolveCmmdSessionPopSigner()?.publicKeyPem ?? null;
|
|
71650
|
-
}
|
|
71651
71630
|
/**
|
|
71652
71631
|
* The three proof headers for one CMMD-origin request, or no headers while
|
|
71653
71632
|
* disabled. Each call carries a fresh nonce; never reuse the result across
|
|
@@ -71663,407 +71642,6 @@ async function cmmdSessionPopRequestHeaders(input) {
|
|
|
71663
71642
|
});
|
|
71664
71643
|
}
|
|
71665
71644
|
//#endregion
|
|
71666
|
-
//#region src/command/cmmdTokenMintLane.ts
|
|
71667
|
-
/**
|
|
71668
|
-
* The CMMD token-mint lane (forge#3518, expand phase).
|
|
71669
|
-
*
|
|
71670
|
-
* A new FIRST rung beside the legacy credential ladder: instead of rotating
|
|
71671
|
-
* CMMD's single-use refresh tokens, the resolver mints a short-lived
|
|
71672
|
-
* forge-scoped access token from the session's durable, non-rotating session
|
|
71673
|
-
* grant (`grant_type: forge_token_mint` on CMMD's token endpoint). The mint
|
|
71674
|
-
* spends nothing that cannot be spent again, so it cannot race itself into
|
|
71675
|
-
* `invalid_grant` the way a rotation can.
|
|
71676
|
-
*
|
|
71677
|
-
* Behind a config flag that is OFF unless explicitly enabled: production
|
|
71678
|
-
* keeps the legacy ladder as the only lane until this is switched on
|
|
71679
|
-
* deliberately. When enabled, the lane is tried before the legacy ladder;
|
|
71680
|
-
* every legacy rung (rotation fence, self-refresh, grant redemption,
|
|
71681
|
-
* bootstrap promotion, subject fallback) still exists below it unchanged and
|
|
71682
|
-
* still guards itself with the rejection ledger.
|
|
71683
|
-
*/
|
|
71684
|
-
/** Env var that enables the mint lane. Unset and anything but an affirmative value is off. */
|
|
71685
|
-
const CMMD_TOKEN_MINT_LANE_ENV = "FORGE_CMMD_TOKEN_MINT_LANE";
|
|
71686
|
-
const AFFIRMATIVE_VALUES = new Set([
|
|
71687
|
-
"1",
|
|
71688
|
-
"on",
|
|
71689
|
-
"true",
|
|
71690
|
-
"yes"
|
|
71691
|
-
]);
|
|
71692
|
-
/**
|
|
71693
|
-
* Whether the mint lane may run. Default off everywhere: a missing env var is
|
|
71694
|
-
* the production posture, and an opt-in flag must not half-default to on in
|
|
71695
|
-
* any environment a misconfigured deploy might land in.
|
|
71696
|
-
*/
|
|
71697
|
-
function isCmmdTokenMintLaneEnabled(env = process.env) {
|
|
71698
|
-
const raw = env[CMMD_TOKEN_MINT_LANE_ENV]?.trim().toLowerCase();
|
|
71699
|
-
return raw !== void 0 && AFFIRMATIVE_VALUES.has(raw);
|
|
71700
|
-
}
|
|
71701
|
-
//#endregion
|
|
71702
|
-
//#region src/command/cmmdRotationFence.ts
|
|
71703
|
-
/**
|
|
71704
|
-
* Log and count a fence application, without changing the verdict it reports.
|
|
71705
|
-
*
|
|
71706
|
-
* The two facts named in the log line are exactly the ones the predicate
|
|
71707
|
-
* above the caller already computed — whether this session ever deposited a
|
|
71708
|
-
* refresh token of its own, and (only when that was false) whether the
|
|
71709
|
-
* session row reads as a desktop bearer session. Neither fact nor the log
|
|
71710
|
-
* line changes what the caller returns.
|
|
71711
|
-
*/
|
|
71712
|
-
function logRotationFenceApplied(input) {
|
|
71713
|
-
return require_Schema$1.logInfo("CMMD rotation fence applied: session may not rotate its own credential.", {
|
|
71714
|
-
sessionId: input.sessionId,
|
|
71715
|
-
hasStoredRefreshToken: input.hasStoredRefreshToken,
|
|
71716
|
-
isDesktopBearerSession: input.isDesktopBearerSession
|
|
71717
|
-
}).pipe(require_Schema$1.andThen(() => require_Schema$1.update(require_Schema$1.withAttributes(cmmdRotationFenceAppliedTotal, metricAttributes({})), 1)));
|
|
71718
|
-
}
|
|
71719
|
-
/**
|
|
71720
|
-
* True when Forge must not rotate this session's CMMD credentials.
|
|
71721
|
-
*
|
|
71722
|
-
* Fails closed: if the session row cannot be read, assume the fence applies.
|
|
71723
|
-
* Spending a token Forge had no right to spend is worse than one extra
|
|
71724
|
-
* reconnect.
|
|
71725
|
-
*/
|
|
71726
|
-
function isRotationFencedSession(sessionId) {
|
|
71727
|
-
const hasStoredRefreshToken = readCommandRefreshTokenForSession(sessionId) !== null;
|
|
71728
|
-
if (hasStoredRefreshToken) return require_Schema$1.succeed(false);
|
|
71729
|
-
return isDesktopBearerAuthSession(sessionId).pipe(require_Schema$1.match({
|
|
71730
|
-
onFailure: () => ({
|
|
71731
|
-
fenced: true,
|
|
71732
|
-
isDesktopBearerSession: null
|
|
71733
|
-
}),
|
|
71734
|
-
onSuccess: (isDesktopBearerSession) => ({
|
|
71735
|
-
fenced: isDesktopBearerSession,
|
|
71736
|
-
isDesktopBearerSession
|
|
71737
|
-
})
|
|
71738
|
-
}), require_Schema$1.tap(({ fenced, isDesktopBearerSession }) => fenced ? logRotationFenceApplied({
|
|
71739
|
-
sessionId,
|
|
71740
|
-
hasStoredRefreshToken,
|
|
71741
|
-
isDesktopBearerSession
|
|
71742
|
-
}) : require_Schema$1.void_), require_Schema$1.map(({ fenced }) => fenced));
|
|
71743
|
-
}
|
|
71744
|
-
//#endregion
|
|
71745
|
-
//#region src/cmmdConnection/cmmdCredentialRejection.ts
|
|
71746
|
-
/**
|
|
71747
|
-
* How long a recorded rejection stays usable.
|
|
71748
|
-
*
|
|
71749
|
-
* Long enough to span the resolver ladder and the response that follows it,
|
|
71750
|
-
* short enough that it cannot outlive the credential it describes.
|
|
71751
|
-
*/
|
|
71752
|
-
const REJECTION_TTL_MS = 6e4;
|
|
71753
|
-
const rejectedAt = /* @__PURE__ */ new Map();
|
|
71754
|
-
/** Record that CMMD refused this session's credential outright. */
|
|
71755
|
-
function noteCmmdCredentialRejected(sessionId) {
|
|
71756
|
-
rejectedAt.set(sessionId, Date.now());
|
|
71757
|
-
}
|
|
71758
|
-
/** Whether CMMD refused this session's credential recently enough to act on. */
|
|
71759
|
-
function hasCmmdCredentialRejection(sessionId) {
|
|
71760
|
-
const observedAt = rejectedAt.get(sessionId);
|
|
71761
|
-
if (observedAt === void 0) return false;
|
|
71762
|
-
if (Date.now() - observedAt <= REJECTION_TTL_MS) return true;
|
|
71763
|
-
rejectedAt.delete(sessionId);
|
|
71764
|
-
return false;
|
|
71765
|
-
}
|
|
71766
|
-
/** Drop a recorded rejection, once it has been acted on or superseded. */
|
|
71767
|
-
function forgetCmmdCredentialRejection(sessionId) {
|
|
71768
|
-
rejectedAt.delete(sessionId);
|
|
71769
|
-
}
|
|
71770
|
-
/**
|
|
71771
|
-
* Clear a session's CMMD credentials, but only if CMMD rejected them.
|
|
71772
|
-
*
|
|
71773
|
-
* This is the only door the 401 path may use. A resolver that returned null
|
|
71774
|
-
* because it was not allowed to try is a policy refusal and leaves the stored
|
|
71775
|
-
* credential exactly where it is, so the next request can rotate it.
|
|
71776
|
-
*/
|
|
71777
|
-
const clearCmmdAuthAfterCmmdRejection = (sessionId) => require_Schema$1.gen(function* () {
|
|
71778
|
-
if (!hasCmmdCredentialRejection(sessionId)) return;
|
|
71779
|
-
yield* clearUnrecoverableCmmdAuthForSession(sessionId, "cmmd-rejected-credential");
|
|
71780
|
-
forgetCmmdCredentialRejection(sessionId);
|
|
71781
|
-
});
|
|
71782
|
-
//#endregion
|
|
71783
|
-
//#region src/command/cmmdJwtResolver.ts
|
|
71784
|
-
function usableCmmdUserJwt(token) {
|
|
71785
|
-
return isCmmdUserAccessJwt(token) ? token : null;
|
|
71786
|
-
}
|
|
71787
|
-
function storeResolvedJwt(sessionId, accessToken, refreshToken) {
|
|
71788
|
-
return setCommandJwtForSession(sessionId, accessToken, refreshToken ? { refreshToken } : {}).pipe(require_Schema$1.ignore({ log: true }));
|
|
71789
|
-
}
|
|
71790
|
-
function logCmmdJwtRung(context, rung, outcome, reason, lane = "legacy") {
|
|
71791
|
-
return (outcome === "failed" ? require_Schema$1.logWarning : require_Schema$1.logInfo)("CMMD JWT resolver rung.", {
|
|
71792
|
-
sessionId: context.sessionId,
|
|
71793
|
-
subject: context.subject,
|
|
71794
|
-
deviceType: context.deviceType,
|
|
71795
|
-
caller: context.caller,
|
|
71796
|
-
lane,
|
|
71797
|
-
rung,
|
|
71798
|
-
outcome,
|
|
71799
|
-
reason
|
|
71800
|
-
});
|
|
71801
|
-
}
|
|
71802
|
-
function recordCmmdCredentialRefreshMetric(rung, outcome) {
|
|
71803
|
-
return require_Schema$1.update(require_Schema$1.withAttributes(cmmdCredentialRefreshTotal, metricAttributes({
|
|
71804
|
-
rung,
|
|
71805
|
-
outcome
|
|
71806
|
-
})), 1);
|
|
71807
|
-
}
|
|
71808
|
-
const inFlightRefreshes = /* @__PURE__ */ new Map();
|
|
71809
|
-
/** In-flight mints, keyed by session, same shape as `inFlightRefreshes`. */
|
|
71810
|
-
const inFlightMints = /* @__PURE__ */ new Map();
|
|
71811
|
-
/**
|
|
71812
|
-
* Collapse concurrent mints for one session into a single upstream call. A
|
|
71813
|
-
* mint cannot race itself into `invalid_grant` (the grant is non-rotating),
|
|
71814
|
-
* so this is call-rate hygiene, not correctness: two tabs observing the same
|
|
71815
|
-
* expiry must not both hit CMMD when one mint would do.
|
|
71816
|
-
*/
|
|
71817
|
-
function mintOnceForSession(sessionId, grantToken) {
|
|
71818
|
-
return require_Schema$1.promise(() => {
|
|
71819
|
-
const existing = inFlightMints.get(sessionId);
|
|
71820
|
-
if (existing) return existing;
|
|
71821
|
-
const pending = mintCmmdShortLivedToken(grantToken).catch(() => null).finally(() => {
|
|
71822
|
-
inFlightMints.delete(sessionId);
|
|
71823
|
-
});
|
|
71824
|
-
inFlightMints.set(sessionId, pending);
|
|
71825
|
-
return pending;
|
|
71826
|
-
});
|
|
71827
|
-
}
|
|
71828
|
-
function refreshOnceForSession(sessionId, refreshToken) {
|
|
71829
|
-
return require_Schema$1.promise(() => {
|
|
71830
|
-
const existing = inFlightRefreshes.get(sessionId);
|
|
71831
|
-
if (existing) return existing;
|
|
71832
|
-
const pending = refreshCmmdJwt(refreshToken, { onCredentialRejected: () => noteCmmdCredentialRejected(sessionId) }).catch(() => null).finally(() => {
|
|
71833
|
-
inFlightRefreshes.delete(sessionId);
|
|
71834
|
-
});
|
|
71835
|
-
inFlightRefreshes.set(sessionId, pending);
|
|
71836
|
-
return pending;
|
|
71837
|
-
});
|
|
71838
|
-
}
|
|
71839
|
-
/**
|
|
71840
|
-
* Rotate this session's **own** CMMD refresh token.
|
|
71841
|
-
*
|
|
71842
|
-
* CMMD rotates refresh tokens single-use — the presented hash is revoked inside
|
|
71843
|
-
* the rotation transaction (CMMD `server/jwt-auth.ts`). A session must
|
|
71844
|
-
* therefore only ever present the refresh token it was issued: rotating a
|
|
71845
|
-
* sibling session's token silently invalidates that sibling's stored copy and
|
|
71846
|
-
* signs it out.
|
|
71847
|
-
*/
|
|
71848
|
-
function trySelfRefresh(sessionId, context) {
|
|
71849
|
-
const currentRefreshToken = readCommandRefreshTokenForSession(sessionId);
|
|
71850
|
-
if (!currentRefreshToken) return logCmmdJwtRung(context, "self-refresh", "skipped", "no refresh token stored for this session").pipe(require_Schema$1.as(null));
|
|
71851
|
-
return require_Schema$1.gen(function* () {
|
|
71852
|
-
yield* logCmmdJwtRung(context, "self-refresh", "attempted", "rotating this session's own stored refresh token");
|
|
71853
|
-
yield* recordCmmdCredentialRefreshMetric("self-refresh", "attempted");
|
|
71854
|
-
const refreshed = yield* refreshOnceForSession(sessionId, currentRefreshToken);
|
|
71855
|
-
const accessToken = usableCmmdUserJwt(refreshed?.accessToken);
|
|
71856
|
-
if (!accessToken) {
|
|
71857
|
-
yield* logCmmdJwtRung(context, "self-refresh", "failed", "CMMD refresh did not return a usable user access JWT");
|
|
71858
|
-
return null;
|
|
71859
|
-
}
|
|
71860
|
-
yield* storeResolvedJwt(sessionId, accessToken, refreshed?.refreshToken);
|
|
71861
|
-
yield* recordCmmdCredentialRefreshMetric("self-refresh", "succeeded");
|
|
71862
|
-
yield* logCmmdJwtRung(context, "self-refresh", "succeeded", "CMMD rotated the stored refresh token");
|
|
71863
|
-
return accessToken;
|
|
71864
|
-
});
|
|
71865
|
-
}
|
|
71866
|
-
/**
|
|
71867
|
-
* The mint lane's rung (forge#3518, expand phase): mint a short-lived
|
|
71868
|
-
* forge-scoped access token from this session's durable session grant.
|
|
71869
|
-
*
|
|
71870
|
-
* Tried FIRST, before the rotation fence and the whole legacy ladder, when
|
|
71871
|
-
* `FORGE_CMMD_TOKEN_MINT_LANE` is on. Minting spends nothing that cannot be
|
|
71872
|
-
* spent again (the grant is non-rotating), so unlike the legacy rungs it is
|
|
71873
|
-
* safe for a session the rotation fence fences off, and it never feeds the
|
|
71874
|
-
* credential-rejection ledger: this rung failing says nothing about whether
|
|
71875
|
-
* the legacy rungs below it can still succeed.
|
|
71876
|
-
*
|
|
71877
|
-
* Only the JWT is persisted (`setCommandJwtForSession`, no refresh token):
|
|
71878
|
-
* the short lifetime is the expiry story, and re-minting is the refresh
|
|
71879
|
-
* story. A session that lives entirely on this lane accumulates no new
|
|
71880
|
-
* long-lived credential state beyond the grant it already had.
|
|
71881
|
-
*/
|
|
71882
|
-
function tryMintShortLivedToken(sessionId, context) {
|
|
71883
|
-
const grantToken = readCommandSessionGrantForSession(sessionId);
|
|
71884
|
-
if (!grantToken) return logCmmdJwtRung(context, "mint", "skipped", "no session grant stored for this session", "mint").pipe(require_Schema$1.as(null));
|
|
71885
|
-
return require_Schema$1.gen(function* () {
|
|
71886
|
-
yield* logCmmdJwtRung(context, "mint", "attempted", "minting a short-lived forge-scoped token from this session's grant", "mint");
|
|
71887
|
-
yield* require_Schema$1.update(require_Schema$1.withAttributes(cmmdTokenMintTotal, metricAttributes({ outcome: "attempted" })), 1);
|
|
71888
|
-
const accessToken = usableCmmdUserJwt((yield* mintOnceForSession(sessionId, grantToken))?.accessToken);
|
|
71889
|
-
if (!accessToken) {
|
|
71890
|
-
yield* logCmmdJwtRung(context, "mint", "failed", "CMMD token mint did not return a usable user access JWT", "mint");
|
|
71891
|
-
return null;
|
|
71892
|
-
}
|
|
71893
|
-
yield* setCommandJwtForSession(sessionId, accessToken, {}).pipe(require_Schema$1.ignore({ log: true }));
|
|
71894
|
-
yield* require_Schema$1.update(require_Schema$1.withAttributes(cmmdTokenMintTotal, metricAttributes({ outcome: "succeeded" })), 1);
|
|
71895
|
-
yield* logCmmdJwtRung(context, "mint", "succeeded", "CMMD minted a short-lived forge-scoped token", "mint");
|
|
71896
|
-
return accessToken;
|
|
71897
|
-
});
|
|
71898
|
-
}
|
|
71899
|
-
/**
|
|
71900
|
-
* Last-resort recovery: redeem this session's durable CMMD session grant.
|
|
71901
|
-
*
|
|
71902
|
-
* A refresh token is single-use, so a session that lost a rotation — raced, or
|
|
71903
|
-
* stranded by a redeploy mid-flight — has no way back and the user is bounced
|
|
71904
|
-
* to /login. CMMD mints a long-lived, non-rotating grant at login for exactly
|
|
71905
|
-
* this, and redemption returns a fresh JWT, refresh token AND MCP PAT.
|
|
71906
|
-
*
|
|
71907
|
-
* Only reached after the session's own refresh has already failed, so this
|
|
71908
|
-
* never competes with a live rotation. The grant is not cleared on failure:
|
|
71909
|
-
* CMMD owns its lifetime, and a transient network failure must not throw away
|
|
71910
|
-
* the one credential that can still recover the session.
|
|
71911
|
-
*/
|
|
71912
|
-
function tryGrantRedemption(sessionId, context) {
|
|
71913
|
-
const grantToken = readCommandSessionGrantForSession(sessionId);
|
|
71914
|
-
if (!grantToken) return logCmmdJwtRung(context, "grant-redemption", "skipped", "no session grant stored for this session").pipe(require_Schema$1.as(null));
|
|
71915
|
-
return require_Schema$1.gen(function* () {
|
|
71916
|
-
yield* logCmmdJwtRung(context, "grant-redemption", "attempted", "redeeming this session's durable CMMD session grant");
|
|
71917
|
-
yield* recordCmmdCredentialRefreshMetric("grant-redemption", "attempted");
|
|
71918
|
-
const redeemed = yield* require_Schema$1.promise(() => redeemCmmdSessionGrant(grantToken, cmmdSessionPopPublicKey())).pipe(require_Schema$1.orElseSucceed(() => null));
|
|
71919
|
-
const accessToken = usableCmmdUserJwt(redeemed?.accessToken);
|
|
71920
|
-
if (!accessToken) {
|
|
71921
|
-
yield* logCmmdJwtRung(context, "grant-redemption", "failed", "session grant redemption did not return a usable user access JWT");
|
|
71922
|
-
return null;
|
|
71923
|
-
}
|
|
71924
|
-
if (redeemed?.mcpToken) yield* setCommandAccessTokenForSession(sessionId, redeemed.mcpToken).pipe(require_Schema$1.ignore({ log: true }));
|
|
71925
|
-
yield* storeResolvedJwt(sessionId, accessToken, redeemed?.refreshToken);
|
|
71926
|
-
yield* recordCmmdCredentialRefreshMetric("grant-redemption", "succeeded");
|
|
71927
|
-
yield* logCmmdJwtRung(context, "grant-redemption", "succeeded", "session grant redeemed");
|
|
71928
|
-
return accessToken;
|
|
71929
|
-
});
|
|
71930
|
-
}
|
|
71931
|
-
/**
|
|
71932
|
-
* Recover using only credentials this session owns, strongest first.
|
|
71933
|
-
*
|
|
71934
|
-
* Rotating its own refresh token is preferred; the durable grant is the
|
|
71935
|
-
* fallback for when that rotation is already gone. Neither touches another
|
|
71936
|
-
* session's credentials, so this ladder is safe to run from any path.
|
|
71937
|
-
*/
|
|
71938
|
-
function tryOwnCredentialRecovery(sessionId, context) {
|
|
71939
|
-
return require_Schema$1.gen(function* () {
|
|
71940
|
-
const refreshed = yield* trySelfRefresh(sessionId, context);
|
|
71941
|
-
if (refreshed) return refreshed;
|
|
71942
|
-
return yield* tryGrantRedemption(sessionId, context);
|
|
71943
|
-
});
|
|
71944
|
-
}
|
|
71945
|
-
function tryPromoteBootstrap(sessionId, subject, forceRefresh, context) {
|
|
71946
|
-
if (subject !== "owner-bootstrap" && subject !== "desktop-bootstrap") return logCmmdJwtRung(context, "promote-bootstrap", "skipped", "subject is not a bootstrap subject").pipe(require_Schema$1.as(null));
|
|
71947
|
-
return require_Schema$1.gen(function* () {
|
|
71948
|
-
yield* logCmmdJwtRung(context, "promote-bootstrap", "attempted", "promoting the most recent owner CMMD auth to this bootstrap session");
|
|
71949
|
-
yield* promoteMostRecentOwnerCommandAuthToSession(sessionId).pipe(require_Schema$1.catch_((error) => require_Schema$1.logWarning("Failed to promote owner CMMD auth for REST JWT resolution.", {
|
|
71950
|
-
cause: error,
|
|
71951
|
-
sessionId,
|
|
71952
|
-
subject
|
|
71953
|
-
})));
|
|
71954
|
-
const promotedJwt = usableCmmdUserJwt(readCommandJwtForSession(sessionId));
|
|
71955
|
-
if (!forceRefresh && promotedJwt && !isJwtExpiredForSession(sessionId)) {
|
|
71956
|
-
yield* logCmmdJwtRung(context, "promote-bootstrap", "succeeded", "promoted owner JWT is usable");
|
|
71957
|
-
return promotedJwt;
|
|
71958
|
-
}
|
|
71959
|
-
yield* logCmmdJwtRung(context, "promote-bootstrap", "failed", forceRefresh ? "forced refresh: a promoted JWT is never reused as-is" : "no promoted JWT available, or it is already expired");
|
|
71960
|
-
return null;
|
|
71961
|
-
});
|
|
71962
|
-
}
|
|
71963
|
-
/**
|
|
71964
|
-
* Borrow a sibling session's still-valid CMMD access JWT.
|
|
71965
|
-
*
|
|
71966
|
-
* `readMostRecentJwtForSubject` only returns a JWT that has not expired, so
|
|
71967
|
-
* this is a cheap read-only recovery that avoids an upstream rotation. The
|
|
71968
|
-
* sibling's *refresh* token is deliberately NOT copied: it is single-use and
|
|
71969
|
-
* must stay private to the session it was issued to (see `trySelfRefresh`).
|
|
71970
|
-
* Copying it is what let one expired tab sign the user out everywhere.
|
|
71971
|
-
*/
|
|
71972
|
-
function trySubjectFallback(sessionId, subject, context) {
|
|
71973
|
-
return require_Schema$1.gen(function* () {
|
|
71974
|
-
yield* logCmmdJwtRung(context, "subject-fallback", "attempted", "looking for a sibling session's still-valid JWT for this subject");
|
|
71975
|
-
const fallbackJwt = usableCmmdUserJwt((yield* readMostRecentJwtForSubject(subject).pipe(require_Schema$1.orElseSucceed(() => null)))?.jwt);
|
|
71976
|
-
if (!fallbackJwt) {
|
|
71977
|
-
yield* logCmmdJwtRung(context, "subject-fallback", "failed", "no sibling session holds a still-valid JWT for this subject");
|
|
71978
|
-
return null;
|
|
71979
|
-
}
|
|
71980
|
-
yield* storeResolvedJwt(sessionId, fallbackJwt, null);
|
|
71981
|
-
yield* logCmmdJwtRung(context, "subject-fallback", "succeeded", "borrowed a sibling session's still-valid JWT");
|
|
71982
|
-
return fallbackJwt;
|
|
71983
|
-
});
|
|
71984
|
-
}
|
|
71985
|
-
/**
|
|
71986
|
-
* The rungs below the rotation fence: forced own-credential recovery,
|
|
71987
|
-
* bootstrap promotion, subject fallback, then own-credential recovery again
|
|
71988
|
-
* as the last resort. Split out of `resolveCmmdJwtForSession` so each half of
|
|
71989
|
-
* the ladder stays independently readable (and independently testable) —
|
|
71990
|
-
* the fence check above is the one decision the split does not touch.
|
|
71991
|
-
*/
|
|
71992
|
-
function runRecoveryLadder(sessionId, normalizedSubject, forceRefresh, context) {
|
|
71993
|
-
return require_Schema$1.gen(function* () {
|
|
71994
|
-
if (forceRefresh) {
|
|
71995
|
-
const forced = yield* tryOwnCredentialRecovery(sessionId, context);
|
|
71996
|
-
if (forced) return forced;
|
|
71997
|
-
}
|
|
71998
|
-
if (normalizedSubject) {
|
|
71999
|
-
const promoted = yield* tryPromoteBootstrap(sessionId, normalizedSubject, forceRefresh, context);
|
|
72000
|
-
if (promoted) return promoted;
|
|
72001
|
-
}
|
|
72002
|
-
if (!normalizedSubject) {
|
|
72003
|
-
yield* logCmmdJwtRung(context, "cache", "failed", "no subject to resolve and forced recovery already exhausted");
|
|
72004
|
-
return null;
|
|
72005
|
-
}
|
|
72006
|
-
if (!forceRefresh) {
|
|
72007
|
-
const fallback = yield* trySubjectFallback(sessionId, normalizedSubject, context);
|
|
72008
|
-
if (fallback) return fallback;
|
|
72009
|
-
}
|
|
72010
|
-
const recovered = yield* tryOwnCredentialRecovery(sessionId, context);
|
|
72011
|
-
if (!recovered) yield* logCmmdJwtRung(context, "cache", "failed", "recovery ladder exhausted: no rung produced a usable JWT");
|
|
72012
|
-
return recovered;
|
|
72013
|
-
});
|
|
72014
|
-
}
|
|
72015
|
-
/**
|
|
72016
|
-
* Resolve the CMMD REST JWT for a Forge session.
|
|
72017
|
-
*
|
|
72018
|
-
* Forge session auth is long-lived compared with the short-lived CMMD JWT.
|
|
72019
|
-
* When the session JWT expires, recover by minting a fresh short-lived token
|
|
72020
|
-
* from the session grant (the mint lane; default-off, forge#3518), or, on
|
|
72021
|
-
* the legacy lane, by promoting a valid subject-level JWT or rotating the
|
|
72022
|
-
* stored CMMD refresh token.
|
|
72023
|
-
*
|
|
72024
|
-
* Fail-closed: never returns a non-user / malformed Bearer. Callers must map
|
|
72025
|
-
* null to CMMD_AUTH_REQUIRED and must not call CMMD upstream with a bad token.
|
|
72026
|
-
*/
|
|
72027
|
-
function resolveCmmdJwtForSession({ sessionId, subject, logPrefix = "cmmd-auth", forceRefresh = false, staleAccessToken = null }) {
|
|
72028
|
-
const cached = usableCmmdUserJwt(readCommandJwtForSession(sessionId));
|
|
72029
|
-
if (cached && !isJwtExpiredForSession(sessionId)) {
|
|
72030
|
-
if (!forceRefresh) return require_Schema$1.succeed(cached);
|
|
72031
|
-
if (staleAccessToken && cached !== staleAccessToken) return require_Schema$1.succeed(cached).pipe(require_Schema$1.tap(() => require_Schema$1.gen(function* () {
|
|
72032
|
-
const deviceType = yield* readClientDeviceTypeForSession(sessionId);
|
|
72033
|
-
yield* logCmmdJwtRung({
|
|
72034
|
-
sessionId,
|
|
72035
|
-
subject: subject?.trim() || null,
|
|
72036
|
-
deviceType,
|
|
72037
|
-
caller: logPrefix
|
|
72038
|
-
}, "cache", "skipped", "forced refresh skipped: cache already holds a token another caller rotated");
|
|
72039
|
-
})));
|
|
72040
|
-
}
|
|
72041
|
-
const normalizedSubject = subject?.trim() || null;
|
|
72042
|
-
const mintLaneEnabled = isCmmdTokenMintLaneEnabled();
|
|
72043
|
-
if (!normalizedSubject && !forceRefresh && !mintLaneEnabled) return require_Schema$1.succeed(null);
|
|
72044
|
-
return require_Schema$1.gen(function* () {
|
|
72045
|
-
const context = {
|
|
72046
|
-
sessionId,
|
|
72047
|
-
subject: normalizedSubject,
|
|
72048
|
-
deviceType: yield* readClientDeviceTypeForSession(sessionId),
|
|
72049
|
-
caller: logPrefix
|
|
72050
|
-
};
|
|
72051
|
-
if (mintLaneEnabled) {
|
|
72052
|
-
const minted = yield* tryMintShortLivedToken(sessionId, context);
|
|
72053
|
-
if (minted) return minted;
|
|
72054
|
-
if (!normalizedSubject && !forceRefresh) {
|
|
72055
|
-
yield* logCmmdJwtRung(context, "mint", "failed", "no subject to resolve and the mint lane produced nothing", "mint");
|
|
72056
|
-
return null;
|
|
72057
|
-
}
|
|
72058
|
-
}
|
|
72059
|
-
if (yield* isRotationFencedSession(sessionId)) {
|
|
72060
|
-
yield* logCmmdJwtRung(context, "rotation-fence", "failed", "session is fenced from rotating CMMD credentials");
|
|
72061
|
-
return null;
|
|
72062
|
-
}
|
|
72063
|
-
return yield* runRecoveryLadder(sessionId, normalizedSubject, forceRefresh, context);
|
|
72064
|
-
});
|
|
72065
|
-
}
|
|
72066
|
-
//#endregion
|
|
72067
71645
|
//#region src/cmmdData/cmmdRestProxyDeadline.ts
|
|
72068
71646
|
var CmmdProxyTimeoutError = class extends Error {
|
|
72069
71647
|
status;
|
|
@@ -102706,6 +102284,22 @@ var WorkspaceFileSystem = class extends require_Schema$1.Service()("forge/worksp
|
|
|
102706
102284
|
//#region src/workspace/Services/WorkspaceBulkTransfer.ts
|
|
102707
102285
|
var WorkspaceBulkTransfer = class extends require_Schema$1.Service()("forge/workspace/Services/WorkspaceBulkTransfer") {};
|
|
102708
102286
|
//#endregion
|
|
102287
|
+
//#region src/workspace/Services/WorkspaceDirectoryListing.ts
|
|
102288
|
+
/**
|
|
102289
|
+
* WorkspaceDirectoryListing - Effect service contract for one directory's
|
|
102290
|
+
* immediate children.
|
|
102291
|
+
*
|
|
102292
|
+
* Owns workspace-root-confined directory listings for the Files tree: the
|
|
102293
|
+
* caller names a workspace root (the thread's checkout) and a root-relative
|
|
102294
|
+
* directory, and gets that directory's children back, one readdir deep.
|
|
102295
|
+
*
|
|
102296
|
+
* @module WorkspaceDirectoryListing
|
|
102297
|
+
*/
|
|
102298
|
+
/**
|
|
102299
|
+
* WorkspaceDirectoryListing - Service tag for workspace directory listings.
|
|
102300
|
+
*/
|
|
102301
|
+
var WorkspaceDirectoryListing = class extends require_Schema$1.Service()("forge/workspace/Services/WorkspaceDirectoryListing") {};
|
|
102302
|
+
//#endregion
|
|
102709
102303
|
//#region src/workspace/Services/WorkspaceReadinessState.ts
|
|
102710
102304
|
var WorkspaceReadinessState = class extends require_Schema$1.Service()("forge/workspace/Services/WorkspaceReadinessState") {};
|
|
102711
102305
|
//#endregion
|
|
@@ -108119,6 +107713,7 @@ const makeWsRpcLayer = (currentSessionId, currentUserId) => WsRpcGroup.toLayer(r
|
|
|
108119
107713
|
const discoverSourceControl = yield* makeCachedSourceControlDiscovery;
|
|
108120
107714
|
const workspaceFileSystem = yield* WorkspaceFileSystem;
|
|
108121
107715
|
const workspaceBulkTransfer = yield* WorkspaceBulkTransfer;
|
|
107716
|
+
const workspaceDirectoryListing = yield* WorkspaceDirectoryListing;
|
|
108122
107717
|
const projectCheckoutDeletion = yield* require_Schema$1.serviceOption(ProjectCheckoutDeletion);
|
|
108123
107718
|
const workspaceReadinessState = yield* WorkspaceReadinessState;
|
|
108124
107719
|
const projectSetupScriptRunner = yield* ProjectSetupScriptRunner;
|
|
@@ -108468,6 +108063,7 @@ const makeWsRpcLayer = (currentSessionId, currentUserId) => WsRpcGroup.toLayer(r
|
|
|
108468
108063
|
blockers: ["deletion_authority_unavailable"]
|
|
108469
108064
|
}), { "rpc.aggregate": "workspace" }),
|
|
108470
108065
|
[WS_METHODS.workspaceReadFile]: (input) => observeRpcStream(WS_METHODS.workspaceReadFile, workspaceBulkTransfer.readFile(input), { "rpc.aggregate": "workspace" }),
|
|
108066
|
+
[WS_METHODS.workspaceListDirectory]: (input) => observeRpcEffect(WS_METHODS.workspaceListDirectory, workspaceDirectoryListing.list(input), { "rpc.aggregate": "workspace" }),
|
|
108471
108067
|
[WS_METHODS.workspaceBeginWriteFile]: (input) => observeRpcEffect(WS_METHODS.workspaceBeginWriteFile, workspaceBulkTransfer.beginWrite(input), { "rpc.aggregate": "workspace" }),
|
|
108472
108068
|
[WS_METHODS.workspaceWriteFileChunk]: (input) => observeRpcEffect(WS_METHODS.workspaceWriteFileChunk, workspaceBulkTransfer.writeChunk(input), { "rpc.aggregate": "workspace" }),
|
|
108473
108069
|
[WS_METHODS.workspaceCommitWriteFile]: (input) => observeRpcEffect(WS_METHODS.workspaceCommitWriteFile, workspaceBulkTransfer.commitWrite(input), { "rpc.aggregate": "workspace" }),
|
|
@@ -116285,7 +115881,7 @@ const postgresSchemaStatements = [
|
|
|
116285
115881
|
"CREATE TABLE IF NOT EXISTS projection_thread_proposed_plans (\n plan_id TEXT PRIMARY KEY,\n thread_id TEXT NOT NULL,\n turn_id TEXT,\n plan_markdown TEXT NOT NULL,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL\n , implemented_at TEXT, implementation_thread_id TEXT)",
|
|
116286
115882
|
"CREATE TABLE IF NOT EXISTS auth_pairing_links (\n id TEXT PRIMARY KEY,\n credential TEXT NOT NULL UNIQUE,\n method TEXT NOT NULL,\n role TEXT NOT NULL,\n subject TEXT NOT NULL,\n created_at TEXT NOT NULL,\n expires_at TEXT NOT NULL,\n consumed_at TEXT,\n revoked_at TEXT\n , label TEXT, credential_lookup TEXT)",
|
|
116287
115883
|
"CREATE TABLE IF NOT EXISTS auth_sessions (\n session_id TEXT PRIMARY KEY,\n subject TEXT NOT NULL,\n role TEXT NOT NULL,\n method TEXT NOT NULL,\n issued_at TEXT NOT NULL,\n expires_at TEXT NOT NULL,\n revoked_at TEXT\n , client_label TEXT, client_ip_address TEXT, client_user_agent TEXT, client_device_type TEXT NOT NULL DEFAULT 'unknown', client_os TEXT, client_browser TEXT, last_connected_at TEXT)",
|
|
116288
|
-
"CREATE TABLE IF NOT EXISTS command_access_tokens (\n session_id TEXT PRIMARY KEY,\n access_token TEXT NOT NULL,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL\n , jwt_token TEXT, jwt_expires_at TEXT,
|
|
115884
|
+
"CREATE TABLE IF NOT EXISTS command_access_tokens (\n session_id TEXT PRIMARY KEY,\n access_token TEXT NOT NULL,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL\n , jwt_token TEXT, jwt_expires_at TEXT, cmmd_user_identity TEXT, session_grant TEXT, session_grant_expires_at TEXT)",
|
|
116289
115885
|
"CREATE TABLE IF NOT EXISTS comms_accounts (\n id TEXT PRIMARY KEY,\n provider TEXT NOT NULL,\n email TEXT NOT NULL,\n display_name TEXT,\n composio_entity_id TEXT NOT NULL,\n composio_connected_account_id TEXT NOT NULL,\n is_active INTEGER NOT NULL DEFAULT 1,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL\n )",
|
|
116290
115886
|
"CREATE TABLE IF NOT EXISTS comms_messages (\n id TEXT PRIMARY KEY,\n provider TEXT NOT NULL,\n provider_message_id TEXT NOT NULL,\n provider_thread_id TEXT,\n account_id TEXT NOT NULL REFERENCES comms_accounts(id),\n direction TEXT NOT NULL DEFAULT 'inbound',\n from_address TEXT NOT NULL,\n from_name TEXT,\n to_addresses TEXT NOT NULL DEFAULT '[]',\n cc_addresses TEXT,\n subject TEXT,\n body_text TEXT,\n body_html TEXT,\n labels TEXT,\n sent_at TEXT NOT NULL,\n received_at TEXT,\n is_read INTEGER NOT NULL DEFAULT 0,\n is_starred INTEGER NOT NULL DEFAULT 0,\n has_attachments INTEGER NOT NULL DEFAULT 0,\n raw_payload TEXT,\n synced_at TEXT NOT NULL,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL\n )",
|
|
116291
115887
|
"CREATE TABLE IF NOT EXISTS comms_sync_cursors (\n id TEXT PRIMARY KEY,\n provider TEXT NOT NULL,\n account_id TEXT NOT NULL REFERENCES comms_accounts(id),\n cursor_type TEXT NOT NULL,\n cursor_value TEXT NOT NULL,\n last_sync_at TEXT NOT NULL,\n last_sync_status TEXT NOT NULL DEFAULT 'success',\n last_error TEXT,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL\n )",
|
|
@@ -116598,7 +116194,8 @@ const postgresUpgradeMigrations = new Map([
|
|
|
116598
116194
|
WHERE model_selection_json IS NULL`
|
|
116599
116195
|
]],
|
|
116600
116196
|
[77, [`ALTER TABLE projection_threads ADD COLUMN IF NOT EXISTS work_session_id TEXT`]],
|
|
116601
|
-
[78, [`ALTER TABLE projection_threads ADD COLUMN IF NOT EXISTS work_session_bound_at TEXT`]]
|
|
116197
|
+
[78, [`ALTER TABLE projection_threads ADD COLUMN IF NOT EXISTS work_session_bound_at TEXT`]],
|
|
116198
|
+
[79, [`ALTER TABLE command_access_tokens DROP COLUMN IF EXISTS refresh_token`]]
|
|
116602
116199
|
]);
|
|
116603
116200
|
function requirePostgresUpgradeStatements(version) {
|
|
116604
116201
|
const statements = postgresUpgradeMigrations.get(version);
|
|
@@ -116624,15 +116221,15 @@ const runPostgresMigrations = require_Schema$1.fn("runPostgresMigrations")(funct
|
|
|
116624
116221
|
ORDER BY version DESC
|
|
116625
116222
|
LIMIT 1
|
|
116626
116223
|
`)[0]?.version ?? 0;
|
|
116627
|
-
if (currentVersion >
|
|
116224
|
+
if (currentVersion > 79) return yield* require_Schema$1.die(/* @__PURE__ */ new Error(`Forge Postgres schema ${currentVersion} is newer than server schema 79.`));
|
|
116628
116225
|
if (currentVersion === 0) {
|
|
116629
116226
|
for (const statement of postgresSchemaStatements) yield* sql.unsafe(statement);
|
|
116630
116227
|
yield* sql`
|
|
116631
116228
|
INSERT INTO forge_schema_migrations (version)
|
|
116632
|
-
VALUES (${
|
|
116229
|
+
VALUES (${79})
|
|
116633
116230
|
ON CONFLICT (version) DO NOTHING
|
|
116634
116231
|
`;
|
|
116635
|
-
} else for (let version = currentVersion + 1; version <=
|
|
116232
|
+
} else for (let version = currentVersion + 1; version <= 79; version += 1) {
|
|
116636
116233
|
const statements = yield* require_Schema$1.sync(() => requirePostgresUpgradeStatements(version));
|
|
116637
116234
|
for (const statement of statements) yield* sql.unsafe(statement);
|
|
116638
116235
|
yield* sql`
|
|
@@ -119441,6 +119038,26 @@ var _078_ProjectionThreadWorkSessionBoundAt_default = require_Schema$1.gen(funct
|
|
|
119441
119038
|
`;
|
|
119442
119039
|
});
|
|
119443
119040
|
//#endregion
|
|
119041
|
+
//#region src/persistence/Migrations/079_DropCommandRefreshToken.ts
|
|
119042
|
+
/**
|
|
119043
|
+
* Drop the single-use CMMD refresh token from the session credential row.
|
|
119044
|
+
*
|
|
119045
|
+
* The rotation it powered is gone. forge#3518 added the CMMD mint lane, and
|
|
119046
|
+
* forge#3521 deleted the legacy ladder that sat beside it: the rotation fence,
|
|
119047
|
+
* the refresh single-flight, self-refresh, grant redemption, the subject
|
|
119048
|
+
* fallback, and the desktop refresh sweep. The resolver now mints a short-lived
|
|
119049
|
+
* JWT from the row's durable, non-rotating session grant, so a credential whose
|
|
119050
|
+
* every use invalidates it has nothing left to do on this row.
|
|
119051
|
+
*
|
|
119052
|
+
* Nothing is copied out first. Every surviving read of the column was deleted
|
|
119053
|
+
* in the same change; the value is a dead secret, and the kind of secret best
|
|
119054
|
+
* kept nowhere. Historical installs that ran migration 034 need this to run
|
|
119055
|
+
* after it, not instead of it.
|
|
119056
|
+
*/
|
|
119057
|
+
var _079_DropCommandRefreshToken_default = require_Schema$1.gen(function* () {
|
|
119058
|
+
yield* (yield* require_SqlError.SqlClient)`ALTER TABLE command_access_tokens DROP COLUMN refresh_token`;
|
|
119059
|
+
});
|
|
119060
|
+
//#endregion
|
|
119444
119061
|
//#region src/persistence/Migrations.ts
|
|
119445
119062
|
/**
|
|
119446
119063
|
* MigrationsLive - Migration runner with inline loader
|
|
@@ -119851,6 +119468,11 @@ const migrationEntries = [
|
|
|
119851
119468
|
78,
|
|
119852
119469
|
"ProjectionThreadWorkSessionBoundAt",
|
|
119853
119470
|
_078_ProjectionThreadWorkSessionBoundAt_default
|
|
119471
|
+
],
|
|
119472
|
+
[
|
|
119473
|
+
79,
|
|
119474
|
+
"DropCommandRefreshToken",
|
|
119475
|
+
_079_DropCommandRefreshToken_default
|
|
119854
119476
|
]
|
|
119855
119477
|
];
|
|
119856
119478
|
const makeMigrationLoader = (throughId) => fromRecord(Object.fromEntries(migrationEntries.filter(([id]) => throughId === void 0 || id <= throughId).map(([id, name, migration]) => [`${id}_${name}`, migration])));
|
|
@@ -298999,6 +298621,106 @@ const makeWorkspaceBulkTransfer = (options = {}) => require_Schema$1.gen(functio
|
|
|
298999
298621
|
});
|
|
299000
298622
|
const WorkspaceBulkTransferLive = require_Schema$1.effect(WorkspaceBulkTransfer, makeWorkspaceBulkTransfer());
|
|
299001
298623
|
//#endregion
|
|
298624
|
+
//#region src/workspace/Layers/WorkspaceDirectoryListing.ts
|
|
298625
|
+
function isPathWithin(path, root, candidate) {
|
|
298626
|
+
const relative = path.relative(root, candidate);
|
|
298627
|
+
return relative.length === 0 || !relative.startsWith("..") && !path.isAbsolute(relative);
|
|
298628
|
+
}
|
|
298629
|
+
/**
|
|
298630
|
+
* Directories sort before files so the tree reads like every other file tree;
|
|
298631
|
+
* names compare case-insensitively, then by raw name so stable order survives
|
|
298632
|
+
* a platform that folds case differently.
|
|
298633
|
+
*/
|
|
298634
|
+
function compareEntries(left, right) {
|
|
298635
|
+
if (left.kind !== right.kind) return left.kind === "directory" ? -1 : 1;
|
|
298636
|
+
const byName = left.name.localeCompare(right.name, void 0, { sensitivity: "accent" });
|
|
298637
|
+
return byName !== 0 ? byName : left.name < right.name ? -1 : left.name > right.name ? 1 : 0;
|
|
298638
|
+
}
|
|
298639
|
+
const makeWorkspaceDirectoryListing = require_Schema$1.gen(function* () {
|
|
298640
|
+
const fileSystem = yield* require_Path.FileSystem;
|
|
298641
|
+
const path = yield* require_Path.Path;
|
|
298642
|
+
const workspacePaths = yield* WorkspacePaths;
|
|
298643
|
+
return { list: require_Schema$1.fn("WorkspaceDirectoryListing.list")(function* (input) {
|
|
298644
|
+
const workspaceRoot = yield* workspacePaths.normalizeWorkspaceRoot(input.cwd).pipe(require_Schema$1.mapError(() => new WorkspaceListDirectoryError({
|
|
298645
|
+
cwd: input.cwd,
|
|
298646
|
+
relativePath: input.relativePath,
|
|
298647
|
+
failure: "workspace_root_invalid",
|
|
298648
|
+
detail: `Workspace root is not a usable directory: ${input.cwd}`
|
|
298649
|
+
})));
|
|
298650
|
+
const trimmed = input.relativePath.trim();
|
|
298651
|
+
if (path.isAbsolute(trimmed)) return yield* new WorkspaceListDirectoryError({
|
|
298652
|
+
cwd: input.cwd,
|
|
298653
|
+
relativePath: input.relativePath,
|
|
298654
|
+
failure: "path_outside_workspace",
|
|
298655
|
+
detail: "Directory listings must use workspace-relative paths."
|
|
298656
|
+
});
|
|
298657
|
+
const resolved = trimmed.length === 0 ? {
|
|
298658
|
+
absolutePath: workspaceRoot,
|
|
298659
|
+
relativePath: ""
|
|
298660
|
+
} : yield* workspacePaths.resolveRelativePathWithinRoot({
|
|
298661
|
+
workspaceRoot,
|
|
298662
|
+
relativePath: trimmed
|
|
298663
|
+
}).pipe(require_Schema$1.mapError(() => new WorkspaceListDirectoryError({
|
|
298664
|
+
cwd: input.cwd,
|
|
298665
|
+
relativePath: input.relativePath,
|
|
298666
|
+
failure: "path_outside_workspace",
|
|
298667
|
+
detail: `Directory is outside the workspace: ${input.relativePath}`
|
|
298668
|
+
})));
|
|
298669
|
+
const canonicalRoot = yield* fileSystem.realPath(workspaceRoot).pipe(require_Schema$1.orDie);
|
|
298670
|
+
const targetRealPath = yield* fileSystem.realPath(resolved.absolutePath).pipe(require_Schema$1.catch_(() => require_Schema$1.succeed(null)));
|
|
298671
|
+
if (targetRealPath === null) return yield* new WorkspaceListDirectoryError({
|
|
298672
|
+
cwd: input.cwd,
|
|
298673
|
+
relativePath: input.relativePath,
|
|
298674
|
+
failure: "read_directory_failed",
|
|
298675
|
+
detail: `Directory does not exist: ${input.relativePath || "."}`
|
|
298676
|
+
});
|
|
298677
|
+
if (!isPathWithin(path, canonicalRoot, targetRealPath)) return yield* new WorkspaceListDirectoryError({
|
|
298678
|
+
cwd: input.cwd,
|
|
298679
|
+
relativePath: input.relativePath,
|
|
298680
|
+
failure: "path_outside_workspace",
|
|
298681
|
+
detail: `Directory does not resolve inside the workspace: ${input.relativePath}`
|
|
298682
|
+
});
|
|
298683
|
+
const targetStat = yield* fileSystem.stat(targetRealPath).pipe(require_Schema$1.option);
|
|
298684
|
+
if (targetStat._tag === "None" || targetStat.value.type !== "Directory") return yield* new WorkspaceListDirectoryError({
|
|
298685
|
+
cwd: input.cwd,
|
|
298686
|
+
relativePath: input.relativePath,
|
|
298687
|
+
failure: "read_directory_failed",
|
|
298688
|
+
detail: `Not a directory: ${input.relativePath}`
|
|
298689
|
+
});
|
|
298690
|
+
const names = yield* fileSystem.readDirectory(targetRealPath).pipe(require_Schema$1.mapError((cause) => new WorkspaceListDirectoryError({
|
|
298691
|
+
cwd: input.cwd,
|
|
298692
|
+
relativePath: input.relativePath,
|
|
298693
|
+
failure: "read_directory_failed",
|
|
298694
|
+
detail: `Could not read directory: ${input.relativePath}`,
|
|
298695
|
+
cause
|
|
298696
|
+
})));
|
|
298697
|
+
return { entries: (yield* require_Schema$1.forEach(names, (name) => require_Schema$1.gen(function* () {
|
|
298698
|
+
const entryPath = path.join(targetRealPath, name);
|
|
298699
|
+
if ((yield* fileSystem.readLink(entryPath).pipe(require_Schema$1.option))._tag === "Some") return {
|
|
298700
|
+
name,
|
|
298701
|
+
kind: "file",
|
|
298702
|
+
hasChildren: false
|
|
298703
|
+
};
|
|
298704
|
+
const stat = yield* fileSystem.stat(entryPath).pipe(require_Schema$1.option);
|
|
298705
|
+
if (stat._tag === "None") return void 0;
|
|
298706
|
+
if (stat.value.type === "Directory") {
|
|
298707
|
+
const children = yield* fileSystem.readDirectory(entryPath).pipe(require_Schema$1.option);
|
|
298708
|
+
return {
|
|
298709
|
+
name,
|
|
298710
|
+
kind: "directory",
|
|
298711
|
+
hasChildren: children._tag === "Some" && children.value.length > 0
|
|
298712
|
+
};
|
|
298713
|
+
}
|
|
298714
|
+
return {
|
|
298715
|
+
name,
|
|
298716
|
+
kind: "file",
|
|
298717
|
+
hasChildren: false
|
|
298718
|
+
};
|
|
298719
|
+
}), { concurrency: 16 })).filter((entry) => entry !== void 0).toSorted(compareEntries) };
|
|
298720
|
+
}) };
|
|
298721
|
+
});
|
|
298722
|
+
const WorkspaceDirectoryListingLive = require_Schema$1.effect(WorkspaceDirectoryListing, makeWorkspaceDirectoryListing);
|
|
298723
|
+
//#endregion
|
|
299002
298724
|
//#region src/workspace/Layers/WorkspacePaths.ts
|
|
299003
298725
|
function toPosixRelativePath(input) {
|
|
299004
298726
|
return input.replaceAll("\\", "/");
|
|
@@ -300611,7 +300333,7 @@ function resolveBuildCommitFromEnv(env) {
|
|
|
300611
300333
|
* environment descriptor down instead of reporting an honest "unknown".
|
|
300612
300334
|
*/
|
|
300613
300335
|
function readBakedBuildCommit() {
|
|
300614
|
-
return "
|
|
300336
|
+
return "15aa06fedcdcf9e165622cf018e31ce724b98ef7";
|
|
300615
300337
|
}
|
|
300616
300338
|
async function resolveServerBuildCommit(input) {
|
|
300617
300339
|
if (isFullCommitSha(input.baked)) return input.baked;
|
|
@@ -300859,14 +300581,13 @@ function identityFromRedeemed(redeemed) {
|
|
|
300859
300581
|
username: redeemed.displayName?.trim() || `user-${redeemed.userId}`
|
|
300860
300582
|
};
|
|
300861
300583
|
}
|
|
300584
|
+
/** Read optional display name, owner assertion, session grant, and expiry from a redeemed handoff. */
|
|
300862
300585
|
function optionalRedeemedFields(body) {
|
|
300863
|
-
const refreshToken = readTrimmedString(body.refreshToken);
|
|
300864
300586
|
const displayName = typeof body.displayName === "string" ? body.displayName : void 0;
|
|
300865
300587
|
const forgeOwnerAssertion = readTrimmedString(body.forgeOwnerAssertion);
|
|
300866
300588
|
const sessionGrant = readTrimmedString(body.sessionGrant);
|
|
300867
300589
|
const sessionGrantExpiresAt = typeof body.sessionGrantExpiresAt === "string" ? body.sessionGrantExpiresAt : void 0;
|
|
300868
300590
|
return {
|
|
300869
|
-
...refreshToken !== void 0 ? { refreshToken } : {},
|
|
300870
300591
|
...displayName !== void 0 ? { displayName } : {},
|
|
300871
300592
|
...forgeOwnerAssertion !== void 0 ? { forgeOwnerAssertion } : {},
|
|
300872
300593
|
...sessionGrant !== void 0 ? { sessionGrant } : {},
|
|
@@ -300934,6 +300655,7 @@ function redeemEmbedCodeWithCmmd(code) {
|
|
|
300934
300655
|
return require_Schema$1.succeed(redeemed);
|
|
300935
300656
|
}));
|
|
300936
300657
|
}
|
|
300658
|
+
/** Store the redeemed PAT, JWT, and identity for a local embed session, failing on write errors. */
|
|
300937
300659
|
function persistLocalEmbedSession(input) {
|
|
300938
300660
|
return require_Schema$1.gen(function* () {
|
|
300939
300661
|
yield* setCommandAccessTokenForSession(input.sessionId, input.redeemed.mcpToken).pipe(require_Schema$1.mapError((cause) => new AuthError({
|
|
@@ -300941,7 +300663,7 @@ function persistLocalEmbedSession(input) {
|
|
|
300941
300663
|
status: 500,
|
|
300942
300664
|
cause
|
|
300943
300665
|
})));
|
|
300944
|
-
yield* setCommandJwtForSession(input.sessionId, input.redeemed.accessToken
|
|
300666
|
+
yield* setCommandJwtForSession(input.sessionId, input.redeemed.accessToken).pipe(require_Schema$1.mapError((cause) => new AuthError({
|
|
300945
300667
|
message: "Failed to persist CMMD JWT.",
|
|
300946
300668
|
status: 500,
|
|
300947
300669
|
cause
|
|
@@ -300996,7 +300718,6 @@ const authCmmdEmbedExchangeRouteLayer = add$1("POST", "/api/auth/cmmd-embed/exch
|
|
|
300996
300718
|
accessToken: redeemed.mcpToken,
|
|
300997
300719
|
identity,
|
|
300998
300720
|
jwtToken: redeemed.accessToken,
|
|
300999
|
-
refreshToken: redeemed.refreshToken,
|
|
301000
300721
|
sessionGrant,
|
|
301001
300722
|
sessionGrantExpiresAt: redeemed.sessionGrantExpiresAt
|
|
301002
300723
|
});
|
|
@@ -301102,7 +300823,7 @@ const cloudOwnerSessionRedeemRouteLayer = add$1("POST", "/api/auth/cloud/session
|
|
|
301102
300823
|
status: 403
|
|
301103
300824
|
});
|
|
301104
300825
|
const tokenResult = yield* redeemSessionGrantWithCmmd(payload);
|
|
301105
|
-
const { tokenObj, cmmdIdentity, userId, accessToken, mcpToken,
|
|
300826
|
+
const { tokenObj, cmmdIdentity, userId, accessToken, mcpToken, forgeOwnerAssertion, sessionGrantExpiresAt, isJwt, patToStore, jwtToStore } = yield* resolveCmmdTokenBundleCredentials(tokenResult);
|
|
301106
300827
|
if (!cmmdIdentity || !userId || !patToStore) {
|
|
301107
300828
|
yield* require_Schema$1.logError("Forge Cloud session redeem: CMMD token response incomplete.", {
|
|
301108
300829
|
clientId: payload.clientId,
|
|
@@ -301111,7 +300832,6 @@ const cloudOwnerSessionRedeemRouteLayer = add$1("POST", "/api/auth/cloud/session
|
|
|
301111
300832
|
tokenResult,
|
|
301112
300833
|
accessToken,
|
|
301113
300834
|
mcpToken,
|
|
301114
|
-
refreshToken,
|
|
301115
300835
|
isJwt,
|
|
301116
300836
|
userId,
|
|
301117
300837
|
patToStore,
|
|
@@ -301134,7 +300854,6 @@ const cloudOwnerSessionRedeemRouteLayer = add$1("POST", "/api/auth/cloud/session
|
|
|
301134
300854
|
accessToken: patToStore,
|
|
301135
300855
|
identity: cmmdIdentity,
|
|
301136
300856
|
jwtToken: jwtToStore,
|
|
301137
|
-
refreshToken,
|
|
301138
300857
|
sessionGrant: payload.sessionGrant,
|
|
301139
300858
|
sessionGrantExpiresAt
|
|
301140
300859
|
});
|
|
@@ -325264,7 +324983,7 @@ const ProjectLeadSchedulerRuntimeLayerLive = makeProjectLeadSchedulerRuntimeLive
|
|
|
325264
324983
|
const GitManagerLayerLive = GitManagerLive.pipe(require_Schema$1.provideMerge(ProjectSetupScriptRunnerLive), require_Schema$1.provideMerge(GitCoreLive), require_Schema$1.provideMerge(GitHubCliLive), require_Schema$1.provideMerge(RoutingTextGenerationLive.pipe(require_Schema$1.provide$1(TextGenerationUsageLive.pipe(require_Schema$1.provide$1(RuntimeReplicationOutboxLive), require_Schema$1.provide$1(TextGenerationSessionRepositoryLive))))));
|
|
325265
324984
|
const GitLayerLive = require_Schema$1.empty$1.pipe(require_Schema$1.provideMerge(GitManagerLayerLive), require_Schema$1.provideMerge(GitStatusBroadcasterLive.pipe(require_Schema$1.provide$1(GitManagerLayerLive))), require_Schema$1.provideMerge(GitCoreLive));
|
|
325266
324985
|
const TerminalLayerLive = TerminalManagerLive.pipe(require_Schema$1.provide$1(PtyAdapterLive));
|
|
325267
|
-
const WorkspaceLayerLive = require_Schema$1.mergeAll(WorkspacePathsLive, ProjectCheckoutDeletionLive.pipe(require_Schema$1.provide$1(GitCoreLive), require_Schema$1.provide$1(ProjectionProjectRepositoryLive)), WorkspaceEntriesLive.pipe(require_Schema$1.provide$1(WorkspacePathsLive)), WorkspaceFileSystemLive.pipe(require_Schema$1.provide$1(WorkspacePathsLive), require_Schema$1.provide$1(WorkspaceEntriesLive.pipe(require_Schema$1.provide$1(WorkspacePathsLive)))), WorkspaceBulkTransferLive.pipe(require_Schema$1.provide$1(WorkspacePathsLive), require_Schema$1.provide$1(WorkspaceEntriesLive.pipe(require_Schema$1.provide$1(WorkspacePathsLive)))));
|
|
324986
|
+
const WorkspaceLayerLive = require_Schema$1.mergeAll(WorkspacePathsLive, ProjectCheckoutDeletionLive.pipe(require_Schema$1.provide$1(GitCoreLive), require_Schema$1.provide$1(ProjectionProjectRepositoryLive)), WorkspaceEntriesLive.pipe(require_Schema$1.provide$1(WorkspacePathsLive)), WorkspaceFileSystemLive.pipe(require_Schema$1.provide$1(WorkspacePathsLive), require_Schema$1.provide$1(WorkspaceEntriesLive.pipe(require_Schema$1.provide$1(WorkspacePathsLive)))), WorkspaceBulkTransferLive.pipe(require_Schema$1.provide$1(WorkspacePathsLive), require_Schema$1.provide$1(WorkspaceEntriesLive.pipe(require_Schema$1.provide$1(WorkspacePathsLive)))), WorkspaceDirectoryListingLive.pipe(require_Schema$1.provide$1(WorkspacePathsLive)));
|
|
325268
324987
|
const AuthLayerLive = ServerAuthLive.pipe(require_Schema$1.provideMerge(PersistenceLayerLive), require_Schema$1.provide$1(ServerSecretStoreLayerLive));
|
|
325269
324988
|
const MiscRuntimeLayerLive = require_Schema$1.mergeAll(AnalyticsServiceLayerLive, OpenLive, ServerLifecycleEventsLive, layer$4);
|
|
325270
324989
|
const RuntimeDependenciesWithoutPullRequestsLive = ReactorLayerLive.pipe(require_Schema$1.provideMerge(CheckpointingLayerLive), require_Schema$1.provideMerge(GitLayerLive), require_Schema$1.provideMerge(OrchestrationLayerLive), require_Schema$1.provideMerge(ProviderLayerLive), require_Schema$1.provideMerge(TerminalLayerLive), require_Schema$1.provideMerge(AgentBrowserManagerLive), require_Schema$1.provideMerge(require_Schema$1.mergeAll(ThreadDevRuntimeLive, DevTunnelLive.pipe(require_Schema$1.provide$1(ThreadDevRuntimeLive)))), require_Schema$1.provideMerge(PersistenceLayerLive), require_Schema$1.provideMerge(KeybindingsLive), require_Schema$1.provideMerge(ProviderRegistryLive), require_Schema$1.provideMerge(require_Schema$1.mergeAll(ServerSettingsLive, UsageServiceLive.pipe(require_Schema$1.provide$1(ServerSettingsLive), require_Schema$1.provide$1(UsageSessionLookupLive), require_Schema$1.provide$1(UsageRecordedSourceLive.pipe(require_Schema$1.provide$1(OrchestrationLayerLive), require_Schema$1.provide$1(RuntimeReplicationOutboxLive), require_Schema$1.provide$1(ProviderSessionRuntimeRepositoryLive), require_Schema$1.provide$1(TextGenerationSessionRepositoryLive)))))), require_Schema$1.provideMerge(WorkspaceLayerLive), require_Schema$1.provideMerge(WorkspaceReadinessStateLive), require_Schema$1.provideMerge(ProjectFaviconResolverLive), require_Schema$1.provideMerge(RepositoryIdentityResolverLive), require_Schema$1.provideMerge(ServerEnvironmentLive), require_Schema$1.provideMerge(AuthLayerLive), require_Schema$1.provideMerge(ServerSecretStoreLayerLive), require_Schema$1.provideMerge(require_Schema$1.mergeAll(CloudRedisCoordinationLive, CloudStateSyncLive.pipe(require_Schema$1.provide$1(PersistenceLayerLive)))), require_Schema$1.provideMerge(MiscRuntimeLayerLive));
|