@securecoms/agent-bridge 0.10.0 → 0.11.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/attachments.d.ts +8 -4
- package/dist/attachments.js +49 -0
- package/dist/auth.js +56 -0
- package/dist/bin.js +1005 -121
- package/dist/bin.js.pre-c1-f803007 +5795 -0
- package/dist/bin.js.pre-c3-096a9a3 +5832 -0
- package/dist/bin.js.pre-ffrebuild-b162d4a +5639 -0
- package/dist/bin.js.pre-kpheal-20260708-150924 +5531 -0
- package/dist/bin.js.pre-kppersist-20260708-152341 +5543 -0
- package/dist/bin.js.pre-p0-20260708-102415 +5196 -0
- package/dist/bin.js.pre-p1b-02f47e1 +5639 -0
- package/dist/bin.js.pre-warmfix-20260708-153705 +5613 -0
- package/dist/bin.js.pretrace-113205 +5395 -0
- package/dist/bridge-agent.d.ts +110 -19
- package/dist/bridge-agent.js +831 -0
- package/dist/config.js +74 -0
- package/dist/coordination.js +63 -0
- package/dist/errors.js +25 -0
- package/dist/http-server.js +220 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1006 -121
- package/dist/index.js.pre-p0-20260708-102415 +4879 -0
- package/dist/latency.js +87 -0
- package/dist/rate-limit.js +56 -0
- package/dist/service/config.js +26 -0
- package/dist/service/install.js +60 -0
- package/dist/service/launchd.js +30 -0
- package/dist/service/systemd.js +25 -0
- package/dist/sodium-ready.d.ts +43 -0
- package/dist/vault.d.ts +96 -0
- package/dist/ws-server.js +254 -0
- package/package.json +2 -2
package/dist/bridge-agent.d.ts
CHANGED
|
@@ -574,6 +574,34 @@ export declare class BridgeAgent {
|
|
|
574
574
|
* DEVICE never held a leaf can 403 on the signal call. That's harmless and
|
|
575
575
|
* must not abort the sweep for every other channel.
|
|
576
576
|
*/
|
|
577
|
+
/**
|
|
578
|
+
* One best-effort device-level KeyPackage rotation attempt.
|
|
579
|
+
*
|
|
580
|
+
* `publishKeyPackage` is a cheap no-op skip unless the SERVER says the held
|
|
581
|
+
* KeyPackage is expired, so calling it every cycle costs one read and rotates at
|
|
582
|
+
* most once per TTL. The catch logs once and continues: it MUST NOT rethrow, MUST
|
|
583
|
+
* NOT abort the sweep, and MUST NOT retry inside the cycle. A KeyPackage that
|
|
584
|
+
* cannot be rotated this cycle is retried on the next interval, and the sweep's
|
|
585
|
+
* per-channel repair work is independent of it.
|
|
586
|
+
*/
|
|
587
|
+
/**
|
|
588
|
+
* Tri-state: "is this device STILL serving a server-expired KeyPackage at the end
|
|
589
|
+
* of the cycle that just tried to fix it?"
|
|
590
|
+
*
|
|
591
|
+
* `false` is the only healthy value, and it requires a POSITIVE, current proof of
|
|
592
|
+
* non-expiry. `null` is unknown and reads exactly like `true` in `degraded`, so a
|
|
593
|
+
* daemon that cannot prove itself un-expired stops reporting healthy. That is the
|
|
594
|
+
* OPPOSITE direction to the rotation guard, which never rotates on unknown.
|
|
595
|
+
* Conflating the two directions recreates the lie this change removes.
|
|
596
|
+
*
|
|
597
|
+
* Initialised `null` and written only by `selfHealSweep`, so a sweep-off daemon
|
|
598
|
+
* reads `null` and therefore degraded.
|
|
599
|
+
*/
|
|
600
|
+
private keypackageExpired;
|
|
601
|
+
/** Resolve the flag from the SAME two inputs the guard uses, with every unknown
|
|
602
|
+
* mapped to `null`. Never throws: a failure to measure is itself unknown. */
|
|
603
|
+
private resolveKeypackageExpired;
|
|
604
|
+
private rotateDeviceKeyPackage;
|
|
577
605
|
private selfHealSweep;
|
|
578
606
|
/**
|
|
579
607
|
* Per-channel send-floor check. No-op on allow; throws RateLimitedError on a
|
|
@@ -617,6 +645,7 @@ export declare class BridgeAgent {
|
|
|
617
645
|
status: "sent";
|
|
618
646
|
}>;
|
|
619
647
|
private sendDeps;
|
|
648
|
+
private receiveDeps;
|
|
620
649
|
sendAttachment(channelId: string, filePath: string, caption?: string): Promise<{
|
|
621
650
|
message_id: string;
|
|
622
651
|
status: "sent";
|
|
@@ -630,8 +659,11 @@ export declare class BridgeAgent {
|
|
|
630
659
|
/**
|
|
631
660
|
* List channels in one enclave (default: the daemon's own home enclave).
|
|
632
661
|
* Task 3: called per-*known* enclave by refreshChannelMeta so a
|
|
633
|
-
* cross-enclave room's is_dm gets resolved
|
|
634
|
-
*
|
|
662
|
+
* cross-enclave room's is_dm gets resolved. ⚠️ The old "same-tenant only; a
|
|
663
|
+
* cross-tenant enclave 404s under the agent's bearer" caveat here is refuted
|
|
664
|
+
* — see the CROSS-TENANT LISTING note on enclavesToCover. This route admits
|
|
665
|
+
* any enclave the caller holds a live membership row in, partner tenants
|
|
666
|
+
* included.
|
|
635
667
|
*/
|
|
636
668
|
listChannels(enclaveId?: string): Promise<Array<{
|
|
637
669
|
channel_id: string;
|
|
@@ -666,12 +698,16 @@ export declare class BridgeAgent {
|
|
|
666
698
|
* room is never listed under the home-enclave-only call, so its is_dm never
|
|
667
699
|
* resolves and the @mention filter fails open for that room.
|
|
668
700
|
*
|
|
669
|
-
* Each enclave is wrapped in its own try/catch
|
|
670
|
-
*
|
|
671
|
-
*
|
|
672
|
-
*
|
|
673
|
-
*
|
|
674
|
-
*
|
|
701
|
+
* Each enclave is wrapped in its own try/catch so one enclave's listing
|
|
702
|
+
* failure leaves only that enclave's channels unresolved (fail open) without
|
|
703
|
+
* aborting the other enclaves in the set.
|
|
704
|
+
*
|
|
705
|
+
* ⚠️ The old scope caveat here (audit H2/tenant) said
|
|
706
|
+
* `list_channels_in_enclave` filters on tenant_id and 404s a cross-TENANT
|
|
707
|
+
* federated-partner enclave. That is refuted — see the CROSS-TENANT LISTING
|
|
708
|
+
* note on enclavesToCover. A partner enclave with a live membership row
|
|
709
|
+
* resolves like any other, so the try/catch now covers transport errors,
|
|
710
|
+
* 5xx, and genuine non-member 404s only.
|
|
675
711
|
*/
|
|
676
712
|
private refreshChannelMeta;
|
|
677
713
|
/** Cache for the server-side enclave enumeration (see enclavesToCover). */
|
|
@@ -686,12 +722,20 @@ export declare class BridgeAgent {
|
|
|
686
722
|
* failure right after a restart would read as "never succeeded" and slip
|
|
687
723
|
* through the expected-gap branch — weaker than the old abort-on-failure).
|
|
688
724
|
* A failure of a member of this set is a REGRESSION and degrades health; a
|
|
689
|
-
* failure of a never-succeeded enclave
|
|
690
|
-
*
|
|
691
|
-
*
|
|
725
|
+
* failure of a never-succeeded enclave is reported but does not degrade.
|
|
726
|
+
*
|
|
727
|
+
* ⚠️ The old justification for that asymmetry named a partner enclave's
|
|
728
|
+
* "tenant-scoped 404, permanent pending backend authz" as the expected gap.
|
|
729
|
+
* That premise is refuted — see the CROSS-TENANT LISTING note on
|
|
730
|
+
* enclavesToCover; a partner enclave with a live membership row lists fine.
|
|
731
|
+
* The asymmetry is kept on its remaining merit: an enclave this process has
|
|
732
|
+
* never listed may simply be one this agent holds no live membership in, and
|
|
733
|
+
* pegging health degraded forever on that is noise, not signal. */
|
|
692
734
|
private readonly enclaveListEverSucceeded;
|
|
693
735
|
/** Warn-once-per-failure-streak, keyed by enclave id (cleared on success):
|
|
694
|
-
* a
|
|
736
|
+
* a persistently-failing enclave must not log 1440 lines a day. (The old
|
|
737
|
+
* wording named "a permanently-404ing partner enclave" — refuted, see the
|
|
738
|
+
* CROSS-TENANT LISTING note on enclavesToCover.) */
|
|
695
739
|
private readonly enclaveListWarned;
|
|
696
740
|
private static readonly ENCLAVE_ENUM_TTL_MS;
|
|
697
741
|
private enclaveEnumWarned;
|
|
@@ -719,11 +763,35 @@ export declare class BridgeAgent {
|
|
|
719
763
|
* membership changes are picked up within one TTL, which matches the sweep
|
|
720
764
|
* cadence this exists to serve.
|
|
721
765
|
*
|
|
722
|
-
*
|
|
723
|
-
*
|
|
724
|
-
*
|
|
725
|
-
*
|
|
726
|
-
*
|
|
766
|
+
* ── CROSS-TENANT LISTING: the canonical statement for this file ──────────
|
|
767
|
+
*
|
|
768
|
+
* ⚠️ CORRECTED 2026-08-21 (#280 spec F6). This block, and five other comments
|
|
769
|
+
* in this file, used to state that `list_channels_in_enclave` is tenant-
|
|
770
|
+
* scoped and 404s a cross-tenant partner enclave under this agent's bearer,
|
|
771
|
+
* leaving a partner enclave in this set with no channels. That has been FALSE
|
|
772
|
+
* since #187 (`d7cc685a`, 2026-07-31). Verified by opening the code:
|
|
773
|
+
*
|
|
774
|
+
* - `list_channels_in_enclave` calls `_resolve_enclave_membership(...,
|
|
775
|
+
* host_only=False)` (backend/app/api/v1/connect_channels.py:897-900).
|
|
776
|
+
* - `host_only=False` OMITS the `ConnectEnclave.tenant_id == auth.tenant_id`
|
|
777
|
+
* predicate and runs the membership query under `bypass_rls`, so the
|
|
778
|
+
* host-owned enclave row is visible to a partner session (same file
|
|
779
|
+
* :519-520, :531-543). The gate is membership, not tenancy.
|
|
780
|
+
* - The channel query it then runs (same file :902-919) reads
|
|
781
|
+
* `connect_channels` joined to `connect_channel_members`, and BOTH tables
|
|
782
|
+
* are among the eight the schema deliberately leaves without RLS for
|
|
783
|
+
* having no `tenant_id` column (alembic 0001_connect_initial_schema.py:
|
|
784
|
+
* 750-755, :778-780). No policy filters those rows by tenant.
|
|
785
|
+
*
|
|
786
|
+
* So a partner-tenant agent holding a live `ConnectEnclaveMember` row
|
|
787
|
+
* (`removed_at IS NULL`) gets 200 with that enclave's real channels. There is
|
|
788
|
+
* no partner-enclave listing gap left to close, and no backend authz work
|
|
789
|
+
* pending for it.
|
|
790
|
+
*
|
|
791
|
+
* What DOES still reach a per-enclave listing failure: a transport error, a
|
|
792
|
+
* 5xx, or a genuine 404 for an enclave this agent holds no live membership
|
|
793
|
+
* in. Those are real outages, so the skip-one, never-abort-all rule stands on
|
|
794
|
+
* its own merit — it just no longer has a standing partner-enclave case.
|
|
727
795
|
*/
|
|
728
796
|
private enclavesToCover;
|
|
729
797
|
/** A live roster for a channel, or undefined if missing OR expired past the TTL. */
|
|
@@ -851,6 +919,12 @@ export declare class BridgeAgent {
|
|
|
851
919
|
rate_limited: number;
|
|
852
920
|
/** Cumulative inbound rows acked-and-dropped because they were provably past-epoch undecryptable (forward-secrecy secret gone). */
|
|
853
921
|
permanently_skipped: number;
|
|
922
|
+
/** SDK-6: cumulative rows ack-drained via QUARANTINE, distinct from the
|
|
923
|
+
* same-group past-epoch permanently_skipped drain. */
|
|
924
|
+
quarantine_drained: number;
|
|
925
|
+
/** SDK-6: cumulative rows ack-drained because their group_id is RETIRED for
|
|
926
|
+
* the channel (superseded fork-loser traffic). */
|
|
927
|
+
retired_drained: number;
|
|
854
928
|
/** Track 5: cumulative artifact_share messages where download/decrypt/digest failed (fail-soft — acked, no redeliver). */
|
|
855
929
|
failed_attachments: number;
|
|
856
930
|
/** 2026-07-03 latency triage: delivery pings received on the WS stream.
|
|
@@ -879,6 +953,12 @@ export declare class BridgeAgent {
|
|
|
879
953
|
/** M6: channels whose divergence verdict is definitively true. Excludes
|
|
880
954
|
* unresolvable/failed probes, which are counted as neither. */
|
|
881
955
|
diverged_channels: number;
|
|
956
|
+
/** Tri-state. `false` means the last sweep PROVED the published KeyPackage is
|
|
957
|
+
* still live. `true` means it is server-expired and the rotation did not heal
|
|
958
|
+
* it. `null` means unknown — no sweep yet, no held kp id, or the expiry could
|
|
959
|
+
* not be resolved. `null` folds into `degraded` exactly like `true`: healthy
|
|
960
|
+
* requires a positive, current proof of non-expiry. */
|
|
961
|
+
keypackage_expired: boolean | null;
|
|
882
962
|
/** A2 (dr-evil F3, decision D-3): the truthful roll-up — true when any
|
|
883
963
|
* channel is definitively diverged OR the observations cannot be trusted
|
|
884
964
|
* (stale/absent). Additive: `status` stays "ok" until the flip ships as
|
|
@@ -891,8 +971,9 @@ export declare class BridgeAgent {
|
|
|
891
971
|
/** C1: enclaves whose channel listing failed on the newest sweep. Non-empty
|
|
892
972
|
* means partial coverage — those enclaves' channels were NOT probed. A
|
|
893
973
|
* PREVIOUSLY-SUCCEEDING (or home) entry also flips `degraded`; a
|
|
894
|
-
* never-succeeded entry
|
|
895
|
-
*
|
|
974
|
+
* never-succeeded entry is reported here without degrading. (The old
|
|
975
|
+
* wording gave "partner enclave, tenant-scoped 404" as the never-succeeded
|
|
976
|
+
* case — refuted, see the CROSS-TENANT LISTING note on enclavesToCover.) */
|
|
896
977
|
sweep_enclaves_failed: string[];
|
|
897
978
|
/** Optional memory-adapter counters (e.g. Hermes provider); present only when the active provider exposes stats(). */
|
|
898
979
|
hermes_no_session?: number;
|
|
@@ -940,9 +1021,19 @@ export declare class BridgeAgent {
|
|
|
940
1021
|
filteredRoomMessages: number;
|
|
941
1022
|
rateLimitedSends: number;
|
|
942
1023
|
permanentlySkipped: number;
|
|
1024
|
+
/** SDK-6: cumulative rows ack-drained via QUARANTINE, distinct from the
|
|
1025
|
+
* same-group past-epoch permanentlySkipped drain. */
|
|
1026
|
+
quarantineDrained: number;
|
|
1027
|
+
/** SDK-6: cumulative rows ack-drained because their group_id is RETIRED for
|
|
1028
|
+
* the channel (superseded fork-loser traffic). */
|
|
1029
|
+
retiredDrained: number;
|
|
943
1030
|
welcomeRequestsSent: number;
|
|
944
1031
|
/** Track 5: cumulative artifact_share failures (download/decrypt/digest). */
|
|
945
1032
|
failedAttachments: number;
|
|
1033
|
+
/** SDK-6: bridge build id, so a /v1/metrics reading captured across a daemon
|
|
1034
|
+
* restart can be attributed to a version — counters are process-lifetime
|
|
1035
|
+
* and zero on restart (spec §5.5). */
|
|
1036
|
+
build: string;
|
|
946
1037
|
};
|
|
947
1038
|
close(): Promise<void>;
|
|
948
1039
|
}
|