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,37 +2,85 @@ import crypto from 'node:crypto';
2
2
  import fs from 'node:fs';
3
3
  import path from 'node:path';
4
4
  import { spawnSync } from 'node:child_process';
5
- export const PHYSICAL_RELEASE_GATES_SCHEMA = 'sks.release-physical-gates.v1';
6
- const MAX_PHYSICAL_RELEASE_ARTIFACT_BYTES = 8 * 1024 * 1024;
5
+ import { desktopBridgeReleaseSourceIdentitySha256, inspectDesktopBridgeReleaseEvidence } from './desktop-bridge-release-evidence.js';
6
+ export const PHYSICAL_RELEASE_GATES_SCHEMA = 'sks.release-physical-gates.v2';
7
+ export const PHYSICAL_RELEASE_GATES_INSPECTION_SCHEMA = 'sks.release-physical-gates-inspection.v2';
8
+ export const PHYSICAL_RELEASE_EVIDENCE_ARTIFACT_MANIFEST_SCHEMA = 'sks.release-physical-evidence-artifact-manifest.v1';
9
+ export const PHYSICAL_RELEASE_EVIDENCE_CAPTURE_WORKFLOW = '.github/workflows/capture-physical-release-evidence.yml';
10
+ export const PHYSICAL_RELEASE_EVIDENCE_MANIFEST = 'physical-evidence-artifact-manifest.json';
11
+ export const PHYSICAL_RELEASE_EVIDENCE_REPOSITORY = 'mandarange/Sneakoscope-Codex';
12
+ const MAX_PHYSICAL_RELEASE_ARTIFACT_BYTES = 32 * 1024 * 1024;
13
+ const MAX_PHYSICAL_RELEASE_EVIDENCE_AGE_MS = 7 * 24 * 60 * 60 * 1000;
14
+ const MAX_PHYSICAL_RELEASE_FUTURE_SKEW_MS = 5 * 60 * 1000;
15
+ const SHA256_PATTERN = /^[a-f0-9]{64}$/i;
16
+ const COMMIT_PATTERN = /^[a-f0-9]{40}$/i;
17
+ const CAPTURE_ID_PATTERN = /^[a-z0-9][a-z0-9._:/-]{2,159}$/i;
7
18
  export const PHYSICAL_RELEASE_GATE_IDS = [
8
19
  'update_5001_directory',
9
20
  'single_menubar_process',
10
21
  'codex_lb_measured_request',
11
- 'telegram_cellular_e2e'
22
+ 'telegram_cellular_e2e',
23
+ 'desktop_bridge_live_evidence'
12
24
  ];
13
25
  export function inspectPhysicalReleaseGates(opts) {
14
26
  const root = path.resolve(opts.root);
15
27
  const evidenceRoot = path.join(root, 'release-evidence', opts.version);
16
28
  const receiptPath = path.join(evidenceRoot, 'physical-gates.json');
17
29
  const headCommit = opts.sourceCommit === undefined ? gitHead(root) : opts.sourceCommit;
30
+ const inspectorPlatform = opts.inspectorPlatform ?? process.platform;
31
+ const now = opts.now?.getTime() ?? Date.now();
18
32
  const blockers = [];
19
- const receipt = readJson(receiptPath);
33
+ const receiptBytes = readRegularFile(receiptPath);
34
+ const receipt = parseJson(receiptBytes);
20
35
  if (!receipt)
21
36
  blockers.push('physical_receipt_missing_or_invalid');
37
+ if (receiptBytes && containsSecretBytes(receiptBytes))
38
+ blockers.push('physical_receipt_secret_material_present');
22
39
  if (receipt && receipt.schema !== PHYSICAL_RELEASE_GATES_SCHEMA)
23
40
  blockers.push('physical_receipt_schema_invalid');
24
41
  if (receipt && receipt.ok !== true)
25
42
  blockers.push('physical_receipt_not_ok');
43
+ if (receipt && receipt.release_authorizing !== true)
44
+ blockers.push('physical_receipt_not_release_authorizing');
45
+ if (receipt && (receipt.fixture !== false || receipt.mock !== false || receipt.synthetic !== false))
46
+ blockers.push('physical_receipt_reality_flags_invalid');
47
+ if (receipt && receipt.capture_platform !== 'darwin')
48
+ blockers.push('physical_receipt_capture_platform_invalid');
49
+ if (receipt && (!Array.isArray(receipt.blockers) || receipt.blockers.length > 0))
50
+ blockers.push('physical_receipt_blockers_present');
26
51
  if (receipt && receipt.package_version !== opts.version)
27
52
  blockers.push('physical_receipt_version_mismatch');
28
- if (!headCommit || !/^[a-f0-9]{40}$/i.test(headCommit))
53
+ validateFreshDate(receipt?.captured_at, 'physical_receipt_time', now, blockers);
54
+ if (!headCommit || !COMMIT_PATTERN.test(headCommit))
29
55
  blockers.push('physical_receipt_head_commit_unavailable');
30
56
  const releaseSourceCommit = String(receipt?.source_commit || '');
31
- if (receipt && !/^[a-f0-9]{40}$/i.test(releaseSourceCommit))
57
+ if (receipt && !COMMIT_PATTERN.test(releaseSourceCommit))
32
58
  blockers.push('physical_receipt_source_commit_invalid');
33
- if (receipt && headCommit && /^[a-f0-9]{40}$/i.test(releaseSourceCommit)) {
34
- validateSourceBinding(root, evidenceRoot, releaseSourceCommit, headCommit, blockers);
59
+ if (receipt && COMMIT_PATTERN.test(releaseSourceCommit)
60
+ && receipt.source_identity_sha256 !== desktopBridgeReleaseSourceIdentitySha256(opts.version, releaseSourceCommit)) {
61
+ blockers.push('physical_receipt_source_identity_mismatch');
35
62
  }
63
+ if (receipt && (!CAPTURE_ID_PATTERN.test(String(receipt.capture_adapter_id || ''))
64
+ || !CAPTURE_ID_PATTERN.test(String(receipt.capture_adapter_version || ''))
65
+ || !CAPTURE_ID_PATTERN.test(String(receipt.capture_adapter_receipt_id || ''))
66
+ || !SHA256_PATTERN.test(String(receipt.capture_adapter_receipt_sha256 || '')))) {
67
+ blockers.push('physical_receipt_capture_adapter_identity_invalid');
68
+ }
69
+ if ((opts.requireMacosInspector ?? true) && inspectorPlatform !== 'darwin') {
70
+ blockers.push(`physical_receipt_requires_macos_inspector:${inspectorPlatform}`);
71
+ }
72
+ if (receipt && headCommit && COMMIT_PATTERN.test(releaseSourceCommit)) {
73
+ validateSourceBinding(releaseSourceCommit, headCommit, blockers);
74
+ }
75
+ const artifactAttestation = inspectPhysicalEvidenceArtifact({
76
+ root,
77
+ version: opts.version,
78
+ sourceCommit: headCommit,
79
+ ...(opts.evidenceArchive !== undefined ? { archivePath: opts.evidenceArchive } : {}),
80
+ ...(opts.evidenceRunId !== undefined ? { evidenceRunId: opts.evidenceRunId } : {}),
81
+ ...(opts.repository !== undefined ? { repository: opts.repository } : {}),
82
+ blockers
83
+ });
36
84
  const gates = Array.isArray(receipt?.gates) ? receipt.gates : [];
37
85
  const ids = gates.map((gate) => String(gate?.id || ''));
38
86
  if (new Set(ids).size !== ids.length)
@@ -46,91 +94,589 @@ export function inspectPhysicalReleaseGates(opts) {
46
94
  blockers.push(`physical_receipt_gate_missing:${id}`);
47
95
  continue;
48
96
  }
49
- validateGate(gate, id, opts.version, evidenceRoot, blockers);
97
+ validateGate(gate, id, opts.version, releaseSourceCommit, evidenceRoot, now, blockers);
50
98
  }
99
+ const desktopBridgeGate = gates.find((entry) => entry?.id === 'desktop_bridge_live_evidence');
100
+ const desktopBridgeEvidence = desktopBridgeGate && COMMIT_PATTERN.test(releaseSourceCommit)
101
+ ? inspectDesktopBridgeReleaseEvidence({
102
+ evidenceRoot,
103
+ reportPath: String(desktopBridgeGate.artifact_path || ''),
104
+ expectedVersion: opts.version,
105
+ expectedSourceCommit: releaseSourceCommit,
106
+ ...(opts.now ? { now: opts.now } : {})
107
+ })
108
+ : null;
109
+ if (!desktopBridgeEvidence)
110
+ blockers.push('physical_receipt_desktop_bridge_evidence_missing');
111
+ else
112
+ blockers.push(...desktopBridgeEvidence.blockers.map((blocker) => `physical_receipt_desktop_bridge:${blocker}`));
113
+ validateCaptureAdapterBinding(receipt, gates, artifactAttestation, desktopBridgeEvidence, blockers);
114
+ const uniqueBlockers = [...new Set(blockers)];
115
+ const ok = uniqueBlockers.length === 0;
51
116
  return {
52
- schema: 'sks.release-physical-gates-inspection.v1',
53
- ok: blockers.length === 0,
117
+ schema: PHYSICAL_RELEASE_GATES_INSPECTION_SCHEMA,
118
+ ok,
119
+ status: ok ? 'passed' : 'blocked',
54
120
  package_version: opts.version,
55
- release_source_commit: /^[a-f0-9]{40}$/i.test(releaseSourceCommit) ? releaseSourceCommit : null,
121
+ release_source_commit: COMMIT_PATTERN.test(releaseSourceCommit) ? releaseSourceCommit : null,
56
122
  head_commit: headCommit || null,
57
123
  receipt_path: path.relative(root, receiptPath).split(path.sep).join('/'),
58
- verified_gate_ids: PHYSICAL_RELEASE_GATE_IDS.filter((id) => !blockers.some((blocker) => blocker.includes(`:${id}`))),
124
+ receipt_sha256: receiptBytes ? sha256(receiptBytes) : null,
125
+ inspector_platform: inspectorPlatform,
126
+ evidence_capture_platform: typeof receipt?.capture_platform === 'string' ? receipt.capture_platform : null,
127
+ inspected_at: new Date(now).toISOString(),
128
+ release_authorizing: ok,
129
+ artifact_attestation: artifactAttestation,
130
+ desktop_bridge_evidence: desktopBridgeEvidence,
131
+ verified_gate_ids: ok ? [...PHYSICAL_RELEASE_GATE_IDS] : [],
132
+ blockers: uniqueBlockers
133
+ };
134
+ }
135
+ export function physicalReleaseGateInspectionPath(root, version) {
136
+ return path.join(path.resolve(root), '.sneakoscope', 'reports', 'release', version, 'physical-gates-inspection.json');
137
+ }
138
+ export function physicalReleaseEvidenceArchivePath(root, version) {
139
+ return path.join(path.resolve(root), '.sneakoscope', 'reports', 'release', version, 'physical-evidence-archive.tgz');
140
+ }
141
+ export function physicalReleaseEvidenceManifestPath(root, version) {
142
+ return path.join(path.resolve(root), '.sneakoscope', 'reports', 'release', version, PHYSICAL_RELEASE_EVIDENCE_MANIFEST);
143
+ }
144
+ export function validatePhysicalReleaseGateInspection(input) {
145
+ const reportPath = physicalReleaseGateInspectionPath(input.root, input.version);
146
+ const bytes = readRegularFile(reportPath);
147
+ const report = parseJson(bytes);
148
+ const blockers = [];
149
+ if (!report || report.schema !== PHYSICAL_RELEASE_GATES_INSPECTION_SCHEMA)
150
+ blockers.push('physical_inspection_missing_or_invalid');
151
+ if (report && (report.ok !== true || report.status !== 'passed' || report.release_authorizing !== true))
152
+ blockers.push('physical_inspection_not_authorizing');
153
+ if (report && report.inspector_platform !== 'darwin')
154
+ blockers.push('physical_inspection_not_macos');
155
+ if (report && report.evidence_capture_platform !== 'darwin')
156
+ blockers.push('physical_inspection_capture_platform_invalid');
157
+ if (report && report.package_version !== input.version)
158
+ blockers.push('physical_inspection_version_mismatch');
159
+ if (report && report.release_source_commit !== input.sourceCommit)
160
+ blockers.push('physical_inspection_source_commit_mismatch');
161
+ if (report && report.head_commit !== input.sourceCommit)
162
+ blockers.push('physical_inspection_head_commit_mismatch');
163
+ if (report && !validDate(report.inspected_at))
164
+ blockers.push('physical_inspection_time_invalid');
165
+ if (report && (!Array.isArray(report.blockers) || report.blockers.length > 0))
166
+ blockers.push('physical_inspection_blockers_present');
167
+ if (report && !sameStrings(report.verified_gate_ids, PHYSICAL_RELEASE_GATE_IDS))
168
+ blockers.push('physical_inspection_gate_inventory_invalid');
169
+ if (report && !report.artifact_attestation)
170
+ blockers.push('physical_inspection_artifact_attestation_missing');
171
+ const current = inspectPhysicalReleaseGates({
172
+ root: input.root,
173
+ version: input.version,
174
+ sourceCommit: input.sourceCommit,
175
+ ...(input.now ? { now: input.now } : {}),
176
+ requireMacosInspector: false,
177
+ evidenceArchive: report?.artifact_attestation?.archive_path
178
+ ? path.resolve(input.root, report.artifact_attestation.archive_path)
179
+ : null,
180
+ evidenceRunId: report?.artifact_attestation?.workflow_run_id || null,
181
+ repository: report?.artifact_attestation?.github_repository || null
182
+ });
183
+ if (!current.ok)
184
+ blockers.push(...current.blockers.map((blocker) => `physical_inspection_revalidation:${blocker}`));
185
+ if (report && report.receipt_sha256 !== current.receipt_sha256)
186
+ blockers.push('physical_inspection_receipt_hash_mismatch');
187
+ if (report && !sameDesktopEvidenceBinding(report.desktop_bridge_evidence, current.desktop_bridge_evidence)) {
188
+ blockers.push('physical_inspection_desktop_bridge_binding_mismatch');
189
+ }
190
+ if (report && !sameArtifactAttestation(report.artifact_attestation, current.artifact_attestation)) {
191
+ blockers.push('physical_inspection_artifact_attestation_mismatch');
192
+ }
193
+ return {
194
+ ok: blockers.length === 0,
195
+ path: path.relative(path.resolve(input.root), reportPath).split(path.sep).join('/'),
196
+ sha256: bytes ? sha256(bytes) : null,
197
+ report,
59
198
  blockers: [...new Set(blockers)]
60
199
  };
61
200
  }
62
- function validateSourceBinding(root, evidenceRoot, releaseSourceCommit, headCommit, blockers) {
63
- if (releaseSourceCommit === headCommit)
64
- return;
65
- const ancestor = spawnSync('git', ['merge-base', '--is-ancestor', releaseSourceCommit, headCommit], { cwd: root, encoding: 'utf8' });
66
- if (ancestor.status !== 0) {
67
- blockers.push('physical_receipt_source_commit_mismatch');
68
- return;
201
+ function sameDesktopEvidenceBinding(left, right) {
202
+ if (!left || !right)
203
+ return false;
204
+ return left.ok === true
205
+ && right.ok === true
206
+ && left.report_sha256 === right.report_sha256
207
+ && left.adapter_receipt_sha256 === right.adapter_receipt_sha256
208
+ && left.native_screenshot_sha256 === right.native_screenshot_sha256
209
+ && left.deep_artifact_count === right.deep_artifact_count
210
+ && left.adapter_id === right.adapter_id
211
+ && left.adapter_version === right.adapter_version
212
+ && left.adapter_run_id === right.adapter_run_id
213
+ && left.adapter_receipt_id === right.adapter_receipt_id
214
+ && sameStrings(left.providers, right.providers);
215
+ }
216
+ function sameArtifactAttestation(left, right) {
217
+ if (!left || !right)
218
+ return false;
219
+ return left.schema === right.schema
220
+ && left.archive_path === right.archive_path
221
+ && left.archive_sha256 === right.archive_sha256
222
+ && left.manifest_sha256 === right.manifest_sha256
223
+ && left.github_repository === right.github_repository
224
+ && left.trusted_workflow === right.trusted_workflow
225
+ && left.workflow_run_id === right.workflow_run_id
226
+ && left.run_invocation_uri === right.run_invocation_uri
227
+ && left.artifact_name === right.artifact_name
228
+ && left.entry_count === right.entry_count
229
+ && left.capture_adapter_executable_sha256 === right.capture_adapter_executable_sha256
230
+ && left.capture_adapter_id === right.capture_adapter_id
231
+ && left.capture_adapter_version === right.capture_adapter_version
232
+ && left.capture_adapter_run_id === right.capture_adapter_run_id
233
+ && left.capture_adapter_receipt_id === right.capture_adapter_receipt_id
234
+ && left.capture_adapter_receipt_sha256 === right.capture_adapter_receipt_sha256;
235
+ }
236
+ function inspectPhysicalEvidenceArtifact(input) {
237
+ const blockerCount = input.blockers.length;
238
+ const archivePath = path.resolve(input.archivePath || physicalReleaseEvidenceArchivePath(input.root, input.version));
239
+ const runId = String(input.evidenceRunId || '');
240
+ const repository = String(input.repository || '');
241
+ const sourceCommit = String(input.sourceCommit || '');
242
+ if (!/^\d+$/.test(runId))
243
+ input.blockers.push('physical_evidence_attestation_run_id_missing_or_invalid');
244
+ if (repository !== PHYSICAL_RELEASE_EVIDENCE_REPOSITORY)
245
+ input.blockers.push('physical_evidence_attestation_repository_missing_or_invalid');
246
+ if (!COMMIT_PATTERN.test(sourceCommit))
247
+ input.blockers.push('physical_evidence_attestation_source_commit_invalid');
248
+ const archiveBytes = readBoundArchive(archivePath);
249
+ if (!archiveBytes)
250
+ input.blockers.push('physical_evidence_attestation_archive_missing_or_invalid');
251
+ if (!archiveBytes || !/^\d+$/.test(runId) || repository !== PHYSICAL_RELEASE_EVIDENCE_REPOSITORY || !COMMIT_PATTERN.test(sourceCommit)) {
252
+ input.blockers.push('physical_evidence_attestation_missing');
253
+ return null;
254
+ }
255
+ const archiveSha256 = sha256(archiveBytes);
256
+ const trustedWorkflow = `${repository}/${PHYSICAL_RELEASE_EVIDENCE_CAPTURE_WORKFLOW}`;
257
+ const invocationUri = verifyGithubArtifactAttestation({
258
+ archivePath,
259
+ repository,
260
+ trustedWorkflow,
261
+ sourceCommit,
262
+ runId,
263
+ blockers: input.blockers
264
+ });
265
+ const listed = listArchiveFiles(archivePath, input.blockers);
266
+ const manifestMember = listed?.get(PHYSICAL_RELEASE_EVIDENCE_MANIFEST);
267
+ if (!manifestMember)
268
+ input.blockers.push('physical_evidence_artifact_manifest_missing_from_archive');
269
+ const archiveManifestBytes = manifestMember
270
+ ? readArchiveMember(archivePath, manifestMember, 'physical_evidence_artifact_manifest', input.blockers)
271
+ : null;
272
+ const extractedManifestPath = physicalReleaseEvidenceManifestPath(input.root, input.version);
273
+ const extractedManifestBytes = readRegularFile(extractedManifestPath);
274
+ if (!extractedManifestBytes)
275
+ input.blockers.push('physical_evidence_artifact_manifest_missing_or_invalid');
276
+ if (archiveManifestBytes && extractedManifestBytes && !archiveManifestBytes.equals(extractedManifestBytes)) {
277
+ input.blockers.push('physical_evidence_artifact_manifest_archive_mismatch');
278
+ }
279
+ const manifest = archiveManifestBytes ? parseJson(archiveManifestBytes) : null;
280
+ const expectedArtifactName = `physical-release-evidence-${sourceCommit}`;
281
+ if (manifest?.schema !== PHYSICAL_RELEASE_EVIDENCE_ARTIFACT_MANIFEST_SCHEMA)
282
+ input.blockers.push('physical_evidence_artifact_manifest_schema_invalid');
283
+ if (manifest?.package_version !== input.version)
284
+ input.blockers.push('physical_evidence_artifact_manifest_version_mismatch');
285
+ if (manifest?.source_commit !== sourceCommit)
286
+ input.blockers.push('physical_evidence_artifact_manifest_source_commit_mismatch');
287
+ if (manifest?.github_repository !== repository)
288
+ input.blockers.push('physical_evidence_artifact_manifest_repository_mismatch');
289
+ if (manifest?.workflow_path !== PHYSICAL_RELEASE_EVIDENCE_CAPTURE_WORKFLOW)
290
+ input.blockers.push('physical_evidence_artifact_manifest_workflow_mismatch');
291
+ if (String(manifest?.workflow_run_id || '') !== runId)
292
+ input.blockers.push('physical_evidence_artifact_manifest_run_id_mismatch');
293
+ if (manifest?.artifact_name !== expectedArtifactName)
294
+ input.blockers.push('physical_evidence_artifact_manifest_name_mismatch');
295
+ if (manifest?.capture_platform !== 'darwin')
296
+ input.blockers.push('physical_evidence_artifact_manifest_platform_mismatch');
297
+ const captureAdapter = manifest?.capture_adapter;
298
+ if (captureAdapter?.schema !== 'sks.release-physical-capture-adapter.v1'
299
+ || captureAdapter?.executable_path !== '/usr/local/bin/sks-physical-release-capture'
300
+ || !SHA256_PATTERN.test(String(captureAdapter?.executable_sha256 || ''))
301
+ || captureAdapter?.owner_uid !== 0
302
+ || !CAPTURE_ID_PATTERN.test(String(captureAdapter?.adapter_id || ''))
303
+ || !CAPTURE_ID_PATTERN.test(String(captureAdapter?.adapter_version || ''))
304
+ || !CAPTURE_ID_PATTERN.test(String(captureAdapter?.run_id || ''))
305
+ || !CAPTURE_ID_PATTERN.test(String(captureAdapter?.receipt_id || ''))
306
+ || captureAdapter?.receipt_path !== 'desktop-bridge/production-adapter-receipt.json'
307
+ || !SHA256_PATTERN.test(String(captureAdapter?.receipt_sha256 || ''))) {
308
+ input.blockers.push('physical_evidence_artifact_manifest_capture_adapter_invalid');
309
+ }
310
+ const entries = Array.isArray(manifest?.entries) ? manifest.entries : [];
311
+ if (entries.length < 1)
312
+ input.blockers.push('physical_evidence_artifact_manifest_entries_invalid');
313
+ const entryPaths = entries.map((entry) => String(entry?.path || ''));
314
+ if (new Set(entryPaths).size !== entryPaths.length || JSON.stringify(entryPaths) !== JSON.stringify([...entryPaths].sort())) {
315
+ input.blockers.push('physical_evidence_artifact_manifest_inventory_invalid');
316
+ }
317
+ const expectedPrefix = `release-evidence/${input.version}/`;
318
+ for (const entry of entries) {
319
+ const relativePath = String(entry?.path || '');
320
+ const expectedSha256 = String(entry?.sha256 || '');
321
+ const expectedBytes = entry?.bytes;
322
+ if (!safeArchivePath(relativePath) || !relativePath.startsWith(expectedPrefix)) {
323
+ input.blockers.push(`physical_evidence_artifact_manifest_path_invalid:${relativePath || 'missing'}`);
324
+ continue;
325
+ }
326
+ if (!SHA256_PATTERN.test(expectedSha256) || !Number.isSafeInteger(expectedBytes) || expectedBytes < 1) {
327
+ input.blockers.push(`physical_evidence_artifact_manifest_entry_invalid:${relativePath}`);
328
+ continue;
329
+ }
330
+ const member = listed?.get(relativePath);
331
+ if (!member) {
332
+ input.blockers.push(`physical_evidence_artifact_archive_entry_missing:${relativePath}`);
333
+ continue;
334
+ }
335
+ const memberBytes = readArchiveMember(archivePath, member, `physical_evidence_artifact_entry:${relativePath}`, input.blockers);
336
+ if (!memberBytes || memberBytes.length !== expectedBytes || sha256(memberBytes) !== expectedSha256.toLowerCase()) {
337
+ input.blockers.push(`physical_evidence_artifact_archive_entry_mismatch:${relativePath}`);
338
+ }
339
+ if (memberBytes && containsSecretBytes(memberBytes)) {
340
+ input.blockers.push(`physical_evidence_artifact_secret_material:${relativePath}`);
341
+ }
342
+ const extractedPath = path.resolve(input.root, relativePath);
343
+ const extractedRelative = path.relative(input.root, extractedPath);
344
+ const extractedBytes = safeArchivePath(extractedRelative.split(path.sep).join('/')) ? readRegularFile(extractedPath) : null;
345
+ if (!extractedBytes || extractedBytes.length !== expectedBytes || sha256(extractedBytes) !== expectedSha256.toLowerCase()) {
346
+ input.blockers.push(`physical_evidence_artifact_extracted_entry_mismatch:${relativePath}`);
347
+ }
348
+ }
349
+ if (listed) {
350
+ const expectedFiles = new Set([PHYSICAL_RELEASE_EVIDENCE_MANIFEST, ...entryPaths]);
351
+ const unexpected = [...listed.keys()].filter((entry) => !expectedFiles.has(entry));
352
+ const missing = [...expectedFiles].filter((entry) => !listed.has(entry));
353
+ if (unexpected.length > 0 || missing.length > 0 || listed.size !== expectedFiles.size) {
354
+ input.blockers.push('physical_evidence_artifact_archive_inventory_mismatch');
355
+ }
356
+ }
357
+ if (input.blockers.length !== blockerCount || !invocationUri || !archiveManifestBytes)
358
+ return null;
359
+ return {
360
+ schema: 'sks.release-physical-evidence-artifact-attestation.v1',
361
+ archive_path: relativeOrAbsolute(input.root, archivePath),
362
+ archive_sha256: archiveSha256,
363
+ manifest_sha256: sha256(archiveManifestBytes),
364
+ github_repository: repository,
365
+ trusted_workflow: trustedWorkflow,
366
+ workflow_run_id: runId,
367
+ run_invocation_uri: invocationUri,
368
+ artifact_name: expectedArtifactName,
369
+ entry_count: entries.length,
370
+ capture_adapter_executable_sha256: String(captureAdapter?.executable_sha256 || '').toLowerCase(),
371
+ capture_adapter_id: String(captureAdapter?.adapter_id || ''),
372
+ capture_adapter_version: String(captureAdapter?.adapter_version || ''),
373
+ capture_adapter_run_id: String(captureAdapter?.run_id || ''),
374
+ capture_adapter_receipt_id: String(captureAdapter?.receipt_id || ''),
375
+ capture_adapter_receipt_sha256: String(captureAdapter?.receipt_sha256 || '').toLowerCase()
376
+ };
377
+ }
378
+ function verifyGithubArtifactAttestation(input) {
379
+ const result = spawnSync('gh', [
380
+ 'attestation', 'verify', input.archivePath,
381
+ '--repo', input.repository,
382
+ '--signer-workflow', input.trustedWorkflow,
383
+ '--source-digest', input.sourceCommit,
384
+ '--format', 'json'
385
+ ], { encoding: 'utf8', maxBuffer: 4 * 1024 * 1024 });
386
+ if (result.status !== 0) {
387
+ input.blockers.push('physical_evidence_attestation_verification_failed');
388
+ return null;
389
+ }
390
+ let rows;
391
+ try {
392
+ rows = JSON.parse(String(result.stdout || ''));
393
+ }
394
+ catch {
395
+ input.blockers.push('physical_evidence_attestation_output_invalid');
396
+ return null;
397
+ }
398
+ const certificates = (Array.isArray(rows) ? rows : [rows])
399
+ .map((row) => row?.verificationResult?.signature?.certificate)
400
+ .filter((certificate) => certificate && typeof certificate === 'object');
401
+ const expected = `https://github.com/${input.repository}/actions/runs/${input.runId}/attempts/`;
402
+ const invocationUris = certificates.flatMap((certificate) => collectNamedStrings(certificate, 'runInvocationURI'));
403
+ const invocationUri = invocationUris.find((uri) => uri.startsWith(expected) && /^\d+$/.test(uri.slice(expected.length))) || null;
404
+ if (!invocationUri)
405
+ input.blockers.push('physical_evidence_attestation_run_binding_invalid');
406
+ return invocationUri;
407
+ }
408
+ function collectNamedStrings(value, wanted) {
409
+ if (Array.isArray(value))
410
+ return value.flatMap((entry) => collectNamedStrings(entry, wanted));
411
+ if (!value || typeof value !== 'object')
412
+ return [];
413
+ const output = [];
414
+ for (const [key, child] of Object.entries(value)) {
415
+ if (key.toLowerCase() === wanted.toLowerCase() && typeof child === 'string')
416
+ output.push(child);
417
+ output.push(...collectNamedStrings(child, wanted));
418
+ }
419
+ return output;
420
+ }
421
+ function readBoundArchive(file) {
422
+ try {
423
+ const stat = fs.lstatSync(file);
424
+ if (!stat.isFile() || stat.isSymbolicLink() || stat.size < 1 || stat.size > 512 * 1024 * 1024)
425
+ return null;
426
+ return fs.readFileSync(file);
427
+ }
428
+ catch {
429
+ return null;
430
+ }
431
+ }
432
+ function listArchiveFiles(archivePath, blockers) {
433
+ const result = spawnSync('tar', ['-tzf', archivePath], { encoding: 'utf8', maxBuffer: 16 * 1024 * 1024 });
434
+ if (result.status !== 0) {
435
+ blockers.push('physical_evidence_artifact_archive_unreadable');
436
+ return null;
437
+ }
438
+ const files = new Map();
439
+ for (const rawLine of String(result.stdout || '').split(/\r?\n/)) {
440
+ const raw = rawLine.trim();
441
+ if (!raw)
442
+ continue;
443
+ const normalized = raw.replace(/^\.\//, '');
444
+ const pathToValidate = normalized.endsWith('/') ? normalized.slice(0, -1) : normalized;
445
+ if (!safeArchivePath(pathToValidate)) {
446
+ blockers.push('physical_evidence_artifact_archive_path_invalid');
447
+ return null;
448
+ }
449
+ if (normalized.endsWith('/'))
450
+ continue;
451
+ if (files.has(normalized))
452
+ blockers.push(`physical_evidence_artifact_archive_duplicate:${normalized}`);
453
+ else
454
+ files.set(normalized, raw);
455
+ }
456
+ return files;
457
+ }
458
+ function readArchiveMember(archivePath, member, label, blockers) {
459
+ const result = spawnSync('tar', ['-xOzf', archivePath, member], { encoding: 'buffer', maxBuffer: 64 * 1024 * 1024 });
460
+ if (result.status !== 0 || !Buffer.isBuffer(result.stdout)) {
461
+ blockers.push(`${label}_unreadable`);
462
+ return null;
69
463
  }
70
- const diff = spawnSync('git', ['diff', '--name-only', `${releaseSourceCommit}..${headCommit}`, '--'], { cwd: root, encoding: 'utf8' });
71
- if (diff.status !== 0) {
72
- blockers.push('physical_receipt_source_diff_unavailable');
464
+ return result.stdout;
465
+ }
466
+ function safeArchivePath(value) {
467
+ if (!value || value.includes('\\') || value.startsWith('/') || value.endsWith('/'))
468
+ return false;
469
+ const parts = value.split('/');
470
+ return parts.every((part) => part.length > 0 && part !== '.' && part !== '..' && /^[A-Za-z0-9._-]+$/.test(part));
471
+ }
472
+ function relativeOrAbsolute(root, file) {
473
+ const relative = path.relative(path.resolve(root), path.resolve(file));
474
+ return relative && !relative.startsWith('..') && !path.isAbsolute(relative)
475
+ ? relative.split(path.sep).join('/')
476
+ : path.resolve(file);
477
+ }
478
+ function validateCaptureAdapterBinding(receipt, gates, attestation, desktop, blockers) {
479
+ if (!attestation || !desktop || !desktop.ok)
73
480
  return;
481
+ const bindingValid = receipt?.capture_adapter_id === desktop.adapter_id
482
+ && receipt?.capture_adapter_version === desktop.adapter_version
483
+ && receipt?.capture_adapter_receipt_id === desktop.adapter_receipt_id
484
+ && receipt?.capture_adapter_receipt_sha256 === desktop.adapter_receipt_sha256
485
+ && attestation.capture_adapter_id === desktop.adapter_id
486
+ && attestation.capture_adapter_version === desktop.adapter_version
487
+ && attestation.capture_adapter_run_id === desktop.adapter_run_id
488
+ && attestation.capture_adapter_receipt_id === desktop.adapter_receipt_id
489
+ && attestation.capture_adapter_receipt_sha256 === desktop.adapter_receipt_sha256;
490
+ if (!bindingValid)
491
+ blockers.push('physical_receipt_capture_adapter_binding_mismatch');
492
+ for (const gate of gates) {
493
+ const id = String(gate?.id || 'missing');
494
+ if (gate?.capture_adapter_id !== desktop.adapter_id
495
+ || gate?.capture_adapter_version !== desktop.adapter_version
496
+ || gate?.producer_receipt_id !== desktop.adapter_receipt_id
497
+ || gate?.producer_receipt_sha256 !== desktop.adapter_receipt_sha256) {
498
+ blockers.push(`physical_receipt_gate_adapter_binding_mismatch:${id}`);
499
+ }
74
500
  }
75
- const evidencePrefix = `${path.relative(root, evidenceRoot).split(path.sep).join('/')}/`;
76
- const changed = String(diff.stdout || '').split(/\r?\n/).map((entry) => entry.trim()).filter(Boolean);
77
- const outsideEvidence = changed.filter((entry) => !entry.startsWith(evidencePrefix));
78
- if (outsideEvidence.length > 0)
79
- blockers.push(`physical_receipt_source_changed_after_capture:${outsideEvidence.slice(0, 8).join(',')}`);
80
501
  }
81
- function validateGate(gate, id, version, evidenceRoot, blockers) {
502
+ function validateSourceBinding(releaseSourceCommit, headCommit, blockers) {
503
+ if (releaseSourceCommit !== headCommit)
504
+ blockers.push('physical_receipt_source_commit_mismatch');
505
+ }
506
+ function validateGate(gate, id, version, sourceCommit, evidenceRoot, now, blockers) {
82
507
  if (gate.ok !== true)
83
508
  blockers.push(`physical_receipt_gate_not_ok:${id}`);
84
509
  if (gate.evidence_kind !== 'real')
85
510
  blockers.push(`physical_receipt_gate_not_real:${id}`);
86
- if (gate.fixture === true || gate.mock === true || gate.synthetic === true)
511
+ if (gate.fixture !== false || gate.mock !== false || gate.synthetic !== false)
87
512
  blockers.push(`physical_receipt_gate_synthetic:${id}`);
88
513
  if (gate.redacted !== true || gate.secrets_present !== false)
89
514
  blockers.push(`physical_receipt_gate_redaction_invalid:${id}`);
90
- if (!validDate(gate.performed_at))
91
- blockers.push(`physical_receipt_gate_time_invalid:${id}`);
515
+ if (!CAPTURE_ID_PATTERN.test(String(gate.capture_adapter_id || ''))
516
+ || !CAPTURE_ID_PATTERN.test(String(gate.capture_adapter_version || ''))
517
+ || !CAPTURE_ID_PATTERN.test(String(gate.producer_receipt_id || ''))
518
+ || !SHA256_PATTERN.test(String(gate.producer_receipt_sha256 || ''))) {
519
+ blockers.push(`physical_receipt_gate_adapter_identity_invalid:${id}`);
520
+ }
521
+ if (gate.package_version !== version)
522
+ blockers.push(`physical_receipt_gate_version_mismatch:${id}`);
523
+ if (!COMMIT_PATTERN.test(String(gate.source_commit || '')) || gate.source_commit !== sourceCommit) {
524
+ blockers.push(`physical_receipt_gate_source_commit_mismatch:${id}`);
525
+ }
526
+ validateFreshDate(gate.performed_at, `physical_receipt_gate_time:${id}`, now, blockers);
92
527
  if (!String(gate.reviewer || '').trim())
93
528
  blockers.push(`physical_receipt_gate_reviewer_missing:${id}`);
94
529
  if (!String(gate.summary || '').trim())
95
530
  blockers.push(`physical_receipt_gate_summary_missing:${id}`);
96
- validateArtifact(gate, id, evidenceRoot, blockers);
97
- validateObservations(gate.observations || {}, id, version, blockers);
531
+ const artifact = validateArtifactReference(gate.artifact_path, gate.artifact_sha256, id, evidenceRoot, null, blockers);
532
+ if (id !== 'desktop_bridge_live_evidence' && artifact) {
533
+ validateGenericGateArtifact(artifact, gate, id, version, sourceCommit, evidenceRoot, blockers);
534
+ }
535
+ if (artifact && containsSecretBytes(artifact))
536
+ blockers.push(`physical_receipt_artifact_secret_material:${id}`);
537
+ validateObservations(gate.observations || {}, id, version, sourceCommit, evidenceRoot, artifact, now, blockers);
98
538
  }
99
- function validateArtifact(gate, id, evidenceRoot, blockers) {
100
- const rel = String(gate.artifact_path || '');
101
- if (!rel || path.isAbsolute(rel)) {
102
- blockers.push(`physical_receipt_artifact_path_invalid:${id}`);
539
+ export function physicalGateObservationsSha256(value) {
540
+ return sha256(Buffer.from(canonicalJson(value)));
541
+ }
542
+ function validateGenericGateArtifact(bytes, gate, id, version, sourceCommit, evidenceRoot, blockers) {
543
+ const artifact = parseJson(bytes);
544
+ const producer = artifact?.producer;
545
+ if (artifact?.schema !== 'sks.release-physical-gate-artifact.v2'
546
+ || artifact?.gate_id !== id
547
+ || artifact?.package_version !== version
548
+ || artifact?.source_commit !== sourceCommit
549
+ || producer?.schema !== 'sks.release-physical-gate-producer.v1'
550
+ || producer?.capture_adapter_id !== gate.capture_adapter_id
551
+ || producer?.capture_adapter_version !== gate.capture_adapter_version
552
+ || producer?.receipt_id !== gate.producer_receipt_id
553
+ || producer?.receipt_sha256 !== gate.producer_receipt_sha256
554
+ || producer?.command_id !== genericGateCommandId(id)
555
+ || !CAPTURE_ID_PATTERN.test(String(producer?.invocation_id || ''))
556
+ || producer?.exit_code !== 0
557
+ || producer?.fixture !== false
558
+ || producer?.mock !== false
559
+ || producer?.synthetic !== false
560
+ || !validDate(producer?.started_at)
561
+ || !validDate(producer?.completed_at)
562
+ || Date.parse(producer.completed_at) < Date.parse(producer.started_at)
563
+ || producer?.completed_at !== gate.performed_at) {
564
+ blockers.push(`physical_receipt_gate_artifact_contract_invalid:${id}`);
103
565
  return;
104
566
  }
567
+ const outputBytes = validateArtifactReference(producer.output_path, producer.output_sha256, id, evidenceRoot, 'producer_output', blockers);
568
+ const output = outputBytes ? parseJson(outputBytes) : null;
569
+ if (outputBytes && containsSecretBytes(outputBytes))
570
+ blockers.push(`physical_receipt_gate_producer_output_secret_material:${id}`);
571
+ const derived = deriveGenericGateObservations(id, output?.measurement);
572
+ if (output?.schema !== 'sks.release-physical-gate-producer-output.v1'
573
+ || output?.gate_id !== id
574
+ || output?.capture_adapter_id !== gate.capture_adapter_id
575
+ || output?.capture_adapter_version !== gate.capture_adapter_version
576
+ || output?.producer_receipt_id !== gate.producer_receipt_id
577
+ || output?.invocation_id !== producer.invocation_id
578
+ || output?.fixture !== false
579
+ || output?.mock !== false
580
+ || output?.synthetic !== false
581
+ || !derived
582
+ || canonicalJson(derived) !== canonicalJson(gate.observations || {})) {
583
+ blockers.push(`physical_receipt_gate_producer_output_invalid:${id}`);
584
+ }
585
+ }
586
+ function genericGateCommandId(id) {
587
+ return {
588
+ update_5001_directory: 'sks.update.physical',
589
+ single_menubar_process: 'sks.menubar.process-readback',
590
+ codex_lb_measured_request: 'sks.codex-lb.measured-request',
591
+ telegram_cellular_e2e: 'sks.telegram.cellular-roundtrip',
592
+ desktop_bridge_live_evidence: 'sks.desktop-bridge.live-evidence'
593
+ }[id];
594
+ }
595
+ function deriveGenericGateObservations(id, measurement) {
596
+ if (id === 'update_5001_directory') {
597
+ if (!Number.isSafeInteger(measurement?.directories_scanned)
598
+ || measurement?.update_exit_code !== 0
599
+ || !Array.isArray(measurement?.warning_codes)
600
+ || !Array.isArray(measurement?.residue_paths))
601
+ return null;
602
+ return {
603
+ directories_encountered: measurement.directories_scanned,
604
+ update_status: 'succeeded',
605
+ warning_code: measurement.warning_codes.includes('guidance_scan_truncated') ? 'guidance_scan_truncated' : '',
606
+ false_residue_blockers: measurement.residue_paths.length
607
+ };
608
+ }
609
+ if (id === 'single_menubar_process') {
610
+ if (!Array.isArray(measurement?.processes) || typeof measurement?.prior_version !== 'string')
611
+ return null;
612
+ const current = measurement.processes.filter((entry) => entry?.role === 'sks-menubar');
613
+ return {
614
+ process_count: current.length,
615
+ running_version: current.length === 1 ? current[0]?.version : null,
616
+ prior_version: measurement.prior_version,
617
+ process_readback: measurement.readback_source === 'launchctl-and-process-table'
618
+ };
619
+ }
620
+ if (id === 'codex_lb_measured_request') {
621
+ if (!CAPTURE_ID_PATTERN.test(String(measurement?.request_id || ''))
622
+ || !Number.isFinite(measurement?.latency_ms))
623
+ return null;
624
+ return {
625
+ selected: measurement.selected_target === 'codex-lb',
626
+ measured: Boolean(measurement.response_sha256 && SHA256_PATTERN.test(String(measurement.response_sha256))),
627
+ target_matches_configured: measurement.selected_target === measurement.configured_target,
628
+ auth_class: measurement.auth_class,
629
+ oauth_fallback: measurement.oauth_header_present === true,
630
+ latency_ms: measurement.latency_ms
631
+ };
632
+ }
633
+ if (id === 'telegram_cellular_e2e') {
634
+ if (!CAPTURE_ID_PATTERN.test(String(measurement?.pairing_id || ''))
635
+ || !SHA256_PATTERN.test(String(measurement?.outbound_message_sha256 || ''))
636
+ || !SHA256_PATTERN.test(String(measurement?.inbound_reply_sha256 || '')))
637
+ return null;
638
+ return {
639
+ network: measurement.network_interface_kind,
640
+ paired: measurement.pairing_verified === true,
641
+ allowlisted: measurement.allowlist_match === true,
642
+ typed_command: measurement.outbound_typed === true,
643
+ reply_received: measurement.inbound_reply_received === true,
644
+ bot_token_recorded: measurement.bot_token_captured === true
645
+ };
646
+ }
647
+ return null;
648
+ }
649
+ function validateArtifactReference(artifactPath, artifactSha256, id, evidenceRoot, scope, blockers) {
650
+ const suffix = scope ? `:${id}:${scope}` : `:${id}`;
651
+ const rel = String(artifactPath || '');
652
+ if (!rel || path.isAbsolute(rel)) {
653
+ blockers.push(`physical_receipt_artifact_path_invalid${suffix}`);
654
+ return null;
655
+ }
105
656
  const file = path.resolve(evidenceRoot, rel);
106
657
  const relative = path.relative(evidenceRoot, file);
107
658
  if (!relative || relative.startsWith('..') || path.isAbsolute(relative)) {
108
- blockers.push(`physical_receipt_artifact_path_invalid:${id}`);
109
- return;
110
- }
111
- let stat;
112
- try {
113
- stat = fs.lstatSync(file);
114
- }
115
- catch {
116
- blockers.push(`physical_receipt_artifact_missing:${id}`);
117
- return;
659
+ blockers.push(`physical_receipt_artifact_path_invalid${suffix}`);
660
+ return null;
118
661
  }
119
- if (!stat.isFile() || stat.isSymbolicLink()) {
120
- blockers.push(`physical_receipt_artifact_not_regular:${id}`);
121
- return;
662
+ const bytes = readRegularFile(file);
663
+ if (!bytes) {
664
+ blockers.push(fs.existsSync(file)
665
+ ? `physical_receipt_artifact_not_regular${suffix}`
666
+ : `physical_receipt_artifact_missing${suffix}`);
667
+ return null;
122
668
  }
123
- if (stat.size < 1 || stat.size > MAX_PHYSICAL_RELEASE_ARTIFACT_BYTES) {
124
- blockers.push(`physical_receipt_artifact_size_invalid:${id}`);
125
- return;
669
+ if (bytes.length < 1 || bytes.length > MAX_PHYSICAL_RELEASE_ARTIFACT_BYTES) {
670
+ blockers.push(`physical_receipt_artifact_size_invalid${suffix}`);
671
+ return null;
126
672
  }
127
- const expected = String(gate.artifact_sha256 || '');
128
- const actual = crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex');
129
- if (!/^[a-f0-9]{64}$/i.test(expected) || actual !== expected.toLowerCase()) {
130
- blockers.push(`physical_receipt_artifact_hash_mismatch:${id}`);
673
+ const expected = String(artifactSha256 || '');
674
+ if (!SHA256_PATTERN.test(expected) || sha256(bytes) !== expected.toLowerCase()) {
675
+ blockers.push(`physical_receipt_artifact_hash_mismatch${suffix}`);
131
676
  }
677
+ return bytes;
132
678
  }
133
- function validateObservations(observations, id, version, blockers) {
679
+ function validateObservations(observations, id, version, sourceCommit, evidenceRoot, artifact, now, blockers) {
134
680
  if (id === 'update_5001_directory') {
135
681
  if (!Number.isSafeInteger(observations.directories_encountered) || observations.directories_encountered < 5001)
136
682
  blockers.push(`physical_receipt_observation_invalid:${id}:directories`);
@@ -171,18 +717,75 @@ function validateObservations(observations, id, version, blockers) {
171
717
  if (observations.bot_token_recorded !== false)
172
718
  blockers.push(`physical_receipt_observation_invalid:${id}:secret`);
173
719
  }
720
+ else if (id === 'desktop_bridge_live_evidence') {
721
+ if (observations.release_authorizing !== true
722
+ || observations.capture_platform !== 'darwin'
723
+ || observations.both_providers_verified !== true
724
+ || observations.oauth_preserved !== true
725
+ || observations.websocket_verified !== true
726
+ || observations.native_ui_screenshot_verified !== true
727
+ || observations.production_deep_artifacts_verified !== true) {
728
+ blockers.push(`physical_receipt_observation_invalid:${id}:release_contract`);
729
+ }
730
+ }
174
731
  }
175
- function readJson(file) {
732
+ function parseJson(bytes) {
733
+ if (!bytes)
734
+ return null;
176
735
  try {
177
- return JSON.parse(fs.readFileSync(file, 'utf8'));
736
+ return JSON.parse(bytes.toString('utf8'));
178
737
  }
179
738
  catch {
180
739
  return null;
181
740
  }
182
741
  }
742
+ function readRegularFile(file) {
743
+ try {
744
+ const stat = fs.lstatSync(file);
745
+ if (!stat.isFile() || stat.isSymbolicLink())
746
+ return null;
747
+ return fs.readFileSync(file);
748
+ }
749
+ catch {
750
+ return null;
751
+ }
752
+ }
753
+ function validateFreshDate(value, blockerPrefix, now, blockers) {
754
+ const timestamp = typeof value === 'string' ? Date.parse(value) : Number.NaN;
755
+ if (!Number.isFinite(timestamp)) {
756
+ blockers.push(`${blockerPrefix}_invalid`);
757
+ return;
758
+ }
759
+ if (timestamp > now + MAX_PHYSICAL_RELEASE_FUTURE_SKEW_MS)
760
+ blockers.push(`${blockerPrefix}_future`);
761
+ if (now - timestamp > MAX_PHYSICAL_RELEASE_EVIDENCE_AGE_MS)
762
+ blockers.push(`${blockerPrefix}_stale`);
763
+ }
183
764
  function validDate(value) {
184
765
  return typeof value === 'string' && Number.isFinite(Date.parse(value));
185
766
  }
767
+ function sameStrings(value, expected) {
768
+ if (!Array.isArray(value) || value.length !== expected.length)
769
+ return false;
770
+ return JSON.stringify(value.map(String).sort()) === JSON.stringify([...expected].sort());
771
+ }
772
+ function containsSecretBytes(bytes) {
773
+ const text = bytes.toString('utf8');
774
+ return /(?:"|')?(?:authorization|api[_-]?key|access[_-]?token|refresh[_-]?token|password|cookie|secret_value|credential_value)(?:"|')?\s*[:=]/i.test(text)
775
+ || /\b(?:sk|or|sess|key)-[A-Za-z0-9_-]{8,}\b|Bearer\s+[A-Za-z0-9._~+/-]{8,}/i.test(text);
776
+ }
777
+ function sha256(bytes) {
778
+ return crypto.createHash('sha256').update(bytes).digest('hex');
779
+ }
780
+ function canonicalJson(value) {
781
+ if (Array.isArray(value))
782
+ return `[${value.map(canonicalJson).join(',')}]`;
783
+ if (value && typeof value === 'object') {
784
+ const record = value;
785
+ return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson(record[key])}`).join(',')}}`;
786
+ }
787
+ return JSON.stringify(value) ?? 'null';
788
+ }
186
789
  function gitHead(root) {
187
790
  const result = spawnSync('git', ['rev-parse', 'HEAD'], { cwd: root, encoding: 'utf8' });
188
791
  return result.status === 0 ? String(result.stdout || '').trim() || null : null;