@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 +6 -5
- package/package.json +1 -1
- package/src/commands/revert.mjs +121 -73
- package/src/git-credential.mjs +21 -2
- package/src/plan.mjs +9 -6
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|
|
|
209
|
-
//
|
|
210
|
-
// force-reset, NO touch to
|
|
211
|
-
//
|
|
212
|
-
// `tot
|
|
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.
|
|
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",
|
package/src/commands/revert.mjs
CHANGED
|
@@ -1,34 +1,41 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `tot revert --preview <PR|
|
|
3
|
-
* already-integrated
|
|
4
|
-
* NEW
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
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
|
|
11
|
-
* channel; it only
|
|
12
|
-
* by construction — there is no `--main` revert. To undo
|
|
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
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
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
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* `
|
|
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 —
|
|
29
|
-
* ALWAYS states the EXACT plan (a NEW
|
|
30
|
-
*
|
|
31
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
63
|
+
const RUNBOOK_URI = "runbook://undo-an-integrated-pr-from-the-shared-preview-aggregate";
|
|
46
64
|
|
|
47
|
-
|
|
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|
|
|
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
|
-
|
|
58
|
-
|
|
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|
|
|
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
|
|
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 `{
|
|
102
|
-
* integer (optionally `#`-prefixed) is a PR number; anything else
|
|
103
|
-
*
|
|
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,
|
|
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,
|
|
110
|
-
if (/^\d+$/.test(t)) return { prNumber: Number(t),
|
|
111
|
-
return { prNumber: null,
|
|
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
|
|
139
|
-
* NEVER a bare "reverted". Pure given its inputs; returns the process
|
|
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 ✓
|
|
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.
|
|
150
|
-
if (result.pointerMoved) console.log(" the shared preview pointer moved to the
|
|
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
|
|
155
|
-
console.log(`\n ✗ ${label} was NOT
|
|
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
|
-
|
|
159
|
-
|
|
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,
|
|
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,
|
|
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}` : `
|
|
231
|
+
const label = prNumber != null ? `PR #${prNumber}` : `change ${changeId}`;
|
|
185
232
|
|
|
186
|
-
// 1. State the EXACT plan (shared affordance) — a NEW
|
|
187
|
-
//
|
|
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
|
-
|
|
240
|
+
changeId,
|
|
193
241
|
});
|
|
194
242
|
const { confirmed, reason } = await confirmPlan(planLines, {
|
|
195
243
|
yes,
|
|
196
|
-
question: `
|
|
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
|
|
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
|
|
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
|
-
"
|
|
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
|
-
...(
|
|
279
|
+
...(changeId ? { changeId } : {}),
|
|
232
280
|
...(message ? { message } : {}),
|
|
233
281
|
};
|
|
234
282
|
|
|
235
|
-
// 3. POST the honest
|
|
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,
|
|
300
|
-
if (prNumber == null && !
|
|
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|
|
|
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
|
-
|
|
364
|
+
changeId,
|
|
317
365
|
message: (args.message || "").trim() || null,
|
|
318
366
|
secret: resolveOperatorSecret(args.secret, env),
|
|
319
367
|
storefrontUrl,
|
package/src/git-credential.mjs
CHANGED
|
@@ -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
|
-
`
|
|
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
|
-
//
|
|
166
|
-
// aggregate by
|
|
167
|
-
//
|
|
168
|
-
//
|
|
165
|
+
// Undo: pull an already-integrated PR back OUT of the protected `preview`
|
|
166
|
+
// aggregate by opening a NEW corrective candidate — cut 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:
|
|
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
|
}
|