Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
578 changes: 569 additions & 9 deletions modules/jarvos-agent-context/test/agent-context.test.js

Large diffs are not rendered by default.

20 changes: 16 additions & 4 deletions modules/jarvos-agent-context/test/work-action-host.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -357,7 +357,7 @@ test('Claude and Codex setup scripts pass optional work-action env and never req
assert.doesNotMatch(source, /: "\$\{JARVOS_PROJECTS_CONTEXT_CONFIG:\?/);
}
assert.match(claude, /claude mcp add --scope user "\$\{MCP_ENV_ARGS\[@\]\}" jarvos -- "\$\{MCP_COMMAND\[@\]\}"/);
assert.match(codex, /codex mcp add "\$\{MCP_ENV_ARGS\[@\]\}" jarvos -- "\$\{MCP_COMMAND\[@\]\}"/);
assert.match(codex, /"\$CODEX_EXECUTABLE" mcp add "\$\{MCP_ENV_ARGS\[@\]\}" jarvos -- "\$\{MCP_COMMAND\[@\]\}"/);
});

// Records the argv of a fake `codex`/`claude` CLI so setup.sh's real MCP
Expand All @@ -366,11 +366,22 @@ function writeFakeMcpCli(binPath, recordPath) {
fs.writeFileSync(binPath, [
'#!/usr/bin/env node',
"const fs = require('fs');",
"const crypto = require('crypto');",
'const args = process.argv.slice(2);',
`const recordPath = ${JSON.stringify(recordPath)};`,
"if (args[0] === 'mcp' && args[1] === 'get') process.exit(1);",
"if (args[0] === 'mcp' && args[1] === 'remove') process.exit(0);",
"if (args[0] === 'mcp' && args[1] === 'add') { fs.writeFileSync(recordPath, JSON.stringify(args)); process.exit(0); }",
"const scope = process.env.CODEX_HOME || process.env.CLAUDE_SETTINGS || process.env.HOME || 'default';",
"const statePath = `${recordPath}.${crypto.createHash('sha256').update(scope).digest('hex')}.state.json`;",
"if (args[0] === 'mcp' && args[1] === 'list') { const entries = fs.existsSync(statePath) ? [JSON.parse(fs.readFileSync(statePath, 'utf8'))] : []; process.stdout.write(JSON.stringify(entries)); process.exit(0); }",
"if (args[0] === 'mcp' && args[1] === 'get') { if (!fs.existsSync(statePath)) process.exit(1); process.stdout.write(fs.readFileSync(statePath, 'utf8')); process.exit(0); }",
"if (args[0] === 'mcp' && args[1] === 'remove') { try { fs.unlinkSync(statePath); } catch (error) { if (error.code !== 'ENOENT') throw error; } process.exit(0); }",
"if (args[0] === 'mcp' && args[1] === 'add') {",
" fs.writeFileSync(recordPath, JSON.stringify(args));",
" const separator = args.indexOf('--'); const nameIndex = args.indexOf('jarvos'); const env = {};",
" for (let index = 2; index < nameIndex; index += 1) if (args[index] === '--env') { const binding = args[++index]; const split = binding.indexOf('='); env[binding.slice(0, split)] = binding.slice(split + 1); }",
" const command = args[separator + 1]; const commandArgs = args.slice(separator + 2);",
" fs.writeFileSync(statePath, JSON.stringify({ name: 'jarvos', enabled: true, startup_timeout_sec: null, tool_timeout_sec: null, enabled_tools: null, disabled_tools: null, transport: { type: 'stdio', command, args: commandArgs, cwd: null, env, env_vars: [] } }));",
" process.exit(0);",
"}",
'process.exit(0);',
'',
].join('\n'), { encoding: 'utf8', mode: 0o755 });
Expand Down Expand Up @@ -524,6 +535,7 @@ test('rerunning Codex setup from a different immutable runtime preserves the sam
fs.writeFileSync(path.join(runtime2CodexDir, 'jarvos-session-turn-hook.js'), '// stub\n');
fs.writeFileSync(path.join(runtime2CodexDir, 'trust-session-start-hook.js'), '// stub\n');
fs.copyFileSync(path.join(REPO_ROOT, 'runtimes', 'codex', 'setup.sh'), path.join(runtime2CodexDir, 'setup.sh'));
fs.copyFileSync(path.join(REPO_ROOT, 'runtimes', 'codex', 'mcp-registration-receipt.js'), path.join(runtime2CodexDir, 'mcp-registration-receipt.js'));
fs.chmodSync(path.join(runtime2CodexDir, 'setup.sh'), 0o755);

const runFrom = (setupPath) => {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@
"clawpatch:map": "node scripts/clawpatch-runner.js map",
"clawpatch:status": "node scripts/clawpatch-runner.js status",
"clawpatch:review:dry-run": "node scripts/clawpatch-runner.js review --limit 1 --dry-run",
"test": "bash tests/smoke-test.sh && node --test modules/jarvos-ontology/test/*.test.js && node --test modules/jarvos-agent-context/test/agent-context.test.js modules/jarvos-agent-context/test/projects-context.test.js && node --test modules/jarvos-control-plane/test/*.test.js && node --test modules/jarvos-coding/test/*.test.js && node --test modules/jarvos-runtime-kit/test/*.test.js && node --test modules/jarvos-skills/test/*.test.js && node --test modules/jarvos-memory/test/transcript-retrieval.test.js && node tests/modules-smoke-test.js && node tests/cli-smoke-test.js && node --test tests/doctor-checks-test.js tests/doctor-modules-test.js && node --test tests/openclaw-plugin-persistence-docs-test.js && node --test tests/pack-manifest-test.js && node --test tests/release-please-config-test.js && node --test tests/release-readiness-check-test.js tests/release-status-test.js && node --test tests/unreleased-drift-check-test.js && node --test tests/public-journal-boundary.test.js tests/active-assistant-runtime-bridge-test.js tests/active-assistant-synthesis-contract-test.js && node --test modules/jarvos-secondbrain/tests/config-resolution.test.js modules/jarvos-secondbrain/tests/journal-lifecycle.test.js modules/jarvos-secondbrain/tests/journal-health.test.js modules/jarvos-secondbrain/tests/journal-maintenance.test.js modules/jarvos-secondbrain/tests/journal-maintenance-schedule.test.js modules/jarvos-secondbrain/tests/journal-maintenance-paths.test.js modules/jarvos-secondbrain/tests/journal-projects-section.test.js modules/jarvos-secondbrain/tests/projects.test.js modules/jarvos-secondbrain/tests/project-context.test.js modules/jarvos-secondbrain/packages/jarvos-secondbrain-projects/test/records.test.js modules/jarvos-secondbrain/packages/jarvos-secondbrain-projects/test/project-context.test.js modules/jarvos-secondbrain/packages/jarvos-secondbrain-projects/test/projects-context.test.js modules/jarvos-secondbrain/packages/jarvos-secondbrain-projects/test/provider-contracts.test.js modules/jarvos-secondbrain/packages/jarvos-secondbrain-projects/test/migrate.test.js modules/jarvos-secondbrain/packages/jarvos-secondbrain-projects/test/journal-projection.test.js modules/jarvos-secondbrain/tests/link-to-journal.test.js modules/jarvos-secondbrain/tests/vault-storage-adapter-journal.test.js modules/jarvos-secondbrain/tests/personality-note-journal-contract.test.js modules/jarvos-secondbrain/tests/journal-backlink-recovery.test.js && node --test tests/secondbrain-external-integrations-doc-test.js && node --test tests/v05-shipgate-test.js && bash scripts/smoke-test.sh",
"test": "bash tests/smoke-test.sh && node --test modules/jarvos-ontology/test/*.test.js && node --test modules/jarvos-agent-context/test/agent-context.test.js modules/jarvos-agent-context/test/projects-context.test.js modules/jarvos-agent-context/test/work-action-host.test.js && node --test modules/jarvos-control-plane/test/*.test.js && node --test modules/jarvos-coding/test/*.test.js && node --test modules/jarvos-runtime-kit/test/*.test.js && node --test modules/jarvos-skills/test/*.test.js && node --test modules/jarvos-memory/test/transcript-retrieval.test.js && node tests/modules-smoke-test.js && node tests/cli-smoke-test.js && node --test tests/doctor-checks-test.js tests/doctor-modules-test.js && node --test tests/openclaw-plugin-persistence-docs-test.js && node --test tests/pack-manifest-test.js && node --test tests/release-please-config-test.js && node --test tests/release-readiness-check-test.js tests/release-status-test.js && node --test tests/unreleased-drift-check-test.js && node --test tests/public-journal-boundary.test.js tests/active-assistant-runtime-bridge-test.js tests/active-assistant-synthesis-contract-test.js && node --test modules/jarvos-secondbrain/tests/config-resolution.test.js modules/jarvos-secondbrain/tests/journal-lifecycle.test.js modules/jarvos-secondbrain/tests/journal-health.test.js modules/jarvos-secondbrain/tests/journal-maintenance.test.js modules/jarvos-secondbrain/tests/journal-maintenance-schedule.test.js modules/jarvos-secondbrain/tests/journal-maintenance-paths.test.js modules/jarvos-secondbrain/tests/journal-projects-section.test.js modules/jarvos-secondbrain/tests/projects.test.js modules/jarvos-secondbrain/tests/project-context.test.js modules/jarvos-secondbrain/packages/jarvos-secondbrain-projects/test/records.test.js modules/jarvos-secondbrain/packages/jarvos-secondbrain-projects/test/project-context.test.js modules/jarvos-secondbrain/packages/jarvos-secondbrain-projects/test/projects-context.test.js modules/jarvos-secondbrain/packages/jarvos-secondbrain-projects/test/provider-contracts.test.js modules/jarvos-secondbrain/packages/jarvos-secondbrain-projects/test/migrate.test.js modules/jarvos-secondbrain/packages/jarvos-secondbrain-projects/test/journal-projection.test.js modules/jarvos-secondbrain/tests/link-to-journal.test.js modules/jarvos-secondbrain/tests/vault-storage-adapter-journal.test.js modules/jarvos-secondbrain/tests/personality-note-journal-contract.test.js modules/jarvos-secondbrain/tests/journal-backlink-recovery.test.js && node --test tests/secondbrain-external-integrations-doc-test.js && node --test tests/v05-shipgate-test.js && bash scripts/smoke-test.sh",
"test:bootstrap": "bash tests/smoke-test.sh",
"test:modules": "node tests/modules-smoke-test.js",
"test:structure": "bash scripts/smoke-test.sh",
Expand Down
24 changes: 24 additions & 0 deletions runtimes/codex/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,10 @@ state, and records the provider-owned additions under the selected
`CODEX_HOME`. It refuses to replace a stale, disabled, locally changed, or
unverifiable installation. `existing` (the default) and `disabled` modes leave
the profile untouched and jarvOS uses native fallback when CE is unavailable.
Managed activation also requires the running Codex CLI version to exactly match
the version recorded by the reviewed conformance receipt. Setup performs this
check before writing the selected profile, and the provider manager repeats it
immediately before activation.

To remove only additions made by jarvOS, use the same profile and explicit
rollback flag:
Expand All @@ -109,6 +113,26 @@ Rollback preserves the marketplace when another plugin now uses it and refuses
to remove provider state that changed after jarvOS installed it. Restart Codex
after activation or rollback before relying on the new profile state.

Codex MCP setup follows the same ownership boundary. A mode-`0600` receipt in
the selected `CODEX_HOME` records only a normalized fingerprint of the jarvOS
registration created by setup. Rollback never invokes name-only
`codex mcp remove`. For a present matching registration it can use an atomic
Codex app-server `config/batchWrite` guarded by the exact user-layer
`expectedVersion`; the receipt is cleared only after app-server confirms the
atomic edit succeeded. Both `ok` and `okOverridden` are successful writes: the
latter means the jarvOS-owned user-layer entry was removed but a higher-priority
layer still supplies the effective value, which rollback preserves. If
app-server CAS is unavailable, the layer or registration changed, or the write
cannot be confirmed, the registration and receipt are preserved for manual
reconciliation and rollback exits nonzero. An already absent registration
clears a valid receipt. Rollback ignores stale forward-install bindings that it
does not need. Missing subsystem prerequisites are reported after every
independent hook, MCP, and Compound Engineering provider cleanup that can still
run; one phase does not short-circuit the others.
This receipt covers MCP ownership only. Hook trust and feature-setting
restoration are separate profile concerns and are not claimed as transactional
by this mechanism.

### Optional authenticated control-plane host

Private installs that supply an authenticated host service and credential file
Expand Down
65 changes: 56 additions & 9 deletions runtimes/codex/compound-engineering-activation.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const ROOT = path.resolve(__dirname, '..', '..');
const CAPABILITY_PATH = path.join(__dirname, 'compound-engineering-capability.json');
const CONFORMANCE_PATH = path.join(__dirname, 'compound-engineering-conformance.json');
const STATE_VERSION = 'jarvos-codex-provider-state/v1';
const CODEX_SEMVER = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
const PROVIDER_ID = 'compound-engineering';
const MARKETPLACE_NAME = 'compound-engineering-plugin';
const PLUGIN_SELECTOR = 'compound-engineering@compound-engineering-plugin';
Expand All @@ -46,10 +47,13 @@ function readJson(filePath, label) {
}
}

function assertProfileDirectory(profilePath) {
function assertProfileDirectory(profilePath, { create = true } = {}) {
if (typeof profilePath !== 'string' || !path.isAbsolute(profilePath)) fail('CODEX_HOME must be absolute');
const absolute = path.resolve(profilePath);
if (!fs.existsSync(absolute)) fs.mkdirSync(absolute, { recursive: true, mode: 0o700 });
if (!fs.existsSync(absolute)) {
if (!create) return absolute;
fs.mkdirSync(absolute, { recursive: true, mode: 0o700 });
}
const stat = fs.lstatSync(absolute);
if (stat.isSymbolicLink() || !stat.isDirectory()) fail('CODEX_HOME must be a real directory');
if (typeof process.getuid === 'function' && stat.uid !== process.getuid()) fail('CODEX_HOME must be owned by the current user');
Expand Down Expand Up @@ -111,8 +115,12 @@ function commandEnvironment() {
return env;
}

function codexExecutable() {
return process.env.JARVOS_CODEX_EXECUTABLE || 'codex';
}

function runCodex(args, { expectJson = false } = {}) {
const result = spawnSync(process.env.JARVOS_CODEX_EXECUTABLE || 'codex', args, {
const result = spawnSync(codexExecutable(), args, {
cwd: ROOT,
env: commandEnvironment(),
encoding: 'utf8',
Expand Down Expand Up @@ -190,7 +198,32 @@ function ensureApprovedEvidence() {
const validation = validateCodexConformanceReceipt(conformance, { capability });
if (!validation.ok) fail('shipped Codex provider conformance receipt is not approved');
if (capability.admission !== 'supported' || capability.activation.candidateOnly !== false) fail('Compound Engineering provider is not admitted for activation');
return capability;
return { capability, conformance };
}

function readLiveCodexVersion() {
const result = spawnSync(codexExecutable(), ['--version'], {
cwd: ROOT,
env: commandEnvironment(),
encoding: 'utf8',
timeout: 30_000,
maxBuffer: 128 * 1024,
});
if (result.error || result.status !== 0) fail('could not read the running Codex CLI version');
const output = `${result.stdout || ''}\n${result.stderr || ''}`;
const versions = output.match(/\b\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?\b/g) || [];
if (versions.length !== 1 || !CODEX_SEMVER.test(versions[0])) fail('running Codex CLI returned an unusable version');
return versions[0];
}

function assertLiveCodexVersion(conformance) {
const expected = conformance?.discovery?.codexVersion;
if (typeof expected !== 'string' || !CODEX_SEMVER.test(expected)) fail('Codex provider conformance has no valid covered CLI version');
const actual = readLiveCodexVersion();
if (actual !== expected) {
fail(`managed Compound Engineering activation is not covered for Codex CLI ${actual}; conformance covers ${expected}. Update Codex or repeat the disposable conformance validation before retrying`);
}
return actual;
}

function stateMatchesInstallation(state, current) {
Expand Down Expand Up @@ -299,7 +332,7 @@ function activate({ capability, statePath }) {
}
}

function rollback({ capability, statePath }) {
function rollback({ statePath }) {
const state = readState(statePath);
if (!state) {
console.log('No jarvOS-owned Compound Engineering activation was found; preserving the Codex profile.');
Expand All @@ -314,16 +347,28 @@ function main() {
const mode = process.env.JARVOS_CODEX_PROVIDER_MODE
|| (process.env.JARVOS_PROFILE === 'codex' ? 'new-managed' : 'existing');
if (!SAFE_MODE.has(mode)) fail('JARVOS_CODEX_PROVIDER_MODE must be existing, new-managed, opt-in, or disabled');
const profile = assertProfileDirectory(process.env.CODEX_HOME || path.join(process.env.HOME || '', '.codex'));
const rollbackRequested = process.env.JARVOS_MANAGED_HARNESS_ROLLBACK === '1';
const preflightRequested = process.env.JARVOS_CODEX_PROVIDER_PREFLIGHT === '1';
const managedMode = mode === 'new-managed' || mode === 'opt-in';
if (preflightRequested && !managedMode) fail('Codex provider preflight requires new-managed or opt-in mode');
const profile = assertProfileDirectory(
process.env.CODEX_HOME || path.join(process.env.HOME || '', '.codex'),
{ create: !preflightRequested && !rollbackRequested },
);
process.env.CODEX_HOME = profile;
const statePath = path.join(profile, 'jarvos-compound-engineering.state.json');
const capability = ensureApprovedEvidence();
if (process.env.JARVOS_MANAGED_HARNESS_ROLLBACK === '1') return rollback({ capability, statePath });
if (rollbackRequested) return rollback({ statePath });
const evidence = ensureApprovedEvidence();
const liveCodexVersion = managedMode ? assertLiveCodexVersion(evidence.conformance) : undefined;
if (preflightRequested) {
console.log(`Codex ${liveCodexVersion} is covered by the reviewed Compound Engineering conformance receipt; no provider changes were made.`);
return { status: 'preflight', codexVersion: liveCodexVersion };
}
if (mode === 'existing' || mode === 'disabled') {
console.log(`Compound Engineering provider setup is ${mode === 'disabled' ? 'disabled' : 'preserve-only'} for this Codex profile.`);
return { status: mode };
}
return activate({ capability, statePath });
return activate({ capability: evidence.capability, statePath });
}

if (require.main === module) {
Expand All @@ -337,7 +382,9 @@ if (require.main === module) {

module.exports = {
activate,
assertLiveCodexVersion,
discover,
main,
readLiveCodexVersion,
rollback,
};
Loading
Loading