fix(content): approve tells the truth on a diverged repo - #200
Open
ABB65 wants to merge 1 commit into
Open
Conversation
Live incident (collabers): the editor approved a change and got "Server Error"; retrying got another. The content had landed — every reader reads `contentrain`, and `contentrain` had it — but the advance to main answered 409 because main carried out-of-Studio `.contentrain/` changes (a dependency migration PR), and three places turned that recoverable state into lies: - the `main → contentrain` sync swallowed the conflict under a comment claiming the branches held "different directories" — they share `.contentrain/`, and the divergence became invisible - `finalizeContentrain` converted only protected-branch failures to a PR; a conflict was thrown raw, so a landed save surfaced as a 500 - the retry then died on "Head does not exist", because step 1 had deleted the cr/* branch the first time around The contract, aligned with the MCP-side R0 vocabulary (reconcile feedback, N3): `merged` answers what the caller actually asks — did the content land on `contentrain`. The advance is a separate fact, `mainAdvance: 'advanced' | 'blocked_diverged'`, and a PR is an attachment (`pullRequestUrl`), not a status. - advance conflict → PR, same fallback as protected main. The PR body names the divergence — it is the artifact a developer resolves. A 422 "already exists" is bookkeeping, not a failed merge; any other PR failure is logged, never thrown, because throwing would repeat the exact lie this removes. - protected fallback now also reports `merged: true` — step 1 had landed the content there too; the old `false` was the same lie. - a second approve of an already-landed branch finishes the half that failed (the advance) instead of 500ing on the deleted head. - a real cr-vs-contentrain conflict stays `merged: false` — the one case where "resolve manually" is the honest answer. - the sync conflict is logged loudly with the project id; writes continue (the editor is never blocked by a divergence a developer has to resolve). The UI says it out loud: merged + advanced → success toast; merged + blocked → warning that publishing awaits a developer review; conflict → error. All three from the dictionary — the success path was hardcoded English. The turn-end finalize log stops claiming divergence "self-heals on next merge" — it never did; finalize now turns it into a PR itself, so what reaches that catch is transient. Interim until the ecosystem's planReconcile ships (AI-REPO-RECONCILE): then the conflict path tries a content-aware three-way merge first and opens a PR only for what survives it.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Katman 0 of the reconcile program (
.internal/refactor/tasks/AI-REPO-RECONCILE.md) — the Studio half that does not wait for the ecosystem'splanReconcile.The incident this fixes
Approve on collabers → "Server Error". Retry → another. Railway logs:
POST /merges → 409×12, thenHead does not exist→ unhandled 500. Repo compare:contentrainandmaindiverged (13/7, 26 files) because the 3.0 migration landed onmainby PR — outside the content pipeline.The content was never lost — step 1 (
cr/* → contentrain) had landed it, and every reader (brain, CDN, MCP) readscontentrain. Three places turned that recoverable state into lies:ensureContentBranchsync catch.contentrain/finalizeContentraincr/*branch → second click died on "Head does not exist"The contract
Aligned with the MCP-side R0 vocabulary (reconcile feedback, N3), so the two ecosystems name the same states the same way:
mergedanswers what the caller actually asks: did the content land oncontentrain.mainAdvanceis the separate fact about main. A PR is an attachment, not a status —pr_openedis derivable asblocked_diverged+pullRequestUrl.Behavior changes
merged: true— step 1 had landed the content there too; the oldfalsereported a landed save as failed. (Deliberate change to an existing behavior; the old test asserted the lie and was updated with a comment saying why.)cr/*vscontentrainconflict staysmerged: false— the one case where "resolve manually" is honest.Verified
pnpm lint0 errors ·pnpm typecheckclean ·pnpm test151 files / 1311 testsNot verified — needs staging
contentrain → main) — this is also the fastest real-world validation, since the repo is still diverged until you resolve itmainintocontentrainfirst, then advance — direction matters), Approve should go back to plain successNext
planReconcileships (mcp minor), the conflict path tries the content-aware three-way merge first; the PR remains only for survivingConflictItems