instar 1.3.688 → 1.3.689
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 +106 -0
- package/dist/commands/server.js.map +1 -1
- package/dist/config/ConfigDefaults.d.ts.map +1 -1
- package/dist/config/ConfigDefaults.js +11 -0
- package/dist/config/ConfigDefaults.js.map +1 -1
- package/dist/core/SessionManager.d.ts.map +1 -1
- package/dist/core/SessionManager.js +8 -0
- package/dist/core/SessionManager.js.map +1 -1
- package/dist/core/devGatedFeatures.d.ts.map +1 -1
- package/dist/core/devGatedFeatures.js +5 -0
- package/dist/core/devGatedFeatures.js.map +1 -1
- package/dist/core/leaseGatedSpawn.d.ts +232 -0
- package/dist/core/leaseGatedSpawn.d.ts.map +1 -0
- package/dist/core/leaseGatedSpawn.js +216 -0
- package/dist/core/leaseGatedSpawn.js.map +1 -0
- package/dist/server/CapabilityIndex.d.ts.map +1 -1
- package/dist/server/CapabilityIndex.js +1 -0
- package/dist/server/CapabilityIndex.js.map +1 -1
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +44 -0
- package/dist/server/routes.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +47 -47
- package/upgrades/1.3.689.md +30 -0
- package/upgrades/side-effects/mesh-self-heal-g3.md +45 -0
- package/upgrades/side-effects/mesh-self-heal-spec.md +37 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/commands/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AA8CH,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAU3D,OAAO,EAAE,eAAe,EAAiC,MAAM,iCAAiC,CAAC;AAyBjG,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAGvD,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/commands/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AA8CH,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAU3D,OAAO,EAAE,eAAe,EAAiC,MAAM,iCAAiC,CAAC;AAyBjG,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAGvD,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAyH7D,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AA+EtD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,gBAAgB,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAC1C,OAAO,CAUT;AAyID,UAAU,YAAY;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;2DACuD;IACvD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAi7CD,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,eAAe,EACzB,cAAc,EAAE,cAAc,EAC9B,YAAY,CAAC,EAAE,YAAY,EAC3B,WAAW,CAAC,EAAE,WAAW,EACzB,WAAW,CAAC,EAAE,WAAW,EACzB,iBAAiB,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,EAGvE,UAAU,CAAC,EAAE,MAAM,OAAO,8BAA8B,EAAE,WAAW,GAAG,IAAI,EAK5E,qBAAqB,CAAC,EAAE,MAAM,OAAO,gCAAgC,EAAE,kBAAkB,GAAG,IAAI,EAKhG,mBAAmB,CAAC,EAAE,MAAM,MAAM,GAAG,IAAI,GAAG,SAAS,GACpD,IAAI,CAihBN;AA2lBD,wBAAsB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CA0vhBtE;AAED,wBAAsB,UAAU,CAAC,OAAO,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAsDzE;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAuD5E"}
|
package/dist/commands/server.js
CHANGED
|
@@ -96,6 +96,7 @@ import { SessionMonitor } from '../monitoring/SessionMonitor.js';
|
|
|
96
96
|
import { SessionRecovery } from '../monitoring/SessionRecovery.js';
|
|
97
97
|
import { MultiMachineCoordinator } from '../core/MultiMachineCoordinator.js';
|
|
98
98
|
import { isRemotelyHandled } from '../core/SessionRouter.js';
|
|
99
|
+
import { decideLeaseGatedSpawn, sharedG3SoakLedger, applyBindingCleanupOnKill } from '../core/leaseGatedSpawn.js';
|
|
99
100
|
import { isSlackSessionKey, reconstructSlackMessage } from '../core/SlackForwardBridge.js';
|
|
100
101
|
import { formatForwardedTopicContext } from '../core/ForwardedTopicContext.js';
|
|
101
102
|
import { resolveAdvertisedMeshUrl, advertiseSelfMeshUrl, detectTailscaleIp, pickPrimaryLanIp, computeSelfMeshEndpoints, advertiseSelfMeshEndpoints, resolveMeshBindHost } from '../core/MeshUrlAdvertiser.js';
|
|
@@ -477,6 +478,41 @@ let _inboundQueueStop = null;
|
|
|
477
478
|
* (forward/spawn on another machine → must NOT also dispatch locally) from a
|
|
478
479
|
* self placement. Set once in startServer()'s mesh block. */
|
|
479
480
|
let _meshSelfId = null;
|
|
481
|
+
/** G3 lease-gated spawn (MESH-SELF-HEAL-SPEC §3.3). "Do I hold the fenced
|
|
482
|
+
* awake-lease?" — null = single-machine / no lease coordinator → the gate is a
|
|
483
|
+
* strict no-op (always spawn locally, byte-for-byte legacy). Set in startServer's
|
|
484
|
+
* mesh block from the lease coordinator. */
|
|
485
|
+
let _holdsLeaseForSpawn = null;
|
|
486
|
+
/** G3: the ownershipCheckedSpawn flag accessor (multiMachine.sessionPool.
|
|
487
|
+
* ownershipCheckedSpawn). Defaults dark (no-op). Set in startServer. */
|
|
488
|
+
let _ownershipCheckedSpawn = () => ({ enabled: false, dryRun: true });
|
|
489
|
+
/** G3: the soak-evidence ledger — records evaluable counterfactual evidence
|
|
490
|
+
* (would-have-prevented-duplicate) + drives a promotion recommendation
|
|
491
|
+
* (operator directive: observe-mode must be able to graduate). Always present;
|
|
492
|
+
* read via the /mesh-selfheal/g3 route (the shared process-wide singleton). */
|
|
493
|
+
const _g3SoakLedger = sharedG3SoakLedger;
|
|
494
|
+
/**
|
|
495
|
+
* G3 — decide spawn-vs-forward for an inbound topic message, RECORD the soak
|
|
496
|
+
* evidence, and return whether to spawn locally. Centralizes the gate so every
|
|
497
|
+
* spawn callsite shares one decision + one ledger. Single-machine / flag-off =
|
|
498
|
+
* always spawn (the gate no-ops). When the decision is 'forward', the caller
|
|
499
|
+
* must NOT spawn and should route via _sessionRouter instead.
|
|
500
|
+
*/
|
|
501
|
+
function g3ShouldSpawnLocally(topicId) {
|
|
502
|
+
const cfg = _ownershipCheckedSpawn();
|
|
503
|
+
const decision = decideLeaseGatedSpawn({
|
|
504
|
+
holdsLease: _holdsLeaseForSpawn ? _holdsLeaseForSpawn() : true,
|
|
505
|
+
flagEnabled: cfg.enabled,
|
|
506
|
+
dryRun: cfg.dryRun,
|
|
507
|
+
singleMachine: _holdsLeaseForSpawn === null,
|
|
508
|
+
forwardAvailable: _sessionRouter !== null,
|
|
509
|
+
});
|
|
510
|
+
_g3SoakLedger.record(decision, new Date().toISOString());
|
|
511
|
+
if (decision.action !== 'spawn') {
|
|
512
|
+
console.log(`[g3-spawn-gate] topic ${topicId}: action=${decision.action} reason=${decision.reason} spawnLocally=${decision.spawnLocally}`);
|
|
513
|
+
}
|
|
514
|
+
return { spawnLocally: decision.spawnLocally, action: decision.action, reason: decision.reason };
|
|
515
|
+
}
|
|
480
516
|
/** WS1.2: the owner-side drain runner (null = pool dark / deps unavailable).
|
|
481
517
|
* Presence IS the heartbeat-advertised ws12DrainReceive capability — a
|
|
482
518
|
* machine only advertises what it can actually execute. */
|
|
@@ -2100,6 +2136,21 @@ getAttentionTopicId) {
|
|
|
2100
2136
|
console.log(`[telegram→session] Spawn already in progress for topic ${topicId} — skipping duplicate`);
|
|
2101
2137
|
return;
|
|
2102
2138
|
}
|
|
2139
|
+
// G3 — lease-gated spawn (MESH-SELF-HEAL-SPEC §3.3): spawn iff I hold the
|
|
2140
|
+
// fenced awake-lease; if not, forward to the holder instead of spawning a
|
|
2141
|
+
// duplicate. Single-machine / flag-off = byte-for-byte legacy (no-op).
|
|
2142
|
+
const _g3 = g3ShouldSpawnLocally(topicId);
|
|
2143
|
+
if (!_g3.spawnLocally) {
|
|
2144
|
+
// action === 'forward': do NOT spawn locally. Hand the inbound to the
|
|
2145
|
+
// lease holder via the existing WS1.1 SessionRouter seam (reuse, not
|
|
2146
|
+
// reinvent). _sessionRouter is non-null here (forwardAvailable gated it).
|
|
2147
|
+
if (_sessionRouter) {
|
|
2148
|
+
_sessionRouter
|
|
2149
|
+
.route({ sessionKey: String(topicId), messageId: String(msg.id), payload: text })
|
|
2150
|
+
.catch((err) => console.error(`[g3-spawn-gate] forward route failed for topic ${topicId}:`, err));
|
|
2151
|
+
}
|
|
2152
|
+
return;
|
|
2153
|
+
}
|
|
2103
2154
|
const _spawnTokC = spawningTopics.add(topicId);
|
|
2104
2155
|
// Resolve topic name — try in-memory, then active probe, then fallback
|
|
2105
2156
|
let spawnName = storedTopicName;
|
|
@@ -4265,6 +4316,15 @@ export async function startServer(options) {
|
|
|
4265
4316
|
});
|
|
4266
4317
|
coordinator.attachLeaseCoordinator(leaseCoordinator);
|
|
4267
4318
|
leaseCoordinatorRef = leaseCoordinator;
|
|
4319
|
+
// G3 — lease-gated spawn: wire the "do I hold the lease?" accessor so the
|
|
4320
|
+
// spawn gate can fire. Setting this non-null also flips the gate out of its
|
|
4321
|
+
// single-machine no-op (it now consults the real lease). The flag accessor
|
|
4322
|
+
// reads the live config each spawn (no restart needed to flip the soak).
|
|
4323
|
+
_holdsLeaseForSpawn = () => coordinator.holdsLease();
|
|
4324
|
+
_ownershipCheckedSpawn = () => {
|
|
4325
|
+
const o = config?.multiMachine?.sessionPool?.ownershipCheckedSpawn ?? {};
|
|
4326
|
+
return { enabled: o.enabled === true, dryRun: o.dryRun !== false };
|
|
4327
|
+
};
|
|
4268
4328
|
await coordinator.initializeLease();
|
|
4269
4329
|
console.log(pc.dim(` Fenced lease active (epoch ${leaseCoordinator.currentEpoch()}, holder=${leaseCoordinator.currentHolder() ?? 'none'})`));
|
|
4270
4330
|
// ── Handoff ack/yield wire (spec §8 G3d/G3e) ───────────────
|
|
@@ -8206,6 +8266,52 @@ export async function startServer(options) {
|
|
|
8206
8266
|
});
|
|
8207
8267
|
});
|
|
8208
8268
|
}
|
|
8269
|
+
// G3.4 — Single-writer binding lifecycle: clear a killed session's stale
|
|
8270
|
+
// topic→session binding so a dead session can't silently resurrect (the
|
|
8271
|
+
// invariant "a binding exists IFF a live session exists for it"). Registered
|
|
8272
|
+
// AFTER the resume-UUID-save listener above so the UUID (kept in
|
|
8273
|
+
// TopicResumeMap, keyed by topicId) is saved BEFORE the binding is cleared —
|
|
8274
|
+
// clearing the binding never loses the resume UUID. Gated behind
|
|
8275
|
+
// ownershipCheckedSpawn (dark + dryRun by default ⇒ strict no-op until the
|
|
8276
|
+
// operator opts in); the next inbound for the topic re-spawns fresh (with
|
|
8277
|
+
// --resume) and re-registers a new binding.
|
|
8278
|
+
sessionManager.on('beforeSessionKill', (session) => {
|
|
8279
|
+
if (!telegram)
|
|
8280
|
+
return;
|
|
8281
|
+
try {
|
|
8282
|
+
const cfg = _ownershipCheckedSpawn();
|
|
8283
|
+
applyBindingCleanupOnKill({
|
|
8284
|
+
// A context-exhaustion / recovery kill is immediately followed by a
|
|
8285
|
+
// SAME-TOPIC respawn (respawnSession / respawnSessionFresh), which
|
|
8286
|
+
// resolves its target via getSessionForTopic. Clearing the binding
|
|
8287
|
+
// first would make that lookup return null and the recovery silently
|
|
8288
|
+
// abort. Mirror the resume-UUID-save listener's contextExhaustionKills
|
|
8289
|
+
// guard so the two beforeSessionKill siblings keep identical
|
|
8290
|
+
// skip-on-respawn semantics. (G3 side-effects second-pass review.)
|
|
8291
|
+
respawnImminent: contextExhaustionKills.has(session.tmuxSession),
|
|
8292
|
+
registry: telegram,
|
|
8293
|
+
sessionName: session.tmuxSession,
|
|
8294
|
+
flagEnabled: cfg.enabled,
|
|
8295
|
+
dryRun: cfg.dryRun,
|
|
8296
|
+
ledger: _g3SoakLedger,
|
|
8297
|
+
nowIso: new Date().toISOString(),
|
|
8298
|
+
log: (msg) => console.log(msg),
|
|
8299
|
+
// Durable audit (spec §6) — one mesh-selfheal.jsonl line per binding
|
|
8300
|
+
// transition. Best-effort: a write failure never blocks the kill path.
|
|
8301
|
+
audit: (entry) => {
|
|
8302
|
+
try {
|
|
8303
|
+
const logsDir = path.join(config.stateDir, '..', 'logs');
|
|
8304
|
+
fs.mkdirSync(logsDir, { recursive: true });
|
|
8305
|
+
fs.appendFileSync(path.join(logsDir, 'mesh-selfheal.jsonl'), JSON.stringify(entry) + '\n');
|
|
8306
|
+
}
|
|
8307
|
+
catch { /* @silent-fallback-ok — audit jsonl is observability only */ }
|
|
8308
|
+
},
|
|
8309
|
+
});
|
|
8310
|
+
}
|
|
8311
|
+
catch (err) {
|
|
8312
|
+
console.error('[g3-binding-cleanup] Failed to clear stale binding on kill:', err);
|
|
8313
|
+
}
|
|
8314
|
+
});
|
|
8209
8315
|
sessionManager.on('injectionReplyDetected', (info) => {
|
|
8210
8316
|
if (!telegram)
|
|
8211
8317
|
return;
|