@tokenfactory/acc-runner 0.47.9 → 0.47.10
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/dist/build-info.generated.d.ts +1 -1
- package/dist/build-info.generated.js +2 -2
- package/dist/companion-autostart.d.ts.map +1 -1
- package/dist/companion-autostart.js +5 -0
- package/dist/companion-autostart.js.map +1 -1
- package/dist/companion-run.d.ts.map +1 -1
- package/dist/companion-run.js +46 -18
- package/dist/companion-run.js.map +1 -1
- package/dist/doctor.d.ts +20 -0
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +183 -3
- package/dist/doctor.js.map +1 -1
- package/dist/keychain.d.ts +31 -0
- package/dist/keychain.d.ts.map +1 -1
- package/dist/keychain.js +70 -1
- package/dist/keychain.js.map +1 -1
- package/dist/program.d.ts.map +1 -1
- package/dist/program.js +16 -1
- package/dist/program.js.map +1 -1
- package/dist/runtime/auth-lifecycle.d.ts +22 -1
- package/dist/runtime/auth-lifecycle.d.ts.map +1 -1
- package/dist/runtime/auth-lifecycle.js +28 -1
- package/dist/runtime/auth-lifecycle.js.map +1 -1
- package/dist/runtime/refresh-lock.d.ts +232 -0
- package/dist/runtime/refresh-lock.d.ts.map +1 -0
- package/dist/runtime/refresh-lock.js +609 -0
- package/dist/runtime/refresh-lock.js.map +1 -0
- package/dist/supervisor.d.ts +32 -0
- package/dist/supervisor.d.ts.map +1 -1
- package/dist/supervisor.js +56 -0
- package/dist/supervisor.js.map +1 -1
- package/dist/token-provider.d.ts +12 -0
- package/dist/token-provider.d.ts.map +1 -1
- package/dist/token-provider.js +23 -3
- package/dist/token-provider.js.map +1 -1
- package/dist/watch.d.ts +4 -2
- package/dist/watch.d.ts.map +1 -1
- package/dist/watch.js +117 -20
- package/dist/watch.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,609 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RUNNER-REFRESH-ROTATION-LOST (ROTATED): coordinate a token refresh across
|
|
3
|
+
* every process on a host that shares ONE keychain slot.
|
|
4
|
+
*
|
|
5
|
+
* The first occurrence (a rotated successor never reaching the keychain) is
|
|
6
|
+
* handled by keychain.ts's verified save. This file handles the SECOND shape:
|
|
7
|
+
* two rotators — a `watch` and a `companion`, or two `watch` units — that both
|
|
8
|
+
* resolve the SAME instance keychain slot. When one rotates the refresh token
|
|
9
|
+
* the server revokes the predecessor; the OTHER process is still holding that
|
|
10
|
+
* predecessor in memory and, on its next refresh, re-presents it. The server's
|
|
11
|
+
* reuse detection then revokes the WHOLE family and both processes die.
|
|
12
|
+
*
|
|
13
|
+
* Two primitives fix it, both keyed to the keychain account (= the instance
|
|
14
|
+
* slot), so coordination is per-slot:
|
|
15
|
+
*
|
|
16
|
+
* 1. acquireRefreshLock() — an instance-scoped advisory lock with TWO layers,
|
|
17
|
+
* so it serializes rotators both within one process and across processes:
|
|
18
|
+
* - an in-process async mutex (keyed by the keychain account) that every
|
|
19
|
+
* same-process acquirer queues on FIRST, so at most one rotator in this
|
|
20
|
+
* process is ever between taking and releasing the lock; and
|
|
21
|
+
* - an O_EXCL lock FILE under the instance-namespaced cache dir that
|
|
22
|
+
* serializes rotators in DISTINCT processes (the `watch` and the
|
|
23
|
+
* `companion` are separate processes with distinct pids).
|
|
24
|
+
* Because the in-process mutex guarantees single-rotator access within the
|
|
25
|
+
* process, the file layer never races a LIVE same-pid holder — a lock file
|
|
26
|
+
* carrying our own pid is therefore always a stale leftover from THIS
|
|
27
|
+
* process (an acquire that could not unlink on release), safe to reclaim.
|
|
28
|
+
* A rotator takes the lock before refreshing so two rotators on one slot
|
|
29
|
+
* serialize instead of racing. It WAITS for a live cross-process holder
|
|
30
|
+
* (polling) and reclaims a dead holder's lock. It fails CLOSED: if the wait
|
|
31
|
+
* budget is exhausted while a live holder still owns it, it throws
|
|
32
|
+
* `RefreshLockBusyError` rather than proceeding lockless. An earlier build
|
|
33
|
+
* failed OPEN here on the theory that the adoption re-read below was a
|
|
34
|
+
* sufficient safety net; it is not — the holder may have called
|
|
35
|
+
* /api/runner/refresh but not yet SAVED the successor, so a lockless
|
|
36
|
+
* rotator re-reads its own (now superseded) token, re-presents it, and
|
|
37
|
+
* trips reuse detection. The HTTP refresh therefore only ever runs while
|
|
38
|
+
* this lock is HELD.
|
|
39
|
+
* 2. coordinatedRefresh() — under the lock, RE-READ the slot. If it now holds
|
|
40
|
+
* a session whose refresh token differs from the one we loaded, another
|
|
41
|
+
* rotator already rotated: ADOPT its successor rather than re-presenting
|
|
42
|
+
* our superseded token (which would trip reuse detection). Only when the
|
|
43
|
+
* slot still matches our token do we actually call /api/runner/refresh.
|
|
44
|
+
* If the lock could not be taken (a live sibling is mid-rotation), it makes
|
|
45
|
+
* no HTTP call: it either adopts a successor the sibling has already saved
|
|
46
|
+
* (safe — nothing is presented) or refuses, surfacing a transient failure
|
|
47
|
+
* the caller retries (by which point the sibling has saved and we adopt).
|
|
48
|
+
*/
|
|
49
|
+
import fs from "node:fs";
|
|
50
|
+
import fsp from "node:fs/promises";
|
|
51
|
+
import path from "node:path";
|
|
52
|
+
import crypto from "node:crypto";
|
|
53
|
+
import chalk from "chalk";
|
|
54
|
+
import { cacheDir } from "./cache-dir.js";
|
|
55
|
+
import { isProcessAlive } from "./locks.js";
|
|
56
|
+
import { loadSessionFromAccount } from "../keychain.js";
|
|
57
|
+
import { expiresSoon, refreshAccessToken } from "./auth-lifecycle.js";
|
|
58
|
+
/**
|
|
59
|
+
* RUNNER-REFRESH-ROTATION-LOST: exit status for a TERMINAL refresh refusal
|
|
60
|
+
* (reuse / expired) or an unprovable keychain save. EX_CONFIG from sysexits(3):
|
|
61
|
+
* the config the process was handed is unusable and a restart would only
|
|
62
|
+
* re-present it, so BOTH the `watch` and `companion` supervisor units carry
|
|
63
|
+
* `RestartPreventExitStatus=78` and the process exits this code rather than
|
|
64
|
+
* letting the supervisor loop every minute and burn every token family it can
|
|
65
|
+
* reach. Defined here (not in watch.ts) so the companion can share it without
|
|
66
|
+
* importing the whole watch task-loop module. watch.ts re-exports it.
|
|
67
|
+
*/
|
|
68
|
+
export const EX_CONFIG = 78;
|
|
69
|
+
/**
|
|
70
|
+
* RUNNER-REFRESH-ROTATION-LOST (ROTATED): print the operator-actionable banner
|
|
71
|
+
* for a TERMINAL refresh refusal and emit the runner-lane `auth.refresh_refused`
|
|
72
|
+
* board breadcrumb — AC3, carrying the `process` field so the board sees WHICH
|
|
73
|
+
* rotator (watch or companion) died on the shared slot. Shared verbatim by both
|
|
74
|
+
* rotators so the payload shape can never drift between them. Does NOT exit; the
|
|
75
|
+
* caller decides (both exit EX_CONFIG). The breadcrumb is emitted only while a
|
|
76
|
+
* usable bearer remains and is strictly best-effort — it never throws and never
|
|
77
|
+
* blocks the exit.
|
|
78
|
+
*/
|
|
79
|
+
export async function emitRefreshRefused(opts) {
|
|
80
|
+
const write = opts.write ?? ((s) => void process.stderr.write(s));
|
|
81
|
+
const { session, instance, account, error } = opts;
|
|
82
|
+
write(chalk.red.bold(`\n[acc-runner] CRITICAL: refresh refused (${error.code}).\n`));
|
|
83
|
+
write(chalk.red(`${error.message}\n`));
|
|
84
|
+
if (error.code === "refresh_reuse_detected") {
|
|
85
|
+
write(chalk.red("If this machine is the legitimate owner, an attacker may have rotated your tokens.\n" +
|
|
86
|
+
"Rotate any other credentials that were on this box.\n"));
|
|
87
|
+
}
|
|
88
|
+
write(chalk.red(`Clear the stale keychain slot "${account}" and re-login: ` +
|
|
89
|
+
`acc-runner login${instance ? ` --instance ${instance}` : ""}\n` +
|
|
90
|
+
`The unit stays failed (exit ${EX_CONFIG}); it will NOT restart-loop.\n`));
|
|
91
|
+
// Emit the board breadcrumb ONLY while the current access token is still
|
|
92
|
+
// usable — a refusal reached after the bearer itself expired has no authority
|
|
93
|
+
// to call log_activity, and must not block the exit waiting on a 401.
|
|
94
|
+
if (!expiresSoon(session, 0)) {
|
|
95
|
+
try {
|
|
96
|
+
const client = opts.makeClient();
|
|
97
|
+
const call = Promise.resolve(client.rpc("log_activity", {
|
|
98
|
+
p_verb: "auth.refresh_refused",
|
|
99
|
+
p_target_id: session.runner_id,
|
|
100
|
+
p_target_type: "runner",
|
|
101
|
+
p_payload: {
|
|
102
|
+
runner_id: session.runner_id,
|
|
103
|
+
instance,
|
|
104
|
+
process: opts.process,
|
|
105
|
+
error: error.code,
|
|
106
|
+
keychain_account: account,
|
|
107
|
+
},
|
|
108
|
+
}));
|
|
109
|
+
await (opts.withDeadline ? opts.withDeadline(call, 5_000) : call);
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
/* best-effort breadcrumb — never let it hold up the terminal exit */
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Thrown by acquireRefreshLock when the instance-scoped lock could not be taken
|
|
118
|
+
* within the wait budget because a LIVE sibling still holds it. It is NOT a
|
|
119
|
+
* terminal auth error — the caller must treat it as transient (refuse to rotate
|
|
120
|
+
* lockless, then retry), never exit on it.
|
|
121
|
+
*/
|
|
122
|
+
/**
|
|
123
|
+
* The keychain slot could not be re-read under the held lock, so the cached
|
|
124
|
+
* refresh token cannot be proven current. No HTTP refresh is made; the caller
|
|
125
|
+
* treats this like a transient refresh failure and retries.
|
|
126
|
+
*/
|
|
127
|
+
export class RefreshSlotUnreadableError extends Error {
|
|
128
|
+
account;
|
|
129
|
+
constructor(account, cause) {
|
|
130
|
+
super(`refresh skipped: keychain slot ${JSON.stringify(account)} could not be re-read ` +
|
|
131
|
+
`(${cause instanceof Error ? cause.message : String(cause)}); not presenting the cached token`);
|
|
132
|
+
this.name = "RefreshSlotUnreadableError";
|
|
133
|
+
this.account = account;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
export class RefreshLockBusyError extends Error {
|
|
137
|
+
account;
|
|
138
|
+
code = "refresh_lock_busy";
|
|
139
|
+
constructor(account) {
|
|
140
|
+
super(`refresh lock for "${account}" is held by a live sibling and was not released ` +
|
|
141
|
+
`within the wait budget; refusing to rotate without it`);
|
|
142
|
+
this.account = account;
|
|
143
|
+
this.name = "RefreshLockBusyError";
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* `<cache>/refresh.<hash>.lock`. The hash is the keychain account (the instance
|
|
148
|
+
* slot), so every process resolving the same slot contends on one file while
|
|
149
|
+
* two distinct instances (distinct slots) never do.
|
|
150
|
+
*/
|
|
151
|
+
export function refreshLockPath(account) {
|
|
152
|
+
const hash = crypto.createHash("sha1").update(account).digest("hex").slice(0, 12);
|
|
153
|
+
return path.join(cacheDir(), `refresh.${hash}.lock`);
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* How long an UNPARSEABLE lock file is presumed to be a live holder's before it
|
|
157
|
+
* may be reclaimed. The lock file is created atomically with its full payload
|
|
158
|
+
* (temp file + hard link, below), so an unreadable or partial file is never a
|
|
159
|
+
* holder mid-write on this build; it is either a crashed older build's leftover
|
|
160
|
+
* or filesystem damage. It is still never reclaimed while fresh: a reader that
|
|
161
|
+
* cannot name the holder's pid has no proof the holder is dead, and unlinking a
|
|
162
|
+
* live holder's lock is exactly the race this lock exists to prevent.
|
|
163
|
+
*/
|
|
164
|
+
export const UNPARSEABLE_LOCK_STALE_MS = 30_000;
|
|
165
|
+
function readRefreshLock(file, now) {
|
|
166
|
+
let raw;
|
|
167
|
+
try {
|
|
168
|
+
raw = fs.readFileSync(file, "utf8");
|
|
169
|
+
}
|
|
170
|
+
catch (err) {
|
|
171
|
+
if (err.code === "ENOENT")
|
|
172
|
+
return { kind: "absent" };
|
|
173
|
+
throw err;
|
|
174
|
+
}
|
|
175
|
+
try {
|
|
176
|
+
const parsed = JSON.parse(raw);
|
|
177
|
+
if (typeof parsed.pid === "number" && Number.isFinite(parsed.pid)) {
|
|
178
|
+
return {
|
|
179
|
+
kind: "holder",
|
|
180
|
+
info: {
|
|
181
|
+
pid: parsed.pid,
|
|
182
|
+
account: typeof parsed.account === "string" ? parsed.account : "<unknown>",
|
|
183
|
+
acquiredAt: typeof parsed.acquiredAt === "string" ? parsed.acquiredAt : "<unknown>",
|
|
184
|
+
},
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
catch {
|
|
189
|
+
/* fall through: unparseable */
|
|
190
|
+
}
|
|
191
|
+
// Corrupt / partial / empty lock file. NOT reclaimable while fresh — see
|
|
192
|
+
// UNPARSEABLE_LOCK_STALE_MS. Its age comes from the file's mtime; an mtime
|
|
193
|
+
// that cannot be read counts as fresh (the conservative direction).
|
|
194
|
+
let ageMs = 0;
|
|
195
|
+
try {
|
|
196
|
+
ageMs = Math.max(0, now() - fs.statSync(file).mtimeMs);
|
|
197
|
+
}
|
|
198
|
+
catch (err) {
|
|
199
|
+
if (err.code === "ENOENT")
|
|
200
|
+
return { kind: "absent" };
|
|
201
|
+
}
|
|
202
|
+
return { kind: "unparseable", ageMs };
|
|
203
|
+
}
|
|
204
|
+
function readRefreshLockInfo(file) {
|
|
205
|
+
const read = readRefreshLock(file, Date.now);
|
|
206
|
+
return read.kind === "holder" ? read.info : null;
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* In-process serialization gate, keyed by keychain account. The O_EXCL lock FILE
|
|
210
|
+
* below serializes rotators ACROSS processes (distinct pids); this async mutex
|
|
211
|
+
* serializes rotators WITHIN one process. Without it, two same-process acquirers
|
|
212
|
+
* would each find their OWN pid on the lock file and — on the stale-reclaim path
|
|
213
|
+
* — the second could delete the first's LIVE lock and proceed in parallel
|
|
214
|
+
* instead of waiting, breaking single-rotator serialization. With it, a
|
|
215
|
+
* same-process acquirer queues here first, so only one is ever between
|
|
216
|
+
* file-create and release; a same-pid lock file the file layer encounters is
|
|
217
|
+
* then provably a stale leftover from THIS process, never a live concurrent
|
|
218
|
+
* holder, and is safe to reclaim.
|
|
219
|
+
*
|
|
220
|
+
* Implemented as a per-account promise chain: each acquirer awaits the previous
|
|
221
|
+
* tail, and its release resolves the gate for the next in line. The map entry is
|
|
222
|
+
* dropped once its queue has fully drained so it cannot grow unbounded.
|
|
223
|
+
*/
|
|
224
|
+
const inProcessGates = new Map();
|
|
225
|
+
function acquireInProcessGate(account) {
|
|
226
|
+
const prev = inProcessGates.get(account) ?? Promise.resolve();
|
|
227
|
+
let release;
|
|
228
|
+
const held = new Promise((r) => {
|
|
229
|
+
release = r;
|
|
230
|
+
});
|
|
231
|
+
const tail = prev.then(() => held);
|
|
232
|
+
inProcessGates.set(account, tail);
|
|
233
|
+
// Hand control to the caller only after every earlier holder has released.
|
|
234
|
+
return prev.then(() => () => {
|
|
235
|
+
release();
|
|
236
|
+
// Drop the entry once we are the tail (queue drained) so long-lived
|
|
237
|
+
// processes rotating many accounts do not accumulate resolved promises.
|
|
238
|
+
if (inProcessGates.get(account) === tail)
|
|
239
|
+
inProcessGates.delete(account);
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Acquire the instance-scoped advisory refresh lock for `account`. Two layers:
|
|
244
|
+
* an in-process mutex (same-process rotators queue) and an O_EXCL lock file
|
|
245
|
+
* (cross-process rotators contend). Blocks (polling) while a LIVE process holds
|
|
246
|
+
* the file and reclaims a DEAD holder's lock. If the wait budget is exhausted
|
|
247
|
+
* while a live holder still owns it, it fails CLOSED — throwing
|
|
248
|
+
* `RefreshLockBusyError` — because an HTTP refresh must never run without the
|
|
249
|
+
* lock held (a lockless rotator can re-present a superseded token mid-rotation
|
|
250
|
+
* and trip reuse detection). The caller treats the throw as transient and
|
|
251
|
+
* retries. The in-process gate is always released, including on a throw, so a
|
|
252
|
+
* failed acquire never wedges later same-process rotators.
|
|
253
|
+
*/
|
|
254
|
+
export async function acquireRefreshLock(account, opts = {}) {
|
|
255
|
+
const releaseGate = await acquireInProcessGate(account);
|
|
256
|
+
try {
|
|
257
|
+
return await acquireFileLock(account, opts, releaseGate);
|
|
258
|
+
}
|
|
259
|
+
catch (err) {
|
|
260
|
+
releaseGate();
|
|
261
|
+
throw err;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* The cross-process half: the O_EXCL lock file. Only ever entered while the
|
|
266
|
+
* in-process gate is held, so a same-pid lock file here is a stale leftover from
|
|
267
|
+
* this process (safe to reclaim), never a live concurrent same-process holder.
|
|
268
|
+
*/
|
|
269
|
+
async function acquireFileLock(account, opts, releaseGate) {
|
|
270
|
+
const file = refreshLockPath(account);
|
|
271
|
+
const pid = opts.pid ?? process.pid;
|
|
272
|
+
const waitMs = opts.waitMs ?? 10_000;
|
|
273
|
+
const pollMs = opts.pollMs ?? 100;
|
|
274
|
+
const now = opts.now ?? Date.now;
|
|
275
|
+
const sleep = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
276
|
+
// Synchronous on the uncontended happy path (no await) so taking the lock
|
|
277
|
+
// adds no extra microtask/IO turn to the refresh — it only awaits when it
|
|
278
|
+
// genuinely has to WAIT for a live holder.
|
|
279
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
280
|
+
const payload = `${JSON.stringify({
|
|
281
|
+
pid,
|
|
282
|
+
account,
|
|
283
|
+
acquiredAt: new Date().toISOString(),
|
|
284
|
+
})}\n`;
|
|
285
|
+
const deadline = now() + waitMs;
|
|
286
|
+
// The lock file appears with its FULL payload or not at all: the payload is
|
|
287
|
+
// written to a private temp file first and then hard-linked to the lock path.
|
|
288
|
+
// link(2) fails with EEXIST when the lock exists, giving O_EXCL semantics,
|
|
289
|
+
// and a sibling that reads the lock can never observe it empty or partial
|
|
290
|
+
// (an `openSync("wx")` + `writeSync` pair had exactly that window, and a
|
|
291
|
+
// sibling reading the empty file in it treated the live lock as corrupt and
|
|
292
|
+
// reclaimed it — the race the reviewer found on 84c75503).
|
|
293
|
+
const tmp = `${file}.${pid}.${crypto.randomBytes(4).toString("hex")}.tmp`;
|
|
294
|
+
for (;;) {
|
|
295
|
+
fs.writeFileSync(tmp, payload, { flag: "w" });
|
|
296
|
+
try {
|
|
297
|
+
fs.linkSync(tmp, file);
|
|
298
|
+
return makeRefreshLock(file, pid, releaseGate);
|
|
299
|
+
}
|
|
300
|
+
catch (err) {
|
|
301
|
+
if (err.code !== "EEXIST")
|
|
302
|
+
throw err;
|
|
303
|
+
const read = readRefreshLock(file, now);
|
|
304
|
+
const reclaimable = read.kind === "absent" ||
|
|
305
|
+
(read.kind === "holder" && (read.info.pid === pid || !isProcessAlive(read.info.pid))) ||
|
|
306
|
+
(read.kind === "unparseable" && read.ageMs >= UNPARSEABLE_LOCK_STALE_MS);
|
|
307
|
+
if (reclaimable) {
|
|
308
|
+
// Reclaimable: a dead cross-process holder, OUR OWN pid, an already
|
|
309
|
+
// vanished file, or an unparseable file that has sat untouched for
|
|
310
|
+
// UNPARSEABLE_LOCK_STALE_MS. Our own pid can only be a stale leftover —
|
|
311
|
+
// the in-process gate above guarantees no other same-process rotator is
|
|
312
|
+
// holding the lock right now — so reclaiming it unwedges this process
|
|
313
|
+
// rather than stomping a live sibling. A live cross-process sibling
|
|
314
|
+
// carries a DIFFERENT pid, and a FRESH unparseable file is treated as a
|
|
315
|
+
// live holder too; both fall through to the wait below.
|
|
316
|
+
try {
|
|
317
|
+
fs.unlinkSync(file);
|
|
318
|
+
}
|
|
319
|
+
catch (unlinkErr) {
|
|
320
|
+
if (unlinkErr.code !== "ENOENT")
|
|
321
|
+
throw unlinkErr;
|
|
322
|
+
}
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
if (now() >= deadline)
|
|
326
|
+
throw new RefreshLockBusyError(account);
|
|
327
|
+
await sleep(pollMs);
|
|
328
|
+
}
|
|
329
|
+
finally {
|
|
330
|
+
try {
|
|
331
|
+
fs.unlinkSync(tmp);
|
|
332
|
+
}
|
|
333
|
+
catch {
|
|
334
|
+
/* already gone */
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
function makeRefreshLock(file, pid, releaseGate) {
|
|
340
|
+
let released = false;
|
|
341
|
+
return {
|
|
342
|
+
held: true,
|
|
343
|
+
async release() {
|
|
344
|
+
// Idempotent: callers may release more than once; the gate is freed once.
|
|
345
|
+
if (released)
|
|
346
|
+
return;
|
|
347
|
+
released = true;
|
|
348
|
+
try {
|
|
349
|
+
const holder = readRefreshLockInfo(file);
|
|
350
|
+
if (holder && holder.pid === pid)
|
|
351
|
+
await fsp.unlink(file);
|
|
352
|
+
}
|
|
353
|
+
catch (err) {
|
|
354
|
+
if (err.code !== "ENOENT")
|
|
355
|
+
throw err;
|
|
356
|
+
}
|
|
357
|
+
finally {
|
|
358
|
+
// Always hand the in-process gate to the next same-process rotator, even
|
|
359
|
+
// if unlinking the file failed, so a stuck unlink cannot wedge the queue.
|
|
360
|
+
releaseGate();
|
|
361
|
+
}
|
|
362
|
+
},
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* RUNNER-REFRESH-ROTATION-LOST (ROTATED): refresh the session the rotation-safe way.
|
|
367
|
+
*
|
|
368
|
+
* - Env-token providers have no shared keychain slot (boundAccount() === null)
|
|
369
|
+
* so they just refresh in place — there is no sibling to race.
|
|
370
|
+
* - Keychain providers take the instance-scoped lock, then RE-READ the slot. A
|
|
371
|
+
* refresh token in the slot that differs from ours means a sibling rotator
|
|
372
|
+
* already rotated while we held our (now superseded) token — adopt its
|
|
373
|
+
* successor instead of re-presenting ours. Only when the slot still matches
|
|
374
|
+
* do we call /api/runner/refresh ourselves and save the successor.
|
|
375
|
+
*/
|
|
376
|
+
export async function coordinatedRefresh(deps) {
|
|
377
|
+
const refresh = deps.refresh ?? refreshAccessToken;
|
|
378
|
+
const account = deps.provider.boundAccount();
|
|
379
|
+
if (account === null) {
|
|
380
|
+
const updated = await refresh(deps.cfg, deps.session);
|
|
381
|
+
await deps.provider.save(updated);
|
|
382
|
+
return { session: updated, adopted: false, refreshed: true };
|
|
383
|
+
}
|
|
384
|
+
const acquire = deps.acquireLock ?? acquireRefreshLock;
|
|
385
|
+
const load = deps.loadFromAccount ?? loadSessionFromAccount;
|
|
386
|
+
let lock;
|
|
387
|
+
try {
|
|
388
|
+
lock = await acquire(account);
|
|
389
|
+
}
|
|
390
|
+
catch (err) {
|
|
391
|
+
if (!(err instanceof RefreshLockBusyError))
|
|
392
|
+
throw err;
|
|
393
|
+
// We could NOT take the instance-scoped lock within the wait budget — a live
|
|
394
|
+
// sibling is mid-rotation. Rotating ourselves now (presenting our token, or
|
|
395
|
+
// even refreshing from a near-expired sibling successor) without the lock
|
|
396
|
+
// can re-trip reuse detection, so make NO HTTP call. The one safe move is to
|
|
397
|
+
// re-read the slot and ADOPT a successor the sibling has already saved
|
|
398
|
+
// (nothing is presented). Otherwise re-throw: the caller surfaces this as a
|
|
399
|
+
// transient refresh failure and retries, by which point the holder has saved
|
|
400
|
+
// and we adopt on the next pass.
|
|
401
|
+
const current = await load(account).catch(() => null);
|
|
402
|
+
if (current?.refresh_token &&
|
|
403
|
+
current.refresh_token !== deps.session.refresh_token &&
|
|
404
|
+
!expiresSoon(current)) {
|
|
405
|
+
return { session: current, adopted: true, refreshed: false };
|
|
406
|
+
}
|
|
407
|
+
throw err;
|
|
408
|
+
}
|
|
409
|
+
try {
|
|
410
|
+
// The slot re-read is the proof that our cached token is still the slot's
|
|
411
|
+
// current one. If the keychain cannot be read, that proof is missing, and
|
|
412
|
+
// presenting the cached token anyway is exactly the superseded-token
|
|
413
|
+
// re-presentation this function exists to prevent — so NO HTTP call: fail
|
|
414
|
+
// this attempt (the caller treats it as transient and retries).
|
|
415
|
+
let current;
|
|
416
|
+
try {
|
|
417
|
+
current = await load(account);
|
|
418
|
+
}
|
|
419
|
+
catch (err) {
|
|
420
|
+
throw new RefreshSlotUnreadableError(account, err);
|
|
421
|
+
}
|
|
422
|
+
if (current && current.refresh_token && current.refresh_token !== deps.session.refresh_token) {
|
|
423
|
+
// A sibling rotator already rotated the slot. Its successor is the only
|
|
424
|
+
// credential the server will still accept; ours was revoked when it
|
|
425
|
+
// rotated. Adopt it if it is still a usable bearer; otherwise refresh
|
|
426
|
+
// FROM it (the freshest refresh token), never from our superseded one.
|
|
427
|
+
// Both branches run under the held lock.
|
|
428
|
+
if (!expiresSoon(current)) {
|
|
429
|
+
return { session: current, adopted: true, refreshed: false };
|
|
430
|
+
}
|
|
431
|
+
const updated = await refresh(deps.cfg, current);
|
|
432
|
+
await deps.provider.save(updated);
|
|
433
|
+
return { session: updated, adopted: false, refreshed: true };
|
|
434
|
+
}
|
|
435
|
+
const updated = await refresh(deps.cfg, deps.session);
|
|
436
|
+
await deps.provider.save(updated);
|
|
437
|
+
return { session: updated, adopted: false, refreshed: true };
|
|
438
|
+
}
|
|
439
|
+
finally {
|
|
440
|
+
await lock.release();
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
/** Map an instance name (or null for the unnamed default) to its keychain slot. */
|
|
444
|
+
export function rotatorSlot(instance) {
|
|
445
|
+
return instance ? `instance:${instance}` : "default";
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* pgrep-style scan for other `acc-runner watch` / `acc-runner companion`
|
|
449
|
+
* processes, extracting the `--instance <name>` each was launched with. Offline
|
|
450
|
+
* of any lock file and best-effort (returns [] on any failure). It reads only
|
|
451
|
+
* the command LINE (`ps -axo args`), never a process's environment — reading
|
|
452
|
+
* /proc/<pid>/environ is banned (it would leak secrets per CLAUDE.md).
|
|
453
|
+
*
|
|
454
|
+
* The process scan only sees RUNNING rotators, and only the slot that appears as
|
|
455
|
+
* `--instance` in argv — a stopped/failed unit, or one that carries its slot in
|
|
456
|
+
* `ACC_RUNNER_INSTANCE` rather than argv, is invisible here. `listRefreshRotatorUnits`
|
|
457
|
+
* below covers both gaps from the read-only systemd unit config.
|
|
458
|
+
*/
|
|
459
|
+
export async function listRefreshRotators(selfPid = process.pid) {
|
|
460
|
+
try {
|
|
461
|
+
const { execa } = await import("execa");
|
|
462
|
+
const { stdout } = await execa("ps", ["-axo", "pid=,args="]);
|
|
463
|
+
const rotators = [];
|
|
464
|
+
for (const line of stdout.split(/\r?\n/)) {
|
|
465
|
+
const m = line.trim().match(/^(\d+)\s+(.*)$/);
|
|
466
|
+
if (!m)
|
|
467
|
+
continue;
|
|
468
|
+
const pid = Number.parseInt(m[1], 10);
|
|
469
|
+
const args = m[2];
|
|
470
|
+
if (pid === selfPid)
|
|
471
|
+
continue;
|
|
472
|
+
if (!/acc-runner\b/.test(args) && !/\bacc-runner-(watch|companion)\b/.test(args))
|
|
473
|
+
continue;
|
|
474
|
+
const kind = /\bwatch\b/.test(args)
|
|
475
|
+
? "watch"
|
|
476
|
+
: /\bcompanion\b/.test(args)
|
|
477
|
+
? "companion"
|
|
478
|
+
: null;
|
|
479
|
+
if (!kind)
|
|
480
|
+
continue;
|
|
481
|
+
const instMatch = args.match(/--instance[=\s]+([a-z0-9][a-z0-9-]{0,31})/);
|
|
482
|
+
rotators.push({ pid, kind, instance: instMatch ? instMatch[1] : null });
|
|
483
|
+
}
|
|
484
|
+
return rotators;
|
|
485
|
+
}
|
|
486
|
+
catch {
|
|
487
|
+
return [];
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
/**
|
|
491
|
+
* Instance name carried in a unit basename: `acc-runner-<name>.service` or the
|
|
492
|
+
* template form `acc-runner@<name>.service` (the impl1–4 lanes). Null for any
|
|
493
|
+
* other name — a companion unit's name (`acc-staging-companion.service`) says
|
|
494
|
+
* nothing about its slot; its environment does.
|
|
495
|
+
*/
|
|
496
|
+
export function instanceFromUnitName(unit) {
|
|
497
|
+
const m = unit.match(/^acc-runner[-@]([a-z0-9][a-z0-9-]{0,31})\.service$/);
|
|
498
|
+
return m ? m[1] : null;
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* Classify one systemd unit as a rotator from what it RUNS and what its
|
|
502
|
+
* environment says, never from its name alone. A unit is a rotator when its
|
|
503
|
+
* ExecStart runs the runner binary (`acc-runner` / `acc`) as `watch` or
|
|
504
|
+
* `companion`, or when its environment carries `ACC_RUNNER_INSTANCE` (a unit
|
|
505
|
+
* that pins a slot refreshes it). Anything else — `acc-keyring.service`, a
|
|
506
|
+
* backup timer, an unrelated service that merely has "acc" in its name — is
|
|
507
|
+
* null. This is the rule the reviewer asked for on 91c768ad: the earlier
|
|
508
|
+
* name filter (`/acc-runner/`) skipped `acc-staging-companion.service`, the
|
|
509
|
+
* very unit that burnt the staging-smoke family (ledger 66).
|
|
510
|
+
*/
|
|
511
|
+
export function classifyRotatorUnit(unit, props) {
|
|
512
|
+
const exec = props.ExecStart ?? "";
|
|
513
|
+
const env = props.Environment ?? "";
|
|
514
|
+
const runsRunner = /(^|[\s/=])(acc-runner|acc)(\s|$)/.test(exec);
|
|
515
|
+
const kind = runsRunner && /\bcompanion\b/.test(exec)
|
|
516
|
+
? "companion"
|
|
517
|
+
: runsRunner && /\bwatch\b/.test(exec)
|
|
518
|
+
? "watch"
|
|
519
|
+
: runsRunner
|
|
520
|
+
? "unknown"
|
|
521
|
+
: null;
|
|
522
|
+
const envMatch = env.match(/\bACC_RUNNER_INSTANCE=([a-z0-9][a-z0-9-]{0,31})/);
|
|
523
|
+
if (kind === null && !envMatch)
|
|
524
|
+
return null;
|
|
525
|
+
// Slot precedence: the unit's ACC_RUNNER_INSTANCE env (authoritative, what
|
|
526
|
+
// instance.ts resolves), then an argv --instance, then the unit basename.
|
|
527
|
+
const argvMatch = exec.match(/--instance[=\s]+([a-z0-9][a-z0-9-]{0,31})/);
|
|
528
|
+
const instance = envMatch ? envMatch[1] : argvMatch ? argvMatch[1] : instanceFromUnitName(unit);
|
|
529
|
+
const mainPidRaw = Number.parseInt(props.MainPID ?? "0", 10);
|
|
530
|
+
return {
|
|
531
|
+
unit,
|
|
532
|
+
kind: kind ?? (/companion/.test(unit) ? "companion" : "unknown"),
|
|
533
|
+
instance,
|
|
534
|
+
state: props.ActiveState ?? "unknown",
|
|
535
|
+
mainPid: Number.isFinite(mainPidRaw) && mainPidRaw > 0 ? mainPidRaw : null,
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
/** Parse `Key=Value` lines from `systemctl show` into a flat map. */
|
|
539
|
+
function parseSystemctlShow(stdout) {
|
|
540
|
+
const out = {};
|
|
541
|
+
for (const line of stdout.split(/\r?\n/)) {
|
|
542
|
+
const eq = line.indexOf("=");
|
|
543
|
+
if (eq <= 0)
|
|
544
|
+
continue;
|
|
545
|
+
out[line.slice(0, eq)] = line.slice(eq + 1);
|
|
546
|
+
}
|
|
547
|
+
return out;
|
|
548
|
+
}
|
|
549
|
+
/**
|
|
550
|
+
* RUNNER-REFRESH-ROTATION-LOST (ROTATED, AC4): enumerate acc-runner rotator
|
|
551
|
+
* units from systemd --user CONFIG, INCLUDING stopped/failed ones, and resolve
|
|
552
|
+
* the keychain slot each targets from its READ-ONLY unit environment rather than
|
|
553
|
+
* a running process's argv. This closes the two gaps in the process scan:
|
|
554
|
+
*
|
|
555
|
+
* - A stopped or failed unit never appears in `ps`, yet it shares the slot it
|
|
556
|
+
* will rotate the instant it (re)starts — so doctor must still count it.
|
|
557
|
+
* - A unit that carries its slot in `Environment=ACC_RUNNER_INSTANCE=<name>`
|
|
558
|
+
* (not in argv) would be misread as the default slot by the argv-only scan.
|
|
559
|
+
* We read it from `systemctl show --property=Environment`, extracting ONLY
|
|
560
|
+
* `ACC_RUNNER_INSTANCE` and discarding the rest of the string, so no other
|
|
561
|
+
* (possibly secret-bearing) unit variable is ever surfaced. This reads the
|
|
562
|
+
* static unit config — NOT a live process's /proc/<pid>/environ, which stays
|
|
563
|
+
* banned per CLAUDE.md.
|
|
564
|
+
*
|
|
565
|
+
* Best-effort: returns [] on any failure (e.g. no systemd, non-Linux).
|
|
566
|
+
*/
|
|
567
|
+
export async function listRefreshRotatorUnits() {
|
|
568
|
+
try {
|
|
569
|
+
const { execa } = await import("execa");
|
|
570
|
+
// `list-units --all` includes loaded units in every state — active, failed,
|
|
571
|
+
// and inactive — so a stopped/failed rotator is enumerated, not skipped.
|
|
572
|
+
const { stdout: list } = await execa("systemctl", [
|
|
573
|
+
"--user",
|
|
574
|
+
"list-units",
|
|
575
|
+
"--all",
|
|
576
|
+
"--no-legend",
|
|
577
|
+
"--plain",
|
|
578
|
+
"--type=service",
|
|
579
|
+
]);
|
|
580
|
+
// Candidate units: every service whose name mentions acc at all (watch
|
|
581
|
+
// units, template lanes, companions, keyring, …). Which of them are rotators
|
|
582
|
+
// is decided by classifyRotatorUnit from ExecStart/Environment, not here.
|
|
583
|
+
const units = list
|
|
584
|
+
.split(/\r?\n/)
|
|
585
|
+
.map((l) => l.trim().split(/\s+/)[0] ?? "")
|
|
586
|
+
.filter((u) => /\.service$/.test(u) && /acc/i.test(u));
|
|
587
|
+
const out = [];
|
|
588
|
+
for (const unit of units) {
|
|
589
|
+
const { stdout } = await execa("systemctl", [
|
|
590
|
+
"--user",
|
|
591
|
+
"show",
|
|
592
|
+
unit,
|
|
593
|
+
"--property=Environment",
|
|
594
|
+
"--property=ExecStart",
|
|
595
|
+
"--property=ActiveState",
|
|
596
|
+
"--property=MainPID",
|
|
597
|
+
]);
|
|
598
|
+
const props = parseSystemctlShow(stdout);
|
|
599
|
+
const classified = classifyRotatorUnit(unit, props);
|
|
600
|
+
if (classified)
|
|
601
|
+
out.push(classified);
|
|
602
|
+
}
|
|
603
|
+
return out;
|
|
604
|
+
}
|
|
605
|
+
catch {
|
|
606
|
+
return [];
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
//# sourceMappingURL=refresh-lock.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"refresh-lock.js","sourceRoot":"","sources":["../../src/runtime/refresh-lock.ts"],"names":[],"mappings":"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"}
|