@justair/justair-library 7.6.0-alpha.f80fd11 → 7.6.0

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.
@@ -11,9 +11,7 @@ export const VISIBLE_SITE_MONITOR_FILTER: Readonly<{
11
11
  isPrivate: Readonly<{
12
12
  $ne: true;
13
13
  }>;
14
- isActive: Readonly<{
15
- $ne: false;
16
- }>;
14
+ isActive: true;
17
15
  }>;
18
16
  export function projectSubscribedMonitors(subscribedDevices?: Array<{
19
17
  deviceId: import("mongoose").Types.ObjectId;
@@ -1 +1 @@
1
- {"version":3,"file":"subscribedDevices.d.ts","sourceRoot":"","sources":["../../src/utils/subscribedDevices.js"],"names":[],"mappings":"AA0FA;;;;;;;;GASG;AAyCH;;;;;;;GAGG;AA6LI,8DANI,KAAK,CAAC;IAAE,QAAQ,EAAE,OAAO,UAAU,EAAE,KAAK,CAAC,QAAQ,CAAC;IAC1D,UAAU,EAAE,OAAO,6BAA6B,EAAE,UAAU,CAAA;CAAE,CAAC,8BACzD;IAAE,qBAAqB,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,UAAU,EAAE,KAAK,CAAC,QAAQ,EAAE,KAC7E,OAAO,CAAC,KAAK,CAAC,OAAO,UAAU,EAAE,KAAK,CAAC,QAAQ,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC,CAAA;CAAE,GAC9D,OAAO,CAAC,OAAO,UAAU,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAC,CA0BxD;AAuEM,0DAPI,KAAK,CAAC;IAAE,GAAG,EAAE,GAAG,CAAC;IAAC,iBAAiB,CAAC,EAAE,KAAK,CAAC;QAClD,QAAQ,EAAE,OAAO,UAAU,EAAE,KAAK,CAAC,QAAQ,CAAC;QAC5C,UAAU,EAAE,OAAO,6BAA6B,EAAE,UAAU,CAAA;KAAE,CAAC,CAAA;CAAE,CAAC,mCAC5D;IAAE,0BAA0B,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,UAAU,EAAE,KAAK,CAAC,QAAQ,EAAE,KAClF,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,UAAU,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAA;CAAE,GACnD,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,UAAU,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC,CAmErE"}
1
+ {"version":3,"file":"subscribedDevices.d.ts","sourceRoot":"","sources":["../../src/utils/subscribedDevices.js"],"names":[],"mappings":"AA0FA;;;;;;;;GASG;AA+CH;;;;;GAGG;AA6LI,8DANI,KAAK,CAAC;IAAE,QAAQ,EAAE,OAAO,UAAU,EAAE,KAAK,CAAC,QAAQ,CAAC;IAC1D,UAAU,EAAE,OAAO,6BAA6B,EAAE,UAAU,CAAA;CAAE,CAAC,8BACzD;IAAE,qBAAqB,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,UAAU,EAAE,KAAK,CAAC,QAAQ,EAAE,KAC7E,OAAO,CAAC,KAAK,CAAC,OAAO,UAAU,EAAE,KAAK,CAAC,QAAQ,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC,CAAA;CAAE,GAC9D,OAAO,CAAC,OAAO,UAAU,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAC,CA0BxD;AA0EM,0DAPI,KAAK,CAAC;IAAE,GAAG,EAAE,GAAG,CAAC;IAAC,iBAAiB,CAAC,EAAE,KAAK,CAAC;QAClD,QAAQ,EAAE,OAAO,UAAU,EAAE,KAAK,CAAC,QAAQ,CAAC;QAC5C,UAAU,EAAE,OAAO,6BAA6B,EAAE,UAAU,CAAA;KAAE,CAAC,CAAA;CAAE,CAAC,mCAC5D;IAAE,0BAA0B,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,UAAU,EAAE,KAAK,CAAC,QAAQ,EAAE,KAClF,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,UAAU,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAA;CAAE,GACnD,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,UAAU,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC,CAmErE"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@justair/justair-library",
3
- "version": "7.6.0-alpha.f80fd11",
3
+ "version": "7.6.0",
4
4
  "description": "JustAir Internal Library",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
package/src/index.js CHANGED
@@ -293,16 +293,16 @@ export {
293
293
  // being retired rather than kept in step with it. Nothing should store this
294
294
  // result — see the module header for the five bugs a stored copy caused.
295
295
  //
296
+ // Both resolvers are only correct for users whose subscribedDevices has been
297
+ // backfilled from subscribedMonitors — read the PRECONDITION block in the
298
+ // module header before pointing a service at either of them.
299
+ //
296
300
  // resolveSubscribedMonitorIdsByUser — the batch form. One Sites lookup for
297
301
  // a whole cohort. What alert runs, org pages and
298
302
  // session responses should call.
299
303
  // projectSubscribedMonitors — the single-user rule the batch form is built
300
304
  // on. Named after the field being retired; it
301
305
  // gets renamed when that field is dropped.
302
- //
303
- // Only correct for users whose subscribedDevices has been backfilled from
304
- // subscribedMonitors — see the PRECONDITION block in the module header
305
- // before pointing a service at these.
306
306
  // VISIBLE_SITE_MONITOR_FILTER — the privacy predicate every consumer must
307
307
  // apply when turning a Site into monitor ids.
308
308
  // Sites.monitorIds is unfiltered; reading it
@@ -116,15 +116,20 @@ export const SUBSCRIBED_DEVICES_ERRORS = Object.freeze({
116
116
  // "_id",
117
117
  // ).lean();
118
118
  //
119
- // `isActive: { $ne: false }` for the same reason, one step weaker than it
120
- // looks: `isActive` is `default: true` and NOT `required` (models/monitors.js),
121
- // so a monitor written before the field existed carries no key. An exact
122
- // `isActive: true` match would drop it out of every Site's audience silently
123
- // the same class of bug as an exact `isPrivate: false` match, and the two
124
- // halves of this predicate should fail the same way. Both clauses are
125
- // deliberately permissive on a missing key: the cost of over-including a stale
126
- // monitor is a redundant alert, the cost of under-including is a person who
127
- // stops being alerted at all.
119
+ // `isActive: true` is an EXACT match, and that asymmetry with the `$ne` above
120
+ // is DELIBERATE do not "fix" it to `{ $ne: false }`. The two clauses answer
121
+ // different questions. `isPrivate` is a privacy gate: a monitor with no
122
+ // `isPrivate` key was never marked private, so excluding it would hide a
123
+ // monitor its followers are entitled to see. `isActive` is an eligibility
124
+ // gate: a monitor only earns a place in an alert audience by positively
125
+ // asserting it is active. A document with no `isActive` key has made no such
126
+ // assertion, and the safe reading of "we don't know" is to leave it out rather
127
+ // than alert on it.
128
+ //
129
+ // So the failure modes are not symmetric and should not be made symmetric.
130
+ // Over-including here means alerting people about a monitor nobody confirmed
131
+ // is live; under-including means one unconfirmed monitor is missing from an
132
+ // audience until someone sets the flag. This predicate takes the second.
128
133
  //
129
134
  // `monitorState: "Deployed"` is deliberately NOT here. That is an
130
135
  // alerting-eligibility rule, not a visibility rule, and it belongs to the
@@ -137,10 +142,11 @@ export const SUBSCRIBED_DEVICES_ERRORS = Object.freeze({
137
142
  // top-level-only freeze still lets a consumer write
138
143
  // `VISIBLE_SITE_MONITOR_FILTER.isPrivate.$ne = false` — and because every
139
144
  // consumer spreads THIS instance, that one write flips the privacy predicate
140
- // for the whole process. Freeze each nested operator object too.
145
+ // for the whole process. `isPrivate` is the only nested object here; freeze it
146
+ // too, and freeze any operator object a future clause introduces.
141
147
  export const VISIBLE_SITE_MONITOR_FILTER = Object.freeze({
142
148
  isPrivate: Object.freeze({ $ne: true }),
143
- isActive: Object.freeze({ $ne: false }),
149
+ isActive: true,
144
150
  });
145
151
 
146
152
  // Dedupe by string form, keep the caller's ObjectId instances. Two equal
@@ -398,10 +404,13 @@ export const projectSubscribedMonitors = async (
398
404
  // to nothing, so their alerts stop) is the subscription-identity question
399
405
  // tracked in TODOS, and is deliberately not papered over here.
400
406
  //
401
- // Users are keyed by `String(user._id)`, so callers look up with the same. A
402
- // user with no `_id` throws rather than being skipped: skipping would silently
403
- // remove a real person from an alert audience, which is the failure mode this
404
- // whole module exists to avoid.
407
+ // Users are keyed by `String(user._id)`, so callers look up with the same.
408
+ // Anything in the cohort that is not a user object carrying an `_id` — a
409
+ // missing `_id`, a `null` slot, a bare id string throws COHORT_USER_MISSING_ID
410
+ // rather than being skipped. Skipping would silently remove a real person from
411
+ // an alert audience, which is the failure mode this whole module exists to
412
+ // avoid, and a caller cannot tell a dropped entry apart from one that resolved
413
+ // to nothing.
405
414
  //
406
415
  // Consumers can shrink the cohort by pushing the AQI-capability predicate into
407
416
  // the Users query, so rows that can never alert are not loaded at all.
@@ -665,15 +665,17 @@ describe('resolveSubscribedMonitorIdsByUser — rejects what would otherwise fai
665
665
  });
666
666
 
667
667
  describe('VISIBLE_SITE_MONITOR_FILTER', () => {
668
- test('both clauses use $ne, so a document missing the key survives', () => {
669
- // $ne matches both the negated value and a MISSING key. isPrivate has no
670
- // key on documents written before it existed; isActive is `default: true`
671
- // and NOT required (models/monitors.js), so it has the same gap. An exact
672
- // match on either would drop those monitors out of every Site's audience
673
- // with no error anywhere.
668
+ test('privacy uses $ne, eligibility uses an exact match deliberately', () => {
669
+ // The asymmetry is the point, and this test exists to stop it being
670
+ // "fixed". isPrivate is a privacy gate: $ne keeps a monitor that was never
671
+ // marked private, which its followers are entitled to see. isActive is an
672
+ // eligibility gate: a monitor earns a place in an alert audience only by
673
+ // positively asserting it is active, so a document that has made no such
674
+ // assertion stays out. Confirmed product decision — flipping either half
675
+ // is a behaviour change, not a cleanup.
674
676
  expect(VISIBLE_SITE_MONITOR_FILTER).toEqual({
675
677
  isPrivate: { $ne: true },
676
- isActive: { $ne: false },
678
+ isActive: true,
677
679
  });
678
680
  });
679
681
 
@@ -683,12 +685,12 @@ describe('VISIBLE_SITE_MONITOR_FILTER', () => {
683
685
  // predicate for every consumer that spreads this instance.
684
686
  expect(Object.isFrozen(VISIBLE_SITE_MONITOR_FILTER)).toBe(true);
685
687
  expect(Object.isFrozen(VISIBLE_SITE_MONITOR_FILTER.isPrivate)).toBe(true);
686
- expect(Object.isFrozen(VISIBLE_SITE_MONITOR_FILTER.isActive)).toBe(true);
687
688
  });
688
689
 
689
690
  test('a nested write is rejected rather than shared with every consumer', () => {
691
+ // ES modules are strict, so a write to a frozen property throws rather
692
+ // than failing silently.
690
693
  expect(() => {
691
- 'use strict';
692
694
  VISIBLE_SITE_MONITOR_FILTER.isPrivate.$ne = false;
693
695
  }).toThrow(TypeError);
694
696