@securecoms/agent-bridge 0.30.0 → 0.30.2

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.
@@ -353,6 +353,9 @@ export interface BridgeAgentDeps {
353
353
  transport?: any;
354
354
  backend?: any;
355
355
  fetchImpl?: typeof fetch;
356
+ /** TEST SEAM: WebSocket constructor for the real RestTransport's delivery-ping
357
+ * socket. Production leaves it undefined (globalThis.WebSocket). */
358
+ wsImpl?: typeof WebSocket;
356
359
  /**
357
360
  * Round 5 (runtime config delivery): the home directory under which the
358
361
  * managed runtime-config directory lives. Injected only by tests, so they can
@@ -714,6 +717,15 @@ export declare class RoomClassChangedSendError extends SendForbiddenError {
714
717
  * `activeGroupId` is the group the backend named, or null when the channel has
715
718
  * no active group (fail closed: nothing to converge to).
716
719
  */
720
+ /**
721
+ * C10 (spec P-15, §4.1.6): start() was superseded (4409) before it finished.
722
+ * start() has already run the memoized close() quiesce when this is thrown, and
723
+ * armed none of its timers or the receive pump. runMode catches it: the process
724
+ * stays alive holding the lock and the port, and serves no route.
725
+ */
726
+ export declare class SessionSupersededError extends Error {
727
+ constructor();
728
+ }
717
729
  export declare class GroupSupersededSendError extends Error {
718
730
  readonly channelId: string;
719
731
  readonly activeGroupId: string | null;
@@ -780,6 +792,13 @@ export declare class BridgeAgent {
780
792
  private readonly backend;
781
793
  private readonly custody;
782
794
  private readonly fetchImpl;
795
+ /** P-1: set once a 4409 close says another copy took this agent's session.
796
+ * The gated fetch built in the constructor refuses every request after it. */
797
+ private sessionSuperseded;
798
+ /** C9: who hears `session_superseded` (runMode's quiesce). */
799
+ private readonly sessionSupersededListeners;
800
+ /** C9 (spec §4.1.6, low 3): the one close() run every caller shares. */
801
+ private closing;
783
802
  /** Round 5: home for the managed runtime-config directory (test seam). */
784
803
  private readonly home;
785
804
  private readonly grantWriters;
@@ -1243,6 +1262,12 @@ export declare class BridgeAgent {
1243
1262
  readonly action: AgentActionAPI;
1244
1263
  private readonly waiters;
1245
1264
  private pingHandle?;
1265
+ /** C10 (P-15): the delivery-ping subscription start() opens at its top,
1266
+ * held unconsumed until startReceivePump() takes it. */
1267
+ private earlyPings;
1268
+ /** C10 fix round 1: settles (never rejects) when start()'s body does; close()
1269
+ * awaits it, bounded, so the store never closes under a start() step. */
1270
+ private startInFlight;
1246
1271
  private safetyTimer?;
1247
1272
  /** The ingest cycle now running, if any (see ingestOnce: single-flight). */
1248
1273
  private ingestInFlight;
@@ -1490,7 +1515,24 @@ export declare class BridgeAgent {
1490
1515
  * shed so a silently-dropping daemon stays observable (item 3 / ADR-0002).
1491
1516
  */
1492
1517
  private capBuffer;
1518
+ /**
1519
+ * C10 (spec P-15, OQ-8 (b)): subscribe early, consume late. The
1520
+ * delivery-ping socket opens here, before anything else, so this copy claims
1521
+ * the device's session (and another copy gets its 4409) BEFORE this copy's
1522
+ * boot drain runs. The pings it carries are NOT consumed until
1523
+ * startReceivePump(), the same place as before: starting the pump first is
1524
+ * still forbidden (DRAIN BEFORE ROTATE, startBody).
1525
+ *
1526
+ * A 4409 during start() (audit H-3): the gated fetch is already set (C9 step
1527
+ * 0). start() then arms nothing more (supersededDuringStart before each
1528
+ * timer and before the pump), runs the memoized close() quiesce, and rejects
1529
+ * with SessionSupersededError. A step that FAILED because of the supersede
1530
+ * (a refused fetch, a store the quiesce closed) is the same outcome.
1531
+ */
1493
1532
  start(): Promise<void>;
1533
+ /** C10: true once a 4409 landed; checked before each arming step of start(). */
1534
+ private supersededDuringStart;
1535
+ private startBody;
1494
1536
  /**
1495
1537
  * #811 L-1: send the re-Welcome signal ONCE for each channel whose onWedged
1496
1538
  * heal the boot drain refused. Runs after the drain settled and after the
@@ -3485,5 +3527,23 @@ export declare class BridgeAgent {
3485
3527
  * and zero on restart (spec §5.5). */
3486
3528
  build: string;
3487
3529
  };
3530
+ /** C9: `session_superseded` once a 4409 said another copy took this
3531
+ * agent's session (the gated fetch already refuses every request). The
3532
+ * local HTTP routes read it: every route but `/healthz` answers 503. */
3533
+ get sessionState(): "live" | "session_superseded";
3534
+ /** C9: subscribe to `session_superseded`. runMode subscribes right after
3535
+ * construction, before start(), so a 4409 during start() is heard too. */
3536
+ on(event: "session_superseded", listener: () => void): () => void;
3537
+ /** P-1 step 0, then the event (spec §4.1.6): another copy of this agent took
3538
+ * the session (4409). The gated-fetch switch is set FIRST, so no HTTP
3539
+ * request leaves the process after this, whatever a listener does. */
3540
+ private onSessionSuperseded;
3541
+ /**
3542
+ * Idempotent (C9, spec §4.1.6 low 3): every call returns the FIRST call's
3543
+ * promise, so a SIGTERM during or after the supersede quiesce never runs
3544
+ * store.close() twice. A rejected close stays rejected: a retry would close
3545
+ * the store a second time.
3546
+ */
3488
3547
  close(): Promise<void>;
3548
+ private closeOnce;
3489
3549
  }
package/dist/config.d.ts CHANGED
@@ -56,6 +56,19 @@ export interface BridgeConfig {
56
56
  ownerGrantPin?: OwnerGrantPinResult;
57
57
  }
58
58
  export declare const REQUIRED: readonly ["api_key", "identity_id", "device_id", "signing_private_key", "enclave_id", "api_base"];
59
+ export type ConfigErrorReason = "credential_not_found" | "credential_unreadable" | "credential_not_json" | "credential_missing_field" | "credential_voice_invalid";
60
+ /**
61
+ * A key-file load failure with a fixed reason code, set by the step that
62
+ * failed (spec 2026-10-15 §5.2). Callers map the code to a log field and a
63
+ * fixed human line; nothing reads `message`, which carries no path, no
64
+ * identifier and no upstream error text.
65
+ */
66
+ export declare class ConfigError extends Error {
67
+ readonly reason: ConfigErrorReason;
68
+ readonly field?: (typeof REQUIRED)[number] | undefined;
69
+ readonly source?: "file" | "env" | undefined;
70
+ constructor(reason: ConfigErrorReason, field?: (typeof REQUIRED)[number] | undefined, source?: "file" | "env" | undefined);
71
+ }
59
72
  export declare function loadConfig(opts: {
60
73
  credentialPath: string;
61
74
  storePath?: string;
package/dist/errors.d.ts CHANGED
@@ -6,7 +6,7 @@
6
6
  * WebSocket returns `{type:"error", error, message, detail?}`. A client gets the
7
7
  * same `error` code regardless of which transport it used. (A2A §9.5/§10.6 pattern.)
8
8
  */
9
- export type BridgeErrorCode = "unauthorized" | "forbidden_origin" | "not_found" | "invalid_request" | "invalid_json" | "body_too_large" | "channel_pending" | "rate_limited" | "agent_paused" | "send_forbidden" | "group_superseded" | "unknown_action_run" | "vault_integrity_failed" | "vault_ref_changed" | "internal";
9
+ export type BridgeErrorCode = "unauthorized" | "forbidden_origin" | "not_found" | "invalid_request" | "invalid_json" | "body_too_large" | "channel_pending" | "rate_limited" | "agent_paused" | "send_forbidden" | "group_superseded" | "unknown_action_run" | "vault_integrity_failed" | "vault_ref_changed" | "session_superseded" | "internal";
10
10
  export interface BridgeErrorSpec {
11
11
  /** HTTP status this code maps to (the WS binding ignores it). */
12
12
  http: number;