Repository navigation
Description
Delivered through
This umbrella PRD is delivered by the feature issues above (rescope 2026-08-24: Milestone > PRD > Feature > Task). It closes when the last of them ships.
Problem
This fork carries deliberate divergence from block/buzz — including product-code changes, not just cohort process files — and nothing owns the job of keeping it current. Upstream moves daily; the fork has not merged upstream since 2026-08-17.
That leaves two costs. The gap compounds: every day of drift makes the eventual merge larger and its conflicts harder, on a ~3,800-file upstream. And the cost lands on the wrong person at the wrong time — a contributor part-way through a feature discovers a conflict that has nothing to do with their work, in files they did not touch and whose divergence they may not know the reason for.
What is needed is a change agent that owns the block/buzz → launchpad-26/buzz relationship: it keeps the mirror current, absorbs the conflict work, resolves what it can safely resolve, and escalates the rest with enough context for a human to decide quickly. Contributors should learn about upstream only when they need to act on it.
Conflict resolution is the product here, not a side effect of syncing. The fork's divergence is intentional and expected to grow.
Evidence
Measured against launchpad-26/buzz and block/buzz on 2026-08-21:
-
launchpadis 255 ahead, 60 behindupstream/main. Merge-base is 2026-08-17; upstream committed as recently as 2026-08-20. No upstream commit has been merged intolaunchpadin that window. -
All 255 divergent commits are cohort-authored (Serina 192, tucktuck101 53, joshuavial 4, Ben Mitchell 6). None came from upstream.
-
Of 324 files the fork has changed, 282 are inside the
launchpad/scope and 42 are upstream files: ~17 process/meta (AGENTS.md,CONTRIBUTING.md, issue templates,.claude/skills/*), ~9 build/tooling (Justfile,lefthook.yml,bin/lefthook,scripts/instance-env.sh,ci.yml), ~5 deployment (Dockerfile,docker.yml,deploy/compose/*), and 9 product code (crates/buzz-cli/src/commands/pack.rs,crates/buzz-persona/src/resolve.rs, 6×desktop/src-tauri/**). -
A reason is recorded for 7 of those 42. ADR-0005 documents five deployment files; ADR-0017 documents the two lefthook pins. The other ~35, including all nine product-code files, have no recorded statement of why they diverge or which side should win a conflict.
-
git rerereis not enabled. Every recurring conflict is re-resolved from scratch, every time. -
.gitattributessets only* text=auto eol=lf. No merge drivers, so files the fork permanently owns still conflict on every sync. -
launchpad-26/mainis a clean, unmodified ancestor ofupstream/main— 0 ahead, 221 behind. Confirmed intentional: it is the mirror through which updates arrive before being worked on locally. -
The 60-commit gap test-merges with exactly three conflicts.
git merge-tree launchpad upstream/mainauto-mergesAGENTS.md,Justfile,crates/buzz-cli/src/lib.rsanddesktop/src-tauri/src/managed_agents/restore.rscleanly, and conflicts on only:lefthook.yml— divergence documented (ADR-0017), so the winner is already known.github/workflows/ci.yml— divergence undocumented, configdesktop/src-tauri/src/managed_agents/runtime.rs— divergence undocumented, product code
One conflict in each of the three resolution classes this PRD proposes: register-driven, mechanical, and semantic. The backlog is small today and the mechanism can be proven against a real case immediately.
-
The only sync automation that exists today runs on a contributor's personal fork and merges
launchpad-26/main, which is itself 416 commits behindlaunchpad. It syncs from a stale mirror.
Success criteria
- The mirror is never stale by more than one cadence period.
launchpad-26/mainfast-forwards fromupstream/mainunattended; a failure to do so is visible, not silent. - A clean upstream merge reaches a reviewable PR with no human involvement — no model invoked, no contributor prompted.
- Every one of the 42 diverged upstream files has a recorded reason and a stated conflict winner, in a register a resolver can consult. New divergence is added to it in the PR that creates the divergence.
- A conflict already resolved once does not require resolving again, by way of
rerereand/or merge drivers. Demonstrated by a repeat sync over the same file pair. - A semantic conflict escalates with context: which files, which register entries apply, what upstream changed, and a one-command local entry point that reproduces the merge with the full toolchain available.
just cigates every sync PR before it is mergeable. A sync that breaks the build is a failed sync, not a merged one.- A contributor mid-feature is never the first to discover an upstream conflict in files they did not touch.
- The agent never merges to
launchpaditself — see non-goals.
Non-goals
- No unilateral merging. The agent opens PRs; a human with write access merges them. This is a hard constraint, not a default: the cohort has already lost a repository to an agent taking an irreversible action with legitimately granted privilege.
- No unattended resolution of semantic conflicts in CI. Resolving a Rust or TSX conflict without being able to build and test it produces confidently broken merges. Mechanical resolution (register-driven,
rerere, merge drivers) is in scope; judgement is escalated. - No PRs sent back to
block/buzz. ADR-0017 records that the cohort is not currently sending changes upstream. If that changes it is a separate decision. - No rebasing of shared branches.
launchpadhas eleven people working off it, and rebasing would defeatrerere. Merge only. - Not an upstream-awareness product. prd-01 — upstream intelligence: Buzz keeps the cohort aware of upstream #3 owns reporting on upstream releases and activity. This PRD owns keeping the fork current. Where they touch — where records persist, how failures are surfaced out-of-band — the ADRs under prd-01 — upstream intelligence: Buzz keeps the cohort aware of upstream #3 (adr: where generated upstream reports persist #51, adr: the minimum Discord fallback for upstream notifications #52) apply rather than being re-decided here.
- Not a reduction in deliberate divergence. The fork's product-code changes are intentional and this PRD does not propose reverting them.
Impacted components
.gitattributes merge drivers for fork-owned files
.github/workflows/launchpad-upstream-sync.yml new — scheduled mirror + merge attempt
launchpad/upstream-sync/DIVERGENCE-REGISTER.md new — the 42 files, reasons, conflict winners
launchpad/upstream-sync/ new — resolution scripts and skill
launchpad/decisions/ ADRs this PRD raises (below)
Justfile local entry point for the escalation path
Security implications
The agent holds write access to shared branches on a public repository and takes actions on them. That is the same risk class as buzz-infrastructure #103, and the mitigations are the ones ADR-0015 already established for host access: the agent never holds a credential of its own beyond the workflow's scoped GITHUB_TOKEN, destructive or irreversible steps are proposed rather than taken, and every action is attributable.
Three specifics:
- Token scope. The workflow needs
contents: writeandpull-requests: writeand nothing else. It must not carry the ability to delete branches, delete the repository, or alter settings. - Branch protection is the backstop, and it is currently thin.
launchpadrequires one approval and has no required status checks, so success criterion 6 (just cigates the merge) needs the check to be required, not merely present — otherwise a red sync PR is one click from landing. - Upstream content is untrusted input. The agent reads and merges code authored outside the cohort, and a resolution prompt containing upstream diff text is attacker-influenceable in the same way
launchpad/review-agent/CONTAINMENT.mddescribes for PR-authored text. That containment contract should be reused, not reinvented. - The escalation path runs on a contributor machine, which is in scope for
buzz-infrastructuregroup_vars must sit beside the inventory, and role defaults are not cross-role scope #103. It is the favourable case for that ADR — human present, explicit per-action instruction — but it should be decided there, and the workflow half ships independently of it.
Decisions this PRD raises
To be filed as child ADRs, not decided here:
- Conflict-winner policy for the ~35 undocumented divergences — per-file, or per-category with named exceptions.
- Cadence — daily is assumed (small deltas conflict far less than large ones); the alternative is upstream-tag-triggered.
- How far mechanical resolution may go before escalating —
rererereplay andmerge=oursare clearly mechanical; a three-way merge the agent believes is unambiguous is the boundary case. - Where the escalation runs — contributor machine with full toolchain, versus a provisioned environment. Interacts with
buzz-infrastructuregroup_vars must sit beside the inventory, and role defaults are not cross-role scope #103.
Filed by an AI agent (Claude Opus 5) on behalf of @tucktuck101, from direction given in session on 2026-08-21. Verified today against the GitHub API and the local clone: all branch divergence counts, the 324/282/42/9 file breakdown and its categories, commit authorship of the 255, the absence of rerere configuration, the contents of .gitattributes, that launchpad-26/main is an unmodified ancestor of upstream/main, and that ADR-0005 and ADR-0017 between them document 7 of the 42 files. Not verified: whether the three conflicts resolve to a correct merge — merge-tree reports textual conflicts, not semantic ones, and the four files that auto-merged cleanly could still be wrong in behaviour. just ci on a real merge branch is what would establish that, and it has not been run.
Activity
tucktuck101 commentedon Aug 21, 2026
Proposal — completing PRD #273 (the change agent)
Nothing was written. All work below is read-only:
gh issue view,gh api,git fetch,git diff,git merge-tree, and readinglaunchpad/AGENTS.md,ADR-0005,ADR-0017,launchpad/AGENT_PR_TEMPLATE.md.0. Re-measured facts, and four places the PRD is wrong
I re-derived everything I rely on. The tip moved; three of the changes matter.
git fetch upstream main)launchpad// 42 upstreamCargo.lockhas joinedf8692fa9b)0.1 The "42 diverged upstream files" is not a 42-file conflict surface
Classifying all 48 by status against the merge-base and against
upstream/main:.claude/skills/*, the six.github/ISSUE_TEMPLATE/0*.yml, the fivelaunchpad-*.ymlworkflows,LAUNCHPAD.md,bin/.lefthook-2.1.10.pkg,desktop/src-tauri/src/managed_agents/runtime/summary.rs,scripts/test-ci-changed-paths-filter.sh. These cannot conflict unless upstream one day creates a file at the same path.bug-report.md,feature-request.md) — these conflict only as modify/delete.So the register has ~27 rows that can ever matter and 8 that matter now. That is good news for scope and it changes sequencing: the register is insurance against future syncs, not the thing blocking this one.
0.2 ADR-0017 does not document
lefthook.ymlThe PRD says
lefthook.yml's divergence is "documented (ADR-0017), so the winner is already known." It is not. ADR-0017 is scoped to "exactlybin/lefthookandbin/.lefthook-*.pkg."lefthook.yml's divergence is a one-linebranch-skewredirect, documented only in an in-file comment citing #15. It is an undocumented divergence for register purposes, and its conflict is in the semantic class, not the register-driven one. That moves the PRD's "one conflict in each of the three classes" claim: today it is two mechanical-ish, two semantic, not one of each.0.3
merge=oursdrivers are wrong for almost every candidate the PRD namesThis is the largest correction. Three of the four candidate groups fail on inspection:
lefthook.yml— actively harmful. Our divergence is one line. Upstream's change since the merge-base is a near-rewrite: it addsrc: bin/.lefthookrc, addsfiles: git diff --name-only origin/main...HEADscoping to six lanes, addspush-head-scopeandfile-size-checklanes, and renamesmobile-fix→mobile-fmtandmobile-test→mobile-checks.merge=ourswould silently discard all of it to preserve one line. The correct resolution is theirs, then re-apply our line.The five ADR-0005 deployment files — contradicts the ADR. ADR-0005 explicitly rejects a fork-local copy of
docker.ymland states the reasoning: "A conflict you must resolve is better than a copy you forget to."merge=oursreintroduces exactly the silent-drift failure that ADR rejected. Adding those drivers would be reversing a decided ADR inside a plumbing PR.bin/lefthookandbin/.lefthook-*.pkg— the ADR states a different rule. ADR-0017: "resolved by taking whichever version is newer." That is neither ours nor theirs; it is a comparison.merge=ourswould pin us to 2.1.10 forever and block an upstream bump to 2.2.x.Only
.github/ISSUE_TEMPLATE/*survives, and mostly it does not need a driver: six of the nine files are fork-only additions, two are deletions, andconfig.ymlis the single genuine ours-wins case.0.4 A concrete hazard already sitting in the 67-commit backlog
Upstream's new
bin/.lefthookrcsetsLEFTHOOK_BINto the Hermit-pinnedbin/lefthookat 2.1.3 — the exact version ADR-0017 records as crashing every first push in this fork, because the branch namelaunchpadcollides with thelaunchpad/directory.lefthook.ymlandbin/.lefthookrcauto-merge or arrive clean; the interaction with our 2.1.10 pin does not. A textually clean merge here reintroduces a known, filed bug (#196). This is the best available argument for the PRD's own "no unattended semantic resolution" non-goal, and it should be the worked example in the escalation contract.1. The eight success criteria
SC1 — mirror never stale by more than one cadence period
Blocked by a permission I cannot grant, and the PRD does not know it.
Push to
mainis restricted to one named user.restrictions.appsis empty, so the ActionsGITHUB_TOKENcannot fast-forward it.allow_fork_syncingisfalse.Three ways out, in preference order: (a) an admin adds
github-actionstorestrictions.apps— one API call, keeps the design intact; (b) the mirror moves to an unprotectedupstream-mirrorbranch andmainstays a human-maintained artifact; (c) a PAT, which I would argue against on ADR-0015 grounds.How I'd satisfy it: scheduled
launchpad-upstream-sync.yml,contents: write+pull-requests: writeonly, fetchingblock/buzzand fast-forwarding. Evidence: a run log showing the FF, plus a deliberate failure injection (point it at a non-fast-forwardable ref) showing the job goes red rather than silent. Not achievable without (a) or (b) being decided by a human with admin.SC2 — clean upstream merge reaches a reviewable PR with no human involvement
Achievable and cheap. Pure shell in the same workflow:
git merge --no-ff, and on success push a branch andgh pr createwith theby:agentlabel and anAGENT_PR_TEMPLATE-shaped body. No model in the loop.Evidence: a run where merge exits 0, the PR exists, and the workflow log shows no model invocation. The honest caveat is that this path is currently unexercised — the backlog conflicts, so the clean-merge branch will not run for real until the four conflicts are cleared. I would prove it against a synthetic ancestor pair rather than claim it works.
SC3 — every diverged upstream file has a recorded reason and a stated conflict winner
Achievable, and the most valuable single deliverable.
launchpad/upstream-sync/DIVERGENCE-REGISTER.md, one row per file: path, category, status vs merge-base, why it diverges, conflict winner, authority (ADR / issue / in-file comment / unrecorded), and who to ask.Two things I would insist on. First, the honest rows: the eight modified product-code files trace to four commits (
f36d10f9cpack JSON output,35cab546acfg(unix) gating,975e6d444configured-relay dial,43366affafile-size ratchet split) but none of those has a recorded statement of upstream-conflict intent. I would record the commit as provenance and mark the winnerUNDECIDED — see ADR-0018, not invent one. Second, "add to the register in the PR that creates the divergence" needs a check to be real; a convention nobody enforces decays. I would extendlaunchpad-pr-check.ymlto fail when a PR modifies an upstream-owned file with no matching register row.Evidence: row count equals the generator's file count; a script that regenerates the file list and diffs it against the register.
SC4 — a conflict resolved once is not resolved again
Achievable in weaker form than the PRD assumes. Two structural facts the PRD does not account for:
rererestate lives in.git/rr-cache/, which is per-clone and never committed. A GitHub Actions runner is ephemeral, sorereregives the sync agent nothing unlessrr-cacheis persisted — viaactions/cache, or committed to a dedicated branch. Contributor machines each get their own, independently..gitattributes— deliberately, for security.merge=oursin.gitattributesis inert until every clone runsgit config merge.ours.driver true.unionis built in;oursis not.So both halves of the PRD's mechanism need a bootstrap that the PRD does not list. I would add a
just upstream-sync-setuprecipe (enablererere, define the drivers) wired intojust setup, plus a documentedrr-cachepersistence strategy for CI.Evidence: the PRD's own test — resolve
lefthook.yml, discard the merge, re-merge, and pastegit rerere statusshowing the replay. Run it twice on the same machine, and once on a second machine to demonstrate the sharing gap honestly.SC5 — semantic conflicts escalate with context
Achievable. On non-zero merge exit: emit conflicted paths, the matching register rows,
git log --oneline merge-base..upstream/main -- <path>per file, and a one-command local entry point (just upstream-sync-resume) that recreates the merge with the toolchain live. The escalation should be a GitHub issue or a draft PR with the conflict markers committed, not a Discord ping alone.Containment: upstream diff text is attacker-influenceable.
launchpad/review-agent/CONTAINMENT.mdandcontain.pyalready exist for PR-authored text and should be reused verbatim rather than reinvented — the PRD says this and it is right.Evidence: the escalation artifact for today's real
runtime.rsconflict, showing it names the code-motion cause.SC6 —
just cigates every sync PRCannot be fully satisfied, and it is broken twice, not once.
The PRD identifies one break: no required status checks (
gh api .../branches/launchpad/protectionreturns"checks": null), and making one required is out of scope for me.The second break is not in the PRD. A PR opened by the Actions
GITHUB_TOKENdoes not triggerpull_requestworkflows.ci.yml's triggers arepush: branches: [main, release]andpull_request:— a sync branch matches neither. So CI would not merely be un-required on the sync PR; it would not run at all. Fixing that needs either a GitHub App / PAT to author the PR, or an explicitpush/workflow_dispatchtrigger for sync branches.I can make CI run. I cannot make it required. I would state that gap in the register and the ADR rather than paper over it.
SC7 — a contributor mid-feature is never first to discover an upstream conflict
Satisfiable in practice, unfalsifiable as written. Contributors branch from and merge to
launchpad; they never merge upstream. The real content is "the sync agent gets there first," which follows from cadence plus SC2/SC5. Evidence: a log of sync runs with dates and conflicted paths, so a contributor who does hit one can be traced to a specific gap. I would rewrite the criterion as a negative-observation record rather than pretend it is testable.SC8 — the agent never merges to
launchpadAchievable, and enforced by the platform rather than by the agent's good behaviour:
launchpadrequires one approving review, andcan_approve_pull_request_reviewsisfalsefor the Actions token. Evidence: the workflow'spermissions:block, plus that API output. Belt and braces — a plan that relies only on the agent choosing not to merge is not a plan, given what the cohort already lost.2. Work items
Each is one branch, one PR, against
launchpad.launchpad/upstream-sync/DIVERGENCE-REGISTER.md, ~48 rows with a generator script for the file list. Winners recorded only where an authority exists; the rest markedUNDECIDED.rerere+ bootstrap —just upstream-sync-setup, wired intojust setup; contributor docs. No.gitattributeschange..gitattributesdrivers, narrow —.github/ISSUE_TEMPLATE/config.ymlonly, plus the driver definition in W2's recipe. Explicitly not the ADR-0005 five, notbin/lefthook*, notlefthook.yml, with the reasoning in the PR body.rerererecording. Four PRs or one, per §3.launchpad-26/mainfromupstream/main, failing loudly.mainpush restrictionjust upstream-sync-resume, containment reuse.launchpad-pr-check.ymlto require a register row for upstream-file changes.ci.ymlactually run on sync branches.Four ADR issues (§4) are filed alongside, not counted as work items — per
launchpad/AGENTS.md§4, an ADR is not a work item.3. Sequencing, and where the PRD's ordering is wrong
Three departures from the PRD's implied order.
(a)
rereremust precede the first hand-resolution, not follow it. The PRD lists plumbing and register as parallel setup. Butrerereonly records resolutions performed while it is enabled. Resolving the four conflicts first and enablingrerereafterwards throws away the single most valuable training set the fork will ever produce. W2 is small and blocks nothing — it should land first, before anyone touches a conflict.(b) The register must precede the merge drivers, and it did the work of shrinking them. The brief I was originally given assumed the ADR-0005 five,
bin/lefthook*,lefthook.yml, and the issue templates were all merge-driver candidates. Writing the register is what shows that all but one are wrong (§0.3). Any ordering that lets the drivers land first ships a change that reverses ADR-0005 by side effect.(c) Clearing the four conflicts should come before the automation, not after. The PRD treats the current backlog as the thing automation will handle. It is not — it is the thing that proves the automation is needed and supplies its test fixtures. A sync workflow built while the branch is 67 behind has no clean-merge path to exercise; every run escalates, and you learn nothing about SC2. Clear the backlog by hand with
rerereon, then automate the steady state.On W4's shape: I would split it.
Cargo.lock(regenerate) and.github/workflows/ci.yml(small — upstream replaced two of our steps withjust file-size-check) are one PR.lefthook.ymlis its own PR, because resolving it correctly means noticing the.lefthookrc/2.1.3 hazard in §0.4 and that is a reviewable judgement in itself.runtime.rsis its own PR: it is a code-motion-versus-edit conflict — we extractedpersona_drift_state,workspace_pair_keyandbuild_managed_agent_summaryintoruntime/summary.rsfor the file-size ratchet, and upstream edited that block in place. Resolving it means porting upstream's edit into our extracted file and building it. No mechanism helps here; this is what escalation is for, and it is the best evidence for ADR-0020's boundary.4. The four ADRs
schedule:line in W5buzz-infrastructure#1030018 is the real gate and the one most likely to stall, because answering it means eight product-code files whose authors may not have thought about upstream conflict at all. I would file it with the register attached as evidence and a recommended default — upstream wins on product code unless a row says otherwise, fork wins on process and deployment files — so the decision is a yes/no on a concrete proposal rather than an open design question. 0019 should be decided by fiat in the PRD, not as an ADR; it is a cron expression, and giving it ADR weight adds a week of latency to a one-line change.
5. What I would do first
W2 then W1, in that order, and they are cheap.
W2 is roughly an hour: a
just upstream-sync-setuprecipe runninggit config rerere.enabled true,git config rerere.autoUpdate true, and the merge-driver definition; a hook intojust setup; a paragraph inlaunchpad/AGENTS.md§3 or a newlaunchpad/upstream-sync/README.md. It blocks nothing, it is reversible, and every hour it is not landed is an hour in which someone might resolve a conflict that is then lost. Evidence would begit config --get rerere.enabledbefore and after, pasted raw.W1 is most of a day. I have already derived the classification (48 files, 21/2/25 split, upstream-touched-since-merge-base per file) with a loop over
git diff --name-statusagainst both sides, so the mechanical part is done. The expensive part is the ~35 unrecorded reasons, which means reading four product-code commits and roughly a dozen process-file commits and writing down what they were actually for. Where I cannot determine intent I would writeunrecorded — see #<commit>, not guess.What it would take beyond my own time: one admin decision before W5 can start — whether
github-actionsjoinsrestrictions.appsonmain, or the mirror moves to an unprotected branch. And one human to name owners for the product-code rows in the register; the "who to ask" column is worthless if I fill it in from commit authorship alone.6. What cannot be done
Cannot be done at all, by me:
mainfast-forward (SC1) until the push restriction is resolved. I have no path around it and would not want one.launchpad/AGENTS.md§5.1: draft everything, approve nothing.Cannot be done well in 27 days:
UNDECIDEDin it, so I would ship the honest version and let the winners fill in.Should not be done, though it was asked for:
merge=ourson the ADR-0005 five,bin/lefthook*, orlefthook.yml. §0.3. Each contradicts a written decision or discards substantial upstream work. If the intent behind that instruction was "make these stop conflicting," the honest answer is that they should keep conflicting, and the register's job is to make each conflict cheap to resolve rather than to make it disappear.Not verified: I have not run
just ci, have not attempted a real merge in a working tree (I usedgit merge-tree --write-tree, which reports textual conflicts only), have not confirmed the four resolutions produce a building tree, have not testedrererereplay end to end, have not confirmed that addinggithub-actionstorestrictions.appsactually permits the FF, and have not read the ~12 process-file commits behind the unrecorded divergences — only the four product-code ones. No VPS access and none needed here.Written by an AI agent (Claude Opus 5) on behalf of @tucktuck101, from direction given in session on 2026-08-21. Read-only investigation:
gh issue view,gh api,git fetch,git diff,git merge-tree, and readinglaunchpad/AGENTS.md,ADR-0005,ADR-0017,launchpad/AGENT_PR_TEMPLATE.md. No repository files were changed, no commits, no pushes, no pull requests. This is a proposal, not a decision — nothing here is approved.36 remaining items
Merge pull request #1923 from launchpad-26/sync-upstream-2026-08-31