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
@@ -2,7 +2,7 @@ import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { spawnSync } from 'node:child_process';
4
4
  import { normalizeReleaseOrigin, RELEASE_ORIGIN_IDENTITY } from './release-origin.js';
5
- import { inspectPhysicalReleaseGates } from './physical-release-gates.js';
5
+ import { validatePhysicalReleaseGateInspection } from './physical-release-gates.js';
6
6
  export function inspectPublishPreflight(options) {
7
7
  const root = path.resolve(options.root);
8
8
  const run = options.run || runCommand;
@@ -54,10 +54,10 @@ export function inspectPublishPreflight(options) {
54
54
  blockers.push(`remote_release_tag_not_head:${releaseTag}`);
55
55
  }
56
56
  const physicalReleaseGates = requirePhysicalReleaseGates
57
- ? inspectPhysicalReleaseGates({
57
+ ? validatePhysicalReleaseGateInspection({
58
58
  root,
59
59
  version,
60
- sourceCommit: validSha(head) ? head : null,
60
+ sourceCommit: validSha(head) ? head : '',
61
61
  })
62
62
  : null;
63
63
  if (physicalReleaseGates && !physicalReleaseGates.ok) {
@@ -5,6 +5,7 @@ import { cleanupReleaseGateHermeticEnv, createReleaseGateHermeticEnv } from './r
5
5
  import { writeReleaseGateJson } from './release-gate-report.js';
6
6
  import { guardedProcessKill, guardContextForRoute } from '../safety/mutation-guard.js';
7
7
  import { createRequestedScopeContract } from '../safety/requested-scope-contract.js';
8
+ import { normalizeReleaseGateOutput } from './release-gate-output-contract.js';
8
9
  const DISALLOWED_BATCH_RESOURCES = new Set(['git-worktree', 'local-llm-real', 'remote-model-real', 'publish', 'global-config', 'timing-sensitive']);
9
10
  export function isReleaseGateBatchable(gate) {
10
11
  if (gate.side_effect !== 'hermetic')
@@ -21,7 +22,7 @@ export async function runReleaseGateBatch(root, gates, input = {}) {
21
22
  const nonBatchable = gates.filter((gate) => !isReleaseGateBatchable(gate));
22
23
  if (nonBatchable.length) {
23
24
  return {
24
- schema: 'sks.release-gate-batch-result.v1',
25
+ schema: 'sks.release-gate-batch-result.v2',
25
26
  ok: false,
26
27
  batch_size: gates.length,
27
28
  completed: 0,
@@ -45,7 +46,7 @@ export async function runReleaseGateBatch(root, gates, input = {}) {
45
46
  await Promise.all(workers);
46
47
  const failed = results.filter((row) => !row.ok).length;
47
48
  return {
48
- schema: 'sks.release-gate-batch-result.v1',
49
+ schema: 'sks.release-gate-batch-result.v2',
49
50
  ok: failed === 0,
50
51
  batch_size: gates.length,
51
52
  completed: results.length - failed,
@@ -57,7 +58,8 @@ function runOne(root, runId, reportRoot, gate) {
57
58
  const started = performance.now();
58
59
  const hermetic = createReleaseGateHermeticEnv({ root, runId, gate, reportRoot });
59
60
  return new Promise((resolve) => {
60
- const child = spawn(gate.command, { cwd: root, shell: true, env: hermetic.env, stdio: ['ignore', 'ignore', 'ignore'], detached: process.platform !== 'win32' });
61
+ const child = spawn(gate.command, { cwd: root, shell: true, env: hermetic.env, stdio: ['ignore', 'pipe', 'pipe'], detached: process.platform !== 'win32' });
62
+ let stdoutTail = '';
61
63
  let timedOut = false;
62
64
  let timeoutCleanup = null;
63
65
  const timer = setTimeout(() => {
@@ -65,20 +67,27 @@ function runOne(root, runId, reportRoot, gate) {
65
67
  timeoutCleanup = cleanupTimedOutGateProcessTree(root, child);
66
68
  }, gate.timeout_ms);
67
69
  timer.unref?.();
70
+ child.stdout.on('data', (chunk) => {
71
+ stdoutTail = appendTail(stdoutTail, String(chunk));
72
+ });
73
+ child.stderr.resume();
68
74
  child.on('close', (code, signal) => {
69
75
  void (async () => {
70
76
  clearTimeout(timer);
71
77
  if (timeoutCleanup)
72
78
  await timeoutCleanup;
73
79
  const exitCode = timedOut ? 124 : code;
80
+ const normalized = normalizeReleaseGateOutput({ gateId: gate.id, status: exitCode, signal, timedOut, stdout: stdoutTail });
74
81
  const result = {
75
82
  id: gate.id,
76
- ok: exitCode === 0,
83
+ ok: normalized.evaluation.ok,
77
84
  exit_code: exitCode,
78
85
  signal,
79
86
  timed_out: timedOut,
80
87
  duration_ms: Math.max(1, Math.round(performance.now() - started)),
81
- report_dir: hermetic.report_dir
88
+ report_dir: hermetic.report_dir,
89
+ output_contract: gate.output_contract,
90
+ gate_result: normalized.gate_result
82
91
  };
83
92
  cleanupReleaseGateHermeticEnv(hermetic);
84
93
  resolve(result);
@@ -86,6 +95,10 @@ function runOne(root, runId, reportRoot, gate) {
86
95
  });
87
96
  });
88
97
  }
98
+ function appendTail(previous, next, limit = 64 * 1024) {
99
+ const combined = `${previous}${next}`;
100
+ return combined.length > limit ? combined.slice(-limit) : combined;
101
+ }
89
102
  async function cleanupTimedOutGateProcessTree(root, child) {
90
103
  await killGateProcessTree(root, child, 'SIGTERM');
91
104
  await sleep(1500);
@@ -118,7 +131,7 @@ function sleep(ms) {
118
131
  function writeChildResult(reportRoot, result) {
119
132
  const dir = result.report_dir || path.join(reportRoot, result.id.replace(/[^A-Za-z0-9_.:-]/g, '_'));
120
133
  writeReleaseGateJson(path.join(dir, 'result.json'), {
121
- schema: 'sks.release-gate-batch-child-result.v1',
134
+ schema: 'sks.release-gate-batch-child-result.v2',
122
135
  ...result
123
136
  });
124
137
  }
@@ -1,6 +1,7 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { spawn } from 'node:child_process';
4
+ import { finished } from 'node:stream/promises';
4
5
  import { cleanupReleaseGateHermeticEnv, cleanupReleaseGateRunTemp, createReleaseGateHermeticEnv } from './release-gate-hermetic-env.js';
5
6
  import { appendReleaseGateJsonl, writeReleaseGateJson } from './release-gate-report.js';
6
7
  import { findReadyReleaseGateNodes, findReleaseGatesBlockedByFailedDeps, pickReadyLaunchableReleaseGates } from './release-gate-scheduler.js';
@@ -15,6 +16,7 @@ import { guardedProcessKill, guardContextForRoute } from '../safety/mutation-gua
15
16
  import { createRequestedScopeContract } from '../safety/requested-scope-contract.js';
16
17
  import { rmrf } from '../fsx.js';
17
18
  import { sweepSksTempDirs } from '../retention.js';
19
+ import { normalizeReleaseGateOutput } from './release-gate-output-contract.js';
18
20
  export function loadReleaseGateManifest(root, file = 'release-gates.v2.json') {
19
21
  const manifestPath = path.join(root, file);
20
22
  const parsed = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
@@ -398,6 +400,7 @@ function runGate(root, runId, reportRoot, gate) {
398
400
  const err = fs.createWriteStream(stderrFile);
399
401
  return new Promise((resolve) => {
400
402
  const child = spawn(gate.command, { cwd: root, shell: true, env: hermetic.env, stdio: ['ignore', 'pipe', 'pipe'], detached: process.platform !== 'win32' });
403
+ let stdoutTail = '';
401
404
  let timedOut = false;
402
405
  let timeoutCleanup = null;
403
406
  const timer = setTimeout(() => {
@@ -405,29 +408,53 @@ function runGate(root, runId, reportRoot, gate) {
405
408
  timeoutCleanup = cleanupTimedOutGateProcessTree(root, child);
406
409
  }, gate.timeout_ms);
407
410
  timer.unref?.();
408
- child.stdout.pipe(out);
409
- child.stderr.pipe(err);
411
+ child.stdout.on('data', (chunk) => {
412
+ stdoutTail = appendTail(stdoutTail, String(chunk));
413
+ });
414
+ child.stdout.pipe(out, { end: false });
415
+ child.stderr.pipe(err, { end: false });
410
416
  child.on('close', (code, signal) => {
411
417
  void (async () => {
412
418
  clearTimeout(timer);
413
419
  if (timeoutCleanup)
414
420
  await timeoutCleanup;
421
+ const exitCode = timedOut ? 124 : code;
422
+ const normalized = normalizeReleaseGateOutput({
423
+ gateId: gate.id,
424
+ status: exitCode,
425
+ signal,
426
+ timedOut,
427
+ stdout: stdoutTail
428
+ });
429
+ out.write(`${stdoutTail.endsWith('\n') || stdoutTail.length === 0 ? '' : '\n'}${normalized.line}\n`);
415
430
  out.end();
416
431
  err.end();
432
+ await Promise.allSettled([finished(out), finished(err)]);
417
433
  const durationMs = Date.now() - started;
418
434
  const stderrText = fs.existsSync(stderrFile) ? fs.readFileSync(stderrFile, 'utf8') : '';
419
435
  const timeoutTail = timedOut ? `release_gate_timeout:${gate.id}:${gate.timeout_ms}ms` : '';
420
436
  const signalTail = !timedOut && signal ? `release_gate_signal:${gate.id}:${signal}` : '';
421
- const stderrTail = tail([stderrText, timeoutTail, signalTail].filter(Boolean).join('\n'));
422
- const exitCode = timedOut ? 124 : code;
423
- const result = { id: gate.id, ok: exitCode === 0, exit_code: exitCode, signal, timed_out: timedOut, duration_ms: durationMs, cached: false, stderr_tail: stderrTail };
424
- writeReleaseGateJson(path.join(hermetic.report_dir, 'result.json'), { schema: 'sks.release-gate-result.v1', ...result, stdout_log: stdoutFile, stderr_log: stderrFile });
437
+ const contractTail = normalized.evaluation.ok ? '' : normalized.gate_result.blockers.map(String).join('\n');
438
+ const stderrTail = tail([stderrText, timeoutTail, signalTail, contractTail].filter(Boolean).join('\n'));
439
+ const result = { id: gate.id, ok: normalized.evaluation.ok, exit_code: exitCode, signal, timed_out: timedOut, duration_ms: durationMs, cached: false, stderr_tail: stderrTail };
440
+ writeReleaseGateJson(path.join(hermetic.report_dir, 'result.json'), {
441
+ schema: 'sks.release-gate-result.v2',
442
+ ...result,
443
+ output_contract: gate.output_contract,
444
+ gate_result: normalized.gate_result,
445
+ stdout_log: stdoutFile,
446
+ stderr_log: stderrFile
447
+ });
425
448
  cleanupReleaseGateHermeticEnv(hermetic);
426
449
  resolve(result);
427
450
  })();
428
451
  });
429
452
  });
430
453
  }
454
+ function appendTail(previous, next, limit = 64 * 1024) {
455
+ const combined = `${previous}${next}`;
456
+ return combined.length > limit ? combined.slice(-limit) : combined;
457
+ }
431
458
  async function cleanupTimedOutGateProcessTree(root, child) {
432
459
  await killGateProcessTree(root, child, 'SIGTERM');
433
460
  await sleep(1500);
@@ -47,10 +47,8 @@ export function validateReleaseGateManifest(input) {
47
47
  errors.push(`gate_side_effect_invalid:${gate?.id || 'unknown'}`);
48
48
  if (!Number.isFinite(Number(gate?.timeout_ms)) || Number(gate.timeout_ms) <= 0)
49
49
  errors.push(`gate_timeout_missing:${gate?.id || 'unknown'}`);
50
- if (gate?.output_contract !== undefined && gate.output_contract !== 'sks.gate-result.v1')
50
+ if (gate?.output_contract !== 'sks.gate-result.v2')
51
51
  errors.push(`gate_output_contract_invalid:${gate?.id || 'unknown'}`);
52
- if (gate?.contract_note !== undefined && typeof gate.contract_note !== 'string')
53
- errors.push(`gate_contract_note_invalid:${gate?.id || 'unknown'}`);
54
52
  if (!gate?.cache || typeof gate.cache.enabled !== 'boolean' || !Array.isArray(gate.cache.inputs))
55
53
  errors.push(`gate_cache_missing:${gate?.id || 'unknown'}`);
56
54
  if (!gate?.isolation || gate.isolation.report_dir !== 'per-gate')
@@ -0,0 +1,55 @@
1
+ import { GATE_RESULT_CONTRACT, GATE_RESULT_CONTRACT_MODE, evaluateGateProcessOutput, parseGateResultFromStdout } from '../commands/gate-result-contract.js';
2
+ export function normalizeReleaseGateOutput(input) {
3
+ const childResult = parseGateResultFromStdout(input.stdout);
4
+ const processBlockers = [
5
+ ...(input.timedOut ? [`release_gate_timeout:${input.gateId}`] : []),
6
+ ...(!input.timedOut && input.signal ? [`release_gate_signal:${input.gateId}:${input.signal}`] : []),
7
+ ...(!input.timedOut && !input.signal && input.status !== 0 ? [`release_gate_exit:${input.gateId}:${String(input.status)}`] : [])
8
+ ];
9
+ const processOk = !input.timedOut && !input.signal && input.status === 0;
10
+ const gateResult = {
11
+ ...(childResult || {}),
12
+ schema: GATE_RESULT_CONTRACT,
13
+ contract_mode: GATE_RESULT_CONTRACT_MODE,
14
+ ok: processOk && (childResult?.ok ?? true),
15
+ blockers: dedupe([
16
+ ...(childResult?.blockers || []),
17
+ ...processBlockers
18
+ ]),
19
+ producer: 'release-gate-command-adapter',
20
+ execution: {
21
+ gate_id: input.gateId,
22
+ child_contract: childResult?.schema || null,
23
+ child_exit_code: input.status,
24
+ child_signal: input.signal,
25
+ timed_out: input.timedOut
26
+ }
27
+ };
28
+ const line = JSON.stringify(gateResult);
29
+ return {
30
+ gate_result: gateResult,
31
+ evaluation: evaluateGateProcessOutput({ status: input.status, stdout: line }),
32
+ line
33
+ };
34
+ }
35
+ function dedupe(values) {
36
+ const seen = new Set();
37
+ const result = [];
38
+ for (const value of values) {
39
+ const key = typeof value === 'string' ? `string:${value}` : `json:${safeJson(value)}`;
40
+ if (seen.has(key))
41
+ continue;
42
+ seen.add(key);
43
+ result.push(value);
44
+ }
45
+ return result;
46
+ }
47
+ function safeJson(value) {
48
+ try {
49
+ const encoded = JSON.stringify(value);
50
+ return encoded === undefined ? String(value) : encoded;
51
+ }
52
+ catch {
53
+ return String(value);
54
+ }
55
+ }
@@ -1,2 +1,2 @@
1
- export const RELEASE_PACK_RECEIPT_SCHEMA = 'sks.release-pack-receipt.v1';
1
+ export const RELEASE_PACK_RECEIPT_SCHEMA = 'sks.release-pack-receipt.v2';
2
2
  export const RELEASE_PACK_COMPARE_SCHEMA = 'sks.release-pack-compare.v1';
@@ -1,7 +1,9 @@
1
1
  import crypto from 'node:crypto';
2
2
  import fs from 'node:fs';
3
+ import os from 'node:os';
3
4
  import path from 'node:path';
4
5
  import { spawnSync } from 'node:child_process';
6
+ import { tmpdir } from '../fsx.js';
5
7
  import { DEFAULT_MAX_PACK_BYTES, DEFAULT_MAX_UNPACKED_BYTES } from './package-size-budget.js';
6
8
  import { readCurrentNpmPackGateArtifacts } from './npm-pack-proof.js';
7
9
  import { scanTarballTextContents } from './release-pack-content-scanner.js';
@@ -42,6 +44,11 @@ export function inspectReleaseTarball(input) {
42
44
  blockers.push('tarball_unpacked_size_unavailable');
43
45
  const sha256 = bytes.length ? hash(bytes, 'sha256', 'hex') : '';
44
46
  const sha512Base64 = bytes.length ? hash(bytes, 'sha512', 'base64') : '';
47
+ const sourceBinding = input.kind === 'local' && input.root && input.sourceCommit && bytes.length && inventory.blockers.length === 0
48
+ ? inspectSourcePackageBinding(input.root, input.sourceCommit, tarball, files, sha256)
49
+ : null;
50
+ if (sourceBinding)
51
+ blockers.push(...sourceBinding.blockers);
45
52
  const budgetBlockers = [
46
53
  ...(bytes.length > DEFAULT_MAX_PACK_BYTES ? ['packed_bytes_over_limit'] : []),
47
54
  ...(unpackedBytes > DEFAULT_MAX_UNPACKED_BYTES ? ['unpacked_bytes_over_limit'] : []),
@@ -55,6 +62,9 @@ export function inspectReleaseTarball(input) {
55
62
  package_name: String(packageJson?.name || ''),
56
63
  package_version: String(packageJson?.version || ''),
57
64
  source_commit: input.sourceCommit || null,
65
+ source_tree_sha256: sourceBinding?.source_tree_sha256 || null,
66
+ source_package_sha256: sourceBinding?.source_package_sha256 || null,
67
+ source_package_binding_sha256: sourceBinding?.source_package_binding_sha256 || null,
58
68
  tarball_name: path.basename(tarball),
59
69
  tarball_path: input.root ? normalizePath(path.relative(input.root, tarball)) : normalizePath(tarball),
60
70
  bytes: bytes.length,
@@ -115,7 +125,10 @@ export function compareReleasePacks(local, staged) {
115
125
  export function validateReleasePackReceipt(value, expectedKind, options = {}) {
116
126
  const receipt = value;
117
127
  const blockers = [];
118
- if (!receipt || receipt.schema !== RELEASE_PACK_RECEIPT_SCHEMA)
128
+ if (receipt?.schema === 'sks.release-pack-receipt.v1') {
129
+ blockers.push('receipt_schema_v1_outdated_regenerate_with_npm_run_release:pack-receipt');
130
+ }
131
+ else if (!receipt || receipt.schema !== RELEASE_PACK_RECEIPT_SCHEMA)
119
132
  blockers.push('schema_invalid');
120
133
  if (receipt?.ok !== true)
121
134
  blockers.push('not_ok');
@@ -192,8 +205,19 @@ export function validateReleasePackReceipt(value, expectedKind, options = {}) {
192
205
  if (!/^[a-f0-9]{64}$/i.test(String(receipt?.npm_pack_proof?.file_list_sha256 || '')))
193
206
  blockers.push('npm_pack_file_list_sha256_invalid');
194
207
  }
195
- if (receipt?.kind === 'local' && !/^[a-f0-9]{40}$/i.test(String(receipt?.source_commit || '')))
196
- blockers.push('source_commit_invalid');
208
+ if (receipt?.kind === 'local') {
209
+ if (!/^[a-f0-9]{40}$/i.test(String(receipt?.source_commit || '')))
210
+ blockers.push('source_commit_invalid');
211
+ if (!/^[a-f0-9]{64}$/i.test(String(receipt?.source_tree_sha256 || '')))
212
+ blockers.push('source_tree_sha256_invalid');
213
+ if (!/^[a-f0-9]{64}$/i.test(String(receipt?.source_package_sha256 || '')))
214
+ blockers.push('source_package_sha256_invalid');
215
+ if (!/^[a-f0-9]{64}$/i.test(String(receipt?.source_package_binding_sha256 || '')))
216
+ blockers.push('source_package_binding_sha256_invalid');
217
+ const expectedBinding = sourcePackageBindingSha256(String(receipt?.source_commit || ''), String(receipt?.source_tree_sha256 || ''), String(receipt?.source_package_sha256 || ''), String(receipt?.sha256 || ''));
218
+ if (receipt?.source_package_binding_sha256 !== expectedBinding)
219
+ blockers.push('source_package_binding_sha256_mismatch');
220
+ }
197
221
  return { ok: blockers.length === 0, receipt: receipt || null, blockers: unique(blockers) };
198
222
  }
199
223
  export function validateLocalReleasePackBinding(root, value) {
@@ -224,9 +248,12 @@ export function validateLocalReleasePackBinding(root, value) {
224
248
  if (receipt?.sha512_integrity !== info.integrity)
225
249
  blockers.push('npm_pack_integrity_mismatch');
226
250
  }
227
- const head = gitHead(root);
228
- if (!head || receipt?.source_commit !== head)
251
+ const sourceState = inspectLocalReleaseSourceState(root);
252
+ if (!sourceState.head || receipt?.source_commit !== sourceState.head)
229
253
  blockers.push('npm_pack_source_commit_mismatch');
254
+ if (!sourceState.source_tree_sha256 || receipt?.source_tree_sha256 !== sourceState.source_tree_sha256)
255
+ blockers.push('npm_pack_source_tree_sha256_mismatch');
256
+ blockers.push(...sourceState.blockers);
230
257
  const tarball = receipt?.tarball_path ? path.resolve(root, receipt.tarball_path) : '';
231
258
  const managedRoot = path.resolve(root, '.sneakoscope', 'reports', 'release', String(receipt?.package_version || ''), 'artifacts');
232
259
  const relative = tarball ? path.relative(managedRoot, tarball) : '..';
@@ -234,7 +261,7 @@ export function validateLocalReleasePackBinding(root, value) {
234
261
  blockers.push('local_tarball_artifact_missing_or_unsafe');
235
262
  else {
236
263
  const actual = inspectReleaseTarball({ tarball, kind: 'local', sourceCommit: receipt?.source_commit || null, root });
237
- for (const key of ['package_name', 'package_version', 'tarball_name', 'tarball_path', 'bytes', 'unpacked_bytes', 'sha256', 'sha512_integrity', 'file_count', 'file_list_sha256']) {
264
+ for (const key of ['package_name', 'package_version', 'source_tree_sha256', 'source_package_sha256', 'source_package_binding_sha256', 'tarball_name', 'tarball_path', 'bytes', 'unpacked_bytes', 'sha256', 'sha512_integrity', 'file_count', 'file_list_sha256']) {
238
265
  if (actual[key] !== receipt?.[key])
239
266
  blockers.push(`local_tarball_artifact_mismatch:${key}`);
240
267
  }
@@ -245,7 +272,72 @@ export function validateLocalReleasePackBinding(root, value) {
245
272
  if (!actual.ok)
246
273
  blockers.push(...actual.blockers.map((blocker) => `local_tarball_artifact:${blocker}`));
247
274
  }
248
- return { ok: blockers.length === 0, receipt, gate, blockers: unique(blockers) };
275
+ return { ok: blockers.length === 0, receipt, gate, sourceState, blockers: unique(blockers) };
276
+ }
277
+ export function inspectLocalReleaseSourceState(root) {
278
+ const blockers = [];
279
+ const head = gitHead(root);
280
+ if (!head)
281
+ blockers.push('npm_pack_source_commit_unavailable');
282
+ const sourceTreeSha256 = head ? gitTreeSha256(root, head) : '';
283
+ if (!sourceTreeSha256)
284
+ blockers.push('npm_pack_source_tree_digest_unavailable');
285
+ const trackedStatus = spawnSync('git', ['status', '--porcelain=v1', '--untracked-files=no'], { cwd: root, encoding: 'utf8' });
286
+ const trackedChanges = trackedStatus.status === 0 && Boolean(String(trackedStatus.stdout || '').trim());
287
+ if (trackedStatus.status !== 0)
288
+ blockers.push('npm_pack_tracked_status_unavailable');
289
+ else if (trackedChanges)
290
+ blockers.push('npm_pack_tracked_changes_present');
291
+ const fullStatus = spawnSync('git', ['status', '--porcelain=v1', '--untracked-files=all'], {
292
+ cwd: root,
293
+ encoding: 'utf8',
294
+ maxBuffer: 32 * 1024 * 1024
295
+ });
296
+ if (fullStatus.status !== 0)
297
+ blockers.push('npm_pack_untracked_status_unavailable');
298
+ else if (String(fullStatus.stdout || '').split(/\r?\n/).some((line) => line.startsWith('?? '))) {
299
+ blockers.push('npm_pack_untracked_files_present');
300
+ }
301
+ const trackedFilesResult = spawnSync('git', ['ls-files', '-z'], { cwd: root, encoding: 'utf8', maxBuffer: 32 * 1024 * 1024 });
302
+ const trackedFiles = trackedFilesResult.status === 0
303
+ ? new Set(String(trackedFilesResult.stdout || '').split('\0').filter(Boolean).map(normalizePath))
304
+ : null;
305
+ if (!trackedFiles)
306
+ blockers.push('npm_pack_tracked_file_inventory_unavailable');
307
+ const dryRun = spawnSync('npm', ['pack', '--dry-run', '--ignore-scripts', '--json'], {
308
+ cwd: root,
309
+ encoding: 'utf8',
310
+ maxBuffer: 32 * 1024 * 1024,
311
+ env: { ...process.env, npm_config_cache: process.env.SKS_RELEASE_NPM_CACHE || path.join(os.tmpdir(), 'sneakoscope-npm-cache') }
312
+ });
313
+ let packedFiles = null;
314
+ if (dryRun.status === 0) {
315
+ try {
316
+ const parsed = JSON.parse(String(dryRun.stdout || '[]'));
317
+ const info = Array.isArray(parsed) ? parsed[0] : parsed;
318
+ packedFiles = Array.isArray(info?.files)
319
+ ? info.files.map((file) => normalizePath(String(file?.path || ''))).filter(Boolean)
320
+ : null;
321
+ }
322
+ catch {
323
+ packedFiles = null;
324
+ }
325
+ }
326
+ if (!packedFiles)
327
+ blockers.push('npm_pack_dry_run_file_inventory_unavailable');
328
+ const packEligibleUntracked = trackedFiles && packedFiles
329
+ ? unique(packedFiles.filter((relative) => !trackedFiles.has(relative) && !isGeneratedDistPackagePath(relative))).sort()
330
+ : [];
331
+ if (packEligibleUntracked.length > 0)
332
+ blockers.push('npm_pack_eligible_untracked_files_present');
333
+ return {
334
+ ok: blockers.length === 0,
335
+ head,
336
+ source_tree_sha256: sourceTreeSha256,
337
+ tracked_changes: trackedChanges,
338
+ pack_eligible_untracked: packEligibleUntracked,
339
+ blockers: unique(blockers)
340
+ };
249
341
  }
250
342
  export function writeReleaseJson(file, value) {
251
343
  fs.mkdirSync(path.dirname(file), { recursive: true });
@@ -284,6 +376,7 @@ const SECRET_PATTERNS = [
284
376
  { kind: 'npm_token', regex: /\bnpm_[A-Za-z0-9]{30,}\b/g },
285
377
  { kind: 'slack_token', regex: /\bxox[baprs]-[A-Za-z0-9-]{20,}\b/g },
286
378
  { kind: 'openai_token', regex: /\bsk-(?:proj-|svcacct-)?[A-Za-z0-9_-]{20,}\b/g },
379
+ { kind: 'supabase_secret_key', regex: /\bsb_secret_[A-Za-z0-9_-]{20,}\b/g },
287
380
  { kind: 'google_api_key', regex: /\bAIza[0-9A-Za-z_-]{30,}\b/g },
288
381
  { kind: 'aws_access_key', regex: /\b(?:AKIA|ASIA)[A-Z0-9]{16}\b/g },
289
382
  { kind: 'jwt', regex: /\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/g },
@@ -323,7 +416,7 @@ const RETIRED_SURFACE_ALLOWLIST = [
323
416
  { path: /^dist\/core\/doctor\/skill-legacy-surface\.js$/, kinds: new Set(RETIRED_SURFACE_PATTERNS.map((pattern) => pattern.kind)) },
324
417
  { path: /^dist\/core\/doctor\/retired-managed-projection-residue\.js$/, kinds: new Set(['retired_team_runtime_identity']) },
325
418
  { path: /^dist\/core\/doctor\/retired-managed-residue(?:-artifact-helpers|-artifacts|-missions|-private|-runtime|-state)?\.js$/, kinds: new Set(['retired_cli_command', 'retired_dollar_command', 'retired_ralph_identity', 'retired_team_current_wording', 'retired_team_profile', 'retired_team_runtime_identity', 'retired_team_workdir']) },
326
- { path: /^dist\/core\/init\/skills\.js$/, kinds: new Set(['retired_ralph_identity']) },
419
+ { path: /^dist\/core\/init\/(?:skills\.js|skills\/inventory\.js)$/, kinds: new Set(['retired_ralph_identity']) },
327
420
  { path: /^dist\/core\/install\/installed-package-smoke\.js$/, kinds: new Set(['retired_agent_option', 'retired_clones_option', 'retired_dollar_command', 'retired_menubar_mcp_identity', 'retired_naruto_option', 'retired_naruto_workers_command', 'retired_ralph_identity']) },
328
421
  { path: /^dist\/core\/ops\/upgrade-migration-fixtures\.js$/, kinds: new Set(['retired_dollar_command']) },
329
422
  { path: /^dist\/core\/release\/release-pack-receipt\.js$/, kinds: new Set(RETIRED_SURFACE_PATTERNS.map((pattern) => pattern.kind)) },
@@ -339,6 +432,103 @@ function retiredSurfaceFindingAllowed(finding) {
339
432
  function hash(value, algorithm, encoding) {
340
433
  return crypto.createHash(algorithm).update(value).digest(encoding);
341
434
  }
435
+ function inspectSourcePackageBinding(root, sourceCommit, tarball, files, tarballSha256) {
436
+ const blockers = [];
437
+ const sourceTreeSha256 = gitTreeSha256(root, sourceCommit);
438
+ if (!sourceTreeSha256)
439
+ blockers.push('source_tree_digest_unavailable');
440
+ const temp = tmpdir('release-source-package-binding-');
441
+ const sourceRoot = path.join(temp, 'source');
442
+ const packageRoot = path.join(temp, 'packed');
443
+ const archive = path.join(temp, 'source.tar');
444
+ const packageHash = crypto.createHash('sha256');
445
+ try {
446
+ fs.mkdirSync(sourceRoot);
447
+ fs.mkdirSync(packageRoot);
448
+ const archived = spawnSync('git', ['archive', '--format=tar', `--output=${archive}`, sourceCommit], {
449
+ cwd: root,
450
+ encoding: 'utf8',
451
+ maxBuffer: 32 * 1024 * 1024
452
+ });
453
+ if (archived.status !== 0)
454
+ blockers.push('source_commit_archive_unavailable');
455
+ const extractedSource = archived.status === 0
456
+ ? spawnSync('tar', ['-xf', archive, '-C', sourceRoot], { encoding: 'utf8', maxBuffer: 32 * 1024 * 1024 })
457
+ : null;
458
+ if (!extractedSource || extractedSource.status !== 0)
459
+ blockers.push('source_commit_extract_unavailable');
460
+ const extractedPackage = spawnSync('tar', ['-xzf', tarball, '-C', packageRoot], { encoding: 'utf8', maxBuffer: 32 * 1024 * 1024 });
461
+ if (extractedPackage.status !== 0)
462
+ blockers.push('tarball_source_binding_extract_unavailable');
463
+ if (extractedSource?.status === 0 && extractedPackage.status === 0) {
464
+ for (const entry of [...files].sort()) {
465
+ const relative = normalizePath(entry).replace(/^package\//, '');
466
+ const packedFile = path.join(packageRoot, 'package', relative);
467
+ const sourceFile = path.join(sourceRoot, relative);
468
+ if (!fs.existsSync(packedFile) || !fs.statSync(packedFile).isFile()) {
469
+ blockers.push(`packed_file_unavailable_for_source_binding:${relative}`);
470
+ continue;
471
+ }
472
+ const packedBytes = fs.readFileSync(packedFile);
473
+ const packedSha256 = hash(packedBytes, 'sha256', 'hex');
474
+ packageHash.update(relative);
475
+ packageHash.update('\0');
476
+ packageHash.update(String(packedBytes.length));
477
+ packageHash.update('\0');
478
+ packageHash.update(packedSha256);
479
+ packageHash.update('\0');
480
+ if (!fs.existsSync(sourceFile) || !fs.statSync(sourceFile).isFile()) {
481
+ if (isGeneratedDistPackagePath(relative)) {
482
+ const builtFile = path.join(root, relative);
483
+ let builtStat = null;
484
+ try {
485
+ builtStat = fs.lstatSync(builtFile);
486
+ }
487
+ catch {
488
+ builtStat = null;
489
+ }
490
+ if (!builtStat?.isFile() || builtStat.isSymbolicLink()) {
491
+ blockers.push(`packed_generated_dist_missing_from_current_build:${relative}`);
492
+ }
493
+ else if (!fs.readFileSync(builtFile).equals(packedBytes)) {
494
+ blockers.push(`packed_generated_dist_differs_from_current_build:${relative}`);
495
+ }
496
+ }
497
+ else {
498
+ blockers.push(`packed_file_missing_from_source_commit:${relative}`);
499
+ }
500
+ continue;
501
+ }
502
+ const sourceBytes = fs.readFileSync(sourceFile);
503
+ if (!sourceBytes.equals(packedBytes))
504
+ blockers.push(`packed_file_differs_from_source_commit:${relative}`);
505
+ }
506
+ }
507
+ }
508
+ finally {
509
+ fs.rmSync(temp, { recursive: true, force: true });
510
+ }
511
+ const sourcePackageSha256 = packageHash.digest('hex');
512
+ return {
513
+ source_tree_sha256: sourceTreeSha256,
514
+ source_package_sha256: sourcePackageSha256,
515
+ source_package_binding_sha256: sourcePackageBindingSha256(sourceCommit, sourceTreeSha256, sourcePackageSha256, tarballSha256),
516
+ blockers: unique(blockers)
517
+ };
518
+ }
519
+ function sourcePackageBindingSha256(sourceCommit, sourceTreeSha256, sourcePackageSha256, tarballSha256) {
520
+ return hash(Buffer.from([
521
+ RELEASE_PACK_RECEIPT_SCHEMA,
522
+ sourceCommit,
523
+ sourceTreeSha256,
524
+ sourcePackageSha256,
525
+ tarballSha256
526
+ ].join('\n')), 'sha256', 'hex');
527
+ }
528
+ function isGeneratedDistPackagePath(relative) {
529
+ const normalized = normalizePath(relative);
530
+ return normalized.startsWith('dist/') && !normalized.includes('/../');
531
+ }
342
532
  function unique(values) {
343
533
  return [...new Set(values.filter(Boolean))];
344
534
  }
@@ -346,6 +536,17 @@ function gitHead(root) {
346
536
  const result = spawnSync('git', ['rev-parse', 'HEAD'], { cwd: root, encoding: 'utf8' });
347
537
  return result.status === 0 ? String(result.stdout || '').trim() : '';
348
538
  }
539
+ function gitTreeSha256(root, commit) {
540
+ if (!/^[a-f0-9]{40}$/i.test(commit))
541
+ return '';
542
+ const verified = spawnSync('git', ['rev-parse', '--verify', `${commit}^{commit}`], { cwd: root, encoding: 'utf8' });
543
+ if (verified.status !== 0 || String(verified.stdout || '').trim().toLowerCase() !== commit.toLowerCase())
544
+ return '';
545
+ const tree = spawnSync('git', ['ls-tree', '-r', '-z', '--full-tree', commit], { cwd: root, maxBuffer: 32 * 1024 * 1024 });
546
+ if (tree.status !== 0 || !Buffer.isBuffer(tree.stdout))
547
+ return '';
548
+ return hash(tree.stdout, 'sha256', 'hex');
549
+ }
349
550
  function normalizePath(value) {
350
551
  return value.split(path.sep).join('/');
351
552
  }
@@ -15,7 +15,7 @@ export const RELEASE_REAL_REQUIRED_CHECK_IDS = Object.freeze([
15
15
  'codex-sdk:real-smoke'
16
16
  ]);
17
17
  export const RELEASE_REAL_OPTIONAL_CHECK_IDS = Object.freeze([
18
- 'codex-lb:desktop-real-evidence',
18
+ 'desktop-bridge:real-evidence',
19
19
  'imagegen:real-smoke',
20
20
  'ux-review:real-imagegen-smoke',
21
21
  'ppt:real-imagegen-smoke'