carthagent 1.0.7 → 1.1.0-beta.1

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 (294) hide show
  1. package/README.md +140 -83
  2. package/bin/ctg.mjs +14 -732
  3. package/docs/beta-rewards-e2e-plan.md +48 -0
  4. package/docs/beta-rewards-e2e-results.md +84 -0
  5. package/docs/glyph-review/host-chrome-glyph-compatibility-v4.md +83 -0
  6. package/docs/glyph-review/test-isolation-v1.md +26 -0
  7. package/docs/glyph-review/typecheck-base.log +1 -0
  8. package/docs/glyph-review/typecheck-v4.log +1 -0
  9. package/docs/glyph-review/unicode-inventory-v4.csv +402 -0
  10. package/docs/help-dispatch-review.md +69 -0
  11. package/docs/launch-readiness-live-results.md +65 -0
  12. package/docs/legacy-cli.md +150 -0
  13. package/docs/live-theme-plugin-api.md +18 -0
  14. package/docs/native-cli.md +102 -0
  15. package/docs/native-compatibility.md +16 -0
  16. package/docs/native-daily-quests.md +132 -0
  17. package/docs/native-glyph-pty.md +59 -0
  18. package/docs/native-manual-testing.md +336 -0
  19. package/docs/native-slash-acceptance-handoff.md +49 -0
  20. package/docs/native-terminal-paired-failure-2026-10-17.md +40 -0
  21. package/docs/native-terminal-performance.md +310 -0
  22. package/docs/native-terminal-semantics-evidence/README.md +25 -0
  23. package/docs/native-terminal-semantics-evidence/controlled-host-model-selftest.json +16 -0
  24. package/docs/native-terminal-semantics-evidence/current-acceptance-status.json +59 -0
  25. package/docs/native-terminal-semantics-evidence/final-binary-focused-85bb44a.json +154 -0
  26. package/docs/native-terminal-semantics-evidence/final-binary-full-incomplete-85bb44a.json +160 -0
  27. package/docs/native-terminal-semantics-evidence/final-binary-layout-85bb44a.json +149 -0
  28. package/docs/native-terminal-semantics-evidence/final-binary-layout-interactive-85bb44a.json +159 -0
  29. package/docs/native-terminal-semantics-evidence/final-binary-model-picker-escape-unchanged-85bb44a.json +44 -0
  30. package/docs/native-terminal-semantics-evidence/final-binary-model-picker-exact-identity-85bb44a.json +47 -0
  31. package/docs/native-terminal-semantics-evidence/final-binary-model-picker-unchanged-model-incomplete-85bb44a.json +58 -0
  32. package/docs/native-terminal-semantics-evidence/final-binary-model-selftest.json +16 -0
  33. package/docs/native-terminal-semantics-evidence/final-binary-picker-cancel-incomplete-85bb44a.json +62 -0
  34. package/docs/native-terminal-semantics-evidence/focused-clipboard-title-stale-binary.json +161 -0
  35. package/docs/native-terminal-semantics-evidence/layout-stale-binary-observation-not-accepted.json +153 -0
  36. package/docs/native-terminal-semantics-evidence/picker-latest-incomplete-attempt.json +62 -0
  37. package/docs/native-terminal-semantics-evidence/picker-previous-probe-pass.json +61 -0
  38. package/docs/native-terminal-semantics.md +89 -0
  39. package/docs/native-upgrade.md +175 -0
  40. package/docs/opencode-composed-source-review.md +89 -0
  41. package/docs/opencode-migration.md +225 -9
  42. package/docs/opencode-owned-runtime.md +241 -0
  43. package/docs/opencode-runtime.md +12 -0
  44. package/docs/opencode-theme-provider-proof/README.md +55 -0
  45. package/docs/opencode-theme-provider-proof/live-theme-api-base.patch +122 -0
  46. package/docs/opencode-theme-provider-proof/plugin-theme-provider.patch +107 -0
  47. package/docs/opencode-theme-provider-proof/session-ui-base.patch +1217 -0
  48. package/docs/owned-host-controls-proof.md +39 -0
  49. package/docs/owned-runtime-release-manifest.md +42 -0
  50. package/docs/owned-visual-clear-proof.md +40 -0
  51. package/docs/patches/opencode-v2.0.32-glyph-spinner-test-isolation-v1.patch +50 -0
  52. package/docs/patches/opencode-v2.0.32-glyphs.patch +3670 -0
  53. package/docs/patches/opencode-v2.0.32-help-dispatch.patch +156 -0
  54. package/docs/patches/opencode-v2.0.32-live-theme-api.patch +122 -0
  55. package/docs/patches/opencode-v2.0.32-session-ui-combined-v1.patch +5272 -0
  56. package/docs/patches/opencode-v2.0.32-session-ui-combined-v2.patch +6052 -0
  57. package/docs/patches/opencode-v2.0.32-session-ui-combined-v3.patch +8657 -0
  58. package/docs/patches/opencode-v2.0.32-session-ui-combined-v4.patch +9928 -0
  59. package/docs/patches/opencode-v2.0.32-session-ui-combined-v5.patch +13965 -0
  60. package/docs/patches/opencode-v2.0.32-session-ui.patch +5137 -0
  61. package/docs/release-notes-beta.1.md +60 -0
  62. package/docs/release-notes-native-draft.md +73 -0
  63. package/docs/release-notes-preview.10.md +121 -0
  64. package/docs/release-notes-preview.11.md +76 -0
  65. package/docs/release-notes-preview.12.md +55 -0
  66. package/docs/release-notes-preview.13.md +68 -0
  67. package/docs/release-notes-preview.9.md +130 -0
  68. package/docs/source-preview.md +120 -0
  69. package/evaluate.mjs +28 -1
  70. package/lib/budget.mjs +1 -1
  71. package/lib/codegraph.mjs +1 -1
  72. package/lib/delivery-review.mjs +19 -6
  73. package/lib/delivery.mjs +3 -2
  74. package/lib/environment.mjs +8 -0
  75. package/lib/guidance.mjs +1 -1
  76. package/lib/opencode/cli-runtime.mjs +1171 -0
  77. package/lib/opencode/cli.mjs +252 -0
  78. package/lib/opencode/credentials.mjs +63 -8
  79. package/lib/opencode/demo.mjs +53 -0
  80. package/lib/opencode/evaluation-budget-plugin/index.ts +3 -0
  81. package/lib/opencode/evaluation.mjs +32 -12
  82. package/lib/opencode/headless.mjs +22 -5
  83. package/lib/opencode/provider-policy.mjs +23 -0
  84. package/lib/opencode/review.mjs +37 -16
  85. package/lib/opencode/runtime-artifact.mjs +335 -0
  86. package/lib/opencode/runtime-github-transport.mjs +121 -0
  87. package/lib/opencode/runtime-release.json +36 -0
  88. package/lib/opencode/runtime.mjs +299 -20
  89. package/lib/opencode/service.mjs +77 -35
  90. package/lib/opencode/session-options.mjs +6 -2
  91. package/lib/opencode/update.mjs +222 -0
  92. package/lib/opencode/workspace.mjs +1 -1
  93. package/lib/planning-advice.mjs +2 -2
  94. package/lib/providers/experiential.mjs +2 -2
  95. package/lib/tui/framing.mjs +1 -80
  96. package/lib/update.mjs +10 -1
  97. package/package.json +33 -7
  98. package/plugins/carthagent/README.md +23 -0
  99. package/plugins/carthagent/src/bootstrap.ts +1 -1
  100. package/plugins/carthagent/src/daily-evidence.mjs +57 -0
  101. package/plugins/carthagent/src/daily-quests-rpc.ts +50 -0
  102. package/plugins/carthagent/src/daily-source-rpc.ts +52 -0
  103. package/plugins/carthagent/src/index.ts +71 -24
  104. package/plugins/carthagent/src/rpc.ts +51 -6
  105. package/plugins/carthagent/src/server/codegraph.ts +12 -5
  106. package/plugins/carthagent/src/server/daily-quests-rpc.ts +277 -0
  107. package/plugins/carthagent/src/server/daily-source-rpc.ts +243 -0
  108. package/plugins/carthagent/src/server/delivery.ts +227 -112
  109. package/plugins/carthagent/src/server/framing.mjs +80 -0
  110. package/plugins/carthagent/src/server/policy.ts +1 -1
  111. package/plugins/carthagent/src/server/providers.ts +59 -31
  112. package/plugins/carthagent/src/server/session-controls.ts +3 -1
  113. package/plugins/carthagent/src/server/ship-agents.ts +77 -0
  114. package/plugins/carthagent/src/server/ship-context.mjs +60 -0
  115. package/plugins/carthagent/src/server/ship-guidance.ts +41 -0
  116. package/plugins/carthagent/src/server/ship-rpc.ts +69 -0
  117. package/plugins/carthagent/src/server/voyages-rpc.ts +280 -0
  118. package/plugins/carthagent/src/testing.ts +5 -0
  119. package/plugins/carthagent/src/tui.tsx +496 -0
  120. package/plugins/carthagent/src/ui/client.ts +127 -0
  121. package/plugins/carthagent/src/ui/commands.ts +70 -0
  122. package/plugins/carthagent/src/ui/daily-editor-focus.mjs +52 -0
  123. package/plugins/carthagent/src/ui/daily-editor.mjs +4 -0
  124. package/plugins/carthagent/src/ui/daily-quests-events.mjs +18 -0
  125. package/plugins/carthagent/src/ui/daily-quests-state.mjs +218 -0
  126. package/plugins/carthagent/src/ui/daily-quests.tsx +235 -0
  127. package/plugins/carthagent/src/ui/host-state.ts +42 -0
  128. package/plugins/carthagent/src/ui/model-command.ts +32 -0
  129. package/plugins/carthagent/src/ui/native-navigation.ts +23 -0
  130. package/plugins/carthagent/src/ui/native-review.d.mts +33 -0
  131. package/plugins/carthagent/src/ui/native-review.mjs +159 -0
  132. package/plugins/carthagent/src/ui/resume-session.mjs +34 -0
  133. package/plugins/carthagent/src/ui/select-mouse.ts +17 -0
  134. package/plugins/carthagent/src/ui/session-preferences.d.mts +7 -0
  135. package/plugins/carthagent/src/ui/session-preferences.mjs +14 -0
  136. package/plugins/carthagent/src/ui/theme-provision.d.mts +3 -0
  137. package/plugins/carthagent/src/ui/theme-provision.mjs +134 -0
  138. package/plugins/carthagent/src/ui/voyages-navigation-state.mjs +56 -0
  139. package/plugins/carthagent/src/ui/voyages-navigation.tsx +131 -0
  140. package/plugins/carthagent/src/ui/voyages-state.mjs +165 -0
  141. package/plugins/carthagent/src/ui/voyages.tsx +131 -0
  142. package/plugins/carthagent/src/ui/workspace-live.ts +61 -0
  143. package/plugins/carthagent/src/ui/workspace.tsx +379 -0
  144. package/plugins/carthagent/src/voyages-rpc.ts +64 -0
  145. package/plugins/carthagent/src/workspace-rpc.ts +36 -0
  146. package/plugins/carthagent/tests/commands.test.ts +107 -0
  147. package/plugins/carthagent/tests/session-preferences.test.mjs +41 -0
  148. package/plugins/carthagent/tests/voyages-navigation.test.tsx +269 -0
  149. package/plugins/carthagent/themes/carthage.json +67 -0
  150. package/plugins/carthagent/tui.ts +26 -1
  151. package/script/ctg/build-owned-runtime.sh +166 -0
  152. package/tests/budget-engine.test.mjs +4 -3
  153. package/tests/cli.test.mjs +52 -96
  154. package/tests/daily-evidence.test.mjs +62 -0
  155. package/tests/delivery-review.test.mjs +57 -2
  156. package/tests/delivery.test.mjs +22 -1
  157. package/tests/fixtures/native-discovery-plugin/server.ts +15 -0
  158. package/tests/generate-owned-runtime-release.test.mjs +354 -0
  159. package/tests/installed-runtime-contract.test.mjs +43 -0
  160. package/tests/native-cli-host-smoke.mjs +457 -0
  161. package/tests/native-daily-editor-focus.test.mjs +213 -0
  162. package/tests/native-daily-editor.test.mjs +43 -0
  163. package/tests/native-daily-quests-layout.test.mjs +55 -0
  164. package/tests/native-daily-quests-rpc.test.mjs +456 -0
  165. package/tests/native-daily-quests-ui.test.mjs +778 -0
  166. package/tests/native-daily-source.test.mjs +525 -0
  167. package/tests/native-demo-ui.test.mjs +48 -0
  168. package/tests/native-evaluation-admission.test.mjs +24 -0
  169. package/tests/native-fixture-browser-cleanup.test.mjs +65 -0
  170. package/tests/native-fixture-runtime.test.mjs +111 -0
  171. package/tests/native-fixture-stop-diagnostics.test.mjs +67 -0
  172. package/tests/native-glyph-pty.test.mjs +107 -0
  173. package/tests/native-manual-projects.test.mjs +170 -0
  174. package/tests/native-model-command.test.mjs +67 -0
  175. package/tests/native-owned-runtime-env.test.mjs +140 -0
  176. package/tests/native-paired-guards.test.mjs +464 -0
  177. package/tests/native-provider-policy.test.mjs +31 -0
  178. package/tests/native-pty-invocation.test.mjs +114 -0
  179. package/tests/native-pty-runtime.test.mjs +45 -0
  180. package/tests/native-resume-session.test.mjs +46 -0
  181. package/tests/native-review-consumer.test.mjs +286 -0
  182. package/tests/native-runtime-identity.test.mjs +214 -0
  183. package/tests/native-select-mouse.test.mjs +23 -0
  184. package/tests/native-ship-guidance.test.mjs +16 -0
  185. package/tests/native-ship-parity.test.mjs +526 -0
  186. package/tests/native-ship-plan.test.mjs +85 -0
  187. package/tests/native-ship-rpc.test.mjs +71 -0
  188. package/tests/native-ui-commands.test.mjs +6 -0
  189. package/tests/native-voyages-layout.test.mjs +28 -0
  190. package/tests/native-voyages-navigation.test.mjs +169 -0
  191. package/tests/native-voyages-rpc.test.mjs +397 -0
  192. package/tests/native-voyages-ui.test.mjs +307 -0
  193. package/tests/opencode-atomic-write.test.mjs +55 -0
  194. package/tests/opencode-cli.test.mjs +1312 -0
  195. package/tests/opencode-codegraph-plugin.test.mjs +47 -0
  196. package/tests/opencode-delivery.test.mjs +16 -0
  197. package/tests/opencode-providers.test.mjs +245 -18
  198. package/tests/opencode-runtime.test.mjs +597 -8
  199. package/tests/opencode-session-controls.test.mjs +15 -0
  200. package/tests/opencode-ship-rpc.test.mjs +71 -0
  201. package/tests/opencode-update.test.mjs +475 -0
  202. package/tests/opencode-workspace.test.mjs +51 -2
  203. package/tests/owned-runtime-publish-workflow.test.mjs +477 -0
  204. package/tests/release-workflow.test.mjs +46 -0
  205. package/tests/runtime-artifact.test.mjs +269 -0
  206. package/tests/runtime-development-trust.test.mjs +231 -0
  207. package/tests/runtime-github-transport.test.mjs +132 -0
  208. package/tests/runtime-token-env.test.mjs +23 -0
  209. package/tests/server.test.mjs +3 -1
  210. package/tests/ship-telemetry.test.mjs +1 -1
  211. package/tests/source-preview.test.mjs +253 -0
  212. package/tests/test_native_terminal_probe.py +76 -0
  213. package/tests/test_native_terminal_semantics.py +232 -0
  214. package/tests/update.test.mjs +42 -5
  215. package/tests/verify-draft-runtime-gate.test.mjs +280 -0
  216. package/tests/verify-owned-runtime-release.test.mjs +371 -0
  217. package/tests/workspace-live.test.mjs +127 -0
  218. package/tools/benchmark/diagnose-request.mjs +59 -36
  219. package/tools/benchmark/eval-common.mjs +3 -3
  220. package/tools/benchmark/evaluators.test.mjs +4 -4
  221. package/tools/benchmark/guard.test.mjs +35 -2
  222. package/tools/benchmark/guard.ts +20 -7
  223. package/tools/benchmark/planning-startup.test.mjs +30 -34
  224. package/tools/benchmark/runner.mjs +12 -7
  225. package/tools/benchmark/runner.test.mjs +23 -7
  226. package/tools/benchmark/upstream-error.test.mjs +16 -2
  227. package/tools/generate-owned-runtime-release.mjs +217 -0
  228. package/tools/installed-runtime-contract.mjs +16 -0
  229. package/tools/manual-runtime-manifest.mjs +39 -0
  230. package/tools/native-fixture-browser-cleanup.mjs +59 -0
  231. package/tools/native-fixture-stop-diagnostics.mjs +43 -0
  232. package/tools/native-fixture.mjs +199 -40
  233. package/tools/native-glyph-helpers.py +127 -0
  234. package/tools/native-glyph-pty.py +144 -0
  235. package/tools/native-manual-projects.mjs +259 -0
  236. package/tools/native-manual.mjs +338 -0
  237. package/tools/native-model-picker-acceptance.py +306 -0
  238. package/tools/native-owned-runtime-env.sh +47 -0
  239. package/tools/native-paired-guards.mjs +282 -0
  240. package/tools/native-paired-manifest.mjs +72 -0
  241. package/tools/native-paired-orchestration.sh +51 -0
  242. package/tools/native-plugin-readiness.mjs +33 -0
  243. package/tools/native-plugin-readiness.test.mjs +53 -0
  244. package/tools/native-pty-invocation.mjs +26 -0
  245. package/tools/native-pty-runtime.mjs +83 -0
  246. package/tools/native-pty-smoke.py +147 -24
  247. package/tools/native-public-repo-shell-result.mjs +13 -0
  248. package/tools/native-public-repo-shell-result.test.mjs +28 -0
  249. package/tools/native-runtime-identity.mjs +201 -0
  250. package/tools/native-slash-acceptance-env.py +38 -0
  251. package/tools/native-terminal-semantics.py +876 -0
  252. package/tools/owned-shell-observer/index.ts +16 -0
  253. package/tools/owned-shell-observer/package.json +1 -0
  254. package/tools/owned-visual-clear-env.py +48 -0
  255. package/tools/owned-visual-clear-model.py +21 -0
  256. package/tools/pinned-owned-runtime-inputs.mjs +46 -0
  257. package/tools/prepare-native-manual.mjs +122 -0
  258. package/tools/run-native-terminal-paired.sh +403 -0
  259. package/tools/runtime-token-env.mjs +8 -0
  260. package/tools/seed-native-switch-fixture.mjs +79 -0
  261. package/tools/test-native-controls.mjs +3 -1
  262. package/tools/test-native-daily-quests.mjs +65 -0
  263. package/tools/test-native-default-cli.mjs +106 -0
  264. package/tools/test-native-delivery-host.mjs +102 -20
  265. package/tools/test-native-evaluation.mjs +7 -3
  266. package/tools/test-native-glyph-pty.mjs +94 -0
  267. package/tools/test-native-host.mjs +87 -63
  268. package/tools/test-native-outcomes.mjs +3 -1
  269. package/tools/test-native-package.mjs +206 -38
  270. package/tools/test-native-performance.mjs +26 -0
  271. package/tools/test-native-product.mjs +90 -0
  272. package/tools/test-native-pty.mjs +34 -9
  273. package/tools/test-native-public-repo.mjs +224 -0
  274. package/tools/test-native-refresh-reentry.mjs +106 -0
  275. package/tools/test-native-report.mjs +27 -20
  276. package/tools/test-native-review.mjs +75 -0
  277. package/tools/test-native-runtime.mjs +120 -0
  278. package/tools/test-native-ship-models.mjs +176 -0
  279. package/tools/test-native-ship-walkthrough.mjs +96 -0
  280. package/tools/test-native-slash-commands.mjs +125 -0
  281. package/tools/test-native-slash-commands.py +242 -0
  282. package/tools/test-native-swe-host.mjs +224 -55
  283. package/tools/test-native-tasks.mjs +77 -0
  284. package/tools/test-native-terminal-performance.py +887 -0
  285. package/tools/test-native-terminal-readiness.sh +85 -0
  286. package/tools/test-native-terminal-semantics-installed.mjs +38 -0
  287. package/tools/test-native-visual-clear-installed.mjs +48 -0
  288. package/tools/test-owned-host-controls.mjs +238 -0
  289. package/tools/test-owned-opencode-runtime.mjs +35 -0
  290. package/tools/test-owned-visual-clear-env.py +120 -0
  291. package/tools/test-owned-visual-clear.py +373 -0
  292. package/tools/verify-draft-runtime-gate.mjs +138 -0
  293. package/tools/verify-owned-runtime-release.mjs +114 -0
  294. package/tools/verify-release-version.mjs +21 -1
@@ -0,0 +1,403 @@
1
+ #!/usr/bin/env bash
2
+ set -u -o pipefail
3
+
4
+ # Controlled-window paired diagnostic: baseline, CTG, CTG, baseline (ABBA).
5
+ # This script intentionally does not decide whether the host is quiet; the
6
+ # operator must schedule it after competing CI/build/evaluation work has ended.
7
+
8
+ repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
9
+ source "$repo_root/tools/native-paired-orchestration.sh"
10
+ source "$repo_root/tools/native-owned-runtime-env.sh"
11
+ binary="${CTG_OPENCODE_BIN:-/tmp/opencode/ctg-product-integration/node_modules/@opencode/cli/bin/opencode.exe}"
12
+ source_root="${CTG_SOURCE_ROOT:-/tmp/opencode/ctg-product-integration}"
13
+ source_commit="${CTG_SOURCE_COMMIT:-}"
14
+ samples="${SAMPLES:-30}"
15
+ output_root="${OUTPUT_DIR:-/tmp/opencode/ctg-native-terminal-paired-$(date -u +%Y%m%dT%H%M%SZ)}"
16
+ owned_root="${CTG_OWNED_OPENCODE_RUNTIME:-$(dirname "$(dirname "$(dirname "$binary")")")}"
17
+ release_manifest="${CTG_TEST_RUNTIME_RELEASE_MANIFEST:-$repo_root/lib/opencode/runtime-release.json}"
18
+
19
+ if [[ ! -x "$binary" ]]; then
20
+ printf 'Pinned executable is missing or not executable: %s\n' "$binary" >&2
21
+ exit 2
22
+ fi
23
+ if [[ -z "$source_commit" ]]; then
24
+ printf 'Set CTG_SOURCE_COMMIT to the reviewed immutable integration commit.\n' >&2
25
+ exit 2
26
+ fi
27
+ if [[ ! "$source_commit" =~ ^[0-9a-f]{40}$ ]]; then
28
+ printf 'CTG_SOURCE_COMMIT must be a full 40-character immutable commit SHA.\n' >&2
29
+ exit 2
30
+ fi
31
+ source_root="$(cd "$source_root" && pwd)"
32
+ source_head="$(git -C "$source_root" rev-parse HEAD)"
33
+ if ! node --input-type=module - "$repo_root/tools/native-paired-guards.mjs" "$source_commit" "$source_head" <<'NODE'
34
+ import { pathToFileURL } from 'node:url';
35
+ const { assertSelectedSourceHead } = await import(pathToFileURL(process.argv[2]).href);
36
+ assertSelectedSourceHead(process.argv[3], process.argv[4]);
37
+ NODE
38
+ then
39
+ printf 'Selected source commit is stale; refusing to stage or run fixtures.\n' >&2
40
+ exit 2
41
+ fi
42
+ if ! git -C "$source_root" cat-file -e "$source_commit^{commit}"; then
43
+ printf 'Selected immutable source commit is unavailable: %s\n' "$source_commit" >&2
44
+ exit 2
45
+ fi
46
+ if [[ ! -f "$release_manifest" ]]; then
47
+ printf 'Trusted CTG runtime release record is required: %s (or set CTG_TEST_RUNTIME_RELEASE_MANIFEST)\n' "$release_manifest" >&2
48
+ exit 2
49
+ fi
50
+ binary="$(realpath "$binary")"
51
+ owned_root="$(realpath "$owned_root")"
52
+ release_manifest="$(realpath "$release_manifest")"
53
+ binary_node_modules="$(realpath "$source_root/node_modules")"
54
+ if [[ ! -d "$binary_node_modules/@opencode/client" ]]; then
55
+ printf 'Selected source snapshot dependency install is missing @opencode/client: %s\n' "$binary_node_modules" >&2
56
+ exit 2
57
+ fi
58
+ source_lock_sha256="$(git -C "$source_root" show "$source_commit:package-lock.json" | sha256sum | awk '{print $1}')"
59
+ installed_lock_sha256="$(sha256sum "$source_root/package-lock.json" | awk '{print $1}')"
60
+ if [[ "$source_lock_sha256" != "$installed_lock_sha256" ]]; then
61
+ printf 'Selected source commit lockfile differs from the physical dependency install lockfile.\n' >&2
62
+ exit 2
63
+ fi
64
+ install_manifest="$(git -C "$source_root" show "$source_commit:plugins/carthagent/package.json")"
65
+ if ! grep -q '"@opencode/plugin": "2.0.32"' <<<"$install_manifest"; then
66
+ printf 'Selected integration source does not carry the expected native plugin dependency.\n' >&2
67
+ exit 2
68
+ fi
69
+ if [[ "$samples" -lt 30 ]]; then
70
+ printf 'SAMPLES must be at least 30 (got %s)\n' "$samples" >&2
71
+ exit 2
72
+ fi
73
+ umask 077
74
+ mkdir -p "$output_root"
75
+ output_root="$(cd "$output_root" && pwd)"
76
+ if [[ -n "$(find "$output_root" -mindepth 1 -maxdepth 1 -print -quit)" ]]; then
77
+ printf 'Output directory must be empty and unique: %s\n' "$output_root" >&2
78
+ exit 2
79
+ fi
80
+ chmod 700 "$output_root"
81
+ set -e
82
+
83
+ work=''
84
+ statuses=''
85
+ active_fixture=''
86
+ active_pid=''
87
+ active_pid_label=''
88
+ active_run_pid=''
89
+ interrupted=0
90
+ cleanup() {
91
+ if [[ -n "$active_run_pid" ]]; then
92
+ if [[ -n "$active_pid" ]]; then
93
+ printf 'Interrupted condition %s; retaining artifacts and marking failed.\n' "$active_pid" >> "$output_root/interrupt.log"
94
+ interrupted=1
95
+ if [[ -n "$statuses" ]]; then printf 'interrupted\t1\n' >> "$statuses"; fi
96
+ fi
97
+ kill -TERM "$active_run_pid" 2>/dev/null || true
98
+ kill -TERM -- "-$active_run_pid" 2>/dev/null || true
99
+ run_group="$(ps -o pgid= -p "$active_run_pid" | tr -d ' ')"
100
+ if [[ -n "$run_group" ]]; then pkill -TERM -g "$run_group" 2>/dev/null || true; fi
101
+ for _ in $(seq 1 50); do
102
+ kill -0 "$active_run_pid" 2>/dev/null || break
103
+ sleep 0.1
104
+ done
105
+ kill -KILL "$active_run_pid" 2>/dev/null || true
106
+ if [[ -n "${run_group:-}" ]]; then pkill -KILL -g "$run_group" 2>/dev/null || true; fi
107
+ wait "$active_run_pid" 2>/dev/null || true
108
+ active_run_pid=''
109
+ fi
110
+ if [[ -n "$active_pid" ]]; then
111
+ label_for_failure="${active_pid_label:-unknown}"
112
+ printf '%s\t1\t%s\n' "$label_for_failure" "$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> "$output_root/order.log"
113
+ if [[ -n "$statuses" ]]; then printf '%s\t1\n' "$label_for_failure" >> "$statuses"; fi
114
+ interrupted=1
115
+ kill -TERM -- "-$active_pid" 2>/dev/null || kill -TERM "$active_pid" 2>/dev/null || true
116
+ for _ in $(seq 1 50); do
117
+ kill -0 "$active_pid" 2>/dev/null || break
118
+ sleep 0.1
119
+ done
120
+ kill -KILL -- "-$active_pid" 2>/dev/null || kill -KILL "$active_pid" 2>/dev/null || true
121
+ wait "$active_pid" 2>/dev/null || true
122
+ active_pid=''
123
+ active_pid_label=''
124
+ fi
125
+ if [[ -n "$active_fixture" && -f "$active_fixture" ]]; then
126
+ temp_path="$(node -e 'process.stdout.write(JSON.parse(require("node:fs").readFileSync(process.argv[1],"utf8")).temp)' "$active_fixture")"
127
+ if [[ "$temp_path" == /tmp/ctg-native-fixture-* && "$temp_path" != *..* ]]; then rm -rf -- "$temp_path"; fi
128
+ fi
129
+ if [[ -n "$work" ]]; then remove_paired_workdir "$work"; fi
130
+ }
131
+ on_int() { interrupted=1; exit 130; }
132
+ on_term() { interrupted=1; exit 143; }
133
+ register_paired_exit_cleanup cleanup
134
+ trap on_int INT
135
+ trap on_term TERM
136
+
137
+ work="$(mktemp -d /tmp/ctg-native-paired.XXXXXX)"
138
+ statuses="$work/condition-status.tsv"
139
+ runner="$work/runner"
140
+ mkdir -p "$runner/source"
141
+ source_archive="$output_root/source-$source_commit.tar"
142
+ git -C "$source_root" archive --format=tar "$source_commit" > "$source_archive"
143
+ source_archive_sha256="$(sha256sum "$source_archive" | awk '{print $1}')"
144
+ tar -xf "$source_archive" -C "$runner/source"
145
+ ln -s "$binary_node_modules" "$runner/source/node_modules"
146
+ mkdir -p "$runner/source/plugins/carthagent/node_modules/@opencode"
147
+ ln -s "$binary_node_modules/@opencode/plugin" "$runner/source/plugins/carthagent/node_modules/@opencode/plugin"
148
+ cp "$repo_root/tools/native-fixture.mjs" "$repo_root/tools/seed-native-switch-fixture.mjs" \
149
+ "$repo_root/tools/native-paired-guards.mjs" "$repo_root/tools/native-paired-manifest.mjs" \
150
+ "$repo_root/tools/native-runtime-identity.mjs" \
151
+ "$repo_root/tools/native-owned-runtime-env.sh" \
152
+ "$repo_root/tools/native-paired-orchestration.sh" "$repo_root/tools/test-native-terminal-performance.py" \
153
+ "$repo_root/tools/test-native-terminal-readiness.sh" "$runner/source/tools/"
154
+ mkdir -p "$runner/source/docs"
155
+ cp "$repo_root/docs/native-terminal-paired-failure-2026-10-17.md" "$runner/source/docs/"
156
+ execution_archive="$output_root/execution-snapshot.tar"
157
+ tar -C "$runner/source" --exclude=node_modules --exclude='*/node_modules' --exclude='**/node_modules' -cf "$execution_archive" .
158
+ source_snapshot_sha256="$(sha256sum "$execution_archive" | awk '{print $1}')"
159
+ binary_sha256="$(sha256sum "$binary" | awk '{print $1}')"
160
+ source_tree="$(git -C "$source_root" rev-parse "$source_commit^{tree}")"
161
+ runtime_identity_json="$(node --input-type=module - "$repo_root/tools/native-runtime-identity.mjs" "$binary" "$owned_root" "$release_manifest" <<'NODE'
162
+ import { pathToFileURL } from 'node:url';
163
+ const { loadNativeTestRuntime } = await import(pathToFileURL(process.argv[2]).href);
164
+ const { readFile } = await import('node:fs/promises');
165
+ const env = process.env.CTG_TEST_RUNTIME_SHA256
166
+ ? { ...process.env, CTG_TEST_RUNTIME_SHA256: process.env.CTG_TEST_RUNTIME_SHA256 }
167
+ : process.env;
168
+ const identity = await loadNativeTestRuntime({
169
+ binary: process.argv[3], ownedRoot: process.argv[4],
170
+ expectedManifest: JSON.parse(await readFile(process.argv[5], 'utf8')),
171
+ env,
172
+ });
173
+ process.stdout.write(JSON.stringify(identity));
174
+ NODE
175
+ )"
176
+ runtime_version="$(node -e 'process.stdout.write(JSON.parse(process.argv[1]).runtimeVersion)' "$runtime_identity_json")"
177
+ runtime_source_revision="$(node -e 'process.stdout.write(JSON.parse(process.argv[1]).sourceRevision ?? "")' "$runtime_identity_json")"
178
+ runtime_patch_sha256="$(node -e 'process.stdout.write(JSON.parse(process.argv[1]).patchSha256 ?? "")' "$runtime_identity_json")"
179
+ runtime_lock_sha256="$(node -e 'process.stdout.write(JSON.parse(process.argv[1]).lockSha256 ?? "")' "$runtime_identity_json")"
180
+ binary_sha256="$(node -e 'process.stdout.write(JSON.parse(process.argv[1]).binarySha256)' "$runtime_identity_json")"
181
+ binary_version=2.0.32
182
+ source_component_sha256="$(cd "$runner/source" && find lib plugins/carthagent -type f -print0 | sort -z | xargs -0 sha256sum | sha256sum | awk '{print $1}')"
183
+ harness_component_sha256="$(cd "$runner/source" && sha256sum tools/native-fixture.mjs tools/native-runtime-identity.mjs tools/native-owned-runtime-env.sh tools/seed-native-switch-fixture.mjs tools/native-paired-guards.mjs tools/native-paired-manifest.mjs tools/native-paired-orchestration.sh tools/test-native-terminal-performance.py tools/test-native-terminal-readiness.sh docs/native-terminal-paired-failure-2026-10-17.md | sha256sum | awk '{print $1}')"
184
+ git -C "$source_root" status --porcelain=v1 > "$work/source-checkout-dirty-status"
185
+ git -C "$repo_root" status --porcelain=v1 > "$work/harness-dirty-status"
186
+ node --input-type=module - "$runner/source/tools/native-paired-guards.mjs" "$binary_node_modules" "$runner/source/package.json" "$runner/source" > "$work/dependency-packages.json" <<'NODE'
187
+ import { pathToFileURL } from 'node:url';
188
+ const { hashDependencyPackages } = await import(pathToFileURL(process.argv[2]).href);
189
+ process.stdout.write(JSON.stringify(hashDependencyPackages(process.argv[3], process.argv[4], process.argv[5])));
190
+ NODE
191
+ dependency_packages_json="$(cat "$work/dependency-packages.json")"
192
+ dependency_install_json="$(node --input-type=module - "$runner/source/tools/native-paired-guards.mjs" "$binary_node_modules" <<'NODE'
193
+ import { pathToFileURL } from 'node:url';
194
+ const { hashDependencyInstall } = await import(pathToFileURL(process.argv[2]).href);
195
+ process.stdout.write(JSON.stringify(hashDependencyInstall(process.argv[3])));
196
+ NODE
197
+ )"
198
+ dependency_install_sha256="$(node -e 'process.stdout.write(JSON.parse(process.argv[1]).treeSha256)' "$dependency_install_json")"
199
+ dependency_install_realpath="$(node -e 'process.stdout.write(JSON.parse(process.argv[1]).realpath)' "$dependency_install_json")"
200
+
201
+ configure_native_owned_runtime "$binary" "$owned_root" "$release_manifest"
202
+ export CTG_PERF_SOURCE_COMMIT="$source_commit"
203
+ export CTG_PERF_SOURCE_DIR="$runner/source"
204
+ export CTG_PERF_SOURCE_SNAPSHOT_SHA256="$source_snapshot_sha256"
205
+ export CTG_PERF_EXPECT_PLUGIN=''
206
+ export CTG_PERF_MANIFEST="$output_root/manifest.json"
207
+ export NODE_PATH="$binary_node_modules"
208
+ export CTG_PERF_DEPENDENCY_INSTALL_SHA256="$dependency_install_sha256"
209
+ wait_ready() {
210
+ local file="$1" pid="$2" label="$3" log="$4"
211
+ for _ in $(seq 1 300); do
212
+ [[ -s "$file" ]] && return 0
213
+ if ! kill -0 "$pid" 2>/dev/null; then
214
+ printf '%s fixture seeder exited; see %s\n' "$label" "$log" >&2
215
+ return 1
216
+ fi
217
+ sleep 0.1
218
+ done
219
+ printf 'Timed out waiting for %s fixture; seeder log: %s\n' "$label" "$log" >&2
220
+ return 1
221
+ }
222
+
223
+ run_one() {
224
+ local label="$1" mode="$2" status ready log
225
+ active_pid_label="$label"
226
+ ready="$work/$label-ready"
227
+ log="$output_root/$label-seeder.log"
228
+ local seed_args=("$runner/source/tools/seed-native-switch-fixture.mjs" "$runner/source/plugins/carthagent" --serve "--ready-file=$ready")
229
+ if [[ "$mode" == "baseline" ]]; then
230
+ seed_args+=(--no-plugin)
231
+ elif [[ "$mode" != "ctg" ]]; then
232
+ printf 'Unknown condition: %s\n' "$mode" >&2
233
+ return 2
234
+ fi
235
+ printf 'Seeding %s at %s\n' "$label" "$(date -u +%Y-%m-%dT%H:%M:%SZ)" | tee -a "$output_root/order.log"
236
+ CTG_PERF_MANIFEST="$output_root/manifest.json" setsid node "${seed_args[@]}" >"$log" 2>&1 &
237
+ active_pid=$!
238
+ if ! wait_ready "$ready" "$active_pid" "$label" "$log"; then
239
+ return 1
240
+ fi
241
+ active_fixture="$(cat "$ready")"
242
+ active_temp="$(node -e 'process.stdout.write(JSON.parse(require("node:fs").readFileSync(process.argv[1],"utf8")).temp)' "$active_fixture")" || return 1
243
+ if [[ "$active_temp" != /tmp/ctg-native-fixture-* || "$active_temp" == *..* ]]; then
244
+ printf 'Refusing to clean a fixture outside the native disposable-temp prefix: %s\n' "$active_temp" >&2
245
+ return 1
246
+ fi
247
+ if ! python3 -c 'import json,sys; f=json.load(open(sys.argv[1])); assert f["roots"]["alpha"] == f["roots"]["bravo"]; assert f["sessions"]["alpha"]["directory"] == f["sessions"]["bravo"]["directory"]; assert f["sessions"]["alpha"]["id"] != f["sessions"]["bravo"]["id"]; assert f["sessions"]["alpha"]["marker"] != f["sessions"]["bravo"]["marker"]' "$active_fixture"; then
248
+ printf 'Paired sessions are not equivalent same-project destinations.\n' >&2
249
+ return 1
250
+ fi
251
+ if ! node --input-type=module - "$active_fixture" "$output_root/$label-fixture.json" "$output_root/manifest.json" "$runner/source/tools/native-paired-guards.mjs" "$mode" <<'NODE'
252
+ import { readFileSync, writeFileSync } from 'node:fs';
253
+ import { pathToFileURL } from 'node:url';
254
+ const { verifySnapshotIdentity, assertPluginCondition } = await import(pathToFileURL(process.argv[5]).href);
255
+ const f = JSON.parse(readFileSync(process.argv[2], 'utf8'));
256
+ const manifest = JSON.parse(readFileSync(process.argv[4], 'utf8'));
257
+ verifySnapshotIdentity(manifest, f.provenance, { verifyArtifactFiles: true });
258
+ const expectedActive = process.argv[6] === 'ctg';
259
+ if (expectedActive && (f.pluginHealth?.hostVersion !== '2.0.32' || f.pluginHealth?.protocol !== 1 || f.pluginHealth?.directoryMatches !== true)) {
260
+ throw new Error('CTG plugin health RPC was not validated for this fixture');
261
+ }
262
+ const checked = assertPluginCondition(f.pluginInventory, expectedActive);
263
+ if (checked.observed !== f.pluginObservation?.observed) throw new Error('Plugin state changed while producing fixture metadata');
264
+ const safe = { roots: f.roots, sessions: f.sessions, pluginObservation: f.pluginObservation,
265
+ pluginInventory: f.pluginInventory, pluginHealth: f.pluginHealth, provenance: f.provenance };
266
+ writeFileSync(process.argv[3], JSON.stringify(safe, null, 2) + '\n', { mode: 0o600 });
267
+ NODE
268
+ then
269
+ return 1
270
+ fi
271
+ printf 'Starting %s measurement at %s\n' "$label" "$(date -u +%Y-%m-%dT%H:%M:%SZ)" | tee -a "$output_root/order.log"
272
+ if ! setsid -w true; then
273
+ printf 'setsid is required to guarantee interrupt cleanup of the measurement process tree.\n' >&2
274
+ return 1
275
+ fi
276
+ set +e
277
+ CTG_PERF_EXPECT_PLUGIN="$([[ "$mode" == ctg ]] && printf active || printf absent)" setsid python3 "$runner/source/tools/test-native-terminal-performance.py" \
278
+ --binary "$binary" --switch-fixture "$active_fixture" --samples "$samples" \
279
+ --host-sample-interval 1 --idle-seconds 10 \
280
+ --report "$output_root/$label.json" \
281
+ >"$output_root/$label.stdout.log" 2>"$output_root/$label.stderr.log" &
282
+ active_run_pid=$!
283
+ wait "$active_run_pid"
284
+ status=$?
285
+ active_run_pid=''
286
+ set -e
287
+ printf '%s\t%s\t%s\n' "$label" "$status" "$(date -u +%Y-%m-%dT%H:%M:%SZ)" >>"$output_root/order.log"
288
+ kill -TERM "$active_pid" 2>/dev/null || true
289
+ kill -TERM -- "-$active_pid" 2>/dev/null || true
290
+ for _ in $(seq 1 50); do
291
+ kill -0 "$active_pid" 2>/dev/null || break
292
+ sleep 0.1
293
+ done
294
+ kill -KILL -- "-$active_pid" 2>/dev/null || kill -KILL "$active_pid" 2>/dev/null || true
295
+ wait "$active_pid" 2>/dev/null || true
296
+ active_pid=''
297
+ active_pid_label=''
298
+ temp_path="$(node -e 'process.stdout.write(JSON.parse(require("node:fs").readFileSync(process.argv[1],"utf8")).temp)' "$active_fixture")"
299
+ if [[ "$temp_path" == /tmp/ctg-native-fixture-* && "$temp_path" != *..* ]]; then
300
+ rm -rf -- "$temp_path" || status=1
301
+ else
302
+ printf 'Unsafe fixture cleanup path %s\n' "$temp_path" >&2
303
+ status=1
304
+ fi
305
+ active_fixture=''
306
+ printf '%s\t%s\n' "$label" "$status" >> "$statuses"
307
+ return 0
308
+ }
309
+
310
+ run_condition() {
311
+ local label="$1" mode="$2" before_count log
312
+ log="$output_root/$label-seeder.log"
313
+ before_count=0
314
+ [[ -f "$statuses" ]] && before_count="$(wc -l < "$statuses")"
315
+ set +e
316
+ run_one "$label" "$mode"
317
+ local condition_status=$?
318
+ set -e
319
+ if [[ -f "$statuses" ]] && [[ "$(wc -l < "$statuses")" -gt "$before_count" ]]; then return 0; fi
320
+ if [[ "$condition_status" -eq 0 ]]; then
321
+ printf '%s\t1\n' "$label" >> "$statuses"
322
+ return 0
323
+ fi
324
+ printf '%s\t1\t%s\n' "$label" "$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> "$output_root/order.log"
325
+ printf '%s\t1\n' "$label" >> "$statuses"
326
+ if [[ -f "$log" ]]; then cat "$log" >> "$output_root/$label.stderr.log"; fi
327
+ printf 'Condition %s seeder or activation failed; logs retained.\n' "$label" >> "$output_root/$label.stderr.log"
328
+ if [[ -n "$active_pid" ]]; then
329
+ kill -TERM "$active_pid" 2>/dev/null || true
330
+ kill -TERM -- "-$active_pid" 2>/dev/null || true
331
+ for _ in $(seq 1 50); do
332
+ kill -0 "$active_pid" 2>/dev/null || break
333
+ sleep 0.1
334
+ done
335
+ kill -KILL -- "-$active_pid" 2>/dev/null || kill -KILL "$active_pid" 2>/dev/null || true
336
+ wait "$active_pid" 2>/dev/null || true
337
+ active_pid=''
338
+ active_pid_label=''
339
+ fi
340
+ if [[ -z "$active_fixture" ]]; then
341
+ ready_path="$work/$label-ready"
342
+ if [[ -s "$ready_path" ]]; then active_fixture="$(cat "$ready_path")"; fi
343
+ fi
344
+ if [[ -n "$active_fixture" && -f "$active_fixture" ]]; then
345
+ temp_path="$(node -e 'process.stdout.write(JSON.parse(require("node:fs").readFileSync(process.argv[1],"utf8")).temp)' "$active_fixture")" || temp_path=''
346
+ if [[ "$temp_path" == /tmp/ctg-native-fixture-* ]]; then rm -rf -- "$temp_path"; fi
347
+ active_fixture=''
348
+ fi
349
+ printf 'Condition %s failed before producing a complete run; continuing ABBA.\n' "$label" | tee -a "$output_root/$label.stderr.log"
350
+ return 0
351
+ }
352
+
353
+ SOURCE_ROOT="$source_root" SOURCE_HEAD="$source_head" SOURCE_COMMIT="$source_commit" \
354
+ SOURCE_TREE="$source_tree" SOURCE_ARCHIVE="$source_archive" SOURCE_ARCHIVE_SHA256="$source_archive_sha256" \
355
+ EXECUTION_ARCHIVE="$execution_archive" EXECUTION_SHA256="$source_snapshot_sha256" \
356
+ SOURCE_SNAPSHOT_SHA256="$source_snapshot_sha256" SOURCE_LOCK_SHA256="$source_lock_sha256" \
357
+ INSTALLED_LOCK_SHA256="$installed_lock_sha256" DEPENDENCY_ROOT="$binary_node_modules" \
358
+ DEPENDENCY_SHA256="$dependency_install_sha256" DEPENDENCY_REALPATH="$dependency_install_realpath" \
359
+ DEPENDENCY_PACKAGES_FILE="$work/dependency-packages.json" SOURCE_STATUS_FILE="$work/source-checkout-dirty-status" \
360
+ HARNESS_STATUS_FILE="$work/harness-dirty-status" HARNESS_WORKTREE="$repo_root" \
361
+ HARNESS_COMMIT="$(git -C "$repo_root" rev-parse HEAD)" HARNESS_MANIFEST_SHA256="$harness_component_sha256" \
362
+ BINARY_PATH="$binary" BINARY_VERSION="$binary_version" RUNTIME_VERSION="$runtime_version" BINARY_SHA256="$binary_sha256" \
363
+ RUNTIME_SOURCE_REVISION="$runtime_source_revision" RUNTIME_PATCH_SHA256="$runtime_patch_sha256" RUNTIME_LOCK_SHA256="$runtime_lock_sha256" \
364
+ SAMPLES_PER_RUN="$samples" SOURCE_COMPONENT_SHA256="$source_component_sha256" \
365
+ node --input-type=module - <<'NODE' | node "$runner/source/tools/native-paired-manifest.mjs" > "$output_root/manifest.json"
366
+ import { readFileSync } from 'node:fs';
367
+ const read = key => process.env[key];
368
+ const fields = {
369
+ createdAt: new Date().toISOString(),
370
+ binary: read('BINARY_PATH'), binaryVersion: read('BINARY_VERSION'), compatibilityVersion: read('BINARY_VERSION'),
371
+ runtimeVersion: read('RUNTIME_VERSION'), binarySha256: read('BINARY_SHA256'),
372
+ runtimeSourceRevision: read('RUNTIME_SOURCE_REVISION'), runtimePatchSha256: read('RUNTIME_PATCH_SHA256'),
373
+ runtimeLockSha256: read('RUNTIME_LOCK_SHA256'),
374
+ sourceCheckout: read('SOURCE_ROOT'), sourceCheckoutHead: read('SOURCE_HEAD'),
375
+ sourceCheckoutDirtyStatus: readFileSync(read('SOURCE_STATUS_FILE'), 'utf8'),
376
+ sourceCommit: read('SOURCE_COMMIT'), sourceTree: read('SOURCE_TREE'),
377
+ sourceSnapshotTar: read('SOURCE_ARCHIVE'), sourceArchiveSha256: read('SOURCE_ARCHIVE_SHA256'),
378
+ executionSnapshotTar: read('EXECUTION_ARCHIVE'), executionSnapshotSha256: read('EXECUTION_SHA256'),
379
+ sourceSnapshotSha256: read('SOURCE_SNAPSHOT_SHA256'), packageLockSha256: read('SOURCE_LOCK_SHA256'),
380
+ installedPackageLockSha256: read('INSTALLED_LOCK_SHA256'),
381
+ dependencyNodeModulesRealpath: read('DEPENDENCY_ROOT'), dependencyInstallSha256: read('DEPENDENCY_SHA256'),
382
+ dependencyInstallRealpath: read('DEPENDENCY_REALPATH'),
383
+ dependencyPackages: JSON.parse(readFileSync(read('DEPENDENCY_PACKAGES_FILE'), 'utf8')),
384
+ harnessWorktree: read('HARNESS_WORKTREE'), harnessCommit: read('HARNESS_COMMIT'),
385
+ harnessDirtyStatus: readFileSync(read('HARNESS_STATUS_FILE'), 'utf8'),
386
+ harnessSourceManifestSha256: read('HARNESS_MANIFEST_SHA256'), samplesPerRun: Number(read('SAMPLES_PER_RUN')),
387
+ sourceComponentSha256: read('SOURCE_COMPONENT_SHA256'), harnessComponentSha256: read('HARNESS_MANIFEST_SHA256'),
388
+ };
389
+ process.stdout.write(JSON.stringify(fields));
390
+ NODE
391
+
392
+ node --input-type=module - "$output_root/manifest.json" "$runner/source/tools/native-paired-guards.mjs" <<'NODE'
393
+ import { readFileSync } from 'node:fs';
394
+ import { pathToFileURL } from 'node:url';
395
+ const { assertSnapshotProvenance } = await import(pathToFileURL(process.argv[3]).href);
396
+ const manifest = JSON.parse(readFileSync(process.argv[2], 'utf8'));
397
+ assertSnapshotProvenance(manifest, { verifyArtifactFiles: true });
398
+ NODE
399
+
400
+ run_paired_abba run_condition "$statuses" "$runner/source/tools/native-paired-guards.mjs" "$interrupted"
401
+ exit_status="$paired_exit_status"
402
+ printf 'Paired diagnostic artifacts: %s (aggregate exit %s)\n' "$output_root" "$exit_status"
403
+ exit "$exit_status"
@@ -0,0 +1,8 @@
1
+ /** Return a child environment without the optional runtime-download credential. */
2
+ export function withoutRuntimeGithubToken(env) {
3
+ const sanitized = { ...env };
4
+ for (const key of Object.keys(sanitized)) {
5
+ if (key.toUpperCase() === 'CTG_RUNTIME_GITHUB_TOKEN') delete sanitized[key];
6
+ }
7
+ return sanitized;
8
+ }
@@ -0,0 +1,79 @@
1
+ #!/usr/bin/env node
2
+ /** Prepare two rendered native sessions in an isolated pinned-host database. */
3
+ import { mkdir, readFile, writeFile } from 'node:fs/promises';
4
+ import { join } from 'node:path';
5
+ import { nativeFixture, streamText } from './native-fixture.mjs';
6
+
7
+ const plugin = process.argv[2];
8
+ if (!plugin) throw new Error('Usage: seed-native-switch-fixture.mjs /absolute/path/to/snapshot/plugins/carthagent');
9
+ const keepServer = process.argv.includes('--serve');
10
+ const noPlugin = process.argv.includes('--no-plugin');
11
+ const readyFile = process.argv.find(value => value.startsWith('--ready-file='))?.slice('--ready-file='.length);
12
+ const manifestPath = process.env.CTG_PERF_MANIFEST;
13
+ if (!manifestPath) throw new Error('CTG_PERF_MANIFEST must identify the paired-run provenance manifest');
14
+ const provenance = JSON.parse(await readFile(manifestPath, 'utf8'));
15
+ const host = await nativeFixture({
16
+ plugin: noPlugin ? null : plugin,
17
+ verifyInventory: true,
18
+ pluginExpected: noPlugin ? 'absent' : 'active',
19
+ provenance,
20
+ preserveTemp: true,
21
+ respond(body, response) {
22
+ const serialized = JSON.stringify(body);
23
+ const marker = serialized.includes('UNIQUE_RENDERED_DESTINATION_BRAVO_7C91')
24
+ ? 'UNIQUE_RENDERED_DESTINATION_BRAVO_7C91'
25
+ : 'UNIQUE_RENDERED_DESTINATION_ALPHA_7C91';
26
+ streamText(response, marker);
27
+ },
28
+ });
29
+ let fixtureReady = false;
30
+ try {
31
+ // Keep both sessions in one selected project; Open Sessions exposes native
32
+ // session rows within the active project workspace.
33
+ const roots = { alpha: join(host.temp, 'project-alpha'), bravo: join(host.temp, 'project-alpha') };
34
+ await Promise.all(Object.values(roots).map(path => mkdir(path, { recursive: true })));
35
+ const fixtures = {};
36
+ for (const [name, title, marker] of [
37
+ ['alpha', 'PERF SESSION ALPHA', 'UNIQUE_RENDERED_DESTINATION_ALPHA_7C91'],
38
+ ['bravo', 'PERF SESSION BRAVO', 'UNIQUE_RENDERED_DESTINATION_BRAVO_7C91'],
39
+ ]) {
40
+ const location = { directory: roots[name] };
41
+ const session = await host.client.session.create({ location, model: host.model, title }, host.request());
42
+ await host.client.session.prompt({ sessionID: session.id, text: `Reply with exactly this fixture marker and nothing else: ${marker}` }, host.request());
43
+ await host.client.session.wait({ sessionID: session.id }, host.request());
44
+ const messages = await host.client.session.context({ sessionID: session.id }, host.request());
45
+ if (!messages.some(message => message.type === 'assistant' &&
46
+ message.content?.some(part => part.type === 'text' && part.text.includes(marker)))) {
47
+ throw new Error(`Session ${name} did not persist unique assistant marker ${marker}`);
48
+ }
49
+ fixtures[name] = { id: session.id, title, marker, directory: roots[name] };
50
+ }
51
+ const readinessSession = await host.client.session.create({
52
+ location: { directory: roots.alpha }, model: host.model, title: 'PERF READINESS EMPTY SESSION',
53
+ }, host.request());
54
+ const fixture = { temp: host.temp, roots, sessions: fixtures,
55
+ readinessSession: { id: readinessSession.id, directory: roots.alpha },
56
+ runtimeIdentity: host.runtimeIdentity,
57
+ serverPid: host.serverPid(),
58
+ pluginObservation: host.pluginObservation, pluginInventory: host.pluginInventory,
59
+ pluginHealth: host.pluginHealth, provenance };
60
+ fixture.baseUrl = host.baseUrl;
61
+ fixture.password = host.password;
62
+ const agentDir = join(host.temp, 'home/.carthagent/agent');
63
+ await mkdir(agentDir, { recursive: true });
64
+ await writeFile(join(agentDir, 'keybindings.json'), JSON.stringify({ 'open.menu': 'ctrl+o' }));
65
+ const path = join(host.temp, 'native-switch-fixture.json');
66
+ await writeFile(path, JSON.stringify(fixture, null, 2));
67
+ // The fixture file contains the local server password; keep it out of retained logs.
68
+ console.log(JSON.stringify({ path }));
69
+ if (readyFile) await writeFile(readyFile, path);
70
+ fixtureReady = true;
71
+ if (keepServer) {
72
+ await new Promise(resolve => {
73
+ process.once('SIGINT', resolve);
74
+ process.once('SIGTERM', resolve);
75
+ });
76
+ }
77
+ } finally {
78
+ await host.close({ preserveTemp: fixtureReady });
79
+ }
@@ -6,6 +6,7 @@ import { join } from 'node:path';
6
6
  import { nativeFixture } from './native-fixture.mjs';
7
7
 
8
8
  const host = await nativeFixture({ plugin: process.argv[2] });
9
+ const identity = host.runtimeIdentity;
9
10
  try {
10
11
  let { client } = host;
11
12
  const { location, request, model, requests } = host;
@@ -74,7 +75,8 @@ try {
74
75
  assert.equal(afterA.at(-1).outcome, 'succeeded');
75
76
  assert.equal(afterB.at(-1).outcome, 'succeeded');
76
77
  assert.deepEqual(host.providerErrors, []);
77
- console.log(JSON.stringify({ host: '2.0.32', sessions: 2, providerRequests: requests.length,
78
+ console.log(JSON.stringify({ host: identity.runtimeVersion, compatibilityVersion: identity.compatibilityVersion,
79
+ ...(identity.binarySha256 ? { binarySha256: identity.binarySha256 } : {}), sessions: 2, providerRequests: requests.length,
78
80
  framing: 'passed', raw: 'passed', compaction: 'passed', locationOwnership: 'passed', serviceRestart: 'passed' }));
79
81
  } catch (error) {
80
82
  console.error(host.output);
@@ -0,0 +1,65 @@
1
+ #!/usr/bin/env node
2
+ /** Real native RPC registration and source projection, with no cloud submission. */
3
+ import assert from 'node:assert/strict';
4
+ import { randomUUID } from 'node:crypto';
5
+ import { writeFile } from 'node:fs/promises';
6
+ import { join } from 'node:path';
7
+ import { checkDigest, fingerprint } from '../lib/delivery.mjs';
8
+ import { createReport } from '../lib/reports.mjs';
9
+ import { selectReport } from '../lib/workspace.mjs';
10
+ import { nativeFixture } from './native-fixture.mjs';
11
+
12
+ const host = await nativeFixture({ plugin: process.argv[2] });
13
+ try {
14
+ await host.active();
15
+ const rpc = (rpcID, method, input) => host.client.rpc.call({ rpcID, method, input, location: host.location }, host.request());
16
+ const daily = (method, input = {}) => rpc('carthagent.dailyQuests', method, input);
17
+ const source = sessionID => rpc('carthagent.daily-source', 'report', { sessionID });
18
+ const authFailure = promise => assert.rejects(promise, error => {
19
+ assert.equal(error.type, 'dailyQuestError');
20
+ assert.equal(error.data.code, 'authentication_required');
21
+ return true;
22
+ });
23
+ // This fixture has no managed device credential. All six real handlers must
24
+ // fail with their declared auth error, never reach an external API/provider.
25
+ await authFailure(daily('snapshot'));
26
+ await authFailure(daily('detail', { id: randomUUID() }));
27
+ await authFailure(daily('poll', { id: randomUUID() }));
28
+ await authFailure(daily('retry', { id: randomUUID(), accountKey: '0'.repeat(64) }));
29
+ await authFailure(daily('forgetPayload', { id: randomUUID(), accountKey: '0'.repeat(64) }));
30
+ await authFailure(daily('submit', { id: randomUUID(), questId: randomUUID(), accountKey: '0'.repeat(64), consent: true, evidence: {
31
+ projectId: randomUUID(), outcomeId: randomUUID(), runId: randomUUID(), revision: 'a'.repeat(40),
32
+ goal: '', change: '', checks: [], findings: [],
33
+ } }));
34
+
35
+ const session = await host.client.session.create({ location: host.location, model: host.model, title: 'Daily source schema probe' }, host.request());
36
+ assert.deepEqual((await source(session.id)).output, { source: null });
37
+ await writeFile(join(host.cwd, 'source.txt'), 'local schema probe, not execution evidence\n');
38
+ const runId = randomUUID(), revision = fingerprint(host.cwd, ['.']);
39
+ const check = { id: 'unit', kind: 'test', argv: ['never-execute'], timeoutSeconds: 20 };
40
+ const report = createReport(host.cwd, {
41
+ runId, revision: 1, status: 'implementing',
42
+ plan: { goal: 'Exercise the real local RPC output validator', artifacts: ['.'], steps: ['Inspect'], acceptance: [{ requirement: 'Return the selected local projection', checks: ['unit'] }], checks: [check] },
43
+ // Deliberately constructed schema receipt, not proof that a command ran.
44
+ evidence: { unit: { runId, revision: 1, executedRevision: 1, passed: true, code: 0, checkDigest: checkDigest(check), fingerprint: revision, timedOut: false, cancelled: false, changedDuringCheck: false, outputTail: 'Selected local-only schema text' } },
45
+ }, session.id);
46
+ selectReport(host.cwd, report.path);
47
+ const selected = (await source(session.id)).output.source;
48
+ assert.equal(selected.runId, runId);
49
+ assert.equal(selected.revision, revision);
50
+ assert.equal(selected.planRevision, 1);
51
+ assert.equal(selected.change, '');
52
+ assert.deepEqual(Object.keys(selected.checks[0]).sort(), ['after', 'before', 'id', 'result', 'revision']);
53
+ assert.equal(selected.checks[0].id, 'unit');
54
+ assert.equal(selected.checks[0].before, 'not_run');
55
+ assert.equal(selected.checks[0].after, 'passed');
56
+ assert.equal(selected.checks[0].result, 'Selected local-only schema text');
57
+ await writeFile(join(host.cwd, 'source.txt'), 'changed\n');
58
+ const stale = (await source(session.id)).output.source;
59
+ assert.equal(stale.freshness, 'stale');
60
+ assert.equal(stale.checks[0].after, 'not_run');
61
+ assert.equal(host.requests.length, 0, 'No model requests are authorized by this probe');
62
+ console.log(JSON.stringify({ runtime: host.runtimeIdentity.runtimeVersion, binarySha256: host.runtimeIdentity.binarySha256, dailyRegistration: 'passed', authFailures: 6, sourceOutputSchema: 'passed', staleProjection: 'passed', cloudSubmissions: 0, providerRequests: 0, productionAcceptance: false }));
63
+ } finally {
64
+ await host.close();
65
+ }