@cmmd-center/forge 0.13.69 → 0.13.71

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 CHANGED
@@ -66275,7 +66275,7 @@ function normalizeNumberish(value) {
66275
66275
  }
66276
66276
  //#endregion
66277
66277
  //#region package.json
66278
- var version$1 = "0.13.69";
66278
+ var version$1 = "0.13.71";
66279
66279
  //#endregion
66280
66280
  //#region src/sentry.ts
66281
66281
  const SERVER_APP_NAME = "forge-server";
@@ -67235,6 +67235,7 @@ const runtimeReplicationPublicationAttemptsTotal = require_Schema$1.counter("t3_
67235
67235
  const runtimeReplicationPublicationRetriesTotal = require_Schema$1.counter("t3_runtime_replication_publication_retries_total", { description: "Total runtime replication retries scheduled after recoverable failures." });
67236
67236
  const runtimeReplicationPublicationLatency = require_Schema$1.timer("t3_runtime_replication_publication_latency", { description: "Time from durable enqueue to canonical CMMD acknowledgement." });
67237
67237
  const cmmdCredentialRefreshTotal = require_Schema$1.counter("t3_cmmd_credential_refresh_total", { description: "CMMD JWT refresh attempts for a Forge session, by rung (self-refresh | grant-redemption) and outcome (attempted | succeeded)." });
67238
+ 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." });
67238
67239
  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." });
67239
67240
  const cmmdCredentialRowDeletedTotal = require_Schema$1.counter("t3_cmmd_credential_row_deleted_total", { description: "command_access_tokens rows actually deleted by clearUnrecoverableCmmdAuthForSession, by reason." });
67240
67241
  const metricAttributes = (attributes) => Object.entries(compactMetricAttributes(attributes));
@@ -71265,6 +71266,63 @@ function parseCmmdOAuthRefreshResponse(value) {
71265
71266
  };
71266
71267
  }
71267
71268
  /**
71269
+ * Read the one field the mint lane consumes from CMMD's mint response.
71270
+ * Returns null unless `access_token` is present and non-empty.
71271
+ */
71272
+ function parseCmmdTokenMintResponse(value) {
71273
+ if (!value || typeof value !== "object") return null;
71274
+ const accessToken = value["access_token"];
71275
+ if (typeof accessToken !== "string" || accessToken.trim().length === 0) return null;
71276
+ return { accessToken: accessToken.trim() };
71277
+ }
71278
+ /**
71279
+ * How long the mint may take before the lane gives up on it. Minting is one
71280
+ * grant lookup and one signature on CMMD's side; anything slower is treated
71281
+ * like a failure and the resolver falls through to the legacy ladder.
71282
+ */
71283
+ const MINT_TIMEOUT_MS = 1e4;
71284
+ /**
71285
+ * Mint a short-lived forge-scoped access token from a durable session grant
71286
+ * (forge#3518 expand phase).
71287
+ *
71288
+ * The grant is non-rotating and redeemable on demand, so this is the mint
71289
+ * lane's entire refresh story: call it again when the short-lived token
71290
+ * expires. CMMD answers with `access_token` only (no refresh token, no MCP
71291
+ * PAT, nothing durable), so a session that lives entirely on this lane never
71292
+ * accumulates long-lived credential state on Forge's side.
71293
+ *
71294
+ * Returns null for every failure because the resolver wants a value, not a
71295
+ * thrown error; the failure modes are logged by status and OAuth error code
71296
+ * the same way `refreshCmmdJwt` logs them. A rejection here is never fed to
71297
+ * the credential-rejection ledger: the ledger exists to prove a session's
71298
+ * *rotation* credential is dead, and the grant dying says nothing about the
71299
+ * refresh token the legacy fallback still owns.
71300
+ */
71301
+ function mintCmmdShortLivedToken(grantToken) {
71302
+ return fetch(new URL("/api/mcp-server/oauth/token", resolveCommandAppUrl()).toString(), {
71303
+ method: "POST",
71304
+ headers: { "Content-Type": "application/json" },
71305
+ body: JSON.stringify({
71306
+ grant_type: "forge_token_mint",
71307
+ grant_token: grantToken,
71308
+ client_id: "cmmd-forge-web"
71309
+ }),
71310
+ signal: AbortSignal.timeout(MINT_TIMEOUT_MS)
71311
+ }).then(async (response) => {
71312
+ if (!response.ok) {
71313
+ const errorCode = await readOAuthErrorCode(response);
71314
+ console.warn(`[cmmd-auth] CMMD refused a forge token mint with ${response.status}${errorCode === null ? "" : ` (${errorCode})`}`);
71315
+ return null;
71316
+ }
71317
+ const parsed = parseCmmdTokenMintResponse(await response.json());
71318
+ if (!parsed) console.warn("[cmmd-auth] CMMD accepted a forge token mint but returned no usable access token");
71319
+ return parsed;
71320
+ }).catch((cause) => {
71321
+ console.warn(`[cmmd-auth] CMMD forge token mint could not be completed: ${String(cause)}`);
71322
+ return null;
71323
+ });
71324
+ }
71325
+ /**
71268
71326
  * Redeem a durable CMMD session grant for a fresh credential set.
71269
71327
  *
71270
71328
  * The grant is CMMD's designed recovery path for exactly the case a rotating
@@ -71355,6 +71413,42 @@ async function readOAuthErrorCode(response) {
71355
71413
  }
71356
71414
  }
71357
71415
  //#endregion
71416
+ //#region src/command/cmmdTokenMintLane.ts
71417
+ /**
71418
+ * The CMMD token-mint lane (forge#3518, expand phase).
71419
+ *
71420
+ * A new FIRST rung beside the legacy credential ladder: instead of rotating
71421
+ * CMMD's single-use refresh tokens, the resolver mints a short-lived
71422
+ * forge-scoped access token from the session's durable, non-rotating session
71423
+ * grant (`grant_type: forge_token_mint` on CMMD's token endpoint). The mint
71424
+ * spends nothing that cannot be spent again, so it cannot race itself into
71425
+ * `invalid_grant` the way a rotation can.
71426
+ *
71427
+ * Behind a config flag that is OFF unless explicitly enabled: production
71428
+ * keeps the legacy ladder as the only lane until this is switched on
71429
+ * deliberately. When enabled, the lane is tried before the legacy ladder;
71430
+ * every legacy rung (rotation fence, self-refresh, grant redemption,
71431
+ * bootstrap promotion, subject fallback) still exists below it unchanged and
71432
+ * still guards itself with the rejection ledger.
71433
+ */
71434
+ /** Env var that enables the mint lane. Unset and anything but an affirmative value is off. */
71435
+ const CMMD_TOKEN_MINT_LANE_ENV = "FORGE_CMMD_TOKEN_MINT_LANE";
71436
+ const AFFIRMATIVE_VALUES = new Set([
71437
+ "1",
71438
+ "on",
71439
+ "true",
71440
+ "yes"
71441
+ ]);
71442
+ /**
71443
+ * Whether the mint lane may run. Default off everywhere: a missing env var is
71444
+ * the production posture, and an opt-in flag must not half-default to on in
71445
+ * any environment a misconfigured deploy might land in.
71446
+ */
71447
+ function isCmmdTokenMintLaneEnabled(env = process.env) {
71448
+ const raw = env[CMMD_TOKEN_MINT_LANE_ENV]?.trim().toLowerCase();
71449
+ return raw !== void 0 && AFFIRMATIVE_VALUES.has(raw);
71450
+ }
71451
+ //#endregion
71358
71452
  //#region src/command/cmmdRotationFence.ts
71359
71453
  /**
71360
71454
  * Log and count a fence application, without changing the verdict it reports.
@@ -71443,12 +71537,13 @@ function usableCmmdUserJwt(token) {
71443
71537
  function storeResolvedJwt(sessionId, accessToken, refreshToken) {
71444
71538
  return setCommandJwtForSession(sessionId, accessToken, refreshToken ? { refreshToken } : {}).pipe(require_Schema$1.ignore({ log: true }));
71445
71539
  }
71446
- function logCmmdJwtRung(context, rung, outcome, reason) {
71540
+ function logCmmdJwtRung(context, rung, outcome, reason, lane = "legacy") {
71447
71541
  return (outcome === "failed" ? require_Schema$1.logWarning : require_Schema$1.logInfo)("CMMD JWT resolver rung.", {
71448
71542
  sessionId: context.sessionId,
71449
71543
  subject: context.subject,
71450
71544
  deviceType: context.deviceType,
71451
71545
  caller: context.caller,
71546
+ lane,
71452
71547
  rung,
71453
71548
  outcome,
71454
71549
  reason
@@ -71461,6 +71556,25 @@ function recordCmmdCredentialRefreshMetric(rung, outcome) {
71461
71556
  })), 1);
71462
71557
  }
71463
71558
  const inFlightRefreshes = /* @__PURE__ */ new Map();
71559
+ /** In-flight mints, keyed by session, same shape as `inFlightRefreshes`. */
71560
+ const inFlightMints = /* @__PURE__ */ new Map();
71561
+ /**
71562
+ * Collapse concurrent mints for one session into a single upstream call. A
71563
+ * mint cannot race itself into `invalid_grant` (the grant is non-rotating),
71564
+ * so this is call-rate hygiene, not correctness: two tabs observing the same
71565
+ * expiry must not both hit CMMD when one mint would do.
71566
+ */
71567
+ function mintOnceForSession(sessionId, grantToken) {
71568
+ return require_Schema$1.promise(() => {
71569
+ const existing = inFlightMints.get(sessionId);
71570
+ if (existing) return existing;
71571
+ const pending = mintCmmdShortLivedToken(grantToken).catch(() => null).finally(() => {
71572
+ inFlightMints.delete(sessionId);
71573
+ });
71574
+ inFlightMints.set(sessionId, pending);
71575
+ return pending;
71576
+ });
71577
+ }
71464
71578
  function refreshOnceForSession(sessionId, refreshToken) {
71465
71579
  return require_Schema$1.promise(() => {
71466
71580
  const existing = inFlightRefreshes.get(sessionId);
@@ -71500,6 +71614,39 @@ function trySelfRefresh(sessionId, context) {
71500
71614
  });
71501
71615
  }
71502
71616
  /**
71617
+ * The mint lane's rung (forge#3518, expand phase): mint a short-lived
71618
+ * forge-scoped access token from this session's durable session grant.
71619
+ *
71620
+ * Tried FIRST, before the rotation fence and the whole legacy ladder, when
71621
+ * `FORGE_CMMD_TOKEN_MINT_LANE` is on. Minting spends nothing that cannot be
71622
+ * spent again (the grant is non-rotating), so unlike the legacy rungs it is
71623
+ * safe for a session the rotation fence fences off, and it never feeds the
71624
+ * credential-rejection ledger: this rung failing says nothing about whether
71625
+ * the legacy rungs below it can still succeed.
71626
+ *
71627
+ * Only the JWT is persisted (`setCommandJwtForSession`, no refresh token):
71628
+ * the short lifetime is the expiry story, and re-minting is the refresh
71629
+ * story. A session that lives entirely on this lane accumulates no new
71630
+ * long-lived credential state beyond the grant it already had.
71631
+ */
71632
+ function tryMintShortLivedToken(sessionId, context) {
71633
+ const grantToken = readCommandSessionGrantForSession(sessionId);
71634
+ if (!grantToken) return logCmmdJwtRung(context, "mint", "skipped", "no session grant stored for this session", "mint").pipe(require_Schema$1.as(null));
71635
+ return require_Schema$1.gen(function* () {
71636
+ yield* logCmmdJwtRung(context, "mint", "attempted", "minting a short-lived forge-scoped token from this session's grant", "mint");
71637
+ yield* require_Schema$1.update(require_Schema$1.withAttributes(cmmdTokenMintTotal, metricAttributes({ outcome: "attempted" })), 1);
71638
+ const accessToken = usableCmmdUserJwt((yield* mintOnceForSession(sessionId, grantToken))?.accessToken);
71639
+ if (!accessToken) {
71640
+ yield* logCmmdJwtRung(context, "mint", "failed", "CMMD token mint did not return a usable user access JWT", "mint");
71641
+ return null;
71642
+ }
71643
+ yield* setCommandJwtForSession(sessionId, accessToken, {}).pipe(require_Schema$1.ignore({ log: true }));
71644
+ yield* require_Schema$1.update(require_Schema$1.withAttributes(cmmdTokenMintTotal, metricAttributes({ outcome: "succeeded" })), 1);
71645
+ yield* logCmmdJwtRung(context, "mint", "succeeded", "CMMD minted a short-lived forge-scoped token", "mint");
71646
+ return accessToken;
71647
+ });
71648
+ }
71649
+ /**
71503
71650
  * Last-resort recovery: redeem this session's durable CMMD session grant.
71504
71651
  *
71505
71652
  * A refresh token is single-use, so a session that lost a rotation — raced, or
@@ -71619,8 +71766,10 @@ function runRecoveryLadder(sessionId, normalizedSubject, forceRefresh, context)
71619
71766
  * Resolve the CMMD REST JWT for a Forge session.
71620
71767
  *
71621
71768
  * Forge session auth is long-lived compared with the short-lived CMMD JWT.
71622
- * When the session JWT expires, recover by promoting a valid subject-level JWT
71623
- * or rotating the stored CMMD refresh token.
71769
+ * When the session JWT expires, recover by minting a fresh short-lived token
71770
+ * from the session grant (the mint lane; default-off, forge#3518), or, on
71771
+ * the legacy lane, by promoting a valid subject-level JWT or rotating the
71772
+ * stored CMMD refresh token.
71624
71773
  *
71625
71774
  * Fail-closed: never returns a non-user / malformed Bearer. Callers must map
71626
71775
  * null to CMMD_AUTH_REQUIRED and must not call CMMD upstream with a bad token.
@@ -71640,7 +71789,8 @@ function resolveCmmdJwtForSession({ sessionId, subject, logPrefix = "cmmd-auth",
71640
71789
  })));
71641
71790
  }
71642
71791
  const normalizedSubject = subject?.trim() || null;
71643
- if (!normalizedSubject && !forceRefresh) return require_Schema$1.succeed(null);
71792
+ const mintLaneEnabled = isCmmdTokenMintLaneEnabled();
71793
+ if (!normalizedSubject && !forceRefresh && !mintLaneEnabled) return require_Schema$1.succeed(null);
71644
71794
  return require_Schema$1.gen(function* () {
71645
71795
  const context = {
71646
71796
  sessionId,
@@ -71648,6 +71798,14 @@ function resolveCmmdJwtForSession({ sessionId, subject, logPrefix = "cmmd-auth",
71648
71798
  deviceType: yield* readClientDeviceTypeForSession(sessionId),
71649
71799
  caller: logPrefix
71650
71800
  };
71801
+ if (mintLaneEnabled) {
71802
+ const minted = yield* tryMintShortLivedToken(sessionId, context);
71803
+ if (minted) return minted;
71804
+ if (!normalizedSubject && !forceRefresh) {
71805
+ yield* logCmmdJwtRung(context, "mint", "failed", "no subject to resolve and the mint lane produced nothing", "mint");
71806
+ return null;
71807
+ }
71808
+ }
71651
71809
  if (yield* isRotationFencedSession(sessionId)) {
71652
71810
  yield* logCmmdJwtRung(context, "rotation-fence", "failed", "session is fenced from rotating CMMD credentials");
71653
71811
  return null;
@@ -87868,6 +88026,38 @@ function readRuntimeBrokerAuthority(input) {
87868
88026
  if (authority.credentialEpoch !== input.credentialEpoch) return null;
87869
88027
  return authority.token;
87870
88028
  }
88029
+ /**
88030
+ * Why a scoped lookup would miss, without mutating the map.
88031
+ *
88032
+ * "no live broker authority for this session" used to be one flat reason
88033
+ * whether nothing had ever been recorded, an entry had just expired, or an
88034
+ * entry existed for the wrong runtime, machine, or epoch. This lets a caller
88035
+ * name the distinction to an operator reading a production refusal.
88036
+ *
88037
+ * Read-only by design: call this BEFORE readRuntimeBrokerAuthority for the
88038
+ * same input. That read evicts an expired entry as it scans, so calling this
88039
+ * afterward would find the entry already gone and report "absent" for what
88040
+ * was really "expired".
88041
+ */
88042
+ function describeRuntimeBrokerAuthorityMiss(input) {
88043
+ const nowSeconds = input.nowSeconds ?? Math.floor(Date.now() / 1e3);
88044
+ const scopedAuthorities = authorities.get(input.sessionId)?.get(input.requiredScope);
88045
+ if (!scopedAuthorities || scopedAuthorities.size === 0) return "absent";
88046
+ let selected = null;
88047
+ let sawExpired = false;
88048
+ for (const authority of scopedAuthorities.values()) {
88049
+ if (authority.expiresAtSeconds <= nowSeconds) {
88050
+ sawExpired = true;
88051
+ continue;
88052
+ }
88053
+ if (!selected || authority.expiresAtSeconds >= selected.expiresAtSeconds) selected = authority;
88054
+ }
88055
+ if (!selected) return sawExpired ? "expired" : "absent";
88056
+ if (selected.runtimeId !== input.runtimeId) return "runtime mismatch";
88057
+ if (selected.machineId !== input.machineId) return "machine mismatch";
88058
+ if (selected.credentialEpoch !== input.credentialEpoch) return "epoch mismatch";
88059
+ return "absent";
88060
+ }
87871
88061
  function readRuntimeBrokerAuthorityForRuntime(input) {
87872
88062
  for (const sessionId of authorities.keys()) {
87873
88063
  const token = readRuntimeBrokerAuthority({
@@ -152394,6 +152584,30 @@ function runtimeAuthoritySessionId(input) {
152394
152584
  if (input.authSessionId) return input.authSessionId;
152395
152585
  return input.threadId ? readThreadSessionId(input.threadId) : null;
152396
152586
  }
152587
+ const BROKER_AUTHORITY_MISS_PHRASE = {
152588
+ absent: "nothing has ever been recorded for this session and scope",
152589
+ expired: "the recorded authority already passed its expiry",
152590
+ "runtime mismatch": "the recorded authority names a different runtime",
152591
+ "machine mismatch": "the recorded authority names a different machine",
152592
+ "epoch mismatch": "the recorded authority names a different credential epoch"
152593
+ };
152594
+ /**
152595
+ * The base "no live broker authority" reason, enriched with what the lookup
152596
+ * compared and why it missed when that detail is available.
152597
+ *
152598
+ * A production refusal used to say only "no live broker authority for this
152599
+ * session", true whether nothing had ever been recorded, an entry had just
152600
+ * expired, or an entry existed for the wrong runtime, machine, or epoch. An
152601
+ * operator reading the log had no way to tell those apart (CMMD-Center/forge#3547).
152602
+ * `missDetail` is optional so a caller that has not resolved it (or a test
152603
+ * pinning the plain-outcome mapping) still gets the original, unqualified
152604
+ * sentence.
152605
+ */
152606
+ function describeNoLiveBrokerAuthorityReason(sessionId, missDetail) {
152607
+ const base = "no live broker authority for this session";
152608
+ if (!missDetail) return base;
152609
+ return `${base} (looked up session ${sessionId}, scope ${missDetail.requiredScope}, runtime ${missDetail.runtimeId}, machine ${missDetail.machineId}, epoch ${missDetail.credentialEpoch}: ${BROKER_AUTHORITY_MISS_PHRASE[missDetail.missReason]})`;
152610
+ }
152397
152611
  /**
152398
152612
  * Pure mapping from the three preconditions to an outcome.
152399
152613
  *
@@ -152419,7 +152633,7 @@ function resolveBrokerTicketOutcome(input) {
152419
152633
  token: input.brokerAuthority
152420
152634
  } : {
152421
152635
  kind: "unavailable",
152422
- reason: "no live broker authority for this session"
152636
+ reason: describeNoLiveBrokerAuthorityReason(input.sessionId, input.missDetail ?? null)
152423
152637
  };
152424
152638
  }
152425
152639
  /**
@@ -152451,17 +152665,43 @@ function runtimeProviderBrokerToken(input) {
152451
152665
  brokerAuthority: null
152452
152666
  });
152453
152667
  const sessionId = runtimeAuthoritySessionId(input);
152668
+ if (!sessionId) return resolveBrokerTicketOutcome({
152669
+ identityMode: "exact",
152670
+ legacyGrantToken: null,
152671
+ sessionId: null,
152672
+ brokerAuthority: null
152673
+ });
152674
+ const runtimeId = process.env.FORGE_CMMD_RUNTIME_ID;
152675
+ const requiredScope = "provider";
152676
+ const nowSeconds = Math.floor(Date.now() / 1e3);
152677
+ const missReason = describeRuntimeBrokerAuthorityMiss({
152678
+ sessionId,
152679
+ runtimeId,
152680
+ machineId: identity.machineId,
152681
+ credentialEpoch: identity.credentialEpoch,
152682
+ requiredScope,
152683
+ nowSeconds
152684
+ });
152685
+ const brokerAuthority = readRuntimeBrokerAuthority({
152686
+ sessionId,
152687
+ runtimeId,
152688
+ machineId: identity.machineId,
152689
+ credentialEpoch: identity.credentialEpoch,
152690
+ requiredScope,
152691
+ nowSeconds
152692
+ });
152454
152693
  return resolveBrokerTicketOutcome({
152455
152694
  identityMode: "exact",
152456
152695
  legacyGrantToken: null,
152457
152696
  sessionId,
152458
- brokerAuthority: sessionId ? readRuntimeBrokerAuthority({
152459
- sessionId,
152460
- runtimeId: process.env.FORGE_CMMD_RUNTIME_ID,
152697
+ brokerAuthority,
152698
+ missDetail: brokerAuthority ? null : {
152699
+ requiredScope,
152700
+ runtimeId,
152461
152701
  machineId: identity.machineId,
152462
152702
  credentialEpoch: identity.credentialEpoch,
152463
- requiredScope: "provider"
152464
- }) : null
152703
+ missReason
152704
+ }
152465
152705
  });
152466
152706
  }
152467
152707
  async function fetchRuntimeProviderGrant(input) {
@@ -298409,7 +298649,7 @@ function resolveBuildCommitFromEnv(env) {
298409
298649
  * environment descriptor down instead of reporting an honest "unknown".
298410
298650
  */
298411
298651
  function readBakedBuildCommit() {
298412
- return "ac91dc3fd479252ab728872fa5df9aaf5959eca7";
298652
+ return "8e9fcb0318612dd80655f7c9662dd9128b8f005c";
298413
298653
  }
298414
298654
  async function resolveServerBuildCommit(input) {
298415
298655
  if (isFullCommitSha(input.baked)) return input.baked;