From 60676577514500a0615af8925cda055ef3beedb0 Mon Sep 17 00:00:00 2001 From: "Mark E. DeYoung" <1854350+mark-e-deyoung@users.noreply.github.com> Date: Sat, 12 Sep 2026 11:57:13 +0200 Subject: [PATCH 1/4] exp: add non-mutating Jules target/task readiness preflight --- scripts/jules_preflight.py | 44 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 scripts/jules_preflight.py diff --git a/scripts/jules_preflight.py b/scripts/jules_preflight.py new file mode 100644 index 0000000..d242d0d --- /dev/null +++ b/scripts/jules_preflight.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 +"""Sanitized, non-mutating readiness preflight for approved Jules dispatch inputs. + +The preflight deliberately reuses the hardened dispatcher policy and provider +source-resolution functions. It validates only that the opaque target and task +are admitted and that the target has exactly one Jules Source. It never creates +a Jules session and never prints repository, branch, source, prompt, credential, +or provider metadata. +""" + +from __future__ import annotations + +import argparse +import json + +from jules import load_task, resolve_source, resolve_target + + +def preflight(target: str, task: str) -> dict: + repository, _branch = resolve_target(target) + load_task(task) + resolve_source(repository) + return { + "ready": True, + "target": target, + "task": task, + "target_policy": "approved", + "task_policy": "approved", + "jules_source": "available-unique", + "session_created": False, + "authority_mutation": False, + } + + +def main() -> None: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--target", required=True, help="approved opaque target ID") + parser.add_argument("--task", required=True, help="approved task ID") + args = parser.parse_args() + print(json.dumps(preflight(args.target, args.task), indent=2, sort_keys=True)) + + +if __name__ == "__main__": + main() From fdc08508f3fe61e8e8e1d7c2596f0c534d6d5c0f Mon Sep 17 00:00:00 2001 From: "Mark E. DeYoung" <1854350+mark-e-deyoung@users.noreply.github.com> Date: Sat, 12 Sep 2026 11:57:21 +0200 Subject: [PATCH 2/4] exp: add protected Jules readiness preflight workflow --- .github/workflows/jules-preflight.yml | 42 +++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 .github/workflows/jules-preflight.yml diff --git a/.github/workflows/jules-preflight.yml b/.github/workflows/jules-preflight.yml new file mode 100644 index 0000000..c14fd8b --- /dev/null +++ b/.github/workflows/jules-preflight.yml @@ -0,0 +1,42 @@ +name: Jules Readiness Preflight +run-name: Jules preflight by ${{ github.actor }} + +on: + workflow_dispatch: + inputs: + target: + description: "Approved opaque target ID" + required: true + default: "phase0" + type: string + task: + description: "Approved task ID" + required: true + default: "phase0-private-repo-smoke-test" + type: string + +permissions: + contents: read + +jobs: + preflight: + # Match the dispatch actor gate. The environment retains the existing + # credential/policy boundary; this workflow performs no provider mutation. + if: github.actor == 'mark-e-deyoung' + runs-on: ubuntu-latest + timeout-minutes: 2 + environment: jules-dispatch + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 + with: + persist-credentials: false + - name: Check approved target/task and Jules Source readiness + env: + JULES_API_KEY: ${{ secrets.JULES_API_KEY }} + JULES_TARGETS_JSON: ${{ secrets.JULES_TARGETS_JSON }} + TARGET_ID: ${{ inputs.target }} + TASK_ID: ${{ inputs.task }} + run: | + python3 scripts/jules_preflight.py \ + --target "$TARGET_ID" \ + --task "$TASK_ID" From 6bbd5fbd01a29e08b3ba2148ad6c4839f32576f1 Mon Sep 17 00:00:00 2001 From: "Mark E. DeYoung" <1854350+mark-e-deyoung@users.noreply.github.com> Date: Sat, 12 Sep 2026 11:57:36 +0200 Subject: [PATCH 3/4] test: falsify Jules preflight leakage and mutation --- tests/test_jules_preflight.py | 76 +++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 tests/test_jules_preflight.py diff --git a/tests/test_jules_preflight.py b/tests/test_jules_preflight.py new file mode 100644 index 0000000..ba5df38 --- /dev/null +++ b/tests/test_jules_preflight.py @@ -0,0 +1,76 @@ +import os +import sys +import unittest +from pathlib import Path +from unittest.mock import patch + + +ROOT = Path(__file__).resolve().parents[1] +SCRIPTS = ROOT / "scripts" +sys.path.insert(0, str(SCRIPTS)) + +import jules # noqa: E402 +import jules_preflight # noqa: E402 + + +class JulesPreflightTests(unittest.TestCase): + def test_preflight_returns_only_sanitized_readiness(self): + private_repo = "private-owner/secret-repository" + private_branch = "secret-branch" + private_source = {"name": "sources/secret-provider-id", "githubRepo": {"owner": "private-owner", "repo": "secret-repository"}} + + with ( + patch.object(jules_preflight, "resolve_target", return_value=(private_repo, private_branch)), + patch.object(jules_preflight, "load_task", return_value="PRIVATE PROMPT CONTENT"), + patch.object(jules_preflight, "resolve_source", return_value=private_source), + ): + result = jules_preflight.preflight("opaque-target", "approved-task") + + self.assertEqual( + result, + { + "ready": True, + "target": "opaque-target", + "task": "approved-task", + "target_policy": "approved", + "task_policy": "approved", + "jules_source": "available-unique", + "session_created": False, + "authority_mutation": False, + }, + ) + rendered = repr(result) + self.assertNotIn(private_repo, rendered) + self.assertNotIn(private_branch, rendered) + self.assertNotIn("secret-provider-id", rendered) + self.assertNotIn("PRIVATE PROMPT CONTENT", rendered) + + def test_preflight_does_not_call_provider_session_creation(self): + with ( + patch.object(jules_preflight, "resolve_target", return_value=("owner/repo", "main")), + patch.object(jules_preflight, "load_task", return_value="task"), + patch.object(jules_preflight, "resolve_source", return_value={"name": "sources/1"}), + patch.object(jules, "request") as provider_request, + ): + result = jules_preflight.preflight("opaque-target", "approved-task") + + self.assertTrue(result["ready"]) + provider_request.assert_not_called() + + def test_real_target_policy_rejects_unapproved_alias_without_disclosure(self): + old = os.environ.get("JULES_TARGETS_JSON") + os.environ["JULES_TARGETS_JSON"] = '{"approved":{"repository":"owner/private","branch":"main"}}' + try: + with self.assertRaises(SystemExit) as raised: + jules.resolve_target("not-approved") + self.assertEqual(str(raised.exception), "Target is not approved") + self.assertNotIn("owner/private", str(raised.exception)) + finally: + if old is None: + os.environ.pop("JULES_TARGETS_JSON", None) + else: + os.environ["JULES_TARGETS_JSON"] = old + + +if __name__ == "__main__": + unittest.main() From f41d084c03c3a4dd99163f3836b07ff78697e941 Mon Sep 17 00:00:00 2001 From: "Mark E. DeYoung" <1854350+mark-e-deyoung@users.noreply.github.com> Date: Sat, 12 Sep 2026 11:57:43 +0200 Subject: [PATCH 4/4] test: run secret-free Jules preflight safety checks --- .github/workflows/jules-preflight-tests.yml | 30 +++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 .github/workflows/jules-preflight-tests.yml diff --git a/.github/workflows/jules-preflight-tests.yml b/.github/workflows/jules-preflight-tests.yml new file mode 100644 index 0000000..5452c6b --- /dev/null +++ b/.github/workflows/jules-preflight-tests.yml @@ -0,0 +1,30 @@ +name: Jules Preflight Tests + +on: + pull_request: + paths: + - 'scripts/jules.py' + - 'scripts/jules_preflight.py' + - 'tests/test_jules_preflight.py' + - '.github/workflows/jules-preflight.yml' + - '.github/workflows/jules-preflight-tests.yml' + workflow_dispatch: + +permissions: + contents: read + +jobs: + test: + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 + with: + persist-credentials: false + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + - name: Compile dispatcher scripts + run: python3 -m py_compile scripts/jules.py scripts/jules_preflight.py + - name: Run preflight safety tests + run: python3 -m unittest -v tests.test_jules_preflight