carthagent 1.0.7 → 1.1.0-beta.2
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.
- package/README.md +140 -83
- package/bin/ctg.mjs +14 -732
- package/docs/beta-rewards-e2e-plan.md +48 -0
- package/docs/beta-rewards-e2e-results.md +84 -0
- package/docs/cloud-onboarding-acceptance.md +65 -0
- package/docs/glyph-review/host-chrome-glyph-compatibility-v4.md +83 -0
- package/docs/glyph-review/test-isolation-v1.md +26 -0
- package/docs/glyph-review/typecheck-base.log +1 -0
- package/docs/glyph-review/typecheck-v4.log +1 -0
- package/docs/glyph-review/unicode-inventory-v4.csv +402 -0
- package/docs/help-dispatch-review.md +69 -0
- package/docs/launch-readiness-live-results.md +65 -0
- package/docs/legacy-cli.md +150 -0
- package/docs/live-theme-plugin-api.md +18 -0
- package/docs/native-cli.md +102 -0
- package/docs/native-compatibility.md +16 -0
- package/docs/native-daily-quests.md +132 -0
- package/docs/native-glyph-pty.md +59 -0
- package/docs/native-manual-testing.md +336 -0
- package/docs/native-slash-acceptance-handoff.md +49 -0
- package/docs/native-terminal-paired-failure-2026-10-17.md +40 -0
- package/docs/native-terminal-performance.md +310 -0
- package/docs/native-terminal-semantics-evidence/README.md +25 -0
- package/docs/native-terminal-semantics-evidence/controlled-host-model-selftest.json +16 -0
- package/docs/native-terminal-semantics-evidence/current-acceptance-status.json +59 -0
- package/docs/native-terminal-semantics-evidence/final-binary-focused-85bb44a.json +154 -0
- package/docs/native-terminal-semantics-evidence/final-binary-full-incomplete-85bb44a.json +160 -0
- package/docs/native-terminal-semantics-evidence/final-binary-layout-85bb44a.json +149 -0
- package/docs/native-terminal-semantics-evidence/final-binary-layout-interactive-85bb44a.json +159 -0
- package/docs/native-terminal-semantics-evidence/final-binary-model-picker-escape-unchanged-85bb44a.json +44 -0
- package/docs/native-terminal-semantics-evidence/final-binary-model-picker-exact-identity-85bb44a.json +47 -0
- package/docs/native-terminal-semantics-evidence/final-binary-model-picker-unchanged-model-incomplete-85bb44a.json +58 -0
- package/docs/native-terminal-semantics-evidence/final-binary-model-selftest.json +16 -0
- package/docs/native-terminal-semantics-evidence/final-binary-picker-cancel-incomplete-85bb44a.json +62 -0
- package/docs/native-terminal-semantics-evidence/focused-clipboard-title-stale-binary.json +161 -0
- package/docs/native-terminal-semantics-evidence/layout-stale-binary-observation-not-accepted.json +153 -0
- package/docs/native-terminal-semantics-evidence/picker-latest-incomplete-attempt.json +62 -0
- package/docs/native-terminal-semantics-evidence/picker-previous-probe-pass.json +61 -0
- package/docs/native-terminal-semantics.md +89 -0
- package/docs/native-upgrade.md +175 -0
- package/docs/opencode-composed-source-review.md +89 -0
- package/docs/opencode-migration.md +225 -9
- package/docs/opencode-owned-runtime.md +241 -0
- package/docs/opencode-runtime.md +12 -0
- package/docs/opencode-theme-provider-proof/README.md +55 -0
- package/docs/opencode-theme-provider-proof/live-theme-api-base.patch +122 -0
- package/docs/opencode-theme-provider-proof/plugin-theme-provider.patch +107 -0
- package/docs/opencode-theme-provider-proof/session-ui-base.patch +1217 -0
- package/docs/owned-host-controls-proof.md +39 -0
- package/docs/owned-runtime-release-manifest.md +42 -0
- package/docs/owned-visual-clear-proof.md +40 -0
- package/docs/patches/opencode-v2.0.32-glyph-spinner-test-isolation-v1.patch +50 -0
- package/docs/patches/opencode-v2.0.32-glyphs.patch +3670 -0
- package/docs/patches/opencode-v2.0.32-help-dispatch.patch +156 -0
- package/docs/patches/opencode-v2.0.32-live-theme-api.patch +122 -0
- package/docs/patches/opencode-v2.0.32-session-ui-combined-v1.patch +5272 -0
- package/docs/patches/opencode-v2.0.32-session-ui-combined-v2.patch +6052 -0
- package/docs/patches/opencode-v2.0.32-session-ui-combined-v3.patch +8657 -0
- package/docs/patches/opencode-v2.0.32-session-ui-combined-v4.patch +9928 -0
- package/docs/patches/opencode-v2.0.32-session-ui-combined-v5.patch +13965 -0
- package/docs/patches/opencode-v2.0.32-session-ui.patch +5137 -0
- package/docs/release-notes-beta.1.md +60 -0
- package/docs/release-notes-beta.2.md +60 -0
- package/docs/release-notes-native-draft.md +73 -0
- package/docs/release-notes-preview.10.md +121 -0
- package/docs/release-notes-preview.11.md +76 -0
- package/docs/release-notes-preview.12.md +55 -0
- package/docs/release-notes-preview.13.md +68 -0
- package/docs/release-notes-preview.9.md +130 -0
- package/docs/source-preview.md +120 -0
- package/evaluate.mjs +28 -1
- package/lib/budget.mjs +1 -1
- package/lib/cloud/client.mjs +13 -7
- package/lib/codegraph.mjs +1 -1
- package/lib/delivery-review.mjs +19 -6
- package/lib/delivery.mjs +3 -2
- package/lib/environment.mjs +8 -0
- package/lib/guidance.mjs +1 -1
- package/lib/opencode/cli-runtime.mjs +1188 -0
- package/lib/opencode/cli.mjs +253 -0
- package/lib/opencode/credentials.mjs +63 -8
- package/lib/opencode/demo.mjs +53 -0
- package/lib/opencode/evaluation-budget-plugin/index.ts +3 -0
- package/lib/opencode/evaluation.mjs +32 -12
- package/lib/opencode/headless.mjs +22 -5
- package/lib/opencode/provider-policy.mjs +23 -0
- package/lib/opencode/review.mjs +37 -16
- package/lib/opencode/runtime-artifact.mjs +335 -0
- package/lib/opencode/runtime-github-transport.mjs +121 -0
- package/lib/opencode/runtime-release.json +36 -0
- package/lib/opencode/runtime.mjs +299 -20
- package/lib/opencode/service.mjs +77 -35
- package/lib/opencode/session-options.mjs +6 -2
- package/lib/opencode/update.mjs +222 -0
- package/lib/opencode/workspace.mjs +1 -1
- package/lib/planning-advice.mjs +2 -2
- package/lib/providers/experiential.mjs +2 -2
- package/lib/providers/names.mjs +1 -1
- package/lib/tui/framing.mjs +1 -80
- package/lib/tui/models.mjs +1 -1
- package/lib/update.mjs +10 -1
- package/package.json +33 -7
- package/plugins/carthagent/README.md +23 -0
- package/plugins/carthagent/src/bootstrap.ts +1 -1
- package/plugins/carthagent/src/daily-evidence.mjs +57 -0
- package/plugins/carthagent/src/daily-quests-rpc.ts +50 -0
- package/plugins/carthagent/src/daily-source-rpc.ts +52 -0
- package/plugins/carthagent/src/index.ts +71 -24
- package/plugins/carthagent/src/rpc.ts +51 -6
- package/plugins/carthagent/src/server/codegraph.ts +12 -5
- package/plugins/carthagent/src/server/daily-quests-rpc.ts +277 -0
- package/plugins/carthagent/src/server/daily-source-rpc.ts +243 -0
- package/plugins/carthagent/src/server/delivery.ts +227 -112
- package/plugins/carthagent/src/server/framing.mjs +80 -0
- package/plugins/carthagent/src/server/policy.ts +1 -1
- package/plugins/carthagent/src/server/providers.ts +65 -36
- package/plugins/carthagent/src/server/session-controls.ts +3 -1
- package/plugins/carthagent/src/server/ship-agents.ts +77 -0
- package/plugins/carthagent/src/server/ship-context.mjs +60 -0
- package/plugins/carthagent/src/server/ship-guidance.ts +41 -0
- package/plugins/carthagent/src/server/ship-rpc.ts +71 -0
- package/plugins/carthagent/src/server/voyages-rpc.ts +280 -0
- package/plugins/carthagent/src/testing.ts +5 -0
- package/plugins/carthagent/src/tui.tsx +506 -0
- package/plugins/carthagent/src/ui/client.ts +127 -0
- package/plugins/carthagent/src/ui/commands.ts +70 -0
- package/plugins/carthagent/src/ui/daily-editor-focus.mjs +52 -0
- package/plugins/carthagent/src/ui/daily-editor.mjs +4 -0
- package/plugins/carthagent/src/ui/daily-quests-events.mjs +18 -0
- package/plugins/carthagent/src/ui/daily-quests-state.mjs +218 -0
- package/plugins/carthagent/src/ui/daily-quests.tsx +235 -0
- package/plugins/carthagent/src/ui/host-state.ts +42 -0
- package/plugins/carthagent/src/ui/model-command.ts +32 -0
- package/plugins/carthagent/src/ui/native-navigation.ts +23 -0
- package/plugins/carthagent/src/ui/native-review.d.mts +33 -0
- package/plugins/carthagent/src/ui/native-review.mjs +159 -0
- package/plugins/carthagent/src/ui/resume-session.mjs +34 -0
- package/plugins/carthagent/src/ui/select-mouse.ts +17 -0
- package/plugins/carthagent/src/ui/session-preferences.d.mts +7 -0
- package/plugins/carthagent/src/ui/session-preferences.mjs +14 -0
- package/plugins/carthagent/src/ui/theme-provision.d.mts +3 -0
- package/plugins/carthagent/src/ui/theme-provision.mjs +134 -0
- package/plugins/carthagent/src/ui/voyages-navigation-state.mjs +56 -0
- package/plugins/carthagent/src/ui/voyages-navigation.tsx +131 -0
- package/plugins/carthagent/src/ui/voyages-state.mjs +165 -0
- package/plugins/carthagent/src/ui/voyages.tsx +131 -0
- package/plugins/carthagent/src/ui/workspace-live.ts +61 -0
- package/plugins/carthagent/src/ui/workspace.tsx +379 -0
- package/plugins/carthagent/src/voyages-rpc.ts +64 -0
- package/plugins/carthagent/src/workspace-rpc.ts +36 -0
- package/plugins/carthagent/tests/commands.test.ts +107 -0
- package/plugins/carthagent/tests/session-preferences.test.mjs +41 -0
- package/plugins/carthagent/tests/voyages-navigation.test.tsx +269 -0
- package/plugins/carthagent/themes/carthage.json +67 -0
- package/plugins/carthagent/tui.ts +26 -1
- package/script/ctg/build-owned-runtime.sh +166 -0
- package/tests/budget-engine.test.mjs +4 -3
- package/tests/cli.test.mjs +52 -96
- package/tests/cloud-client.test.mjs +2 -2
- package/tests/daily-evidence.test.mjs +62 -0
- package/tests/delivery-review.test.mjs +57 -2
- package/tests/delivery.test.mjs +22 -1
- package/tests/experiential-extension.test.mjs +2 -2
- package/tests/experiential.test.mjs +1 -1
- package/tests/fixtures/native-discovery-plugin/server.ts +15 -0
- package/tests/generate-owned-runtime-release.test.mjs +354 -0
- package/tests/installed-runtime-contract.test.mjs +43 -0
- package/tests/native-cli-host-smoke.mjs +457 -0
- package/tests/native-daily-editor-focus.test.mjs +213 -0
- package/tests/native-daily-editor.test.mjs +43 -0
- package/tests/native-daily-quests-layout.test.mjs +55 -0
- package/tests/native-daily-quests-rpc.test.mjs +456 -0
- package/tests/native-daily-quests-ui.test.mjs +778 -0
- package/tests/native-daily-source.test.mjs +525 -0
- package/tests/native-demo-ui.test.mjs +48 -0
- package/tests/native-evaluation-admission.test.mjs +24 -0
- package/tests/native-fixture-browser-cleanup.test.mjs +65 -0
- package/tests/native-fixture-runtime.test.mjs +111 -0
- package/tests/native-fixture-stop-diagnostics.test.mjs +67 -0
- package/tests/native-glyph-pty.test.mjs +107 -0
- package/tests/native-manual-projects.test.mjs +170 -0
- package/tests/native-model-command.test.mjs +67 -0
- package/tests/native-owned-runtime-env.test.mjs +140 -0
- package/tests/native-paired-guards.test.mjs +464 -0
- package/tests/native-provider-policy.test.mjs +33 -0
- package/tests/native-pty-invocation.test.mjs +114 -0
- package/tests/native-pty-runtime.test.mjs +45 -0
- package/tests/native-resume-session.test.mjs +46 -0
- package/tests/native-review-consumer.test.mjs +286 -0
- package/tests/native-runtime-identity.test.mjs +214 -0
- package/tests/native-select-mouse.test.mjs +23 -0
- package/tests/native-ship-guidance.test.mjs +16 -0
- package/tests/native-ship-parity.test.mjs +526 -0
- package/tests/native-ship-plan.test.mjs +85 -0
- package/tests/native-ship-rpc.test.mjs +83 -0
- package/tests/native-ui-commands.test.mjs +6 -0
- package/tests/native-voyages-layout.test.mjs +28 -0
- package/tests/native-voyages-navigation.test.mjs +169 -0
- package/tests/native-voyages-rpc.test.mjs +397 -0
- package/tests/native-voyages-ui.test.mjs +307 -0
- package/tests/opencode-atomic-write.test.mjs +55 -0
- package/tests/opencode-cli.test.mjs +1312 -0
- package/tests/opencode-codegraph-plugin.test.mjs +47 -0
- package/tests/opencode-delivery.test.mjs +16 -0
- package/tests/opencode-providers.test.mjs +248 -20
- package/tests/opencode-runtime.test.mjs +597 -8
- package/tests/opencode-session-controls.test.mjs +15 -0
- package/tests/opencode-ship-rpc.test.mjs +71 -0
- package/tests/opencode-update.test.mjs +475 -0
- package/tests/opencode-workspace.test.mjs +51 -2
- package/tests/owned-runtime-publish-workflow.test.mjs +477 -0
- package/tests/release-workflow.test.mjs +46 -0
- package/tests/runtime-artifact.test.mjs +269 -0
- package/tests/runtime-development-trust.test.mjs +231 -0
- package/tests/runtime-github-transport.test.mjs +132 -0
- package/tests/runtime-token-env.test.mjs +23 -0
- package/tests/server.test.mjs +3 -1
- package/tests/ship-telemetry.test.mjs +1 -1
- package/tests/source-preview.test.mjs +253 -0
- package/tests/test_native_terminal_probe.py +76 -0
- package/tests/test_native_terminal_semantics.py +232 -0
- package/tests/tui.test.mjs +2 -2
- package/tests/update.test.mjs +42 -5
- package/tests/verify-draft-runtime-gate.test.mjs +280 -0
- package/tests/verify-owned-runtime-release.test.mjs +371 -0
- package/tests/workspace-live.test.mjs +127 -0
- package/tools/benchmark/diagnose-request.mjs +59 -36
- package/tools/benchmark/eval-common.mjs +3 -3
- package/tools/benchmark/evaluators.test.mjs +4 -4
- package/tools/benchmark/guard.test.mjs +35 -2
- package/tools/benchmark/guard.ts +20 -7
- package/tools/benchmark/planning-startup.test.mjs +30 -34
- package/tools/benchmark/runner.mjs +12 -7
- package/tools/benchmark/runner.test.mjs +23 -7
- package/tools/benchmark/upstream-error.test.mjs +16 -2
- package/tools/generate-owned-runtime-release.mjs +217 -0
- package/tools/installed-runtime-contract.mjs +16 -0
- package/tools/manual-runtime-manifest.mjs +39 -0
- package/tools/native-fixture-browser-cleanup.mjs +59 -0
- package/tools/native-fixture-stop-diagnostics.mjs +45 -0
- package/tools/native-fixture.mjs +199 -40
- package/tools/native-glyph-helpers.py +127 -0
- package/tools/native-glyph-pty.py +144 -0
- package/tools/native-manual-projects.mjs +259 -0
- package/tools/native-manual.mjs +338 -0
- package/tools/native-model-picker-acceptance.py +306 -0
- package/tools/native-owned-runtime-env.sh +47 -0
- package/tools/native-paired-guards.mjs +282 -0
- package/tools/native-paired-manifest.mjs +72 -0
- package/tools/native-paired-orchestration.sh +51 -0
- package/tools/native-plugin-readiness.mjs +33 -0
- package/tools/native-plugin-readiness.test.mjs +53 -0
- package/tools/native-pty-invocation.mjs +26 -0
- package/tools/native-pty-runtime.mjs +83 -0
- package/tools/native-pty-smoke.py +147 -24
- package/tools/native-public-repo-shell-result.mjs +13 -0
- package/tools/native-public-repo-shell-result.test.mjs +28 -0
- package/tools/native-runtime-identity.mjs +201 -0
- package/tools/native-slash-acceptance-env.py +38 -0
- package/tools/native-terminal-semantics.py +882 -0
- package/tools/owned-shell-observer/index.ts +16 -0
- package/tools/owned-shell-observer/package.json +1 -0
- package/tools/owned-visual-clear-env.py +48 -0
- package/tools/owned-visual-clear-model.py +21 -0
- package/tools/pinned-owned-runtime-inputs.mjs +46 -0
- package/tools/prepare-native-manual.mjs +122 -0
- package/tools/run-native-terminal-paired.sh +403 -0
- package/tools/runtime-token-env.mjs +8 -0
- package/tools/seed-native-switch-fixture.mjs +79 -0
- package/tools/test-native-controls.mjs +3 -1
- package/tools/test-native-daily-quests.mjs +65 -0
- package/tools/test-native-default-cli.mjs +106 -0
- package/tools/test-native-delivery-host.mjs +102 -20
- package/tools/test-native-evaluation.mjs +7 -3
- package/tools/test-native-glyph-pty.mjs +94 -0
- package/tools/test-native-host.mjs +91 -65
- package/tools/test-native-outcomes.mjs +3 -1
- package/tools/test-native-package.mjs +206 -38
- package/tools/test-native-performance.mjs +26 -0
- package/tools/test-native-product.mjs +90 -0
- package/tools/test-native-pty.mjs +34 -9
- package/tools/test-native-public-repo.mjs +224 -0
- package/tools/test-native-refresh-reentry.mjs +106 -0
- package/tools/test-native-report.mjs +27 -20
- package/tools/test-native-review.mjs +75 -0
- package/tools/test-native-runtime.mjs +120 -0
- package/tools/test-native-ship-models.mjs +176 -0
- package/tools/test-native-ship-walkthrough.mjs +96 -0
- package/tools/test-native-slash-commands.mjs +125 -0
- package/tools/test-native-slash-commands.py +242 -0
- package/tools/test-native-swe-host.mjs +224 -55
- package/tools/test-native-tasks.mjs +77 -0
- package/tools/test-native-terminal-performance.py +887 -0
- package/tools/test-native-terminal-readiness.sh +85 -0
- package/tools/test-native-terminal-semantics-installed.mjs +38 -0
- package/tools/test-native-visual-clear-installed.mjs +48 -0
- package/tools/test-owned-host-controls.mjs +238 -0
- package/tools/test-owned-opencode-runtime.mjs +35 -0
- package/tools/test-owned-visual-clear-env.py +120 -0
- package/tools/test-owned-visual-clear.py +373 -0
- package/tools/verify-draft-runtime-gate.mjs +138 -0
- package/tools/verify-owned-runtime-release.mjs +114 -0
- 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:
|
|
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
|
+
}
|