@scp-dev/hub 0.2.0 → 0.3.1
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/index.d.ts +177 -3
- package/dist/index.js +668 -119
- package/package.json +2 -2
- package/src/federation.ts +24 -1
- package/src/hub.ts +102 -12
- package/src/index.ts +11 -0
- package/src/judge-gate.ts +575 -0
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as _scp_dev_core from '@scp-dev/core';
|
|
2
|
+
import { Envelope, RelayCore, CoreOptions, EndpointRow, PolicySubject, ScopeSubject, ScopeNode, ScopeDirection, Admission, MessageRow, EndpointView, ScopeCap, ScopeEntry, Claim, SendParams, SendResult, EndpointKind, Presence, StatusState, Verdict, JudgeModelConfig, JudgeClient, Sql, JudgePolicy, Wake, CrossAccountLink, XDelivered, XListed, ActivityRow, XRelease } from '@scp-dev/core';
|
|
2
3
|
|
|
3
4
|
type RelayAuth = Envelope["from"]["auth"];
|
|
4
5
|
/** a machine name: one relay host of an account */
|
|
@@ -58,6 +59,8 @@ interface MirrorView extends MirrorScope {
|
|
|
58
59
|
presence?: Presence;
|
|
59
60
|
last_seen?: string | null;
|
|
60
61
|
title?: string | null;
|
|
62
|
+
/** an agent's {@link Wake}; a value this hub does not know is kept as none */
|
|
63
|
+
wake?: string | null;
|
|
61
64
|
/** the handle the endpoint had before a rename: its mirror is renamed in place */
|
|
62
65
|
was?: string;
|
|
63
66
|
}
|
|
@@ -273,6 +276,11 @@ declare class FederatedCore extends RelayCore {
|
|
|
273
276
|
recordForward(from: string, params: SendParams, result: SendResult, origin?: {
|
|
274
277
|
tty?: boolean;
|
|
275
278
|
}): void;
|
|
279
|
+
/**
|
|
280
|
+
* records that a forwarded message reached mirrored endpoints of other machines, unacknowledged, as a hub's delivery rows would: their
|
|
281
|
+
* replies then join its thread here. For links that deliver machine to machine; a hub bridge never needs it
|
|
282
|
+
*/
|
|
283
|
+
recordRemoteDelivery(messageId: string, endpoints: string[]): void;
|
|
276
284
|
/** the earlier result of a forwarded send with this client_id, or null; a different message under the same client_id is refused */
|
|
277
285
|
forwarded(from: string, params: SendParams): SendResult | null;
|
|
278
286
|
/** one delivery as the hub pushes it, or null when it is gone, acked or expired */
|
|
@@ -380,6 +388,139 @@ declare class FederatedCore extends RelayCore {
|
|
|
380
388
|
judgeNoticeTo(endpoint: string, text: string): number;
|
|
381
389
|
}
|
|
382
390
|
|
|
391
|
+
type JudgeClientFactory = (model: JudgeModelConfig, key: () => Promise<string>) => JudgeClient;
|
|
392
|
+
/** `machine`: the hub machine a direct client's unqualified addresses resolve on */
|
|
393
|
+
type Origin = {
|
|
394
|
+
tty?: boolean;
|
|
395
|
+
unverified?: boolean;
|
|
396
|
+
machine?: string | null;
|
|
397
|
+
};
|
|
398
|
+
/** sends as `from` through the same path an unjudged send takes, waking recipients */
|
|
399
|
+
type Deliver = (from: string, params: SendParams, origin: Origin) => Promise<SendResult>;
|
|
400
|
+
interface JudgeGateOptions {
|
|
401
|
+
sql: Sql;
|
|
402
|
+
core: FederatedCore;
|
|
403
|
+
now: () => number;
|
|
404
|
+
/** where key_env is read */
|
|
405
|
+
env: Record<string, string | undefined>;
|
|
406
|
+
client?: JudgeClientFactory | undefined;
|
|
407
|
+
log: (line: string) => void;
|
|
408
|
+
/** an endpoint got a notice */
|
|
409
|
+
wake: (endpoint: string) => void;
|
|
410
|
+
/** a status notice to a hold's sender; default core.judgeNotice, then wake */
|
|
411
|
+
notice?: ((endpoint: string, text: string) => void) | undefined;
|
|
412
|
+
/** runs fn where the sender's unqualified addresses resolve (default: as it is) */
|
|
413
|
+
within?: (<T>(origin: Origin, fn: () => T) => T) | undefined;
|
|
414
|
+
/** the reader of a model's key_file; without one a key_file model judges with the prefilter only */
|
|
415
|
+
keyFile?: ((path: string) => (() => Promise<string>) | null) | undefined;
|
|
416
|
+
/** overrides of {@link JUDGE_LIMITS}; `Infinity` disables a cap */
|
|
417
|
+
limits?: Partial<JudgeLimits> | undefined;
|
|
418
|
+
}
|
|
419
|
+
/** judged sends per sender and lineage tree, and a sender's waiting holds */
|
|
420
|
+
interface JudgeLimits {
|
|
421
|
+
endpointPerMinute: number;
|
|
422
|
+
endpointPerHour: number;
|
|
423
|
+
treePerMinute: number;
|
|
424
|
+
treePerHour: number;
|
|
425
|
+
heldPerSender: number;
|
|
426
|
+
}
|
|
427
|
+
/** held sends never reach the send's rate limits, so they are capped here */
|
|
428
|
+
declare const JUDGE_LIMITS: Readonly<JudgeLimits>;
|
|
429
|
+
interface HoldRow {
|
|
430
|
+
id: string;
|
|
431
|
+
from_ep: string;
|
|
432
|
+
params: string;
|
|
433
|
+
origin: string;
|
|
434
|
+
verdict: string;
|
|
435
|
+
created_at: number;
|
|
436
|
+
expires_at: number;
|
|
437
|
+
state: "held" | "releasing" | "released" | "refused" | "expired";
|
|
438
|
+
by: string | null;
|
|
439
|
+
resolved_at: number | null;
|
|
440
|
+
}
|
|
441
|
+
/** a hold as the human's review lists it: never the body */
|
|
442
|
+
interface HoldView {
|
|
443
|
+
id: string;
|
|
444
|
+
from: string;
|
|
445
|
+
to: string[];
|
|
446
|
+
state: HoldRow["state"];
|
|
447
|
+
created_at: string;
|
|
448
|
+
expires_at: string;
|
|
449
|
+
categories: string[];
|
|
450
|
+
reason: string;
|
|
451
|
+
boundary: string;
|
|
452
|
+
source: Verdict["source"];
|
|
453
|
+
bytes: number;
|
|
454
|
+
by: string | null;
|
|
455
|
+
resolved_at: string | null;
|
|
456
|
+
}
|
|
457
|
+
/**
|
|
458
|
+
* a judge in front of a relay's sends (the broker's policy `judge`, or a hosted hub's) (policy `judge`): shadow judges after delivery and only audits; enforce judges before delivery and passes,
|
|
459
|
+
* redacts, refuses or holds. Holds wait in `judge_holds` for the human at a terminal; their body is purged once resolved or expired
|
|
460
|
+
*/
|
|
461
|
+
declare class JudgeGate {
|
|
462
|
+
private readonly o;
|
|
463
|
+
readonly stats: _scp_dev_core.JudgeStats;
|
|
464
|
+
private policy;
|
|
465
|
+
private version;
|
|
466
|
+
private client;
|
|
467
|
+
private readonly cache;
|
|
468
|
+
private readonly shadows;
|
|
469
|
+
/** judged sends in the last hour per budget: `ep:<id>`, `tree:<root id>` and `*` for the whole broker */
|
|
470
|
+
private readonly spent;
|
|
471
|
+
private readonly limits;
|
|
472
|
+
constructor(o: JudgeGateOptions);
|
|
473
|
+
/** the policy's judge section; a model client only when its key is readable, else the prefilter alone */
|
|
474
|
+
configure(policy: JudgePolicy | undefined): void;
|
|
475
|
+
private keyReader;
|
|
476
|
+
/** resolves once every shadow judgement started so far finished */
|
|
477
|
+
settled(): Promise<void>;
|
|
478
|
+
/** a send or ask through the judge; an unjudged one goes straight to `deliver` */
|
|
479
|
+
send(from: string, params: SendParams, origin: Origin, deliver: Deliver): Promise<SendResult>;
|
|
480
|
+
/** an earlier judged send or waiting hold under the send's client_id: the same message is answered as before, another one refused as core refuses it */
|
|
481
|
+
private seen;
|
|
482
|
+
private remember;
|
|
483
|
+
/**
|
|
484
|
+
* a judged send counts before any model call against its sender's, its lineage tree's and the whole broker's budget, since refused and
|
|
485
|
+
* held sends never reach the send's own limits, and a new handle or a helper process must not bring a fresh budget
|
|
486
|
+
*/
|
|
487
|
+
private spend;
|
|
488
|
+
/** how many budgets are tracked (idle ones are evicted by the sweep) */
|
|
489
|
+
budgetKeys(): number;
|
|
490
|
+
/** the held messages, newest first, without bodies */
|
|
491
|
+
holds(state?: string): HoldView[];
|
|
492
|
+
/** one hold with its body and the judge's redactions while it waits */
|
|
493
|
+
show(id: string): HoldView & {
|
|
494
|
+
body?: string;
|
|
495
|
+
redactions?: {
|
|
496
|
+
quote?: string;
|
|
497
|
+
kind: string;
|
|
498
|
+
}[];
|
|
499
|
+
};
|
|
500
|
+
/** delivers a held message as its sender, as stored or redacted: scope, policy and limits are checked again by the send itself */
|
|
501
|
+
release(id: string, options: {
|
|
502
|
+
redacted?: boolean;
|
|
503
|
+
by: string;
|
|
504
|
+
}, deliver: Deliver): Promise<SendResult>;
|
|
505
|
+
/** refuses a held message: it is never delivered, and its sender hears the human's reason */
|
|
506
|
+
refuse(id: string, reason: string, by: string): HoldView;
|
|
507
|
+
/** expires holds past their message TTL (body purged, sender told) and drops resolved ones past the audit window */
|
|
508
|
+
sweep(): void;
|
|
509
|
+
private expire;
|
|
510
|
+
/** the recipients by the boundary their message crosses; throws as send does for a target the sender cannot reach */
|
|
511
|
+
private boundaries;
|
|
512
|
+
private judge;
|
|
513
|
+
private shadow;
|
|
514
|
+
private hold;
|
|
515
|
+
private detail;
|
|
516
|
+
private storedDetail;
|
|
517
|
+
/** marks a hold resolved and purges its body and the judge's quotes of it */
|
|
518
|
+
private resolve;
|
|
519
|
+
private tell;
|
|
520
|
+
private row;
|
|
521
|
+
private view;
|
|
522
|
+
}
|
|
523
|
+
|
|
383
524
|
type HubAuth = "account" | "account+bridge";
|
|
384
525
|
type HubRole = "owner" | "admin" | "member";
|
|
385
526
|
/** what the host proved about the token, plus its privilege */
|
|
@@ -428,7 +569,7 @@ interface GraphNode {
|
|
|
428
569
|
status: EndpointView["status"];
|
|
429
570
|
last_seen: string | null;
|
|
430
571
|
parent: string | null;
|
|
431
|
-
wake?:
|
|
572
|
+
wake?: Wake | null;
|
|
432
573
|
}
|
|
433
574
|
/** `spawn`: parent to child; `message`: sender to recipient, with the count and newest send in the graph's window */
|
|
434
575
|
type GraphEdge = {
|
|
@@ -475,6 +616,15 @@ interface HubDeps {
|
|
|
475
616
|
/** the link to other accounts' relays; without it an address of another account is not found */
|
|
476
617
|
crossAccount?: CrossAccountLink;
|
|
477
618
|
}
|
|
619
|
+
/** what a configured judge needs besides its policy */
|
|
620
|
+
interface HubJudgeOptions {
|
|
621
|
+
/** where the model's key_env is read */
|
|
622
|
+
env: Record<string, string | undefined>;
|
|
623
|
+
/** the model client (default the provider's HTTP API) */
|
|
624
|
+
client?: JudgeClientFactory | undefined;
|
|
625
|
+
/** overrides of the judge's own caps; `Infinity` disables one */
|
|
626
|
+
limits?: Partial<JudgeLimits> | undefined;
|
|
627
|
+
}
|
|
478
628
|
declare const HUB_LIMITS: {
|
|
479
629
|
readonly machines: 16;
|
|
480
630
|
readonly endpoints: 512;
|
|
@@ -513,6 +663,8 @@ declare class RelayHub {
|
|
|
513
663
|
private xcounters;
|
|
514
664
|
private xkey;
|
|
515
665
|
private readonly limits;
|
|
666
|
+
private judgeConfig;
|
|
667
|
+
private gate;
|
|
516
668
|
constructor(deps: HubDeps);
|
|
517
669
|
/** idempotent: migrates and records the account handle (the address user part, e.g. "ada"); call before handling each request */
|
|
518
670
|
init(account: {
|
|
@@ -520,6 +672,22 @@ declare class RelayHub {
|
|
|
520
672
|
}): void;
|
|
521
673
|
/** forgets the core and in-memory state after the object's storage was deleted; the next init migrates afresh */
|
|
522
674
|
reset(): void;
|
|
675
|
+
/**
|
|
676
|
+
* judges this account's own sends (client send and ask, bridge forward) before delivery; sends to other accounts are left to the
|
|
677
|
+
* cross-account gate. Without a policy, or with mode off, the hub holds no judge and creates no judge tables
|
|
678
|
+
*/
|
|
679
|
+
configureJudge(policy: JudgePolicy | undefined, options?: HubJudgeOptions): void;
|
|
680
|
+
/** the held messages, newest first, without bodies */
|
|
681
|
+
holds(state?: string): HoldView[];
|
|
682
|
+
/** one hold with its body while it waits */
|
|
683
|
+
holdShow(id: string): ReturnType<JudgeGate["show"]>;
|
|
684
|
+
/** delivers a held message as its sender, every send check run again */
|
|
685
|
+
holdRelease(id: string, options: {
|
|
686
|
+
redacted?: boolean;
|
|
687
|
+
by: string;
|
|
688
|
+
}): Promise<SendResult>;
|
|
689
|
+
/** refuses a held message: never delivered, its sender told why */
|
|
690
|
+
holdRefuse(id: string, reason: string, by: string): HoldView;
|
|
523
691
|
/** a fresh connection state for an accepted socket */
|
|
524
692
|
static connection(grant: HubGrant): HubConn;
|
|
525
693
|
/** a socket would exceed the `sockets` limit: the host refuses it (429) instead of accepting */
|
|
@@ -602,6 +770,12 @@ declare class RelayHub {
|
|
|
602
770
|
private xinbound;
|
|
603
771
|
/** what every contact this account may message lets it reach */
|
|
604
772
|
private contactList;
|
|
773
|
+
/** the judge, made on first use once configured; null when it is not */
|
|
774
|
+
private judgeGate;
|
|
775
|
+
private judged;
|
|
776
|
+
/** a send of this account as it runs unjudged: room for the body, the core send on the sender's machine, then push */
|
|
777
|
+
private deliverOn;
|
|
778
|
+
private readonly deliverJudged;
|
|
605
779
|
private delivered;
|
|
606
780
|
/** the body's bytes when the account can hold them; refuses a send past the `storedBytes` limit */
|
|
607
781
|
private room;
|
|
@@ -668,4 +842,4 @@ declare class RelayHub {
|
|
|
668
842
|
xnotice(endpoint: string, text: string): void;
|
|
669
843
|
}
|
|
670
844
|
|
|
671
|
-
export { type Delivery, FederatedCore, type FederatedOptions, GRAPH_MINUTES, GRAPH_MINUTES_MAX, type GraphEdge, type GraphNode, HUB_LIMITS, type HubAuth, type HubConn, type HubDeps, type HubGrant, type HubGraph, type HubLimits, type HubRole, type HubSocket, MACHINE_NAME, type MachineRow, type MirrorCaps, type MirrorChange, type MirrorRules, type MirrorScope, type MirrorView, type RelayAuth, RelayHub, type RemoteView, type XEndpoint };
|
|
845
|
+
export { type Deliver, type Delivery, FederatedCore, type FederatedOptions, GRAPH_MINUTES, GRAPH_MINUTES_MAX, type GraphEdge, type GraphNode, HUB_LIMITS, type HoldView, type HubAuth, type HubConn, type HubDeps, type HubGrant, type HubGraph, type HubJudgeOptions, type HubLimits, type HubRole, type HubSocket, JUDGE_LIMITS, type JudgeClientFactory, JudgeGate, type JudgeGateOptions, type JudgeLimits, type Origin as JudgeOrigin, MACHINE_NAME, type MachineRow, type MirrorCaps, type MirrorChange, type MirrorRules, type MirrorScope, type MirrorView, type RelayAuth, RelayHub, type RemoteView, type XEndpoint };
|