From eaea09027ba7ae278d3f9936b1057ad25979623e Mon Sep 17 00:00:00 2001 From: Seungpyo1007 Date: Thu, 29 Oct 2026 12:12:14 +0900 Subject: [PATCH 1/3] ci: run TechAPI automation as reusable workflows without personal tokens Workflows used the maintainer's personal access token (TECHAPI_TOKEN) and the TechEngineBot account token to push, open PRs, comment and file issues on TechAPI, so automation ran under real accounts. Move every TechAPI-touching job behind workflow_call so TechAPI calls it and the caller's GITHUB_TOKEN does the writing as github-actions[bot]. - techapi-pr-validation-comment, techapi-verify-comment, weekly-refresh, weekly-ingest and verify-network become workflow_call-only and check out TechEngine explicitly (a bare checkout fetches the caller). - validate-data checks out TechEngine explicitly for the same reason. - coverage-report runs weekly and updates only this repo's issue. - Remove bump-techapi, notify-techapi, verify-status and dump-refresh; TechAPI's dump-develop takes over the status aggregate and full dump refresh. - Record the decision as ADR-017 and update SPEC 16.1, README and DEVELOPMENT. --- .github/workflows/bump-techapi.yml | 59 --------- .github/workflows/coverage-report.yml | 56 +++----- .github/workflows/dump-refresh.yml | 123 ------------------ .github/workflows/notify-techapi.yml | 33 ----- .../techapi-pr-validation-comment.yml | 62 +++++---- .github/workflows/techapi-verify-comment.yml | 91 +++++++------ .github/workflows/validate-data.yml | 3 + .github/workflows/verify-network.yml | 40 +++--- .github/workflows/verify-status.yml | 66 ---------- .github/workflows/weekly-ingest.yml | 33 ++--- .github/workflows/weekly-refresh.yml | 44 ++----- README.md | 45 ++++--- app/verify/baseline.py | 2 +- app/verify/cli.py | 3 +- docs/DEVELOPMENT.md | 7 +- docs/SPEC.md | 53 +++++--- 16 files changed, 214 insertions(+), 506 deletions(-) delete mode 100644 .github/workflows/bump-techapi.yml delete mode 100644 .github/workflows/dump-refresh.yml delete mode 100644 .github/workflows/notify-techapi.yml delete mode 100644 .github/workflows/verify-status.yml diff --git a/.github/workflows/bump-techapi.yml b/.github/workflows/bump-techapi.yml deleted file mode 100644 index d410772..0000000 --- a/.github/workflows/bump-techapi.yml +++ /dev/null @@ -1,59 +0,0 @@ -name: bump-techapi - -# Reverse of notify-techapi: advance THIS repo's TechAPI submodule pointer to -# TechAPI's latest main. Triggered by a repository_dispatch (`techapi-updated`) -# that TechAPI's notify-engine.yml sends on a real data/site change, or run -# manually. Keeps the "TechAPI @ " browsing link from going stale. -# -# Loop-safe by construction: -# * the bump commit is pushed with the default GITHUB_TOKEN, and pushes made -# by GITHUB_TOKEN do NOT trigger other workflows — so notify-techapi never -# fires on it and there is no ping-pong back to TechAPI; -# * TechAPI's notify-engine.yml must `paths-ignore` its own TechEngine gitlink -# so it doesn't dispatch on bump-only commits (sender-side guard); -# * the bump is idempotent — if the pointer already matches, it no-ops. -on: - repository_dispatch: - types: [techapi-updated] - workflow_dispatch: - -permissions: - contents: write - -concurrency: - group: bump-techapi - cancel-in-progress: false - -jobs: - bump: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - ref: main - - - name: Bump TechAPI submodule pointer to TechAPI main - env: - PAYLOAD_SHA: ${{ github.event.client_payload.sha }} - run: | - set -euo pipefail - # Prefer the sha from the dispatch payload; fall back to remote main HEAD - # (covers manual runs and payload-less dispatches). - SHA="${PAYLOAD_SHA:-}" - if [ -z "$SHA" ]; then - SHA=$(git ls-remote https://github.com/GetTechAPI/TechAPI.git refs/heads/main | awk '{print $1}') - fi - if [ -z "$SHA" ]; then - echo "::error::could not resolve TechAPI main sha"; exit 1 - fi - CUR=$(git ls-tree HEAD TechAPI | awk '{print $3}') - if [ "$SHA" = "$CUR" ]; then - echo "TechAPI pointer already at ${SHA:0:7}; nothing to do." - exit 0 - fi - git update-index --cacheinfo "160000,${SHA},TechAPI" - git config user.name "TechEngineBot" - git config user.email "289859915+TechEngineBot@users.noreply.github.com" - git commit -m "chore: bump TechAPI submodule to ${SHA:0:7}" - git pull --rebase origin main - git push origin HEAD:main diff --git a/.github/workflows/coverage-report.yml b/.github/workflows/coverage-report.yml index 2c38f77..6b0429b 100644 --- a/.github/workflows/coverage-report.yml +++ b/.github/workflows/coverage-report.yml @@ -1,17 +1,17 @@ name: coverage-report -# Daily: pull upstream catalogs, diff vs the curated TechAPI dataset, post -# the gap list as a sticky issue (auto-updates the existing one each run). +# Weekly: pull upstream catalogs, diff vs the curated TechAPI dataset, post +# the gap list as a sticky issue in this repo (auto-updates the existing one). on: schedule: - - cron: "23 6 * * *" # Daily 06:23 UTC (reads curated data/, so safe to run every day) + - cron: "23 6 * * 1" # Mondays 06:23 UTC workflow_dispatch: permissions: contents: read issues: write -# Guard against a slow run overlapping the next day's scheduled run. +# Guard against a slow run overlapping the next scheduled run. concurrency: group: coverage-report cancel-in-progress: false @@ -45,42 +45,18 @@ jobs: name: coverage-report path: coverage-report.md - # Sticky issue: keep one open issue with the well-known title per repo, - # updating it in place. Posts to BOTH TechEngine and TechAPI. - # - # Authorship: a GitHub issue's author is the account behind the *token* - # (unlike a commit, it cannot be set via git config). To attribute the - # auto-generated issues to TechEngineBot, set a TechEngineBot PAT with - # Issues:write on both repos as the TECHENGINEBOT_TOKEN secret; the steps - # below prefer it. Without it they fall back to the default GITHUB_TOKEN - # (TechEngine, authored by github-actions) and TECHAPI_TOKEN (TechAPI). - - name: Sync sticky coverage issue (TechEngine + TechAPI) + # Sticky issue: keep one open issue with the well-known title, updating it + # in place. Only this repo, with its own GITHUB_TOKEN (github-actions[bot]). + - name: Sync sticky coverage issue env: - BOT_TOKEN: ${{ secrets.TECHENGINEBOT_TOKEN }} - SELF_TOKEN: ${{ secrets.GITHUB_TOKEN }} - TECHAPI_TOKEN: ${{ secrets.TECHAPI_TOKEN }} + GH_TOKEN: ${{ github.token }} run: | - set -uo pipefail + set -euo pipefail TITLE="Coverage gaps (auto-generated)" - BODY="$(cat coverage-report.md)" - sync_issue() { - repo="$1"; token="$2" - if [ -z "$token" ]; then - echo "::warning::no token for $repo; skipping coverage issue" - return 0 - fi - NUMBER=$(GH_TOKEN="$token" gh issue list --repo "$repo" --state open \ - --search "in:title \"$TITLE\"" --json number --jq '.[0].number // empty') || return 1 - if [ -z "${NUMBER:-}" ]; then - GH_TOKEN="$token" gh issue create --repo "$repo" --title "$TITLE" --body "$BODY" - else - GH_TOKEN="$token" gh issue edit "$NUMBER" --repo "$repo" --body "$BODY" - fi - } - # Prefer the TechEngineBot PAT (so the issue is authored by the bot); - # fall back to the per-repo defaults when it is not configured. - sync_issue "${{ github.repository }}" "${BOT_TOKEN:-$SELF_TOKEN}" \ - || echo "::warning::TechEngine coverage issue sync failed" - # TechAPI: best-effort — requires Issues:write on the chosen token. - sync_issue "GetTechAPI/TechAPI" "${BOT_TOKEN:-$TECHAPI_TOKEN}" \ - || echo "::warning::TechAPI coverage issue sync failed — token likely lacks Issues:write" + NUMBER=$(gh issue list --repo "${{ github.repository }}" --state open \ + --search "in:title \"$TITLE\"" --json number --jq '.[0].number // empty') + if [ -z "${NUMBER:-}" ]; then + gh issue create --repo "${{ github.repository }}" --title "$TITLE" --body-file coverage-report.md + else + gh issue edit "$NUMBER" --repo "${{ github.repository }}" --body-file coverage-report.md + fi diff --git a/.github/workflows/dump-refresh.yml b/.github/workflows/dump-refresh.yml deleted file mode 100644 index e3bd674..0000000 --- a/.github/workflows/dump-refresh.yml +++ /dev/null @@ -1,123 +0,0 @@ -name: dump-refresh - -# On-demand: regenerate the static v1 dump from the CURRENT TechAPI data (no -# scraping) and open a refresh PR to TechAPI. Use this to re-publish the dump -# after an engine-side change that affects serialized output (e.g. a scoring -# version bump) — weekly-refresh.yml also does this but behind a slow live scrape. -on: - # The dump has to be reconciled with data/ on a schedule of its own. - # weekly-refresh also regenerates it, but only after a multi-hour live - # scrape, and when that scrape runs long the dump is the step that gets - # dropped — which is how the published pages drifted away from the - # records for six weeks. Run a day after the Monday jobs so this picks - # up whatever they merged. - schedule: - - cron: "0 3 * * 2" # Tuesdays 03:00 UTC - workflow_dispatch: - -permissions: - contents: read - -concurrency: - group: dump-refresh - cancel-in-progress: false - -jobs: - dump: - runs-on: ubuntu-latest - # Well under GitHub's 6h ceiling, which reports an over-run as - # "cancelled" rather than failed and so hides the breakage. - timeout-minutes: 355 - env: - TECHAPI_WRITE_TOKEN: ${{ secrets.TECHENGINEBOT_TOKEN || secrets.TECHAPI_TOKEN }} - # seed/validate/dump read the data tree from here. - TECHAPI_DATA_DIR: ${{ github.workspace }}/techapi/data - steps: - - name: Checkout TechEngine - uses: actions/checkout@v4 - - - name: Checkout TechAPI - uses: actions/checkout@v4 - with: - repository: GetTechAPI/TechAPI - path: techapi - token: ${{ secrets.TECHENGINEBOT_TOKEN || secrets.TECHAPI_TOKEN || secrets.GITHUB_TOKEN }} - # created_at/updated_at come from each record's git history; with the - # default depth-1 checkout every record would report the tip commit. - fetch-depth: 0 - - - uses: actions/setup-python@v6 - with: - python-version: "3.12" - - - name: Install TechEngine - run: pip install -e . - - - name: Compute date - id: meta - run: echo "date=$(date -u +%Y-%m-%d)" >> "$GITHUB_OUTPUT" - - # Integrity gate: never publish a dump from data that fails the validator. - - name: Validate (schema / range / slug / FK) - run: python -m app.validate - - - name: Generate static dump → site/public - run: python -m app.dump --output ./techapi/site/public - - - name: Build PR body - run: | - { - echo "# Dump refresh — ${{ steps.meta.outputs.date }}" - echo - echo "Regenerates \`site/public/v1\` from the current data with no re-scrape." - echo "Picks up engine-side serialization changes (scoring v2.0.0: hybrid" - echo "absolute+relative scores for smartphones/CPUs/GPUs/SoCs, \`/score\` files," - echo "\`scored\` manifest counts)." - echo - echo "- \`app.validate\` (schema/range/slug/FK): **passed**" - } > pr-body.md - - - name: Upload dump artifact (no-token fallback) - if: env.TECHAPI_WRITE_TOKEN == '' - uses: actions/upload-artifact@v4 - with: - name: dump-${{ steps.meta.outputs.date }} - path: | - techapi/site/public/v1 - techapi/site/public/openapi.json - if-no-files-found: ignore - - - name: Create dump-refresh PR - id: cpr - if: env.TECHAPI_WRITE_TOKEN != '' - # A dump PR touches so many pages that GitHub may answer "diff is taking - # too long to generate" after the PR exists; the next step decides. - continue-on-error: true - uses: peter-evans/create-pull-request@v6 - with: - path: ./techapi - token: ${{ env.TECHAPI_WRITE_TOKEN }} - branch: dump-refresh/${{ steps.meta.outputs.date }} - base: develop - add-paths: | - site/public/v1 - site/public/openapi.json - commit-message: "chore(site): refresh public dump for scoring v2.0.0" - title: "chore(site): refresh public dump (scoring v2.0.0)" - body-path: pr-body.md - committer: TechEngineBot <289859915+TechEngineBot@users.noreply.github.com> - author: TechEngineBot <289859915+TechEngineBot@users.noreply.github.com> - delete-branch: true - - - name: Confirm dump-refresh PR exists - if: steps.cpr.outcome == 'failure' - env: - GH_TOKEN: ${{ env.TECHAPI_WRITE_TOKEN }} - BRANCH: dump-refresh/${{ steps.meta.outputs.date }} - run: | - n=$(gh api "repos/GetTechAPI/TechAPI/pulls?head=GetTechAPI:${BRANCH}&state=open" -q '.[0].number') - if [ -z "$n" ]; then - echo "::error::create-pull-request failed and no open PR exists for ${BRANCH}" - exit 1 - fi - echo "::warning::PR #${n} exists; the failure was GitHub's diff timeout on a large PR" diff --git a/.github/workflows/notify-techapi.yml b/.github/workflows/notify-techapi.yml deleted file mode 100644 index c601442..0000000 --- a/.github/workflows/notify-techapi.yml +++ /dev/null @@ -1,33 +0,0 @@ -name: notify-techapi - -# On every push to main, ping TechAPI so it bumps its TechEngine submodule -# pointer to the new commit. The bump itself happens in TechAPI's bump-engine.yml -# (triggered by this repository_dispatch); TechEngine only fires the signal. -# -# No loop: TechAPI's bump commit lands in TechAPI, never pushed back here. -# Requires TECHAPI_TOKEN (Contents: write on GetTechAPI/TechAPI) — already set. -on: - push: - branches: [main] - # Don't ping TechAPI for commits that only move the TechAPI submodule pointer - # (bump-techapi.yml's own bumps): TechAPI doesn't need to track those, and - # skipping them keeps each real change to exactly one bump — symmetric with - # TechAPI's notify-engine.yml ignoring its TechEngine gitlink. - paths-ignore: - - TechAPI - -permissions: - contents: read - -jobs: - notify: - runs-on: ubuntu-latest - steps: - - name: Dispatch engine-updated to TechAPI - uses: peter-evans/repository-dispatch@v3 - with: - token: ${{ secrets.TECHAPI_TOKEN }} - repository: GetTechAPI/TechAPI - event-type: engine-updated - client-payload: | - {"sha": "${{ github.sha }}", "ref": "${{ github.ref }}"} diff --git a/.github/workflows/techapi-pr-validation-comment.yml b/.github/workflows/techapi-pr-validation-comment.yml index 8b2dc45..11ffbe6 100644 --- a/.github/workflows/techapi-pr-validation-comment.yml +++ b/.github/workflows/techapi-pr-validation-comment.yml @@ -1,12 +1,11 @@ name: techapi-pr-validation-comment -# TechAPI data PRs can ask this repository to validate their head commit and -# leave a curator-facing comment back on the PR. This keeps the PR owned by the -# human contributor while TechEngineBot reports the engine verdict. +# Reusable: TechAPI's pr-validation.yml calls this to validate a PR head commit +# and leave a curator-facing comment on the PR. Called from TechAPI, the default +# github.token is TechAPI's own GITHUB_TOKEN, so the comment is posted by +# github-actions[bot] and no personal or bot-account token is involved. on: - repository_dispatch: - types: [techapi-pr-validate] - workflow_dispatch: + workflow_call: inputs: pr_number: description: "TechAPI PR number to comment on" @@ -16,6 +15,11 @@ on: description: "TechAPI commit SHA to validate" type: string required: true + head_ref: + description: "TechAPI PR head branch" + type: string + required: false + default: "" pr_url: description: "TechAPI PR URL" type: string @@ -23,39 +27,51 @@ on: default: "" base_ref: description: "TechAPI PR base branch (resolved from PR if omitted)" + type: string required: false + default: "" base_sha: description: "TechAPI PR base SHA (resolved from PR if omitted)" + type: string + required: false + default: "" + requested_by: + description: "Login shown as the requester in the comment" + type: string required: false + default: "" permissions: contents: read + pull-requests: write concurrency: - group: techapi-pr-validation-${{ github.event.client_payload.pr_number || inputs.pr_number }} + group: techapi-pr-validation-${{ inputs.pr_number }} cancel-in-progress: true jobs: validate: runs-on: ubuntu-latest env: - TECHAPI_COMMENT_TOKEN: ${{ secrets.TECHENGINEBOT_TOKEN || secrets.TECHAPI_TOKEN }} - TECHAPI_PR_NUMBER: ${{ github.event.client_payload.pr_number || inputs.pr_number }} - TECHAPI_BASE_REF: ${{ github.event.client_payload.base_ref || inputs.base_ref }} - TECHAPI_BASE_SHA: ${{ github.event.client_payload.base_sha || inputs.base_sha }} - TECHAPI_HEAD_SHA: ${{ github.event.client_payload.head_sha || inputs.head_sha }} - TECHAPI_HEAD_REF: ${{ github.event.client_payload.head_ref || '' }} - TECHAPI_PR_URL: ${{ github.event.client_payload.pr_url || inputs.pr_url }} - REQUESTED_BY: ${{ github.event.client_payload.requested_by || github.actor }} + TECHAPI_PR_NUMBER: ${{ inputs.pr_number }} + TECHAPI_BASE_REF: ${{ inputs.base_ref }} + TECHAPI_BASE_SHA: ${{ inputs.base_sha }} + TECHAPI_HEAD_SHA: ${{ inputs.head_sha }} + TECHAPI_HEAD_REF: ${{ inputs.head_ref }} + TECHAPI_PR_URL: ${{ inputs.pr_url }} + REQUESTED_BY: ${{ inputs.requested_by || github.actor }} TECHAPI_DATA_DIR: ${{ github.workspace }}/TechAPI/data TECHAPI_SITE_DIR: ${{ github.workspace }}/TechAPI/site steps: + # In a called workflow a bare checkout would fetch the caller (TechAPI). - name: Checkout TechEngine uses: actions/checkout@v4 + with: + repository: GetTechAPI/TechEngine - name: Resolve actual PR base env: - GH_TOKEN: ${{ env.TECHAPI_COMMENT_TOKEN || github.token }} + GH_TOKEN: ${{ github.token }} shell: bash run: | set -euo pipefail @@ -735,10 +751,16 @@ jobs: cat validation-notes.md } > stats-comment.md + - name: Write job summary + shell: bash + run: cat change-comment.md stats-comment.md >> "$GITHUB_STEP_SUMMARY" + + # Fork PRs get a read-only token, so commenting can fail there; the job + # summary above still carries the full report. - name: Comment on TechAPI PR - if: env.TECHAPI_COMMENT_TOKEN != '' + continue-on-error: true env: - GH_TOKEN: ${{ env.TECHAPI_COMMENT_TOKEN }} + GH_TOKEN: ${{ github.token }} shell: bash run: | set -euo pipefail @@ -764,10 +786,6 @@ jobs: upsert_comment "" change-comment.md change-comment.json upsert_comment "" stats-comment.md stats-comment.json - - name: Warn when comment token is unset - if: env.TECHAPI_COMMENT_TOKEN == '' - run: echo "::warning::TECHENGINEBOT_TOKEN/TECHAPI_TOKEN is not configured; validation ran but no PR comment was posted." - - name: Fail on validation errors if: steps.validate.outputs.status != 'success' || (steps.site_changes.outputs.changed == 'true' && steps.site_build.outputs.status != '0') run: exit 1 diff --git a/.github/workflows/techapi-verify-comment.yml b/.github/workflows/techapi-verify-comment.yml index 5a55c96..a4ce5fe 100644 --- a/.github/workflows/techapi-verify-comment.yml +++ b/.github/workflows/techapi-verify-comment.yml @@ -1,32 +1,47 @@ name: techapi-verify-comment -# On-demand Tier 0 data verification for a TechAPI PR. Triggered by an English -# command comment (`/verify`) on the PR, relayed here as a repository_dispatch by -# TechAPI's verify-command.yml. Checks out the PR head, runs TechAPI's app.verify, -# and posts the green/yellow/red band report back on the PR as TechEngineBot. +# Reusable: Tier 0 data verification for a TechAPI PR. TechAPI calls this from +# verify-report.yml (mode=tier0, every data PR) and verify-command.yml (mode=all, +# on a `/verify` comment). Checks out the PR head, runs app.verify, and posts the +# green/yellow/red band report on the PR with TechAPI's own GITHUB_TOKEN. on: - repository_dispatch: - types: [techapi-verify] - workflow_dispatch: + workflow_call: inputs: pr_number: description: "TechAPI PR number to verify + comment on" + type: string required: true head_sha: description: "TechAPI commit SHA to verify" + type: string required: true base_ref: description: "TechAPI PR base branch (resolved from PR if omitted)" + type: string required: false + default: "" base_sha: description: "TechAPI PR base SHA (resolved from PR if omitted)" + type: string required: false + default: "" + mode: + description: "tier0 = automatic PR report; all = on-demand /verify (Tiers 0-3)" + type: string + required: false + default: "all" + requested_by: + description: "Login shown as the requester in the comment" + type: string + required: false + default: "" permissions: contents: read + pull-requests: write concurrency: - group: techapi-verify-${{ github.event.client_payload.pr_number || inputs.pr_number }}-${{ github.event.client_payload.mode || 'all' }} + group: techapi-verify-${{ inputs.pr_number }}-${{ inputs.mode }} cancel-in-progress: true jobs: @@ -34,42 +49,22 @@ jobs: runs-on: ubuntu-latest env: PYTHONIOENCODING: utf-8 - TECHAPI_COMMENT_TOKEN: ${{ secrets.TECHENGINEBOT_TOKEN || secrets.TECHAPI_TOKEN }} - TECHAPI_PR_NUMBER: ${{ github.event.client_payload.pr_number || inputs.pr_number }} - TECHAPI_BASE_REF: ${{ github.event.client_payload.base_ref || inputs.base_ref }} - TECHAPI_BASE_SHA: ${{ github.event.client_payload.base_sha || inputs.base_sha }} - TECHAPI_HEAD_SHA: ${{ github.event.client_payload.head_sha || inputs.head_sha }} - REQUESTED_BY: ${{ github.event.client_payload.requested_by || github.actor }} - TECHAPI_COMMENT_ID: ${{ github.event.client_payload.comment_id }} - # "tier0" = auto PR report (relayed by TechAPI verify-report.yml); else the - # on-demand /verify all-tiers run. - MODE: ${{ github.event.client_payload.mode || 'all' }} + TECHAPI_PR_NUMBER: ${{ inputs.pr_number }} + TECHAPI_BASE_REF: ${{ inputs.base_ref }} + TECHAPI_BASE_SHA: ${{ inputs.base_sha }} + TECHAPI_HEAD_SHA: ${{ inputs.head_sha }} + REQUESTED_BY: ${{ inputs.requested_by || github.actor }} + MODE: ${{ inputs.mode }} steps: - # Acknowledge the /verify command as TechEngineBot (the bot holds the token; - # the relay side must not react, or it looks like the requester self-reacting). - - name: Acknowledge command (TechEngineBot 👀) - if: env.TECHAPI_COMMENT_TOKEN != '' && env.TECHAPI_COMMENT_ID != '' - uses: actions/github-script@v7 - with: - github-token: ${{ secrets.TECHENGINEBOT_TOKEN || secrets.TECHAPI_TOKEN }} - script: | - const comment_id = Number(process.env.TECHAPI_COMMENT_ID); - if (comment_id) { - try { - await github.rest.reactions.createForIssueComment({ - owner: 'GetTechAPI', repo: 'TechAPI', comment_id, content: 'eyes', - }); - } catch (e) { core.info(`reaction skipped: ${e.message}`); } - } - env: - TECHAPI_COMMENT_ID: ${{ github.event.client_payload.comment_id }} - + # In a called workflow a bare checkout would fetch the caller (TechAPI). - name: Checkout TechEngine uses: actions/checkout@v4 + with: + repository: GetTechAPI/TechEngine - name: Resolve actual PR base env: - GH_TOKEN: ${{ env.TECHAPI_COMMENT_TOKEN || github.token }} + GH_TOKEN: ${{ github.token }} shell: bash run: | set -euo pipefail @@ -132,8 +127,15 @@ jobs: echo VERIFY_EOF } >> "$GITHUB_OUTPUT" - - name: Post verification comment (TechEngineBot) - if: env.TECHAPI_COMMENT_TOKEN != '' + - name: Write job summary + env: + REPORT: ${{ steps.verify.outputs.report }} + run: printf '%s\n' "$REPORT" >> "$GITHUB_STEP_SUMMARY" + + # Fork PRs get a read-only token, so commenting can fail there; the job + # summary above still carries the report. + - name: Post verification comment + continue-on-error: true uses: actions/github-script@v7 env: REPORT: ${{ steps.verify.outputs.report }} @@ -141,7 +143,6 @@ jobs: REQUESTED_BY: ${{ env.REQUESTED_BY }} MODE: ${{ env.MODE }} with: - github-token: ${{ secrets.TECHENGINEBOT_TOKEN || secrets.TECHAPI_TOKEN }} script: | const report = (process.env.REPORT || '').trim() || '(no output)'; const by = process.env.REQUESTED_BY || 'someone'; @@ -152,12 +153,12 @@ jobs: : ''; const head = isTier0 ? ['## 🔎 Data verification — Tier 0 (offline existence/trust)', '', - 'Scored by `app.verify`; posted by **TechEngineBot**. Informational only — the structural gate (`app.validate`) is separate and authoritative for merge.', '', + 'Scored by `app.verify`. Informational only — the structural gate (`app.validate`) is separate and authoritative for merge.', '', report] : [report]; // `pr` emits its own H2 heading + tier sections const footer = isTier0 ? 'green = authoritative source + complete + consistent · yellow = plausible, needs confirmation · red = sparse/weak source or a hard contradiction. Promotion to `verified` runs in the scheduled `verify-network` workflow.' - : `Requested by @${by} via \`/verify\` · scored by \`app.verify\`, posted by **TechEngineBot**. Informational only — the structural gate (\`app.validate\`) is separate; Tier 3 here is dry-run.`; + : `Requested by @${by} via \`/verify\` · scored by \`app.verify\`. Informational only — the structural gate (\`app.validate\`) is separate; Tier 3 here is dry-run.`; const body = [marker, ...head, '', footer].join('\n'); const owner = 'GetTechAPI'; const repo = 'TechAPI'; @@ -171,7 +172,3 @@ jobs: } else { await github.rest.issues.createComment({ owner, repo, issue_number, body }); } - - - name: Dormant when no bot token - if: env.TECHAPI_COMMENT_TOKEN == '' - run: echo "::warning::No TECHENGINEBOT_TOKEN/TECHAPI_TOKEN; verification ran but no comment was posted." diff --git a/.github/workflows/validate-data.yml b/.github/workflows/validate-data.yml index d4cacf7..89e66e1 100644 --- a/.github/workflows/validate-data.yml +++ b/.github/workflows/validate-data.yml @@ -26,7 +26,10 @@ jobs: validate: runs-on: ubuntu-latest steps: + # When called from TechAPI a bare checkout would fetch the caller instead. - uses: actions/checkout@v4 + with: + repository: GetTechAPI/TechEngine - uses: actions/checkout@v4 with: repository: GetTechAPI/TechAPI diff --git a/.github/workflows/verify-network.yml b/.github/workflows/verify-network.yml index 12c1df0..374dd29 100644 --- a/.github/workflows/verify-network.yml +++ b/.github/workflows/verify-network.yml @@ -2,11 +2,14 @@ name: verify-network # Network verification tiers (source-URL liveness + external cross-reference) and # verified promotion, run in TechEngine against a TechAPI checkout. NEVER on -# pull_request — external sites, rate-limited. Scheduled + manual. Promotions are -# committed as TechEngineBot and opened as a human-gated PR on TechAPI; the job -# hard-guards that nothing but `verified` flags + the ledger changed. +# pull_request — external sites, rate-limited. Promotions are opened as a +# human-gated PR on TechAPI; the job hard-guards that nothing but `verified` +# flags + the ledger changed. +# +# Reusable: TechAPI's weekly-data.yml calls this, so github.token is TechAPI's +# own GITHUB_TOKEN and the branch + PR are created by github-actions[bot]. on: - workflow_dispatch: + workflow_call: inputs: apply: description: "Flip verified->true and open a PR on TechAPI (else dry-run)" @@ -14,18 +17,20 @@ on: default: false max_urls: description: "Frontier records to URL-check" + type: string default: "2000" max_wikidata: description: "Maximum Wikidata QIDs to batch-check" + type: string default: "20000" max_crossref: description: "Records to cross-reference" + type: string default: "500" - schedule: - - cron: "0 4 * * 1" # Mondays 04:00 UTC permissions: - contents: read + contents: write + pull-requests: write concurrency: group: verify-network @@ -36,12 +41,14 @@ jobs: runs-on: ubuntu-latest env: PYTHONIOENCODING: utf-8 - APPLY: ${{ github.event_name == 'schedule' || github.event.inputs.apply == 'true' }} + APPLY: ${{ inputs.apply }} TECHAPI_DATA_DIR: ${{ github.workspace }}/TechAPI/data - WRITE_TOKEN: ${{ secrets.TECHENGINEBOT_TOKEN || secrets.TECHAPI_TOKEN }} steps: + # In a called workflow a bare checkout would fetch the caller (TechAPI). - name: Checkout TechEngine uses: actions/checkout@v4 + with: + repository: GetTechAPI/TechEngine - name: Checkout TechAPI uses: actions/checkout@v4 @@ -53,7 +60,6 @@ jobs: # bot branch behind whatever is already integrated. ref: develop fetch-depth: 0 - token: ${{ secrets.TECHENGINEBOT_TOKEN || secrets.TECHAPI_TOKEN || secrets.GITHUB_TOKEN }} - uses: actions/setup-python@v6 with: @@ -73,13 +79,13 @@ jobs: run: python -m app.verify score --no-cache - name: Tier 1 source-URL liveness - run: python -m app.verify check-urls --max ${{ github.event.inputs.max_urls || '2000' }} + run: python -m app.verify check-urls --max ${{ inputs.max_urls }} - name: Batched Wikidata source liveness - run: python -m app.verify check-wikidata --max ${{ github.event.inputs.max_wikidata || '20000' }} + run: python -m app.verify check-wikidata --max ${{ inputs.max_wikidata }} - name: Tier 2 external cross-reference - run: python -m app.verify crossref --max ${{ github.event.inputs.max_crossref || '500' }} + run: python -m app.verify crossref --max ${{ inputs.max_crossref }} - name: Tier 3 promote (dry-run) run: python -m app.verify promote @@ -118,10 +124,10 @@ jobs: print("diff scope OK: only verified toggles") PY - - name: Open promotion PR on TechAPI (TechEngineBot) + - name: Open promotion PR on TechAPI if: ${{ env.APPLY == 'true' }} env: - GH_TOKEN: ${{ env.WRITE_TOKEN }} + GH_TOKEN: ${{ github.token }} run: | set -e cd TechAPI @@ -129,8 +135,8 @@ jobs: echo "no promotions to commit"; exit 0 fi branch="verify/promote-${{ github.run_id }}" - git config user.name "TechEngineBot" - git config user.email "289859915+TechEngineBot@users.noreply.github.com" + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" git checkout -b "$branch" git add data/ git commit -m "data(verify): promote records to verified (reality cross-reference) diff --git a/.github/workflows/verify-status.yml b/.github/workflows/verify-status.yml deleted file mode 100644 index 966e9e2..0000000 --- a/.github/workflows/verify-status.yml +++ /dev/null @@ -1,66 +0,0 @@ -name: verify-status - -# Keep TechAPI's data/_verify/status.json (the verification-state aggregate: how -# many records are verified + Tier 0 bands per category) current. Runs in -# TechEngine against a TechAPI checkout and commits the refreshed file back to -# TechAPI develop as TechEngineBot. Fires when TechAPI main changes (relayed by -# TechAPI's notify-engine -> techapi-updated) and daily as a backstop. The -# "commit only if changed" guard means the status commit can't loop. -on: - repository_dispatch: - types: [techapi-updated] - schedule: - - cron: "30 5 * * *" # daily 05:30 UTC backstop - workflow_dispatch: - -permissions: - contents: read - -concurrency: - group: verify-status - cancel-in-progress: false - -jobs: - status: - runs-on: ubuntu-latest - env: - PYTHONIOENCODING: utf-8 - TECHAPI_DATA_DIR: ${{ github.workspace }}/TechAPI/data - steps: - - name: Checkout TechEngine - uses: actions/checkout@v4 - - - name: Checkout TechAPI - uses: actions/checkout@v4 - with: - repository: GetTechAPI/TechAPI - path: TechAPI - # TechAPI follows git-flow. This already resolved to develop as the - # default branch — pin it so it cannot drift from the push target. - ref: develop - token: ${{ secrets.TECHENGINEBOT_TOKEN || secrets.TECHAPI_TOKEN || secrets.GITHUB_TOKEN }} - - - uses: actions/setup-python@v6 - with: - python-version: "3.12" - - - name: Install TechEngine - run: pip install -e . - - - name: Regenerate verification status aggregate - run: python -m app.verify status --output TechAPI/data/_verify/status.json - - - name: Commit to TechAPI if changed (TechEngineBot) - run: | - cd TechAPI - git add data/_verify/status.json - if git diff --cached --quiet; then - echo "verification status unchanged"; exit 0 - fi - git config user.name "TechEngineBot" - git config user.email "289859915+TechEngineBot@users.noreply.github.com" - git commit -m "chore(verify): refresh verification status aggregate" - # Rebase first: several bots write to develop, and a status refresh is - # never worth failing the run over a race. - git pull --rebase origin develop - git push origin HEAD:develop diff --git a/.github/workflows/weekly-ingest.yml b/.github/workflows/weekly-ingest.yml index af0b3f3..dbac82e 100644 --- a/.github/workflows/weekly-ingest.yml +++ b/.github/workflows/weekly-ingest.yml @@ -2,15 +2,15 @@ name: weekly-ingest # Weekly: scrape upstream catalogs, draft missing SKUs into a TechAPI worktree, # open a PR for curator review. +# +# Reusable: TechAPI's weekly-data.yml calls this, so github.token is TechAPI's +# own GITHUB_TOKEN and the branch + PR are created by github-actions[bot]. on: - schedule: - - cron: "29 6 * * 1" # Mondays 06:29 UTC (weekly to keep curator review load sane) - workflow_dispatch: + workflow_call: inputs: category: - description: "Category to ingest" - type: choice - options: [cpu, gpu, smartphone] + description: "Category to ingest (cpu, gpu or smartphone)" + type: string default: cpu limit: description: "Max candidates per source" @@ -22,7 +22,8 @@ on: default: false permissions: - contents: read + contents: write + pull-requests: write jobs: ingest: @@ -31,13 +32,12 @@ jobs: CATEGORY: ${{ inputs.category || 'cpu' }} LIMIT: ${{ inputs.limit || '50' }} INCLUDE_DRAFTS: ${{ inputs.include_drafts || 'false' }} - TECHAPI_TOKEN: ${{ secrets.TECHAPI_TOKEN }} - TECHAPI_WRITE_TOKEN: ${{ secrets.TECHENGINEBOT_TOKEN || secrets.TECHAPI_TOKEN }} steps: + # In a called workflow a bare checkout would fetch the caller (TechAPI). - uses: actions/checkout@v4 + with: + repository: GetTechAPI/TechEngine - # Use the PAT when present so we can push to TechAPI later; - # fall back to the default token for read-only test runs. - uses: actions/checkout@v4 with: repository: GetTechAPI/TechAPI @@ -45,7 +45,6 @@ jobs: # TechAPI follows git-flow: ingest is integration work, so branch from # develop and open the PR there. main only moves through a release PR. ref: develop - token: ${{ secrets.TECHENGINEBOT_TOKEN || secrets.TECHAPI_TOKEN || secrets.GITHUB_TOKEN }} - uses: actions/setup-python@v6 with: @@ -115,17 +114,13 @@ jobs: - name: Open PR against TechAPI if: steps.changes.outputs.has_changes == 'true' env: - GH_TOKEN: ${{ env.TECHAPI_WRITE_TOKEN }} + GH_TOKEN: ${{ github.token }} run: | set -euo pipefail - if [ -z "${GH_TOKEN:-}" ]; then - echo "::warning::Ingest produced additions but TECHAPI_TOKEN is unset; skipping PR. Summary attached as artifact." - exit 0 - fi cd TechAPI BRANCH="ingest/${CATEGORY}-$(date -u +%Y%m%d-%H%M%S)" - git config user.name "TechEngineBot" - git config user.email "289859915+TechEngineBot@users.noreply.github.com" + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" git checkout -b "$BRANCH" git add data/ git commit -m "feat(data/${CATEGORY}): weekly ingest" diff --git a/.github/workflows/weekly-refresh.yml b/.github/workflows/weekly-refresh.yml index e0c1b65..1eb97b7 100644 --- a/.github/workflows/weekly-refresh.yml +++ b/.github/workflows/weekly-refresh.yml @@ -8,15 +8,10 @@ name: weekly-refresh # # TechEngine owns collection/validation/dump; TechAPI owns data/site/deploy. # -# Token model: TechAPI is public, so the checkout uses the default GITHUB_TOKEN -# (read-only) as a fallback — that lets the collect→validate→dump path run on -# every push even when no PAT is configured. Only the cross-repo PR needs write -# access, so just that step is guarded by a PAT. Prefer TECHENGINEBOT_TOKEN -# so PRs are authored by TechEngineBot; fall back to TECHAPI_TOKEN. +# Reusable: TechAPI's weekly-data.yml calls this, so github.token is TechAPI's +# own GITHUB_TOKEN and the branch + PR are created by github-actions[bot]. on: - schedule: - - cron: "0 6 * * 1" # Mondays 06:00 UTC (live scrape: weekly to respect upstream ToS/rate limits) - workflow_dispatch: + workflow_call: inputs: sleep: description: "Seconds between scrape requests (politeness)" @@ -24,7 +19,8 @@ on: default: "1.0" permissions: - contents: read + contents: write + pull-requests: write concurrency: group: weekly-refresh @@ -38,22 +34,20 @@ jobs: timeout-minutes: 330 env: SLEEP: ${{ inputs.sleep || '1.0' }} - TECHAPI_TOKEN: ${{ secrets.TECHAPI_TOKEN }} - TECHAPI_WRITE_TOKEN: ${{ secrets.TECHENGINEBOT_TOKEN || secrets.TECHAPI_TOKEN }} # Validate/seed/dump all read the data tree from this env var. TECHAPI_DATA_DIR: ${{ github.workspace }}/techapi/data steps: + # In a called workflow a bare checkout would fetch the caller (TechAPI). - name: Checkout TechEngine uses: actions/checkout@v4 + with: + repository: GetTechAPI/TechEngine - # Read-only with the default token when no PAT is set; the PAT (when - # present) lets peter-evans push the refresh branch back later. - name: Checkout TechAPI uses: actions/checkout@v4 with: repository: GetTechAPI/TechAPI path: techapi - token: ${{ secrets.TECHENGINEBOT_TOKEN || secrets.TECHAPI_TOKEN || secrets.GITHUB_TOKEN }} fetch-depth: 0 - uses: actions/setup-python@v6 @@ -182,26 +176,13 @@ jobs: pr-body.md if-no-files-found: ignore - # Fallback when no PAT: keep the regenerated dump so the work isn't lost. - - name: Upload dump artifact (no-token fallback) - if: env.TECHAPI_WRITE_TOKEN == '' - uses: actions/upload-artifact@v4 - with: - name: dump-${{ steps.meta.outputs.date }} - path: | - techapi/site/public/v1 - techapi/site/public/openapi.json - if-no-files-found: ignore - - # --- 4. Dated branch + auto PR against TechAPI (only with a PAT) --- + # --- 4. Dated branch + auto PR against TechAPI --- - name: Create refresh PR id: create_pr - if: env.TECHAPI_WRITE_TOKEN != '' continue-on-error: true uses: peter-evans/create-pull-request@v6 with: path: ./techapi - token: ${{ env.TECHAPI_WRITE_TOKEN }} branch: refresh/${{ steps.meta.outputs.date }} base: develop add-paths: | @@ -211,14 +192,13 @@ jobs: commit-message: "chore(data): weekly refresh ${{ steps.meta.outputs.date }}" title: "chore(data): weekly refresh ${{ steps.meta.outputs.date }}" body-path: pr-body.md - committer: TechEngineBot <289859915+TechEngineBot@users.noreply.github.com> - author: TechEngineBot <289859915+TechEngineBot@users.noreply.github.com> + committer: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> + author: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> delete-branch: true - name: Confirm refresh PR exists - if: env.TECHAPI_WRITE_TOKEN != '' env: - GH_TOKEN: ${{ env.TECHAPI_WRITE_TOKEN }} + GH_TOKEN: ${{ github.token }} run: | set -euo pipefail branch="refresh/${{ steps.meta.outputs.date }}" diff --git a/README.md b/README.md index 60e8ab5..8d9a716 100644 --- a/README.md +++ b/README.md @@ -33,31 +33,34 @@ site/ # Astro engine landing (deploys to Pages) docs/ # SPEC / DATA_PIPELINE / DEVELOPMENT TechAPI/ # submodule -> GetTechAPI/TechAPI (clickable @ link) .github/workflows/ - validate-data.yml # workflow_call: PR-time data validation for TechAPI - weekly-refresh.yml # cron: live-scrape -> integrity gate -> dump -> PR to TechAPI - weekly-ingest.yml # cron: draft new SKUs, open PR against TechAPI - coverage-report.yml # cron: gap report, sticky issue (TechEngine + TechAPI) - refresh-data.yml # smoke-test: rebuild the dump on engine (app/**) changes - notify-techapi.yml # push to main: ping TechAPI to bump its TechEngine submodule - bump-techapi.yml # dispatch: advance this repo's TechAPI submodule pointer - deploy-pages.yml # build & deploy engine site + dump - test.yml # lint + type-check + tests + validate-data.yml # workflow_call: PR-time data validation for TechAPI + techapi-pr-validation-comment.yml # workflow_call: TechAPI PR review comment + techapi-verify-comment.yml # workflow_call: Tier 0 / `/verify` report on a TechAPI PR + weekly-refresh.yml # workflow_call: live-scrape -> integrity gate -> dump -> PR + weekly-ingest.yml # workflow_call: draft new SKUs, open PR against TechAPI + verify-network.yml # workflow_call: Tier 1-3 checks + verified promotion PR + coverage-report.yml # cron (weekly): gap report, sticky issue in this repo + refresh-data.yml # smoke-test: rebuild the dump on engine (app/**) changes + deploy-pages.yml # build & deploy engine site + dump + test.yml # lint + type-check + tests ``` ## How The Two Repos Connect Both repos live in the **GetTechAPI** org and each includes the other as a git -**submodule** (a clickable `@ ` pin). Three automations keep them in step: - -- **validate-data.yml** (`workflow_call`) - TechAPI's PR-time check calls into - TechEngine to validate its data. -- **weekly-refresh.yml** - live-scrapes benchmarks, runs the full-dataset - integrity gate (`app.validate` + `integrity_check.py --strict`), regenerates - the static dump, and opens a dated refresh PR against TechAPI. -- **Submodule autosync** - every push to TechEngine `main` fires - `notify-techapi.yml`, which pings TechAPI to bump its TechEngine pointer; - conversely `bump-techapi.yml` advances TechEngine's TechAPI pointer when - TechAPI changes. Bumps are loop-guarded, so each real change converges to one. +**submodule** (a clickable `@ ` pin). Pointers are bumped by hand; nothing +syncs them automatically. + +Every automation that touches TechAPI runs **in TechAPI**, which calls the +TechEngine workflows above as reusable workflows (`workflow_call`, `@main`). +A called workflow gets the caller's `GITHUB_TOKEN`, so branches, PRs and +comments are made by `github-actions[bot]`. No personal access token or bot +account is used anywhere (ADR-017): + +- **validate-data.yml / techapi-pr-validation-comment.yml / + techapi-verify-comment.yml** - TechAPI PRs are validated and reviewed. +- **weekly-refresh.yml / weekly-ingest.yml / verify-network.yml** - TechAPI's + `weekly-data.yml` runs them and they open PRs against TechAPI `develop`. Every Python entry point reads data from a **TechAPI checkout**. The location can be overridden via `TECHAPI_DATA_DIR`; by default TechEngine supports both @@ -96,7 +99,7 @@ Spins up Postgres 16, seeds from the mounted TechAPI checkout, serves on `:8000` and surface missing SKUs as a sticky weekly issue ([#1](https://github.com/GetTechAPI/TechEngine/issues/1)) - [x] **Weekly ingestion crawler** - scrape canonical sources and open PRs - against TechAPI with new SKUs (requires the `TECHAPI_TOKEN` secret to push) + against TechAPI with new SKUs ([#2](https://github.com/GetTechAPI/TechEngine/issues/2)) - [x] **Weekly refresh pipeline** - live benchmark enrichment -> full-dataset integrity gate -> static dump -> dated refresh PR (`weekly-refresh.yml`) diff --git a/app/verify/baseline.py b/app/verify/baseline.py index f182fe6..3d1d248 100644 --- a/app/verify/baseline.py +++ b/app/verify/baseline.py @@ -13,7 +13,7 @@ def main(path: str) -> int: print("_status.json not available on this ref._") return 0 d = json.loads(p.read_text(encoding="utf-8")) - print(f"_As of {d['generated_at']} (daily `verify-status` aggregate)._\n") + print(f"_As of {d['generated_at']} (post-merge `dump-develop` aggregate)._\n") print("| category | total | green | yellow | red |") print("| --- | ---: | ---: | ---: | ---: |") for cat, c in d["by_category"].items(): diff --git a/app/verify/cli.py b/app/verify/cli.py index 1245d36..fdac18d 100644 --- a/app/verify/cli.py +++ b/app/verify/cli.py @@ -310,8 +310,7 @@ def cmd_status(args: argparse.Namespace) -> int: out = args.output or (VERIFY_DIR / "status.json") # Idempotence: only `generated_at` changes between runs when the verification # numbers are unchanged. Preserve the previous timestamp so the file stays - # byte-identical — otherwise every run rewrites it, commits, and (via the - # techapi-updated dispatch) re-triggers verify-status in an endless loop. + # byte-identical — otherwise every run rewrites it and commits a no-op change. if not args.stdout and out.exists(): try: prev = json.loads(out.read_text(encoding="utf-8")) diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index 941de17..220ea68 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -61,9 +61,10 @@ scripts/ dump.py DB → static JSON dump (replays API in-process) → ./dump tests/ unit/ + integration/ (conftest seeds a temp SQLite from data/) docs/ SPEC.md, DATA_PIPELINE.md, DEVELOPMENT.md -.github/workflows/ test.yml, validate-data.yml, weekly-refresh.yml, weekly-ingest.yml, - coverage-report.yml, refresh-data.yml, notify-techapi.yml, - bump-techapi.yml, deploy-pages.yml +.github/workflows/ test.yml, deploy-pages.yml, refresh-data.yml, coverage-report.yml, and + the reusable workflows TechAPI calls: validate-data.yml, + techapi-pr-validation-comment.yml, techapi-verify-comment.yml, + weekly-refresh.yml, weekly-ingest.yml, verify-network.yml ``` > Note: **data folders are singular** (`data/soc/…`) but **API routes are plural** diff --git a/docs/SPEC.md b/docs/SPEC.md index 562ecf2..6cd06e1 100644 --- a/docs/SPEC.md +++ b/docs/SPEC.md @@ -1442,37 +1442,34 @@ chore: bump fastapi to 0.115 ### 16.1 GitHub Actions 워크플로 (실제) +> ADR-017 (2026-10-29): 개인 토큰(PAT)과 봇 계정 토큰을 쓰는 워크플로를 모두 없앴다. TechAPI를 건드리는 자동화는 전부 **TechAPI 안에서** 돌고, TechEngine 워크플로를 재사용 워크플로(`workflow_call`)로 호출한다. 호출된 워크플로는 호출한 쪽의 `GITHUB_TOKEN`을 받으므로 브랜치·PR·댓글은 `github-actions[bot]`이 만든다. 서브모듈 포인터는 손으로 올린다. + **TechAPI** | 워크플로 | 트리거 | 작업 | |---|---|---| -| `validate-data.yml` | PR (data/ 변경) | JSON 스키마 검증 | -| `pr-metadata.yml` | PR 열림 | 담당자·라벨·마일스톤·프로젝트 필드 자동 부여 | -| `request-engine-pr-validation.yml` | PR | TechEngine에 검증 요청 (relay) | -| `bump-engine.yml` | TechEngine 릴리스 | 서브모듈 포인터 자동 bump | -| `notify-engine.yml` | `data/` push | TechEngine에 변경 알림 (무한루프 방지용 paths-ignore 적용) | +| `validate-data.yml` | PR, `main`/`develop` push (data/ 변경) | 자체 검증 + TechEngine `validate-data.yml` 호출 | +| `pr-validation.yml` | PR (data/·site/ 변경) | TechEngine `techapi-pr-validation-comment.yml` 호출 → 리뷰 댓글 | +| `verify-report.yml` | PR (data/ 변경) | TechEngine `techapi-verify-comment.yml`(tier0) 호출 → Tier 0 댓글 | +| `verify-command.yml` | PR 댓글 `/verify` (OWNER/MEMBER/COLLABORATOR) | TechEngine `techapi-verify-comment.yml`(all) 호출 | +| `dump-develop.yml` | `develop` push (data/ 변경) + 수동 | 정적 덤프 + `data/_verify/status.json` 갱신 후 develop에 커밋 | | `deploy-pages.yml` | `main` push | GitHub Pages 배포 (공개 사이트) | -| `verify-command.yml` | 수동/커맨트 트리거 | 검증 계층 커맨드 실행 | -| `verify-report.yml` | PR | Tier 0 분석 봇 코멘트 | +| `weekly-data.yml` | 수동 (검증 후 cron 활성화) | TechEngine `weekly-refresh`/`weekly-ingest`/`verify-network` 호출 → develop 대상 PR | **TechEngine** | 워크플로 | 트리거 | 작업 | |---|---|---| | `test.yml` | PR, push | 테스트 + 린트 | -| `validate-data.yml` | PR | TechAPI 데이터 스키마 검증 | -| `techapi-pr-validation-comment.yml` | TechAPI PR | `npm run build`로 사이트 빌드 검증 + TechEngineBot 리뷰 코멘트 | -| `techapi-verify-comment.yml` | TechAPI PR | 검증 계층 분석 코멘트 | -| `weekly-ingest.yml` | cron (주간) | 신규 SKU 수집 → TechAPI PR | -| `weekly-refresh.yml` | cron (주간) | 실사이트 스크레이핑 + 덤프 갱신 | -| `dump-refresh.yml` | cron + 수동 | 스크레이핑 없이 현재 데이터로 정적 덤프만 재생성 | -| `refresh-data.yml` | 수동 | 데이터 리프레시 (tar 묶음 업로드) | -| `verify-network.yml` | cron (월요일) + 수동(`apply` 입력) | Tier 1-3 네트워크 검증 + `verified` 승급, TechAPI에 human-gated PR | -| `verify-status.yml` | `data/` push + 매일 | `data/_verify/status.json` 집계 갱신 | -| `bump-techapi.yml` | TechAPI 릴리스 | 서브모듈 포인터 자동 bump | -| `notify-techapi.yml` | TechEngine push | TechAPI에 변경 알림 | -| `coverage-report.yml` | 매일 | 커버리지 갭 이슈 자동 갱신 | -| `deploy-pages.yml` | `main` push | GitHub Pages 배포 | +| `deploy-pages.yml` | `main` push | 엔진 사이트 GitHub Pages 배포 | +| `refresh-data.yml` | `app/**` push + 수동 | 덤프 생성 스모크 테스트 (tar 묶음 업로드) | +| `coverage-report.yml` | cron (월요일) + 수동 | 커버리지 갭 이슈 갱신 (TechEngine 레포만) | +| `validate-data.yml` | `workflow_call` | TechAPI 데이터 스키마 검증 | +| `techapi-pr-validation-comment.yml` | `workflow_call` | 사이트 빌드·데이터 검증 + PR 리뷰 댓글 | +| `techapi-verify-comment.yml` | `workflow_call` | 검증 계층 분석 댓글 | +| `weekly-refresh.yml` | `workflow_call` | 실사이트 스크레이핑 + 무결성 게이트 + 덤프 → PR | +| `weekly-ingest.yml` | `workflow_call` | 신규 SKU 수집 → PR | +| `verify-network.yml` | `workflow_call` (`apply` 입력) | Tier 1-3 네트워크 검증 + `verified` 승급 PR | **TechMachine** (레포 관리 전용, 서비스 코드 아님) — 매일 org 전체 워크플로 상태 점검, 문제 목록이 바뀔 때만 메인테이너 멘션 알림. 위성 레포 생성 틀도 여기서 제공. @@ -1489,7 +1486,7 @@ chore: bump fastapi to 0.115 ↓ 3. deploy-pages.yml이 site/를 빌드해 GitHub Pages에 배포 ↓ -4. notify-engine.yml이 TechEngine에 알림 → bump-engine이 필요시 반영 +4. (TechEngine 서브모듈 포인터는 필요할 때 손으로 올린다) ``` ### 16.4 롤백 @@ -2008,6 +2005,20 @@ TechAPI의 **핵심 목표 중 하나**. TechPicks 외에 다양한 앱·플랫 - **근거(약관)**: Play 개발자 배포 계약에 콘솔 제공 정보의 기밀·재배포 제한 조항이 없고, 같은 내보내기를 Apache-2.0으로 공개한 저장소(hossain-khan/android-device-catalog-parser)가 있다. 명시적 허락도 없으므로 사실 정보(사양 수치)만 담고 원본 파일은 저장소에 넣지 않는다. - **영향**: TechEngine #121(스키마), TechAPI 데이터 PR(보강·카탈로그·브랜드). +### ADR-017: GitHub Actions에서 개인 토큰·봇 계정 제거, TechAPI가 TechEngine 재사용 워크플로를 호출 + +- **날짜**: 2026-10-29 +- **상태**: Accepted +- **배경**: TechAPI 워크플로는 `ENGINE_TOKEN`·`PROJECT_TOKEN`(메인테이너 개인 PAT)으로 TechEngine에 이벤트를 중계하고 프로젝트 보드를 수정했고, TechEngine 워크플로는 `TECHAPI_TOKEN`(개인 PAT)·`TECHENGINEBOT_TOKEN`(별도 봇 계정)으로 TechAPI에 push·PR·댓글·이슈를 만들었다. 양방향 서브모듈 자동 bump까지 겹쳐 변경 하나가 계정 활동 여러 건으로 불어났고, 자동화가 실제 계정 이름으로 실행되어 계정 정지로 이어졌다(봇 계정도 한 차례 정지). +- **결정**: + 1. 워크플로에서 PAT·봇 계정 토큰을 쓰지 않는다. 각 레포 기본 `GITHUB_TOKEN`만 쓴다. + 2. TechAPI를 건드리는 작업은 TechAPI에서 실행하고, TechEngine 워크플로를 `workflow_call`로 호출한다(TechEngine은 공개 레포라 토큰 없이 체크아웃된다). 호출된 워크플로는 호출한 쪽의 `GITHUB_TOKEN`을 받으므로 결과물은 `github-actions[bot]` 이름으로 남는다. + 3. 레포 간 `repository_dispatch` 중계와 서브모듈 자동 bump(`notify-*`, `bump-*`)는 없앤다. + 4. `verify-status`는 TechAPI `dump-develop`에 합치고, `dump-refresh`는 `dump-develop`의 수동 전체 재생성으로 대체한다. 커버리지 리포트는 주 1회, TechEngine 레포 이슈만 갱신한다. + 5. 스크레이핑 워크플로(`weekly-data.yml`)는 수동 실행으로 검증한 뒤에 cron을 켠다. +- **트레이드오프**: `GITHUB_TOKEN`이 만든 PR은 다른 워크플로를 트리거하지 않으므로, 주간 PR은 생성 전에 같은 작업 안에서 검증을 끝낸다. 포크 PR에는 토큰이 읽기 전용이라 댓글 대신 Job Summary로 결과를 남긴다. Projects 보드 필드 자동 입력(`pr-metadata`)은 없애고 GitHub Projects 내장 Auto-add를 쓴다. +- **필요 설정**: 두 레포 Settings → Actions → General에서 "Allow GitHub Actions to create and approve pull requests"를 켜야 PR 생성이 된다. + --- ## 24. 용어집 From 89cc59549cc8686cee3ccddbd16af008f30dc410 Mon Sep 17 00:00:00 2001 From: Seungpyo1007 Date: Thu, 29 Oct 2026 12:19:51 +0900 Subject: [PATCH 2/3] docs(spec): limit TechEngineBot to maintainer-requested final reviews --- docs/SPEC.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/SPEC.md b/docs/SPEC.md index 6cd06e1..7e3cef9 100644 --- a/docs/SPEC.md +++ b/docs/SPEC.md @@ -1455,6 +1455,7 @@ chore: bump fastapi to 0.115 | `dump-develop.yml` | `develop` push (data/ 변경) + 수동 | 정적 덤프 + `data/_verify/status.json` 갱신 후 develop에 커밋 | | `deploy-pages.yml` | `main` push | GitHub Pages 배포 (공개 사이트) | | `weekly-data.yml` | 수동 (검증 후 cron 활성화) | TechEngine `weekly-refresh`/`weekly-ingest`/`verify-network` 호출 → develop 대상 PR | +| `final-review.yml` | PR 댓글 `/final-review` (OWNER/MEMBER) | PR head의 체크 결과를 모아 TechEngineBot이 최종 확인 댓글 1개 작성 | **TechEngine** @@ -2016,6 +2017,7 @@ TechAPI의 **핵심 목표 중 하나**. TechPicks 외에 다양한 앱·플랫 3. 레포 간 `repository_dispatch` 중계와 서브모듈 자동 bump(`notify-*`, `bump-*`)는 없앤다. 4. `verify-status`는 TechAPI `dump-develop`에 합치고, `dump-refresh`는 `dump-develop`의 수동 전체 재생성으로 대체한다. 커버리지 리포트는 주 1회, TechEngine 레포 이슈만 갱신한다. 5. 스크레이핑 워크플로(`weekly-data.yml`)는 수동 실행으로 검증한 뒤에 cron을 켠다. + 6. **예외 — TechEngineBot 최종 확인**: TechEngineBot 계정은 중요한 PR의 최종 확인자 역할로만 쓴다. 메인테이너(OWNER/MEMBER)가 `/final-review` 댓글을 달 때만 TechAPI `final-review.yml`이 돌고, PR 하나에 댓글 하나(재실행 시 같은 댓글 수정)만 남긴다. 조회는 `GITHUB_TOKEN`으로 하고, `TECHENGINEBOT_TOKEN`(TechAPI 레포 한정 fine-grained 토큰, Pull requests: write)은 그 댓글에만 쓴다. 스케줄·push·PR 이벤트로 TechEngineBot이 움직이는 워크플로는 만들지 않는다. - **트레이드오프**: `GITHUB_TOKEN`이 만든 PR은 다른 워크플로를 트리거하지 않으므로, 주간 PR은 생성 전에 같은 작업 안에서 검증을 끝낸다. 포크 PR에는 토큰이 읽기 전용이라 댓글 대신 Job Summary로 결과를 남긴다. Projects 보드 필드 자동 입력(`pr-metadata`)은 없애고 GitHub Projects 내장 Auto-add를 쓴다. - **필요 설정**: 두 레포 Settings → Actions → General에서 "Allow GitHub Actions to create and approve pull requests"를 켜야 PR 생성이 된다. From dc314387be920ff72d7459ae1d435d91d37d2d06 Mon Sep 17 00:00:00 2001 From: Seungpyo1007 Date: Thu, 29 Oct 2026 12:28:38 +0900 Subject: [PATCH 3/3] docs(spec): reword ADR-017 background and final-review access --- docs/SPEC.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/SPEC.md b/docs/SPEC.md index 7e3cef9..d4b8762 100644 --- a/docs/SPEC.md +++ b/docs/SPEC.md @@ -1455,7 +1455,7 @@ chore: bump fastapi to 0.115 | `dump-develop.yml` | `develop` push (data/ 변경) + 수동 | 정적 덤프 + `data/_verify/status.json` 갱신 후 develop에 커밋 | | `deploy-pages.yml` | `main` push | GitHub Pages 배포 (공개 사이트) | | `weekly-data.yml` | 수동 (검증 후 cron 활성화) | TechEngine `weekly-refresh`/`weekly-ingest`/`verify-network` 호출 → develop 대상 PR | -| `final-review.yml` | PR 댓글 `/final-review` (OWNER/MEMBER) | PR head의 체크 결과를 모아 TechEngineBot이 최종 확인 댓글 1개 작성 | +| `final-review.yml` | PR 댓글 `/final-review` (OWNER/MEMBER/COLLABORATOR) | PR head의 체크 결과를 모아 TechEngineBot이 최종 확인 댓글 1개 작성 | **TechEngine** @@ -2010,14 +2010,14 @@ TechAPI의 **핵심 목표 중 하나**. TechPicks 외에 다양한 앱·플랫 - **날짜**: 2026-10-29 - **상태**: Accepted -- **배경**: TechAPI 워크플로는 `ENGINE_TOKEN`·`PROJECT_TOKEN`(메인테이너 개인 PAT)으로 TechEngine에 이벤트를 중계하고 프로젝트 보드를 수정했고, TechEngine 워크플로는 `TECHAPI_TOKEN`(개인 PAT)·`TECHENGINEBOT_TOKEN`(별도 봇 계정)으로 TechAPI에 push·PR·댓글·이슈를 만들었다. 양방향 서브모듈 자동 bump까지 겹쳐 변경 하나가 계정 활동 여러 건으로 불어났고, 자동화가 실제 계정 이름으로 실행되어 계정 정지로 이어졌다(봇 계정도 한 차례 정지). +- **배경**: TechAPI 워크플로는 `ENGINE_TOKEN`·`PROJECT_TOKEN`(메인테이너 개인 PAT)으로 TechEngine에 이벤트를 중계하고 프로젝트 보드를 수정했고, TechEngine 워크플로는 `TECHAPI_TOKEN`(개인 PAT)·`TECHENGINEBOT_TOKEN`(별도 봇 계정)으로 TechAPI에 push·PR·댓글·이슈를 만들었다. 양방향 서브모듈 자동 bump까지 겹쳐 변경 하나가 계정 활동 여러 건으로 불어났다. 자동화가 사람 계정 이름으로 실행되는 구조라 계정 제재 위험이 컸다. - **결정**: 1. 워크플로에서 PAT·봇 계정 토큰을 쓰지 않는다. 각 레포 기본 `GITHUB_TOKEN`만 쓴다. 2. TechAPI를 건드리는 작업은 TechAPI에서 실행하고, TechEngine 워크플로를 `workflow_call`로 호출한다(TechEngine은 공개 레포라 토큰 없이 체크아웃된다). 호출된 워크플로는 호출한 쪽의 `GITHUB_TOKEN`을 받으므로 결과물은 `github-actions[bot]` 이름으로 남는다. 3. 레포 간 `repository_dispatch` 중계와 서브모듈 자동 bump(`notify-*`, `bump-*`)는 없앤다. 4. `verify-status`는 TechAPI `dump-develop`에 합치고, `dump-refresh`는 `dump-develop`의 수동 전체 재생성으로 대체한다. 커버리지 리포트는 주 1회, TechEngine 레포 이슈만 갱신한다. 5. 스크레이핑 워크플로(`weekly-data.yml`)는 수동 실행으로 검증한 뒤에 cron을 켠다. - 6. **예외 — TechEngineBot 최종 확인**: TechEngineBot 계정은 중요한 PR의 최종 확인자 역할로만 쓴다. 메인테이너(OWNER/MEMBER)가 `/final-review` 댓글을 달 때만 TechAPI `final-review.yml`이 돌고, PR 하나에 댓글 하나(재실행 시 같은 댓글 수정)만 남긴다. 조회는 `GITHUB_TOKEN`으로 하고, `TECHENGINEBOT_TOKEN`(TechAPI 레포 한정 fine-grained 토큰, Pull requests: write)은 그 댓글에만 쓴다. 스케줄·push·PR 이벤트로 TechEngineBot이 움직이는 워크플로는 만들지 않는다. + 6. **예외 — TechEngineBot 최종 확인**: TechEngineBot 계정은 중요한 PR의 최종 확인자 역할로만 쓴다. 쓰기 권한이 있는 메인테이너(OWNER/MEMBER/COLLABORATOR)가 `/final-review` 댓글을 달 때만 TechAPI `final-review.yml`이 돌고, PR 하나에 댓글 하나(재실행 시 같은 댓글 수정)만 남긴다. 조회는 `GITHUB_TOKEN`으로 하고, `TECHENGINEBOT_TOKEN`(TechAPI 레포 한정 fine-grained 토큰, Pull requests: write)은 그 댓글에만 쓴다. 스케줄·push·PR 이벤트로 TechEngineBot이 움직이는 워크플로는 만들지 않는다. - **트레이드오프**: `GITHUB_TOKEN`이 만든 PR은 다른 워크플로를 트리거하지 않으므로, 주간 PR은 생성 전에 같은 작업 안에서 검증을 끝낸다. 포크 PR에는 토큰이 읽기 전용이라 댓글 대신 Job Summary로 결과를 남긴다. Projects 보드 필드 자동 입력(`pr-metadata`)은 없애고 GitHub Projects 내장 Auto-add를 쓴다. - **필요 설정**: 두 레포 Settings → Actions → General에서 "Allow GitHub Actions to create and approve pull requests"를 켜야 PR 생성이 된다.