filegrc 0.16.3 → 0.16.5
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/agent.js +2 -1
- package/src/audit-preparation.js +6 -1
- package/src/cli.js +71 -33
- package/src/index.js +1 -0
- package/src/program-path.js +141 -75
- package/src/program-readiness.js +194 -25
- package/src/reconciliation.js +1 -0
- package/src/retention-schedule-approval.js +5 -5
- package/src/state.js +1 -0
- package/src/web.js +72 -22
- package/src/workflow.js +1 -0
package/package.json
CHANGED
package/src/agent.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { modelSupports } from "../model/index.js";
|
|
2
2
|
import { createResourceId } from "./id.js";
|
|
3
3
|
import { markdownEntries } from "./resource-markdown.js";
|
|
4
|
-
import { RESOURCE_INSTRUCTIONS, resourceProgramContext } from "./program-path.js";
|
|
4
|
+
import { RESOURCE_INSTRUCTIONS, RESOURCE_OUTPUTS, resourceProgramContext } from "./program-path.js";
|
|
5
5
|
import { assessCollectionReview } from "./collection-review.js";
|
|
6
6
|
import { resolveProgram } from "./program.js";
|
|
7
7
|
|
|
@@ -80,6 +80,7 @@ export function buildAgentGuide(loaded, type, options = {}) {
|
|
|
80
80
|
title: definition.title,
|
|
81
81
|
pluralTitle: definition.pluralTitle,
|
|
82
82
|
instructions: RESOURCE_INSTRUCTIONS[type] || definition.description,
|
|
83
|
+
output: RESOURCE_OUTPUTS[type] || `A validated ${definition.title} record.`,
|
|
83
84
|
use: definition.description,
|
|
84
85
|
purpose: definition.description,
|
|
85
86
|
programStep: resourceProgramContext(type, loaded.model),
|
package/src/audit-preparation.js
CHANGED
|
@@ -69,8 +69,13 @@ export async function assessAuditPreparation(input, options = {}) {
|
|
|
69
69
|
? audits.find((record) => record.id === options.auditId)
|
|
70
70
|
: options.selectDefault === false
|
|
71
71
|
? null
|
|
72
|
-
: selectDefaultAudit(
|
|
72
|
+
: selectDefaultAudit(options.programId && modelSupports(loaded.model, "program-scope")
|
|
73
|
+
? audits.filter((record) => record.programId === options.programId)
|
|
74
|
+
: audits, calendarAsOf);
|
|
73
75
|
if (options.auditId && !audit) throw new Error(`Audit "${options.auditId}" was not found.`);
|
|
76
|
+
if (audit && options.programId && modelSupports(loaded.model, "program-scope") && audit.programId !== options.programId) {
|
|
77
|
+
throw new Error(`Audit "${audit.id}" does not belong to Program "${options.programId}".`);
|
|
78
|
+
}
|
|
74
79
|
|
|
75
80
|
const formalPeriodEnd = audit?.auditKind === "soc-2-type-2" ? coverageEnd(audit.coverage) : null;
|
|
76
81
|
const readinessAsOf = formalPeriodEnd && formalPeriodEnd < calendarAsOf ? formalPeriodEnd : calendarAsOf;
|
package/src/cli.js
CHANGED
|
@@ -289,10 +289,11 @@ export async function runCli(argv = process.argv.slice(2)) {
|
|
|
289
289
|
const audit = auditId ? loaded.resources.find(({ id, type }) => id === auditId && type === "audit") : null;
|
|
290
290
|
const programId = flags.program || audit?.programId;
|
|
291
291
|
const readiness = await assessProgramReadiness(loaded, { asOf: flags["as-of"], programId });
|
|
292
|
-
const auditReadiness =
|
|
292
|
+
const auditReadiness = await assessAuditPreparation(loaded, {
|
|
293
293
|
auditId,
|
|
294
|
-
asOf: flags["as-of"]
|
|
295
|
-
|
|
294
|
+
asOf: flags["as-of"],
|
|
295
|
+
programId
|
|
296
|
+
});
|
|
296
297
|
const result = buildProgramPathResult(loaded.model, readiness, auditReadiness);
|
|
297
298
|
const output = selectProgramPathOutput(result, flags);
|
|
298
299
|
if (flags.json) console.log(JSON.stringify(output, null, 2));
|
|
@@ -458,15 +459,29 @@ export async function runCli(argv = process.argv.slice(2)) {
|
|
|
458
459
|
if (command === "program-readiness") {
|
|
459
460
|
const loaded = await loadWorkspace(root);
|
|
460
461
|
const result = await assessProgramReadiness(loaded, { asOf: flags["as-of"], programId: flags.program });
|
|
461
|
-
|
|
462
|
+
if (flags.control && flags.summary) throw new Error("Use either --control or --summary for program-readiness.");
|
|
463
|
+
const controlItem = flags.control
|
|
464
|
+
? result.stages.find(({ id }) => id === "controls")?.items.find(({ resourceType, resourceId }) => (
|
|
465
|
+
resourceType === "control" && resourceId === flags.control
|
|
466
|
+
))
|
|
467
|
+
: null;
|
|
468
|
+
if (flags.control && !controlItem) throw new Error(`Control "${flags.control}" is not selected in this Program.`);
|
|
469
|
+
const output = controlItem
|
|
470
|
+
? { asOf: result.asOf, programId: result.program?.id, ...controlItem }
|
|
471
|
+
: flags.summary ? summarizeProgramReadiness(result) : result;
|
|
462
472
|
const headlineStatus = result.progress.status || result.status;
|
|
463
473
|
if (flags.json) console.log(JSON.stringify(output, null, 2));
|
|
474
|
+
else if (controlItem) {
|
|
475
|
+
console.log(`${controlItem.status.toUpperCase()}\t${controlItem.title}`);
|
|
476
|
+
if (!controlItem.nextSteps?.length) console.log(` ${controlItem.message}`);
|
|
477
|
+
printGuidanceSteps(controlItem);
|
|
478
|
+
}
|
|
464
479
|
else if (flags.summary) {
|
|
465
480
|
console.log(`${headlineStatus.toUpperCase()}: ${result.progress.label} ${result.progress.percent}%. ${result.progress.detail}`);
|
|
466
481
|
for (const stage of output.stages) {
|
|
467
482
|
console.log(`${stage.status.toUpperCase()}\t${stage.title}\t${stage.counts.action} checks need action`);
|
|
468
483
|
}
|
|
469
|
-
if (output.firstAction)
|
|
484
|
+
if (output.firstAction) printNextAction(output.firstAction);
|
|
470
485
|
}
|
|
471
486
|
else {
|
|
472
487
|
console.log(`${headlineStatus.toUpperCase()}: ${result.progress.label} ${result.progress.percent}%. ${result.progress.detail}`);
|
|
@@ -480,7 +495,10 @@ export async function runCli(argv = process.argv.slice(2)) {
|
|
|
480
495
|
);
|
|
481
496
|
for (const stage of result.stages) {
|
|
482
497
|
console.log(`\n${stage.title}`);
|
|
483
|
-
for (const item of stage.items)
|
|
498
|
+
for (const item of stage.items) {
|
|
499
|
+
console.log(`${item.status.toUpperCase()}\t${item.title}${item.nextSteps?.length ? "" : `\t${item.message}`}`);
|
|
500
|
+
printGuidanceSteps(item);
|
|
501
|
+
}
|
|
484
502
|
}
|
|
485
503
|
if (result.policyActivations.length) {
|
|
486
504
|
console.log("\nPolicy activation assessments");
|
|
@@ -492,7 +510,7 @@ export async function runCli(argv = process.argv.slice(2)) {
|
|
|
492
510
|
console.log(`\nEvidence Ready: management can start the candidate Type 2 period on or after ${result.suggestedCandidatePeriodStart || result.asOf}.`);
|
|
493
511
|
}
|
|
494
512
|
}
|
|
495
|
-
if (flags["require-ready"] && !result.evidenceReady) process.exitCode = 2;
|
|
513
|
+
if (flags["require-ready"] && (controlItem ? controlItem.status !== "complete" : !result.evidenceReady)) process.exitCode = 2;
|
|
496
514
|
return output;
|
|
497
515
|
}
|
|
498
516
|
if (command === "reporting-route-sets") {
|
|
@@ -1347,7 +1365,7 @@ Usage:
|
|
|
1347
1365
|
filegrc list [resource-type] [--workflow] [--json]
|
|
1348
1366
|
filegrc search <query> [--type resource-type] [--json]
|
|
1349
1367
|
filegrc obligations [--program program-id] [--as-of YYYY-MM-DD] [--from YYYY-MM-DD] [--through YYYY-MM-DD] [--now RFC3339] [--complete] [--json]
|
|
1350
|
-
filegrc program-readiness [--as-of YYYY-MM-DD] [--require-ready] [--summary] [--json]
|
|
1368
|
+
filegrc program-readiness [--as-of YYYY-MM-DD] [--require-ready] [--summary | --control CONTROL_ID] [--json]
|
|
1351
1369
|
filegrc program-amendment <source-resource-id> [--json]
|
|
1352
1370
|
filegrc review-bindings <retention-or-mapping-id> [--json]
|
|
1353
1371
|
filegrc evidence-map [--as-of YYYY-MM-DD] [--json]
|
|
@@ -1365,7 +1383,7 @@ Usage:
|
|
|
1365
1383
|
filegrc policy-library [--json | --accept proposal-id --proposal-revision revision --yes]
|
|
1366
1384
|
filegrc trigger <event-type> (--occurred-on YYYY-MM-DD | --occurred-at RFC3339) [--program program-id] [--risk-level normal|high] [--subject resource-id[,resource-id]] [--title text] [--json]
|
|
1367
1385
|
filegrc evidence-packet [--audit audit-id | --program program-id] [--start YYYY-MM-DD] [--end YYYY-MM-DD] [--output .filegrc/path] [--preview] [--require-ready] [--json]
|
|
1368
|
-
filegrc get [resource-type] <id> [--mutation]
|
|
1386
|
+
filegrc get [resource-type] <id> [--mutation|--workflow]
|
|
1369
1387
|
filegrc references <id> [--json]
|
|
1370
1388
|
filegrc preview-mutation <preview.json|-> [--json]
|
|
1371
1389
|
filegrc create <mutation.json|-> [--json]
|
|
@@ -1496,8 +1514,9 @@ source. No audit ID or CPA firm is required.
|
|
|
1496
1514
|
Options:
|
|
1497
1515
|
--program <id> Program to assess when more than one active Program exists
|
|
1498
1516
|
--as-of <date> Evaluate effective dates and obligations on YYYY-MM-DD
|
|
1499
|
-
--require-ready Exit with code 2 unless the
|
|
1517
|
+
--require-ready Exit with code 2 unless the selected Control, or the full program, is ready
|
|
1500
1518
|
--summary Omit item details and print stage counts and next actions
|
|
1519
|
+
--control <id> Show the current setup steps for one selected Control
|
|
1501
1520
|
--json Print the result as JSON
|
|
1502
1521
|
--root <path> Workspace path
|
|
1503
1522
|
--help Show this help`);
|
|
@@ -1749,6 +1768,7 @@ function printAgentGuide(result) {
|
|
|
1749
1768
|
console.log(`Program step: ${result.programStep.order ? `Step ${result.programStep.order}` : `Step ${result.programStep.number}`} · ${result.programStep.title}`);
|
|
1750
1769
|
}
|
|
1751
1770
|
console.log(`Instructions: ${result.instructions}`);
|
|
1771
|
+
console.log(`Output: ${result.output}`);
|
|
1752
1772
|
console.log(`Use: ${result.use}`);
|
|
1753
1773
|
console.log(`Policy basis: ${result.policyBasis}`);
|
|
1754
1774
|
console.log(`Timing: ${result.cadence}`);
|
|
@@ -1810,23 +1830,21 @@ function buildProgramPathResult(model, readiness, auditReadiness) {
|
|
|
1810
1830
|
const readinessById = new Map(readiness.stages.map((stage) => [stage.id, stage]));
|
|
1811
1831
|
const stages = buildAgentProgramPath(model).map((stage) => {
|
|
1812
1832
|
if (stage.id === "audit") {
|
|
1813
|
-
const auditAction = auditReadiness
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
: null
|
|
1829
|
-
);
|
|
1833
|
+
const auditAction = !auditReadiness || auditReadiness.status === "not-started"
|
|
1834
|
+
? {
|
|
1835
|
+
id: "create-audit",
|
|
1836
|
+
status: "action",
|
|
1837
|
+
title: "Record the CPA engagement",
|
|
1838
|
+
message: "After engaging the CPA firm, create an Audit with the firm-agreed type, scope, and dates.",
|
|
1839
|
+
resourceType: "audit",
|
|
1840
|
+
commands: [
|
|
1841
|
+
"npx filegrc guide audit --json",
|
|
1842
|
+
"npx filegrc scaffold audit --title \"YEAR SOC 2 TYPE\" > audit-mutation.json",
|
|
1843
|
+
"npx filegrc create audit-mutation.json --json",
|
|
1844
|
+
"npx filegrc prepare-audit AUDIT_ID --json"
|
|
1845
|
+
]
|
|
1846
|
+
}
|
|
1847
|
+
: auditReadiness?.stages?.flatMap(({ items }) => items).find(({ status }) => status === "action") || null;
|
|
1830
1848
|
return {
|
|
1831
1849
|
...stage,
|
|
1832
1850
|
status: auditReadiness?.status || "not-started",
|
|
@@ -1866,12 +1884,29 @@ function buildProgramPathResult(model, readiness, auditReadiness) {
|
|
|
1866
1884
|
};
|
|
1867
1885
|
}
|
|
1868
1886
|
|
|
1887
|
+
function printGuidanceSteps(item) {
|
|
1888
|
+
item.nextSteps?.forEach((step, index) => console.log(` ${index + 1}. ${step}`));
|
|
1889
|
+
}
|
|
1890
|
+
|
|
1891
|
+
function printNextAction(action) {
|
|
1892
|
+
if (action.nextSteps?.length) {
|
|
1893
|
+
console.log(`Next: ${action.title}`);
|
|
1894
|
+
printGuidanceSteps(action);
|
|
1895
|
+
} else {
|
|
1896
|
+
console.log(`Next: ${action.title} · ${action.message}`);
|
|
1897
|
+
}
|
|
1898
|
+
}
|
|
1899
|
+
|
|
1869
1900
|
function printProgramPath(result) {
|
|
1870
1901
|
console.log(`Current: Step ${result.currentStep.number}, ${result.currentStep.title}`);
|
|
1871
1902
|
console.log(`Evidence Ready: ${result.evidenceReady ? "yes" : "no"}; operating: ${result.operating ? "yes" : "no"}`);
|
|
1872
1903
|
for (const stage of result.stages) {
|
|
1873
1904
|
console.log(`\nStep ${stage.number}. ${stage.title} [${String(stage.status).toUpperCase()}]`);
|
|
1874
1905
|
console.log(stage.summary);
|
|
1906
|
+
for (const section of stage.sections) {
|
|
1907
|
+
console.log(` ${section.title}`);
|
|
1908
|
+
section.steps.forEach((step, index) => console.log(` ${index + 1}. ${step}`));
|
|
1909
|
+
}
|
|
1875
1910
|
for (const page of stage.pages) {
|
|
1876
1911
|
console.log(`${page.order ? `Step ${page.order}` : "Operating area"} · ${page.title} (${page.type || `utility:${page.utility}`})`);
|
|
1877
1912
|
console.log(` ${page.summary}`);
|
|
@@ -1885,7 +1920,7 @@ function printProgramPath(result) {
|
|
|
1885
1920
|
}
|
|
1886
1921
|
console.log("Commands:");
|
|
1887
1922
|
for (const command of stage.commands) console.log(` ${command}`);
|
|
1888
|
-
for (const action of stage.nextActions)
|
|
1923
|
+
for (const action of stage.nextActions) printNextAction(action);
|
|
1889
1924
|
}
|
|
1890
1925
|
}
|
|
1891
1926
|
|
|
@@ -1953,6 +1988,7 @@ function summarizePathAction(action) {
|
|
|
1953
1988
|
status: action.status,
|
|
1954
1989
|
title: action.title,
|
|
1955
1990
|
message: action.message,
|
|
1991
|
+
...(action.nextSteps?.length ? { nextSteps: action.nextSteps } : {}),
|
|
1956
1992
|
...(action.resourceType ? { resourceType: action.resourceType } : {}),
|
|
1957
1993
|
...(action.resourceId ? { resourceId: action.resourceId } : {})
|
|
1958
1994
|
};
|
|
@@ -1965,11 +2001,11 @@ function nextActionCommands(stage, action) {
|
|
|
1965
2001
|
const commands = [`npx filegrc guide ${resourceType} --json`];
|
|
1966
2002
|
if (action.resourceId) {
|
|
1967
2003
|
const resourceId = shellArgument(action.resourceId);
|
|
1968
|
-
commands.push(`npx filegrc get ${resourceId} --mutation`);
|
|
2004
|
+
commands.push(`npx filegrc get ${resourceId} --mutation > MUTATION.json`);
|
|
1969
2005
|
commands.push(`npx filegrc update ${resourceType} ${resourceId} MUTATION.json --json`);
|
|
1970
2006
|
} else {
|
|
1971
2007
|
commands.push(`npx filegrc list ${resourceType} --json`);
|
|
1972
|
-
commands.push(`npx filegrc scaffold ${resourceType} --title "NAME"`);
|
|
2008
|
+
commands.push(`npx filegrc scaffold ${resourceType} --title "NAME" > MUTATION.json`);
|
|
1973
2009
|
commands.push("npx filegrc create MUTATION.json --json");
|
|
1974
2010
|
}
|
|
1975
2011
|
return commands;
|
|
@@ -1998,13 +2034,13 @@ function printProgramPathOutput(result, flags) {
|
|
|
1998
2034
|
console.log(`${String(stage.status).toUpperCase()}\tStep ${stage.number}\t${stage.title}`);
|
|
1999
2035
|
}
|
|
2000
2036
|
const current = result.stages.find(({ id }) => id === result.currentStep.id);
|
|
2001
|
-
if (current?.nextAction)
|
|
2037
|
+
if (current?.nextAction) printNextAction(current.nextAction);
|
|
2002
2038
|
return;
|
|
2003
2039
|
}
|
|
2004
2040
|
if (flags.next) {
|
|
2005
2041
|
console.log(`Current: Step ${result.currentStep.number}, ${result.currentStep.title}`);
|
|
2006
2042
|
if (result.step?.nextAction) {
|
|
2007
|
-
|
|
2043
|
+
printNextAction(result.step.nextAction);
|
|
2008
2044
|
}
|
|
2009
2045
|
for (const command of result.step?.commands || []) console.log(` ${command}`);
|
|
2010
2046
|
return;
|
|
@@ -2020,7 +2056,8 @@ function printWorkflow(result) {
|
|
|
2020
2056
|
console.log(`\n${result.counts.findings.ready || 0} ready findings, ${result.counts.workItems.overdue || 0} overdue Work Items`);
|
|
2021
2057
|
if (result.recommended) {
|
|
2022
2058
|
console.log(`Next: ${result.recommended.title}`);
|
|
2023
|
-
if (result.recommended.message) console.log(` ${result.recommended.message}`);
|
|
2059
|
+
if (!result.recommended.nextSteps?.length && result.recommended.message) console.log(` ${result.recommended.message}`);
|
|
2060
|
+
printGuidanceSteps(result.recommended);
|
|
2024
2061
|
const command = result.recommended.nextAction?.command || result.recommended.actions?.[0]?.command;
|
|
2025
2062
|
if (command) console.log(` ${command}`);
|
|
2026
2063
|
}
|
|
@@ -2035,6 +2072,7 @@ function summarizeProgramReadiness(result) {
|
|
|
2035
2072
|
status: item.status,
|
|
2036
2073
|
title: item.title,
|
|
2037
2074
|
...(options.message === false ? {} : { message: item.message }),
|
|
2075
|
+
...(item.nextSteps?.length ? { nextSteps: item.nextSteps } : {}),
|
|
2038
2076
|
...(item.resourceType ? { resourceType: item.resourceType } : {}),
|
|
2039
2077
|
...(item.resourceId ? { resourceId: item.resourceId } : {})
|
|
2040
2078
|
} : null;
|