@juspay/neurolink 11.29.1 → 11.30.0
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/CHANGELOG.md +5 -1
- package/dist/adapters/video/vertexVideoHandler.d.ts +1 -0
- package/dist/adapters/video/vertexVideoHandler.js +97 -14
- package/dist/auth/anthropicOAuth.d.ts +50 -0
- package/dist/auth/anthropicOAuth.js +78 -0
- package/dist/browser/neurolink.min.js +393 -393
- package/dist/cli/commands/proxy.d.ts +2 -0
- package/dist/cli/commands/proxy.js +284 -4
- package/dist/cli/commands/proxyExpose.d.ts +35 -0
- package/dist/cli/commands/proxyExpose.js +252 -0
- package/dist/cli/commands/proxyPeer.d.ts +29 -0
- package/dist/cli/commands/proxyPeer.js +738 -0
- package/dist/cli/commands/proxyShare.d.ts +37 -0
- package/dist/cli/commands/proxyShare.js +1080 -0
- package/dist/cli/parser.js +7 -1
- package/dist/core/baseProvider.js +5 -0
- package/dist/proxy/peerStore.d.ts +52 -0
- package/dist/proxy/peerStore.js +324 -0
- package/dist/proxy/peerTransport.d.ts +38 -0
- package/dist/proxy/peerTransport.js +242 -0
- package/dist/proxy/proxyPaths.d.ts +8 -0
- package/dist/proxy/proxyPaths.js +55 -17
- package/dist/proxy/requestLogger.js +8 -0
- package/dist/proxy/residentGrants.d.ts +57 -0
- package/dist/proxy/residentGrants.js +393 -0
- package/dist/proxy/shareAudit.d.ts +81 -0
- package/dist/proxy/shareAudit.js +280 -0
- package/dist/proxy/shareContext.d.ts +38 -0
- package/dist/proxy/shareContext.js +92 -0
- package/dist/proxy/shareGate.d.ts +64 -0
- package/dist/proxy/shareGate.js +216 -0
- package/dist/proxy/shareGrants.d.ts +115 -0
- package/dist/proxy/shareGrants.js +590 -0
- package/dist/proxy/shareLease.d.ts +101 -0
- package/dist/proxy/shareLease.js +192 -0
- package/dist/proxy/shareLedger.d.ts +105 -0
- package/dist/proxy/shareLedger.js +406 -0
- package/dist/proxy/shareListener.d.ts +60 -0
- package/dist/proxy/shareListener.js +143 -0
- package/dist/proxy/shareNotes.d.ts +97 -0
- package/dist/proxy/shareNotes.js +234 -0
- package/dist/proxy/sharePolicy.d.ts +110 -0
- package/dist/proxy/sharePolicy.js +366 -0
- package/dist/proxy/shareProvisioning.d.ts +110 -0
- package/dist/proxy/shareProvisioning.js +237 -0
- package/dist/proxy/shareReceipts.d.ts +99 -0
- package/dist/proxy/shareReceipts.js +303 -0
- package/dist/proxy/shareSigning.d.ts +40 -0
- package/dist/proxy/shareSigning.js +78 -0
- package/dist/server/routes/claudeProxyRoutes.js +1066 -3
- package/dist/types/cli.d.ts +61 -0
- package/dist/types/proxy.d.ts +781 -0
- package/dist/types/video.d.ts +5 -0
- package/dist/utils/videoProcessor.js +27 -2
- package/package.json +3 -1
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Forwarding a borrowed request to a lender's proxy.
|
|
3
|
+
*
|
|
4
|
+
* The wire format is unchanged Anthropic Messages in both directions, so this is
|
|
5
|
+
* a passthrough, not a translation: the lender's proxy speaks exactly what the
|
|
6
|
+
* borrower's client already sent. That is what makes peer borrowing cheap
|
|
7
|
+
* compared with the provider fallback chain, which has to reshape the request
|
|
8
|
+
* for a different API.
|
|
9
|
+
*
|
|
10
|
+
* **Reading the refusal, not the status.** A lender's 429 can mean "your grant
|
|
11
|
+
* is spent" or "the upstream throttled me"; those want opposite reactions from
|
|
12
|
+
* the borrower. The distinction is carried in `x-neurolink-grant-reason`, so
|
|
13
|
+
* that header — not the status code — decides how long the peer is parked.
|
|
14
|
+
*
|
|
15
|
+
* @module proxy/peerTransport
|
|
16
|
+
*/
|
|
17
|
+
import { logger } from "../utils/logger.js";
|
|
18
|
+
import { coolPeer, recordPeerSuccess } from "./peerStore.js";
|
|
19
|
+
/** A peer is a fallback, so it gets a short leash before we move on. */
|
|
20
|
+
const PEER_CONNECT_TIMEOUT_MS = 15_000;
|
|
21
|
+
/**
|
|
22
|
+
* Longest silence tolerated once a peer has started answering.
|
|
23
|
+
*
|
|
24
|
+
* The connect timer is cleared as soon as headers arrive, which on a streamed
|
|
25
|
+
* response is long before the answer is. Without a second deadline a lender
|
|
26
|
+
* that opens the stream and then stops writing holds our client open forever —
|
|
27
|
+
* a borrowed request is the fallback path and should never be the one that
|
|
28
|
+
* hangs. Generous, because a long thinking pause is a legitimate silence.
|
|
29
|
+
*/
|
|
30
|
+
const PEER_IDLE_TIMEOUT_MS = 120_000;
|
|
31
|
+
/**
|
|
32
|
+
* Map a lender's refusal to how long the peer should be left alone.
|
|
33
|
+
*
|
|
34
|
+
* Anything not recognized is treated as an upstream problem rather than a grant
|
|
35
|
+
* problem — the conservative reading, since it recovers soonest.
|
|
36
|
+
*/
|
|
37
|
+
export function peerReasonFromRefusal(grantReason) {
|
|
38
|
+
switch (grantReason) {
|
|
39
|
+
case "exhausted":
|
|
40
|
+
return "exhausted";
|
|
41
|
+
case "paused":
|
|
42
|
+
return "paused";
|
|
43
|
+
case "revoked":
|
|
44
|
+
return "revoked";
|
|
45
|
+
case "expired":
|
|
46
|
+
return "expired";
|
|
47
|
+
case "reserve_floor":
|
|
48
|
+
case "spillover_inactive":
|
|
49
|
+
case "slice_exhausted":
|
|
50
|
+
case "no_capacity":
|
|
51
|
+
return "withheld";
|
|
52
|
+
case "missing_token":
|
|
53
|
+
case "unknown_token":
|
|
54
|
+
case "malformed_token":
|
|
55
|
+
// The lender does not recognize us at all. Treat it like a revocation:
|
|
56
|
+
// retrying a token the lender has forgotten cannot start working again.
|
|
57
|
+
return "revoked";
|
|
58
|
+
default:
|
|
59
|
+
// No grant reason means the lender never got as far as our grant — this
|
|
60
|
+
// is its own upstream or credential trouble, not a statement about us.
|
|
61
|
+
// Reading a bare 401 as a revocation would park a perfectly good peer for
|
|
62
|
+
// a day because the lender briefly had no usable account.
|
|
63
|
+
return "upstream_error";
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* A numeric header, or `undefined` when the peer did not send one.
|
|
68
|
+
*
|
|
69
|
+
* `Number(null)` and `Number("")` are both `0`, and `0` passes
|
|
70
|
+
* `Number.isFinite` — so reading these headers directly turns "the lender said
|
|
71
|
+
* nothing" into "the lender said zero", which on a remaining-coins header reads
|
|
72
|
+
* as an exhausted peer.
|
|
73
|
+
*/
|
|
74
|
+
function numericHeader(response, name) {
|
|
75
|
+
const raw = response.headers.get(name);
|
|
76
|
+
if (raw === null || raw.trim() === "") {
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
79
|
+
const value = Number(raw);
|
|
80
|
+
return Number.isFinite(value) ? value : undefined;
|
|
81
|
+
}
|
|
82
|
+
function observationFrom(response) {
|
|
83
|
+
const remaining = numericHeader(response, "x-neurolink-grant-remaining-coins");
|
|
84
|
+
return {
|
|
85
|
+
observedAt: Date.now(),
|
|
86
|
+
...(response.headers.get("x-neurolink-grant-status")
|
|
87
|
+
? { grantStatus: response.headers.get("x-neurolink-grant-status") ?? "" }
|
|
88
|
+
: {}),
|
|
89
|
+
...(response.headers.get("x-neurolink-grant-reason")
|
|
90
|
+
? { grantReason: response.headers.get("x-neurolink-grant-reason") ?? "" }
|
|
91
|
+
: {}),
|
|
92
|
+
...(remaining !== undefined ? { remainingCoins: remaining } : {}),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Re-arm a deadline on every chunk, and abandon the peer if one never comes.
|
|
97
|
+
*
|
|
98
|
+
* The body is passed through rather than buffered: the point of handing the
|
|
99
|
+
* upstream response back is that a stream keeps streaming, and collecting it
|
|
100
|
+
* here would add the whole generation time to time-to-first-token on a path
|
|
101
|
+
* that is already a second hop.
|
|
102
|
+
*/
|
|
103
|
+
function withIdleDeadline(response, peer, controller) {
|
|
104
|
+
const body = response.body;
|
|
105
|
+
if (!body) {
|
|
106
|
+
return response;
|
|
107
|
+
}
|
|
108
|
+
let idle;
|
|
109
|
+
const disarm = () => {
|
|
110
|
+
if (idle !== undefined) {
|
|
111
|
+
clearTimeout(idle);
|
|
112
|
+
idle = undefined;
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
const arm = () => {
|
|
116
|
+
disarm();
|
|
117
|
+
idle = setTimeout(() => {
|
|
118
|
+
logger.always(`[proxy] peer=${peer.name} went quiet mid-response; abandoning it`);
|
|
119
|
+
// Cool it here as well. The success path cleared this peer's cooldown the
|
|
120
|
+
// moment the response headers arrived, which is long before a body stops
|
|
121
|
+
// arriving — so a peer that answers 200 and then stalls would otherwise
|
|
122
|
+
// stay perfectly healthy in the store and be picked again, and again, for
|
|
123
|
+
// the same stall. Failing mid-body is a failure like any other.
|
|
124
|
+
void coolPeer(peer.name, "unreachable").catch((error) => {
|
|
125
|
+
logger.debug(`[proxy] could not cool peer=${peer.name} after a stall: ${error instanceof Error ? error.message : String(error)}`);
|
|
126
|
+
});
|
|
127
|
+
// Aborting the fetch errors the stream, which is what the caller needs to
|
|
128
|
+
// see — a truncated answer presented as a complete one would be worse.
|
|
129
|
+
controller.abort();
|
|
130
|
+
}, PEER_IDLE_TIMEOUT_MS);
|
|
131
|
+
idle.unref?.();
|
|
132
|
+
};
|
|
133
|
+
const watched = body.pipeThrough(new TransformStream({
|
|
134
|
+
start: arm,
|
|
135
|
+
transform(chunk, target) {
|
|
136
|
+
arm();
|
|
137
|
+
target.enqueue(chunk);
|
|
138
|
+
},
|
|
139
|
+
flush: disarm,
|
|
140
|
+
}));
|
|
141
|
+
controller.signal.addEventListener("abort", disarm, { once: true });
|
|
142
|
+
return new Response(watched, {
|
|
143
|
+
status: response.status,
|
|
144
|
+
statusText: response.statusText,
|
|
145
|
+
headers: response.headers,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Send one request to one peer.
|
|
150
|
+
*
|
|
151
|
+
* On success the upstream `Response` is handed back with only an idle deadline
|
|
152
|
+
* wrapped around its body, so a stream keeps streaming — buffering it here
|
|
153
|
+
* would add the whole generation time to time-to-first-token on a path that is
|
|
154
|
+
* already a second hop.
|
|
155
|
+
*/
|
|
156
|
+
export async function forwardToPeer(args) {
|
|
157
|
+
const { peer, body, stream } = args;
|
|
158
|
+
const controller = new AbortController();
|
|
159
|
+
const timeout = setTimeout(() => controller.abort(), PEER_CONNECT_TIMEOUT_MS);
|
|
160
|
+
if (args.signal) {
|
|
161
|
+
if (args.signal.aborted) {
|
|
162
|
+
controller.abort();
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
args.signal.addEventListener("abort", () => controller.abort(), {
|
|
166
|
+
once: true,
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
try {
|
|
171
|
+
const response = await fetch(`${peer.url}/v1/messages`, {
|
|
172
|
+
method: "POST",
|
|
173
|
+
headers: {
|
|
174
|
+
"content-type": "application/json",
|
|
175
|
+
"x-neurolink-share-token": peer.token,
|
|
176
|
+
accept: stream ? "text/event-stream" : "application/json",
|
|
177
|
+
},
|
|
178
|
+
body,
|
|
179
|
+
signal: controller.signal,
|
|
180
|
+
});
|
|
181
|
+
if (response.ok) {
|
|
182
|
+
// Outside the classification below on purpose. This is bookkeeping over a
|
|
183
|
+
// response that already succeeded, and a disk that will not take the note
|
|
184
|
+
// says nothing about the peer — letting it fall through to the catch
|
|
185
|
+
// would cool a peer that had just answered correctly.
|
|
186
|
+
await recordPeerSuccess(peer.name, observationFrom(response)).catch((error) => {
|
|
187
|
+
logger.debug(`[proxy] could not record success for peer=${peer.name}: ${error instanceof Error ? error.message : String(error)}`);
|
|
188
|
+
});
|
|
189
|
+
return {
|
|
190
|
+
ok: true,
|
|
191
|
+
response: withIdleDeadline(response, peer, controller),
|
|
192
|
+
peer,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
const grantReason = response.headers.get("x-neurolink-grant-reason");
|
|
196
|
+
const reason = peerReasonFromRefusal(grantReason);
|
|
197
|
+
const retryAfter = numericHeader(response, "retry-after");
|
|
198
|
+
await coolPeer(peer.name, reason, retryAfter);
|
|
199
|
+
// The body is drained but deliberately not surfaced: it is the lender's
|
|
200
|
+
// wording about the lender's pool, and forwarding it to our client would
|
|
201
|
+
// leak their account state into an error our client cannot act on.
|
|
202
|
+
await response.text().catch(() => "");
|
|
203
|
+
logger.always(`[proxy] peer=${peer.name} declined (${reason}); cooling before retry`);
|
|
204
|
+
return {
|
|
205
|
+
ok: false,
|
|
206
|
+
peer,
|
|
207
|
+
status: response.status,
|
|
208
|
+
reason,
|
|
209
|
+
message: `peer ${peer.name} declined: ${reason}`,
|
|
210
|
+
...(retryAfter !== undefined ? { retryAfterSeconds: retryAfter } : {}),
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
catch (error) {
|
|
214
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
215
|
+
if (args.signal?.aborted) {
|
|
216
|
+
// Our own client hung up, and the abort we are catching is the one we
|
|
217
|
+
// forwarded on its behalf. The peer did nothing wrong — cooling it here
|
|
218
|
+
// would let a client that cancels quickly take a whole mesh out of
|
|
219
|
+
// rotation one peer per cancellation.
|
|
220
|
+
logger.debug(`[proxy] peer=${peer.name} attempt cancelled by the caller: ${message}`);
|
|
221
|
+
return {
|
|
222
|
+
ok: false,
|
|
223
|
+
peer,
|
|
224
|
+
reason: "unreachable",
|
|
225
|
+
message: `peer ${peer.name} attempt cancelled`,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
await coolPeer(peer.name, "unreachable").catch((coolError) => {
|
|
229
|
+
logger.debug(`[proxy] could not cool peer=${peer.name}: ${coolError instanceof Error ? coolError.message : String(coolError)}`);
|
|
230
|
+
});
|
|
231
|
+
logger.always(`[proxy] peer=${peer.name} unreachable: ${message}`);
|
|
232
|
+
return {
|
|
233
|
+
ok: false,
|
|
234
|
+
peer,
|
|
235
|
+
reason: "unreachable",
|
|
236
|
+
message: `peer ${peer.name} unreachable`,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
finally {
|
|
240
|
+
clearTimeout(timeout);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
@@ -15,3 +15,11 @@
|
|
|
15
15
|
import type { ProxyPaths } from "../types/index.js";
|
|
16
16
|
export declare function resolveProxyPaths(dev: boolean): ProxyPaths;
|
|
17
17
|
export declare function resolveProxyUsageStatsPath(paths: ProxyPaths): string;
|
|
18
|
+
export declare function resolveProxyGrantsPath(paths: ProxyPaths): string;
|
|
19
|
+
export declare function resolveProxyLedgerPath(paths: ProxyPaths): string;
|
|
20
|
+
export declare function resolveProxyPeersPath(paths: ProxyPaths): string;
|
|
21
|
+
export declare function resolveProxyResidentGrantsPath(paths: ProxyPaths): string;
|
|
22
|
+
export declare function resolveProxyShareAuditPath(paths: ProxyPaths): string;
|
|
23
|
+
export declare function resolveProxyProvisioningPath(paths: ProxyPaths): string;
|
|
24
|
+
export declare function resolveProxyReceiptsPath(paths: ProxyPaths): string;
|
|
25
|
+
export declare function resolveProxyNotesPath(paths: ProxyPaths): string;
|
package/dist/proxy/proxyPaths.js
CHANGED
|
@@ -14,28 +14,66 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import { homedir } from "node:os";
|
|
16
16
|
import { join } from "node:path";
|
|
17
|
+
/**
|
|
18
|
+
* The state files, named once.
|
|
19
|
+
*
|
|
20
|
+
* Each name used to appear three times — dev branch, home branch, and the
|
|
21
|
+
* resolver's fallback — which is three places to keep in step and two chances
|
|
22
|
+
* to write a dev proxy's state into the global directory.
|
|
23
|
+
*/
|
|
24
|
+
const FILE_NAMES = {
|
|
25
|
+
quota: "account-quotas.json",
|
|
26
|
+
cooldown: "account-cooldowns.json",
|
|
27
|
+
stats: "proxy-usage-stats.json",
|
|
28
|
+
grants: "proxy-grants.json",
|
|
29
|
+
ledger: "proxy-share-ledger.json",
|
|
30
|
+
peers: "proxy-peers.json",
|
|
31
|
+
residentGrants: "proxy-resident-grants.json",
|
|
32
|
+
shareAudit: "proxy-share-audit.json",
|
|
33
|
+
provisioning: "proxy-share-provisioning.json",
|
|
34
|
+
receipts: "proxy-share-receipts.json",
|
|
35
|
+
notes: "proxy-share-notes.json",
|
|
36
|
+
};
|
|
17
37
|
export function resolveProxyPaths(dev) {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
stateDir: base,
|
|
22
|
-
logsDir: join(base, "logs"),
|
|
23
|
-
quotaFile: join(base, "account-quotas.json"),
|
|
24
|
-
cooldownFile: join(base, "account-cooldowns.json"),
|
|
25
|
-
statsFile: join(base, "proxy-usage-stats.json"),
|
|
26
|
-
isDev: true,
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
const base = join(homedir(), ".neurolink");
|
|
38
|
+
const base = dev
|
|
39
|
+
? join(process.cwd(), ".neurolink-dev")
|
|
40
|
+
: join(homedir(), ".neurolink");
|
|
30
41
|
return {
|
|
31
42
|
stateDir: base,
|
|
32
43
|
logsDir: join(base, "logs"),
|
|
33
|
-
quotaFile: join(base,
|
|
34
|
-
cooldownFile: join(base,
|
|
35
|
-
statsFile: join(base,
|
|
36
|
-
|
|
44
|
+
quotaFile: join(base, FILE_NAMES.quota),
|
|
45
|
+
cooldownFile: join(base, FILE_NAMES.cooldown),
|
|
46
|
+
statsFile: join(base, FILE_NAMES.stats),
|
|
47
|
+
grantsFile: join(base, FILE_NAMES.grants),
|
|
48
|
+
ledgerFile: join(base, FILE_NAMES.ledger),
|
|
49
|
+
peersFile: join(base, FILE_NAMES.peers),
|
|
50
|
+
isDev: dev,
|
|
37
51
|
};
|
|
38
52
|
}
|
|
39
53
|
export function resolveProxyUsageStatsPath(paths) {
|
|
40
|
-
return paths.statsFile ?? join(paths.stateDir,
|
|
54
|
+
return paths.statsFile ?? join(paths.stateDir, FILE_NAMES.stats);
|
|
55
|
+
}
|
|
56
|
+
export function resolveProxyGrantsPath(paths) {
|
|
57
|
+
return paths.grantsFile ?? join(paths.stateDir, FILE_NAMES.grants);
|
|
58
|
+
}
|
|
59
|
+
export function resolveProxyLedgerPath(paths) {
|
|
60
|
+
return paths.ledgerFile ?? join(paths.stateDir, FILE_NAMES.ledger);
|
|
61
|
+
}
|
|
62
|
+
export function resolveProxyPeersPath(paths) {
|
|
63
|
+
return paths.peersFile ?? join(paths.stateDir, FILE_NAMES.peers);
|
|
64
|
+
}
|
|
65
|
+
export function resolveProxyResidentGrantsPath(paths) {
|
|
66
|
+
return join(paths.stateDir, FILE_NAMES.residentGrants);
|
|
67
|
+
}
|
|
68
|
+
export function resolveProxyShareAuditPath(paths) {
|
|
69
|
+
return join(paths.stateDir, FILE_NAMES.shareAudit);
|
|
70
|
+
}
|
|
71
|
+
export function resolveProxyProvisioningPath(paths) {
|
|
72
|
+
return join(paths.stateDir, FILE_NAMES.provisioning);
|
|
73
|
+
}
|
|
74
|
+
export function resolveProxyReceiptsPath(paths) {
|
|
75
|
+
return join(paths.stateDir, FILE_NAMES.receipts);
|
|
76
|
+
}
|
|
77
|
+
export function resolveProxyNotesPath(paths) {
|
|
78
|
+
return join(paths.stateDir, FILE_NAMES.notes);
|
|
41
79
|
}
|
|
@@ -13,6 +13,7 @@ import { writeFile } from "fs/promises";
|
|
|
13
13
|
import { createHash } from "crypto";
|
|
14
14
|
import { promisify } from "util";
|
|
15
15
|
import { gzip as gzipCallback } from "zlib";
|
|
16
|
+
import { isBorrowedRequest } from "./shareContext.js";
|
|
16
17
|
import { OtelBridge } from "../observability/otelBridge.js";
|
|
17
18
|
import { SeverityNumber } from "@opentelemetry/api-logs";
|
|
18
19
|
import { configureProxyLifecycleLogger } from "./proxyLifecycle.js";
|
|
@@ -558,6 +559,13 @@ export async function logBodyCapture(entry) {
|
|
|
558
559
|
if (!logEnabled || !logDir) {
|
|
559
560
|
return;
|
|
560
561
|
}
|
|
562
|
+
// Borrowed traffic is somebody else's conversation. Capturing it would leave
|
|
563
|
+
// a peer's prompts and the model's replies on this machine's disk, which is
|
|
564
|
+
// not something a share token can be read as consenting to. The request is
|
|
565
|
+
// still logged; only the bodies are dropped.
|
|
566
|
+
if (isBorrowedRequest()) {
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
561
569
|
const bridge = new OtelBridge();
|
|
562
570
|
const traceCtx = entry.traceId && entry.spanId
|
|
563
571
|
? { traceId: entry.traceId, spanId: entry.spanId }
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Credentials a lender provisioned onto this device, and the leases that keep
|
|
3
|
+
* them legitimate.
|
|
4
|
+
*
|
|
5
|
+
* This is the borrower's half of **complete** sharing. The credential itself
|
|
6
|
+
* lives in the normal token store and is routed like any other account — that is
|
|
7
|
+
* the point, since it survives the lender being offline. What lives here is the
|
|
8
|
+
* proof that the lender still consents, and the machinery to keep it fresh.
|
|
9
|
+
*
|
|
10
|
+
* **The enforcement is cooperative and the code should say so.** A resident
|
|
11
|
+
* credential sits on a machine its holder controls, and the token store is
|
|
12
|
+
* obfuscated rather than encrypted. Someone determined to bypass this can. What
|
|
13
|
+
* these checks buy is that the honest path is also the correct one: a borrower
|
|
14
|
+
* running the shipped software stops when the lender says stop, and stops on its
|
|
15
|
+
* own if the lender becomes unreachable for longer than the lease allows.
|
|
16
|
+
*
|
|
17
|
+
* @module proxy/residentGrants
|
|
18
|
+
*/
|
|
19
|
+
import type { ProxyResidentGrant, ProxyShareLease, ProxyShareLeaseVerdict } from "../types/index.js";
|
|
20
|
+
export declare function initResidentGrants(filePath: string): void;
|
|
21
|
+
export declare function listResidentGrants(): Promise<ProxyResidentGrant[]>;
|
|
22
|
+
export declare function getResidentGrantForAccount(accountKeyOrLabel: string): Promise<ProxyResidentGrant | undefined>;
|
|
23
|
+
export declare function saveResidentGrant(grant: ProxyResidentGrant): Promise<void>;
|
|
24
|
+
export declare function removeResidentGrant(accountLabel: string): Promise<boolean>;
|
|
25
|
+
/**
|
|
26
|
+
* May this resident account serve right now?
|
|
27
|
+
*
|
|
28
|
+
* Returns `undefined` for an account that is not resident at all — the node's
|
|
29
|
+
* own credentials, which answer to nobody.
|
|
30
|
+
*/
|
|
31
|
+
export declare function evaluateResidentAccount(accountKeyOrLabel: string, now?: number): Promise<ProxyShareLeaseVerdict | undefined>;
|
|
32
|
+
/** Accumulate spend the borrower owes the lender an account of. */
|
|
33
|
+
export declare function recordResidentSpend(accountLabel: string, coins: number): Promise<void>;
|
|
34
|
+
/**
|
|
35
|
+
* Check in with a lender: report what was spent, collect a fresh lease.
|
|
36
|
+
*
|
|
37
|
+
* Reporting happens **before** the new lease is stored, and the counters are
|
|
38
|
+
* only drawn down once the lender has acknowledged them — a heartbeat that
|
|
39
|
+
* fails halfway leaves the spend to be reported again rather than losing it.
|
|
40
|
+
*
|
|
41
|
+
* A `stop` answer is honored immediately by clearing the lease's grace: the
|
|
42
|
+
* lender has said no, and there is nothing to wait out.
|
|
43
|
+
*/
|
|
44
|
+
export declare function heartbeatResidentGrant(resident: ProxyResidentGrant, now?: number): Promise<{
|
|
45
|
+
ok: boolean;
|
|
46
|
+
stopped: boolean;
|
|
47
|
+
detail: string;
|
|
48
|
+
}>;
|
|
49
|
+
/**
|
|
50
|
+
* Check in with every lender whose heartbeat is due.
|
|
51
|
+
*
|
|
52
|
+
* Best-effort by design: a lender being unreachable is the case the offline
|
|
53
|
+
* grace exists for, not an error to surface.
|
|
54
|
+
*/
|
|
55
|
+
export declare function heartbeatDueResidentGrants(now?: number): Promise<void>;
|
|
56
|
+
/** The lease a resident account is currently operating under. */
|
|
57
|
+
export declare function residentLease(resident: ProxyResidentGrant): ProxyShareLease;
|