From da3bb0d130a40e831f227cae4be5cf2ccba38472 Mon Sep 17 00:00:00 2001 From: Jacob Cable Date: Thu, 4 Jun 2026 12:17:00 +0100 Subject: [PATCH] fix(stage): stop leaking the private mirror into upstream PRs `stage --pr` (and `issue stage`) appended a footer "Upstreamed from internal review ()" to the upstream PR/issue body, where points at the private mirror. The synthetic fallback body also stated "Staged from a private mirror". Both violate the core principle that the mirror is invisible to upstream. Drop the footer from translateInternalBody (now strips internal blocks only) and reword buildSyntheticBody to a neutral commit summary. The internal PR URL is still recorded in mirror config and shown locally, never sent upstream. --- README.md | 2 +- src/commands.ts | 41 +++++++++++++++---------------------- tests/e2e/sync-flow.test.ts | 5 ++++- 3 files changed, 21 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 5afd4ac..ef99a1a 100644 --- a/README.md +++ b/README.md @@ -313,7 +313,7 @@ venfork stage feature-auth --pr --base develop **What `--pr` adds:** 1. Looks up the most recent PR on the private mirror with `--head ` (open first, then most recent of any state). -2. Renders the upstream PR body by stripping any `...` blocks and appending a footer linking back to the internal review. +2. Renders the upstream PR body by stripping any `...` blocks. The private mirror stays invisible to upstream — no back-link to the internal review and no hint that one exists. The internal PR URL is recorded only in your mirror config (step 5). 3. Shows you the translated body **before** confirming, so you can catch redaction mistakes before they go public. 4. Runs `gh pr create --repo --base --head :` and surfaces the resulting PR URL. 5. Records the linkage in `venfork-config.shippedBranches[]` for later tracking. diff --git a/src/commands.ts b/src/commands.ts index 67e2966..70b0d2f 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -2560,24 +2560,16 @@ async function findInternalPr( } /** - * Strips `...` blocks via - * a depth-tracking pass (see `stripInternalBlocks`) and appends a footer - * linking back to the internal review PR or issue. Only the team can follow - * that link; the upstream maintainer sees only that there *was* an internal - * review. + * Renders an internal PR/issue body for upstream by stripping + * `...` blocks via a + * depth-tracking pass (see `stripInternalBlocks`). + * + * The private mirror must stay invisible to upstream: no back-link to the + * internal PR/issue and no hint that one exists. The upstream maintainer sees + * only the public-facing body. */ -function translateInternalBody( - body: string, - internalUrl: string | undefined, - kind: 'pr' | 'issue' = 'pr' -): string { - const stripped = stripInternalBlocks(body).trim(); - const footer = internalUrl - ? kind === 'issue' - ? `\n\n> Upstreamed from internal issue (${internalUrl}).` - : `\n\n> Upstreamed from internal review (${internalUrl}).` - : ''; - return `${stripped}${footer}`.trim(); +function translateInternalBody(body: string): string { + return stripInternalBlocks(body).trim(); } /** @@ -2586,6 +2578,9 @@ function translateInternalBody( * `upstream/..` so the upstream maintainer sees what * the change actually is, rather than a "please add a description" placeholder. * + * The body must not reveal the private mirror — upstream only ever sees the + * commit summary, never that the work was staged from a mirror. + * * Fetches `upstream/` first so the log works even when schedule * is disabled and the ref may not exist locally yet. */ @@ -2601,14 +2596,14 @@ async function buildSyntheticBody( reject: false, })`git log --oneline --no-merges upstream/${defaultBranch}..${branch}`; if (log.exitCode !== 0 || !log.stdout.trim()) { - return 'Staged from a private mirror. No internal review PR was open at stage time.'; + return 'No description provided.'; } const lines = log.stdout .trim() .split('\n') .map((line) => `- ${line}`) .join('\n'); - return `Staged from a private mirror. Commits in this branch:\n\n${lines}\n\n_(No internal review PR was found at stage time.)_`; + return `Commits in this branch:\n\n${lines}`; } /** @@ -2624,7 +2619,7 @@ async function buildUpstreamPrPayload( if (internal) { return { title: override.title ?? internal.title, - body: override.body ?? translateInternalBody(internal.body, internal.url), + body: override.body ?? translateInternalBody(internal.body), }; } return { @@ -3304,11 +3299,7 @@ export async function issueCommand( const internal = await readIssue(mirrorRepoPath, internalNumber, repoDir); s.stop(`Read: ${internal.title}`); - const translatedBody = translateInternalBody( - internal.body, - internal.url, - 'issue' - ); + const translatedBody = translateInternalBody(internal.body); const upstreamTitle = options.title ?? internal.title; p.note( diff --git a/tests/e2e/sync-flow.test.ts b/tests/e2e/sync-flow.test.ts index f463ae8..fe5bfa2 100644 --- a/tests/e2e/sync-flow.test.ts +++ b/tests/e2e/sync-flow.test.ts @@ -371,7 +371,10 @@ e2eDescribe('venfork e2e — scheduled sync flow', () => { expect(upstreamPr.body).not.toContain('Internal note'); expect(upstreamPr.body).not.toContain('venfork:internal'); expect(upstreamPr.body).toContain('Public summary'); - expect(upstreamPr.body).toContain('Upstreamed from internal review'); + // The private mirror must stay invisible to upstream: no back-link to the + // internal review PR and no hint that one exists. + expect(upstreamPr.body).not.toContain('Upstreamed from internal'); + expect(upstreamPr.body).not.toContain(names.mirrorBare); expect(upstreamPr.isDraft).toBe(true); }, 300_000);