filegrc 0.9.2 → 0.11.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/model/index.js +8 -5
- package/model/v7.json +10359 -0
- package/model/v8.json +10947 -0
- package/package.json +1 -1
- package/src/applicability-scope.js +211 -0
- package/src/audit-preparation.js +156 -20
- package/src/batch-review.js +40 -24
- package/src/cli.js +85 -7
- package/src/collection-review.js +16 -3
- package/src/collection-revision.js +23 -3
- package/src/collection-scope.js +94 -7
- package/src/document-activation.js +13 -1
- package/src/git.js +4 -4
- package/src/index.js +3 -0
- package/src/model-migration.js +381 -35
- package/src/obligations.js +142 -39
- package/src/policy-activation.js +5 -0
- package/src/policy-library/data-retention-schedule-v2.md +25 -0
- package/src/policy-library.js +52 -24
- package/src/program-amendment.js +222 -0
- package/src/program-lifecycle.js +1 -1
- package/src/program-path.js +13 -8
- package/src/program-readiness.js +43 -13
- package/src/reconciliation.js +15 -3
- package/src/requirement-mapping.js +61 -0
- package/src/retention.js +261 -0
- package/src/server.js +76 -2
- package/src/setup.js +1 -1
- package/src/source-coverage.js +21 -7
- package/src/state.js +171 -2
- package/src/validate.js +106 -11
- package/src/web.js +441 -70
- package/src/workflow.js +33 -13
package/src/workflow.js
CHANGED
|
@@ -2,6 +2,7 @@ import { cp, mkdtemp, rm } from "node:fs/promises";
|
|
|
2
2
|
import { tmpdir } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { modelSupports } from "../model/index.js";
|
|
5
|
+
import { applicabilityReviewIsCurrent } from "./applicability-scope.js";
|
|
5
6
|
import { assessRequiredAppointments } from "./appointments.js";
|
|
6
7
|
import { assessSourceCoverageReadiness } from "./source-coverage.js";
|
|
7
8
|
import { coverageEnd, coverageStart } from "./coverage.js";
|
|
@@ -22,6 +23,7 @@ import {
|
|
|
22
23
|
import { assessAuditPreparation } from "./audit-preparation.js";
|
|
23
24
|
import { assessProgramReadiness } from "./program-readiness.js";
|
|
24
25
|
import { resolveProgram } from "./program.js";
|
|
26
|
+
import { resourceReviewRevisions, retentionReviewResourceIds, retentionRuleIsCurrent } from "./retention.js";
|
|
25
27
|
import { signatoryAppointmentIssue, soc2ReportEvidenceIssue, subsequentEventsReviewIssue } from "./soc2.js";
|
|
26
28
|
import { planReconciliation } from "./reconciliation.js";
|
|
27
29
|
import { currentCalendarDate } from "./time.js";
|
|
@@ -110,8 +112,8 @@ async function assessWorkflowUnmeasured(input, options = {}) {
|
|
|
110
112
|
audit.id,
|
|
111
113
|
await assessAuditPreparation(loaded, {
|
|
112
114
|
auditId: audit.id,
|
|
113
|
-
|
|
114
|
-
|
|
115
|
+
asOf,
|
|
116
|
+
generatedAt: evaluatedAt
|
|
115
117
|
})
|
|
116
118
|
])
|
|
117
119
|
));
|
|
@@ -140,10 +142,10 @@ async function assessWorkflowUnmeasured(input, options = {}) {
|
|
|
140
142
|
...programFindings(program),
|
|
141
143
|
...Object.values(auditPreparations).flatMap(auditFindings),
|
|
142
144
|
...appointmentTemplateFindings(loaded),
|
|
143
|
-
...sourceCoverageFindings(loaded, programRecord),
|
|
145
|
+
...await sourceCoverageFindings(loaded, programRecord),
|
|
144
146
|
...reconciliationFindings(reconciliation),
|
|
145
147
|
...periodFindings,
|
|
146
|
-
...auditLifecycleFindings(loaded, audits, programRecord)
|
|
148
|
+
...await auditLifecycleFindings(loaded, audits, programRecord)
|
|
147
149
|
];
|
|
148
150
|
const findings = [
|
|
149
151
|
...validationFindings(validation, loaded),
|
|
@@ -363,7 +365,7 @@ function recordFinalizationFindings(loaded, program) {
|
|
|
363
365
|
if (incomplete) {
|
|
364
366
|
findings.push(finalizationFinding(record, incomplete));
|
|
365
367
|
}
|
|
366
|
-
for (const missing of finalizationFields(record, loaded.model)) {
|
|
368
|
+
for (const missing of finalizationFields(record, loaded.model, loaded.resources, program)) {
|
|
367
369
|
findings.push(fieldFinding(record, missing));
|
|
368
370
|
}
|
|
369
371
|
}
|
|
@@ -498,16 +500,16 @@ function recordFinalizationRequiredness(record, loaded, program) {
|
|
|
498
500
|
return "conditional";
|
|
499
501
|
}
|
|
500
502
|
|
|
501
|
-
function finalizationFields(record, model) {
|
|
503
|
+
function finalizationFields(record, model, resources, program) {
|
|
502
504
|
const fields = [];
|
|
503
505
|
const needsReview = ["requirement", "commitment", "complementary-control", "control"].includes(record.type)
|
|
504
506
|
&& model.resources[record.type]?.fields?.applicabilityReview
|
|
505
|
-
&& !record.applicabilityReview;
|
|
507
|
+
&& !applicabilityReviewIsCurrent(record.applicabilityReview, record, program, resources, model);
|
|
506
508
|
if (needsReview) {
|
|
507
509
|
fields.push({
|
|
508
510
|
field: "applicabilityReview",
|
|
509
511
|
requiredness: "required",
|
|
510
|
-
message: "Record the reviewed applicability decision, rationale, reviewer, and date. FileGRC records the current scope automatically."
|
|
512
|
+
message: "Record or refresh the reviewed applicability decision, rationale, reviewer, and date. FileGRC records the current material scope automatically."
|
|
511
513
|
});
|
|
512
514
|
}
|
|
513
515
|
if (record.type === "policy" && model.resources.policy?.fields?.programRole && !record.programRole) {
|
|
@@ -637,7 +639,7 @@ function appointmentFinding(kind, template, record, state, requiredness) {
|
|
|
637
639
|
};
|
|
638
640
|
}
|
|
639
641
|
|
|
640
|
-
function sourceCoverageFindings(loaded, program) {
|
|
642
|
+
async function sourceCoverageFindings(loaded, program) {
|
|
641
643
|
const selected = modelSupports(loaded.model, "program-scope")
|
|
642
644
|
? new Set(program?.controlIds || [])
|
|
643
645
|
: null;
|
|
@@ -649,7 +651,7 @@ function sourceCoverageFindings(loaded, program) {
|
|
|
649
651
|
&& (!selected || selected.has(record.id))
|
|
650
652
|
))
|
|
651
653
|
.map(({ id }) => id);
|
|
652
|
-
return assessSourceCoverageReadiness(loaded, selectedControlIds, program)
|
|
654
|
+
return (await assessSourceCoverageReadiness(loaded, selectedControlIds, program))
|
|
653
655
|
.map(({ family, record, complete }) => {
|
|
654
656
|
const state = complete ? "complete" : "ready";
|
|
655
657
|
const code = `evidence-source.${family.id}.coverage`;
|
|
@@ -884,10 +886,12 @@ async function assessPeriodHealth(loaded, options) {
|
|
|
884
886
|
return findings;
|
|
885
887
|
}
|
|
886
888
|
|
|
887
|
-
function auditLifecycleFindings(loaded, audits, program) {
|
|
889
|
+
async function auditLifecycleFindings(loaded, audits, program) {
|
|
888
890
|
if (!modelSupports(loaded.model, "guided-workflow")) return [];
|
|
889
891
|
const target = program || resolveProgram(loaded);
|
|
890
892
|
const byId = new Map(loaded.resources.map((record) => [record.id, record]));
|
|
893
|
+
const retentionRules = loaded.resources.filter((record) => record.type === "retention-schedule-item");
|
|
894
|
+
const retentionRevisions = await resourceReviewRevisions(loaded, retentionRules.flatMap((rule) => retentionReviewResourceIds(rule, loaded)));
|
|
891
895
|
const findings = [];
|
|
892
896
|
for (const audit of audits) {
|
|
893
897
|
if (!(audit.controlIds || []).length) {
|
|
@@ -1015,11 +1019,21 @@ function auditLifecycleFindings(loaded, audits, program) {
|
|
|
1015
1019
|
`${records.length} ${noun} remain open, so this engagement cannot be treated as closed.`
|
|
1016
1020
|
));
|
|
1017
1021
|
}
|
|
1022
|
+
const linkedRetentionCurrent = (audit.retentionScheduleItemIds || []).some((id) => {
|
|
1023
|
+
const rule = byId.get(id);
|
|
1024
|
+
return rule?.type === "retention-schedule-item"
|
|
1025
|
+
&& rule.status === "active"
|
|
1026
|
+
&& (rule.scopeResourceIds || []).some((scopeId) => scopeId === audit.id || scopeId === target?.id)
|
|
1027
|
+
&& retentionRuleIsCurrent(rule, retentionRevisions, byId, loaded);
|
|
1028
|
+
});
|
|
1029
|
+
const retentionClosure = loaded.model.resources.audit?.fields?.retentionScheduleItemIds
|
|
1030
|
+
? ["retentionScheduleItemIds", "Link the audit retention schedule", "Link an active, current retention schedule item scoped to this Audit or its Program."]
|
|
1031
|
+
: ["retentionDecision", "Record the retention decision", "Record the approved retention and authorized distribution decision."];
|
|
1018
1032
|
for (const [field, title, message] of [
|
|
1019
|
-
|
|
1033
|
+
retentionClosure,
|
|
1020
1034
|
["carryForwardActionIds", "Review carry-forward work", "Record the next-period actions, including an explicit empty list when no carry-forward work remains."]
|
|
1021
1035
|
]) {
|
|
1022
|
-
if (field === "carryForwardActionIds" ? Array.isArray(audit[field]) : present(audit[field])) continue;
|
|
1036
|
+
if (field === "retentionScheduleItemIds" ? linkedRetentionCurrent : field === "carryForwardActionIds" ? Array.isArray(audit[field]) : present(audit[field])) continue;
|
|
1023
1037
|
findings.push(auditLifecycleFinding(audit, field, "audit-closure", title, message));
|
|
1024
1038
|
}
|
|
1025
1039
|
}
|
|
@@ -1081,6 +1095,12 @@ function normalizeFinding(code, item, context) {
|
|
|
1081
1095
|
severity: findingSeverity(state, context.assessment),
|
|
1082
1096
|
title: item.title,
|
|
1083
1097
|
message: item.message,
|
|
1098
|
+
...(item.resourceId ? { resourceId: item.resourceId } : {}),
|
|
1099
|
+
...(item.createResourceType ? { createResourceType: item.createResourceType } : {}),
|
|
1100
|
+
...(item.sourceResourceIds ? { sourceResourceIds: item.sourceResourceIds } : {}),
|
|
1101
|
+
...(item.informationTypeId ? { informationTypeId: item.informationTypeId } : {}),
|
|
1102
|
+
...(item.retentionScheduleItemIds ? { retentionScheduleItemIds: item.retentionScheduleItemIds } : {}),
|
|
1103
|
+
...(item.staleResourceIds ? { staleResourceIds: item.staleResourceIds } : {}),
|
|
1084
1104
|
...(subject ? { subject } : {}),
|
|
1085
1105
|
dependencies,
|
|
1086
1106
|
actions: (item.commands || []).map((command) => ({
|