Files
GDevelop-Heretek/.github/workflows/upstream-sync.yml
T
John Smith 48ed4e38d0 chore(maintenance): make fork divergence and static-analysis scope deliberate
Two problems made this fork expensive to maintain: scanner noise nobody
could act on, and divergence from upstream that nothing tracked.

Code scanning reported 3,518 open alerts. 3,449 were fallow findings and
2,399 of those sat in vendored bundles `.fallow.toml` never listed
(jolt-physics.wasm.js 436, box2djs/box2d.js 328, the Firebase SDK 655,
Recast, PeerJS, draco). Listing them plus raising the CRAP thresholds from
the defaults to evidence-based values takes fallow from 3,449 findings to
155, all in maintained code. The remaining 69 were CodeQL: 8 third-party
actions are now pinned to full commit SHAs with `# vN` comments (so
Dependabot keeps updating them) and all 10 workflows declare an explicit
least-privilege `permissions:` block.

SonarCloud's 2,845 open issues are mostly not this fork's code — 2,526 sit
in files byte-identical to upstream. The real defect was configuration:
~200 lines of `sonar.issue.ignore.multicriteria.e1..e88` in
.sonarcloud.properties, plus scripts/sonarcloud-apply-suppressions.sh, were
inert. That key is a Project Settings UI setting and cannot be read from
that file under automatic analysis, so two phases of remediation work
chased config the server never saw. Both are deleted; the exclusions that
do work are kept and extended. `sonar.test.inclusions` no longer declares
production source directories as test inclusions.

Divergence is now bounded and checked. `fork-divergence.json` is a
checked-in allowlist of the 111 modified, 51 fork-only and 7
upstream-missing paths, and `scripts/check-fork-divergence.js` fails CI on
divergence that is not listed or on stale entries. It reads upstream
through the GitHub trees API because 4ian/GDevelop is ~1.7GB and this runs
on every PR. Restoring Extensions/Physics3DBehavior/tests/
Physics3DRuntimeBehavior.spec.js — deleted by an earlier SonarCloud pass to
silence one rule — removes one entry each.

Repairs found along the way: every `// nosonar` marker sat on the line
before the statement SonarCloud flags, where the directive does nothing;
each is now on the exact reported line. Four garbled comment fragments left
in multiplayertools.ts by the same pass are gone. A two-line re-export shim
with no importers (AiGeneration/CustomAIClient.js) is deleted.

`scripts/sonarcloud-drift.js` now writes its outputs to $GITHUB_OUTPUT and
seeds its own baseline, so the weekly drift job's ceiling can actually fire;
it previously ran behind `continue-on-error` against a threshold that
predated the exclusions and was unreachable.

Root documentation collapses to MAINTENANCE.md. The three SonarCloud phase
plans and the Dependabot plan described steps that were proven impossible
and are deleted; AI_GAME_STUDIO_ASSESSMENT.md is a design record and stays.

Verified: newIDE/app 176/176 suites and 1,922 tests pass; GDevelop.js 33/33
suites and 1,177 tests pass; GDJS Karma 1,287 pass with the same 9
pre-existing failures (Firebase network tests) reproduced on a pristine
tree. check-format lists 37 files against the documented 41-file baseline,
so no new violations. Lockfile, drift and divergence guards all exit 0.
2026-09-22 20:01:28 -04:00

195 lines
8.8 KiB
YAML

name: Upstream Sync (PR-Only)
on:
schedule:
- cron: '0 6 * * *' # Daily at 06:00 UTC
workflow_dispatch:
# Minimal least-privilege permissions: strictly cannot mint OIDC tokens, cannot modify packages
permissions:
contents: write
pull-requests: write
issues: write # conflict path calls `gh issue create`
jobs:
prepare-sync-pr:
name: Sync Upstream & Create PR
runs-on: ubuntu-latest
steps:
- name: Hardened Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Configure Git Identity
run: |
git config user.name "heretek-sync-bot[bot]"
git config user.email "[email protected]"
- name: Fetch Upstream
run: |
git remote add upstream https://github.com/4ian/GDevelop.git || true
git fetch upstream master --tags
- name: Check for New Upstream Commits
id: check_diff
run: |
git checkout master
UPSTREAM_COMMITS=$(git rev-list master..upstream/master --count)
echo "Upstream commits ahead: $UPSTREAM_COMMITS"
echo "commits_count=$UPSTREAM_COMMITS" >> $GITHUB_OUTPUT
- name: Create Isolated Sync Branch
if: steps.check_diff.outputs.commits_count != '0'
id: sync_branch
run: |
SYNC_BRANCH="upstream-sync-$(date +%Y-%m-%d)"
git checkout -B "$SYNC_BRANCH" master
# Attempt merge
MERGE_SUCCESS=true
if ! git merge upstream/master -m "chore(sync): sync upstream 4ian/GDevelop changes"; then
echo "Merge conflicts detected."
MERGE_SUCCESS=false
git merge --abort
fi
# SECURITY: Restore and lock local workflows to prevent upstream workflow tampering (e.g. Shai-Hulud)
echo "Locking local .github/workflows to prevent upstream workflow tampering..."
git checkout master -- .github/workflows
# INTEGRITY CHECK: Ensure BYOK core files were not deleted or stripped
REQUIRED_BYOK_FILES=(
"newIDE/app/src/AI/CustomAIClient.js"
"newIDE/app/src/AI/CustomAIClient.spec.js"
"newIDE/app/src/AiGeneration/AiConfiguration.js"
"newIDE/app/src/MainFrame/Preferences/PreferencesDialog.js"
"newIDE/app/src/MainFrame/Preferences/PreferencesProvider.js"
)
for file in "${REQUIRED_BYOK_FILES[@]}"; do
if [ ! -f "$file" ]; then
echo "CRITICAL: Required BYOK file $file is missing after merge! Restoring from master..."
git checkout master -- "$file"
fi
done
git commit -am "chore(sync): lock workflows and verify BYOK integrity" || true
git push origin "$SYNC_BRANCH" --force
echo "sync_branch=$SYNC_BRANCH" >> $GITHUB_OUTPUT
echo "merge_success=$MERGE_SUCCESS" >> $GITHUB_OUTPUT
- name: Verify fork divergence is unchanged by the sync
# A sync merge can silently re-introduce upstream copies of files the
# fork deliberately deletes or modifies (it is how
# Extensions/Physics3DBehavior/tests/Physics3DRuntimeBehavior.spec.js
# was deleted by hand and how the obsolete wavesurfer patch keeps
# coming back). `fork-divergence.json` is the checked-in allowlist of
# intended divergence; this step reports any drift instead of letting
# it reach a reviewer as a mystery diff. Informational only: the
# reviewer decides whether to record the drift or revert it.
if: steps.check_diff.outputs.commits_count != '0'
continue-on-error: true
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
if node scripts/check-fork-divergence.js; then
echo "Divergence matches fork-divergence.json."
else
{
echo "### Fork divergence changed in this sync"
echo ""
echo "The sync introduced or removed divergence not recorded in"
echo "\`fork-divergence.json\`. Review the list above, then either"
echo "record it (\`node scripts/check-fork-divergence.js --update\`)"
echo "or revert the accidental part before merging."
} >> "$GITHUB_STEP_SUMMARY"
echo "::warning::Fork divergence changed; see the job summary."
fi
- name: Open or Update Sync PR
if: steps.check_diff.outputs.commits_count != '0'
run: |
BRANCH="${{ steps.sync_branch.outputs.sync_branch }}"
MERGE_OK="${{ steps.sync_branch.outputs.merge_success }}"
PR_TITLE="[Upstream Sync] 4ian/GDevelop updates ($(date +%Y-%m-%d))"
PR_BODY="### Automated Upstream Synchronization
- **Upstream Source**: \`4ian/GDevelop:master\`
- **Target**: \`Heretek-AI/GDevelop-Heretek:master\`
- **Branch**: \`$BRANCH\`
- **Clean Merge**: \`$MERGE_OK\`
---
### Security & Integrity Controls
- **No Direct Push**: \`master\` is never modified automatically.
- **Workflow Poisoning Protection**: \`.github/workflows/\` were locked to local \`master\` to prevent malicious workflow injection (Shai-Hulud vectors).
- **BYOK File Integrity**: Core BYOK modules and test suites verified before PR creation.
*Please review the diff and run tests before merging into master.*"
# Check if an open PR for this branch already exists
if [ "$MERGE_OK" != "true" ]; then
echo "Upstream merge has conflicts that need manual resolution."
gh issue list --search "upstream sync conflicts in:title state:open" --json number -q 'map(.number) | length' > /tmp/conflict_issues.txt 2>/dev/null || echo 0 > /tmp/conflict_issues.txt
if [ "$(cat /tmp/conflict_issues.txt)" = "0" ]; then
gh issue create \
--title "Upstream sync needs manual conflict resolution ($(date +%Y-%m-%d))" \
--body "The automated upstream sync from \`4ian/GDevelop:master\` hit merge conflicts. Please run a manual \`git fetch upstream && git merge upstream/master\` on a branch, resolve conflicts, and open a PR. Workflow/BYOK integrity guards still apply after resolving." || true
fi
exit 0
fi
EXISTING_PR=$(gh pr list --head "$BRANCH" --json number -q '.[0].number' 2>/dev/null || echo "")
if [ -n "$EXISTING_PR" ]; then
echo "PR #$EXISTING_PR already exists for $BRANCH."
exit 0
fi
# `gh pr create` from this workflow fails with "GraphQL: Resource not
# accessible by integration (createPullRequest)". The cause is NOT a
# read-only token - that was an earlier misdiagnosis. Verified by
# probe against this repo:
#
# REST POST /pulls -> OK (created a PR)
# gh pr create -> OK (created a PR)
# gh pr create on an
# upstream-sync-* branch -> OK (created a PR)
#
# all as app/github-actions with secrets.GITHUB_TOKEN, while
# default_workflow_permissions = write
# can_approve_pull_request_reviews = true
# runtime grant = Contents:write + PullRequests:write
# git push with the same token = succeeds
# SYNC_PAT = not set
#
# So the token can create PRs here. The branch HAS been pushed, so
# degrade to a ready-to-click compare URL rather than failing the run.
if gh pr create \
--base master \
--head "$BRANCH" \
--title "$PR_TITLE" \
--body "$PR_BODY"; then
echo "Sync PR created."
else
COMPARE_URL="https://github.com/${GITHUB_REPOSITORY}/compare/master...${BRANCH}?expand=1"
{
echo "### Upstream sync branch is ready — no PR was opened"
echo ""
echo "The sync branch \`$BRANCH\` was pushed successfully, but this"
echo "workflow could not open the pull request. \`gh pr create\`"
echo "returned a permissions error even though the same token"
echo "creates PRs successfully from other workflows; the cause is"
echo "still unidentified (see the comment in this step)."
echo ""
echo "Open it manually: $COMPARE_URL"
} >> "$GITHUB_STEP_SUMMARY"
echo "::warning::Could not open the sync PR. Open manually: $COMPARE_URL"
fi
env:
GH_TOKEN: ${{ secrets.SYNC_PAT != '' && secrets.SYNC_PAT || secrets.GITHUB_TOKEN }}