instar 1.3.704 → 1.3.706
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 +232 -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 +128 -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 +18 -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/monitoring/guardAcceptedFallbacks.d.ts +48 -0
- package/dist/monitoring/guardAcceptedFallbacks.d.ts.map +1 -0
- package/dist/monitoring/guardAcceptedFallbacks.js +106 -0
- package/dist/monitoring/guardAcceptedFallbacks.js.map +1 -0
- package/dist/monitoring/guardManifest.d.ts +25 -0
- package/dist/monitoring/guardManifest.d.ts.map +1 -1
- package/dist/monitoring/guardManifest.js +48 -0
- package/dist/monitoring/guardManifest.js.map +1 -1
- package/dist/monitoring/guardPostureView.d.ts +23 -0
- package/dist/monitoring/guardPostureView.d.ts.map +1 -1
- package/dist/monitoring/guardPostureView.js +66 -0
- package/dist/monitoring/guardPostureView.js.map +1 -1
- package/dist/monitoring/probes/GuardPostureProbe.d.ts +13 -2
- package/dist/monitoring/probes/GuardPostureProbe.d.ts.map +1 -1
- package/dist/monitoring/probes/GuardPostureProbe.js +143 -40
- package/dist/monitoring/probes/GuardPostureProbe.js.map +1 -1
- package/dist/scaffold/templates.d.ts.map +1 -1
- package/dist/scaffold/templates.js +3 -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 +82 -1
- 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/data/state-coherence-registry.json +18 -0
- package/src/scaffold/templates.ts +3 -1
- package/upgrades/1.3.705.md +79 -0
- package/upgrades/1.3.706.md +80 -0
- package/upgrades/side-effects/g3-dark-but-load-bearing-guards.md +143 -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;AA6H7D,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
|
@@ -86,6 +86,7 @@ import { SessionWatchdog } from '../monitoring/SessionWatchdog.js';
|
|
|
86
86
|
import { GuardRegistry } from '../monitoring/GuardRegistry.js';
|
|
87
87
|
import { resolveGuardConfigSnapshot, readGuardPostureBootSnapshot } from '../monitoring/guardPosture.js';
|
|
88
88
|
import { buildGuardInventory, buildHeartbeatPostureBlock } from '../monitoring/guardPostureView.js';
|
|
89
|
+
import { readAcceptedFallbacks, scopeAcceptedFallbacks } from '../monitoring/guardAcceptedFallbacks.js';
|
|
89
90
|
import { createGuardPostureProbes } from '../monitoring/probes/GuardPostureProbe.js';
|
|
90
91
|
import { GuardPostureStore } from '../core/GuardPostureStore.js';
|
|
91
92
|
import { isPeerUrlAllowedForCredentials } from '../server/peerUrlGuard.js';
|
|
@@ -127,7 +128,10 @@ import { HandoffWireTransport } from '../core/HandoffWireTransport.js';
|
|
|
127
128
|
import { createHandoffReceiverWiring } from '../core/handoffReceiverWiring.js';
|
|
128
129
|
import { createHandoffSentinelBootWiring } from '../core/handoffSentinelBootWiring.js';
|
|
129
130
|
import { MessageProcessingLedger } from '../messaging/MessageProcessingLedger.js';
|
|
131
|
+
import { dedupeKeyFor } from '../messaging/ingressDedup.js';
|
|
130
132
|
import { recoverStuckMessages } from '../messaging/stuckMessageRecovery.js';
|
|
133
|
+
import { SenderRejectionNoticer, SENDER_DEAUTHORIZED_CAUSE } from '../core/senderRejectionNotice.js';
|
|
134
|
+
import { appendMeshRejection } from '../core/meshRejectionLog.js';
|
|
131
135
|
import { ReplyMarkerTransport } from '../core/ReplyMarkerTransport.js';
|
|
132
136
|
import { decryptFromSync, encryptForSync } from '../core/SecretStore.js';
|
|
133
137
|
import { createPrivateKey, createPublicKey, createHash } from 'node:crypto';
|
|
@@ -192,6 +196,7 @@ import { DEFAULT_RELAY_HOST } from '../threadline/constants.js';
|
|
|
192
196
|
import { createUnifiedTrustSystem } from '../threadline/UnifiedTrustWiring.js';
|
|
193
197
|
import { toInjection } from '../types/pipeline.js';
|
|
194
198
|
import { UserManager } from '../users/UserManager.js';
|
|
199
|
+
import { loadTestIdentityKey } from '../users/testIdentityMarkers.js';
|
|
195
200
|
import { formatUserContextForSession, hasUserContext } from '../users/UserContextBuilder.js';
|
|
196
201
|
import { SafeFsExecutor } from '../core/SafeFsExecutor.js';
|
|
197
202
|
// setup.ts uses @inquirer/prompts which requires Node 20.12+
|
|
@@ -483,6 +488,11 @@ let _meshSelfId = null;
|
|
|
483
488
|
* strict no-op (always spawn locally, byte-for-byte legacy). Set in startServer's
|
|
484
489
|
* mesh block from the lease coordinator. */
|
|
485
490
|
let _holdsLeaseForSpawn = null;
|
|
491
|
+
/** silent-loss-refusal-conservation §2.C — the unified sender-rejection loss
|
|
492
|
+
* noticer. Constructed in startServer (needs messageLedger + notify + userManager);
|
|
493
|
+
* referenced module-wide (incl. wireTelegramRouting's live consumer) via this ref.
|
|
494
|
+
* null = not yet wired → the consumer falls back to the deterministic adapter send. */
|
|
495
|
+
let _senderRejectionNoticer = null;
|
|
486
496
|
/** G3: the ownershipCheckedSpawn flag accessor (multiMachine.sessionPool.
|
|
487
497
|
* ownershipCheckedSpawn). Defaults dark (no-op). Set in startServer. */
|
|
488
498
|
let _ownershipCheckedSpawn = () => ({ enabled: false, dryRun: true });
|
|
@@ -2004,6 +2014,33 @@ getAttentionTopicId) {
|
|
|
2004
2014
|
// box (the recognizer logs its pin, but route()'s actual placement/forward
|
|
2005
2015
|
// decision was invisible; that hid the bug below from the first live test).
|
|
2006
2016
|
console.log(`[session-pool] route topic ${topicId} → action=${outcome.action} owner=${outcome.owner ?? '?'} self=${_meshSelfId ?? '?'} acked=${outcome.acked}`);
|
|
2017
|
+
// silent-loss-refusal-conservation §2.A/§2.C — a first-class terminal
|
|
2018
|
+
// `rejected` (the owner re-validated the sender and refused) is NOT a
|
|
2019
|
+
// successful forward: TELL the user via the ORIGINATING adapter and
|
|
2020
|
+
// terminal-return WITHOUT local dispatch, BEFORE the isRemotelyHandled
|
|
2021
|
+
// test (which returns false for 'rejected' → would otherwise fall through
|
|
2022
|
+
// to a local inject the owner already refused). User-originated only (a
|
|
2023
|
+
// real platform uid + a bound topic — job/sentinel/canary keys excluded).
|
|
2024
|
+
if (outcome.action === 'rejected') {
|
|
2025
|
+
console.log(`[session-pool] topic ${topicId} REJECTED (${outcome.detail ?? 'sender-deauthorized'}) — not dispatching locally; notifying user`);
|
|
2026
|
+
if (telegramUserId) {
|
|
2027
|
+
const noticer = _senderRejectionNoticer;
|
|
2028
|
+
if (noticer) {
|
|
2029
|
+
noticer.onRejected({
|
|
2030
|
+
adapter: 'telegram',
|
|
2031
|
+
topicId,
|
|
2032
|
+
messageId: dedupeKeyFor('telegram', topicId, msg.id),
|
|
2033
|
+
senderUid: telegramUserId,
|
|
2034
|
+
peer: outcome.owner ?? undefined,
|
|
2035
|
+
});
|
|
2036
|
+
}
|
|
2037
|
+
else {
|
|
2038
|
+
// Noticer not yet wired — the deterministic adapter send is the floor.
|
|
2039
|
+
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(() => { });
|
|
2040
|
+
}
|
|
2041
|
+
}
|
|
2042
|
+
return;
|
|
2043
|
+
}
|
|
2007
2044
|
// Short-circuit local dispatch whenever the session ended up on ANOTHER machine
|
|
2008
2045
|
// (forward/duplicate, OR a fresh remote 'spawned'/'owner-dead-replaced'). Before
|
|
2009
2046
|
// this, only 'forwarded'/'duplicate' were caught, so a just-moved topic was
|
|
@@ -2214,7 +2251,11 @@ getAttentionTopicId) {
|
|
|
2214
2251
|
?? _topicPinStore?.asTopicMetadata(dmsg.sessionKey),
|
|
2215
2252
|
senderEnvelope: dmsg.senderEnvelope,
|
|
2216
2253
|
});
|
|
2217
|
-
|
|
2254
|
+
// silent-loss-refusal-conservation §2.A — forwardToOwner no longer masquerades
|
|
2255
|
+
// a refusal as `forwarded`; it returns the first-class terminal `rejected`.
|
|
2256
|
+
// Map it to the drain's `sender-rejected` disposition (→ terminal
|
|
2257
|
+
// `sender-deauthorized` + the unified §2.C loss notice via reportLoss).
|
|
2258
|
+
if (outcome.action === 'rejected') {
|
|
2218
2259
|
return { kind: 'sender-rejected' };
|
|
2219
2260
|
}
|
|
2220
2261
|
if (isRemotelyHandled(outcome, _meshSelfId)) {
|
|
@@ -5744,7 +5785,12 @@ export async function startServer(options) {
|
|
|
5744
5785
|
// collector runs even when the lifeline owns Telegram polling. See the
|
|
5745
5786
|
// "Account switcher + quota collector pipeline" section above.
|
|
5746
5787
|
// Initialize persistent UserManager for user identity resolution (Gap 8)
|
|
5747
|
-
|
|
5788
|
+
// silent-loss-refusal-conservation §2.D — the authoritative UserManager
|
|
5789
|
+
// carries the server-held allow-marker key so a legitimate fixture-collision
|
|
5790
|
+
// profile's signed `allowTestIdentity` VERIFIES on load (a bogus marker is
|
|
5791
|
+
// quarantined). Read-only probes elsewhere construct WITHOUT the key (fixtures
|
|
5792
|
+
// skipped — the safe direction).
|
|
5793
|
+
const userManager = new UserManager(config.stateDir, config.users, { testIdentityKey: loadTestIdentityKey(config.stateDir) });
|
|
5748
5794
|
attachUserReplication(userManager); // WS2.6 send-side (dark by default)
|
|
5749
5795
|
// Fix command dependencies — populated later when subsystems initialize.
|
|
5750
5796
|
// Uses a mutable ref so wireTelegramRouting can capture it in a closure now.
|
|
@@ -6559,6 +6605,30 @@ export async function startServer(options) {
|
|
|
6559
6605
|
channel: { platform: 'slack', workspaceId: _slackAdapter?.getWorkspaceId(), channelId },
|
|
6560
6606
|
});
|
|
6561
6607
|
console.log(`[session-pool] slack route key=${routingKey} → action=${outcome.action} owner=${outcome.owner ?? '?'} self=${_meshSelfId ?? '?'} acked=${outcome.acked}`);
|
|
6608
|
+
// silent-loss-refusal-conservation §2.A/§2.C — a first-class terminal
|
|
6609
|
+
// `rejected` is NOT a successful forward: reply in-thread on the
|
|
6610
|
+
// originating Slack channel and terminal-return WITHOUT local dispatch,
|
|
6611
|
+
// BEFORE the isRemotelyHandled test. Slack NOTICE parity ships now;
|
|
6612
|
+
// Slack SENDER re-validation is tracked-followup 4.
|
|
6613
|
+
if (outcome.action === 'rejected') {
|
|
6614
|
+
console.log(`[session-pool] slack key ${routingKey} REJECTED (${outcome.detail ?? 'sender-deauthorized'}) — not dispatching locally; notifying user`);
|
|
6615
|
+
const slackUid = message.metadata?.slackUserId || undefined;
|
|
6616
|
+
if (slackUid) {
|
|
6617
|
+
const noticer = _senderRejectionNoticer;
|
|
6618
|
+
if (noticer) {
|
|
6619
|
+
noticer.onRejected({
|
|
6620
|
+
adapter: 'slack',
|
|
6621
|
+
slackKey: routingKey,
|
|
6622
|
+
messageId: dedupeKeyFor('slack', routingKey, String(message.id)),
|
|
6623
|
+
peer: outcome.owner ?? undefined,
|
|
6624
|
+
});
|
|
6625
|
+
}
|
|
6626
|
+
else {
|
|
6627
|
+
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(() => { });
|
|
6628
|
+
}
|
|
6629
|
+
}
|
|
6630
|
+
return;
|
|
6631
|
+
}
|
|
6562
6632
|
if (isRemotelyHandled(outcome, _meshSelfId)) {
|
|
6563
6633
|
console.log(`[session-pool] slack key ${routingKey} handled by owner ${outcome.owner ?? '?'} (${outcome.action}) — not dispatching locally`);
|
|
6564
6634
|
return;
|
|
@@ -13228,6 +13298,10 @@ export async function startServer(options) {
|
|
|
13228
13298
|
snapshot: snap,
|
|
13229
13299
|
bootSnapshot: readGuardPostureBootSnapshot(config.stateDir),
|
|
13230
13300
|
registry: guardRegistry,
|
|
13301
|
+
now: Date.now(),
|
|
13302
|
+
// G3 (§2.6): thread THIS machine's operator-accept map (one read),
|
|
13303
|
+
// so an accepted load-bearing guard never surfaces as a gap here.
|
|
13304
|
+
acceptedFallbacks: scopeAcceptedFallbacks(readAcceptedFallbacks(config.stateDir), _meshSelfId ?? null),
|
|
13231
13305
|
});
|
|
13232
13306
|
}
|
|
13233
13307
|
catch {
|
|
@@ -13278,6 +13352,11 @@ export async function startServer(options) {
|
|
|
13278
13352
|
await telegram.createAttentionItem(item);
|
|
13279
13353
|
},
|
|
13280
13354
|
stateDir: config.stateDir,
|
|
13355
|
+
// G3 (§6): the load-bearing-gap ATTENTION alert defaults on (soak-gated is
|
|
13356
|
+
// automatic — soak windows are manifest constants). Rollback = set this
|
|
13357
|
+
// false; /guards keeps the classification either way.
|
|
13358
|
+
alertLoadBearingGaps: config.monitoring
|
|
13359
|
+
?.guardPostureProbe?.alertLoadBearingGaps ?? true,
|
|
13281
13360
|
}),
|
|
13282
13361
|
...createPlatformProbes({
|
|
13283
13362
|
tmuxPath,
|
|
@@ -15284,6 +15363,46 @@ export async function startServer(options) {
|
|
|
15284
15363
|
console.error(`[exactly-once] ledger open failed, gate stays dark: ${err instanceof Error ? err.message : err}`);
|
|
15285
15364
|
}
|
|
15286
15365
|
}
|
|
15366
|
+
// ── Sender-rejection loss noticer (silent-loss-refusal-conservation §2.C) ──
|
|
15367
|
+
// The single funnel the live consumer (Telegram/Slack) AND the queue-drain loss
|
|
15368
|
+
// path route through so a first-class `rejected` outcome ALWAYS tells the user,
|
|
15369
|
+
// deduped + ceilinged. Durable per-messageId dedupe uses the ledger's
|
|
15370
|
+
// `markRejected` marker when the ledger is present; otherwise the in-memory
|
|
15371
|
+
// 30-min window bounds it. Divergence probe reads a fresh read-only UserManager.
|
|
15372
|
+
{
|
|
15373
|
+
const ledgerForNotice = messageLedger;
|
|
15374
|
+
_senderRejectionNoticer = new SenderRejectionNoticer({
|
|
15375
|
+
// @silent-fallback-ok: fire-and-forget notice send; the deterministic
|
|
15376
|
+
// delivery path + DeliveryFailureSentinel own retry, not this callsite.
|
|
15377
|
+
sendTelegram: (topicId, text) => { telegram?.sendToTopic(topicId, text).catch(() => { }); },
|
|
15378
|
+
sendSlack: (slackKey, text) => {
|
|
15379
|
+
const channelId = slackKey.split(':')[0];
|
|
15380
|
+
// @silent-fallback-ok: fire-and-forget notice send (see sendTelegram).
|
|
15381
|
+
_slackAdapter?.sendToChannel(channelId, text).catch(() => { });
|
|
15382
|
+
},
|
|
15383
|
+
alertHub: (title, body) => notify('IMMEDIATE', 'sender-rejection', `${title}: ${body}`),
|
|
15384
|
+
markRejectedDurable: ledgerForNotice
|
|
15385
|
+
// @silent-fallback-ok: a dedupe-ledger fault returns true (treat as
|
|
15386
|
+
// first-transition → SEND the notice) — fails toward TELLING the user.
|
|
15387
|
+
? (messageId) => { try {
|
|
15388
|
+
return ledgerForNotice.markRejected(messageId, 0, { platform: 'mesh' });
|
|
15389
|
+
}
|
|
15390
|
+
catch {
|
|
15391
|
+
return true;
|
|
15392
|
+
} }
|
|
15393
|
+
: undefined,
|
|
15394
|
+
resolvesLocally: (uid) => {
|
|
15395
|
+
// @silent-fallback-ok: divergence-probe read helper; a resolve fault →
|
|
15396
|
+
// false (no divergence signal this tick), advisory only.
|
|
15397
|
+
try {
|
|
15398
|
+
return new UserManager(config.stateDir).resolveFromTelegramUserId(uid) !== null;
|
|
15399
|
+
}
|
|
15400
|
+
catch {
|
|
15401
|
+
return false;
|
|
15402
|
+
}
|
|
15403
|
+
},
|
|
15404
|
+
});
|
|
15405
|
+
}
|
|
15287
15406
|
// ── ReleaseReadinessSentinel (Layer B of release-readiness-visibility) ──
|
|
15288
15407
|
// Repo-gated dev-environment watchdog: only constructed when the install has
|
|
15289
15408
|
// an analyzable instar git repo AND the feature is enabled in config. On a
|
|
@@ -15570,6 +15689,12 @@ export async function startServer(options) {
|
|
|
15570
15689
|
snapshot: snap,
|
|
15571
15690
|
bootSnapshot: readGuardPostureBootSnapshot(config.stateDir),
|
|
15572
15691
|
registry: guardRegistry,
|
|
15692
|
+
now: Date.now(),
|
|
15693
|
+
// G3 (§2.6, round-4 3rd-site fix): the heartbeat compute MUST thread
|
|
15694
|
+
// the local accept map + now, else an operator-accepted load-bearing
|
|
15695
|
+
// guard is re-derived as a Gap here and shipped to peers as a FALSE
|
|
15696
|
+
// load-bearing-gap (and the soak lapse would misfire with an unthreaded now).
|
|
15697
|
+
acceptedFallbacks: scopeAcceptedFallbacks(readAcceptedFallbacks(config.stateDir), _meshSelfId ?? null),
|
|
15573
15698
|
});
|
|
15574
15699
|
return buildHeartbeatPostureBlock(inv, new Date().toISOString());
|
|
15575
15700
|
}
|
|
@@ -16366,6 +16491,76 @@ export async function startServer(options) {
|
|
|
16366
16491
|
// activation; the durable receipt + ACK is the complete dark-phase contract.)
|
|
16367
16492
|
const deliverMod = await import('../core/DeliverMessageHandler.js');
|
|
16368
16493
|
const deliverSeenFallback = new Set(); // used only if the SQLite ledger is unavailable
|
|
16494
|
+
// silent-loss-refusal-conservation §2.D — the wiring-time registry gate.
|
|
16495
|
+
// A stat-gated, READ-ONLY (no initialUsers merge — the old closure passed
|
|
16496
|
+
// config.users, which can writeFileSync per message) resolver + the
|
|
16497
|
+
// degenerate/unknown-unsafe/operator-resolution arm decision. Constructed
|
|
16498
|
+
// ONCE; the operator-uid probe reads the LOCAL topic-operator binding of the
|
|
16499
|
+
// deciding machine (KYP) via the late-bound AgentServer ref.
|
|
16500
|
+
const { SenderValidationGate } = await import('../core/senderValidationGate.js');
|
|
16501
|
+
const _svUsersFile = path.join(config.stateDir, 'users.json');
|
|
16502
|
+
const _svStatUsers = () => {
|
|
16503
|
+
// @silent-fallback-ok: stat fault → null (ENOENT/unreadable) is a first-class
|
|
16504
|
+
// input to classifyRegistry, which fails TOWARD delivery on a missing store.
|
|
16505
|
+
try {
|
|
16506
|
+
const s = fs.statSync(_svUsersFile);
|
|
16507
|
+
return { mtimeMs: s.mtimeMs, size: s.size };
|
|
16508
|
+
}
|
|
16509
|
+
catch {
|
|
16510
|
+
return null;
|
|
16511
|
+
}
|
|
16512
|
+
};
|
|
16513
|
+
let _svManager = null;
|
|
16514
|
+
let _svManagerStat = undefined;
|
|
16515
|
+
const _svResolveUid = (uid) => {
|
|
16516
|
+
const s = _svStatUsers();
|
|
16517
|
+
const changed = _svManagerStat === undefined ||
|
|
16518
|
+
(s === null) !== (_svManagerStat === null) ||
|
|
16519
|
+
(s !== null && _svManagerStat != null && (s.mtimeMs !== _svManagerStat.mtimeMs || s.size !== _svManagerStat.size));
|
|
16520
|
+
if (changed || !_svManager) {
|
|
16521
|
+
// READ-ONLY load: NO initialUsers → loadUsers runs no merge/persist.
|
|
16522
|
+
// @silent-fallback-ok: a load fault → null manager → resolveUid below
|
|
16523
|
+
// returns false; the gate's own degenerate-state check then fails toward
|
|
16524
|
+
// delivery rather than silently rejecting.
|
|
16525
|
+
try {
|
|
16526
|
+
_svManager = new UserManager(config.stateDir);
|
|
16527
|
+
}
|
|
16528
|
+
catch {
|
|
16529
|
+
_svManager = null;
|
|
16530
|
+
}
|
|
16531
|
+
_svManagerStat = s;
|
|
16532
|
+
}
|
|
16533
|
+
// @silent-fallback-ok: a resolve fault → false (unresolved) is safe — the
|
|
16534
|
+
// gate never rejects on an unresolved sender against a degenerate registry.
|
|
16535
|
+
try {
|
|
16536
|
+
return _svManager?.resolveFromTelegramUserId(uid) != null;
|
|
16537
|
+
}
|
|
16538
|
+
catch {
|
|
16539
|
+
return false;
|
|
16540
|
+
}
|
|
16541
|
+
};
|
|
16542
|
+
const senderValidationGate = new SenderValidationGate({
|
|
16543
|
+
usersFilePath: _svUsersFile,
|
|
16544
|
+
stateDir: config.stateDir,
|
|
16545
|
+
statUsers: _svStatUsers,
|
|
16546
|
+
resolveUid: _svResolveUid,
|
|
16547
|
+
operatorUidForTopic: (session) => {
|
|
16548
|
+
try {
|
|
16549
|
+
const op = _agentServerRef?.getTopicOperatorStore()?.getOperator(session);
|
|
16550
|
+
if (!op)
|
|
16551
|
+
return null;
|
|
16552
|
+
const n = Number(op.uid);
|
|
16553
|
+
return Number.isFinite(n) && n !== 0 ? n : null;
|
|
16554
|
+
}
|
|
16555
|
+
catch {
|
|
16556
|
+
// @silent-fallback-ok: an operator-store fault → null → the gate
|
|
16557
|
+
// declines to arm (fails toward delivery + alerts), never a silent reject.
|
|
16558
|
+
return null;
|
|
16559
|
+
}
|
|
16560
|
+
},
|
|
16561
|
+
alert: (level, cause, message) => notify(level === 'HIGH' ? 'IMMEDIATE' : 'SUMMARY', 'sender-validation', `[${cause}] ${message}`),
|
|
16562
|
+
log: (line) => console.warn(pc.yellow(line)),
|
|
16563
|
+
});
|
|
16369
16564
|
const deliverMessageHandler = deliverMod.createDeliverMessageHandler({
|
|
16370
16565
|
ownerEpochOf: (s) => ownReg.read(s)?.ownershipEpoch ?? null,
|
|
16371
16566
|
recordReceipt: (messageId, session) => {
|
|
@@ -16385,22 +16580,26 @@ export async function startServer(options) {
|
|
|
16385
16580
|
deliverSeenFallback.add(messageId);
|
|
16386
16581
|
return true;
|
|
16387
16582
|
},
|
|
16388
|
-
// §3.4 sender re-validation
|
|
16389
|
-
//
|
|
16390
|
-
//
|
|
16391
|
-
//
|
|
16392
|
-
|
|
16583
|
+
// §3.4 sender re-validation, hardened by the §2.D wiring gate: a carried
|
|
16584
|
+
// envelope whose userId no longer resolves on THIS machine NACKs
|
|
16585
|
+
// `sender-rejected` — BUT the gate never arms against a degenerate /
|
|
16586
|
+
// never-populated / corrupt / operator-unresolvable registry (it fails
|
|
16587
|
+
// toward delivery + shouts instead of rejecting everyone, the incident
|
|
16588
|
+
// fix). Envelope absent (old peer / live local frame) → not consulted.
|
|
16589
|
+
validateSender: (envelope, session) => {
|
|
16393
16590
|
const uid = Number(envelope.userId);
|
|
16394
|
-
|
|
16395
|
-
|
|
16396
|
-
|
|
16397
|
-
|
|
16398
|
-
|
|
16399
|
-
|
|
16400
|
-
|
|
16401
|
-
|
|
16402
|
-
|
|
16403
|
-
|
|
16591
|
+
return senderValidationGate.decide(uid, session).verdict === 'deliver';
|
|
16592
|
+
},
|
|
16593
|
+
// §2.B — the receiver-side rejection trace (metadata-only). The DECIDING
|
|
16594
|
+
// machine records its own refusal to logs/mesh-rejections.jsonl so a
|
|
16595
|
+
// future incident is never forensically blank on the machine that decided.
|
|
16596
|
+
onRejected: (meta) => {
|
|
16597
|
+
appendMeshRejection(config.stateDir, {
|
|
16598
|
+
reason: meta.reason,
|
|
16599
|
+
session: meta.session,
|
|
16600
|
+
messageId: meta.messageId,
|
|
16601
|
+
senderUid: meta.senderUid,
|
|
16602
|
+
});
|
|
16404
16603
|
},
|
|
16405
16604
|
// Owner-side bridge (§L4 handoff): a forwarded message landed → spawn/resume
|
|
16406
16605
|
// the local session for the topic so the conversation continues on THIS machine.
|
|
@@ -17950,6 +18149,22 @@ export async function startServer(options) {
|
|
|
17950
18149
|
}
|
|
17951
18150
|
},
|
|
17952
18151
|
reportLoss: (items, reason) => {
|
|
18152
|
+
// silent-loss-refusal-conservation §2.C — a `sender-deauthorized`
|
|
18153
|
+
// loss is a first-class REFUSAL, not a generic "didn't get to it":
|
|
18154
|
+
// route each item through the UNIFIED noticer (same neutral
|
|
18155
|
+
// wording + dedupe as the live path), NOT the generic message.
|
|
18156
|
+
if (reason === SENDER_DEAUTHORIZED_CAUSE && _senderRejectionNoticer) {
|
|
18157
|
+
for (const it of items) {
|
|
18158
|
+
const tid = Number(it.sessionKey);
|
|
18159
|
+
if (Number.isFinite(tid) && tid > 0) {
|
|
18160
|
+
_senderRejectionNoticer.onRejected({ adapter: 'telegram', topicId: tid, messageId: it.messageId });
|
|
18161
|
+
}
|
|
18162
|
+
else {
|
|
18163
|
+
_senderRejectionNoticer.onRejected({ adapter: 'slack', slackKey: it.sessionKey, messageId: it.messageId });
|
|
18164
|
+
}
|
|
18165
|
+
}
|
|
18166
|
+
return;
|
|
18167
|
+
}
|
|
17953
18168
|
notifyInboundLoss(items, 'SUMMARY', (count) => `I didn't get to ${count} of your message(s) (${reason}) — resend anything still needed.`);
|
|
17954
18169
|
},
|
|
17955
18170
|
reportPossiblyNotInjected: (items) => {
|