@twinsearth/w2m-dsh-plugin 0.8.0 → 0.8.3

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.
@@ -16,7 +16,9 @@ import { createHash, randomBytes } from 'node:crypto';
16
16
  // v0.7.7: fleet scoping. Imported rather than inlined so the rule has one home with its own
17
17
  // exhaustive tests — three lines inside this object would be untestable without a live relay.
18
18
  import { targetsFor } from '../chatops/fleet-wiring.mjs';
19
- import { fleetForPairingCode, deviceFleetFields } from '../chatops/fleet-wiring.mjs';
19
+ import { fleetForPairingCode, deviceFleetFields } from '../chatops/fleet-wiring.mjs';
20
+ // v0.8.1: the account-derived fleet key, so a machine can join with no pairing code.
21
+ import { planJoin } from '../chatops/fleet-join.mjs';
20
22
 
21
23
  export const PROTOCOL_VERSION = 1;
22
24
 
@@ -754,7 +756,41 @@ export class RabbitState {
754
756
 
755
757
  /** §2.2: exchange a one-time code for a device_token. §1: idempotent. */
756
758
  pair(request = {}) {
757
- const rec = this.validatePairingCode(request.pairing_code);
759
+ /**
760
+ * v0.8.1: a pairing code **or** an account-derived fleet key.
761
+ *
762
+ * The code path is unchanged. The key path is what lets a second machine on one account join
763
+ * with no human step and no relay configuration — the gap that left a new PC invisible beside
764
+ * its own fleet.
765
+ *
766
+ * ## Why a key may stand in for a code
767
+ *
768
+ * They authorise different things. A **code** is the operator saying *"let this machine in"*,
769
+ * and it is the only way to place a device in a fleet the device could not compute for itself.
770
+ * A **key** is the account saying *"this machine is mine"*, and it can only ever place the
771
+ * device in the fleet derived from that key — so it cannot be used to reach a fleet its holder
772
+ * is not already entitled to.
773
+ *
774
+ * ## Why a malformed key refuses rather than falling back
775
+ *
776
+ * A request with a `fleet_key` is a request that *meant* to join by account. Quietly demanding a
777
+ * code instead would surface at the client as an unexplained `PAIRING_INVALID`, which is the
778
+ * silent-wrong-answer shape this codebase keeps producing; the reason is named instead.
779
+ */
780
+ const hasKey = typeof request.fleet_key === 'string' && request.fleet_key !== '';
781
+ let rec = null;
782
+ if (hasKey) {
783
+ const join = planJoin({ fleetKeyEncoded: request.fleet_key });
784
+ if (!join.ok) {
785
+ throw new ProtocolError('PAIRING_INVALID', join.text, { reason: join.reason });
786
+ }
787
+ // A synthetic code record so the fleet binding below reads from one place. `fleet_id` is the
788
+ // DERIVED value, never anything the request claimed.
789
+ rec = { code: null, fleet_id: join.fleetId, from_fleet_key: true };
790
+ } else {
791
+ rec = this.validatePairingCode(request.pairing_code);
792
+ }
793
+
758
794
  const machineId = request.machine_id;
759
795
  if (typeof machineId !== 'string' || machineId.length === 0) {
760
796
  throw new ProtocolError('BAD_REQUEST', 'machine_id is required');
@@ -778,7 +814,17 @@ export class RabbitState {
778
814
  // to it. `deviceFleetFields` is the single place that binding is made, and it records
779
815
  // its provenance so a fleet that arrived from a request is visible as such in
780
816
  // `devices.json` rather than looking identical to one that came from a code.
781
- ...deviceFleetFields({ codeRecord: rec, requestedFleet: null }),
817
+ //
818
+ // v0.8.1 adds the third source: an account-derived `fleet_key`. That is what lets a second
819
+ // machine on one account join with **no human step and no relay configuration** -- the gap
820
+ // that left a new PC invisible beside its own fleet. The relay does not trust a claimed
821
+ // fleet id; it *derives* one from the key, so a device can only land in the fleet whose key
822
+ // it holds and anything else forms a separate, empty fleet.
823
+ ...deviceFleetFields({
824
+ codeRecord: rec,
825
+ fleetKeyEncoded: typeof request.fleet_key === 'string' ? request.fleet_key : null,
826
+ requestedFleet: null,
827
+ }),
782
828
  /**
783
829
  * v0.7.10: the device's Ed25519 public key, if it offered one.
784
830
  *