@byollm/relay 0.1.0-alpha.21 → 0.1.0-alpha.23

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.js CHANGED
@@ -180,7 +180,15 @@ var DaemonPlane = class {
180
180
  kinds: new Set(request.capabilities.map((c) => c.kind)),
181
181
  // The projection, collapsed to data the store can match on — a
182
182
  // predicate does not travel.
183
- owners: new Set(this.#deps.projection.ownersRunnableBy(device.owner)),
183
+ //
184
+ // `routableOwners`, not `ownersRunnableBy`: the roster says whose
185
+ // work *may* land here and consent says whether it moves at all. A
186
+ // paused consent (cloud_008 finding 48) leaves this set empty rather
187
+ // than refusing the call, which is the whole difference between "we
188
+ // are waiting for you to read something" and "a human cut you off".
189
+ owners: new Set(
190
+ this.#deps.projection.routableOwners(device.owner, this.#deps.siteId)
191
+ ),
184
192
  max: request.max,
185
193
  leaseMs: this.#deps.leaseMs
186
194
  });
@@ -382,7 +390,31 @@ var ConsentRecord = z2.object({
382
390
  /** The user, as the control plane identifies them. */
383
391
  owner: z2.string().min(1),
384
392
  /** Which site this consent is for. Scoped: consent is never global. */
385
- siteId: z2.string().min(1)
393
+ siteId: z2.string().min(1),
394
+ /**
395
+ * The consent stands, and nothing routes under it — cloud_008 finding 48.
396
+ *
397
+ * The disclosure this user agreed to no longer describes their
398
+ * arrangements: they read that their prompts stay on machines they own,
399
+ * and they have since been added to a roster whose owner can read them.
400
+ * Until they have been shown the other sentence and clicked, their work
401
+ * does not move.
402
+ *
403
+ * **A third state, because the two we had are both wrong here.** Dropping
404
+ * the consent makes `consentFor` return null, and the daemon plane reads
405
+ * exactly that as revoked: heartbeat answers `revoked: true` with `lost:
406
+ * all`, and the daemon prints "this runner was revoked" and *deletes its
407
+ * pairing*. So a user whose team changed a setting would be told a human
408
+ * cut them off, lose their pinned keys, and have to re-run `byollm
409
+ * connect` after re-consenting. Under cloud_009 that is worse still: the
410
+ * pairing is keyed by origin, so one stale consent would drop the pairing
411
+ * for every other site reached through that hub.
412
+ *
413
+ * Reporting it as revoked is the same falsehood finding 48 exists to
414
+ * delete, told one layer down. So the record stays, the relationship
415
+ * stays, and the routing stops.
416
+ */
417
+ paused: z2.boolean().default(false)
386
418
  }).strict();
387
419
  var RosterRecord = z2.object({
388
420
  /** Stable id for the group, used only inside the relay. */
@@ -460,7 +492,15 @@ var Projection = class {
460
492
  deviceByFingerprint(identityPublic) {
461
493
  return this.#fixture.devices.find((d) => d.device.identity === identityPublic) ?? null;
462
494
  }
463
- /** The consent binding this owner to this site, if it exists and stands. */
495
+ /**
496
+ * The consent binding this owner to this site, if it exists and stands.
497
+ *
498
+ * **Liveness, not routing.** A paused consent is returned here: the
499
+ * relationship exists, the daemon is not revoked, the pairing stands. Ask
500
+ * {@link Projection.mayRouteFor} before moving anybody's work — the two
501
+ * questions have different answers and one method answering both is how a
502
+ * paused user would quietly start routing again.
503
+ */
464
504
  consentFor(owner, siteId) {
465
505
  const revoked = this.#fixture.revoked.some(
466
506
  (r) => r.owner === owner && r.siteId === siteId
@@ -470,6 +510,46 @@ var Projection = class {
470
510
  (c) => c.owner === owner && c.siteId === siteId
471
511
  ) ?? null;
472
512
  }
513
+ /**
514
+ * May this owner's work move for this site, right now?
515
+ *
516
+ * Consent exists, was not revoked, and is not paused. The routing question,
517
+ * kept apart from {@link Projection.consentFor}'s liveness one so that a
518
+ * caller has to pick which it means.
519
+ */
520
+ mayRouteFor(owner, siteId) {
521
+ const consent = this.consentFor(owner, siteId);
522
+ return consent !== null && !consent.paused;
523
+ }
524
+ /** Whether this pair is consented and paused — what heartbeat reports. */
525
+ pausedFor(owner, siteId) {
526
+ return this.consentFor(owner, siteId)?.paused === true;
527
+ }
528
+ /**
529
+ * Whose work this device may run for this site — the set a claim carries.
530
+ *
531
+ * {@link Projection.ownersRunnableBy} collapsed and then filtered by
532
+ * consent, which is two things this relay was doing in one place and one
533
+ * place respectively:
534
+ *
535
+ * - **The device's own owner is checked first**, and an unroutable one
536
+ * empties the whole set. A machine whose owner has not agreed to this
537
+ * site's current terms runs nothing for it, including a roster member's
538
+ * work: the roster says whose jobs may land here, and consent says
539
+ * whether this machine is available to the site at all.
540
+ * - **Every job owner is checked too.** That check did not exist. Consent
541
+ * was enforced by the daemon plane's blanket revoked guard, which asks
542
+ * only about the *claiming* device's owner — so a roster member who never
543
+ * consented to a site could have their work claimed by their admin's
544
+ * machine, which is `CONSENT_BEFORE_ROUTE` read the other way round. The
545
+ * site plane does not check consent at enqueue either, so nothing did.
546
+ */
547
+ routableOwners(deviceOwner, siteId) {
548
+ if (!this.mayRouteFor(deviceOwner, siteId)) return [];
549
+ return this.ownersRunnableBy(deviceOwner).filter(
550
+ (owner) => this.mayRouteFor(owner, siteId)
551
+ );
552
+ }
473
553
  /**
474
554
  * Every owner whose work this device's owner may run, as a list.
475
555
  *