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,137 @@
1
+ import fs from 'node:fs/promises';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ import { writeJsonAtomic } from '../../fsx.js';
5
+ import { providerCode, unique } from './runtime.js';
6
+ import { resolveAllProviderCredentials, resolveProviderCredential } from './resolution.js';
7
+ import { withFileLock } from '../../locks/file-lock.js';
8
+ import { withProviderCredentialLock } from './locks.js';
9
+ export function providerCredentialValidationPath(home = process.env.HOME || os.homedir()) {
10
+ return path.join(path.resolve(home), '.codex', 'sks', 'sks-bridge-provider-validation.json');
11
+ }
12
+ export async function resolveAllProviderCredentialsWithValidation(options = {}) {
13
+ const home = options.home || process.env.HOME || os.homedir();
14
+ const raw = await resolveAllProviderCredentials({
15
+ codexLb: { home, ...(options.codexLb || {}) },
16
+ openrouter: { home, ...(options.openrouter || {}) }
17
+ });
18
+ const store = await readProviderCredentialValidationStore(options.validationPath || providerCredentialValidationPath(home));
19
+ const validationFor = (providerId) => {
20
+ const credential = raw[providerId];
21
+ const record = store.providers[providerId];
22
+ if (!record
23
+ || !credential.fingerprint
24
+ || record.credential_fingerprint !== credential.fingerprint
25
+ || record.endpoint_url !== String(credential.endpoint_url || '')) {
26
+ return null;
27
+ }
28
+ return {
29
+ state: record.state,
30
+ checked_at: record.checked_at,
31
+ blockers: record.blockers,
32
+ warnings: record.warnings
33
+ };
34
+ };
35
+ const [codexLb, openrouter] = await Promise.all([
36
+ resolveProviderCredential('codex-lb', {
37
+ home,
38
+ ...(options.codexLb || {}),
39
+ validation: validationFor('codex-lb')
40
+ }),
41
+ resolveProviderCredential('openrouter', {
42
+ home,
43
+ ...(options.openrouter || {}),
44
+ validation: validationFor('openrouter')
45
+ })
46
+ ]);
47
+ return { 'codex-lb': codexLb, openrouter };
48
+ }
49
+ export async function recordProviderCredentialValidation(input) {
50
+ if (!input.credential.fingerprint || !input.credential.endpoint_url) {
51
+ throw new Error(`${providerCode(input.provider_id)}_credential_validation_binding_missing`);
52
+ }
53
+ const home = input.home || process.env.HOME || os.homedir();
54
+ const file = input.validationPath || providerCredentialValidationPath(home);
55
+ return withProviderCredentialLock(home, input.provider_id, () => withFileLock({
56
+ lockPath: `${path.resolve(file)}.lock`, timeoutMs: 10_000, staleMs: 60_000,
57
+ }, async () => {
58
+ if (input.resolveCurrentCredential) {
59
+ const currentCredential = await input.resolveCurrentCredential();
60
+ if (currentCredential.fingerprint !== input.credential.fingerprint
61
+ || currentCredential.endpoint_url !== input.credential.endpoint_url) {
62
+ throw new Error(`${providerCode(input.provider_id)}_credential_validation_conflict`);
63
+ }
64
+ }
65
+ const current = await readProviderCredentialValidationStore(file);
66
+ const next = {
67
+ schema: 'sks.provider-credential-validation-store.v1',
68
+ providers: {
69
+ ...current.providers,
70
+ [input.provider_id]: {
71
+ credential_fingerprint: input.credential.fingerprint,
72
+ endpoint_url: input.credential.endpoint_url,
73
+ state: input.state,
74
+ checked_at: input.checked_at || new Date().toISOString(),
75
+ blockers: unique(input.blockers || []),
76
+ warnings: unique(input.warnings || [])
77
+ }
78
+ }
79
+ };
80
+ await writeJsonAtomic(file, next, { mode: 0o600 });
81
+ await fs.chmod(file, 0o600);
82
+ return next;
83
+ }));
84
+ }
85
+ export async function readProviderCredentialValidationStore(file) {
86
+ const empty = {
87
+ schema: 'sks.provider-credential-validation-store.v1',
88
+ providers: {}
89
+ };
90
+ const stat = await fs.lstat(file).catch(() => null);
91
+ if (!stat)
92
+ return empty;
93
+ const expectedUid = typeof process.getuid === 'function' ? process.getuid() : null;
94
+ if (!stat.isFile()
95
+ || stat.isSymbolicLink()
96
+ || (expectedUid !== null && stat.uid !== expectedUid)
97
+ || (process.platform !== 'win32' && (stat.mode & 0o777) !== 0o600)) {
98
+ throw new Error('provider_credential_validation_store_insecure');
99
+ }
100
+ let parsed;
101
+ try {
102
+ parsed = JSON.parse(await fs.readFile(file, 'utf8'));
103
+ }
104
+ catch {
105
+ throw new Error('provider_credential_validation_store_invalid');
106
+ }
107
+ if (!isValidationStore(parsed))
108
+ throw new Error('provider_credential_validation_store_invalid');
109
+ return parsed;
110
+ }
111
+ function isValidationStore(value) {
112
+ if (!value || typeof value !== 'object' || Array.isArray(value))
113
+ return false;
114
+ const row = value;
115
+ if (row.schema !== 'sks.provider-credential-validation-store.v1'
116
+ || !row.providers
117
+ || typeof row.providers !== 'object'
118
+ || Array.isArray(row.providers))
119
+ return false;
120
+ return Object.entries(row.providers).every(([providerId, entry]) => {
121
+ if (providerId !== 'codex-lb' && providerId !== 'openrouter')
122
+ return false;
123
+ if (!entry || typeof entry !== 'object' || Array.isArray(entry))
124
+ return false;
125
+ const record = entry;
126
+ return typeof record.credential_fingerprint === 'string'
127
+ && /^[a-f0-9]{16}$/.test(record.credential_fingerprint)
128
+ && typeof record.endpoint_url === 'string'
129
+ && ['ready', 'rejected', 'unavailable', 'stale'].includes(String(record.state || ''))
130
+ && typeof record.checked_at === 'string'
131
+ && Number.isFinite(Date.parse(record.checked_at))
132
+ && Array.isArray(record.blockers)
133
+ && record.blockers.every((item) => typeof item === 'string')
134
+ && Array.isArray(record.warnings)
135
+ && record.warnings.every((item) => typeof item === 'string');
136
+ });
137
+ }
@@ -0,0 +1,5 @@
1
+ export { configureProviderCredential } from './provider-credentials/configuration.js';
2
+ export { removeProviderCredential } from './provider-credentials/removal.js';
3
+ export { resolveAllProviderCredentials, resolveProviderCredential } from './provider-credentials/resolution.js';
4
+ export { providerCredentialStatus } from './provider-credentials/runtime.js';
5
+ export { providerCredentialValidationPath, readProviderCredentialValidationStore, recordProviderCredentialValidation, resolveAllProviderCredentialsWithValidation } from './provider-credentials/validation-store.js';
@@ -0,0 +1,323 @@
1
+ import crypto from 'node:crypto';
2
+ import fs from 'node:fs/promises';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+ import { providerCredentialStatus, resolveAllProviderCredentials } from './provider-credentials.js';
6
+ import { codexLbBaseUrlSecurityBlocker } from './codex-lb-env.js';
7
+ import { writeJsonAtomic } from '../fsx.js';
8
+ import { withFileLock } from '../locks/file-lock.js';
9
+ export const BRIDGE_PROVIDER_REGISTRY_SCHEMA = 'sks.bridge-provider-registry.v1';
10
+ export const BRIDGE_PROVIDER_REGISTRY_FILENAME = 'sks-bridge-provider-registry.json';
11
+ export const OPENROUTER_ALLOWED_ORIGIN = 'https://openrouter.ai';
12
+ export function bridgeProviderRegistryPath(home = process.env.HOME || os.homedir()) {
13
+ return path.join(path.resolve(home), '.codex', 'sks', BRIDGE_PROVIDER_REGISTRY_FILENAME);
14
+ }
15
+ export async function resolveBridgeProviderRegistry(options = {}) {
16
+ const credentials = options.credentials || await resolveAllProviderCredentials(options.credentialOptions || {});
17
+ const file = options.registryPath || bridgeProviderRegistryPath(options.home);
18
+ const read = options.storedRegistry
19
+ ? { registry: options.storedRegistry, blockers: [] }
20
+ : await readStoredBridgeProviderRegistry(file);
21
+ const stored = read.registry || defaultStoredRegistry(credentials);
22
+ const profiles = {
23
+ 'codex-lb': resolveProfile('codex-lb', stored.profiles['codex-lb'], credentials['codex-lb']),
24
+ openrouter: resolveProfile('openrouter', stored.profiles.openrouter, credentials.openrouter)
25
+ };
26
+ return {
27
+ schema: BRIDGE_PROVIDER_REGISTRY_SCHEMA,
28
+ generation: digest({
29
+ profiles: Object.fromEntries(Object.entries(profiles).map(([provider, profile]) => [provider, {
30
+ enabled: profile.enabled,
31
+ profile_generation: profile.profile_generation
32
+ }]))
33
+ }),
34
+ profiles,
35
+ blockers: unique([
36
+ ...read.blockers,
37
+ ...profiles['codex-lb'].blockers,
38
+ ...profiles.openrouter.blockers
39
+ ]),
40
+ warnings: unique([
41
+ ...profiles['codex-lb'].warnings,
42
+ ...profiles.openrouter.warnings
43
+ ])
44
+ };
45
+ }
46
+ export async function loadStoredBridgeProviderRegistry(options = {}) {
47
+ const file = options.registryPath || bridgeProviderRegistryPath(options.home);
48
+ const read = await readStoredBridgeProviderRegistry(file);
49
+ return { path: file, registry: read.registry, blockers: read.blockers };
50
+ }
51
+ export function buildStoredBridgeProviderRegistry(input) {
52
+ const base = defaultStoredRegistry(input.credentials);
53
+ const profiles = { ...base.profiles };
54
+ for (const providerId of ['codex-lb', 'openrouter']) {
55
+ const override = input.overrides?.[providerId];
56
+ if (!override?.present)
57
+ continue;
58
+ const endpoint = normalizeEndpoint(override.endpoint_url || base.profiles[providerId].endpoint_url || '');
59
+ const origin = endpointOrigin(endpoint);
60
+ const authTransport = override.auth_transport || defaultAuthTransport(providerId);
61
+ const blocker = providerEndpointSecurityBlocker(providerId, endpoint, origin ? [origin] : []);
62
+ if (blocker)
63
+ throw new Error(blocker);
64
+ profiles[providerId] = {
65
+ enabled: override.enabled,
66
+ endpoint_url: endpoint,
67
+ allowed_origins: origin ? [origin] : [],
68
+ auth_transport: authTransport
69
+ };
70
+ }
71
+ return { schema: BRIDGE_PROVIDER_REGISTRY_SCHEMA, profiles };
72
+ }
73
+ export function serializeStoredBridgeProviderRegistry(registry) {
74
+ if (!isStoredRegistry(registry))
75
+ throw new Error('provider_registry_schema_invalid');
76
+ return `${JSON.stringify(registry, null, 2)}\n`;
77
+ }
78
+ export async function setBridgeProviderEnabled(input) {
79
+ const credentials = input.credentials || await resolveAllProviderCredentials(input.credentialOptions || {});
80
+ const file = input.registryPath || bridgeProviderRegistryPath(input.home);
81
+ return withFileLock(registryLockOptions(file), async () => {
82
+ const read = await readStoredBridgeProviderRegistry(file);
83
+ if (read.blockers.length > 0)
84
+ throw new Error(read.blockers[0]);
85
+ const current = read.registry || defaultStoredRegistry(credentials);
86
+ const next = {
87
+ schema: BRIDGE_PROVIDER_REGISTRY_SCHEMA,
88
+ profiles: {
89
+ ...current.profiles,
90
+ [input.provider_id]: {
91
+ ...current.profiles[input.provider_id],
92
+ enabled: input.enabled
93
+ }
94
+ }
95
+ };
96
+ await writeJsonAtomic(file, next, { mode: 0o600 });
97
+ return resolveBridgeProviderRegistry({
98
+ ...(input.home ? { home: input.home } : {}),
99
+ registryPath: file,
100
+ credentials
101
+ });
102
+ });
103
+ }
104
+ export async function configureBridgeProviderProfile(input) {
105
+ const credentials = input.credentials || await resolveAllProviderCredentials();
106
+ const endpoint = normalizeEndpoint(input.endpoint_url);
107
+ const origin = endpointOrigin(endpoint) || '';
108
+ const blocker = providerEndpointSecurityBlocker(input.provider_id, endpoint, input.allowed_origins || [origin]);
109
+ if (blocker)
110
+ throw new Error(blocker);
111
+ const file = input.registryPath || bridgeProviderRegistryPath(input.home);
112
+ return withFileLock(registryLockOptions(file), async () => {
113
+ const read = await readStoredBridgeProviderRegistry(file);
114
+ if (read.blockers.length > 0)
115
+ throw new Error(read.blockers[0]);
116
+ const current = read.registry || defaultStoredRegistry(credentials);
117
+ const nextProfile = {
118
+ enabled: input.enabled ?? current.profiles[input.provider_id].enabled,
119
+ endpoint_url: endpoint,
120
+ allowed_origins: normalizeOrigins(input.allowed_origins || [origin]),
121
+ auth_transport: input.auth_transport || defaultAuthTransport(input.provider_id)
122
+ };
123
+ const next = {
124
+ schema: BRIDGE_PROVIDER_REGISTRY_SCHEMA,
125
+ profiles: { ...current.profiles, [input.provider_id]: nextProfile }
126
+ };
127
+ await writeJsonAtomic(file, next, { mode: 0o600 });
128
+ return resolveBridgeProviderRegistry({
129
+ ...(input.home ? { home: input.home } : {}),
130
+ registryPath: file,
131
+ credentials
132
+ });
133
+ });
134
+ }
135
+ function registryLockOptions(file) {
136
+ return { lockPath: `${path.resolve(file)}.lock`, timeoutMs: 10_000, staleMs: 60_000 };
137
+ }
138
+ export function providerEndpointSecurityBlocker(providerId, endpointUrl, allowedOrigins) {
139
+ if (!endpointUrl)
140
+ return `${providerCode(providerId)}_endpoint_missing`;
141
+ try {
142
+ const url = new URL(endpointUrl);
143
+ if (url.username || url.password || url.search || url.hash)
144
+ return 'provider_endpoint_components_forbidden';
145
+ const normalizedAllowed = normalizeOrigins(allowedOrigins);
146
+ if (!normalizedAllowed.includes(url.origin))
147
+ return 'provider_endpoint_origin_not_allowlisted';
148
+ if (providerId === 'openrouter') {
149
+ if (url.protocol !== 'https:' || url.origin !== OPENROUTER_ALLOWED_ORIGIN) {
150
+ return 'openrouter_endpoint_origin_not_allowlisted';
151
+ }
152
+ return null;
153
+ }
154
+ return codexLbBaseUrlSecurityBlocker(endpointUrl);
155
+ }
156
+ catch {
157
+ return 'provider_endpoint_invalid';
158
+ }
159
+ }
160
+ async function readStoredBridgeProviderRegistry(file) {
161
+ const stat = await fs.lstat(file).catch(() => null);
162
+ if (!stat)
163
+ return { registry: null, blockers: [] };
164
+ if (!stat.isFile() || stat.isSymbolicLink()) {
165
+ return { registry: null, blockers: ['provider_registry_not_regular_file'] };
166
+ }
167
+ const expectedUid = typeof process.getuid === 'function' ? process.getuid() : null;
168
+ if (expectedUid !== null && stat.uid !== expectedUid) {
169
+ return { registry: null, blockers: ['provider_registry_owner_mismatch'] };
170
+ }
171
+ if (process.platform !== 'win32' && (stat.mode & 0o777) !== 0o600) {
172
+ return { registry: null, blockers: ['provider_registry_mode_insecure'] };
173
+ }
174
+ try {
175
+ const value = JSON.parse(await fs.readFile(file, 'utf8'));
176
+ if (!isStoredRegistry(value))
177
+ return { registry: null, blockers: ['provider_registry_schema_invalid'] };
178
+ return { registry: value, blockers: [] };
179
+ }
180
+ catch {
181
+ return { registry: null, blockers: ['provider_registry_json_invalid'] };
182
+ }
183
+ }
184
+ function defaultStoredRegistry(credentials) {
185
+ const codexEndpoint = credentials['codex-lb'].endpoint_url;
186
+ const openRouterEndpoint = credentials.openrouter.endpoint_url || 'https://openrouter.ai/api/v1';
187
+ return {
188
+ schema: BRIDGE_PROVIDER_REGISTRY_SCHEMA,
189
+ profiles: {
190
+ 'codex-lb': defaultProfile('codex-lb', codexEndpoint, credentials['codex-lb'].state !== 'not_configured'),
191
+ openrouter: defaultProfile('openrouter', openRouterEndpoint, credentials.openrouter.state !== 'not_configured')
192
+ }
193
+ };
194
+ }
195
+ function defaultProfile(providerId, endpointUrl, enabled) {
196
+ const endpoint = normalizeEndpoint(endpointUrl || '');
197
+ const origin = endpointOrigin(endpoint);
198
+ return {
199
+ enabled,
200
+ endpoint_url: endpoint,
201
+ allowed_origins: origin ? [origin] : [],
202
+ auth_transport: defaultAuthTransport(providerId)
203
+ };
204
+ }
205
+ function resolveProfile(providerId, stored, credential) {
206
+ const endpoint = normalizeEndpoint(stored.endpoint_url || credential.endpoint_url || '');
207
+ const endpointBlocker = providerEndpointSecurityBlocker(providerId, endpoint, stored.allowed_origins);
208
+ const credentialStatus = providerCredentialStatus(credential);
209
+ const blockers = unique([
210
+ ...(endpointBlocker ? [endpointBlocker] : []),
211
+ ...credentialStatus.blockers
212
+ ]);
213
+ const state = !stored.enabled
214
+ ? 'disabled'
215
+ : blockers.length > 0 && credentialStatus.state !== 'not_configured'
216
+ ? 'blocked'
217
+ : credentialStatus.state === 'ready'
218
+ ? 'ready'
219
+ : credentialStatus.state === 'not_configured'
220
+ ? 'not_configured'
221
+ : 'configured_unverified';
222
+ const origin = endpointOrigin(endpoint);
223
+ const profileGeneration = digest({
224
+ provider_id: providerId,
225
+ enabled: stored.enabled,
226
+ credential_fingerprint: credentialStatus.fingerprint,
227
+ credential_state: credentialStatus.state,
228
+ endpoint,
229
+ allowed_origins: [...stored.allowed_origins].sort(),
230
+ auth_transport: stored.auth_transport
231
+ });
232
+ const endpointStatus = {
233
+ configured: Boolean(endpoint) && !endpointBlocker,
234
+ origin,
235
+ origin_redacted: origin,
236
+ allowed_origins: [...stored.allowed_origins].sort(),
237
+ auth_transport: stored.auth_transport,
238
+ blockers: endpointBlocker ? [endpointBlocker] : []
239
+ };
240
+ Object.defineProperty(endpointStatus, 'url', {
241
+ value: endpoint,
242
+ enumerable: false,
243
+ configurable: false,
244
+ writable: false
245
+ });
246
+ return {
247
+ provider_id: providerId,
248
+ enabled: stored.enabled,
249
+ profile_generation: profileGeneration,
250
+ credential: credentialStatus,
251
+ endpoint: endpointStatus,
252
+ state,
253
+ blockers,
254
+ warnings: [...credentialStatus.warnings]
255
+ };
256
+ }
257
+ function isStoredRegistry(value) {
258
+ if (value.schema !== BRIDGE_PROVIDER_REGISTRY_SCHEMA || !value.profiles)
259
+ return false;
260
+ return ['codex-lb', 'openrouter'].every((providerId) => {
261
+ const profile = value.profiles?.[providerId];
262
+ return Boolean(profile
263
+ && typeof profile.enabled === 'boolean'
264
+ && (profile.endpoint_url === null || typeof profile.endpoint_url === 'string')
265
+ && Array.isArray(profile.allowed_origins)
266
+ && profile.allowed_origins.every((origin) => typeof origin === 'string')
267
+ && ['authorization-bearer', 'x-codex-lb-api-key', 'openrouter-bearer'].includes(profile.auth_transport));
268
+ });
269
+ }
270
+ function defaultAuthTransport(providerId) {
271
+ return providerId === 'openrouter' ? 'openrouter-bearer' : 'authorization-bearer';
272
+ }
273
+ function normalizeEndpoint(value) {
274
+ const text = String(value || '').trim().replace(/\/+$/, '');
275
+ if (!text)
276
+ return null;
277
+ try {
278
+ return new URL(text).toString().replace(/\/+$/, '');
279
+ }
280
+ catch {
281
+ return text;
282
+ }
283
+ }
284
+ function endpointOrigin(endpoint) {
285
+ if (!endpoint)
286
+ return null;
287
+ try {
288
+ return new URL(endpoint).origin;
289
+ }
290
+ catch {
291
+ return null;
292
+ }
293
+ }
294
+ function normalizeOrigins(values) {
295
+ return unique(values.map((value) => {
296
+ try {
297
+ return new URL(value).origin;
298
+ }
299
+ catch {
300
+ return '';
301
+ }
302
+ })).sort();
303
+ }
304
+ function providerCode(providerId) {
305
+ return providerId === 'codex-lb' ? 'codex_lb' : 'openrouter';
306
+ }
307
+ function digest(value) {
308
+ return crypto.createHash('sha256').update(stableStringify(value)).digest('hex');
309
+ }
310
+ function stableStringify(value) {
311
+ if (Array.isArray(value))
312
+ return `[${value.map(stableStringify).join(',')}]`;
313
+ if (value && typeof value === 'object') {
314
+ return `{${Object.entries(value)
315
+ .sort(([left], [right]) => left.localeCompare(right))
316
+ .map(([key, entry]) => `${JSON.stringify(key)}:${stableStringify(entry)}`)
317
+ .join(',')}}`;
318
+ }
319
+ return JSON.stringify(value) ?? 'null';
320
+ }
321
+ function unique(values) {
322
+ return [...new Set(values.map((value) => String(value || '').trim()).filter(Boolean))];
323
+ }
@@ -0,0 +1,118 @@
1
+ import fs from 'node:fs/promises';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ import { canonicalizeBridgeModelId, normalizeBridgeUpstreamModelId, sha256Stable } from './route-index.js';
5
+ import { writeJsonAtomic } from '../fsx.js';
6
+ import { withFileLock } from '../locks/file-lock.js';
7
+ export const BRIDGE_ROUTE_POLICY_FILENAME = 'sks-bridge-route-policy.json';
8
+ export function bridgeRoutePolicyPath(codexHome = process.env.CODEX_HOME || path.join(os.homedir(), '.codex')) {
9
+ return path.join(path.resolve(codexHome), 'sks', BRIDGE_ROUTE_POLICY_FILENAME);
10
+ }
11
+ export function buildBridgeRoutingPolicy(input) {
12
+ const routes = canonicalRoutes(input.route_index.routes);
13
+ const semantic = {
14
+ default_provider_id: input.default_provider_id ?? null,
15
+ fallback: 'none',
16
+ model_routes: routes,
17
+ catalog_generation: input.catalog_generation
18
+ };
19
+ return {
20
+ schema: 'sks.bridge-routing-policy.v1',
21
+ ...semantic,
22
+ policy_generation: sha256Stable(semantic),
23
+ changed_at: input.changed_at || new Date().toISOString()
24
+ };
25
+ }
26
+ export function setBridgeRoutingDefault(policy, providerId, changedAt) {
27
+ const semantic = {
28
+ default_provider_id: providerId,
29
+ fallback: 'none',
30
+ model_routes: canonicalRoutes(policy.model_routes),
31
+ catalog_generation: policy.catalog_generation
32
+ };
33
+ return {
34
+ schema: 'sks.bridge-routing-policy.v1',
35
+ ...semantic,
36
+ policy_generation: sha256Stable(semantic),
37
+ changed_at: changedAt || new Date().toISOString()
38
+ };
39
+ }
40
+ export function validateBridgeRoutingPolicy(policy, routeIndex) {
41
+ const blockers = [];
42
+ if (policy.schema !== 'sks.bridge-routing-policy.v1')
43
+ blockers.push('bridge_route_policy_schema_invalid');
44
+ if (policy.fallback !== 'none')
45
+ blockers.push('bridge_route_policy_fallback_forbidden');
46
+ if (!policy.catalog_generation)
47
+ blockers.push('bridge_route_policy_catalog_generation_missing');
48
+ const canonical = canonicalRoutes(policy.model_routes);
49
+ const semantic = {
50
+ default_provider_id: policy.default_provider_id,
51
+ fallback: 'none',
52
+ model_routes: canonical,
53
+ catalog_generation: policy.catalog_generation
54
+ };
55
+ if (sha256Stable(semantic) !== policy.policy_generation)
56
+ blockers.push('bridge_route_policy_generation_invalid');
57
+ if (Object.keys(canonical).length !== Object.keys(policy.model_routes).length)
58
+ blockers.push('bridge_route_policy_model_invalid');
59
+ if (routeIndex) {
60
+ for (const [model, target] of Object.entries(canonical)) {
61
+ const indexed = routeIndex.routes[model];
62
+ if (!indexed || !sameTarget(indexed, target))
63
+ blockers.push('bridge_route_policy_route_index_mismatch');
64
+ }
65
+ }
66
+ return unique(blockers);
67
+ }
68
+ export async function writeBridgeRoutingPolicy(file, policy, routeIndex) {
69
+ const blockers = validateBridgeRoutingPolicy(policy, routeIndex);
70
+ if (blockers.length > 0)
71
+ throw new Error(blockers[0]);
72
+ await withFileLock({
73
+ lockPath: `${path.resolve(file)}.lock`,
74
+ timeoutMs: 10_000,
75
+ staleMs: 60_000
76
+ }, () => writeJsonAtomic(file, policy, { mode: 0o600 }));
77
+ }
78
+ export async function readBridgeRoutingPolicy(file) {
79
+ const stat = await fs.lstat(file).catch(() => null);
80
+ if (!stat)
81
+ return { policy: null, blockers: ['bridge_route_policy_missing'] };
82
+ if (!stat.isFile() || stat.isSymbolicLink())
83
+ return { policy: null, blockers: ['bridge_route_policy_not_regular_file'] };
84
+ const expectedUid = typeof process.getuid === 'function' ? process.getuid() : null;
85
+ if (expectedUid !== null && stat.uid !== expectedUid)
86
+ return { policy: null, blockers: ['bridge_route_policy_owner_mismatch'] };
87
+ if (process.platform !== 'win32' && (stat.mode & 0o777) !== 0o600) {
88
+ return { policy: null, blockers: ['bridge_route_policy_mode_insecure'] };
89
+ }
90
+ try {
91
+ const policy = JSON.parse(await fs.readFile(file, 'utf8'));
92
+ const blockers = validateBridgeRoutingPolicy(policy);
93
+ return blockers.length > 0 ? { policy: null, blockers } : { policy, blockers: [] };
94
+ }
95
+ catch {
96
+ return { policy: null, blockers: ['bridge_route_policy_json_invalid'] };
97
+ }
98
+ }
99
+ function canonicalRoutes(routes) {
100
+ const rows = [];
101
+ for (const [model, target] of Object.entries(routes)) {
102
+ const canonicalModel = canonicalizeBridgeModelId(model);
103
+ const upstreamModel = normalizeBridgeUpstreamModelId(target.upstream_model);
104
+ if (!canonicalModel || !upstreamModel || !isProviderId(target.provider_id))
105
+ continue;
106
+ rows.push([canonicalModel, { provider_id: target.provider_id, upstream_model: upstreamModel }]);
107
+ }
108
+ return Object.fromEntries(rows.sort(([left], [right]) => left.localeCompare(right)));
109
+ }
110
+ function isProviderId(value) {
111
+ return value === 'codex-lb' || value === 'openrouter';
112
+ }
113
+ function sameTarget(left, right) {
114
+ return left.provider_id === right.provider_id && left.upstream_model === right.upstream_model;
115
+ }
116
+ function unique(values) {
117
+ return [...new Set(values)];
118
+ }