filegrc 0.14.0 → 0.15.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/state.js CHANGED
@@ -150,17 +150,7 @@ export async function createAppStateSection(input, section, options = {}) {
150
150
  collectionReviews: Object.fromEntries(
151
151
  assessCollectionReviews(loaded, { programId: activeProgram.id }).map((assessment) => [
152
152
  assessment.resourceType,
153
- {
154
- resourceType: assessment.resourceType,
155
- configuration: assessment.configuration,
156
- recordCount: assessment.recordCount,
157
- review: assessment.review,
158
- reviewRevision: assessment.reviewRevision,
159
- collectionRevision: assessment.collectionRevision,
160
- status: assessment.status,
161
- complete: assessment.complete,
162
- message: assessment.message
163
- }
153
+ serializeCollectionReviewAssessment(assessment)
164
154
  ])
165
155
  ),
166
156
  applicabilityConstraints: Object.fromEntries(loaded.resources.flatMap((record) => {
@@ -318,17 +308,7 @@ async function createAppStateUnlocked(input, options) {
318
308
  const collectionReviews = Object.fromEntries(
319
309
  assessCollectionReviews(loaded, { programId: activeProgram.id }).map((assessment) => [
320
310
  assessment.resourceType,
321
- {
322
- resourceType: assessment.resourceType,
323
- configuration: assessment.configuration,
324
- recordCount: assessment.recordCount,
325
- review: assessment.review,
326
- reviewRevision: assessment.reviewRevision,
327
- collectionRevision: assessment.collectionRevision,
328
- status: assessment.status,
329
- complete: assessment.complete,
330
- message: assessment.message
331
- }
311
+ serializeCollectionReviewAssessment(assessment)
332
312
  ])
333
313
  );
334
314
  return {
@@ -356,6 +336,23 @@ async function createAppStateUnlocked(input, options) {
356
336
  };
357
337
  }
358
338
 
339
+ function serializeCollectionReviewAssessment(assessment) {
340
+ return {
341
+ resourceType: assessment.resourceType,
342
+ configuration: assessment.configuration,
343
+ recordCount: assessment.recordCount,
344
+ review: assessment.review,
345
+ reviewRevision: assessment.reviewRevision,
346
+ collectionRevision: assessment.collectionRevision,
347
+ status: assessment.status,
348
+ complete: assessment.complete,
349
+ message: assessment.message,
350
+ ...(assessment.eligibleReviewerIds ? { eligibleReviewerIds: assessment.eligibleReviewerIds } : {}),
351
+ ...(assessment.reviewerConflictIds ? { reviewerConflictIds: assessment.reviewerConflictIds } : {}),
352
+ ...(assessment.approvalIssues ? { approvalIssues: assessment.approvalIssues } : {})
353
+ };
354
+ }
355
+
359
356
  export async function createResourceDetail(input, type, id, options = {}) {
360
357
  if (input?.entries && input?.root) return createResourceDetailFromLoaded(input, type, id, options);
361
358
  return serializeWorkspaceMutation(input, async (root) => {
package/src/validate.js CHANGED
@@ -19,6 +19,7 @@ import {
19
19
  validCalendarRecurrence
20
20
  } from "./recurrence.js";
21
21
  import { resourceReviewRevisions, retentionReviewResourceIds } from "./retention.js";
22
+ import { retentionScheduleApprovalIssues } from "./retention-schedule-approval.js";
22
23
  import { obligationIsEnabled } from "./program-lifecycle.js";
23
24
  import { currentPartyPeople, partyPeople } from "./parties.js";
24
25
  import { isMarkdownChoice, markdownEntries } from "./resource-markdown.js";
@@ -1296,6 +1297,32 @@ function validateCollectionReview(record, loaded, byId, path, diagnostics) {
1296
1297
  ));
1297
1298
  }
1298
1299
  }
1300
+ if (current && record.resourceType === "retention-schedule-item" && modelSupports(model, "retention-schedule-approval")) {
1301
+ const rows = scopedCollectionRecords(loaded, record.resourceType, program);
1302
+ for (const issue of retentionScheduleApprovalIssues(loaded, program || {}, rows, {
1303
+ informationTypesReviewed: collectionReviewIsCurrent(loaded, "information-type", program)
1304
+ })) {
1305
+ diagnostics.push(error(issue.code, path, issue.message));
1306
+ }
1307
+ const owners = new Set();
1308
+ for (const candidate of loaded.resources.filter((candidate) => (
1309
+ candidate.type === "document"
1310
+ && candidate.documentKind === "schedule"
1311
+ && candidate.workflowScope === "program"
1312
+ && !["superseded", "retired"].includes(candidate.status)
1313
+ || rows.includes(candidate)
1314
+ ))) {
1315
+ for (const id of currentPartyPeople(candidate.ownerIds || [], byId)) owners.add(id);
1316
+ }
1317
+ const conflictedReviewers = (record.reviewedByIds || []).filter((id) => owners.has(id));
1318
+ if (conflictedReviewers.length) {
1319
+ diagnostics.push(error(
1320
+ "conflicted-retention-schedule-reviewer",
1321
+ path,
1322
+ `A Data Retention Schedule reviewer cannot own its governing document or an included schedule row: ${conflictedReviewers.join(", ")}.`
1323
+ ));
1324
+ }
1325
+ }
1299
1326
  if (modelSupports(model, "temporal-collection-reviews")) {
1300
1327
  const temporalValues = [record.coverage, record.knowledgeCutoffAt, record.populationResourceIds];
1301
1328
  const hasTemporalBinding = temporalValues.every((value) => value !== undefined && value !== null);
@@ -1364,6 +1391,25 @@ function validateCollectionReview(record, loaded, byId, path, diagnostics) {
1364
1391
  }
1365
1392
  }
1366
1393
 
1394
+ function collectionReviewIsCurrent(loaded, resourceType, program) {
1395
+ const review = loaded.resources.find((record) => (
1396
+ record.type === "collection-review"
1397
+ && record.resourceType === resourceType
1398
+ && record.status === "active"
1399
+ && (!modelSupports(loaded.model, "program-scope") || (record.scopeResourceIds || []).includes(program?.id))
1400
+ ));
1401
+ if (!review || !(loaded.model.collectionReviews?.[resourceType]?.decisions || ["complete"]).includes(review.decision)) return false;
1402
+ const records = scopedCollectionRecords(loaded, resourceType, program);
1403
+ const revisionMatches = collectionRevisionMatches(loaded, resourceType, review.collectionRevision, { program });
1404
+ if (!modelSupports(loaded.model, "temporal-collection-reviews")) return revisionMatches;
1405
+ return revisionMatches
1406
+ && review.coverage?.kind === "as-of"
1407
+ && review.coverage.on === review.reviewedOn
1408
+ && Boolean(review.knowledgeCutoffAt)
1409
+ && Array.isArray(review.populationResourceIds)
1410
+ && JSON.stringify([...review.populationResourceIds].sort()) === JSON.stringify(records.map(({ id }) => id).sort());
1411
+ }
1412
+
1367
1413
  function validateCollectionReviewSet(resources, pathById, diagnostics) {
1368
1414
  const current = new Map();
1369
1415
  for (const review of resources.filter((record) => record.type === "collection-review" && record.status !== "retired")) {