agent-embassy 1.4.0 → 1.5.0

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.
Files changed (82) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/CONTRIBUTING.md +61 -6
  3. package/README.md +6 -5
  4. package/README.zh-CN.md +4 -4
  5. package/SECURITY.md +128 -24
  6. package/dist/src/gateway/claude-peer.d.ts +8 -5
  7. package/dist/src/gateway/claude-peer.js +81 -36
  8. package/dist/src/gateway/claude-peer.js.map +1 -1
  9. package/dist/src/gateway/claude-runtime.d.ts +4 -0
  10. package/dist/src/gateway/claude-runtime.js +50 -39
  11. package/dist/src/gateway/claude-runtime.js.map +1 -1
  12. package/dist/src/gateway/cli-copy.d.ts +1 -1
  13. package/dist/src/gateway/cli-copy.en.d.ts +1 -1
  14. package/dist/src/gateway/cli-copy.en.js +1 -1
  15. package/dist/src/gateway/cli-copy.en.js.map +1 -1
  16. package/dist/src/gateway/cli-copy.js +1 -1
  17. package/dist/src/gateway/cli-copy.js.map +1 -1
  18. package/dist/src/gateway/cli-copy.zh-CN.d.ts +1 -1
  19. package/dist/src/gateway/cli-copy.zh-CN.js +1 -1
  20. package/dist/src/gateway/cli-copy.zh-CN.js.map +1 -1
  21. package/dist/src/gateway/cli.d.ts +1 -1
  22. package/dist/src/gateway/cli.js +3 -7
  23. package/dist/src/gateway/cli.js.map +1 -1
  24. package/dist/src/gateway/codex-app-server.d.ts +2 -1
  25. package/dist/src/gateway/codex-app-server.js +1 -1
  26. package/dist/src/gateway/codex-app-server.js.map +1 -1
  27. package/dist/src/gateway/codex-local-transport.d.ts +10 -6
  28. package/dist/src/gateway/codex-local-transport.js +101 -56
  29. package/dist/src/gateway/codex-local-transport.js.map +1 -1
  30. package/dist/src/gateway/compatibility.d.ts +11 -0
  31. package/dist/src/gateway/compatibility.js +104 -22
  32. package/dist/src/gateway/compatibility.js.map +1 -1
  33. package/dist/src/gateway/control.js +28 -30
  34. package/dist/src/gateway/control.js.map +1 -1
  35. package/dist/src/gateway/dashboard-copy.d.ts +1 -1
  36. package/dist/src/gateway/dashboard-copy.en.d.ts +36 -17
  37. package/dist/src/gateway/dashboard-copy.en.js +50 -31
  38. package/dist/src/gateway/dashboard-copy.en.js.map +1 -1
  39. package/dist/src/gateway/dashboard-copy.js +36 -17
  40. package/dist/src/gateway/dashboard-copy.js.map +1 -1
  41. package/dist/src/gateway/dashboard-copy.zh-CN.d.ts +36 -17
  42. package/dist/src/gateway/dashboard-copy.zh-CN.js +50 -31
  43. package/dist/src/gateway/dashboard-copy.zh-CN.js.map +1 -1
  44. package/dist/src/gateway/dashboard-model.d.ts +176 -8
  45. package/dist/src/gateway/dashboard-model.js +420 -70
  46. package/dist/src/gateway/dashboard-model.js.map +1 -1
  47. package/dist/src/gateway/dashboard.js +48 -135
  48. package/dist/src/gateway/dashboard.js.map +1 -1
  49. package/dist/src/gateway/live-dashboard-app/app.js +148 -247
  50. package/dist/src/gateway/live-dashboard-assets.js +2 -1
  51. package/dist/src/gateway/live-dashboard-assets.js.map +1 -1
  52. package/dist/src/gateway/live-dashboard-stream.d.ts +2 -2
  53. package/dist/src/gateway/live-dashboard-stream.js +2 -2
  54. package/dist/src/gateway/live-dashboard-stream.js.map +1 -1
  55. package/dist/src/gateway/progress-watch-machine.d.ts +50 -64
  56. package/dist/src/gateway/progress-watch-machine.js +42 -133
  57. package/dist/src/gateway/progress-watch-machine.js.map +1 -1
  58. package/dist/src/gateway/provenance-envelope.d.ts +2 -0
  59. package/dist/src/gateway/provenance-envelope.js +13 -3
  60. package/dist/src/gateway/provenance-envelope.js.map +1 -1
  61. package/dist/src/gateway/providers.d.ts +69 -15
  62. package/dist/src/gateway/providers.js +456 -109
  63. package/dist/src/gateway/providers.js.map +1 -1
  64. package/dist/src/gateway/server.d.ts +8 -5
  65. package/dist/src/gateway/server.js +94 -13
  66. package/dist/src/gateway/server.js.map +1 -1
  67. package/dist/src/gateway/service.d.ts +25 -1
  68. package/dist/src/gateway/service.js +252 -69
  69. package/dist/src/gateway/service.js.map +1 -1
  70. package/dist/src/gateway/store.d.ts +23 -28
  71. package/dist/src/gateway/store.js +479 -467
  72. package/dist/src/gateway/store.js.map +1 -1
  73. package/dist/src/gateway/types.d.ts +38 -11
  74. package/dist/src/gateway/types.js +141 -3
  75. package/dist/src/gateway/types.js.map +1 -1
  76. package/docs/CONFIGURATION.md +8 -8
  77. package/docs/CONFIGURATION.zh-CN.md +8 -8
  78. package/docs/DASHBOARD.md +29 -6
  79. package/docs/DASHBOARD.zh-CN.md +1 -1
  80. package/docs/GATEWAY-ARCHITECTURE.md +143 -57
  81. package/package.json +1 -1
  82. package/skills/embassy-peer/SKILL.md +2 -2
@@ -1,14 +1,14 @@
1
- import { createHash } from "node:crypto";
2
1
  import path from "node:path";
3
2
  import { BridgeError } from "../errors.js";
4
3
  import { CLAUDE_PEER_COMPATIBILITY, ClaudePeerAdapter, } from "./claude-peer.js";
5
- import { certifiedCompatibilityVersions, compatibilityProbeNames, evaluateCompatibilityAttestation, sharesCompatibilityMajor, } from "./compatibility.js";
4
+ import { certifiedCompatibilityVersions, compatibilityProbeNames, evaluateCompatibilityAttestation, isCompatibilityAttestation, isCompatibilityVersion, sharesCompatibilityMajor, UNKNOWN_COMPATIBILITY_VERSION, } from "./compatibility.js";
6
5
  import { CODEX_APP_SERVER_WRITABLE_VERSIONS, CodexAppServerConnector, CodexConnectorError, } from "./codex-app-server.js";
7
6
  import { LocalCodexTransportError } from "./codex-local-transport.js";
8
7
  import { ClaudeNativeHelperSupervisor, } from "./claude-helper-supervisor.js";
9
8
  import { isDashboardLocale } from "./locale.js";
10
9
  import { composeProvenanceEnvelope } from "./provenance-envelope.js";
11
10
  const LOCAL_HOST = "this-mac";
11
+ const STABLE_CLAUDE_ENDPOINT_GENERATION = "claude_local_endpoint";
12
12
  const NATIVE_CLAUDE_NAME = /^[a-z][a-z0-9_-]{0,31}$/;
13
13
  const PUBLIC_ALIAS = /^[a-z][a-z0-9_-]{0,31}@[a-z0-9](?:[a-z0-9.-]{0,61}[a-z0-9])?$/;
14
14
  const OPAQUE_ROUTE = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$/;
@@ -30,6 +30,27 @@ const CLAUDE_CLEAN_PREWRITE_RETRY_CODES = new Set([
30
30
  "CLAUDE_PEER_TARGET_CHANGED",
31
31
  "CLAUDE_PEER_WORKSPACE_UNATTESTED",
32
32
  ]);
33
+ const CODEX_TRUST_FAILURE_CODES = new Set([
34
+ "HOME_INVALID",
35
+ "MANAGED_CODEX_INVALID",
36
+ "LOCAL_APP_SERVER_ENDPOINT_UNSAFE",
37
+ "APP_SERVER_VERSION_MISMATCH",
38
+ "ENDPOINT_GENERATION_CHANGED",
39
+ "CLEANUP_FAILED",
40
+ "FACTORY_CLOSED",
41
+ "INVALID_CONFIGURATION",
42
+ "CODEX_ROUTE_CLEANUP_FAILED",
43
+ "CODEX_PROVIDER_CLEANUP_FAILED",
44
+ "CODEX_PROVIDER_CLEANUP_AMBIGUOUS",
45
+ "CODEX_PROVIDER_CLEANUP_TIMEOUT",
46
+ ]);
47
+ function isCodexTrustFailure(error) {
48
+ return (typeof error === "object" &&
49
+ error !== null &&
50
+ "code" in error &&
51
+ typeof error.code === "string" &&
52
+ CODEX_TRUST_FAILURE_CODES.has(error.code));
53
+ }
33
54
  function claudeCleanPrewriteResult(error) {
34
55
  if (error instanceof BridgeError &&
35
56
  error.code === "CLAUDE_PEER_MESSAGE_EXPIRED") {
@@ -105,33 +126,184 @@ function invokeCallback(operation) {
105
126
  }
106
127
  }
107
128
  function validateAttestedClaudeRuntime(runtime) {
108
- if (runtime.claudeCodeVersion !==
109
- CLAUDE_PEER_COMPATIBILITY.claudeCodeVersion ||
110
- [
111
- runtime.claudeExecutable,
112
- runtime.sessionsDir,
113
- runtime.socketDir,
114
- ].some((value) => !path.isAbsolute(value) ||
115
- path.normalize(value) !== value ||
116
- value.includes("\0"))) {
117
- throw new BridgeError("CLAUDE_RUNTIME_ATTESTATION_INVALID", "The local Claude provider requires exact pinned runtime evidence.");
118
- }
129
+ if (runtime.versionEvidenceFailure !== undefined) {
130
+ throw new BridgeError(runtime.versionEvidenceFailure, "The local Claude provider requires bounded, internally consistent runtime version evidence.");
131
+ }
132
+ const majorEvidence = runtime.claudeCodeVersion === UNKNOWN_COMPATIBILITY_VERSION
133
+ ? runtime.launcherVersionEvidence
134
+ : runtime.claudeCodeVersion;
135
+ if (majorEvidence === undefined ||
136
+ !sharesCompatibilityMajor(majorEvidence, CLAUDE_PEER_COMPATIBILITY.claudeCodeVersion)) {
137
+ throw new BridgeError(majorEvidence === undefined
138
+ ? "CLAUDE_VERSION_UNPARSEABLE"
139
+ : "CLAUDE_PEER_VERSION_UNSUPPORTED", "The local Claude provider requires a positively observed supported runtime major.");
140
+ }
141
+ validateClaudeRuntimePathEvidence(runtime);
119
142
  }
120
- function claudeEndpointGeneration(runtime) {
121
- return `claude_${createHash("sha256")
122
- .update(runtime.claudeCodeVersion)
123
- .update("\0")
124
- .update(runtime.claudeExecutable)
125
- .update("\0")
126
- .update(runtime.sessionsDir)
127
- .update("\0")
128
- .update(runtime.socketDir)
129
- .digest("hex")
130
- .slice(0, 32)}`;
143
+ function validateClaudeRuntimePathEvidence(runtime) {
144
+ if ([
145
+ runtime.claudeExecutable,
146
+ runtime.sessionsDir,
147
+ runtime.socketDir,
148
+ ].some((value) => !path.isAbsolute(value) ||
149
+ path.normalize(value) !== value ||
150
+ value.includes("\0"))) {
151
+ throw new BridgeError("CLAUDE_RUNTIME_ATTESTATION_INVALID", "The local Claude provider requires bounded absolute runtime evidence.");
152
+ }
131
153
  }
132
154
  function claudeRouteState(peer) {
133
155
  return peer.status === "idle" ? "idle" : "busy";
134
156
  }
157
+ function claudeRegistryObservation(discovery) {
158
+ const rejected = Object.entries(discovery.rejected)
159
+ .filter((entry) => Number.isSafeInteger(entry[1]) && entry[1] > 0)
160
+ .sort(([left], [right]) => left.localeCompare(right))
161
+ .map(([safeErrorCode, count]) => Object.freeze({ safeErrorCode, count }));
162
+ return Object.freeze({
163
+ entriesScanned: discovery.entriesScanned,
164
+ parseableRecords: discovery.parseableRecords,
165
+ rejected: Object.freeze(rejected),
166
+ });
167
+ }
168
+ function unavailableClaudeRegistryObservation() {
169
+ return Object.freeze({
170
+ entriesScanned: 0,
171
+ parseableRecords: 0,
172
+ rejected: Object.freeze([
173
+ Object.freeze({
174
+ safeErrorCode: "CLAUDE_REGISTRY_UNAVAILABLE",
175
+ count: 1,
176
+ }),
177
+ ]),
178
+ });
179
+ }
180
+ function isClaudeRegistryAvailabilityError(error) {
181
+ if (typeof error !== "object" || error === null || !("code" in error)) {
182
+ return false;
183
+ }
184
+ return (error.code === "ENOENT" ||
185
+ error.code === "EACCES" ||
186
+ error.code === "EPERM" ||
187
+ error.code === "UNSAFE_PEER_DIRECTORY");
188
+ }
189
+ /**
190
+ * A bounded observation-only surface for an installed provider generation
191
+ * whose protocol cannot be used safely. It deliberately performs no provider
192
+ * discovery, listener construction, route selection, or message write.
193
+ */
194
+ export class IncompatibleGatewayProvider {
195
+ identity;
196
+ protocol = "not-attested";
197
+ protocolVersion = "unknown";
198
+ version;
199
+ probes;
200
+ safeErrorCode;
201
+ initialized = false;
202
+ closed = false;
203
+ constructor(options) {
204
+ const attestation = evaluateCompatibilityAttestation({
205
+ surface: options.identity.provider,
206
+ version: options.version,
207
+ checkedAt: "2000-01-01T00:00:00.000Z",
208
+ certifiedVersions: certifiedCompatibilityVersions[options.identity.provider],
209
+ probes: options.probes,
210
+ });
211
+ if (attestation.tier !== "incompatible" ||
212
+ attestation.safeErrorCode !== options.safeErrorCode) {
213
+ throw new BridgeError("INCOMPATIBLE_PROVIDER_EVIDENCE_INVALID", "A disabled provider requires exact bounded incompatibility evidence.");
214
+ }
215
+ exactLocalHost(options.identity.hostId);
216
+ this.identity = Object.freeze({ ...options.identity });
217
+ this.version = options.version;
218
+ this.probes = Object.freeze(options.probes.map((probe) => Object.freeze({ ...probe })));
219
+ this.safeErrorCode = options.safeErrorCode;
220
+ }
221
+ compatibilitySurface() {
222
+ return { surface: this.identity.provider, version: this.version };
223
+ }
224
+ async runCompatibilityProbes() {
225
+ return this.probes;
226
+ }
227
+ acceptCompatibilityAttestation(attestation) {
228
+ if (!isCompatibilityAttestation(attestation) ||
229
+ attestation.surface !== this.identity.provider ||
230
+ attestation.version !== this.version ||
231
+ attestation.tier !== "incompatible" ||
232
+ attestation.safeErrorCode !== this.safeErrorCode ||
233
+ attestation.probes.length !== this.probes.length ||
234
+ attestation.probes.some((probe, index) => probe.name !== this.probes[index]?.name ||
235
+ probe.outcome !== this.probes[index]?.outcome ||
236
+ probe.safeErrorCode !== this.probes[index]?.safeErrorCode)) {
237
+ throw new BridgeError("INCOMPATIBLE_PROVIDER_ATTESTATION_MISMATCH", "The compatibility attestation does not match this disabled provider surface.");
238
+ }
239
+ }
240
+ async initialize(_callbacks) {
241
+ if (this.closed || this.initialized) {
242
+ throw new BridgeError("INCOMPATIBLE_PROVIDER_UNAVAILABLE", "The disabled provider surface cannot be initialized again.");
243
+ }
244
+ this.initialized = true;
245
+ return {
246
+ health: "degraded",
247
+ compatibility: "incompatible",
248
+ safeErrorCode: this.safeErrorCode,
249
+ };
250
+ }
251
+ async discoverClaudePeers() {
252
+ throw this.unavailable();
253
+ }
254
+ async selectRoute(_input) {
255
+ throw this.unavailable();
256
+ }
257
+ async assertWorkspaceDisjoint(_routeHandle, _stateRoot) {
258
+ throw this.unavailable();
259
+ }
260
+ async resolveReplyAddress(_address) {
261
+ throw this.unavailable();
262
+ }
263
+ async advertiseNativeCodexPeer(_input) {
264
+ throw this.unavailable();
265
+ }
266
+ async dispatch(_input) {
267
+ throw this.unavailable();
268
+ }
269
+ async releaseRoute(_routeHandle) { }
270
+ async close() {
271
+ this.closed = true;
272
+ }
273
+ unavailable() {
274
+ return new BridgeError(this.safeErrorCode, "This provider surface is observation-only because its compatibility boundary did not pass.");
275
+ }
276
+ }
277
+ export function createIncompatibleClaudeGatewayProvider(options) {
278
+ validateClaudeRuntimePathEvidence(options.runtime);
279
+ const [launcher, version, ...unobserved] = compatibilityProbeNames.claude;
280
+ return new IncompatibleGatewayProvider({
281
+ identity: {
282
+ provider: "claude",
283
+ hostId: LOCAL_HOST,
284
+ endpointGeneration: STABLE_CLAUDE_ENDPOINT_GENERATION,
285
+ },
286
+ version: options.version,
287
+ probes: [
288
+ passedProbe(launcher),
289
+ failedProbe(version, options.safeErrorCode),
290
+ ...unobserved.map((name) => failedProbe(name, options.safeErrorCode)),
291
+ ],
292
+ safeErrorCode: options.safeErrorCode,
293
+ });
294
+ }
295
+ export function createIncompatibleCodexGatewayProvider(options) {
296
+ const [installation, ...unobserved] = compatibilityProbeNames.codex;
297
+ return new IncompatibleGatewayProvider({
298
+ identity: options.identity,
299
+ version: options.version,
300
+ probes: [
301
+ passedProbe(installation),
302
+ ...unobserved.map((name) => failedProbe(name, options.safeErrorCode)),
303
+ ],
304
+ safeErrorCode: options.safeErrorCode,
305
+ });
306
+ }
135
307
  export class LocalClaudeGatewayProvider {
136
308
  identity;
137
309
  protocol = "claude-peer";
@@ -165,6 +337,7 @@ export class LocalClaudeGatewayProvider {
165
337
  /** Provider-owned terminal rejections that never enter the service queue. */
166
338
  rejectedNativeInbound = new Map();
167
339
  discoveryInFlight;
340
+ registryObservation;
168
341
  monitorTimer;
169
342
  /** Invocation-ordered registry mutations; the tail itself never rejects. */
170
343
  nativeCodexRegistrationTail = Promise.resolve();
@@ -175,10 +348,25 @@ export class LocalClaudeGatewayProvider {
175
348
  retiredNativeCodexListener;
176
349
  /** Fences discovery callbacks and monitor restarts across listener rotation. */
177
350
  nativeCodexSuccessionFreeze;
351
+ /** A completed failed probe set fences every authority-bearing operation. */
352
+ compatibilityBlocked = false;
353
+ compatibilityFailureCode;
178
354
  initialized = false;
179
355
  closed = false;
180
356
  constructor(options) {
181
357
  validateAttestedClaudeRuntime(options.runtime);
358
+ const compatibilityVersion = options.compatibilityVersion ??
359
+ (options.runtime.claudeCodeVersion === UNKNOWN_COMPATIBILITY_VERSION
360
+ ? options.runtime.launcherVersionEvidence
361
+ : options.runtime.claudeCodeVersion);
362
+ if (compatibilityVersion === undefined ||
363
+ !isCompatibilityVersion(compatibilityVersion) ||
364
+ (compatibilityVersion !== options.runtime.claudeCodeVersion &&
365
+ !(options.runtime.claudeCodeVersion ===
366
+ UNKNOWN_COMPATIBILITY_VERSION &&
367
+ options.runtime.launcherVersionEvidence === compatibilityVersion))) {
368
+ throw new BridgeError("CLAUDE_RUNTIME_ATTESTATION_INVALID", "Claude compatibility classification requires exact bounded launcher evidence.");
369
+ }
182
370
  if (options.locale !== undefined && !isDashboardLocale(options.locale)) {
183
371
  throw new BridgeError("DASHBOARD_LOCALE_UNSUPPORTED", "The Claude provider notice locale is unsupported.");
184
372
  }
@@ -186,12 +374,20 @@ export class LocalClaudeGatewayProvider {
186
374
  this.identity = {
187
375
  provider: "claude",
188
376
  hostId,
189
- endpointGeneration: claudeEndpointGeneration(options.runtime),
377
+ endpointGeneration: STABLE_CLAUDE_ENDPOINT_GENERATION,
190
378
  };
191
- this.runtimeVersion = options.runtime.claudeCodeVersion;
379
+ this.runtimeVersion = compatibilityVersion;
192
380
  this.maxPending = positiveBounded(options.maxPendingMessages, MAX_CLAUDE_PENDING, 4_096);
193
381
  this.discoveryPollMs = positiveBounded(options.discoveryPollMs, DEFAULT_CLAUDE_DISCOVERY_POLL_MS, 30_000);
194
382
  this.now = options.now ?? Date.now;
383
+ const protocolRuntime = {
384
+ claudeExecutable: options.runtime.claudeExecutable,
385
+ claudeCodeVersion: options.runtime.claudeCodeVersion === UNKNOWN_COMPATIBILITY_VERSION
386
+ ? options.runtime.launcherVersionEvidence
387
+ : options.runtime.claudeCodeVersion,
388
+ sessionsDir: options.runtime.sessionsDir,
389
+ socketDir: options.runtime.socketDir,
390
+ };
195
391
  this.peer = (options.peerFactory ??
196
392
  ((runtime, locale, deliveryNotices) => new ClaudePeerAdapter({
197
393
  sessionsDir: runtime.sessionsDir,
@@ -199,18 +395,18 @@ export class LocalClaudeGatewayProvider {
199
395
  attestedClaudeCodeVersion: runtime.claudeCodeVersion,
200
396
  locale,
201
397
  deliveryNotices,
202
- })))(options.runtime, options.locale ?? "en", options.deliveryNotices ?? "merged");
398
+ })))(protocolRuntime, options.locale ?? "en", options.deliveryNotices ?? "merged");
203
399
  this.nativeHelpers =
204
400
  options.nativeHelpers === undefined
205
401
  ? undefined
206
402
  : new ClaudeNativeHelperSupervisor({
207
403
  identity: this.identity,
208
- runtime: options.runtime,
404
+ runtime: protocolRuntime,
209
405
  locale: options.locale ?? "en",
210
406
  deliveryNotices: options.deliveryNotices ?? "merged",
211
407
  maxPendingMessages: this.maxPending,
212
408
  maxHelpers: positiveBounded(options.nativeHelpers.maxHelpers, 256, 128),
213
- callbacks: () => this.callbacks,
409
+ callbacks: () => this.compatibilityBlocked ? undefined : this.callbacks,
214
410
  ...(options.nativeHelpers.factory === undefined
215
411
  ? {}
216
412
  : { factory: options.nativeHelpers.factory }),
@@ -223,33 +419,49 @@ export class LocalClaudeGatewayProvider {
223
419
  const [launcher, version, registry, socket, protocol] = compatibilityProbeNames.claude;
224
420
  try {
225
421
  const discovery = await this.peer.discover();
226
- // Runtime discovery is deliberately per-record fail-safe: malformed,
227
- // dead, unsafe, or non-routable records are omitted rather than allowed
228
- // to poison the whole registry. Startup compatibility must use the same
229
- // boundary. Fail only when the bounded scan itself is incomplete, or a
230
- // nonempty registry contains no record with a parseable closed schema.
231
- const registryUnusable = discovery.truncated ||
232
- (discovery.entriesScanned > 0 && discovery.parseableRecords === 0);
422
+ this.registryObservation = claudeRegistryObservation(discovery);
233
423
  return [
234
424
  passedProbe(launcher),
235
425
  passedProbe(version),
236
- registryUnusable
237
- ? failedProbe(registry, "CLAUDE_REGISTRY_SCHEMA_REJECTED")
238
- : passedProbe(registry),
426
+ passedProbe(registry),
239
427
  passedProbe(socket),
240
428
  passedProbe(protocol),
241
429
  ];
242
430
  }
243
- catch {
431
+ catch (error) {
432
+ if (!isClaudeRegistryAvailabilityError(error))
433
+ throw error;
434
+ this.registryObservation = unavailableClaudeRegistryObservation();
435
+ // A missing or unreadable registry is provider-local incompatibility:
436
+ // keep the broker and the other surface alive, but fence this provider
437
+ // before listener construction can turn the same missing path into a
438
+ // broker-wide startup failure.
244
439
  return [
245
440
  passedProbe(launcher),
246
441
  passedProbe(version),
247
442
  failedProbe(registry, "CLAUDE_REGISTRY_UNAVAILABLE"),
248
- failedProbe(socket, "CLAUDE_SOCKET_VALIDATION_UNAVAILABLE"),
443
+ failedProbe(socket, "CLAUDE_REGISTRY_UNAVAILABLE"),
249
444
  passedProbe(protocol),
250
445
  ];
251
446
  }
252
447
  }
448
+ latestRegistryObservation() {
449
+ return this.registryObservation;
450
+ }
451
+ acceptCompatibilityAttestation(attestation) {
452
+ if (!isCompatibilityAttestation(attestation) ||
453
+ attestation.surface !== "claude" ||
454
+ attestation.version !== this.runtimeVersion) {
455
+ throw new BridgeError("CLAUDE_COMPATIBILITY_ATTESTATION_MISMATCH", "The Claude compatibility attestation does not match this runtime.");
456
+ }
457
+ this.compatibilityBlocked = attestation.tier === "incompatible";
458
+ this.compatibilityFailureCode = attestation.safeErrorCode;
459
+ if (this.compatibilityBlocked) {
460
+ if (this.monitorTimer !== undefined)
461
+ clearTimeout(this.monitorTimer);
462
+ this.monitorTimer = undefined;
463
+ }
464
+ }
253
465
  async initialize(callbacks) {
254
466
  if (this.closed) {
255
467
  throw new BridgeError("CLAUDE_PROVIDER_CLOSED", "The local Claude provider is closed.");
@@ -258,6 +470,14 @@ export class LocalClaudeGatewayProvider {
258
470
  throw new BridgeError("CLAUDE_PROVIDER_ALREADY_INITIALIZED", "The local Claude provider can be initialized only once.");
259
471
  }
260
472
  this.callbacks = callbacks;
473
+ if (this.compatibilityBlocked) {
474
+ this.initialized = true;
475
+ return {
476
+ health: "degraded",
477
+ compatibility: "incompatible",
478
+ safeErrorCode: this.compatibilityFailureCode ?? "CLAUDE_COMPATIBILITY_CHECK_FAILED",
479
+ };
480
+ }
261
481
  if (this.nativeHelpers !== undefined) {
262
482
  this.initialized = true;
263
483
  return { health: "healthy", compatibility: "compatible" };
@@ -297,8 +517,24 @@ export class LocalClaudeGatewayProvider {
297
517
  this.initialized = true;
298
518
  return { health: "healthy", compatibility: "compatible" };
299
519
  }
300
- catch {
520
+ catch (error) {
521
+ if (error instanceof BridgeError &&
522
+ error.code === "UNSAFE_PEER_DIRECTORY") {
523
+ this.compatibilityBlocked = true;
524
+ this.compatibilityFailureCode = "CLAUDE_REGISTRY_UNAVAILABLE";
525
+ this.registryObservation = unavailableClaudeRegistryObservation();
526
+ this.initialized = true;
527
+ return {
528
+ health: "degraded",
529
+ compatibility: "incompatible",
530
+ safeErrorCode: "CLAUDE_REGISTRY_UNAVAILABLE",
531
+ };
532
+ }
301
533
  this.callbacks = undefined;
534
+ if (error instanceof BridgeError &&
535
+ error.code === "CLAUDE_PEER_CALLBACK_UNSAFE") {
536
+ throw error;
537
+ }
302
538
  throw new BridgeError("CLAUDE_CALLBACK_UNAVAILABLE", "The private Claude callback listener could not be established.");
303
539
  }
304
540
  }
@@ -769,6 +1005,12 @@ export class LocalClaudeGatewayProvider {
769
1005
  active.inboundQuiesced = true;
770
1006
  }
771
1007
  async dispatch(input) {
1008
+ if (this.compatibilityBlocked) {
1009
+ return {
1010
+ state: "failed",
1011
+ safeErrorCode: this.compatibilityFailureCode ?? "CLAUDE_COMPATIBILITY_CHECK_FAILED",
1012
+ };
1013
+ }
772
1014
  if (this.nativeHelpers !== undefined) {
773
1015
  if (input.sourceAlias === undefined ||
774
1016
  !this.initialized ||
@@ -1050,7 +1292,7 @@ export class LocalClaudeGatewayProvider {
1050
1292
  }
1051
1293
  }
1052
1294
  assertReady() {
1053
- if (!this.initialized || this.closed) {
1295
+ if (!this.initialized || this.closed || this.compatibilityBlocked) {
1054
1296
  throw new BridgeError("CLAUDE_PROVIDER_UNAVAILABLE", "The local Claude provider is not available.", true);
1055
1297
  }
1056
1298
  }
@@ -1109,7 +1351,8 @@ export class LocalClaudeGatewayProvider {
1109
1351
  await this.rejectNativeInboundReceipt(listenerGeneration, message.receiptHandle, diagnosticCode);
1110
1352
  }
1111
1353
  };
1112
- if (this.closed ||
1354
+ if (this.compatibilityBlocked ||
1355
+ this.closed ||
1113
1356
  listenerGeneration.listener.closed ||
1114
1357
  !this.ownsNativeCodexListener(listenerGeneration)) {
1115
1358
  try {
@@ -1433,13 +1676,21 @@ export class LocalClaudeGatewayProvider {
1433
1676
  dispatchEpochAtStart.set(routeHandle, this.selectedDispatchEpoch.get(routeHandle) ?? 0);
1434
1677
  }
1435
1678
  const pendingAtStart = new Set(this.pendingTargetByGatewayId.values());
1436
- const discovery = await this.peer.discover();
1679
+ let discovery;
1680
+ try {
1681
+ discovery = await this.peer.discover();
1682
+ }
1683
+ catch (error) {
1684
+ this.registryObservation = unavailableClaudeRegistryObservation();
1685
+ throw error;
1686
+ }
1687
+ const registry = claudeRegistryObservation(discovery);
1688
+ this.registryObservation = registry;
1437
1689
  this.discovered.clear();
1438
1690
  const rows = [];
1439
1691
  for (const peer of discovery.peers) {
1440
1692
  if (peer.kind !== "interactive" ||
1441
- !NATIVE_CLAUDE_NAME.test(peer.alias) ||
1442
- peer.alias.startsWith("codex-")) {
1693
+ !NATIVE_CLAUDE_NAME.test(peer.alias)) {
1443
1694
  continue;
1444
1695
  }
1445
1696
  const alias = `${peer.alias}@${this.identity.hostId}`;
@@ -1480,7 +1731,7 @@ export class LocalClaudeGatewayProvider {
1480
1731
  this.emitClaudeRouteObservation(routeHandle, row.state, undefined, true);
1481
1732
  }
1482
1733
  }
1483
- return { peers: rows, complete: !discovery.truncated };
1734
+ return { peers: rows, complete: !discovery.truncated, registry };
1484
1735
  }
1485
1736
  emitClaudeRouteObservation(routeHandle, state, safeErrorCode, authoritative = false) {
1486
1737
  if (this.nativeCodexSuccessionFreeze !== undefined)
@@ -1540,33 +1791,52 @@ export class LocalClaudeGatewayProvider {
1540
1791
  export function createLocalClaudeGatewayProvider(options) {
1541
1792
  return new LocalClaudeGatewayProvider(options);
1542
1793
  }
1543
- function validateCodexFactory(factory) {
1794
+ function codexVersionFailureCode(version) {
1795
+ if (version === UNKNOWN_COMPATIBILITY_VERSION) {
1796
+ return "CODEX_APP_SERVER_VERSION_UNPARSEABLE";
1797
+ }
1798
+ if (!isCompatibilityVersion(version)) {
1799
+ throw new BridgeError("CODEX_FACTORY_ATTESTATION_INVALID", "The local Codex factory version evidence is malformed.");
1800
+ }
1801
+ return CODEX_APP_SERVER_WRITABLE_VERSIONS.some((certifiedVersion) => sharesCompatibilityMajor(certifiedVersion, version))
1802
+ ? undefined
1803
+ : "CODEX_APP_SERVER_VERSION_UNSUPPORTED";
1804
+ }
1805
+ function validateCodexFactory(factory, options = {}) {
1544
1806
  const schema = factory.schemaCompatibility;
1545
1807
  const write = factory.writeCompatibility;
1808
+ const versionFailure = codexVersionFailureCode(factory.appServerVersion);
1546
1809
  const certified = CODEX_APP_SERVER_WRITABLE_VERSIONS.includes(factory.appServerVersion);
1547
1810
  const schemaCandidate = schema.observedSchemaCandidate === true &&
1548
1811
  CODEX_APP_SERVER_WRITABLE_VERSIONS.some((version) => sharesCompatibilityMajor(version, factory.appServerVersion));
1812
+ const unsupportedVersionInvalid = versionFailure !== undefined &&
1813
+ (options.allowUnsupportedVersion !== true ||
1814
+ write !== null ||
1815
+ factory.writableReady);
1816
+ const supportedSchemaInvalid = versionFailure === undefined &&
1817
+ ((!certified &&
1818
+ (!schemaCandidate || write !== null || factory.writableReady)) ||
1819
+ schema.protocol !== "app-server-v2-stable" ||
1820
+ schema.steering?.method !== "turn/steer" ||
1821
+ schema.steering.requestSchema !== "expected-turn-id-text-v1" ||
1822
+ schema.steering.deliveryBoundary !== "next-tool-call-boundary" ||
1823
+ (write !== null &&
1824
+ (write.appServerVersion !== schema.appServerVersion ||
1825
+ write.endpointGeneration !== schema.endpointGeneration ||
1826
+ write.protocol !== schema.protocol ||
1827
+ write.observedSchemaCandidate !== schema.observedSchemaCandidate ||
1828
+ write.steering?.method !== schema.steering.method ||
1829
+ write.steering.requestSchema !== schema.steering.requestSchema ||
1830
+ write.steering.deliveryBoundary !==
1831
+ schema.steering.deliveryBoundary)) ||
1832
+ factory.writableReady !== (write !== null));
1549
1833
  if (factory.hostId !== LOCAL_HOST ||
1550
1834
  factory.protocol !== "codex-app-server" ||
1551
1835
  factory.protocolVersion !== factory.appServerVersion ||
1552
- (!certified &&
1553
- (!schemaCandidate || write !== null || factory.writableReady)) ||
1554
1836
  schema.appServerVersion !== factory.appServerVersion ||
1555
1837
  schema.endpointGeneration !== factory.endpointGeneration ||
1556
- schema.protocol !== "app-server-v2-stable" ||
1557
- schema.steering?.method !== "turn/steer" ||
1558
- schema.steering.requestSchema !== "expected-turn-id-text-v1" ||
1559
- schema.steering.deliveryBoundary !== "next-tool-call-boundary" ||
1560
- (write !== null &&
1561
- (write.appServerVersion !== schema.appServerVersion ||
1562
- write.endpointGeneration !== schema.endpointGeneration ||
1563
- write.protocol !== schema.protocol ||
1564
- write.observedSchemaCandidate !== schema.observedSchemaCandidate ||
1565
- write.steering?.method !== schema.steering.method ||
1566
- write.steering.requestSchema !== schema.steering.requestSchema ||
1567
- write.steering.deliveryBoundary !==
1568
- schema.steering.deliveryBoundary)) ||
1569
- factory.writableReady !== (write !== null)) {
1838
+ unsupportedVersionInvalid ||
1839
+ supportedSchemaInvalid) {
1570
1840
  throw new BridgeError("CODEX_FACTORY_ATTESTATION_INVALID", "The local Codex provider requires exact schema and write attestations.");
1571
1841
  }
1572
1842
  }
@@ -1628,6 +1898,7 @@ export class LocalCodexGatewayProvider {
1628
1898
  endpointActivationRetryTimer;
1629
1899
  callbackDrainFrozen = false;
1630
1900
  compatibilityAttested;
1901
+ compatibilityFailureCode;
1631
1902
  endpointUnavailable = false;
1632
1903
  pendingEndpointAttestation;
1633
1904
  pendingEndpointRefreshEvent;
@@ -1673,26 +1944,48 @@ export class LocalCodexGatewayProvider {
1673
1944
  }
1674
1945
  acceptCompatibilityAttestation(attestation) {
1675
1946
  const pending = this.pendingEndpointAttestation;
1676
- if (attestation.surface !== "codex" ||
1947
+ const pendingMatches = pending === undefined ||
1948
+ (attestation.checkedAt === pending.checkedAt &&
1949
+ attestation.tier === pending.tier &&
1950
+ attestation.safeErrorCode === pending.safeErrorCode &&
1951
+ attestation.probes.length === pending.probes.length &&
1952
+ attestation.probes.every((probe, index) => probe.name === pending.probes[index]?.name &&
1953
+ probe.outcome === pending.probes[index]?.outcome &&
1954
+ probe.safeErrorCode === pending.probes[index]?.safeErrorCode));
1955
+ if (!isCompatibilityAttestation(attestation) ||
1956
+ attestation.surface !== "codex" ||
1677
1957
  attestation.version !== this.factory.appServerVersion ||
1678
- attestation.tier !== "certified" ||
1679
- !CODEX_APP_SERVER_WRITABLE_VERSIONS.includes(attestation.version) ||
1958
+ !pendingMatches) {
1959
+ throw new BridgeError("CODEX_COMPATIBILITY_ATTESTATION_MISMATCH", "The Codex compatibility attestation does not match this endpoint.");
1960
+ }
1961
+ if (attestation.tier === "incompatible") {
1962
+ this.pendingEndpointAttestation = undefined;
1963
+ this.pendingEndpointRefreshEvent = undefined;
1964
+ this.clearEndpointActivationRetry();
1965
+ this.compatibilityAttested = false;
1966
+ this.endpointUnavailable = true;
1967
+ this.compatibilityFailureCode =
1968
+ attestation.safeErrorCode ?? "CODEX_COMPATIBILITY_CHECK_FAILED";
1969
+ return;
1970
+ }
1971
+ const writableCertified = attestation.tier === "certified" &&
1972
+ CODEX_APP_SERVER_WRITABLE_VERSIONS.includes(attestation.version) &&
1973
+ this.factory.writableReady &&
1974
+ this.factory.writeCompatibility !== null;
1975
+ const schemaObserved = attestation.tier === "schema_attested" &&
1976
+ !this.factory.writableReady &&
1977
+ this.factory.writeCompatibility === null;
1978
+ if ((!writableCertified && !schemaObserved) ||
1680
1979
  (this.endpointUnavailable && pending === undefined) ||
1681
- (pending !== undefined &&
1682
- (attestation.checkedAt !== pending.checkedAt ||
1683
- attestation.tier !== pending.tier ||
1684
- attestation.safeErrorCode !== pending.safeErrorCode ||
1685
- attestation.probes.length !== pending.probes.length ||
1686
- attestation.probes.some((probe, index) => probe.name !== pending.probes[index]?.name ||
1687
- probe.outcome !== pending.probes[index]?.outcome ||
1688
- probe.safeErrorCode !== pending.probes[index]?.safeErrorCode)))) {
1980
+ !pendingMatches) {
1689
1981
  throw new BridgeError("CODEX_COMPATIBILITY_ATTESTATION_MISMATCH", "The Codex compatibility attestation does not match this endpoint.");
1690
1982
  }
1691
1983
  this.pendingEndpointAttestation = undefined;
1692
1984
  this.pendingEndpointRefreshEvent = undefined;
1693
1985
  this.clearEndpointActivationRetry();
1694
- this.compatibilityAttested = true;
1695
- this.endpointUnavailable = false;
1986
+ this.compatibilityAttested = writableCertified;
1987
+ this.endpointUnavailable = !writableCertified;
1988
+ this.compatibilityFailureCode = undefined;
1696
1989
  }
1697
1990
  async runCompatibilityProbes() {
1698
1991
  return await this.runCompatibilityProbesFor(this.factory);
@@ -1731,19 +2024,26 @@ export class LocalCodexGatewayProvider {
1731
2024
  if (this.isEndpointGenerationChanged(error))
1732
2025
  throw error;
1733
2026
  if (stage === "transport") {
1734
- const controlFailure = error instanceof LocalCodexTransportError &&
2027
+ const availabilityFailure = error instanceof LocalCodexTransportError &&
1735
2028
  [
2029
+ "MANAGED_CODEX_UNAVAILABLE",
1736
2030
  "LOCAL_APP_SERVER_NOT_RUNNING",
1737
- "LOCAL_APP_SERVER_ENDPOINT_UNSAFE",
2031
+ "SPAWN_FAILED",
2032
+ "SPAWN_TIMEOUT",
2033
+ "PROXY_STDERR_LIMIT",
1738
2034
  "TRANSPORT_CONNECT_FAILED",
1739
2035
  ].includes(error.code);
2036
+ if (!availabilityFailure)
2037
+ throw error;
2038
+ const installationUnavailable = error instanceof LocalCodexTransportError &&
2039
+ error.code === "MANAGED_CODEX_UNAVAILABLE";
1740
2040
  return [
1741
- controlFailure
1742
- ? passedProbe(installation)
1743
- : failedProbe(installation, "CODEX_INSTALLATION_INVALID"),
1744
- failedProbe(control, controlFailure
1745
- ? "CODEX_CONTROL_SOCKET_UNAVAILABLE"
1746
- : "CODEX_COMPAT_PROBE_BLOCKED"),
2041
+ installationUnavailable
2042
+ ? failedProbe(installation, "CODEX_INSTALLATION_INVALID")
2043
+ : passedProbe(installation),
2044
+ failedProbe(control, installationUnavailable
2045
+ ? "CODEX_COMPAT_PROBE_BLOCKED"
2046
+ : "CODEX_CONTROL_SOCKET_UNAVAILABLE"),
1747
2047
  failedProbe(initialize, "CODEX_COMPAT_PROBE_BLOCKED"),
1748
2048
  failedProbe(threadList, "CODEX_COMPAT_PROBE_BLOCKED"),
1749
2049
  ];
@@ -1759,10 +2059,10 @@ export class LocalCodexGatewayProvider {
1759
2059
  }
1760
2060
  finally {
1761
2061
  if (connector !== undefined) {
1762
- await connector.close().catch(() => undefined);
2062
+ await connector.close();
1763
2063
  }
1764
2064
  else if (transport !== undefined) {
1765
- await transport.close().catch(() => undefined);
2065
+ await transport.close();
1766
2066
  }
1767
2067
  }
1768
2068
  }
@@ -1772,6 +2072,13 @@ export class LocalCodexGatewayProvider {
1772
2072
  }
1773
2073
  this.callbacks = callbacks;
1774
2074
  this.initialized = true;
2075
+ if (this.compatibilityFailureCode !== undefined) {
2076
+ return {
2077
+ health: "degraded",
2078
+ compatibility: "incompatible",
2079
+ safeErrorCode: this.compatibilityFailureCode,
2080
+ };
2081
+ }
1775
2082
  return this.factory.writeCompatibility === null
1776
2083
  ? {
1777
2084
  health: "degraded",
@@ -1977,6 +2284,9 @@ export class LocalCodexGatewayProvider {
1977
2284
  ...(input.progressWatchActive === true
1978
2285
  ? { progressWatchActive: true }
1979
2286
  : {}),
2287
+ ...(input.steer === true && input.queuedAhead !== undefined
2288
+ ? { queuedAhead: input.queuedAhead }
2289
+ : {}),
1980
2290
  });
1981
2291
  }
1982
2292
  catch (error) {
@@ -2214,13 +2524,23 @@ export class LocalCodexGatewayProvider {
2214
2524
  return route;
2215
2525
  }
2216
2526
  catch (error) {
2527
+ let cleanupFailure;
2217
2528
  if (connector !== undefined) {
2218
- await connector.close().catch(() => undefined);
2529
+ await connector.close().catch((closeError) => {
2530
+ cleanupFailure = closeError;
2531
+ });
2219
2532
  }
2220
2533
  else if (transport !== undefined) {
2221
- await transport.close().catch(() => undefined);
2534
+ await transport.close().catch((closeError) => {
2535
+ cleanupFailure = closeError;
2536
+ });
2222
2537
  }
2223
- if (this.isEndpointGenerationChanged(error))
2538
+ if (cleanupFailure !== undefined) {
2539
+ if (isCodexTrustFailure(cleanupFailure))
2540
+ throw cleanupFailure;
2541
+ throw new BridgeError("CODEX_ROUTE_CLEANUP_FAILED", "Codex route setup could not prove exact provider-process cleanup.");
2542
+ }
2543
+ if (isCodexTrustFailure(error))
2224
2544
  throw error;
2225
2545
  if (error instanceof BridgeError)
2226
2546
  throw error;
@@ -2415,18 +2735,14 @@ export class LocalCodexGatewayProvider {
2415
2735
  candidate = undefined;
2416
2736
  return undefined;
2417
2737
  }
2418
- this.factory = candidate;
2419
- candidate = undefined;
2420
- this.retiredEndpointGeneration = undefined;
2421
- this.compatibilityAttested = false;
2422
- this.endpointUnavailable = true;
2423
- this.pendingEndpointAttestation = attestation;
2738
+ const replacementFactory = candidate;
2424
2739
  const refreshedRoutes = [];
2740
+ const stagedRoutes = [];
2425
2741
  for (const routeHandle of [...this.trackedRoutes]) {
2426
2742
  if (this.closing || this.closed)
2427
2743
  break;
2428
2744
  try {
2429
- const route = await this.createRoute(routeHandle, this.factory, false);
2745
+ const route = await this.createRoute(routeHandle, replacementFactory, false);
2430
2746
  if (this.closing || this.closed) {
2431
2747
  await this.closeRoute(route);
2432
2748
  break;
@@ -2436,17 +2752,34 @@ export class LocalCodexGatewayProvider {
2436
2752
  await this.closeRoute(route);
2437
2753
  throw new BridgeError("CODEX_ROUTE_SETUP_REJECTED", "The exact Codex task was stale on the replacement endpoint.");
2438
2754
  }
2439
- this.routes.set(routeHandle, route);
2440
- this.routesRequiringRecovery.delete(routeHandle);
2441
- this.routeRecoveryAttempts.delete(routeHandle);
2442
- refreshedRoutes.push({ routeHandle, state });
2755
+ stagedRoutes.push({ routeHandle, route, state });
2443
2756
  }
2444
- catch {
2757
+ catch (error) {
2445
2758
  this.routesRequiringRecovery.add(routeHandle);
2759
+ if (isCodexTrustFailure(error))
2760
+ throw error;
2446
2761
  }
2447
2762
  }
2448
- if (this.closing || this.closed)
2763
+ if (this.closing || this.closed) {
2764
+ await replacementFactory.close();
2765
+ candidate = undefined;
2449
2766
  return undefined;
2767
+ }
2768
+ this.factory = replacementFactory;
2769
+ candidate = undefined;
2770
+ this.retiredEndpointGeneration = undefined;
2771
+ this.compatibilityAttested = false;
2772
+ this.endpointUnavailable = true;
2773
+ this.pendingEndpointAttestation = attestation;
2774
+ for (const staged of stagedRoutes) {
2775
+ this.routes.set(staged.routeHandle, staged.route);
2776
+ this.routesRequiringRecovery.delete(staged.routeHandle);
2777
+ this.routeRecoveryAttempts.delete(staged.routeHandle);
2778
+ refreshedRoutes.push({
2779
+ routeHandle: staged.routeHandle,
2780
+ state: staged.state,
2781
+ });
2782
+ }
2450
2783
  const result = {
2451
2784
  event: {
2452
2785
  outcome: "compatible",
@@ -2474,7 +2807,7 @@ export class LocalCodexGatewayProvider {
2474
2807
  }
2475
2808
  catch (error) {
2476
2809
  if (candidate !== undefined && candidate !== previousFactory) {
2477
- await candidate.close().catch(() => undefined);
2810
+ await candidate.close();
2478
2811
  }
2479
2812
  throw error;
2480
2813
  }
@@ -2490,7 +2823,10 @@ export class LocalCodexGatewayProvider {
2490
2823
  let candidate;
2491
2824
  try {
2492
2825
  candidate = await refreshFactory();
2493
- validateCodexFactory(candidate);
2826
+ const versionFailure = codexVersionFailureCode(candidate.appServerVersion);
2827
+ validateCodexFactory(candidate, {
2828
+ allowUnsupportedVersion: versionFailure !== undefined,
2829
+ });
2494
2830
  if (candidate.hostId !== previous.hostId ||
2495
2831
  candidate.protocol !== previousFactory.protocol) {
2496
2832
  throw new BridgeError("CODEX_FACTORY_ATTESTATION_INVALID", "The replacement Codex factory moved outside its pinned boundary.");
@@ -2500,14 +2836,25 @@ export class LocalCodexGatewayProvider {
2500
2836
  if (candidate !== previousFactory)
2501
2837
  await candidate.close();
2502
2838
  candidate = undefined;
2839
+ if (this.endpointUnavailable &&
2840
+ this.compatibilityFailureCode === undefined &&
2841
+ !this.factory.writableReady &&
2842
+ this.factory.writeCompatibility === null) {
2843
+ throw new BridgeError("CODEX_MONITOR_ONLY", "This Codex App Server remains monitor-only until a write-attested generation is available.");
2844
+ }
2503
2845
  if (attempt + 1 < MAX_CODEX_ENDPOINT_REFRESH_CANDIDATES) {
2504
2846
  continue;
2505
2847
  }
2506
2848
  throw new BridgeError("CODEX_ENDPOINT_GENERATION_CHURN", "The Codex endpoint did not stabilize within its bounded refresh window.", true);
2507
2849
  }
2508
- // Endpoint generations never inherit cached compatibility. Every
2509
- // candidate independently proves all four read-only wire probes.
2510
- const probes = await this.runCompatibilityProbesFor(candidate);
2850
+ // Endpoint generations never inherit cached compatibility. A version
2851
+ // outside the tested major is classified from OS-attested factory
2852
+ // evidence alone; no connector or transport may touch that protocol.
2853
+ const probes = versionFailure === undefined
2854
+ ? await this.runCompatibilityProbesFor(candidate)
2855
+ : compatibilityProbeNames.codex.map((name, index) => index === 0
2856
+ ? passedProbe(name)
2857
+ : failedProbe(name, versionFailure));
2511
2858
  const attestation = evaluateCompatibilityAttestation({
2512
2859
  surface: "codex",
2513
2860
  version: candidate.appServerVersion,