instar 1.2.35 → 1.2.37
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/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +20 -67
- package/dist/commands/server.js.map +1 -1
- package/dist/core/AutoUpdater.d.ts.map +1 -1
- package/dist/core/AutoUpdater.js +25 -1
- package/dist/core/AutoUpdater.js.map +1 -1
- package/dist/core/PostUpdateMigrator.d.ts +1 -0
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +91 -0
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/core/version-skew.d.ts +77 -0
- package/dist/core/version-skew.d.ts.map +1 -0
- package/dist/core/version-skew.js +144 -0
- package/dist/core/version-skew.js.map +1 -0
- package/dist/lifeline/RestartOrchestrator.d.ts +1 -1
- package/dist/lifeline/RestartOrchestrator.d.ts.map +1 -1
- package/dist/lifeline/TelegramLifeline.d.ts +14 -0
- package/dist/lifeline/TelegramLifeline.d.ts.map +1 -1
- package/dist/lifeline/TelegramLifeline.js +60 -0
- package/dist/lifeline/TelegramLifeline.js.map +1 -1
- package/dist/lifeline/rateLimitState.d.ts +1 -1
- package/dist/lifeline/rateLimitState.d.ts.map +1 -1
- package/dist/lifeline/rateLimitState.js +16 -11
- package/dist/lifeline/rateLimitState.js.map +1 -1
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +20 -0
- package/dist/server/routes.js.map +1 -1
- package/dist/tunnel/TunnelManager.d.ts +99 -62
- package/dist/tunnel/TunnelManager.d.ts.map +1 -1
- package/dist/tunnel/TunnelManager.js +340 -395
- package/dist/tunnel/TunnelManager.js.map +1 -1
- package/package.json +1 -1
- package/scripts/instar-dev-precommit.js +128 -0
- package/src/data/builtin-manifest.json +64 -64
- package/src/templates/scripts/instar-watchdog.sh +80 -3
- package/upgrades/1.2.36.md +111 -0
- package/upgrades/1.2.37.md +53 -0
- package/upgrades/side-effects/auto-updater-lifeline-coordination.md +199 -0
- package/upgrades/side-effects/tunnel-manager-rewrite.md +129 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version-skew.d.ts","sourceRoot":"","sources":["../../src/core/version-skew.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAMH,MAAM,WAAW,qBAAqB;IACpC,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,cAAc,GAAG,YAAY,GAAG,gCAAgC,CAAC;IAC9E,MAAM,EAAE,MAAM,CAAC;IACf,eAAe,EAAE,MAAM,CAAC;IACxB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;CACnB;AAID;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CASzG;AAED,MAAM,WAAW,eAAe;IAC9B,iEAAiE;IACjE,QAAQ,EAAE,MAAM,CAAC;IACjB,4DAA4D;IAC5D,WAAW,EAAE,qBAAqB,CAAC,aAAa,CAAC,CAAC;IAClD,uEAAuE;IACvE,MAAM,EAAE,MAAM,CAAC;IACf,eAAe,EAAE,MAAM,CAAC;IACxB,aAAa,EAAE,MAAM,CAAC;IACtB,uCAAuC;IACvC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gCAAgC;IAChC,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED;;;;;GAKG;AACH,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,eAAe,GAAG,SAAS,GAAG,eAAe,GAAG,gBAAgB,CA6ChH;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,GAAE,MAAmB,GAAG,qBAAqB,GAAG,IAAI,CAkBlH;AAED;;;GAGG;AACH,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAGjE;AAED,8DAA8D;AAC9D,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAElE"}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Version-skew detection + lifeline-restart signal coordination.
|
|
3
|
+
*
|
|
4
|
+
* The signal file `state/lifeline-restart-requested.json` is written by THREE
|
|
5
|
+
* independent channels (no single-point-of-failure):
|
|
6
|
+
*
|
|
7
|
+
* 1. AutoUpdater.requestRestart — when an apply crosses major.minor.
|
|
8
|
+
* 2. Server /internal/telegram-forward 426 handler — direct evidence of skew.
|
|
9
|
+
* 3. PostUpdateMigrator — one-time bootstrap nudge for currently-stuck lifelines.
|
|
10
|
+
*
|
|
11
|
+
* The signal is consumed by THREE independent readers:
|
|
12
|
+
*
|
|
13
|
+
* 1. TelegramLifeline tick loop — calls initiateRestart('plannedUpgrade').
|
|
14
|
+
* 2. ServerSupervisor poll — force-restarts the lifeline if its tick hasn't
|
|
15
|
+
* acted within 60s of the signal write.
|
|
16
|
+
* 3. (Future) v3 Remediator probe — absorbs both channels.
|
|
17
|
+
*
|
|
18
|
+
* Spec: docs/specs/auto-updater-lifeline-coordination.md
|
|
19
|
+
*/
|
|
20
|
+
import fs from 'node:fs';
|
|
21
|
+
import path from 'node:path';
|
|
22
|
+
import { SafeFsExecutor } from './SafeFsExecutor.js';
|
|
23
|
+
const SIGNAL_RELPATH = path.join('state', 'lifeline-restart-requested.json');
|
|
24
|
+
/**
|
|
25
|
+
* Predicate: does going from `prev` to `next` cross a major.minor boundary?
|
|
26
|
+
*
|
|
27
|
+
* Examples:
|
|
28
|
+
* crossesBreaking('1.1.0', '1.2.28') === true // minor crossed
|
|
29
|
+
* crossesBreaking('1.2.0', '1.2.28') === false // same major.minor
|
|
30
|
+
* crossesBreaking('1.2.28', '2.0.0') === true // major crossed
|
|
31
|
+
* crossesBreaking('1.2.28', '1.2.28') === false // identity
|
|
32
|
+
*
|
|
33
|
+
* Malformed inputs are treated as breaking (fail safe — prefer over-signal
|
|
34
|
+
* to under-signal, since the consequence of false-positive is a single
|
|
35
|
+
* harmless lifeline restart, and the consequence of false-negative is the
|
|
36
|
+
* exact incident class we're closing).
|
|
37
|
+
*/
|
|
38
|
+
export function crossesBreaking(prev, next) {
|
|
39
|
+
if (!prev || !next)
|
|
40
|
+
return true;
|
|
41
|
+
const semverRe = /^(\d+)\.(\d+)\./;
|
|
42
|
+
const mp = semverRe.exec(prev);
|
|
43
|
+
const mn = semverRe.exec(next);
|
|
44
|
+
if (!mp || !mn)
|
|
45
|
+
return true;
|
|
46
|
+
const [, pMaj, pMin] = mp;
|
|
47
|
+
const [, nMaj, nMin] = mn;
|
|
48
|
+
return pMaj !== nMaj || pMin !== nMin;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Write the lifeline-restart signal atomically. Idempotent: if a non-expired
|
|
52
|
+
* signal already exists for the SAME targetVersion, no-ops.
|
|
53
|
+
*
|
|
54
|
+
* Returns 'written' / 'skipped-fresh' / 'replaced-stale'.
|
|
55
|
+
*/
|
|
56
|
+
export function writeLifelineRestartSignal(opts) {
|
|
57
|
+
const now = opts.now ?? Date.now();
|
|
58
|
+
const ttlMs = opts.ttlMs ?? 60 * 60 * 1000;
|
|
59
|
+
const signalPath = path.join(opts.stateDir, SIGNAL_RELPATH);
|
|
60
|
+
// Idempotency: if an existing signal targets the same version and hasn't
|
|
61
|
+
// expired, don't overwrite. This prevents duplicate audit-log entries when
|
|
62
|
+
// multiple writers fire on the same boundary crossing.
|
|
63
|
+
let outcome = 'written';
|
|
64
|
+
try {
|
|
65
|
+
const existing = JSON.parse(fs.readFileSync(signalPath, 'utf-8'));
|
|
66
|
+
const existingExpires = Date.parse(existing.expiresAt);
|
|
67
|
+
if (existing.targetVersion === opts.targetVersion &&
|
|
68
|
+
Number.isFinite(existingExpires) &&
|
|
69
|
+
existingExpires > now) {
|
|
70
|
+
return 'skipped-fresh';
|
|
71
|
+
}
|
|
72
|
+
outcome = 'replaced-stale';
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
// No existing signal or unreadable — proceed to write a fresh one.
|
|
76
|
+
}
|
|
77
|
+
const signal = {
|
|
78
|
+
requestedAt: new Date(now).toISOString(),
|
|
79
|
+
requestedBy: opts.requestedBy,
|
|
80
|
+
reason: opts.reason,
|
|
81
|
+
previousVersion: opts.previousVersion,
|
|
82
|
+
targetVersion: opts.targetVersion,
|
|
83
|
+
expiresAt: new Date(now + ttlMs).toISOString(),
|
|
84
|
+
};
|
|
85
|
+
// Atomic write: tmp + rename to defend against partial reads under race.
|
|
86
|
+
const dir = path.dirname(signalPath);
|
|
87
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
88
|
+
const tmpPath = `${signalPath}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
89
|
+
try {
|
|
90
|
+
fs.writeFileSync(tmpPath, JSON.stringify(signal, null, 2));
|
|
91
|
+
fs.renameSync(tmpPath, signalPath);
|
|
92
|
+
}
|
|
93
|
+
catch (err) {
|
|
94
|
+
try {
|
|
95
|
+
SafeFsExecutor.safeUnlinkSync(tmpPath, { operation: 'src/core/version-skew.ts:writeLifelineRestartSignal-cleanup' });
|
|
96
|
+
}
|
|
97
|
+
catch { /* ignore */ }
|
|
98
|
+
throw err;
|
|
99
|
+
}
|
|
100
|
+
return outcome;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Read the signal. Returns null if missing, malformed, or expired.
|
|
104
|
+
* The reader is expected to delete the file as the first step of acting on it
|
|
105
|
+
* (so a respawn doesn't re-fire on the same signal).
|
|
106
|
+
*/
|
|
107
|
+
export function readLifelineRestartSignal(stateDir, now = Date.now()) {
|
|
108
|
+
const signalPath = path.join(stateDir, SIGNAL_RELPATH);
|
|
109
|
+
let raw;
|
|
110
|
+
try {
|
|
111
|
+
raw = fs.readFileSync(signalPath, 'utf-8');
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
let parsed;
|
|
117
|
+
try {
|
|
118
|
+
parsed = JSON.parse(raw);
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
// Corrupt → treat as absent. The next write replaces it cleanly.
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
const expires = Date.parse(parsed.expiresAt);
|
|
125
|
+
if (!Number.isFinite(expires) || expires <= now)
|
|
126
|
+
return null;
|
|
127
|
+
return parsed;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Delete the signal. Called by readers as the first step of acting on the
|
|
131
|
+
* signal so a restart loop can't re-fire on the same write.
|
|
132
|
+
*/
|
|
133
|
+
export function clearLifelineRestartSignal(stateDir) {
|
|
134
|
+
const signalPath = path.join(stateDir, SIGNAL_RELPATH);
|
|
135
|
+
try {
|
|
136
|
+
SafeFsExecutor.safeUnlinkSync(signalPath, { operation: 'src/core/version-skew.ts:clearLifelineRestartSignal' });
|
|
137
|
+
}
|
|
138
|
+
catch { /* ok — already gone */ }
|
|
139
|
+
}
|
|
140
|
+
/** Absolute path of the signal file for a given state dir. */
|
|
141
|
+
export function lifelineRestartSignalPath(stateDir) {
|
|
142
|
+
return path.join(stateDir, SIGNAL_RELPATH);
|
|
143
|
+
}
|
|
144
|
+
//# sourceMappingURL=version-skew.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version-skew.js","sourceRoot":"","sources":["../../src/core/version-skew.ts"],"names":[],"mappings":"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"}
|
|
@@ -22,7 +22,7 @@ import { DegradationReporter } from '../monitoring/DegradationReporter.js';
|
|
|
22
22
|
export type RestartState = 'idle' | 'quiescing' | 'persisting' | 'exiting';
|
|
23
23
|
export interface RestartRequest {
|
|
24
24
|
reason: string;
|
|
25
|
-
bucket: 'watchdog' | 'versionSkew';
|
|
25
|
+
bucket: 'watchdog' | 'versionSkew' | 'plannedUpgrade';
|
|
26
26
|
context?: Record<string, unknown>;
|
|
27
27
|
}
|
|
28
28
|
export interface OrchestratorDeps {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RestartOrchestrator.d.ts","sourceRoot":"","sources":["../../src/lifeline/RestartOrchestrator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,mBAAmB,EAAE,MAAM,sCAAsC,CAAC;AAE3E,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,WAAW,GAAG,YAAY,GAAG,SAAS,CAAC;AAE3E,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,UAAU,GAAG,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"RestartOrchestrator.d.ts","sourceRoot":"","sources":["../../src/lifeline/RestartOrchestrator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,mBAAmB,EAAE,MAAM,sCAAsC,CAAC;AAE3E,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,WAAW,GAAG,YAAY,GAAG,SAAS,CAAC;AAE3E,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,UAAU,GAAG,aAAa,GAAG,gBAAgB,CAAC;IACtD,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;AAED,MAAM,WAAW,gBAAgB;IAC/B,qGAAqG;IACrG,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7B,4EAA4E;IAC5E,UAAU,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAChC,wEAAwE;IACxE,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,qEAAqE;IACrE,YAAY,EAAE,OAAO,CAAC;IACtB;;;;OAIG;IACH,uBAAuB,CAAC,EAAE,MAAM,OAAO,CAAC;IACxC,uDAAuD;IACvD,QAAQ,CAAC,EAAE,mBAAmB,CAAC;IAC/B,6DAA6D;IAC7D,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,sEAAsE;IACtE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,+FAA+F;IAC/F,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,qBAAa,mBAAmB;IAOlB,OAAO,CAAC,QAAQ,CAAC,IAAI;IANjC,OAAO,CAAC,MAAM,CAAwB;IACtC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAsB;IAC/C,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;IACzC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IAC7B,cAAc,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,YAAY,CAAA;KAAE,GAAG,IAAI,CAAQ;gBAEvD,IAAI,EAAE,gBAAgB;IAMnD,IAAI,KAAK,IAAI,YAAY,CAExB;IAED;;;;OAIG;IACG,cAAc,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,YAAY,GAAG,WAAW,CAAC;IAuF9E,4EAA4E;IAC5E,gBAAgB,IAAI,IAAI;CAIzB"}
|
|
@@ -38,6 +38,7 @@ export declare class TelegramLifeline {
|
|
|
38
38
|
private offsetPath;
|
|
39
39
|
private stopHeartbeat;
|
|
40
40
|
private replayInterval;
|
|
41
|
+
private restartSignalInterval;
|
|
41
42
|
private lifelineTopicId;
|
|
42
43
|
private lockPath;
|
|
43
44
|
private consecutive409s;
|
|
@@ -136,6 +137,19 @@ export declare class TelegramLifeline {
|
|
|
136
137
|
* match (loopback impostor), treat as transient.
|
|
137
138
|
*/
|
|
138
139
|
private handleVersionSkew;
|
|
140
|
+
/**
|
|
141
|
+
* Per-tick check of the coordinated-restart signal file.
|
|
142
|
+
*
|
|
143
|
+
* Three independent writers (AutoUpdater on major.minor crossing; the
|
|
144
|
+
* server on a 426 forward; PostUpdateMigrator's one-time stale-lifeline
|
|
145
|
+
* bootstrap) drop a signal at `state/lifeline-restart-requested.json`.
|
|
146
|
+
* We read it, validate, delete it (so a respawn doesn't re-fire), and
|
|
147
|
+
* call initiateRestart on the `plannedUpgrade` bucket which bypasses
|
|
148
|
+
* the watchdog cooldown.
|
|
149
|
+
*
|
|
150
|
+
* Spec: docs/specs/auto-updater-lifeline-coordination.md
|
|
151
|
+
*/
|
|
152
|
+
private checkLifelineRestartSignal;
|
|
139
153
|
/** Watchdog-tracked counters/state. */
|
|
140
154
|
private consecutiveForwardFailures;
|
|
141
155
|
private lastForwardSuccessAt;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TelegramLifeline.d.ts","sourceRoot":"","sources":["../../src/lifeline/TelegramLifeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;
|
|
1
|
+
{"version":3,"file":"TelegramLifeline.d.ts","sourceRoot":"","sources":["../../src/lifeline/TelegramLifeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAqNH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,MAAM,CAAiB;IAC/B,OAAO,CAAC,aAAa,CAAgC;IACrD,OAAO,CAAC,KAAK,CAAe;IAC5B,OAAO,CAAC,UAAU,CAAmB;IACrC,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,WAAW,CAA8C;IACjE,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,aAAa,CAA6B;IAClD,OAAO,CAAC,cAAc,CAA+C;IACrE,OAAO,CAAC,qBAAqB,CAA+C;IAC5E,OAAO,CAAC,eAAe,CAAuB;IAC9C,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,eAAe,CAAK;IAC5B,OAAO,CAAC,eAAe,CAAK;IAC5B,OAAO,CAAC,aAAa,CAAQ;IAG7B,OAAO,CAAC,mBAAmB,CAAuB;IAClD,OAAO,CAAC,kBAAkB,CAAuB;IACjD,OAAO,CAAC,oBAAoB,CAA8C;gBAE9D,UAAU,CAAC,EAAE,MAAM;IA+E/B;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IA+K5B;;;OAGG;YACW,iBAAiB;IAY/B;;;;;;OAMG;IACH,OAAO,CAAC,8BAA8B;IAkEtC,mEAAmE;IACnE,OAAO,CAAC,yBAAyB;IAOjC,qDAAqD;IACrD,OAAO,CAAC,sBAAsB;IA6B9B,0EAA0E;YAC5D,oBAAoB;IAOlC;;;;OAIG;IACH,OAAO,CAAC,eAAe;IAwCvB;;;;;;;OAOG;YACW,oBAAoB;YAkDpB,IAAI;YAwEJ,aAAa;IAwF3B;;OAEG;YACW,kBAAkB;IAoDhC;;OAEG;YACW,aAAa;IAwB3B;;;OAGG;YACW,gBAAgB;IA2B9B;;OAEG;YACW,qBAAqB;IAmDnC;;;OAGG;YACW,oBAAoB;IAmDlC;;;;;;;;OAQG;IACH,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,gBAAgB,CAAK;IAC7C,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,uBAAuB,CAAQ;IAEvD;;;;OAIG;IACH,OAAO,CAAC,kBAAkB,CAAS;IAEnC,+DAA+D;IAC/D,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAsB;YAExC,eAAe;IAyG7B;;;;;OAKG;IACH,OAAO,CAAC,iBAAiB;IAwCzB;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,0BAA0B;IA8BlC,uCAAuC;IACvC,OAAO,CAAC,0BAA0B,CAAK;IACvC,OAAO,CAAC,oBAAoB,CAAK;IACjC,OAAO,CAAC,oBAAoB,CAAuB;IAEnD;;;;;;;;;OASG;IACH,OAAO,CAAC,iBAAiB,CAAS;IAClC;;+CAE2C;IAC3C,OAAO,CAAC,sBAAsB,CAAK;IAEnC,OAAO,CAAC,YAAY,CAAoC;IACxD,OAAO,CAAC,QAAQ,CAAuC;YAIzC,qBAAqB;IAoGnC,gEAAgE;IAChE,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,mBAAmB,CAAK;YAElC,WAAW;IAsHzB,gFAAgF;IAChF,OAAO,CAAC,qBAAqB,CAAS;IACtC,4DAA4D;IAC5D,OAAO,CAAC,yBAAyB,CAAK;IACtC,0FAA0F;IAC1F,OAAO,CAAC,sBAAsB,CAAK;IACnC,mGAAmG;IACnG,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,uBAAuB,CAAe;IAE9D,4EAA4E;IAC5E,OAAO,CAAC,cAAc,CAA6B;IACnD,0FAA0F;IAC1F,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,uBAAuB,CAAc;IAC7D,6EAA6E;IAC7E,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,4BAA4B,CAAO;IAE3D;;;;OAIG;IACH,OAAO,CAAC,kBAAkB;IAc1B,OAAO,CAAC,kBAAkB;IAkB1B;;;OAGG;IACH,OAAO,CAAC,kBAAkB;YAgBZ,gBAAgB;YAmChB,mBAAmB;IA8BjC;;OAEG;YACW,mBAAmB;IA+CjC;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAiC1B;;OAEG;YACW,sBAAsB;IAsFpC;;;;;;;;;;;;OAYG;YACW,kBAAkB;IAoGhC;;OAEG;IACH,OAAO,CAAC,cAAc;IA4BtB;;OAEG;IACH,OAAO,CAAC,yBAAyB;IAWjC;;OAEG;YACW,oBAAoB;IASlC;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAcxB;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAkBzB;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAa5B;;;;;;;;;OASG;IACH;;;;;;OAMG;IACH,OAAO,CAAC,oBAAoB;YA0Cd,aAAa;IAmG3B;;OAEG;YACW,mBAAmB;IAuDjC;;OAEG;IACH,OAAO,CAAC,sBAAsB;YAyBhB,WAAW;YAqBX,UAAU;IASxB;;;;;;OAMG;IACH,OAAO,CAAC,iBAAiB;YAMX,OAAO;IAmErB,OAAO,CAAC,UAAU;IAiBlB,OAAO,CAAC,UAAU;CAWnB"}
|
|
@@ -36,6 +36,7 @@ import pc from 'picocolors';
|
|
|
36
36
|
import { loadConfig, ensureStateDir, detectTmuxPath, getInstarVersion } from '../core/Config.js';
|
|
37
37
|
import { registerAgent, unregisterAgent, startHeartbeat } from '../core/AgentRegistry.js';
|
|
38
38
|
import { SafeFsExecutor } from '../core/SafeFsExecutor.js';
|
|
39
|
+
import { readLifelineRestartSignal, clearLifelineRestartSignal, } from '../core/version-skew.js';
|
|
39
40
|
// setup.ts uses @inquirer/prompts which requires Node 20.12+
|
|
40
41
|
// Dynamic import to avoid breaking the lifeline on older Node versions
|
|
41
42
|
// import { installAutoStart } from '../commands/setup.js';
|
|
@@ -199,6 +200,7 @@ export class TelegramLifeline {
|
|
|
199
200
|
offsetPath;
|
|
200
201
|
stopHeartbeat = null;
|
|
201
202
|
replayInterval = null;
|
|
203
|
+
restartSignalInterval = null;
|
|
202
204
|
lifelineTopicId = null;
|
|
203
205
|
lockPath;
|
|
204
206
|
consecutive409s = 0;
|
|
@@ -360,6 +362,19 @@ export class TelegramLifeline {
|
|
|
360
362
|
this.replayQueue();
|
|
361
363
|
}
|
|
362
364
|
}, 15_000);
|
|
365
|
+
// Coordinated restart channel: every 30s, check for a lifeline-restart
|
|
366
|
+
// signal written by the AutoUpdater (major.minor crossing), the server
|
|
367
|
+
// (426 evidence), or the PostUpdateMigrator (one-time stale-lifeline
|
|
368
|
+
// bootstrap). On match, restart via the planned-upgrade bucket which
|
|
369
|
+
// bypasses the watchdog cooldown.
|
|
370
|
+
// Spec: docs/specs/auto-updater-lifeline-coordination.md
|
|
371
|
+
this.restartSignalInterval = setInterval(() => {
|
|
372
|
+
this.checkLifelineRestartSignal();
|
|
373
|
+
}, 30_000);
|
|
374
|
+
// Run once immediately so a fresh lifeline picks up a signal written
|
|
375
|
+
// before it started (e.g. by PostUpdateMigrator during the previous
|
|
376
|
+
// launchctl cycle).
|
|
377
|
+
this.checkLifelineRestartSignal();
|
|
363
378
|
// Stage B: install the restart orchestrator and health watchdog.
|
|
364
379
|
// In unsupervised mode (no INSTAR_SUPERVISED=1 and no launchd parent),
|
|
365
380
|
// the orchestrator emits signals and logs but skips process.exit.
|
|
@@ -429,6 +444,10 @@ export class TelegramLifeline {
|
|
|
429
444
|
clearInterval(this.replayInterval);
|
|
430
445
|
this.replayInterval = null;
|
|
431
446
|
}
|
|
447
|
+
if (this.restartSignalInterval) {
|
|
448
|
+
clearInterval(this.restartSignalInterval);
|
|
449
|
+
this.restartSignalInterval = null;
|
|
450
|
+
}
|
|
432
451
|
if (this.watchdog)
|
|
433
452
|
this.watchdog.stop();
|
|
434
453
|
try {
|
|
@@ -1161,6 +1180,47 @@ export class TelegramLifeline {
|
|
|
1161
1180
|
lifelineVersion: this.lifelineVersion,
|
|
1162
1181
|
});
|
|
1163
1182
|
}
|
|
1183
|
+
/**
|
|
1184
|
+
* Per-tick check of the coordinated-restart signal file.
|
|
1185
|
+
*
|
|
1186
|
+
* Three independent writers (AutoUpdater on major.minor crossing; the
|
|
1187
|
+
* server on a 426 forward; PostUpdateMigrator's one-time stale-lifeline
|
|
1188
|
+
* bootstrap) drop a signal at `state/lifeline-restart-requested.json`.
|
|
1189
|
+
* We read it, validate, delete it (so a respawn doesn't re-fire), and
|
|
1190
|
+
* call initiateRestart on the `plannedUpgrade` bucket which bypasses
|
|
1191
|
+
* the watchdog cooldown.
|
|
1192
|
+
*
|
|
1193
|
+
* Spec: docs/specs/auto-updater-lifeline-coordination.md
|
|
1194
|
+
*/
|
|
1195
|
+
checkLifelineRestartSignal() {
|
|
1196
|
+
let signal;
|
|
1197
|
+
try {
|
|
1198
|
+
signal = readLifelineRestartSignal(this.projectConfig.stateDir);
|
|
1199
|
+
}
|
|
1200
|
+
catch (err) {
|
|
1201
|
+
console.warn(`[Lifeline] failed to read restart signal: ${err}`);
|
|
1202
|
+
return;
|
|
1203
|
+
}
|
|
1204
|
+
if (!signal)
|
|
1205
|
+
return;
|
|
1206
|
+
// Same-version no-op: a respawned lifeline that already matches the
|
|
1207
|
+
// target version should clear the stale signal and not loop.
|
|
1208
|
+
if (signal.targetVersion === this.lifelineVersion) {
|
|
1209
|
+
clearLifelineRestartSignal(this.projectConfig.stateDir);
|
|
1210
|
+
return;
|
|
1211
|
+
}
|
|
1212
|
+
// First step: delete the file. If initiateRestart goes through, the
|
|
1213
|
+
// process exits and a fresh lifeline starts on the new version — it
|
|
1214
|
+
// must not re-read the same signal.
|
|
1215
|
+
clearLifelineRestartSignal(this.projectConfig.stateDir);
|
|
1216
|
+
console.log(`[Lifeline] coordinated restart signal received from ${signal.requestedBy}: ` +
|
|
1217
|
+
`v${signal.previousVersion} → v${signal.targetVersion} (${signal.reason})`);
|
|
1218
|
+
this.initiateRestart('plannedUpgrade', signal.reason, {
|
|
1219
|
+
requestedBy: signal.requestedBy,
|
|
1220
|
+
previousVersion: signal.previousVersion,
|
|
1221
|
+
targetVersion: signal.targetVersion,
|
|
1222
|
+
});
|
|
1223
|
+
}
|
|
1164
1224
|
/** Watchdog-tracked counters/state. */
|
|
1165
1225
|
consecutiveForwardFailures = 0;
|
|
1166
1226
|
lastForwardSuccessAt = 0;
|