From 2be0bb286e9e181b060e3dcfae7d18501543ae7e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 19 Aug 2026 21:28:15 +0900 Subject: [PATCH 1/7] feat: enforce adaptive orchestration defaults --- .../contextual-orchestrator-policy.yml | 54 ++++++ CHANGELOG.md | 1 + ...aptive-orchestration-default-governance.md | 37 ++++ .../check_contextual_orchestrator_defaults.py | 171 ++++++++++++++++++ .../test_contextual_orchestrator_defaults.py | 149 +++++++++++++++ 5 files changed, 412 insertions(+) create mode 100644 .github/workflows/contextual-orchestrator-policy.yml create mode 100644 docs/adr/0012-adaptive-orchestration-default-governance.md create mode 100644 scripts/ci/check_contextual_orchestrator_defaults.py create mode 100644 tests/test_contextual_orchestrator_defaults.py diff --git a/.github/workflows/contextual-orchestrator-policy.yml b/.github/workflows/contextual-orchestrator-policy.yml new file mode 100644 index 0000000000..90b7b9b8f5 --- /dev/null +++ b/.github/workflows/contextual-orchestrator-policy.yml @@ -0,0 +1,54 @@ +name: Contextual Orchestrator Adaptive Default + +on: + workflow_call: + inputs: + governance_sha: + description: Exact central .github commit containing the scanner. + required: true + type: string + target_ref: + description: Exact target repository commit to scan. + required: true + type: string + +permissions: + contents: read + +jobs: + adaptive-default-policy: + name: contextual-orchestrator adaptive default + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Checkout target commit + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.2.2 + with: + repository: ${{ github.repository }} + ref: ${{ inputs.target_ref }} + path: target + persist-credentials: false + + - name: Checkout immutable governance source + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.2.2 + with: + repository: ContextualWisdomLab/.github + ref: ${{ inputs.governance_sha }} + path: governance + fetch-depth: 1 + persist-credentials: false + + - name: Enforce explicit adaptive orchestration defaults + run: >- + python governance/scripts/ci/check_contextual_orchestrator_defaults.py + target + --json-output "$RUNNER_TEMP/contextual-orchestrator-policy.json" + + - name: Upload bounded policy evidence + if: always() + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: contextual-orchestrator-policy-${{ inputs.target_ref }} + path: ${{ runner.temp }}/contextual-orchestrator-policy.json + if-no-files-found: ignore + retention-days: 14 diff --git a/CHANGELOG.md b/CHANGELOG.md index fd1aebf43f..5724be42c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ Semantic Versioning where the repository publishes a release. - Added a dedicated Clearfolio hourly caller that invokes the product-neutral central scheduler with the exact repository, protected base branch, one-dispatch budget, one-hour retry floor, single-flight concurrency, and only the established scheduler credentials. - Added a dedicated DiskSage hourly caller that invokes the same product-neutral RCA and remediation-feasibility scheduler with an exact repository target, one-dispatch budget, two-hour same-head retry floor, non-cancelling single-flight heartbeat, and explicit established scheduler credentials. - Added a dedicated fast-mlsirm hourly caller that preserves Rust-owned psychometric arithmetic while dispatching at most one exact-head, root-cause-driven repair with a two-hour same-head retry floor. +- Added a read-only, exact-ref reusable policy workflow and 100%-covered scanner that rejects production contextual-orchestrator route pinning or omitted adaptive mode, with narrow reviewed path exceptions and bounded evidence. ### Changed diff --git a/docs/adr/0012-adaptive-orchestration-default-governance.md b/docs/adr/0012-adaptive-orchestration-default-governance.md new file mode 100644 index 0000000000..0579ae49eb --- /dev/null +++ b/docs/adr/0012-adaptive-orchestration-default-governance.md @@ -0,0 +1,37 @@ +# ADR-0012: Govern adaptive orchestration defaults centrally + +- Status: Accepted +- Date: 2026-08-19 + +## Context + +Consumers can silently bypass contextual-orchestrator by forcing a fixed +`route` mode or by omitting the mode from a chat request. That makes the +quality-before-cost policy in the consumer contract unenforceable by review +alone. + +## Decision + +The central repository ships a small source scanner and a reusable workflow. +Production source that names contextual-orchestrator and constructs a chat +request must explicitly select `auto`; fixed-mode exceptions are narrow, +path-scoped, and declared in `.cwl/contextual_orchestrator_policy.json`. +The workflow scans an exact target commit and checks out the scanner from an +explicit central commit. It has read-only contents permission, no repository +write step, and publishes bounded evidence only. + +`auto` delegates topology to contextual-orchestrator: capability, quality, and +safety are satisfied before trustworthy known cost; absent or invalid prices +are unpriced, not free. The scanner is a regression guard, not a semantic +quality or SLO proof. + +## Consequences + +Consumers must call the reusable workflow with both the exact target commit and +the exact central governance commit. A deliberate fixed route requires a +reviewed path exception and separate benchmark/rollback evidence. The scanner +does not inspect tests, documentation, examples, migrations, or vendor code. + +## References + +See the repository-level adaptive consumer rule in [AGENTS.md](../../AGENTS.md) and the operational review-boundary record in [hourly-review-repair.md](../doctoring/hourly-review-repair.md). diff --git a/scripts/ci/check_contextual_orchestrator_defaults.py b/scripts/ci/check_contextual_orchestrator_defaults.py new file mode 100644 index 0000000000..b3bb2c15d5 --- /dev/null +++ b/scripts/ci/check_contextual_orchestrator_defaults.py @@ -0,0 +1,171 @@ +#!/usr/bin/env python3 +"""Fail closed when production consumers bypass adaptive orchestration.""" + +from __future__ import annotations + +import argparse +import fnmatch +import json +import re +from dataclasses import dataclass +from pathlib import Path +from typing import Iterable, Sequence + + +SOURCE_SUFFIXES = frozenset( + {".py", ".js", ".mjs", ".cjs", ".ts", ".tsx", ".rs", ".go", ".java", ".kt", ".cs"} +) +EXCLUDED_PARTS = frozenset( + { + ".git", + ".github", + "build", + "dist", + "docs", + "examples", + "fixtures", + "migrations", + "node_modules", + "scripts", + "spec", + "specs", + "target", + "test", + "tests", + "vendor", + } +) +ORCHESTRATOR_MARKERS = ("contextual-orchestrator", "contextual_orchestrator") +CHAT_ENDPOINT_MARKERS = ("/v1/chat/completions", "/chat/completions", "chat/completions") +FORCED_ROUTE_PATTERN = re.compile( + r"(?:orchestration_mode|mode)(?:\s*:\s*str)?\s*[:=]\s*[\"']route[\"']", + re.IGNORECASE, +) +AUTO_PATTERN = re.compile( + r"(?:orchestration_mode|mode)(?:\s*:\s*str)?\s*[:=]\s*[\"']auto[\"']", + re.IGNORECASE, +) + + +@dataclass(frozen=True) +class Finding: + """One source-backed adaptive-orchestration policy violation.""" + + finding_code: str + source_path: str + message: str + + def as_dict(self) -> dict[str, str]: + """Return the stable JSON representation used by workflow evidence.""" + + return { + "finding_code": self.finding_code, + "source_path": self.source_path, + "message": self.message, + } + + +def _load_policy(root: Path) -> dict[str, list[str]]: + """Load narrowly scoped path exceptions, failing closed when malformed.""" + + path = root / ".cwl" / "contextual_orchestrator_policy.json" + if not path.exists(): + return {"allowed_fixed_mode_paths": [], "request_constructor_exemptions": []} + value = json.loads(path.read_text(encoding="utf-8")) + if not isinstance(value, dict): + raise ValueError("contextual_orchestrator_policy.json must contain an object") + policy: dict[str, list[str]] = {} + for key in ("allowed_fixed_mode_paths", "request_constructor_exemptions"): + entries = value.get(key, []) + if not isinstance(entries, list) or any(not isinstance(item, str) for item in entries): + raise ValueError(f"{key} must be an array of path globs") + policy[key] = entries + return policy + + +def _matches(path: str, patterns: Iterable[str]) -> bool: + """Return whether a relative source path matches an exception glob.""" + + return any(fnmatch.fnmatch(path, pattern) for pattern in patterns) + + +def _production_sources(root: Path) -> Iterable[Path]: + """Yield supported source files outside non-production path components.""" + + for path in sorted(root.rglob("*")): + if not path.is_file() or path.suffix.lower() not in SOURCE_SUFFIXES: + continue + relative = path.relative_to(root) + if {part.lower() for part in relative.parts} & EXCLUDED_PARTS: + continue + yield path + + +def inspect_repository(root: Path) -> list[Finding]: + """Return every forced-route or implicit-mode production violation.""" + + policy = _load_policy(root) + findings: list[Finding] = [] + for path in _production_sources(root): + relative = path.relative_to(root).as_posix() + text = path.read_text(encoding="utf-8", errors="strict") + lowered = text.lower() + if not any(marker in lowered for marker in ORCHESTRATOR_MARKERS): + continue + fixed_allowed = _matches(relative, policy["allowed_fixed_mode_paths"]) + constructor_exempt = _matches(relative, policy["request_constructor_exemptions"]) + if FORCED_ROUTE_PATTERN.search(text) and not fixed_allowed: + findings.append( + Finding( + "forced_single_route", + relative, + "production contextual-orchestrator code forces route instead of delegating to auto", + ) + ) + has_chat_request = any(marker in lowered for marker in CHAT_ENDPOINT_MARKERS) + names_gateway_model = "contextual-orchestrator" in lowered + if ( + has_chat_request + and names_gateway_model + and not AUTO_PATTERN.search(text) + and not constructor_exempt + and not fixed_allowed + ): + findings.append( + Finding( + "implicit_orchestration_mode", + relative, + "production chat request must explicitly select contextual-orchestrator auto", + ) + ) + return findings + + +def main(argv: Sequence[str] | None = None) -> int: + """Scan one repository, print bounded JSON, and return a policy status.""" + + parser = argparse.ArgumentParser() + parser.add_argument("repository_root", nargs="?", default=".") + parser.add_argument("--json-output") + args = parser.parse_args(argv) + root = Path(args.repository_root).resolve() + findings = inspect_repository(root) + rendered = json.dumps( + { + "policy_name": "contextual_orchestrator_adaptive_default", + "repository_root": str(root), + "finding_count": len(findings), + "findings": [finding.as_dict() for finding in findings], + }, + ensure_ascii=False, + indent=2, + sort_keys=True, + ) + print(rendered) + if args.json_output: + Path(args.json_output).write_text(rendered + "\n", encoding="utf-8") + return 1 if findings else 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tests/test_contextual_orchestrator_defaults.py b/tests/test_contextual_orchestrator_defaults.py new file mode 100644 index 0000000000..009652469f --- /dev/null +++ b/tests/test_contextual_orchestrator_defaults.py @@ -0,0 +1,149 @@ +"""Tests for the fail-closed adaptive-orchestration policy scanner.""" + +from __future__ import annotations + +import json +import runpy +import sys +from pathlib import Path + +import pytest + +from scripts.ci import check_contextual_orchestrator_defaults as policy + +ROOT = Path(__file__).parents[1] + + +def _write(root: Path, relative: str, content: str) -> None: + """Write one UTF-8 source fixture below a temporary repository root.""" + + path = root / relative + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(content, encoding="utf-8") + + +def test_inspect_repository_reports_forced_and_implicit_modes(tmp_path: Path) -> None: + """Production route and omitted mode are both reported with stable fields.""" + + _write( + tmp_path, + "src/forced.py", + 'endpoint = "/v1/chat/completions"\nmodel = "contextual-orchestrator"\nmode = "route"\n', + ) + _write( + tmp_path, + "src/implicit.ts", + "const endpoint = '/v1/chat/completions';\nconst body = { model: 'contextual-orchestrator' };\n", + ) + _write( + tmp_path, + "src/auto.rs", + 'json!({"model": "contextual-orchestrator", "orchestration_mode": "auto"});\n', + ) + _write(tmp_path, "docs/example.py", 'mode = "route"\n') + _write(tmp_path, "tests/example.py", 'mode = "route"\n') + _write(tmp_path, "src/readme.txt", 'contextual-orchestrator mode = "route"\n') + _write(tmp_path, "src/other.py", "value = 1\n") + + findings = policy.inspect_repository(tmp_path) + + assert [(item.finding_code, item.source_path) for item in findings] == [ + ("forced_single_route", "src/forced.py"), + ("implicit_orchestration_mode", "src/forced.py"), + ("implicit_orchestration_mode", "src/implicit.ts"), + ] + assert findings[0].as_dict()["message"].startswith("production contextual-orchestrator") + + +def test_policy_exceptions_are_path_scoped(tmp_path: Path) -> None: + """Fixed modes pass only when their exact paths are explicitly exempted.""" + + _write( + tmp_path, + "src/forced.py", + 'model = "contextual-orchestrator"\nmode = "route"\n', + ) + _write( + tmp_path, + "src/constructor.ts", + "const endpoint = '/v1/chat/completions';\nconst body = { model: 'contextual-orchestrator' };\n", + ) + _write( + tmp_path, + ".cwl/contextual_orchestrator_policy.json", + json.dumps( + { + "allowed_fixed_mode_paths": ["src/forced.py"], + "request_constructor_exemptions": ["src/constructor.ts"], + } + ), + ) + + assert policy.inspect_repository(tmp_path) == [] + + +def test_malformed_policy_fails_closed(tmp_path: Path) -> None: + """Malformed exception configuration raises instead of weakening the scan.""" + + path = tmp_path / ".cwl" / "contextual_orchestrator_policy.json" + path.parent.mkdir() + path.write_text("[]", encoding="utf-8") + with pytest.raises(ValueError, match="object"): + policy.inspect_repository(tmp_path) + + path.write_text(json.dumps({"allowed_fixed_mode_paths": [1]}), encoding="utf-8") + with pytest.raises(ValueError, match="allowed_fixed_mode_paths"): + policy.inspect_repository(tmp_path) + + +def test_main_writes_bounded_json_and_returns_status(tmp_path: Path, capsys) -> None: + """The CLI emits machine-readable evidence and a non-zero finding status.""" + + _write(tmp_path, "src/client.py", 'mode = "route"\nmodel = "contextual-orchestrator"\n') + output = tmp_path / "evidence.json" + + assert policy.main([str(tmp_path), "--json-output", str(output)]) == 1 + rendered = json.loads(output.read_text(encoding="utf-8")) + assert rendered["finding_count"] == 1 + assert json.loads(capsys.readouterr().out)["policy_name"] == ( + "contextual_orchestrator_adaptive_default" + ) + + _write(tmp_path, "src/clean.py", 'mode = "auto"\nmodel = "contextual-orchestrator"\n') + assert policy.main([str(tmp_path)]) == 1 + + +def test_source_read_errors_are_not_suppressed(tmp_path: Path) -> None: + """Invalid source bytes fail closed rather than being silently skipped.""" + + path = tmp_path / "src" / "broken.py" + path.parent.mkdir() + path.write_bytes(b'model = "contextual-orchestrator"\n\xff') + + with pytest.raises(UnicodeDecodeError): + policy.inspect_repository(tmp_path) + + +def test_module_entrypoint_returns_clean_status(tmp_path: Path, monkeypatch) -> None: + """The standalone scanner entrypoint exits successfully for a clean tree.""" + + monkeypatch.setattr(sys, "argv", [str(policy.__file__), str(tmp_path)]) + with pytest.raises(SystemExit) as exit_info: + runpy.run_path(str(policy.__file__), run_name="__main__") + assert exit_info.value.code == 0 + + +def test_reusable_workflow_is_exact_ref_read_only_governance() -> None: + """The reusable workflow pins both source identities and cannot mutate repos.""" + + workflow = (ROOT / ".github/workflows/contextual-orchestrator-policy.yml").read_text( + encoding="utf-8" + ) + assert "governance_sha:" in workflow + assert "target_ref:" in workflow + assert "ref: ${{ inputs.governance_sha }}" in workflow + assert "ref: ${{ inputs.target_ref }}" in workflow + assert "permissions:\n contents: read" in workflow + assert "contents: write" not in workflow + assert "persist-credentials: false" in workflow + assert "actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02" in workflow From 41bb2309de39c090d773ed5089729e2a3d4ae785 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 19 Aug 2026 21:39:55 +0900 Subject: [PATCH 2/7] fix(policy): enforce exact commit inputs --- .github/workflows/contextual-orchestrator-policy.yml | 12 ++++++++++++ tests/test_contextual_orchestrator_defaults.py | 2 ++ 2 files changed, 14 insertions(+) diff --git a/.github/workflows/contextual-orchestrator-policy.yml b/.github/workflows/contextual-orchestrator-policy.yml index 90b7b9b8f5..7c2683c23e 100644 --- a/.github/workflows/contextual-orchestrator-policy.yml +++ b/.github/workflows/contextual-orchestrator-policy.yml @@ -21,6 +21,18 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 10 steps: + - name: Validate exact commit inputs + env: + GOVERNANCE_SHA: ${{ inputs.governance_sha }} + TARGET_REF: ${{ inputs.target_ref }} + run: | + set -euo pipefail + if ! [[ "$GOVERNANCE_SHA" =~ ^[0-9a-fA-F]{40}$ ]] || + ! [[ "$TARGET_REF" =~ ^[0-9a-fA-F]{40}$ ]]; then + echo "::error::governance_sha and target_ref must be 40-character commit SHAs." + exit 1 + fi + - name: Checkout target commit uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.2.2 with: diff --git a/tests/test_contextual_orchestrator_defaults.py b/tests/test_contextual_orchestrator_defaults.py index 009652469f..71073a853c 100644 --- a/tests/test_contextual_orchestrator_defaults.py +++ b/tests/test_contextual_orchestrator_defaults.py @@ -141,6 +141,8 @@ def test_reusable_workflow_is_exact_ref_read_only_governance() -> None: ) assert "governance_sha:" in workflow assert "target_ref:" in workflow + assert '[[ "$GOVERNANCE_SHA" =~ ^[0-9a-fA-F]{40}$ ]]' in workflow + assert '[[ "$TARGET_REF" =~ ^[0-9a-fA-F]{40}$ ]]' in workflow assert "ref: ${{ inputs.governance_sha }}" in workflow assert "ref: ${{ inputs.target_ref }}" in workflow assert "permissions:\n contents: read" in workflow From d569bc53ab87b5b0e80a5f69559bf4e704edc6fa Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 19 Aug 2026 23:54:49 +0900 Subject: [PATCH 3/7] docs(ci): complete coordinator client contract --- scripts/ci/organization_commercial_readiness_loop.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/ci/organization_commercial_readiness_loop.py b/scripts/ci/organization_commercial_readiness_loop.py index c00cfa1e0a..d001bd7c25 100644 --- a/scripts/ci/organization_commercial_readiness_loop.py +++ b/scripts/ci/organization_commercial_readiness_loop.py @@ -239,6 +239,8 @@ class GitHubClient: """Use the GitHub CLI as an authenticated, bounded REST transport.""" def __init__(self, token: str, *, timeout_seconds: int = 60) -> None: + """Create a bounded GitHub client using the explicit coordinator token.""" + if not token: raise GitHubError("GH_TOKEN is required for organization coordination") self._token = token @@ -853,4 +855,4 @@ def main( if __name__ == "__main__": # pragma: no cover - exercised through main() - raise SystemExit(main()) \ No newline at end of file + raise SystemExit(main()) From 39da3cf23513aa7c6f73376dca5a45c8b611faa8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 15:40:13 +0900 Subject: [PATCH 4/7] fix(policy): bind evidence output without symlink traversal --- .../check_contextual_orchestrator_defaults.py | 18 +++++++++++++++++- tests/test_contextual_orchestrator_defaults.py | 14 ++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/scripts/ci/check_contextual_orchestrator_defaults.py b/scripts/ci/check_contextual_orchestrator_defaults.py index b3bb2c15d5..86ae6782be 100644 --- a/scripts/ci/check_contextual_orchestrator_defaults.py +++ b/scripts/ci/check_contextual_orchestrator_defaults.py @@ -6,6 +6,7 @@ import argparse import fnmatch import json +import os import re from dataclasses import dataclass from pathlib import Path @@ -141,6 +142,21 @@ def inspect_repository(root: Path) -> list[Finding]: return findings +def _write_json_output(path_value: str, rendered: str) -> None: + """Write evidence through a real parent and a no-follow output binding.""" + + requested = Path(path_value) + parent = requested.parent.resolve(strict=True) + target = parent / requested.name + if target.exists() and target.is_symlink(): + raise ValueError("json output must not be a symbolic link") + flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC + no_follow = getattr(os, "O_NOFOLLOW", 0) + descriptor = os.open(target, flags | no_follow, 0o600) + with os.fdopen(descriptor, "w", encoding="utf-8") as output: + output.write(rendered + "\n") + + def main(argv: Sequence[str] | None = None) -> int: """Scan one repository, print bounded JSON, and return a policy status.""" @@ -163,7 +179,7 @@ def main(argv: Sequence[str] | None = None) -> int: ) print(rendered) if args.json_output: - Path(args.json_output).write_text(rendered + "\n", encoding="utf-8") + _write_json_output(args.json_output, rendered) return 1 if findings else 0 diff --git a/tests/test_contextual_orchestrator_defaults.py b/tests/test_contextual_orchestrator_defaults.py index 71073a853c..64a5857a51 100644 --- a/tests/test_contextual_orchestrator_defaults.py +++ b/tests/test_contextual_orchestrator_defaults.py @@ -113,6 +113,20 @@ def test_main_writes_bounded_json_and_returns_status(tmp_path: Path, capsys) -> assert policy.main([str(tmp_path)]) == 1 +def test_json_output_rejects_symbolic_link(tmp_path: Path) -> None: + """Evidence cannot overwrite a path that resolves through a symbolic link.""" + + target = tmp_path / "target.json" + target.write_text("keep\n", encoding="utf-8") + output = tmp_path / "evidence.json" + output.symlink_to(target) + + with pytest.raises(ValueError, match="symbolic link"): + policy._write_json_output(str(output), "{}") + + assert target.read_text(encoding="utf-8") == "keep\n" + + def test_source_read_errors_are_not_suppressed(tmp_path: Path) -> None: """Invalid source bytes fail closed rather than being silently skipped.""" From a56faa3775011a63f3bfad3c3d8f6fcfbb508c37 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 15:45:29 +0900 Subject: [PATCH 5/7] fix(coordinator): protect report and summary outputs --- .../organization_commercial_readiness_loop.py | 19 ++++++++++++++++--- ...n_commercial_readiness_loop_coordinator.py | 15 +++++++++++++++ 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/scripts/ci/organization_commercial_readiness_loop.py b/scripts/ci/organization_commercial_readiness_loop.py index d001bd7c25..e144f0a1ff 100644 --- a/scripts/ci/organization_commercial_readiness_loop.py +++ b/scripts/ci/organization_commercial_readiness_loop.py @@ -57,6 +57,19 @@ class SnapshotChanged(RuntimeError): """Signal that a repository moved while one snapshot was materialized.""" +def _open_private_output(path: Path, *, append: bool) -> Any: + """Open an existing-parent output path without following its final link.""" + + parent = path.parent.resolve(strict=True) + target = parent / path.name + if target.exists() and target.is_symlink(): + raise ValueError(f"output path must not be a symbolic link: {path}") + flags = os.O_WRONLY | os.O_CREAT + flags |= os.O_APPEND if append else os.O_TRUNC + descriptor = os.open(target, flags | getattr(os, "O_NOFOLLOW", 0), 0o600) + return os.fdopen(descriptor, "a" if append else "w", encoding="utf-8") + + class ActionKind(str, enum.Enum): """Supported coordinator mutation classes.""" @@ -837,13 +850,13 @@ def main( return 2 text = report.to_json() + "\n" if args.json_output is not None: - args.json_output.parent.mkdir(parents=True, exist_ok=True) - args.json_output.write_text(text, encoding="utf-8") + with _open_private_output(args.json_output, append=False) as handle: + handle.write(text) else: sys.stdout.write(text) summary_path = os.environ.get("GITHUB_STEP_SUMMARY") if summary_path: - with Path(summary_path).open("a", encoding="utf-8") as handle: + with _open_private_output(Path(summary_path), append=True) as handle: handle.write(report.to_markdown()) all_selected_inspections_failed = ( report.inspected_repositories == 0 and bool(report.inspection_errors) diff --git a/tests/test_organization_commercial_readiness_loop_coordinator.py b/tests/test_organization_commercial_readiness_loop_coordinator.py index 0bd601d266..fee96946c1 100644 --- a/tests/test_organization_commercial_readiness_loop_coordinator.py +++ b/tests/test_organization_commercial_readiness_loop_coordinator.py @@ -19,6 +19,7 @@ GitHubError, PlanItem, SnapshotChanged, + _open_private_output, main, run_once, ) @@ -185,3 +186,17 @@ def test_main_writes_file_summary_stdout_and_failure_paths( ) == 2 assert "GitHubError: auth" in capsys.readouterr().err assert main(["--max-repositories", "-1"], client_factory=lambda: empty) == 2 + + +def test_private_outputs_reject_symbolic_links(tmp_path: Path) -> None: + """The coordinator cannot overwrite a report or summary symlink.""" + + target = tmp_path / "target.txt" + target.write_text("keep\n", encoding="utf-8") + report = tmp_path / "report.json" + report.symlink_to(target) + + with pytest.raises(ValueError, match="symbolic link"): + _open_private_output(report, append=False) + + assert target.read_text(encoding="utf-8") == "keep\n" From 729c73d17d80e19e90d1410cab7b5d94273f2fd7 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 12:07:56 -0700 Subject: [PATCH 6/7] fix(coordinator): create report output parents --- .../organization_commercial_readiness_loop.py | 1 + ...n_commercial_readiness_loop_coordinator.py | 21 +++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/scripts/ci/organization_commercial_readiness_loop.py b/scripts/ci/organization_commercial_readiness_loop.py index e144f0a1ff..9ab3644cf9 100644 --- a/scripts/ci/organization_commercial_readiness_loop.py +++ b/scripts/ci/organization_commercial_readiness_loop.py @@ -850,6 +850,7 @@ def main( return 2 text = report.to_json() + "\n" if args.json_output is not None: + args.json_output.parent.mkdir(parents=True, exist_ok=True) with _open_private_output(args.json_output, append=False) as handle: handle.write(text) else: diff --git a/tests/test_organization_commercial_readiness_loop_coordinator.py b/tests/test_organization_commercial_readiness_loop_coordinator.py index fee96946c1..a41762f024 100644 --- a/tests/test_organization_commercial_readiness_loop_coordinator.py +++ b/tests/test_organization_commercial_readiness_loop_coordinator.py @@ -200,3 +200,24 @@ def test_private_outputs_reject_symbolic_links(tmp_path: Path) -> None: _open_private_output(report, append=False) assert target.read_text(encoding="utf-8") == "keep\n" + + +def test_main_creates_missing_json_output_parent(tmp_path: Path) -> None: + """A nested CLI report path preserves the established create-parent contract.""" + + output = tmp_path / "nested" / "reports" / "report.json" + empty = FakeClient([], {}) + + assert not output.parent.exists() + assert main( + [ + "--max-repositories", + "0", + "--max-review-dispatches", + "0", + "--json-output", + str(output), + ], + client_factory=lambda: empty, + ) == 0 + assert json.loads(output.read_text(encoding="utf-8"))["inspected_repositories"] == 0 From 2451889cc80afa9101275e1356f8757fabc69b44 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 21 Aug 2026 10:49:41 +0900 Subject: [PATCH 7/7] fix(security): detect unquoted orchestration modes --- .../check_contextual_orchestrator_defaults.py | 4 +-- .../test_contextual_orchestrator_defaults.py | 34 +++++++++++++++++++ 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/scripts/ci/check_contextual_orchestrator_defaults.py b/scripts/ci/check_contextual_orchestrator_defaults.py index 86ae6782be..88616693f8 100644 --- a/scripts/ci/check_contextual_orchestrator_defaults.py +++ b/scripts/ci/check_contextual_orchestrator_defaults.py @@ -39,11 +39,11 @@ ORCHESTRATOR_MARKERS = ("contextual-orchestrator", "contextual_orchestrator") CHAT_ENDPOINT_MARKERS = ("/v1/chat/completions", "/chat/completions", "chat/completions") FORCED_ROUTE_PATTERN = re.compile( - r"(?:orchestration_mode|mode)(?:\s*:\s*str)?\s*[:=]\s*[\"']route[\"']", + r"(?:orchestration_mode|mode)(?:\s*:\s*str)?\s*[:=]\s*[\"']?\broute\b[\"']?", re.IGNORECASE, ) AUTO_PATTERN = re.compile( - r"(?:orchestration_mode|mode)(?:\s*:\s*str)?\s*[:=]\s*[\"']auto[\"']", + r"(?:orchestration_mode|mode)(?:\s*:\s*str)?\s*[:=]\s*[\"']?\bauto\b[\"']?", re.IGNORECASE, ) diff --git a/tests/test_contextual_orchestrator_defaults.py b/tests/test_contextual_orchestrator_defaults.py index 64a5857a51..64a9999736 100644 --- a/tests/test_contextual_orchestrator_defaults.py +++ b/tests/test_contextual_orchestrator_defaults.py @@ -82,6 +82,40 @@ def test_policy_exceptions_are_path_scoped(tmp_path: Path) -> None: assert policy.inspect_repository(tmp_path) == [] +def test_unquoted_modes_are_detected_without_partial_matches(tmp_path: Path) -> None: + """Detect equivalent unquoted assignments while rejecting longer values.""" + + _write( + tmp_path, + "src/forced.py", + 'endpoint = "/v1/chat/completions"\n' + 'model = "contextual-orchestrator"\n' + "orchestration_mode = route\n", + ) + _write( + tmp_path, + "src/partial.py", + 'endpoint = "/v1/chat/completions"\n' + 'model = "contextual-orchestrator"\n' + 'mode = "router"\n', + ) + _write( + tmp_path, + "src/auto.py", + 'endpoint = "/v1/chat/completions"\n' + 'model = "contextual-orchestrator"\n' + "mode = auto\n", + ) + + findings = policy.inspect_repository(tmp_path) + + assert [(item.finding_code, item.source_path) for item in findings] == [ + ("forced_single_route", "src/forced.py"), + ("implicit_orchestration_mode", "src/forced.py"), + ("implicit_orchestration_mode", "src/partial.py"), + ] + + def test_malformed_policy_fails_closed(tmp_path: Path) -> None: """Malformed exception configuration raises instead of weakening the scan."""