filegrc 0.3.4 → 0.4.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.
package/src/mutation.js CHANGED
@@ -3,6 +3,7 @@ import { resolveWorkspaceRoot } from "./paths.js";
3
3
 
4
4
  const mutationQueues = new Map();
5
5
  const activeMutation = new AsyncLocalStorage();
6
+ const deferredValidation = new AsyncLocalStorage();
6
7
 
7
8
  export function serializeWorkspaceMutation(input, task) {
8
9
  const root = resolveWorkspaceRoot(input);
@@ -16,3 +17,44 @@ export function serializeWorkspaceMutation(input, task) {
16
17
  mutationQueues.set(root, tracked);
17
18
  return tracked;
18
19
  }
20
+
21
+ export function withDeferredWorkspaceValidation(task) {
22
+ return deferredValidation.run(true, task);
23
+ }
24
+
25
+ export function workspaceValidationDeferred() {
26
+ return deferredValidation.getStore() === true;
27
+ }
28
+
29
+ export function normalizeResourceMutation(value, options = {}) {
30
+ if (!value || Array.isArray(value) || typeof value !== "object") {
31
+ throw new Error("A { record, content, revision, contentRevisions } mutation object is required.");
32
+ }
33
+ if (!Object.hasOwn(value, "record")) {
34
+ throw new Error("A mutation envelope with a record property is required.");
35
+ }
36
+ if (!value.record || Array.isArray(value.record) || typeof value.record !== "object") {
37
+ throw new Error("Mutation record must be a JSON object.");
38
+ }
39
+ if (value.content !== undefined && (Array.isArray(value.content) || typeof value.content !== "object" || value.content === null)) {
40
+ throw new Error("Mutation content must be an object keyed by Markdown slot.");
41
+ }
42
+ if (value.revision !== undefined && typeof value.revision !== "string") {
43
+ throw new Error("Mutation revision must be a string.");
44
+ }
45
+ if (options.requireRevision && (typeof value.revision !== "string" || value.revision.length === 0)) {
46
+ throw new Error("Mutation revision is required when updating a resource.");
47
+ }
48
+ if (
49
+ value.contentRevisions !== undefined
50
+ && (Array.isArray(value.contentRevisions) || typeof value.contentRevisions !== "object" || value.contentRevisions === null)
51
+ ) {
52
+ throw new Error("Mutation contentRevisions must be an object keyed by data-relative Markdown path.");
53
+ }
54
+ return {
55
+ record: value.record,
56
+ content: value.content,
57
+ revision: value.revision,
58
+ contentRevisions: value.contentRevisions
59
+ };
60
+ }
@@ -1,5 +1,7 @@
1
1
  import { createResourceId } from "./id.js";
2
2
  import { createResourceAndLink, createResources, updateResource } from "./files.js";
3
+ import { loadModel } from "../model/index.js";
4
+ import { coverageEnd } from "./coverage.js";
3
5
  import {
4
6
  addCalendarDays,
5
7
  calendarDayDifference,
@@ -16,22 +18,25 @@ const COMPLETION_DATE_FIELDS = [
16
18
  "completedOn",
17
19
  "performedOn",
18
20
  "reviewedOn",
19
- "assessmentDate",
20
- "meetingDate",
21
- "scheduledOn",
22
21
  "occurredOn",
23
22
  "collectedOn",
24
23
  "verifiedOn",
25
24
  "approvedOn",
26
25
  "submittedOn",
27
26
  "closedOn",
28
- "reportDate",
29
- "periodEnd"
27
+ "reportDate"
28
+ ];
29
+ const COMPLETION_TIMESTAMP_FIELDS = [
30
+ "completedAt",
31
+ "endedAt",
32
+ "closedAt",
33
+ "provisionedOn",
34
+ "deprovisionedOn"
30
35
  ];
31
36
  const MAX_PLANNED_ITEMS = 10_000;
32
- const DEFAULT_EVENT_DEADLINE_DAYS = 30;
33
37
 
34
38
  export function planObligations(resources, options = {}) {
39
+ const model = options.model || loadModel("2");
35
40
  const asOf = requireDate(options.asOf ?? new Date().toISOString().slice(0, 10), "as-of date");
36
41
  const defaultNow = options.asOf ? `${asOf}T23:59:59Z` : new Date().toISOString();
37
42
  const now = requireTimestamp(options.now ?? defaultNow, "current timestamp");
@@ -56,12 +61,15 @@ export function planObligations(resources, options = {}) {
56
61
  };
57
62
 
58
63
  for (const obligation of obligations) {
64
+ const activity = obligationActivity(model, obligation.activityType);
65
+ const expectedCompletionTypes = activity.completionResourceTypes;
59
66
  const programStatus = obligationProgramStatus(obligation, byId, asOf);
60
67
  if (obligation.recurrence?.mode === "event" && obligation.recurrence.eventType) {
61
68
  const eventType = obligation.recurrence.eventType;
62
69
  const group = triggerGroups.get(eventType) ?? {
63
70
  eventType,
64
- prompt: obligation.triggerPrompt || humanize(eventType),
71
+ title: model.policyEvents?.[eventType]?.title || humanize(eventType),
72
+ prompt: obligation.triggerPrompt || model.policyEvents?.[eventType]?.title || humanize(eventType),
65
73
  policyIds: [],
66
74
  obligationIds: [],
67
75
  programStatus,
@@ -79,7 +87,8 @@ export function planObligations(resources, options = {}) {
79
87
  controlIds: obligation.controlIds || [],
80
88
  scopeResourceIds: obligation.scopeResourceIds || [],
81
89
  templateResourceId: obligation.templateResourceId || null,
82
- completionResourceTypes: obligation.completionResourceTypes || [],
90
+ completionResourceTypes: expectedCompletionTypes,
91
+ completionType: activity.completionType,
83
92
  programStatus,
84
93
  window: normalizedEventWindow(obligation.window)
85
94
  });
@@ -113,7 +122,7 @@ export function planObligations(resources, options = {}) {
113
122
  .filter((record) => (
114
123
  record
115
124
  && completionFallsInWindow(record, window)
116
- && completionTypeMatches(record, obligation.completionResourceTypes)
125
+ && completionTypeMatches(record, expectedCompletionTypes)
117
126
  ));
118
127
  const timingStatus = occurrenceStatus(window, asOf, completions.length > 0);
119
128
  const status = timingStatus === "complete" || programStatus === "accepted" ? timingStatus : "proposed";
@@ -131,6 +140,8 @@ export function planObligations(resources, options = {}) {
131
140
  policyIds: obligation.policyIds || [],
132
141
  controlIds: obligation.controlIds || [],
133
142
  scopeResourceIds: obligation.scopeResourceIds || [],
143
+ completionResourceTypes: expectedCompletionTypes,
144
+ completionType: activity.completionType,
134
145
  completionResourceIds: completions.map((record) => record.id),
135
146
  status,
136
147
  timingStatus,
@@ -156,7 +167,14 @@ export function planObligations(resources, options = {}) {
156
167
  if (!actionsBySource.has(record.sourceResourceId)) actionsBySource.set(record.sourceResourceId, []);
157
168
  actionsBySource.get(record.sourceResourceId).push(record);
158
169
  }
159
- const eventRuns = events.map((event) => planEventRun(event, actionsBySource.get(event.id) || [], byId, asOf, now));
170
+ const eventRuns = events.map((event) => planEventRun(
171
+ event,
172
+ actionsBySource.get(event.id) || [],
173
+ byId,
174
+ asOf,
175
+ now,
176
+ model
177
+ ));
160
178
  const eventItems = eventRuns
161
179
  .filter((run) => run.status !== "canceled")
162
180
  .flatMap((run) => run.actions)
@@ -216,7 +234,7 @@ export async function createObligationEvent(input, options) {
216
234
  if (templates.some((record) => obligationProgramStatus(record, byId, occurredOn) === "proposed")) {
217
235
  throw new Error(`Event type "${eventType}" still has starter proposals. Make every governing policy effective and implement at least one linked control before starting this workflow.`);
218
236
  }
219
- if (templates.some((record) => Number.isInteger(normalizedEventWindow(record.window).endOffsetHours)) && !occurredAt) {
237
+ if (templates.some((record) => normalizedEventWindow(record.window).precision === "timestamp") && !occurredAt) {
220
238
  throw new Error(`Event type "${eventType}" has hour-based deadlines and requires an RFC 3339 occurredAt timestamp.`);
221
239
  }
222
240
  const subjectResourceIds = [...new Set((options.subjectResourceIds || []).map(String).filter(Boolean))];
@@ -230,7 +248,6 @@ export async function createObligationEvent(input, options) {
230
248
  existingIds.push(id);
231
249
  const window = eventWindow(obligation, occurredOn, occurredAt, loaded.workspace.timezone);
232
250
  return {
233
- schemaVersion: 1,
234
251
  id,
235
252
  type: "action-item",
236
253
  title: obligation.title,
@@ -238,16 +255,11 @@ export async function createObligationEvent(input, options) {
238
255
  assigneeIds: obligation.ownerIds || [],
239
256
  sourceResourceId: eventId,
240
257
  obligationId: obligation.id,
241
- description: eventActionDescription(obligation, eventType),
242
- dueWindowStart: window.dueWindowStart,
243
- ...(window.dueWindowEnd ? { dueWindowEnd: window.dueWindowEnd, dueOn: window.dueWindowEnd } : {}),
244
- ...(window.overdueOn ? { overdueOn: window.overdueOn } : {}),
245
- ...(window.dueWindowStartAt ? { dueWindowStartAt: window.dueWindowStartAt } : {}),
246
- ...(window.dueWindowEndAt ? { dueWindowEndAt: window.dueWindowEndAt, overdueAt: window.dueWindowEndAt } : {})
258
+ description: eventActionDescription(obligation, eventType, loaded.model),
259
+ completionWindow: storedCompletionWindow(window, loaded.workspace.timezone)
247
260
  };
248
261
  });
249
262
  const event = {
250
- schemaVersion: 1,
251
263
  id: eventId,
252
264
  type: "obligation-event",
253
265
  title,
@@ -269,7 +281,7 @@ export async function completeObligationOccurrence(input, options) {
269
281
  record.type === "obligation" && record.id === options?.obligationId
270
282
  ));
271
283
  if (!obligation) throw new Error(`Obligation "${options?.obligationId ?? ""}" was not found.`);
272
- assertExpectedCompletionType(obligation, options?.record);
284
+ assertExpectedCompletionType(obligation, options?.record, loaded.model);
273
285
  return createResourceAndLink(loaded.root, options.record, {
274
286
  type: "obligation",
275
287
  id: obligation.id,
@@ -289,7 +301,7 @@ export async function completeObligationAction(input, options) {
289
301
  record.type === "obligation" && record.id === action.obligationId
290
302
  ));
291
303
  if (!obligation) throw new Error(`Obligation "${action.obligationId}" was not found.`);
292
- assertExpectedCompletionType(obligation, options?.record);
304
+ assertExpectedCompletionType(obligation, options?.record, loaded.model);
293
305
  const completedOn = requireDate(options?.completedOn, "completion date");
294
306
  const event = loaded.resources.find((record) => record.type === "obligation-event" && record.id === action.sourceResourceId);
295
307
  if (event?.occurredOn && completedOn < event.occurredOn) {
@@ -320,7 +332,8 @@ export async function completeObligationEvent(input, options) {
320
332
  const plan = planObligations(loaded.resources, {
321
333
  asOf: completedOn,
322
334
  through: completedOn,
323
- includeComplete: true
335
+ includeComplete: true,
336
+ model: loaded.model
324
337
  });
325
338
  const run = plan.eventRuns.find((item) => item.id === event.id);
326
339
  if (!run || run.actions.length === 0) {
@@ -341,11 +354,11 @@ export async function completeObligationEvent(input, options) {
341
354
  });
342
355
  }
343
356
 
344
- function assertExpectedCompletionType(obligation, record) {
357
+ function assertExpectedCompletionType(obligation, record, model) {
345
358
  if (!record || typeof record !== "object" || Array.isArray(record)) {
346
359
  throw new Error("A completion resource record is required.");
347
360
  }
348
- const expected = obligation.completionResourceTypes ?? [];
361
+ const expected = obligationActivity(model, obligation.activityType).completionResourceTypes;
349
362
  if (expected.length && !expected.includes(record.type)) {
350
363
  throw new Error(
351
364
  `Obligation "${obligation.id}" expects a completion resource of type ${expected.join(" or ")}, not "${record.type ?? ""}".`
@@ -357,10 +370,12 @@ function calendarWindow(recurrence, configuredWindow, index) {
357
370
  const occurrence = calendarOccurrence(recurrence, index);
358
371
  const next = calendarOccurrence(recurrence, index + 1);
359
372
  if (!occurrence || !next) return null;
360
- const startOffset = Number.isInteger(configuredWindow?.startOffsetDays) ? configuredWindow.startOffsetDays : 0;
373
+ const startOffset = configuredWindow?.precision === "date" && Number.isInteger(configuredWindow.startsAfter)
374
+ ? configuredWindow.startsAfter
375
+ : 0;
361
376
  const dueWindowStart = addCalendarDays(occurrence, startOffset);
362
- const dueWindowEnd = Number.isInteger(configuredWindow?.endOffsetDays)
363
- ? addCalendarDays(occurrence, configuredWindow.endOffsetDays)
377
+ const dueWindowEnd = configuredWindow?.precision === "date" && Number.isInteger(configuredWindow.dueAfter)
378
+ ? addCalendarDays(occurrence, configuredWindow.dueAfter)
364
379
  : addCalendarDays(next, -1);
365
380
  const overdueOn = dueWindowEnd ? addCalendarDays(dueWindowEnd, 1) : null;
366
381
  if (!dueWindowStart || !dueWindowEnd || !overdueOn) return null;
@@ -373,10 +388,10 @@ function calendarWindow(recurrence, configuredWindow, index) {
373
388
 
374
389
  function eventWindow(obligation, occurredOn, occurredAt, timezone) {
375
390
  const configuredWindow = normalizedEventWindow(obligation.window);
376
- if (Number.isInteger(configuredWindow.endOffsetHours) && occurredAt) {
377
- const startOffset = Number.isInteger(configuredWindow.startOffsetHours) ? configuredWindow.startOffsetHours : 0;
391
+ if (configuredWindow.precision === "timestamp" && occurredAt) {
392
+ const startOffset = Number.isInteger(configuredWindow.startsAfter) ? configuredWindow.startsAfter : 0;
378
393
  const dueWindowStartAt = addHours(occurredAt, startOffset);
379
- const dueWindowEndAt = addHours(occurredAt, configuredWindow.endOffsetHours);
394
+ const dueWindowEndAt = addHours(occurredAt, configuredWindow.dueAfter);
380
395
  return {
381
396
  dueWindowStart: timestampCalendarDate(dueWindowStartAt, timezone),
382
397
  dueWindowEnd: timestampCalendarDate(dueWindowEndAt, timezone),
@@ -386,9 +401,9 @@ function eventWindow(obligation, occurredOn, occurredAt, timezone) {
386
401
  overdueAt: dueWindowEndAt
387
402
  };
388
403
  }
389
- const startOffset = Number.isInteger(configuredWindow.startOffsetDays) ? configuredWindow.startOffsetDays : 0;
404
+ const startOffset = Number.isInteger(configuredWindow.startsAfter) ? configuredWindow.startsAfter : 0;
390
405
  const dueWindowStart = addCalendarDays(occurredOn, startOffset);
391
- const dueWindowEnd = addCalendarDays(occurredOn, configuredWindow.endOffsetDays);
406
+ const dueWindowEnd = addCalendarDays(occurredOn, configuredWindow.dueAfter);
392
407
  const overdueOn = dueWindowEnd ? addCalendarDays(dueWindowEnd, 1) : null;
393
408
  if (!dueWindowStart || !dueWindowEnd || !overdueOn) {
394
409
  throw new Error("Event deadline dates must fall within the supported calendar range.");
@@ -400,11 +415,13 @@ function eventWindow(obligation, occurredOn, occurredAt, timezone) {
400
415
  };
401
416
  }
402
417
 
403
- function planEventRun(event, actionItems, byId, asOf, now) {
418
+ function planEventRun(event, actionItems, byId, asOf, now, model = loadModel("2")) {
404
419
  const actions = actionItems
405
420
  .map((record) => {
406
421
  const obligation = byId.get(record.obligationId);
407
- const expectedCompletionTypes = obligation?.completionResourceTypes || [];
422
+ const expectedCompletionTypes = obligation?.type === "obligation"
423
+ ? obligationActivity(model, obligation.activityType).completionResourceTypes
424
+ : [];
408
425
  const linkedCompletionIds = [...new Set([
409
426
  ...(record.completionResourceIds || []),
410
427
  ...(record.evidenceIds || [])
@@ -413,28 +430,7 @@ function planEventRun(event, actionItems, byId, asOf, now) {
413
430
  const completionSatisfied = expectedCompletionTypes.length === 0
414
431
  || matchingCompletionIds.length > 0;
415
432
  const complete = record.status === "done" && completionSatisfied;
416
- const configuredWindow = normalizedEventWindow(obligation?.window);
417
- const fallbackEndOffsetDays = Number.isInteger(configuredWindow.endOffsetDays)
418
- ? configuredWindow.endOffsetDays
419
- : Math.ceil(configuredWindow.endOffsetHours / 24);
420
- const dueWindowStart = record.dueWindowStart || event.occurredOn;
421
- const dueWindowEnd = record.dueWindowEnd || record.dueOn || addCalendarDays(event.occurredOn, fallbackEndOffsetDays);
422
- const dueWindowStartAt = record.dueWindowStartAt
423
- || (event.occurredAt && Number.isInteger(configuredWindow.startOffsetHours)
424
- ? addHours(event.occurredAt, configuredWindow.startOffsetHours)
425
- : null);
426
- const dueWindowEndAt = record.dueWindowEndAt
427
- || (event.occurredAt && Number.isInteger(configuredWindow.endOffsetHours)
428
- ? addHours(event.occurredAt, configuredWindow.endOffsetHours)
429
- : null);
430
- const window = {
431
- dueWindowStart,
432
- dueWindowEnd,
433
- overdueOn: record.overdueOn || (dueWindowEnd ? addCalendarDays(dueWindowEnd, 1) : null),
434
- dueWindowStartAt,
435
- dueWindowEndAt,
436
- overdueAt: record.overdueAt || dueWindowEndAt
437
- };
433
+ const window = plannedCompletionWindow(record.completionWindow);
438
434
  const status = complete
439
435
  ? "complete"
440
436
  : window.overdueAt && new Date(now) > new Date(window.overdueAt)
@@ -498,18 +494,7 @@ function planEventRun(event, actionItems, byId, asOf, now) {
498
494
 
499
495
  function planStandaloneAction(record, byId, asOf, now) {
500
496
  const source = byId.get(record.sourceResourceId);
501
- const dueWindowEnd = record.dueWindowEnd || record.dueOn || null;
502
- const dueWindowStart = record.dueWindowStart || dueWindowEnd;
503
- const dueWindowEndAt = record.dueWindowEndAt || null;
504
- const dueWindowStartAt = record.dueWindowStartAt || dueWindowEndAt;
505
- const window = {
506
- dueWindowStart,
507
- dueWindowEnd,
508
- overdueOn: record.overdueOn || (dueWindowEnd ? addCalendarDays(dueWindowEnd, 1) : null),
509
- dueWindowStartAt,
510
- dueWindowEndAt,
511
- overdueAt: record.overdueAt || dueWindowEndAt
512
- };
497
+ const window = plannedCompletionWindow(record.completionWindow);
513
498
  const complete = ["done", "canceled"].includes(record.status);
514
499
  const status = complete
515
500
  ? "complete"
@@ -543,27 +528,54 @@ function planStandaloneAction(record, byId, asOf, now) {
543
528
  };
544
529
  }
545
530
 
531
+ function storedCompletionWindow(window, timezone) {
532
+ if (window.dueWindowEndAt) {
533
+ return {
534
+ precision: "timestamp",
535
+ startsAt: window.dueWindowStartAt,
536
+ dueAt: window.dueWindowEndAt,
537
+ overdueAt: window.overdueAt || window.dueWindowEndAt,
538
+ timezone
539
+ };
540
+ }
541
+ return {
542
+ precision: "date",
543
+ startsOn: window.dueWindowStart,
544
+ dueOn: window.dueWindowEnd,
545
+ overdueOn: window.overdueOn
546
+ };
547
+ }
548
+
549
+ function plannedCompletionWindow(window) {
550
+ if (window?.precision === "timestamp") {
551
+ return {
552
+ dueWindowStart: timestampCalendarDate(window.startsAt, window.timezone),
553
+ dueWindowEnd: timestampCalendarDate(window.dueAt, window.timezone),
554
+ overdueOn: timestampCalendarDate(window.overdueAt, window.timezone),
555
+ dueWindowStartAt: window.startsAt,
556
+ dueWindowEndAt: window.dueAt,
557
+ overdueAt: window.overdueAt
558
+ };
559
+ }
560
+ return {
561
+ dueWindowStart: window?.startsOn || null,
562
+ dueWindowEnd: window?.dueOn || null,
563
+ overdueOn: window?.overdueOn || null,
564
+ dueWindowStartAt: null,
565
+ dueWindowEndAt: null,
566
+ overdueAt: null
567
+ };
568
+ }
569
+
546
570
  function completionFallsInWindow(record, window) {
547
571
  const date = completionDate(record);
548
572
  return Boolean(date && date >= window.dueWindowStart && date <= window.dueWindowEnd);
549
573
  }
550
574
 
551
575
  function normalizedEventWindow(configuredWindow) {
552
- const window = configuredWindow && !Array.isArray(configuredWindow) && typeof configuredWindow === "object"
576
+ return configuredWindow && !Array.isArray(configuredWindow) && typeof configuredWindow === "object"
553
577
  ? configuredWindow
554
578
  : {};
555
- if (Number.isInteger(window.endOffsetDays) || Number.isInteger(window.endOffsetHours)) return window;
556
- if (Number.isInteger(window.startOffsetHours)) {
557
- return {
558
- ...window,
559
- endOffsetHours: window.startOffsetHours + (DEFAULT_EVENT_DEADLINE_DAYS * 24)
560
- };
561
- }
562
- return {
563
- ...window,
564
- startOffsetDays: Number.isInteger(window.startOffsetDays) ? window.startOffsetDays : 0,
565
- endOffsetDays: DEFAULT_EVENT_DEADLINE_DAYS
566
- };
567
579
  }
568
580
 
569
581
  function completionTypeMatches(record, expectedTypes = []) {
@@ -571,6 +583,11 @@ function completionTypeMatches(record, expectedTypes = []) {
571
583
  }
572
584
 
573
585
  function completionDate(record) {
586
+ const coverageDate = coverageEnd(record.coverage);
587
+ if (parseCalendarDate(coverageDate)) return coverageDate;
588
+ for (const field of COMPLETION_TIMESTAMP_FIELDS) {
589
+ if (isRfc3339Timestamp(record[field])) return record[field].slice(0, 10);
590
+ }
574
591
  for (const field of COMPLETION_DATE_FIELDS) {
575
592
  if (parseCalendarDate(record[field])) return record[field];
576
593
  }
@@ -625,15 +642,22 @@ function comparePlannedItems(a, b) {
625
642
  || a.title.localeCompare(b.title);
626
643
  }
627
644
 
628
- function eventActionDescription(obligation, eventType) {
645
+ function eventActionDescription(obligation, eventType, model) {
629
646
  const policy = obligation.policyIds?.length ? ` Policy sources: ${obligation.policyIds.join(", ")}.` : "";
630
647
  const scope = obligation.scopeResourceIds?.length ? ` Review scoped resources: ${obligation.scopeResourceIds.join(", ")}.` : "";
631
- const completion = obligation.completionResourceTypes?.length
632
- ? ` Link completion records of type ${obligation.completionResourceTypes.join(", ")} and any evidence before marking this done.`
648
+ const expected = obligationActivity(model, obligation.activityType).completionResourceTypes;
649
+ const completion = expected.length
650
+ ? ` Link completion records of type ${expected.join(", ")} and any evidence before marking this done.`
633
651
  : " Link the completion record and evidence before marking this done.";
634
652
  return `Triggered by ${eventType}.${policy}${scope}${completion}`;
635
653
  }
636
654
 
655
+ function obligationActivity(model, activityType) {
656
+ const activity = model.obligationActivities?.[activityType];
657
+ if (!activity) throw new Error(`Unknown obligation activity type "${activityType ?? ""}".`);
658
+ return activity;
659
+ }
660
+
637
661
  function requireDate(value, label) {
638
662
  if (!parseCalendarDate(value)) throw new Error(`A valid ${label} is required.`);
639
663
  return value;
package/src/parties.js CHANGED
@@ -1,5 +1,6 @@
1
- const CURRENT_PERSON_STATUSES = new Set(["active", "external"]);
1
+ const CURRENT_PERSON_STATUSES = new Set(["active"]);
2
2
  const CURRENT_TEAM_STATUSES = new Set(["active"]);
3
+ const CURRENT_APPOINTMENT_STATUSES = new Set(["active"]);
3
4
 
4
5
  export function partyPeople(ids = [], byId, options = {}, seen = new Set()) {
5
6
  const people = new Set();
@@ -26,6 +27,19 @@ export function partyPeople(ids = [], byId, options = {}, seen = new Set()) {
26
27
  people.add(personId);
27
28
  }
28
29
  }
30
+ if (
31
+ record?.type === "appointment"
32
+ && (!options.appointmentStatuses || options.appointmentStatuses.has(record.status))
33
+ ) {
34
+ for (const personId of partyPeople(
35
+ [record.holderId],
36
+ byId,
37
+ options,
38
+ seen
39
+ )) {
40
+ people.add(personId);
41
+ }
42
+ }
29
43
  }
30
44
  return people;
31
45
  }
@@ -33,7 +47,8 @@ export function partyPeople(ids = [], byId, options = {}, seen = new Set()) {
33
47
  export function currentPartyPeople(ids = [], byId) {
34
48
  return partyPeople(ids, byId, {
35
49
  personStatuses: CURRENT_PERSON_STATUSES,
36
- teamStatuses: CURRENT_TEAM_STATUSES
50
+ teamStatuses: CURRENT_TEAM_STATUSES,
51
+ appointmentStatuses: CURRENT_APPOINTMENT_STATUSES
37
52
  });
38
53
  }
39
54