instar 1.3.1348 → 1.3.1349
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 +15 -2
- package/dist/commands/server.js.map +1 -1
- package/dist/core/LifelineIngressSecret.d.ts +83 -0
- package/dist/core/LifelineIngressSecret.d.ts.map +1 -0
- package/dist/core/LifelineIngressSecret.js +203 -0
- package/dist/core/LifelineIngressSecret.js.map +1 -0
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +15 -0
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/core/WriteDomainRegistry.d.ts.map +1 -1
- package/dist/core/WriteDomainRegistry.js +17 -0
- package/dist/core/WriteDomainRegistry.js.map +1 -1
- package/dist/core/devGatedFeatures.d.ts.map +1 -1
- package/dist/core/devGatedFeatures.js +6 -0
- package/dist/core/devGatedFeatures.js.map +1 -1
- package/dist/core/keyMaterialPaths.d.ts.map +1 -1
- package/dist/core/keyMaterialPaths.js +16 -0
- package/dist/core/keyMaterialPaths.js.map +1 -1
- package/dist/core/types.d.ts +23 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/core/version-skew.d.ts +4 -2
- package/dist/core/version-skew.d.ts.map +1 -1
- package/dist/core/version-skew.js +3 -1
- package/dist/core/version-skew.js.map +1 -1
- package/dist/data/standards-guard-index.json +1 -1
- package/dist/data/standards-guard-index.meta.json +2 -2
- package/dist/data/standards-registry.meta.json +1 -1
- package/dist/lifeline/TelegramLifeline.d.ts +49 -0
- package/dist/lifeline/TelegramLifeline.d.ts.map +1 -1
- package/dist/lifeline/TelegramLifeline.js +202 -17
- package/dist/lifeline/TelegramLifeline.js.map +1 -1
- package/dist/lifeline/forwardErrors.d.ts +11 -0
- package/dist/lifeline/forwardErrors.d.ts.map +1 -1
- package/dist/lifeline/forwardErrors.js +15 -1
- package/dist/lifeline/forwardErrors.js.map +1 -1
- package/dist/monitoring/guardManifest.d.ts.map +1 -1
- package/dist/monitoring/guardManifest.js +24 -0
- package/dist/monitoring/guardManifest.js.map +1 -1
- package/dist/scaffold/templates.d.ts.map +1 -1
- package/dist/scaffold/templates.js +4 -0
- package/dist/scaffold/templates.js.map +1 -1
- package/dist/server/LifelineIngressGuard.d.ts +326 -0
- package/dist/server/LifelineIngressGuard.d.ts.map +1 -0
- package/dist/server/LifelineIngressGuard.js +713 -0
- package/dist/server/LifelineIngressGuard.js.map +1 -0
- package/dist/server/TelegramCallbackDedupe.d.ts +58 -0
- package/dist/server/TelegramCallbackDedupe.d.ts.map +1 -0
- package/dist/server/TelegramCallbackDedupe.js +151 -0
- package/dist/server/TelegramCallbackDedupe.js.map +1 -0
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +218 -10
- package/dist/server/routes.js.map +1 -1
- package/dist/testing/selfActionRegistry.d.ts.map +1 -1
- package/dist/testing/selfActionRegistry.js +98 -0
- package/dist/testing/selfActionRegistry.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +64 -64
- package/src/data/standards-guard-index.json +1 -1
- package/src/data/standards-guard-index.meta.json +2 -2
- package/src/data/standards-registry.meta.json +1 -1
- package/src/data/state-coherence-registry.json +19 -0
- package/src/scaffold/templates.ts +4 -0
- package/upgrades/1.3.1349.md +59 -0
- package/upgrades/side-effects/telegram-internal-route-lifeline-secret.md +187 -0
- package/upgrades/1.3.1348.md +0 -24
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"version-skew.js","sourceRoot":"","sources":["../../src/core/version-skew.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"version-skew.js","sourceRoot":"","sources":["../../src/core/version-skew.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"generatedFrom": "source-tree",
|
|
4
4
|
"registrySha256": "35448db940d659661ebdfb09761439033f93a15157540ee2f6eecf825488e1e6",
|
|
5
|
-
"packageVersion": "1.3.
|
|
5
|
+
"packageVersion": "1.3.1349",
|
|
6
6
|
"guards": [
|
|
7
7
|
{
|
|
8
8
|
"ref": "docs/audits/phase-b/f10-triage.md",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"sha256": "
|
|
2
|
+
"sha256": "8b8dc4dd06983cb7d7b51d73c18a48abda82621ac694c0daab2cbc6bbddc8ff0",
|
|
3
3
|
"registrySha256": "35448db940d659661ebdfb09761439033f93a15157540ee2f6eecf825488e1e6",
|
|
4
|
-
"packageVersion": "1.3.
|
|
4
|
+
"packageVersion": "1.3.1349"
|
|
5
5
|
}
|
|
@@ -154,6 +154,54 @@ export declare class TelegramLifeline {
|
|
|
154
154
|
private legacyStrictServer;
|
|
155
155
|
/** Full semver of this lifeline, read once at construction. */
|
|
156
156
|
private readonly lifelineVersion;
|
|
157
|
+
/**
|
|
158
|
+
* The lifeline ingress secret (docs/specs/telegram-internal-route-lifeline-secret.md).
|
|
159
|
+
* Held in this process's memory and in its 0600 file only — never in
|
|
160
|
+
* `process.env`, so nothing this process spawns inherits it.
|
|
161
|
+
*/
|
|
162
|
+
private _ingressSecret;
|
|
163
|
+
private get ingressSecret();
|
|
164
|
+
private lastSecretRefusalLogAt;
|
|
165
|
+
private lastIngressProofAt;
|
|
166
|
+
/** Start of the current secret-refusal episode (0 = none). */
|
|
167
|
+
private secretRefusedSince;
|
|
168
|
+
private secretRefusedLastAt;
|
|
169
|
+
private secretRefusalNoticeSent;
|
|
170
|
+
private secretRefusalNoticeInFlight;
|
|
171
|
+
private replayInFlight;
|
|
172
|
+
/** Consecutive refused replay passes in the current episode (drives the backoff). */
|
|
173
|
+
private secretRefusedPasses;
|
|
174
|
+
/**
|
|
175
|
+
* While the server refuses our secret, replay backs off: one minute, then
|
|
176
|
+
* doubling, capped at fifteen. The message is never dropped; only the rate
|
|
177
|
+
* of asking falls.
|
|
178
|
+
*/
|
|
179
|
+
private static readonly SECRET_REFUSAL_RETRY_MS;
|
|
180
|
+
private static readonly SECRET_REFUSAL_RETRY_MAX_MS;
|
|
181
|
+
private secretRefusalBackoffMs;
|
|
182
|
+
/** After this long refused, tell the user once (per episode). */
|
|
183
|
+
private static readonly SECRET_REFUSAL_NOTICE_AFTER_MS;
|
|
184
|
+
/**
|
|
185
|
+
* The server refused our lifeline secret even after a re-read. Not a
|
|
186
|
+
* property of the message and not an outage: the message stays queued with
|
|
187
|
+
* no drop budget burned. This bounds the consequences — one log line a
|
|
188
|
+
* minute, replay backs off from one minute to fifteen (see replayQueue), and
|
|
189
|
+
* at the first refusal after ten minutes the user is told ONCE, on the
|
|
190
|
+
* lifeline's own always-available channel, that messages are held and why.
|
|
191
|
+
*/
|
|
192
|
+
private noteSecretRefusal;
|
|
193
|
+
private addIngressSecretHeader;
|
|
194
|
+
/** The server accepted a secret that is not its own (observe): re-read ours. */
|
|
195
|
+
private observeStaleSecretHint;
|
|
196
|
+
/**
|
|
197
|
+
* Tell the server that THIS lifeline sends the ingress secret — at lifeline
|
|
198
|
+
* start, whenever the server comes up, and every 15 minutes — so the
|
|
199
|
+
* server's guard is armed within seconds instead of waiting for the next
|
|
200
|
+
* Telegram message (a standby lifeline never forwards one at all).
|
|
201
|
+
* Best-effort: an older server answers 404 and nothing changes.
|
|
202
|
+
*/
|
|
203
|
+
private proveIngressSecret;
|
|
204
|
+
private isLifelineSecretRefusal;
|
|
157
205
|
/**
|
|
158
206
|
* Agent hard-sleep wake trigger. When the ServerSupervisor has intentionally
|
|
159
207
|
* slept the server (a `state/slept-marker.json` is present), write
|
|
@@ -235,6 +283,7 @@ export declare class TelegramLifeline {
|
|
|
235
283
|
private watchdog;
|
|
236
284
|
private handleLifelineCommand;
|
|
237
285
|
private replayQueue;
|
|
286
|
+
private replayQueueLocked;
|
|
238
287
|
/** Whether we've already notified for the current outage. Reset on recovery. */
|
|
239
288
|
private hasNotifiedServerDown;
|
|
240
289
|
/** Suppressed "server down" count during current outage. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TelegramLifeline.d.ts","sourceRoot":"","sources":["../../src/lifeline/TelegramLifeline.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"TelegramLifeline.d.ts","sourceRoot":"","sources":["../../src/lifeline/TelegramLifeline.ts"],"names":[],"mappings":"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"}
|
|
@@ -39,6 +39,7 @@ import { loadConfig, ensureStateDir, detectTmuxPath, getInstarVersion } from '..
|
|
|
39
39
|
import { registerAgent, unregisterAgent, startHeartbeat } from '../core/AgentRegistry.js';
|
|
40
40
|
import { SafeFsExecutor } from '../core/SafeFsExecutor.js';
|
|
41
41
|
import { readLifelineRestartSignal, clearLifelineRestartSignal, } from '../core/version-skew.js';
|
|
42
|
+
import { LifelineIngressSecretHolder, LIFELINE_SECRET_HEADER } from '../core/LifelineIngressSecret.js';
|
|
42
43
|
// setup.ts uses @inquirer/prompts which requires Node 20.12+
|
|
43
44
|
// Dynamic import to avoid breaking the lifeline on older Node versions
|
|
44
45
|
// import { installAutoStart } from '../commands/setup.js';
|
|
@@ -47,7 +48,7 @@ import { ServerSupervisor } from './ServerSupervisor.js';
|
|
|
47
48
|
import { createProductionSessionRecoveryConsumer, stageCActivationLeaseIsLive } from '../core/CodexLifecycleProductionComposition.js';
|
|
48
49
|
import { retryWithBackoff } from './retryWithBackoff.js';
|
|
49
50
|
import { notifyMessageDropped } from './droppedMessages.js';
|
|
50
|
-
import { ForwardTransientError, ForwardBadRequestError, ForwardServerBootError, ForwardVersionSkewError, isTerminalForwardError, } from './forwardErrors.js';
|
|
51
|
+
import { ForwardTransientError, ForwardBadRequestError, ForwardServerBootError, ForwardVersionSkewError, isTerminalForwardError, ForwardSecretRefusedError, } from './forwardErrors.js';
|
|
51
52
|
import { decideReplay } from './replayPolicy.js';
|
|
52
53
|
import { writeStartupMarker } from './startupMarker.js';
|
|
53
54
|
import { acquireLockFile } from './lifelineLock.js';
|
|
@@ -147,6 +148,9 @@ export class TelegramLifeline {
|
|
|
147
148
|
this.loadRateLimitState();
|
|
148
149
|
// Wire supervisor events
|
|
149
150
|
this.supervisor.on('serverUp', () => {
|
|
151
|
+
// A server (re)start re-reads the secret: probe at once, not after the backoff.
|
|
152
|
+
this.secretRefusedLastAt = 0;
|
|
153
|
+
void this.proveIngressSecret();
|
|
150
154
|
console.log('[Lifeline] Server is up — replaying queued messages');
|
|
151
155
|
if (this.hasNotifiedServerDown) {
|
|
152
156
|
this.hasNotifiedServerDown = false;
|
|
@@ -324,6 +328,9 @@ export class TelegramLifeline {
|
|
|
324
328
|
if (this.supervisor.healthy && this.queue.length > 0) {
|
|
325
329
|
this.replayQueue();
|
|
326
330
|
}
|
|
331
|
+
if (this.supervisor.healthy && Date.now() - this.lastIngressProofAt > 15 * 60_000) {
|
|
332
|
+
void this.proveIngressSecret();
|
|
333
|
+
}
|
|
327
334
|
// B1 — reconcile poll-ownership to the lease intent (no-op unless
|
|
328
335
|
// pollFollowsLease is on; DRY-RUN by default — see reconcilePolling).
|
|
329
336
|
this.reconcilePolling();
|
|
@@ -1229,23 +1236,32 @@ export class TelegramLifeline {
|
|
|
1229
1236
|
const controller = new AbortController();
|
|
1230
1237
|
const timer = setTimeout(() => controller.abort(), 10_000);
|
|
1231
1238
|
try {
|
|
1232
|
-
const
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
body: JSON.stringify({
|
|
1240
|
-
callbackQueryId: query.id,
|
|
1241
|
-
data: query.data,
|
|
1242
|
-
fromUserId: query.from.id,
|
|
1243
|
-
fromUsername: query.from.username,
|
|
1244
|
-
messageId: query.message?.message_id,
|
|
1245
|
-
chatId: query.message?.chat?.id,
|
|
1246
|
-
}),
|
|
1247
|
-
signal: controller.signal,
|
|
1239
|
+
const cbBody = JSON.stringify({
|
|
1240
|
+
callbackQueryId: query.id,
|
|
1241
|
+
data: query.data,
|
|
1242
|
+
fromUserId: query.from.id,
|
|
1243
|
+
fromUsername: query.from.username,
|
|
1244
|
+
messageId: query.message?.message_id,
|
|
1245
|
+
chatId: query.message?.chat?.id,
|
|
1248
1246
|
});
|
|
1247
|
+
const sendCallback = () => {
|
|
1248
|
+
const cbHeaders = { 'Content-Type': 'application/json' };
|
|
1249
|
+
if (this.projectConfig.authToken) {
|
|
1250
|
+
cbHeaders['Authorization'] = `Bearer ${this.projectConfig.authToken}`;
|
|
1251
|
+
}
|
|
1252
|
+
this.addIngressSecretHeader(cbHeaders);
|
|
1253
|
+
return fetch(`http://127.0.0.1:${this.projectConfig.port}/internal/telegram-callback`, { method: 'POST', headers: cbHeaders, body: cbBody, signal: controller.signal });
|
|
1254
|
+
};
|
|
1255
|
+
let response = await sendCallback();
|
|
1256
|
+
// The server refused our secret: the file may have been replaced since
|
|
1257
|
+
// we cached it. Re-read once and retry. The server dedupes on the
|
|
1258
|
+
// callback id, so a retried tap can never be applied twice.
|
|
1259
|
+
if (response.status === 401 && await this.isLifelineSecretRefusal(response)) {
|
|
1260
|
+
this.ingressSecret.reload();
|
|
1261
|
+
response = await sendCallback();
|
|
1262
|
+
}
|
|
1263
|
+
if (response.ok)
|
|
1264
|
+
this.observeStaleSecretHint(response);
|
|
1249
1265
|
if (!response.ok) {
|
|
1250
1266
|
await this.apiCall('answerCallbackQuery', {
|
|
1251
1267
|
callback_query_id: query.id,
|
|
@@ -1283,6 +1299,122 @@ export class TelegramLifeline {
|
|
|
1283
1299
|
legacyStrictServer = false;
|
|
1284
1300
|
/** Full semver of this lifeline, read once at construction. */
|
|
1285
1301
|
lifelineVersion = getInstarVersion();
|
|
1302
|
+
/**
|
|
1303
|
+
* The lifeline ingress secret (docs/specs/telegram-internal-route-lifeline-secret.md).
|
|
1304
|
+
* Held in this process's memory and in its 0600 file only — never in
|
|
1305
|
+
* `process.env`, so nothing this process spawns inherits it.
|
|
1306
|
+
*/
|
|
1307
|
+
_ingressSecret = null;
|
|
1308
|
+
get ingressSecret() {
|
|
1309
|
+
if (!this._ingressSecret)
|
|
1310
|
+
this._ingressSecret = new LifelineIngressSecretHolder(this.projectConfig.stateDir);
|
|
1311
|
+
return this._ingressSecret;
|
|
1312
|
+
}
|
|
1313
|
+
lastSecretRefusalLogAt = 0;
|
|
1314
|
+
lastIngressProofAt = 0;
|
|
1315
|
+
/** Start of the current secret-refusal episode (0 = none). */
|
|
1316
|
+
secretRefusedSince = 0;
|
|
1317
|
+
secretRefusedLastAt = 0;
|
|
1318
|
+
secretRefusalNoticeSent = false;
|
|
1319
|
+
secretRefusalNoticeInFlight = false;
|
|
1320
|
+
replayInFlight = false;
|
|
1321
|
+
/** Consecutive refused replay passes in the current episode (drives the backoff). */
|
|
1322
|
+
secretRefusedPasses = 0;
|
|
1323
|
+
/**
|
|
1324
|
+
* While the server refuses our secret, replay backs off: one minute, then
|
|
1325
|
+
* doubling, capped at fifteen. The message is never dropped; only the rate
|
|
1326
|
+
* of asking falls.
|
|
1327
|
+
*/
|
|
1328
|
+
static SECRET_REFUSAL_RETRY_MS = 60_000;
|
|
1329
|
+
static SECRET_REFUSAL_RETRY_MAX_MS = 15 * 60_000;
|
|
1330
|
+
secretRefusalBackoffMs() {
|
|
1331
|
+
const n = Math.max(0, this.secretRefusedPasses - 1);
|
|
1332
|
+
return Math.min(TelegramLifeline.SECRET_REFUSAL_RETRY_MAX_MS, TelegramLifeline.SECRET_REFUSAL_RETRY_MS * 2 ** Math.min(n, 10));
|
|
1333
|
+
}
|
|
1334
|
+
/** After this long refused, tell the user once (per episode). */
|
|
1335
|
+
static SECRET_REFUSAL_NOTICE_AFTER_MS = 10 * 60_000;
|
|
1336
|
+
/**
|
|
1337
|
+
* The server refused our lifeline secret even after a re-read. Not a
|
|
1338
|
+
* property of the message and not an outage: the message stays queued with
|
|
1339
|
+
* no drop budget burned. This bounds the consequences — one log line a
|
|
1340
|
+
* minute, replay backs off from one minute to fifteen (see replayQueue), and
|
|
1341
|
+
* at the first refusal after ten minutes the user is told ONCE, on the
|
|
1342
|
+
* lifeline's own always-available channel, that messages are held and why.
|
|
1343
|
+
*/
|
|
1344
|
+
noteSecretRefusal(inboundTopicId) {
|
|
1345
|
+
const nowMs = Date.now();
|
|
1346
|
+
if (this.secretRefusedSince === 0)
|
|
1347
|
+
this.secretRefusedSince = nowMs;
|
|
1348
|
+
this.secretRefusedLastAt = nowMs;
|
|
1349
|
+
if (nowMs - this.lastSecretRefusalLogAt > 60_000) {
|
|
1350
|
+
this.lastSecretRefusalLogAt = nowMs;
|
|
1351
|
+
console.error('[Lifeline] server refused the lifeline ingress secret after a re-read — ' +
|
|
1352
|
+
`messages stay queued. Secret readable here: ${this.ingressSecret.get() !== null}`);
|
|
1353
|
+
}
|
|
1354
|
+
if (!this.secretRefusalNoticeSent && !this.secretRefusalNoticeInFlight
|
|
1355
|
+
&& nowMs - this.secretRefusedSince >= TelegramLifeline.SECRET_REFUSAL_NOTICE_AFTER_MS) {
|
|
1356
|
+
const topic = this.resolveUpdatesAlertTopic();
|
|
1357
|
+
if (topic !== null) {
|
|
1358
|
+
// "Sent" is recorded only once the send succeeded; a failure leaves it
|
|
1359
|
+
// unset, so the next refusal (the backoff cadence) tries again.
|
|
1360
|
+
this.secretRefusalNoticeInFlight = true;
|
|
1361
|
+
this.sendToTopic(topic, 'Heads up: your Telegram messages are reaching my lifeline but my server is not accepting them, ' +
|
|
1362
|
+
'because the two no longer agree on their shared key. Your messages are NOT lost — they are saved ' +
|
|
1363
|
+
'and will be delivered as soon as this is fixed. Sending /lifeline restart usually resolves it.').then(() => {
|
|
1364
|
+
this.secretRefusalNoticeSent = true;
|
|
1365
|
+
}).finally(() => {
|
|
1366
|
+
this.secretRefusalNoticeInFlight = false;
|
|
1367
|
+
}).catch((sendErr) => {
|
|
1368
|
+
console.warn(`[Lifeline] failed to send secret-refusal notice to topic ${topic} (inbound was ${inboundTopicId}): ` +
|
|
1369
|
+
`${sendErr instanceof Error ? sendErr.message : sendErr}`);
|
|
1370
|
+
});
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
addIngressSecretHeader(headers) {
|
|
1375
|
+
const secret = this.ingressSecret.get();
|
|
1376
|
+
if (secret)
|
|
1377
|
+
headers[LIFELINE_SECRET_HEADER] = secret;
|
|
1378
|
+
}
|
|
1379
|
+
/** The server accepted a secret that is not its own (observe): re-read ours. */
|
|
1380
|
+
observeStaleSecretHint(response) {
|
|
1381
|
+
if (response.headers.get('x-instar-lifeline-secret-stale') === '1') {
|
|
1382
|
+
this.ingressSecret.reload();
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
/**
|
|
1386
|
+
* Tell the server that THIS lifeline sends the ingress secret — at lifeline
|
|
1387
|
+
* start, whenever the server comes up, and every 15 minutes — so the
|
|
1388
|
+
* server's guard is armed within seconds instead of waiting for the next
|
|
1389
|
+
* Telegram message (a standby lifeline never forwards one at all).
|
|
1390
|
+
* Best-effort: an older server answers 404 and nothing changes.
|
|
1391
|
+
*/
|
|
1392
|
+
async proveIngressSecret() {
|
|
1393
|
+
try {
|
|
1394
|
+
const headers = { 'Content-Type': 'application/json' };
|
|
1395
|
+
if (this.projectConfig.authToken)
|
|
1396
|
+
headers['Authorization'] = `Bearer ${this.projectConfig.authToken}`;
|
|
1397
|
+
this.addIngressSecretHeader(headers);
|
|
1398
|
+
const post = () => fetch(`http://127.0.0.1:${this.projectConfig.port}/internal/lifeline-proof`, { method: 'POST', headers, body: JSON.stringify({ lifelineVersion: this.lifelineVersion }), signal: AbortSignal.timeout(5_000) });
|
|
1399
|
+
this.lastIngressProofAt = Date.now();
|
|
1400
|
+
let response = await post();
|
|
1401
|
+
if (response.status === 401 && await this.isLifelineSecretRefusal(response)) {
|
|
1402
|
+
this.ingressSecret.reload();
|
|
1403
|
+
this.addIngressSecretHeader(headers);
|
|
1404
|
+
response = await post();
|
|
1405
|
+
}
|
|
1406
|
+
if (response.ok)
|
|
1407
|
+
this.observeStaleSecretHint(response);
|
|
1408
|
+
}
|
|
1409
|
+
catch {
|
|
1410
|
+
// @silent-fallback-ok — the proof is an optimisation; the first real
|
|
1411
|
+
// forward carries the same secret and arms the guard just the same.
|
|
1412
|
+
}
|
|
1413
|
+
}
|
|
1414
|
+
async isLifelineSecretRefusal(response) {
|
|
1415
|
+
const body = (await response.clone().json().catch(() => ({})));
|
|
1416
|
+
return body.error === 'lifeline-secret-required';
|
|
1417
|
+
}
|
|
1286
1418
|
/**
|
|
1287
1419
|
* Agent hard-sleep wake trigger. When the ServerSupervisor has intentionally
|
|
1288
1420
|
* slept the server (a `state/slept-marker.json` is present), write
|
|
@@ -1350,6 +1482,7 @@ export class TelegramLifeline {
|
|
|
1350
1482
|
...(senderBotId !== undefined ? { senderBotId } : {}),
|
|
1351
1483
|
...(includeVersion ? { lifelineVersion: this.lifelineVersion } : {}),
|
|
1352
1484
|
});
|
|
1485
|
+
let secretReloaded = false;
|
|
1353
1486
|
const doForward = async () => {
|
|
1354
1487
|
const controller = new AbortController();
|
|
1355
1488
|
const timer = setTimeout(() => controller.abort(), 10_000);
|
|
@@ -1358,6 +1491,7 @@ export class TelegramLifeline {
|
|
|
1358
1491
|
if (this.projectConfig.authToken) {
|
|
1359
1492
|
fwdHeaders['Authorization'] = `Bearer ${this.projectConfig.authToken}`;
|
|
1360
1493
|
}
|
|
1494
|
+
this.addIngressSecretHeader(fwdHeaders);
|
|
1361
1495
|
const response = await fetch(`http://127.0.0.1:${this.projectConfig.port}/internal/telegram-forward`, {
|
|
1362
1496
|
method: 'POST',
|
|
1363
1497
|
headers: fwdHeaders,
|
|
@@ -1366,6 +1500,7 @@ export class TelegramLifeline {
|
|
|
1366
1500
|
});
|
|
1367
1501
|
if (response.ok) {
|
|
1368
1502
|
this.observeForwardResponseDriftHeader(response);
|
|
1503
|
+
this.observeStaleSecretHint(response);
|
|
1369
1504
|
return true;
|
|
1370
1505
|
}
|
|
1371
1506
|
if (response.status === 426) {
|
|
@@ -1376,6 +1511,19 @@ export class TelegramLifeline {
|
|
|
1376
1511
|
const body = (await response.json().catch(() => ({})));
|
|
1377
1512
|
throw new ForwardServerBootError(body.retryAfterMs ?? 1000);
|
|
1378
1513
|
}
|
|
1514
|
+
if (response.status === 401 && await this.isLifelineSecretRefusal(response)) {
|
|
1515
|
+
// The server refused our secret: the file may have been replaced
|
|
1516
|
+
// since we cached it. Re-read it ONCE per forward and let
|
|
1517
|
+
// retryWithBackoff try again with the fresh value. If the server
|
|
1518
|
+
// still refuses, stop retrying: the message is re-queued without
|
|
1519
|
+
// burning any replay budget (never dropped as "server unreachable").
|
|
1520
|
+
if (!secretReloaded) {
|
|
1521
|
+
secretReloaded = true;
|
|
1522
|
+
this.ingressSecret.reload();
|
|
1523
|
+
throw new ForwardTransientError(401);
|
|
1524
|
+
}
|
|
1525
|
+
throw new ForwardSecretRefusedError();
|
|
1526
|
+
}
|
|
1379
1527
|
if (response.status === 400) {
|
|
1380
1528
|
const body = await response.json().catch(() => ({}));
|
|
1381
1529
|
// Graceful degradation: if we included lifelineVersion and the
|
|
@@ -1416,6 +1564,11 @@ export class TelegramLifeline {
|
|
|
1416
1564
|
// Record success for watchdog.
|
|
1417
1565
|
this.consecutiveForwardFailures = 0;
|
|
1418
1566
|
this.lastForwardSuccessAt = Date.now();
|
|
1567
|
+
// A delivered forward ends any secret-refusal episode.
|
|
1568
|
+
this.secretRefusedSince = 0;
|
|
1569
|
+
this.secretRefusedLastAt = 0;
|
|
1570
|
+
this.secretRefusedPasses = 0;
|
|
1571
|
+
this.secretRefusalNoticeSent = false;
|
|
1419
1572
|
// Successful forward → the skew episode (if any) has resolved.
|
|
1420
1573
|
// Clear the flag and the alert-dedupe timestamp so the next episode
|
|
1421
1574
|
// gets its own user-visible notification.
|
|
@@ -1429,6 +1582,13 @@ export class TelegramLifeline {
|
|
|
1429
1582
|
this.handleVersionSkew(err, topicId);
|
|
1430
1583
|
return 'skew';
|
|
1431
1584
|
}
|
|
1585
|
+
// The server will not accept our lifeline secret. Not a property of the
|
|
1586
|
+
// message and not an outage: re-queue with no budget burned (the same
|
|
1587
|
+
// replay decision as a skew), and say so loudly — at most once a minute.
|
|
1588
|
+
if (err instanceof ForwardSecretRefusedError) {
|
|
1589
|
+
this.noteSecretRefusal(topicId);
|
|
1590
|
+
return 'skew';
|
|
1591
|
+
}
|
|
1432
1592
|
this.consecutiveForwardFailures++;
|
|
1433
1593
|
// A genuine HTTP-400 is the ONLY message-specific ("poison") failure —
|
|
1434
1594
|
// the server looked at this message and refused it. Everything else
|
|
@@ -1693,9 +1853,28 @@ export class TelegramLifeline {
|
|
|
1693
1853
|
// the very episodes that trigger queuing) lost the in-memory messages with
|
|
1694
1854
|
// no trace. That is the 2026-06-06 topic-21487 untracked-loss: a real user
|
|
1695
1855
|
// question vanished without even a dropped-messages.json record.
|
|
1856
|
+
// Single flight: a pass that outlives the tick must not overlap the next.
|
|
1857
|
+
if (this.replayInFlight)
|
|
1858
|
+
return;
|
|
1859
|
+
// While the server refuses our secret every forward fails identically, so
|
|
1860
|
+
// back off (one minute, doubling, capped at fifteen) instead of
|
|
1861
|
+
// re-forwarding the whole queue on every tick.
|
|
1862
|
+
if (this.secretRefusedLastAt !== 0
|
|
1863
|
+
&& Date.now() - this.secretRefusedLastAt < this.secretRefusalBackoffMs())
|
|
1864
|
+
return;
|
|
1696
1865
|
const messages = this.queue.peek();
|
|
1697
1866
|
if (messages.length === 0)
|
|
1698
1867
|
return;
|
|
1868
|
+
this.replayInFlight = true;
|
|
1869
|
+
try {
|
|
1870
|
+
await this.replayQueueLocked(messages);
|
|
1871
|
+
}
|
|
1872
|
+
finally {
|
|
1873
|
+
this.replayInFlight = false;
|
|
1874
|
+
}
|
|
1875
|
+
}
|
|
1876
|
+
/* @self-action-controller: lifeline-secret-refusal-redrive */
|
|
1877
|
+
async replayQueueLocked(messages) {
|
|
1699
1878
|
console.log(`[Lifeline] Replaying ${messages.length} queued messages`);
|
|
1700
1879
|
let replayed = 0;
|
|
1701
1880
|
let failed = 0;
|
|
@@ -1725,6 +1904,12 @@ export class TelegramLifeline {
|
|
|
1725
1904
|
poisonFailures: msg.replayFailures ?? 0,
|
|
1726
1905
|
transientFailures: msg.transientReplayFailures ?? 0,
|
|
1727
1906
|
});
|
|
1907
|
+
// The server refused our secret: the rest of the queue would be refused
|
|
1908
|
+
// the same way. Stop this pass; everything stays on disk, no budget burned.
|
|
1909
|
+
if (outcome === 'skew' && this.secretRefusedLastAt !== 0 && !this.versionSkewActive) {
|
|
1910
|
+
this.secretRefusedPasses++; // one refused PASS — drives the backoff
|
|
1911
|
+
break;
|
|
1912
|
+
}
|
|
1728
1913
|
if (decision.action === 'delivered') {
|
|
1729
1914
|
this.queue.markDelivered(msg.id); // remember id so a redelivery can't re-queue it
|
|
1730
1915
|
replayed++;
|