@tokenoftrust/cli 2.0.5 → 2.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/tot.mjs CHANGED
@@ -205,11 +205,12 @@ async function dispatch(cmd, rest, ctx) {
205
205
  return run(rest, ctx);
206
206
  }
207
207
 
208
- // `revert --preview <PR|integration-sha>` removes already-integrated content from
209
- // the protected `preview` aggregate via a NEW auditable revert commit — NO
210
- // force-reset, NO touch to main/live. Backed by POST /api/changes/revert
211
- // (TenantIntegrationQueue.enqueueRevert). To undo something already LIVE, that's
212
- // `tot rollback`, not this. See revert.mjs's header.
208
+ // `revert --preview <PR|change-id>` undoes an already-integrated PR out of the
209
+ // protected `preview` aggregate by opening a NEW corrective candidate and
210
+ // merging it through the SAME accept pipeline — NO force-reset, NO touch to
211
+ // main/live. Backed by POST /api/changes/revert (TenantIntegrationQueue.enqueue,
212
+ // the same pipeline `tot accept` drives). To undo something already LIVE,
213
+ // that's `tot rollback`, not this. See revert.mjs's header.
213
214
  if (cmd === "revert") {
214
215
  const { run } = await import("../src/commands/revert.mjs");
215
216
  return run(rest, ctx);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tokenoftrust/cli",
3
- "version": "2.0.5",
3
+ "version": "2.0.7",
4
4
  "description": "Token of Trust developer CLI — clone a tenant store, run it locally with save→reload, and submit it for preview. Installs the `tot` command.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Token of Trust",
@@ -1,34 +1,41 @@
1
1
  /**
2
- * `tot revert --preview <PR|integration-sha>` — OPERATOR verb: REMOVE
3
- * already-integrated content from the protected `preview` AGGREGATE by creating a
4
- * NEW auditable revert commit, then rebuilding the aggregate. It is the explicit,
5
- * safe answer to "several candidates are accepted, but one must be removed"
6
- * (branch-lifecycle contract): NEVER a force-reset, NEVER a branch delete — a revert
7
- * is a new commit on `preview`, and the shared preview moves only when the rebuilt
8
- * aggregate is green again. Full history is preserved.
2
+ * `tot revert --preview <PR|change-id>` — OPERATOR verb: undo an
3
+ * already-integrated PR OUT of the protected `preview` AGGREGATE by opening a
4
+ * NEW CORRECTIVE CANDIDATE cut from the exact state of `preview` the instant
5
+ * before that PR landed and running it through the SAME `candidate_open` +
6
+ * `candidate_accept` pipeline every other change uses. NEVER a force-reset,
7
+ * NEVER a bespoke revert-commit engine, NEVER a branch delete: Gitea's own real
8
+ * 3-way merge is the conflict detector, so a genuine overlap with something that
9
+ * landed since surfaces as an ORDINARY `merge_failed` refusal on that corrective
10
+ * candidate — the identical shape any other candidate's conflict produces.
9
11
  *
10
- * DISTINCT from `tot ship`: revert is NOT go-live. It touches NO `main` and NO live
11
- * channel; it only rewinds the SHARED preview aggregate. The verb is `--preview`-only
12
- * by construction — there is no `--main` revert. To undo something already LIVE, use
13
- * `tot rollback` (a live re-point), not this.
12
+ * DISTINCT from `tot ship`: revert is NOT go-live. It touches NO `main` and NO
13
+ * live channel; it only corrects the SHARED preview aggregate. The verb is
14
+ * `--preview`-only by construction — there is no `--main` revert. To undo
15
+ * something already LIVE, use `tot rollback` (a live re-point), not this.
14
16
  *
15
- * TARGET — `--preview <target>` names EITHER a PR number (`42` or `#42`) OR an exact
16
- * `preview` integration SHA. A numeric target is sent as `prNumber`; anything else as
17
- * `integrationSha`. The server resolves either to the exact integration commit to
18
- * undo (the resolver, inside the tenant-serialized queue lock), and REFUSES a
19
- * target that was never integrated or has already been reverted.
17
+ * TARGET — `--preview <target>` names EITHER a PR number (`42` or `#42`) OR the
18
+ * change's own id (as shown by `tot ship`'s included-PR listing). A numeric
19
+ * target is sent as `prNumber`; anything else as `changeId`. The server resolves
20
+ * either against the aggregate's OWN integration-run history to the exact
21
+ * `preview` state to undo from, and refuses honestly before opening
22
+ * anything — when there's nothing TO undo (never integrated, no longer part of
23
+ * the current aggregate, or already undone).
20
24
  *
21
- * TRANSPORT — `POST /api/changes/revert` (the ONE call site of
22
- * `TenantIntegrationQueue.enqueueRevert`). Like `tot accept` / `tot ship`, the CLI
23
- * reaches it with the OPERATOR-SECRET Bearer transport (`resolveOperatorSecret` +
24
- * `X-Tot-Owner` + `x-tot-capability`). The response is the honest terminal aggregate
25
- * state `queueState` / `runState` / `pointerMoved` / `aggregateSha` /
26
- * `revertedSha` / `statusMessage` rendered VERBATIM, never a bare "reverted".
25
+ * TRANSPORT — `POST /api/changes/revert` (opens the corrective candidate, then
26
+ * runs it through the SAME `TenantIntegrationQueue.enqueue` accept pipeline
27
+ * `tot accept` drives). Like `tot accept` / `tot ship`, the CLI reaches it with
28
+ * the OPERATOR-SECRET Bearer transport (`resolveOperatorSecret` + `X-Tot-Owner`
29
+ * + `x-tot-capability`). The response is the honest terminal aggregate state —
30
+ * `queueState` / `runState` / `pointerMoved` / `aggregateSha` / `changeId` (the
31
+ * corrective candidate's own id) / `statusMessage` — rendered VERBATIM, never a
32
+ * bare "reverted".
27
33
  *
28
- * HUMAN GATE — rewinding the shared preview is a decision a human makes, so this
29
- * ALWAYS states the EXACT plan (a NEW revert commit, NO force-reset, NO touch to
30
- * main/live) and requires an explicit confirm. `--yes` is an explicit affirmative; a
31
- * non-TTY without `--yes` is refused (mirrors `tot accept` / `tot ship`).
34
+ * HUMAN GATE — correcting the shared preview is a decision a human makes, so
35
+ * this ALWAYS states the EXACT plan (a NEW corrective candidate, merged through
36
+ * the normal accept pipeline, NO force-reset, NO touch to main/live) and
37
+ * requires an explicit confirm. `--yes` is an explicit affirmative; a non-TTY
38
+ * without `--yes` is refused (mirrors `tot accept` / `tot ship`).
32
39
  *
33
40
  * Dependency-free (global fetch + the shared plan module).
34
41
  */
@@ -40,28 +47,48 @@ import { resolveOperatorSecret } from "./ship.mjs";
40
47
 
41
48
  const DEFAULT_STOREFRONT_URL = "https://storefront.tokenoftrust.store";
42
49
 
43
- const USAGE = `tot revert remove already-integrated content from the preview aggregate (auditable)
50
+ // Refusal reasons where there is honestly nothing TO undo right now (never
51
+ // integrated / dropped out of the current aggregate generation / already
52
+ // undone / the first-ever integration with no recorded prior state / the
53
+ // target's own integration commit touched nothing) — a calm, non-alarming
54
+ // message, never framed as an error to fix.
55
+ const NOTHING_TO_UNDO_REASONS = new Set([
56
+ "not_integrated",
57
+ "not_currently_integrated",
58
+ "already_undone",
59
+ "no_prior_state",
60
+ "no_changes",
61
+ ]);
44
62
 
45
- tot revert --preview <PR|integration-sha> --tenant <t>
63
+ const RUNBOOK_URI = "runbook://undo-an-integrated-pr-from-the-shared-preview-aggregate";
46
64
 
47
- Creates a NEW auditable revert commit on the tenant's protected \`preview\`
48
- aggregate that undoes one prior integration, then rebuilds the aggregate and
49
- runs combined evidence. The shared preview advances only when it is GREEN
50
- again. A revert is NEVER a force-reset and NEVER deletes commits — full history
51
- is preserved. It does NOT touch main and does NOT go live.
65
+ const USAGE = `tot revert undo an already-integrated PR out of the preview aggregate
52
66
 
53
- <PR|integration-sha> is EITHER a PR number (e.g. 42 or #42) OR an exact preview
54
- integration SHA. A target that was never integrated, or has already been
55
- reverted, is refused before any change is made.
67
+ tot revert --preview <PR|change-id> --tenant <t>
56
68
 
57
- Rewinding the shared preview is a human decision: this ALWAYS prints the exact
58
- plan and asks for an explicit confirm. A non-TTY without --yes is refused.
69
+ Opens a NEW corrective candidate that undoes one prior integration cut from
70
+ the exact \`preview\` state just before it landed and merges it through the
71
+ SAME accept pipeline every other change uses. The shared preview advances
72
+ only when that merge lands GREEN. This is NEVER a force-reset and NEVER
73
+ deletes commits — full history is preserved. It does NOT touch main and does
74
+ NOT go live.
75
+
76
+ <PR|change-id> is EITHER a PR number (e.g. 42 or #42) OR the change's own id.
77
+ A target that was never integrated, is no longer part of the current
78
+ aggregate, or has already been undone, is refused before anything is opened.
79
+ A genuine conflict (something else touched the same content since) opens the
80
+ corrective candidate anyway and reports it honestly — resolve it with
81
+ \`tot preview resolve\`, or hand ${RUNBOOK_URI} to your AI agent.
82
+
83
+ Correcting the shared preview is a human decision: this ALWAYS prints the
84
+ exact plan and asks for an explicit confirm. A non-TTY without --yes is
85
+ refused.
59
86
 
60
87
  Options:
61
- --preview <PR|sha> REQUIRED. The PR number or integration SHA to revert.
88
+ --preview <PR|change-id> REQUIRED. The PR number or change id to undo.
62
89
  --tenant <appDomain> Target tenant (e.g. tokenoftrust.com). Defaults to the
63
90
  current checkout's tenant when run inside one.
64
- --message <msg> Optional revert-commit message.
91
+ --message <msg> Optional note recorded on the corrective candidate.
65
92
  --url <origin> storefront origin (default: env TOT_STOREFRONT_URL)
66
93
  --secret <s> operator secret (prefer the env vars below)
67
94
  --yes, -y Skip the interactive confirm (still an explicit human
@@ -98,23 +125,31 @@ export function parseRevertArgs(argv) {
98
125
  }
99
126
 
100
127
  /**
101
- * Classify a `--preview` target into `{ prNumber }` or `{ integrationSha }`. A bare
102
- * integer (optionally `#`-prefixed) is a PR number; anything else (a hex-ish SHA) is
103
- * an integration SHA. Pure unit-tested.
128
+ * Classify a `--preview` target into `{ prNumber }` or `{ changeId }`. A bare
129
+ * integer (optionally `#`-prefixed) is a PR number; anything else is the
130
+ * change's own id, sent verbatim (the server resolves either against the
131
+ * aggregate's own integration-run history — no local sha/shape validation).
132
+ * Pure — unit-tested.
104
133
  * @param {string|null} raw
105
- * @returns {{ prNumber:number|null, integrationSha:string|null }}
134
+ * @returns {{ prNumber:number|null, changeId:string|null }}
106
135
  */
107
136
  export function classifyRevertTarget(raw) {
108
137
  const t = (raw || "").trim().replace(/^#/, "");
109
- if (!t) return { prNumber: null, integrationSha: null };
110
- if (/^\d+$/.test(t)) return { prNumber: Number(t), integrationSha: null };
111
- return { prNumber: null, integrationSha: t };
138
+ if (!t) return { prNumber: null, changeId: null };
139
+ if (/^\d+$/.test(t)) return { prNumber: Number(t), changeId: null };
140
+ return { prNumber: null, changeId: t };
112
141
  }
113
142
 
114
143
  /**
115
144
  * Normalise a `POST /api/changes/revert` body to the honest terminal aggregate
116
145
  * fields this verb renders. Reads defensively so a plausible field rename degrades
117
146
  * rather than crashes. Pure — unit-tested.
147
+ *
148
+ * `changeId` here is the SERVER's response field — the corrective candidate's
149
+ * own id (`undo-<original>`), not the CLI's `--preview` input. There is no
150
+ * "revertedSha" concept anymore (undo is a merge, not a direct commit rewrite);
151
+ * `aggregateSha` is the honest replacement — the aggregate sha the corrective
152
+ * candidate landed at.
118
153
  * @param {any} data
119
154
  */
120
155
  export function normalizeRevertResponse(data) {
@@ -125,7 +160,6 @@ export function normalizeRevertResponse(data) {
125
160
  runState: typeof o.runState === "string" ? o.runState : null,
126
161
  pointerMoved: o.pointerMoved === true,
127
162
  aggregateSha: typeof o.aggregateSha === "string" ? o.aggregateSha : null,
128
- revertedSha: typeof o.revertedSha === "string" ? o.revertedSha : null,
129
163
  statusMessage: typeof o.statusMessage === "string" ? o.statusMessage : null,
130
164
  reason: typeof o.reason === "string" ? o.reason : null,
131
165
  changeId: typeof o.changeId === "string" ? o.changeId : null,
@@ -135,8 +169,9 @@ export function normalizeRevertResponse(data) {
135
169
  }
136
170
 
137
171
  /**
138
- * Render the terminal aggregate status in house style — the honest queue/run state,
139
- * NEVER a bare "reverted". Pure given its inputs; returns the process exit code.
172
+ * Render the terminal aggregate status in house style — the honest queue/run
173
+ * state, NEVER a bare "reverted". Pure given its inputs; returns the process
174
+ * exit code.
140
175
  * @param {ReturnType<typeof normalizeRevertResponse>} result
141
176
  * @param {{ tenant:string, label:string }} ctx
142
177
  * @returns {number}
@@ -144,19 +179,31 @@ export function normalizeRevertResponse(data) {
144
179
  export function reportReverted(result, { tenant, label }) {
145
180
  const state = `${result.queueState ?? "?"}/${result.runState ?? "—"}`;
146
181
  if (result.ok) {
147
- console.log(`\n ✓ reverted ${label} out of ${tenant}'s preview aggregate — it is GREEN.`);
182
+ console.log(`\n ✓ undid ${label} in ${tenant}'s preview aggregate — it is GREEN.`);
148
183
  console.log(` aggregate: ${state}${result.aggregateSha ? ` (${result.aggregateSha})` : ""}`);
149
- if (result.revertedSha) console.log(` reverted integration commit: ${result.revertedSha}`);
150
- if (result.pointerMoved) console.log(" the shared preview pointer moved to the reverted aggregate.");
184
+ if (result.changeId) console.log(` corrective candidate: ${result.changeId}`);
185
+ if (result.pointerMoved) console.log(" the shared preview pointer moved to the corrected aggregate.");
151
186
  console.log(" → next: review the aggregate preview, then `tot ship` to publish it live.");
152
187
  return 0;
153
188
  }
154
- // Honest non-green: the revert did NOT land in the shippable aggregate.
155
- console.log(`\n ✗ ${label} was NOT reverted out of ${tenant}'s preview aggregate.`);
189
+ // Honest non-green: the undo did NOT land in the shippable aggregate.
190
+ console.log(`\n ✗ ${label} was NOT undone in ${tenant}'s preview aggregate.`);
156
191
  console.log(` aggregate: ${state}${result.reason ? ` (${result.reason})` : ""}`);
157
192
  if (result.statusMessage) console.log(` why: ${result.statusMessage}`);
158
- if (result.reason === "target_not_found" || result.reason === "already_reverted") {
159
- console.log(` check the target: \`tot ship\` shows the aggregate's included PRs.`);
193
+
194
+ if (result.reason === "merge_failed") {
195
+ // A genuine conflict: the corrective candidate WAS opened, but something
196
+ // else touched the same content since — the ordinary conflict path, not a
197
+ // special "revert" failure. Point at the manual/agent recovery, naming the
198
+ // ALREADY-OPEN candidate so it's resolved, not re-derived from scratch.
199
+ console.log(" → this is a genuine merge conflict: something else touched the same content since.");
200
+ console.log(" → resolve it with `tot preview resolve`, or hand this to your AI agent:");
201
+ console.log(` Read ${RUNBOOK_URI} via the Token of Trust MCP and follow it.`);
202
+ if (result.changeId) {
203
+ console.log(` The corrective candidate "${result.changeId}" is ALREADY OPEN — resolve that one, don't start over.`);
204
+ }
205
+ } else if (result.reason && NOTHING_TO_UNDO_REASONS.has(result.reason)) {
206
+ console.log(` → nothing to do here: ${label} isn't something this can currently undo.`);
160
207
  } else {
161
208
  console.log(" → the aggregate is unchanged (last green preserved); fix forward or retry.");
162
209
  }
@@ -169,43 +216,44 @@ export function reportReverted(result, { tenant, label }) {
169
216
  * aggregate status. `fetch`/`confirmPlan` injected so it is unit-tested with no
170
217
  * network/TTY.
171
218
  *
172
- * @param {{ tenant:string, prNumber:number|null, integrationSha:string|null,
219
+ * @param {{ tenant:string, prNumber:number|null, changeId:string|null,
173
220
  * message:string|null, secret:string, storefrontUrl?:string|null, yes?:boolean }} params
174
221
  * @param {{ fetch?:typeof fetch, confirmPlan?:typeof printPlanAndConfirm }} [deps]
175
222
  * @returns {Promise<number>} process exit code
176
223
  */
177
224
  export async function runRevert(
178
- { tenant, prNumber, integrationSha, message = null, secret, storefrontUrl = null, yes = false },
225
+ { tenant, prNumber, changeId, message = null, secret, storefrontUrl = null, yes = false },
179
226
  deps = {},
180
227
  ) {
181
228
  const fetchImpl = deps.fetch || globalThis.fetch;
182
229
  const confirmPlan = deps.confirmPlan || printPlanAndConfirm;
183
230
  const base = (storefrontUrl || DEFAULT_STOREFRONT_URL).trim().replace(/\/+$/, "");
184
- const label = prNumber != null ? `PR #${prNumber}` : `integration ${integrationSha}`;
231
+ const label = prNumber != null ? `PR #${prNumber}` : `change ${changeId}`;
185
232
 
186
- // 1. State the EXACT plan (shared affordance) — a NEW revert commit on
187
- // preview, NO force-reset, NO touch to main/live — and gate on a confirm.
233
+ // 1. State the EXACT plan (shared affordance) — a NEW corrective candidate
234
+ // merged through the normal accept pipeline, NO force-reset, NO touch to
235
+ // main/live — and gate on a confirm.
188
236
  const planLines = planForAction({
189
237
  action: "revert",
190
238
  tenant,
191
239
  pr: prNumber,
192
- integrationSha,
240
+ changeId,
193
241
  });
194
242
  const { confirmed, reason } = await confirmPlan(planLines, {
195
243
  yes,
196
- question: `Revert ${label} out of ${tenant}'s preview aggregate (a new revert commit)?`,
244
+ question: `Undo ${label} out of ${tenant}'s preview aggregate (a new corrective candidate)?`,
197
245
  });
198
246
  if (!confirmed) {
199
247
  if (reason === "non-tty") {
200
248
  console.error(
201
249
  fail(
202
- "refusing to revert without confirmation on a non-TTY.",
250
+ "refusing to undo without confirmation on a non-TTY.",
203
251
  "re-run with --yes (an explicit human affirmative), or from an interactive terminal.",
204
252
  ),
205
253
  );
206
254
  return 2;
207
255
  }
208
- console.log("Aborted — nothing was reverted.");
256
+ console.log("Aborted — nothing was undone.");
209
257
  return 1;
210
258
  }
211
259
 
@@ -213,7 +261,7 @@ export async function runRevert(
213
261
  if (!secret) {
214
262
  console.error(
215
263
  fail(
216
- "reverting the preview aggregate is an OPERATOR action — it needs an operator secret",
264
+ "undoing an integrated PR is an OPERATOR action — it needs an operator secret",
217
265
  "set PREVIEW_RECONCILE_SECRET (or GRANTS_ADMIN_SECRET / TOT_OPERATOR_SECRET), or pass --secret",
218
266
  ),
219
267
  );
@@ -228,11 +276,11 @@ export async function runRevert(
228
276
  const requestBody = {
229
277
  repo: tenant,
230
278
  ...(prNumber != null ? { prNumber } : {}),
231
- ...(integrationSha ? { integrationSha } : {}),
279
+ ...(changeId ? { changeId } : {}),
232
280
  ...(message ? { message } : {}),
233
281
  };
234
282
 
235
- // 3. POST the honest revert path (queue enqueueRevert) and render terminal state.
283
+ // 3. POST the honest undo-as-candidate path and render the terminal state.
236
284
  let res;
237
285
  try {
238
286
  res = await fetchImpl(`${base}/api/changes/revert`, {
@@ -296,12 +344,12 @@ export async function run(argv, ctx) {
296
344
  return 2;
297
345
  }
298
346
 
299
- const { prNumber, integrationSha } = classifyRevertTarget(args.preview);
300
- if (prNumber == null && !integrationSha) {
347
+ const { prNumber, changeId } = classifyRevertTarget(args.preview);
348
+ if (prNumber == null && !changeId) {
301
349
  console.error(
302
350
  fail(
303
351
  "no revert target.",
304
- "pass --preview <PR|integration-sha> (a PR number like 42, or an exact preview integration SHA).",
352
+ "pass --preview <PR|change-id> (a PR number like 42, or the change's own id).",
305
353
  ),
306
354
  );
307
355
  return 2;
@@ -313,7 +361,7 @@ export async function run(argv, ctx) {
313
361
  return await runRevert({
314
362
  tenant,
315
363
  prNumber,
316
- integrationSha,
364
+ changeId,
317
365
  message: (args.message || "").trim() || null,
318
366
  secret: resolveOperatorSecret(args.secret, env),
319
367
  storefrontUrl,
@@ -159,7 +159,15 @@ export function forgeShimPath(env = process.env) {
159
159
  /** The shim's contents: pin the ABSOLUTE node + CLI entry active at write time, with a
160
160
  * PATH-search fallback for node, and FAIL LOUD to stderr (never silently) when no node
161
161
  * is found — so a broken helper says how to fix itself instead of yielding an opaque
162
- * "Repository not found". Pure — unit-tested. */
162
+ * "Repository not found". Pure — unit-tested.
163
+ *
164
+ * The pinned ENTRY gets the same fail-loud treatment as NODE, because the path pinned at
165
+ * write time can stop existing: running the CLI from a dev checkout or a git worktree pins
166
+ * the shim to THAT tree, and reaping it breaks forge git auth for every checkout on the
167
+ * machine — including ones created by the globally-installed `tot`. Unguarded, the failure
168
+ * surfaces three layers away as a raw node MODULE_NOT_FOUND followed by git's
169
+ * "could not read Username" / "Repository not found", which names neither the stale path
170
+ * nor the fix. */
163
171
  export function renderForgeShim(nodePath, entryPath) {
164
172
  return [
165
173
  "#!/bin/sh",
@@ -171,7 +179,18 @@ export function renderForgeShim(nodePath, entryPath) {
171
179
  ' echo "tot: no node runtime for the git credential helper — reinstall: npm i -g @tokenoftrust/cli@latest" >&2',
172
180
  " exit 1",
173
181
  "fi",
174
- `exec "$NODE" ${shq(entryPath)} git-credential "$@"`,
182
+ `ENTRY=${shq(entryPath)}`,
183
+ 'if [ ! -f "$ENTRY" ]; then',
184
+ ' echo "tot: the git credential helper points at a CLI that no longer exists:" >&2',
185
+ ' echo " $ENTRY" >&2',
186
+ ' echo " This shim was pinned by whichever tot ran last — if that was a dev" >&2',
187
+ ' echo " checkout or a git worktree that has since been removed, forge git auth" >&2',
188
+ ' echo " is broken for EVERY checkout on this machine until it is re-pinned." >&2',
189
+ ' echo " Fix: run any tot command that touches git (e.g. \\`tot clone <tenant>\\`)" >&2',
190
+ ' echo " from an installed tot, or reinstall: npm i -g @tokenoftrust/cli@latest" >&2',
191
+ " exit 1",
192
+ "fi",
193
+ 'exec "$NODE" "$ENTRY" git-credential "$@"',
175
194
  "",
176
195
  ].join("\n");
177
196
  }
package/src/plan.mjs CHANGED
@@ -162,14 +162,17 @@ export function planForAction({
162
162
  break;
163
163
  }
164
164
  case "revert": {
165
- // Revert: REMOVE already-integrated content from the protected `preview`
166
- // aggregate by creating a NEW auditable revert commit never a force-reset,
167
- // never a branch delete. The aggregate rebuilds and ships only when green
168
- // again. State exactly that: preview-only, a new commit, NO touch to main.
165
+ // Undo: pull an already-integrated PR back OUT of the protected `preview`
166
+ // aggregate by opening a NEW corrective candidatecut from the exact
167
+ // `preview` state just before it landed and merging it through the SAME
168
+ // accept pipeline every other change uses. Never a force-reset, never a
169
+ // bespoke revert-commit engine, never a branch delete: a genuine conflict
170
+ // with something that landed since surfaces as an ordinary merge_failed
171
+ // on that candidate. State exactly that: preview-only, no touch to main.
169
172
  const from = tenant ? `${tenant}'s preview aggregate` : "the preview aggregate";
170
- const what = integrationSha ? `integration ${integrationSha}` : label;
171
173
  lines.push(
172
- ` effect: revert ${what} out of ${from} — a NEW revert commit, NO force-reset, NO merge to main, NO go-live.`,
174
+ ` effect: undo ${label} open a NEW corrective candidate that undoes it, ` +
175
+ `then attempt to merge it into ${from}. NO force-reset, NO merge to main, NO go-live.`,
173
176
  );
174
177
  break;
175
178
  }