fix(release): ff-only sync local branches to origin before push (unblocks deploys) #4

Merged
kavi merged 2 commits from fix/3-release-ff-only into production 2026-07-12 07:20:37 +00:00
Owner

Fixes #3. Crown-jewel engine change — reviewed PR only; human merges + operates the control-plane rollout (P-ROLLOUT). Do not auto-deploy.

The bug

release() pushed the engine clone`s local source branch without syncing to origin first:

await run(`git -C ${repoDir} fetch ${remote}`);            // remote-tracking only
await run(`git -C ${repoDir} checkout ${sourceBranch}`);
await run(`git -C ${repoDir} push ${remote} ${sourceBranch}`);  // local main is stale -> non-ff REJECTED

Branch merges land via forge PRs, never in the engines /root/apps/ clone, so that clones local main is routinely behind origin/main. The push is rejected non-fast-forward and release() 500s at push_sourceblocking every deploy. This stalled muralla ~36 commits. (The handoff misdiagnosed it as a known_hosts perms issue; the real cause is the stale local ref.)

The fix (+11 lines, no behavior change on the happy path)

Add git merge --ff-only <remote>/<branch> after fetch, on both the source- and deploy-branch paths:

  • behind origin -> fast-forwards (push becomes a clean no-op)
  • equal/ahead -> no-op (push still sends any local commits)
  • genuinely diverged -> errors loudly (a real problem worth surfacing, not silently force-anything)

node --check clean. Follows the existing run() idiom exactly.

Base = production (deliberate)

Targets the deployed/hardened ref (ed47680) because main is security-regressed pending the P2 reconcile (#1). Once P2 lands (production -> main), this comes along / rebases onto main. Landing it on production is what actually unblocks the running engine.

Verified out-of-band

The equivalent one-liner (git -C /root/apps/muralla merge --ff-only origin/main) applied by hand on sol-prod on 2026-07-10 unblocked release(); the muralla deploy then ran end-to-end (build/deploy/migrate/health all green, container swapped).

Fixes #3. **Crown-jewel engine change — reviewed PR only; human merges + operates the control-plane rollout (P-ROLLOUT). Do not auto-deploy.** ## The bug `release()` pushed the engine clone`s **local** source branch without syncing to origin first: ```js await run(`git -C ${repoDir} fetch ${remote}`); // remote-tracking only await run(`git -C ${repoDir} checkout ${sourceBranch}`); await run(`git -C ${repoDir} push ${remote} ${sourceBranch}`); // local main is stale -> non-ff REJECTED ``` Branch merges land via forge PRs, never in the engine`s `/root/apps/<app>` clone, so that clone`s local `main` is routinely behind `origin/main`. The push is rejected non-fast-forward and `release()` 500s at `push_source` — **blocking every deploy**. This stalled muralla ~36 commits. (The handoff misdiagnosed it as a `known_hosts` perms issue; the real cause is the stale local ref.) ## The fix (+11 lines, no behavior change on the happy path) Add `git merge --ff-only <remote>/<branch>` after fetch, on **both** the source- and deploy-branch paths: - **behind** origin -> fast-forwards (push becomes a clean no-op) - **equal/ahead** -> no-op (push still sends any local commits) - **genuinely diverged** -> errors loudly (a real problem worth surfacing, not silently force-anything) `node --check` clean. Follows the existing `run()` idiom exactly. ## Base = `production` (deliberate) Targets the deployed/hardened ref (`ed47680`) because `main` is security-regressed pending the P2 reconcile (#1). Once P2 lands (production -> main), this comes along / rebases onto main. Landing it on production is what actually unblocks the running engine. ## Verified out-of-band The equivalent one-liner (`git -C /root/apps/muralla merge --ff-only origin/main`) applied by hand on sol-prod on 2026-07-10 unblocked `release()`; the muralla deploy then ran end-to-end (build/deploy/migrate/health all green, container swapped).
release() pushed the engine clone's LOCAL source branch without first syncing
it to origin. Because branch merges land via forge PRs (never in the engine's
/root/apps/<app> clone), the clone's local main is routinely behind origin, so
`git push origin main` was rejected non-fast-forward and release() 500'd at
push_source — blocking every deploy (this stalled muralla ~36 commits; the
handoff misread it as a known_hosts perms issue, but the cause is the stale ref).

Add `git merge --ff-only <remote>/<branch>` after fetch on both the source- and
deploy-branch paths: no-op when local is equal/ahead, fast-forwards when behind,
errors loudly only on genuine divergence.

Targets production (the deployed/hardened ref); main is security-regressed
pending the P2 reconcile (#1), so this rebases onto main once that lands.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Author
Owner

Adversarial review (Fable) — verdict SAFE-TO-MERGE. Two follow-ups captured:

  1. Checkout .ok guard — applied in this PR (commit above): a silent checkout failure before merge --ff-only (which acts on HEAD) could ff the wrong branch. Now asserted.
  2. Forward-port to main (operator sequencing, non-negotiable): this PR targets production as a bootstrap because release() is broken until it deploys (chicken-and-egg). Recommended sequence:
    • merge #5 (reconcile) → main becomes == production (ed47680)
    • rebase this branch onto main, merge → main gains the ff-only fix
    • manual control-plane rollout of kua-deploy from main (P-ROLLOUT) — release() cannot self-deploy the fix that unblocks release()
    • after that, normal release-app flow works again
      Otherwise main would ship without the ff-only fix and re-open the exact main↔production drift #5 just cured.

Pre-existing, out of scope (later PRs): release() runs without the per-app lock (concurrent releases interleave git ops); brand-new-branch source push edge case.

Adversarial review (Fable) — verdict **SAFE-TO-MERGE**. Two follow-ups captured: 1. **Checkout `.ok` guard** — applied in this PR (commit above): a silent checkout failure before `merge --ff-only` (which acts on HEAD) could ff the wrong branch. Now asserted. 2. **Forward-port to `main`** (operator sequencing, non-negotiable): this PR targets `production` as a bootstrap because `release()` is broken until it deploys (chicken-and-egg). Recommended sequence: - merge #5 (reconcile) → `main` becomes == `production` (ed47680) - rebase this branch onto `main`, merge → `main` gains the ff-only fix - **manual control-plane rollout** of kua-deploy from `main` (P-ROLLOUT) — release() cannot self-deploy the fix that unblocks release() - after that, normal `release-app` flow works again Otherwise `main` would ship without the ff-only fix and re-open the exact main↔production drift #5 just cured. Pre-existing, out of scope (later PRs): `release()` runs without the per-app lock (concurrent releases interleave git ops); brand-new-branch source push edge case.
merge --ff-only acts on HEAD, so a silently-failed 'git checkout <branch>'
before it would fast-forward whatever branch is currently checked out toward
origin/<sourceBranch> — mutating the wrong local ref. Assert .ok on both the
source- and deploy-branch checkouts so the release aborts loudly instead.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
kavi merged commit adcb72b58f into production 2026-07-12 07:20:37 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
kavi/kua-deploy!4
No description provided.