instar 1.3.687 → 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.
Files changed (38) hide show
  1. package/dist/commands/server.d.ts.map +1 -1
  2. package/dist/commands/server.js +106 -0
  3. package/dist/commands/server.js.map +1 -1
  4. package/dist/config/ConfigDefaults.d.ts.map +1 -1
  5. package/dist/config/ConfigDefaults.js +11 -0
  6. package/dist/config/ConfigDefaults.js.map +1 -1
  7. package/dist/core/SessionManager.d.ts.map +1 -1
  8. package/dist/core/SessionManager.js +8 -0
  9. package/dist/core/SessionManager.js.map +1 -1
  10. package/dist/core/devGatedFeatures.d.ts.map +1 -1
  11. package/dist/core/devGatedFeatures.js +5 -0
  12. package/dist/core/devGatedFeatures.js.map +1 -1
  13. package/dist/core/leaseGatedSpawn.d.ts +232 -0
  14. package/dist/core/leaseGatedSpawn.d.ts.map +1 -0
  15. package/dist/core/leaseGatedSpawn.js +216 -0
  16. package/dist/core/leaseGatedSpawn.js.map +1 -0
  17. package/dist/monitoring/ReleaseReadinessSentinel.d.ts +9 -0
  18. package/dist/monitoring/ReleaseReadinessSentinel.d.ts.map +1 -1
  19. package/dist/monitoring/ReleaseReadinessSentinel.js +14 -5
  20. package/dist/monitoring/ReleaseReadinessSentinel.js.map +1 -1
  21. package/dist/server/CapabilityIndex.d.ts.map +1 -1
  22. package/dist/server/CapabilityIndex.js +1 -0
  23. package/dist/server/CapabilityIndex.js.map +1 -1
  24. package/dist/server/routes.d.ts.map +1 -1
  25. package/dist/server/routes.js +44 -0
  26. package/dist/server/routes.js.map +1 -1
  27. package/package.json +1 -1
  28. package/scripts/check-release-fragment.mjs +152 -0
  29. package/scripts/lint-no-direct-destructive.js +6 -0
  30. package/scripts/pre-push-gate.js +11 -4
  31. package/scripts/release-relevant-paths.mjs +171 -0
  32. package/scripts/release-skip-annotate.mjs +133 -0
  33. package/src/data/builtin-manifest.json +47 -47
  34. package/upgrades/1.3.688.md +61 -0
  35. package/upgrades/1.3.689.md +30 -0
  36. package/upgrades/side-effects/mesh-self-heal-g3.md +45 -0
  37. package/upgrades/side-effects/mesh-self-heal-spec.md +37 -0
  38. package/upgrades/side-effects/release-fragment-gate.md +136 -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;AAwH7D,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;AA84CD,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,CAigBN;AA2lBD,wBAAsB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAqshBtE;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"}
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"}
@@ -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;