carthagent 1.1.0-beta.3 → 1.1.0-beta.4
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/docs/beta3-published-acceptance.md +75 -0
- package/docs/cloud-fresh-install-acceptance.md +135 -0
- package/docs/patches/opencode-v2.0.32-session-ui-combined-v6.patch +14046 -0
- package/docs/release-notes-beta.4.md +18 -0
- package/lib/opencode/cli-runtime.mjs +12 -3
- package/lib/opencode/runtime-release.json +12 -12
- package/lib/opencode/runtime.mjs +2 -2
- package/package.json +1 -1
- package/plugins/carthagent/src/tui.tsx +2 -2
- package/script/ctg/build-owned-runtime.sh +3 -3
- package/tests/native-ship-plan.test.mjs +9 -4
- package/tests/opencode-cli.test.mjs +33 -3
- package/tests/owned-runtime-publish-workflow.test.mjs +12 -6
- package/tests/runtime-development-trust.test.mjs +1 -1
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Carthagent 1.1.0-beta.4
|
|
2
|
+
|
|
3
|
+
## Cloud-first onboarding
|
|
4
|
+
|
|
5
|
+
- Fresh home shows **Continue with Carthagent Cloud** and explains sign-in and eligible starter credit.
|
|
6
|
+
- `/login` opens Cloud authorization directly. `/connect` opens the provider picker with Cloud first under Recommended.
|
|
7
|
+
- Terminal `ctg login` accepts Enter to choose its recommended Cloud default.
|
|
8
|
+
|
|
9
|
+
## Continuing useful tasks
|
|
10
|
+
|
|
11
|
+
- An explicit model selection when resuming a headless session now changes that session's model before continuing.
|
|
12
|
+
- Headless observation allows at least 30 minutes instead of interrupting tasks after two minutes. Persistent execution budgets still apply, and `--reset-budget` remains an explicit authorization.
|
|
13
|
+
|
|
14
|
+
## Validation scope
|
|
15
|
+
|
|
16
|
+
The onboarding candidate was exercised in real narrow and wide terminals, with email authorization and actual model calls. A real Cloud ledger and Stripe test-mode payment verified quota rejection, payment activation, and explicit paid continuation of the same session. Payment alone did not restart the task or modify its files.
|
|
17
|
+
|
|
18
|
+
Independent review rejected the generated expense-reporting task as unfinished and incorrect; this release does not claim successful completion of that task or production inference acceptance. See [the acceptance record](cloud-fresh-install-acceptance.md) for details and the local-profile backup incident.
|
|
@@ -644,6 +644,11 @@ export async function createOpenCodeCliRuntime({ cwd = process.cwd(), io = conso
|
|
|
644
644
|
let result;
|
|
645
645
|
let headlessError;
|
|
646
646
|
const requestedSession = options.session;
|
|
647
|
+
if (requestedSession && explicitModelSelection && model && !signal.controller.signal.aborted) {
|
|
648
|
+
const resumed = await client.session.get({ sessionID: requestedSession }, { signal: signal.controller.signal });
|
|
649
|
+
assertSessionWorkspace(resumed, cwd);
|
|
650
|
+
await client.session.switchModel({ sessionID: requestedSession, model }, { location: { directory: cwd }, signal: signal.controller.signal });
|
|
651
|
+
}
|
|
647
652
|
if (requestedSession && Object.keys(sessionSettings).length && !signal.controller.signal.aborted) {
|
|
648
653
|
await persistSessionOptions(client, requestedSession, metadata, sessionSettings, { signal: signal.controller.signal, directory: cwd });
|
|
649
654
|
}
|
|
@@ -658,6 +663,9 @@ export async function createOpenCodeCliRuntime({ cwd = process.cwd(), io = conso
|
|
|
658
663
|
}
|
|
659
664
|
try {
|
|
660
665
|
result = await headlessApi.runHeadless(service, cwd, framedPrompt || prompt || '', {
|
|
666
|
+
// The transport observer must allow useful coding tasks to finish.
|
|
667
|
+
// Persistent execution budgets remain enforced by the host plugin.
|
|
668
|
+
timeout: Math.max(30 * 60_000, (Number(sessionSettings.maxSeconds) || 0) * 1000 + 5000),
|
|
661
669
|
sessionID: options.session,
|
|
662
670
|
session: {
|
|
663
671
|
title: promptText.slice(0, 80), ...(model ? { model } : {}), metadata,
|
|
@@ -781,9 +789,10 @@ export async function createOpenCodeCliRuntime({ cwd = process.cwd(), io = conso
|
|
|
781
789
|
: interactive.find(item => item.id === 'experiential-labs') || interactive[0];
|
|
782
790
|
if (!target && interactive.length > 1) {
|
|
783
791
|
const prompt = readlineFactory({ input: stdin, output: stdout });
|
|
784
|
-
const
|
|
785
|
-
const
|
|
786
|
-
const
|
|
792
|
+
const defaultIndex = interactive.indexOf(integration);
|
|
793
|
+
const choices = interactive.map((item, index) => `${index + 1}) ${item.name}${item.id === 'experiential-labs' ? ' (recommended)' : ''}`).join('\n');
|
|
794
|
+
const selected = await questionWithAbort(prompt, `Select provider:\n${choices}\nProvider number [${defaultIndex + 1}]: `, controller.signal);
|
|
795
|
+
const index = selected.trim() === '' ? defaultIndex : Number(selected) - 1;
|
|
787
796
|
if (!Number.isInteger(index) || index < 0 || index >= interactive.length) throw new Error('Select a listed provider number.');
|
|
788
797
|
integration = interactive[index];
|
|
789
798
|
}
|
|
@@ -1,36 +1,36 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 1,
|
|
3
|
-
"releaseVersion": "1.1.0-beta.
|
|
3
|
+
"releaseVersion": "1.1.0-beta.4",
|
|
4
4
|
"compatibilityVersion": "2.0.32",
|
|
5
5
|
"runtimeVersion": "0.0.0-ctg.2.0.32",
|
|
6
6
|
"sourceRevision": "843a6c514e606b436a7fe713422ace0a93ad3d61",
|
|
7
|
-
"patchSha256": "
|
|
7
|
+
"patchSha256": "31e15c7f92f82177bf1b6106732926951c4d751bde36b61f3e1e283818ea5631",
|
|
8
8
|
"lockSha256": "9d78379cd264584a495440a217b9d2ecab524c9a56f7f8b4b5bed3926daa330d",
|
|
9
9
|
"assets": {
|
|
10
10
|
"linux-x64": {
|
|
11
11
|
"name": "opencode-linux-x64.tar.gz",
|
|
12
|
-
"archiveSha256": "
|
|
13
|
-
"executableSha256": "
|
|
12
|
+
"archiveSha256": "abdecf765542afe7e843059e73ecdd35d189ae8d02a7d41524cd7c28f02fce99",
|
|
13
|
+
"executableSha256": "9f998cb2d23e2c18258a5bcb9a9588228e6fef9f089b13bb2f74b80d8ee8d7f6"
|
|
14
14
|
},
|
|
15
15
|
"linux-arm64": {
|
|
16
16
|
"name": "opencode-linux-arm64.tar.gz",
|
|
17
|
-
"archiveSha256": "
|
|
18
|
-
"executableSha256": "
|
|
17
|
+
"archiveSha256": "d5e2315f3df07cb79c50c6b5221e1f5bb5ce029997ff30eaba5c71834d95d467",
|
|
18
|
+
"executableSha256": "38d4707b301966145cd6cc1dbf23c656a52f42d536c5ae219507f0473ebae97d"
|
|
19
19
|
},
|
|
20
20
|
"darwin-x64": {
|
|
21
21
|
"name": "opencode-darwin-x64.tar.gz",
|
|
22
|
-
"archiveSha256": "
|
|
23
|
-
"executableSha256": "
|
|
22
|
+
"archiveSha256": "568b1e3a5103421f0a08627b985b53becd727097644c1e5e6f5d5be393ecaea3",
|
|
23
|
+
"executableSha256": "4f69cbae17e1c83149cf1066adc117b0e35173158315fe8e841606510f34f770"
|
|
24
24
|
},
|
|
25
25
|
"darwin-arm64": {
|
|
26
26
|
"name": "opencode-darwin-arm64.tar.gz",
|
|
27
|
-
"archiveSha256": "
|
|
28
|
-
"executableSha256": "
|
|
27
|
+
"archiveSha256": "1b164b4bcec032d7bc8276a6e585d7a469625a22408c3499f210bea562c96075",
|
|
28
|
+
"executableSha256": "42733a9a95257373f62ed9663ffbed2478456c537ee7f0d3f3bbdf91418de697"
|
|
29
29
|
},
|
|
30
30
|
"windows-x64": {
|
|
31
31
|
"name": "opencode-windows-x64.tar.gz",
|
|
32
|
-
"archiveSha256": "
|
|
33
|
-
"executableSha256": "
|
|
32
|
+
"archiveSha256": "535ecb051cf4f43f2acf3c18bec599a6739d7ff6565e32852e041addcc868806",
|
|
33
|
+
"executableSha256": "64b3052060df8ed07406c4b42b3a1d56e3500a7e2cd732756d615d443d9c0f33"
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
36
|
}
|
package/lib/opencode/runtime.mjs
CHANGED
|
@@ -13,12 +13,12 @@ export const OPENCODE_VERSION = '2.0.32';
|
|
|
13
13
|
export const CTG_RUNTIME_VERSION = '0.0.0-ctg.2.0.32';
|
|
14
14
|
export const CTG_RUNTIME_SOURCE = '843a6c514e606b436a7fe713422ace0a93ad3d61';
|
|
15
15
|
export const CTG_RUNTIME_LOCK_SHA256 = '9d78379cd264584a495440a217b9d2ecab524c9a56f7f8b4b5bed3926daa330d';
|
|
16
|
-
export const CTG_RUNTIME_PATCH_SHA256 = '
|
|
16
|
+
export const CTG_RUNTIME_PATCH_SHA256 = '31e15c7f92f82177bf1b6106732926951c4d751bde36b61f3e1e283818ea5631';
|
|
17
17
|
// Update only this development pin after reviewing a new checked-in patch and
|
|
18
18
|
// script/ctg/prepare-development-runtime.sh. The published release pin above,
|
|
19
19
|
// its build recipe, and the release manifest stay immutable until a separately
|
|
20
20
|
// verified runtime release is promoted.
|
|
21
|
-
export const CTG_DEVELOPMENT_RUNTIME_PATCH_SHA256 = '
|
|
21
|
+
export const CTG_DEVELOPMENT_RUNTIME_PATCH_SHA256 = '31e15c7f92f82177bf1b6106732926951c4d751bde36b61f3e1e283818ea5631';
|
|
22
22
|
const DEFAULT_ROOT = path.join(homedir(), '.carthagent', 'opencode');
|
|
23
23
|
const require = createRequire(import.meta.url);
|
|
24
24
|
const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..');
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@ import { Plugin } from "@opencode/plugin/tui";
|
|
|
3
3
|
import { Show } from "solid-js";
|
|
4
4
|
import { writeFile } from "node:fs/promises";
|
|
5
5
|
import { resolve } from "node:path";
|
|
6
|
-
import { nativeProviderName } from "../../../lib/opencode/provider-policy.mjs";
|
|
6
|
+
import { nativeLoginProvider, nativeProviderName } from "../../../lib/opencode/provider-policy.mjs";
|
|
7
7
|
import { framePrompt } from "../../../lib/tui/framing.mjs";
|
|
8
8
|
import { carthagentConfigPath, loadCarthagentConfig, resolveReviewMode, reviewKickoff, saveCarthagentConfig } from "../../../lib/review.mjs";
|
|
9
9
|
import { COMMANDS, NATIVE_COMMAND_COLLISIONS } from "./ui/commands.js";
|
|
@@ -114,7 +114,7 @@ export default Plugin.define({
|
|
|
114
114
|
context.ui.router.navigate({ type: "plugin", name: "daily-quests", data: { ...(sessionID ? { sessionID } : {}) } });
|
|
115
115
|
return;
|
|
116
116
|
case "login":
|
|
117
|
-
context.keymap.dispatch("provider.connect");
|
|
117
|
+
context.keymap.dispatch("provider.connect", arg ? nativeLoginProvider(arg)?.id ?? arg : "experiential-labs");
|
|
118
118
|
return;
|
|
119
119
|
case "help": case "ctg-help":
|
|
120
120
|
await context.ui.dialog.alert({ title: "Carthagent commands", message: COMMANDS.map((command) => `/${command.name}${command.args ? ` ${command.args}` : ""}${command.aliases?.length ? ` (${command.aliases.map((alias) => `/${alias}`).join(", ")})` : ""} — ${command.description}`).join("\n") + "\n\nCtrl+R sessions · Ctrl+T settings · Ctrl+N new session are native Carthagent actions." });
|
|
@@ -6,7 +6,7 @@ set -euo pipefail
|
|
|
6
6
|
ROOT="$(git rev-parse --show-toplevel)"
|
|
7
7
|
EXPECTED_SOURCE="843a6c514e606b436a7fe713422ace0a93ad3d61"
|
|
8
8
|
EXPECTED_LOCK="9d78379cd264584a495440a217b9d2ecab524c9a56f7f8b4b5bed3926daa330d"
|
|
9
|
-
EXPECTED_PATCH="
|
|
9
|
+
EXPECTED_PATCH="31e15c7f92f82177bf1b6106732926951c4d751bde36b61f3e1e283818ea5631"
|
|
10
10
|
TARGET="${1:-opencode-linux-x64}"
|
|
11
11
|
HOST_PLATFORM="$(node -p 'process.platform')"
|
|
12
12
|
HOST_ARCH="$(node -p 'process.arch')"
|
|
@@ -34,7 +34,7 @@ if ! git diff --cached --quiet HEAD --; then
|
|
|
34
34
|
exit 1
|
|
35
35
|
fi
|
|
36
36
|
expected_inputs=(
|
|
37
|
-
"docs/patches/opencode-v2.0.32-session-ui-combined-
|
|
37
|
+
"docs/patches/opencode-v2.0.32-session-ui-combined-v6.patch"
|
|
38
38
|
"script/ctg/build-owned-runtime.sh"
|
|
39
39
|
)
|
|
40
40
|
actual_status="$(git status --porcelain --untracked-files=all)"
|
|
@@ -86,7 +86,7 @@ if [[ "$(bun --version)" != "1.4.2" ]]; then
|
|
|
86
86
|
exit 1
|
|
87
87
|
fi
|
|
88
88
|
|
|
89
|
-
patch_file="$ROOT/docs/patches/opencode-v2.0.32-session-ui-combined-
|
|
89
|
+
patch_file="$ROOT/docs/patches/opencode-v2.0.32-session-ui-combined-v6.patch"
|
|
90
90
|
actual_patch="$("${HASH_COMMAND[@]}" "$patch_file" | awk '{print $1}')"
|
|
91
91
|
if [[ "$actual_patch" != "$EXPECTED_PATCH" ]]; then
|
|
92
92
|
echo "Patch checksum mismatch: $actual_patch" >&2
|
|
@@ -10,7 +10,7 @@ import ts from 'typescript';
|
|
|
10
10
|
import { parseOpenCodeArgs } from '../lib/opencode/cli.mjs';
|
|
11
11
|
import { normalizeSessionOptions } from '../lib/opencode/session-options.mjs';
|
|
12
12
|
|
|
13
|
-
const patch = readFileSync(new URL('../docs/patches/opencode-v2.0.32-session-ui-combined-
|
|
13
|
+
const patch = readFileSync(new URL('../docs/patches/opencode-v2.0.32-session-ui-combined-v6.patch', import.meta.url), 'utf8');
|
|
14
14
|
const path = 'packages/tui/src/routes/session/plan-items.ts';
|
|
15
15
|
const section = patch.split(`diff --git a/${path} b/${path}\n`)[1]?.split('\ndiff --git ')[0];
|
|
16
16
|
assert.ok(section, 'the plan graph parser is part of the reproducible runtime patch');
|
|
@@ -72,14 +72,19 @@ test('development runtime source and preparation recipe pin the actual Ship grap
|
|
|
72
72
|
assert.equal(CTG_DEVELOPMENT_RUNTIME_PATCH_SHA256, digest);
|
|
73
73
|
const prepare = readFileSync(new URL('../script/ctg/prepare-development-runtime.sh', import.meta.url), 'utf8');
|
|
74
74
|
assert.ok(prepare.includes(`EXPECTED_PATCH="${digest}"`));
|
|
75
|
-
assert.ok(prepare.includes('docs/patches/opencode-v2.0.32-session-ui-combined-
|
|
75
|
+
assert.ok(prepare.includes('docs/patches/opencode-v2.0.32-session-ui-combined-v6.patch'));
|
|
76
76
|
assert.doesNotMatch(prepare, /bun (install|run|build)|gh release|npm publish/);
|
|
77
77
|
});
|
|
78
78
|
|
|
79
|
-
test('the
|
|
80
|
-
const published = readFileSync(new URL('../docs/patches/opencode-v2.0.32-session-ui-combined-
|
|
79
|
+
test('the published runtime pin stays on its immutable release patch', () => {
|
|
80
|
+
const published = readFileSync(new URL('../docs/patches/opencode-v2.0.32-session-ui-combined-v6.patch', import.meta.url));
|
|
81
81
|
const digest = createHash('sha256').update(published).digest('hex');
|
|
82
82
|
assert.equal(CTG_RUNTIME_PATCH_SHA256, digest);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test('the candidate build recipe pins the reviewed onboarding patch', () => {
|
|
86
|
+
const digest = createHash('sha256').update(patch).digest('hex');
|
|
83
87
|
const build = readFileSync(new URL('../script/ctg/build-owned-runtime.sh', import.meta.url), 'utf8');
|
|
84
88
|
assert.ok(build.includes(`EXPECTED_PATCH="${digest}"`));
|
|
89
|
+
assert.ok(build.includes('docs/patches/opencode-v2.0.32-session-ui-combined-v6.patch'));
|
|
85
90
|
});
|
|
@@ -552,6 +552,27 @@ test('no-prompt interactive explicit model and thinking selection applies to fre
|
|
|
552
552
|
}
|
|
553
553
|
});
|
|
554
554
|
|
|
555
|
+
test('headless continuation switches an explicitly requested model before submitting to the same session', async () => {
|
|
556
|
+
const calls = [];
|
|
557
|
+
const runtime = await createOpenCodeCliRuntime({ cwd: '/project', stdout: { write() {} }, native: {
|
|
558
|
+
runtime: {}, service: { ensureService: async () => ({ url: 'http://127.0.0.1:1', headers: {} }) },
|
|
559
|
+
headless: { runHeadless: async (_service, _cwd, _prompt, options) => {
|
|
560
|
+
calls.push(['run', options.sessionID]);
|
|
561
|
+
assert.equal(options.timeout, 30 * 60_000);
|
|
562
|
+
return { session: { id: options.sessionID }, outcome: 'failed', terminal: true, messages: [] };
|
|
563
|
+
} },
|
|
564
|
+
clientFactory: async () => ({ session: {
|
|
565
|
+
get: async () => ({ id: 'ses_existing', location: { directory: '/project' }, metadata: {} }),
|
|
566
|
+
switchModel: async input => calls.push(['switch', input]),
|
|
567
|
+
} }),
|
|
568
|
+
} });
|
|
569
|
+
await runtime.run({ mode: 'headless', options: { session: 'ses_existing', provider: 'fixture', model: 'cheaper-model' }, prompt: 'continue' });
|
|
570
|
+
assert.deepEqual(calls, [
|
|
571
|
+
['switch', { sessionID: 'ses_existing', model: { providerID: 'fixture', id: 'cheaper-model' } }],
|
|
572
|
+
['run', 'ses_existing'],
|
|
573
|
+
]);
|
|
574
|
+
});
|
|
575
|
+
|
|
555
576
|
test('resumed interactive model selection verifies workspace ownership before any session mutation or launch', async t => {
|
|
556
577
|
const { mkdtempSync, rmSync } = await import('node:fs');
|
|
557
578
|
const { tmpdir } = await import('node:os');
|
|
@@ -576,6 +597,11 @@ test('resumed interactive model selection verifies workspace ownership before an
|
|
|
576
597
|
mode: 'interactive', options: { session: 'ses_foreign', model: 'fixture/explicit-model', maxTools: '3' }, prompt: '',
|
|
577
598
|
}), /another workspace/);
|
|
578
599
|
assert.deepEqual(calls, [['get', 'ses_foreign']], 'ownership is checked before switching models, updating metadata, or launching TUI');
|
|
600
|
+
calls.length = 0;
|
|
601
|
+
await assert.rejects(runtime.run({
|
|
602
|
+
mode: 'headless', options: { session: 'ses_foreign', model: 'fixture/explicit-model' }, prompt: 'continue',
|
|
603
|
+
}), /another workspace/);
|
|
604
|
+
assert.deepEqual(calls, [['get', 'ses_foreign']], 'headless model switches enforce the same workspace boundary');
|
|
579
605
|
});
|
|
580
606
|
|
|
581
607
|
test('interactive provider or thinking flags without a model fail instead of being silently ignored', async () => {
|
|
@@ -786,18 +812,19 @@ test('resumed native sessions merge explicit launcher options into their own wor
|
|
|
786
812
|
assert.deepEqual(framingOverrides, [{ sessionID: 'ses_existing', enabled: false }]);
|
|
787
813
|
});
|
|
788
814
|
|
|
789
|
-
test('native login
|
|
815
|
+
test('native login defaults to Cloud on Enter and completes the host OAuth lifecycle', async () => {
|
|
790
816
|
const calls = [];
|
|
791
817
|
const opened = [];
|
|
818
|
+
const prompts = [];
|
|
792
819
|
const service = { url: 'http://127.0.0.1:1', headers: {} };
|
|
793
820
|
const integration = {
|
|
794
821
|
id: 'experiential-labs', name: 'Carthagent Ship',
|
|
795
822
|
methods: [{ id: 'carthagent-device', type: 'oauth', label: 'Sign in with Carthagent Ship' }],
|
|
796
823
|
};
|
|
797
|
-
const runtime = await createOpenCodeCliRuntime({ cwd: '/project', io: ioCapture().io, stdin: { isTTY: true }, stdout: { isTTY: true }, openBrowserImpl: async url => { opened.push(url); return true; }, native: {
|
|
824
|
+
const runtime = await createOpenCodeCliRuntime({ cwd: '/project', io: ioCapture().io, stdin: { isTTY: true }, stdout: { isTTY: true }, readlineFactory: () => ({ close() {}, question: async text => { prompts.push(text); return ''; } }), openBrowserImpl: async url => { opened.push(url); return true; }, native: {
|
|
798
825
|
runtime: {}, service: { ensureService: async () => service }, headless: {},
|
|
799
826
|
clientFactory: async () => ({ integration: {
|
|
800
|
-
list: async input => { calls.push(['list', input]); return { data: [integration] }; },
|
|
827
|
+
list: async input => { calls.push(['list', input]); return { data: [{ id: 'openai', name: 'OpenAI', methods: [{ type: 'key' }] }, integration] }; },
|
|
801
828
|
oauth: {
|
|
802
829
|
connect: async input => { calls.push(['connect', input]); return { data: {
|
|
803
830
|
attemptID: 'attempt-1', url: 'https://auth.example/device', instructions: 'Approve access', mode: 'auto',
|
|
@@ -809,6 +836,9 @@ test('native login uses OpenCode integration OAuth status and completion lifecyc
|
|
|
809
836
|
} }),
|
|
810
837
|
} });
|
|
811
838
|
assert.equal(await runtime.login(), 0);
|
|
839
|
+
assert.match(prompts[0], /1\) Carthagent Cloud \(recommended\)/);
|
|
840
|
+
assert.match(prompts[0], /Provider number \[1\]:/);
|
|
841
|
+
assert.equal(calls[1][1].integrationID, 'experiential-labs');
|
|
812
842
|
assert.deepEqual(opened, ['https://auth.example/device']);
|
|
813
843
|
assert.deepEqual(calls.map(([name]) => name), ['list', 'connect', 'status']);
|
|
814
844
|
assert.equal(calls[1][1].methodID, 'carthagent-device');
|
|
@@ -106,7 +106,7 @@ function workflowJob(name) {
|
|
|
106
106
|
return lines.slice(start, end).join('\n');
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
-
const combinedPatchPath = 'docs/patches/opencode-v2.0.32-session-ui-combined-
|
|
109
|
+
const combinedPatchPath = 'docs/patches/opencode-v2.0.32-session-ui-combined-v6.patch';
|
|
110
110
|
|
|
111
111
|
function sha256(bytes) {
|
|
112
112
|
return createHash('sha256').update(bytes).digest('hex');
|
|
@@ -181,7 +181,13 @@ async function fixture(t) {
|
|
|
181
181
|
execFileSync('git', ['init', '-q'], { cwd: ctg });
|
|
182
182
|
execFileSync('git', ['-c', 'user.name=Workflow Fixture', '-c', 'user.email=fixture@example.invalid', 'add', combinedPatchPath, 'script/ctg/build-owned-runtime.sh'], { cwd: ctg });
|
|
183
183
|
execFileSync('git', ['-c', 'user.name=Workflow Fixture', '-c', 'user.email=fixture@example.invalid', 'commit', '-qm', 'fixture candidate patch'], { cwd: ctg });
|
|
184
|
-
|
|
184
|
+
// Publication uses a final package whose trust pin has been promoted after
|
|
185
|
+
// prebuild; the working tree may still retain the previous public release.
|
|
186
|
+
const runtimeSource = await readFile(path.join(root, 'lib/opencode/runtime.mjs'), 'utf8');
|
|
187
|
+
await writeFile(path.join(ctg, 'lib/opencode/runtime.mjs'), runtimeSource.replace(
|
|
188
|
+
/CTG_RUNTIME_PATCH_SHA256 = '[a-f0-9]{64}'/,
|
|
189
|
+
`CTG_RUNTIME_PATCH_SHA256 = '${patchSha256}'`,
|
|
190
|
+
));
|
|
185
191
|
|
|
186
192
|
const manifest = {
|
|
187
193
|
version: 1,
|
|
@@ -403,9 +409,9 @@ test('draft metadata validation has scoped write capability; artifact mutation s
|
|
|
403
409
|
assert.doesNotMatch(assembly, /contents: write/);
|
|
404
410
|
assert.match(publish, /contents: write/);
|
|
405
411
|
assert.match(uploadBlock, /gh release upload/);
|
|
406
|
-
assert.match(workflow, /git -C ctg show HEAD:docs\/patches\/opencode-v2\.0\.32-session-ui-combined-
|
|
407
|
-
assert.match(workflow, /patch_sha256="\$\(hash_file opencode\/docs\/patches\/opencode-v2\.0\.32-session-ui-combined-
|
|
408
|
-
assert.equal((workflow.match(/git -C ctg show HEAD:docs\/patches\/opencode-v2\.0\.32-session-ui-combined-
|
|
412
|
+
assert.match(workflow, /git -C ctg show HEAD:docs\/patches\/opencode-v2\.0\.32-session-ui-combined-v6\.patch > opencode\/docs\/patches\/opencode-v2\.0\.32-session-ui-combined-v6\.patch/);
|
|
413
|
+
assert.match(workflow, /patch_sha256="\$\(hash_file opencode\/docs\/patches\/opencode-v2\.0\.32-session-ui-combined-v6\.patch\)"/);
|
|
414
|
+
assert.equal((workflow.match(/git -C ctg show HEAD:docs\/patches\/opencode-v2\.0\.32-session-ui-combined-v6\.patch > ctg\/docs\/patches\/opencode-v2\.0\.32-session-ui\.patch/g) ?? []).length, 1,
|
|
409
415
|
'candidate assembly must extract exact committed bytes at the pinned historical path');
|
|
410
416
|
assert.match(verifyBlock, /git -C ctg show HEAD:script\/ctg\/build-owned-runtime\.sh/);
|
|
411
417
|
assert.match(verifyBlock, /git -C ctg show "HEAD:\$patch_file" > ctg\/docs\/patches\/opencode-v2\.0\.32-session-ui\.patch/);
|
|
@@ -413,7 +419,7 @@ test('draft metadata validation has scoped write capability; artifact mutation s
|
|
|
413
419
|
assert.match(workflow, /chmod \+x opencode\/script\/ctg\/build-owned-runtime\.sh/);
|
|
414
420
|
assert.match(workflow, /GIT_CONFIG_KEY_0:\s*core\.autocrlf[\s\S]*?GIT_CONFIG_VALUE_0:\s*'false'/);
|
|
415
421
|
assert.match(workflow, /git -C opencode config --local core\.autocrlf false/);
|
|
416
|
-
assert.match(workflow, /patch_sha256="\$\(hash_file opencode\/docs\/patches\/opencode-v2\.0\.32-session-ui-combined-
|
|
422
|
+
assert.match(workflow, /patch_sha256="\$\(hash_file opencode\/docs\/patches\/opencode-v2\.0\.32-session-ui-combined-v6\.patch\)"/);
|
|
417
423
|
});
|
|
418
424
|
|
|
419
425
|
test('review-branch prebuild source is exact dispatch SHA; publish validation is default-branch-only', { skip: requiresUbuntuWorkflowShell, timeout: 60000 }, async (t) => {
|
|
@@ -83,7 +83,7 @@ async function fixture(t, patchSha256 = developmentPatch) {
|
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
test('packaged release trust stays valid independently of the development patch pin', async t => {
|
|
86
|
-
assert.equal(CTG_RUNTIME_PATCH_SHA256, '
|
|
86
|
+
assert.equal(CTG_RUNTIME_PATCH_SHA256, '31e15c7f92f82177bf1b6106732926951c4d751bde36b61f3e1e283818ea5631');
|
|
87
87
|
const published = await readRuntimeReleaseManifest();
|
|
88
88
|
const packageJson = JSON.parse(await readFile(new URL('../package.json', import.meta.url), 'utf8'));
|
|
89
89
|
assert.equal(published.releaseVersion, packageJson.version);
|