instar 1.3.930 → 1.3.932

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 (47) hide show
  1. package/dashboard/subscriptions.js +60 -6
  2. package/dist/commands/server.d.ts.map +1 -1
  3. package/dist/commands/server.js +41 -10
  4. package/dist/commands/server.js.map +1 -1
  5. package/dist/coordination/FollowMeConsumerBackoffStore.d.ts +36 -0
  6. package/dist/coordination/FollowMeConsumerBackoffStore.d.ts.map +1 -0
  7. package/dist/coordination/FollowMeConsumerBackoffStore.js +96 -0
  8. package/dist/coordination/FollowMeConsumerBackoffStore.js.map +1 -0
  9. package/dist/core/QuotaPoller.d.ts.map +1 -1
  10. package/dist/core/QuotaPoller.js +3 -14
  11. package/dist/core/QuotaPoller.js.map +1 -1
  12. package/dist/core/SubscriptionAccountEmailRepair.d.ts +19 -0
  13. package/dist/core/SubscriptionAccountEmailRepair.d.ts.map +1 -0
  14. package/dist/core/SubscriptionAccountEmailRepair.js +58 -0
  15. package/dist/core/SubscriptionAccountEmailRepair.js.map +1 -0
  16. package/dist/core/SubscriptionPool.d.ts +80 -6
  17. package/dist/core/SubscriptionPool.d.ts.map +1 -1
  18. package/dist/core/SubscriptionPool.js +201 -29
  19. package/dist/core/SubscriptionPool.js.map +1 -1
  20. package/dist/core/WriteDomainRegistry.d.ts.map +1 -1
  21. package/dist/core/WriteDomainRegistry.js +4 -0
  22. package/dist/core/WriteDomainRegistry.js.map +1 -1
  23. package/dist/core/resolveFollowMeEnrollTarget.d.ts +1 -0
  24. package/dist/core/resolveFollowMeEnrollTarget.d.ts.map +1 -1
  25. package/dist/core/resolveFollowMeEnrollTarget.js +37 -19
  26. package/dist/core/resolveFollowMeEnrollTarget.js.map +1 -1
  27. package/dist/messaging/slack/SlackApiClient.d.ts +8 -0
  28. package/dist/messaging/slack/SlackApiClient.d.ts.map +1 -1
  29. package/dist/messaging/slack/SlackApiClient.js +44 -2
  30. package/dist/messaging/slack/SlackApiClient.js.map +1 -1
  31. package/dist/server/AgentServer.d.ts +5 -0
  32. package/dist/server/AgentServer.d.ts.map +1 -1
  33. package/dist/server/AgentServer.js +74 -24
  34. package/dist/server/AgentServer.js.map +1 -1
  35. package/dist/server/routes.d.ts +17 -0
  36. package/dist/server/routes.d.ts.map +1 -1
  37. package/dist/server/routes.js +202 -29
  38. package/dist/server/routes.js.map +1 -1
  39. package/dist/testing/selfActionRegistry.d.ts.map +1 -1
  40. package/dist/testing/selfActionRegistry.js +28 -0
  41. package/dist/testing/selfActionRegistry.js.map +1 -1
  42. package/package.json +2 -1
  43. package/src/data/builtin-manifest.json +47 -47
  44. package/upgrades/1.3.931.md +46 -0
  45. package/upgrades/1.3.932.md +29 -0
  46. package/upgrades/side-effects/slack-outbound-transport-recovery.md +14 -0
  47. package/upgrades/side-effects/subscription-account-email-invariant.md +169 -0
@@ -471,6 +471,7 @@ export function renderPendingLogins(doc, target, logins, now = Date.now(), outco
471
471
  /** Pivot the pool-scope + pending-scope bodies into a grid model. Pure + testable. */
472
472
  export function buildMatrixModel(poolScope, pendingScope, transient = {}) {
473
473
  const accountRows = (poolScope && Array.isArray(poolScope.accounts)) ? poolScope.accounts : [];
474
+ const gapRows = (poolScope && Array.isArray(poolScope.emailGaps)) ? poolScope.emailGaps : [];
474
475
  const pendingRows = (pendingScope && Array.isArray(pendingScope.logins)) ? pendingScope.logins : [];
475
476
  const failed = (poolScope && poolScope.pool && Array.isArray(poolScope.pool.failed)) ? poolScope.pool.failed : [];
476
477
  const selfMachineId = (poolScope && poolScope.pool && poolScope.pool.selfMachineId) || null;
@@ -485,6 +486,15 @@ export function buildMatrixModel(poolScope, pendingScope, transient = {}) {
485
486
  if (!mid || offlineMachineIds.has(mid)) continue;
486
487
  if (!machines.has(mid)) machines.set(mid, { machineId: mid, nickname: (a.machineNickname || mid), offline: false });
487
488
  }
489
+ for (const gap of gapRows) {
490
+ const mid = gap && gap.machineId;
491
+ if (!mid || offlineMachineIds.has(mid)) continue;
492
+ if (!machines.has(mid)) machines.set(mid, {
493
+ machineId: mid,
494
+ nickname: gap.machineNickname || mid,
495
+ offline: false,
496
+ });
497
+ }
488
498
  for (const f of failed) {
489
499
  const mid = f && f.machineId;
490
500
  if (!mid) continue;
@@ -500,6 +510,13 @@ export function buildMatrixModel(poolScope, pendingScope, transient = {}) {
500
510
  if (!accounts.has(id)) accounts.set(id, { accountId: id, email: a.email || id });
501
511
  else if (!accounts.get(id).email && a.email) accounts.get(id).email = a.email;
502
512
  }
513
+ for (const gap of gapRows) {
514
+ const id = gap && gap.accountId;
515
+ if (id && !accounts.has(id)) accounts.set(id, {
516
+ accountId: id,
517
+ email: gap.nickname || id,
518
+ });
519
+ }
503
520
  // A pending matrix login can reference an account not yet in any pool row — surface its row too.
504
521
  for (const l of pendingRows) {
505
522
  const id = l && l.id;
@@ -529,6 +546,11 @@ export function buildMatrixModel(poolScope, pendingScope, transient = {}) {
529
546
  for (const l of pendingRows) {
530
547
  if (l && l.id && l.machineId) inProgress.set(`${l.id}::${l.machineId}`, l);
531
548
  }
549
+ const emailMissing = new Set(
550
+ gapRows
551
+ .filter((gap) => gap && gap.accountId && gap.machineId)
552
+ .map((gap) => `${gap.accountId}::${gap.machineId}`),
553
+ );
532
554
 
533
555
  const machineList = Array.from(machines.values());
534
556
  const accountList = Array.from(accounts.values());
@@ -552,6 +574,7 @@ export function buildMatrixModel(poolScope, pendingScope, transient = {}) {
552
574
  if (m.offline) state = 'offline'; // whole column offline (FD6)
553
575
  else if (t && t.state === 'held') state = 'held';
554
576
  else if (t && t.state === 'cant-resolve') state = 'cant-resolve';
577
+ else if (emailMissing.has(key) || (t && t.state === 'email-missing')) state = 'email-missing';
555
578
  // Durable pending state wins over enrollment bookkeeping after restart: the
556
579
  // full flow rehydrates into its cell even if the pool row still says Active.
557
580
  // broken (D5): the server says this attempt's sign-in pane is DEAD (record ⟂ pane
@@ -586,12 +609,13 @@ export function buildMatrixModel(poolScope, pendingScope, transient = {}) {
586
609
 
587
610
  const MATRIX_CELL_GLYPH = {
588
611
  active: '✓', 'needs-reauth': '⟳', 'in-progress': '◷', offline: '—', held: '⚠', 'cant-resolve': '✗',
589
- expired: '✗', 'just-verified': '✓', broken: '✗',
612
+ 'email-missing': '⚠', expired: '✗', 'just-verified': '✓', broken: '✗',
590
613
  };
591
614
  const MATRIX_CELL_WORD = {
592
615
  active: 'Active', 'needs-reauth': 'Needs sign-in', 'in-progress': 'Signing in…',
593
616
  offline: 'Machine offline', held: 'Didn’t match — re-try', 'cant-resolve': 'Can’t set up', other: 'Set up',
594
- expired: 'Sign-in link expired', 'just-verified': 'Set up complete', broken: 'Sign-in needs a restart',
617
+ 'email-missing': 'Account record is missing its email', expired: 'Sign-in link expired',
618
+ 'just-verified': 'Set up complete', broken: 'Sign-in needs a restart',
595
619
  };
596
620
 
597
621
  /** D5 wording floor: never show a raw internal machine id (m_<hex>) to the operator —
@@ -700,17 +724,21 @@ export function renderAccountMatrix(doc, target, poolScope, pendingScope, transi
700
724
  const td = el(doc, 'td', `sub-matrix-cell sub-matrix-${c.state}${justVerified && c.state !== 'just-verified' ? ' sub-matrix-just-verified' : ''}`);
701
725
  // Stable cell identity for the interaction-hold rule + targeted merge updates (F9).
702
726
  td.setAttribute('data-cell-key', sanitizeForDisplay(`${c.accountId}::${c.machineId}`, 'url'));
703
- if (c.state === 'empty' || c.state === 'needs-reauth' || c.state === 'held' || c.state === 'cant-resolve' || c.state === 'expired' || c.state === 'broken') {
727
+ if (c.state === 'empty' || c.state === 'needs-reauth' || c.state === 'held' || c.state === 'cant-resolve' || c.state === 'email-missing' || c.state === 'expired' || c.state === 'broken') {
704
728
  // An actionable cell → a button that runs the SAME in-dashboard sign-in flow (PIN → link →
705
729
  // paste code). empty → "Set up"; needs-reauth (an existing account whose login expired) →
706
730
  // "Sign in"; held/cant-resolve/expired/broken → "Retry". A needs-reauth account already
707
731
  // resolves to its email, so the start-cell orchestrator drives a real re-auth — never a
708
732
  // cosmetic button, and a broken (dead-pane) attempt is superseded server-side on Retry.
709
- const label = c.state === 'empty' ? 'Set up' : (c.state === 'needs-reauth' ? 'Sign in' : 'Retry');
733
+ const label = c.state === 'empty' ? 'Set up'
734
+ : c.state === 'needs-reauth' ? 'Sign in'
735
+ : c.state === 'email-missing' ? 'Repair identity'
736
+ : 'Retry';
710
737
  const btn = el(doc, 'button', 'sub-matrix-setup', label);
711
738
  btn.setAttribute('data-matrix-setup', '1');
712
739
  btn.setAttribute('data-account-id', sanitizeForDisplay(c.accountId, 'label'));
713
740
  btn.setAttribute('data-machine-id', sanitizeForDisplay(c.machineId, 'label'));
741
+ if (c.state === 'email-missing') btn.setAttribute('data-email-repair', '1');
714
742
  if (c.state !== 'empty') {
715
743
  // Show the status word ("⟳ Needs sign-in" / "⚠ Didn't match…") ABOVE the button.
716
744
  td.appendChild(el(doc, 'div', 'sub-matrix-glyph', `${MATRIX_CELL_GLYPH[c.state]} ${MATRIX_CELL_WORD[c.state]}`));
@@ -1197,6 +1225,7 @@ export function createController(opts) {
1197
1225
  if (!cell) return;
1198
1226
  const accountId = btn.getAttribute('data-account-id');
1199
1227
  const machineId = btn.getAttribute('data-machine-id');
1228
+ const emailRepair = btn.getAttribute('data-email-repair') === '1';
1200
1229
  if (!accountId || !machineId) return;
1201
1230
  // A retry clears the previous attempt's terminal presentation for this cell.
1202
1231
  delete state.matrixTransient[`${accountId}::${machineId}`];
@@ -1212,6 +1241,7 @@ export function createController(opts) {
1212
1241
  confirm.setAttribute('data-matrix-confirm', '1');
1213
1242
  confirm.setAttribute('data-account-id', accountId);
1214
1243
  confirm.setAttribute('data-machine-id', machineId);
1244
+ if (emailRepair) confirm.setAttribute('data-email-repair', '1');
1215
1245
  cell.appendChild(confirm);
1216
1246
  // An explicit way OUT of the interaction (the hold would otherwise pin the cell
1217
1247
  // forever if the operator changes their mind) — client-side only, nothing started yet.
@@ -1237,6 +1267,7 @@ export function createController(opts) {
1237
1267
  if (!cell) return;
1238
1268
  const accountId = btn.getAttribute('data-account-id');
1239
1269
  const machineId = btn.getAttribute('data-machine-id');
1270
+ const emailRepair = btn.getAttribute('data-email-repair') === '1';
1240
1271
  const pinInput = cell.querySelector('.sub-matrix-pin');
1241
1272
  const pin = pinInput ? pinInput.value.trim() : '';
1242
1273
  if (!accountId || !machineId) { setCellStatus(cell, 'Couldn’t prepare this — please refresh.'); return; }
@@ -1245,6 +1276,22 @@ export function createController(opts) {
1245
1276
  btn.setAttribute('disabled', '1');
1246
1277
  void (async () => {
1247
1278
  try {
1279
+ if (emailRepair) {
1280
+ const r = await postJson(`/subscription-pool/${encodeURIComponent(accountId)}/repair-email`, { pin });
1281
+ if (pinInput) pinInput.value = '';
1282
+ if (r.ok) {
1283
+ cell.removeAttribute('data-interaction-open');
1284
+ setCellStatus(cell, '✓ Account identity repaired.');
1285
+ await tick();
1286
+ return;
1287
+ }
1288
+ const msg = r.json && r.json.error
1289
+ ? r.json.error
1290
+ : 'Couldn’t verify this account from its signed-in credential.';
1291
+ setCellStatus(cell, msg);
1292
+ btn.removeAttribute('disabled');
1293
+ return;
1294
+ }
1248
1295
  const r = await postJson(URLS.startCell, { accountId, machineId, pin });
1249
1296
  if (pinInput) pinInput.value = ''; // PIN is memory-only — clear it immediately
1250
1297
  if (r.ok && r.json && r.json.verificationUrl) {
@@ -1257,8 +1304,15 @@ export function createController(opts) {
1257
1304
  ttlExpiresAt: r.json.ttlExpiresAt, notice: r.json.notice, kind: r.json.kind,
1258
1305
  });
1259
1306
  } else if (r.status === 409) {
1260
- state.matrixTransient[`${accountId}::${machineId}`] = { state: 'cant-resolve', at: now() };
1261
- setCellStatus(cell, 'Can’t set this account up here — its details couldn’t be resolved.');
1307
+ const code = r.json && r.json.code;
1308
+ state.matrixTransient[`${accountId}::${machineId}`] = {
1309
+ state: code === 'account-record-missing-email' ? 'email-missing' : 'cant-resolve',
1310
+ at: now(),
1311
+ };
1312
+ const message = r.json && r.json.error
1313
+ ? r.json.error
1314
+ : 'Can’t set this account up here — its identity details could not be verified.';
1315
+ setCellStatus(cell, message);
1262
1316
  btn.removeAttribute('disabled');
1263
1317
  } else {
1264
1318
  const msg = (r.json && (r.json.error || r.json.reason)) ? (r.json.error || r.json.reason) : `failed (${r.status})`;
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/commands/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AA4EH,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAU3D,OAAO,EAAE,eAAe,EAAiC,MAAM,iCAAiC,CAAC;AA0BjG,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAGvD,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AA8H7D,OAAO,KAAK,EAAW,oBAAoB,EAAe,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACjG,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAgFtD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,gBAAgB,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAC1C,OAAO,CAUT;AAuID,UAAU,YAAY;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;2DACuD;IACvD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AA+rDD;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,YAAY,EACpB,YAAY,EAAE,oBAAoB,GAAG,IAAI,GACxC;IACD,YAAY,EAAE,oBAAoB,GAAG,IAAI,CAAC;IAC1C,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,OAAO,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAAC;IAC3C,QAAQ,EAAE,MAAM,CAAC;CAClB,CAqBA;AAED,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,EAQrD,oBAAoB,CAAC,EAAE,MAAM;IAC3B,YAAY,EAAE,OAAO,kBAAkB,EAAE,oBAAoB,GAAG,IAAI,CAAC;IACrE,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,OAAO,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAAC;IAC3C,QAAQ,EAAE,MAAM,CAAC;CAClB,GAAG,IAAI,GACP,IAAI,CAwtBN;AAgmBD,wBAAsB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CA24pBtE;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;AA4EH,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAU3D,OAAO,EAAE,eAAe,EAAiC,MAAM,iCAAiC,CAAC;AA0BjG,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAGvD,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AA8H7D,OAAO,KAAK,EAAW,oBAAoB,EAAe,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACjG,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAgFtD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,gBAAgB,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAC1C,OAAO,CAUT;AAuID,UAAU,YAAY;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;2DACuD;IACvD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AA+rDD;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,YAAY,EACpB,YAAY,EAAE,oBAAoB,GAAG,IAAI,GACxC;IACD,YAAY,EAAE,oBAAoB,GAAG,IAAI,CAAC;IAC1C,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,OAAO,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAAC;IAC3C,QAAQ,EAAE,MAAM,CAAC;CAClB,CAqBA;AAED,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,EAQrD,oBAAoB,CAAC,EAAE,MAAM;IAC3B,YAAY,EAAE,OAAO,kBAAkB,EAAE,oBAAoB,GAAG,IAAI,CAAC;IACrE,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,OAAO,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAAC;IAC3C,QAAQ,EAAE,MAAM,CAAC;CAClB,GAAG,IAAI,GACP,IAAI,CAwtBN;AAgmBD,wBAAsB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAs7pBtE;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"}
@@ -12165,6 +12165,7 @@ export async function startServer(options) {
12165
12165
  // compat); only UNKNOWN mode (corrupt on-disk) raises a HIGH attention item, never throws.
12166
12166
  const { CredentialLocationLedger, shouldBootSeedCredentialLedger, shouldRunIdentityAudit } = await import('../core/CredentialLocationLedger.js');
12167
12167
  const { CredentialIdentityOracle } = await import('../core/CredentialIdentityOracle.js');
12168
+ const credentialIdentityOracle = new CredentialIdentityOracle();
12168
12169
  const { CredentialLocationGate } = await import('../core/CredentialLocationGate.js');
12169
12170
  const credentialGateEmitAttention = telegram
12170
12171
  ? (item) => void telegram.createAttentionItem({
@@ -12180,9 +12181,24 @@ export async function startServer(options) {
12180
12181
  const credentialLocationLedger = new CredentialLocationLedger({
12181
12182
  stateDir: config.stateDir,
12182
12183
  pool: subscriptionPool,
12183
- oracle: new CredentialIdentityOracle(),
12184
+ oracle: credentialIdentityOracle,
12184
12185
  emitAttention: credentialGateEmitAttention,
12185
12186
  });
12187
+ // One-shot legacy identity reconciliation. It reads only each record's own
12188
+ // credential slot through the real provider oracle; unresolved gaps remain
12189
+ // quarantined and visible instead of being guessed from metadata.
12190
+ const { repairMissingSubscriptionEmails } = await import('../core/SubscriptionAccountEmailRepair.js');
12191
+ const { SubscriptionAccountEmailRegistrar, SubscriptionEmailReconciliationBarrier, } = await import('../core/SubscriptionPool.js');
12192
+ const subscriptionEmailBarrier = new SubscriptionEmailReconciliationBarrier();
12193
+ const subscriptionEmailRegistrar = new SubscriptionAccountEmailRegistrar(subscriptionPool, credentialIdentityOracle, credentialLocationLedger);
12194
+ const runSubscriptionEmailRepair = async () => {
12195
+ const emailRepair = await repairMissingSubscriptionEmails(subscriptionPool, credentialIdentityOracle, credentialLocationLedger);
12196
+ subscriptionEmailBarrier.finish(emailRepair.unresolved);
12197
+ if (emailRepair.repaired.length > 0 || emailRepair.unresolved.length > 0) {
12198
+ console.log(pc.yellow(` Subscription email reconciliation: ${emailRepair.repaired.length} repaired, ` +
12199
+ `${emailRepair.unresolved.length} unresolved`));
12200
+ }
12201
+ };
12186
12202
  // The §2.10 env-token gate (Step 8): the §0.b applicability precondition, enforced. Evaluates
12187
12203
  // BOTH `config.anthropicApiKey` (read LIVE per call — restartless) AND the live running fleet's
12188
12204
  // durable per-session `credentialSource` flag, so a mid-run flip to an env token cannot silently
@@ -12424,9 +12440,10 @@ export async function startServer(options) {
12424
12440
  // their respective awaits. isSeeded() does NOT protect this (seedFromOracle bumps version at
12425
12441
  // 'begin', so isSeeded() flips true mid-seed), so the audit gate also checks this flag.
12426
12442
  let credSeedInFlight = false;
12427
- if (shouldBootSeedCredentialLedger(resolveDevAgentGate(config.subscriptionPool?.credentialRepointing?.enabled, config), credentialLocationLedger.isSeeded())) {
12443
+ const shouldSeedCredentialLedger = shouldBootSeedCredentialLedger(resolveDevAgentGate(config.subscriptionPool?.credentialRepointing?.enabled, config), credentialLocationLedger.isSeeded());
12444
+ const seedCredentialLedger = () => {
12428
12445
  credSeedInFlight = true;
12429
- void credentialLocationLedger
12446
+ return credentialLocationLedger
12430
12447
  .seedFromOracle()
12431
12448
  .then((outcomes) => {
12432
12449
  const assigned = outcomes.filter((o) => o.result === 'assigned').length;
@@ -12434,7 +12451,21 @@ export async function startServer(options) {
12434
12451
  })
12435
12452
  .catch((e) => console.warn(`[CredentialLedger] boot seed failed: ${e instanceof Error ? e.message : String(e)}`))
12436
12453
  .finally(() => { credSeedInFlight = false; });
12437
- }
12454
+ };
12455
+ // A legacy email gap must be repaired against an already-existing,
12456
+ // independent slot→tenant binding. seedFromOracle derives its candidates
12457
+ // from complete pool rows and clears assignments first, so running it before
12458
+ // repair could erase the only proof for an email-less row. Preserve that
12459
+ // evidence: seed only when there are no quarantined gaps.
12460
+ let credentialSeedReady = shouldSeedCredentialLedger && subscriptionPool.listEmailGaps().length === 0
12461
+ ? seedCredentialLedger()
12462
+ : Promise.resolve();
12463
+ void credentialSeedReady
12464
+ .then(() => runSubscriptionEmailRepair())
12465
+ .catch((error) => {
12466
+ subscriptionEmailBarrier.finish(subscriptionPool.listEmailGaps().length);
12467
+ console.warn(`[subscription-email] reconciliation degraded: ${error instanceof Error ? error.message : String(error)}`);
12468
+ });
12438
12469
  // B3b — the periodic balancer pass. tick() is a strict no-op while the feature resolves dark
12439
12470
  // (so the timer can always run; the gate lives INSIDE tick()), and on a dev agent it runs the
12440
12471
  // full decision loop in dry-run (zero writes). REENTRANCY-GUARDED: a slow tick never overlaps
@@ -12600,8 +12631,8 @@ export async function startServer(options) {
12600
12631
  // WS5.2 §5.3/S7 — the follow-me completion gate reads the minted login's account email
12601
12632
  // from its config-home slot (the Anthropic OAuth profile endpoint) and validates it against
12602
12633
  // operator expectation before the account is selectable. Same oracle the credential-location
12603
- // ledger uses; constructed fresh here (the ledger's instance is not in scope at this point).
12604
- oracle: new CredentialIdentityOracle(),
12634
+ // ledger uses; one process-wide oracle keeps identity evidence coherent.
12635
+ oracle: credentialIdentityOracle,
12605
12636
  // A HELD follow-me completion (surprise/mismatched/unverifiable email) raises a HIGH
12606
12637
  // attention item for the operator. Map the email-gate's {id,title,body,priority,source}
12607
12638
  // shape onto the telegram attention-queue createAttentionItem shape.
@@ -12683,13 +12714,13 @@ export async function startServer(options) {
12683
12714
  // Upsert (D5): a re-auth of an EXISTING pool account updates it back to
12684
12715
  // active; only a genuinely-new account is added (add() refuses dup ids).
12685
12716
  if (subscriptionPool.get(login.id)) {
12686
- subscriptionPool.update(login.id, {
12687
- nickname: login.label, status: 'active', email,
12717
+ subscriptionEmailRegistrar.completeValidated(login.id, email, {
12718
+ nickname: login.label, status: 'active',
12688
12719
  ...(login.configHome ? { configHome: login.configHome } : {}),
12689
12720
  });
12690
12721
  }
12691
12722
  else {
12692
- subscriptionPool.add({
12723
+ subscriptionEmailRegistrar.completeNewValidated({
12693
12724
  id: login.id, nickname: login.label, provider: login.provider,
12694
12725
  framework: login.framework, configHome: login.configHome ?? '', status: 'active', email,
12695
12726
  });
@@ -23272,7 +23303,7 @@ export async function startServer(options) {
23272
23303
  catch (err) { /* @silent-fallback-ok: fleet-dark optional observer; failure is logged and adds no authority */
23273
23304
  console.warn('[AutonomousThroughputFloor] init failed:', err.message);
23274
23305
  }
23275
- const server = new AgentServer({ config, sessionManager, llmQueue: sharedLlmQueue, state, scheduler, telegram, relationships, feedback, feedbackAnomalyDetector, dispatches, updateChecker, autoUpdater, autoDispatcher, quotaTracker, quotaManager, publisher, viewer, tunnel, evolution, watchdog, topicMemory, triageNurse, projectMapper, cartographer: cartographer ?? undefined, coherenceGate: scopeVerifier, contextHierarchy, canonicalState, operationGate, sentinel, adaptiveTrust, memoryMonitor, orphanReaper, coherenceMonitor, commitmentTracker, subscriptionPool, accountFollowMePeerViews: async () => { const nickById = new Map((_listPoolMachines?.() ?? []).map((m) => [m.machineId, m.nickname ?? m.machineId])); let peers = (_resolvePeerUrls?.() ?? []).map((p) => ({ machineId: p.machineId, nickname: nickById.get(p.machineId) ?? p.machineId, url: p.url })); if (peers.length === 0) {
23306
+ const server = new AgentServer({ config, subscriptionEmailBinding: credentialLocationLedger, subscriptionEmailBarrier, subscriptionIdentityOracle: credentialIdentityOracle, sessionManager, llmQueue: sharedLlmQueue, state, scheduler, telegram, relationships, feedback, feedbackAnomalyDetector, dispatches, updateChecker, autoUpdater, autoDispatcher, quotaTracker, quotaManager, publisher, viewer, tunnel, evolution, watchdog, topicMemory, triageNurse, projectMapper, cartographer: cartographer ?? undefined, coherenceGate: scopeVerifier, contextHierarchy, canonicalState, operationGate, sentinel, adaptiveTrust, memoryMonitor, orphanReaper, coherenceMonitor, commitmentTracker, subscriptionPool, accountFollowMePeerViews: async () => { const nickById = new Map((_listPoolMachines?.() ?? []).map((m) => [m.machineId, m.nickname ?? m.machineId])); let peers = (_resolvePeerUrls?.() ?? []).map((p) => ({ machineId: p.machineId, nickname: nickById.get(p.machineId) ?? p.machineId, url: p.url })); if (peers.length === 0) {
23276
23307
  peers = (_listPoolMachines?.() ?? []).filter((m) => m.machineId !== _meshSelfId && !!m.lastKnownUrl).map((m) => ({ machineId: m.machineId, nickname: m.nickname ?? m.machineId, url: m.lastKnownUrl }));
23277
23308
  } if (peers.length === 0)
23278
23309
  return []; const { fetchPeerSubscriptionViews } = await import('../core/fetchPeerSubscriptionViews.js'); return fetchPeerSubscriptionViews({ peers: () => peers, fetchImpl: fetch, authToken: config.authToken ?? '' }); }, quotaPoller, quotaAwareScheduler: _quotaAwareScheduler ?? undefined, proactiveSwapMonitor: _proactiveSwapMonitor ?? undefined, inUseAccountResolver, enrollmentWizard, accountFollowMeRevocation, credentialRepointing, semanticMemory, activitySentinel, rateLimitSentinel, releaseReadinessSentinel: releaseReadinessSentinel ?? undefined, greenPrAutoMerger: greenPrAutoMerger ?? undefined, guardLatchStore: guardLatchStore ?? undefined, messageRouter, summarySentinel, spawnManager, systemReviewer, capabilityMapper, selfKnowledgeTree, coverageAuditor, topicResumeMap: _topicResumeMap ?? undefined, topicProfile: _topicProfileCtx ?? undefined, sessionRefresh: _sessionRefresh ?? undefined, autonomyManager, trustElevationTracker, autonomousEvolution, coordinator: coordinator.enabled ? coordinator : undefined, meshBindActive: coordinator.managers.identityManager.hasIdentity() && config.multiMachine?.meshTransport?.enabled !== false, localSigningKeyPem, leaseTransport, peerEndpointRecorder, getSelfMeshEndpoints, onLeasePullRequest: () => leaseCoordinatorRef?.currentLease() ?? null, liveTailReceiver, handoffWireTransport, onHandoffBegin, onHandoffInitiate: handoffInitiate, handoffInProgress: handoffSentinelInProgress, messageLedger, currentInboundByTopic, replyMarkerTransport, onReplyMarker: messageLedger ? (marker) => { const m = marker; messageLedger.applyRemoteReplyMarker(m.dedupeKey, { platform: m.platform, replyIdempotencyKey: m.replyIdempotencyKey, epoch: m.epoch, topic: m.topic ?? null }); } : undefined, whatsapp: whatsappAdapter, slack: slackAdapter, imessage: imessageAdapter, conversationRegistry, conversationBindAuth, conversationFollowThrough, whatsappBusinessBackend, messageBridge, hookEventReceiver, worktreeMonitor, subagentTracker, instructionsVerifier, handshakeManager: threadlineHandshake, threadlineRouter, conversationStore, threadLog, threadMessageRecorder, warrantsReplyGate, collaborationSurfacer, threadResumeMap, topicLinkageHandler: topicLinkageHandler ?? undefined, threadlineRelayClient, threadlineReplyWaiters, listenerManager: listenerManager ?? undefined, a2aDeliveryTracker: a2aDeliveryTracker ?? undefined, responseReviewGate, reviewCanaryBattery, messagingToneGate, outboundDedupGate, telemetryHeartbeat, pasteManager, featureRegistry, discoveryEvaluator, completionEvaluator, unifiedTrust, liveConfig, sharedStateLedger, ledgerSessionRegistry, worktreeManager, oidcEnrolledRepos: parallelDevConfig?.oidcEnrolledRepos, initiativeTracker, projectRoundRunner, projectDriftChecker, machineHeartbeat, machinePoolRegistry, ropeHealthMonitor, writeAdmission: writeAdmission ?? undefined, getInboundQueue: () => _inboundQueue, getMachineCoherence: () => _machineCoherenceSentinel, getSingleMachineFailoverGap: () => _singleMachineFailoverGap, getMissingLoginSession: () => _missingLoginSession, getSessionPoolFailoverRunner: () => _sessionPoolFailoverRunnerDriver?.status() ?? null, sessionPoolPromotionActivation: _sessionPoolPromotionActivation, meshRpcDispatcher, deliverA2aToMachine: _deliverA2aToMachine ?? undefined, workingSetPullCoordinator, workingSetArtifactManager, orchestratorPoller, commitmentReplicaStore, preferenceReplicaStore, replicatedRecordEmitter, conflictStore, rollbackUnmerge, droppedOriginRegistry, preferencesUnionReader, forwardCommitmentMutate, sessionOwnershipRegistry, sendDrain: _sendDrain ?? undefined, topicPinStore: _topicPinStore ?? undefined, topicPinSkewQuarantine: _topicPinSkewQuarantine ?? undefined, topicPinFoldView: _topicPinFoldView ?? undefined, ownershipReconciler: _ownershipReconciler ?? undefined, staleOwnerEngine: _staleOwnerEngine ?? undefined, duplicateReconciler: _duplicateReconciler ?? undefined, ownerDarkLadder: _ownerDarkLadder ?? undefined, spawnAdmission: _spawnAdmission ?? undefined, judgmentProvenance: _judgmentProvenance ?? undefined, leaseHandback: _leaseHandbackCtx ?? undefined, streamTicketStore: _streamTicketStore ?? undefined, poolStreamAllowRemoteInput: config.dashboard?.poolStream?.allowRemoteInput ?? false, poolStreamConnector: _poolStreamConnector ?? undefined, secretSync: _secretSyncHandle ?? undefined, meshSelfId: _meshSelfId ?? undefined, resolveRouterUrl: _resolveRouterUrl ?? undefined, resolvePeerUrls: _resolvePeerUrls ?? undefined, guardRegistry, listPoolMachines: _listPoolMachines ?? undefined, deliverMandateToMachine: _deliverMandateToMachine ?? undefined, poolLink: _poolLink ?? undefined, poolPollCache: _poolPollCache ?? undefined, sessionPoolE2EResultStore, proxyCoordinator, topicIntentStore, topicIntentArcCheck, usherSignalStore, intelligence: sharedIntelligence ?? undefined, telegramBridgeConfig, telegramBridge: telegramBridge ?? undefined, threadlineObservability, briefDeps, workingMemory, taskFlowRegistry, threadlineFlowBridge, sessionReaper, agentWorktreeReaper, externalHogSentinel, orphanedWorkSentinel, mcpProcessReaper, geminiLoopRunner, sleepController, agentActivityState, reapLog, resumeQueue, resumeDrainer, autonomousLivenessReconciler, enforcedTerminationStatus: () => enforcedTerminationWatchdog?.guardStatus() ?? null, prHandLease: prHandLease ?? undefined, operatorStopRecorder: recordOperatorStop, sleepWakeDetector, unjustifiedStopGate, stopGateDb, stopNotifier, liveTestGate, liveTestGateMode, liveTestRunnerCtx }); // Resolve the late-bound topic-operator getter (increment 2e): routing was