Goal: make a product change, merge it, get a green fork/integration, and deploy — without
waiting for a full upstream / Tim / candidates restack.
Nothing in the runnable stack is optional. Every layer below is required for a complete deploy tip. The only choice is when you advance provenance layers (slow path), not whether overlays or Tim/candidates “count.”
pingdotgg/t3code:main required mirror
└── fork/tim required Tim Smart integrations
└── fork/candidates required selected open upstream PRs
└── fork/changes required shared downstream product
├── ordinary feature PRs → merge into fork/changes
├── registered overlays required client layers (parallel drafts)
│ (desktop, discord, vscode, … as listed in the manifest)
└── compose → fork/integration required runnable / deploy tip
= fork/changes + every registered overlay in order
| Layer | Required? | What “required” means |
|---|---|---|
main / fork/tim / fork/candidates |
Yes | Permanent parents of product. You do not drop them. You only rebuild them on the slow path. |
fork/changes |
Yes | Shared product default branch. Ordinary features merge here. |
| Every registered overlay | Yes | If it is in integrationOverlays, it must be based on current fork/changes and included in every compose of fork/integration. Skipping an overlay is not a valid ship. |
fork/integration |
Yes | Only tip you run and deploy. Never a feature/import branch. |
Overlays are not “nice-to-have clients.” They are long-lived product slices kept out of
fork/changes so shared history stays clean — but the runnable product always includes them.
This document is the planned split between a fast ship path and a slow layer-rebuild path.
It supersedes the older assumption that every merge to fork/changes must run the full
Rebase fork PR stack mega-job.
Related: fork-stack.md (topology, overlays, conflict resolutions), stack-history-rewrite.md (history hygiene).
Merging to
fork/changesand composingfork/integrationmust not require a successful full stack rewrite. Full stack rewrite (main → tim → candidates → changes → rebase every overlay → compose) is a separate, stop-the-line operation.
Composing without every registered overlay rebased onto currentfork/changesis incomplete.
ordinary shared work → branch from fork/changes → PR base fork/changes
overlay-owned work → branch from that overlay → PR base = overlay branch
run / deploy → always fork/integration (compose)
Why this graph stays simple:
- One merge target for shared product (
fork/changes). - Overlays stay parallel (not stacked on each other), rebased when
fork/changesmoves. - Compose is a pure function:
changes + ordered overlay tips → integration. - Ordinary PRs do not encode multi-parent dependencies on desktop+discord+vscode.
Branch off fork/integration, develop against the full product tree, then somehow land the PR
“on top of changes → overlays (aka integration).”
Why it is attractive:
- Local and CI see Discord / desktop / VS Code + shared code without a separate compose step.
- Matches “the product is integration” intuition.
Why we do not make this the default model:
- Merge target ambiguity. GitHub PRs have one base. Landing into “the full product” either means merging into a permanent open overlay/integration PR forest, or inventing multi-base merges. That is hard to automate and easy to get wrong.
- Dependency explosion. A change that touches shared code and two clients becomes “this PR
depends on two other open PRs.” Stacks of N open PRs with cross edges are confusing for humans
and agents, and
fork:stack update/ rebase automation gets brittle. - Permanent open PR tax. Integration-as-base works only if every overlay (and often integration itself) stays a permanent open PR surface. That is already painful for overlays; expanding it to every feature is worse.
- Compose already defines integration. The shipable tree is reproducible from manifest branches. Branching from a composed tip couples you to a generated history and invites tip-only fixes on integration.
If we ever revisit this, the design bar is: one clear base per PR, no multi-parent feature
graphs, and integration remains compose-generated (not a merge destination for ordinary
features). Until then: branch from fork/changes (or the owning overlay), compose for the full
product.
Unblocks “I just want to ship.” Still ends with full integration (all required overlays).
pnpm fork:stack start my-fix # from fork/changes
→ implement + local gates (vp check, typecheck, focused tests)
→ PR → fork/changes → merge
→ Compose fork integration workflow (automatic):
1. auto-rebase every registered overlay onto current fork/changes
2. compose overlays onto fork/changes → push fork/integration
3. dispatch Fork CI on that SHA
→ smart poller deploys (if enabled)
Does not rebuild main, fork/tim, or fork/candidates.
Does not auto-rebase the entire open feature PR forest (only registered overlays).
Does not wait for a mega restack job.
Does require clean overlay rebases — real product conflicts still fail the job (fix that overlay, re-run compose).
After fork/changes moves (or an overlay tip moves), Compose fork integration runs. Locally:
# From a clean checkout of fork/changes (with push credentials for stack branches)
node scripts/rebase-integration-overlays.ts # no-op when already based
node scripts/compose-integration-overlays.ts --push
gh workflow run "Fork CI" --repo patroza/t3code --ref fork/integrationOr one shot:
gh workflow run compose-integration.yml --repo patroza/t3code --ref fork/changesCompose pipeline:
- Auto-rebase overlays (
scripts/rebase-integration-overlays.ts): for each registered overlay not based on currentfork/changes,git rebase --ontousing the merge-base with the new tip, then force-with-lease push. Skips when already based. - Compose (
scripts/compose-integration-overlays.ts): currentfork/changes+ every overlay tip (manifest order) →fork/integration(lockfile regen as needed). - Dispatch Fork CI on the composed tip.
If an overlay conflicts during auto-rebase, the job fails with the branch + paths. Fix that
overlay tip (or add a durable product merge), push it based on current fork/changes, then re-run
compose — do not leave the overlay stale and expect a partial ship.
Do not rebuild Tim/candidates to fix one overlay. Do not ship integration with a missing or stale registered overlay.
Keeping open feature PRs rebased onto fork/changes is handoff work for that PR:
pnpm fork:stack update --push # current branch / its PR
pnpm fork:stack update --push <pr> # explicit PR
pnpm fork:stack pull # after remote rewrote your branchGlobal “rebase every open feature PR on every parent move” is not part of the fast path.
Keeping registered overlays on current fork/changes is part of the ship path whenever
you compose.
Runs when you choose to take new upstream, Tim imports, or candidate imports — not on every product merge.
GitHub Actions: the Rebase fork PR stack workflow stays disabled_manually. Do not
enable it and do not gh workflow run rebase-pr-stack.yml. Restacks are operator/local only:
export GH_TOKEN="$(gh auth token)"
# from a checkout of fork/changes with write access to stack branches:
node scripts/rebase-pr-stack.ts sync --dry-run # inspect first
node scripts/rebase-pr-stack.ts sync --push # only when intentional
# or rebuild layers by hand, green gate each tip before the next childmirror main (exact pingdotgg/t3code:main)
→ rebuild fork/tim full local CI green on tip (stop the line)
→ rebuild fork/candidates full local CI green on tip
→ rebuild fork/changes full local CI green on tip
→ rebase each registered overlay onto new fork/changes (each required)
→ compose fork/integration
→ Fork CI + deploy
Rules:
- One red layer blocks the next. Never stack “green later.” Overlays and integration are
layers in that sense after
fork/changesis green. - Prefer compose for
fork/integrationafter rewrites; do not rebase an old integration tip onto rewritten history. - Record durable
conflictResolutionsin.github/pr-stack.jsonwhen the same path always takes the same side — but never whole-fileours/theirson shared product paths (ChatView, VCS drivers, contracts RPC, etc.). Those need a real 3-way product merge. - Product-facing recovery belongs in product-named commits, not permanent tip-only
fix(stack).
While a slow path is in flight, the default is still: do not block unrelated product PRs unless you intentionally freeze merges for a cutover window.
| Job | Intended role | Status |
|---|---|---|
| Compose fork integration | Integration tip from changes + all overlays | Active — .github/workflows/compose-integration.yml on push/merge to fork/changes and registered overlay branches (and workflow_dispatch) |
| Fork CI | Green gate on PR tips / composed integration | Active |
| Rebase fork PR stack | Full layer rebuild + PR cascade | disabled_manually in GitHub Actions — leave it that way. Do not enable or dispatch this workflow. Slow-path restacks are local only (see slow path section). |
| Smart integration poller | Deploy CI-approved integration SHA | On when fleet should track green integration |
Do not re-enable Rebase fork PR stack. Operators who need a full upstream rewrite run
node scripts/rebase-pr-stack.ts sync --push (or equivalent) locally with appropriate credentials,
layer by layer, green gates first. GitHub Actions must not auto-restack the provenance stack.
Registered in .github/pr-stack.json → integrationOverlays.
| Rule | Detail |
|---|---|
| Required for integration? | Yes — every registered overlay must be rebased onto current fork/changes and composed |
| Base | Always current fork/changes (never based on each other) |
| Source of truth | Branch tip in the manifest; draft PR is for review/tracking |
| Labels | Use OVERLAY on the permanent draft PR |
| Draft | Draft = “do not merge into fork/changes”; health CI can still be green |
| Ship impact | Behind overlay ⇒ rebase that overlay + compose; do not restack Tim; do not skip the overlay |
Planned automation fix: compose/stack tooling should key off branch names + label, not “PR must be open.” Closed overlay PRs must not brick the ship path. Prefer reopening the same PR numbers after history moves; only create replacements when GitHub refuses reopen. Until tooling is fixed, keep registered overlay PRs open (draft) so automation does not fail mid-ship.
| Layer | Rule |
|---|---|
Feature PR / commit on fork/changes |
If any workspace package.json changes, the same commit/PR updates pnpm-lock.yaml (CI= pnpm install). Frozen install must pass. |
| Tim / candidates replay | Commits that change manifests regenerate lock in that commit during a planned rebuild. |
| Overlay tips | Self-consistent for that overlay’s packages; lock-only commits may diverge by design. |
| Integration compose | Compose skips lock-only overlay commits and may commit one generated chore(integration): regenerate pnpm-lock.yaml… as a compose artifact, not as product history. |
Tip-only lock fixes on product layers are process failures. Generated integration lock after multi-overlay compose is acceptable when product and overlay PRs were already self-consistent.
pnpm fork:stack start <branch>from up-to-datefork/changes(not from integration).- Implement; run focused tests + package typecheck while iterating.
- Before ready handoff: root
vp check+ full monorepo typecheck (see AGENTS.md). - Open/update PR against
fork/changesonly (nevermain, neverfork/integrationas merge base for ordinary features). - Merge when green.
- Rebase every registered overlay onto the new
fork/changestip if needed. - Compose integration + dispatch Fork CI (or rely on compose-on-merge when enabled).
- Confirm poller/deploy only if runtime-affecting and CI succeeded.
- Work on the overlay branch via a child PR targeting the overlay (preferred), or on the overlay tip only when using an intentional bypass actor for stack maintenance.
- Do not duplicate the change into
fork/changes. - Same local gate as any feature PR before ready/merge (root
vp check+ full monorepo typecheck + focused tests). Overlay-child bases are protected with the same required Fork CI checks asfork/changes. “Compose only ran” or “draft permanent overlay PR is green” does not mean the child tip was checked. - Rebase onto latest
fork/changesif needed; merge only when Check/Test are green; compose full integration (all overlays); confirm integration CI.
- Schedule a slow path rebuild; do not mix with unrelated feature landings if avoidable.
- Stop the line per layer; product 3-way merges for conflicts (no blind whole-file product
ours/theirs). - Rebase all registered overlays; compose; CI; then resume normal fast path.
The stack model is “good enough” when:
- A normal product PR can merge to
fork/changesand reach greenfork/integrationthe same day without running a full main→tim→candidates rewrite. - That integration tip always includes every registered overlay on current
fork/changes. - A full restack is rare, deliberate, and fully green per layer (including each overlay tip, then composed integration) before the next layer advances.
- One closed overlay PR or one conflicted feature PR cannot block unrelated product deploys once tooling keys off branches (until then: keep overlay drafts open).
fork/timandfork/candidatesremain permanent required parents of product — updated on the slow path only.- We do not force ordinary features to branch from integration or encode multi-overlay PR dependencies.
Compose-on-merge is landed. Remaining improvements:
- Keep Rebase fork PR stack
disabled_manuallyforever for automation. Prefer localnode scripts/rebase-pr-stack.ts …for slow-path rewrites. Do not re-enable the workflow for schedule/push, and do not treatworkflow_dispatchas the default agent path. - Overlay validation in compose/stack scripts: branch existence + OVERLAY label;
do not require
state=openas a hard gate for compose. - Conflict resolution policy: forbid durable whole-file product path strategies in
conflictResolutionsfor shared app/package sources (warn → error over time). - Optional later: local tooling helpers for per-layer rebuild (
tim/candidates/changes) with stop-the-line gates — still not a GitHub Actions mega-restack.