From 4ebb123ba36ab2c97403de18dc8197e464d0c03e Mon Sep 17 00:00:00 2001 From: Jason Woltje Date: Wed, 2 Sep 2026 21:19:22 -0500 Subject: [PATCH] feat(adapters): sanctioned mission directives injection (#17) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - load-contracts.sh: MOSAIC_MISSION_FILE (readable) appends a MISSION (runtime) section — objective + directives — after the immutable contracts; unreadable path is a hard error, absent env changes nothing - mosaic-task.mjs: exports MOSAIC_MISSION_FILE as the run snapshot's container path (/var/lib/mosaic/runs//mission.json), with an outside-dataRoot guard; also exports the configured adapter Verified: contract-only prompt has no mission section; mission-bearing run shows objective + directives in the generated prompt, snapshot recorded, real provider returns exactly MOSAIC_HELLO_OK. Closes #17 --- scripts/mosaic-task.mjs | 15 ++++++++++++++- scripts/test-config.sh | 14 ++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/scripts/mosaic-task.mjs b/scripts/mosaic-task.mjs index 12610036..b224cf43 100755 --- a/scripts/mosaic-task.mjs +++ b/scripts/mosaic-task.mjs @@ -210,12 +210,25 @@ function runTask(taskFile) { const startedAt = new Date(); const stderrFile = path.join(runDir, "stderr.txt"); + + // Sanctioned mission injection: point the container at the run snapshot's + // CONTAINER path (dataRoot maps to /var/lib/mosaic in the image). + const spawnEnv = { ...process.env }; + spawnEnv.MOSAIC_ADAPTER = resolved.execution.adapter; + if (task.missionSnapshot) { + const relative = path.relative(resolved.dataRoot, runDir); + if (relative.startsWith("..") || path.isAbsolute(relative)) { + fail(4, `run directory is outside the configured dataRoot: ${runDir}`); + } + spawnEnv.MOSAIC_MISSION_FILE = `/var/lib/mosaic/${relative.split(path.sep).join("/")}/mission.json`; + } + const proc = spawnSync( "docker", ["compose", "run", "--rm", "-T", "mosaic-agent", task.prompt], { cwd: PROJECT_ROOT, - env: process.env, // MOSAIC_DATA_ROOT / MOSAIC_PROVIDER / MOSAIC_MODEL resolved by run-task.sh + env: spawnEnv, input: "", // stdin detached: print mode must never wait on a terminal (see issue #5) encoding: "utf8", maxBuffer: 16 * 1024 * 1024, diff --git a/scripts/test-config.sh b/scripts/test-config.sh index cc5e34b5..b1b5d5a1 100755 --- a/scripts/test-config.sh +++ b/scripts/test-config.sh @@ -40,6 +40,20 @@ cfg() { printf '%s' "$2" > "$SANDBOX/$1"; } DATA_ROOT="$SANDBOX/data" +# --- adapter selection (M4) --- +cfg default-adapter.json '{"configVersion":1,"environment":"development","dataRoot":"'$DATA_ROOT'","execution":{"backend":"docker","provider":"zai","model":"m"}}' +MOSAIC_CONFIG="$SANDBOX/default-adapter.json" $CONFIG_OP validate | grep -q '"adapter": "pi"' +check "absent adapter defaults to pi" $? + +cfg mock-adapter.json '{"configVersion":1,"environment":"development","dataRoot":"'$DATA_ROOT'","execution":{"backend":"docker","provider":"zai","model":"m","adapter":"mock"}}' +expect_exit "adapter mock validates" 0 -- env MOSAIC_CONFIG="$SANDBOX/mock-adapter.json" $CONFIG_OP validate + +cfg bad-adapter.json '{"configVersion":1,"environment":"development","dataRoot":"'$DATA_ROOT'","execution":{"backend":"docker","provider":"zai","model":"m","adapter":"claude"}}' +expect_exit "unsupported adapter exits 2" 2 -- env MOSAIC_CONFIG="$SANDBOX/bad-adapter.json" $CONFIG_OP validate + +MOSAIC_CONFIG="$SANDBOX/mock-adapter.json" $CONFIG_OP env | grep -q "MOSAIC_ADAPTER='mock'" +check "env exports adapter" $? + # --- bootstrap --- rm -f "$SANDBOX/config.json" expect_exit "bootstrap creates default when absent" 0 -- \