@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 CHANGED
@@ -1,4 +1,5 @@
1
- import { Envelope, RelayCore, CoreOptions, EndpointRow, PolicySubject, ScopeSubject, ScopeNode, ScopeDirection, Admission, MessageRow, EndpointView, ScopeCap, ScopeEntry, Claim, SendParams, SendResult, EndpointKind, Presence, StatusState, Sql, CrossAccountLink, XDelivered, XListed, ActivityRow, XRelease } from '@scp-dev/core';
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?: string | null;
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 };