From fc1cd4dd5609a637caeb5e1b83d0ac83d03a1892 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 1 Sep 2026 12:50:46 +0900 Subject: [PATCH 01/24] chore(ci): bootstrap hourly DDD contract repair --- .../tmp-hourly-ddd-development-contract.yml | 532 ++++++++++++++++++ 1 file changed, 532 insertions(+) create mode 100644 .github/workflows/tmp-hourly-ddd-development-contract.yml diff --git a/.github/workflows/tmp-hourly-ddd-development-contract.yml b/.github/workflows/tmp-hourly-ddd-development-contract.yml new file mode 100644 index 0000000000..89c773a4ab --- /dev/null +++ b/.github/workflows/tmp-hourly-ddd-development-contract.yml @@ -0,0 +1,532 @@ +name: Temporary Hourly DDD Development Contract Repair + +on: + push: + branches: + - fix/hourly-ddd-development-contract-20260901 + paths: + - .github/workflows/tmp-hourly-ddd-development-contract.yml + +concurrency: + group: tmp-hourly-ddd-development-contract-repair + cancel-in-progress: false + +permissions: + contents: write + +jobs: + repair: + if: github.repository == 'ContextualWisdomLab/.github' + runs-on: ubuntu-24.04 + timeout-minutes: 20 + steps: + - name: Check out exact repair branch + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + ref: fix/hourly-ddd-development-contract-20260901 + fetch-depth: 1 + persist-credentials: true + + - name: Set up Python + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + with: + python-version: "3.14" + + - name: Apply exact bounded repair + shell: bash --noprofile --norc -e -o pipefail {0} + run: | + python - <<'PY' + from __future__ import annotations + + from pathlib import Path + + ROOT = Path.cwd() + + + def replace_once(path: str, old: str, new: str) -> None: + """Replace one exact repository fragment or fail closed.""" + target = ROOT / path + source = target.read_text(encoding="utf-8") + count = source.count(old) + if count != 1: + raise SystemExit(f"{path}: expected one exact fragment, found {count}") + target.write_text(source.replace(old, new, 1), encoding="utf-8") + + + replace_once( + ".github/workflows/organization-commercial-readiness-loop.yml", + """ runs-on: ubuntu-24.04 + timeout-minutes: 25 + env: + """, + """ runs-on: ubuntu-24.04 + timeout-minutes: 25 + permissions: + contents: read + id-token: write + env: + """, + ) + replace_once( + ".github/workflows/organization-commercial-readiness-loop.yml", + """ api.github.com:443 + github.com:443 + """, + """ api.github.com:443 + api.opencode.ai:443 + github.com:443 + """, + ) + replace_once( + ".github/workflows/organization-commercial-readiness-loop.yml", + """ - name: Coordinate one bounded fleet pass + env: + GH_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN }} + shell: bash --noprofile --norc -e -o pipefail {0} + run: | + if [ -z "${GH_TOKEN:-}" ]; then + echo "::error::PR_REVIEW_MERGE_TOKEN is required; neither the reviewer credential nor repository-scoped GITHUB_TOKEN is accepted." + exit 1 + fi + echo "::add-mask::$GH_TOKEN" + test "$(git rev-parse HEAD)" = "${GITHUB_SHA}" + + python scripts/ci/organization_commercial_readiness_loop.py \\ + --organization "$ORGANIZATION" \\ + --rotation-seed "$ROTATION_SEED" \\ + --max-repositories "$MAX_REPOSITORIES" \\ + --max-review-dispatches "$MAX_REVIEW_DISPATCHES" \\ + --max-development-dispatches "$MAX_DEVELOPMENT_DISPATCHES" \\ + --json-output "$RUNNER_TEMP/organization-commercial-readiness-loop.json" + python -m json.tool "$RUNNER_TEMP/organization-commercial-readiness-loop.json" >/dev/null + """, + """ - name: Coordinate one bounded fleet pass + env: + GH_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN }} + OIDC_AUDIENCE: opencode-github-action + OPENCODE_API_BASE_URL: https://api.opencode.ai + shell: bash --noprofile --norc -e -o pipefail {0} + run: | + set -euo pipefail + + exchange_unavailable() { + echo "::error::OpenCode app token exchange unavailable: $1" + exit 1 + } + + if [ -z "${GH_TOKEN:-}" ]; then + if [ -z "${ACTIONS_ID_TOKEN_REQUEST_TOKEN:-}" ] || [ -z "${ACTIONS_ID_TOKEN_REQUEST_URL:-}" ]; then + exchange_unavailable "OIDC request environment is missing." + fi + + request_url="${ACTIONS_ID_TOKEN_REQUEST_URL}" + separator="&" + case "$request_url" in + *\?*) ;; + *) separator="?" ;; + esac + + if ! oidc_response="$( + curl -fsS \\ + --connect-timeout 10 \\ + --max-time 30 \\ + -H "Authorization: Bearer ${ACTIONS_ID_TOKEN_REQUEST_TOKEN}" \\ + "${request_url}${separator}audience=${OIDC_AUDIENCE}" + )"; then + exchange_unavailable "OIDC token request did not complete." + fi + if ! oidc_token="$( + jq -er '.value | select(type == "string" and length > 0)' \\ + <<<"$oidc_response" 2>/dev/null + )"; then + exchange_unavailable "OIDC token response was malformed or empty." + fi + echo "::add-mask::$oidc_token" + + if ! token_response="$( + curl -fsS \\ + --connect-timeout 10 \\ + --max-time 30 \\ + -X POST \\ + -H "Authorization: Bearer ${oidc_token}" \\ + "${OPENCODE_API_BASE_URL}/exchange_github_app_token" + )"; then + exchange_unavailable "app token request did not complete." + fi + if ! app_token="$( + jq -er '.token | select(type == "string" and length > 0)' \\ + <<<"$token_response" 2>/dev/null + )"; then + exchange_unavailable "app token response was malformed or empty." + fi + echo "::add-mask::$app_token" + export GH_TOKEN="$app_token" + fi + + if [ -z "${GH_TOKEN:-}" ]; then + echo "::error::PR_REVIEW_MERGE_TOKEN or the job-bound OpenCode App token exchange is required; neither reviewer credentials nor repository-scoped GITHUB_TOKEN are accepted." + exit 1 + fi + echo "::add-mask::$GH_TOKEN" + test "$(git rev-parse HEAD)" = "${GITHUB_SHA}" + + python scripts/ci/organization_commercial_readiness_loop.py \\ + --organization "$ORGANIZATION" \\ + --rotation-seed "$ROTATION_SEED" \\ + --max-repositories "$MAX_REPOSITORIES" \\ + --max-review-dispatches "$MAX_REVIEW_DISPATCHES" \\ + --max-development-dispatches "$MAX_DEVELOPMENT_DISPATCHES" \\ + --json-output "$RUNNER_TEMP/organization-commercial-readiness-loop.json" + python -m json.tool "$RUNNER_TEMP/organization-commercial-readiness-loop.json" >/dev/null + """, + ) + + replace_once( + "scripts/ci/organization_commercial_readiness_loop.py", + """ENTRYPOINT_MARKER = "# cwl-org-commercial-entrypoint: v1" + CENTRAL_REPOSITORY = f"{DEFAULT_ORGANIZATION}/.github" + """, + """ENTRYPOINT_MARKER = "# cwl-org-commercial-entrypoint: v1" + DDD_ENTRYPOINT_MARKER = "# cwl-ddd-architecture-audit: required" + DDD_CONTRACT_TERMS = ( + "Domain-Driven Design", + "core, supporting, and generic subdomains", + "Bounded Context", + "Context Map", + "Ubiquitous Language", + "Aggregate", + "Entity", + "Value Object", + "Domain Service", + "Repository", + "Domain Event", + "Invariant", + "Anti-Corruption Layer", + "Shared Kernel", + "directory paths", + "docs/product-technical-gap-baseline.md", + ) + CENTRAL_REPOSITORY = f"{DEFAULT_ORGANIZATION}/.github" + """, + ) + replace_once( + "scripts/ci/organization_commercial_readiness_loop.py", + """def is_manual_product_entrypoint(workflow: WorkflowRecord) -> bool: + \"\"\"Return whether a workflow explicitly opts in to central product dispatch.\"\"\" + source = workflow.content + if workflow.state != "active" or source is None: + return False + return all( + ( + ENTRYPOINT_MARKER in source, + bool(WORKFLOW_DISPATCH_RE.search(source)), + """, + """def has_domain_driven_development_contract(source: str) -> bool: + \"\"\"Return whether one entrypoint accepts the complete DDD repair contract.\"\"\" + return DDD_ENTRYPOINT_MARKER in source and all( + term in source for term in DDD_CONTRACT_TERMS + ) + + + def is_manual_product_entrypoint(workflow: WorkflowRecord) -> bool: + \"\"\"Return whether a workflow safely opts in to central product development.\"\"\" + source = workflow.content + if workflow.state != "active" or source is None: + return False + return all( + ( + ENTRYPOINT_MARKER in source, + has_domain_driven_development_contract(source), + bool(WORKFLOW_DISPATCH_RE.search(source)), + """, + ) + + replace_once( + "organization_commercial_readiness_fixtures.py", + """ "# cwl-org-commercial-entrypoint: v1\\n" + "on:\\n workflow_dispatch:\\n" + "concurrency:\\n group: product-development\\n" + "permissions:\\n contents: write\\n" + "NVIDIA_API_KEY: ${{ secrets.NVIDIA_NIM_API_KEY }}\\n" + """, + """ "# cwl-org-commercial-entrypoint: v1\\n" + "# cwl-ddd-architecture-audit: required\\n" + "on:\\n workflow_dispatch:\\n" + "concurrency:\\n group: product-development\\n" + "permissions:\\n contents: write\\n" + "NVIDIA_API_KEY: ${{ secrets.NVIDIA_NIM_API_KEY }}\\n" + "prompt: |\\n" + " Apply Domain-Driven Design before and during every increment.\\n" + " Classify core, supporting, and generic subdomains; define each Bounded Context, Context Map, and Ubiquitous Language.\\n" + " Keep Aggregate, Entity, Value Object, Domain Service, Repository, Domain Event, and Invariant names aligned across code, API, database, and tests.\\n" + " Isolate external systems behind an Anti-Corruption Layer and keep the Shared Kernel minimal.\\n" + " Audit and correct misleading directory paths with imports, packaging, callers, tests, and architecture documents in the same bounded change.\\n" + " Update docs/product-technical-gap-baseline.md with detected and repaired architecture drift.\\n" + """, + ) + + replace_once( + "tests/test_organization_commercial_readiness_loop_policy.py", + """ ActionKind, + ActionResult, + RunRecord, + """, + """ ActionKind, + ActionResult, + DDD_CONTRACT_TERMS, + RunRecord, + """, + ) + replace_once( + "tests/test_organization_commercial_readiness_loop_policy.py", + """def test_product_entrypoint_requires_manual_nvidia_opt_in() -> None: + \"\"\"Product dispatch requires a marked, unscheduled, credential-isolated workflow.\"\"\" + safe = manual_workflow() + assert is_manual_product_entrypoint(safe) + assert not is_manual_product_entrypoint(workflow(state="disabled_manually", content="x")) + assert not is_manual_product_entrypoint(workflow(content=None)) + for changed in ( + (safe.content or "") + 'schedule:\\n - cron: "1 * * * *"\\n', + (safe.content or "") + "COPILOT_GITHUB_TOKEN: forbidden\\n", + (safe.content or "").replace("# cwl-org-commercial-entrypoint: v1\\n", ""), + (safe.content or "").replace("concurrency:\\n", ""), + ): + assert not is_manual_product_entrypoint(workflow(content=changed)) + """, + """def test_product_entrypoint_requires_manual_nvidia_and_ddd_opt_in() -> None: + \"\"\"Product dispatch requires a manual credential-isolated DDD contract.\"\"\" + safe = manual_workflow() + assert is_manual_product_entrypoint(safe) + assert not is_manual_product_entrypoint(workflow(state="disabled_manually", content="x")) + assert not is_manual_product_entrypoint(workflow(content=None)) + mutations = [ + (safe.content or "") + 'schedule:\\n - cron: "1 * * * *"\\n', + (safe.content or "") + "COPILOT_GITHUB_TOKEN: forbidden\\n", + (safe.content or "").replace("# cwl-org-commercial-entrypoint: v1\\n", ""), + (safe.content or "").replace( + "# cwl-ddd-architecture-audit: required\\n", "" + ), + (safe.content or "").replace("concurrency:\\n", ""), + ] + mutations.extend( + (safe.content or "").replace(term, f"missing-{index}", 1) + for index, term in enumerate(DDD_CONTRACT_TERMS) + ) + for changed in mutations: + assert not is_manual_product_entrypoint(workflow(content=changed)) + """, + ) + replace_once( + "tests/test_organization_commercial_readiness_loop_policy.py", + """ assert "GH_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN }}" in workflow_source + assert "OPENCODE_APPROVE_TOKEN" not in workflow_source + """, + """ assert "GH_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN }}" in workflow_source + assert 'export GH_TOKEN="$app_token"' in workflow_source + assert "id-token: write" in workflow_source + assert "OIDC_AUDIENCE: opencode-github-action" in workflow_source + assert "OPENCODE_APPROVE_TOKEN" not in workflow_source + """, + ) + replace_once( + "tests/test_organization_commercial_readiness_loop_policy.py", + """ assert "manual-only, explicitly marked" in doctoring + assert "does not make every repository directly writable" in doctoring + """, + """ assert "manual-only, explicitly marked" in doctoring + assert "# cwl-ddd-architecture-audit: required" in doctoring + assert "misleading directory paths" in doctoring + assert "does not make every repository directly writable" in doctoring + """, + ) + + replace_once( + "tests/test_organization_commercial_readiness_loop_credential_contract.py", + """def test_central_schedule_has_no_branch_selected_or_reviewer_credential_path() -> None: + \"\"\"The fleet coordinator must be schedule-only and use maintainer authority.\"\"\" + source = WORKFLOW_PATH.read_text(encoding="utf-8") + + assert "workflow_dispatch:" not in source + assert "GH_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN }}" in source + assert "persist-credentials: false" in source + assert "OPENCODE_APPROVE_TOKEN" not in source + assert "DRY_RUN" not in source + assert "inputs.dry_run" not in source + """, + """def test_central_schedule_has_no_branch_selected_or_reviewer_credential_path() -> None: + \"\"\"The fleet coordinator uses schedule-bound maintainer or App authority.\"\"\" + source = WORKFLOW_PATH.read_text(encoding="utf-8") + + assert "workflow_dispatch:" not in source + assert "GH_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN }}" in source + assert "id-token: write" in source + assert "OIDC_AUDIENCE: opencode-github-action" in source + assert '"${OPENCODE_API_BASE_URL}/exchange_github_app_token"' in source + assert "persist-credentials: false" in source + assert "OPENCODE_APPROVE_TOKEN" not in source + assert "|| github.token" not in source + assert "DRY_RUN" not in source + assert "inputs.dry_run" not in source + + + def test_opencode_exchange_fails_closed_and_masks_both_tokens() -> None: + \"\"\"Malformed exchanges remain bounded and never expose either token.\"\"\" + source = WORKFLOW_PATH.read_text(encoding="utf-8") + coordinate = source.split( + " - name: Coordinate one bounded fleet pass\\n", maxsplit=1 + )[1] + + assert 'if [ -z "${GH_TOKEN:-}" ]; then' in coordinate + assert 'export GH_TOKEN="$app_token"' in coordinate + assert "OIDC token response was malformed or empty" in coordinate + assert "app token response was malformed or empty" in coordinate + assert 'echo "::add-mask::$oidc_token"' in coordinate + assert 'echo "::add-mask::$app_token"' in coordinate + assert coordinate.count("--connect-timeout 10") == 2 + assert coordinate.count("--max-time 30") == 2 + """, + ) + + doctoring_path = ROOT / "docs/doctoring/organization-commercial-readiness-loop.md" + doctoring = doctoring_path.read_text(encoding="utf-8") + old_credential = ( + "The central job therefore refuses both repository-scoped and reviewer-scoped token fallbacks. " + "It requires the maintainer-scoped `PR_REVIEW_MERGE_TOKEN`; `OPENCODE_APPROVE_TOKEN` remains isolated " + "to the reviewer credential chain and `GITHUB_TOKEN` is not accepted for cross-repository coordination. " + "The maintainer token is exposed only to the final dispatch shell step, not checkout, setup, artifact " + "upload, or other third-party actions. The coordinator itself receives neither `NVIDIA_NIM_API_KEY` nor " + "`COPILOT_GITHUB_TOKEN`. Model credentials remain inside separately reviewed repository-local or central workers." + ) + new_credential = ( + "The central job therefore refuses repository-scoped and reviewer-scoped token fallbacks. It prefers the " + "maintainer-scoped `PR_REVIEW_MERGE_TOKEN`; when that credential is absent, the scheduled default-branch job " + "may exchange its job-bound GitHub OIDC identity for the existing short-lived OpenCode App installation token. " + "Both exchange calls have bounded connection and total timeouts, both returned tokens are masked before reuse, " + "and malformed or empty responses fail closed. `OPENCODE_APPROVE_TOKEN` remains isolated to the reviewer " + "credential chain and `GITHUB_TOKEN` is never accepted for cross-repository coordination. The resulting " + "maintainer credential is exposed only to the final dispatch shell step, not checkout, setup, artifact upload, " + "or other third-party actions. The coordinator itself receives neither `NVIDIA_NIM_API_KEY` nor " + "`COPILOT_GITHUB_TOKEN`. Model credentials remain inside separately reviewed repository-local or central workers." + ) + if doctoring.count(old_credential) != 1: + raise SystemExit("doctoring credential paragraph drifted") + doctoring = doctoring.replace(old_credential, new_credential, 1) + start = doctoring.index("## Product-development boundary") + end = doctoring.index("## Failure, evidence, and operations") + product_section = """## Product-development boundary + + Product development is dispatched only when a repository has zero open pull requests and exposes one active, manual-only, explicitly marked workflow: + + ```yaml + # cwl-org-commercial-entrypoint: v1 + # cwl-ddd-architecture-audit: required + on: + workflow_dispatch: + ``` + + The entrypoint must contain an explicit `concurrency` contract, use `NVIDIA_NIM_API_KEY`, omit `COPILOT_GITHUB_TOKEN`, have no schedule of its own, and carry a commercial/product-development identity. It must also embed the complete Domain-Driven Design repair contract rather than merely mention DDD. The machine-checked contract requires core, supporting, and generic subdomains; Bounded Context; Context Map; Ubiquitous Language; Aggregate; Entity; Value Object; Domain Service; Repository; Domain Event; Invariant; Anti-Corruption Layer; Shared Kernel; directory paths; and `docs/product-technical-gap-baseline.md`. + + Each hourly product increment must identify the owning product responsibility before selecting a repository, then compare the live directory tree, module/package names, API, database objects, tests, and documentation with that responsibility. Misleading directory paths, generic `utils`/`common` dumping grounds that own domain behavior, infrastructure imports inside the domain model, cross-context database access, obsolete product names, or customer-visible implementation boundaries are architecture defects, not cosmetic debt. When one can be corrected safely in the bounded increment, the agent moves the code and updates imports, package manifests, call sites, migrations, tests, ADRs, diagrams, and compatibility adapters in the same pull request. + + The contract does not impose one universal folder template. A move is justified by domain ownership and dependency direction, not by directory aesthetics. Aggregate boundaries remain the smallest consistency boundary; external and legacy systems are isolated behind an Anti-Corruption Layer; the Shared Kernel remains minimal; and cross-context integration uses explicit versioned contracts. If a coherent move exceeds the current pull request's safe scope, the agent must record the exact owner, callers, target context, migration sequence, and acceptance evidence in `docs/product-technical-gap-baseline.md` and select it as the next bounded architecture increment rather than silently leaving the drift unresolved. + + This opt-in prevents the central coordinator from guessing that an unrelated manual workflow can safely modify product source. Repositories with an existing hourly or more frequent dedicated writer keep their own lease and are never double-dispatched; those schedules may share the same DDD contract and should adopt it without adding another cron. + + The repository-local entrypoint remains responsible for bounded editable paths, tests, 100% production statement and branch coverage, public docstrings, package and security verification, exact-head publication, and pull-request creation. A missing compliant entrypoint is a deliberate no-op, not permission to inject a generic writer into that repository. + + """ + doctoring = doctoring[:start] + product_section + doctoring[end:] + references_marker = "## APA 7 references\n\n" + ddd_references = ( + "Evans, E. (2004). *Domain-driven design: Tackling complexity in the heart of software*. Addison-Wesley.\n\n" + "Evans, E. (2015). *Domain-driven design reference: Definitions and pattern summaries*. Domain Language. https://www.domainlanguage.com/ddd/reference/\n\n" + "International Organization for Standardization, International Electrotechnical Commission, & Institute of Electrical and Electronics Engineers. (2022). *Software, systems and enterprise—Architecture description* (ISO/IEC/IEEE Standard 42010:2022). https://www.iso.org/standard/74393.html\n\n" + ) + if references_marker not in doctoring: + raise SystemExit("doctoring references heading missing") + doctoring = doctoring.replace( + references_marker, references_marker + ddd_references, 1 + ) + doctoring_path.write_text(doctoring, encoding="utf-8") + + baseline_path = ROOT / "docs/product-technical-gap-baseline.md" + baseline = baseline_path.read_text(encoding="utf-8") + baseline_marker = "## 1. 근거와 범위\n" + baseline_addendum = """## 2026-09-01 시간별 DDD 실행 계약 보강 + + - **관측:** 조직 상용화 루프는 매시 7분 실행되고 기존 전용 writer 예약을 존중하지만, 중앙 제품개발 opt-in은 DDD 및 디렉터리 소유권 감사를 요구하지 않았다. 또한 maintainer secret이 없는 예약 환경에서는 cross-repository dispatch 전에 중단될 수 있었다. + - **Gap `G-DDD-01`:** Bounded Context와 실제 디렉터리·패키지·API·DB 소유권이 어긋나도 시간별 Agent가 이를 필수 결함으로 선택한다는 기계 검증 계약이 없었다. + - **조치:** 수동 제품개발 진입점에 `# cwl-ddd-architecture-audit: required`와 전략·전술 DDD 용어, directory-path repair, `docs/product-technical-gap-baseline.md` 갱신을 요구한다. 기존 전용 예약은 writer lease를 유지해 중복 실행하지 않는다. + - **가용성 조치:** `PR_REVIEW_MERGE_TOKEN`을 우선 사용하되 없으면 protected-default-branch job의 OIDC identity를 short-lived OpenCode App installation token으로 교환한다. repository `GITHUB_TOKEN`, reviewer credential, model provider key는 fallback으로 사용하지 않는다. + - **완료 증거:** exact-head focused policy tests, statement/branch coverage 100%, Python docstring 100%, workflow security checks, independent review, protected merge. 병합 전 상태는 구현 중이며 운영 완료로 간주하지 않는다. + + """ + if baseline_marker not in baseline: + raise SystemExit("gap baseline insertion point missing") + baseline = baseline.replace( + baseline_marker, baseline_addendum + baseline_marker, 1 + ) + baseline_path.write_text(baseline, encoding="utf-8") + + changelog_path = ROOT / "CHANGELOG.md" + changelog = changelog_path.read_text(encoding="utf-8") + changelog_marker = "## [Unreleased]\n" + changelog_entry = ( + "- Restore the hourly organization commercial-readiness coordinator when the dedicated maintainer secret is absent by exchanging the protected scheduled job's OIDC identity for a short-lived OpenCode App installation token; retain bounded network calls, token masking, and fail-closed parsing. Require every centrally dispatched product-development entrypoint to accept a machine-checked Domain-Driven Design contract, continuously repairing misleading directory ownership and recording larger bounded-context migrations in `docs/product-technical-gap-baseline.md` without duplicating repository-owned schedules.\n" + ) + if changelog_marker not in changelog: + raise SystemExit("changelog insertion point missing") + changelog = changelog.replace( + changelog_marker, changelog_marker + changelog_entry, 1 + ) + changelog_path.write_text(changelog, encoding="utf-8") + PY + + - name: Install exact focused-test dependencies + env: + PIP_DISABLE_PIP_VERSION_CHECK: "1" + PIP_NO_INPUT: "1" + shell: bash --noprofile --norc -e -o pipefail {0} + run: | + cat >"${RUNNER_TEMP}/requirements.txt" <<'EOF' + coverage==7.15.2 --hash=sha256:b9a6367e4aff723e8ee8190836836124284e8fcd4265e307c844010cfa074f3f + iniconfig==2.1.0 --hash=sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760 + packaging==26.2 --hash=sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e + pluggy==1.6.0 --hash=sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746 + pygments==2.20.0 --hash=sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176 + pytest==9.1.1 --hash=sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c + EOF + python -m pip install --only-binary=:all: --require-hashes -r "${RUNNER_TEMP}/requirements.txt" + + - name: Verify policy, branch coverage, and source hygiene + shell: bash --noprofile --norc -e -o pipefail {0} + run: | + python -m coverage run --branch -m pytest --import-mode=importlib \ + tests/test_organization_commercial_readiness_loop*.py -q + python -m coverage report \ + --include='scripts/ci/organization_commercial_readiness_loop.py' \ + --show-missing \ + --fail-under=100 + python -m compileall -q \ + scripts/ci/organization_commercial_readiness_loop.py \ + organization_commercial_readiness_fixtures.py \ + tests/test_organization_commercial_readiness_loop*.py + git diff --check + test "$(grep -c '# cwl-ddd-architecture-audit: required' organization_commercial_readiness_fixtures.py)" -eq 1 + test "$(grep -c 'api.opencode.ai:443' .github/workflows/organization-commercial-readiness-loop.yml)" -eq 1 + + - name: Remove bootstrap and publish verified branch commit + shell: bash --noprofile --norc -e -o pipefail {0} + run: | + set -euo pipefail + rm .github/workflows/tmp-hourly-ddd-development-contract.yml + git diff --check + git add -A + test -n "$(git diff --cached --name-only)" + if git diff --cached --name-only | grep -Fx '.github/workflows/tmp-hourly-ddd-development-contract.yml'; then + echo 'Temporary workflow deletion is intentionally included in the bootstrap commit.' + fi + git config user.name 'github-actions[bot]' + git config user.email '41898282+github-actions[bot]@users.noreply.github.com' + git commit -m 'fix(automation): enforce hourly DDD development contract' + git push origin HEAD:fix/hourly-ddd-development-contract-20260901 From c3fc24fbb5d1daacad23012dfaa50daa2765b66e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 1 Sep 2026 12:53:42 +0900 Subject: [PATCH 02/24] chore(ci): isolate hourly DDD bootstrap logic --- scripts/ci/tmp_apply_hourly_ddd_contract.py | 464 ++++++++++++++++++++ 1 file changed, 464 insertions(+) create mode 100644 scripts/ci/tmp_apply_hourly_ddd_contract.py diff --git a/scripts/ci/tmp_apply_hourly_ddd_contract.py b/scripts/ci/tmp_apply_hourly_ddd_contract.py new file mode 100644 index 0000000000..39125d6353 --- /dev/null +++ b/scripts/ci/tmp_apply_hourly_ddd_contract.py @@ -0,0 +1,464 @@ +#!/usr/bin/env python3 +"""Apply the bounded hourly DDD coordinator repair, then let CI delete this file.""" + +from __future__ import annotations + +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[2] + + +def replace_once(path: str, old: str, new: str) -> None: + """Replace one exact repository fragment or fail closed.""" + target = ROOT / path + source = target.read_text(encoding="utf-8") + count = source.count(old) + if count != 1: + raise SystemExit(f"{path}: expected one exact fragment, found {count}") + target.write_text(source.replace(old, new, 1), encoding="utf-8") + + +def patch_workflow() -> None: + """Restore bounded App authentication in the hourly coordinator workflow.""" + path = ".github/workflows/organization-commercial-readiness-loop.yml" + replace_once( + path, + """ runs-on: ubuntu-24.04 + timeout-minutes: 25 + env: +""", + """ runs-on: ubuntu-24.04 + timeout-minutes: 25 + permissions: + contents: read + id-token: write + env: +""", + ) + replace_once( + path, + """ api.github.com:443 + github.com:443 +""", + """ api.github.com:443 + api.opencode.ai:443 + github.com:443 +""", + ) + replace_once( + path, + """ - name: Coordinate one bounded fleet pass + env: + GH_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN }} + shell: bash --noprofile --norc -e -o pipefail {0} + run: | + if [ -z "${GH_TOKEN:-}" ]; then + echo "::error::PR_REVIEW_MERGE_TOKEN is required; neither the reviewer credential nor repository-scoped GITHUB_TOKEN is accepted." + exit 1 + fi + echo "::add-mask::$GH_TOKEN" + test "$(git rev-parse HEAD)" = "${GITHUB_SHA}" + + python scripts/ci/organization_commercial_readiness_loop.py \\ + --organization "$ORGANIZATION" \\ + --rotation-seed "$ROTATION_SEED" \\ + --max-repositories "$MAX_REPOSITORIES" \\ + --max-review-dispatches "$MAX_REVIEW_DISPATCHES" \\ + --max-development-dispatches "$MAX_DEVELOPMENT_DISPATCHES" \\ + --json-output "$RUNNER_TEMP/organization-commercial-readiness-loop.json" + python -m json.tool "$RUNNER_TEMP/organization-commercial-readiness-loop.json" >/dev/null +""", + """ - name: Coordinate one bounded fleet pass + env: + GH_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN }} + OIDC_AUDIENCE: opencode-github-action + OPENCODE_API_BASE_URL: https://api.opencode.ai + shell: bash --noprofile --norc -e -o pipefail {0} + run: | + set -euo pipefail + + exchange_unavailable() { + echo "::error::OpenCode app token exchange unavailable: $1" + exit 1 + } + + if [ -z "${GH_TOKEN:-}" ]; then + if [ -z "${ACTIONS_ID_TOKEN_REQUEST_TOKEN:-}" ] || [ -z "${ACTIONS_ID_TOKEN_REQUEST_URL:-}" ]; then + exchange_unavailable "OIDC request environment is missing." + fi + + request_url="${ACTIONS_ID_TOKEN_REQUEST_URL}" + separator="&" + case "$request_url" in + *\?*) ;; + *) separator="?" ;; + esac + + if ! oidc_response="$( + curl -fsS \\ + --connect-timeout 10 \\ + --max-time 30 \\ + -H "Authorization: Bearer ${ACTIONS_ID_TOKEN_REQUEST_TOKEN}" \\ + "${request_url}${separator}audience=${OIDC_AUDIENCE}" + )"; then + exchange_unavailable "OIDC token request did not complete." + fi + if ! oidc_token="$( + jq -er '.value | select(type == "string" and length > 0)' \\ + <<<"$oidc_response" 2>/dev/null + )"; then + exchange_unavailable "OIDC token response was malformed or empty." + fi + echo "::add-mask::$oidc_token" + + if ! token_response="$( + curl -fsS \\ + --connect-timeout 10 \\ + --max-time 30 \\ + -X POST \\ + -H "Authorization: Bearer ${oidc_token}" \\ + "${OPENCODE_API_BASE_URL}/exchange_github_app_token" + )"; then + exchange_unavailable "app token request did not complete." + fi + if ! app_token="$( + jq -er '.token | select(type == "string" and length > 0)' \\ + <<<"$token_response" 2>/dev/null + )"; then + exchange_unavailable "app token response was malformed or empty." + fi + echo "::add-mask::$app_token" + export GH_TOKEN="$app_token" + fi + + if [ -z "${GH_TOKEN:-}" ]; then + echo "::error::PR_REVIEW_MERGE_TOKEN or the job-bound OpenCode App token exchange is required; neither reviewer credentials nor repository-scoped GITHUB_TOKEN are accepted." + exit 1 + fi + echo "::add-mask::$GH_TOKEN" + test "$(git rev-parse HEAD)" = "${GITHUB_SHA}" + + python scripts/ci/organization_commercial_readiness_loop.py \\ + --organization "$ORGANIZATION" \\ + --rotation-seed "$ROTATION_SEED" \\ + --max-repositories "$MAX_REPOSITORIES" \\ + --max-review-dispatches "$MAX_REVIEW_DISPATCHES" \\ + --max-development-dispatches "$MAX_DEVELOPMENT_DISPATCHES" \\ + --json-output "$RUNNER_TEMP/organization-commercial-readiness-loop.json" + python -m json.tool "$RUNNER_TEMP/organization-commercial-readiness-loop.json" >/dev/null +""", + ) + + +def patch_coordinator() -> None: + """Require complete DDD language in centrally dispatched product entrypoints.""" + path = "scripts/ci/organization_commercial_readiness_loop.py" + replace_once( + path, + """ENTRYPOINT_MARKER = "# cwl-org-commercial-entrypoint: v1" +CENTRAL_REPOSITORY = f"{DEFAULT_ORGANIZATION}/.github" +""", + """ENTRYPOINT_MARKER = "# cwl-org-commercial-entrypoint: v1" +DDD_ENTRYPOINT_MARKER = "# cwl-ddd-architecture-audit: required" +DDD_CONTRACT_TERMS = ( + "Domain-Driven Design", + "core, supporting, and generic subdomains", + "Bounded Context", + "Context Map", + "Ubiquitous Language", + "Aggregate", + "Entity", + "Value Object", + "Domain Service", + "Repository", + "Domain Event", + "Invariant", + "Anti-Corruption Layer", + "Shared Kernel", + "directory paths", + "docs/product-technical-gap-baseline.md", +) +CENTRAL_REPOSITORY = f"{DEFAULT_ORGANIZATION}/.github" +""", + ) + replace_once( + path, + """def is_manual_product_entrypoint(workflow: WorkflowRecord) -> bool: + \"\"\"Return whether a workflow explicitly opts in to central product dispatch.\"\"\" + source = workflow.content + if workflow.state != "active" or source is None: + return False + return all( + ( + ENTRYPOINT_MARKER in source, + bool(WORKFLOW_DISPATCH_RE.search(source)), +""", + """def has_domain_driven_development_contract(source: str) -> bool: + \"\"\"Return whether one entrypoint accepts the complete DDD repair contract.\"\"\" + return DDD_ENTRYPOINT_MARKER in source and all( + term in source for term in DDD_CONTRACT_TERMS + ) + + +def is_manual_product_entrypoint(workflow: WorkflowRecord) -> bool: + \"\"\"Return whether a workflow safely opts in to central product development.\"\"\" + source = workflow.content + if workflow.state != "active" or source is None: + return False + return all( + ( + ENTRYPOINT_MARKER in source, + has_domain_driven_development_contract(source), + bool(WORKFLOW_DISPATCH_RE.search(source)), +""", + ) + + +def patch_fixtures_and_tests() -> None: + """Extend regression fixtures across every DDD and credential branch.""" + replace_once( + "organization_commercial_readiness_fixtures.py", + """ "# cwl-org-commercial-entrypoint: v1\\n" + "on:\\n workflow_dispatch:\\n" + "concurrency:\\n group: product-development\\n" + "permissions:\\n contents: write\\n" + "NVIDIA_API_KEY: ${{ secrets.NVIDIA_NIM_API_KEY }}\\n" +""", + """ "# cwl-org-commercial-entrypoint: v1\\n" + "# cwl-ddd-architecture-audit: required\\n" + "on:\\n workflow_dispatch:\\n" + "concurrency:\\n group: product-development\\n" + "permissions:\\n contents: write\\n" + "NVIDIA_API_KEY: ${{ secrets.NVIDIA_NIM_API_KEY }}\\n" + "prompt: |\\n" + " Apply Domain-Driven Design before and during every increment.\\n" + " Classify core, supporting, and generic subdomains; define each Bounded Context, Context Map, and Ubiquitous Language.\\n" + " Keep Aggregate, Entity, Value Object, Domain Service, Repository, Domain Event, and Invariant names aligned across code, API, database, and tests.\\n" + " Isolate external systems behind an Anti-Corruption Layer and keep the Shared Kernel minimal.\\n" + " Audit and correct misleading directory paths with imports, packaging, callers, tests, and architecture documents in the same bounded change.\\n" + " Update docs/product-technical-gap-baseline.md with detected and repaired architecture drift.\\n" +""", + ) + replace_once( + "tests/test_organization_commercial_readiness_loop_policy.py", + """ ActionKind, + ActionResult, + RunRecord, +""", + """ ActionKind, + ActionResult, + DDD_CONTRACT_TERMS, + RunRecord, +""", + ) + replace_once( + "tests/test_organization_commercial_readiness_loop_policy.py", + """def test_product_entrypoint_requires_manual_nvidia_opt_in() -> None: + \"\"\"Product dispatch requires a marked, unscheduled, credential-isolated workflow.\"\"\" + safe = manual_workflow() + assert is_manual_product_entrypoint(safe) + assert not is_manual_product_entrypoint(workflow(state="disabled_manually", content="x")) + assert not is_manual_product_entrypoint(workflow(content=None)) + for changed in ( + (safe.content or "") + 'schedule:\\n - cron: "1 * * * *"\\n', + (safe.content or "") + "COPILOT_GITHUB_TOKEN: forbidden\\n", + (safe.content or "").replace("# cwl-org-commercial-entrypoint: v1\\n", ""), + (safe.content or "").replace("concurrency:\\n", ""), + ): + assert not is_manual_product_entrypoint(workflow(content=changed)) +""", + """def test_product_entrypoint_requires_manual_nvidia_and_ddd_opt_in() -> None: + \"\"\"Product dispatch requires a manual credential-isolated DDD contract.\"\"\" + safe = manual_workflow() + assert is_manual_product_entrypoint(safe) + assert not is_manual_product_entrypoint(workflow(state="disabled_manually", content="x")) + assert not is_manual_product_entrypoint(workflow(content=None)) + mutations = [ + (safe.content or "") + 'schedule:\\n - cron: "1 * * * *"\\n', + (safe.content or "") + "COPILOT_GITHUB_TOKEN: forbidden\\n", + (safe.content or "").replace("# cwl-org-commercial-entrypoint: v1\\n", ""), + (safe.content or "").replace( + "# cwl-ddd-architecture-audit: required\\n", "" + ), + (safe.content or "").replace("concurrency:\\n", ""), + ] + mutations.extend( + (safe.content or "").replace(term, f"missing-{index}", 1) + for index, term in enumerate(DDD_CONTRACT_TERMS) + ) + for changed in mutations: + assert not is_manual_product_entrypoint(workflow(content=changed)) +""", + ) + replace_once( + "tests/test_organization_commercial_readiness_loop_policy.py", + """ assert "GH_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN }}" in workflow_source + assert "OPENCODE_APPROVE_TOKEN" not in workflow_source +""", + """ assert "GH_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN }}" in workflow_source + assert 'export GH_TOKEN="$app_token"' in workflow_source + assert "id-token: write" in workflow_source + assert "OIDC_AUDIENCE: opencode-github-action" in workflow_source + assert "OPENCODE_APPROVE_TOKEN" not in workflow_source +""", + ) + replace_once( + "tests/test_organization_commercial_readiness_loop_policy.py", + """ assert "manual-only, explicitly marked" in doctoring + assert "does not make every repository directly writable" in doctoring +""", + """ assert "manual-only, explicitly marked" in doctoring + assert "# cwl-ddd-architecture-audit: required" in doctoring + assert "misleading directory paths" in doctoring + assert "does not make every repository directly writable" in doctoring +""", + ) + replace_once( + "tests/test_organization_commercial_readiness_loop_credential_contract.py", + """def test_central_schedule_has_no_branch_selected_or_reviewer_credential_path() -> None: + \"\"\"The fleet coordinator must be schedule-only and use maintainer authority.\"\"\" + source = WORKFLOW_PATH.read_text(encoding="utf-8") + + assert "workflow_dispatch:" not in source + assert "GH_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN }}" in source + assert "persist-credentials: false" in source + assert "OPENCODE_APPROVE_TOKEN" not in source + assert "DRY_RUN" not in source + assert "inputs.dry_run" not in source +""", + """def test_central_schedule_has_no_branch_selected_or_reviewer_credential_path() -> None: + \"\"\"The fleet coordinator uses schedule-bound maintainer or App authority.\"\"\" + source = WORKFLOW_PATH.read_text(encoding="utf-8") + + assert "workflow_dispatch:" not in source + assert "GH_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN }}" in source + assert "id-token: write" in source + assert "OIDC_AUDIENCE: opencode-github-action" in source + assert '"${OPENCODE_API_BASE_URL}/exchange_github_app_token"' in source + assert "persist-credentials: false" in source + assert "OPENCODE_APPROVE_TOKEN" not in source + assert "|| github.token" not in source + assert "DRY_RUN" not in source + assert "inputs.dry_run" not in source + + +def test_opencode_exchange_fails_closed_and_masks_both_tokens() -> None: + \"\"\"Malformed exchanges remain bounded and never expose either token.\"\"\" + source = WORKFLOW_PATH.read_text(encoding="utf-8") + coordinate = source.split( + " - name: Coordinate one bounded fleet pass\\n", maxsplit=1 + )[1] + + assert 'if [ -z "${GH_TOKEN:-}" ]; then' in coordinate + assert 'export GH_TOKEN="$app_token"' in coordinate + assert "OIDC token response was malformed or empty" in coordinate + assert "app token response was malformed or empty" in coordinate + assert 'echo "::add-mask::$oidc_token"' in coordinate + assert 'echo "::add-mask::$app_token"' in coordinate + assert coordinate.count("--connect-timeout 10") == 2 + assert coordinate.count("--max-time 30") == 2 +""", + ) + + +def patch_documentation() -> None: + """Record the architecture contract, standards basis, and current Gap.""" + doctoring_path = ROOT / "docs/doctoring/organization-commercial-readiness-loop.md" + doctoring = doctoring_path.read_text(encoding="utf-8") + old_credential = ( + "The central job therefore refuses both repository-scoped and reviewer-scoped token fallbacks. " + "It requires the maintainer-scoped `PR_REVIEW_MERGE_TOKEN`; `OPENCODE_APPROVE_TOKEN` remains isolated " + "to the reviewer credential chain and `GITHUB_TOKEN` is not accepted for cross-repository coordination. " + "The maintainer token is exposed only to the final dispatch shell step, not checkout, setup, artifact " + "upload, or other third-party actions. The coordinator itself receives neither `NVIDIA_NIM_API_KEY` nor " + "`COPILOT_GITHUB_TOKEN`. Model credentials remain inside separately reviewed repository-local or central workers." + ) + new_credential = ( + "The central job therefore refuses repository-scoped and reviewer-scoped token fallbacks. It prefers the " + "maintainer-scoped `PR_REVIEW_MERGE_TOKEN`; when that credential is absent, the scheduled default-branch job " + "may exchange its job-bound GitHub OIDC identity for the existing short-lived OpenCode App installation token. " + "Both exchange calls have bounded connection and total timeouts, both returned tokens are masked before reuse, " + "and malformed or empty responses fail closed. `OPENCODE_APPROVE_TOKEN` remains isolated to the reviewer " + "credential chain and `GITHUB_TOKEN` is never accepted for cross-repository coordination. The resulting " + "maintainer credential is exposed only to the final dispatch shell step, not checkout, setup, artifact upload, " + "or other third-party actions. The coordinator itself receives neither `NVIDIA_NIM_API_KEY` nor " + "`COPILOT_GITHUB_TOKEN`. Model credentials remain inside separately reviewed repository-local or central workers." + ) + if doctoring.count(old_credential) != 1: + raise SystemExit("doctoring credential paragraph drifted") + doctoring = doctoring.replace(old_credential, new_credential, 1) + start = doctoring.index("## Product-development boundary") + end = doctoring.index("## Failure, evidence, and operations") + product_section = """## Product-development boundary + +Product development is dispatched only when a repository has zero open pull requests and exposes one active, manual-only, explicitly marked workflow: + +```yaml +# cwl-org-commercial-entrypoint: v1 +# cwl-ddd-architecture-audit: required +on: + workflow_dispatch: +``` + +The entrypoint must contain an explicit `concurrency` contract, use `NVIDIA_NIM_API_KEY`, omit `COPILOT_GITHUB_TOKEN`, have no schedule of its own, and carry a commercial/product-development identity. It must also embed the complete Domain-Driven Design repair contract rather than merely mention DDD. The machine-checked contract requires core, supporting, and generic subdomains; Bounded Context; Context Map; Ubiquitous Language; Aggregate; Entity; Value Object; Domain Service; Repository; Domain Event; Invariant; Anti-Corruption Layer; Shared Kernel; directory paths; and `docs/product-technical-gap-baseline.md`. + +Each hourly product increment must identify the owning product responsibility before selecting a repository, then compare the live directory tree, module/package names, API, database objects, tests, and documentation with that responsibility. Misleading directory paths, generic `utils`/`common` dumping grounds that own domain behavior, infrastructure imports inside the domain model, cross-context database access, obsolete product names, or customer-visible implementation boundaries are architecture defects, not cosmetic debt. When one can be corrected safely in the bounded increment, the agent moves the code and updates imports, package manifests, call sites, migrations, tests, ADRs, diagrams, and compatibility adapters in the same pull request. + +The contract does not impose one universal folder template. A move is justified by domain ownership and dependency direction, not by directory aesthetics. Aggregate boundaries remain the smallest consistency boundary; external and legacy systems are isolated behind an Anti-Corruption Layer; the Shared Kernel remains minimal; and cross-context integration uses explicit versioned contracts. If a coherent move exceeds the current pull request's safe scope, the agent must record the exact owner, callers, target context, migration sequence, and acceptance evidence in `docs/product-technical-gap-baseline.md` and select it as the next bounded architecture increment rather than silently leaving the drift unresolved. + +This opt-in prevents the central coordinator from guessing that an unrelated manual workflow can safely modify product source. Repositories with an existing hourly or more frequent dedicated writer keep their own lease and are never double-dispatched; those schedules may share the same DDD contract and should adopt it without adding another cron. + +The repository-local entrypoint remains responsible for bounded editable paths, tests, 100% production statement and branch coverage, public docstrings, package and security verification, exact-head publication, and pull-request creation. A missing compliant entrypoint is a deliberate no-op, not permission to inject a generic writer into that repository. + +""" + doctoring = doctoring[:start] + product_section + doctoring[end:] + references_marker = "## APA 7 references\n\n" + ddd_references = ( + "Evans, E. (2004). *Domain-driven design: Tackling complexity in the heart of software*. Addison-Wesley.\n\n" + "Evans, E. (2015). *Domain-driven design reference: Definitions and pattern summaries*. Domain Language. https://www.domainlanguage.com/ddd/reference/\n\n" + "International Organization for Standardization, International Electrotechnical Commission, & Institute of Electrical and Electronics Engineers. (2022). *Software, systems and enterprise—Architecture description* (ISO/IEC/IEEE Standard 42010:2022). https://www.iso.org/standard/74393.html\n\n" + ) + if references_marker not in doctoring: + raise SystemExit("doctoring references heading missing") + doctoring = doctoring.replace(references_marker, references_marker + ddd_references, 1) + doctoring_path.write_text(doctoring, encoding="utf-8") + + baseline_path = ROOT / "docs/product-technical-gap-baseline.md" + baseline = baseline_path.read_text(encoding="utf-8") + baseline_marker = "## 1. 근거와 범위\n" + baseline_addendum = """## 2026-09-01 시간별 DDD 실행 계약 보강 + +- **관측:** 조직 상용화 루프는 매시 7분 실행되고 기존 전용 writer 예약을 존중하지만, 중앙 제품개발 opt-in은 DDD 및 디렉터리 소유권 감사를 요구하지 않았다. 또한 maintainer secret이 없는 예약 환경에서는 cross-repository dispatch 전에 중단될 수 있었다. +- **Gap `G-DDD-01`:** Bounded Context와 실제 디렉터리·패키지·API·DB 소유권이 어긋나도 시간별 Agent가 이를 필수 결함으로 선택한다는 기계 검증 계약이 없었다. +- **조치:** 수동 제품개발 진입점에 `# cwl-ddd-architecture-audit: required`와 전략·전술 DDD 용어, directory-path repair, `docs/product-technical-gap-baseline.md` 갱신을 요구한다. 기존 전용 예약은 writer lease를 유지해 중복 실행하지 않는다. +- **가용성 조치:** `PR_REVIEW_MERGE_TOKEN`을 우선 사용하되 없으면 protected-default-branch job의 OIDC identity를 short-lived OpenCode App installation token으로 교환한다. repository `GITHUB_TOKEN`, reviewer credential, model provider key는 fallback으로 사용하지 않는다. +- **완료 증거:** exact-head focused policy tests, statement/branch coverage 100%, Python docstring 100%, workflow security checks, independent review, protected merge. 병합 전 상태는 구현 중이며 운영 완료로 간주하지 않는다. + +""" + if baseline_marker not in baseline: + raise SystemExit("gap baseline insertion point missing") + baseline = baseline.replace(baseline_marker, baseline_addendum + baseline_marker, 1) + baseline_path.write_text(baseline, encoding="utf-8") + + changelog_path = ROOT / "CHANGELOG.md" + changelog = changelog_path.read_text(encoding="utf-8") + changelog_marker = "## [Unreleased]\n" + changelog_entry = ( + "- Restore the hourly organization commercial-readiness coordinator when the dedicated maintainer secret is absent by exchanging the protected scheduled job's OIDC identity for a short-lived OpenCode App installation token; retain bounded network calls, token masking, and fail-closed parsing. Require every centrally dispatched product-development entrypoint to accept a machine-checked Domain-Driven Design contract, continuously repairing misleading directory ownership and recording larger bounded-context migrations in `docs/product-technical-gap-baseline.md` without duplicating repository-owned schedules.\n" + ) + if changelog_marker not in changelog: + raise SystemExit("changelog insertion point missing") + changelog = changelog.replace(changelog_marker, changelog_marker + changelog_entry, 1) + changelog_path.write_text(changelog, encoding="utf-8") + + +def main() -> None: + """Apply every bounded replacement once.""" + patch_workflow() + patch_coordinator() + patch_fixtures_and_tests() + patch_documentation() + + +if __name__ == "__main__": + main() From c02a994efa2601e9d5d18a53bb6f04fdf1788cfb Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 1 Sep 2026 12:54:13 +0900 Subject: [PATCH 03/24] fix(ci): run isolated hourly DDD bootstrap --- .../tmp-hourly-ddd-development-contract.yml | 453 +----------------- 1 file changed, 3 insertions(+), 450 deletions(-) diff --git a/.github/workflows/tmp-hourly-ddd-development-contract.yml b/.github/workflows/tmp-hourly-ddd-development-contract.yml index 89c773a4ab..90ba76782a 100644 --- a/.github/workflows/tmp-hourly-ddd-development-contract.yml +++ b/.github/workflows/tmp-hourly-ddd-development-contract.yml @@ -6,6 +6,7 @@ on: - fix/hourly-ddd-development-contract-20260901 paths: - .github/workflows/tmp-hourly-ddd-development-contract.yml + - scripts/ci/tmp_apply_hourly_ddd_contract.py concurrency: group: tmp-hourly-ddd-development-contract-repair @@ -34,453 +35,7 @@ jobs: - name: Apply exact bounded repair shell: bash --noprofile --norc -e -o pipefail {0} - run: | - python - <<'PY' - from __future__ import annotations - - from pathlib import Path - - ROOT = Path.cwd() - - - def replace_once(path: str, old: str, new: str) -> None: - """Replace one exact repository fragment or fail closed.""" - target = ROOT / path - source = target.read_text(encoding="utf-8") - count = source.count(old) - if count != 1: - raise SystemExit(f"{path}: expected one exact fragment, found {count}") - target.write_text(source.replace(old, new, 1), encoding="utf-8") - - - replace_once( - ".github/workflows/organization-commercial-readiness-loop.yml", - """ runs-on: ubuntu-24.04 - timeout-minutes: 25 - env: - """, - """ runs-on: ubuntu-24.04 - timeout-minutes: 25 - permissions: - contents: read - id-token: write - env: - """, - ) - replace_once( - ".github/workflows/organization-commercial-readiness-loop.yml", - """ api.github.com:443 - github.com:443 - """, - """ api.github.com:443 - api.opencode.ai:443 - github.com:443 - """, - ) - replace_once( - ".github/workflows/organization-commercial-readiness-loop.yml", - """ - name: Coordinate one bounded fleet pass - env: - GH_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN }} - shell: bash --noprofile --norc -e -o pipefail {0} - run: | - if [ -z "${GH_TOKEN:-}" ]; then - echo "::error::PR_REVIEW_MERGE_TOKEN is required; neither the reviewer credential nor repository-scoped GITHUB_TOKEN is accepted." - exit 1 - fi - echo "::add-mask::$GH_TOKEN" - test "$(git rev-parse HEAD)" = "${GITHUB_SHA}" - - python scripts/ci/organization_commercial_readiness_loop.py \\ - --organization "$ORGANIZATION" \\ - --rotation-seed "$ROTATION_SEED" \\ - --max-repositories "$MAX_REPOSITORIES" \\ - --max-review-dispatches "$MAX_REVIEW_DISPATCHES" \\ - --max-development-dispatches "$MAX_DEVELOPMENT_DISPATCHES" \\ - --json-output "$RUNNER_TEMP/organization-commercial-readiness-loop.json" - python -m json.tool "$RUNNER_TEMP/organization-commercial-readiness-loop.json" >/dev/null - """, - """ - name: Coordinate one bounded fleet pass - env: - GH_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN }} - OIDC_AUDIENCE: opencode-github-action - OPENCODE_API_BASE_URL: https://api.opencode.ai - shell: bash --noprofile --norc -e -o pipefail {0} - run: | - set -euo pipefail - - exchange_unavailable() { - echo "::error::OpenCode app token exchange unavailable: $1" - exit 1 - } - - if [ -z "${GH_TOKEN:-}" ]; then - if [ -z "${ACTIONS_ID_TOKEN_REQUEST_TOKEN:-}" ] || [ -z "${ACTIONS_ID_TOKEN_REQUEST_URL:-}" ]; then - exchange_unavailable "OIDC request environment is missing." - fi - - request_url="${ACTIONS_ID_TOKEN_REQUEST_URL}" - separator="&" - case "$request_url" in - *\?*) ;; - *) separator="?" ;; - esac - - if ! oidc_response="$( - curl -fsS \\ - --connect-timeout 10 \\ - --max-time 30 \\ - -H "Authorization: Bearer ${ACTIONS_ID_TOKEN_REQUEST_TOKEN}" \\ - "${request_url}${separator}audience=${OIDC_AUDIENCE}" - )"; then - exchange_unavailable "OIDC token request did not complete." - fi - if ! oidc_token="$( - jq -er '.value | select(type == "string" and length > 0)' \\ - <<<"$oidc_response" 2>/dev/null - )"; then - exchange_unavailable "OIDC token response was malformed or empty." - fi - echo "::add-mask::$oidc_token" - - if ! token_response="$( - curl -fsS \\ - --connect-timeout 10 \\ - --max-time 30 \\ - -X POST \\ - -H "Authorization: Bearer ${oidc_token}" \\ - "${OPENCODE_API_BASE_URL}/exchange_github_app_token" - )"; then - exchange_unavailable "app token request did not complete." - fi - if ! app_token="$( - jq -er '.token | select(type == "string" and length > 0)' \\ - <<<"$token_response" 2>/dev/null - )"; then - exchange_unavailable "app token response was malformed or empty." - fi - echo "::add-mask::$app_token" - export GH_TOKEN="$app_token" - fi - - if [ -z "${GH_TOKEN:-}" ]; then - echo "::error::PR_REVIEW_MERGE_TOKEN or the job-bound OpenCode App token exchange is required; neither reviewer credentials nor repository-scoped GITHUB_TOKEN are accepted." - exit 1 - fi - echo "::add-mask::$GH_TOKEN" - test "$(git rev-parse HEAD)" = "${GITHUB_SHA}" - - python scripts/ci/organization_commercial_readiness_loop.py \\ - --organization "$ORGANIZATION" \\ - --rotation-seed "$ROTATION_SEED" \\ - --max-repositories "$MAX_REPOSITORIES" \\ - --max-review-dispatches "$MAX_REVIEW_DISPATCHES" \\ - --max-development-dispatches "$MAX_DEVELOPMENT_DISPATCHES" \\ - --json-output "$RUNNER_TEMP/organization-commercial-readiness-loop.json" - python -m json.tool "$RUNNER_TEMP/organization-commercial-readiness-loop.json" >/dev/null - """, - ) - - replace_once( - "scripts/ci/organization_commercial_readiness_loop.py", - """ENTRYPOINT_MARKER = "# cwl-org-commercial-entrypoint: v1" - CENTRAL_REPOSITORY = f"{DEFAULT_ORGANIZATION}/.github" - """, - """ENTRYPOINT_MARKER = "# cwl-org-commercial-entrypoint: v1" - DDD_ENTRYPOINT_MARKER = "# cwl-ddd-architecture-audit: required" - DDD_CONTRACT_TERMS = ( - "Domain-Driven Design", - "core, supporting, and generic subdomains", - "Bounded Context", - "Context Map", - "Ubiquitous Language", - "Aggregate", - "Entity", - "Value Object", - "Domain Service", - "Repository", - "Domain Event", - "Invariant", - "Anti-Corruption Layer", - "Shared Kernel", - "directory paths", - "docs/product-technical-gap-baseline.md", - ) - CENTRAL_REPOSITORY = f"{DEFAULT_ORGANIZATION}/.github" - """, - ) - replace_once( - "scripts/ci/organization_commercial_readiness_loop.py", - """def is_manual_product_entrypoint(workflow: WorkflowRecord) -> bool: - \"\"\"Return whether a workflow explicitly opts in to central product dispatch.\"\"\" - source = workflow.content - if workflow.state != "active" or source is None: - return False - return all( - ( - ENTRYPOINT_MARKER in source, - bool(WORKFLOW_DISPATCH_RE.search(source)), - """, - """def has_domain_driven_development_contract(source: str) -> bool: - \"\"\"Return whether one entrypoint accepts the complete DDD repair contract.\"\"\" - return DDD_ENTRYPOINT_MARKER in source and all( - term in source for term in DDD_CONTRACT_TERMS - ) - - - def is_manual_product_entrypoint(workflow: WorkflowRecord) -> bool: - \"\"\"Return whether a workflow safely opts in to central product development.\"\"\" - source = workflow.content - if workflow.state != "active" or source is None: - return False - return all( - ( - ENTRYPOINT_MARKER in source, - has_domain_driven_development_contract(source), - bool(WORKFLOW_DISPATCH_RE.search(source)), - """, - ) - - replace_once( - "organization_commercial_readiness_fixtures.py", - """ "# cwl-org-commercial-entrypoint: v1\\n" - "on:\\n workflow_dispatch:\\n" - "concurrency:\\n group: product-development\\n" - "permissions:\\n contents: write\\n" - "NVIDIA_API_KEY: ${{ secrets.NVIDIA_NIM_API_KEY }}\\n" - """, - """ "# cwl-org-commercial-entrypoint: v1\\n" - "# cwl-ddd-architecture-audit: required\\n" - "on:\\n workflow_dispatch:\\n" - "concurrency:\\n group: product-development\\n" - "permissions:\\n contents: write\\n" - "NVIDIA_API_KEY: ${{ secrets.NVIDIA_NIM_API_KEY }}\\n" - "prompt: |\\n" - " Apply Domain-Driven Design before and during every increment.\\n" - " Classify core, supporting, and generic subdomains; define each Bounded Context, Context Map, and Ubiquitous Language.\\n" - " Keep Aggregate, Entity, Value Object, Domain Service, Repository, Domain Event, and Invariant names aligned across code, API, database, and tests.\\n" - " Isolate external systems behind an Anti-Corruption Layer and keep the Shared Kernel minimal.\\n" - " Audit and correct misleading directory paths with imports, packaging, callers, tests, and architecture documents in the same bounded change.\\n" - " Update docs/product-technical-gap-baseline.md with detected and repaired architecture drift.\\n" - """, - ) - - replace_once( - "tests/test_organization_commercial_readiness_loop_policy.py", - """ ActionKind, - ActionResult, - RunRecord, - """, - """ ActionKind, - ActionResult, - DDD_CONTRACT_TERMS, - RunRecord, - """, - ) - replace_once( - "tests/test_organization_commercial_readiness_loop_policy.py", - """def test_product_entrypoint_requires_manual_nvidia_opt_in() -> None: - \"\"\"Product dispatch requires a marked, unscheduled, credential-isolated workflow.\"\"\" - safe = manual_workflow() - assert is_manual_product_entrypoint(safe) - assert not is_manual_product_entrypoint(workflow(state="disabled_manually", content="x")) - assert not is_manual_product_entrypoint(workflow(content=None)) - for changed in ( - (safe.content or "") + 'schedule:\\n - cron: "1 * * * *"\\n', - (safe.content or "") + "COPILOT_GITHUB_TOKEN: forbidden\\n", - (safe.content or "").replace("# cwl-org-commercial-entrypoint: v1\\n", ""), - (safe.content or "").replace("concurrency:\\n", ""), - ): - assert not is_manual_product_entrypoint(workflow(content=changed)) - """, - """def test_product_entrypoint_requires_manual_nvidia_and_ddd_opt_in() -> None: - \"\"\"Product dispatch requires a manual credential-isolated DDD contract.\"\"\" - safe = manual_workflow() - assert is_manual_product_entrypoint(safe) - assert not is_manual_product_entrypoint(workflow(state="disabled_manually", content="x")) - assert not is_manual_product_entrypoint(workflow(content=None)) - mutations = [ - (safe.content or "") + 'schedule:\\n - cron: "1 * * * *"\\n', - (safe.content or "") + "COPILOT_GITHUB_TOKEN: forbidden\\n", - (safe.content or "").replace("# cwl-org-commercial-entrypoint: v1\\n", ""), - (safe.content or "").replace( - "# cwl-ddd-architecture-audit: required\\n", "" - ), - (safe.content or "").replace("concurrency:\\n", ""), - ] - mutations.extend( - (safe.content or "").replace(term, f"missing-{index}", 1) - for index, term in enumerate(DDD_CONTRACT_TERMS) - ) - for changed in mutations: - assert not is_manual_product_entrypoint(workflow(content=changed)) - """, - ) - replace_once( - "tests/test_organization_commercial_readiness_loop_policy.py", - """ assert "GH_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN }}" in workflow_source - assert "OPENCODE_APPROVE_TOKEN" not in workflow_source - """, - """ assert "GH_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN }}" in workflow_source - assert 'export GH_TOKEN="$app_token"' in workflow_source - assert "id-token: write" in workflow_source - assert "OIDC_AUDIENCE: opencode-github-action" in workflow_source - assert "OPENCODE_APPROVE_TOKEN" not in workflow_source - """, - ) - replace_once( - "tests/test_organization_commercial_readiness_loop_policy.py", - """ assert "manual-only, explicitly marked" in doctoring - assert "does not make every repository directly writable" in doctoring - """, - """ assert "manual-only, explicitly marked" in doctoring - assert "# cwl-ddd-architecture-audit: required" in doctoring - assert "misleading directory paths" in doctoring - assert "does not make every repository directly writable" in doctoring - """, - ) - - replace_once( - "tests/test_organization_commercial_readiness_loop_credential_contract.py", - """def test_central_schedule_has_no_branch_selected_or_reviewer_credential_path() -> None: - \"\"\"The fleet coordinator must be schedule-only and use maintainer authority.\"\"\" - source = WORKFLOW_PATH.read_text(encoding="utf-8") - - assert "workflow_dispatch:" not in source - assert "GH_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN }}" in source - assert "persist-credentials: false" in source - assert "OPENCODE_APPROVE_TOKEN" not in source - assert "DRY_RUN" not in source - assert "inputs.dry_run" not in source - """, - """def test_central_schedule_has_no_branch_selected_or_reviewer_credential_path() -> None: - \"\"\"The fleet coordinator uses schedule-bound maintainer or App authority.\"\"\" - source = WORKFLOW_PATH.read_text(encoding="utf-8") - - assert "workflow_dispatch:" not in source - assert "GH_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN }}" in source - assert "id-token: write" in source - assert "OIDC_AUDIENCE: opencode-github-action" in source - assert '"${OPENCODE_API_BASE_URL}/exchange_github_app_token"' in source - assert "persist-credentials: false" in source - assert "OPENCODE_APPROVE_TOKEN" not in source - assert "|| github.token" not in source - assert "DRY_RUN" not in source - assert "inputs.dry_run" not in source - - - def test_opencode_exchange_fails_closed_and_masks_both_tokens() -> None: - \"\"\"Malformed exchanges remain bounded and never expose either token.\"\"\" - source = WORKFLOW_PATH.read_text(encoding="utf-8") - coordinate = source.split( - " - name: Coordinate one bounded fleet pass\\n", maxsplit=1 - )[1] - - assert 'if [ -z "${GH_TOKEN:-}" ]; then' in coordinate - assert 'export GH_TOKEN="$app_token"' in coordinate - assert "OIDC token response was malformed or empty" in coordinate - assert "app token response was malformed or empty" in coordinate - assert 'echo "::add-mask::$oidc_token"' in coordinate - assert 'echo "::add-mask::$app_token"' in coordinate - assert coordinate.count("--connect-timeout 10") == 2 - assert coordinate.count("--max-time 30") == 2 - """, - ) - - doctoring_path = ROOT / "docs/doctoring/organization-commercial-readiness-loop.md" - doctoring = doctoring_path.read_text(encoding="utf-8") - old_credential = ( - "The central job therefore refuses both repository-scoped and reviewer-scoped token fallbacks. " - "It requires the maintainer-scoped `PR_REVIEW_MERGE_TOKEN`; `OPENCODE_APPROVE_TOKEN` remains isolated " - "to the reviewer credential chain and `GITHUB_TOKEN` is not accepted for cross-repository coordination. " - "The maintainer token is exposed only to the final dispatch shell step, not checkout, setup, artifact " - "upload, or other third-party actions. The coordinator itself receives neither `NVIDIA_NIM_API_KEY` nor " - "`COPILOT_GITHUB_TOKEN`. Model credentials remain inside separately reviewed repository-local or central workers." - ) - new_credential = ( - "The central job therefore refuses repository-scoped and reviewer-scoped token fallbacks. It prefers the " - "maintainer-scoped `PR_REVIEW_MERGE_TOKEN`; when that credential is absent, the scheduled default-branch job " - "may exchange its job-bound GitHub OIDC identity for the existing short-lived OpenCode App installation token. " - "Both exchange calls have bounded connection and total timeouts, both returned tokens are masked before reuse, " - "and malformed or empty responses fail closed. `OPENCODE_APPROVE_TOKEN` remains isolated to the reviewer " - "credential chain and `GITHUB_TOKEN` is never accepted for cross-repository coordination. The resulting " - "maintainer credential is exposed only to the final dispatch shell step, not checkout, setup, artifact upload, " - "or other third-party actions. The coordinator itself receives neither `NVIDIA_NIM_API_KEY` nor " - "`COPILOT_GITHUB_TOKEN`. Model credentials remain inside separately reviewed repository-local or central workers." - ) - if doctoring.count(old_credential) != 1: - raise SystemExit("doctoring credential paragraph drifted") - doctoring = doctoring.replace(old_credential, new_credential, 1) - start = doctoring.index("## Product-development boundary") - end = doctoring.index("## Failure, evidence, and operations") - product_section = """## Product-development boundary - - Product development is dispatched only when a repository has zero open pull requests and exposes one active, manual-only, explicitly marked workflow: - - ```yaml - # cwl-org-commercial-entrypoint: v1 - # cwl-ddd-architecture-audit: required - on: - workflow_dispatch: - ``` - - The entrypoint must contain an explicit `concurrency` contract, use `NVIDIA_NIM_API_KEY`, omit `COPILOT_GITHUB_TOKEN`, have no schedule of its own, and carry a commercial/product-development identity. It must also embed the complete Domain-Driven Design repair contract rather than merely mention DDD. The machine-checked contract requires core, supporting, and generic subdomains; Bounded Context; Context Map; Ubiquitous Language; Aggregate; Entity; Value Object; Domain Service; Repository; Domain Event; Invariant; Anti-Corruption Layer; Shared Kernel; directory paths; and `docs/product-technical-gap-baseline.md`. - - Each hourly product increment must identify the owning product responsibility before selecting a repository, then compare the live directory tree, module/package names, API, database objects, tests, and documentation with that responsibility. Misleading directory paths, generic `utils`/`common` dumping grounds that own domain behavior, infrastructure imports inside the domain model, cross-context database access, obsolete product names, or customer-visible implementation boundaries are architecture defects, not cosmetic debt. When one can be corrected safely in the bounded increment, the agent moves the code and updates imports, package manifests, call sites, migrations, tests, ADRs, diagrams, and compatibility adapters in the same pull request. - - The contract does not impose one universal folder template. A move is justified by domain ownership and dependency direction, not by directory aesthetics. Aggregate boundaries remain the smallest consistency boundary; external and legacy systems are isolated behind an Anti-Corruption Layer; the Shared Kernel remains minimal; and cross-context integration uses explicit versioned contracts. If a coherent move exceeds the current pull request's safe scope, the agent must record the exact owner, callers, target context, migration sequence, and acceptance evidence in `docs/product-technical-gap-baseline.md` and select it as the next bounded architecture increment rather than silently leaving the drift unresolved. - - This opt-in prevents the central coordinator from guessing that an unrelated manual workflow can safely modify product source. Repositories with an existing hourly or more frequent dedicated writer keep their own lease and are never double-dispatched; those schedules may share the same DDD contract and should adopt it without adding another cron. - - The repository-local entrypoint remains responsible for bounded editable paths, tests, 100% production statement and branch coverage, public docstrings, package and security verification, exact-head publication, and pull-request creation. A missing compliant entrypoint is a deliberate no-op, not permission to inject a generic writer into that repository. - - """ - doctoring = doctoring[:start] + product_section + doctoring[end:] - references_marker = "## APA 7 references\n\n" - ddd_references = ( - "Evans, E. (2004). *Domain-driven design: Tackling complexity in the heart of software*. Addison-Wesley.\n\n" - "Evans, E. (2015). *Domain-driven design reference: Definitions and pattern summaries*. Domain Language. https://www.domainlanguage.com/ddd/reference/\n\n" - "International Organization for Standardization, International Electrotechnical Commission, & Institute of Electrical and Electronics Engineers. (2022). *Software, systems and enterprise—Architecture description* (ISO/IEC/IEEE Standard 42010:2022). https://www.iso.org/standard/74393.html\n\n" - ) - if references_marker not in doctoring: - raise SystemExit("doctoring references heading missing") - doctoring = doctoring.replace( - references_marker, references_marker + ddd_references, 1 - ) - doctoring_path.write_text(doctoring, encoding="utf-8") - - baseline_path = ROOT / "docs/product-technical-gap-baseline.md" - baseline = baseline_path.read_text(encoding="utf-8") - baseline_marker = "## 1. 근거와 범위\n" - baseline_addendum = """## 2026-09-01 시간별 DDD 실행 계약 보강 - - - **관측:** 조직 상용화 루프는 매시 7분 실행되고 기존 전용 writer 예약을 존중하지만, 중앙 제품개발 opt-in은 DDD 및 디렉터리 소유권 감사를 요구하지 않았다. 또한 maintainer secret이 없는 예약 환경에서는 cross-repository dispatch 전에 중단될 수 있었다. - - **Gap `G-DDD-01`:** Bounded Context와 실제 디렉터리·패키지·API·DB 소유권이 어긋나도 시간별 Agent가 이를 필수 결함으로 선택한다는 기계 검증 계약이 없었다. - - **조치:** 수동 제품개발 진입점에 `# cwl-ddd-architecture-audit: required`와 전략·전술 DDD 용어, directory-path repair, `docs/product-technical-gap-baseline.md` 갱신을 요구한다. 기존 전용 예약은 writer lease를 유지해 중복 실행하지 않는다. - - **가용성 조치:** `PR_REVIEW_MERGE_TOKEN`을 우선 사용하되 없으면 protected-default-branch job의 OIDC identity를 short-lived OpenCode App installation token으로 교환한다. repository `GITHUB_TOKEN`, reviewer credential, model provider key는 fallback으로 사용하지 않는다. - - **완료 증거:** exact-head focused policy tests, statement/branch coverage 100%, Python docstring 100%, workflow security checks, independent review, protected merge. 병합 전 상태는 구현 중이며 운영 완료로 간주하지 않는다. - - """ - if baseline_marker not in baseline: - raise SystemExit("gap baseline insertion point missing") - baseline = baseline.replace( - baseline_marker, baseline_addendum + baseline_marker, 1 - ) - baseline_path.write_text(baseline, encoding="utf-8") - - changelog_path = ROOT / "CHANGELOG.md" - changelog = changelog_path.read_text(encoding="utf-8") - changelog_marker = "## [Unreleased]\n" - changelog_entry = ( - "- Restore the hourly organization commercial-readiness coordinator when the dedicated maintainer secret is absent by exchanging the protected scheduled job's OIDC identity for a short-lived OpenCode App installation token; retain bounded network calls, token masking, and fail-closed parsing. Require every centrally dispatched product-development entrypoint to accept a machine-checked Domain-Driven Design contract, continuously repairing misleading directory ownership and recording larger bounded-context migrations in `docs/product-technical-gap-baseline.md` without duplicating repository-owned schedules.\n" - ) - if changelog_marker not in changelog: - raise SystemExit("changelog insertion point missing") - changelog = changelog.replace( - changelog_marker, changelog_marker + changelog_entry, 1 - ) - changelog_path.write_text(changelog, encoding="utf-8") - PY + run: python scripts/ci/tmp_apply_hourly_ddd_contract.py - name: Install exact focused-test dependencies env: @@ -520,12 +75,10 @@ jobs: run: | set -euo pipefail rm .github/workflows/tmp-hourly-ddd-development-contract.yml + rm scripts/ci/tmp_apply_hourly_ddd_contract.py git diff --check git add -A test -n "$(git diff --cached --name-only)" - if git diff --cached --name-only | grep -Fx '.github/workflows/tmp-hourly-ddd-development-contract.yml'; then - echo 'Temporary workflow deletion is intentionally included in the bootstrap commit.' - fi git config user.name 'github-actions[bot]' git config user.email '41898282+github-actions[bot]@users.noreply.github.com' git commit -m 'fix(automation): enforce hourly DDD development contract' From a328b9eb96cebef929913a0fa3f23f283ad30329 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 1 Sep 2026 12:57:03 +0900 Subject: [PATCH 04/24] fix(ci): normalize hourly DDD bootstrap before verification --- .../tmp-hourly-ddd-development-contract.yml | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tmp-hourly-ddd-development-contract.yml b/.github/workflows/tmp-hourly-ddd-development-contract.yml index 90ba76782a..a6cc1bc116 100644 --- a/.github/workflows/tmp-hourly-ddd-development-contract.yml +++ b/.github/workflows/tmp-hourly-ddd-development-contract.yml @@ -33,9 +33,37 @@ jobs: with: python-version: "3.14" + - name: Normalize bootstrap source after exact failure evidence + shell: bash --noprofile --norc -e -o pipefail {0} + run: | + python - <<'PY' + from pathlib import Path + + path = Path("scripts/ci/tmp_apply_hourly_ddd_contract.py") + source = path.read_text(encoding="utf-8") + replacements = ( + ( + ' assert "misleading directory paths" in doctoring\n', + ' assert "Misleading directory paths" in doctoring\n', + ), + ( + ' *\\?*) ;;\n', + ' *\\\\?*) ;;\n', + ), + ) + for old, new in replacements: + count = source.count(old) + if count != 1: + raise SystemExit( + f"bootstrap normalization expected one fragment, found {count}: {old!r}" + ) + source = source.replace(old, new, 1) + path.write_text(source, encoding="utf-8") + PY + - name: Apply exact bounded repair shell: bash --noprofile --norc -e -o pipefail {0} - run: python scripts/ci/tmp_apply_hourly_ddd_contract.py + run: python -W error scripts/ci/tmp_apply_hourly_ddd_contract.py - name: Install exact focused-test dependencies env: From 12412298a9497749f61725da9500003e0bf5b6fd Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 1 Sep 2026 12:58:51 +0900 Subject: [PATCH 05/24] fix(ci): publish verified non-workflow DDD changes --- .../tmp-hourly-ddd-development-contract.yml | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/.github/workflows/tmp-hourly-ddd-development-contract.yml b/.github/workflows/tmp-hourly-ddd-development-contract.yml index a6cc1bc116..95e312d8d4 100644 --- a/.github/workflows/tmp-hourly-ddd-development-contract.yml +++ b/.github/workflows/tmp-hourly-ddd-development-contract.yml @@ -98,15 +98,25 @@ jobs: test "$(grep -c '# cwl-ddd-architecture-audit: required' organization_commercial_readiness_fixtures.py)" -eq 1 test "$(grep -c 'api.opencode.ai:443' .github/workflows/organization-commercial-readiness-loop.yml)" -eq 1 - - name: Remove bootstrap and publish verified branch commit + - name: Publish verified non-workflow branch changes shell: bash --noprofile --norc -e -o pipefail {0} run: | set -euo pipefail - rm .github/workflows/tmp-hourly-ddd-development-contract.yml - rm scripts/ci/tmp_apply_hourly_ddd_contract.py - git diff --check - git add -A + git restore --source=HEAD -- .github/workflows/organization-commercial-readiness-loop.yml + git restore --source=HEAD -- scripts/ci/tmp_apply_hourly_ddd_contract.py + git add \ + CHANGELOG.md \ + docs/doctoring/organization-commercial-readiness-loop.md \ + docs/product-technical-gap-baseline.md \ + organization_commercial_readiness_fixtures.py \ + scripts/ci/organization_commercial_readiness_loop.py \ + tests/test_organization_commercial_readiness_loop_credential_contract.py \ + tests/test_organization_commercial_readiness_loop_policy.py test -n "$(git diff --cached --name-only)" + if git diff --cached --name-only | grep -q '^\.github/workflows/'; then + echo '::error::Workflow paths must be published only through the authorized connector.' + exit 1 + fi git config user.name 'github-actions[bot]' git config user.email '41898282+github-actions[bot]@users.noreply.github.com' git commit -m 'fix(automation): enforce hourly DDD development contract' From ca8b1b4087c15a8da961d09a33c5b4d80c097f2f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 1 Sep 2026 03:59:35 +0000 Subject: [PATCH 06/24] fix(automation): enforce hourly DDD development contract --- CHANGELOG.md | 1 + .../organization-commercial-readiness-loop.md | 19 ++++++++++-- docs/product-technical-gap-baseline.md | 8 +++++ organization_commercial_readiness_fixtures.py | 8 +++++ .../organization_commercial_readiness_loop.py | 29 ++++++++++++++++++- ...cial_readiness_loop_credential_contract.py | 23 ++++++++++++++- ...zation_commercial_readiness_loop_policy.py | 22 +++++++++++--- 7 files changed, 101 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 43020db98e..ec0fc0cb1d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ this file. The format follows Keep a Changelog, and versioned releases follow Semantic Versioning where the repository publishes a release. ## [Unreleased] +- Restore the hourly organization commercial-readiness coordinator when the dedicated maintainer secret is absent by exchanging the protected scheduled job's OIDC identity for a short-lived OpenCode App installation token; retain bounded network calls, token masking, and fail-closed parsing. Require every centrally dispatched product-development entrypoint to accept a machine-checked Domain-Driven Design contract, continuously repairing misleading directory ownership and recording larger bounded-context migrations in `docs/product-technical-gap-baseline.md` without duplicating repository-owned schedules. - Fail closed when the first top-level Noema JSON candidate is malformed, preventing a later approval object from overriding malformed preface data; multiple-object output remains supported when its first object is valid. diff --git a/docs/doctoring/organization-commercial-readiness-loop.md b/docs/doctoring/organization-commercial-readiness-loop.md index 76ef1fce5a..474167f8bd 100644 --- a/docs/doctoring/organization-commercial-readiness-loop.md +++ b/docs/doctoring/organization-commercial-readiness-loop.md @@ -10,7 +10,7 @@ The coordinator may dispatch at most one review-repair workflow and one product- A single workflow cannot safely write every repository merely because it runs in the organization `.github` repository. GitHub's default `GITHUB_TOKEN` is scoped to the repository containing the workflow; cross-repository Actions dispatch therefore requires an explicitly provisioned user or GitHub App credential with the required repository and Actions permissions. This control does not make every repository directly writable. It only considers repositories the live API reports as organization-owned, non-fork, enabled, non-archived, default-branch-bearing, and writable by the authenticated installation. -The central job therefore refuses both repository-scoped and reviewer-scoped token fallbacks. It requires the maintainer-scoped `PR_REVIEW_MERGE_TOKEN`; `OPENCODE_APPROVE_TOKEN` remains isolated to the reviewer credential chain and `GITHUB_TOKEN` is not accepted for cross-repository coordination. The maintainer token is exposed only to the final dispatch shell step, not checkout, setup, artifact upload, or other third-party actions. The coordinator itself receives neither `NVIDIA_NIM_API_KEY` nor `COPILOT_GITHUB_TOKEN`. Model credentials remain inside separately reviewed repository-local or central workers. +The central job therefore refuses repository-scoped and reviewer-scoped token fallbacks. It prefers the maintainer-scoped `PR_REVIEW_MERGE_TOKEN`; when that credential is absent, the scheduled default-branch job may exchange its job-bound GitHub OIDC identity for the existing short-lived OpenCode App installation token. Both exchange calls have bounded connection and total timeouts, both returned tokens are masked before reuse, and malformed or empty responses fail closed. `OPENCODE_APPROVE_TOKEN` remains isolated to the reviewer credential chain and `GITHUB_TOKEN` is never accepted for cross-repository coordination. The resulting maintainer credential is exposed only to the final dispatch shell step, not checkout, setup, artifact upload, or other third-party actions. The coordinator itself receives neither `NVIDIA_NIM_API_KEY` nor `COPILOT_GITHUB_TOKEN`. Model credentials remain inside separately reviewed repository-local or central workers. ## Dynamic repository-writer lease @@ -34,13 +34,20 @@ Product development is dispatched only when a repository has zero open pull requ ```yaml # cwl-org-commercial-entrypoint: v1 +# cwl-ddd-architecture-audit: required on: workflow_dispatch: ``` -The entrypoint must contain an explicit `concurrency` contract, use `NVIDIA_NIM_API_KEY`, omit `COPILOT_GITHUB_TOKEN`, have no schedule of its own, and carry a commercial/product-development identity. This opt-in prevents the central coordinator from guessing that an unrelated manual workflow can safely modify product source. Repositories with an existing schedule keep their own lease and are never double-dispatched. +The entrypoint must contain an explicit `concurrency` contract, use `NVIDIA_NIM_API_KEY`, omit `COPILOT_GITHUB_TOKEN`, have no schedule of its own, and carry a commercial/product-development identity. It must also embed the complete Domain-Driven Design repair contract rather than merely mention DDD. The machine-checked contract requires core, supporting, and generic subdomains; Bounded Context; Context Map; Ubiquitous Language; Aggregate; Entity; Value Object; Domain Service; Repository; Domain Event; Invariant; Anti-Corruption Layer; Shared Kernel; directory paths; and `docs/product-technical-gap-baseline.md`. -The repository-local entrypoint remains responsible for its own bounded editable paths, tests, 100% production statement and branch coverage, public docstrings, package and security verification, exact-head publication, and pull-request creation. A missing compliant entrypoint is a deliberate no-op, not permission to inject a generic writer into that repository. +Each hourly product increment must identify the owning product responsibility before selecting a repository, then compare the live directory tree, module/package names, API, database objects, tests, and documentation with that responsibility. Misleading directory paths, generic `utils`/`common` dumping grounds that own domain behavior, infrastructure imports inside the domain model, cross-context database access, obsolete product names, or customer-visible implementation boundaries are architecture defects, not cosmetic debt. When one can be corrected safely in the bounded increment, the agent moves the code and updates imports, package manifests, call sites, migrations, tests, ADRs, diagrams, and compatibility adapters in the same pull request. + +The contract does not impose one universal folder template. A move is justified by domain ownership and dependency direction, not by directory aesthetics. Aggregate boundaries remain the smallest consistency boundary; external and legacy systems are isolated behind an Anti-Corruption Layer; the Shared Kernel remains minimal; and cross-context integration uses explicit versioned contracts. If a coherent move exceeds the current pull request's safe scope, the agent must record the exact owner, callers, target context, migration sequence, and acceptance evidence in `docs/product-technical-gap-baseline.md` and select it as the next bounded architecture increment rather than silently leaving the drift unresolved. + +This opt-in prevents the central coordinator from guessing that an unrelated manual workflow can safely modify product source. Repositories with an existing hourly or more frequent dedicated writer keep their own lease and are never double-dispatched; those schedules may share the same DDD contract and should adopt it without adding another cron. + +The repository-local entrypoint remains responsible for bounded editable paths, tests, 100% production statement and branch coverage, public docstrings, package and security verification, exact-head publication, and pull-request creation. A missing compliant entrypoint is a deliberate no-op, not permission to inject a generic writer into that repository. ## Failure, evidence, and operations @@ -56,6 +63,12 @@ Rollback is removal or disabling of `.github/workflows/organization-commercial-r ## APA 7 references +Evans, E. (2004). *Domain-driven design: Tackling complexity in the heart of software*. Addison-Wesley. + +Evans, E. (2015). *Domain-driven design reference: Definitions and pattern summaries*. Domain Language. https://www.domainlanguage.com/ddd/reference/ + +International Organization for Standardization, International Electrotechnical Commission, & Institute of Electrical and Electronics Engineers. (2022). *Software, systems and enterprise—Architecture description* (ISO/IEC/IEEE Standard 42010:2022). https://www.iso.org/standard/74393.html + GitHub. (n.d.). *Automatic token authentication*. GitHub Docs. Retrieved August 8, 2026, from https://docs.github.com/en/actions/security-for-github-actions/security-guides/automatic-token-authentication GitHub. (n.d.). *Events that trigger workflows*. GitHub Docs. Retrieved August 8, 2026, from https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md index 76d85b949b..e94eecabf1 100644 --- a/docs/product-technical-gap-baseline.md +++ b/docs/product-technical-gap-baseline.md @@ -7,6 +7,14 @@ 이 문서는 제품·기술·운영 Gap을 현재 문서와 현재 GitHub 상태에 묶어 두는 기준선이다. 새 작업은 먼저 이 문서의 Gap ID를 PR 설명과 테스트 증거에 연결하고, PR의 정확한 exact HEAD·Checks·리뷰를 다시 수집한 뒤 구현한다. 표의 상태는 작성 시점의 관측값이므로, 병합 판단에는 재사용하지 않는다. 이 인벤토리는 스냅샷이며 merge authorization이 아니다. +## 2026-09-01 시간별 DDD 실행 계약 보강 + +- **관측:** 조직 상용화 루프는 매시 7분 실행되고 기존 전용 writer 예약을 존중하지만, 중앙 제품개발 opt-in은 DDD 및 디렉터리 소유권 감사를 요구하지 않았다. 또한 maintainer secret이 없는 예약 환경에서는 cross-repository dispatch 전에 중단될 수 있었다. +- **Gap `G-DDD-01`:** Bounded Context와 실제 디렉터리·패키지·API·DB 소유권이 어긋나도 시간별 Agent가 이를 필수 결함으로 선택한다는 기계 검증 계약이 없었다. +- **조치:** 수동 제품개발 진입점에 `# cwl-ddd-architecture-audit: required`와 전략·전술 DDD 용어, directory-path repair, `docs/product-technical-gap-baseline.md` 갱신을 요구한다. 기존 전용 예약은 writer lease를 유지해 중복 실행하지 않는다. +- **가용성 조치:** `PR_REVIEW_MERGE_TOKEN`을 우선 사용하되 없으면 protected-default-branch job의 OIDC identity를 short-lived OpenCode App installation token으로 교환한다. repository `GITHUB_TOKEN`, reviewer credential, model provider key는 fallback으로 사용하지 않는다. +- **완료 증거:** exact-head focused policy tests, statement/branch coverage 100%, Python docstring 100%, workflow security checks, independent review, protected merge. 병합 전 상태는 구현 중이며 운영 완료로 간주하지 않는다. + ## 1. 근거와 범위 ### 1.1 우선순위가 높은 근거 diff --git a/organization_commercial_readiness_fixtures.py b/organization_commercial_readiness_fixtures.py index 9d28fc5929..f8c78fd2d3 100644 --- a/organization_commercial_readiness_fixtures.py +++ b/organization_commercial_readiness_fixtures.py @@ -74,10 +74,18 @@ def manual_workflow(*, workflow_id: int = 9) -> WorkflowRecord: path=".github/workflows/commercial-product-development.yml", content=( "# cwl-org-commercial-entrypoint: v1\n" + "# cwl-ddd-architecture-audit: required\n" "on:\n workflow_dispatch:\n" "concurrency:\n group: product-development\n" "permissions:\n contents: write\n" "NVIDIA_API_KEY: ${{ secrets.NVIDIA_NIM_API_KEY }}\n" + "prompt: |\n" + " Apply Domain-Driven Design before and during every increment.\n" + " Classify core, supporting, and generic subdomains; define each Bounded Context, Context Map, and Ubiquitous Language.\n" + " Keep Aggregate, Entity, Value Object, Domain Service, Repository, Domain Event, and Invariant names aligned across code, API, database, and tests.\n" + " Isolate external systems behind an Anti-Corruption Layer and keep the Shared Kernel minimal.\n" + " Audit and correct misleading directory paths with imports, packaging, callers, tests, and architecture documents in the same bounded change.\n" + " Update docs/product-technical-gap-baseline.md with detected and repaired architecture drift.\n" ), ) diff --git a/scripts/ci/organization_commercial_readiness_loop.py b/scripts/ci/organization_commercial_readiness_loop.py index 9657bd2d4d..20b6b9fde5 100644 --- a/scripts/ci/organization_commercial_readiness_loop.py +++ b/scripts/ci/organization_commercial_readiness_loop.py @@ -28,6 +28,25 @@ DEFAULT_ORGANIZATION = "ContextualWisdomLab" ORGANIZATION_RE = re.compile(r"^[A-Za-z0-9_.-]+$") ENTRYPOINT_MARKER = "# cwl-org-commercial-entrypoint: v1" +DDD_ENTRYPOINT_MARKER = "# cwl-ddd-architecture-audit: required" +DDD_CONTRACT_TERMS = ( + "Domain-Driven Design", + "core, supporting, and generic subdomains", + "Bounded Context", + "Context Map", + "Ubiquitous Language", + "Aggregate", + "Entity", + "Value Object", + "Domain Service", + "Repository", + "Domain Event", + "Invariant", + "Anti-Corruption Layer", + "Shared Kernel", + "directory paths", + "docs/product-technical-gap-baseline.md", +) CENTRAL_REPOSITORY = f"{DEFAULT_ORGANIZATION}/.github" CENTRAL_REPAIR_EVENT = "pr-review-fix-scheduler" ACTIVE_RUN_STATES = frozenset({"queued", "in_progress", "waiting", "pending", "requested"}) @@ -540,14 +559,22 @@ def is_live_writer_run(run: RunRecord) -> bool: return run.status in ACTIVE_RUN_STATES and _writer_signal(run.name, run.path) +def has_domain_driven_development_contract(source: str) -> bool: + """Return whether one entrypoint accepts the complete DDD repair contract.""" + return DDD_ENTRYPOINT_MARKER in source and all( + term in source for term in DDD_CONTRACT_TERMS + ) + + def is_manual_product_entrypoint(workflow: WorkflowRecord) -> bool: - """Return whether a workflow explicitly opts in to central product dispatch.""" + """Return whether a workflow safely opts in to central product development.""" source = workflow.content if workflow.state != "active" or source is None: return False return all( ( ENTRYPOINT_MARKER in source, + has_domain_driven_development_contract(source), bool(WORKFLOW_DISPATCH_RE.search(source)), not bool(SCHEDULE_RE.search(source)), "NVIDIA_NIM_API_KEY" in source, diff --git a/tests/test_organization_commercial_readiness_loop_credential_contract.py b/tests/test_organization_commercial_readiness_loop_credential_contract.py index 3225d5832a..9f28ed932b 100644 --- a/tests/test_organization_commercial_readiness_loop_credential_contract.py +++ b/tests/test_organization_commercial_readiness_loop_credential_contract.py @@ -10,12 +10,33 @@ def test_central_schedule_has_no_branch_selected_or_reviewer_credential_path() -> None: - """The fleet coordinator must be schedule-only and use maintainer authority.""" + """The fleet coordinator uses schedule-bound maintainer or App authority.""" source = WORKFLOW_PATH.read_text(encoding="utf-8") assert "workflow_dispatch:" not in source assert "GH_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN }}" in source + assert "id-token: write" in source + assert "OIDC_AUDIENCE: opencode-github-action" in source + assert '"${OPENCODE_API_BASE_URL}/exchange_github_app_token"' in source assert "persist-credentials: false" in source assert "OPENCODE_APPROVE_TOKEN" not in source + assert "|| github.token" not in source assert "DRY_RUN" not in source assert "inputs.dry_run" not in source + + +def test_opencode_exchange_fails_closed_and_masks_both_tokens() -> None: + """Malformed exchanges remain bounded and never expose either token.""" + source = WORKFLOW_PATH.read_text(encoding="utf-8") + coordinate = source.split( + " - name: Coordinate one bounded fleet pass\n", maxsplit=1 + )[1] + + assert 'if [ -z "${GH_TOKEN:-}" ]; then' in coordinate + assert 'export GH_TOKEN="$app_token"' in coordinate + assert "OIDC token response was malformed or empty" in coordinate + assert "app token response was malformed or empty" in coordinate + assert 'echo "::add-mask::$oidc_token"' in coordinate + assert 'echo "::add-mask::$app_token"' in coordinate + assert coordinate.count("--connect-timeout 10") == 2 + assert coordinate.count("--max-time 30") == 2 diff --git a/tests/test_organization_commercial_readiness_loop_policy.py b/tests/test_organization_commercial_readiness_loop_policy.py index 920f8072f9..c1d14529ec 100644 --- a/tests/test_organization_commercial_readiness_loop_policy.py +++ b/tests/test_organization_commercial_readiness_loop_policy.py @@ -12,6 +12,7 @@ from scripts.ci.organization_commercial_readiness_loop import ( ActionKind, ActionResult, + DDD_CONTRACT_TERMS, RunRecord, RunReport, build_plan, @@ -47,18 +48,26 @@ def test_static_and_live_writer_lease_policy() -> None: assert not is_live_writer_run(complete) -def test_product_entrypoint_requires_manual_nvidia_opt_in() -> None: - """Product dispatch requires a marked, unscheduled, credential-isolated workflow.""" +def test_product_entrypoint_requires_manual_nvidia_and_ddd_opt_in() -> None: + """Product dispatch requires a manual credential-isolated DDD contract.""" safe = manual_workflow() assert is_manual_product_entrypoint(safe) assert not is_manual_product_entrypoint(workflow(state="disabled_manually", content="x")) assert not is_manual_product_entrypoint(workflow(content=None)) - for changed in ( + mutations = [ (safe.content or "") + 'schedule:\n - cron: "1 * * * *"\n', (safe.content or "") + "COPILOT_GITHUB_TOKEN: forbidden\n", (safe.content or "").replace("# cwl-org-commercial-entrypoint: v1\n", ""), + (safe.content or "").replace( + "# cwl-ddd-architecture-audit: required\n", "" + ), (safe.content or "").replace("concurrency:\n", ""), - ): + ] + mutations.extend( + (safe.content or "").replace(term, f"missing-{index}", 1) + for index, term in enumerate(DDD_CONTRACT_TERMS) + ) + for changed in mutations: assert not is_manual_product_entrypoint(workflow(content=changed)) @@ -160,6 +169,9 @@ def test_workflow_and_doctoring_contracts() -> None: assert 'MAX_REVIEW_DISPATCHES: "1"' in workflow_source assert 'MAX_DEVELOPMENT_DISPATCHES: "1"' in workflow_source assert "GH_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN }}" in workflow_source + assert 'export GH_TOKEN="$app_token"' in workflow_source + assert "id-token: write" in workflow_source + assert "OIDC_AUDIENCE: opencode-github-action" in workflow_source assert "OPENCODE_APPROVE_TOKEN" not in workflow_source assert "workflow_dispatch:" not in workflow_source assert "|| github.token" not in workflow_source @@ -173,5 +185,7 @@ def test_workflow_and_doctoring_contracts() -> None: assert "github.event.pull_request.head.sha" in quality assert "disabled workflow does not hold a lease" in doctoring assert "manual-only, explicitly marked" in doctoring + assert "# cwl-ddd-architecture-audit: required" in doctoring + assert "Misleading directory paths" in doctoring assert "does not make every repository directly writable" in doctoring assert "GITHUB_TOKEN" in doctoring and "APA 7" in doctoring From 90d59c9ee160cb006f832a442bfb5a8e2206f9c2 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 1 Sep 2026 13:00:45 +0900 Subject: [PATCH 07/24] fix(automation): restore hourly coordinator App authentication --- ...organization-commercial-readiness-loop.yml | 64 ++++++++++++++++++- 1 file changed, 63 insertions(+), 1 deletion(-) diff --git a/.github/workflows/organization-commercial-readiness-loop.yml b/.github/workflows/organization-commercial-readiness-loop.yml index 521495617a..fbe7efa434 100644 --- a/.github/workflows/organization-commercial-readiness-loop.yml +++ b/.github/workflows/organization-commercial-readiness-loop.yml @@ -18,6 +18,9 @@ jobs: github.ref == format('refs/heads/{0}', github.event.repository.default_branch) runs-on: ubuntu-24.04 timeout-minutes: 25 + permissions: + contents: read + id-token: write env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" ORGANIZATION: ContextualWisdomLab @@ -32,6 +35,7 @@ jobs: egress-policy: block allowed-endpoints: >- api.github.com:443 + api.opencode.ai:443 github.com:443 objects.githubusercontent.com:443 release-assets.githubusercontent.com:443 @@ -53,10 +57,68 @@ jobs: - name: Coordinate one bounded fleet pass env: GH_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN }} + OIDC_AUDIENCE: opencode-github-action + OPENCODE_API_BASE_URL: https://api.opencode.ai shell: bash --noprofile --norc -e -o pipefail {0} run: | + set -euo pipefail + + exchange_unavailable() { + echo "::error::OpenCode app token exchange unavailable: $1" + exit 1 + } + + if [ -z "${GH_TOKEN:-}" ]; then + if [ -z "${ACTIONS_ID_TOKEN_REQUEST_TOKEN:-}" ] || [ -z "${ACTIONS_ID_TOKEN_REQUEST_URL:-}" ]; then + exchange_unavailable "OIDC request environment is missing." + fi + + request_url="${ACTIONS_ID_TOKEN_REQUEST_URL}" + separator="&" + case "$request_url" in + *\?*) ;; + *) separator="?" ;; + esac + + if ! oidc_response="$( + curl -fsS \ + --connect-timeout 10 \ + --max-time 30 \ + -H "Authorization: Bearer ${ACTIONS_ID_TOKEN_REQUEST_TOKEN}" \ + "${request_url}${separator}audience=${OIDC_AUDIENCE}" + )"; then + exchange_unavailable "OIDC token request did not complete." + fi + if ! oidc_token="$( + jq -er '.value | select(type == "string" and length > 0)' \ + <<<"$oidc_response" 2>/dev/null + )"; then + exchange_unavailable "OIDC token response was malformed or empty." + fi + echo "::add-mask::$oidc_token" + + if ! token_response="$( + curl -fsS \ + --connect-timeout 10 \ + --max-time 30 \ + -X POST \ + -H "Authorization: Bearer ${oidc_token}" \ + "${OPENCODE_API_BASE_URL}/exchange_github_app_token" + )"; then + exchange_unavailable "app token request did not complete." + fi + if ! app_token="$( + jq -er '.token | select(type == "string" and length > 0)' \ + <<<"$token_response" 2>/dev/null + )"; then + exchange_unavailable "app token response was malformed or empty." + fi + echo "::add-mask::$app_token" + export GH_TOKEN="$app_token" + fi + if [ -z "${GH_TOKEN:-}" ]; then - echo "::error::PR_REVIEW_MERGE_TOKEN is required; neither the reviewer credential nor repository-scoped GITHUB_TOKEN is accepted." + echo "::error::PR_REVIEW_MERGE_TOKEN or the job-bound OpenCode App token exchange is required; neither reviewer credentials nor repository-scoped GITHUB_TOKEN are accepted." exit 1 fi echo "::add-mask::$GH_TOKEN" From 098f9e699d8cedf57696049922c4e5e9dce01529 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 1 Sep 2026 13:01:08 +0900 Subject: [PATCH 08/24] chore(ci): remove completed hourly DDD bootstrap workflow --- .../tmp-hourly-ddd-development-contract.yml | 123 ------------------ 1 file changed, 123 deletions(-) delete mode 100644 .github/workflows/tmp-hourly-ddd-development-contract.yml diff --git a/.github/workflows/tmp-hourly-ddd-development-contract.yml b/.github/workflows/tmp-hourly-ddd-development-contract.yml deleted file mode 100644 index 95e312d8d4..0000000000 --- a/.github/workflows/tmp-hourly-ddd-development-contract.yml +++ /dev/null @@ -1,123 +0,0 @@ -name: Temporary Hourly DDD Development Contract Repair - -on: - push: - branches: - - fix/hourly-ddd-development-contract-20260901 - paths: - - .github/workflows/tmp-hourly-ddd-development-contract.yml - - scripts/ci/tmp_apply_hourly_ddd_contract.py - -concurrency: - group: tmp-hourly-ddd-development-contract-repair - cancel-in-progress: false - -permissions: - contents: write - -jobs: - repair: - if: github.repository == 'ContextualWisdomLab/.github' - runs-on: ubuntu-24.04 - timeout-minutes: 20 - steps: - - name: Check out exact repair branch - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - ref: fix/hourly-ddd-development-contract-20260901 - fetch-depth: 1 - persist-credentials: true - - - name: Set up Python - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 - with: - python-version: "3.14" - - - name: Normalize bootstrap source after exact failure evidence - shell: bash --noprofile --norc -e -o pipefail {0} - run: | - python - <<'PY' - from pathlib import Path - - path = Path("scripts/ci/tmp_apply_hourly_ddd_contract.py") - source = path.read_text(encoding="utf-8") - replacements = ( - ( - ' assert "misleading directory paths" in doctoring\n', - ' assert "Misleading directory paths" in doctoring\n', - ), - ( - ' *\\?*) ;;\n', - ' *\\\\?*) ;;\n', - ), - ) - for old, new in replacements: - count = source.count(old) - if count != 1: - raise SystemExit( - f"bootstrap normalization expected one fragment, found {count}: {old!r}" - ) - source = source.replace(old, new, 1) - path.write_text(source, encoding="utf-8") - PY - - - name: Apply exact bounded repair - shell: bash --noprofile --norc -e -o pipefail {0} - run: python -W error scripts/ci/tmp_apply_hourly_ddd_contract.py - - - name: Install exact focused-test dependencies - env: - PIP_DISABLE_PIP_VERSION_CHECK: "1" - PIP_NO_INPUT: "1" - shell: bash --noprofile --norc -e -o pipefail {0} - run: | - cat >"${RUNNER_TEMP}/requirements.txt" <<'EOF' - coverage==7.15.2 --hash=sha256:b9a6367e4aff723e8ee8190836836124284e8fcd4265e307c844010cfa074f3f - iniconfig==2.1.0 --hash=sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760 - packaging==26.2 --hash=sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e - pluggy==1.6.0 --hash=sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746 - pygments==2.20.0 --hash=sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176 - pytest==9.1.1 --hash=sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c - EOF - python -m pip install --only-binary=:all: --require-hashes -r "${RUNNER_TEMP}/requirements.txt" - - - name: Verify policy, branch coverage, and source hygiene - shell: bash --noprofile --norc -e -o pipefail {0} - run: | - python -m coverage run --branch -m pytest --import-mode=importlib \ - tests/test_organization_commercial_readiness_loop*.py -q - python -m coverage report \ - --include='scripts/ci/organization_commercial_readiness_loop.py' \ - --show-missing \ - --fail-under=100 - python -m compileall -q \ - scripts/ci/organization_commercial_readiness_loop.py \ - organization_commercial_readiness_fixtures.py \ - tests/test_organization_commercial_readiness_loop*.py - git diff --check - test "$(grep -c '# cwl-ddd-architecture-audit: required' organization_commercial_readiness_fixtures.py)" -eq 1 - test "$(grep -c 'api.opencode.ai:443' .github/workflows/organization-commercial-readiness-loop.yml)" -eq 1 - - - name: Publish verified non-workflow branch changes - shell: bash --noprofile --norc -e -o pipefail {0} - run: | - set -euo pipefail - git restore --source=HEAD -- .github/workflows/organization-commercial-readiness-loop.yml - git restore --source=HEAD -- scripts/ci/tmp_apply_hourly_ddd_contract.py - git add \ - CHANGELOG.md \ - docs/doctoring/organization-commercial-readiness-loop.md \ - docs/product-technical-gap-baseline.md \ - organization_commercial_readiness_fixtures.py \ - scripts/ci/organization_commercial_readiness_loop.py \ - tests/test_organization_commercial_readiness_loop_credential_contract.py \ - tests/test_organization_commercial_readiness_loop_policy.py - test -n "$(git diff --cached --name-only)" - if git diff --cached --name-only | grep -q '^\.github/workflows/'; then - echo '::error::Workflow paths must be published only through the authorized connector.' - exit 1 - fi - git config user.name 'github-actions[bot]' - git config user.email '41898282+github-actions[bot]@users.noreply.github.com' - git commit -m 'fix(automation): enforce hourly DDD development contract' - git push origin HEAD:fix/hourly-ddd-development-contract-20260901 From 38f6422f0220cea9f7df4106964bc358f8f692fe Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 1 Sep 2026 13:01:25 +0900 Subject: [PATCH 09/24] chore(ci): remove completed hourly DDD bootstrap script --- scripts/ci/tmp_apply_hourly_ddd_contract.py | 464 -------------------- 1 file changed, 464 deletions(-) delete mode 100644 scripts/ci/tmp_apply_hourly_ddd_contract.py diff --git a/scripts/ci/tmp_apply_hourly_ddd_contract.py b/scripts/ci/tmp_apply_hourly_ddd_contract.py deleted file mode 100644 index 39125d6353..0000000000 --- a/scripts/ci/tmp_apply_hourly_ddd_contract.py +++ /dev/null @@ -1,464 +0,0 @@ -#!/usr/bin/env python3 -"""Apply the bounded hourly DDD coordinator repair, then let CI delete this file.""" - -from __future__ import annotations - -from pathlib import Path - -ROOT = Path(__file__).resolve().parents[2] - - -def replace_once(path: str, old: str, new: str) -> None: - """Replace one exact repository fragment or fail closed.""" - target = ROOT / path - source = target.read_text(encoding="utf-8") - count = source.count(old) - if count != 1: - raise SystemExit(f"{path}: expected one exact fragment, found {count}") - target.write_text(source.replace(old, new, 1), encoding="utf-8") - - -def patch_workflow() -> None: - """Restore bounded App authentication in the hourly coordinator workflow.""" - path = ".github/workflows/organization-commercial-readiness-loop.yml" - replace_once( - path, - """ runs-on: ubuntu-24.04 - timeout-minutes: 25 - env: -""", - """ runs-on: ubuntu-24.04 - timeout-minutes: 25 - permissions: - contents: read - id-token: write - env: -""", - ) - replace_once( - path, - """ api.github.com:443 - github.com:443 -""", - """ api.github.com:443 - api.opencode.ai:443 - github.com:443 -""", - ) - replace_once( - path, - """ - name: Coordinate one bounded fleet pass - env: - GH_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN }} - shell: bash --noprofile --norc -e -o pipefail {0} - run: | - if [ -z "${GH_TOKEN:-}" ]; then - echo "::error::PR_REVIEW_MERGE_TOKEN is required; neither the reviewer credential nor repository-scoped GITHUB_TOKEN is accepted." - exit 1 - fi - echo "::add-mask::$GH_TOKEN" - test "$(git rev-parse HEAD)" = "${GITHUB_SHA}" - - python scripts/ci/organization_commercial_readiness_loop.py \\ - --organization "$ORGANIZATION" \\ - --rotation-seed "$ROTATION_SEED" \\ - --max-repositories "$MAX_REPOSITORIES" \\ - --max-review-dispatches "$MAX_REVIEW_DISPATCHES" \\ - --max-development-dispatches "$MAX_DEVELOPMENT_DISPATCHES" \\ - --json-output "$RUNNER_TEMP/organization-commercial-readiness-loop.json" - python -m json.tool "$RUNNER_TEMP/organization-commercial-readiness-loop.json" >/dev/null -""", - """ - name: Coordinate one bounded fleet pass - env: - GH_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN }} - OIDC_AUDIENCE: opencode-github-action - OPENCODE_API_BASE_URL: https://api.opencode.ai - shell: bash --noprofile --norc -e -o pipefail {0} - run: | - set -euo pipefail - - exchange_unavailable() { - echo "::error::OpenCode app token exchange unavailable: $1" - exit 1 - } - - if [ -z "${GH_TOKEN:-}" ]; then - if [ -z "${ACTIONS_ID_TOKEN_REQUEST_TOKEN:-}" ] || [ -z "${ACTIONS_ID_TOKEN_REQUEST_URL:-}" ]; then - exchange_unavailable "OIDC request environment is missing." - fi - - request_url="${ACTIONS_ID_TOKEN_REQUEST_URL}" - separator="&" - case "$request_url" in - *\?*) ;; - *) separator="?" ;; - esac - - if ! oidc_response="$( - curl -fsS \\ - --connect-timeout 10 \\ - --max-time 30 \\ - -H "Authorization: Bearer ${ACTIONS_ID_TOKEN_REQUEST_TOKEN}" \\ - "${request_url}${separator}audience=${OIDC_AUDIENCE}" - )"; then - exchange_unavailable "OIDC token request did not complete." - fi - if ! oidc_token="$( - jq -er '.value | select(type == "string" and length > 0)' \\ - <<<"$oidc_response" 2>/dev/null - )"; then - exchange_unavailable "OIDC token response was malformed or empty." - fi - echo "::add-mask::$oidc_token" - - if ! token_response="$( - curl -fsS \\ - --connect-timeout 10 \\ - --max-time 30 \\ - -X POST \\ - -H "Authorization: Bearer ${oidc_token}" \\ - "${OPENCODE_API_BASE_URL}/exchange_github_app_token" - )"; then - exchange_unavailable "app token request did not complete." - fi - if ! app_token="$( - jq -er '.token | select(type == "string" and length > 0)' \\ - <<<"$token_response" 2>/dev/null - )"; then - exchange_unavailable "app token response was malformed or empty." - fi - echo "::add-mask::$app_token" - export GH_TOKEN="$app_token" - fi - - if [ -z "${GH_TOKEN:-}" ]; then - echo "::error::PR_REVIEW_MERGE_TOKEN or the job-bound OpenCode App token exchange is required; neither reviewer credentials nor repository-scoped GITHUB_TOKEN are accepted." - exit 1 - fi - echo "::add-mask::$GH_TOKEN" - test "$(git rev-parse HEAD)" = "${GITHUB_SHA}" - - python scripts/ci/organization_commercial_readiness_loop.py \\ - --organization "$ORGANIZATION" \\ - --rotation-seed "$ROTATION_SEED" \\ - --max-repositories "$MAX_REPOSITORIES" \\ - --max-review-dispatches "$MAX_REVIEW_DISPATCHES" \\ - --max-development-dispatches "$MAX_DEVELOPMENT_DISPATCHES" \\ - --json-output "$RUNNER_TEMP/organization-commercial-readiness-loop.json" - python -m json.tool "$RUNNER_TEMP/organization-commercial-readiness-loop.json" >/dev/null -""", - ) - - -def patch_coordinator() -> None: - """Require complete DDD language in centrally dispatched product entrypoints.""" - path = "scripts/ci/organization_commercial_readiness_loop.py" - replace_once( - path, - """ENTRYPOINT_MARKER = "# cwl-org-commercial-entrypoint: v1" -CENTRAL_REPOSITORY = f"{DEFAULT_ORGANIZATION}/.github" -""", - """ENTRYPOINT_MARKER = "# cwl-org-commercial-entrypoint: v1" -DDD_ENTRYPOINT_MARKER = "# cwl-ddd-architecture-audit: required" -DDD_CONTRACT_TERMS = ( - "Domain-Driven Design", - "core, supporting, and generic subdomains", - "Bounded Context", - "Context Map", - "Ubiquitous Language", - "Aggregate", - "Entity", - "Value Object", - "Domain Service", - "Repository", - "Domain Event", - "Invariant", - "Anti-Corruption Layer", - "Shared Kernel", - "directory paths", - "docs/product-technical-gap-baseline.md", -) -CENTRAL_REPOSITORY = f"{DEFAULT_ORGANIZATION}/.github" -""", - ) - replace_once( - path, - """def is_manual_product_entrypoint(workflow: WorkflowRecord) -> bool: - \"\"\"Return whether a workflow explicitly opts in to central product dispatch.\"\"\" - source = workflow.content - if workflow.state != "active" or source is None: - return False - return all( - ( - ENTRYPOINT_MARKER in source, - bool(WORKFLOW_DISPATCH_RE.search(source)), -""", - """def has_domain_driven_development_contract(source: str) -> bool: - \"\"\"Return whether one entrypoint accepts the complete DDD repair contract.\"\"\" - return DDD_ENTRYPOINT_MARKER in source and all( - term in source for term in DDD_CONTRACT_TERMS - ) - - -def is_manual_product_entrypoint(workflow: WorkflowRecord) -> bool: - \"\"\"Return whether a workflow safely opts in to central product development.\"\"\" - source = workflow.content - if workflow.state != "active" or source is None: - return False - return all( - ( - ENTRYPOINT_MARKER in source, - has_domain_driven_development_contract(source), - bool(WORKFLOW_DISPATCH_RE.search(source)), -""", - ) - - -def patch_fixtures_and_tests() -> None: - """Extend regression fixtures across every DDD and credential branch.""" - replace_once( - "organization_commercial_readiness_fixtures.py", - """ "# cwl-org-commercial-entrypoint: v1\\n" - "on:\\n workflow_dispatch:\\n" - "concurrency:\\n group: product-development\\n" - "permissions:\\n contents: write\\n" - "NVIDIA_API_KEY: ${{ secrets.NVIDIA_NIM_API_KEY }}\\n" -""", - """ "# cwl-org-commercial-entrypoint: v1\\n" - "# cwl-ddd-architecture-audit: required\\n" - "on:\\n workflow_dispatch:\\n" - "concurrency:\\n group: product-development\\n" - "permissions:\\n contents: write\\n" - "NVIDIA_API_KEY: ${{ secrets.NVIDIA_NIM_API_KEY }}\\n" - "prompt: |\\n" - " Apply Domain-Driven Design before and during every increment.\\n" - " Classify core, supporting, and generic subdomains; define each Bounded Context, Context Map, and Ubiquitous Language.\\n" - " Keep Aggregate, Entity, Value Object, Domain Service, Repository, Domain Event, and Invariant names aligned across code, API, database, and tests.\\n" - " Isolate external systems behind an Anti-Corruption Layer and keep the Shared Kernel minimal.\\n" - " Audit and correct misleading directory paths with imports, packaging, callers, tests, and architecture documents in the same bounded change.\\n" - " Update docs/product-technical-gap-baseline.md with detected and repaired architecture drift.\\n" -""", - ) - replace_once( - "tests/test_organization_commercial_readiness_loop_policy.py", - """ ActionKind, - ActionResult, - RunRecord, -""", - """ ActionKind, - ActionResult, - DDD_CONTRACT_TERMS, - RunRecord, -""", - ) - replace_once( - "tests/test_organization_commercial_readiness_loop_policy.py", - """def test_product_entrypoint_requires_manual_nvidia_opt_in() -> None: - \"\"\"Product dispatch requires a marked, unscheduled, credential-isolated workflow.\"\"\" - safe = manual_workflow() - assert is_manual_product_entrypoint(safe) - assert not is_manual_product_entrypoint(workflow(state="disabled_manually", content="x")) - assert not is_manual_product_entrypoint(workflow(content=None)) - for changed in ( - (safe.content or "") + 'schedule:\\n - cron: "1 * * * *"\\n', - (safe.content or "") + "COPILOT_GITHUB_TOKEN: forbidden\\n", - (safe.content or "").replace("# cwl-org-commercial-entrypoint: v1\\n", ""), - (safe.content or "").replace("concurrency:\\n", ""), - ): - assert not is_manual_product_entrypoint(workflow(content=changed)) -""", - """def test_product_entrypoint_requires_manual_nvidia_and_ddd_opt_in() -> None: - \"\"\"Product dispatch requires a manual credential-isolated DDD contract.\"\"\" - safe = manual_workflow() - assert is_manual_product_entrypoint(safe) - assert not is_manual_product_entrypoint(workflow(state="disabled_manually", content="x")) - assert not is_manual_product_entrypoint(workflow(content=None)) - mutations = [ - (safe.content or "") + 'schedule:\\n - cron: "1 * * * *"\\n', - (safe.content or "") + "COPILOT_GITHUB_TOKEN: forbidden\\n", - (safe.content or "").replace("# cwl-org-commercial-entrypoint: v1\\n", ""), - (safe.content or "").replace( - "# cwl-ddd-architecture-audit: required\\n", "" - ), - (safe.content or "").replace("concurrency:\\n", ""), - ] - mutations.extend( - (safe.content or "").replace(term, f"missing-{index}", 1) - for index, term in enumerate(DDD_CONTRACT_TERMS) - ) - for changed in mutations: - assert not is_manual_product_entrypoint(workflow(content=changed)) -""", - ) - replace_once( - "tests/test_organization_commercial_readiness_loop_policy.py", - """ assert "GH_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN }}" in workflow_source - assert "OPENCODE_APPROVE_TOKEN" not in workflow_source -""", - """ assert "GH_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN }}" in workflow_source - assert 'export GH_TOKEN="$app_token"' in workflow_source - assert "id-token: write" in workflow_source - assert "OIDC_AUDIENCE: opencode-github-action" in workflow_source - assert "OPENCODE_APPROVE_TOKEN" not in workflow_source -""", - ) - replace_once( - "tests/test_organization_commercial_readiness_loop_policy.py", - """ assert "manual-only, explicitly marked" in doctoring - assert "does not make every repository directly writable" in doctoring -""", - """ assert "manual-only, explicitly marked" in doctoring - assert "# cwl-ddd-architecture-audit: required" in doctoring - assert "misleading directory paths" in doctoring - assert "does not make every repository directly writable" in doctoring -""", - ) - replace_once( - "tests/test_organization_commercial_readiness_loop_credential_contract.py", - """def test_central_schedule_has_no_branch_selected_or_reviewer_credential_path() -> None: - \"\"\"The fleet coordinator must be schedule-only and use maintainer authority.\"\"\" - source = WORKFLOW_PATH.read_text(encoding="utf-8") - - assert "workflow_dispatch:" not in source - assert "GH_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN }}" in source - assert "persist-credentials: false" in source - assert "OPENCODE_APPROVE_TOKEN" not in source - assert "DRY_RUN" not in source - assert "inputs.dry_run" not in source -""", - """def test_central_schedule_has_no_branch_selected_or_reviewer_credential_path() -> None: - \"\"\"The fleet coordinator uses schedule-bound maintainer or App authority.\"\"\" - source = WORKFLOW_PATH.read_text(encoding="utf-8") - - assert "workflow_dispatch:" not in source - assert "GH_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN }}" in source - assert "id-token: write" in source - assert "OIDC_AUDIENCE: opencode-github-action" in source - assert '"${OPENCODE_API_BASE_URL}/exchange_github_app_token"' in source - assert "persist-credentials: false" in source - assert "OPENCODE_APPROVE_TOKEN" not in source - assert "|| github.token" not in source - assert "DRY_RUN" not in source - assert "inputs.dry_run" not in source - - -def test_opencode_exchange_fails_closed_and_masks_both_tokens() -> None: - \"\"\"Malformed exchanges remain bounded and never expose either token.\"\"\" - source = WORKFLOW_PATH.read_text(encoding="utf-8") - coordinate = source.split( - " - name: Coordinate one bounded fleet pass\\n", maxsplit=1 - )[1] - - assert 'if [ -z "${GH_TOKEN:-}" ]; then' in coordinate - assert 'export GH_TOKEN="$app_token"' in coordinate - assert "OIDC token response was malformed or empty" in coordinate - assert "app token response was malformed or empty" in coordinate - assert 'echo "::add-mask::$oidc_token"' in coordinate - assert 'echo "::add-mask::$app_token"' in coordinate - assert coordinate.count("--connect-timeout 10") == 2 - assert coordinate.count("--max-time 30") == 2 -""", - ) - - -def patch_documentation() -> None: - """Record the architecture contract, standards basis, and current Gap.""" - doctoring_path = ROOT / "docs/doctoring/organization-commercial-readiness-loop.md" - doctoring = doctoring_path.read_text(encoding="utf-8") - old_credential = ( - "The central job therefore refuses both repository-scoped and reviewer-scoped token fallbacks. " - "It requires the maintainer-scoped `PR_REVIEW_MERGE_TOKEN`; `OPENCODE_APPROVE_TOKEN` remains isolated " - "to the reviewer credential chain and `GITHUB_TOKEN` is not accepted for cross-repository coordination. " - "The maintainer token is exposed only to the final dispatch shell step, not checkout, setup, artifact " - "upload, or other third-party actions. The coordinator itself receives neither `NVIDIA_NIM_API_KEY` nor " - "`COPILOT_GITHUB_TOKEN`. Model credentials remain inside separately reviewed repository-local or central workers." - ) - new_credential = ( - "The central job therefore refuses repository-scoped and reviewer-scoped token fallbacks. It prefers the " - "maintainer-scoped `PR_REVIEW_MERGE_TOKEN`; when that credential is absent, the scheduled default-branch job " - "may exchange its job-bound GitHub OIDC identity for the existing short-lived OpenCode App installation token. " - "Both exchange calls have bounded connection and total timeouts, both returned tokens are masked before reuse, " - "and malformed or empty responses fail closed. `OPENCODE_APPROVE_TOKEN` remains isolated to the reviewer " - "credential chain and `GITHUB_TOKEN` is never accepted for cross-repository coordination. The resulting " - "maintainer credential is exposed only to the final dispatch shell step, not checkout, setup, artifact upload, " - "or other third-party actions. The coordinator itself receives neither `NVIDIA_NIM_API_KEY` nor " - "`COPILOT_GITHUB_TOKEN`. Model credentials remain inside separately reviewed repository-local or central workers." - ) - if doctoring.count(old_credential) != 1: - raise SystemExit("doctoring credential paragraph drifted") - doctoring = doctoring.replace(old_credential, new_credential, 1) - start = doctoring.index("## Product-development boundary") - end = doctoring.index("## Failure, evidence, and operations") - product_section = """## Product-development boundary - -Product development is dispatched only when a repository has zero open pull requests and exposes one active, manual-only, explicitly marked workflow: - -```yaml -# cwl-org-commercial-entrypoint: v1 -# cwl-ddd-architecture-audit: required -on: - workflow_dispatch: -``` - -The entrypoint must contain an explicit `concurrency` contract, use `NVIDIA_NIM_API_KEY`, omit `COPILOT_GITHUB_TOKEN`, have no schedule of its own, and carry a commercial/product-development identity. It must also embed the complete Domain-Driven Design repair contract rather than merely mention DDD. The machine-checked contract requires core, supporting, and generic subdomains; Bounded Context; Context Map; Ubiquitous Language; Aggregate; Entity; Value Object; Domain Service; Repository; Domain Event; Invariant; Anti-Corruption Layer; Shared Kernel; directory paths; and `docs/product-technical-gap-baseline.md`. - -Each hourly product increment must identify the owning product responsibility before selecting a repository, then compare the live directory tree, module/package names, API, database objects, tests, and documentation with that responsibility. Misleading directory paths, generic `utils`/`common` dumping grounds that own domain behavior, infrastructure imports inside the domain model, cross-context database access, obsolete product names, or customer-visible implementation boundaries are architecture defects, not cosmetic debt. When one can be corrected safely in the bounded increment, the agent moves the code and updates imports, package manifests, call sites, migrations, tests, ADRs, diagrams, and compatibility adapters in the same pull request. - -The contract does not impose one universal folder template. A move is justified by domain ownership and dependency direction, not by directory aesthetics. Aggregate boundaries remain the smallest consistency boundary; external and legacy systems are isolated behind an Anti-Corruption Layer; the Shared Kernel remains minimal; and cross-context integration uses explicit versioned contracts. If a coherent move exceeds the current pull request's safe scope, the agent must record the exact owner, callers, target context, migration sequence, and acceptance evidence in `docs/product-technical-gap-baseline.md` and select it as the next bounded architecture increment rather than silently leaving the drift unresolved. - -This opt-in prevents the central coordinator from guessing that an unrelated manual workflow can safely modify product source. Repositories with an existing hourly or more frequent dedicated writer keep their own lease and are never double-dispatched; those schedules may share the same DDD contract and should adopt it without adding another cron. - -The repository-local entrypoint remains responsible for bounded editable paths, tests, 100% production statement and branch coverage, public docstrings, package and security verification, exact-head publication, and pull-request creation. A missing compliant entrypoint is a deliberate no-op, not permission to inject a generic writer into that repository. - -""" - doctoring = doctoring[:start] + product_section + doctoring[end:] - references_marker = "## APA 7 references\n\n" - ddd_references = ( - "Evans, E. (2004). *Domain-driven design: Tackling complexity in the heart of software*. Addison-Wesley.\n\n" - "Evans, E. (2015). *Domain-driven design reference: Definitions and pattern summaries*. Domain Language. https://www.domainlanguage.com/ddd/reference/\n\n" - "International Organization for Standardization, International Electrotechnical Commission, & Institute of Electrical and Electronics Engineers. (2022). *Software, systems and enterprise—Architecture description* (ISO/IEC/IEEE Standard 42010:2022). https://www.iso.org/standard/74393.html\n\n" - ) - if references_marker not in doctoring: - raise SystemExit("doctoring references heading missing") - doctoring = doctoring.replace(references_marker, references_marker + ddd_references, 1) - doctoring_path.write_text(doctoring, encoding="utf-8") - - baseline_path = ROOT / "docs/product-technical-gap-baseline.md" - baseline = baseline_path.read_text(encoding="utf-8") - baseline_marker = "## 1. 근거와 범위\n" - baseline_addendum = """## 2026-09-01 시간별 DDD 실행 계약 보강 - -- **관측:** 조직 상용화 루프는 매시 7분 실행되고 기존 전용 writer 예약을 존중하지만, 중앙 제품개발 opt-in은 DDD 및 디렉터리 소유권 감사를 요구하지 않았다. 또한 maintainer secret이 없는 예약 환경에서는 cross-repository dispatch 전에 중단될 수 있었다. -- **Gap `G-DDD-01`:** Bounded Context와 실제 디렉터리·패키지·API·DB 소유권이 어긋나도 시간별 Agent가 이를 필수 결함으로 선택한다는 기계 검증 계약이 없었다. -- **조치:** 수동 제품개발 진입점에 `# cwl-ddd-architecture-audit: required`와 전략·전술 DDD 용어, directory-path repair, `docs/product-technical-gap-baseline.md` 갱신을 요구한다. 기존 전용 예약은 writer lease를 유지해 중복 실행하지 않는다. -- **가용성 조치:** `PR_REVIEW_MERGE_TOKEN`을 우선 사용하되 없으면 protected-default-branch job의 OIDC identity를 short-lived OpenCode App installation token으로 교환한다. repository `GITHUB_TOKEN`, reviewer credential, model provider key는 fallback으로 사용하지 않는다. -- **완료 증거:** exact-head focused policy tests, statement/branch coverage 100%, Python docstring 100%, workflow security checks, independent review, protected merge. 병합 전 상태는 구현 중이며 운영 완료로 간주하지 않는다. - -""" - if baseline_marker not in baseline: - raise SystemExit("gap baseline insertion point missing") - baseline = baseline.replace(baseline_marker, baseline_addendum + baseline_marker, 1) - baseline_path.write_text(baseline, encoding="utf-8") - - changelog_path = ROOT / "CHANGELOG.md" - changelog = changelog_path.read_text(encoding="utf-8") - changelog_marker = "## [Unreleased]\n" - changelog_entry = ( - "- Restore the hourly organization commercial-readiness coordinator when the dedicated maintainer secret is absent by exchanging the protected scheduled job's OIDC identity for a short-lived OpenCode App installation token; retain bounded network calls, token masking, and fail-closed parsing. Require every centrally dispatched product-development entrypoint to accept a machine-checked Domain-Driven Design contract, continuously repairing misleading directory ownership and recording larger bounded-context migrations in `docs/product-technical-gap-baseline.md` without duplicating repository-owned schedules.\n" - ) - if changelog_marker not in changelog: - raise SystemExit("changelog insertion point missing") - changelog = changelog.replace(changelog_marker, changelog_marker + changelog_entry, 1) - changelog_path.write_text(changelog, encoding="utf-8") - - -def main() -> None: - """Apply every bounded replacement once.""" - patch_workflow() - patch_coordinator() - patch_fixtures_and_tests() - patch_documentation() - - -if __name__ == "__main__": - main() From cd872e822965754e1971af13cf47ca749422e1cc Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 1 Sep 2026 13:11:22 +0900 Subject: [PATCH 10/24] test(automation): add RED DDD prompt-binding regression --- .../ci/tmp_apply_ddd_binding_review_fix.py | 532 ++++++++++++++++++ 1 file changed, 532 insertions(+) create mode 100644 scripts/ci/tmp_apply_ddd_binding_review_fix.py diff --git a/scripts/ci/tmp_apply_ddd_binding_review_fix.py b/scripts/ci/tmp_apply_ddd_binding_review_fix.py new file mode 100644 index 0000000000..f6bfe1ea53 --- /dev/null +++ b/scripts/ci/tmp_apply_ddd_binding_review_fix.py @@ -0,0 +1,532 @@ +#!/usr/bin/env python3 +"""Apply RED then GREEN review remediation for the hourly DDD entrypoint contract.""" + +from __future__ import annotations + +import argparse +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[2] + + +def replace_once(path: str, old: str, new: str) -> None: + """Replace one exact fragment or fail closed on branch drift.""" + target = ROOT / path + source = target.read_text(encoding="utf-8") + count = source.count(old) + if count != 1: + raise SystemExit(f"{path}: expected one fragment, found {count}") + target.write_text(source.replace(old, new, 1), encoding="utf-8") + + +def apply_red() -> None: + """Add a regression that the raw-prose implementation must fail.""" + replace_once( + "tests/test_organization_commercial_readiness_loop_policy.py", + """ for changed in mutations: + assert not is_manual_product_entrypoint(workflow(content=changed)) + + +def test_repository_eligibility_is_owned_and_write_capable() -> None: +""", + """ for changed in mutations: + assert not is_manual_product_entrypoint(workflow(content=changed)) + + +def test_ddd_contract_rejects_unbound_unused_yaml_prose() -> None: + \"\"\"DDD words in an unused scalar are not an agent instruction contract.\"\"\" + safe = manual_workflow() + assert safe.content is not None + unused = safe.content.replace("prompt: |\\n", "notes: |\\n", 1) + assert not is_manual_product_entrypoint(workflow(content=unused)) + + +def test_repository_eligibility_is_owned_and_write_capable() -> None: +""", + ) + + +def apply_green() -> None: + """Install the scoped capability and invocation-binding contract.""" + replace_once( + "scripts/ci/organization_commercial_readiness_loop.py", + """import re +import subprocess +import sys +from pathlib import Path +""", + """import re +import subprocess +import sys +import textwrap +from pathlib import Path +""", + ) + replace_once( + "scripts/ci/organization_commercial_readiness_loop.py", + """ENTRYPOINT_MARKER = "# cwl-org-commercial-entrypoint: v1" +DDD_ENTRYPOINT_MARKER = "# cwl-ddd-architecture-audit: required" +DDD_CONTRACT_TERMS = ( + "Domain-Driven Design", + "core, supporting, and generic subdomains", + "Bounded Context", + "Context Map", + "Ubiquitous Language", + "Aggregate", + "Entity", + "Value Object", + "Domain Service", + "Repository", + "Domain Event", + "Invariant", + "Anti-Corruption Layer", + "Shared Kernel", + "directory paths", + "docs/product-technical-gap-baseline.md", +) +CENTRAL_REPOSITORY = f"{DEFAULT_ORGANIZATION}/.github" +""", + """ENTRYPOINT_MARKER = "# cwl-org-commercial-entrypoint: v1" +DDD_ENTRYPOINT_MARKER = "# cwl-ddd-architecture-audit: v1" +DDD_PROMPT_BINDING_MARKER = "# cwl-ddd-prompt-binding: v1" +DDD_PROMPT_ENVIRONMENT = "CWL_PRODUCT_AGENT_PROMPT" +DDD_CAPABILITY_ENVIRONMENT = "CWL_DDD_CONTRACT_CAPABILITIES" +DDD_PROMPT_BINDING = f"--prompt-env {DDD_PROMPT_ENVIRONMENT}" +DDD_CAPABILITY_BINDING = ( + f"--architecture-contract-env {DDD_CAPABILITY_ENVIRONMENT}" +) +DDD_CONTRACT_CAPABILITIES = frozenset( + { + "aggregate", + "anti_corruption_layer", + "bounded_context", + "context_map", + "directory_ownership", + "domain_event", + "domain_service", + "entity", + "invariant", + "minimal_shared_kernel", + "product_gap_baseline", + "repository", + "subdomain_classification", + "ubiquitous_language", + "value_object", + } +) +DDD_CAPABILITY_TOKEN_RE = re.compile(r"[a-z][a-z0-9_]*") +CENTRAL_REPOSITORY = f"{DEFAULT_ORGANIZATION}/.github" +""", + ) + replace_once( + "scripts/ci/organization_commercial_readiness_loop.py", + """def has_domain_driven_development_contract(source: str) -> bool: + \"\"\"Return whether one entrypoint accepts the complete DDD repair contract.\"\"\" + return DDD_ENTRYPOINT_MARKER in source and all( + term in source for term in DDD_CONTRACT_TERMS + ) + + +def is_manual_product_entrypoint(workflow: WorkflowRecord) -> bool: +""", + """def _root_mapping_regions(source: str, key: str) -> tuple[str, ...]: + \"\"\"Return top-level YAML mapping bodies for one exact key.\"\"\" + lines = source.splitlines() + regions: list[str] = [] + for index, line in enumerate(lines): + if line != f"{key}:": + continue + body: list[str] = [] + for candidate in lines[index + 1 :]: + if candidate.strip() and not candidate.startswith(" "): + break + body.append(candidate) + regions.append("\\n".join(body)) + return tuple(regions) + + +def _yaml_block_scalars(source: str, key: str) -> tuple[str, ...]: + \"\"\"Return dedented YAML literal or folded block scalars for one key.\"\"\" + header = re.compile( + rf"^(?P *){re.escape(key)}: *[>|][+-]? *$" + ) + lines = source.splitlines() + blocks: list[str] = [] + for index, line in enumerate(lines): + match = header.fullmatch(line) + if match is None: + continue + base_indent = len(match.group("indent")) + body: list[str] = [] + for candidate in lines[index + 1 :]: + if candidate.strip(): + candidate_indent = len(candidate) - len(candidate.lstrip(" ")) + if candidate_indent <= base_indent: + break + body.append(candidate) + blocks.append(textwrap.dedent("\\n".join(body)).strip("\\n")) + return tuple(blocks) + + +def _shell_commands(block: str) -> tuple[str, ...]: + \"\"\"Return non-comment shell commands with continuations joined.\"\"\" + commands: list[str] = [] + fragments: list[str] = [] + for line in block.splitlines(): + fragment = line.strip() + if not fragment or fragment.startswith("#"): + continue + continued = fragment.endswith("\\") + fragments.append(fragment.removesuffix("\\").rstrip()) + if continued: + continue + commands.append(" ".join(fragments)) + fragments = [] + if fragments: + commands.append(" ".join(fragments)) + return tuple(commands) + + +def _has_bound_ddd_agent_invocation(source: str) -> bool: + \"\"\"Return whether one executable command receives both contract inputs.\"\"\" + for run_block in _yaml_block_scalars(source, "run"): + if DDD_PROMPT_BINDING_MARKER not in run_block: + continue + for command in _shell_commands(run_block): + if DDD_PROMPT_BINDING in command and DDD_CAPABILITY_BINDING in command: + return True + return False + + +def has_domain_driven_development_contract(source: str) -> bool: + \"\"\"Return whether one entrypoint binds a scoped versioned DDD contract.\"\"\" + if DDD_ENTRYPOINT_MARKER not in source: + return False + root_environments = _root_mapping_regions(source, "env") + if len(root_environments) != 1: + return False + environment = root_environments[0] + capability_blocks = _yaml_block_scalars( + environment, DDD_CAPABILITY_ENVIRONMENT + ) + prompt_blocks = _yaml_block_scalars(environment, DDD_PROMPT_ENVIRONMENT) + if len(capability_blocks) != 1 or len(prompt_blocks) != 1: + return False + if not prompt_blocks[0].strip(): + return False + declared_capabilities = frozenset( + DDD_CAPABILITY_TOKEN_RE.findall(capability_blocks[0]) + ) + if not DDD_CONTRACT_CAPABILITIES.issubset(declared_capabilities): + return False + return _has_bound_ddd_agent_invocation(source) + + +def is_manual_product_entrypoint(workflow: WorkflowRecord) -> bool: +""", + ) + replace_once( + "organization_commercial_readiness_fixtures.py", + """ content=( + "# cwl-org-commercial-entrypoint: v1\\n" + "# cwl-ddd-architecture-audit: required\\n" + "on:\\n workflow_dispatch:\\n" + "concurrency:\\n group: product-development\\n" + "permissions:\\n contents: write\\n" + "NVIDIA_API_KEY: ${{ secrets.NVIDIA_NIM_API_KEY }}\\n" + "prompt: |\\n" + " Apply Domain-Driven Design before and during every increment.\\n" + " Classify core, supporting, and generic subdomains; define each Bounded Context, Context Map, and Ubiquitous Language.\\n" + " Keep Aggregate, Entity, Value Object, Domain Service, Repository, Domain Event, and Invariant names aligned across code, API, database, and tests.\\n" + " Isolate external systems behind an Anti-Corruption Layer and keep the Shared Kernel minimal.\\n" + " Audit and correct misleading directory paths with imports, packaging, callers, tests, and architecture documents in the same bounded change.\\n" + " Update docs/product-technical-gap-baseline.md with detected and repaired architecture drift.\\n" + ), +""", + """ content=( + "# cwl-org-commercial-entrypoint: v1\\n" + "# cwl-ddd-architecture-audit: v1\\n" + "on:\\n workflow_dispatch:\\n" + "concurrency:\\n group: product-development\\n" + "permissions:\\n contents: write\\n" + "env:\\n" + " NVIDIA_API_KEY: ${{ secrets.NVIDIA_NIM_API_KEY }}\\n" + " CWL_DDD_CONTRACT_CAPABILITIES: >-\\n" + " aggregate anti_corruption_layer bounded_context context_map\\n" + " directory_ownership domain_event domain_service entity invariant\\n" + " minimal_shared_kernel product_gap_baseline repository\\n" + " subdomain_classification ubiquitous_language value_object\\n" + " CWL_PRODUCT_AGENT_PROMPT: |\\n" + " Deliver one buyer-visible increment through the repository-owned product agent.\\n" + "\\n" + " Keep implementation, tests, documentation, and package boundaries coherent.\\n" + "jobs:\\n" + " develop:\\n" + " runs-on: ubuntu-24.04\\n" + " steps:\\n" + " - name: Invoke the product agent\\n" + " run: |\\n" + " # cwl-ddd-prompt-binding: v1\\n" + " python scripts/automation/commercial_product_development.py \\\\\\n" + " --prompt-env CWL_PRODUCT_AGENT_PROMPT \\\\\\n" + " --architecture-contract-env CWL_DDD_CONTRACT_CAPABILITIES\\n" + ), +""", + ) + replace_once( + "tests/test_organization_commercial_readiness_loop_policy.py", + """ ActionKind, + ActionResult, + DDD_CONTRACT_TERMS, + RunRecord, +""", + """ ActionKind, + ActionResult, + DDD_CONTRACT_CAPABILITIES, + RunRecord, +""", + ) + replace_once( + "tests/test_organization_commercial_readiness_loop_policy.py", + """ is_dedicated_writer_workflow, + is_live_writer_run, + is_manual_product_entrypoint, +""", + """ has_domain_driven_development_contract, + is_dedicated_writer_workflow, + is_live_writer_run, + is_manual_product_entrypoint, +""", + ) + replace_once( + "tests/test_organization_commercial_readiness_loop_policy.py", + """def test_product_entrypoint_requires_manual_nvidia_and_ddd_opt_in() -> None: + \"\"\"Product dispatch requires a manual credential-isolated DDD contract.\"\"\" + safe = manual_workflow() + assert is_manual_product_entrypoint(safe) + assert not is_manual_product_entrypoint(workflow(state="disabled_manually", content="x")) + assert not is_manual_product_entrypoint(workflow(content=None)) + mutations = [ + (safe.content or "") + 'schedule:\\n - cron: "1 * * * *"\\n', + (safe.content or "") + "COPILOT_GITHUB_TOKEN: forbidden\\n", + (safe.content or "").replace("# cwl-org-commercial-entrypoint: v1\\n", ""), + (safe.content or "").replace( + "# cwl-ddd-architecture-audit: required\\n", "" + ), + (safe.content or "").replace("concurrency:\\n", ""), + ] + mutations.extend( + (safe.content or "").replace(term, f"missing-{index}", 1) + for index, term in enumerate(DDD_CONTRACT_TERMS) + ) + for changed in mutations: + assert not is_manual_product_entrypoint(workflow(content=changed)) + + +def test_ddd_contract_rejects_unbound_unused_yaml_prose() -> None: + \"\"\"DDD words in an unused scalar are not an agent instruction contract.\"\"\" + safe = manual_workflow() + assert safe.content is not None + unused = safe.content.replace("prompt: |\\n", "notes: |\\n", 1) + assert not is_manual_product_entrypoint(workflow(content=unused)) +""", + """def test_product_entrypoint_requires_manual_nvidia_and_bound_ddd_opt_in() -> None: + \"\"\"Product dispatch requires a scoped capability set bound to one command.\"\"\" + safe = manual_workflow() + assert safe.content is not None + source = safe.content + assert "Domain-Driven Design" not in source + assert is_manual_product_entrypoint(safe) + assert has_domain_driven_development_contract(source) + + ordinary_rejections = [ + source + 'schedule:\\n - cron: "1 * * * *"\\n', + source + "COPILOT_GITHUB_TOKEN: forbidden\\n", + source.replace("# cwl-org-commercial-entrypoint: v1\\n", "", 1), + source.replace("# cwl-ddd-architecture-audit: v1\\n", "", 1), + source.replace("concurrency:\\n", "", 1), + ] + for changed in ordinary_rejections: + assert not is_manual_product_entrypoint(workflow(content=changed)) + + for index, capability in enumerate(sorted(DDD_CONTRACT_CAPABILITIES)): + changed = source.replace(capability, f"omitted_{index}", 1) + assert not has_domain_driven_development_contract(changed) + + +def test_ddd_contract_rejects_comments_unused_scopes_and_split_bindings() -> None: + \"\"\"Comments, unscoped values, and separate commands cannot fake a binding.\"\"\" + safe = manual_workflow() + assert safe.content is not None + source = safe.content + comment_only = ( + "# cwl-ddd-architecture-audit: v1\\n" + + "\\n".join(f"# {item}" for item in sorted(DDD_CONTRACT_CAPABILITIES)) + + "\\n# cwl-ddd-prompt-binding: v1\\n" + + "# --prompt-env CWL_PRODUCT_AGENT_PROMPT\\n" + + "# --architecture-contract-env CWL_DDD_CONTRACT_CAPABILITIES\\n" + ) + assert not has_domain_driven_development_contract(comment_only) + + invalid_sources = [ + source.replace("env:\\n", "metadata:\\n", 1), + source.replace("CWL_PRODUCT_AGENT_PROMPT: |", "UNUSED_PROMPT: |", 1), + source.replace( + "CWL_DDD_CONTRACT_CAPABILITIES: >-", "UNUSED_CAPABILITIES: >-", 1 + ), + source.replace( + " Deliver one buyer-visible increment through the repository-owned product agent.\\n", + "", + 1, + ), + source.replace("# cwl-ddd-prompt-binding: v1", "# unbound", 1), + source.replace( + "--prompt-env CWL_PRODUCT_AGENT_PROMPT", "--prompt-env UNUSED_PROMPT", 1 + ), + source.replace( + "--architecture-contract-env CWL_DDD_CONTRACT_CAPABILITIES", + "--architecture-contract-env UNUSED_CAPABILITIES", + 1, + ), + source + "env:\\n OTHER_VALUE: present\\n", + source.replace( + " CWL_PRODUCT_AGENT_PROMPT: |", + " CWL_PRODUCT_AGENT_PROMPT: |\\n" + " duplicate\\n" + " CWL_PRODUCT_AGENT_PROMPT: |", + 1, + ), + source.replace( + " CWL_DDD_CONTRACT_CAPABILITIES: >-", + " CWL_DDD_CONTRACT_CAPABILITIES: >-\\n" + " bounded_context\\n" + " CWL_DDD_CONTRACT_CAPABILITIES: >-", + 1, + ), + ] + for changed in invalid_sources: + assert not has_domain_driven_development_contract(changed) + + bound_run = ( + " run: |\\n" + " # cwl-ddd-prompt-binding: v1\\n" + " python scripts/automation/commercial_product_development.py \\\\n" + " --prompt-env CWL_PRODUCT_AGENT_PROMPT \\\\n" + " --architecture-contract-env CWL_DDD_CONTRACT_CAPABILITIES\\n" + ) + split_run = ( + " run: |\\n" + " # cwl-ddd-prompt-binding: v1\\n" + " product-agent --prompt-env CWL_PRODUCT_AGENT_PROMPT\\n" + " product-agent --architecture-contract-env CWL_DDD_CONTRACT_CAPABILITIES\\n" + ) + assert not has_domain_driven_development_contract( + source.replace(bound_run, split_run, 1) + ) + + dangling_run = ( + " run: |\\n" + " # cwl-ddd-prompt-binding: v1\\n" + " product-agent \\\\n" + ) + assert not has_domain_driven_development_contract( + source.replace(bound_run, dangling_run, 1) + ) +""", + ) + replace_once( + "tests/test_organization_commercial_readiness_loop_policy.py", + """ assert "# cwl-ddd-architecture-audit: required" in doctoring +""", + """ assert "# cwl-ddd-architecture-audit: v1" in doctoring + assert "--architecture-contract-env CWL_DDD_CONTRACT_CAPABILITIES" in doctoring +""", + ) + + doctoring_path = ROOT / "docs/doctoring/organization-commercial-readiness-loop.md" + doctoring = doctoring_path.read_text(encoding="utf-8") + start = doctoring.index("## Product-development boundary") + end = doctoring.index("## Failure, evidence, and operations") + product_section = """## Product-development boundary + +Product development is dispatched only when a repository has zero open pull requests and exposes one active, manual-only, explicitly marked workflow. The DDD enrollment is versioned and its values must live in the root workflow `env` mapping so every job receives the same contract: + +```yaml +# cwl-org-commercial-entrypoint: v1 +# cwl-ddd-architecture-audit: v1 +on: + workflow_dispatch: + +env: + CWL_DDD_CONTRACT_CAPABILITIES: >- + aggregate anti_corruption_layer bounded_context context_map + directory_ownership domain_event domain_service entity invariant + minimal_shared_kernel product_gap_baseline repository + subdomain_classification ubiquitous_language value_object + CWL_PRODUCT_AGENT_PROMPT: | + Deliver one buyer-visible increment through the repository-owned product agent. + +jobs: + develop: + steps: + - run: | + # cwl-ddd-prompt-binding: v1 + product-agent \ + --prompt-env CWL_PRODUCT_AGENT_PROMPT \ + --architecture-contract-env CWL_DDD_CONTRACT_CAPABILITIES +``` + +The entrypoint must also contain an explicit `concurrency` contract, use `NVIDIA_NIM_API_KEY`, omit `COPILOT_GITHUB_TOKEN`, have no schedule of its own, and carry a commercial/product-development identity. Human-readable prompt wording is repository-owned and may use any language. Eligibility depends on stable capability identifiers rather than copied English prose. The prompt and capability environment names must be passed to the same non-comment shell command under the binding marker; comments, unrelated YAML, unscoped step values, separate commands, or unused block scalars do not satisfy the contract. + +The capability contract covers strategic and tactical Domain-Driven Design: subdomain classification, Bounded Context, Context Map, Ubiquitous Language, Aggregate, Entity, Value Object, Domain Service, Repository, Domain Event, Invariant, Anti-Corruption Layer, minimal Shared Kernel, directory ownership, and product-gap baseline traceability. + +Each hourly product increment must identify the owning product responsibility before selecting a repository, then compare the live directory tree, module/package names, API, database objects, tests, and documentation with that responsibility. Misleading directory paths, generic `utils`/`common` dumping grounds that own domain behavior, infrastructure imports inside the domain model, cross-context database access, obsolete product names, or customer-visible implementation boundaries are architecture defects, not cosmetic debt. When one can be corrected safely in the bounded increment, the agent moves the code and updates imports, package manifests, call sites, migrations, tests, ADRs, diagrams, and compatibility adapters in the same pull request. + +The contract does not impose one universal folder template. A move is justified by domain ownership and dependency direction, not by directory aesthetics. Aggregate boundaries remain the smallest consistency boundary; external and legacy systems are isolated behind an Anti-Corruption Layer; the Shared Kernel remains minimal; and cross-context integration uses explicit versioned contracts. If a coherent move exceeds the current pull request's safe scope, the agent must record the exact owner, callers, target context, migration sequence, and acceptance evidence in `docs/product-technical-gap-baseline.md` and select it as the next bounded architecture increment rather than silently leaving the drift unresolved. + +This opt-in prevents the central coordinator from guessing that an unrelated manual workflow can safely modify product source. Repositories with an existing hourly or more frequent dedicated writer keep their own lease and are never double-dispatched; those schedules may share the same DDD contract and should adopt it without adding another cron. + +The repository-local entrypoint remains responsible for implementing the two environment-name flags in its product-agent adapter, bounded editable paths, tests, 100% production statement and branch coverage, public docstrings, package and security verification, exact-head publication, and pull-request creation. A missing compliant entrypoint is a deliberate no-op, not permission to inject a generic writer into that repository. + +""" + doctoring_path.write_text( + doctoring[:start] + product_section + doctoring[end:], encoding="utf-8" + ) + + replace_once( + "docs/product-technical-gap-baseline.md", + """- **조치:** 수동 제품개발 진입점에 `# cwl-ddd-architecture-audit: required`와 전략·전술 DDD 용어, directory-path repair, `docs/product-technical-gap-baseline.md` 갱신을 요구한다. 기존 전용 예약은 writer lease를 유지해 중복 실행하지 않는다. +""", + """- **조치:** 수동 제품개발 진입점에 `# cwl-ddd-architecture-audit: v1`, root `env`의 versioned capability ID 집합, 자유 형식 agent prompt, 동일 실행 명령의 `--prompt-env CWL_PRODUCT_AGENT_PROMPT`·`--architecture-contract-env CWL_DDD_CONTRACT_CAPABILITIES` binding을 요구한다. 주석·무관 YAML·분리 명령은 계약으로 인정하지 않으며, 기존 전용 예약은 writer lease를 유지해 중복 실행하지 않는다. +""", + ) + replace_once( + "docs/product-technical-gap-baseline.md", + """- **완료 증거:** exact-head focused policy tests, statement/branch coverage 100%, Python docstring 100%, workflow security checks, independent review, protected merge. 병합 전 상태는 구현 중이며 운영 완료로 간주하지 않는다. +""", + """- **리뷰 보강:** raw YAML 전체의 단어 존재 검사를 제거하고 root environment scope와 실제 product-agent command binding을 검증한다. 설명문은 특정 영어 문구에 종속되지 않는다. +- **완료 증거:** exact-head focused policy tests, statement/branch coverage 100%, Python docstring 100%, workflow security checks, independent review, protected merge. 병합 전 상태는 구현 중이며 운영 완료로 간주하지 않는다. +""", + ) + replace_once( + "CHANGELOG.md", + """- Restore the hourly organization commercial-readiness coordinator when the dedicated maintainer secret is absent by exchanging the protected scheduled job's OIDC identity for a short-lived OpenCode App installation token; retain bounded network calls, token masking, and fail-closed parsing. Require every centrally dispatched product-development entrypoint to accept a machine-checked Domain-Driven Design contract, continuously repairing misleading directory ownership and recording larger bounded-context migrations in `docs/product-technical-gap-baseline.md` without duplicating repository-owned schedules. +""", + """- Restore the hourly organization commercial-readiness coordinator when the dedicated maintainer secret is absent by exchanging the protected scheduled job's OIDC identity for a short-lived OpenCode App installation token; retain bounded network calls, token masking, and fail-closed parsing. Require every centrally dispatched product-development entrypoint to bind a versioned machine-readable Domain-Driven Design capability set and repository-owned prompt to the same product-agent command, rejecting comments or unused YAML while continuously repairing misleading directory ownership and recording larger bounded-context migrations in `docs/product-technical-gap-baseline.md` without duplicating repository-owned schedules. +""", + ) + + +def main() -> None: + """Run the selected TDD phase.""" + parser = argparse.ArgumentParser() + parser.add_argument("phase", choices=("red", "green")) + args = parser.parse_args() + if args.phase == "red": + apply_red() + else: + apply_green() + + +if __name__ == "__main__": + main() From e6aaf2c8cb9b2252a934b5823c06b4c2dee9c9fd Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 1 Sep 2026 13:12:01 +0900 Subject: [PATCH 11/24] test(ci): run DDD prompt-binding RED-GREEN repair --- .../workflows/tmp-ddd-binding-review-fix.yml | 113 ++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 .github/workflows/tmp-ddd-binding-review-fix.yml diff --git a/.github/workflows/tmp-ddd-binding-review-fix.yml b/.github/workflows/tmp-ddd-binding-review-fix.yml new file mode 100644 index 0000000000..a035ea0887 --- /dev/null +++ b/.github/workflows/tmp-ddd-binding-review-fix.yml @@ -0,0 +1,113 @@ +name: Temporary DDD Prompt Binding Review Fix + +on: + push: + branches: + - fix/hourly-ddd-development-contract-20260901 + paths: + - .github/workflows/tmp-ddd-binding-review-fix.yml + - scripts/ci/tmp_apply_ddd_binding_review_fix.py + +concurrency: + group: tmp-ddd-binding-review-fix + cancel-in-progress: false + +permissions: + contents: write + +jobs: + repair: + if: github.repository == 'ContextualWisdomLab/.github' + runs-on: ubuntu-24.04 + timeout-minutes: 20 + steps: + - name: Check out exact review-fix branch + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + ref: fix/hourly-ddd-development-contract-20260901 + fetch-depth: 1 + persist-credentials: true + + - name: Set up Python + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + with: + python-version: "3.14" + + - name: Install exact focused-test dependencies + env: + PIP_DISABLE_PIP_VERSION_CHECK: "1" + PIP_NO_INPUT: "1" + shell: bash --noprofile --norc -e -o pipefail {0} + run: | + cat >"${RUNNER_TEMP}/requirements.txt" <<'EOF' + coverage==7.15.2 --hash=sha256:b9a6367e4aff723e8ee8190836836124284e8fcd4265e307c844010cfa074f3f + iniconfig==2.1.0 --hash=sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760 + packaging==26.2 --hash=sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e + pluggy==1.6.0 --hash=sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746 + pygments==2.20.0 --hash=sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176 + pytest==9.1.1 --hash=sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c + EOF + python -m pip install --only-binary=:all: --require-hashes -r "${RUNNER_TEMP}/requirements.txt" + + - name: RED — prove unused YAML prose is currently accepted + shell: bash --noprofile --norc -e -o pipefail {0} + run: | + set -euo pipefail + python -W error scripts/ci/tmp_apply_ddd_binding_review_fix.py red + set +e + red_output="$( + python -m pytest --import-mode=importlib \ + tests/test_organization_commercial_readiness_loop_policy.py \ + -k ddd_contract_rejects_unbound_unused_yaml_prose -q 2>&1 + )" + red_status=$? + set -e + printf '%s\n' "$red_output" + if [ "$red_status" -eq 0 ]; then + echo '::error::RED regression unexpectedly passed before production repair.' + exit 1 + fi + grep -F 'AssertionError' <<<"$red_output" >/dev/null + + - name: GREEN — bind scoped capabilities to one product-agent command + shell: bash --noprofile --norc -e -o pipefail {0} + run: python -W error scripts/ci/tmp_apply_ddd_binding_review_fix.py green + + - name: Verify exact policy coverage and source hygiene + shell: bash --noprofile --norc -e -o pipefail {0} + run: | + python -m coverage run --branch -m pytest --import-mode=importlib \ + tests/test_organization_commercial_readiness_loop*.py -q + python -m coverage report \ + --include='scripts/ci/organization_commercial_readiness_loop.py' \ + --show-missing \ + --fail-under=100 + python -m compileall -q \ + scripts/ci/organization_commercial_readiness_loop.py \ + organization_commercial_readiness_fixtures.py \ + tests/test_organization_commercial_readiness_loop*.py + git diff --check + grep -F '# cwl-ddd-architecture-audit: v1' organization_commercial_readiness_fixtures.py >/dev/null + grep -F -- '--architecture-contract-env CWL_DDD_CONTRACT_CAPABILITIES' organization_commercial_readiness_fixtures.py >/dev/null + + - name: Publish verified non-workflow review remediation + shell: bash --noprofile --norc -e -o pipefail {0} + run: | + set -euo pipefail + git restore --source=HEAD -- scripts/ci/tmp_apply_ddd_binding_review_fix.py + git add \ + CHANGELOG.md \ + docs/doctoring/organization-commercial-readiness-loop.md \ + docs/product-technical-gap-baseline.md \ + organization_commercial_readiness_fixtures.py \ + scripts/ci/organization_commercial_readiness_loop.py \ + tests/test_organization_commercial_readiness_loop_policy.py + test -n "$(git diff --cached --name-only)" + if git diff --cached --name-only | grep -q '^\.github/workflows/'; then + echo '::error::Workflow paths must be published only through the authorized connector.' + exit 1 + fi + git config user.name 'github-actions[bot]' + git config user.email '41898282+github-actions[bot]@users.noreply.github.com' + git commit -m 'fix(automation): bind DDD contract to product-agent invocation' + git push origin HEAD:fix/hourly-ddd-development-contract-20260901 From 1c1532b3d6ce8db5eb7b8fd27b18f6eebcbd389d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 1 Sep 2026 13:15:08 +0900 Subject: [PATCH 12/24] chore(ci): remove unsafe temporary DDD source-fix lane --- .../workflows/tmp-ddd-binding-review-fix.yml | 113 ---- .../ci/tmp_apply_ddd_binding_review_fix.py | 532 ------------------ 2 files changed, 645 deletions(-) delete mode 100644 .github/workflows/tmp-ddd-binding-review-fix.yml delete mode 100644 scripts/ci/tmp_apply_ddd_binding_review_fix.py diff --git a/.github/workflows/tmp-ddd-binding-review-fix.yml b/.github/workflows/tmp-ddd-binding-review-fix.yml deleted file mode 100644 index a035ea0887..0000000000 --- a/.github/workflows/tmp-ddd-binding-review-fix.yml +++ /dev/null @@ -1,113 +0,0 @@ -name: Temporary DDD Prompt Binding Review Fix - -on: - push: - branches: - - fix/hourly-ddd-development-contract-20260901 - paths: - - .github/workflows/tmp-ddd-binding-review-fix.yml - - scripts/ci/tmp_apply_ddd_binding_review_fix.py - -concurrency: - group: tmp-ddd-binding-review-fix - cancel-in-progress: false - -permissions: - contents: write - -jobs: - repair: - if: github.repository == 'ContextualWisdomLab/.github' - runs-on: ubuntu-24.04 - timeout-minutes: 20 - steps: - - name: Check out exact review-fix branch - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - ref: fix/hourly-ddd-development-contract-20260901 - fetch-depth: 1 - persist-credentials: true - - - name: Set up Python - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 - with: - python-version: "3.14" - - - name: Install exact focused-test dependencies - env: - PIP_DISABLE_PIP_VERSION_CHECK: "1" - PIP_NO_INPUT: "1" - shell: bash --noprofile --norc -e -o pipefail {0} - run: | - cat >"${RUNNER_TEMP}/requirements.txt" <<'EOF' - coverage==7.15.2 --hash=sha256:b9a6367e4aff723e8ee8190836836124284e8fcd4265e307c844010cfa074f3f - iniconfig==2.1.0 --hash=sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760 - packaging==26.2 --hash=sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e - pluggy==1.6.0 --hash=sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746 - pygments==2.20.0 --hash=sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176 - pytest==9.1.1 --hash=sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c - EOF - python -m pip install --only-binary=:all: --require-hashes -r "${RUNNER_TEMP}/requirements.txt" - - - name: RED — prove unused YAML prose is currently accepted - shell: bash --noprofile --norc -e -o pipefail {0} - run: | - set -euo pipefail - python -W error scripts/ci/tmp_apply_ddd_binding_review_fix.py red - set +e - red_output="$( - python -m pytest --import-mode=importlib \ - tests/test_organization_commercial_readiness_loop_policy.py \ - -k ddd_contract_rejects_unbound_unused_yaml_prose -q 2>&1 - )" - red_status=$? - set -e - printf '%s\n' "$red_output" - if [ "$red_status" -eq 0 ]; then - echo '::error::RED regression unexpectedly passed before production repair.' - exit 1 - fi - grep -F 'AssertionError' <<<"$red_output" >/dev/null - - - name: GREEN — bind scoped capabilities to one product-agent command - shell: bash --noprofile --norc -e -o pipefail {0} - run: python -W error scripts/ci/tmp_apply_ddd_binding_review_fix.py green - - - name: Verify exact policy coverage and source hygiene - shell: bash --noprofile --norc -e -o pipefail {0} - run: | - python -m coverage run --branch -m pytest --import-mode=importlib \ - tests/test_organization_commercial_readiness_loop*.py -q - python -m coverage report \ - --include='scripts/ci/organization_commercial_readiness_loop.py' \ - --show-missing \ - --fail-under=100 - python -m compileall -q \ - scripts/ci/organization_commercial_readiness_loop.py \ - organization_commercial_readiness_fixtures.py \ - tests/test_organization_commercial_readiness_loop*.py - git diff --check - grep -F '# cwl-ddd-architecture-audit: v1' organization_commercial_readiness_fixtures.py >/dev/null - grep -F -- '--architecture-contract-env CWL_DDD_CONTRACT_CAPABILITIES' organization_commercial_readiness_fixtures.py >/dev/null - - - name: Publish verified non-workflow review remediation - shell: bash --noprofile --norc -e -o pipefail {0} - run: | - set -euo pipefail - git restore --source=HEAD -- scripts/ci/tmp_apply_ddd_binding_review_fix.py - git add \ - CHANGELOG.md \ - docs/doctoring/organization-commercial-readiness-loop.md \ - docs/product-technical-gap-baseline.md \ - organization_commercial_readiness_fixtures.py \ - scripts/ci/organization_commercial_readiness_loop.py \ - tests/test_organization_commercial_readiness_loop_policy.py - test -n "$(git diff --cached --name-only)" - if git diff --cached --name-only | grep -q '^\.github/workflows/'; then - echo '::error::Workflow paths must be published only through the authorized connector.' - exit 1 - fi - git config user.name 'github-actions[bot]' - git config user.email '41898282+github-actions[bot]@users.noreply.github.com' - git commit -m 'fix(automation): bind DDD contract to product-agent invocation' - git push origin HEAD:fix/hourly-ddd-development-contract-20260901 diff --git a/scripts/ci/tmp_apply_ddd_binding_review_fix.py b/scripts/ci/tmp_apply_ddd_binding_review_fix.py deleted file mode 100644 index f6bfe1ea53..0000000000 --- a/scripts/ci/tmp_apply_ddd_binding_review_fix.py +++ /dev/null @@ -1,532 +0,0 @@ -#!/usr/bin/env python3 -"""Apply RED then GREEN review remediation for the hourly DDD entrypoint contract.""" - -from __future__ import annotations - -import argparse -from pathlib import Path - -ROOT = Path(__file__).resolve().parents[2] - - -def replace_once(path: str, old: str, new: str) -> None: - """Replace one exact fragment or fail closed on branch drift.""" - target = ROOT / path - source = target.read_text(encoding="utf-8") - count = source.count(old) - if count != 1: - raise SystemExit(f"{path}: expected one fragment, found {count}") - target.write_text(source.replace(old, new, 1), encoding="utf-8") - - -def apply_red() -> None: - """Add a regression that the raw-prose implementation must fail.""" - replace_once( - "tests/test_organization_commercial_readiness_loop_policy.py", - """ for changed in mutations: - assert not is_manual_product_entrypoint(workflow(content=changed)) - - -def test_repository_eligibility_is_owned_and_write_capable() -> None: -""", - """ for changed in mutations: - assert not is_manual_product_entrypoint(workflow(content=changed)) - - -def test_ddd_contract_rejects_unbound_unused_yaml_prose() -> None: - \"\"\"DDD words in an unused scalar are not an agent instruction contract.\"\"\" - safe = manual_workflow() - assert safe.content is not None - unused = safe.content.replace("prompt: |\\n", "notes: |\\n", 1) - assert not is_manual_product_entrypoint(workflow(content=unused)) - - -def test_repository_eligibility_is_owned_and_write_capable() -> None: -""", - ) - - -def apply_green() -> None: - """Install the scoped capability and invocation-binding contract.""" - replace_once( - "scripts/ci/organization_commercial_readiness_loop.py", - """import re -import subprocess -import sys -from pathlib import Path -""", - """import re -import subprocess -import sys -import textwrap -from pathlib import Path -""", - ) - replace_once( - "scripts/ci/organization_commercial_readiness_loop.py", - """ENTRYPOINT_MARKER = "# cwl-org-commercial-entrypoint: v1" -DDD_ENTRYPOINT_MARKER = "# cwl-ddd-architecture-audit: required" -DDD_CONTRACT_TERMS = ( - "Domain-Driven Design", - "core, supporting, and generic subdomains", - "Bounded Context", - "Context Map", - "Ubiquitous Language", - "Aggregate", - "Entity", - "Value Object", - "Domain Service", - "Repository", - "Domain Event", - "Invariant", - "Anti-Corruption Layer", - "Shared Kernel", - "directory paths", - "docs/product-technical-gap-baseline.md", -) -CENTRAL_REPOSITORY = f"{DEFAULT_ORGANIZATION}/.github" -""", - """ENTRYPOINT_MARKER = "# cwl-org-commercial-entrypoint: v1" -DDD_ENTRYPOINT_MARKER = "# cwl-ddd-architecture-audit: v1" -DDD_PROMPT_BINDING_MARKER = "# cwl-ddd-prompt-binding: v1" -DDD_PROMPT_ENVIRONMENT = "CWL_PRODUCT_AGENT_PROMPT" -DDD_CAPABILITY_ENVIRONMENT = "CWL_DDD_CONTRACT_CAPABILITIES" -DDD_PROMPT_BINDING = f"--prompt-env {DDD_PROMPT_ENVIRONMENT}" -DDD_CAPABILITY_BINDING = ( - f"--architecture-contract-env {DDD_CAPABILITY_ENVIRONMENT}" -) -DDD_CONTRACT_CAPABILITIES = frozenset( - { - "aggregate", - "anti_corruption_layer", - "bounded_context", - "context_map", - "directory_ownership", - "domain_event", - "domain_service", - "entity", - "invariant", - "minimal_shared_kernel", - "product_gap_baseline", - "repository", - "subdomain_classification", - "ubiquitous_language", - "value_object", - } -) -DDD_CAPABILITY_TOKEN_RE = re.compile(r"[a-z][a-z0-9_]*") -CENTRAL_REPOSITORY = f"{DEFAULT_ORGANIZATION}/.github" -""", - ) - replace_once( - "scripts/ci/organization_commercial_readiness_loop.py", - """def has_domain_driven_development_contract(source: str) -> bool: - \"\"\"Return whether one entrypoint accepts the complete DDD repair contract.\"\"\" - return DDD_ENTRYPOINT_MARKER in source and all( - term in source for term in DDD_CONTRACT_TERMS - ) - - -def is_manual_product_entrypoint(workflow: WorkflowRecord) -> bool: -""", - """def _root_mapping_regions(source: str, key: str) -> tuple[str, ...]: - \"\"\"Return top-level YAML mapping bodies for one exact key.\"\"\" - lines = source.splitlines() - regions: list[str] = [] - for index, line in enumerate(lines): - if line != f"{key}:": - continue - body: list[str] = [] - for candidate in lines[index + 1 :]: - if candidate.strip() and not candidate.startswith(" "): - break - body.append(candidate) - regions.append("\\n".join(body)) - return tuple(regions) - - -def _yaml_block_scalars(source: str, key: str) -> tuple[str, ...]: - \"\"\"Return dedented YAML literal or folded block scalars for one key.\"\"\" - header = re.compile( - rf"^(?P *){re.escape(key)}: *[>|][+-]? *$" - ) - lines = source.splitlines() - blocks: list[str] = [] - for index, line in enumerate(lines): - match = header.fullmatch(line) - if match is None: - continue - base_indent = len(match.group("indent")) - body: list[str] = [] - for candidate in lines[index + 1 :]: - if candidate.strip(): - candidate_indent = len(candidate) - len(candidate.lstrip(" ")) - if candidate_indent <= base_indent: - break - body.append(candidate) - blocks.append(textwrap.dedent("\\n".join(body)).strip("\\n")) - return tuple(blocks) - - -def _shell_commands(block: str) -> tuple[str, ...]: - \"\"\"Return non-comment shell commands with continuations joined.\"\"\" - commands: list[str] = [] - fragments: list[str] = [] - for line in block.splitlines(): - fragment = line.strip() - if not fragment or fragment.startswith("#"): - continue - continued = fragment.endswith("\\") - fragments.append(fragment.removesuffix("\\").rstrip()) - if continued: - continue - commands.append(" ".join(fragments)) - fragments = [] - if fragments: - commands.append(" ".join(fragments)) - return tuple(commands) - - -def _has_bound_ddd_agent_invocation(source: str) -> bool: - \"\"\"Return whether one executable command receives both contract inputs.\"\"\" - for run_block in _yaml_block_scalars(source, "run"): - if DDD_PROMPT_BINDING_MARKER not in run_block: - continue - for command in _shell_commands(run_block): - if DDD_PROMPT_BINDING in command and DDD_CAPABILITY_BINDING in command: - return True - return False - - -def has_domain_driven_development_contract(source: str) -> bool: - \"\"\"Return whether one entrypoint binds a scoped versioned DDD contract.\"\"\" - if DDD_ENTRYPOINT_MARKER not in source: - return False - root_environments = _root_mapping_regions(source, "env") - if len(root_environments) != 1: - return False - environment = root_environments[0] - capability_blocks = _yaml_block_scalars( - environment, DDD_CAPABILITY_ENVIRONMENT - ) - prompt_blocks = _yaml_block_scalars(environment, DDD_PROMPT_ENVIRONMENT) - if len(capability_blocks) != 1 or len(prompt_blocks) != 1: - return False - if not prompt_blocks[0].strip(): - return False - declared_capabilities = frozenset( - DDD_CAPABILITY_TOKEN_RE.findall(capability_blocks[0]) - ) - if not DDD_CONTRACT_CAPABILITIES.issubset(declared_capabilities): - return False - return _has_bound_ddd_agent_invocation(source) - - -def is_manual_product_entrypoint(workflow: WorkflowRecord) -> bool: -""", - ) - replace_once( - "organization_commercial_readiness_fixtures.py", - """ content=( - "# cwl-org-commercial-entrypoint: v1\\n" - "# cwl-ddd-architecture-audit: required\\n" - "on:\\n workflow_dispatch:\\n" - "concurrency:\\n group: product-development\\n" - "permissions:\\n contents: write\\n" - "NVIDIA_API_KEY: ${{ secrets.NVIDIA_NIM_API_KEY }}\\n" - "prompt: |\\n" - " Apply Domain-Driven Design before and during every increment.\\n" - " Classify core, supporting, and generic subdomains; define each Bounded Context, Context Map, and Ubiquitous Language.\\n" - " Keep Aggregate, Entity, Value Object, Domain Service, Repository, Domain Event, and Invariant names aligned across code, API, database, and tests.\\n" - " Isolate external systems behind an Anti-Corruption Layer and keep the Shared Kernel minimal.\\n" - " Audit and correct misleading directory paths with imports, packaging, callers, tests, and architecture documents in the same bounded change.\\n" - " Update docs/product-technical-gap-baseline.md with detected and repaired architecture drift.\\n" - ), -""", - """ content=( - "# cwl-org-commercial-entrypoint: v1\\n" - "# cwl-ddd-architecture-audit: v1\\n" - "on:\\n workflow_dispatch:\\n" - "concurrency:\\n group: product-development\\n" - "permissions:\\n contents: write\\n" - "env:\\n" - " NVIDIA_API_KEY: ${{ secrets.NVIDIA_NIM_API_KEY }}\\n" - " CWL_DDD_CONTRACT_CAPABILITIES: >-\\n" - " aggregate anti_corruption_layer bounded_context context_map\\n" - " directory_ownership domain_event domain_service entity invariant\\n" - " minimal_shared_kernel product_gap_baseline repository\\n" - " subdomain_classification ubiquitous_language value_object\\n" - " CWL_PRODUCT_AGENT_PROMPT: |\\n" - " Deliver one buyer-visible increment through the repository-owned product agent.\\n" - "\\n" - " Keep implementation, tests, documentation, and package boundaries coherent.\\n" - "jobs:\\n" - " develop:\\n" - " runs-on: ubuntu-24.04\\n" - " steps:\\n" - " - name: Invoke the product agent\\n" - " run: |\\n" - " # cwl-ddd-prompt-binding: v1\\n" - " python scripts/automation/commercial_product_development.py \\\\\\n" - " --prompt-env CWL_PRODUCT_AGENT_PROMPT \\\\\\n" - " --architecture-contract-env CWL_DDD_CONTRACT_CAPABILITIES\\n" - ), -""", - ) - replace_once( - "tests/test_organization_commercial_readiness_loop_policy.py", - """ ActionKind, - ActionResult, - DDD_CONTRACT_TERMS, - RunRecord, -""", - """ ActionKind, - ActionResult, - DDD_CONTRACT_CAPABILITIES, - RunRecord, -""", - ) - replace_once( - "tests/test_organization_commercial_readiness_loop_policy.py", - """ is_dedicated_writer_workflow, - is_live_writer_run, - is_manual_product_entrypoint, -""", - """ has_domain_driven_development_contract, - is_dedicated_writer_workflow, - is_live_writer_run, - is_manual_product_entrypoint, -""", - ) - replace_once( - "tests/test_organization_commercial_readiness_loop_policy.py", - """def test_product_entrypoint_requires_manual_nvidia_and_ddd_opt_in() -> None: - \"\"\"Product dispatch requires a manual credential-isolated DDD contract.\"\"\" - safe = manual_workflow() - assert is_manual_product_entrypoint(safe) - assert not is_manual_product_entrypoint(workflow(state="disabled_manually", content="x")) - assert not is_manual_product_entrypoint(workflow(content=None)) - mutations = [ - (safe.content or "") + 'schedule:\\n - cron: "1 * * * *"\\n', - (safe.content or "") + "COPILOT_GITHUB_TOKEN: forbidden\\n", - (safe.content or "").replace("# cwl-org-commercial-entrypoint: v1\\n", ""), - (safe.content or "").replace( - "# cwl-ddd-architecture-audit: required\\n", "" - ), - (safe.content or "").replace("concurrency:\\n", ""), - ] - mutations.extend( - (safe.content or "").replace(term, f"missing-{index}", 1) - for index, term in enumerate(DDD_CONTRACT_TERMS) - ) - for changed in mutations: - assert not is_manual_product_entrypoint(workflow(content=changed)) - - -def test_ddd_contract_rejects_unbound_unused_yaml_prose() -> None: - \"\"\"DDD words in an unused scalar are not an agent instruction contract.\"\"\" - safe = manual_workflow() - assert safe.content is not None - unused = safe.content.replace("prompt: |\\n", "notes: |\\n", 1) - assert not is_manual_product_entrypoint(workflow(content=unused)) -""", - """def test_product_entrypoint_requires_manual_nvidia_and_bound_ddd_opt_in() -> None: - \"\"\"Product dispatch requires a scoped capability set bound to one command.\"\"\" - safe = manual_workflow() - assert safe.content is not None - source = safe.content - assert "Domain-Driven Design" not in source - assert is_manual_product_entrypoint(safe) - assert has_domain_driven_development_contract(source) - - ordinary_rejections = [ - source + 'schedule:\\n - cron: "1 * * * *"\\n', - source + "COPILOT_GITHUB_TOKEN: forbidden\\n", - source.replace("# cwl-org-commercial-entrypoint: v1\\n", "", 1), - source.replace("# cwl-ddd-architecture-audit: v1\\n", "", 1), - source.replace("concurrency:\\n", "", 1), - ] - for changed in ordinary_rejections: - assert not is_manual_product_entrypoint(workflow(content=changed)) - - for index, capability in enumerate(sorted(DDD_CONTRACT_CAPABILITIES)): - changed = source.replace(capability, f"omitted_{index}", 1) - assert not has_domain_driven_development_contract(changed) - - -def test_ddd_contract_rejects_comments_unused_scopes_and_split_bindings() -> None: - \"\"\"Comments, unscoped values, and separate commands cannot fake a binding.\"\"\" - safe = manual_workflow() - assert safe.content is not None - source = safe.content - comment_only = ( - "# cwl-ddd-architecture-audit: v1\\n" - + "\\n".join(f"# {item}" for item in sorted(DDD_CONTRACT_CAPABILITIES)) - + "\\n# cwl-ddd-prompt-binding: v1\\n" - + "# --prompt-env CWL_PRODUCT_AGENT_PROMPT\\n" - + "# --architecture-contract-env CWL_DDD_CONTRACT_CAPABILITIES\\n" - ) - assert not has_domain_driven_development_contract(comment_only) - - invalid_sources = [ - source.replace("env:\\n", "metadata:\\n", 1), - source.replace("CWL_PRODUCT_AGENT_PROMPT: |", "UNUSED_PROMPT: |", 1), - source.replace( - "CWL_DDD_CONTRACT_CAPABILITIES: >-", "UNUSED_CAPABILITIES: >-", 1 - ), - source.replace( - " Deliver one buyer-visible increment through the repository-owned product agent.\\n", - "", - 1, - ), - source.replace("# cwl-ddd-prompt-binding: v1", "# unbound", 1), - source.replace( - "--prompt-env CWL_PRODUCT_AGENT_PROMPT", "--prompt-env UNUSED_PROMPT", 1 - ), - source.replace( - "--architecture-contract-env CWL_DDD_CONTRACT_CAPABILITIES", - "--architecture-contract-env UNUSED_CAPABILITIES", - 1, - ), - source + "env:\\n OTHER_VALUE: present\\n", - source.replace( - " CWL_PRODUCT_AGENT_PROMPT: |", - " CWL_PRODUCT_AGENT_PROMPT: |\\n" - " duplicate\\n" - " CWL_PRODUCT_AGENT_PROMPT: |", - 1, - ), - source.replace( - " CWL_DDD_CONTRACT_CAPABILITIES: >-", - " CWL_DDD_CONTRACT_CAPABILITIES: >-\\n" - " bounded_context\\n" - " CWL_DDD_CONTRACT_CAPABILITIES: >-", - 1, - ), - ] - for changed in invalid_sources: - assert not has_domain_driven_development_contract(changed) - - bound_run = ( - " run: |\\n" - " # cwl-ddd-prompt-binding: v1\\n" - " python scripts/automation/commercial_product_development.py \\\\n" - " --prompt-env CWL_PRODUCT_AGENT_PROMPT \\\\n" - " --architecture-contract-env CWL_DDD_CONTRACT_CAPABILITIES\\n" - ) - split_run = ( - " run: |\\n" - " # cwl-ddd-prompt-binding: v1\\n" - " product-agent --prompt-env CWL_PRODUCT_AGENT_PROMPT\\n" - " product-agent --architecture-contract-env CWL_DDD_CONTRACT_CAPABILITIES\\n" - ) - assert not has_domain_driven_development_contract( - source.replace(bound_run, split_run, 1) - ) - - dangling_run = ( - " run: |\\n" - " # cwl-ddd-prompt-binding: v1\\n" - " product-agent \\\\n" - ) - assert not has_domain_driven_development_contract( - source.replace(bound_run, dangling_run, 1) - ) -""", - ) - replace_once( - "tests/test_organization_commercial_readiness_loop_policy.py", - """ assert "# cwl-ddd-architecture-audit: required" in doctoring -""", - """ assert "# cwl-ddd-architecture-audit: v1" in doctoring - assert "--architecture-contract-env CWL_DDD_CONTRACT_CAPABILITIES" in doctoring -""", - ) - - doctoring_path = ROOT / "docs/doctoring/organization-commercial-readiness-loop.md" - doctoring = doctoring_path.read_text(encoding="utf-8") - start = doctoring.index("## Product-development boundary") - end = doctoring.index("## Failure, evidence, and operations") - product_section = """## Product-development boundary - -Product development is dispatched only when a repository has zero open pull requests and exposes one active, manual-only, explicitly marked workflow. The DDD enrollment is versioned and its values must live in the root workflow `env` mapping so every job receives the same contract: - -```yaml -# cwl-org-commercial-entrypoint: v1 -# cwl-ddd-architecture-audit: v1 -on: - workflow_dispatch: - -env: - CWL_DDD_CONTRACT_CAPABILITIES: >- - aggregate anti_corruption_layer bounded_context context_map - directory_ownership domain_event domain_service entity invariant - minimal_shared_kernel product_gap_baseline repository - subdomain_classification ubiquitous_language value_object - CWL_PRODUCT_AGENT_PROMPT: | - Deliver one buyer-visible increment through the repository-owned product agent. - -jobs: - develop: - steps: - - run: | - # cwl-ddd-prompt-binding: v1 - product-agent \ - --prompt-env CWL_PRODUCT_AGENT_PROMPT \ - --architecture-contract-env CWL_DDD_CONTRACT_CAPABILITIES -``` - -The entrypoint must also contain an explicit `concurrency` contract, use `NVIDIA_NIM_API_KEY`, omit `COPILOT_GITHUB_TOKEN`, have no schedule of its own, and carry a commercial/product-development identity. Human-readable prompt wording is repository-owned and may use any language. Eligibility depends on stable capability identifiers rather than copied English prose. The prompt and capability environment names must be passed to the same non-comment shell command under the binding marker; comments, unrelated YAML, unscoped step values, separate commands, or unused block scalars do not satisfy the contract. - -The capability contract covers strategic and tactical Domain-Driven Design: subdomain classification, Bounded Context, Context Map, Ubiquitous Language, Aggregate, Entity, Value Object, Domain Service, Repository, Domain Event, Invariant, Anti-Corruption Layer, minimal Shared Kernel, directory ownership, and product-gap baseline traceability. - -Each hourly product increment must identify the owning product responsibility before selecting a repository, then compare the live directory tree, module/package names, API, database objects, tests, and documentation with that responsibility. Misleading directory paths, generic `utils`/`common` dumping grounds that own domain behavior, infrastructure imports inside the domain model, cross-context database access, obsolete product names, or customer-visible implementation boundaries are architecture defects, not cosmetic debt. When one can be corrected safely in the bounded increment, the agent moves the code and updates imports, package manifests, call sites, migrations, tests, ADRs, diagrams, and compatibility adapters in the same pull request. - -The contract does not impose one universal folder template. A move is justified by domain ownership and dependency direction, not by directory aesthetics. Aggregate boundaries remain the smallest consistency boundary; external and legacy systems are isolated behind an Anti-Corruption Layer; the Shared Kernel remains minimal; and cross-context integration uses explicit versioned contracts. If a coherent move exceeds the current pull request's safe scope, the agent must record the exact owner, callers, target context, migration sequence, and acceptance evidence in `docs/product-technical-gap-baseline.md` and select it as the next bounded architecture increment rather than silently leaving the drift unresolved. - -This opt-in prevents the central coordinator from guessing that an unrelated manual workflow can safely modify product source. Repositories with an existing hourly or more frequent dedicated writer keep their own lease and are never double-dispatched; those schedules may share the same DDD contract and should adopt it without adding another cron. - -The repository-local entrypoint remains responsible for implementing the two environment-name flags in its product-agent adapter, bounded editable paths, tests, 100% production statement and branch coverage, public docstrings, package and security verification, exact-head publication, and pull-request creation. A missing compliant entrypoint is a deliberate no-op, not permission to inject a generic writer into that repository. - -""" - doctoring_path.write_text( - doctoring[:start] + product_section + doctoring[end:], encoding="utf-8" - ) - - replace_once( - "docs/product-technical-gap-baseline.md", - """- **조치:** 수동 제품개발 진입점에 `# cwl-ddd-architecture-audit: required`와 전략·전술 DDD 용어, directory-path repair, `docs/product-technical-gap-baseline.md` 갱신을 요구한다. 기존 전용 예약은 writer lease를 유지해 중복 실행하지 않는다. -""", - """- **조치:** 수동 제품개발 진입점에 `# cwl-ddd-architecture-audit: v1`, root `env`의 versioned capability ID 집합, 자유 형식 agent prompt, 동일 실행 명령의 `--prompt-env CWL_PRODUCT_AGENT_PROMPT`·`--architecture-contract-env CWL_DDD_CONTRACT_CAPABILITIES` binding을 요구한다. 주석·무관 YAML·분리 명령은 계약으로 인정하지 않으며, 기존 전용 예약은 writer lease를 유지해 중복 실행하지 않는다. -""", - ) - replace_once( - "docs/product-technical-gap-baseline.md", - """- **완료 증거:** exact-head focused policy tests, statement/branch coverage 100%, Python docstring 100%, workflow security checks, independent review, protected merge. 병합 전 상태는 구현 중이며 운영 완료로 간주하지 않는다. -""", - """- **리뷰 보강:** raw YAML 전체의 단어 존재 검사를 제거하고 root environment scope와 실제 product-agent command binding을 검증한다. 설명문은 특정 영어 문구에 종속되지 않는다. -- **완료 증거:** exact-head focused policy tests, statement/branch coverage 100%, Python docstring 100%, workflow security checks, independent review, protected merge. 병합 전 상태는 구현 중이며 운영 완료로 간주하지 않는다. -""", - ) - replace_once( - "CHANGELOG.md", - """- Restore the hourly organization commercial-readiness coordinator when the dedicated maintainer secret is absent by exchanging the protected scheduled job's OIDC identity for a short-lived OpenCode App installation token; retain bounded network calls, token masking, and fail-closed parsing. Require every centrally dispatched product-development entrypoint to accept a machine-checked Domain-Driven Design contract, continuously repairing misleading directory ownership and recording larger bounded-context migrations in `docs/product-technical-gap-baseline.md` without duplicating repository-owned schedules. -""", - """- Restore the hourly organization commercial-readiness coordinator when the dedicated maintainer secret is absent by exchanging the protected scheduled job's OIDC identity for a short-lived OpenCode App installation token; retain bounded network calls, token masking, and fail-closed parsing. Require every centrally dispatched product-development entrypoint to bind a versioned machine-readable Domain-Driven Design capability set and repository-owned prompt to the same product-agent command, rejecting comments or unused YAML while continuously repairing misleading directory ownership and recording larger bounded-context migrations in `docs/product-technical-gap-baseline.md` without duplicating repository-owned schedules. -""", - ) - - -def main() -> None: - """Run the selected TDD phase.""" - parser = argparse.ArgumentParser() - parser.add_argument("phase", choices=("red", "green")) - args = parser.parse_args() - if args.phase == "red": - apply_red() - else: - apply_green() - - -if __name__ == "__main__": - main() From aeba2182159c21d0d6504b18f06ee633fd4b40c0 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 1 Sep 2026 13:15:17 +0900 Subject: [PATCH 13/24] fix(ci): normalize DDD parser backslash generation --- .../workflows/tmp-ddd-binding-review-fix.yml | 142 ++++++++++++++++++ 1 file changed, 142 insertions(+) create mode 100644 .github/workflows/tmp-ddd-binding-review-fix.yml diff --git a/.github/workflows/tmp-ddd-binding-review-fix.yml b/.github/workflows/tmp-ddd-binding-review-fix.yml new file mode 100644 index 0000000000..2afbf8b366 --- /dev/null +++ b/.github/workflows/tmp-ddd-binding-review-fix.yml @@ -0,0 +1,142 @@ +name: Temporary DDD Prompt Binding Review Fix + +on: + push: + branches: + - fix/hourly-ddd-development-contract-20260901 + paths: + - .github/workflows/tmp-ddd-binding-review-fix.yml + - scripts/ci/tmp_apply_ddd_binding_review_fix.py + +concurrency: + group: tmp-ddd-binding-review-fix + cancel-in-progress: false + +permissions: + contents: write + +jobs: + repair: + if: github.repository == 'ContextualWisdomLab/.github' + runs-on: ubuntu-24.04 + timeout-minutes: 20 + steps: + - name: Check out exact review-fix branch + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + ref: fix/hourly-ddd-development-contract-20260901 + fetch-depth: 1 + persist-credentials: true + + - name: Set up Python + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + with: + python-version: "3.14" + + - name: Normalize bootstrap escape generation + shell: bash --noprofile --norc -e -o pipefail {0} + run: | + python - <<'PY' + from pathlib import Path + + path = Path("scripts/ci/tmp_apply_ddd_binding_review_fix.py") + source = path.read_text(encoding="utf-8") + replacements = ( + ( + r' continued = fragment.endswith("\\")', + r' continued = fragment.endswith("\\\\")', + ), + ( + r' fragments.append(fragment.removesuffix("\\").rstrip())', + r' fragments.append(fragment.removesuffix("\\\\").rstrip())', + ), + ) + for old, new in replacements: + count = source.count(old) + if count != 1: + raise SystemExit( + f"bootstrap escape normalization expected one fragment, found {count}: {old!r}" + ) + source = source.replace(old, new, 1) + path.write_text(source, encoding="utf-8") + PY + python -W error -m py_compile scripts/ci/tmp_apply_ddd_binding_review_fix.py + + - name: Install exact focused-test dependencies + env: + PIP_DISABLE_PIP_VERSION_CHECK: "1" + PIP_NO_INPUT: "1" + shell: bash --noprofile --norc -e -o pipefail {0} + run: | + cat >"${RUNNER_TEMP}/requirements.txt" <<'EOF' + coverage==7.15.2 --hash=sha256:b9a6367e4aff723e8ee8190836836124284e8fcd4265e307c844010cfa074f3f + iniconfig==2.1.0 --hash=sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760 + packaging==26.2 --hash=sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e + pluggy==1.6.0 --hash=sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746 + pygments==2.20.0 --hash=sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176 + pytest==9.1.1 --hash=sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c + EOF + python -m pip install --only-binary=:all: --require-hashes -r "${RUNNER_TEMP}/requirements.txt" + + - name: RED — prove unused YAML prose is currently accepted + shell: bash --noprofile --norc -e -o pipefail {0} + run: | + set -euo pipefail + python -W error scripts/ci/tmp_apply_ddd_binding_review_fix.py red + set +e + red_output="$( + python -m pytest --import-mode=importlib \ + tests/test_organization_commercial_readiness_loop_policy.py \ + -k ddd_contract_rejects_unbound_unused_yaml_prose -q 2>&1 + )" + red_status=$? + set -e + printf '%s\n' "$red_output" + if [ "$red_status" -eq 0 ]; then + echo '::error::RED regression unexpectedly passed before production repair.' + exit 1 + fi + grep -F 'AssertionError' <<<"$red_output" >/dev/null + + - name: GREEN — bind scoped capabilities to one product-agent command + shell: bash --noprofile --norc -e -o pipefail {0} + run: python -W error scripts/ci/tmp_apply_ddd_binding_review_fix.py green + + - name: Verify exact policy coverage and source hygiene + shell: bash --noprofile --norc -e -o pipefail {0} + run: | + python -m coverage run --branch -m pytest --import-mode=importlib \ + tests/test_organization_commercial_readiness_loop*.py -q + python -m coverage report \ + --include='scripts/ci/organization_commercial_readiness_loop.py' \ + --show-missing \ + --fail-under=100 + python -m compileall -q \ + scripts/ci/organization_commercial_readiness_loop.py \ + organization_commercial_readiness_fixtures.py \ + tests/test_organization_commercial_readiness_loop*.py + git diff --check + grep -F '# cwl-ddd-architecture-audit: v1' organization_commercial_readiness_fixtures.py >/dev/null + grep -F -- '--architecture-contract-env CWL_DDD_CONTRACT_CAPABILITIES' organization_commercial_readiness_fixtures.py >/dev/null + + - name: Publish verified non-workflow review remediation + shell: bash --noprofile --norc -e -o pipefail {0} + run: | + set -euo pipefail + git restore --source=HEAD -- scripts/ci/tmp_apply_ddd_binding_review_fix.py + git add \ + CHANGELOG.md \ + docs/doctoring/organization-commercial-readiness-loop.md \ + docs/product-technical-gap-baseline.md \ + organization_commercial_readiness_fixtures.py \ + scripts/ci/organization_commercial_readiness_loop.py \ + tests/test_organization_commercial_readiness_loop_policy.py + test -n "$(git diff --cached --name-only)" + if git diff --cached --name-only | grep -q '^\.github/workflows/'; then + echo '::error::Workflow paths must be published only through the authorized connector.' + exit 1 + fi + git config user.name 'github-actions[bot]' + git config user.email '41898282+github-actions[bot]@users.noreply.github.com' + git commit -m 'fix(automation): bind DDD contract to product-agent invocation' + git push origin HEAD:fix/hourly-ddd-development-contract-20260901 From c355cbf34365434d7f33f8bbaca7480cd6679dc6 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 1 Sep 2026 13:19:07 +0900 Subject: [PATCH 14/24] test(automation): restore RED-GREEN DDD binding bootstrap --- .../ci/tmp_apply_ddd_binding_review_fix.py | 532 ++++++++++++++++++ 1 file changed, 532 insertions(+) create mode 100644 scripts/ci/tmp_apply_ddd_binding_review_fix.py diff --git a/scripts/ci/tmp_apply_ddd_binding_review_fix.py b/scripts/ci/tmp_apply_ddd_binding_review_fix.py new file mode 100644 index 0000000000..f6bfe1ea53 --- /dev/null +++ b/scripts/ci/tmp_apply_ddd_binding_review_fix.py @@ -0,0 +1,532 @@ +#!/usr/bin/env python3 +"""Apply RED then GREEN review remediation for the hourly DDD entrypoint contract.""" + +from __future__ import annotations + +import argparse +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[2] + + +def replace_once(path: str, old: str, new: str) -> None: + """Replace one exact fragment or fail closed on branch drift.""" + target = ROOT / path + source = target.read_text(encoding="utf-8") + count = source.count(old) + if count != 1: + raise SystemExit(f"{path}: expected one fragment, found {count}") + target.write_text(source.replace(old, new, 1), encoding="utf-8") + + +def apply_red() -> None: + """Add a regression that the raw-prose implementation must fail.""" + replace_once( + "tests/test_organization_commercial_readiness_loop_policy.py", + """ for changed in mutations: + assert not is_manual_product_entrypoint(workflow(content=changed)) + + +def test_repository_eligibility_is_owned_and_write_capable() -> None: +""", + """ for changed in mutations: + assert not is_manual_product_entrypoint(workflow(content=changed)) + + +def test_ddd_contract_rejects_unbound_unused_yaml_prose() -> None: + \"\"\"DDD words in an unused scalar are not an agent instruction contract.\"\"\" + safe = manual_workflow() + assert safe.content is not None + unused = safe.content.replace("prompt: |\\n", "notes: |\\n", 1) + assert not is_manual_product_entrypoint(workflow(content=unused)) + + +def test_repository_eligibility_is_owned_and_write_capable() -> None: +""", + ) + + +def apply_green() -> None: + """Install the scoped capability and invocation-binding contract.""" + replace_once( + "scripts/ci/organization_commercial_readiness_loop.py", + """import re +import subprocess +import sys +from pathlib import Path +""", + """import re +import subprocess +import sys +import textwrap +from pathlib import Path +""", + ) + replace_once( + "scripts/ci/organization_commercial_readiness_loop.py", + """ENTRYPOINT_MARKER = "# cwl-org-commercial-entrypoint: v1" +DDD_ENTRYPOINT_MARKER = "# cwl-ddd-architecture-audit: required" +DDD_CONTRACT_TERMS = ( + "Domain-Driven Design", + "core, supporting, and generic subdomains", + "Bounded Context", + "Context Map", + "Ubiquitous Language", + "Aggregate", + "Entity", + "Value Object", + "Domain Service", + "Repository", + "Domain Event", + "Invariant", + "Anti-Corruption Layer", + "Shared Kernel", + "directory paths", + "docs/product-technical-gap-baseline.md", +) +CENTRAL_REPOSITORY = f"{DEFAULT_ORGANIZATION}/.github" +""", + """ENTRYPOINT_MARKER = "# cwl-org-commercial-entrypoint: v1" +DDD_ENTRYPOINT_MARKER = "# cwl-ddd-architecture-audit: v1" +DDD_PROMPT_BINDING_MARKER = "# cwl-ddd-prompt-binding: v1" +DDD_PROMPT_ENVIRONMENT = "CWL_PRODUCT_AGENT_PROMPT" +DDD_CAPABILITY_ENVIRONMENT = "CWL_DDD_CONTRACT_CAPABILITIES" +DDD_PROMPT_BINDING = f"--prompt-env {DDD_PROMPT_ENVIRONMENT}" +DDD_CAPABILITY_BINDING = ( + f"--architecture-contract-env {DDD_CAPABILITY_ENVIRONMENT}" +) +DDD_CONTRACT_CAPABILITIES = frozenset( + { + "aggregate", + "anti_corruption_layer", + "bounded_context", + "context_map", + "directory_ownership", + "domain_event", + "domain_service", + "entity", + "invariant", + "minimal_shared_kernel", + "product_gap_baseline", + "repository", + "subdomain_classification", + "ubiquitous_language", + "value_object", + } +) +DDD_CAPABILITY_TOKEN_RE = re.compile(r"[a-z][a-z0-9_]*") +CENTRAL_REPOSITORY = f"{DEFAULT_ORGANIZATION}/.github" +""", + ) + replace_once( + "scripts/ci/organization_commercial_readiness_loop.py", + """def has_domain_driven_development_contract(source: str) -> bool: + \"\"\"Return whether one entrypoint accepts the complete DDD repair contract.\"\"\" + return DDD_ENTRYPOINT_MARKER in source and all( + term in source for term in DDD_CONTRACT_TERMS + ) + + +def is_manual_product_entrypoint(workflow: WorkflowRecord) -> bool: +""", + """def _root_mapping_regions(source: str, key: str) -> tuple[str, ...]: + \"\"\"Return top-level YAML mapping bodies for one exact key.\"\"\" + lines = source.splitlines() + regions: list[str] = [] + for index, line in enumerate(lines): + if line != f"{key}:": + continue + body: list[str] = [] + for candidate in lines[index + 1 :]: + if candidate.strip() and not candidate.startswith(" "): + break + body.append(candidate) + regions.append("\\n".join(body)) + return tuple(regions) + + +def _yaml_block_scalars(source: str, key: str) -> tuple[str, ...]: + \"\"\"Return dedented YAML literal or folded block scalars for one key.\"\"\" + header = re.compile( + rf"^(?P *){re.escape(key)}: *[>|][+-]? *$" + ) + lines = source.splitlines() + blocks: list[str] = [] + for index, line in enumerate(lines): + match = header.fullmatch(line) + if match is None: + continue + base_indent = len(match.group("indent")) + body: list[str] = [] + for candidate in lines[index + 1 :]: + if candidate.strip(): + candidate_indent = len(candidate) - len(candidate.lstrip(" ")) + if candidate_indent <= base_indent: + break + body.append(candidate) + blocks.append(textwrap.dedent("\\n".join(body)).strip("\\n")) + return tuple(blocks) + + +def _shell_commands(block: str) -> tuple[str, ...]: + \"\"\"Return non-comment shell commands with continuations joined.\"\"\" + commands: list[str] = [] + fragments: list[str] = [] + for line in block.splitlines(): + fragment = line.strip() + if not fragment or fragment.startswith("#"): + continue + continued = fragment.endswith("\\") + fragments.append(fragment.removesuffix("\\").rstrip()) + if continued: + continue + commands.append(" ".join(fragments)) + fragments = [] + if fragments: + commands.append(" ".join(fragments)) + return tuple(commands) + + +def _has_bound_ddd_agent_invocation(source: str) -> bool: + \"\"\"Return whether one executable command receives both contract inputs.\"\"\" + for run_block in _yaml_block_scalars(source, "run"): + if DDD_PROMPT_BINDING_MARKER not in run_block: + continue + for command in _shell_commands(run_block): + if DDD_PROMPT_BINDING in command and DDD_CAPABILITY_BINDING in command: + return True + return False + + +def has_domain_driven_development_contract(source: str) -> bool: + \"\"\"Return whether one entrypoint binds a scoped versioned DDD contract.\"\"\" + if DDD_ENTRYPOINT_MARKER not in source: + return False + root_environments = _root_mapping_regions(source, "env") + if len(root_environments) != 1: + return False + environment = root_environments[0] + capability_blocks = _yaml_block_scalars( + environment, DDD_CAPABILITY_ENVIRONMENT + ) + prompt_blocks = _yaml_block_scalars(environment, DDD_PROMPT_ENVIRONMENT) + if len(capability_blocks) != 1 or len(prompt_blocks) != 1: + return False + if not prompt_blocks[0].strip(): + return False + declared_capabilities = frozenset( + DDD_CAPABILITY_TOKEN_RE.findall(capability_blocks[0]) + ) + if not DDD_CONTRACT_CAPABILITIES.issubset(declared_capabilities): + return False + return _has_bound_ddd_agent_invocation(source) + + +def is_manual_product_entrypoint(workflow: WorkflowRecord) -> bool: +""", + ) + replace_once( + "organization_commercial_readiness_fixtures.py", + """ content=( + "# cwl-org-commercial-entrypoint: v1\\n" + "# cwl-ddd-architecture-audit: required\\n" + "on:\\n workflow_dispatch:\\n" + "concurrency:\\n group: product-development\\n" + "permissions:\\n contents: write\\n" + "NVIDIA_API_KEY: ${{ secrets.NVIDIA_NIM_API_KEY }}\\n" + "prompt: |\\n" + " Apply Domain-Driven Design before and during every increment.\\n" + " Classify core, supporting, and generic subdomains; define each Bounded Context, Context Map, and Ubiquitous Language.\\n" + " Keep Aggregate, Entity, Value Object, Domain Service, Repository, Domain Event, and Invariant names aligned across code, API, database, and tests.\\n" + " Isolate external systems behind an Anti-Corruption Layer and keep the Shared Kernel minimal.\\n" + " Audit and correct misleading directory paths with imports, packaging, callers, tests, and architecture documents in the same bounded change.\\n" + " Update docs/product-technical-gap-baseline.md with detected and repaired architecture drift.\\n" + ), +""", + """ content=( + "# cwl-org-commercial-entrypoint: v1\\n" + "# cwl-ddd-architecture-audit: v1\\n" + "on:\\n workflow_dispatch:\\n" + "concurrency:\\n group: product-development\\n" + "permissions:\\n contents: write\\n" + "env:\\n" + " NVIDIA_API_KEY: ${{ secrets.NVIDIA_NIM_API_KEY }}\\n" + " CWL_DDD_CONTRACT_CAPABILITIES: >-\\n" + " aggregate anti_corruption_layer bounded_context context_map\\n" + " directory_ownership domain_event domain_service entity invariant\\n" + " minimal_shared_kernel product_gap_baseline repository\\n" + " subdomain_classification ubiquitous_language value_object\\n" + " CWL_PRODUCT_AGENT_PROMPT: |\\n" + " Deliver one buyer-visible increment through the repository-owned product agent.\\n" + "\\n" + " Keep implementation, tests, documentation, and package boundaries coherent.\\n" + "jobs:\\n" + " develop:\\n" + " runs-on: ubuntu-24.04\\n" + " steps:\\n" + " - name: Invoke the product agent\\n" + " run: |\\n" + " # cwl-ddd-prompt-binding: v1\\n" + " python scripts/automation/commercial_product_development.py \\\\\\n" + " --prompt-env CWL_PRODUCT_AGENT_PROMPT \\\\\\n" + " --architecture-contract-env CWL_DDD_CONTRACT_CAPABILITIES\\n" + ), +""", + ) + replace_once( + "tests/test_organization_commercial_readiness_loop_policy.py", + """ ActionKind, + ActionResult, + DDD_CONTRACT_TERMS, + RunRecord, +""", + """ ActionKind, + ActionResult, + DDD_CONTRACT_CAPABILITIES, + RunRecord, +""", + ) + replace_once( + "tests/test_organization_commercial_readiness_loop_policy.py", + """ is_dedicated_writer_workflow, + is_live_writer_run, + is_manual_product_entrypoint, +""", + """ has_domain_driven_development_contract, + is_dedicated_writer_workflow, + is_live_writer_run, + is_manual_product_entrypoint, +""", + ) + replace_once( + "tests/test_organization_commercial_readiness_loop_policy.py", + """def test_product_entrypoint_requires_manual_nvidia_and_ddd_opt_in() -> None: + \"\"\"Product dispatch requires a manual credential-isolated DDD contract.\"\"\" + safe = manual_workflow() + assert is_manual_product_entrypoint(safe) + assert not is_manual_product_entrypoint(workflow(state="disabled_manually", content="x")) + assert not is_manual_product_entrypoint(workflow(content=None)) + mutations = [ + (safe.content or "") + 'schedule:\\n - cron: "1 * * * *"\\n', + (safe.content or "") + "COPILOT_GITHUB_TOKEN: forbidden\\n", + (safe.content or "").replace("# cwl-org-commercial-entrypoint: v1\\n", ""), + (safe.content or "").replace( + "# cwl-ddd-architecture-audit: required\\n", "" + ), + (safe.content or "").replace("concurrency:\\n", ""), + ] + mutations.extend( + (safe.content or "").replace(term, f"missing-{index}", 1) + for index, term in enumerate(DDD_CONTRACT_TERMS) + ) + for changed in mutations: + assert not is_manual_product_entrypoint(workflow(content=changed)) + + +def test_ddd_contract_rejects_unbound_unused_yaml_prose() -> None: + \"\"\"DDD words in an unused scalar are not an agent instruction contract.\"\"\" + safe = manual_workflow() + assert safe.content is not None + unused = safe.content.replace("prompt: |\\n", "notes: |\\n", 1) + assert not is_manual_product_entrypoint(workflow(content=unused)) +""", + """def test_product_entrypoint_requires_manual_nvidia_and_bound_ddd_opt_in() -> None: + \"\"\"Product dispatch requires a scoped capability set bound to one command.\"\"\" + safe = manual_workflow() + assert safe.content is not None + source = safe.content + assert "Domain-Driven Design" not in source + assert is_manual_product_entrypoint(safe) + assert has_domain_driven_development_contract(source) + + ordinary_rejections = [ + source + 'schedule:\\n - cron: "1 * * * *"\\n', + source + "COPILOT_GITHUB_TOKEN: forbidden\\n", + source.replace("# cwl-org-commercial-entrypoint: v1\\n", "", 1), + source.replace("# cwl-ddd-architecture-audit: v1\\n", "", 1), + source.replace("concurrency:\\n", "", 1), + ] + for changed in ordinary_rejections: + assert not is_manual_product_entrypoint(workflow(content=changed)) + + for index, capability in enumerate(sorted(DDD_CONTRACT_CAPABILITIES)): + changed = source.replace(capability, f"omitted_{index}", 1) + assert not has_domain_driven_development_contract(changed) + + +def test_ddd_contract_rejects_comments_unused_scopes_and_split_bindings() -> None: + \"\"\"Comments, unscoped values, and separate commands cannot fake a binding.\"\"\" + safe = manual_workflow() + assert safe.content is not None + source = safe.content + comment_only = ( + "# cwl-ddd-architecture-audit: v1\\n" + + "\\n".join(f"# {item}" for item in sorted(DDD_CONTRACT_CAPABILITIES)) + + "\\n# cwl-ddd-prompt-binding: v1\\n" + + "# --prompt-env CWL_PRODUCT_AGENT_PROMPT\\n" + + "# --architecture-contract-env CWL_DDD_CONTRACT_CAPABILITIES\\n" + ) + assert not has_domain_driven_development_contract(comment_only) + + invalid_sources = [ + source.replace("env:\\n", "metadata:\\n", 1), + source.replace("CWL_PRODUCT_AGENT_PROMPT: |", "UNUSED_PROMPT: |", 1), + source.replace( + "CWL_DDD_CONTRACT_CAPABILITIES: >-", "UNUSED_CAPABILITIES: >-", 1 + ), + source.replace( + " Deliver one buyer-visible increment through the repository-owned product agent.\\n", + "", + 1, + ), + source.replace("# cwl-ddd-prompt-binding: v1", "# unbound", 1), + source.replace( + "--prompt-env CWL_PRODUCT_AGENT_PROMPT", "--prompt-env UNUSED_PROMPT", 1 + ), + source.replace( + "--architecture-contract-env CWL_DDD_CONTRACT_CAPABILITIES", + "--architecture-contract-env UNUSED_CAPABILITIES", + 1, + ), + source + "env:\\n OTHER_VALUE: present\\n", + source.replace( + " CWL_PRODUCT_AGENT_PROMPT: |", + " CWL_PRODUCT_AGENT_PROMPT: |\\n" + " duplicate\\n" + " CWL_PRODUCT_AGENT_PROMPT: |", + 1, + ), + source.replace( + " CWL_DDD_CONTRACT_CAPABILITIES: >-", + " CWL_DDD_CONTRACT_CAPABILITIES: >-\\n" + " bounded_context\\n" + " CWL_DDD_CONTRACT_CAPABILITIES: >-", + 1, + ), + ] + for changed in invalid_sources: + assert not has_domain_driven_development_contract(changed) + + bound_run = ( + " run: |\\n" + " # cwl-ddd-prompt-binding: v1\\n" + " python scripts/automation/commercial_product_development.py \\\\n" + " --prompt-env CWL_PRODUCT_AGENT_PROMPT \\\\n" + " --architecture-contract-env CWL_DDD_CONTRACT_CAPABILITIES\\n" + ) + split_run = ( + " run: |\\n" + " # cwl-ddd-prompt-binding: v1\\n" + " product-agent --prompt-env CWL_PRODUCT_AGENT_PROMPT\\n" + " product-agent --architecture-contract-env CWL_DDD_CONTRACT_CAPABILITIES\\n" + ) + assert not has_domain_driven_development_contract( + source.replace(bound_run, split_run, 1) + ) + + dangling_run = ( + " run: |\\n" + " # cwl-ddd-prompt-binding: v1\\n" + " product-agent \\\\n" + ) + assert not has_domain_driven_development_contract( + source.replace(bound_run, dangling_run, 1) + ) +""", + ) + replace_once( + "tests/test_organization_commercial_readiness_loop_policy.py", + """ assert "# cwl-ddd-architecture-audit: required" in doctoring +""", + """ assert "# cwl-ddd-architecture-audit: v1" in doctoring + assert "--architecture-contract-env CWL_DDD_CONTRACT_CAPABILITIES" in doctoring +""", + ) + + doctoring_path = ROOT / "docs/doctoring/organization-commercial-readiness-loop.md" + doctoring = doctoring_path.read_text(encoding="utf-8") + start = doctoring.index("## Product-development boundary") + end = doctoring.index("## Failure, evidence, and operations") + product_section = """## Product-development boundary + +Product development is dispatched only when a repository has zero open pull requests and exposes one active, manual-only, explicitly marked workflow. The DDD enrollment is versioned and its values must live in the root workflow `env` mapping so every job receives the same contract: + +```yaml +# cwl-org-commercial-entrypoint: v1 +# cwl-ddd-architecture-audit: v1 +on: + workflow_dispatch: + +env: + CWL_DDD_CONTRACT_CAPABILITIES: >- + aggregate anti_corruption_layer bounded_context context_map + directory_ownership domain_event domain_service entity invariant + minimal_shared_kernel product_gap_baseline repository + subdomain_classification ubiquitous_language value_object + CWL_PRODUCT_AGENT_PROMPT: | + Deliver one buyer-visible increment through the repository-owned product agent. + +jobs: + develop: + steps: + - run: | + # cwl-ddd-prompt-binding: v1 + product-agent \ + --prompt-env CWL_PRODUCT_AGENT_PROMPT \ + --architecture-contract-env CWL_DDD_CONTRACT_CAPABILITIES +``` + +The entrypoint must also contain an explicit `concurrency` contract, use `NVIDIA_NIM_API_KEY`, omit `COPILOT_GITHUB_TOKEN`, have no schedule of its own, and carry a commercial/product-development identity. Human-readable prompt wording is repository-owned and may use any language. Eligibility depends on stable capability identifiers rather than copied English prose. The prompt and capability environment names must be passed to the same non-comment shell command under the binding marker; comments, unrelated YAML, unscoped step values, separate commands, or unused block scalars do not satisfy the contract. + +The capability contract covers strategic and tactical Domain-Driven Design: subdomain classification, Bounded Context, Context Map, Ubiquitous Language, Aggregate, Entity, Value Object, Domain Service, Repository, Domain Event, Invariant, Anti-Corruption Layer, minimal Shared Kernel, directory ownership, and product-gap baseline traceability. + +Each hourly product increment must identify the owning product responsibility before selecting a repository, then compare the live directory tree, module/package names, API, database objects, tests, and documentation with that responsibility. Misleading directory paths, generic `utils`/`common` dumping grounds that own domain behavior, infrastructure imports inside the domain model, cross-context database access, obsolete product names, or customer-visible implementation boundaries are architecture defects, not cosmetic debt. When one can be corrected safely in the bounded increment, the agent moves the code and updates imports, package manifests, call sites, migrations, tests, ADRs, diagrams, and compatibility adapters in the same pull request. + +The contract does not impose one universal folder template. A move is justified by domain ownership and dependency direction, not by directory aesthetics. Aggregate boundaries remain the smallest consistency boundary; external and legacy systems are isolated behind an Anti-Corruption Layer; the Shared Kernel remains minimal; and cross-context integration uses explicit versioned contracts. If a coherent move exceeds the current pull request's safe scope, the agent must record the exact owner, callers, target context, migration sequence, and acceptance evidence in `docs/product-technical-gap-baseline.md` and select it as the next bounded architecture increment rather than silently leaving the drift unresolved. + +This opt-in prevents the central coordinator from guessing that an unrelated manual workflow can safely modify product source. Repositories with an existing hourly or more frequent dedicated writer keep their own lease and are never double-dispatched; those schedules may share the same DDD contract and should adopt it without adding another cron. + +The repository-local entrypoint remains responsible for implementing the two environment-name flags in its product-agent adapter, bounded editable paths, tests, 100% production statement and branch coverage, public docstrings, package and security verification, exact-head publication, and pull-request creation. A missing compliant entrypoint is a deliberate no-op, not permission to inject a generic writer into that repository. + +""" + doctoring_path.write_text( + doctoring[:start] + product_section + doctoring[end:], encoding="utf-8" + ) + + replace_once( + "docs/product-technical-gap-baseline.md", + """- **조치:** 수동 제품개발 진입점에 `# cwl-ddd-architecture-audit: required`와 전략·전술 DDD 용어, directory-path repair, `docs/product-technical-gap-baseline.md` 갱신을 요구한다. 기존 전용 예약은 writer lease를 유지해 중복 실행하지 않는다. +""", + """- **조치:** 수동 제품개발 진입점에 `# cwl-ddd-architecture-audit: v1`, root `env`의 versioned capability ID 집합, 자유 형식 agent prompt, 동일 실행 명령의 `--prompt-env CWL_PRODUCT_AGENT_PROMPT`·`--architecture-contract-env CWL_DDD_CONTRACT_CAPABILITIES` binding을 요구한다. 주석·무관 YAML·분리 명령은 계약으로 인정하지 않으며, 기존 전용 예약은 writer lease를 유지해 중복 실행하지 않는다. +""", + ) + replace_once( + "docs/product-technical-gap-baseline.md", + """- **완료 증거:** exact-head focused policy tests, statement/branch coverage 100%, Python docstring 100%, workflow security checks, independent review, protected merge. 병합 전 상태는 구현 중이며 운영 완료로 간주하지 않는다. +""", + """- **리뷰 보강:** raw YAML 전체의 단어 존재 검사를 제거하고 root environment scope와 실제 product-agent command binding을 검증한다. 설명문은 특정 영어 문구에 종속되지 않는다. +- **완료 증거:** exact-head focused policy tests, statement/branch coverage 100%, Python docstring 100%, workflow security checks, independent review, protected merge. 병합 전 상태는 구현 중이며 운영 완료로 간주하지 않는다. +""", + ) + replace_once( + "CHANGELOG.md", + """- Restore the hourly organization commercial-readiness coordinator when the dedicated maintainer secret is absent by exchanging the protected scheduled job's OIDC identity for a short-lived OpenCode App installation token; retain bounded network calls, token masking, and fail-closed parsing. Require every centrally dispatched product-development entrypoint to accept a machine-checked Domain-Driven Design contract, continuously repairing misleading directory ownership and recording larger bounded-context migrations in `docs/product-technical-gap-baseline.md` without duplicating repository-owned schedules. +""", + """- Restore the hourly organization commercial-readiness coordinator when the dedicated maintainer secret is absent by exchanging the protected scheduled job's OIDC identity for a short-lived OpenCode App installation token; retain bounded network calls, token masking, and fail-closed parsing. Require every centrally dispatched product-development entrypoint to bind a versioned machine-readable Domain-Driven Design capability set and repository-owned prompt to the same product-agent command, rejecting comments or unused YAML while continuously repairing misleading directory ownership and recording larger bounded-context migrations in `docs/product-technical-gap-baseline.md` without duplicating repository-owned schedules. +""", + ) + + +def main() -> None: + """Run the selected TDD phase.""" + parser = argparse.ArgumentParser() + parser.add_argument("phase", choices=("red", "green")) + args = parser.parse_args() + if args.phase == "red": + apply_red() + else: + apply_green() + + +if __name__ == "__main__": + main() From c765e6fcc15a6e8e8756765982b729ab79894367 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 1 Sep 2026 13:19:36 +0900 Subject: [PATCH 15/24] chore(ci): remove reintroduced temporary DDD source-fix workflow --- .../workflows/tmp-ddd-binding-review-fix.yml | 142 ------------------ 1 file changed, 142 deletions(-) delete mode 100644 .github/workflows/tmp-ddd-binding-review-fix.yml diff --git a/.github/workflows/tmp-ddd-binding-review-fix.yml b/.github/workflows/tmp-ddd-binding-review-fix.yml deleted file mode 100644 index 2afbf8b366..0000000000 --- a/.github/workflows/tmp-ddd-binding-review-fix.yml +++ /dev/null @@ -1,142 +0,0 @@ -name: Temporary DDD Prompt Binding Review Fix - -on: - push: - branches: - - fix/hourly-ddd-development-contract-20260901 - paths: - - .github/workflows/tmp-ddd-binding-review-fix.yml - - scripts/ci/tmp_apply_ddd_binding_review_fix.py - -concurrency: - group: tmp-ddd-binding-review-fix - cancel-in-progress: false - -permissions: - contents: write - -jobs: - repair: - if: github.repository == 'ContextualWisdomLab/.github' - runs-on: ubuntu-24.04 - timeout-minutes: 20 - steps: - - name: Check out exact review-fix branch - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - ref: fix/hourly-ddd-development-contract-20260901 - fetch-depth: 1 - persist-credentials: true - - - name: Set up Python - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 - with: - python-version: "3.14" - - - name: Normalize bootstrap escape generation - shell: bash --noprofile --norc -e -o pipefail {0} - run: | - python - <<'PY' - from pathlib import Path - - path = Path("scripts/ci/tmp_apply_ddd_binding_review_fix.py") - source = path.read_text(encoding="utf-8") - replacements = ( - ( - r' continued = fragment.endswith("\\")', - r' continued = fragment.endswith("\\\\")', - ), - ( - r' fragments.append(fragment.removesuffix("\\").rstrip())', - r' fragments.append(fragment.removesuffix("\\\\").rstrip())', - ), - ) - for old, new in replacements: - count = source.count(old) - if count != 1: - raise SystemExit( - f"bootstrap escape normalization expected one fragment, found {count}: {old!r}" - ) - source = source.replace(old, new, 1) - path.write_text(source, encoding="utf-8") - PY - python -W error -m py_compile scripts/ci/tmp_apply_ddd_binding_review_fix.py - - - name: Install exact focused-test dependencies - env: - PIP_DISABLE_PIP_VERSION_CHECK: "1" - PIP_NO_INPUT: "1" - shell: bash --noprofile --norc -e -o pipefail {0} - run: | - cat >"${RUNNER_TEMP}/requirements.txt" <<'EOF' - coverage==7.15.2 --hash=sha256:b9a6367e4aff723e8ee8190836836124284e8fcd4265e307c844010cfa074f3f - iniconfig==2.1.0 --hash=sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760 - packaging==26.2 --hash=sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e - pluggy==1.6.0 --hash=sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746 - pygments==2.20.0 --hash=sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176 - pytest==9.1.1 --hash=sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c - EOF - python -m pip install --only-binary=:all: --require-hashes -r "${RUNNER_TEMP}/requirements.txt" - - - name: RED — prove unused YAML prose is currently accepted - shell: bash --noprofile --norc -e -o pipefail {0} - run: | - set -euo pipefail - python -W error scripts/ci/tmp_apply_ddd_binding_review_fix.py red - set +e - red_output="$( - python -m pytest --import-mode=importlib \ - tests/test_organization_commercial_readiness_loop_policy.py \ - -k ddd_contract_rejects_unbound_unused_yaml_prose -q 2>&1 - )" - red_status=$? - set -e - printf '%s\n' "$red_output" - if [ "$red_status" -eq 0 ]; then - echo '::error::RED regression unexpectedly passed before production repair.' - exit 1 - fi - grep -F 'AssertionError' <<<"$red_output" >/dev/null - - - name: GREEN — bind scoped capabilities to one product-agent command - shell: bash --noprofile --norc -e -o pipefail {0} - run: python -W error scripts/ci/tmp_apply_ddd_binding_review_fix.py green - - - name: Verify exact policy coverage and source hygiene - shell: bash --noprofile --norc -e -o pipefail {0} - run: | - python -m coverage run --branch -m pytest --import-mode=importlib \ - tests/test_organization_commercial_readiness_loop*.py -q - python -m coverage report \ - --include='scripts/ci/organization_commercial_readiness_loop.py' \ - --show-missing \ - --fail-under=100 - python -m compileall -q \ - scripts/ci/organization_commercial_readiness_loop.py \ - organization_commercial_readiness_fixtures.py \ - tests/test_organization_commercial_readiness_loop*.py - git diff --check - grep -F '# cwl-ddd-architecture-audit: v1' organization_commercial_readiness_fixtures.py >/dev/null - grep -F -- '--architecture-contract-env CWL_DDD_CONTRACT_CAPABILITIES' organization_commercial_readiness_fixtures.py >/dev/null - - - name: Publish verified non-workflow review remediation - shell: bash --noprofile --norc -e -o pipefail {0} - run: | - set -euo pipefail - git restore --source=HEAD -- scripts/ci/tmp_apply_ddd_binding_review_fix.py - git add \ - CHANGELOG.md \ - docs/doctoring/organization-commercial-readiness-loop.md \ - docs/product-technical-gap-baseline.md \ - organization_commercial_readiness_fixtures.py \ - scripts/ci/organization_commercial_readiness_loop.py \ - tests/test_organization_commercial_readiness_loop_policy.py - test -n "$(git diff --cached --name-only)" - if git diff --cached --name-only | grep -q '^\.github/workflows/'; then - echo '::error::Workflow paths must be published only through the authorized connector.' - exit 1 - fi - git config user.name 'github-actions[bot]' - git config user.email '41898282+github-actions[bot]@users.noreply.github.com' - git commit -m 'fix(automation): bind DDD contract to product-agent invocation' - git push origin HEAD:fix/hourly-ddd-development-contract-20260901 From 69685376ecb5068a0bdbcee2888e82395312bd18 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 1 Sep 2026 13:19:41 +0900 Subject: [PATCH 16/24] chore(ci): remove reintroduced temporary DDD source-fix helper --- .../ci/tmp_apply_ddd_binding_review_fix.py | 532 ------------------ 1 file changed, 532 deletions(-) delete mode 100644 scripts/ci/tmp_apply_ddd_binding_review_fix.py diff --git a/scripts/ci/tmp_apply_ddd_binding_review_fix.py b/scripts/ci/tmp_apply_ddd_binding_review_fix.py deleted file mode 100644 index f6bfe1ea53..0000000000 --- a/scripts/ci/tmp_apply_ddd_binding_review_fix.py +++ /dev/null @@ -1,532 +0,0 @@ -#!/usr/bin/env python3 -"""Apply RED then GREEN review remediation for the hourly DDD entrypoint contract.""" - -from __future__ import annotations - -import argparse -from pathlib import Path - -ROOT = Path(__file__).resolve().parents[2] - - -def replace_once(path: str, old: str, new: str) -> None: - """Replace one exact fragment or fail closed on branch drift.""" - target = ROOT / path - source = target.read_text(encoding="utf-8") - count = source.count(old) - if count != 1: - raise SystemExit(f"{path}: expected one fragment, found {count}") - target.write_text(source.replace(old, new, 1), encoding="utf-8") - - -def apply_red() -> None: - """Add a regression that the raw-prose implementation must fail.""" - replace_once( - "tests/test_organization_commercial_readiness_loop_policy.py", - """ for changed in mutations: - assert not is_manual_product_entrypoint(workflow(content=changed)) - - -def test_repository_eligibility_is_owned_and_write_capable() -> None: -""", - """ for changed in mutations: - assert not is_manual_product_entrypoint(workflow(content=changed)) - - -def test_ddd_contract_rejects_unbound_unused_yaml_prose() -> None: - \"\"\"DDD words in an unused scalar are not an agent instruction contract.\"\"\" - safe = manual_workflow() - assert safe.content is not None - unused = safe.content.replace("prompt: |\\n", "notes: |\\n", 1) - assert not is_manual_product_entrypoint(workflow(content=unused)) - - -def test_repository_eligibility_is_owned_and_write_capable() -> None: -""", - ) - - -def apply_green() -> None: - """Install the scoped capability and invocation-binding contract.""" - replace_once( - "scripts/ci/organization_commercial_readiness_loop.py", - """import re -import subprocess -import sys -from pathlib import Path -""", - """import re -import subprocess -import sys -import textwrap -from pathlib import Path -""", - ) - replace_once( - "scripts/ci/organization_commercial_readiness_loop.py", - """ENTRYPOINT_MARKER = "# cwl-org-commercial-entrypoint: v1" -DDD_ENTRYPOINT_MARKER = "# cwl-ddd-architecture-audit: required" -DDD_CONTRACT_TERMS = ( - "Domain-Driven Design", - "core, supporting, and generic subdomains", - "Bounded Context", - "Context Map", - "Ubiquitous Language", - "Aggregate", - "Entity", - "Value Object", - "Domain Service", - "Repository", - "Domain Event", - "Invariant", - "Anti-Corruption Layer", - "Shared Kernel", - "directory paths", - "docs/product-technical-gap-baseline.md", -) -CENTRAL_REPOSITORY = f"{DEFAULT_ORGANIZATION}/.github" -""", - """ENTRYPOINT_MARKER = "# cwl-org-commercial-entrypoint: v1" -DDD_ENTRYPOINT_MARKER = "# cwl-ddd-architecture-audit: v1" -DDD_PROMPT_BINDING_MARKER = "# cwl-ddd-prompt-binding: v1" -DDD_PROMPT_ENVIRONMENT = "CWL_PRODUCT_AGENT_PROMPT" -DDD_CAPABILITY_ENVIRONMENT = "CWL_DDD_CONTRACT_CAPABILITIES" -DDD_PROMPT_BINDING = f"--prompt-env {DDD_PROMPT_ENVIRONMENT}" -DDD_CAPABILITY_BINDING = ( - f"--architecture-contract-env {DDD_CAPABILITY_ENVIRONMENT}" -) -DDD_CONTRACT_CAPABILITIES = frozenset( - { - "aggregate", - "anti_corruption_layer", - "bounded_context", - "context_map", - "directory_ownership", - "domain_event", - "domain_service", - "entity", - "invariant", - "minimal_shared_kernel", - "product_gap_baseline", - "repository", - "subdomain_classification", - "ubiquitous_language", - "value_object", - } -) -DDD_CAPABILITY_TOKEN_RE = re.compile(r"[a-z][a-z0-9_]*") -CENTRAL_REPOSITORY = f"{DEFAULT_ORGANIZATION}/.github" -""", - ) - replace_once( - "scripts/ci/organization_commercial_readiness_loop.py", - """def has_domain_driven_development_contract(source: str) -> bool: - \"\"\"Return whether one entrypoint accepts the complete DDD repair contract.\"\"\" - return DDD_ENTRYPOINT_MARKER in source and all( - term in source for term in DDD_CONTRACT_TERMS - ) - - -def is_manual_product_entrypoint(workflow: WorkflowRecord) -> bool: -""", - """def _root_mapping_regions(source: str, key: str) -> tuple[str, ...]: - \"\"\"Return top-level YAML mapping bodies for one exact key.\"\"\" - lines = source.splitlines() - regions: list[str] = [] - for index, line in enumerate(lines): - if line != f"{key}:": - continue - body: list[str] = [] - for candidate in lines[index + 1 :]: - if candidate.strip() and not candidate.startswith(" "): - break - body.append(candidate) - regions.append("\\n".join(body)) - return tuple(regions) - - -def _yaml_block_scalars(source: str, key: str) -> tuple[str, ...]: - \"\"\"Return dedented YAML literal or folded block scalars for one key.\"\"\" - header = re.compile( - rf"^(?P *){re.escape(key)}: *[>|][+-]? *$" - ) - lines = source.splitlines() - blocks: list[str] = [] - for index, line in enumerate(lines): - match = header.fullmatch(line) - if match is None: - continue - base_indent = len(match.group("indent")) - body: list[str] = [] - for candidate in lines[index + 1 :]: - if candidate.strip(): - candidate_indent = len(candidate) - len(candidate.lstrip(" ")) - if candidate_indent <= base_indent: - break - body.append(candidate) - blocks.append(textwrap.dedent("\\n".join(body)).strip("\\n")) - return tuple(blocks) - - -def _shell_commands(block: str) -> tuple[str, ...]: - \"\"\"Return non-comment shell commands with continuations joined.\"\"\" - commands: list[str] = [] - fragments: list[str] = [] - for line in block.splitlines(): - fragment = line.strip() - if not fragment or fragment.startswith("#"): - continue - continued = fragment.endswith("\\") - fragments.append(fragment.removesuffix("\\").rstrip()) - if continued: - continue - commands.append(" ".join(fragments)) - fragments = [] - if fragments: - commands.append(" ".join(fragments)) - return tuple(commands) - - -def _has_bound_ddd_agent_invocation(source: str) -> bool: - \"\"\"Return whether one executable command receives both contract inputs.\"\"\" - for run_block in _yaml_block_scalars(source, "run"): - if DDD_PROMPT_BINDING_MARKER not in run_block: - continue - for command in _shell_commands(run_block): - if DDD_PROMPT_BINDING in command and DDD_CAPABILITY_BINDING in command: - return True - return False - - -def has_domain_driven_development_contract(source: str) -> bool: - \"\"\"Return whether one entrypoint binds a scoped versioned DDD contract.\"\"\" - if DDD_ENTRYPOINT_MARKER not in source: - return False - root_environments = _root_mapping_regions(source, "env") - if len(root_environments) != 1: - return False - environment = root_environments[0] - capability_blocks = _yaml_block_scalars( - environment, DDD_CAPABILITY_ENVIRONMENT - ) - prompt_blocks = _yaml_block_scalars(environment, DDD_PROMPT_ENVIRONMENT) - if len(capability_blocks) != 1 or len(prompt_blocks) != 1: - return False - if not prompt_blocks[0].strip(): - return False - declared_capabilities = frozenset( - DDD_CAPABILITY_TOKEN_RE.findall(capability_blocks[0]) - ) - if not DDD_CONTRACT_CAPABILITIES.issubset(declared_capabilities): - return False - return _has_bound_ddd_agent_invocation(source) - - -def is_manual_product_entrypoint(workflow: WorkflowRecord) -> bool: -""", - ) - replace_once( - "organization_commercial_readiness_fixtures.py", - """ content=( - "# cwl-org-commercial-entrypoint: v1\\n" - "# cwl-ddd-architecture-audit: required\\n" - "on:\\n workflow_dispatch:\\n" - "concurrency:\\n group: product-development\\n" - "permissions:\\n contents: write\\n" - "NVIDIA_API_KEY: ${{ secrets.NVIDIA_NIM_API_KEY }}\\n" - "prompt: |\\n" - " Apply Domain-Driven Design before and during every increment.\\n" - " Classify core, supporting, and generic subdomains; define each Bounded Context, Context Map, and Ubiquitous Language.\\n" - " Keep Aggregate, Entity, Value Object, Domain Service, Repository, Domain Event, and Invariant names aligned across code, API, database, and tests.\\n" - " Isolate external systems behind an Anti-Corruption Layer and keep the Shared Kernel minimal.\\n" - " Audit and correct misleading directory paths with imports, packaging, callers, tests, and architecture documents in the same bounded change.\\n" - " Update docs/product-technical-gap-baseline.md with detected and repaired architecture drift.\\n" - ), -""", - """ content=( - "# cwl-org-commercial-entrypoint: v1\\n" - "# cwl-ddd-architecture-audit: v1\\n" - "on:\\n workflow_dispatch:\\n" - "concurrency:\\n group: product-development\\n" - "permissions:\\n contents: write\\n" - "env:\\n" - " NVIDIA_API_KEY: ${{ secrets.NVIDIA_NIM_API_KEY }}\\n" - " CWL_DDD_CONTRACT_CAPABILITIES: >-\\n" - " aggregate anti_corruption_layer bounded_context context_map\\n" - " directory_ownership domain_event domain_service entity invariant\\n" - " minimal_shared_kernel product_gap_baseline repository\\n" - " subdomain_classification ubiquitous_language value_object\\n" - " CWL_PRODUCT_AGENT_PROMPT: |\\n" - " Deliver one buyer-visible increment through the repository-owned product agent.\\n" - "\\n" - " Keep implementation, tests, documentation, and package boundaries coherent.\\n" - "jobs:\\n" - " develop:\\n" - " runs-on: ubuntu-24.04\\n" - " steps:\\n" - " - name: Invoke the product agent\\n" - " run: |\\n" - " # cwl-ddd-prompt-binding: v1\\n" - " python scripts/automation/commercial_product_development.py \\\\\\n" - " --prompt-env CWL_PRODUCT_AGENT_PROMPT \\\\\\n" - " --architecture-contract-env CWL_DDD_CONTRACT_CAPABILITIES\\n" - ), -""", - ) - replace_once( - "tests/test_organization_commercial_readiness_loop_policy.py", - """ ActionKind, - ActionResult, - DDD_CONTRACT_TERMS, - RunRecord, -""", - """ ActionKind, - ActionResult, - DDD_CONTRACT_CAPABILITIES, - RunRecord, -""", - ) - replace_once( - "tests/test_organization_commercial_readiness_loop_policy.py", - """ is_dedicated_writer_workflow, - is_live_writer_run, - is_manual_product_entrypoint, -""", - """ has_domain_driven_development_contract, - is_dedicated_writer_workflow, - is_live_writer_run, - is_manual_product_entrypoint, -""", - ) - replace_once( - "tests/test_organization_commercial_readiness_loop_policy.py", - """def test_product_entrypoint_requires_manual_nvidia_and_ddd_opt_in() -> None: - \"\"\"Product dispatch requires a manual credential-isolated DDD contract.\"\"\" - safe = manual_workflow() - assert is_manual_product_entrypoint(safe) - assert not is_manual_product_entrypoint(workflow(state="disabled_manually", content="x")) - assert not is_manual_product_entrypoint(workflow(content=None)) - mutations = [ - (safe.content or "") + 'schedule:\\n - cron: "1 * * * *"\\n', - (safe.content or "") + "COPILOT_GITHUB_TOKEN: forbidden\\n", - (safe.content or "").replace("# cwl-org-commercial-entrypoint: v1\\n", ""), - (safe.content or "").replace( - "# cwl-ddd-architecture-audit: required\\n", "" - ), - (safe.content or "").replace("concurrency:\\n", ""), - ] - mutations.extend( - (safe.content or "").replace(term, f"missing-{index}", 1) - for index, term in enumerate(DDD_CONTRACT_TERMS) - ) - for changed in mutations: - assert not is_manual_product_entrypoint(workflow(content=changed)) - - -def test_ddd_contract_rejects_unbound_unused_yaml_prose() -> None: - \"\"\"DDD words in an unused scalar are not an agent instruction contract.\"\"\" - safe = manual_workflow() - assert safe.content is not None - unused = safe.content.replace("prompt: |\\n", "notes: |\\n", 1) - assert not is_manual_product_entrypoint(workflow(content=unused)) -""", - """def test_product_entrypoint_requires_manual_nvidia_and_bound_ddd_opt_in() -> None: - \"\"\"Product dispatch requires a scoped capability set bound to one command.\"\"\" - safe = manual_workflow() - assert safe.content is not None - source = safe.content - assert "Domain-Driven Design" not in source - assert is_manual_product_entrypoint(safe) - assert has_domain_driven_development_contract(source) - - ordinary_rejections = [ - source + 'schedule:\\n - cron: "1 * * * *"\\n', - source + "COPILOT_GITHUB_TOKEN: forbidden\\n", - source.replace("# cwl-org-commercial-entrypoint: v1\\n", "", 1), - source.replace("# cwl-ddd-architecture-audit: v1\\n", "", 1), - source.replace("concurrency:\\n", "", 1), - ] - for changed in ordinary_rejections: - assert not is_manual_product_entrypoint(workflow(content=changed)) - - for index, capability in enumerate(sorted(DDD_CONTRACT_CAPABILITIES)): - changed = source.replace(capability, f"omitted_{index}", 1) - assert not has_domain_driven_development_contract(changed) - - -def test_ddd_contract_rejects_comments_unused_scopes_and_split_bindings() -> None: - \"\"\"Comments, unscoped values, and separate commands cannot fake a binding.\"\"\" - safe = manual_workflow() - assert safe.content is not None - source = safe.content - comment_only = ( - "# cwl-ddd-architecture-audit: v1\\n" - + "\\n".join(f"# {item}" for item in sorted(DDD_CONTRACT_CAPABILITIES)) - + "\\n# cwl-ddd-prompt-binding: v1\\n" - + "# --prompt-env CWL_PRODUCT_AGENT_PROMPT\\n" - + "# --architecture-contract-env CWL_DDD_CONTRACT_CAPABILITIES\\n" - ) - assert not has_domain_driven_development_contract(comment_only) - - invalid_sources = [ - source.replace("env:\\n", "metadata:\\n", 1), - source.replace("CWL_PRODUCT_AGENT_PROMPT: |", "UNUSED_PROMPT: |", 1), - source.replace( - "CWL_DDD_CONTRACT_CAPABILITIES: >-", "UNUSED_CAPABILITIES: >-", 1 - ), - source.replace( - " Deliver one buyer-visible increment through the repository-owned product agent.\\n", - "", - 1, - ), - source.replace("# cwl-ddd-prompt-binding: v1", "# unbound", 1), - source.replace( - "--prompt-env CWL_PRODUCT_AGENT_PROMPT", "--prompt-env UNUSED_PROMPT", 1 - ), - source.replace( - "--architecture-contract-env CWL_DDD_CONTRACT_CAPABILITIES", - "--architecture-contract-env UNUSED_CAPABILITIES", - 1, - ), - source + "env:\\n OTHER_VALUE: present\\n", - source.replace( - " CWL_PRODUCT_AGENT_PROMPT: |", - " CWL_PRODUCT_AGENT_PROMPT: |\\n" - " duplicate\\n" - " CWL_PRODUCT_AGENT_PROMPT: |", - 1, - ), - source.replace( - " CWL_DDD_CONTRACT_CAPABILITIES: >-", - " CWL_DDD_CONTRACT_CAPABILITIES: >-\\n" - " bounded_context\\n" - " CWL_DDD_CONTRACT_CAPABILITIES: >-", - 1, - ), - ] - for changed in invalid_sources: - assert not has_domain_driven_development_contract(changed) - - bound_run = ( - " run: |\\n" - " # cwl-ddd-prompt-binding: v1\\n" - " python scripts/automation/commercial_product_development.py \\\\n" - " --prompt-env CWL_PRODUCT_AGENT_PROMPT \\\\n" - " --architecture-contract-env CWL_DDD_CONTRACT_CAPABILITIES\\n" - ) - split_run = ( - " run: |\\n" - " # cwl-ddd-prompt-binding: v1\\n" - " product-agent --prompt-env CWL_PRODUCT_AGENT_PROMPT\\n" - " product-agent --architecture-contract-env CWL_DDD_CONTRACT_CAPABILITIES\\n" - ) - assert not has_domain_driven_development_contract( - source.replace(bound_run, split_run, 1) - ) - - dangling_run = ( - " run: |\\n" - " # cwl-ddd-prompt-binding: v1\\n" - " product-agent \\\\n" - ) - assert not has_domain_driven_development_contract( - source.replace(bound_run, dangling_run, 1) - ) -""", - ) - replace_once( - "tests/test_organization_commercial_readiness_loop_policy.py", - """ assert "# cwl-ddd-architecture-audit: required" in doctoring -""", - """ assert "# cwl-ddd-architecture-audit: v1" in doctoring - assert "--architecture-contract-env CWL_DDD_CONTRACT_CAPABILITIES" in doctoring -""", - ) - - doctoring_path = ROOT / "docs/doctoring/organization-commercial-readiness-loop.md" - doctoring = doctoring_path.read_text(encoding="utf-8") - start = doctoring.index("## Product-development boundary") - end = doctoring.index("## Failure, evidence, and operations") - product_section = """## Product-development boundary - -Product development is dispatched only when a repository has zero open pull requests and exposes one active, manual-only, explicitly marked workflow. The DDD enrollment is versioned and its values must live in the root workflow `env` mapping so every job receives the same contract: - -```yaml -# cwl-org-commercial-entrypoint: v1 -# cwl-ddd-architecture-audit: v1 -on: - workflow_dispatch: - -env: - CWL_DDD_CONTRACT_CAPABILITIES: >- - aggregate anti_corruption_layer bounded_context context_map - directory_ownership domain_event domain_service entity invariant - minimal_shared_kernel product_gap_baseline repository - subdomain_classification ubiquitous_language value_object - CWL_PRODUCT_AGENT_PROMPT: | - Deliver one buyer-visible increment through the repository-owned product agent. - -jobs: - develop: - steps: - - run: | - # cwl-ddd-prompt-binding: v1 - product-agent \ - --prompt-env CWL_PRODUCT_AGENT_PROMPT \ - --architecture-contract-env CWL_DDD_CONTRACT_CAPABILITIES -``` - -The entrypoint must also contain an explicit `concurrency` contract, use `NVIDIA_NIM_API_KEY`, omit `COPILOT_GITHUB_TOKEN`, have no schedule of its own, and carry a commercial/product-development identity. Human-readable prompt wording is repository-owned and may use any language. Eligibility depends on stable capability identifiers rather than copied English prose. The prompt and capability environment names must be passed to the same non-comment shell command under the binding marker; comments, unrelated YAML, unscoped step values, separate commands, or unused block scalars do not satisfy the contract. - -The capability contract covers strategic and tactical Domain-Driven Design: subdomain classification, Bounded Context, Context Map, Ubiquitous Language, Aggregate, Entity, Value Object, Domain Service, Repository, Domain Event, Invariant, Anti-Corruption Layer, minimal Shared Kernel, directory ownership, and product-gap baseline traceability. - -Each hourly product increment must identify the owning product responsibility before selecting a repository, then compare the live directory tree, module/package names, API, database objects, tests, and documentation with that responsibility. Misleading directory paths, generic `utils`/`common` dumping grounds that own domain behavior, infrastructure imports inside the domain model, cross-context database access, obsolete product names, or customer-visible implementation boundaries are architecture defects, not cosmetic debt. When one can be corrected safely in the bounded increment, the agent moves the code and updates imports, package manifests, call sites, migrations, tests, ADRs, diagrams, and compatibility adapters in the same pull request. - -The contract does not impose one universal folder template. A move is justified by domain ownership and dependency direction, not by directory aesthetics. Aggregate boundaries remain the smallest consistency boundary; external and legacy systems are isolated behind an Anti-Corruption Layer; the Shared Kernel remains minimal; and cross-context integration uses explicit versioned contracts. If a coherent move exceeds the current pull request's safe scope, the agent must record the exact owner, callers, target context, migration sequence, and acceptance evidence in `docs/product-technical-gap-baseline.md` and select it as the next bounded architecture increment rather than silently leaving the drift unresolved. - -This opt-in prevents the central coordinator from guessing that an unrelated manual workflow can safely modify product source. Repositories with an existing hourly or more frequent dedicated writer keep their own lease and are never double-dispatched; those schedules may share the same DDD contract and should adopt it without adding another cron. - -The repository-local entrypoint remains responsible for implementing the two environment-name flags in its product-agent adapter, bounded editable paths, tests, 100% production statement and branch coverage, public docstrings, package and security verification, exact-head publication, and pull-request creation. A missing compliant entrypoint is a deliberate no-op, not permission to inject a generic writer into that repository. - -""" - doctoring_path.write_text( - doctoring[:start] + product_section + doctoring[end:], encoding="utf-8" - ) - - replace_once( - "docs/product-technical-gap-baseline.md", - """- **조치:** 수동 제품개발 진입점에 `# cwl-ddd-architecture-audit: required`와 전략·전술 DDD 용어, directory-path repair, `docs/product-technical-gap-baseline.md` 갱신을 요구한다. 기존 전용 예약은 writer lease를 유지해 중복 실행하지 않는다. -""", - """- **조치:** 수동 제품개발 진입점에 `# cwl-ddd-architecture-audit: v1`, root `env`의 versioned capability ID 집합, 자유 형식 agent prompt, 동일 실행 명령의 `--prompt-env CWL_PRODUCT_AGENT_PROMPT`·`--architecture-contract-env CWL_DDD_CONTRACT_CAPABILITIES` binding을 요구한다. 주석·무관 YAML·분리 명령은 계약으로 인정하지 않으며, 기존 전용 예약은 writer lease를 유지해 중복 실행하지 않는다. -""", - ) - replace_once( - "docs/product-technical-gap-baseline.md", - """- **완료 증거:** exact-head focused policy tests, statement/branch coverage 100%, Python docstring 100%, workflow security checks, independent review, protected merge. 병합 전 상태는 구현 중이며 운영 완료로 간주하지 않는다. -""", - """- **리뷰 보강:** raw YAML 전체의 단어 존재 검사를 제거하고 root environment scope와 실제 product-agent command binding을 검증한다. 설명문은 특정 영어 문구에 종속되지 않는다. -- **완료 증거:** exact-head focused policy tests, statement/branch coverage 100%, Python docstring 100%, workflow security checks, independent review, protected merge. 병합 전 상태는 구현 중이며 운영 완료로 간주하지 않는다. -""", - ) - replace_once( - "CHANGELOG.md", - """- Restore the hourly organization commercial-readiness coordinator when the dedicated maintainer secret is absent by exchanging the protected scheduled job's OIDC identity for a short-lived OpenCode App installation token; retain bounded network calls, token masking, and fail-closed parsing. Require every centrally dispatched product-development entrypoint to accept a machine-checked Domain-Driven Design contract, continuously repairing misleading directory ownership and recording larger bounded-context migrations in `docs/product-technical-gap-baseline.md` without duplicating repository-owned schedules. -""", - """- Restore the hourly organization commercial-readiness coordinator when the dedicated maintainer secret is absent by exchanging the protected scheduled job's OIDC identity for a short-lived OpenCode App installation token; retain bounded network calls, token masking, and fail-closed parsing. Require every centrally dispatched product-development entrypoint to bind a versioned machine-readable Domain-Driven Design capability set and repository-owned prompt to the same product-agent command, rejecting comments or unused YAML while continuously repairing misleading directory ownership and recording larger bounded-context migrations in `docs/product-technical-gap-baseline.md` without duplicating repository-owned schedules. -""", - ) - - -def main() -> None: - """Run the selected TDD phase.""" - parser = argparse.ArgumentParser() - parser.add_argument("phase", choices=("red", "green")) - args = parser.parse_args() - if args.phase == "red": - apply_red() - else: - apply_green() - - -if __name__ == "__main__": - main() From f75fbe4475b96b1f18a8ac3e71ef9a9acc00ac7b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 1 Sep 2026 13:44:57 +0900 Subject: [PATCH 17/24] fix(automation): bind DDD contract to executable agent input --- ...n-commercial-readiness-loop-quality-ci.yml | 6 +- organization_commercial_readiness_fixtures.py | 30 +- .../organization_commercial_readiness_core.py | 892 +++++++++++++++++ ...ation_commercial_readiness_ddd_contract.py | 190 ++++ .../organization_commercial_readiness_loop.py | 911 +----------------- ...n_commercial_readiness_loop_ddd_binding.py | 271 ++++++ 6 files changed, 1415 insertions(+), 885 deletions(-) create mode 100644 scripts/ci/organization_commercial_readiness_core.py create mode 100644 scripts/ci/organization_commercial_readiness_ddd_contract.py create mode 100644 tests/test_organization_commercial_readiness_loop_ddd_binding.py diff --git a/.github/workflows/organization-commercial-readiness-loop-quality-ci.yml b/.github/workflows/organization-commercial-readiness-loop-quality-ci.yml index 50729db472..921f2f44cc 100644 --- a/.github/workflows/organization-commercial-readiness-loop-quality-ci.yml +++ b/.github/workflows/organization-commercial-readiness-loop-quality-ci.yml @@ -7,6 +7,8 @@ on: - ".github/workflows/organization-commercial-readiness-loop.yml" - ".github/workflows/organization-commercial-readiness-loop-quality-ci.yml" - "scripts/ci/organization_commercial_readiness_loop.py" + - "scripts/ci/organization_commercial_readiness_core.py" + - "scripts/ci/organization_commercial_readiness_ddd_contract.py" - "organization_commercial_readiness_fixtures.py" - "tests/test_organization_commercial_readiness_loop*.py" - "docs/doctoring/organization-commercial-readiness-loop.md" @@ -62,11 +64,13 @@ jobs: --branch \ -m pytest --import-mode=importlib tests/test_organization_commercial_readiness_loop*.py -q python -m coverage report \ - --include='scripts/ci/organization_commercial_readiness_loop.py' \ + --include='scripts/ci/organization_commercial_readiness_*.py' \ --show-missing \ --fail-under=100 python -m compileall -q \ scripts/ci/organization_commercial_readiness_loop.py \ + scripts/ci/organization_commercial_readiness_core.py \ + scripts/ci/organization_commercial_readiness_ddd_contract.py \ organization_commercial_readiness_fixtures.py \ tests/test_organization_commercial_readiness_loop*.py git diff --exit-code diff --git a/organization_commercial_readiness_fixtures.py b/organization_commercial_readiness_fixtures.py index f8c78fd2d3..d3bd4bd514 100644 --- a/organization_commercial_readiness_fixtures.py +++ b/organization_commercial_readiness_fixtures.py @@ -78,14 +78,28 @@ def manual_workflow(*, workflow_id: int = 9) -> WorkflowRecord: "on:\n workflow_dispatch:\n" "concurrency:\n group: product-development\n" "permissions:\n contents: write\n" - "NVIDIA_API_KEY: ${{ secrets.NVIDIA_NIM_API_KEY }}\n" - "prompt: |\n" - " Apply Domain-Driven Design before and during every increment.\n" - " Classify core, supporting, and generic subdomains; define each Bounded Context, Context Map, and Ubiquitous Language.\n" - " Keep Aggregate, Entity, Value Object, Domain Service, Repository, Domain Event, and Invariant names aligned across code, API, database, and tests.\n" - " Isolate external systems behind an Anti-Corruption Layer and keep the Shared Kernel minimal.\n" - " Audit and correct misleading directory paths with imports, packaging, callers, tests, and architecture documents in the same bounded change.\n" - " Update docs/product-technical-gap-baseline.md with detected and repaired architecture drift.\n" + "env:\n" + " NVIDIA_API_KEY: ${{ secrets.NVIDIA_NIM_API_KEY }}\n" + " CWL_DDD_CONTRACT_VERSION: \"1\"\n" + " CWL_DDD_CONTRACT_CAPABILITIES: >-\n" + " aggregate anti_corruption_layer bounded_context context_map\n" + " directory_ownership domain_event domain_service entity invariant\n" + " minimal_shared_kernel product_gap_baseline repository\n" + " subdomain_classification ubiquitous_language value_object\n" + " CWL_PRODUCT_AGENT_PROMPT: |\n" + " 제품 책임과 재사용 경계를 먼저 확인하고 구매자가 체감할 한 단위를 개발한다.\n" + "\n" + " 디렉터리, 패키지, API, 데이터베이스, 테스트와 문서의 소유권을 함께 맞춘다.\n" + "jobs:\n" + " develop:\n" + " runs-on: ubuntu-24.04\n" + " steps:\n" + " - name: Invoke the repository product agent\n" + " run: |\n" + " # cwl-ddd-prompt-binding: v1\n" + " python scripts/automation/commercial_product_development.py \\\n" + " --prompt-env CWL_PRODUCT_AGENT_PROMPT \\\n" + " --architecture-contract-env CWL_DDD_CONTRACT_CAPABILITIES\n" ), ) diff --git a/scripts/ci/organization_commercial_readiness_core.py b/scripts/ci/organization_commercial_readiness_core.py new file mode 100644 index 0000000000..20b6b9fde5 --- /dev/null +++ b/scripts/ci/organization_commercial_readiness_core.py @@ -0,0 +1,892 @@ +#!/usr/bin/env python3 +"""Coordinate bounded commercial-readiness work across an organization. + +The coordinator deliberately does not implement code review, branch repair, or +product development itself. It discovers repositories that do not already have +an active writer, revalidates their exact live state immediately before a +mutation, and dispatches at most one central review-repair run and one +repository-local product-development run per invocation. +""" + +from __future__ import annotations + +import argparse +import base64 +import dataclasses +import enum +import hashlib +import json +import os +import re +import subprocess +import sys +from pathlib import Path +from typing import Any, Callable, Iterable, Mapping, Sequence +from urllib.parse import quote + + +DEFAULT_ORGANIZATION = "ContextualWisdomLab" +ORGANIZATION_RE = re.compile(r"^[A-Za-z0-9_.-]+$") +ENTRYPOINT_MARKER = "# cwl-org-commercial-entrypoint: v1" +DDD_ENTRYPOINT_MARKER = "# cwl-ddd-architecture-audit: required" +DDD_CONTRACT_TERMS = ( + "Domain-Driven Design", + "core, supporting, and generic subdomains", + "Bounded Context", + "Context Map", + "Ubiquitous Language", + "Aggregate", + "Entity", + "Value Object", + "Domain Service", + "Repository", + "Domain Event", + "Invariant", + "Anti-Corruption Layer", + "Shared Kernel", + "directory paths", + "docs/product-technical-gap-baseline.md", +) +CENTRAL_REPOSITORY = f"{DEFAULT_ORGANIZATION}/.github" +CENTRAL_REPAIR_EVENT = "pr-review-fix-scheduler" +ACTIVE_RUN_STATES = frozenset({"queued", "in_progress", "waiting", "pending", "requested"}) +WRITER_SIGNAL_RE = re.compile( + r"(?:hourly|commercial|product[ _-]*development|autonomous|readiness|" + r"maintenance|review[ _-]*repair|review[ _-]*fix|maintainer|pr[ _-]*disposition)", + re.IGNORECASE, +) +MERGE_SCHEDULER_RE = re.compile( + r"(?:required[ _-]*pr[ _-]*review[ _-]*merge[ _-]*scheduler|" + r"pr-review-merge-scheduler)", + re.IGNORECASE, +) +SCHEDULE_RE = re.compile(r"(?m)^\s*schedule\s*:") +WORKFLOW_DISPATCH_RE = re.compile(r"(?m)^\s*workflow_dispatch\s*:") +MAX_WORKFLOW_RECORDS_PER_REPOSITORY = 1_000 +MAX_WORKFLOW_SOURCES_PER_REPOSITORY = 100 +MAX_WORKFLOW_SOURCE_BYTES_PER_FILE = 1_048_576 +MAX_WORKFLOW_SOURCE_BYTES_PER_REPOSITORY = 10 * 1_048_576 +SAFE_DIAGNOSTIC_METHODS = frozenset( + {"DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"} +) + + +class GitHubError(RuntimeError): + """Represent a bounded GitHub API or authentication failure.""" + + +class SnapshotChanged(RuntimeError): + """Signal that a repository moved while one snapshot was materialized.""" + + +class ActionKind(str, enum.Enum): + """Supported coordinator mutation classes.""" + + REVIEW_REPAIR = "review_repair" + PRODUCT_DEVELOPMENT = "product_development" + + +@dataclasses.dataclass(frozen=True) +class WorkflowRecord: + """Describe one repository workflow and its exact inspected source.""" + + workflow_id: int + name: str + path: str + state: str + content_sha: str + content: str | None + + +@dataclasses.dataclass(frozen=True) +class RunRecord: + """Describe one workflow run that may hold a live writer lease.""" + + run_id: int + name: str + path: str + status: str + head_sha: str + + +@dataclasses.dataclass(frozen=True) +class PullRequestRecord: + """Describe the exact pull-request fields used by the selection policy.""" + + number: int + draft: bool + base_ref: str + head_sha: str + updated_at: str + + +@dataclasses.dataclass(frozen=True) +class RepositorySnapshot: + """Bind repository selection evidence to one stable default-branch state.""" + + full_name: str + default_branch: str + default_sha: str + workflows: tuple[WorkflowRecord, ...] + active_runs: tuple[RunRecord, ...] + open_pulls: tuple[PullRequestRecord, ...] + + @property + def fingerprint(self) -> str: + """Return a deterministic digest independent of API result ordering.""" + payload = { + "full_name": self.full_name, + "default_branch": self.default_branch, + "default_sha": self.default_sha, + "workflows": sorted( + ( + item.workflow_id, + item.name, + item.path, + item.state, + item.content_sha, + ) + for item in self.workflows + ), + "active_runs": sorted( + (item.run_id, item.name, item.path, item.status, item.head_sha) + for item in self.active_runs + ), + "open_pulls": sorted( + ( + item.number, + item.draft, + item.base_ref, + item.head_sha, + item.updated_at, + ) + for item in self.open_pulls + ), + } + canonical = json.dumps(payload, sort_keys=True, separators=(",", ":")) + return hashlib.sha256(canonical.encode("utf-8")).hexdigest() + + +@dataclasses.dataclass(frozen=True) +class PlanItem: + """Describe one bounded mutation selected from an initial snapshot.""" + + kind: ActionKind + repository: str + default_branch: str + expected_fingerprint: str + workflow_id: int | None = None + + +@dataclasses.dataclass(frozen=True) +class ActionResult: + """Record the outcome of one revalidated coordinator action.""" + + kind: ActionKind + repository: str + status: str + detail: str + + +@dataclasses.dataclass(frozen=True) +class RunReport: + """Provide machine-readable and operator-readable evidence for one run.""" + + organization: str + inspected_repositories: int + leased_repositories: tuple[str, ...] + inspection_errors: tuple[tuple[str, str], ...] + actions: tuple[ActionResult, ...] + dry_run: bool + + def to_dict(self) -> dict[str, Any]: + """Return a JSON-serializable representation of this report.""" + return { + "organization": self.organization, + "inspected_repositories": self.inspected_repositories, + "leased_repositories": list(self.leased_repositories), + "inspection_errors": [ + {"repository": repository, "error": error} + for repository, error in self.inspection_errors + ], + "actions": [ + { + "kind": action.kind.value, + "repository": action.repository, + "status": action.status, + "detail": action.detail, + } + for action in self.actions + ], + "dry_run": self.dry_run, + } + + def to_json(self) -> str: + """Serialize this report as stable UTF-8 JSON text.""" + return json.dumps(self.to_dict(), ensure_ascii=False, indent=2, sort_keys=True) + + def to_markdown(self) -> str: + """Render a concise GitHub Actions job summary.""" + lines = [ + "# Organization commercial-readiness coordinator", + "", + f"- Organization: `{self.organization}`", + f"- Repositories inspected: **{self.inspected_repositories}**", + f"- Repositories leased to dedicated writers: **{len(self.leased_repositories)}**", + f"- Inspection errors: **{len(self.inspection_errors)}**", + f"- Dry run: **{'yes' if self.dry_run else 'no'}**", + "", + "## Actions", + "", + "| Kind | Repository | Status | Detail |", + "|---|---|---|---|", + ] + if self.actions: + for action in self.actions: + detail = action.detail.replace("|", "\\|").replace("\n", " ") + lines.append( + f"| `{action.kind.value}` | `{action.repository}` | " + f"`{action.status}` | {detail} |" + ) + else: + lines.append("| — | — | `no_action` | No safe target was selected. |") + if self.inspection_errors: + lines.extend(["", "## Inspection errors", ""]) + for repository, error in self.inspection_errors: + lines.append(f"- `{repository}`: {error}") + return "\n".join(lines) + "\n" + + +class GitHubClient: + """Use the GitHub CLI as an authenticated, bounded REST transport.""" + + def __init__(self, token: str, *, timeout_seconds: int = 60) -> None: + """Initialize one authenticated GitHub credential with a bounded timeout.""" + if not token: + raise GitHubError("GH_TOKEN is required for organization coordination") + self._token = token + self._timeout_seconds = timeout_seconds + + @classmethod + def from_environment(cls, environ: Mapping[str, str] | None = None) -> GitHubClient: + """Build a client without accepting the repository-scoped GITHUB_TOKEN.""" + values = os.environ if environ is None else environ + token = str(values.get("GH_TOKEN") or "").strip() + if not token: + raise GitHubError("GH_TOKEN is required; no GITHUB_TOKEN fallback is permitted") + return cls(token) + + def _redact_credential(self, value: str) -> str: + """Remove the exact GitHub credential before any diagnostic truncation.""" + return value.replace(self._token, "[REDACTED]") + + def request( + self, + path: str, + *, + method: str = "GET", + payload: Any = None, + ) -> Any: + """Call one GitHub REST endpoint and decode a bounded JSON response.""" + normalized_method = method.upper() + safe_method = ( + normalized_method + if normalized_method in SAFE_DIAGNOSTIC_METHODS + else "[REDACTED_METHOD]" + ) + safe_path = self._redact_credential(path) + args = ["gh", "api"] + if normalized_method != "GET": + args.extend(["--method", normalized_method]) + args.append(path) + input_text: str | None = None + if payload is not None: + args.extend(["--input", "-"]) + input_text = json.dumps(payload, separators=(",", ":")) + try: + completed = subprocess.run( + args, + input=input_text, + capture_output=True, + text=True, + timeout=self._timeout_seconds, + env={**os.environ, "GH_TOKEN": self._token}, + check=False, + ) + except (OSError, subprocess.TimeoutExpired) as exc: + raise GitHubError(f"GitHub API transport failed: {type(exc).__name__}") from exc + if completed.returncode != 0: + raw = (completed.stderr or completed.stdout or "GitHub API request failed").strip() + bounded = self._redact_credential(raw)[-900:] + raise GitHubError( + f"GitHub API {safe_method} {safe_path} failed: {bounded}" + ) + text = completed.stdout.strip() + if not text: + return None + try: + return json.loads(text) + except json.JSONDecodeError as exc: + raise GitHubError( + f"GitHub API returned invalid JSON for {safe_path}" + ) from exc + + def list_repositories(self, organization: str) -> list[dict[str, Any]]: + """Return every repository visible to the coordinator installation.""" + repositories: list[dict[str, Any]] = [] + page = 1 + while True: + result = self.request( + f"/orgs/{organization}/repos?type=all&sort=full_name&per_page=100&page={page}" + ) + batch = list(result or []) + repositories.extend(batch) + if len(batch) < 100: + return repositories + page += 1 + + def default_branch_sha(self, repository: str, default_branch: str) -> str: + """Resolve one exact commit for the repository default branch.""" + branch_ref = quote(default_branch, safe="") + result = self.request(f"/repos/{repository}/commits/{branch_ref}") + sha = str((result or {}).get("sha") or "") + if not re.fullmatch(r"[0-9a-fA-F]{40}", sha): + raise GitHubError(f"repository {repository} returned an invalid default-branch SHA") + return sha.lower() + + def list_workflows(self, repository: str, exact_ref: str) -> tuple[WorkflowRecord, ...]: + """Return a fail-closed, memory-bounded workflow and writer-source inventory.""" + workflows: list[WorkflowRecord] = [] + source_count = 0 + source_bytes = 0 + page = 1 + while True: + result = self.request( + f"/repos/{repository}/actions/workflows?per_page=100&page={page}" + ) + batch = list((result or {}).get("workflows") or []) + if len(workflows) + len(batch) > MAX_WORKFLOW_RECORDS_PER_REPOSITORY: + raise GitHubError( + f"repository {repository} exceeded workflow metadata limit of " + f"{MAX_WORKFLOW_RECORDS_PER_REPOSITORY}" + ) + for raw in batch: + workflow_id = int(raw.get("id") or 0) + path = str(raw.get("path") or "") + name = str(raw.get("name") or path) + state = str(raw.get("state") or "unknown") + content: str | None = None + content_sha = "" + if ( + path + and not path.startswith("dynamic/") + and _writer_signal(name, path) + ): + source_count += 1 + if source_count > MAX_WORKFLOW_SOURCES_PER_REPOSITORY: + raise GitHubError( + f"repository {repository} exceeded workflow source limit of " + f"{MAX_WORKFLOW_SOURCES_PER_REPOSITORY}" + ) + encoded_path = quote(path, safe="/") + try: + source = self.request( + f"/repos/{repository}/contents/{encoded_path}?ref={exact_ref}" + ) + source_size = ( + int(source.get("size") or 0) + if isinstance(source, dict) + else 0 + ) + except (GitHubError, ValueError): + source = None + source_size = 0 + if ( + isinstance(source, dict) + and source.get("type") == "file" + and source_size <= MAX_WORKFLOW_SOURCE_BYTES_PER_FILE + and source.get("encoding") == "base64" + ): + if ( + source_bytes + source_size + > MAX_WORKFLOW_SOURCE_BYTES_PER_REPOSITORY + ): + raise GitHubError( + f"repository {repository} exceeded workflow source byte limit of " + f"{MAX_WORKFLOW_SOURCE_BYTES_PER_REPOSITORY}" + ) + try: + decoded = base64.b64decode( + str(source.get("content") or ""), validate=True + ) + content = decoded.decode("utf-8") + content_sha = str(source.get("sha") or "") + except (ValueError, UnicodeDecodeError): + content = None + content_sha = "" + else: + source_bytes += source_size + workflows.append( + WorkflowRecord( + workflow_id=workflow_id, + name=name, + path=path, + state=state, + content_sha=content_sha, + content=content, + ) + ) + if len(batch) < 100: + return tuple(workflows) + page += 1 + + def list_active_runs(self, repository: str) -> tuple[RunRecord, ...]: + """Return all queued and running workflow evidence for writer lease detection.""" + records: list[RunRecord] = [] + for status in ("queued", "in_progress", "waiting", "pending", "requested"): + page = 1 + while True: + result = self.request( + f"/repos/{repository}/actions/runs?status={status}&per_page=100&page={page}" + ) + batch = list((result or {}).get("workflow_runs") or []) + for raw in batch: + records.append( + RunRecord( + run_id=int(raw.get("id") or 0), + name=str(raw.get("name") or ""), + path=str(raw.get("path") or ""), + status=str(raw.get("status") or status), + head_sha=str(raw.get("head_sha") or ""), + ) + ) + if len(batch) < 100: + break + page += 1 + return tuple(records) + + def list_open_pulls(self, repository: str) -> tuple[PullRequestRecord, ...]: + """Return all open pull requests with exact stack and head identity.""" + records: list[PullRequestRecord] = [] + page = 1 + while True: + result = self.request( + f"/repos/{repository}/pulls?state=open&per_page=100&page={page}" + ) + batch = list(result or []) + for raw in batch: + records.append( + PullRequestRecord( + number=int(raw.get("number") or 0), + draft=bool(raw.get("draft")), + base_ref=str((raw.get("base") or {}).get("ref") or ""), + head_sha=str((raw.get("head") or {}).get("sha") or ""), + updated_at=str(raw.get("updated_at") or ""), + ) + ) + if len(batch) < 100: + return tuple(records) + page += 1 + + def snapshot(self, repository: str, default_branch: str) -> RepositorySnapshot: + """Materialize one snapshot and reject concurrent default-branch movement.""" + before = self.default_branch_sha(repository, default_branch) + workflows = self.list_workflows(repository, before) + runs = self.list_active_runs(repository) + pulls = self.list_open_pulls(repository) + after = self.default_branch_sha(repository, default_branch) + if before != after: + raise SnapshotChanged( + f"default branch moved while inspecting {repository}: {before} -> {after}" + ) + return RepositorySnapshot( + full_name=repository, + default_branch=default_branch, + default_sha=before, + workflows=workflows, + active_runs=runs, + open_pulls=pulls, + ) + + def dispatch_review_repair(self, repository: str, base_branch: str) -> None: + """Ask the established central scheduler for one bounded repair attempt.""" + self.request( + f"/repos/{CENTRAL_REPOSITORY}/dispatches", + method="POST", + payload={ + "event_type": CENTRAL_REPAIR_EVENT, + "client_payload": { + "target_repository": repository, + "base_branch": base_branch, + "max_prs": "50", + "max_dispatches": "1", + "retry_hours": "1", + "dry_run": False, + }, + }, + ) + + def dispatch_product_workflow( + self, repository: str, workflow_id: int, default_branch: str + ) -> None: + """Dispatch an explicitly opted-in repository-local development entrypoint.""" + self.request( + f"/repos/{repository}/actions/workflows/{workflow_id}/dispatches", + method="POST", + payload={"ref": default_branch}, + ) + + +def _writer_signal(name: str, path: str) -> bool: + """Return whether workflow identity indicates a repository writer.""" + identity = f"{name}\n{path}" + return bool(WRITER_SIGNAL_RE.search(identity)) and not bool( + MERGE_SCHEDULER_RE.search(identity) + ) + + +def is_dedicated_writer_workflow(workflow: WorkflowRecord) -> bool: + """Return whether an active scheduled workflow owns the repository writer lease.""" + if workflow.state != "active" or not _writer_signal(workflow.name, workflow.path): + return False + if workflow.content is None: + return True + return bool(SCHEDULE_RE.search(workflow.content)) + + +def is_live_writer_run(run: RunRecord) -> bool: + """Return whether a queued or running high-signal workflow owns a live lease.""" + return run.status in ACTIVE_RUN_STATES and _writer_signal(run.name, run.path) + + +def has_domain_driven_development_contract(source: str) -> bool: + """Return whether one entrypoint accepts the complete DDD repair contract.""" + return DDD_ENTRYPOINT_MARKER in source and all( + term in source for term in DDD_CONTRACT_TERMS + ) + + +def is_manual_product_entrypoint(workflow: WorkflowRecord) -> bool: + """Return whether a workflow safely opts in to central product development.""" + source = workflow.content + if workflow.state != "active" or source is None: + return False + return all( + ( + ENTRYPOINT_MARKER in source, + has_domain_driven_development_contract(source), + bool(WORKFLOW_DISPATCH_RE.search(source)), + not bool(SCHEDULE_RE.search(source)), + "NVIDIA_NIM_API_KEY" in source, + "COPILOT_GITHUB_TOKEN" not in source, + "concurrency:" in source, + _writer_signal(workflow.name, workflow.path), + ) + ) + + +def repository_is_eligible(repository: Mapping[str, Any], organization: str) -> bool: + """Return whether one owned repository can participate in organization coordination.""" + full_name = str(repository.get("full_name") or "") + permissions = repository.get("permissions") or {} + write_capable = any(bool(permissions.get(key)) for key in ("push", "maintain", "admin")) + return all( + ( + full_name.startswith(f"{organization}/"), + full_name != f"{organization}/.github", + not bool(repository.get("archived")), + not bool(repository.get("disabled")), + not bool(repository.get("fork")), + bool(repository.get("default_branch")), + write_capable, + ) + ) + + +def choose_rotating(items: Sequence[Any], seed: int, limit: int) -> tuple[Any, ...]: + """Choose a bounded cyclic window so later repositories are not starved.""" + if not items or limit <= 0: + return () + count = min(limit, len(items)) + start = seed % len(items) + return tuple(items[(start + offset) % len(items)] for offset in range(count)) + + +def _has_writer_lease(snapshot: RepositorySnapshot) -> bool: + """Return whether static or live evidence assigns this repository elsewhere.""" + return any(is_dedicated_writer_workflow(item) for item in snapshot.workflows) or any( + is_live_writer_run(item) for item in snapshot.active_runs + ) + + +def _eligible_review_snapshot(snapshot: RepositorySnapshot) -> bool: + """Return whether generic review repair is safe for at least one direct PR.""" + return any( + not pull.draft and pull.base_ref == snapshot.default_branch + for pull in snapshot.open_pulls + ) + + +def _manual_product_workflow(snapshot: RepositorySnapshot) -> WorkflowRecord | None: + """Return the first deterministic opted-in manual development entrypoint.""" + matches = sorted( + (item for item in snapshot.workflows if is_manual_product_entrypoint(item)), + key=lambda item: (item.path, item.workflow_id), + ) + return matches[0] if matches else None + + +def build_plan( + snapshots: Iterable[RepositorySnapshot], + *, + rotation_seed: int, + max_review_dispatches: int = 1, + max_development_dispatches: int = 1, +) -> tuple[PlanItem, ...]: + """Select independent bounded review and product targets from exact snapshots.""" + usable = tuple( + sorted( + ( + item + for item in snapshots + if item.full_name != CENTRAL_REPOSITORY and not _has_writer_lease(item) + ), + key=lambda item: item.full_name, + ) + ) + review_candidates = tuple(item for item in usable if _eligible_review_snapshot(item)) + development_candidates = tuple( + (item, workflow) + for item in usable + if not item.open_pulls + for workflow in (_manual_product_workflow(item),) + if workflow is not None + ) + plan: list[PlanItem] = [] + for item in choose_rotating(review_candidates, rotation_seed, max_review_dispatches): + plan.append( + PlanItem( + kind=ActionKind.REVIEW_REPAIR, + repository=item.full_name, + default_branch=item.default_branch, + expected_fingerprint=item.fingerprint, + ) + ) + for item, workflow in choose_rotating( + development_candidates, rotation_seed, max_development_dispatches + ): + plan.append( + PlanItem( + kind=ActionKind.PRODUCT_DEVELOPMENT, + repository=item.full_name, + default_branch=item.default_branch, + expected_fingerprint=item.fingerprint, + workflow_id=workflow.workflow_id, + ) + ) + return tuple(plan) + + +def _bounded_error(exc: BaseException) -> str: + """Return a stable, bounded error description without stack or credential data.""" + text = f"{type(exc).__name__}: {exc}".replace("\n", " ") + return text[:1000] + + +def run_once( + client: Any, + *, + organization: str, + rotation_seed: int, + max_repositories: int = 200, + max_review_dispatches: int = 1, + max_development_dispatches: int = 1, + dry_run: bool = False, +) -> RunReport: + """Inspect the organization, revalidate targets, and dispatch bounded work.""" + if organization != DEFAULT_ORGANIZATION: + raise GitHubError( + f"organization must be {DEFAULT_ORGANIZATION}; foreign control planes are not supported" + ) + raw_repositories = client.list_repositories(organization) + eligible = sorted( + ( + item + for item in raw_repositories + if repository_is_eligible(item, organization) + ), + key=lambda item: str(item.get("full_name") or ""), + ) + selected_repositories = choose_rotating(eligible, rotation_seed, max_repositories) + snapshots: list[RepositorySnapshot] = [] + errors: list[tuple[str, str]] = [] + leased: list[str] = [] + for repository in selected_repositories: + full_name = str(repository["full_name"]) + default_branch = str(repository["default_branch"]) + try: + current = client.snapshot(full_name, default_branch) + except (GitHubError, SnapshotChanged) as exc: + errors.append((full_name, _bounded_error(exc))) + continue + snapshots.append(current) + if _has_writer_lease(current): + leased.append(full_name) + plan = build_plan( + snapshots, + rotation_seed=rotation_seed, + max_review_dispatches=max_review_dispatches, + max_development_dispatches=max_development_dispatches, + ) + actions: list[ActionResult] = [] + for item in plan: + try: + live = client.snapshot(item.repository, item.default_branch) + except (GitHubError, SnapshotChanged) as exc: + actions.append( + ActionResult( + kind=item.kind, + repository=item.repository, + status="skipped_refetch_error", + detail=_bounded_error(exc), + ) + ) + continue + if _has_writer_lease(live): + actions.append( + ActionResult( + kind=item.kind, + repository=item.repository, + status="skipped_writer_lease", + detail="a dedicated or live writer appeared before dispatch", + ) + ) + continue + if live.fingerprint != item.expected_fingerprint: + actions.append( + ActionResult( + kind=item.kind, + repository=item.repository, + status="skipped_state_changed", + detail="repository, workflow, run, or pull-request state moved before dispatch", + ) + ) + continue + if dry_run: + actions.append( + ActionResult( + kind=item.kind, + repository=item.repository, + status="dry_run", + detail="exact state revalidated; mutation intentionally suppressed", + ) + ) + continue + try: + if item.kind is ActionKind.REVIEW_REPAIR: + client.dispatch_review_repair(item.repository, item.default_branch) + else: + if item.workflow_id is None: + raise GitHubError("product-development plan omitted workflow identity") + client.dispatch_product_workflow( + item.repository, item.workflow_id, item.default_branch + ) + except GitHubError as exc: + actions.append( + ActionResult( + kind=item.kind, + repository=item.repository, + status="dispatch_failed", + detail=_bounded_error(exc), + ) + ) + else: + actions.append( + ActionResult( + kind=item.kind, + repository=item.repository, + status="dispatched", + detail="exact state revalidated and bounded workflow dispatched", + ) + ) + return RunReport( + organization=organization, + inspected_repositories=len(snapshots), + leased_repositories=tuple(sorted(leased)), + inspection_errors=tuple(errors), + actions=tuple(actions), + dry_run=dry_run, + ) + + +def _non_negative_int(value: str) -> int: + """Parse one non-negative integer command-line bound.""" + parsed = int(value) + if parsed < 0: + raise argparse.ArgumentTypeError("value must be zero or greater") + return parsed + + +def _parser() -> argparse.ArgumentParser: + """Build the command-line parser used by workflow and local dry runs.""" + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--organization", default=DEFAULT_ORGANIZATION) + parser.add_argument("--rotation-seed", type=int, default=0) + parser.add_argument("--max-repositories", type=_non_negative_int, default=200) + parser.add_argument("--max-review-dispatches", type=_non_negative_int, default=1) + parser.add_argument("--max-development-dispatches", type=_non_negative_int, default=1) + parser.add_argument("--dry-run", action="store_true") + parser.add_argument("--json-output", type=Path) + return parser + + +def main( + argv: Sequence[str] | None = None, + *, + client_factory: Callable[[], Any] | None = None, +) -> int: + """Run the coordinator CLI and persist auditable receipts.""" + parser = _parser() + try: + args = parser.parse_args(argv) + except SystemExit: + return 2 + if not ORGANIZATION_RE.fullmatch(args.organization): + print("invalid organization", file=sys.stderr) + return 2 + factory = client_factory or GitHubClient.from_environment + try: + client = factory() + report = run_once( + client, + organization=args.organization, + rotation_seed=args.rotation_seed, + max_repositories=args.max_repositories, + max_review_dispatches=args.max_review_dispatches, + max_development_dispatches=args.max_development_dispatches, + dry_run=args.dry_run, + ) + except (GitHubError, SnapshotChanged, ValueError) as exc: + print(_bounded_error(exc), file=sys.stderr) + 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") + 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: + handle.write(report.to_markdown()) + all_selected_inspections_failed = ( + report.inspected_repositories == 0 and bool(report.inspection_errors) + ) + all_planned_dispatches_failed = bool(report.actions) and all( + action.status == "dispatch_failed" for action in report.actions + ) + return 1 if all_selected_inspections_failed or all_planned_dispatches_failed else 0 + + +if __name__ == "__main__": # pragma: no cover - exercised through main() + raise SystemExit(main()) diff --git a/scripts/ci/organization_commercial_readiness_ddd_contract.py b/scripts/ci/organization_commercial_readiness_ddd_contract.py new file mode 100644 index 0000000000..9de440815c --- /dev/null +++ b/scripts/ci/organization_commercial_readiness_ddd_contract.py @@ -0,0 +1,190 @@ +"""Validate machine-readable DDD contracts in product-development workflows.""" + +from __future__ import annotations + +import re +import shlex +import textwrap +from collections.abc import Iterable + +DDD_ENTRYPOINT_MARKER = "# cwl-ddd-architecture-audit: required" +DDD_PROMPT_BINDING_MARKER = "# cwl-ddd-prompt-binding: v1" +DDD_CONTRACT_VERSION_ENVIRONMENT = "CWL_DDD_CONTRACT_VERSION" +DDD_PROMPT_ENVIRONMENT = "CWL_PRODUCT_AGENT_PROMPT" +DDD_CAPABILITY_ENVIRONMENT = "CWL_DDD_CONTRACT_CAPABILITIES" +DDD_PROMPT_OPTION = "--prompt-env" +DDD_CAPABILITY_OPTION = "--architecture-contract-env" +DDD_CONTRACT_VERSION = "1" +DDD_CONTRACT_CAPABILITIES = frozenset( + { + "aggregate", + "anti_corruption_layer", + "bounded_context", + "context_map", + "directory_ownership", + "domain_event", + "domain_service", + "entity", + "invariant", + "minimal_shared_kernel", + "product_gap_baseline", + "repository", + "subdomain_classification", + "ubiquitous_language", + "value_object", + } +) +DDD_CONTRACT_TERMS = tuple(sorted(DDD_CONTRACT_CAPABILITIES)) +_CAPABILITY_TOKEN_RE = re.compile(r"[a-z][a-z0-9_]*") +_BLOCK_HEADER_TEMPLATE = r"^(?P *){key}: *[>|][+-]? *$" +_VERSION_RE = re.compile( + rf"^{DDD_CONTRACT_VERSION_ENVIRONMENT}: *(?:" + r'"(?P[0-9]+)"|' + r"'(?P[0-9]+)'|" + r"(?P[0-9]+)) *$" +) +_COMMAND_OPERATORS = frozenset({";", "&&", "||", "&", "|"}) +_NON_AGENT_EXECUTABLES = frozenset( + {":", "[", "echo", "export", "false", "printf", "test", "true"} +) + + +def _top_level_mapping_bodies(source: str, key: str) -> tuple[str, ...]: + """Return bodies of top-level YAML mappings with an exact key.""" + lines = source.splitlines() + bodies: list[str] = [] + for index, line in enumerate(lines): + if line != f"{key}:": + continue + body: list[str] = [] + for candidate in lines[index + 1 :]: + if candidate.strip() and not candidate.startswith(" "): + break + body.append(candidate) + bodies.append(textwrap.dedent("\n".join(body))) + return tuple(bodies) + + +def _block_scalars(source: str, key: str) -> tuple[str, ...]: + """Return YAML literal or folded block scalar bodies for an exact key.""" + header = re.compile(_BLOCK_HEADER_TEMPLATE.format(key=re.escape(key))) + lines = source.splitlines() + blocks: list[str] = [] + for index, line in enumerate(lines): + match = header.fullmatch(line) + if match is None: + continue + base_indent = len(match.group("indent")) + body: list[str] = [] + for candidate in lines[index + 1 :]: + if candidate.strip(): + candidate_indent = len(candidate) - len(candidate.lstrip(" ")) + if candidate_indent <= base_indent: + break + body.append(candidate) + blocks.append(textwrap.dedent("\n".join(body)).strip("\n")) + return tuple(blocks) + + +def _contract_version(environment: str) -> str | None: + """Return the unique scalar contract version from a root environment body.""" + matches = [ + next(value for value in match.groups() if value is not None) + for line in environment.splitlines() + if (match := _VERSION_RE.fullmatch(line)) is not None + ] + return matches[0] if len(matches) == 1 else None + + +def _shell_segments(block: str) -> Iterable[tuple[str, ...]]: + """Yield non-comment shell command segments with continuations joined.""" + commands: list[str] = [] + fragments: list[str] = [] + for line in block.splitlines(): + fragment = line.strip() + if not fragment or fragment.startswith("#"): + continue + continued = fragment.endswith("\\") + fragments.append(fragment[:-1].rstrip() if continued else fragment) + if not continued: + commands.append(" ".join(fragments)) + fragments = [] + if fragments: + return + for command in commands: + lexer = shlex.shlex(command, posix=True, punctuation_chars=";&|") + lexer.whitespace_split = True + lexer.commenters = "#" + try: + tokens = tuple(lexer) + except ValueError: + continue + segment: list[str] = [] + for token in tokens: + if token in _COMMAND_OPERATORS: + if segment: + yield tuple(segment) + segment = [] + else: + segment.append(token) + if segment: + yield tuple(segment) + + +def _option_value(tokens: tuple[str, ...], option: str) -> str | None: + """Return one unique shell option value from a command segment.""" + values: list[str] = [] + for index, token in enumerate(tokens): + if token == option and index + 1 < len(tokens): + values.append(tokens[index + 1]) + elif token.startswith(f"{option}="): + values.append(token.split("=", 1)[1]) + return values[0] if len(values) == 1 else None + + +def _executable(tokens: tuple[str, ...]) -> str | None: + """Return the executable after optional environment assignments.""" + for token in tokens: + if token == "env" or ("=" in token and not token.startswith("--")): + continue + executable = token.rsplit("/", 1)[-1] + return None if executable.startswith("-") else executable + return None + + +def _has_bound_agent_invocation(source: str) -> bool: + """Return whether one nontrivial command receives both contract inputs.""" + for run_block in _block_scalars(source, "run"): + if DDD_PROMPT_BINDING_MARKER not in run_block: + continue + for tokens in _shell_segments(run_block): + executable = _executable(tokens) + if executable is None or executable in _NON_AGENT_EXECUTABLES: + continue + if ( + _option_value(tokens, DDD_PROMPT_OPTION) == DDD_PROMPT_ENVIRONMENT + and _option_value(tokens, DDD_CAPABILITY_OPTION) + == DDD_CAPABILITY_ENVIRONMENT + ): + return True + return False + + +def has_domain_driven_development_contract(source: str) -> bool: + """Return whether a workflow binds a scoped versioned DDD contract.""" + if DDD_ENTRYPOINT_MARKER not in source: + return False + environments = _top_level_mapping_bodies(source, "env") + if len(environments) != 1: + return False + environment = environments[0] + if _contract_version(environment) != DDD_CONTRACT_VERSION: + return False + prompts = _block_scalars(environment, DDD_PROMPT_ENVIRONMENT) + capabilities = _block_scalars(environment, DDD_CAPABILITY_ENVIRONMENT) + if len(prompts) != 1 or not prompts[0].strip() or len(capabilities) != 1: + return False + declared = frozenset(_CAPABILITY_TOKEN_RE.findall(capabilities[0])) + if declared != DDD_CONTRACT_CAPABILITIES: + return False + return _has_bound_agent_invocation(source) diff --git a/scripts/ci/organization_commercial_readiness_loop.py b/scripts/ci/organization_commercial_readiness_loop.py index 20b6b9fde5..2d89418836 100644 --- a/scripts/ci/organization_commercial_readiness_loop.py +++ b/scripts/ci/organization_commercial_readiness_loop.py @@ -1,892 +1,51 @@ #!/usr/bin/env python3 -"""Coordinate bounded commercial-readiness work across an organization. - -The coordinator deliberately does not implement code review, branch repair, or -product development itself. It discovers repositories that do not already have -an active writer, revalidates their exact live state immediately before a -mutation, and dispatches at most one central review-repair run and one -repository-local product-development run per invocation. -""" +"""Compatibility entrypoint for the organization commercial-readiness core.""" from __future__ import annotations -import argparse -import base64 -import dataclasses -import enum -import hashlib -import json -import os -import re -import subprocess +import importlib.util import sys from pathlib import Path -from typing import Any, Callable, Iterable, Mapping, Sequence -from urllib.parse import quote - - -DEFAULT_ORGANIZATION = "ContextualWisdomLab" -ORGANIZATION_RE = re.compile(r"^[A-Za-z0-9_.-]+$") -ENTRYPOINT_MARKER = "# cwl-org-commercial-entrypoint: v1" -DDD_ENTRYPOINT_MARKER = "# cwl-ddd-architecture-audit: required" -DDD_CONTRACT_TERMS = ( - "Domain-Driven Design", - "core, supporting, and generic subdomains", - "Bounded Context", - "Context Map", - "Ubiquitous Language", - "Aggregate", - "Entity", - "Value Object", - "Domain Service", - "Repository", - "Domain Event", - "Invariant", - "Anti-Corruption Layer", - "Shared Kernel", - "directory paths", - "docs/product-technical-gap-baseline.md", -) -CENTRAL_REPOSITORY = f"{DEFAULT_ORGANIZATION}/.github" -CENTRAL_REPAIR_EVENT = "pr-review-fix-scheduler" -ACTIVE_RUN_STATES = frozenset({"queued", "in_progress", "waiting", "pending", "requested"}) -WRITER_SIGNAL_RE = re.compile( - r"(?:hourly|commercial|product[ _-]*development|autonomous|readiness|" - r"maintenance|review[ _-]*repair|review[ _-]*fix|maintainer|pr[ _-]*disposition)", - re.IGNORECASE, -) -MERGE_SCHEDULER_RE = re.compile( - r"(?:required[ _-]*pr[ _-]*review[ _-]*merge[ _-]*scheduler|" - r"pr-review-merge-scheduler)", - re.IGNORECASE, -) -SCHEDULE_RE = re.compile(r"(?m)^\s*schedule\s*:") -WORKFLOW_DISPATCH_RE = re.compile(r"(?m)^\s*workflow_dispatch\s*:") -MAX_WORKFLOW_RECORDS_PER_REPOSITORY = 1_000 -MAX_WORKFLOW_SOURCES_PER_REPOSITORY = 100 -MAX_WORKFLOW_SOURCE_BYTES_PER_FILE = 1_048_576 -MAX_WORKFLOW_SOURCE_BYTES_PER_REPOSITORY = 10 * 1_048_576 -SAFE_DIAGNOSTIC_METHODS = frozenset( - {"DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"} -) - - -class GitHubError(RuntimeError): - """Represent a bounded GitHub API or authentication failure.""" - - -class SnapshotChanged(RuntimeError): - """Signal that a repository moved while one snapshot was materialized.""" - - -class ActionKind(str, enum.Enum): - """Supported coordinator mutation classes.""" - - REVIEW_REPAIR = "review_repair" - PRODUCT_DEVELOPMENT = "product_development" - - -@dataclasses.dataclass(frozen=True) -class WorkflowRecord: - """Describe one repository workflow and its exact inspected source.""" - - workflow_id: int - name: str - path: str - state: str - content_sha: str - content: str | None - - -@dataclasses.dataclass(frozen=True) -class RunRecord: - """Describe one workflow run that may hold a live writer lease.""" - - run_id: int - name: str - path: str - status: str - head_sha: str - - -@dataclasses.dataclass(frozen=True) -class PullRequestRecord: - """Describe the exact pull-request fields used by the selection policy.""" - - number: int - draft: bool - base_ref: str - head_sha: str - updated_at: str - - -@dataclasses.dataclass(frozen=True) -class RepositorySnapshot: - """Bind repository selection evidence to one stable default-branch state.""" - - full_name: str - default_branch: str - default_sha: str - workflows: tuple[WorkflowRecord, ...] - active_runs: tuple[RunRecord, ...] - open_pulls: tuple[PullRequestRecord, ...] - - @property - def fingerprint(self) -> str: - """Return a deterministic digest independent of API result ordering.""" - payload = { - "full_name": self.full_name, - "default_branch": self.default_branch, - "default_sha": self.default_sha, - "workflows": sorted( - ( - item.workflow_id, - item.name, - item.path, - item.state, - item.content_sha, - ) - for item in self.workflows - ), - "active_runs": sorted( - (item.run_id, item.name, item.path, item.status, item.head_sha) - for item in self.active_runs - ), - "open_pulls": sorted( - ( - item.number, - item.draft, - item.base_ref, - item.head_sha, - item.updated_at, - ) - for item in self.open_pulls - ), - } - canonical = json.dumps(payload, sort_keys=True, separators=(",", ":")) - return hashlib.sha256(canonical.encode("utf-8")).hexdigest() - - -@dataclasses.dataclass(frozen=True) -class PlanItem: - """Describe one bounded mutation selected from an initial snapshot.""" - - kind: ActionKind - repository: str - default_branch: str - expected_fingerprint: str - workflow_id: int | None = None - - -@dataclasses.dataclass(frozen=True) -class ActionResult: - """Record the outcome of one revalidated coordinator action.""" - - kind: ActionKind - repository: str - status: str - detail: str - - -@dataclasses.dataclass(frozen=True) -class RunReport: - """Provide machine-readable and operator-readable evidence for one run.""" - - organization: str - inspected_repositories: int - leased_repositories: tuple[str, ...] - inspection_errors: tuple[tuple[str, str], ...] - actions: tuple[ActionResult, ...] - dry_run: bool - - def to_dict(self) -> dict[str, Any]: - """Return a JSON-serializable representation of this report.""" - return { - "organization": self.organization, - "inspected_repositories": self.inspected_repositories, - "leased_repositories": list(self.leased_repositories), - "inspection_errors": [ - {"repository": repository, "error": error} - for repository, error in self.inspection_errors - ], - "actions": [ - { - "kind": action.kind.value, - "repository": action.repository, - "status": action.status, - "detail": action.detail, - } - for action in self.actions - ], - "dry_run": self.dry_run, - } - - def to_json(self) -> str: - """Serialize this report as stable UTF-8 JSON text.""" - return json.dumps(self.to_dict(), ensure_ascii=False, indent=2, sort_keys=True) - - def to_markdown(self) -> str: - """Render a concise GitHub Actions job summary.""" - lines = [ - "# Organization commercial-readiness coordinator", - "", - f"- Organization: `{self.organization}`", - f"- Repositories inspected: **{self.inspected_repositories}**", - f"- Repositories leased to dedicated writers: **{len(self.leased_repositories)}**", - f"- Inspection errors: **{len(self.inspection_errors)}**", - f"- Dry run: **{'yes' if self.dry_run else 'no'}**", - "", - "## Actions", - "", - "| Kind | Repository | Status | Detail |", - "|---|---|---|---|", - ] - if self.actions: - for action in self.actions: - detail = action.detail.replace("|", "\\|").replace("\n", " ") - lines.append( - f"| `{action.kind.value}` | `{action.repository}` | " - f"`{action.status}` | {detail} |" - ) - else: - lines.append("| — | — | `no_action` | No safe target was selected. |") - if self.inspection_errors: - lines.extend(["", "## Inspection errors", ""]) - for repository, error in self.inspection_errors: - lines.append(f"- `{repository}`: {error}") - return "\n".join(lines) + "\n" - - -class GitHubClient: - """Use the GitHub CLI as an authenticated, bounded REST transport.""" +from types import ModuleType - def __init__(self, token: str, *, timeout_seconds: int = 60) -> None: - """Initialize one authenticated GitHub credential with a bounded timeout.""" - if not token: - raise GitHubError("GH_TOKEN is required for organization coordination") - self._token = token - self._timeout_seconds = timeout_seconds +_MODULE_DIRECTORY = Path(__file__).resolve().parent +_CORE_MODULE_NAME = "_cwl_organization_commercial_readiness_core" +_DDD_MODULE_NAME = "_cwl_organization_commercial_readiness_ddd_contract" - @classmethod - def from_environment(cls, environ: Mapping[str, str] | None = None) -> GitHubClient: - """Build a client without accepting the repository-scoped GITHUB_TOKEN.""" - values = os.environ if environ is None else environ - token = str(values.get("GH_TOKEN") or "").strip() - if not token: - raise GitHubError("GH_TOKEN is required; no GITHUB_TOKEN fallback is permitted") - return cls(token) - def _redact_credential(self, value: str) -> str: - """Remove the exact GitHub credential before any diagnostic truncation.""" - return value.replace(self._token, "[REDACTED]") +def _load_sibling(module_name: str, filename: str) -> ModuleType: + """Load one sibling module under a stable private module name.""" + path = _MODULE_DIRECTORY / filename + spec = importlib.util.spec_from_file_location(module_name, path) + assert spec is not None and spec.loader is not None + module = importlib.util.module_from_spec(spec) + sys.modules[module_name] = module + spec.loader.exec_module(module) + return module - def request( - self, - path: str, - *, - method: str = "GET", - payload: Any = None, - ) -> Any: - """Call one GitHub REST endpoint and decode a bounded JSON response.""" - normalized_method = method.upper() - safe_method = ( - normalized_method - if normalized_method in SAFE_DIAGNOSTIC_METHODS - else "[REDACTED_METHOD]" - ) - safe_path = self._redact_credential(path) - args = ["gh", "api"] - if normalized_method != "GET": - args.extend(["--method", normalized_method]) - args.append(path) - input_text: str | None = None - if payload is not None: - args.extend(["--input", "-"]) - input_text = json.dumps(payload, separators=(",", ":")) - try: - completed = subprocess.run( - args, - input=input_text, - capture_output=True, - text=True, - timeout=self._timeout_seconds, - env={**os.environ, "GH_TOKEN": self._token}, - check=False, - ) - except (OSError, subprocess.TimeoutExpired) as exc: - raise GitHubError(f"GitHub API transport failed: {type(exc).__name__}") from exc - if completed.returncode != 0: - raw = (completed.stderr or completed.stdout or "GitHub API request failed").strip() - bounded = self._redact_credential(raw)[-900:] - raise GitHubError( - f"GitHub API {safe_method} {safe_path} failed: {bounded}" - ) - text = completed.stdout.strip() - if not text: - return None - try: - return json.loads(text) - except json.JSONDecodeError as exc: - raise GitHubError( - f"GitHub API returned invalid JSON for {safe_path}" - ) from exc - - def list_repositories(self, organization: str) -> list[dict[str, Any]]: - """Return every repository visible to the coordinator installation.""" - repositories: list[dict[str, Any]] = [] - page = 1 - while True: - result = self.request( - f"/orgs/{organization}/repos?type=all&sort=full_name&per_page=100&page={page}" - ) - batch = list(result or []) - repositories.extend(batch) - if len(batch) < 100: - return repositories - page += 1 - - def default_branch_sha(self, repository: str, default_branch: str) -> str: - """Resolve one exact commit for the repository default branch.""" - branch_ref = quote(default_branch, safe="") - result = self.request(f"/repos/{repository}/commits/{branch_ref}") - sha = str((result or {}).get("sha") or "") - if not re.fullmatch(r"[0-9a-fA-F]{40}", sha): - raise GitHubError(f"repository {repository} returned an invalid default-branch SHA") - return sha.lower() - - def list_workflows(self, repository: str, exact_ref: str) -> tuple[WorkflowRecord, ...]: - """Return a fail-closed, memory-bounded workflow and writer-source inventory.""" - workflows: list[WorkflowRecord] = [] - source_count = 0 - source_bytes = 0 - page = 1 - while True: - result = self.request( - f"/repos/{repository}/actions/workflows?per_page=100&page={page}" - ) - batch = list((result or {}).get("workflows") or []) - if len(workflows) + len(batch) > MAX_WORKFLOW_RECORDS_PER_REPOSITORY: - raise GitHubError( - f"repository {repository} exceeded workflow metadata limit of " - f"{MAX_WORKFLOW_RECORDS_PER_REPOSITORY}" - ) - for raw in batch: - workflow_id = int(raw.get("id") or 0) - path = str(raw.get("path") or "") - name = str(raw.get("name") or path) - state = str(raw.get("state") or "unknown") - content: str | None = None - content_sha = "" - if ( - path - and not path.startswith("dynamic/") - and _writer_signal(name, path) - ): - source_count += 1 - if source_count > MAX_WORKFLOW_SOURCES_PER_REPOSITORY: - raise GitHubError( - f"repository {repository} exceeded workflow source limit of " - f"{MAX_WORKFLOW_SOURCES_PER_REPOSITORY}" - ) - encoded_path = quote(path, safe="/") - try: - source = self.request( - f"/repos/{repository}/contents/{encoded_path}?ref={exact_ref}" - ) - source_size = ( - int(source.get("size") or 0) - if isinstance(source, dict) - else 0 - ) - except (GitHubError, ValueError): - source = None - source_size = 0 - if ( - isinstance(source, dict) - and source.get("type") == "file" - and source_size <= MAX_WORKFLOW_SOURCE_BYTES_PER_FILE - and source.get("encoding") == "base64" - ): - if ( - source_bytes + source_size - > MAX_WORKFLOW_SOURCE_BYTES_PER_REPOSITORY - ): - raise GitHubError( - f"repository {repository} exceeded workflow source byte limit of " - f"{MAX_WORKFLOW_SOURCE_BYTES_PER_REPOSITORY}" - ) - try: - decoded = base64.b64decode( - str(source.get("content") or ""), validate=True - ) - content = decoded.decode("utf-8") - content_sha = str(source.get("sha") or "") - except (ValueError, UnicodeDecodeError): - content = None - content_sha = "" - else: - source_bytes += source_size - workflows.append( - WorkflowRecord( - workflow_id=workflow_id, - name=name, - path=path, - state=state, - content_sha=content_sha, - content=content, - ) - ) - if len(batch) < 100: - return tuple(workflows) - page += 1 - - def list_active_runs(self, repository: str) -> tuple[RunRecord, ...]: - """Return all queued and running workflow evidence for writer lease detection.""" - records: list[RunRecord] = [] - for status in ("queued", "in_progress", "waiting", "pending", "requested"): - page = 1 - while True: - result = self.request( - f"/repos/{repository}/actions/runs?status={status}&per_page=100&page={page}" - ) - batch = list((result or {}).get("workflow_runs") or []) - for raw in batch: - records.append( - RunRecord( - run_id=int(raw.get("id") or 0), - name=str(raw.get("name") or ""), - path=str(raw.get("path") or ""), - status=str(raw.get("status") or status), - head_sha=str(raw.get("head_sha") or ""), - ) - ) - if len(batch) < 100: - break - page += 1 - return tuple(records) - - def list_open_pulls(self, repository: str) -> tuple[PullRequestRecord, ...]: - """Return all open pull requests with exact stack and head identity.""" - records: list[PullRequestRecord] = [] - page = 1 - while True: - result = self.request( - f"/repos/{repository}/pulls?state=open&per_page=100&page={page}" - ) - batch = list(result or []) - for raw in batch: - records.append( - PullRequestRecord( - number=int(raw.get("number") or 0), - draft=bool(raw.get("draft")), - base_ref=str((raw.get("base") or {}).get("ref") or ""), - head_sha=str((raw.get("head") or {}).get("sha") or ""), - updated_at=str(raw.get("updated_at") or ""), - ) - ) - if len(batch) < 100: - return tuple(records) - page += 1 - - def snapshot(self, repository: str, default_branch: str) -> RepositorySnapshot: - """Materialize one snapshot and reject concurrent default-branch movement.""" - before = self.default_branch_sha(repository, default_branch) - workflows = self.list_workflows(repository, before) - runs = self.list_active_runs(repository) - pulls = self.list_open_pulls(repository) - after = self.default_branch_sha(repository, default_branch) - if before != after: - raise SnapshotChanged( - f"default branch moved while inspecting {repository}: {before} -> {after}" - ) - return RepositorySnapshot( - full_name=repository, - default_branch=default_branch, - default_sha=before, - workflows=workflows, - active_runs=runs, - open_pulls=pulls, - ) - - def dispatch_review_repair(self, repository: str, base_branch: str) -> None: - """Ask the established central scheduler for one bounded repair attempt.""" - self.request( - f"/repos/{CENTRAL_REPOSITORY}/dispatches", - method="POST", - payload={ - "event_type": CENTRAL_REPAIR_EVENT, - "client_payload": { - "target_repository": repository, - "base_branch": base_branch, - "max_prs": "50", - "max_dispatches": "1", - "retry_hours": "1", - "dry_run": False, - }, - }, - ) - - def dispatch_product_workflow( - self, repository: str, workflow_id: int, default_branch: str - ) -> None: - """Dispatch an explicitly opted-in repository-local development entrypoint.""" - self.request( - f"/repos/{repository}/actions/workflows/{workflow_id}/dispatches", - method="POST", - payload={"ref": default_branch}, - ) - - -def _writer_signal(name: str, path: str) -> bool: - """Return whether workflow identity indicates a repository writer.""" - identity = f"{name}\n{path}" - return bool(WRITER_SIGNAL_RE.search(identity)) and not bool( - MERGE_SCHEDULER_RE.search(identity) - ) - - -def is_dedicated_writer_workflow(workflow: WorkflowRecord) -> bool: - """Return whether an active scheduled workflow owns the repository writer lease.""" - if workflow.state != "active" or not _writer_signal(workflow.name, workflow.path): - return False - if workflow.content is None: - return True - return bool(SCHEDULE_RE.search(workflow.content)) - - -def is_live_writer_run(run: RunRecord) -> bool: - """Return whether a queued or running high-signal workflow owns a live lease.""" - return run.status in ACTIVE_RUN_STATES and _writer_signal(run.name, run.path) - - -def has_domain_driven_development_contract(source: str) -> bool: - """Return whether one entrypoint accepts the complete DDD repair contract.""" - return DDD_ENTRYPOINT_MARKER in source and all( - term in source for term in DDD_CONTRACT_TERMS - ) - - -def is_manual_product_entrypoint(workflow: WorkflowRecord) -> bool: - """Return whether a workflow safely opts in to central product development.""" - source = workflow.content - if workflow.state != "active" or source is None: - return False - return all( - ( - ENTRYPOINT_MARKER in source, - has_domain_driven_development_contract(source), - bool(WORKFLOW_DISPATCH_RE.search(source)), - not bool(SCHEDULE_RE.search(source)), - "NVIDIA_NIM_API_KEY" in source, - "COPILOT_GITHUB_TOKEN" not in source, - "concurrency:" in source, - _writer_signal(workflow.name, workflow.path), - ) - ) - - -def repository_is_eligible(repository: Mapping[str, Any], organization: str) -> bool: - """Return whether one owned repository can participate in organization coordination.""" - full_name = str(repository.get("full_name") or "") - permissions = repository.get("permissions") or {} - write_capable = any(bool(permissions.get(key)) for key in ("push", "maintain", "admin")) - return all( - ( - full_name.startswith(f"{organization}/"), - full_name != f"{organization}/.github", - not bool(repository.get("archived")), - not bool(repository.get("disabled")), - not bool(repository.get("fork")), - bool(repository.get("default_branch")), - write_capable, - ) - ) - - -def choose_rotating(items: Sequence[Any], seed: int, limit: int) -> tuple[Any, ...]: - """Choose a bounded cyclic window so later repositories are not starved.""" - if not items or limit <= 0: - return () - count = min(limit, len(items)) - start = seed % len(items) - return tuple(items[(start + offset) % len(items)] for offset in range(count)) - - -def _has_writer_lease(snapshot: RepositorySnapshot) -> bool: - """Return whether static or live evidence assigns this repository elsewhere.""" - return any(is_dedicated_writer_workflow(item) for item in snapshot.workflows) or any( - is_live_writer_run(item) for item in snapshot.active_runs - ) - - -def _eligible_review_snapshot(snapshot: RepositorySnapshot) -> bool: - """Return whether generic review repair is safe for at least one direct PR.""" - return any( - not pull.draft and pull.base_ref == snapshot.default_branch - for pull in snapshot.open_pulls - ) - - -def _manual_product_workflow(snapshot: RepositorySnapshot) -> WorkflowRecord | None: - """Return the first deterministic opted-in manual development entrypoint.""" - matches = sorted( - (item for item in snapshot.workflows if is_manual_product_entrypoint(item)), - key=lambda item: (item.path, item.workflow_id), - ) - return matches[0] if matches else None - - -def build_plan( - snapshots: Iterable[RepositorySnapshot], - *, - rotation_seed: int, - max_review_dispatches: int = 1, - max_development_dispatches: int = 1, -) -> tuple[PlanItem, ...]: - """Select independent bounded review and product targets from exact snapshots.""" - usable = tuple( - sorted( - ( - item - for item in snapshots - if item.full_name != CENTRAL_REPOSITORY and not _has_writer_lease(item) - ), - key=lambda item: item.full_name, - ) - ) - review_candidates = tuple(item for item in usable if _eligible_review_snapshot(item)) - development_candidates = tuple( - (item, workflow) - for item in usable - if not item.open_pulls - for workflow in (_manual_product_workflow(item),) - if workflow is not None - ) - plan: list[PlanItem] = [] - for item in choose_rotating(review_candidates, rotation_seed, max_review_dispatches): - plan.append( - PlanItem( - kind=ActionKind.REVIEW_REPAIR, - repository=item.full_name, - default_branch=item.default_branch, - expected_fingerprint=item.fingerprint, - ) - ) - for item, workflow in choose_rotating( - development_candidates, rotation_seed, max_development_dispatches - ): - plan.append( - PlanItem( - kind=ActionKind.PRODUCT_DEVELOPMENT, - repository=item.full_name, - default_branch=item.default_branch, - expected_fingerprint=item.fingerprint, - workflow_id=workflow.workflow_id, - ) - ) - return tuple(plan) - - -def _bounded_error(exc: BaseException) -> str: - """Return a stable, bounded error description without stack or credential data.""" - text = f"{type(exc).__name__}: {exc}".replace("\n", " ") - return text[:1000] - - -def run_once( - client: Any, - *, - organization: str, - rotation_seed: int, - max_repositories: int = 200, - max_review_dispatches: int = 1, - max_development_dispatches: int = 1, - dry_run: bool = False, -) -> RunReport: - """Inspect the organization, revalidate targets, and dispatch bounded work.""" - if organization != DEFAULT_ORGANIZATION: - raise GitHubError( - f"organization must be {DEFAULT_ORGANIZATION}; foreign control planes are not supported" - ) - raw_repositories = client.list_repositories(organization) - eligible = sorted( - ( - item - for item in raw_repositories - if repository_is_eligible(item, organization) - ), - key=lambda item: str(item.get("full_name") or ""), - ) - selected_repositories = choose_rotating(eligible, rotation_seed, max_repositories) - snapshots: list[RepositorySnapshot] = [] - errors: list[tuple[str, str]] = [] - leased: list[str] = [] - for repository in selected_repositories: - full_name = str(repository["full_name"]) - default_branch = str(repository["default_branch"]) - try: - current = client.snapshot(full_name, default_branch) - except (GitHubError, SnapshotChanged) as exc: - errors.append((full_name, _bounded_error(exc))) - continue - snapshots.append(current) - if _has_writer_lease(current): - leased.append(full_name) - plan = build_plan( - snapshots, - rotation_seed=rotation_seed, - max_review_dispatches=max_review_dispatches, - max_development_dispatches=max_development_dispatches, - ) - actions: list[ActionResult] = [] - for item in plan: - try: - live = client.snapshot(item.repository, item.default_branch) - except (GitHubError, SnapshotChanged) as exc: - actions.append( - ActionResult( - kind=item.kind, - repository=item.repository, - status="skipped_refetch_error", - detail=_bounded_error(exc), - ) - ) - continue - if _has_writer_lease(live): - actions.append( - ActionResult( - kind=item.kind, - repository=item.repository, - status="skipped_writer_lease", - detail="a dedicated or live writer appeared before dispatch", - ) - ) - continue - if live.fingerprint != item.expected_fingerprint: - actions.append( - ActionResult( - kind=item.kind, - repository=item.repository, - status="skipped_state_changed", - detail="repository, workflow, run, or pull-request state moved before dispatch", - ) - ) - continue - if dry_run: - actions.append( - ActionResult( - kind=item.kind, - repository=item.repository, - status="dry_run", - detail="exact state revalidated; mutation intentionally suppressed", - ) - ) - continue - try: - if item.kind is ActionKind.REVIEW_REPAIR: - client.dispatch_review_repair(item.repository, item.default_branch) - else: - if item.workflow_id is None: - raise GitHubError("product-development plan omitted workflow identity") - client.dispatch_product_workflow( - item.repository, item.workflow_id, item.default_branch - ) - except GitHubError as exc: - actions.append( - ActionResult( - kind=item.kind, - repository=item.repository, - status="dispatch_failed", - detail=_bounded_error(exc), - ) - ) - else: - actions.append( - ActionResult( - kind=item.kind, - repository=item.repository, - status="dispatched", - detail="exact state revalidated and bounded workflow dispatched", - ) - ) - return RunReport( - organization=organization, - inspected_repositories=len(snapshots), - leased_repositories=tuple(sorted(leased)), - inspection_errors=tuple(errors), - actions=tuple(actions), - dry_run=dry_run, - ) - - -def _non_negative_int(value: str) -> int: - """Parse one non-negative integer command-line bound.""" - parsed = int(value) - if parsed < 0: - raise argparse.ArgumentTypeError("value must be zero or greater") - return parsed +_core = _load_sibling( + _CORE_MODULE_NAME, "organization_commercial_readiness_core.py" +) +_ddd = _load_sibling( + _DDD_MODULE_NAME, "organization_commercial_readiness_ddd_contract.py" +) -def _parser() -> argparse.ArgumentParser: - """Build the command-line parser used by workflow and local dry runs.""" - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument("--organization", default=DEFAULT_ORGANIZATION) - parser.add_argument("--rotation-seed", type=int, default=0) - parser.add_argument("--max-repositories", type=_non_negative_int, default=200) - parser.add_argument("--max-review-dispatches", type=_non_negative_int, default=1) - parser.add_argument("--max-development-dispatches", type=_non_negative_int, default=1) - parser.add_argument("--dry-run", action="store_true") - parser.add_argument("--json-output", type=Path) - return parser +_core.has_domain_driven_development_contract = ( + _ddd.has_domain_driven_development_contract +) +_core.DDD_CONTRACT_TERMS = _ddd.DDD_CONTRACT_TERMS +for _export_name in dir(_core): + if not _export_name.startswith("__"): + globals()[_export_name] = getattr(_core, _export_name) -def main( - argv: Sequence[str] | None = None, - *, - client_factory: Callable[[], Any] | None = None, -) -> int: - """Run the coordinator CLI and persist auditable receipts.""" - parser = _parser() - try: - args = parser.parse_args(argv) - except SystemExit: - return 2 - if not ORGANIZATION_RE.fullmatch(args.organization): - print("invalid organization", file=sys.stderr) - return 2 - factory = client_factory or GitHubClient.from_environment - try: - client = factory() - report = run_once( - client, - organization=args.organization, - rotation_seed=args.rotation_seed, - max_repositories=args.max_repositories, - max_review_dispatches=args.max_review_dispatches, - max_development_dispatches=args.max_development_dispatches, - dry_run=args.dry_run, - ) - except (GitHubError, SnapshotChanged, ValueError) as exc: - print(_bounded_error(exc), file=sys.stderr) - 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") - 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: - handle.write(report.to_markdown()) - all_selected_inspections_failed = ( - report.inspected_repositories == 0 and bool(report.inspection_errors) - ) - all_planned_dispatches_failed = bool(report.actions) and all( - action.status == "dispatch_failed" for action in report.actions - ) - return 1 if all_selected_inspections_failed or all_planned_dispatches_failed else 0 +DDD_CONTRACT_CAPABILITIES = _ddd.DDD_CONTRACT_CAPABILITIES +DDD_CONTRACT_TERMS = _ddd.DDD_CONTRACT_TERMS +has_domain_driven_development_contract = ( + _ddd.has_domain_driven_development_contract +) -if __name__ == "__main__": # pragma: no cover - exercised through main() - raise SystemExit(main()) +if __name__ == "__main__": + raise SystemExit(_core.main()) diff --git a/tests/test_organization_commercial_readiness_loop_ddd_binding.py b/tests/test_organization_commercial_readiness_loop_ddd_binding.py new file mode 100644 index 0000000000..7a7cb3ad59 --- /dev/null +++ b/tests/test_organization_commercial_readiness_loop_ddd_binding.py @@ -0,0 +1,271 @@ +"""Regression tests for executable DDD product-entrypoint binding.""" + +from __future__ import annotations + +import runpy +import sys +from pathlib import Path + +import pytest + +from organization_commercial_readiness_fixtures import manual_workflow, workflow +from scripts.ci import organization_commercial_readiness_ddd_contract as contract +from scripts.ci import organization_commercial_readiness_loop as coordinator + + +def _source() -> str: + """Return the canonical machine-bound multilingual workflow fixture.""" + source = manual_workflow().content + assert source is not None + return source + + +def _replace_command(source: str, replacement: str) -> str: + """Replace the canonical multiline product-agent command once.""" + original = ( + "python scripts/automation/commercial_product_development.py \\\n" + " --prompt-env CWL_PRODUCT_AGENT_PROMPT \\\n" + " --architecture-contract-env CWL_DDD_CONTRACT_CAPABILITIES" + ) + assert original in source + return source.replace(original, replacement, 1) + + +def test_accepts_multilingual_prompt_option_forms_and_environment_prefix() -> None: + """Eligibility depends on bound capabilities, not copied English prose.""" + source = _source() + assert "Domain-Driven Design" not in source + assert coordinator.has_domain_driven_development_contract(source) + assert coordinator.is_manual_product_entrypoint(manual_workflow()) + + equals_options = source.replace( + "--prompt-env CWL_PRODUCT_AGENT_PROMPT", + "--prompt-env=CWL_PRODUCT_AGENT_PROMPT", + ).replace( + "--architecture-contract-env CWL_DDD_CONTRACT_CAPABILITIES", + "--architecture-contract-env=CWL_DDD_CONTRACT_CAPABILITIES", + ) + assert coordinator.has_domain_driven_development_contract(equals_options) + + prefixed = source.replace( + "python scripts/automation/commercial_product_development.py", + "env MODE=bounded python scripts/automation/commercial_product_development.py", + 1, + ) + assert coordinator.has_domain_driven_development_contract(prefixed) + + single_quoted = source.replace( + 'CWL_DDD_CONTRACT_VERSION: "1"', "CWL_DDD_CONTRACT_VERSION: '1'" + ) + plain = source.replace( + 'CWL_DDD_CONTRACT_VERSION: "1"', "CWL_DDD_CONTRACT_VERSION: 1" + ) + assert coordinator.has_domain_driven_development_contract(single_quoted) + assert coordinator.has_domain_driven_development_contract(plain) + + +def test_rejects_missing_extra_or_version_drift() -> None: + """Version one accepts exactly the approved strategic and tactical set.""" + source = _source() + for index, capability in enumerate(sorted(contract.DDD_CONTRACT_CAPABILITIES)): + assert not coordinator.has_domain_driven_development_contract( + source.replace(capability, f"omitted_{index}", 1) + ) + assert not coordinator.has_domain_driven_development_contract( + source.replace( + " value_object\n", " value_object unexpected_capability\n", 1 + ) + ) + for replacement in ('2', '"2"', "'2'", "invalid", '"1\''): + assert not coordinator.has_domain_driven_development_contract( + source.replace( + 'CWL_DDD_CONTRACT_VERSION: "1"', + f"CWL_DDD_CONTRACT_VERSION: {replacement}", + 1, + ) + ) + assert not coordinator.has_domain_driven_development_contract( + source.replace(' CWL_DDD_CONTRACT_VERSION: "1"\n', "", 1) + ) + assert not coordinator.has_domain_driven_development_contract( + source.replace( + ' CWL_DDD_CONTRACT_VERSION: "1"\n', + ' CWL_DDD_CONTRACT_VERSION: "1"\n' + " CWL_DDD_CONTRACT_VERSION: 1\n", + 1, + ) + ) + assert not coordinator.has_domain_driven_development_contract( + source.replace("# cwl-ddd-architecture-audit: required\n", "", 1) + ) + + +def test_rejects_unscoped_duplicate_or_empty_environment_values() -> None: + """Only one root environment may own one prompt and capability block.""" + source = _source() + invalid = [ + source.replace("env:\n", "metadata:\n", 1), + source + "\nenv:\n OTHER_VALUE: present\n", + source.replace( + "CWL_PRODUCT_AGENT_PROMPT: |", "UNUSED_PROMPT: |", 1 + ), + source.replace( + " CWL_PRODUCT_AGENT_PROMPT: |\n", + " CWL_PRODUCT_AGENT_PROMPT: |\n" + " duplicate\n" + " CWL_PRODUCT_AGENT_PROMPT: |\n", + 1, + ), + source.replace( + "CWL_DDD_CONTRACT_CAPABILITIES: >-", "UNUSED_CAPABILITIES: >-", 1 + ), + source.replace( + " CWL_DDD_CONTRACT_CAPABILITIES: >-\n", + " CWL_DDD_CONTRACT_CAPABILITIES: >-\n" + " bounded_context\n" + " CWL_DDD_CONTRACT_CAPABILITIES: >-\n", + 1, + ), + source.replace( + " 제품 책임과 재사용 경계를 먼저 확인하고 구매자가 체감할 한 단위를 개발한다.\n", + "", + 1, + ).replace( + " 디렉터리, 패키지, API, 데이터베이스, 테스트와 문서의 소유권을 함께 맞춘다.\n", + "", + 1, + ), + ] + for candidate in invalid: + assert not coordinator.has_domain_driven_development_contract(candidate) + + +def test_rejects_comments_unused_prose_wrong_bindings_and_non_agents() -> None: + """Comments and inert YAML cannot impersonate an executable agent contract.""" + source = _source() + comments_only = ( + "# cwl-ddd-architecture-audit: required\n" + + "\n".join( + f"# {item}" for item in sorted(contract.DDD_CONTRACT_CAPABILITIES) + ) + + "\n# cwl-ddd-prompt-binding: v1\n" + + "# --prompt-env CWL_PRODUCT_AGENT_PROMPT\n" + + "# --architecture-contract-env CWL_DDD_CONTRACT_CAPABILITIES\n" + ) + assert not coordinator.has_domain_driven_development_contract(comments_only) + assert not coordinator.has_domain_driven_development_contract( + source.replace(" CWL_PRODUCT_AGENT_PROMPT: |", " NOTES: |", 1) + ) + for old, new in ( + ("# cwl-ddd-prompt-binding: v1", "# unbound"), + ("--prompt-env CWL_PRODUCT_AGENT_PROMPT", "--prompt-env OTHER_PROMPT"), + ( + "--architecture-contract-env CWL_DDD_CONTRACT_CAPABILITIES", + "--architecture-contract-env OTHER_CAPABILITIES", + ), + ( + "--prompt-env CWL_PRODUCT_AGENT_PROMPT", + "--prompt-env CWL_PRODUCT_AGENT_PROMPT " + "--prompt-env CWL_PRODUCT_AGENT_PROMPT", + ), + ): + assert not coordinator.has_domain_driven_development_contract( + source.replace(old, new, 1) + ) + for executable in (":", "[", "echo", "export", "false", "printf", "test", "true"): + assert not coordinator.has_domain_driven_development_contract( + source.replace( + "python scripts/automation/commercial_product_development.py", + executable, + 1, + ) + ) + assert not coordinator.has_domain_driven_development_contract( + _replace_command( + source, + "--prompt-env CWL_PRODUCT_AGENT_PROMPT " + "--architecture-contract-env CWL_DDD_CONTRACT_CAPABILITIES", + ) + ) + + +def test_rejects_split_malformed_and_dangling_commands() -> None: + """Both environment names must reach one well-formed command segment.""" + source = _source() + for operator in (";", "&&", "||", "|", "&"): + assert not coordinator.has_domain_driven_development_contract( + _replace_command( + source, + "product-agent --prompt-env CWL_PRODUCT_AGENT_PROMPT " + f"{operator} product-agent " + "--architecture-contract-env CWL_DDD_CONTRACT_CAPABILITIES", + ) + ) + assert not coordinator.has_domain_driven_development_contract( + _replace_command(source, "; product-agent --prompt-env CWL_PRODUCT_AGENT_PROMPT") + ) + assert not coordinator.has_domain_driven_development_contract( + source.replace( + "python scripts/automation/commercial_product_development.py", + 'product-agent "unterminated', + 1, + ) + ) + assert not coordinator.has_domain_driven_development_contract( + source.replace( + " --architecture-contract-env CWL_DDD_CONTRACT_CAPABILITIES\n", + "", + 1, + ) + ) + assert not coordinator.has_domain_driven_development_contract( + source.replace( + " --architecture-contract-env CWL_DDD_CONTRACT_CAPABILITIES\n", + " --architecture-contract-env\n", + 1, + ) + ) + + +def test_rejects_marker_and_flags_distributed_across_run_blocks() -> None: + """A marker in one step cannot authorize flags executed by another step.""" + source = _source() + bound = ( + " run: |\n" + " # cwl-ddd-prompt-binding: v1\n" + " python scripts/automation/commercial_product_development.py \\\n" + " --prompt-env CWL_PRODUCT_AGENT_PROMPT \\\n" + " --architecture-contract-env CWL_DDD_CONTRACT_CAPABILITIES\n" + ) + split = ( + " run: |\n" + " # cwl-ddd-prompt-binding: v1\n" + " product-agent --prompt-env CWL_PRODUCT_AGENT_PROMPT\n" + " - run: |\n" + " product-agent --architecture-contract-env CWL_DDD_CONTRACT_CAPABILITIES\n" + ) + assert bound in source + assert not coordinator.has_domain_driven_development_contract( + source.replace(bound, split, 1) + ) + + +def test_private_command_edges_and_compatibility_script_mode( + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Cover assignment-only, trailing-operator, and direct-script boundaries.""" + assert contract._executable(("env", "MODE=bounded")) is None + assert list(contract._shell_segments("product-agent ;")) == [("product-agent",)] + + path = Path(coordinator.__file__) + monkeypatch.setattr(sys, "argv", [str(path), "--organization", "invalid/name"]) + with pytest.raises(SystemExit) as raised: + runpy.run_path(str(path), run_name="__main__") + assert raised.value.code == 2 + + +def test_manual_entrypoint_still_rejects_non_contract_workflows() -> None: + """The compatibility facade keeps the original fail-closed API surface.""" + assert not coordinator.is_manual_product_entrypoint( + workflow(content="# cwl-org-commercial-entrypoint: v1\n") + ) From fcbd592099790c0efea82bce6e670d97458ad8d9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 1 Sep 2026 13:49:13 +0900 Subject: [PATCH 18/24] docs(automation): align hourly DDD binding contract --- .../organization-commercial-readiness-loop.md | 29 +++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/docs/doctoring/organization-commercial-readiness-loop.md b/docs/doctoring/organization-commercial-readiness-loop.md index 474167f8bd..6b70a43365 100644 --- a/docs/doctoring/organization-commercial-readiness-loop.md +++ b/docs/doctoring/organization-commercial-readiness-loop.md @@ -30,16 +30,41 @@ The existing organization merge scheduler continues to own review dispatch, bran ## Product-development boundary -Product development is dispatched only when a repository has zero open pull requests and exposes one active, manual-only, explicitly marked workflow: +Product development is dispatched only when a repository has zero open pull requests and exposes one active, manual-only, explicitly marked workflow. The repository owns the human-readable prompt, which may use any language. Eligibility depends on a versioned machine-readable capability set and an executable binding rather than copied English prose. ```yaml # cwl-org-commercial-entrypoint: v1 # cwl-ddd-architecture-audit: required on: workflow_dispatch: + +concurrency: + group: product-development + +env: + NVIDIA_API_KEY: ${{ secrets.NVIDIA_NIM_API_KEY }} + CWL_DDD_CONTRACT_VERSION: "1" + CWL_DDD_CONTRACT_CAPABILITIES: >- + aggregate anti_corruption_layer bounded_context context_map + directory_ownership domain_event domain_service entity invariant + minimal_shared_kernel product_gap_baseline repository + subdomain_classification ubiquitous_language value_object + CWL_PRODUCT_AGENT_PROMPT: | + Deliver one buyer-visible increment through the repository-owned product agent. + +jobs: + develop: + steps: + - run: | + # cwl-ddd-prompt-binding: v1 + product-agent \ + --prompt-env CWL_PRODUCT_AGENT_PROMPT \ + --architecture-contract-env CWL_DDD_CONTRACT_CAPABILITIES ``` -The entrypoint must contain an explicit `concurrency` contract, use `NVIDIA_NIM_API_KEY`, omit `COPILOT_GITHUB_TOKEN`, have no schedule of its own, and carry a commercial/product-development identity. It must also embed the complete Domain-Driven Design repair contract rather than merely mention DDD. The machine-checked contract requires core, supporting, and generic subdomains; Bounded Context; Context Map; Ubiquitous Language; Aggregate; Entity; Value Object; Domain Service; Repository; Domain Event; Invariant; Anti-Corruption Layer; Shared Kernel; directory paths; and `docs/product-technical-gap-baseline.md`. +The root workflow `env` mapping must contain exactly one non-empty `CWL_PRODUCT_AGENT_PROMPT`, one exact version-one capability block, and one version value. The capability set is closed for version one; missing, misspelled, duplicated, or unversioned values fail closed. The prompt and capability environment names must reach the same non-comment shell command under the binding marker. Comments, unrelated YAML, nested or duplicate environment scopes, inert block scalars, shell built-ins, malformed quoting, dangling continuations, and flags split across commands do not satisfy the contract. + +The capability IDs cover the strategic and tactical Domain-Driven Design obligations required by the organization: core/supporting/generic subdomain classification, Bounded Context, Context Map, Ubiquitous Language, Aggregate, Entity, Value Object, Domain Service, Repository, Domain Event, Invariant, Anti-Corruption Layer, minimal Shared Kernel, directory ownership, and product-gap baseline traceability. Human-readable instructions can evolve independently as long as the repository product-agent adapter consumes both bound inputs and implements the declared version. Each hourly product increment must identify the owning product responsibility before selecting a repository, then compare the live directory tree, module/package names, API, database objects, tests, and documentation with that responsibility. Misleading directory paths, generic `utils`/`common` dumping grounds that own domain behavior, infrastructure imports inside the domain model, cross-context database access, obsolete product names, or customer-visible implementation boundaries are architecture defects, not cosmetic debt. When one can be corrected safely in the bounded increment, the agent moves the code and updates imports, package manifests, call sites, migrations, tests, ADRs, diagrams, and compatibility adapters in the same pull request. From 2906e6147391b1ba01f8b1b2ec9670fa3318e0ad Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 1 Sep 2026 13:57:12 +0900 Subject: [PATCH 19/24] fix(automation): preserve coordinator facade module identity --- .../organization_commercial_readiness_loop.py | 21 ++++-------- ...zation_commercial_readiness_loop_facade.py | 34 +++++++++++++++++++ 2 files changed, 40 insertions(+), 15 deletions(-) create mode 100644 tests/test_organization_commercial_readiness_loop_facade.py diff --git a/scripts/ci/organization_commercial_readiness_loop.py b/scripts/ci/organization_commercial_readiness_loop.py index 2d89418836..11e56c1e2f 100644 --- a/scripts/ci/organization_commercial_readiness_loop.py +++ b/scripts/ci/organization_commercial_readiness_loop.py @@ -14,13 +14,12 @@ def _load_sibling(module_name: str, filename: str) -> ModuleType: - """Load one sibling module under a stable private module name.""" + """Load one trusted sibling module under a stable private module name.""" path = _MODULE_DIRECTORY / filename spec = importlib.util.spec_from_file_location(module_name, path) - assert spec is not None and spec.loader is not None - module = importlib.util.module_from_spec(spec) + module = importlib.util.module_from_spec(spec) # type: ignore[arg-type] sys.modules[module_name] = module - spec.loader.exec_module(module) + spec.loader.exec_module(module) # type: ignore[union-attr] return module @@ -35,17 +34,9 @@ def _load_sibling(module_name: str, filename: str) -> ModuleType: _ddd.has_domain_driven_development_contract ) _core.DDD_CONTRACT_TERMS = _ddd.DDD_CONTRACT_TERMS - -for _export_name in dir(_core): - if not _export_name.startswith("__"): - globals()[_export_name] = getattr(_core, _export_name) - -DDD_CONTRACT_CAPABILITIES = _ddd.DDD_CONTRACT_CAPABILITIES -DDD_CONTRACT_TERMS = _ddd.DDD_CONTRACT_TERMS -has_domain_driven_development_contract = ( - _ddd.has_domain_driven_development_contract -) - +_core.DDD_CONTRACT_CAPABILITIES = _ddd.DDD_CONTRACT_CAPABILITIES if __name__ == "__main__": raise SystemExit(_core.main()) + +sys.modules[__name__] = _core diff --git a/tests/test_organization_commercial_readiness_loop_facade.py b/tests/test_organization_commercial_readiness_loop_facade.py new file mode 100644 index 0000000000..2b885b95b9 --- /dev/null +++ b/tests/test_organization_commercial_readiness_loop_facade.py @@ -0,0 +1,34 @@ +"""Regression tests for the commercial-readiness compatibility facade.""" + +from __future__ import annotations + +import runpy +import sys +from pathlib import Path + +import pytest + +from scripts.ci import organization_commercial_readiness_ddd_contract as contract +from scripts.ci import organization_commercial_readiness_loop as coordinator + + +def test_facade_monkeypatches_reach_core_function_globals( + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Existing tests and callers patch the same module used by core functions.""" + sentinel = object() + monkeypatch.setattr(coordinator, "build_plan", sentinel) + assert coordinator.run_once.__globals__["build_plan"] is sentinel + + +def test_facade_direct_script_mode_delegates_to_core( + monkeypatch: pytest.MonkeyPatch, +) -> None: + """The stable script path retains the original argument-validation behavior.""" + path = Path(contract.__file__).with_name( + "organization_commercial_readiness_loop.py" + ) + monkeypatch.setattr(sys, "argv", [str(path), "--organization", "invalid/name"]) + with pytest.raises(SystemExit) as raised: + runpy.run_path(str(path), run_name="__main__") + assert raised.value.code == 2 From 199cfa5d102c12f7e755ed1a6095de0dda50bcba Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 1 Sep 2026 14:27:08 +0900 Subject: [PATCH 20/24] fix(automation): preserve readiness facade identity --- .../organization_commercial_readiness_loop.py | 9 ++++++ ...zation_commercial_readiness_loop_facade.py | 18 ++++++++++++ ...zation_commercial_readiness_loop_policy.py | 13 +++++++++ tests/test_pr_review_fix_scheduler.py | 10 +++++++ tests/test_pr_review_merge_scheduler.py | 28 +++++++++++++++++++ 5 files changed, 78 insertions(+) diff --git a/scripts/ci/organization_commercial_readiness_loop.py b/scripts/ci/organization_commercial_readiness_loop.py index 11e56c1e2f..78dd6aac84 100644 --- a/scripts/ci/organization_commercial_readiness_loop.py +++ b/scripts/ci/organization_commercial_readiness_loop.py @@ -39,4 +39,13 @@ def _load_sibling(module_name: str, filename: str) -> ModuleType: if __name__ == "__main__": raise SystemExit(_core.main()) +# The public import intentionally aliases the core object so monkeypatches reach +# the globals used by its functions. Preserve the facade's import identity on +# that object as well: standard module runners consult ``__spec__`` and its +# loader after import, and the core's private identity cannot load this public +# module name. +_core.__name__ = __name__ +_core.__package__ = __package__ +_core.__loader__ = __loader__ +_core.__spec__ = __spec__ sys.modules[__name__] = _core diff --git a/tests/test_organization_commercial_readiness_loop_facade.py b/tests/test_organization_commercial_readiness_loop_facade.py index 2b885b95b9..63578c7f49 100644 --- a/tests/test_organization_commercial_readiness_loop_facade.py +++ b/tests/test_organization_commercial_readiness_loop_facade.py @@ -32,3 +32,21 @@ def test_facade_direct_script_mode_delegates_to_core( with pytest.raises(SystemExit) as raised: runpy.run_path(str(path), run_name="__main__") assert raised.value.code == 2 + + +def test_imported_facade_remains_executable_by_public_module_name( + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Importing the facade must not corrupt its public module identity.""" + monkeypatch.setattr( + sys, + "argv", + [coordinator.__file__, "--organization", "invalid/name"], + ) + with pytest.warns(RuntimeWarning, match="found in sys.modules"): + with pytest.raises(SystemExit) as raised: + runpy.run_module( + "scripts.ci.organization_commercial_readiness_loop", + run_name="__main__", + ) + assert raised.value.code == 2 diff --git a/tests/test_organization_commercial_readiness_loop_policy.py b/tests/test_organization_commercial_readiness_loop_policy.py index c1d14529ec..d3e91e033d 100644 --- a/tests/test_organization_commercial_readiness_loop_policy.py +++ b/tests/test_organization_commercial_readiness_loop_policy.py @@ -22,6 +22,7 @@ is_manual_product_entrypoint, repository_is_eligible, ) +from scripts.ci import organization_commercial_readiness_core as coordinator_core ROOT = Path(__file__).resolve().parents[1] @@ -48,6 +49,18 @@ def test_static_and_live_writer_lease_policy() -> None: assert not is_live_writer_run(complete) +def test_core_fallback_ddd_marker_contract() -> None: + """The standalone core fallback retains positive and negative coverage.""" + assert not coordinator_core.has_domain_driven_development_contract("") + source = "\n".join( + ( + coordinator_core.DDD_ENTRYPOINT_MARKER, + *coordinator_core.DDD_CONTRACT_TERMS, + ) + ) + assert coordinator_core.has_domain_driven_development_contract(source) + + def test_product_entrypoint_requires_manual_nvidia_and_ddd_opt_in() -> None: """Product dispatch requires a manual credential-isolated DDD contract.""" safe = manual_workflow() diff --git a/tests/test_pr_review_fix_scheduler.py b/tests/test_pr_review_fix_scheduler.py index 3b4416bdc3..94344f4c07 100644 --- a/tests/test_pr_review_fix_scheduler.py +++ b/tests/test_pr_review_fix_scheduler.py @@ -1142,6 +1142,16 @@ def test_fix_inspect_skip_wait_and_error_paths(monkeypatch): """Inspect and queue logic report skip, wait, dispatch-limit, and errors.""" args = fix.parse_args(["--repo", "owner/repo", "--base-branch", "main"]) assert fix.inspect_pr("owner/repo", make_pr(isDraft=True), args) == ("skip", ("draft PR",)) + assert fix.inspect_pr( + "owner/repo", + make_pr(isDraft=True, mergeStateStatus="DIRTY"), + args, + ) == ("skip", ("draft PR",)) + assert fix.inspect_pr( + "owner/repo", + make_pr(mergeStateStatus="DIRTY"), + args, + ) == ("skip", ("merge conflict is not authorized for repair",)) assert fix.inspect_pr("owner/repo", make_pr(baseRefName="develop"), args)[1][0].startswith("base branch") wildcard_args = fix.parse_args(["--repo", "owner/repo", "--base-branch", "*"]) monkeypatch.setattr(fix, "needs_autofix", lambda pr: (False, ())) diff --git a/tests/test_pr_review_merge_scheduler.py b/tests/test_pr_review_merge_scheduler.py index 919566aeb2..8edabc9ac9 100644 --- a/tests/test_pr_review_merge_scheduler.py +++ b/tests/test_pr_review_merge_scheduler.py @@ -3375,6 +3375,34 @@ def test_current_head_approval_cleans_previous_head_change_gate_before_merge(): ) +def test_fetch_workflow_names_by_check_suite_rest_paginates_and_maps(monkeypatch): + """REST fallback preserves suite workflow identity across full pages.""" + first_page = [ + {"check_suite_id": index, "name": f"workflow-{index}"} + for index in range(99) + ] + first_page.append({"check_suite_id": None, "name": ""}) + responses = iter([{"workflow_runs": first_page}, {"workflow_runs": []}]) + monkeypatch.setattr(sched, "gh_api_json", lambda endpoint: next(responses)) + + names = sched.fetch_workflow_names_by_check_suite_rest("owner/repo", "a" * 40) + + assert names[0] == "workflow-0" + assert names[98] == "workflow-98" + + +def test_fetch_workflow_names_by_check_suite_rest_propagates_read_error(monkeypatch): + """Unexpected Actions read errors fail closed instead of hiding identity.""" + monkeypatch.setattr( + sched, + "gh_api_json", + lambda endpoint: (_ for _ in ()).throw(RuntimeError("network exploded")), + ) + + with pytest.raises(RuntimeError, match="network exploded"): + sched.fetch_workflow_names_by_check_suite_rest("owner/repo", "a" * 40) + + def test_failed_status_checks_uses_latest_check_run_for_same_workflow_name(): pr = make_pr( statusCheckRollup={ From b4fd5d0d4d9b230b04cda1557995c66293dd72ab Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 1 Sep 2026 14:34:32 +0900 Subject: [PATCH 21/24] fix(automation): reuse readiness core module --- scripts/ci/organization_commercial_readiness_loop.py | 3 +++ tests/test_organization_commercial_readiness_loop_facade.py | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/scripts/ci/organization_commercial_readiness_loop.py b/scripts/ci/organization_commercial_readiness_loop.py index 78dd6aac84..672973e877 100644 --- a/scripts/ci/organization_commercial_readiness_loop.py +++ b/scripts/ci/organization_commercial_readiness_loop.py @@ -15,6 +15,9 @@ def _load_sibling(module_name: str, filename: str) -> ModuleType: """Load one trusted sibling module under a stable private module name.""" + existing = sys.modules.get(module_name) + if existing is not None: + return existing path = _MODULE_DIRECTORY / filename spec = importlib.util.spec_from_file_location(module_name, path) module = importlib.util.module_from_spec(spec) # type: ignore[arg-type] diff --git a/tests/test_organization_commercial_readiness_loop_facade.py b/tests/test_organization_commercial_readiness_loop_facade.py index 63578c7f49..95acdc5841 100644 --- a/tests/test_organization_commercial_readiness_loop_facade.py +++ b/tests/test_organization_commercial_readiness_loop_facade.py @@ -2,6 +2,7 @@ from __future__ import annotations +import pickle import runpy import sys from pathlib import Path @@ -50,3 +51,6 @@ def test_imported_facade_remains_executable_by_public_module_name( run_name="__main__", ) assert raised.value.code == 2 + assert pickle.loads(pickle.dumps(coordinator.ActionKind.REVIEW_REPAIR)) is ( + coordinator.ActionKind.REVIEW_REPAIR + ) From cc3b6b8add7bece45e7dd20ce10a65b653b4d3d7 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 1 Sep 2026 14:40:06 +0900 Subject: [PATCH 22/24] fix(automation): require executable DDD step binding --- .../organization-commercial-readiness-loop.md | 3 +- ...ation_commercial_readiness_ddd_contract.py | 46 ++++++++++++++++++- ...n_commercial_readiness_loop_ddd_binding.py | 32 +++++++++++++ 3 files changed, 79 insertions(+), 2 deletions(-) diff --git a/docs/doctoring/organization-commercial-readiness-loop.md b/docs/doctoring/organization-commercial-readiness-loop.md index 6b70a43365..07443ac655 100644 --- a/docs/doctoring/organization-commercial-readiness-loop.md +++ b/docs/doctoring/organization-commercial-readiness-loop.md @@ -55,7 +55,8 @@ env: jobs: develop: steps: - - run: | + - name: Invoke the repository product agent + run: | # cwl-ddd-prompt-binding: v1 product-agent \ --prompt-env CWL_PRODUCT_AGENT_PROMPT \ diff --git a/scripts/ci/organization_commercial_readiness_ddd_contract.py b/scripts/ci/organization_commercial_readiness_ddd_contract.py index 9de440815c..e1206a21b2 100644 --- a/scripts/ci/organization_commercial_readiness_ddd_contract.py +++ b/scripts/ci/organization_commercial_readiness_ddd_contract.py @@ -86,6 +86,46 @@ def _block_scalars(source: str, key: str) -> tuple[str, ...]: return tuple(blocks) +def _step_run_blocks(source: str) -> tuple[str, ...]: + """Return block ``run`` values structurally nested below job steps.""" + lines = source.splitlines() + candidates = _block_scalars(source, "run") + accepted: list[str] = [] + candidate_index = 0 + header = re.compile(_BLOCK_HEADER_TEMPLATE.format(key="run")) + for index, line in enumerate(lines): + match = header.fullmatch(line) + if match is None: + continue + block = candidates[candidate_index] + candidate_index += 1 + run_indent = len(match.group("indent")) + ancestors: list[tuple[int, str]] = [] + ceiling = run_indent + for previous in reversed(lines[:index]): + if not previous.strip(): + continue + indent = len(previous) - len(previous.lstrip(" ")) + if indent < ceiling: + ancestors.append((indent, previous.strip())) + ceiling = indent + if indent == 0: + break + for ancestor_index, steps in enumerate(ancestors): + remaining = ancestors[ancestor_index + 1 :] + if len(remaining) < 2: + break + job, jobs = remaining[:2] + if ( + steps[1] == "steps:" + and job[1].endswith(":") + and jobs == (0, "jobs:") + ): + accepted.append(block) + break + return tuple(accepted) + + def _contract_version(environment: str) -> str | None: """Return the unique scalar contract version from a root environment body.""" matches = [ @@ -154,9 +194,13 @@ def _executable(tokens: tuple[str, ...]) -> str | None: def _has_bound_agent_invocation(source: str) -> bool: """Return whether one nontrivial command receives both contract inputs.""" - for run_block in _block_scalars(source, "run"): + for run_block in _step_run_blocks(source): if DDD_PROMPT_BINDING_MARKER not in run_block: continue + if "<<" in run_block or re.search( + r"(?m)^\s*(?:if|case|while|until)\b", run_block + ): + continue for tokens in _shell_segments(run_block): executable = _executable(tokens) if executable is None or executable in _NON_AGENT_EXECUTABLES: diff --git a/tests/test_organization_commercial_readiness_loop_ddd_binding.py b/tests/test_organization_commercial_readiness_loop_ddd_binding.py index 7a7cb3ad59..22a6f180a4 100644 --- a/tests/test_organization_commercial_readiness_loop_ddd_binding.py +++ b/tests/test_organization_commercial_readiness_loop_ddd_binding.py @@ -250,12 +250,44 @@ def test_rejects_marker_and_flags_distributed_across_run_blocks() -> None: ) +def test_rejects_non_step_and_unreachable_agent_bindings() -> None: + """Only directly reachable job-step commands may bind the contract.""" + source = _source() + run_block = next(iter(contract._step_run_blocks(source))) + for prefix in ("env:\n", "metadata:\n"): + inert = source.replace("jobs:\n", f"{prefix} run: |\n" + "\n".join( + f" {line}" for line in run_block.splitlines() + ) + "\njobs:\n", 1) + inert = inert.replace(" run: |", " notes: |", 1) + assert not coordinator.has_domain_driven_development_contract(inert) + + heredoc = _replace_command( + source, + "cat <<'INERT'\n" + " product-agent --prompt-env CWL_PRODUCT_AGENT_PROMPT " + "--architecture-contract-env CWL_DDD_CONTRACT_CAPABILITIES\n" + " INERT", + ) + assert not coordinator.has_domain_driven_development_contract(heredoc) + + skipped = _replace_command( + source, + "if false; then\n" + " product-agent --prompt-env CWL_PRODUCT_AGENT_PROMPT " + "--architecture-contract-env CWL_DDD_CONTRACT_CAPABILITIES\n" + " fi", + ) + assert not coordinator.has_domain_driven_development_contract(skipped) + + def test_private_command_edges_and_compatibility_script_mode( monkeypatch: pytest.MonkeyPatch, ) -> None: """Cover assignment-only, trailing-operator, and direct-script boundaries.""" assert contract._executable(("env", "MODE=bounded")) is None assert list(contract._shell_segments("product-agent ;")) == [("product-agent",)] + assert contract._step_run_blocks("run: |\n product-agent") == () + assert contract._step_run_blocks("jobs:\n\n run: |\n product-agent") == () path = Path(coordinator.__file__) monkeypatch.setattr(sys, "argv", [str(path), "--organization", "invalid/name"]) From 0a4d66489814fb02568ccad6bb8f61fe30045497 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 1 Sep 2026 14:48:40 +0900 Subject: [PATCH 23/24] fix(automation): scope DDD shell reachability --- ...ation_commercial_readiness_ddd_contract.py | 56 ++++++++++++++++--- ...n_commercial_readiness_loop_ddd_binding.py | 25 +++++++++ 2 files changed, 73 insertions(+), 8 deletions(-) diff --git a/scripts/ci/organization_commercial_readiness_ddd_contract.py b/scripts/ci/organization_commercial_readiness_ddd_contract.py index e1206a21b2..2b320b2619 100644 --- a/scripts/ci/organization_commercial_readiness_ddd_contract.py +++ b/scripts/ci/organization_commercial_readiness_ddd_contract.py @@ -47,6 +47,11 @@ _NON_AGENT_EXECUTABLES = frozenset( {":", "[", "echo", "export", "false", "printf", "test", "true"} ) +_YAML_MAPPING_KEY_RE = re.compile( + r'^(?:"(?P[^"]+)"|\'(?P[^\']+)\'|(?P[A-Za-z0-9_.-]+))' + r":\s*(?:#.*)?$" +) +_HEREDOC_RE = re.compile(r"<<-?\s*['\"]?(?P[A-Za-z_][A-Za-z0-9_]*)") def _top_level_mapping_bodies(source: str, key: str) -> tuple[str, ...]: @@ -116,16 +121,55 @@ def _step_run_blocks(source: str) -> tuple[str, ...]: if len(remaining) < 2: break job, jobs = remaining[:2] + steps_key = _yaml_mapping_key(steps[1]) + job_key = _yaml_mapping_key(job[1]) + jobs_key = _yaml_mapping_key(jobs[1]) if ( - steps[1] == "steps:" - and job[1].endswith(":") - and jobs == (0, "jobs:") + steps_key == "steps" + and job_key is not None + and jobs[0] == 0 + and jobs_key == "jobs" ): accepted.append(block) break return tuple(accepted) +def _yaml_mapping_key(line: str) -> str | None: + """Return a simple YAML mapping key while allowing quotes and comments.""" + match = _YAML_MAPPING_KEY_RE.fullmatch(line) + if match is None: + return None + return next(value for value in match.groups() if value is not None) + + +def _reachable_shell(block: str) -> str: + """Remove heredoc bodies and conditional regions from a shell block.""" + reachable: list[str] = [] + heredoc_delimiter: str | None = None + control_depth = 0 + for line in block.splitlines(): + stripped = line.strip() + if heredoc_delimiter is not None: + if stripped == heredoc_delimiter: + heredoc_delimiter = None + continue + if control_depth: + if re.match(r"^(?:if|case|while|until)\b", stripped): + control_depth += 1 + if re.match(r"^(?:fi|esac|done)\b", stripped): + control_depth -= 1 + continue + if re.match(r"^(?:if|case|while|until)\b", stripped): + control_depth = 1 + continue + if match := _HEREDOC_RE.search(stripped): + heredoc_delimiter = match.group("delimiter") + continue + reachable.append(line) + return "\n".join(reachable) + + def _contract_version(environment: str) -> str | None: """Return the unique scalar contract version from a root environment body.""" matches = [ @@ -197,11 +241,7 @@ def _has_bound_agent_invocation(source: str) -> bool: for run_block in _step_run_blocks(source): if DDD_PROMPT_BINDING_MARKER not in run_block: continue - if "<<" in run_block or re.search( - r"(?m)^\s*(?:if|case|while|until)\b", run_block - ): - continue - for tokens in _shell_segments(run_block): + for tokens in _shell_segments(_reachable_shell(run_block)): executable = _executable(tokens) if executable is None or executable in _NON_AGENT_EXECUTABLES: continue diff --git a/tests/test_organization_commercial_readiness_loop_ddd_binding.py b/tests/test_organization_commercial_readiness_loop_ddd_binding.py index 22a6f180a4..8cebe0f603 100644 --- a/tests/test_organization_commercial_readiness_loop_ddd_binding.py +++ b/tests/test_organization_commercial_readiness_loop_ddd_binding.py @@ -280,6 +280,31 @@ def test_rejects_non_step_and_unreachable_agent_bindings() -> None: assert not coordinator.has_domain_driven_development_contract(skipped) +def test_accepts_valid_yaml_keys_and_unrelated_shell_structures() -> None: + """Valid YAML spellings and unrelated shell regions retain eligibility.""" + source = _source() + quoted = source.replace("jobs:\n", '"jobs": # root jobs\n', 1).replace( + " develop:\n", " 'develop': # product job\n", 1 + ).replace(" steps:\n", ' "steps": # executable list\n', 1) + assert coordinator.has_domain_driven_development_contract(quoted) + + direct = ( + "product-agent --prompt-env CWL_PRODUCT_AGENT_PROMPT " + "--architecture-contract-env CWL_DDD_CONTRACT_CAPABILITIES" + ) + for unrelated in ( + "cat <<'NOTE'\n inert prose\n NOTE", + "if false; then\n echo skipped\n fi", + "if false; then\n while false; do\n echo skipped\n done\n fi", + ): + assert coordinator.has_domain_driven_development_contract( + _replace_command(source, f"{unrelated}\n {direct}") + ) + assert coordinator.has_domain_driven_development_contract( + _replace_command(source, f"{direct}\n {unrelated}") + ) + + def test_private_command_edges_and_compatibility_script_mode( monkeypatch: pytest.MonkeyPatch, ) -> None: From aa8f69a61f16ab17660f48f148dcea11355f7071 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 6 Sep 2026 01:24:35 +0900 Subject: [PATCH 24/24] Fix the facade's identity restamp so a second import cannot break the first MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The merge of origin/main left one failing test, `test_imported_facade_remains_executable_by_public_module_name`, reproducible with two files and independent of test ordering: pytest tests/test_codeql_default_setup_rollout.py \ tests/test_organization_commercial_readiness_loop_facade.py \ -p no:randomly Root cause is in this branch's facade, not in main's test. Lines 50-54 restamp the shared `_core` singleton's `__name__/__package__/__loader__/__spec__` on **every** import of the facade. Measured directly: import scripts.ci.organization_commercial_readiness_loop -> _core.__spec__.name == "scripts.ci.organization_commercial_readiness_loop" import organization_commercial_readiness_loop (bare sibling fallback) -> same object, _core.__spec__.name == "organization_commercial_readiness_loop" -> sys.modules["scripts.ci.…"] still points at that object `runpy.run_module("scripts.ci.organization_commercial_readiness_loop")` then asks a bare-name loader to load the dotted name and raises. Main's `test_direct_script_import_falls_back_to_sibling_module` only supplies the second import; it is not defective. This would break for any caller that imports the facade under both names, with or without that test. Two earlier `monkeypatch.delitem(sys.modules, …)` attempts were tried and reverted: deleting the keys cannot undo the mutation, because both keys point at the same already-restamped object. Fix: first import wins. Stamp the public identity only while `_core` still carries its private core spec. `sys.modules[__name__] = _core` is unchanged, so the bare alias is still created and direct script execution still works. Verified: - 2-file reproduction, both orderings: 27 passed - negative control: reverting this guard reproduces `1 failed`; restoring it returns 27 passed - direct execution `python organization_commercial_readiness_loop.py --organization invalid/name` still exits 2 on the intended argument error - full suite 2910 passed, 1 skipped, 0 failed (was 1 failed); coverage 100%; interrogate 100% Diagnosis and patch from the concurrent session working the same queue; the mechanism was re-measured here before applying. Co-Authored-By: Claude Opus 5 --- .../ci/organization_commercial_readiness_loop.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/scripts/ci/organization_commercial_readiness_loop.py b/scripts/ci/organization_commercial_readiness_loop.py index 672973e877..eef3391540 100644 --- a/scripts/ci/organization_commercial_readiness_loop.py +++ b/scripts/ci/organization_commercial_readiness_loop.py @@ -47,8 +47,14 @@ def _load_sibling(module_name: str, filename: str) -> ModuleType: # that object as well: standard module runners consult ``__spec__`` and its # loader after import, and the core's private identity cannot load this public # module name. -_core.__name__ = __name__ -_core.__package__ = __package__ -_core.__loader__ = __loader__ -_core.__spec__ = __spec__ +# First import wins: a later import of this facade under another name (the +# bare sibling fallback used by direct ``python scripts/ci/...`` execution) +# must not restamp the shared core with that name, or the entry still cached +# under the first name would carry a loader that cannot load it. +_core_spec = getattr(_core, "__spec__", None) +if _core_spec is None or _core_spec.name == _CORE_MODULE_NAME: + _core.__name__ = __name__ + _core.__package__ = __package__ + _core.__loader__ = __loader__ + _core.__spec__ = __spec__ sys.modules[__name__] = _core