Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
497a329
docs: design naruon hourly commercial readiness loop
seonghobae Aug 3, 2026
32da808
docs: plan naruon hourly commercial readiness loop
seonghobae Aug 3, 2026
00f50a4
test: define naruon hourly commercial readiness contracts
seonghobae Aug 3, 2026
dfc2c40
feat(automation): schedule naruon commercial readiness hourly
seonghobae Aug 3, 2026
0e5ec8b
feat(automation): develop one naruon buyer gap at zero PRs
seonghobae Aug 3, 2026
1f58756
test: align hourly loop checks with active-run guard
seonghobae Aug 3, 2026
a19ddc0
fix(automation): grant dispatch permission and mirror backend CI
seonghobae Aug 3, 2026
43900b9
chore(ci): bootstrap PR 709 least-privilege repair
seonghobae Aug 3, 2026
c2ff6cc
fix(ci): make PR 709 repair expression-safe
seonghobae Aug 3, 2026
ab16ee5
chore(ci): remove inactive PR 709 bootstrap workflow
seonghobae Aug 3, 2026
aa24cfb
fix(automation): stage comprehensive Naruon loop hardening
seonghobae Aug 3, 2026
55396be
fix(ci): run comprehensive PR 709 hardening
seonghobae Aug 3, 2026
b0ba250
fix(ci): trigger comprehensive PR 709 hardening
seonghobae Aug 3, 2026
8bcecef
chore(ci): trigger PR 709 least-privilege repair
seonghobae Aug 3, 2026
884a0d5
chore(ci): retrigger PR 709 security hardening
seonghobae Aug 3, 2026
4896dac
fix(ci): normalize PR 709 branch guard before hardening
seonghobae Aug 3, 2026
d175e1c
ci(pr709): run final least-privilege materialization
seonghobae Aug 4, 2026
a16bad1
ci(pr709): trigger finalization workflow
seonghobae Aug 4, 2026
2afe1c2
chore(pr709): remove inactive finalizer workflow
seonghobae Aug 4, 2026
5904a4e
chore(automation): remove superseded PR repair artifacts
seonghobae Aug 4, 2026
c9bcdf1
ci(pr709): arm least-privilege finalizer
seonghobae Aug 4, 2026
ffbeaec
ci: retry verified PR 709 finalization
seonghobae Aug 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
741 changes: 741 additions & 0 deletions .github/workflows/naruon-commercial-readiness-development.yml

Large diffs are not rendered by default.

179 changes: 179 additions & 0 deletions .github/workflows/naruon-commercial-readiness-hourly.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
name: Naruon Commercial Readiness Hourly Loop

on:
schedule:
- cron: "7 * * * *"
repository_dispatch:
types: [naruon-commercial-readiness-hourly]

concurrency:
group: naruon-commercial-readiness-hourly
cancel-in-progress: false

permissions:
contents: read

jobs:
orchestrate:
runs-on: ubuntu-latest
Comment on lines +17 to +18

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

작업에 타임아웃이 없습니다.

orchestrate 작업에 timeout-minutes가 없습니다. concurrency.cancel-in-progress: false(11번 줄)와 결합되면, gh api 호출 하나가 멈출 경우 작업이 GitHub Actions 기본 최대 시간(6시간)까지 실행될 수 있고, 그동안 동시성 그룹이 점유되어 후속 시간별 실행이 모두 차단됩니다. 이 워크플로는 시간마다 실행되도록 설계되었으므로 지연에 특히 취약합니다.

짧은 timeout-minutes 값(예: 10-15분)을 작업에 추가하십시오.

⏱️ 제안된 수정
 jobs:
   orchestrate:
     runs-on: ubuntu-latest
+    timeout-minutes: 15
     permissions:
       actions: write
       contents: write
       pull-requests: read
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
orchestrate:
runs-on: ubuntu-latest
orchestrate:
runs-on: ubuntu-latest
timeout-minutes: 15
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/naruon-commercial-readiness-hourly.yml around lines 17 -
18, Set a short job-level timeout for the orchestrate job by adding
timeout-minutes alongside runs-on, using a value appropriate for the hourly
workflow such as 10–15 minutes, so stalled gh api calls cannot occupy the
concurrency group until GitHub’s default limit.

permissions:
actions: write
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
contents: write
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
pull-requests: read
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
GH_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN || secrets.OPENCODE_APPROVE_TOKEN || github.token }}
TARGET_REPOSITORY: ContextualWisdomLab/naruon
TARGET_BASE_BRANCH: develop
DISPATCH_REPOSITORY: ContextualWisdomLab/.github
steps:
- name: Harden runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
with:
egress-policy: audit

- name: Validate fixed target
env:
REQUESTED_TARGET_REPOSITORY: ${{ github.event.client_payload.target_repository || '' }}
REQUESTED_BASE_BRANCH: ${{ github.event.client_payload.base_branch || '' }}
run: |
set -euo pipefail
if [ -n "$REQUESTED_TARGET_REPOSITORY" ] \
&& [ "$REQUESTED_TARGET_REPOSITORY" != "$TARGET_REPOSITORY" ]; then
echo "::error::Hourly dispatch cannot target ${REQUESTED_TARGET_REPOSITORY}."
exit 1
fi
if [ -n "$REQUESTED_BASE_BRANCH" ] \
&& [ "$REQUESTED_BASE_BRANCH" != "$TARGET_BASE_BRANCH" ]; then
echo "::error::Hourly dispatch cannot target base ${REQUESTED_BASE_BRANCH}."
exit 1
fi
if [ "$GITHUB_REPOSITORY" != "$DISPATCH_REPOSITORY" ]; then
echo "::error::Hourly loop must execute from ${DISPATCH_REPOSITORY}."
exit 1
fi

- name: Read live pull request queue
id: queue
run: |
set -euo pipefail
open_pr_json="$(
gh api --paginate \
-H "Accept: application/vnd.github+json" \
"repos/${TARGET_REPOSITORY}/pulls?state=open&base=${TARGET_BASE_BRANCH}&per_page=100" \
| jq -s 'add // []'
)"
open_pr_count="$(jq 'length' <<<"$open_pr_json")"
open_pr_numbers="$(
jq -r 'map(.number | tostring) | join(",")' <<<"$open_pr_json"
)"
{
printf 'count=%s\n' "$open_pr_count"
printf 'numbers=%s\n' "$open_pr_numbers"
} >>"$GITHUB_OUTPUT"
printf 'Open PR queue: count=%s numbers=%s\n' \
"$open_pr_count" "${open_pr_numbers:-none}"

- name: Dispatch review feedback fixes
run: |
set -euo pipefail
cat >"$RUNNER_TEMP/fix-payload.json" <<'JSON'
{
"event_type": "pr-review-fix-scheduler",
"client_payload": {
"target_repository": "ContextualWisdomLab/naruon",
"base_branch": "develop",
"max_prs": "100",
"max_dispatches": "10",
"retry_hours": "1",
"dry_run": false
}
}
JSON
gh api -X POST \
"repos/${DISPATCH_REPOSITORY}/dispatches" \
--input "$RUNNER_TEMP/fix-payload.json"

- name: Dispatch current-head review and merge processing
run: |
set -euo pipefail
cat >"$RUNNER_TEMP/merge-payload.json" <<'JSON'
{
"event_type": "merge-scheduler",
"client_payload": {
"target_repository": "ContextualWisdomLab/naruon",
"base_branch": "develop",
"max_prs": "100",
"trigger_reviews": true,
"review_dispatch_limit": "-1",
"branch_update_limit": "10",
"enable_auto_merge": true,
"merge_mode": "direct_or_auto",
"update_branches": true,
"stale_opencode_minutes": "60"
}
}
JSON
gh api -X POST \
"repos/${DISPATCH_REPOSITORY}/dispatches" \
--input "$RUNNER_TEMP/merge-payload.json"

- name: Decide whether product development may run
id: development
env:
OPEN_PR_COUNT: ${{ steps.queue.outputs.count }}
run: |
set -euo pipefail
if [ "$OPEN_PR_COUNT" -ne 0 ]; then
echo "decision=skipped-open-prs" >>"$GITHUB_OUTPUT"
echo "Development suppressed because ${OPEN_PR_COUNT} PR(s) remain open."
exit 0
fi

active_run_count="$(
gh api \
"repos/${DISPATCH_REPOSITORY}/actions/workflows/naruon-commercial-readiness-development.yml/runs?per_page=30" \
| jq '[.workflow_runs[] | select(.status == "queued" or .status == "in_progress")] | length'
)"
if [ "$active_run_count" -ne 0 ]; then
echo "decision=skipped-active-development" >>"$GITHUB_OUTPUT"
echo "Development suppressed because a worker is already queued or running."
exit 0
fi

echo "decision=dispatch" >>"$GITHUB_OUTPUT"

- name: Dispatch one buyer-visible product gap
if: steps.development.outputs.decision == 'dispatch'
run: |
set -euo pipefail
cat >"$RUNNER_TEMP/development-payload.json" <<'JSON'
{
"event_type": "naruon-commercial-readiness-development",
"client_payload": {
"target_repository": "ContextualWisdomLab/naruon",
"base_branch": "develop"
}
}
JSON
gh api -X POST \
"repos/${DISPATCH_REPOSITORY}/dispatches" \
--input "$RUNNER_TEMP/development-payload.json"

- name: Summarize loop
if: always()
env:
OPEN_PR_COUNT: ${{ steps.queue.outputs.count || 'unknown' }}
OPEN_PR_NUMBERS: ${{ steps.queue.outputs.numbers || '' }}
DEVELOPMENT_DECISION: ${{ steps.development.outputs.decision || 'not-evaluated' }}
run: |
{
echo "## Naruon commercial readiness loop"
echo "- Repository: ${TARGET_REPOSITORY}"
echo "- Base branch: ${TARGET_BASE_BRANCH}"
echo "- Open PR count: ${OPEN_PR_COUNT}"
echo "- Open PR numbers: ${OPEN_PR_NUMBERS:-none}"
echo "- Review-feedback fix dispatch: submitted"
echo "- Current-head review/merge dispatch: submitted"
echo "- Product-development decision: ${DEVELOPMENT_DECISION}"
} >>"$GITHUB_STEP_SUMMARY"
125 changes: 125 additions & 0 deletions .github/workflows/pr709-finalize-least-privilege-v2.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
name: PR 709 finalize least-privilege automation v2

on:
push:
branches:
- ops/naruon-hourly-commercial-readiness-20260803
paths:
- .github/workflows/pr709-finalize-least-privilege-v2.yml

permissions:
contents: read

concurrency:
group: pr-709-finalize-least-privilege-automation-v2
cancel-in-progress: false

jobs:
finalize:
if: github.repository == 'ContextualWisdomLab/.github' && github.ref_name == 'ops/naruon-hourly-commercial-readiness-20260803'
runs-on: ubuntu-24.04
timeout-minutes: 30
permissions:
contents: write
steps:
- name: Harden runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
with:
egress-policy: audit

- name: Checkout exact branch head
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
ref: ops/naruon-hourly-commercial-readiness-20260803
fetch-depth: 0
persist-credentials: true

- name: Normalize bounded-change source block
shell: bash
run: |
set -euo pipefail
python - <<'PY'
from pathlib import Path

path = Path('.github/workflows/naruon-commercial-readiness-development.yml')
text = path.read_text(encoding='utf-8')
replacement = ''' git add -A -N
git add -A
mapfile -t changed_files < <(
git diff --cached --name-only | sort -u
)
if [ "${#changed_files[@]}" -eq 0 ]; then
echo "has_changes=false" >>"$GITHUB_OUTPUT"
echo "Agent produced no safe repository change."
exit 0
fi
echo "has_changes=true" >>"$GITHUB_OUTPUT"

if git diff --cached --numstat \
| awk '$1 == "-" || $2 == "-" {found=1} END {exit !found}'; then
echo "::error::Binary changes are outside the bounded autonomous product-edit contract."
exit 1
fi
changed_file_count="${#changed_files[@]}"
new_file_count="$(
git diff --cached --name-only --diff-filter=A \
| awk 'NF {count += 1} END {print count + 0}'
)"
changed_lines="$(
git diff --cached --numstat \
| awk '{added += $1; deleted += $2} END {print added + deleted + 0}'
)"
'''
if replacement not in text:
start_marker = ' git add -N -- .\n'
end_marker = ' if [ "$changed_file_count" -gt "$MAX_CHANGED_FILES" ]; then\n'
start = text.index(start_marker)
end = text.index(end_marker, start)
text = text[:start] + replacement + text[end:]
path.write_text(text, encoding='utf-8')
PY

- name: Materialize retained workflows and contracts
run: python scripts/ci/pr709_finalize.py

- name: Validate YAML and automation contracts
shell: bash
run: |
set -euo pipefail
ruby -e 'require "yaml"; ARGV.each { |path| YAML.safe_load(File.read(path), aliases: true) }' \
.github/workflows/naruon-commercial-readiness-hourly.yml \
.github/workflows/naruon-commercial-readiness-development.yml
python -m pytest -q \
tests/test_naruon_commercial_readiness_hourly_contract.py \
tests/test_required_workflow_queue_contract.py
python -m pytest -q
git diff --check
test ! -e .github/workflows/pr709-commercial-readiness-hardening-v2.yml
test ! -e .github/workflows/pr709-least-privilege-repair.yml
test ! -e .github/workflows/pr709-finalize-commercial-readiness.yml
test ! -e scripts/ci/bootstrap_naruon_commercial_readiness_hardening_v2.py
test ! -e scripts/ci/pr709_finalize.py
agent_block="$(sed -n '/name: Run one commercial-readiness implementation slice/,/name: Validate bounded changed-file/p' .github/workflows/naruon-commercial-readiness-development.yml)"
! grep -q 'GITHUB_TOKEN:' <<<"$agent_block"
! grep -q 'USE_GITHUB_TOKEN:' <<<"$agent_block"
grep -q 'target_token="$APP_TOKEN"' .github/workflows/naruon-commercial-readiness-development.yml
grep -q 'timeout-minutes: 15' .github/workflows/naruon-commercial-readiness-hourly.yml

- name: Commit verified final scope
shell: bash
env:
BRANCH_NAME: ops/naruon-hourly-commercial-readiness-20260803
run: |
set -euo pipefail
test "$GITHUB_REPOSITORY" = "ContextualWisdomLab/.github"
test "$GITHUB_REF_NAME" = "$BRANCH_NAME"
rm -f \
.github/workflows/pr709-finalize-least-privilege.yml \
.github/workflows/pr709-finalize-least-privilege-v2.yml
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add -A
git diff --cached --check
git diff --cached --quiet && { echo "No verified finalization changes to commit."; exit 1; }
git commit -m "fix(automation): enforce least-privilege hourly loop"
git push origin "HEAD:${BRANCH_NAME}"
69 changes: 69 additions & 0 deletions .github/workflows/pr709-finalize-least-privilege.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
name: PR 709 finalize least-privilege automation

on:
pull_request:
branches:
- main
types: [synchronize]

permissions:
contents: read

concurrency:
group: pr-709-finalize-least-privilege-automation
cancel-in-progress: true

jobs:
finalize:
if: github.event.pull_request.head.ref == 'ops/naruon-hourly-commercial-readiness-20260803'
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: write
steps:
- name: Harden runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
with:
egress-policy: audit

- name: Checkout pull request branch
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v6
with:
ref: ops/naruon-hourly-commercial-readiness-20260803
fetch-depth: 0

- name: Materialize retained workflows and contracts
run: python scripts/ci/pr709_finalize.py

- name: Validate YAML and automation contracts
shell: bash
run: |
set -euo pipefail
ruby -e 'require "yaml"; ARGV.each { |path| YAML.safe_load(File.read(path), aliases: true) }' \
.github/workflows/naruon-commercial-readiness-hourly.yml \
.github/workflows/naruon-commercial-readiness-development.yml
python -m pytest -q \
tests/test_naruon_commercial_readiness_hourly_contract.py \
tests/test_required_workflow_queue_contract.py
python -m pytest -q
git diff --check
test ! -e .github/workflows/pr709-commercial-readiness-hardening-v2.yml
test ! -e .github/workflows/pr709-least-privilege-repair.yml
test ! -e .github/workflows/pr709-finalize-commercial-readiness.yml
test ! -e scripts/ci/bootstrap_naruon_commercial_readiness_hardening_v2.py
test ! -e scripts/ci/pr709_finalize.py
agent_block="$(sed -n '/name: Run one commercial-readiness implementation slice/,/name: Validate bounded changed-file/p' .github/workflows/naruon-commercial-readiness-development.yml)"
! grep -q 'GITHUB_TOKEN:' <<<"$agent_block"
! grep -q 'USE_GITHUB_TOKEN:' <<<"$agent_block"

- name: Commit final exact scope
shell: bash
run: |
set -euo pipefail
rm -- .github/workflows/pr709-finalize-least-privilege.yml
git diff --check
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add -A
git commit -m "fix(automation): enforce least-privilege hourly loop"
git push origin HEAD:ops/naruon-hourly-commercial-readiness-20260803
Loading
Loading