instar 1.3.1337 → 1.3.1339
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 +8 -0
- package/dist/commands/server.js.map +1 -1
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +13 -0
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/core/SessionRefresh.d.ts +21 -0
- package/dist/core/SessionRefresh.d.ts.map +1 -1
- package/dist/core/SessionRefresh.js +81 -30
- package/dist/core/SessionRefresh.js.map +1 -1
- package/dist/core/devGatedFeatures.d.ts.map +1 -1
- package/dist/core/devGatedFeatures.js +6 -0
- package/dist/core/devGatedFeatures.js.map +1 -1
- 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/data/standards-guard-index.json +1 -1
- package/dist/data/standards-guard-index.meta.json +2 -2
- package/dist/data/standards-registry.meta.json +1 -1
- package/dist/scaffold/templates.d.ts.map +1 -1
- package/dist/scaffold/templates.js +4 -0
- package/dist/scaffold/templates.js.map +1 -1
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +32 -0
- package/dist/server/routes.js.map +1 -1
- package/dist/threadline/AgentTrustManager.d.ts +40 -3
- package/dist/threadline/AgentTrustManager.d.ts.map +1 -1
- package/dist/threadline/AgentTrustManager.js +55 -7
- package/dist/threadline/AgentTrustManager.js.map +1 -1
- package/dist/threadline/ThreadlineBootstrap.d.ts +7 -0
- package/dist/threadline/ThreadlineBootstrap.d.ts.map +1 -1
- package/dist/threadline/ThreadlineBootstrap.js +34 -15
- package/dist/threadline/ThreadlineBootstrap.js.map +1 -1
- package/dist/threadline/inboundIdLedgerWiring.d.ts +2 -2
- package/dist/threadline/relayUnknownSenderTrust.d.ts +136 -0
- package/dist/threadline/relayUnknownSenderTrust.d.ts.map +1 -0
- package/dist/threadline/relayUnknownSenderTrust.js +208 -0
- package/dist/threadline/relayUnknownSenderTrust.js.map +1 -0
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +63 -63
- package/src/data/standards-guard-index.json +1 -1
- package/src/data/standards-guard-index.meta.json +2 -2
- package/src/data/standards-registry.meta.json +1 -1
- package/src/scaffold/templates.ts +4 -0
- package/upgrades/1.3.1338.md +30 -0
- package/upgrades/1.3.1339.md +20 -0
- package/upgrades/a2a-relay-unknown-sender-trust.eli16.md +21 -0
- package/upgrades/side-effects/a2a-relay-unknown-sender-trust.md +129 -0
- package/upgrades/side-effects/sessions-refresh-prebind.md +113 -0
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Relay unknown-sender trust (docs/specs/a2a-relay-unknown-sender-trust.md, ACT-066).
|
|
3
|
+
*
|
|
4
|
+
* The relay emits `unknown-sender` for a message whose sender's keys this agent
|
|
5
|
+
* does not hold (it cannot decrypt it, so the payload is plaintext). Until this
|
|
6
|
+
* module, that path skipped InboundMessageGate entirely and handed every such
|
|
7
|
+
* message onward at trust level `verified` (reason `relay-authenticated`) — and
|
|
8
|
+
* recording the interaction created a fingerprint trust profile at `verified`,
|
|
9
|
+
* so the sender stayed `verified` for every later message on every path.
|
|
10
|
+
*
|
|
11
|
+
* The relay proves the sender holds the private key of its fingerprint. It does
|
|
12
|
+
* not prove who the sender is, and nobody granted that fingerprint anything.
|
|
13
|
+
*
|
|
14
|
+
* This module resolves the sender's trust from the SAME AgentTrustManager the
|
|
15
|
+
* relay gate reads and applies the SAME operation-permission table. The trust
|
|
16
|
+
* manager stays the single authority for level → operations; nothing here
|
|
17
|
+
* re-implements it. Dry-run (the default when on) logs and counts would-refuse
|
|
18
|
+
* verdicts and delivers exactly as before; only an explicit `dryRun: false`
|
|
19
|
+
* refuses, emits the held level, and stops new fingerprint profiles from being
|
|
20
|
+
* created at `verified`.
|
|
21
|
+
*/
|
|
22
|
+
import type { AgentTrustLevel, AgentTrustManager } from './AgentTrustManager.js';
|
|
23
|
+
import type { ReceivedMessage } from './client/ThreadlineClient.js';
|
|
24
|
+
/** The slice of AgentTrustManager this module reads and writes. */
|
|
25
|
+
export type RelayUnknownSenderTrustManager = Pick<AgentTrustManager, 'getProfileByFingerprint' | 'getAllowedOperationsByFingerprint' | 'recordMessageReceivedByFingerprint' | 'getOrCreateProfileByFingerprint'>;
|
|
26
|
+
/** An operation name no trust level allows: a wire `type` that is present but unusable. */
|
|
27
|
+
export declare const INVALID_RELAY_OPERATION = "(invalid-type)";
|
|
28
|
+
/**
|
|
29
|
+
* The operation a plaintext relay payload asks for. The plaintext wire carries
|
|
30
|
+
* `chat` for a message and `ack` for a delivery ack (ThreadlineClient); neither
|
|
31
|
+
* name is in the level table, so `chat` (and a missing type) map to `message`.
|
|
32
|
+
* Any other string is taken as the operation name, exactly as the relay gate
|
|
33
|
+
* takes an E2E `type`; a non-string type maps to a name nothing allows.
|
|
34
|
+
*/
|
|
35
|
+
export declare function classifyRelayPlaintextOperation(wireType: unknown): string;
|
|
36
|
+
export interface RelayUnknownSenderVerdict {
|
|
37
|
+
/** The level the trust manager holds for the fingerprint (`untrusted` with no profile). */
|
|
38
|
+
level: AgentTrustLevel;
|
|
39
|
+
operation: string;
|
|
40
|
+
/** May this level perform this operation, per the trust manager's table. */
|
|
41
|
+
allowed: boolean;
|
|
42
|
+
/** A trust profile counts for the fingerprint (a first-contact profile does not). */
|
|
43
|
+
hasProfile: boolean;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Resolve the sender's held trust level and whether it may perform the
|
|
47
|
+
* operation. Reads only.
|
|
48
|
+
*
|
|
49
|
+
* A profile this path itself wrote at first contact while observing
|
|
50
|
+
* (`relayFirstContact`, source still `setup-default`) counts as no profile:
|
|
51
|
+
* under enforcement it would never have been written at `verified`. Once the
|
|
52
|
+
* operator or a pairing decides (any other source), the profile counts.
|
|
53
|
+
*/
|
|
54
|
+
export declare function resolveRelayUnknownSenderTrust(tm: Pick<RelayUnknownSenderTrustManager, 'getProfileByFingerprint' | 'getAllowedOperationsByFingerprint'>, fingerprint: string, wireType: unknown): RelayUnknownSenderVerdict;
|
|
55
|
+
export interface RelayUnknownSenderTrustMode {
|
|
56
|
+
/** Gate-resolved: `enabled` omitted ⇒ the developmentAgent gate decides. */
|
|
57
|
+
enabled: boolean;
|
|
58
|
+
/** Default TRUE: log + count would-refuse verdicts, deliver exactly as today. */
|
|
59
|
+
dryRun: boolean;
|
|
60
|
+
}
|
|
61
|
+
interface RelayUnknownSenderTrustConfigShape {
|
|
62
|
+
developmentAgent?: boolean;
|
|
63
|
+
threadline?: {
|
|
64
|
+
relayUnknownSenderTrust?: {
|
|
65
|
+
enabled?: boolean;
|
|
66
|
+
dryRun?: boolean;
|
|
67
|
+
};
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/** The live mode (spec "Configuration"). Read per message. */
|
|
71
|
+
export declare function resolveRelayUnknownSenderTrustMode(live: {
|
|
72
|
+
enabled?: boolean;
|
|
73
|
+
dryRun?: boolean;
|
|
74
|
+
}, config: RelayUnknownSenderTrustConfigShape): RelayUnknownSenderTrustMode;
|
|
75
|
+
/** True only when the check is on AND enforcing. */
|
|
76
|
+
export declare function isRelayUnknownSenderTrustEnforcing(mode: RelayUnknownSenderTrustMode): boolean;
|
|
77
|
+
/**
|
|
78
|
+
* The level a NEW fingerprint trust profile is created at. Today `verified`;
|
|
79
|
+
* `untrusted` while the check is enforcing — a first contact is not a grant.
|
|
80
|
+
*/
|
|
81
|
+
export declare function newFingerprintProfileLevel(mode: RelayUnknownSenderTrustMode): AgentTrustLevel;
|
|
82
|
+
/** In-memory counters on the authed /health (the rollout evidence). */
|
|
83
|
+
export interface RelayUnknownSenderTrustCounters {
|
|
84
|
+
/** Unknown-sender messages the check ran on. */
|
|
85
|
+
evaluated: number;
|
|
86
|
+
/** The held level may perform the operation. */
|
|
87
|
+
allowed: number;
|
|
88
|
+
/** Dry-run: would have been refused; delivered as today. */
|
|
89
|
+
wouldRefuse: number;
|
|
90
|
+
/** Enforcing: refused, not delivered. */
|
|
91
|
+
refused: number;
|
|
92
|
+
/** Dry-run: a first contact wrote a `verified` profile, marked `relayFirstContact` (enforcement would write none). */
|
|
93
|
+
firstContactProfiles: number;
|
|
94
|
+
/** The trust lookup threw. */
|
|
95
|
+
lookupErrors: number;
|
|
96
|
+
/** New fingerprint profiles created at `untrusted` because the check was enforcing. */
|
|
97
|
+
profilesCreatedUntrusted: number;
|
|
98
|
+
}
|
|
99
|
+
export declare function createRelayUnknownSenderTrustCounters(): RelayUnknownSenderTrustCounters;
|
|
100
|
+
/** The one server-log line per would-refuse / refuse verdict. */
|
|
101
|
+
export declare function relayUnknownSenderTrustLogLine(kind: 'would-refuse' | 'refuse', fingerprint: string, v: RelayUnknownSenderVerdict): string;
|
|
102
|
+
/** What `gate-passed` carries (the shape the server's relay consumer reads). */
|
|
103
|
+
export interface RelayUnknownSenderPassDecision {
|
|
104
|
+
action: 'pass';
|
|
105
|
+
reason: 'relay-authenticated' | 'probe';
|
|
106
|
+
trustLevel: AgentTrustLevel;
|
|
107
|
+
fingerprint: string;
|
|
108
|
+
message: ReceivedMessage;
|
|
109
|
+
}
|
|
110
|
+
export interface RelayUnknownSenderHandlerDeps {
|
|
111
|
+
trustManager: RelayUnknownSenderTrustManager;
|
|
112
|
+
mode: () => RelayUnknownSenderTrustMode;
|
|
113
|
+
counters: RelayUnknownSenderTrustCounters;
|
|
114
|
+
emit: (decision: RelayUnknownSenderPassDecision) => void;
|
|
115
|
+
log?: (line: string) => void;
|
|
116
|
+
}
|
|
117
|
+
export type RelayUnknownSenderOutcome = 'passed-legacy' | 'passed-dry-run' | 'would-refuse-delivered' | 'passed' | 'refused' | 'refused-lookup-error';
|
|
118
|
+
/**
|
|
119
|
+
* Handle one unknown-sender relay message after the payload-size check.
|
|
120
|
+
*
|
|
121
|
+
* - off: exactly today's behaviour (record, emit `verified`).
|
|
122
|
+
* - dry-run: today's delivery, plus the verdict counted and logged.
|
|
123
|
+
* - enforcing: refuse when the held level may not perform the operation;
|
|
124
|
+
* otherwise emit the held level. The interaction is recorded only for a
|
|
125
|
+
* sender that already has a profile, so a stranger's probe writes nothing.
|
|
126
|
+
*/
|
|
127
|
+
export declare function handleRelayUnknownSender(deps: RelayUnknownSenderHandlerDeps, received: ReceivedMessage, wireType: unknown): RelayUnknownSenderOutcome;
|
|
128
|
+
/**
|
|
129
|
+
* Process-wide counters (one relay client per server process). The bootstrap
|
|
130
|
+
* writes them; the authed /health reads them.
|
|
131
|
+
*/
|
|
132
|
+
export declare const relayUnknownSenderTrustCounters: RelayUnknownSenderTrustCounters;
|
|
133
|
+
/** Tests only: zero the process-wide counters. */
|
|
134
|
+
export declare function resetRelayUnknownSenderTrustCounters(): void;
|
|
135
|
+
export {};
|
|
136
|
+
//# sourceMappingURL=relayUnknownSenderTrust.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"relayUnknownSenderTrust.d.ts","sourceRoot":"","sources":["../../src/threadline/relayUnknownSenderTrust.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Relay unknown-sender trust (docs/specs/a2a-relay-unknown-sender-trust.md, ACT-066).
|
|
3
|
+
*
|
|
4
|
+
* The relay emits `unknown-sender` for a message whose sender's keys this agent
|
|
5
|
+
* does not hold (it cannot decrypt it, so the payload is plaintext). Until this
|
|
6
|
+
* module, that path skipped InboundMessageGate entirely and handed every such
|
|
7
|
+
* message onward at trust level `verified` (reason `relay-authenticated`) — and
|
|
8
|
+
* recording the interaction created a fingerprint trust profile at `verified`,
|
|
9
|
+
* so the sender stayed `verified` for every later message on every path.
|
|
10
|
+
*
|
|
11
|
+
* The relay proves the sender holds the private key of its fingerprint. It does
|
|
12
|
+
* not prove who the sender is, and nobody granted that fingerprint anything.
|
|
13
|
+
*
|
|
14
|
+
* This module resolves the sender's trust from the SAME AgentTrustManager the
|
|
15
|
+
* relay gate reads and applies the SAME operation-permission table. The trust
|
|
16
|
+
* manager stays the single authority for level → operations; nothing here
|
|
17
|
+
* re-implements it. Dry-run (the default when on) logs and counts would-refuse
|
|
18
|
+
* verdicts and delivers exactly as before; only an explicit `dryRun: false`
|
|
19
|
+
* refuses, emits the held level, and stops new fingerprint profiles from being
|
|
20
|
+
* created at `verified`.
|
|
21
|
+
*/
|
|
22
|
+
import { resolveDevAgentGate } from '../core/devAgentGate.js';
|
|
23
|
+
/** An operation name no trust level allows: a wire `type` that is present but unusable. */
|
|
24
|
+
export const INVALID_RELAY_OPERATION = '(invalid-type)';
|
|
25
|
+
/** Probes, as in InboundMessageGate: they pass with reason `probe`. */
|
|
26
|
+
const PROBE_OPS = new Set(['ping', 'health']);
|
|
27
|
+
/**
|
|
28
|
+
* Delivery acks are consumed by the ack stage before the inbox, the warrants
|
|
29
|
+
* gate and every router (docs/specs/a2a-ack-never-acked.md): they only record
|
|
30
|
+
* that our own earlier send arrived. Refusing them would make every peer we
|
|
31
|
+
* write to look unreachable, so every level may send one.
|
|
32
|
+
*/
|
|
33
|
+
const ACK_OP = 'ack';
|
|
34
|
+
/**
|
|
35
|
+
* The operation a plaintext relay payload asks for. The plaintext wire carries
|
|
36
|
+
* `chat` for a message and `ack` for a delivery ack (ThreadlineClient); neither
|
|
37
|
+
* name is in the level table, so `chat` (and a missing type) map to `message`.
|
|
38
|
+
* Any other string is taken as the operation name, exactly as the relay gate
|
|
39
|
+
* takes an E2E `type`; a non-string type maps to a name nothing allows.
|
|
40
|
+
*/
|
|
41
|
+
export function classifyRelayPlaintextOperation(wireType) {
|
|
42
|
+
if (wireType === undefined || wireType === null || wireType === 'chat')
|
|
43
|
+
return 'message';
|
|
44
|
+
if (typeof wireType === 'string' && wireType)
|
|
45
|
+
return wireType;
|
|
46
|
+
return INVALID_RELAY_OPERATION;
|
|
47
|
+
}
|
|
48
|
+
/** Credentials never travel the plaintext fallback (verified-pairing spec). */
|
|
49
|
+
const CREDENTIAL_SHARE_OP = 'credential-share';
|
|
50
|
+
/**
|
|
51
|
+
* Resolve the sender's held trust level and whether it may perform the
|
|
52
|
+
* operation. Reads only.
|
|
53
|
+
*
|
|
54
|
+
* A profile this path itself wrote at first contact while observing
|
|
55
|
+
* (`relayFirstContact`, source still `setup-default`) counts as no profile:
|
|
56
|
+
* under enforcement it would never have been written at `verified`. Once the
|
|
57
|
+
* operator or a pairing decides (any other source), the profile counts.
|
|
58
|
+
*/
|
|
59
|
+
export function resolveRelayUnknownSenderTrust(tm, fingerprint, wireType) {
|
|
60
|
+
const operation = classifyRelayPlaintextOperation(wireType);
|
|
61
|
+
const found = tm.getProfileByFingerprint(fingerprint);
|
|
62
|
+
const profile = found && !(found.relayFirstContact && found.source === 'setup-default') ? found : null;
|
|
63
|
+
const level = profile?.level ?? 'untrusted';
|
|
64
|
+
let allowed;
|
|
65
|
+
if (operation === ACK_OP) {
|
|
66
|
+
allowed = true;
|
|
67
|
+
}
|
|
68
|
+
else if (operation === CREDENTIAL_SHARE_OP) {
|
|
69
|
+
allowed = false;
|
|
70
|
+
}
|
|
71
|
+
else if (level === 'untrusted') {
|
|
72
|
+
// One intended difference from the gate's table: an `untrusted` sender's
|
|
73
|
+
// probe is NOT passed on. The gate passes probes expecting them handled
|
|
74
|
+
// inline, but the server's `gate-passed` consumer routes every message it
|
|
75
|
+
// receives to a session — so passing a stranger's ping would hand it the
|
|
76
|
+
// very session this check exists to deny. Only its delivery acks pass.
|
|
77
|
+
allowed = false;
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
allowed = tm.getAllowedOperationsByFingerprint(fingerprint).includes(operation);
|
|
81
|
+
}
|
|
82
|
+
return { level, operation, allowed, hasProfile: !!profile };
|
|
83
|
+
}
|
|
84
|
+
/** The live mode (spec "Configuration"). Read per message. */
|
|
85
|
+
export function resolveRelayUnknownSenderTrustMode(live, config) {
|
|
86
|
+
const block = config.threadline?.relayUnknownSenderTrust;
|
|
87
|
+
const enabled = resolveDevAgentGate(live.enabled ?? block?.enabled, config);
|
|
88
|
+
// Only an explicit `false` leaves dry-run; anything else observes.
|
|
89
|
+
const dryRun = (live.dryRun ?? block?.dryRun) !== false;
|
|
90
|
+
return { enabled, dryRun };
|
|
91
|
+
}
|
|
92
|
+
/** True only when the check is on AND enforcing. */
|
|
93
|
+
export function isRelayUnknownSenderTrustEnforcing(mode) {
|
|
94
|
+
return mode.enabled && !mode.dryRun;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* The level a NEW fingerprint trust profile is created at. Today `verified`;
|
|
98
|
+
* `untrusted` while the check is enforcing — a first contact is not a grant.
|
|
99
|
+
*/
|
|
100
|
+
export function newFingerprintProfileLevel(mode) {
|
|
101
|
+
return isRelayUnknownSenderTrustEnforcing(mode) ? 'untrusted' : 'verified';
|
|
102
|
+
}
|
|
103
|
+
export function createRelayUnknownSenderTrustCounters() {
|
|
104
|
+
return {
|
|
105
|
+
evaluated: 0, allowed: 0, wouldRefuse: 0, refused: 0,
|
|
106
|
+
firstContactProfiles: 0, lookupErrors: 0, profilesCreatedUntrusted: 0,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
/** Peer-supplied text is never logged raw: printable ASCII only, bounded. */
|
|
110
|
+
function logSafe(value) {
|
|
111
|
+
return value.replace(/[^\x21-\x7e]/g, '?').slice(0, 48) || 'unknown';
|
|
112
|
+
}
|
|
113
|
+
/** The one server-log line per would-refuse / refuse verdict. */
|
|
114
|
+
export function relayUnknownSenderTrustLogLine(kind, fingerprint, v) {
|
|
115
|
+
return `[relay-unknown-sender-trust] ${kind} fp=${logSafe(fingerprint).slice(0, 12)} profile=${v.hasProfile ? 'yes' : 'no'} trust=${v.level} op=${logSafe(v.operation)}`;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Handle one unknown-sender relay message after the payload-size check.
|
|
119
|
+
*
|
|
120
|
+
* - off: exactly today's behaviour (record, emit `verified`).
|
|
121
|
+
* - dry-run: today's delivery, plus the verdict counted and logged.
|
|
122
|
+
* - enforcing: refuse when the held level may not perform the operation;
|
|
123
|
+
* otherwise emit the held level. The interaction is recorded only for a
|
|
124
|
+
* sender that already has a profile, so a stranger's probe writes nothing.
|
|
125
|
+
*/
|
|
126
|
+
export function handleRelayUnknownSender(deps, received, wireType) {
|
|
127
|
+
const { trustManager: tm, counters } = deps;
|
|
128
|
+
const log = deps.log ?? ((line) => console.log(line));
|
|
129
|
+
const fingerprint = received.from;
|
|
130
|
+
const mode = deps.mode();
|
|
131
|
+
const legacyDeliver = () => {
|
|
132
|
+
// Evidence only: an unreadable profile counts as existing (no first-contact mark).
|
|
133
|
+
let existed = true;
|
|
134
|
+
try {
|
|
135
|
+
existed = !!tm.getProfileByFingerprint(fingerprint);
|
|
136
|
+
}
|
|
137
|
+
catch { /* @silent-fallback-ok — counter evidence only */ }
|
|
138
|
+
if (mode.enabled && !existed) {
|
|
139
|
+
// Dry-run keeps today's profile write, but the profile is created already
|
|
140
|
+
// marked (one atomic write — a crash cannot leave it unmarked), so neither
|
|
141
|
+
// later dry-run verdicts nor enforcement count it as a grant.
|
|
142
|
+
counters.firstContactProfiles++;
|
|
143
|
+
try {
|
|
144
|
+
tm.getOrCreateProfileByFingerprint(fingerprint, undefined, { relayFirstContact: true });
|
|
145
|
+
}
|
|
146
|
+
catch { /* @silent-fallback-ok — the record below writes today's profile */ }
|
|
147
|
+
}
|
|
148
|
+
tm.recordMessageReceivedByFingerprint(fingerprint);
|
|
149
|
+
deps.emit({ action: 'pass', reason: 'relay-authenticated', trustLevel: 'verified', fingerprint, message: received });
|
|
150
|
+
};
|
|
151
|
+
if (!mode.enabled) {
|
|
152
|
+
legacyDeliver();
|
|
153
|
+
return 'passed-legacy';
|
|
154
|
+
}
|
|
155
|
+
let verdict;
|
|
156
|
+
try {
|
|
157
|
+
verdict = resolveRelayUnknownSenderTrust(tm, fingerprint, wireType);
|
|
158
|
+
}
|
|
159
|
+
catch (err) {
|
|
160
|
+
counters.lookupErrors++;
|
|
161
|
+
log(`[relay-unknown-sender-trust] lookup failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
162
|
+
if (mode.dryRun) {
|
|
163
|
+
legacyDeliver();
|
|
164
|
+
return 'passed-dry-run';
|
|
165
|
+
}
|
|
166
|
+
// Enforcing and the authority cannot answer: fail closed. The relay sender
|
|
167
|
+
// gets no answer either way, exactly as for a gate block.
|
|
168
|
+
return 'refused-lookup-error';
|
|
169
|
+
}
|
|
170
|
+
counters.evaluated++;
|
|
171
|
+
if (mode.dryRun) {
|
|
172
|
+
if (verdict.allowed) {
|
|
173
|
+
counters.allowed++;
|
|
174
|
+
legacyDeliver();
|
|
175
|
+
return 'passed-dry-run';
|
|
176
|
+
}
|
|
177
|
+
counters.wouldRefuse++;
|
|
178
|
+
log(relayUnknownSenderTrustLogLine('would-refuse', fingerprint, verdict));
|
|
179
|
+
legacyDeliver();
|
|
180
|
+
return 'would-refuse-delivered';
|
|
181
|
+
}
|
|
182
|
+
if (!verdict.allowed) {
|
|
183
|
+
counters.refused++;
|
|
184
|
+
log(relayUnknownSenderTrustLogLine('refuse', fingerprint, verdict));
|
|
185
|
+
return 'refused';
|
|
186
|
+
}
|
|
187
|
+
counters.allowed++;
|
|
188
|
+
if (verdict.hasProfile)
|
|
189
|
+
tm.recordMessageReceivedByFingerprint(fingerprint);
|
|
190
|
+
deps.emit({
|
|
191
|
+
action: 'pass',
|
|
192
|
+
reason: PROBE_OPS.has(verdict.operation) ? 'probe' : 'relay-authenticated',
|
|
193
|
+
trustLevel: verdict.level,
|
|
194
|
+
fingerprint,
|
|
195
|
+
message: received,
|
|
196
|
+
});
|
|
197
|
+
return 'passed';
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Process-wide counters (one relay client per server process). The bootstrap
|
|
201
|
+
* writes them; the authed /health reads them.
|
|
202
|
+
*/
|
|
203
|
+
export const relayUnknownSenderTrustCounters = createRelayUnknownSenderTrustCounters();
|
|
204
|
+
/** Tests only: zero the process-wide counters. */
|
|
205
|
+
export function resetRelayUnknownSenderTrustCounters() {
|
|
206
|
+
Object.assign(relayUnknownSenderTrustCounters, createRelayUnknownSenderTrustCounters());
|
|
207
|
+
}
|
|
208
|
+
//# sourceMappingURL=relayUnknownSenderTrust.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"relayUnknownSenderTrust.js","sourceRoot":"","sources":["../../src/threadline/relayUnknownSenderTrust.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED