filegrc 0.7.1 → 0.9.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/README.md +7 -7
- package/model/index.js +22 -4
- package/model/v5.json +10233 -0
- package/model/v6.json +10358 -0
- package/package.json +4 -2
- package/src/agent.js +4 -3
- package/src/audit-preparation.js +182 -45
- package/src/audit-transition.js +3 -2
- package/src/batch-review.js +7 -6
- package/src/cli.js +143 -12
- package/src/collection-review.js +4 -3
- package/src/collection-scope.js +4 -3
- package/src/document-activation.js +181 -0
- package/src/evidence-packet.js +199 -78
- package/src/external-reviewer.js +5 -4
- package/src/files.js +173 -35
- package/src/git.js +71 -7
- package/src/index.js +11 -1
- package/src/model-migration.js +363 -7
- package/src/obligations.js +14 -11
- package/src/policy-library.js +7 -3
- package/src/program-lifecycle.js +131 -3
- package/src/program-path.js +19 -13
- package/src/program-readiness.js +338 -71
- package/src/program.js +5 -3
- package/src/reconciliation.js +3 -2
- package/src/server.js +36 -7
- package/src/setup.js +8 -5
- package/src/soc2.js +3 -2
- package/src/validate.js +178 -16
- package/src/web.js +264 -17
- package/src/workflow.js +38 -7
- package/src/workspace.js +5 -0
package/src/program-lifecycle.js
CHANGED
|
@@ -1,6 +1,131 @@
|
|
|
1
1
|
import { currentPartyPeople } from "./parties.js";
|
|
2
|
+
import { modelSupports } from "../model/index.js";
|
|
2
3
|
|
|
3
|
-
|
|
4
|
+
const requiredDocumentsByControlCache = new WeakMap();
|
|
5
|
+
|
|
6
|
+
export function auditSpecificDocumentKinds(model) {
|
|
7
|
+
return new Set([
|
|
8
|
+
...(model?.auditReadiness?.managementDocuments || []).map(({ kind }) => kind),
|
|
9
|
+
"soc2-engagement-terms"
|
|
10
|
+
]);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function documentIsAuditSpecific(document, model) {
|
|
14
|
+
if (document?.type !== "document") return false;
|
|
15
|
+
if (modelSupports(model, "document-workflow-scope")) {
|
|
16
|
+
return document.workflowScope === "engagement";
|
|
17
|
+
}
|
|
18
|
+
return auditSpecificDocumentKinds(model).has(document.documentKind);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function governedDocumentIsOperating(document, asOf, model) {
|
|
22
|
+
if (document?.type !== "document" || document.status !== "active") return false;
|
|
23
|
+
if (!document.effectiveOn || document.effectiveOn > asOf) return false;
|
|
24
|
+
if (!modelSupports(model, "governed-document-activation")) return true;
|
|
25
|
+
if (document.activationBasis === "legacy-v4") {
|
|
26
|
+
return document.workflowScope === "engagement"
|
|
27
|
+
&& Boolean(document.approvedOn && document.approvedContentRevisions);
|
|
28
|
+
}
|
|
29
|
+
return Boolean(
|
|
30
|
+
document.activationBasis === "recorded"
|
|
31
|
+
&& document.approvedOn
|
|
32
|
+
&& document.approvedContentRevisions
|
|
33
|
+
&& document.activatedOn
|
|
34
|
+
&& document.activatedOn <= asOf
|
|
35
|
+
&& (document.activatedByIds || []).length
|
|
36
|
+
&& document.activatedContentRevisions
|
|
37
|
+
&& contentRevisionBindingsMatch(document.approvedContentRevisions, document.activatedContentRevisions)
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function governedTrainingIsOperating(training, asOf, model) {
|
|
42
|
+
if (training?.type !== "training" || training.status !== "active") return false;
|
|
43
|
+
if (!training.effectiveOn || training.effectiveOn > asOf) return false;
|
|
44
|
+
if (!modelSupports(model, "governed-training-activation")) return true;
|
|
45
|
+
if (training.activationBasis === "legacy-v5") {
|
|
46
|
+
return Boolean(training.approvedOn && training.approvedContentRevisions);
|
|
47
|
+
}
|
|
48
|
+
return Boolean(
|
|
49
|
+
training.activationBasis === "recorded"
|
|
50
|
+
&& training.approvedOn
|
|
51
|
+
&& training.approvedContentRevisions
|
|
52
|
+
&& training.activatedOn
|
|
53
|
+
&& training.activatedOn <= asOf
|
|
54
|
+
&& (training.activatedByIds || []).length
|
|
55
|
+
&& training.activatedContentRevisions
|
|
56
|
+
&& contentRevisionBindingsMatch(training.approvedContentRevisions, training.activatedContentRevisions)
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function governedContentIsOperating(record, asOf, model) {
|
|
61
|
+
if (record?.type === "document") return governedDocumentIsOperating(record, asOf, model);
|
|
62
|
+
if (record?.type === "training") return governedTrainingIsOperating(record, asOf, model);
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function contentRevisionBindingsMatch(left, right) {
|
|
67
|
+
if (!left || !right || Array.isArray(left) || Array.isArray(right)) return false;
|
|
68
|
+
const normalize = (value) => Object.fromEntries(Object.entries(value).sort(([a], [b]) => a.localeCompare(b)));
|
|
69
|
+
return JSON.stringify(normalize(left)) === JSON.stringify(normalize(right));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function obligationGovernedDocuments(obligation, byId, model) {
|
|
73
|
+
if (!modelSupports(model, "governed-document-activation")) return [];
|
|
74
|
+
const policyDocumentIds = (obligation.policyIds || []).flatMap((id) => {
|
|
75
|
+
const policy = byId.get(id);
|
|
76
|
+
return policy?.type === "policy" ? policy.relatedDocumentIds || [] : [];
|
|
77
|
+
});
|
|
78
|
+
const directDocumentIds = [
|
|
79
|
+
...(obligation.scopeResourceIds || []),
|
|
80
|
+
...(obligation.templateResourceId ? [obligation.templateResourceId] : [])
|
|
81
|
+
];
|
|
82
|
+
const requiredDocumentsByControl = indexRequiredDocumentsByControl(byId, model);
|
|
83
|
+
const controlDocumentIds = (obligation.controlIds || [])
|
|
84
|
+
.flatMap((id) => requiredDocumentsByControl.get(id) || []);
|
|
85
|
+
return [...new Set([...policyDocumentIds, ...directDocumentIds, ...controlDocumentIds])]
|
|
86
|
+
.map((id) => byId.get(id))
|
|
87
|
+
.filter((record) => (
|
|
88
|
+
record?.type === "document"
|
|
89
|
+
&& !["superseded", "retired"].includes(record.status)
|
|
90
|
+
&& !documentIsAuditSpecific(record, model)
|
|
91
|
+
));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function obligationGovernedContent(obligation, byId, model) {
|
|
95
|
+
const documents = obligationGovernedDocuments(obligation, byId, model);
|
|
96
|
+
if (!modelSupports(model, "governed-training-activation")) return documents;
|
|
97
|
+
const directIds = [
|
|
98
|
+
...(obligation.scopeResourceIds || []),
|
|
99
|
+
...(obligation.templateResourceId ? [obligation.templateResourceId] : [])
|
|
100
|
+
];
|
|
101
|
+
const training = [...new Set(directIds)]
|
|
102
|
+
.map((id) => byId.get(id))
|
|
103
|
+
.filter((record) => record?.type === "training" && !["superseded", "retired"].includes(record.status));
|
|
104
|
+
return [...documents, ...training];
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function indexRequiredDocumentsByControl(byId, model) {
|
|
108
|
+
const modelVersion = String(model?.modelVersion || "");
|
|
109
|
+
const cached = requiredDocumentsByControlCache.get(byId);
|
|
110
|
+
if (cached?.modelVersion === modelVersion) return cached.index;
|
|
111
|
+
const index = new Map();
|
|
112
|
+
for (const record of byId.values()) {
|
|
113
|
+
if (
|
|
114
|
+
record.type !== "document"
|
|
115
|
+
|| record.programRole !== "required"
|
|
116
|
+
|| ["superseded", "retired"].includes(record.status)
|
|
117
|
+
|| documentIsAuditSpecific(record, model)
|
|
118
|
+
) continue;
|
|
119
|
+
for (const controlId of record.controlIds || []) {
|
|
120
|
+
if (!index.has(controlId)) index.set(controlId, []);
|
|
121
|
+
index.get(controlId).push(record.id);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
requiredDocumentsByControlCache.set(byId, { modelVersion, index });
|
|
125
|
+
return index;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function obligationProgramStatus(obligation, byId, asOf, model) {
|
|
4
129
|
if (obligation.status !== "active") return "proposed";
|
|
5
130
|
if (currentPartyPeople(obligation.ownerIds || [], byId).size === 0) return "proposed";
|
|
6
131
|
const policyIds = obligation.policyIds || [];
|
|
@@ -12,6 +137,9 @@ export function obligationProgramStatus(obligation, byId, asOf) {
|
|
|
12
137
|
&& policy.effectiveOn <= asOf;
|
|
13
138
|
});
|
|
14
139
|
if (!policiesReady) return "proposed";
|
|
140
|
+
const governedContentReady = obligationGovernedContent(obligation, byId, model)
|
|
141
|
+
.every((record) => governedContentIsOperating(record, asOf, model));
|
|
142
|
+
if (!governedContentReady) return "proposed";
|
|
15
143
|
const controlIds = obligation.controlIds || [];
|
|
16
144
|
if (!controlIds.length) return "accepted";
|
|
17
145
|
return controlIds.some((id) => byId.get(id)?.type === "control" && byId.get(id).status === "implemented")
|
|
@@ -19,10 +147,10 @@ export function obligationProgramStatus(obligation, byId, asOf) {
|
|
|
19
147
|
: "proposed";
|
|
20
148
|
}
|
|
21
149
|
|
|
22
|
-
export function obligationIsRunning(obligation, byId, asOf) {
|
|
150
|
+
export function obligationIsRunning(obligation, byId, asOf, model) {
|
|
23
151
|
return obligation?.type === "obligation"
|
|
24
152
|
&& obligation.status === "active"
|
|
25
|
-
&& obligationProgramStatus(obligation, byId, asOf) === "accepted";
|
|
153
|
+
&& obligationProgramStatus(obligation, byId, asOf, model) === "accepted";
|
|
26
154
|
}
|
|
27
155
|
|
|
28
156
|
export function obligationIsEnabled(obligation) {
|
package/src/program-path.js
CHANGED
|
@@ -12,7 +12,7 @@ export const RESOURCE_INSTRUCTIONS = {
|
|
|
12
12
|
requirement: "Keep the published criterion as catalog content. Record management applicability and rationale on the selected Program.",
|
|
13
13
|
commitment: "Record supplemental customer promises and service requirements that shape the scope or control design. The Commitment’s systemIds and controlIds are authoritative for what fulfills it.",
|
|
14
14
|
policy: "Tailor each Policy to match what the company is committing to. Clear placeholders, assign an owner and separate approver, then bind approval to the reviewed content. Approval does not prove implementation. Activate the Policy during the Step 3 cutover after reviewing its implementation gaps.",
|
|
15
|
-
document: "
|
|
15
|
+
document: "Complete required program Documents in Step 2, assign an owner and separate approver, and bind approval to the intended values and exact Markdown. Implement the linked requirements and activate that approved revision in Step 3. Prepare Audit Documents in Step 5.",
|
|
16
16
|
control: "Finish each applicable starter Control with the procedure people follow, its owner, bounded System scope, operating Components, authoritative evidence-source Components, governing Policy and Requirement mappings, and implementation date. Put calendar and event schedules in Obligations.",
|
|
17
17
|
"complementary-control": "Review whether any in-scope Control depends on a customer or carved-out provider action. Record each real dependency, or confirm that the current scope has none.",
|
|
18
18
|
evidence: "Create an Evidence Artifact when a real export, report, screenshot, signed file, or approved external reference exists. Select its authoritative source Component, link the Controls and operating records it supports, retain the fixed artifact or reference, and have another person verify it before audit use.",
|
|
@@ -28,7 +28,7 @@ export const RESOURCE_INSTRUCTIONS = {
|
|
|
28
28
|
"access-grant": "Record each person’s or service account’s access to a Component, including approval, provisioning, changes, and removal.",
|
|
29
29
|
"access-review": "Review access on schedule, record each decision, and assign any access changes that result.",
|
|
30
30
|
"service-account": "Catalog non-human accounts that need separate tracking, including their owner, purpose, System, privilege, and expiry.",
|
|
31
|
-
training: "
|
|
31
|
+
training: "Review and approve the exact Training content in Step 2, then activate the unchanged revision during Step 3 after its linked Controls and assignment Obligations are ready.",
|
|
32
32
|
attestation: "Record each person’s completion or acknowledgement against the exact policy or training revision.",
|
|
33
33
|
"vulnerability-scan": "Record each required scan, including its scope, timing, result, and evidence.",
|
|
34
34
|
vulnerability: "Track confirmed weaknesses that need separate remediation, acceptance, or closure.",
|
|
@@ -99,15 +99,20 @@ export const PROGRAM_PATH = [
|
|
|
99
99
|
id: "policies",
|
|
100
100
|
number: 2,
|
|
101
101
|
title: "Approve Policies",
|
|
102
|
-
description: "
|
|
103
|
-
summary: "
|
|
102
|
+
description: "Review governed content and approvals",
|
|
103
|
+
summary: "Review and independently approve Policies, program Documents, and Training content.",
|
|
104
104
|
sections: [
|
|
105
|
-
{ id: "
|
|
105
|
+
{ id: "policy-content", title: "Policies", description: "Review every program Policy, Document, and Training record in one table, then bind independent approval to each exact revision.", steps: ["Review each governed Markdown artifact and replace every organization placeholder.", "Confirm the owner, separate approver, linked Controls, audience, and intended values that apply to each artifact.", "Record approval and its date against the exact revision. Leave approved content inactive until its Step 3 implementation cutover."], types: [], relatedLinks: [{ type: "policy", label: "Policies", href: "#/stage/policies" }], defaultOpen: true }
|
|
106
106
|
],
|
|
107
|
-
resourceTypes: [
|
|
107
|
+
resourceTypes: [],
|
|
108
|
+
supportingResourceTypes: ["policy", "document", "training"],
|
|
108
109
|
commands: [
|
|
109
110
|
"filegrc guide policy --json",
|
|
111
|
+
"filegrc guide document --json",
|
|
112
|
+
"filegrc guide training --json",
|
|
110
113
|
"filegrc list policy --json",
|
|
114
|
+
"filegrc list document --json",
|
|
115
|
+
"filegrc list training --json",
|
|
111
116
|
"filegrc get POLICY_ID --mutation"
|
|
112
117
|
]
|
|
113
118
|
},
|
|
@@ -118,14 +123,17 @@ export const PROGRAM_PATH = [
|
|
|
118
123
|
description: "Finish controls and their evidence sources",
|
|
119
124
|
summary: "Describe each Control and connect its evidence source.",
|
|
120
125
|
sections: [
|
|
121
|
-
{ id: "catalog", title: "Control Catalog", description: "
|
|
126
|
+
{ id: "catalog", title: "Control Catalog", description: "Implement approved requirements, configure Obligations, activate approved program content, finish authoritative evidence sources, then activate the Policies at cutover.", steps: ["Open every planned Control and confirm its mappings and operation pattern.", "Write the real procedure in Record Markdown, add bounded System scope, and map the operating and authoritative evidence-source Components.", "Review and enable every calendar and event schedule as an Obligation. Enabled work remains dormant until its governing Policy is active.", "Confirm each source Component is active, has an evidence-source role and rationale in the Control's System scope, has current access owners, and includes repeatable retrieval instructions in Record Markdown.", "Implement every requirement linked from an approved program Document or Training record, then activate the unchanged approved revisions with separate activation dates and bindings.", "Use the activation review to inspect planned or partial Controls, inactive governed content, missing Components or sources, missing schedules, and unresolved Exceptions.", "Choose the approved Policies that should take effect, set the real effective date, and confirm the Step 3 cutover."], types: ["control", "complementary-control", "obligation"], defaultOpen: true }
|
|
122
127
|
],
|
|
123
|
-
resourceTypes: ["control", "complementary-control", "
|
|
128
|
+
resourceTypes: ["control", "complementary-control", "obligation"],
|
|
124
129
|
commands: [
|
|
125
130
|
"filegrc guide control --json",
|
|
126
131
|
"filegrc list control --json",
|
|
127
132
|
"filegrc get CONTROL_ID --mutation",
|
|
133
|
+
"filegrc guide obligation --json",
|
|
134
|
+
"filegrc list obligation --json",
|
|
128
135
|
"filegrc review-collection complementary-control --scaffold",
|
|
136
|
+
"filegrc activate-content --scaffold",
|
|
129
137
|
"filegrc activate-policies --scaffold",
|
|
130
138
|
"filegrc evidence-map --json",
|
|
131
139
|
"filegrc program-readiness --json"
|
|
@@ -139,11 +147,11 @@ export const PROGRAM_PATH = [
|
|
|
139
147
|
summary: "Complete scheduled and event work. Keep dated proof.",
|
|
140
148
|
sections: [
|
|
141
149
|
{ id: "risk", title: "Risk", description: "Maintain the program’s risk assessments and risk register as the service, threats, suppliers, and control needs change.", steps: ["Complete and approve risk assessments on schedule and after material changes.", "Record risks that need treatment, acceptance, or ongoing tracking.", "Add or update controls when the assessment identifies a new or changed response."], types: ["risk-assessment", "risk"], defaultOpen: true },
|
|
142
|
-
{ id: "queue", title: "Work Queue", description: "Complete recurring
|
|
150
|
+
{ id: "queue", title: "Work Queue", description: "Complete recurring occurrences, Policy Event tasks, and assigned follow-up within their required windows.", steps: ["Complete due work within its allowed window and link dated proof.", "Start Policy Events when hiring, departures, incidents, or material changes occur.", "Resolve every other open Action Item from the same queue."], types: ["obligation-event", "data-request"], utility: "obligation-board", defaultOpen: true },
|
|
143
151
|
{ id: "evidence", title: "Evidence Artifacts", description: "Create records only for real exports, reports, screenshots, signed files, or approved external references collected during operation.", steps: ["Create an Evidence Artifact when the artifact exists or an operating record needs fixed supporting proof.", "Select the authoritative source Component, link the Controls and source operating record, and retain the fixed attachment or approved reference.", "Record the collector and Classification, then have another person verify the artifact before audit use."], types: ["evidence"], defaultOpen: true },
|
|
144
152
|
{ id: "governance", title: "Governance", description: "Record formal reviews, oversight meetings, and approved policy or control exceptions.", steps: ["Complete scheduled policy reviews and oversight meetings.", "Record decisions, attendees, follow-up work, and evidence.", "Approve time-bound exceptions before the departure begins."], types: ["policy-review", "meeting", "exception"], defaultOpen: false },
|
|
145
153
|
{ id: "inventories", title: "Assets and Vendors", description: "Maintain the asset inventory and recurring reviews of supplier relationships during operation.", steps: ["Keep ownership, custody, status, and lifecycle current for important assets.", "Perform vendor reviews on schedule and after material supplier changes.", "Link fixed reports and review evidence to the operating records."], types: ["asset", "vendor-review"], defaultOpen: false },
|
|
146
|
-
{ id: "access-training", title: "Access and Training", description: "Inventory service accounts
|
|
154
|
+
{ id: "access-training", title: "Access and Training Completion", description: "Inventory service accounts and retain access decisions, Training assignments, and acknowledgements produced during operation.", steps: ["Catalog service accounts that need separate tracking.", "Preserve access approvals and removals as they occur, then complete periodic access reviews and resolve exceptions.", "Retain each Training assignment and Attestation against the exact active content revision."], types: ["service-account", "access-grant", "access-review", "attestation"], defaultOpen: false },
|
|
147
155
|
{ id: "security", title: "Security Operations", description: "Record vulnerability work, applicable penetration testing, and incident response activity for the period.", steps: ["Retain scan scope, results, vulnerabilities, remediation, and exceptions.", "When the approved applicability review requires penetration testing, record the test and follow-up findings.", "Start the incident workflow when a qualifying event occurs."], types: ["vulnerability-scan", "vulnerability", "penetration-test", "incident"], defaultOpen: false },
|
|
148
156
|
{ id: "resilience", title: "Resilience", description: "Preserve proof that backups, restoration, continuity, and incident exercises work as designed.", steps: ["Record backup restoration tests and their results.", "Run continuity and incident exercises on schedule.", "Assign and close follow-up work from failed objectives or lessons learned."], types: ["backup-test", "exercise"], defaultOpen: false },
|
|
149
157
|
{ id: "issues", title: "Issues and Remediation", description: "Keep observations in the source report and track only confirmed gaps that need a separate remediation lifecycle.", steps: ["Create a Finding only when a confirmed gap needs its own owner, due date, status, or verified closure.", "Use the Finding itself for straightforward remediation; create Action Items only for separate assigned tasks.", "Work Action Items from Work Queue and close the Finding only after remediation is independently verified."], types: ["finding"], defaultOpen: false }
|
|
@@ -151,7 +159,6 @@ export const PROGRAM_PATH = [
|
|
|
151
159
|
resourceTypes: [
|
|
152
160
|
"risk-assessment",
|
|
153
161
|
"risk",
|
|
154
|
-
"obligation",
|
|
155
162
|
"obligation-event",
|
|
156
163
|
"data-request",
|
|
157
164
|
"evidence",
|
|
@@ -163,7 +170,6 @@ export const PROGRAM_PATH = [
|
|
|
163
170
|
"service-account",
|
|
164
171
|
"access-grant",
|
|
165
172
|
"access-review",
|
|
166
|
-
"training",
|
|
167
173
|
"attestation",
|
|
168
174
|
"vulnerability-scan",
|
|
169
175
|
"vulnerability",
|
|
@@ -217,7 +223,7 @@ export const PROGRAM_PATH = [
|
|
|
217
223
|
summary: "Track the CPA engagement, fieldwork, and evidence packet.",
|
|
218
224
|
sections: [
|
|
219
225
|
{ id: "engagement", title: "Engagement", description: "Record the actual CPA engagement, formal scope and dates, requests, and management responses.", steps: ["Create the Audit after the CPA firm is engaged.", "Record the firm-agreed type, scope, systems, criteria, and dates.", "Track incoming requests and approved response material."], types: ["audit", "audit-request"], defaultOpen: true },
|
|
220
|
-
{ id: "fieldwork", title: "Fieldwork", description: "Prepare
|
|
226
|
+
{ id: "fieldwork", title: "Fieldwork", description: "Prepare Audit Documents, reconcile Type 2 populations, review both evidence paths, support testing, and build the indexed packet.", steps: ["Initialize and complete engagement-specific management Documents and populations.", "Approve and activate each Audit Document only when its engagement facts and timing are final.", "Review dated FileGRC operating records and verified Evidence Artifacts for the formal period.", "Reconcile complete populations, link samples, and resolve fieldwork requests and Findings.", "Build the packet from a clean Git revision; it includes FileGRC records, Markdown, Evidence Artifacts, attachments, indexes, history, and checksums."], types: ["audit-population", "control-test"], relatedLinks: [{ type: "document", label: "Audit Documents", href: "#/resources/document?stage=audit&documentScope=audit" }], utility: "audit-packet", defaultOpen: true }
|
|
221
227
|
],
|
|
222
228
|
resourceTypes: ["audit", "audit-request", "audit-population", "control-test"],
|
|
223
229
|
utilities: [
|