@securecoms/agent-bridge 0.11.0 → 0.11.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.
@@ -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 — see the scope caveat there
634
- * (same-tenant only; a cross-tenant enclave 404s under the agent's bearer).
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 (scope caveat — audit
670
- * H2/tenant): list_channels_in_enclave filters on tenant_id, so a
671
- * same-tenant enclave the agent has learned about resolves fine, but a
672
- * cross-TENANT federated-partner enclave 404s under this agent's bearer;
673
- * that enclave's channels are left unresolved (fail open) without aborting
674
- * the other enclaves in the set.
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 (partner: tenant-scoped 404, permanent
690
- * pending backend authz) is an EXPECTED gap — reported, not degrading, or
691
- * every federated agent would peg degraded forever. */
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 permanently-404ing partner enclave must not log 1440 lines a day. */
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
- * ⚠️ Residual gap, stated: the enumeration RETURNS cross-tenant partner
723
- * enclaves, but `list_channels_in_enclave` is tenant-scoped and 404s them
724
- * under this agent's bearer — so a partner enclave in this set still yields
725
- * no channels. Callers must treat a per-enclave listing failure as skip-one,
726
- * never abort-all. Closing that needs backend authz work, not daemon work.
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. */
@@ -885,6 +953,12 @@ export declare class BridgeAgent {
885
953
  /** M6: channels whose divergence verdict is definitively true. Excludes
886
954
  * unresolvable/failed probes, which are counted as neither. */
887
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;
888
962
  /** A2 (dr-evil F3, decision D-3): the truthful roll-up — true when any
889
963
  * channel is definitively diverged OR the observations cannot be trusted
890
964
  * (stale/absent). Additive: `status` stays "ok" until the flip ships as
@@ -897,8 +971,9 @@ export declare class BridgeAgent {
897
971
  /** C1: enclaves whose channel listing failed on the newest sweep. Non-empty
898
972
  * means partial coverage — those enclaves' channels were NOT probed. A
899
973
  * PREVIOUSLY-SUCCEEDING (or home) entry also flips `degraded`; a
900
- * never-succeeded entry (partner enclave, tenant-scoped 404) is reported
901
- * here without degrading. */
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.) */
902
977
  sweep_enclaves_failed: string[];
903
978
  /** Optional memory-adapter counters (e.g. Hermes provider); present only when the active provider exposes stats(). */
904
979
  hermes_no_session?: number;