From 0fd95e4bcb70af198296ab411542e394898bef25 Mon Sep 17 00:00:00 2001 From: "Mark E. DeYoung" Date: Sat, 29 Aug 2026 11:21:06 +0200 Subject: [PATCH 1/6] Add opaque private contract loader transport --- scripts/jules.py | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/scripts/jules.py b/scripts/jules.py index 8bd427a..303c793 100644 --- a/scripts/jules.py +++ b/scripts/jules.py @@ -18,6 +18,7 @@ ROOT = Path(__file__).resolve().parents[1] TASK_REGISTRY = ROOT / "config" / "tasks.json" ID_RE = re.compile(r"^[a-z0-9][a-z0-9._-]{0,63}$") +PRIVATE_CONTRACT_TASK = "private-contract-v1" def fail(message: str) -> NoReturn: @@ -44,8 +45,6 @@ def request(method: str, path: str, body: dict | None = None) -> dict: payload = response.read() return json.loads(payload) if payload else {} except urllib.error.HTTPError as exc: - # Do not echo provider response bodies in a public Actions log; they can - # contain private source names or other account metadata. fail(f"Jules API request failed with HTTP {exc.code}") except urllib.error.URLError: fail("Jules API request failed") @@ -79,7 +78,7 @@ def resolve_target(target_id: str) -> tuple[str, str]: return repository, branch -def load_task(task_id: str) -> str: +def load_task(task_id: str, correlation: str | None = None) -> str: if not ID_RE.fullmatch(task_id): fail("Invalid task identifier") try: @@ -99,7 +98,15 @@ def load_task(task_id: str) -> str: fail("Task registry entry violates path policy") if not path.is_file(): fail("Approved task file is unavailable") - return path.read_text(encoding="utf-8") + prompt = path.read_text(encoding="utf-8") + + if task_id == PRIVATE_CONTRACT_TASK: + if correlation is None or not ID_RE.fullmatch(correlation): + fail("Private-contract task requires a valid opaque correlation identifier") + prompt = prompt.replace("{{CORRELATION}}", correlation) + elif correlation: + fail("Correlation input is only valid for the private-contract task") + return prompt def list_sources() -> list[dict]: @@ -132,7 +139,7 @@ def resolve_source(repo_full_name: str) -> dict: def dispatch(args: argparse.Namespace) -> None: repository, branch = resolve_target(args.target) - prompt = load_task(args.task) + prompt = load_task(args.task, args.correlation) source = resolve_source(repository) body = { "prompt": prompt, @@ -146,21 +153,17 @@ def dispatch(args: argparse.Namespace) -> None: } result = request("POST", "/sessions", body) session_id = str(result.get("id") or "") - correlation = hashlib.sha256(session_id.encode("utf-8")).hexdigest()[:12] if session_id else None - # Public workflow output intentionally omits repository, branch, session ID, - # provider URL, prompt, source identifier, activities, patches and PR URLs. + provider_correlation = hashlib.sha256(session_id.encode("utf-8")).hexdigest()[:12] if session_id else None print(json.dumps({ "accepted": bool(session_id), "target": args.target, "task": args.task, - "correlation": correlation, + "request_correlation": args.correlation, + "provider_correlation": provider_correlation, }, indent=2)) def status(args: argparse.Namespace) -> None: - # Intended for trusted/local use only. There is deliberately no public - # GitHub Actions status workflow because its output can disclose private - # target and PR metadata. session_id = args.session.removeprefix("sessions/") result = request("GET", f"/sessions/{urllib.parse.quote(session_id, safe='')}") prs = [] @@ -183,6 +186,7 @@ def main() -> None: p = sub.add_parser("dispatch", help="create an approved Jules session") p.add_argument("--target", required=True, help="approved opaque target ID") p.add_argument("--task", required=True, help="approved task ID") + p.add_argument("--correlation", help="opaque private-contract locator") p.set_defaults(func=dispatch) p = sub.add_parser("status", help="retrieve a Jules session result (trusted/local use)") From 75c0d6eba76c5ad10a000324d7387040aa090189 Mon Sep 17 00:00:00 2001 From: "Mark E. DeYoung" Date: Sat, 29 Aug 2026 11:21:19 +0200 Subject: [PATCH 2/6] Add generic private contract loader task --- tasks/private-contract-v1.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 tasks/private-contract-v1.md diff --git a/tasks/private-contract-v1.md b/tasks/private-contract-v1.md new file mode 100644 index 0000000..edbe165 --- /dev/null +++ b/tasks/private-contract-v1.md @@ -0,0 +1,14 @@ +# Private contract loader v1 + +Open `.agent/transport/{{CORRELATION}}.json` in the already-authorized target repository. + +Treat that transport record only as a locator. Follow its `dispatch_ref` to the authoritative private delegation record, then follow that record's `objective_ref` and execute exactly that private task. + +Rules: + +- do not infer or expand authority beyond the private delegation/task records; +- do not copy private repository names, private task content, source URLs, artifact identities, provider metadata, or result details into public repositories; +- obey the private task's egress, validator, attempt, and completion policies; +- do not weaken validation or classification controls to obtain a passing result; +- produce the durable result in the target repository as required by the private task; +- if the private locator/dispatch/task record is missing or malformed, stop without improvising. From 4280b3adff8ab23f4ff46425e95dadae4b5c5077 Mon Sep 17 00:00:00 2001 From: "Mark E. DeYoung" Date: Sat, 29 Aug 2026 11:21:28 +0200 Subject: [PATCH 3/6] Register generic private contract loader --- config/tasks.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/config/tasks.json b/config/tasks.json index 3453859..204c110 100644 --- a/config/tasks.json +++ b/config/tasks.json @@ -1,3 +1,4 @@ { - "phase0-private-repo-smoke-test": "tasks/phase0-private-repo-smoke-test.md" + "phase0-private-repo-smoke-test": "tasks/phase0-private-repo-smoke-test.md", + "private-contract-v1": "tasks/private-contract-v1.md" } From d3c0a6a2889af1c8ebaa800e079d3c23baacb528 Mon Sep 17 00:00:00 2001 From: "Mark E. DeYoung" Date: Sat, 29 Aug 2026 11:21:45 +0200 Subject: [PATCH 4/6] Accept opaque private contract correlation --- .github/workflows/jules-dispatch.yml | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/.github/workflows/jules-dispatch.yml b/.github/workflows/jules-dispatch.yml index ccd4a73..1be8775 100644 --- a/.github/workflows/jules-dispatch.yml +++ b/.github/workflows/jules-dispatch.yml @@ -14,14 +14,17 @@ on: required: true default: "phase0-private-repo-smoke-test" type: string + correlation: + description: "Opaque private-contract locator; only used by private-contract-v1" + required: false + default: "" + type: string permissions: contents: read jobs: dispatch: - # Defense in depth. Configure GitHub Workflow Execution Protection as the - # primary actor gate; this check prevents accidental execution by others. if: github.actor == 'mark-e-deyoung' runs-on: ubuntu-latest timeout-minutes: 2 @@ -36,7 +39,10 @@ jobs: JULES_TARGETS_JSON: ${{ secrets.JULES_TARGETS_JSON }} TARGET_ID: ${{ inputs.target }} TASK_ID: ${{ inputs.task }} + CORRELATION_ID: ${{ inputs.correlation }} run: | - python3 scripts/jules.py dispatch \ - --target "$TARGET_ID" \ - --task "$TASK_ID" + args=(dispatch --target "$TARGET_ID" --task "$TASK_ID") + if [ -n "$CORRELATION_ID" ]; then + args+=(--correlation "$CORRELATION_ID") + fi + python3 scripts/jules.py "${args[@]}" From dfa291a43d60d04825f543ffbf28b08e94a5f8c5 Mon Sep 17 00:00:00 2001 From: "Mark E. DeYoung" Date: Sat, 29 Aug 2026 11:24:14 +0200 Subject: [PATCH 5/6] Allow additive private target policy secret --- scripts/jules.py | 50 +++++++++++++++++++++--------------------------- 1 file changed, 22 insertions(+), 28 deletions(-) diff --git a/scripts/jules.py b/scripts/jules.py index 303c793..1f10c04 100644 --- a/scripts/jules.py +++ b/scripts/jules.py @@ -34,11 +34,7 @@ def request(method: str, path: str, body: dict | None = None) -> dict: BASE_URL + path, data=data, method=method, - headers={ - "x-goog-api-key": key, - "Content-Type": "application/json", - "Accept": "application/json", - }, + headers={"x-goog-api-key": key, "Content-Type": "application/json", "Accept": "application/json"}, ) try: with urllib.request.urlopen(req, timeout=30) as response: @@ -50,17 +46,28 @@ def request(method: str, path: str, body: dict | None = None) -> dict: fail("Jules API request failed") -def load_targets() -> dict[str, dict]: - raw = os.environ.get("JULES_TARGETS_JSON") +def parse_target_policy(name: str, required: bool) -> dict[str, dict]: + raw = os.environ.get(name) if not raw: - fail("Jules target policy is not configured") + if required: + fail("Jules target policy is not configured") + return {} try: - targets = json.loads(raw) + value = json.loads(raw) except json.JSONDecodeError: - fail("Jules target policy is invalid JSON") - if not isinstance(targets, dict): - fail("Jules target policy must be a JSON object") - return targets + fail(f"{name} is invalid JSON") + if not isinstance(value, dict): + fail(f"{name} must be a JSON object") + return value + + +def load_targets() -> dict[str, dict]: + targets = parse_target_policy("JULES_TARGETS_JSON", required=True) + extra = parse_target_policy("JULES_TARGETS_JSON_EXTRA", required=False) + overlap = set(targets).intersection(extra) + if overlap: + fail("Extra target policy may not override an existing target alias") + return {**targets, **extra} def resolve_target(target_id: str) -> tuple[str, str]: @@ -88,7 +95,6 @@ def load_task(task_id: str, correlation: str | None = None) -> str: rel = registry.get(task_id) if isinstance(registry, dict) else None if not isinstance(rel, str): fail("Task is not approved") - tasks_root = (ROOT / "tasks").resolve() candidate = ROOT / rel if candidate.is_symlink(): @@ -99,7 +105,6 @@ def load_task(task_id: str, correlation: str | None = None) -> str: if not path.is_file(): fail("Approved task file is unavailable") prompt = path.read_text(encoding="utf-8") - if task_id == PRIVATE_CONTRACT_TASK: if correlation is None or not ID_RE.fullmatch(correlation): fail("Private-contract task requires a valid opaque correlation identifier") @@ -144,10 +149,7 @@ def dispatch(args: argparse.Namespace) -> None: body = { "prompt": prompt, "title": f"agent-dispatch:{args.target}:{args.task}", - "sourceContext": { - "source": source["name"], - "githubRepoContext": {"startingBranch": branch}, - }, + "sourceContext": {"source": source["name"], "githubRepoContext": {"startingBranch": branch}}, "requirePlanApproval": False, "automationMode": "AUTO_CREATE_PR", } @@ -171,28 +173,20 @@ def status(args: argparse.Namespace) -> None: pr = output.get("pullRequest") if pr: prs.append({"url": pr.get("url"), "title": pr.get("title")}) - print(json.dumps({ - "id": result.get("id"), - "state": result.get("state"), - "url": result.get("url"), - "pullRequests": prs, - }, indent=2)) + print(json.dumps({"id": result.get("id"), "state": result.get("state"), "url": result.get("url"), "pullRequests": prs}, indent=2)) def main() -> None: parser = argparse.ArgumentParser(description=__doc__) sub = parser.add_subparsers(dest="command", required=True) - p = sub.add_parser("dispatch", help="create an approved Jules session") p.add_argument("--target", required=True, help="approved opaque target ID") p.add_argument("--task", required=True, help="approved task ID") p.add_argument("--correlation", help="opaque private-contract locator") p.set_defaults(func=dispatch) - p = sub.add_parser("status", help="retrieve a Jules session result (trusted/local use)") p.add_argument("session") p.set_defaults(func=status) - args = parser.parse_args() args.func(args) From 4312bd844ffec2c801225a685de7aadd5d092437 Mon Sep 17 00:00:00 2001 From: "Mark E. DeYoung" Date: Sat, 29 Aug 2026 11:24:32 +0200 Subject: [PATCH 6/6] Load additive target policy secret --- .github/workflows/jules-dispatch.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/jules-dispatch.yml b/.github/workflows/jules-dispatch.yml index 1be8775..fbb04e2 100644 --- a/.github/workflows/jules-dispatch.yml +++ b/.github/workflows/jules-dispatch.yml @@ -37,6 +37,7 @@ jobs: env: JULES_API_KEY: ${{ secrets.JULES_API_KEY }} JULES_TARGETS_JSON: ${{ secrets.JULES_TARGETS_JSON }} + JULES_TARGETS_JSON_EXTRA: ${{ secrets.JULES_TARGETS_JSON_EXTRA }} TARGET_ID: ${{ inputs.target }} TASK_ID: ${{ inputs.task }} CORRELATION_ID: ${{ inputs.correlation }}