instar 1.3.704 → 1.3.705
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 +216 -17
- package/dist/commands/server.js.map +1 -1
- package/dist/core/DeliverMessageHandler.d.ts +16 -0
- package/dist/core/DeliverMessageHandler.d.ts.map +1 -1
- package/dist/core/DeliverMessageHandler.js +0 -0
- package/dist/core/DeliverMessageHandler.js.map +1 -1
- package/dist/core/PostUpdateMigrator.d.ts +15 -0
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +108 -0
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/core/QueueDrainLoop.d.ts +6 -2
- package/dist/core/QueueDrainLoop.d.ts.map +1 -1
- package/dist/core/QueueDrainLoop.js +0 -0
- package/dist/core/QueueDrainLoop.js.map +1 -1
- package/dist/core/SessionRouter.d.ts +24 -4
- package/dist/core/SessionRouter.d.ts.map +1 -1
- package/dist/core/SessionRouter.js +25 -2
- package/dist/core/SessionRouter.js.map +1 -1
- package/dist/core/meshRejectionLog.d.ts +37 -0
- package/dist/core/meshRejectionLog.d.ts.map +1 -0
- package/dist/core/meshRejectionLog.js +73 -0
- package/dist/core/meshRejectionLog.js.map +1 -0
- package/dist/core/registryHighWater.d.ts +59 -0
- package/dist/core/registryHighWater.d.ts.map +1 -0
- package/dist/core/registryHighWater.js +120 -0
- package/dist/core/registryHighWater.js.map +1 -0
- package/dist/core/senderRejectionNotice.d.ts +89 -0
- package/dist/core/senderRejectionNotice.d.ts.map +1 -0
- package/dist/core/senderRejectionNotice.js +149 -0
- package/dist/core/senderRejectionNotice.js.map +1 -0
- package/dist/core/senderValidationGate.d.ts +80 -0
- package/dist/core/senderValidationGate.d.ts.map +1 -0
- package/dist/core/senderValidationGate.js +133 -0
- package/dist/core/senderValidationGate.js.map +1 -0
- package/dist/core/types.d.ts +12 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/messaging/MessageProcessingLedger.d.ts +33 -5
- package/dist/messaging/MessageProcessingLedger.d.ts.map +1 -1
- package/dist/messaging/MessageProcessingLedger.js +50 -7
- package/dist/messaging/MessageProcessingLedger.js.map +1 -1
- package/dist/messaging/ingressDedup.d.ts.map +1 -1
- package/dist/messaging/ingressDedup.js +9 -0
- package/dist/messaging/ingressDedup.js.map +1 -1
- package/dist/scaffold/templates.d.ts.map +1 -1
- package/dist/scaffold/templates.js +2 -1
- package/dist/scaffold/templates.js.map +1 -1
- 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 +36 -0
- package/dist/server/routes.js.map +1 -1
- package/dist/users/UserManager.d.ts +22 -1
- package/dist/users/UserManager.d.ts.map +1 -1
- package/dist/users/UserManager.js +87 -2
- package/dist/users/UserManager.js.map +1 -1
- package/dist/users/testIdentityMarkers.d.ts +94 -0
- package/dist/users/testIdentityMarkers.d.ts.map +1 -0
- package/dist/users/testIdentityMarkers.js +170 -0
- package/dist/users/testIdentityMarkers.js.map +1 -0
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +63 -63
- package/src/scaffold/templates.ts +2 -1
- package/upgrades/1.3.705.md +79 -0
- package/upgrades/side-effects/silent-loss-refusal-conservation.md +124 -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;AA4H7D,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;AAyID,UAAU,YAAY;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;2DACuD;IACvD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AA07CD,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,CAijBN;AA2lBD,wBAAsB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAq+hBtE;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
|
@@ -127,7 +127,10 @@ import { HandoffWireTransport } from '../core/HandoffWireTransport.js';
|
|
|
127
127
|
import { createHandoffReceiverWiring } from '../core/handoffReceiverWiring.js';
|
|
128
128
|
import { createHandoffSentinelBootWiring } from '../core/handoffSentinelBootWiring.js';
|
|
129
129
|
import { MessageProcessingLedger } from '../messaging/MessageProcessingLedger.js';
|
|
130
|
+
import { dedupeKeyFor } from '../messaging/ingressDedup.js';
|
|
130
131
|
import { recoverStuckMessages } from '../messaging/stuckMessageRecovery.js';
|
|
132
|
+
import { SenderRejectionNoticer, SENDER_DEAUTHORIZED_CAUSE } from '../core/senderRejectionNotice.js';
|
|
133
|
+
import { appendMeshRejection } from '../core/meshRejectionLog.js';
|
|
131
134
|
import { ReplyMarkerTransport } from '../core/ReplyMarkerTransport.js';
|
|
132
135
|
import { decryptFromSync, encryptForSync } from '../core/SecretStore.js';
|
|
133
136
|
import { createPrivateKey, createPublicKey, createHash } from 'node:crypto';
|
|
@@ -192,6 +195,7 @@ import { DEFAULT_RELAY_HOST } from '../threadline/constants.js';
|
|
|
192
195
|
import { createUnifiedTrustSystem } from '../threadline/UnifiedTrustWiring.js';
|
|
193
196
|
import { toInjection } from '../types/pipeline.js';
|
|
194
197
|
import { UserManager } from '../users/UserManager.js';
|
|
198
|
+
import { loadTestIdentityKey } from '../users/testIdentityMarkers.js';
|
|
195
199
|
import { formatUserContextForSession, hasUserContext } from '../users/UserContextBuilder.js';
|
|
196
200
|
import { SafeFsExecutor } from '../core/SafeFsExecutor.js';
|
|
197
201
|
// setup.ts uses @inquirer/prompts which requires Node 20.12+
|
|
@@ -483,6 +487,11 @@ let _meshSelfId = null;
|
|
|
483
487
|
* strict no-op (always spawn locally, byte-for-byte legacy). Set in startServer's
|
|
484
488
|
* mesh block from the lease coordinator. */
|
|
485
489
|
let _holdsLeaseForSpawn = null;
|
|
490
|
+
/** silent-loss-refusal-conservation §2.C — the unified sender-rejection loss
|
|
491
|
+
* noticer. Constructed in startServer (needs messageLedger + notify + userManager);
|
|
492
|
+
* referenced module-wide (incl. wireTelegramRouting's live consumer) via this ref.
|
|
493
|
+
* null = not yet wired → the consumer falls back to the deterministic adapter send. */
|
|
494
|
+
let _senderRejectionNoticer = null;
|
|
486
495
|
/** G3: the ownershipCheckedSpawn flag accessor (multiMachine.sessionPool.
|
|
487
496
|
* ownershipCheckedSpawn). Defaults dark (no-op). Set in startServer. */
|
|
488
497
|
let _ownershipCheckedSpawn = () => ({ enabled: false, dryRun: true });
|
|
@@ -2004,6 +2013,33 @@ getAttentionTopicId) {
|
|
|
2004
2013
|
// box (the recognizer logs its pin, but route()'s actual placement/forward
|
|
2005
2014
|
// decision was invisible; that hid the bug below from the first live test).
|
|
2006
2015
|
console.log(`[session-pool] route topic ${topicId} → action=${outcome.action} owner=${outcome.owner ?? '?'} self=${_meshSelfId ?? '?'} acked=${outcome.acked}`);
|
|
2016
|
+
// silent-loss-refusal-conservation §2.A/§2.C — a first-class terminal
|
|
2017
|
+
// `rejected` (the owner re-validated the sender and refused) is NOT a
|
|
2018
|
+
// successful forward: TELL the user via the ORIGINATING adapter and
|
|
2019
|
+
// terminal-return WITHOUT local dispatch, BEFORE the isRemotelyHandled
|
|
2020
|
+
// test (which returns false for 'rejected' → would otherwise fall through
|
|
2021
|
+
// to a local inject the owner already refused). User-originated only (a
|
|
2022
|
+
// real platform uid + a bound topic — job/sentinel/canary keys excluded).
|
|
2023
|
+
if (outcome.action === 'rejected') {
|
|
2024
|
+
console.log(`[session-pool] topic ${topicId} REJECTED (${outcome.detail ?? 'sender-deauthorized'}) — not dispatching locally; notifying user`);
|
|
2025
|
+
if (telegramUserId) {
|
|
2026
|
+
const noticer = _senderRejectionNoticer;
|
|
2027
|
+
if (noticer) {
|
|
2028
|
+
noticer.onRejected({
|
|
2029
|
+
adapter: 'telegram',
|
|
2030
|
+
topicId,
|
|
2031
|
+
messageId: dedupeKeyFor('telegram', topicId, msg.id),
|
|
2032
|
+
senderUid: telegramUserId,
|
|
2033
|
+
peer: outcome.owner ?? undefined,
|
|
2034
|
+
});
|
|
2035
|
+
}
|
|
2036
|
+
else {
|
|
2037
|
+
// Noticer not yet wired — the deterministic adapter send is the floor.
|
|
2038
|
+
telegram.sendToTopic(topicId, "I got your message but couldn't confirm you as an approved sender, so it wasn't delivered. I've logged the details so this can be diagnosed.").catch(() => { });
|
|
2039
|
+
}
|
|
2040
|
+
}
|
|
2041
|
+
return;
|
|
2042
|
+
}
|
|
2007
2043
|
// Short-circuit local dispatch whenever the session ended up on ANOTHER machine
|
|
2008
2044
|
// (forward/duplicate, OR a fresh remote 'spawned'/'owner-dead-replaced'). Before
|
|
2009
2045
|
// this, only 'forwarded'/'duplicate' were caught, so a just-moved topic was
|
|
@@ -2214,7 +2250,11 @@ getAttentionTopicId) {
|
|
|
2214
2250
|
?? _topicPinStore?.asTopicMetadata(dmsg.sessionKey),
|
|
2215
2251
|
senderEnvelope: dmsg.senderEnvelope,
|
|
2216
2252
|
});
|
|
2217
|
-
|
|
2253
|
+
// silent-loss-refusal-conservation §2.A — forwardToOwner no longer masquerades
|
|
2254
|
+
// a refusal as `forwarded`; it returns the first-class terminal `rejected`.
|
|
2255
|
+
// Map it to the drain's `sender-rejected` disposition (→ terminal
|
|
2256
|
+
// `sender-deauthorized` + the unified §2.C loss notice via reportLoss).
|
|
2257
|
+
if (outcome.action === 'rejected') {
|
|
2218
2258
|
return { kind: 'sender-rejected' };
|
|
2219
2259
|
}
|
|
2220
2260
|
if (isRemotelyHandled(outcome, _meshSelfId)) {
|
|
@@ -5744,7 +5784,12 @@ export async function startServer(options) {
|
|
|
5744
5784
|
// collector runs even when the lifeline owns Telegram polling. See the
|
|
5745
5785
|
// "Account switcher + quota collector pipeline" section above.
|
|
5746
5786
|
// Initialize persistent UserManager for user identity resolution (Gap 8)
|
|
5747
|
-
|
|
5787
|
+
// silent-loss-refusal-conservation §2.D — the authoritative UserManager
|
|
5788
|
+
// carries the server-held allow-marker key so a legitimate fixture-collision
|
|
5789
|
+
// profile's signed `allowTestIdentity` VERIFIES on load (a bogus marker is
|
|
5790
|
+
// quarantined). Read-only probes elsewhere construct WITHOUT the key (fixtures
|
|
5791
|
+
// skipped — the safe direction).
|
|
5792
|
+
const userManager = new UserManager(config.stateDir, config.users, { testIdentityKey: loadTestIdentityKey(config.stateDir) });
|
|
5748
5793
|
attachUserReplication(userManager); // WS2.6 send-side (dark by default)
|
|
5749
5794
|
// Fix command dependencies — populated later when subsystems initialize.
|
|
5750
5795
|
// Uses a mutable ref so wireTelegramRouting can capture it in a closure now.
|
|
@@ -6559,6 +6604,30 @@ export async function startServer(options) {
|
|
|
6559
6604
|
channel: { platform: 'slack', workspaceId: _slackAdapter?.getWorkspaceId(), channelId },
|
|
6560
6605
|
});
|
|
6561
6606
|
console.log(`[session-pool] slack route key=${routingKey} → action=${outcome.action} owner=${outcome.owner ?? '?'} self=${_meshSelfId ?? '?'} acked=${outcome.acked}`);
|
|
6607
|
+
// silent-loss-refusal-conservation §2.A/§2.C — a first-class terminal
|
|
6608
|
+
// `rejected` is NOT a successful forward: reply in-thread on the
|
|
6609
|
+
// originating Slack channel and terminal-return WITHOUT local dispatch,
|
|
6610
|
+
// BEFORE the isRemotelyHandled test. Slack NOTICE parity ships now;
|
|
6611
|
+
// Slack SENDER re-validation is tracked-followup 4.
|
|
6612
|
+
if (outcome.action === 'rejected') {
|
|
6613
|
+
console.log(`[session-pool] slack key ${routingKey} REJECTED (${outcome.detail ?? 'sender-deauthorized'}) — not dispatching locally; notifying user`);
|
|
6614
|
+
const slackUid = message.metadata?.slackUserId || undefined;
|
|
6615
|
+
if (slackUid) {
|
|
6616
|
+
const noticer = _senderRejectionNoticer;
|
|
6617
|
+
if (noticer) {
|
|
6618
|
+
noticer.onRejected({
|
|
6619
|
+
adapter: 'slack',
|
|
6620
|
+
slackKey: routingKey,
|
|
6621
|
+
messageId: dedupeKeyFor('slack', routingKey, String(message.id)),
|
|
6622
|
+
peer: outcome.owner ?? undefined,
|
|
6623
|
+
});
|
|
6624
|
+
}
|
|
6625
|
+
else {
|
|
6626
|
+
slackAdapter.sendToChannel(channelId, "I got your message but couldn't confirm you as an approved sender, so it wasn't delivered. I've logged the details so this can be diagnosed.").catch(() => { });
|
|
6627
|
+
}
|
|
6628
|
+
}
|
|
6629
|
+
return;
|
|
6630
|
+
}
|
|
6562
6631
|
if (isRemotelyHandled(outcome, _meshSelfId)) {
|
|
6563
6632
|
console.log(`[session-pool] slack key ${routingKey} handled by owner ${outcome.owner ?? '?'} (${outcome.action}) — not dispatching locally`);
|
|
6564
6633
|
return;
|
|
@@ -15284,6 +15353,46 @@ export async function startServer(options) {
|
|
|
15284
15353
|
console.error(`[exactly-once] ledger open failed, gate stays dark: ${err instanceof Error ? err.message : err}`);
|
|
15285
15354
|
}
|
|
15286
15355
|
}
|
|
15356
|
+
// ── Sender-rejection loss noticer (silent-loss-refusal-conservation §2.C) ──
|
|
15357
|
+
// The single funnel the live consumer (Telegram/Slack) AND the queue-drain loss
|
|
15358
|
+
// path route through so a first-class `rejected` outcome ALWAYS tells the user,
|
|
15359
|
+
// deduped + ceilinged. Durable per-messageId dedupe uses the ledger's
|
|
15360
|
+
// `markRejected` marker when the ledger is present; otherwise the in-memory
|
|
15361
|
+
// 30-min window bounds it. Divergence probe reads a fresh read-only UserManager.
|
|
15362
|
+
{
|
|
15363
|
+
const ledgerForNotice = messageLedger;
|
|
15364
|
+
_senderRejectionNoticer = new SenderRejectionNoticer({
|
|
15365
|
+
// @silent-fallback-ok: fire-and-forget notice send; the deterministic
|
|
15366
|
+
// delivery path + DeliveryFailureSentinel own retry, not this callsite.
|
|
15367
|
+
sendTelegram: (topicId, text) => { telegram?.sendToTopic(topicId, text).catch(() => { }); },
|
|
15368
|
+
sendSlack: (slackKey, text) => {
|
|
15369
|
+
const channelId = slackKey.split(':')[0];
|
|
15370
|
+
// @silent-fallback-ok: fire-and-forget notice send (see sendTelegram).
|
|
15371
|
+
_slackAdapter?.sendToChannel(channelId, text).catch(() => { });
|
|
15372
|
+
},
|
|
15373
|
+
alertHub: (title, body) => notify('IMMEDIATE', 'sender-rejection', `${title}: ${body}`),
|
|
15374
|
+
markRejectedDurable: ledgerForNotice
|
|
15375
|
+
// @silent-fallback-ok: a dedupe-ledger fault returns true (treat as
|
|
15376
|
+
// first-transition → SEND the notice) — fails toward TELLING the user.
|
|
15377
|
+
? (messageId) => { try {
|
|
15378
|
+
return ledgerForNotice.markRejected(messageId, 0, { platform: 'mesh' });
|
|
15379
|
+
}
|
|
15380
|
+
catch {
|
|
15381
|
+
return true;
|
|
15382
|
+
} }
|
|
15383
|
+
: undefined,
|
|
15384
|
+
resolvesLocally: (uid) => {
|
|
15385
|
+
// @silent-fallback-ok: divergence-probe read helper; a resolve fault →
|
|
15386
|
+
// false (no divergence signal this tick), advisory only.
|
|
15387
|
+
try {
|
|
15388
|
+
return new UserManager(config.stateDir).resolveFromTelegramUserId(uid) !== null;
|
|
15389
|
+
}
|
|
15390
|
+
catch {
|
|
15391
|
+
return false;
|
|
15392
|
+
}
|
|
15393
|
+
},
|
|
15394
|
+
});
|
|
15395
|
+
}
|
|
15287
15396
|
// ── ReleaseReadinessSentinel (Layer B of release-readiness-visibility) ──
|
|
15288
15397
|
// Repo-gated dev-environment watchdog: only constructed when the install has
|
|
15289
15398
|
// an analyzable instar git repo AND the feature is enabled in config. On a
|
|
@@ -16366,6 +16475,76 @@ export async function startServer(options) {
|
|
|
16366
16475
|
// activation; the durable receipt + ACK is the complete dark-phase contract.)
|
|
16367
16476
|
const deliverMod = await import('../core/DeliverMessageHandler.js');
|
|
16368
16477
|
const deliverSeenFallback = new Set(); // used only if the SQLite ledger is unavailable
|
|
16478
|
+
// silent-loss-refusal-conservation §2.D — the wiring-time registry gate.
|
|
16479
|
+
// A stat-gated, READ-ONLY (no initialUsers merge — the old closure passed
|
|
16480
|
+
// config.users, which can writeFileSync per message) resolver + the
|
|
16481
|
+
// degenerate/unknown-unsafe/operator-resolution arm decision. Constructed
|
|
16482
|
+
// ONCE; the operator-uid probe reads the LOCAL topic-operator binding of the
|
|
16483
|
+
// deciding machine (KYP) via the late-bound AgentServer ref.
|
|
16484
|
+
const { SenderValidationGate } = await import('../core/senderValidationGate.js');
|
|
16485
|
+
const _svUsersFile = path.join(config.stateDir, 'users.json');
|
|
16486
|
+
const _svStatUsers = () => {
|
|
16487
|
+
// @silent-fallback-ok: stat fault → null (ENOENT/unreadable) is a first-class
|
|
16488
|
+
// input to classifyRegistry, which fails TOWARD delivery on a missing store.
|
|
16489
|
+
try {
|
|
16490
|
+
const s = fs.statSync(_svUsersFile);
|
|
16491
|
+
return { mtimeMs: s.mtimeMs, size: s.size };
|
|
16492
|
+
}
|
|
16493
|
+
catch {
|
|
16494
|
+
return null;
|
|
16495
|
+
}
|
|
16496
|
+
};
|
|
16497
|
+
let _svManager = null;
|
|
16498
|
+
let _svManagerStat = undefined;
|
|
16499
|
+
const _svResolveUid = (uid) => {
|
|
16500
|
+
const s = _svStatUsers();
|
|
16501
|
+
const changed = _svManagerStat === undefined ||
|
|
16502
|
+
(s === null) !== (_svManagerStat === null) ||
|
|
16503
|
+
(s !== null && _svManagerStat != null && (s.mtimeMs !== _svManagerStat.mtimeMs || s.size !== _svManagerStat.size));
|
|
16504
|
+
if (changed || !_svManager) {
|
|
16505
|
+
// READ-ONLY load: NO initialUsers → loadUsers runs no merge/persist.
|
|
16506
|
+
// @silent-fallback-ok: a load fault → null manager → resolveUid below
|
|
16507
|
+
// returns false; the gate's own degenerate-state check then fails toward
|
|
16508
|
+
// delivery rather than silently rejecting.
|
|
16509
|
+
try {
|
|
16510
|
+
_svManager = new UserManager(config.stateDir);
|
|
16511
|
+
}
|
|
16512
|
+
catch {
|
|
16513
|
+
_svManager = null;
|
|
16514
|
+
}
|
|
16515
|
+
_svManagerStat = s;
|
|
16516
|
+
}
|
|
16517
|
+
// @silent-fallback-ok: a resolve fault → false (unresolved) is safe — the
|
|
16518
|
+
// gate never rejects on an unresolved sender against a degenerate registry.
|
|
16519
|
+
try {
|
|
16520
|
+
return _svManager?.resolveFromTelegramUserId(uid) != null;
|
|
16521
|
+
}
|
|
16522
|
+
catch {
|
|
16523
|
+
return false;
|
|
16524
|
+
}
|
|
16525
|
+
};
|
|
16526
|
+
const senderValidationGate = new SenderValidationGate({
|
|
16527
|
+
usersFilePath: _svUsersFile,
|
|
16528
|
+
stateDir: config.stateDir,
|
|
16529
|
+
statUsers: _svStatUsers,
|
|
16530
|
+
resolveUid: _svResolveUid,
|
|
16531
|
+
operatorUidForTopic: (session) => {
|
|
16532
|
+
try {
|
|
16533
|
+
const op = _agentServerRef?.getTopicOperatorStore()?.getOperator(session);
|
|
16534
|
+
if (!op)
|
|
16535
|
+
return null;
|
|
16536
|
+
const n = Number(op.uid);
|
|
16537
|
+
return Number.isFinite(n) && n !== 0 ? n : null;
|
|
16538
|
+
}
|
|
16539
|
+
catch {
|
|
16540
|
+
// @silent-fallback-ok: an operator-store fault → null → the gate
|
|
16541
|
+
// declines to arm (fails toward delivery + alerts), never a silent reject.
|
|
16542
|
+
return null;
|
|
16543
|
+
}
|
|
16544
|
+
},
|
|
16545
|
+
alert: (level, cause, message) => notify(level === 'HIGH' ? 'IMMEDIATE' : 'SUMMARY', 'sender-validation', `[${cause}] ${message}`),
|
|
16546
|
+
log: (line) => console.warn(pc.yellow(line)),
|
|
16547
|
+
});
|
|
16369
16548
|
const deliverMessageHandler = deliverMod.createDeliverMessageHandler({
|
|
16370
16549
|
ownerEpochOf: (s) => ownReg.read(s)?.ownershipEpoch ?? null,
|
|
16371
16550
|
recordReceipt: (messageId, session) => {
|
|
@@ -16385,22 +16564,26 @@ export async function startServer(options) {
|
|
|
16385
16564
|
deliverSeenFallback.add(messageId);
|
|
16386
16565
|
return true;
|
|
16387
16566
|
},
|
|
16388
|
-
// §3.4 sender re-validation
|
|
16389
|
-
//
|
|
16390
|
-
//
|
|
16391
|
-
//
|
|
16392
|
-
|
|
16567
|
+
// §3.4 sender re-validation, hardened by the §2.D wiring gate: a carried
|
|
16568
|
+
// envelope whose userId no longer resolves on THIS machine NACKs
|
|
16569
|
+
// `sender-rejected` — BUT the gate never arms against a degenerate /
|
|
16570
|
+
// never-populated / corrupt / operator-unresolvable registry (it fails
|
|
16571
|
+
// toward delivery + shouts instead of rejecting everyone, the incident
|
|
16572
|
+
// fix). Envelope absent (old peer / live local frame) → not consulted.
|
|
16573
|
+
validateSender: (envelope, session) => {
|
|
16393
16574
|
const uid = Number(envelope.userId);
|
|
16394
|
-
|
|
16395
|
-
|
|
16396
|
-
|
|
16397
|
-
|
|
16398
|
-
|
|
16399
|
-
|
|
16400
|
-
|
|
16401
|
-
|
|
16402
|
-
|
|
16403
|
-
|
|
16575
|
+
return senderValidationGate.decide(uid, session).verdict === 'deliver';
|
|
16576
|
+
},
|
|
16577
|
+
// §2.B — the receiver-side rejection trace (metadata-only). The DECIDING
|
|
16578
|
+
// machine records its own refusal to logs/mesh-rejections.jsonl so a
|
|
16579
|
+
// future incident is never forensically blank on the machine that decided.
|
|
16580
|
+
onRejected: (meta) => {
|
|
16581
|
+
appendMeshRejection(config.stateDir, {
|
|
16582
|
+
reason: meta.reason,
|
|
16583
|
+
session: meta.session,
|
|
16584
|
+
messageId: meta.messageId,
|
|
16585
|
+
senderUid: meta.senderUid,
|
|
16586
|
+
});
|
|
16404
16587
|
},
|
|
16405
16588
|
// Owner-side bridge (§L4 handoff): a forwarded message landed → spawn/resume
|
|
16406
16589
|
// the local session for the topic so the conversation continues on THIS machine.
|
|
@@ -17950,6 +18133,22 @@ export async function startServer(options) {
|
|
|
17950
18133
|
}
|
|
17951
18134
|
},
|
|
17952
18135
|
reportLoss: (items, reason) => {
|
|
18136
|
+
// silent-loss-refusal-conservation §2.C — a `sender-deauthorized`
|
|
18137
|
+
// loss is a first-class REFUSAL, not a generic "didn't get to it":
|
|
18138
|
+
// route each item through the UNIFIED noticer (same neutral
|
|
18139
|
+
// wording + dedupe as the live path), NOT the generic message.
|
|
18140
|
+
if (reason === SENDER_DEAUTHORIZED_CAUSE && _senderRejectionNoticer) {
|
|
18141
|
+
for (const it of items) {
|
|
18142
|
+
const tid = Number(it.sessionKey);
|
|
18143
|
+
if (Number.isFinite(tid) && tid > 0) {
|
|
18144
|
+
_senderRejectionNoticer.onRejected({ adapter: 'telegram', topicId: tid, messageId: it.messageId });
|
|
18145
|
+
}
|
|
18146
|
+
else {
|
|
18147
|
+
_senderRejectionNoticer.onRejected({ adapter: 'slack', slackKey: it.sessionKey, messageId: it.messageId });
|
|
18148
|
+
}
|
|
18149
|
+
}
|
|
18150
|
+
return;
|
|
18151
|
+
}
|
|
17953
18152
|
notifyInboundLoss(items, 'SUMMARY', (count) => `I didn't get to ${count} of your message(s) (${reason}) — resend anything still needed.`);
|
|
17954
18153
|
},
|
|
17955
18154
|
reportPossiblyNotInjected: (items) => {
|