@cmmd-center/forge 0.9.9-rc.1 → 0.9.9-rc.2

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
@@ -64277,7 +64277,7 @@ function normalizeNumberish(value) {
64277
64277
  }
64278
64278
  //#endregion
64279
64279
  //#region package.json
64280
- var version$1 = "0.9.9-rc.1";
64280
+ var version$1 = "0.9.9-rc.2";
64281
64281
  //#endregion
64282
64282
  //#region src/sentry.ts
64283
64283
  const SERVER_APP_NAME = "forge-server";
@@ -92088,6 +92088,31 @@ function describeHydrationOutcome(outcome) {
92088
92088
  };
92089
92089
  }
92090
92090
  //#endregion
92091
+ //#region src/runtimeReplication/replicaThreadQuarantine.ts
92092
+ /**
92093
+ * Which replica threads hydration should stop retrying.
92094
+ *
92095
+ * A thread that fails its identity or generation check was recorded against a
92096
+ * runtime that no longer exists. Nothing about a later boot changes that, and
92097
+ * because a failed thread is never inserted it never becomes "known", so the
92098
+ * next boot fetches and fails it again. Measured on a production sprite: 24
92099
+ * such threads retried on every one of three boots.
92100
+ *
92101
+ * Quarantine is deliberately NARROW. Only a permanent failure qualifies. A
92102
+ * thread that merely disappeared may come back, and a project that does not
92103
+ * resolve to an active workspace starts resolving the moment that project is
92104
+ * imported. Quarantining either would turn a recoverable gap into a permanent
92105
+ * one, which is worse than the retry it saves.
92106
+ */
92107
+ /** The one failure that a later boot can never turn into a success. */
92108
+ const PERMANENT_FAILURE = "failed identity or generation checks";
92109
+ function isQuarantinedReplicaThread(quarantined, threadId) {
92110
+ return quarantined.has(threadId);
92111
+ }
92112
+ function shouldQuarantineReplicaThread(detail) {
92113
+ return detail.includes(PERMANENT_FAILURE);
92114
+ }
92115
+ //#endregion
92091
92116
  //#region src/runtimeReplication/replicaHydrationIdentity.ts
92092
92117
  function stableReplicaDigest(...values) {
92093
92118
  return node_crypto.createHash("sha256").update(values.join("\0")).digest("base64url");
@@ -92467,6 +92492,10 @@ const prepareReplicaSummary = require_Schema$1.fn("prepareReplicaSummary")(funct
92467
92492
  state.skippedExisting += 1;
92468
92493
  return;
92469
92494
  }
92495
+ if (isQuarantinedReplicaThread(state.quarantinedThreadIds, summary.threadId)) {
92496
+ state.skippedQuarantined += 1;
92497
+ return;
92498
+ }
92470
92499
  const localResolution = yield* resolveLocalReplicaProjects(state, index, summary, dependencies);
92471
92500
  const localProjects = localResolution.projects;
92472
92501
  if (localProjects.length !== 1) return yield* replicaHydrationFailure("replica project '" + summary.projectId + "' does not resolve to one active workspace");
@@ -92507,13 +92536,18 @@ const prepareReplicaSummary = require_Schema$1.fn("prepareReplicaSummary")(funct
92507
92536
  * repeated cursor) stay fatal on purpose: those mean the index itself cannot be
92508
92537
  * trusted, so continuing would hydrate an unknown mixture.
92509
92538
  */
92510
- function skipUnhydratableThread(operation, threadId, state) {
92539
+ function skipUnhydratableThread(operation, threadId, state, dependencies) {
92511
92540
  return operation.pipe(require_Schema$1.catchTag("RuntimeReplicaHydrationError", (error) => require_Schema$1.gen(function* () {
92512
92541
  state.unhydratableThreadIds.push(threadId);
92513
92542
  yield* require_Schema$1.logWarning("skipped a thread that could not be hydrated", {
92514
92543
  threadId,
92515
92544
  detail: error.detail
92516
92545
  });
92546
+ if (!shouldQuarantineReplicaThread(error.detail)) return;
92547
+ yield* dependencies.quarantineThread({
92548
+ threadId,
92549
+ detail: error.detail
92550
+ }).pipe(require_Schema$1.ignore);
92517
92551
  })));
92518
92552
  }
92519
92553
  const applyPreparedReplica = require_Schema$1.fn("applyPreparedReplica")(function* (expectedRuntimeId, prepared, state, dependencies) {
@@ -92541,7 +92575,7 @@ function prepareReplicaPages(expectedRuntimeId, cursor, state, dependencies) {
92541
92575
  if (index === null) return;
92542
92576
  if (state.runtimeIdentity === null) state.runtimeIdentity = index.runtime;
92543
92577
  else if (!replicaRuntimeMatches(state.runtimeIdentity, index.runtime)) return yield* replicaHydrationFailure("index runtime identity changed between pages");
92544
- yield* require_Schema$1.forEach(index.threads, (summary) => skipUnhydratableThread(prepareReplicaSummary(expectedRuntimeId, index, summary, state, dependencies), summary.threadId, state), {
92578
+ yield* require_Schema$1.forEach(index.threads, (summary) => skipUnhydratableThread(prepareReplicaSummary(expectedRuntimeId, index, summary, state, dependencies), summary.threadId, state, dependencies), {
92545
92579
  concurrency: 1,
92546
92580
  discard: true
92547
92581
  });
@@ -92562,16 +92596,19 @@ const hydrateRuntimeReplica = require_Schema$1.fn("hydrateRuntimeReplica")(funct
92562
92596
  runtimeIdentity: null,
92563
92597
  hydrated: 0,
92564
92598
  skippedExisting: 0,
92599
+ skippedQuarantined: 0,
92600
+ quarantinedThreadIds: yield* dependencies.readQuarantinedThreadIds().pipe(require_Schema$1.orElseSucceed(() => /* @__PURE__ */ new Set())),
92565
92601
  unhydratableThreadIds: []
92566
92602
  };
92567
92603
  yield* prepareReplicaPages(input.expectedRuntimeId, void 0, state, dependencies);
92568
- yield* require_Schema$1.forEach(state.prepared, (prepared) => skipUnhydratableThread(applyPreparedReplica(input.expectedRuntimeId, prepared, state, dependencies), prepared.summary.threadId, state), {
92604
+ yield* require_Schema$1.forEach(state.prepared, (prepared) => skipUnhydratableThread(applyPreparedReplica(input.expectedRuntimeId, prepared, state, dependencies), prepared.summary.threadId, state, dependencies), {
92569
92605
  concurrency: 1,
92570
92606
  discard: true
92571
92607
  });
92572
92608
  const outcome = {
92573
92609
  hydrated: state.hydrated,
92574
92610
  skippedExisting: state.skippedExisting,
92611
+ skippedQuarantined: state.skippedQuarantined,
92575
92612
  skippedUnhydratable: state.unhydratableThreadIds.length,
92576
92613
  unhydratableThreadIds: [...state.unhydratableThreadIds]
92577
92614
  };
@@ -92626,6 +92663,29 @@ async function requestReplicaRead(input, fetchImpl = fetch) {
92626
92663
  }
92627
92664
  }
92628
92665
  //#endregion
92666
+ //#region src/runtimeReplication/replicaThreadQuarantineStore.ts
92667
+ /**
92668
+ * Durable record of replica threads that can never hydrate.
92669
+ *
92670
+ * Lives on the guest rather than in CMMD's replica because CMMD exposes no
92671
+ * write surface for it. That bounds what this can fix: it stops one Environment
92672
+ * re-fetching known-dead threads on every boot, and it does NOT remove the rows
92673
+ * from durable history, so a fresh Environment pays the cost once again.
92674
+ */
92675
+ const makeReplicaThreadQuarantineStore = require_Schema$1.gen(function* () {
92676
+ const sql = yield* require_SqlError.SqlClient;
92677
+ const readQuarantinedThreadIds = () => sql`SELECT thread_id FROM replica_thread_quarantine`.pipe(require_Schema$1.map((rows) => new Set(rows.map((row) => row.thread_id))), require_Schema$1.mapError((cause) => replicaHydrationFailure("quarantine read failed", cause)));
92678
+ const quarantineThread = (input) => sql`
92679
+ INSERT INTO replica_thread_quarantine (thread_id, detail, quarantined_at)
92680
+ VALUES (${input.threadId}, ${input.detail}, ${(/* @__PURE__ */ new Date()).toISOString()})
92681
+ ON CONFLICT (thread_id) DO NOTHING
92682
+ `.pipe(require_Schema$1.asVoid, require_Schema$1.mapError((cause) => replicaHydrationFailure("quarantine write failed", cause)));
92683
+ return {
92684
+ readQuarantinedThreadIds,
92685
+ quarantineThread
92686
+ };
92687
+ });
92688
+ //#endregion
92629
92689
  //#region src/runtimeReplication/replicaHydrationCloud.ts
92630
92690
  const REPLICA_INDEX_PAGE_SIZE = 100;
92631
92691
  const registerCloudRuntimeForReplicaHydration = require_Schema$1.gen(function* () {
@@ -92662,6 +92722,7 @@ const hydrateCloudRuntimeReplica = require_Schema$1.gen(function* () {
92662
92722
  }),
92663
92723
  catch: (cause) => replicaHydrationFailure("runtime replica read failed", cause)
92664
92724
  });
92725
+ const quarantine = yield* makeReplicaThreadQuarantineStore;
92665
92726
  const dependencies = {
92666
92727
  dispatch: (command) => engine.dispatch(command).pipe(require_Schema$1.mapError((cause) => replicaHydrationFailure("orchestration dispatch failed", cause))),
92667
92728
  getReadModel: engine.getReadModel,
@@ -92669,6 +92730,8 @@ const hydrateCloudRuntimeReplica = require_Schema$1.gen(function* () {
92669
92730
  readThread: ({ threadId }) => request({ threadId }),
92670
92731
  restoreThreadCursor: (input) => outbox.restoreThreadCursor(input).pipe(require_Schema$1.mapError((cause) => replicaHydrationFailure("cursor restore failed", cause))),
92671
92732
  resolveProjectRepositoryFullName: (workspaceRoot) => repositoryIdentityResolver.resolve(workspaceRoot).pipe(require_Schema$1.map((identity) => identity?.owner && identity.name ? `${identity.owner}/${identity.name}` : null), require_Schema$1.mapError((cause) => replicaHydrationFailure("repository identity resolution failed", cause))),
92733
+ readQuarantinedThreadIds: quarantine.readQuarantinedThreadIds,
92734
+ quarantineThread: quarantine.quarantineThread,
92672
92735
  now: () => (/* @__PURE__ */ new Date()).toISOString()
92673
92736
  };
92674
92737
  return yield* hydrateRuntimeReplica({ expectedRuntimeId: grant.runtimeId }, dependencies);
@@ -109937,6 +110000,28 @@ var _072_ProjectDefaultThreadEnvMode_default = require_Schema$1.gen(function* ()
109937
110000
  `;
109938
110001
  });
109939
110002
  //#endregion
110003
+ //#region src/persistence/Migrations/073_ReplicaThreadQuarantine.ts
110004
+ /**
110005
+ * Threads that can never hydrate, so hydration stops retrying them.
110006
+ *
110007
+ * A thread recorded against a destroyed runtime fails its identity or
110008
+ * generation check forever. It is never inserted, so it never becomes "known",
110009
+ * so the next boot fetches and fails it again. Measured on a production sprite:
110010
+ * 24 such threads, retried on every one of three boots.
110011
+ *
110012
+ * Keyed by thread alone, not by runtime: the whole point is that the runtime
110013
+ * that recorded the row is gone.
110014
+ */
110015
+ var _073_ReplicaThreadQuarantine_default = require_Schema$1.gen(function* () {
110016
+ yield* (yield* require_SqlError.SqlClient)`
110017
+ CREATE TABLE IF NOT EXISTS replica_thread_quarantine (
110018
+ thread_id TEXT NOT NULL PRIMARY KEY,
110019
+ detail TEXT NOT NULL,
110020
+ quarantined_at TEXT NOT NULL
110021
+ )
110022
+ `;
110023
+ });
110024
+ //#endregion
109940
110025
  //#region src/persistence/Migrations.ts
109941
110026
  /**
109942
110027
  * MigrationsLive - Migration runner with inline loader
@@ -110317,6 +110402,11 @@ const migrationEntries = [
110317
110402
  72,
110318
110403
  "ProjectDefaultThreadEnvMode",
110319
110404
  _072_ProjectDefaultThreadEnvMode_default
110405
+ ],
110406
+ [
110407
+ 73,
110408
+ "ReplicaThreadQuarantine",
110409
+ _073_ReplicaThreadQuarantine_default
110320
110410
  ]
110321
110411
  ];
110322
110412
  const makeMigrationLoader = (throughId) => fromRecord(Object.fromEntries(migrationEntries.filter(([id]) => throughId === void 0 || id <= throughId).map(([id, name, migration]) => [`${id}_${name}`, migration])));
package/dist/bin.mjs CHANGED
@@ -22,7 +22,7 @@ import { createCipheriv, createDecipheriv, createHash, createHmac, createPublicK
22
22
  import * as FS$1 from "node:fs";
23
23
  import fs, { accessSync, chmodSync, constants, createReadStream, existsSync, mkdirSync, mkdtempSync, promises, readFile, readFileSync, readdir, readdirSync, realpathSync, rmSync, statSync, writeFileSync } from "node:fs";
24
24
  import * as NodeOS from "node:os";
25
- import $U, { homedir, networkInterfaces, tmpdir } from "node:os";
25
+ import os, { homedir, networkInterfaces, tmpdir } from "node:os";
26
26
  import * as Path$1 from "node:path";
27
27
  import path, { basename, delimiter, dirname, extname, isAbsolute, join, posix, relative, resolve, sep } from "node:path";
28
28
  import { pathToFileURL } from "node:url";
@@ -48656,7 +48656,7 @@ function _supportsColor(haveStream, { streamIsTTY, sniffFlags = true } = {}) {
48656
48656
  const min = forceColor || 0;
48657
48657
  if (env.TERM === "dumb") return min;
48658
48658
  if (process$1.platform === "win32") {
48659
- const osRelease = $U.release().split(".");
48659
+ const osRelease = os.release().split(".");
48660
48660
  if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) return Number(osRelease[2]) >= 14931 ? 3 : 2;
48661
48661
  return 1;
48662
48662
  }
@@ -51099,7 +51099,7 @@ var require_ProcessDetector = /* @__PURE__ */ __commonJSMin(((exports) => {
51099
51099
  exports.processDetector = void 0;
51100
51100
  const api_1 = (init_esm$1(), __toCommonJS(esm_exports$1));
51101
51101
  const semconv_1 = require_semconv$1();
51102
- const os$3 = __require("os");
51102
+ const os$4 = __require("os");
51103
51103
  /**
51104
51104
  * ProcessDetector will be used to detect the resources related current process running
51105
51105
  * and being instrumented from the NodeJS Process module.
@@ -51121,7 +51121,7 @@ var require_ProcessDetector = /* @__PURE__ */ __commonJSMin(((exports) => {
51121
51121
  };
51122
51122
  if (process.argv.length > 1) attributes[semconv_1.ATTR_PROCESS_COMMAND] = process.argv[1];
51123
51123
  try {
51124
- const userInfo = os$3.userInfo();
51124
+ const userInfo = os$4.userInfo();
51125
51125
  attributes[semconv_1.ATTR_PROCESS_OWNER] = userInfo.username;
51126
51126
  } catch (e) {
51127
51127
  api_1.diag.debug(`error obtaining process owner: ${e}`);
@@ -64044,7 +64044,7 @@ function normalizeNumberish(value) {
64044
64044
  }
64045
64045
  //#endregion
64046
64046
  //#region package.json
64047
- var version$1 = "0.9.9-rc.1";
64047
+ var version$1 = "0.9.9-rc.2";
64048
64048
  //#endregion
64049
64049
  //#region src/sentry.ts
64050
64050
  const SERVER_APP_NAME = "forge-server";
@@ -72623,7 +72623,7 @@ var require_node_gyp_build = /* @__PURE__ */ __commonJSMin(((exports, module) =>
72623
72623
  var fs$6 = __require("fs");
72624
72624
  var path$5 = __require("path");
72625
72625
  var url$1 = __require("url");
72626
- var os$2 = __require("os");
72626
+ var os$3 = __require("os");
72627
72627
  var runtimeRequire = typeof __webpack_require__ === "function" ? __non_webpack_require__ : __require;
72628
72628
  var vars = process.config && process.config.variables || {};
72629
72629
  var prebuildsOnly = !!process.env.PREBUILDS_ONLY;
@@ -72631,8 +72631,8 @@ var require_node_gyp_build = /* @__PURE__ */ __commonJSMin(((exports, module) =>
72631
72631
  var abi = versions.modules;
72632
72632
  if (versions.deno || process.isBun) abi = "unsupported";
72633
72633
  var runtime = isElectron() ? "electron" : isNwjs() ? "node-webkit" : "node";
72634
- var arch = process.env.npm_config_arch || os$2.arch();
72635
- var platform = process.env.npm_config_platform || os$2.platform();
72634
+ var arch = process.env.npm_config_arch || os$3.arch();
72635
+ var platform = process.env.npm_config_platform || os$3.platform();
72636
72636
  var libc = process.env.LIBC || (isMusl(platform) ? "musl" : "glibc");
72637
72637
  var armv = process.env.ARM_VERSION || (arch === "arm64" ? "8" : vars.arm_version) || "";
72638
72638
  var uv = (versions.uv || "").split(".")[0];
@@ -90267,7 +90267,7 @@ const launchDetached = (launch) => gen(function* () {
90267
90267
  });
90268
90268
  const OpenLive = effect(Open, gen(function* () {
90269
90269
  const open = yield* tryPromise({
90270
- try: () => import("./open-D8mGyWmt.mjs"),
90270
+ try: () => import("./open-FIgTDosf.mjs"),
90271
90271
  catch: (cause) => new OpenError$1({
90272
90272
  message: "failed to load browser opener",
90273
90273
  cause
@@ -91787,6 +91787,31 @@ function describeHydrationOutcome(outcome) {
91787
91787
  };
91788
91788
  }
91789
91789
  //#endregion
91790
+ //#region src/runtimeReplication/replicaThreadQuarantine.ts
91791
+ /**
91792
+ * Which replica threads hydration should stop retrying.
91793
+ *
91794
+ * A thread that fails its identity or generation check was recorded against a
91795
+ * runtime that no longer exists. Nothing about a later boot changes that, and
91796
+ * because a failed thread is never inserted it never becomes "known", so the
91797
+ * next boot fetches and fails it again. Measured on a production sprite: 24
91798
+ * such threads retried on every one of three boots.
91799
+ *
91800
+ * Quarantine is deliberately NARROW. Only a permanent failure qualifies. A
91801
+ * thread that merely disappeared may come back, and a project that does not
91802
+ * resolve to an active workspace starts resolving the moment that project is
91803
+ * imported. Quarantining either would turn a recoverable gap into a permanent
91804
+ * one, which is worse than the retry it saves.
91805
+ */
91806
+ /** The one failure that a later boot can never turn into a success. */
91807
+ const PERMANENT_FAILURE = "failed identity or generation checks";
91808
+ function isQuarantinedReplicaThread(quarantined, threadId) {
91809
+ return quarantined.has(threadId);
91810
+ }
91811
+ function shouldQuarantineReplicaThread(detail) {
91812
+ return detail.includes(PERMANENT_FAILURE);
91813
+ }
91814
+ //#endregion
91790
91815
  //#region src/runtimeReplication/replicaHydrationIdentity.ts
91791
91816
  function stableReplicaDigest(...values) {
91792
91817
  return Crypto.createHash("sha256").update(values.join("\0")).digest("base64url");
@@ -92166,6 +92191,10 @@ const prepareReplicaSummary = fn$1("prepareReplicaSummary")(function* (expectedR
92166
92191
  state.skippedExisting += 1;
92167
92192
  return;
92168
92193
  }
92194
+ if (isQuarantinedReplicaThread(state.quarantinedThreadIds, summary.threadId)) {
92195
+ state.skippedQuarantined += 1;
92196
+ return;
92197
+ }
92169
92198
  const localResolution = yield* resolveLocalReplicaProjects(state, index, summary, dependencies);
92170
92199
  const localProjects = localResolution.projects;
92171
92200
  if (localProjects.length !== 1) return yield* replicaHydrationFailure("replica project '" + summary.projectId + "' does not resolve to one active workspace");
@@ -92206,13 +92235,18 @@ const prepareReplicaSummary = fn$1("prepareReplicaSummary")(function* (expectedR
92206
92235
  * repeated cursor) stay fatal on purpose: those mean the index itself cannot be
92207
92236
  * trusted, so continuing would hydrate an unknown mixture.
92208
92237
  */
92209
- function skipUnhydratableThread(operation, threadId, state) {
92238
+ function skipUnhydratableThread(operation, threadId, state, dependencies) {
92210
92239
  return operation.pipe(catchTag("RuntimeReplicaHydrationError", (error) => gen(function* () {
92211
92240
  state.unhydratableThreadIds.push(threadId);
92212
92241
  yield* logWarning$1("skipped a thread that could not be hydrated", {
92213
92242
  threadId,
92214
92243
  detail: error.detail
92215
92244
  });
92245
+ if (!shouldQuarantineReplicaThread(error.detail)) return;
92246
+ yield* dependencies.quarantineThread({
92247
+ threadId,
92248
+ detail: error.detail
92249
+ }).pipe(ignore);
92216
92250
  })));
92217
92251
  }
92218
92252
  const applyPreparedReplica = fn$1("applyPreparedReplica")(function* (expectedRuntimeId, prepared, state, dependencies) {
@@ -92240,7 +92274,7 @@ function prepareReplicaPages(expectedRuntimeId, cursor, state, dependencies) {
92240
92274
  if (index === null) return;
92241
92275
  if (state.runtimeIdentity === null) state.runtimeIdentity = index.runtime;
92242
92276
  else if (!replicaRuntimeMatches(state.runtimeIdentity, index.runtime)) return yield* replicaHydrationFailure("index runtime identity changed between pages");
92243
- yield* forEach(index.threads, (summary) => skipUnhydratableThread(prepareReplicaSummary(expectedRuntimeId, index, summary, state, dependencies), summary.threadId, state), {
92277
+ yield* forEach(index.threads, (summary) => skipUnhydratableThread(prepareReplicaSummary(expectedRuntimeId, index, summary, state, dependencies), summary.threadId, state, dependencies), {
92244
92278
  concurrency: 1,
92245
92279
  discard: true
92246
92280
  });
@@ -92261,16 +92295,19 @@ const hydrateRuntimeReplica = fn$1("hydrateRuntimeReplica")(function* (input, de
92261
92295
  runtimeIdentity: null,
92262
92296
  hydrated: 0,
92263
92297
  skippedExisting: 0,
92298
+ skippedQuarantined: 0,
92299
+ quarantinedThreadIds: yield* dependencies.readQuarantinedThreadIds().pipe(orElseSucceed(() => /* @__PURE__ */ new Set())),
92264
92300
  unhydratableThreadIds: []
92265
92301
  };
92266
92302
  yield* prepareReplicaPages(input.expectedRuntimeId, void 0, state, dependencies);
92267
- yield* forEach(state.prepared, (prepared) => skipUnhydratableThread(applyPreparedReplica(input.expectedRuntimeId, prepared, state, dependencies), prepared.summary.threadId, state), {
92303
+ yield* forEach(state.prepared, (prepared) => skipUnhydratableThread(applyPreparedReplica(input.expectedRuntimeId, prepared, state, dependencies), prepared.summary.threadId, state, dependencies), {
92268
92304
  concurrency: 1,
92269
92305
  discard: true
92270
92306
  });
92271
92307
  const outcome = {
92272
92308
  hydrated: state.hydrated,
92273
92309
  skippedExisting: state.skippedExisting,
92310
+ skippedQuarantined: state.skippedQuarantined,
92274
92311
  skippedUnhydratable: state.unhydratableThreadIds.length,
92275
92312
  unhydratableThreadIds: [...state.unhydratableThreadIds]
92276
92313
  };
@@ -92325,6 +92362,29 @@ async function requestReplicaRead(input, fetchImpl = fetch) {
92325
92362
  }
92326
92363
  }
92327
92364
  //#endregion
92365
+ //#region src/runtimeReplication/replicaThreadQuarantineStore.ts
92366
+ /**
92367
+ * Durable record of replica threads that can never hydrate.
92368
+ *
92369
+ * Lives on the guest rather than in CMMD's replica because CMMD exposes no
92370
+ * write surface for it. That bounds what this can fix: it stops one Environment
92371
+ * re-fetching known-dead threads on every boot, and it does NOT remove the rows
92372
+ * from durable history, so a fresh Environment pays the cost once again.
92373
+ */
92374
+ const makeReplicaThreadQuarantineStore = gen(function* () {
92375
+ const sql = yield* SqlClient;
92376
+ const readQuarantinedThreadIds = () => sql`SELECT thread_id FROM replica_thread_quarantine`.pipe(map$3((rows) => new Set(rows.map((row) => row.thread_id))), mapError((cause) => replicaHydrationFailure("quarantine read failed", cause)));
92377
+ const quarantineThread = (input) => sql`
92378
+ INSERT INTO replica_thread_quarantine (thread_id, detail, quarantined_at)
92379
+ VALUES (${input.threadId}, ${input.detail}, ${(/* @__PURE__ */ new Date()).toISOString()})
92380
+ ON CONFLICT (thread_id) DO NOTHING
92381
+ `.pipe(asVoid, mapError((cause) => replicaHydrationFailure("quarantine write failed", cause)));
92382
+ return {
92383
+ readQuarantinedThreadIds,
92384
+ quarantineThread
92385
+ };
92386
+ });
92387
+ //#endregion
92328
92388
  //#region src/runtimeReplication/replicaHydrationCloud.ts
92329
92389
  const REPLICA_INDEX_PAGE_SIZE = 100;
92330
92390
  const registerCloudRuntimeForReplicaHydration = gen(function* () {
@@ -92361,6 +92421,7 @@ const hydrateCloudRuntimeReplica = gen(function* () {
92361
92421
  }),
92362
92422
  catch: (cause) => replicaHydrationFailure("runtime replica read failed", cause)
92363
92423
  });
92424
+ const quarantine = yield* makeReplicaThreadQuarantineStore;
92364
92425
  const dependencies = {
92365
92426
  dispatch: (command) => engine.dispatch(command).pipe(mapError((cause) => replicaHydrationFailure("orchestration dispatch failed", cause))),
92366
92427
  getReadModel: engine.getReadModel,
@@ -92368,6 +92429,8 @@ const hydrateCloudRuntimeReplica = gen(function* () {
92368
92429
  readThread: ({ threadId }) => request({ threadId }),
92369
92430
  restoreThreadCursor: (input) => outbox.restoreThreadCursor(input).pipe(mapError((cause) => replicaHydrationFailure("cursor restore failed", cause))),
92370
92431
  resolveProjectRepositoryFullName: (workspaceRoot) => repositoryIdentityResolver.resolve(workspaceRoot).pipe(map$3((identity) => identity?.owner && identity.name ? `${identity.owner}/${identity.name}` : null), mapError((cause) => replicaHydrationFailure("repository identity resolution failed", cause))),
92432
+ readQuarantinedThreadIds: quarantine.readQuarantinedThreadIds,
92433
+ quarantineThread: quarantine.quarantineThread,
92371
92434
  now: () => (/* @__PURE__ */ new Date()).toISOString()
92372
92435
  };
92373
92436
  return yield* hydrateRuntimeReplica({ expectedRuntimeId: grant.runtimeId }, dependencies);
@@ -109614,6 +109677,28 @@ var _072_ProjectDefaultThreadEnvMode_default = gen(function* () {
109614
109677
  `;
109615
109678
  });
109616
109679
  //#endregion
109680
+ //#region src/persistence/Migrations/073_ReplicaThreadQuarantine.ts
109681
+ /**
109682
+ * Threads that can never hydrate, so hydration stops retrying them.
109683
+ *
109684
+ * A thread recorded against a destroyed runtime fails its identity or
109685
+ * generation check forever. It is never inserted, so it never becomes "known",
109686
+ * so the next boot fetches and fails it again. Measured on a production sprite:
109687
+ * 24 such threads, retried on every one of three boots.
109688
+ *
109689
+ * Keyed by thread alone, not by runtime: the whole point is that the runtime
109690
+ * that recorded the row is gone.
109691
+ */
109692
+ var _073_ReplicaThreadQuarantine_default = gen(function* () {
109693
+ yield* (yield* SqlClient)`
109694
+ CREATE TABLE IF NOT EXISTS replica_thread_quarantine (
109695
+ thread_id TEXT NOT NULL PRIMARY KEY,
109696
+ detail TEXT NOT NULL,
109697
+ quarantined_at TEXT NOT NULL
109698
+ )
109699
+ `;
109700
+ });
109701
+ //#endregion
109617
109702
  //#region src/persistence/Migrations.ts
109618
109703
  /**
109619
109704
  * MigrationsLive - Migration runner with inline loader
@@ -109994,6 +110079,11 @@ const migrationEntries = [
109994
110079
  72,
109995
110080
  "ProjectDefaultThreadEnvMode",
109996
110081
  _072_ProjectDefaultThreadEnvMode_default
110082
+ ],
110083
+ [
110084
+ 73,
110085
+ "ReplicaThreadQuarantine",
110086
+ _073_ReplicaThreadQuarantine_default
109997
110087
  ]
109998
110088
  ];
109999
110089
  const makeMigrationLoader = (throughId) => fromRecord(Object.fromEntries(migrationEntries.filter(([id]) => throughId === void 0 || id <= throughId).map(([id, name, migration]) => [`${id}_${name}`, migration])));
@@ -182748,7 +182838,7 @@ var Vce = {
182748
182838
  function Ve() {
182749
182839
  return Vce;
182750
182840
  }
182751
- var Us = $U.homedir(), lk = $U.tmpdir(), { env: Vc } = process$1, Yce = (e) => {
182841
+ var Us = os.homedir(), lk = os.tmpdir(), { env: Vc } = process$1, Yce = (e) => {
182752
182842
  let t = path.join(Us, "Library");
182753
182843
  return {
182754
182844
  data: path.join(t, "Application Support", e),
@@ -192948,12 +193038,12 @@ function qZ(e, t) {
192948
193038
  }
192949
193039
  return n;
192950
193040
  }
192951
- var os$1 = "https://code.claude.com/docs/en", kKe = [
193041
+ var os$2 = "https://code.claude.com/docs/en", kKe = [
192952
193042
  {
192953
193043
  matches: (e) => e.path === "permissions.defaultMode" && e.code === "invalid_value",
192954
193044
  tip: {
192955
193045
  suggestion: "Valid modes: \"acceptEdits\" (ask before file changes), \"plan\" (analysis only), \"bypassPermissions\" (auto-accept all), or \"default\" (standard behavior)",
192956
- docLink: `${os$1}/iam#permission-modes`
193046
+ docLink: `${os$2}/iam#permission-modes`
192957
193047
  }
192958
193048
  },
192959
193049
  {
@@ -192968,7 +193058,7 @@ var os$1 = "https://code.claude.com/docs/en", kKe = [
192968
193058
  matches: (e) => e.path.startsWith("env.") && e.code === "invalid_type",
192969
193059
  tip: {
192970
193060
  suggestion: "Environment variables must be strings. Wrap numbers and booleans in quotes. Example: \"DEBUG\": \"true\", \"PORT\": \"3000\"",
192971
- docLink: `${os$1}/settings#environment-variables`
193061
+ docLink: `${os$2}/settings#environment-variables`
192972
193062
  }
192973
193063
  },
192974
193064
  {
@@ -192979,14 +193069,14 @@ var os$1 = "https://code.claude.com/docs/en", kKe = [
192979
193069
  matches: (e) => e.path.startsWith("hooks.") && e.code === "invalid_key",
192980
193070
  tip: {
192981
193071
  suggestion: "Not a recognized hook event. Common events: PreToolUse, PostToolUse, UserPromptSubmit, SessionStart, SessionEnd, Stop. Check spelling and capitalization.",
192982
- docLink: `${os$1}/hooks`
193072
+ docLink: `${os$2}/hooks`
192983
193073
  }
192984
193074
  },
192985
193075
  {
192986
193076
  matches: (e) => /\.hooks\.\d+\.command$/.test(e.path) && e.code === "invalid_type" && e.received === "undefined",
192987
193077
  tip: {
192988
193078
  suggestion: "Command hooks require `command`. For exec form (no shell), set `command` to the executable and `args` to its arguments: {\"type\": \"command\", \"command\": \"echo\", \"args\": [\"hi\"]}. For shell form, set `command` to the full shell string: {\"type\": \"command\", \"command\": \"echo hi\"}.",
192989
- docLink: `${os$1}/hooks#exec-form-and-shell-form`
193079
+ docLink: `${os$2}/hooks#exec-form-and-shell-form`
192990
193080
  }
192991
193081
  },
192992
193082
  {
@@ -193001,7 +193091,7 @@ var os$1 = "https://code.claude.com/docs/en", kKe = [
193001
193091
  matches: (e) => e.code === "unrecognized_keys",
193002
193092
  tip: {
193003
193093
  suggestion: "Check for typos or refer to the documentation for valid fields",
193004
- docLink: `${os$1}/settings`
193094
+ docLink: `${os$2}/settings`
193005
193095
  }
193006
193096
  },
193007
193097
  {
@@ -193016,13 +193106,13 @@ var os$1 = "https://code.claude.com/docs/en", kKe = [
193016
193106
  matches: (e) => e.path === "permissions.additionalDirectories" && e.code === "invalid_type",
193017
193107
  tip: {
193018
193108
  suggestion: "Must be an array of directory paths. Example: [\"~/projects\", \"/tmp/workspace\"]. You can also use --add-dir flag or /add-dir command",
193019
- docLink: `${os$1}/iam#working-directories`
193109
+ docLink: `${os$2}/iam#working-directories`
193020
193110
  }
193021
193111
  }
193022
193112
  ], AKe = {
193023
- permissions: `${os$1}/iam#configuring-permissions`,
193024
- env: `${os$1}/settings#environment-variables`,
193025
- hooks: `${os$1}/hooks`
193113
+ permissions: `${os$2}/iam#configuring-permissions`,
193114
+ env: `${os$2}/settings#environment-variables`,
193115
+ hooks: `${os$2}/hooks`
193026
193116
  };
193027
193117
  function GZ(e) {
193028
193118
  let t = kKe.find((r) => r.matches(e));
@@ -257167,7 +257257,7 @@ var import_multicast_dns = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMi
257167
257257
  var dgram = __require("dgram");
257168
257258
  var thunky = require_thunky();
257169
257259
  var events = __require("events");
257170
- var os = __require("os");
257260
+ var os$1 = __require("os");
257171
257261
  var noop = function() {};
257172
257262
  module.exports = function(opts) {
257173
257263
  if (!opts) opts = {};
@@ -257298,14 +257388,14 @@ var import_multicast_dns = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMi
257298
257388
  return that;
257299
257389
  };
257300
257390
  function defaultInterface() {
257301
- var networks = os.networkInterfaces();
257391
+ var networks = os$1.networkInterfaces();
257302
257392
  var names = Object.keys(networks);
257303
257393
  for (var i = 0; i < names.length; i++) {
257304
257394
  var net = networks[names[i]];
257305
257395
  for (var j = 0; j < net.length; j++) {
257306
257396
  var iface = net[j];
257307
257397
  if (isIPv4(iface.family) && !iface.internal) {
257308
- if (os.platform() === "darwin" && names[i] === "en0") return iface.address;
257398
+ if (os$1.platform() === "darwin" && names[i] === "en0") return iface.address;
257309
257399
  return "0.0.0.0";
257310
257400
  }
257311
257401
  }
@@ -257313,7 +257403,7 @@ var import_multicast_dns = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMi
257313
257403
  return "127.0.0.1";
257314
257404
  }
257315
257405
  function allInterfaces() {
257316
- var networks = os.networkInterfaces();
257406
+ var networks = os$1.networkInterfaces();
257317
257407
  var names = Object.keys(networks);
257318
257408
  var res = [];
257319
257409
  for (var i = 0; i < names.length; i++) {
@@ -308149,7 +308239,7 @@ const connectLoginCommand = make$32("login", {
308149
308239
  })).json();
308150
308240
  },
308151
308241
  openBrowser: async (url) => {
308152
- const open = (await import("./open-D8mGyWmt.mjs")).default;
308242
+ const open = (await import("./open-FIgTDosf.mjs")).default;
308153
308243
  await open(url);
308154
308244
  return true;
308155
308245
  },
@@ -2,7 +2,7 @@
2
2
 
3
3
  import childProcess, { execFile } from "node:child_process";
4
4
  import fs from "node:fs";
5
- import $U from "node:os";
5
+ import os from "node:os";
6
6
  import path from "node:path";
7
7
  import { fileURLToPath } from "node:url";
8
8
  import process from "node:process";
@@ -49,7 +49,7 @@ function isInsideContainer() {
49
49
  //#region ../../node_modules/.bun/is-wsl@3.1.1/node_modules/is-wsl/index.js
50
50
  const isWsl = () => {
51
51
  if (process.platform !== "linux") return false;
52
- if ($U.release().toLowerCase().includes("microsoft")) {
52
+ if (os.release().toLowerCase().includes("microsoft")) {
53
53
  if (isInsideContainer()) return false;
54
54
  return true;
55
55
  }
@@ -589,4 +589,4 @@ defineLazyProperty(apps, "safari", () => detectPlatformBinary({ darwin: "Safari"
589
589
  //#endregion
590
590
  export { apps, open as default, openApp };
591
591
 
592
- //# sourceMappingURL=open-D8mGyWmt.mjs.map
592
+ //# sourceMappingURL=open-FIgTDosf.mjs.map
package/package.json CHANGED
@@ -9,7 +9,7 @@
9
9
  "cmmd-forge": "dist/bin.mjs"
10
10
  },
11
11
  "type": "module",
12
- "version": "0.9.9-rc.1",
12
+ "version": "0.9.9-rc.2",
13
13
  "engines": {
14
14
  "node": "^22.16 || ^23.11 || >=24.10"
15
15
  },