@cmmd-center/forge 0.13.2 → 0.13.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin.cjs CHANGED
@@ -16544,7 +16544,7 @@ function makeNetServiceShape(createServer = node_net.createServer) {
16544
16544
  }
16545
16545
  //#endregion
16546
16546
  //#region ../../packages/shared/src/releaseInfo.generated.ts
16547
- const FORGE_RELEASE_VERSION = "0.13.2";
16547
+ const FORGE_RELEASE_VERSION = "0.13.3";
16548
16548
  //#endregion
16549
16549
  //#region ../../packages/shared/src/hostProcess.ts
16550
16550
  /**
@@ -65864,7 +65864,7 @@ function normalizeNumberish(value) {
65864
65864
  }
65865
65865
  //#endregion
65866
65866
  //#region package.json
65867
- var version$1 = "0.13.2";
65867
+ var version$1 = "0.13.3";
65868
65868
  //#endregion
65869
65869
  //#region src/sentry.ts
65870
65870
  const SERVER_APP_NAME = "forge-server";
@@ -96204,33 +96204,34 @@ function serializeNativeToolActivity(activity) {
96204
96204
  }
96205
96205
  };
96206
96206
  }
96207
- /** The failure activities `appendProviderFailureActivity` emits, all `provider.*.failed`. */
96208
- function isProviderFailureActivity(activity) {
96209
- return activity.kind.startsWith("provider.") && activity.kind.endsWith(".failed");
96207
+ function isReplicatedFailureActivity(activity) {
96208
+ return activity.kind === "runtime.error" || activity.kind.startsWith("provider.") && activity.kind.endsWith(".failed");
96210
96209
  }
96211
96210
  function failureDetail(activity) {
96212
96211
  const payload = activity.payload;
96213
- if (!payload || typeof payload !== "object" || !("detail" in payload)) return null;
96214
- const detail = payload.detail;
96215
- return typeof detail === "string" && detail.trim() ? detail.trim() : null;
96212
+ if (!payload || typeof payload !== "object") return null;
96213
+ const record = payload;
96214
+ const detail = typeof record.detail === "string" ? record.detail.trim() : "";
96215
+ if (detail) return detail;
96216
+ return (typeof record.message === "string" ? record.message.trim() : "") || null;
96216
96217
  }
96217
96218
  /**
96218
96219
  * Replicates why a turn failed.
96219
96220
  *
96220
- * Without this a failure never crossed the replication boundary — only `tool.`
96221
- * activities were serialized — so durable history could not show one and every
96221
+ * Without this a failure never crossed the replication boundary. Only `tool.`
96222
+ * activities were serialized, so durable history could not show one and every
96222
96223
  * failure reached the user as an endless "Working" spinner. Measured in
96223
96224
  * production on 2026-08-30: zero failure events had EVER been recorded against
96224
96225
  * 652 tool_call rows, which is how a workspace-path block, a broker-token
96225
96226
  * refusal and a dispatch into a deleted directory all looked identical.
96226
96227
  *
96227
- * `error_summary` is used rather than a new kind because both the Forge
96228
- * protocol and CMMD's ingest schema already accept it; nothing here needed a
96229
- * contract change. The detail carries the actionable sentence, so it is joined
96230
- * onto the summary — the event has no separate detail field.
96228
+ * `error_summary` is used because both the Forge protocol and CMMD's ingest
96229
+ * schema already accept it. Provider startup failures store `detail`, while
96230
+ * active runtime failures store `message`. The event has no separate detail
96231
+ * field, so either value is joined onto the summary.
96231
96232
  */
96232
- function serializeProviderFailureActivity(activity) {
96233
- if (!isProviderFailureActivity(activity)) return null;
96233
+ function serializeFailureActivity(activity) {
96234
+ if (!isReplicatedFailureActivity(activity)) return null;
96234
96235
  const summary = text(activity.summary, 4e3);
96235
96236
  if (summary === null) return null;
96236
96237
  const detail = failureDetail(activity);
@@ -96572,7 +96573,7 @@ function isTurnUsageTokens(value) {
96572
96573
  function serializeRuntimeThreadEvents(thread) {
96573
96574
  const messages = thread.messages.flatMap(serializeThreadMessage);
96574
96575
  const failureActivities = thread.activities.flatMap((activity) => {
96575
- const event = serializeProviderFailureActivity(activity);
96576
+ const event = serializeFailureActivity(activity);
96576
96577
  return event === null ? [] : [event];
96577
96578
  });
96578
96579
  const replicaActivities = thread.activities.flatMap((activity) => {
@@ -100840,6 +100841,31 @@ function claudeWriteToken(env = process.env) {
100840
100841
  return accessor.read(env);
100841
100842
  }
100842
100843
  //#endregion
100844
+ //#region ../../packages/shared/src/claudeReadinessReason.ts
100845
+ /**
100846
+ * The reason literals Forge acts on, confirmed against CMMD-36441's readiness
100847
+ * service. If the route renames one, the rename lands here as a fall-through
100848
+ * to null, never as the wrong kind.
100849
+ *
100850
+ * Reading the reason is what makes `stale` legible. CMMD-36441 answers
100851
+ * `credential_reconnect_required` on a `stale` answer that still reports
100852
+ * `connected: true`, and the older `credential_generation_not_applied` on a
100853
+ * `stale` answer that simply has not landed yet. Those want opposite
100854
+ * handling, and both used to get "Reconnect Claude in Settings" because the
100855
+ * guard read only the delivery status.
100856
+ */
100857
+ const CODE_BY_UPSTREAM_REASON$1 = {
100858
+ credential_expired_refreshing: "CREDENTIAL_EXPIRED_REFRESHING",
100859
+ credential_reconnect_required: "CREDENTIAL_RECONNECT_REQUIRED",
100860
+ credential_not_connected: "CREDENTIAL_RECONNECT_REQUIRED",
100861
+ credential_generation_not_applied: "CREDENTIAL_GENERATION_NOT_APPLIED",
100862
+ runtime_unavailable: "RUNTIME_UNAVAILABLE"
100863
+ };
100864
+ function claudeReadinessReasonCode(reason) {
100865
+ if (typeof reason !== "string" || !Object.hasOwn(CODE_BY_UPSTREAM_REASON$1, reason)) return null;
100866
+ return CODE_BY_UPSTREAM_REASON$1[reason] ?? null;
100867
+ }
100868
+ //#endregion
100843
100869
  //#region src/egress/providerDeliveryReceipt.ts
100844
100870
  const PROVIDER_ACCOUNT_IDENTITY$2 = /^pai_v1_[A-Za-z0-9_-]{43}$/;
100845
100871
  const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
@@ -101014,82 +101040,6 @@ function providerDeliveryBindingsEqual(left, right) {
101014
101040
  return classifyProviderDeliveryBindingChange(left, right) === "unchanged";
101015
101041
  }
101016
101042
  //#endregion
101017
- //#region src/provider/claudeReadinessReason.ts
101018
- /**
101019
- * The reason literals Forge has copy for, confirmed against CMMD-36441's
101020
- * readiness service. If the route renames one, the rename lands here as a
101021
- * fall-through to the generic sentence, never as a wrong sentence.
101022
- *
101023
- * Reading the reason is what makes `stale` legible. CMMD-36441 answers
101024
- * `credential_reconnect_required` on a `stale` answer that still reports
101025
- * `connected: true`, and the older `credential_generation_not_applied` on a
101026
- * `stale` answer that simply has not landed yet. Those want opposite
101027
- * sentences, and both used to get "Reconnect Claude in Settings" because the
101028
- * guard read only the delivery status.
101029
- */
101030
- const CODE_BY_UPSTREAM_REASON$1 = {
101031
- credential_expired_refreshing: "CREDENTIAL_EXPIRED_REFRESHING",
101032
- credential_reconnect_required: "CREDENTIAL_RECONNECT_REQUIRED",
101033
- credential_not_connected: "CREDENTIAL_RECONNECT_REQUIRED",
101034
- credential_generation_not_applied: "CREDENTIAL_GENERATION_NOT_APPLIED",
101035
- runtime_unavailable: "RUNTIME_UNAVAILABLE"
101036
- };
101037
- const CLAUDE_NATIVE_CREDENTIAL_UNUSABLE_COPY = "The delivered Claude credential is unavailable or unusable in this runtime.";
101038
- /**
101039
- * An ISO 8601 instant, with or without fractional seconds, UTC or offset.
101040
- * Anchored to nothing on purpose: CMMD wraps the time in a sentence, and the
101041
- * time is the only part of that sentence Forge repeats.
101042
- */
101043
- const ISO_INSTANT_PATTERN = /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})/;
101044
- function claudeReadinessReasonCode(reason) {
101045
- if (typeof reason !== "string" || !Object.hasOwn(CODE_BY_UPSTREAM_REASON$1, reason)) return null;
101046
- return CODE_BY_UPSTREAM_REASON$1[reason] ?? null;
101047
- }
101048
- function expiredAtFromReadinessMessage(message) {
101049
- if (typeof message !== "string") return null;
101050
- return ISO_INSTANT_PATTERN.exec(message)?.[0] ?? null;
101051
- }
101052
- function claudeCredentialExpiredTurnCopy(expiredAt) {
101053
- return `The delivered Claude credential expired at ${expiredAt}. CMMD is delivering a refreshed one, so retry this turn in a moment.`;
101054
- }
101055
- function claudeCredentialExpiredCardCopy(expiredAt) {
101056
- return `The delivered Claude credential expired at ${expiredAt}. CMMD is delivering a refreshed one to this runtime.`;
101057
- }
101058
- /**
101059
- * The last allowlisted readiness reason this process saw.
101060
- *
101061
- * A server process serves exactly one runtime, so one slot is the whole store.
101062
- * It holds a reason code and a timestamp, never credential bytes. The provider
101063
- * catalog reads a local credential file and cannot tell an expired snapshot
101064
- * from a blank one, so without this the card can only offer the generic
101065
- * sentence while the guard already knows better.
101066
- */
101067
- let latestVerdict = null;
101068
- /**
101069
- * Remember, or clear, what the readiness answer said.
101070
- *
101071
- * An answer carrying no allowlisted reason clears the slot, which is what makes
101072
- * the card go back to the generic sentence the moment CMMD applies a
101073
- * credential: an applied answer carries no reason.
101074
- */
101075
- function recordClaudeReadinessVerdict(readiness) {
101076
- const code = claudeReadinessReasonCode(readiness.reason);
101077
- latestVerdict = code === null ? null : {
101078
- code,
101079
- expiredAt: expiredAtFromReadinessMessage(readiness.lastErrorMessage)
101080
- };
101081
- }
101082
- /**
101083
- * What the provider status card says about a delivered credential it cannot
101084
- * use. Names the expiry only when a readiness answer supplied the time; a
101085
- * guessed time would be worse than the generic sentence.
101086
- */
101087
- function claudeNativeCredentialUnusableMessage() {
101088
- const verdict = latestVerdict;
101089
- if (verdict?.code === "CREDENTIAL_EXPIRED_REFRESHING" && verdict.expiredAt !== null) return claudeCredentialExpiredCardCopy(verdict.expiredAt);
101090
- return CLAUDE_NATIVE_CREDENTIAL_UNUSABLE_COPY;
101091
- }
101092
- //#endregion
101093
101043
  //#region src/provider/providerReadiness.ts
101094
101044
  const GENERATION_HASH_PATTERN$1 = /^[a-f0-9]{64}$/;
101095
101045
  const STILL_APPLYING_STATUSES = new Set([
@@ -101134,10 +101084,18 @@ async function queryProviderReadiness(input) {
101134
101084
  }
101135
101085
  return classifyClaudeSnapshot(snapshot, sprite);
101136
101086
  }
101137
- function isAppliedClaudeSnapshot(snapshot) {
101138
- return snapshot.connected === true && snapshot.deliveryStatus === "applied" && snapshot.nativeExecutable === true && typeof snapshot.generationId === "string" && snapshot.generationId.trim().length > 0;
101087
+ /**
101088
+ * Whether CMMD allows a turn to start on this runtime. The delivery status is
101089
+ * not part of it: a refresh that failed over a credential the guest verified
101090
+ * and that has not expired reads as failed with nativeExecutable true, and the
101091
+ * turn is the probe (FOR-3085). The generation fence and the receipt below
101092
+ * still bind the answer.
101093
+ */
101094
+ function claudeSnapshotAllowsTurn(snapshot) {
101095
+ return snapshot.connected === true && snapshot.nativeExecutable === true && typeof snapshot.generationId === "string" && snapshot.generationId.trim().length > 0;
101139
101096
  }
101140
- function classifyUnappliedClaudeSnapshot(snapshot) {
101097
+ /** Why a turn may not start, for an answer that does not allow one. */
101098
+ function classifyClaudeTurnRefusal(snapshot) {
101141
101099
  const code = claudeReadinessReasonCode(snapshot.reason);
101142
101100
  if (code === "RUNTIME_UNAVAILABLE") return {
101143
101101
  kind: "not_ready",
@@ -101165,7 +101123,7 @@ function classifyUnappliedClaudeSnapshot(snapshot) {
101165
101123
  };
101166
101124
  }
101167
101125
  function classifyClaudeSnapshot(snapshot, sprite) {
101168
- if (!isAppliedClaudeSnapshot(snapshot)) return classifyUnappliedClaudeSnapshot(snapshot);
101126
+ if (!claudeSnapshotAllowsTurn(snapshot)) return classifyClaudeTurnRefusal(snapshot);
101169
101127
  const generationId = snapshot.generationId.trim();
101170
101128
  if ((0, node_crypto.createHash)("sha256").update(generationId).digest("hex") !== sprite.localGenerationHash) return {
101171
101129
  kind: "not_ready",
@@ -211732,6 +211690,83 @@ const makeProviderAuthProvenance = (input) => {
211732
211690
  };
211733
211691
  };
211734
211692
  //#endregion
211693
+ //#region src/provider/claudeReadinessReason.ts
211694
+ /**
211695
+ * What Forge says about a delivered Claude credential that expired.
211696
+ *
211697
+ * CMMD's credential authority rotates the refresh token on every refresh, and a
211698
+ * runtime holds a one-time snapshot, so the snapshot's refresh token dies at
211699
+ * the next rotation. Nothing re-delivers on its own, and on 2026-09-04 23:07Z
211700
+ * the turn met a dead credential: the CLI said "OAuth session expired and could
211701
+ * not be refreshed", the thread said Claude was unavailable on this
211702
+ * Environment, and the provider card said the credential was "unavailable or
211703
+ * unusable". Three sentences, none of them the fact, which is that the
211704
+ * credential expired at a known time and a replacement is on its way.
211705
+ *
211706
+ * CMMD-36441 now answers the pre-turn readiness read with a typed reason and
211707
+ * queues a same-generation re-delivery. This module is the one place that
211708
+ * turns those reasons into words.
211709
+ *
211710
+ * Which kind of answer a reason names comes from
211711
+ * `@cmmd-forge/shared/claudeReadinessReason`, which the browser readiness poll
211712
+ * reads too, so the two cannot disagree about a reason (FOR-3085). A reason
211713
+ * that maps to nothing falls through to the generic copy, and every sentence a
211714
+ * person reads is written here rather than forwarded from upstream. Only an
211715
+ * ISO timestamp is ever lifted out of CMMD's message, so no credential detail
211716
+ * in an upstream string can reach a browser.
211717
+ */
211718
+ const CLAUDE_NATIVE_CREDENTIAL_UNUSABLE_COPY = "The delivered Claude credential is unavailable or unusable in this runtime.";
211719
+ /**
211720
+ * An ISO 8601 instant, with or without fractional seconds, UTC or offset.
211721
+ * Anchored to nothing on purpose: CMMD wraps the time in a sentence, and the
211722
+ * time is the only part of that sentence Forge repeats.
211723
+ */
211724
+ const ISO_INSTANT_PATTERN = /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})/;
211725
+ function expiredAtFromReadinessMessage(message) {
211726
+ if (typeof message !== "string") return null;
211727
+ return ISO_INSTANT_PATTERN.exec(message)?.[0] ?? null;
211728
+ }
211729
+ function claudeCredentialExpiredTurnCopy(expiredAt) {
211730
+ return `The delivered Claude credential expired at ${expiredAt}. CMMD is delivering a refreshed one, so retry this turn in a moment.`;
211731
+ }
211732
+ function claudeCredentialExpiredCardCopy(expiredAt) {
211733
+ return `The delivered Claude credential expired at ${expiredAt}. CMMD is delivering a refreshed one to this runtime.`;
211734
+ }
211735
+ /**
211736
+ * The last allowlisted readiness reason this process saw.
211737
+ *
211738
+ * A server process serves exactly one runtime, so one slot is the whole store.
211739
+ * It holds a reason code and a timestamp, never credential bytes. The provider
211740
+ * catalog reads a local credential file and cannot tell an expired snapshot
211741
+ * from a blank one, so without this the card can only offer the generic
211742
+ * sentence while the guard already knows better.
211743
+ */
211744
+ let latestVerdict = null;
211745
+ /**
211746
+ * Remember, or clear, what the readiness answer said.
211747
+ *
211748
+ * An answer carrying no allowlisted reason clears the slot, which is what makes
211749
+ * the card go back to the generic sentence the moment CMMD applies a
211750
+ * credential: an applied answer carries no reason.
211751
+ */
211752
+ function recordClaudeReadinessVerdict(readiness) {
211753
+ const code = claudeReadinessReasonCode(readiness.reason);
211754
+ latestVerdict = code === null ? null : {
211755
+ code,
211756
+ expiredAt: expiredAtFromReadinessMessage(readiness.lastErrorMessage)
211757
+ };
211758
+ }
211759
+ /**
211760
+ * What the provider status card says about a delivered credential it cannot
211761
+ * use. Names the expiry only when a readiness answer supplied the time; a
211762
+ * guessed time would be worse than the generic sentence.
211763
+ */
211764
+ function claudeNativeCredentialUnusableMessage() {
211765
+ const verdict = latestVerdict;
211766
+ if (verdict?.code === "CREDENTIAL_EXPIRED_REFRESHING" && verdict.expiredAt !== null) return claudeCredentialExpiredCardCopy(verdict.expiredAt);
211767
+ return CLAUDE_NATIVE_CREDENTIAL_UNUSABLE_COPY;
211768
+ }
211769
+ //#endregion
211735
211770
  //#region src/provider/claudeCredentialStore.ts
211736
211771
  /**
211737
211772
  * Which Credential Store the Claude CLI uses on this machine, read the way the
@@ -295274,7 +295309,7 @@ function resolveBuildCommitFromEnv(env) {
295274
295309
  * environment descriptor down instead of reporting an honest "unknown".
295275
295310
  */
295276
295311
  function readBakedBuildCommit() {
295277
- return "69affcc8397652715f8bd06fe2f226f4b607e1db";
295312
+ return "1881e36c16315be6322e1a109dd4a331b484b963";
295278
295313
  }
295279
295314
  async function resolveServerBuildCommit(input) {
295280
295315
  if (isFullCommitSha(input.baked)) return input.baked;
package/dist/bin.mjs CHANGED
@@ -16520,7 +16520,7 @@ function makeNetServiceShape(createServer = Net.createServer) {
16520
16520
  }
16521
16521
  //#endregion
16522
16522
  //#region ../../packages/shared/src/releaseInfo.generated.ts
16523
- const FORGE_RELEASE_VERSION = "0.13.2";
16523
+ const FORGE_RELEASE_VERSION = "0.13.3";
16524
16524
  //#endregion
16525
16525
  //#region ../../packages/shared/src/hostProcess.ts
16526
16526
  /**
@@ -65582,7 +65582,7 @@ function normalizeNumberish(value) {
65582
65582
  }
65583
65583
  //#endregion
65584
65584
  //#region package.json
65585
- var version$1 = "0.13.2";
65585
+ var version$1 = "0.13.3";
65586
65586
  //#endregion
65587
65587
  //#region src/sentry.ts
65588
65588
  const SERVER_APP_NAME = "forge-server";
@@ -95829,33 +95829,34 @@ function serializeNativeToolActivity(activity) {
95829
95829
  }
95830
95830
  };
95831
95831
  }
95832
- /** The failure activities `appendProviderFailureActivity` emits, all `provider.*.failed`. */
95833
- function isProviderFailureActivity(activity) {
95834
- return activity.kind.startsWith("provider.") && activity.kind.endsWith(".failed");
95832
+ function isReplicatedFailureActivity(activity) {
95833
+ return activity.kind === "runtime.error" || activity.kind.startsWith("provider.") && activity.kind.endsWith(".failed");
95835
95834
  }
95836
95835
  function failureDetail(activity) {
95837
95836
  const payload = activity.payload;
95838
- if (!payload || typeof payload !== "object" || !("detail" in payload)) return null;
95839
- const detail = payload.detail;
95840
- return typeof detail === "string" && detail.trim() ? detail.trim() : null;
95837
+ if (!payload || typeof payload !== "object") return null;
95838
+ const record = payload;
95839
+ const detail = typeof record.detail === "string" ? record.detail.trim() : "";
95840
+ if (detail) return detail;
95841
+ return (typeof record.message === "string" ? record.message.trim() : "") || null;
95841
95842
  }
95842
95843
  /**
95843
95844
  * Replicates why a turn failed.
95844
95845
  *
95845
- * Without this a failure never crossed the replication boundary — only `tool.`
95846
- * activities were serialized — so durable history could not show one and every
95846
+ * Without this a failure never crossed the replication boundary. Only `tool.`
95847
+ * activities were serialized, so durable history could not show one and every
95847
95848
  * failure reached the user as an endless "Working" spinner. Measured in
95848
95849
  * production on 2026-08-30: zero failure events had EVER been recorded against
95849
95850
  * 652 tool_call rows, which is how a workspace-path block, a broker-token
95850
95851
  * refusal and a dispatch into a deleted directory all looked identical.
95851
95852
  *
95852
- * `error_summary` is used rather than a new kind because both the Forge
95853
- * protocol and CMMD's ingest schema already accept it; nothing here needed a
95854
- * contract change. The detail carries the actionable sentence, so it is joined
95855
- * onto the summary — the event has no separate detail field.
95853
+ * `error_summary` is used because both the Forge protocol and CMMD's ingest
95854
+ * schema already accept it. Provider startup failures store `detail`, while
95855
+ * active runtime failures store `message`. The event has no separate detail
95856
+ * field, so either value is joined onto the summary.
95856
95857
  */
95857
- function serializeProviderFailureActivity(activity) {
95858
- if (!isProviderFailureActivity(activity)) return null;
95858
+ function serializeFailureActivity(activity) {
95859
+ if (!isReplicatedFailureActivity(activity)) return null;
95859
95860
  const summary = text(activity.summary, 4e3);
95860
95861
  if (summary === null) return null;
95861
95862
  const detail = failureDetail(activity);
@@ -96197,7 +96198,7 @@ function isTurnUsageTokens(value) {
96197
96198
  function serializeRuntimeThreadEvents(thread) {
96198
96199
  const messages = thread.messages.flatMap(serializeThreadMessage);
96199
96200
  const failureActivities = thread.activities.flatMap((activity) => {
96200
- const event = serializeProviderFailureActivity(activity);
96201
+ const event = serializeFailureActivity(activity);
96201
96202
  return event === null ? [] : [event];
96202
96203
  });
96203
96204
  const replicaActivities = thread.activities.flatMap((activity) => {
@@ -100459,6 +100460,31 @@ function claudeWriteToken(env = process.env) {
100459
100460
  return accessor.read(env);
100460
100461
  }
100461
100462
  //#endregion
100463
+ //#region ../../packages/shared/src/claudeReadinessReason.ts
100464
+ /**
100465
+ * The reason literals Forge acts on, confirmed against CMMD-36441's readiness
100466
+ * service. If the route renames one, the rename lands here as a fall-through
100467
+ * to null, never as the wrong kind.
100468
+ *
100469
+ * Reading the reason is what makes `stale` legible. CMMD-36441 answers
100470
+ * `credential_reconnect_required` on a `stale` answer that still reports
100471
+ * `connected: true`, and the older `credential_generation_not_applied` on a
100472
+ * `stale` answer that simply has not landed yet. Those want opposite
100473
+ * handling, and both used to get "Reconnect Claude in Settings" because the
100474
+ * guard read only the delivery status.
100475
+ */
100476
+ const CODE_BY_UPSTREAM_REASON$1 = {
100477
+ credential_expired_refreshing: "CREDENTIAL_EXPIRED_REFRESHING",
100478
+ credential_reconnect_required: "CREDENTIAL_RECONNECT_REQUIRED",
100479
+ credential_not_connected: "CREDENTIAL_RECONNECT_REQUIRED",
100480
+ credential_generation_not_applied: "CREDENTIAL_GENERATION_NOT_APPLIED",
100481
+ runtime_unavailable: "RUNTIME_UNAVAILABLE"
100482
+ };
100483
+ function claudeReadinessReasonCode(reason) {
100484
+ if (typeof reason !== "string" || !Object.hasOwn(CODE_BY_UPSTREAM_REASON$1, reason)) return null;
100485
+ return CODE_BY_UPSTREAM_REASON$1[reason] ?? null;
100486
+ }
100487
+ //#endregion
100462
100488
  //#region src/egress/providerDeliveryReceipt.ts
100463
100489
  const PROVIDER_ACCOUNT_IDENTITY$2 = /^pai_v1_[A-Za-z0-9_-]{43}$/;
100464
100490
  const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
@@ -100633,82 +100659,6 @@ function providerDeliveryBindingsEqual(left, right) {
100633
100659
  return classifyProviderDeliveryBindingChange(left, right) === "unchanged";
100634
100660
  }
100635
100661
  //#endregion
100636
- //#region src/provider/claudeReadinessReason.ts
100637
- /**
100638
- * The reason literals Forge has copy for, confirmed against CMMD-36441's
100639
- * readiness service. If the route renames one, the rename lands here as a
100640
- * fall-through to the generic sentence, never as a wrong sentence.
100641
- *
100642
- * Reading the reason is what makes `stale` legible. CMMD-36441 answers
100643
- * `credential_reconnect_required` on a `stale` answer that still reports
100644
- * `connected: true`, and the older `credential_generation_not_applied` on a
100645
- * `stale` answer that simply has not landed yet. Those want opposite
100646
- * sentences, and both used to get "Reconnect Claude in Settings" because the
100647
- * guard read only the delivery status.
100648
- */
100649
- const CODE_BY_UPSTREAM_REASON$1 = {
100650
- credential_expired_refreshing: "CREDENTIAL_EXPIRED_REFRESHING",
100651
- credential_reconnect_required: "CREDENTIAL_RECONNECT_REQUIRED",
100652
- credential_not_connected: "CREDENTIAL_RECONNECT_REQUIRED",
100653
- credential_generation_not_applied: "CREDENTIAL_GENERATION_NOT_APPLIED",
100654
- runtime_unavailable: "RUNTIME_UNAVAILABLE"
100655
- };
100656
- const CLAUDE_NATIVE_CREDENTIAL_UNUSABLE_COPY = "The delivered Claude credential is unavailable or unusable in this runtime.";
100657
- /**
100658
- * An ISO 8601 instant, with or without fractional seconds, UTC or offset.
100659
- * Anchored to nothing on purpose: CMMD wraps the time in a sentence, and the
100660
- * time is the only part of that sentence Forge repeats.
100661
- */
100662
- const ISO_INSTANT_PATTERN = /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})/;
100663
- function claudeReadinessReasonCode(reason) {
100664
- if (typeof reason !== "string" || !Object.hasOwn(CODE_BY_UPSTREAM_REASON$1, reason)) return null;
100665
- return CODE_BY_UPSTREAM_REASON$1[reason] ?? null;
100666
- }
100667
- function expiredAtFromReadinessMessage(message) {
100668
- if (typeof message !== "string") return null;
100669
- return ISO_INSTANT_PATTERN.exec(message)?.[0] ?? null;
100670
- }
100671
- function claudeCredentialExpiredTurnCopy(expiredAt) {
100672
- return `The delivered Claude credential expired at ${expiredAt}. CMMD is delivering a refreshed one, so retry this turn in a moment.`;
100673
- }
100674
- function claudeCredentialExpiredCardCopy(expiredAt) {
100675
- return `The delivered Claude credential expired at ${expiredAt}. CMMD is delivering a refreshed one to this runtime.`;
100676
- }
100677
- /**
100678
- * The last allowlisted readiness reason this process saw.
100679
- *
100680
- * A server process serves exactly one runtime, so one slot is the whole store.
100681
- * It holds a reason code and a timestamp, never credential bytes. The provider
100682
- * catalog reads a local credential file and cannot tell an expired snapshot
100683
- * from a blank one, so without this the card can only offer the generic
100684
- * sentence while the guard already knows better.
100685
- */
100686
- let latestVerdict = null;
100687
- /**
100688
- * Remember, or clear, what the readiness answer said.
100689
- *
100690
- * An answer carrying no allowlisted reason clears the slot, which is what makes
100691
- * the card go back to the generic sentence the moment CMMD applies a
100692
- * credential: an applied answer carries no reason.
100693
- */
100694
- function recordClaudeReadinessVerdict(readiness) {
100695
- const code = claudeReadinessReasonCode(readiness.reason);
100696
- latestVerdict = code === null ? null : {
100697
- code,
100698
- expiredAt: expiredAtFromReadinessMessage(readiness.lastErrorMessage)
100699
- };
100700
- }
100701
- /**
100702
- * What the provider status card says about a delivered credential it cannot
100703
- * use. Names the expiry only when a readiness answer supplied the time; a
100704
- * guessed time would be worse than the generic sentence.
100705
- */
100706
- function claudeNativeCredentialUnusableMessage() {
100707
- const verdict = latestVerdict;
100708
- if (verdict?.code === "CREDENTIAL_EXPIRED_REFRESHING" && verdict.expiredAt !== null) return claudeCredentialExpiredCardCopy(verdict.expiredAt);
100709
- return CLAUDE_NATIVE_CREDENTIAL_UNUSABLE_COPY;
100710
- }
100711
- //#endregion
100712
100662
  //#region src/provider/providerReadiness.ts
100713
100663
  const GENERATION_HASH_PATTERN$1 = /^[a-f0-9]{64}$/;
100714
100664
  const STILL_APPLYING_STATUSES = new Set([
@@ -100753,10 +100703,18 @@ async function queryProviderReadiness(input) {
100753
100703
  }
100754
100704
  return classifyClaudeSnapshot(snapshot, sprite);
100755
100705
  }
100756
- function isAppliedClaudeSnapshot(snapshot) {
100757
- return snapshot.connected === true && snapshot.deliveryStatus === "applied" && snapshot.nativeExecutable === true && typeof snapshot.generationId === "string" && snapshot.generationId.trim().length > 0;
100706
+ /**
100707
+ * Whether CMMD allows a turn to start on this runtime. The delivery status is
100708
+ * not part of it: a refresh that failed over a credential the guest verified
100709
+ * and that has not expired reads as failed with nativeExecutable true, and the
100710
+ * turn is the probe (FOR-3085). The generation fence and the receipt below
100711
+ * still bind the answer.
100712
+ */
100713
+ function claudeSnapshotAllowsTurn(snapshot) {
100714
+ return snapshot.connected === true && snapshot.nativeExecutable === true && typeof snapshot.generationId === "string" && snapshot.generationId.trim().length > 0;
100758
100715
  }
100759
- function classifyUnappliedClaudeSnapshot(snapshot) {
100716
+ /** Why a turn may not start, for an answer that does not allow one. */
100717
+ function classifyClaudeTurnRefusal(snapshot) {
100760
100718
  const code = claudeReadinessReasonCode(snapshot.reason);
100761
100719
  if (code === "RUNTIME_UNAVAILABLE") return {
100762
100720
  kind: "not_ready",
@@ -100784,7 +100742,7 @@ function classifyUnappliedClaudeSnapshot(snapshot) {
100784
100742
  };
100785
100743
  }
100786
100744
  function classifyClaudeSnapshot(snapshot, sprite) {
100787
- if (!isAppliedClaudeSnapshot(snapshot)) return classifyUnappliedClaudeSnapshot(snapshot);
100745
+ if (!claudeSnapshotAllowsTurn(snapshot)) return classifyClaudeTurnRefusal(snapshot);
100788
100746
  const generationId = snapshot.generationId.trim();
100789
100747
  if (createHash("sha256").update(generationId).digest("hex") !== sprite.localGenerationHash) return {
100790
100748
  kind: "not_ready",
@@ -211320,6 +211278,83 @@ const makeProviderAuthProvenance = (input) => {
211320
211278
  };
211321
211279
  };
211322
211280
  //#endregion
211281
+ //#region src/provider/claudeReadinessReason.ts
211282
+ /**
211283
+ * What Forge says about a delivered Claude credential that expired.
211284
+ *
211285
+ * CMMD's credential authority rotates the refresh token on every refresh, and a
211286
+ * runtime holds a one-time snapshot, so the snapshot's refresh token dies at
211287
+ * the next rotation. Nothing re-delivers on its own, and on 2026-09-04 23:07Z
211288
+ * the turn met a dead credential: the CLI said "OAuth session expired and could
211289
+ * not be refreshed", the thread said Claude was unavailable on this
211290
+ * Environment, and the provider card said the credential was "unavailable or
211291
+ * unusable". Three sentences, none of them the fact, which is that the
211292
+ * credential expired at a known time and a replacement is on its way.
211293
+ *
211294
+ * CMMD-36441 now answers the pre-turn readiness read with a typed reason and
211295
+ * queues a same-generation re-delivery. This module is the one place that
211296
+ * turns those reasons into words.
211297
+ *
211298
+ * Which kind of answer a reason names comes from
211299
+ * `@cmmd-forge/shared/claudeReadinessReason`, which the browser readiness poll
211300
+ * reads too, so the two cannot disagree about a reason (FOR-3085). A reason
211301
+ * that maps to nothing falls through to the generic copy, and every sentence a
211302
+ * person reads is written here rather than forwarded from upstream. Only an
211303
+ * ISO timestamp is ever lifted out of CMMD's message, so no credential detail
211304
+ * in an upstream string can reach a browser.
211305
+ */
211306
+ const CLAUDE_NATIVE_CREDENTIAL_UNUSABLE_COPY = "The delivered Claude credential is unavailable or unusable in this runtime.";
211307
+ /**
211308
+ * An ISO 8601 instant, with or without fractional seconds, UTC or offset.
211309
+ * Anchored to nothing on purpose: CMMD wraps the time in a sentence, and the
211310
+ * time is the only part of that sentence Forge repeats.
211311
+ */
211312
+ const ISO_INSTANT_PATTERN = /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})/;
211313
+ function expiredAtFromReadinessMessage(message) {
211314
+ if (typeof message !== "string") return null;
211315
+ return ISO_INSTANT_PATTERN.exec(message)?.[0] ?? null;
211316
+ }
211317
+ function claudeCredentialExpiredTurnCopy(expiredAt) {
211318
+ return `The delivered Claude credential expired at ${expiredAt}. CMMD is delivering a refreshed one, so retry this turn in a moment.`;
211319
+ }
211320
+ function claudeCredentialExpiredCardCopy(expiredAt) {
211321
+ return `The delivered Claude credential expired at ${expiredAt}. CMMD is delivering a refreshed one to this runtime.`;
211322
+ }
211323
+ /**
211324
+ * The last allowlisted readiness reason this process saw.
211325
+ *
211326
+ * A server process serves exactly one runtime, so one slot is the whole store.
211327
+ * It holds a reason code and a timestamp, never credential bytes. The provider
211328
+ * catalog reads a local credential file and cannot tell an expired snapshot
211329
+ * from a blank one, so without this the card can only offer the generic
211330
+ * sentence while the guard already knows better.
211331
+ */
211332
+ let latestVerdict = null;
211333
+ /**
211334
+ * Remember, or clear, what the readiness answer said.
211335
+ *
211336
+ * An answer carrying no allowlisted reason clears the slot, which is what makes
211337
+ * the card go back to the generic sentence the moment CMMD applies a
211338
+ * credential: an applied answer carries no reason.
211339
+ */
211340
+ function recordClaudeReadinessVerdict(readiness) {
211341
+ const code = claudeReadinessReasonCode(readiness.reason);
211342
+ latestVerdict = code === null ? null : {
211343
+ code,
211344
+ expiredAt: expiredAtFromReadinessMessage(readiness.lastErrorMessage)
211345
+ };
211346
+ }
211347
+ /**
211348
+ * What the provider status card says about a delivered credential it cannot
211349
+ * use. Names the expiry only when a readiness answer supplied the time; a
211350
+ * guessed time would be worse than the generic sentence.
211351
+ */
211352
+ function claudeNativeCredentialUnusableMessage() {
211353
+ const verdict = latestVerdict;
211354
+ if (verdict?.code === "CREDENTIAL_EXPIRED_REFRESHING" && verdict.expiredAt !== null) return claudeCredentialExpiredCardCopy(verdict.expiredAt);
211355
+ return CLAUDE_NATIVE_CREDENTIAL_UNUSABLE_COPY;
211356
+ }
211357
+ //#endregion
211323
211358
  //#region src/provider/claudeCredentialStore.ts
211324
211359
  /**
211325
211360
  * Which Credential Store the Claude CLI uses on this machine, read the way the
@@ -294724,7 +294759,7 @@ function resolveBuildCommitFromEnv(env) {
294724
294759
  * environment descriptor down instead of reporting an honest "unknown".
294725
294760
  */
294726
294761
  function readBakedBuildCommit() {
294727
- return "69affcc8397652715f8bd06fe2f226f4b607e1db";
294762
+ return "1881e36c16315be6322e1a109dd4a331b484b963";
294728
294763
  }
294729
294764
  async function resolveServerBuildCommit(input) {
294730
294765
  if (isFullCommitSha(input.baked)) return input.baked;