filegrc 0.16.20 → 0.16.21
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/package.json +1 -1
- package/src/retention.js +35 -6
package/package.json
CHANGED
package/src/retention.js
CHANGED
|
@@ -167,8 +167,9 @@ export async function resourceReviewRevisions(loaded, ids, scopeHashInput = "leg
|
|
|
167
167
|
const entry = effectiveReviewEntry(loaded, entries.get(id));
|
|
168
168
|
if (!entry || reviewing.has(key)) return null;
|
|
169
169
|
reviewing.add(key);
|
|
170
|
-
const
|
|
171
|
-
|
|
170
|
+
const decisionHeading = heading || retentionPolicyHeading(loaded, entry, reviewer);
|
|
171
|
+
const parts = [reviewSource(loaded, entry, scopeHashInput, false, decisionHeading, reviewer)];
|
|
172
|
+
let matchedSection = !decisionHeading;
|
|
172
173
|
for (const markdown of reviewMarkdownEntries(loaded, entry, false)) {
|
|
173
174
|
try {
|
|
174
175
|
const source = entry.reviewCommit
|
|
@@ -176,7 +177,7 @@ export async function resourceReviewRevisions(loaded, ids, scopeHashInput = "leg
|
|
|
176
177
|
?? getFileAtRevision(loaded.root, entry.reviewCommit, `data/${markdown.path}`)
|
|
177
178
|
: await readFile(resolveDataPath(loaded.root, markdown.path), "utf8");
|
|
178
179
|
if (source !== null) {
|
|
179
|
-
const section = reviewedMarkdownSection(source,
|
|
180
|
+
const section = reviewedMarkdownSection(source, decisionHeading);
|
|
180
181
|
if (section !== null) { parts.push(section); matchedSection = true; }
|
|
181
182
|
}
|
|
182
183
|
} catch (error) {
|
|
@@ -214,8 +215,9 @@ export function resourceReviewRevisionsSync(loaded, ids, scopeHashInput = "legac
|
|
|
214
215
|
const entry = historicalCommit ? entries.get(id) : effectiveReviewEntry(loaded, entries.get(id));
|
|
215
216
|
if (!entry || reviewing.has(key)) return null;
|
|
216
217
|
reviewing.add(key);
|
|
217
|
-
const
|
|
218
|
-
|
|
218
|
+
const decisionHeading = legacySource ? heading : heading || retentionPolicyHeading(loaded, entry, reviewer);
|
|
219
|
+
const parts = [reviewSource(loaded, entry, scopeHashInput, legacySource, decisionHeading, reviewer)];
|
|
220
|
+
let matchedSection = !decisionHeading;
|
|
219
221
|
for (const markdown of reviewMarkdownEntries(loaded, entry, legacySource)) {
|
|
220
222
|
try {
|
|
221
223
|
const source = (historicalCommit || entry.reviewCommit)
|
|
@@ -224,7 +226,7 @@ export function resourceReviewRevisionsSync(loaded, ids, scopeHashInput = "legac
|
|
|
224
226
|
?? getFileAtRevision(loaded.root, historicalCommit || entry.reviewCommit, `data/${markdown.path}`)
|
|
225
227
|
: readFileSync(resolveDataPath(loaded.root, markdown.path), "utf8");
|
|
226
228
|
if (source !== null) {
|
|
227
|
-
const section = legacySource ? source : reviewedMarkdownSection(source,
|
|
229
|
+
const section = legacySource ? source : reviewedMarkdownSection(source, decisionHeading);
|
|
228
230
|
if (section !== null) { parts.push(section); matchedSection = true; }
|
|
229
231
|
}
|
|
230
232
|
} catch (error) {
|
|
@@ -460,6 +462,32 @@ function sourceHeading(record, sourceId) {
|
|
|
460
462
|
? headings[sourceId] : null;
|
|
461
463
|
}
|
|
462
464
|
|
|
465
|
+
function retentionPolicyHeading(loaded, entry, reviewer) {
|
|
466
|
+
if (reviewer?.type !== "retention-schedule-item" || entry.record.type !== "policy") return null;
|
|
467
|
+
// Older rows often bind a whole Policy. A single retention section gives
|
|
468
|
+
// that decision a narrower basis while an explicit heading still wins.
|
|
469
|
+
const headings = new Set();
|
|
470
|
+
for (const markdown of markdownEntries(loaded.model, entry.record)) {
|
|
471
|
+
let source;
|
|
472
|
+
try {
|
|
473
|
+
source = loaded.historicalFiles?.get(`data/${markdown.path}`)
|
|
474
|
+
?? (entry.reviewCommit
|
|
475
|
+
? entry.reviewFiles?.get(`data/${markdown.path}`)
|
|
476
|
+
?? getFileAtRevision(loaded.root, entry.reviewCommit, `data/${markdown.path}`)
|
|
477
|
+
: readFileSync(resolveDataPath(loaded.root, markdown.path), "utf8"));
|
|
478
|
+
} catch (error) {
|
|
479
|
+
if (error.code === "ENOENT") continue;
|
|
480
|
+
throw error;
|
|
481
|
+
}
|
|
482
|
+
for (const line of (source || "").split("\n")) {
|
|
483
|
+
const heading = /^#{2,6}\s+(.+?)\s*#*\s*$/.exec(line)?.[1];
|
|
484
|
+
if (heading && /\b(retention|disposal|deletion|destruction|archiving)\b/i.test(heading)
|
|
485
|
+
&& reviewedMarkdownSection(source, heading) !== null) headings.add(heading);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
return headings.size === 1 ? [...headings][0] : null;
|
|
489
|
+
}
|
|
490
|
+
|
|
463
491
|
const reviewDecisionFields = {
|
|
464
492
|
control: ["statement", "requirementIds", "code", "activity", "controlType", "operationMode", "operationPattern", "systemIds", "policyIds", "componentIds", "evidenceSourceComponentIds"],
|
|
465
493
|
commitment: ["commitmentKind", "statement", "systemIds", "sourceResourceIds", "sourceSectionHeadings", "requirementIds", "controlIds", "customerFacing", "effectiveOn", "reportingRouteRequirements"],
|
|
@@ -489,6 +517,7 @@ function substantiveReviewSource(record, currentModel, reviewer = null) {
|
|
|
489
517
|
}
|
|
490
518
|
if (record.type === "component" && reviewer?.type === "retention-schedule-item") {
|
|
491
519
|
decision.systemUses = retentionSystemUses(decision.systemUses);
|
|
520
|
+
delete decision.evidenceSourceKinds;
|
|
492
521
|
}
|
|
493
522
|
if (["control", "commitment", "complementary-control"].includes(record.type)) {
|
|
494
523
|
decision.nonApplicableDecision = ["not-applicable", "externally-managed", "zero-population"]
|