sneakoscope 8.1.2 → 8.2.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 (261) hide show
  1. package/README.md +96 -58
  2. package/crates/sks-core/Cargo.lock +1 -1
  3. package/crates/sks-core/Cargo.toml +1 -1
  4. package/dist/bin/fast-inline.js +5 -51
  5. package/dist/cli/command-manifest-lite.js +2 -1
  6. package/dist/cli/command-registry.js +2 -1
  7. package/dist/cli/global-mode-router.js +1 -1
  8. package/dist/cli/install-helpers-codex-lb-config.js +112 -914
  9. package/dist/cli/install-helpers-codex-lb-shared.js +3 -44
  10. package/dist/cli/install-helpers-install-support.js +4 -20
  11. package/dist/cli/install-helpers.js +36 -2716
  12. package/dist/cli/router.js +9 -9
  13. package/dist/commands/bridge-command-output.js +111 -0
  14. package/dist/commands/bridge.js +320 -0
  15. package/dist/commands/codex-app.js +39 -123
  16. package/dist/commands/doctor-helpers.js +380 -0
  17. package/dist/commands/doctor-profile.js +3 -0
  18. package/dist/commands/doctor.js +114 -873
  19. package/dist/commands/mad-sks.js +0 -4
  20. package/dist/config/skills-manifest.json +1 -1
  21. package/dist/core/agents/agent-effort-policy.js +11 -21
  22. package/dist/core/agents/agent-recursion-guard.js +26 -0
  23. package/dist/core/agents/agent-runner-codex-exec.js +7 -9
  24. package/dist/core/architecture-hardening/contracts/contracts.js +225 -161
  25. package/dist/core/architecture-hardening/state/state-service.js +49 -13
  26. package/dist/core/auto-review.js +15 -9
  27. package/dist/core/codex/codex-config-guard.js +8 -0
  28. package/dist/core/codex-adapter.js +8 -10
  29. package/dist/core/codex-app/codex-app-fast-ui-repair.js +7 -29
  30. package/dist/core/codex-app/codex-app-launcher.js +10 -12
  31. package/dist/core/codex-app/codex-app-restart.js +10 -12
  32. package/dist/core/codex-app/menubar/constants.js +2 -5
  33. package/dist/core/codex-app/menubar/global-install.js +3 -2
  34. package/dist/core/codex-app/menubar/installer/result.js +23 -0
  35. package/dist/core/codex-app/menubar/installer/runtime.js +28 -0
  36. package/dist/core/codex-app/menubar/installer.js +5 -96
  37. package/dist/core/codex-app/menubar/migration.js +1 -42
  38. package/dist/core/codex-app/openrouter-provider.js +0 -78
  39. package/dist/core/codex-app.js +97 -360
  40. package/dist/core/codex-control/codex-app-server-runtime-env.js +8 -28
  41. package/dist/core/codex-control/codex-current-image-path-real-probe.js +2 -3
  42. package/dist/core/codex-control/codex-current-web-search-probe.js +2 -3
  43. package/dist/core/codex-control/codex-sdk-adapter.js +5 -48
  44. package/dist/core/codex-control/codex-task-runner.js +16 -60
  45. package/dist/core/codex-control/desktop-bridge-launch-guard.js +227 -0
  46. package/dist/core/codex-exec-output-schema.js +11 -13
  47. package/dist/core/codex-lb/bridge-contracts.js +2 -0
  48. package/dist/core/codex-lb/bridge-runtime-validation/capability.js +104 -0
  49. package/dist/core/codex-lb/bridge-runtime-validation/catalog.js +52 -0
  50. package/dist/core/codex-lb/bridge-runtime-validation/shared.js +109 -0
  51. package/dist/core/codex-lb/bridge-runtime-validation/status.js +250 -0
  52. package/dist/core/codex-lb/bridge-runtime-validation.js +106 -0
  53. package/dist/core/codex-lb/capability-runner.js +505 -201
  54. package/dist/core/codex-lb/codex-lb-env.js +179 -12
  55. package/dist/core/codex-lb/combined-catalog/builder.js +86 -0
  56. package/dist/core/codex-lb/combined-catalog/contracts.js +5 -0
  57. package/dist/core/codex-lb/combined-catalog/normalize.js +170 -0
  58. package/dist/core/codex-lb/combined-catalog/persistence.js +207 -0
  59. package/dist/core/codex-lb/combined-catalog/shared.js +12 -0
  60. package/dist/core/codex-lb/combined-catalog/storage.js +171 -0
  61. package/dist/core/codex-lb/combined-catalog.js +12 -0
  62. package/dist/core/codex-lb/desktop-bridge/client.js +4 -1
  63. package/dist/core/codex-lb/desktop-bridge/header-policy.js +48 -63
  64. package/dist/core/codex-lb/desktop-bridge/http-forward.js +115 -83
  65. package/dist/core/codex-lb/desktop-bridge/launchd.js +20 -13
  66. package/dist/core/codex-lb/desktop-bridge/location-rewrite.js +4 -1
  67. package/dist/core/codex-lb/desktop-bridge/security.js +381 -267
  68. package/dist/core/codex-lb/desktop-bridge/server.js +148 -19
  69. package/dist/core/codex-lb/desktop-bridge/state.js +56 -70
  70. package/dist/core/codex-lb/desktop-bridge/types.js +6 -1
  71. package/dist/core/codex-lb/desktop-bridge/websocket-forward.js +281 -106
  72. package/dist/core/codex-lb/desktop-bridge-migration/historical-config.js +245 -0
  73. package/dist/core/codex-lb/desktop-bridge-migration/historical-toml.js +56 -0
  74. package/dist/core/codex-lb/desktop-bridge-migration/metadata.js +73 -0
  75. package/dist/core/codex-lb/desktop-bridge-migration/receipt.js +52 -0
  76. package/dist/core/codex-lb/desktop-bridge-migration/retired-runtime-cleanup.js +106 -0
  77. package/dist/core/codex-lb/desktop-bridge-migration/types.js +1 -0
  78. package/dist/core/codex-lb/desktop-bridge-migration.js +238 -0
  79. package/dist/core/codex-lb/desktop-controller-v3/catalog.js +257 -0
  80. package/dist/core/codex-lb/desktop-controller-v3/diagnostics.js +62 -0
  81. package/dist/core/codex-lb/desktop-controller-v3/lifecycle-commands.js +203 -0
  82. package/dist/core/codex-lb/desktop-controller-v3/live-probes.js +293 -0
  83. package/dist/core/codex-lb/desktop-controller-v3/preflight-probes.js +144 -0
  84. package/dist/core/codex-lb/desktop-controller-v3/provider-commands.js +182 -0
  85. package/dist/core/codex-lb/desktop-controller-v3/shared.js +279 -0
  86. package/dist/core/codex-lb/desktop-controller-v3/status.js +269 -0
  87. package/dist/core/codex-lb/desktop-controller-v3/types.js +1 -0
  88. package/dist/core/codex-lb/desktop-controller-v3/verification.js +89 -0
  89. package/dist/core/codex-lb/desktop-controller-v3.js +67 -0
  90. package/dist/core/codex-lb/desktop-controller.js +2 -1730
  91. package/dist/core/codex-lb/desktop-service.js +462 -627
  92. package/dist/core/codex-lb/migration-receipt.js +560 -118
  93. package/dist/core/codex-lb/probes/auxiliary-surfaces-probe.js +37 -53
  94. package/dist/core/codex-lb/probes/bridge-probe.js +125 -64
  95. package/dist/core/codex-lb/probes/catalog-probe.js +36 -136
  96. package/dist/core/codex-lb/probes/computer-use-probe.js +63 -39
  97. package/dist/core/codex-lb/probes/image-generation-probe.js +98 -41
  98. package/dist/core/codex-lb/probes/probe-evidence.js +32 -33
  99. package/dist/core/codex-lb/probes/voice-realtime-probe.js +69 -43
  100. package/dist/core/codex-lb/provider-credentials/configuration.js +127 -0
  101. package/dist/core/codex-lb/provider-credentials/locks.js +11 -0
  102. package/dist/core/codex-lb/provider-credentials/private-snapshot.js +28 -0
  103. package/dist/core/codex-lb/provider-credentials/removal.js +33 -0
  104. package/dist/core/codex-lb/provider-credentials/resolution.js +59 -0
  105. package/dist/core/codex-lb/provider-credentials/runtime.js +55 -0
  106. package/dist/core/codex-lb/provider-credentials/types.js +1 -0
  107. package/dist/core/codex-lb/provider-credentials/validation-store.js +137 -0
  108. package/dist/core/codex-lb/provider-credentials.js +5 -0
  109. package/dist/core/codex-lb/provider-registry.js +323 -0
  110. package/dist/core/codex-lb/provider-route-policy.js +118 -0
  111. package/dist/core/codex-lb/request-route-resolver.js +172 -0
  112. package/dist/core/codex-lb/route-index.js +148 -0
  113. package/dist/core/codex-lb/trusted-deep-evidence.js +158 -286
  114. package/dist/core/commands/basic-cli.js +1 -1
  115. package/dist/core/commands/check-command.js +3 -10
  116. package/dist/core/commands/gate-result-contract.js +10 -14
  117. package/dist/core/commands/intent-normalization/normalizer.js +1 -1
  118. package/dist/core/commands/mad-sks-command.js +2 -6
  119. package/dist/core/commands/release-command.js +11 -1
  120. package/dist/core/commands/telegram-command.js +46 -6
  121. package/dist/core/doctor/doctor-readiness-matrix.js +9 -7
  122. package/dist/core/doctor/skill-legacy-surface.js +5 -5
  123. package/dist/core/evidence/v2/evidence-key.js +30 -3
  124. package/dist/core/feature-fixtures.js +8 -1
  125. package/dist/core/feature-registry.js +3 -2
  126. package/dist/core/hooks-runtime/stop-finalization.js +11 -1
  127. package/dist/core/hooks-runtime/subagent-context.js +15 -1
  128. package/dist/core/hooks-runtime/subagent-skill-availability-contract.js +1 -1
  129. package/dist/core/hooks-runtime/tool-output-quarantine.js +1 -1
  130. package/dist/core/hooks-runtime.js +73 -87
  131. package/dist/core/image-ux-review/imagegen-adapter.js +108 -94
  132. package/dist/core/image-ux-review/real-callout-extractor.js +144 -14
  133. package/dist/core/imagegen/desktop-bridge-imagegen-target.js +77 -0
  134. package/dist/core/imagegen/imagegen-capability.js +43 -151
  135. package/dist/core/init/skills/filesystem.js +89 -0
  136. package/dist/core/init/skills/inventory.js +97 -0
  137. package/dist/core/init/skills/manifest.js +151 -0
  138. package/dist/core/init/skills.js +5 -325
  139. package/dist/core/init.js +0 -63
  140. package/dist/core/install/installed-package-smoke.js +1 -1
  141. package/dist/core/managed-assets/managed-assets-manifest.js +0 -1
  142. package/dist/core/policy/latest-version-guidance.js +15 -2
  143. package/dist/core/preflight/parallel-preflight-engine.js +27 -51
  144. package/dist/core/provider/provider-badge.js +9 -4
  145. package/dist/core/provider/provider-context.js +57 -107
  146. package/dist/core/providers/openrouter/openrouter-account.js +3 -1
  147. package/dist/core/providers/openrouter/openrouter-secret-store.js +91 -22
  148. package/dist/core/release/desktop-bridge-release-evidence.js +457 -0
  149. package/dist/core/release/gate-manifest.js +0 -1
  150. package/dist/core/release/main-push-guard.js +13 -0
  151. package/dist/core/release/main-push-receipt.js +11 -0
  152. package/dist/core/release/physical-release-gates.js +664 -61
  153. package/dist/core/release/publish-preflight.js +3 -3
  154. package/dist/core/release/release-gate-batch-runner.js +19 -6
  155. package/dist/core/release/release-gate-dag.js +33 -6
  156. package/dist/core/release/release-gate-node.js +1 -3
  157. package/dist/core/release/release-gate-output-contract.js +55 -0
  158. package/dist/core/release/release-pack-contract.js +1 -1
  159. package/dist/core/release/release-pack-receipt.js +209 -8
  160. package/dist/core/release/release-real-contract.js +1 -1
  161. package/dist/core/release/stage-publish.js +106 -28
  162. package/dist/core/retention/temp-lease.js +204 -0
  163. package/dist/core/retention.js +18 -195
  164. package/dist/core/routes.js +1 -2
  165. package/dist/core/safety/intent-contract/intent-contract.js +3 -3
  166. package/dist/core/subagents/naruto-host-credentials.js +6 -1
  167. package/dist/core/subagents/official-subagent-config.js +27 -9
  168. package/dist/core/subagents/official-subagent-runner.js +14 -31
  169. package/dist/core/subagents/wave-lifecycle.js +36 -8
  170. package/dist/core/subagents/wave-parent-guidance.js +2 -1
  171. package/dist/core/telegram/doctor.js +2 -2
  172. package/dist/core/telegram/liveness.js +10 -4
  173. package/dist/core/triwiki/context-graph/compiler/index.js +18 -18
  174. package/dist/core/update/update-migration-state/fast-mode-config.js +172 -0
  175. package/dist/core/update/update-migration-state/session-state-split.js +247 -0
  176. package/dist/core/update/update-migration-state/simple-stages.js +46 -0
  177. package/dist/core/update/update-migration-state.js +4 -481
  178. package/dist/core/version.js +1 -1
  179. package/dist/native/sks-menubar/Sources/OperationCoordinator.swift +28 -4
  180. package/dist/native/sks-menubar/Sources/OperationModels.swift +112 -2
  181. package/dist/native/sks-menubar/Sources/ProviderRouteExplanation.swift +21 -0
  182. package/dist/native/sks-menubar/Sources/ProvidersBridgeCatalog.swift +140 -0
  183. package/dist/native/sks-menubar/Sources/ProvidersOpenRouter.swift +107 -470
  184. package/dist/native/sks-menubar/Sources/ProvidersReliability.swift +27 -197
  185. package/dist/native/sks-menubar/Sources/ProvidersRoutingTruth.swift +335 -269
  186. package/dist/native/sks-menubar/Sources/ProvidersViewController.swift +233 -437
  187. package/dist/native/sks-menubar/Sources/RemoteCodingViewController.swift +16 -7
  188. package/dist/native/sks-menubar/Sources/SecureProcessEnvelope.swift +39 -9
  189. package/dist/native/sks-menubar/Sources/TelegramSupport.swift +56 -0
  190. package/dist/scripts/canonical-test-runner.js +7 -1
  191. package/dist/scripts/check-runtime-schemas.js +7 -2
  192. package/dist/scripts/codex-sdk-real-smoke-check.js +3 -1
  193. package/dist/scripts/desktop-bridge-capabilities-check.js +123 -0
  194. package/dist/scripts/{codex-lb-desktop-bridge-latency-check.js → desktop-bridge-latency-check.js} +61 -16
  195. package/dist/scripts/desktop-bridge-real-evidence-check.js +64 -0
  196. package/dist/scripts/{codex-lb-desktop-bridge-check.js → desktop-bridge-transport-check.js} +92 -155
  197. package/dist/scripts/desktop-bridge-transport-client.js +116 -0
  198. package/dist/scripts/desktop-bridge-unification-check.js +276 -0
  199. package/dist/scripts/doctor-fixes-codex-app-fast-ui-check.js +12 -8
  200. package/dist/scripts/gate-policy-audit-check.js +52 -2
  201. package/dist/scripts/provider-badge-context-check.js +55 -12
  202. package/dist/scripts/publish-preflight.js +1 -2
  203. package/dist/scripts/release-affected-selector-check.js +1 -2
  204. package/dist/scripts/release-aggressive-resource-governor-check.js +1 -1
  205. package/dist/scripts/release-cache-glob-hashing-check.js +1 -0
  206. package/dist/scripts/release-cache-version-neutral-fixture-check.js +1 -0
  207. package/dist/scripts/release-full-parallelism-blackbox.js +1 -1
  208. package/dist/scripts/release-gate-batch-runner-check.js +9 -0
  209. package/dist/scripts/release-gate-dag-runner-check.js +6 -0
  210. package/dist/scripts/release-gate-dag-runner.js +4 -2
  211. package/dist/scripts/release-metadata-check.js +16 -3
  212. package/dist/scripts/release-pack-receipt.js +29 -6
  213. package/dist/scripts/release-physical-gates-check.js +39 -0
  214. package/dist/scripts/release-real-check.js +7 -5
  215. package/package.json +6 -8
  216. package/release-gates.v2.json +156 -157
  217. package/runtime-required-scripts.json +11 -11
  218. package/schemas/codex/desktop-bridge-status-v3.schema.json +286 -0
  219. package/schemas/codex/desktop-capabilities-v3.schema.json +198 -0
  220. package/schemas/release/release-gate-node.schema.json +2 -3
  221. package/dist/cli/codex-lb-setup-warning-output.js +0 -17
  222. package/dist/cli/install-helpers-codex-lb-chain.js +0 -529
  223. package/dist/commands/codex-lb.js +0 -1232
  224. package/dist/core/architecture-hardening/migration/migration.js +0 -111
  225. package/dist/core/codex-app/auxiliary-oauth-policy.js +0 -36
  226. package/dist/core/codex-app/catalog-compat/catalog-service.js +0 -104
  227. package/dist/core/codex-app/child-policy/child-policy.js +0 -35
  228. package/dist/core/codex-app/desktop-routing-snapshot.js +0 -233
  229. package/dist/core/codex-app/glm-model-profile.js +0 -1
  230. package/dist/core/codex-app/glm-profile-installer.js +0 -191
  231. package/dist/core/codex-app/glm-profile-schema.js +0 -42
  232. package/dist/core/codex-app/multi-provider-router.js +0 -391
  233. package/dist/core/codex-app/openrouter-activate.js +0 -405
  234. package/dist/core/codex-app/openrouter-model-catalog.js +0 -217
  235. package/dist/core/codex-app/provider-child-policy.js +0 -64
  236. package/dist/core/codex-app/provider-mode.js +0 -91
  237. package/dist/core/codex-app/provider-session-policy.js +0 -85
  238. package/dist/core/codex-app/session-policy/session-pinning.js +0 -68
  239. package/dist/core/codex-control/codex-lb-launch-recovery.js +0 -319
  240. package/dist/core/codex-lb/codex-lb-setup.js +0 -293
  241. package/dist/core/codex-lb/codex-lb-tool-catalog.js +0 -456
  242. package/dist/core/codex-lb/codex-lb-tool-output-recovery.js +0 -290
  243. package/dist/core/codex-lb/desktop-center-credentials.js +0 -1083
  244. package/dist/core/codex-lb/desktop-mode.js +0 -32
  245. package/dist/core/codex-lb/desktop-real-evidence.js +0 -168
  246. package/dist/core/codex-lb/legacy-migration.js +0 -576
  247. package/dist/core/codex-lb/native-openai-transport/transport.js +0 -68
  248. package/dist/core/codex-lb/provider-routing/provider-router.js +0 -58
  249. package/dist/core/codex-lb/routing-truth.js +0 -327
  250. package/dist/core/imagegen/codex-lb-imagegen-target.js +0 -92
  251. package/dist/native/sks-menubar/Sources/ProvidersConnectTest.swift +0 -118
  252. package/dist/native/sks-menubar/Sources/ProvidersFastMode.swift +0 -17
  253. package/dist/native/sks-menubar/Sources/ProvidersMultiProvider.swift +0 -331
  254. package/dist/native/sks-menubar/Sources/ProvidersRoleModels.swift +0 -260
  255. package/dist/scripts/codex-lb-catalog-passthrough-check.js +0 -120
  256. package/dist/scripts/codex-lb-desktop-auth-invariant-check.js +0 -169
  257. package/dist/scripts/codex-lb-desktop-capabilities-check.js +0 -287
  258. package/dist/scripts/codex-lb-desktop-real-evidence-check.js +0 -123
  259. package/dist/scripts/codex-lb-fast-mode-truth-check.js +0 -114
  260. package/dist/scripts/codex-lb-fast-ui-preservation-check.js +0 -152
  261. package/dist/scripts/codex-lb-gpt56-fast-profile-check.js +0 -231
@@ -0,0 +1,238 @@
1
+ import fsp from 'node:fs/promises';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ import { DESKTOP_BRIDGE_MANAGED_BASE_URL_MARKER, DESKTOP_BRIDGE_MANAGED_MARKER, DESKTOP_BRIDGE_MANAGED_MODEL_CATALOG_MARKER, upsertDesktopBridgeManagedConfig } from '../../cli/install-helpers-codex-lb-config.js';
5
+ import { safeWriteCodexConfigToml } from '../codex-runtime/codex-desktop-config-policy.js';
6
+ import { messageOf as errorMessage } from '../errors/message.js';
7
+ import { readText } from '../fsx.js';
8
+ import { withFileLock } from '../locks/file-lock.js';
9
+ import { captureCodexAuthSnapshot } from './desktop-auth-invariant.js';
10
+ import { parseHistoricalProviderConfig, prepareHistoricalConfigForDesktopBridgeWriter } from './desktop-bridge-migration/historical-config.js';
11
+ import { hasTopLevelMarker } from './desktop-bridge-migration/historical-toml.js';
12
+ import { normalizedMetadataUpdates, sha256, validateMetadataUpdates, writeBufferAtomic } from './desktop-bridge-migration/metadata.js';
13
+ import { authSemanticIdentityPreserved, buildDesktopBridgeMigrationReceipt } from './desktop-bridge-migration/receipt.js';
14
+ import { backupDesktopBridgeMigrationFile, createDesktopBridgeUnificationReceiptId, desktopBridgeMigrationTransactionLockPath, desktopBridgeUnificationReceiptDir, fileSha256OrMissing, finalizeDesktopBridgeMigrationReceiptFiles, rollbackDesktopBridgeUnificationReceipt, writeDesktopBridgeUnificationReceipt } from './migration-receipt.js';
15
+ export { inspectHistoricalDesktopBridgeIntent } from './desktop-bridge-migration/historical-config.js';
16
+ export async function migrateDesktopBridgeConfig(input) {
17
+ const home = path.resolve(input.home || process.env.HOME || os.homedir());
18
+ return withFileLock({
19
+ lockPath: desktopBridgeMigrationTransactionLockPath(home),
20
+ timeoutMs: 30_000,
21
+ staleMs: 120_000
22
+ }, () => migrateDesktopBridgeConfigUnlocked(input));
23
+ }
24
+ async function migrateDesktopBridgeConfigUnlocked(input) {
25
+ const home = input.home || process.env.HOME || os.homedir();
26
+ const configPath = input.configPath || path.join(home, '.codex', 'config.toml');
27
+ const authPath = input.authPath || path.join(home, '.codex', 'auth.json');
28
+ const receiptDir = input.receiptDir || desktopBridgeUnificationReceiptDir(home);
29
+ const combinedCatalogPath = input.combinedCatalogPath
30
+ || path.join(home, '.codex', 'sks', 'sks-bridge-catalog.json');
31
+ const currentConfig = await readText(configPath, '');
32
+ const authBefore = await captureCodexAuthSnapshot({ home, authPath });
33
+ const historicalState = parseHistoricalProviderConfig(currentConfig);
34
+ const baseResult = {
35
+ schema: 'sks.desktop-bridge-unification-migration.v1',
36
+ config_path: configPath,
37
+ auth_path: authPath,
38
+ migrated_profiles: historicalState.migrated_profiles,
39
+ historical_gateway_auth_transport: historicalState.gateway_auth_transport,
40
+ credentials_deleted: false
41
+ };
42
+ if (historicalState.blockers.length > 0) {
43
+ return {
44
+ ...baseResult,
45
+ ok: false,
46
+ status: 'blocked',
47
+ managed_runtime: null,
48
+ receipt_path: null,
49
+ auth_semantic_identity_preserved: true,
50
+ blockers: historicalState.blockers
51
+ };
52
+ }
53
+ let nextConfig;
54
+ try {
55
+ validateConfigTarget(home, configPath);
56
+ const migrationInputConfig = prepareHistoricalConfigForDesktopBridgeWriter(currentConfig, historicalState);
57
+ nextConfig = upsertDesktopBridgeManagedConfig(migrationInputConfig, {
58
+ bridgeBaseUrl: input.bridgeBaseUrl,
59
+ combinedCatalogPath
60
+ });
61
+ validateMetadataUpdates(input.metadataUpdates || [], { home, configPath, authPath });
62
+ }
63
+ catch (error) {
64
+ const message = errorMessage(error);
65
+ return {
66
+ ...baseResult,
67
+ ok: false,
68
+ status: 'blocked',
69
+ managed_runtime: null,
70
+ receipt_path: null,
71
+ auth_semantic_identity_preserved: true,
72
+ blockers: [message.startsWith('historical_user_owned_config_conflict')
73
+ ? 'historical_user_owned_config_conflict'
74
+ : message],
75
+ error: message
76
+ };
77
+ }
78
+ const metadataUpdates = normalizedMetadataUpdates(input.metadataUpdates || []);
79
+ const metadataAlreadyCurrent = await Promise.all(metadataUpdates.map(async (update) => {
80
+ const currentSha = await fileSha256OrMissing(update.path);
81
+ return currentSha !== null && currentSha === sha256(update.text);
82
+ }));
83
+ const hasUnifiedMarkers = [
84
+ DESKTOP_BRIDGE_MANAGED_MARKER,
85
+ DESKTOP_BRIDGE_MANAGED_BASE_URL_MARKER,
86
+ DESKTOP_BRIDGE_MANAGED_MODEL_CATALOG_MARKER
87
+ ].every((marker) => hasTopLevelMarker(currentConfig, marker));
88
+ if (nextConfig === currentConfig && hasUnifiedMarkers && metadataAlreadyCurrent.every(Boolean)) {
89
+ const authAfter = await captureCodexAuthSnapshot({ home, authPath });
90
+ const authPreserved = authSemanticIdentityPreserved(authBefore, authAfter);
91
+ const configSha = sha256(currentConfig);
92
+ const configUnchanged = await fileSha256OrMissing(configPath) === configSha;
93
+ const authBytesUnchanged = authBefore.sha256 === authAfter.sha256;
94
+ if (!authPreserved || !configUnchanged || !authBytesUnchanged) {
95
+ return {
96
+ ...baseResult,
97
+ ok: false,
98
+ status: 'blocked',
99
+ managed_runtime: null,
100
+ receipt_path: null,
101
+ auth_semantic_identity_preserved: authPreserved,
102
+ blockers: [
103
+ ...(!configUnchanged ? ['desktop_bridge_config_changed_during_noop'] : []),
104
+ ...(!authPreserved ? ['desktop_oauth_identity_changed'] : []),
105
+ ...(authPreserved && !authBytesUnchanged ? ['desktop_auth_changed_during_noop'] : [])
106
+ ]
107
+ };
108
+ }
109
+ return {
110
+ ...baseResult,
111
+ ok: true,
112
+ status: 'already_migrated',
113
+ managed_runtime: 'desktop-bridge',
114
+ receipt_path: null,
115
+ auth_semantic_identity_preserved: true,
116
+ blockers: []
117
+ };
118
+ }
119
+ const now = input.now || new Date();
120
+ const receiptId = createDesktopBridgeUnificationReceiptId(now);
121
+ const backupDir = path.join(receiptDir, receiptId, 'files');
122
+ const backupInputs = [
123
+ { kind: 'config', path: configPath, owned: false },
124
+ ...metadataUpdates.map((update) => ({ kind: update.kind, path: update.path, owned: true }))
125
+ ];
126
+ const backups = [];
127
+ const mutatedPaths = new Set();
128
+ try {
129
+ for (const entry of backupInputs) {
130
+ backups.push({
131
+ kind: entry.kind,
132
+ ...await backupDesktopBridgeMigrationFile(entry.path, backupDir, entry.owned)
133
+ });
134
+ }
135
+ const configWrite = await safeWriteCodexConfigToml(configPath, currentConfig, nextConfig, 'desktop-bridge-unification-migration', { verifyUnchangedBeforeWrite: true });
136
+ if (!configWrite.ok)
137
+ throw new Error(`desktop_bridge_config_write_failed:${configWrite.status}`);
138
+ if (configWrite.status === 'written')
139
+ mutatedPaths.add(path.resolve(configPath));
140
+ if (await fileSha256OrMissing(configPath) !== sha256(nextConfig)) {
141
+ throw new Error('desktop_bridge_config_readback_failed');
142
+ }
143
+ for (const update of metadataUpdates) {
144
+ const backup = backups.find((entry) => path.resolve(entry.path) === path.resolve(update.path));
145
+ if (!backup)
146
+ throw new Error(`desktop_bridge_metadata_backup_missing:${update.path}`);
147
+ if (await fileSha256OrMissing(update.path) !== backup.before_sha256) {
148
+ throw new Error(`desktop_bridge_metadata_changed_during_migration:${update.path}`);
149
+ }
150
+ if (sha256(update.text) !== backup.before_sha256) {
151
+ mutatedPaths.add(path.resolve(update.path));
152
+ await writeBufferAtomic(update.path, Buffer.from(update.text));
153
+ if (await fileSha256OrMissing(update.path) !== sha256(update.text)) {
154
+ throw new Error(`desktop_bridge_metadata_readback_failed:${update.kind}`);
155
+ }
156
+ }
157
+ }
158
+ const authAfter = await captureCodexAuthSnapshot({ home, authPath });
159
+ if (!authSemanticIdentityPreserved(authBefore, authAfter)) {
160
+ throw new Error('desktop_oauth_identity_changed');
161
+ }
162
+ const finalized = await finalizeDesktopBridgeMigrationReceiptFiles(backups);
163
+ const rollbackFiles = finalized.map((file, index) => ({
164
+ ...file,
165
+ kind: backups[index].kind
166
+ }));
167
+ const receipt = buildDesktopBridgeMigrationReceipt({
168
+ receiptId,
169
+ createdAt: now.toISOString(),
170
+ configBeforeSha: sha256(currentConfig),
171
+ configAfterSha: sha256(nextConfig),
172
+ authBeforeSha: authBefore.sha256 || 'missing',
173
+ authAfterSha: authAfter.sha256 || 'missing',
174
+ historicalState,
175
+ newCatalogGeneration: input.newCatalogGeneration || null,
176
+ rollbackFiles
177
+ });
178
+ const receiptPath = await writeDesktopBridgeUnificationReceipt(receipt, { receiptDir });
179
+ return {
180
+ ...baseResult,
181
+ ok: true,
182
+ status: 'migrated',
183
+ managed_runtime: 'desktop-bridge',
184
+ receipt_path: receiptPath,
185
+ receipt,
186
+ auth_semantic_identity_preserved: true,
187
+ blockers: []
188
+ };
189
+ }
190
+ catch (error) {
191
+ const rollbackFiles = backups
192
+ .filter((file) => mutatedPaths.has(path.resolve(file.path)))
193
+ .map((file) => ({
194
+ ...file,
195
+ after_sha256: path.resolve(file.path) === path.resolve(configPath)
196
+ ? sha256(nextConfig)
197
+ : sha256(metadataUpdates.find((update) => path.resolve(update.path) === path.resolve(file.path))?.text || ''),
198
+ kind: file.kind
199
+ }));
200
+ const authAfter = await captureCodexAuthSnapshot({ home, authPath });
201
+ const provisional = buildDesktopBridgeMigrationReceipt({
202
+ receiptId,
203
+ createdAt: now.toISOString(),
204
+ configBeforeSha: sha256(currentConfig),
205
+ configAfterSha: await fileSha256OrMissing(configPath) || 'missing',
206
+ authBeforeSha: authBefore.sha256 || 'missing',
207
+ authAfterSha: authAfter.sha256 || 'missing',
208
+ historicalState,
209
+ newCatalogGeneration: input.newCatalogGeneration || null,
210
+ rollbackFiles,
211
+ blockers: ['desktop_bridge_unification_migration_failed'],
212
+ authSemanticIdentityPreserved: authSemanticIdentityPreserved(authBefore, authAfter)
213
+ });
214
+ const rollback = rollbackFiles.length > 0
215
+ ? await rollbackDesktopBridgeUnificationReceipt({ receipt: provisional, transactionLockHeld: true })
216
+ : undefined;
217
+ return {
218
+ ...baseResult,
219
+ ok: false,
220
+ status: 'failed',
221
+ managed_runtime: null,
222
+ receipt_path: null,
223
+ auth_semantic_identity_preserved: provisional.auth_semantic_identity_preserved,
224
+ blockers: [
225
+ 'desktop_bridge_unification_migration_failed',
226
+ ...(rollback && !rollback.ok ? ['desktop_bridge_unification_rollback_failed'] : [])
227
+ ],
228
+ ...(rollback ? { rollback } : {}),
229
+ error: errorMessage(error)
230
+ };
231
+ }
232
+ }
233
+ function validateConfigTarget(home, configPath) {
234
+ const canonicalPath = path.join(path.resolve(home), '.codex', 'config.toml');
235
+ if (!path.isAbsolute(configPath) || path.resolve(configPath) !== canonicalPath) {
236
+ throw new Error('desktop_bridge_config_path_not_canonical');
237
+ }
238
+ }
@@ -0,0 +1,257 @@
1
+ import { readText } from '../../fsx.js';
2
+ import { listOpenRouterModels } from '../../providers/openrouter/openrouter-account.js';
3
+ import { buildCombinedBridgeCatalog, readActiveCombinedBridgeCatalog, stageCombinedBridgeCatalog } from '../combined-catalog.js';
4
+ import { codexLbEnvPath, codexLbMetadataPath, loadCodexLbEnv, readCodexLbModelCatalog } from '../codex-lb-env.js';
5
+ import { bootstrapExistingDesktopBridgeService, desktopBridgeServicePaths, desktopBridgeServiceStatus, resolveDesktopBridgeActivationSettings } from '../desktop-service.js';
6
+ import { inspectHistoricalDesktopBridgeIntent, migrateDesktopBridgeConfig } from '../desktop-bridge-migration.js';
7
+ import { rollbackDesktopBridgeUnificationReceipt } from '../migration-receipt.js';
8
+ import { recordProviderCredentialValidation } from '../provider-credentials.js';
9
+ import { bridgeProviderRegistryPath, buildStoredBridgeProviderRegistry, loadStoredBridgeProviderRegistry, resolveBridgeProviderRegistry, serializeStoredBridgeProviderRegistry } from '../provider-registry.js';
10
+ import { buildBridgeRoutingPolicy, readBridgeRoutingPolicy } from '../provider-route-policy.js';
11
+ import { sha256Stable } from '../route-index.js';
12
+ import { bridgeBaseUrl, commandResult, controllerEnv, controllerPaths, nowIso, providerCode, providerRegistrySnapshot, resolveRawCredentials, resolveValidatedCredentials, safeCode, serializedSettings, stringArray, timeoutMs, unique } from './shared.js';
13
+ import { desktopBridgeStatusV3 } from './status.js';
14
+ export async function syncCatalog(options) {
15
+ const result = await syncCatalogInternal(options);
16
+ const status = await desktopBridgeStatusV3(options);
17
+ const activation = result.activation && typeof result.activation === 'object'
18
+ ? result.activation
19
+ : {};
20
+ return commandResult('catalog.sync', result.ok === true, status, { catalog_sync: result }, result.ok === true ? [] : stringArray(activation.blockers), options);
21
+ }
22
+ export async function syncCatalogInternal(options) {
23
+ const paths = controllerPaths(options);
24
+ const historicalIntent = inspectHistoricalDesktopBridgeIntent(await readText(paths.configPath, ''));
25
+ if (historicalIntent.blockers.length > 0)
26
+ return blockedSync(historicalIntent, historicalIntent.blockers);
27
+ const rawCredentials = await resolveRawCredentials(options, paths);
28
+ const storedRegistryRead = await loadStoredBridgeProviderRegistry({ home: paths.home });
29
+ if (storedRegistryRead.blockers.length > 0)
30
+ return blockedSync(historicalIntent, storedRegistryRead.blockers);
31
+ const storedRegistry = storedRegistryRead.registry || buildStoredBridgeProviderRegistry({
32
+ credentials: rawCredentials,
33
+ overrides: historicalIntent.providers
34
+ });
35
+ const initialRegistry = await resolveBridgeProviderRegistry({
36
+ home: paths.home,
37
+ credentials: rawCredentials,
38
+ storedRegistry
39
+ });
40
+ const catalogs = await fetchProviderCatalogs(initialRegistry, rawCredentials, paths, options);
41
+ const credentials = await resolveValidatedCredentials(options, paths);
42
+ const registry = await resolveBridgeProviderRegistry({ home: paths.home, credentials, storedRegistry });
43
+ const build = buildCombinedBridgeCatalog(registry, { catalogs, created_at: nowIso(options) });
44
+ const staging = await stageCombinedBridgeCatalog({
45
+ build,
46
+ catalogPath: paths.catalogPath,
47
+ routeIndexPath: paths.routeIndexPath
48
+ });
49
+ if (!staging.staged || !staging.catalog_path || !staging.pointer_text) {
50
+ return { ok: false, build, activation: activationResult(staging, false), migration: null };
51
+ }
52
+ const priorPolicy = await readBridgeRoutingPolicy(paths.routePolicyPath);
53
+ const readyProviders = ['codex-lb', 'openrouter'].filter((providerId) => registry.profiles[providerId].enabled
54
+ && registry.profiles[providerId].state === 'ready'
55
+ && Object.values(build.route_index.routes).some((route) => route.provider_id === providerId));
56
+ const previousDefault = priorPolicy.policy?.default_provider_id || null;
57
+ const historicalDefault = historicalIntent.default_provider_id;
58
+ const defaultProvider = previousDefault && readyProviders.includes(previousDefault)
59
+ ? previousDefault
60
+ : historicalDefault && readyProviders.includes(historicalDefault) ? historicalDefault
61
+ : readyProviders.length === 1 ? readyProviders[0] || null : null;
62
+ const policy = buildBridgeRoutingPolicy({
63
+ route_index: build.route_index,
64
+ catalog_generation: build.catalog.generation,
65
+ default_provider_id: defaultProvider,
66
+ changed_at: nowIso(options)
67
+ });
68
+ let settings;
69
+ let managedBridgeBaseUrl;
70
+ try {
71
+ settings = await resolveDesktopBridgeActivationSettings({
72
+ ...options,
73
+ home: paths.home,
74
+ providerRegistry: providerRegistrySnapshot(registry, build.route_index),
75
+ routePolicy: policy
76
+ });
77
+ managedBridgeBaseUrl = await bridgeBaseUrl(settings, { ...options, home: paths.home });
78
+ }
79
+ catch (error) {
80
+ const blocker = safeCode(error, 'desktop_bridge_client_capability_invalid');
81
+ return {
82
+ ok: false,
83
+ build,
84
+ activation: activationResult(staging, false, [blocker]),
85
+ migration: null,
86
+ active_generation_preserved: true
87
+ };
88
+ }
89
+ const migration = await migrateDesktopBridgeConfig({
90
+ home: paths.home,
91
+ configPath: paths.configPath,
92
+ authPath: paths.authPath,
93
+ receiptDir: paths.receiptDir,
94
+ bridgeBaseUrl: managedBridgeBaseUrl,
95
+ combinedCatalogPath: staging.catalog_path,
96
+ newCatalogGeneration: build.catalog.generation,
97
+ metadataUpdates: [
98
+ {
99
+ kind: 'provider_registry',
100
+ path: bridgeProviderRegistryPath(paths.home),
101
+ text: serializeStoredBridgeProviderRegistry(storedRegistry)
102
+ },
103
+ { kind: 'catalog_binding', path: staging.pointer_path, text: staging.pointer_text },
104
+ { kind: 'route_policy', path: paths.routePolicyPath, text: `${JSON.stringify(policy, null, 2)}\n` },
105
+ { kind: 'bridge_settings', path: desktopBridgeServicePaths(paths.home).settings_path, text: serializedSettings(settings) }
106
+ ]
107
+ });
108
+ if (!migration.ok) {
109
+ return {
110
+ ok: false,
111
+ build,
112
+ activation: activationResult(staging, false, migration.blockers),
113
+ migration,
114
+ active_generation_preserved: true
115
+ };
116
+ }
117
+ const active = await readActiveCombinedBridgeCatalog(paths.catalogPath, paths.routeIndexPath);
118
+ if (!active.ok
119
+ || active.catalog.generation !== build.catalog.generation
120
+ || active.route_index.generation !== build.route_index.generation) {
121
+ const rollback = migration.receipt
122
+ ? await rollbackDesktopBridgeUnificationReceipt({ receipt: migration.receipt })
123
+ : null;
124
+ return {
125
+ ok: false,
126
+ build,
127
+ activation: activationResult(staging, false, ['combined_catalog_activation_verification_failed']),
128
+ migration,
129
+ rollback,
130
+ active_generation_preserved: rollback?.ok === true
131
+ };
132
+ }
133
+ const serviceBefore = await (options.serviceStatusImpl || desktopBridgeServiceStatus)({ ...options, home: paths.home });
134
+ if (serviceBefore.installed || serviceBefore.running) {
135
+ await (options.bootstrapServiceImpl || bootstrapExistingDesktopBridgeService)({
136
+ ...options,
137
+ home: paths.home,
138
+ providerRegistry: providerRegistrySnapshot(registry, build.route_index),
139
+ routePolicy: policy
140
+ });
141
+ }
142
+ return { ok: true, build, activation: activationResult(staging, true), migration };
143
+ }
144
+ function blockedSync(historicalIntent, blockers) {
145
+ return {
146
+ ok: false,
147
+ historical_intent: historicalIntent,
148
+ activation: {
149
+ schema: 'sks.bridge-combined-catalog-activation.v1',
150
+ activated: false,
151
+ blockers
152
+ },
153
+ migration: null
154
+ };
155
+ }
156
+ function activationResult(staging, activated, blockers = staging.blockers) {
157
+ return {
158
+ schema: 'sks.bridge-combined-catalog-activation.v1',
159
+ activated,
160
+ generation: activated ? staging.generation : null,
161
+ previous_generation: staging.previous_generation,
162
+ catalog_path: activated ? staging.catalog_path : null,
163
+ route_index_path: activated ? staging.route_index_path : null,
164
+ pointer_path: staging.pointer_path,
165
+ blockers: unique(blockers)
166
+ };
167
+ }
168
+ async function fetchProviderCatalogs(registry, credentials, paths, options) {
169
+ const checkedAt = nowIso(options);
170
+ const expiresAt = new Date(new Date(checkedAt).getTime() + 15 * 60_000).toISOString();
171
+ const rows = await Promise.all(['codex-lb', 'openrouter'].map(async (providerId) => {
172
+ const profile = registry.profiles[providerId];
173
+ const credential = credentials[providerId];
174
+ if (!profile.enabled)
175
+ return [providerId, emptyProviderCatalog(providerId, 'not_started', checkedAt, [])];
176
+ if (!credential.secret) {
177
+ return [providerId, emptyProviderCatalog(providerId, 'failed', checkedAt, [`${providerCode(providerId)}_credential_missing`])];
178
+ }
179
+ if (providerId === 'codex-lb') {
180
+ const loaded = await loadCodexLbEnv({
181
+ home: paths.home,
182
+ envPath: options.envPath || codexLbEnvPath(paths.home),
183
+ metadataPath: options.metadataPath || codexLbMetadataPath(paths.home),
184
+ processEnv: controllerEnv(options)
185
+ });
186
+ const result = await readCodexLbModelCatalog({
187
+ loadedEnv: loaded,
188
+ ...(options.fetchImpl ? { fetchImpl: options.fetchImpl } : {}),
189
+ ...(options.codexLbLookup ? { lookup: options.codexLbLookup } : {}),
190
+ timeoutMs: timeoutMs(options),
191
+ gatewayAuthTransport: profile.endpoint.auth_transport === 'x-codex-lb-api-key'
192
+ ? 'x-codex-lb-api-key' : 'authorization-bearer'
193
+ });
194
+ await recordProviderCredentialValidation({
195
+ provider_id: providerId,
196
+ credential,
197
+ state: result.ok ? 'ready' : validationFailureState(result.blockers),
198
+ checked_at: checkedAt,
199
+ blockers: result.blockers,
200
+ home: paths.home,
201
+ validationPath: paths.validationPath
202
+ });
203
+ return [providerId, {
204
+ provider_id: providerId,
205
+ state: result.ok ? 'verified' : 'failed',
206
+ generation: result.ok ? sha256Stable({ provider_id: providerId, models: result.models }) : null,
207
+ models: { models: result.models },
208
+ checked_at: checkedAt,
209
+ expires_at: expiresAt,
210
+ blockers: result.blockers,
211
+ warnings: []
212
+ }];
213
+ }
214
+ const result = await listOpenRouterModels({
215
+ env: controllerEnv(options),
216
+ ...(options.fetchImpl ? { fetchImpl: options.fetchImpl } : {}),
217
+ timeoutMs: timeoutMs(options)
218
+ });
219
+ await recordProviderCredentialValidation({
220
+ provider_id: providerId,
221
+ credential,
222
+ state: result.ok ? 'ready' : validationFailureState(result.blockers),
223
+ checked_at: checkedAt,
224
+ blockers: result.blockers,
225
+ warnings: result.warnings,
226
+ home: paths.home,
227
+ validationPath: paths.validationPath
228
+ });
229
+ return [providerId, {
230
+ provider_id: providerId,
231
+ state: result.ok ? 'verified' : 'failed',
232
+ generation: result.ok ? sha256Stable({ provider_id: providerId, models: result.models }) : null,
233
+ models: result.models,
234
+ checked_at: checkedAt,
235
+ expires_at: expiresAt,
236
+ blockers: result.blockers,
237
+ warnings: result.warnings
238
+ }];
239
+ }));
240
+ return Object.fromEntries(rows);
241
+ }
242
+ function emptyProviderCatalog(providerId, state, checkedAt, blockers) {
243
+ return {
244
+ provider_id: providerId,
245
+ state,
246
+ generation: null,
247
+ models: [],
248
+ checked_at: checkedAt,
249
+ expires_at: null,
250
+ blockers,
251
+ warnings: []
252
+ };
253
+ }
254
+ function validationFailureState(blockers) {
255
+ return blockers.some((blocker) => /(?:401|403|auth|rejected|invalid_key|unauthorized)/i.test(blocker))
256
+ ? 'rejected' : 'unavailable';
257
+ }
@@ -0,0 +1,62 @@
1
+ import fs from 'node:fs/promises';
2
+ import { writeJsonAtomic } from '../../fsx.js';
3
+ import { validateDesktopCapabilityReportV3 } from '../bridge-runtime-validation.js';
4
+ const LAST_DIAGNOSTIC_SCHEMA = 'sks.desktop-bridge-last-diagnostic.v1';
5
+ const MAX_DIAGNOSTIC_BYTES = 4 * 1024 * 1024;
6
+ export async function writeLastDiagnostic(file, value) {
7
+ await writeJsonAtomic(file, value, { mode: 0o600 });
8
+ await fs.chmod(file, 0o600);
9
+ }
10
+ export async function readLastDiagnostic(file, currentCatalogGeneration, currentProcessGeneration, lastVerifiedProbeIds) {
11
+ const stat = await fs.lstat(file).catch(() => null);
12
+ if (!stat)
13
+ return null;
14
+ const expectedUid = typeof process.getuid === 'function' ? process.getuid() : null;
15
+ if (!stat.isFile() || stat.isSymbolicLink()
16
+ || stat.size > MAX_DIAGNOSTIC_BYTES
17
+ || (expectedUid !== null && stat.uid !== expectedUid)
18
+ || (process.platform !== 'win32' && (stat.mode & 0o777) !== 0o600))
19
+ return null;
20
+ try {
21
+ const value = JSON.parse(await fs.readFile(file, 'utf8'));
22
+ const validation = validateDesktopCapabilityReportV3(value.report);
23
+ if (value.schema !== LAST_DIAGNOSTIC_SCHEMA
24
+ || !validation.ok
25
+ || !desktopBridgeDiagnosticBindingCurrentV3(value, currentCatalogGeneration, currentProcessGeneration, lastVerifiedProbeIds))
26
+ return null;
27
+ return value;
28
+ }
29
+ catch {
30
+ return null;
31
+ }
32
+ }
33
+ export function desktopBridgeReportReadinessV3(report) {
34
+ const transportVerified = report?.summary.transport_level_satisfied === true
35
+ && (report.requested_level === 'transport' || report.requested_level === 'deep');
36
+ return {
37
+ bridge_ready: transportVerified && report?.summary.bridge_ready === true,
38
+ active_routes_ready: transportVerified && report?.summary.active_routes_ready === true
39
+ };
40
+ }
41
+ export function desktopBridgeDiagnosticBindingCurrentV3(value, currentCatalogGeneration, currentProcessGeneration, lastVerifiedProbeIds) {
42
+ const hasVerifiedProbeBinding = value.report.summary.transport_level_satisfied !== true
43
+ || lastVerifiedProbeIds.some((probeId) => probeId.startsWith(`${value.report.report_id}:`));
44
+ return Boolean(currentProcessGeneration
45
+ && value.catalog_generation === currentCatalogGeneration
46
+ && value.process_generation === currentProcessGeneration
47
+ && hasVerifiedProbeBinding);
48
+ }
49
+ export function verifiedLiveProbeIds(report) {
50
+ const scopes = [
51
+ report.bridge,
52
+ report.native_identity,
53
+ report.providers['codex-lb'],
54
+ report.providers.openrouter,
55
+ report.combined_catalog
56
+ ];
57
+ return scopes.flatMap((scope) => Object.values(scope.capabilities))
58
+ .filter((probe) => probe.state === 'verified'
59
+ && ['transport', 'deep_probe', 'artifact'].includes(probe.source))
60
+ .map((probe) => `${report.report_id}:${probe.scope}:${probe.capability}`)
61
+ .sort();
62
+ }