create-filegrc 0.3.0 → 0.3.1
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 +28 -1
- package/package.json +1 -1
- package/src/cli.js +80 -6
- package/src/defaults.js +12 -7
- package/src/index.js +201 -20
- package/template/AGENTS.md +6 -20
- package/template/README.md +3 -1
- package/template/WORKSPACE.md +4 -13
- package/template/data/evidence/AGENTS.md +1 -1
- package/template/data/workspace.json +2 -2
- package/template/package.json +1 -1
- package/template-parameters.json +32 -0
package/README.md
CHANGED
|
@@ -11,10 +11,37 @@ npm run serve
|
|
|
11
11
|
|
|
12
12
|
Setup asks for the legal organization name, the initial policy owner and their email, a security reporting address, and the program timezone. The generated private project includes a starter Security program, Program Readiness, a policy-driven Work Queue, Policy Events that add linked tasks, later audit preparation, evidence packets, and one dependency: `filegrc`.
|
|
13
13
|
|
|
14
|
+
Creation has two layers. The five-record foundation contains workspace settings, the initial owner, the oversight team, renderer settings, and the filegrc system of record. The default `security` starter adds the framework references, proposed policies, controls, obligations, documents, and training records. Pass `--starter foundation` when you want to stop before selecting a framework.
|
|
15
|
+
|
|
16
|
+
For one noninteractive run, pass company and service fields together or use `--config setup.json`. The optional `setup` object defines the service boundary and management goal after installation. Use `--filegrc-package <directory>` to exercise unpublished local engine changes instead of installing the registry release. This writes a machine-local `file:` dependency, so replace it with a released version before sharing the generated workspace.
|
|
17
|
+
|
|
18
|
+
```json
|
|
19
|
+
{
|
|
20
|
+
"companyName": "Example Company",
|
|
21
|
+
"policyOwnerName": "Security Owner",
|
|
22
|
+
"policyOwnerEmail": "owner@example.com",
|
|
23
|
+
"securityContactEmail": "security@example.com",
|
|
24
|
+
"timezone": "America/Chicago",
|
|
25
|
+
"starter": "security",
|
|
26
|
+
"setup": {
|
|
27
|
+
"serviceName": "Example Service",
|
|
28
|
+
"boundary": "The production service and supporting infrastructure.",
|
|
29
|
+
"criticality": "high",
|
|
30
|
+
"dataClassification": "Confidential",
|
|
31
|
+
"internetExposed": true,
|
|
32
|
+
"programGoal": "type-2"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
```sh
|
|
38
|
+
npx create-filegrc@latest company-grc --config setup.json
|
|
39
|
+
```
|
|
40
|
+
|
|
14
41
|
Generated workspaces also include layered `AGENTS.md` instructions and model-driven headless commands. `filegrc program-path` gives agents the same six steps, exact page guidance, current status, and next actions shown in the renderer. Agents can define program scope, approve policies, implement controls, test External Evidence, complete policy work, trigger event tasks, and prepare later audit packets through the same domain functions used by the renderer.
|
|
15
42
|
|
|
16
43
|
Git is initialized when needed. The browser can create local commits before a remote is configured.
|
|
17
44
|
|
|
18
45
|
Creation output reports the resolved filegrc version, program timezone, starter record counts, whether installation ran, and whether the target joined an existing Git worktree or received a new repository.
|
|
19
46
|
|
|
20
|
-
Use `npx create-filegrc@latest --help` for
|
|
47
|
+
Use `npx create-filegrc@latest --help` for noninteractive options.
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -1,17 +1,42 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { resolve } from "node:path";
|
|
2
3
|
import { createFilegrc } from "./index.js";
|
|
3
4
|
|
|
4
5
|
export async function runCli(argv = process.argv.slice(2)) {
|
|
5
|
-
const
|
|
6
|
+
const parsed = parseArgs(argv);
|
|
7
|
+
const config = parsed.options.config ? await readConfig(parsed.options.config) : {};
|
|
8
|
+
const organization = config.organization && typeof config.organization === "object"
|
|
9
|
+
? config.organization
|
|
10
|
+
: {};
|
|
11
|
+
const options = {
|
|
12
|
+
...organization,
|
|
13
|
+
...config,
|
|
14
|
+
...parsed.options,
|
|
15
|
+
setup: {
|
|
16
|
+
...(config.setup || {}),
|
|
17
|
+
...parsed.setup
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
delete options.organization;
|
|
21
|
+
delete options.config;
|
|
22
|
+
delete options.target;
|
|
23
|
+
if (!Object.keys(options.setup).length) delete options.setup;
|
|
24
|
+
const target = parsed.target ?? config.target ?? "filegrc-program";
|
|
6
25
|
if (options.help) return printHelp();
|
|
7
26
|
if (options.version) return printVersion();
|
|
8
27
|
const result = await createFilegrc({ target, ...options });
|
|
9
28
|
console.log(`Created ${result.target}`);
|
|
10
|
-
console.log(
|
|
29
|
+
console.log(
|
|
30
|
+
`filegrc ${result.engineVersion}${result.enginePackage ? ` from ${result.enginePackage}` : ""}: ` +
|
|
31
|
+
`${result.install === "installed" ? "installed" : "installation skipped"}`
|
|
32
|
+
);
|
|
11
33
|
console.log(`Git: ${result.gitMode === "existing-worktree" ? "joined existing worktree" : "initialized new repository"}`);
|
|
12
34
|
console.log(`Timezone: ${result.values.timezone}`);
|
|
35
|
+
for (const stage of result.stages) {
|
|
36
|
+
console.log(`Stage ${stage.id}: ${stage.status}${stage.status === "created" ? ` (${stage.records} records)` : ""}`);
|
|
37
|
+
}
|
|
13
38
|
console.log(
|
|
14
|
-
|
|
39
|
+
`${result.setup ? "Workspace after setup" : "Program baseline"}: ${result.resourceCounts.total} records, including ` +
|
|
15
40
|
`${result.resourceCounts.byType.requirement || 0} requirements, ` +
|
|
16
41
|
`${result.resourceCounts.byType.control || 0} controls, and ` +
|
|
17
42
|
`${result.resourceCounts.byType.obligation || 0} obligations.`
|
|
@@ -19,11 +44,14 @@ export async function runCli(argv = process.argv.slice(2)) {
|
|
|
19
44
|
console.log("");
|
|
20
45
|
console.log(` cd ${shellQuote(result.target)}`);
|
|
21
46
|
if (options.install === false) console.log(" npm install");
|
|
22
|
-
console.log(" npx filegrc setup");
|
|
47
|
+
if (!result.setup) console.log(" npx filegrc setup");
|
|
23
48
|
console.log(" npm run validate");
|
|
24
49
|
console.log(" npm run serve");
|
|
25
50
|
console.log("");
|
|
26
|
-
|
|
51
|
+
if (result.setup) {
|
|
52
|
+
console.log(`Service setup: ${result.setup.system.id} (${result.setup.system.status}), target ${result.setup.target.assuranceGoal}.`);
|
|
53
|
+
}
|
|
54
|
+
console.log("Review the generated records, then commit the approved baseline.");
|
|
27
55
|
}
|
|
28
56
|
|
|
29
57
|
function shellQuote(value) {
|
|
@@ -33,6 +61,7 @@ function shellQuote(value) {
|
|
|
33
61
|
function parseArgs(argv) {
|
|
34
62
|
let target;
|
|
35
63
|
const options = {};
|
|
64
|
+
const setup = {};
|
|
36
65
|
for (let index = 0; index < argv.length; index += 1) {
|
|
37
66
|
const value = argv[index];
|
|
38
67
|
if (!value.startsWith("-") && !target) {
|
|
@@ -46,15 +75,26 @@ function parseArgs(argv) {
|
|
|
46
75
|
else if (name === "yes" || name === "y") options.yes = true;
|
|
47
76
|
else if (name === "force") options.force = true;
|
|
48
77
|
else if (name === "no-install") options.install = false;
|
|
78
|
+
else if (name === "config") options.config = next();
|
|
49
79
|
else if (name === "company-name") options.companyName = next();
|
|
50
80
|
else if (name === "policy-owner-name") options.policyOwnerName = next();
|
|
51
81
|
else if (name === "policy-owner-email") options.policyOwnerEmail = next();
|
|
52
82
|
else if (name === "security-contact-email") options.securityContactEmail = next();
|
|
53
83
|
else if (name === "timezone") options.timezone = next();
|
|
84
|
+
else if (name === "starter") options.starter = next();
|
|
54
85
|
else if (name === "filegrc-version") options.filegrcVersion = next();
|
|
86
|
+
else if (name === "filegrc-package") options.filegrcPackage = next();
|
|
87
|
+
else if (name === "service-name") setup.serviceName = next();
|
|
88
|
+
else if (name === "boundary") setup.boundary = next();
|
|
89
|
+
else if (name === "service-owner") setup.ownerId = next();
|
|
90
|
+
else if (name === "criticality") setup.criticality = next();
|
|
91
|
+
else if (name === "classification") setup.dataClassification = next();
|
|
92
|
+
else if (name === "internet-exposed") setup.internetExposed = booleanOption(next(), "internet-exposed");
|
|
93
|
+
else if (name === "program-goal") setup.programGoal = next();
|
|
94
|
+
else if (name === "setup-draft") setup.draft = true;
|
|
55
95
|
else throw new Error(`Unknown option "${value}".`);
|
|
56
96
|
}
|
|
57
|
-
return { target
|
|
97
|
+
return { target, options, setup };
|
|
58
98
|
}
|
|
59
99
|
|
|
60
100
|
function printHelp() {
|
|
@@ -63,12 +103,23 @@ function printHelp() {
|
|
|
63
103
|
Create a filegrc workspace for a SOC 2 program.
|
|
64
104
|
|
|
65
105
|
Options:
|
|
106
|
+
--config <json-file|-> Read creation and optional setup values
|
|
66
107
|
--company-name <legal-name> Legal organization name
|
|
67
108
|
--policy-owner-name <name> Initial policy owner
|
|
68
109
|
--policy-owner-email <email> Policy owner's email address
|
|
69
110
|
--security-contact-email <email> Security reporting address
|
|
70
111
|
--timezone <iana-timezone> Program timezone, such as America/Chicago
|
|
112
|
+
--starter <profile> security (default) or foundation
|
|
71
113
|
--filegrc-version <version> Override resolved engine version
|
|
114
|
+
--filegrc-package <directory> Install an unpublished local filegrc package
|
|
115
|
+
--service-name <name> Complete service setup after creation
|
|
116
|
+
--boundary <description> Initial service boundary
|
|
117
|
+
--service-owner <person-id> Defaults to person-policy-owner
|
|
118
|
+
--criticality <level> low, medium, high, or critical
|
|
119
|
+
--classification <name> Initial data classification
|
|
120
|
+
--internet-exposed <bool> true or false
|
|
121
|
+
--program-goal <goal> none, readiness, type-1, or type-2
|
|
122
|
+
--setup-draft Save combined service setup as a draft
|
|
72
123
|
--no-install Write files and a preliminary lockfile only
|
|
73
124
|
--force Allow a non-empty target without overwriting files
|
|
74
125
|
--yes Use generic prompt defaults
|
|
@@ -80,3 +131,26 @@ async function printVersion() {
|
|
|
80
131
|
const packageJson = JSON.parse(await readFile(new URL("../package.json", import.meta.url), "utf8"));
|
|
81
132
|
console.log(packageJson.version);
|
|
82
133
|
}
|
|
134
|
+
|
|
135
|
+
async function readConfig(path) {
|
|
136
|
+
const source = path === "-"
|
|
137
|
+
? await readStdin()
|
|
138
|
+
: await readFile(resolve(String(path)), "utf8");
|
|
139
|
+
const parsed = JSON.parse(source);
|
|
140
|
+
if (!parsed || Array.isArray(parsed) || typeof parsed !== "object") {
|
|
141
|
+
throw new Error("Creation config must be a JSON object.");
|
|
142
|
+
}
|
|
143
|
+
return parsed;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async function readStdin() {
|
|
147
|
+
const chunks = [];
|
|
148
|
+
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
149
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function booleanOption(value, name) {
|
|
153
|
+
if (value === "true") return true;
|
|
154
|
+
if (value === "false") return false;
|
|
155
|
+
throw new Error(`--${name} must be true or false.`);
|
|
156
|
+
}
|
package/src/defaults.js
CHANGED
|
@@ -884,11 +884,11 @@ const obligations = [
|
|
|
884
884
|
}
|
|
885
885
|
];
|
|
886
886
|
|
|
887
|
-
export function baselineRecordPaths() {
|
|
888
|
-
return baselineRecordFiles("2000-01-01").map(({ path }) => path);
|
|
887
|
+
export function baselineRecordPaths(starter = "security") {
|
|
888
|
+
return baselineRecordFiles("2000-01-01", starter).map(({ path }) => path);
|
|
889
889
|
}
|
|
890
890
|
|
|
891
|
-
export function baselineRecordFiles(effectiveDate) {
|
|
891
|
+
export function baselineRecordFiles(effectiveDate, starter = "security") {
|
|
892
892
|
const framework = {
|
|
893
893
|
schemaVersion: 1,
|
|
894
894
|
id: FRAMEWORK_ID,
|
|
@@ -1006,20 +1006,25 @@ export function baselineRecordFiles(effectiveDate) {
|
|
|
1006
1006
|
policyIds: obligation.policyIds
|
|
1007
1007
|
}));
|
|
1008
1008
|
|
|
1009
|
+
const foundation = [
|
|
1010
|
+
recordFile("systems", programRepository),
|
|
1011
|
+
recordFile("teams", team)
|
|
1012
|
+
];
|
|
1013
|
+
if (starter === "foundation") return foundation;
|
|
1014
|
+
if (starter !== "security") throw new Error(`Unknown starter profile "${starter}".`);
|
|
1009
1015
|
return [
|
|
1010
1016
|
recordFile("frameworks", framework),
|
|
1011
1017
|
recordFile("frameworks", descriptionFramework),
|
|
1012
1018
|
...commonRequirements.map((record) => recordFile("requirements", record)),
|
|
1013
1019
|
...descriptionRequirements.map((record) => recordFile("requirements", record)),
|
|
1014
1020
|
...controlRecords.map((record) => recordFile("controls", record)),
|
|
1015
|
-
|
|
1016
|
-
recordFile("teams", team),
|
|
1021
|
+
...foundation,
|
|
1017
1022
|
...obligationRecords.map((record) => recordFile("obligations", record))
|
|
1018
1023
|
];
|
|
1019
1024
|
}
|
|
1020
1025
|
|
|
1021
|
-
export async function writeBaselineRecords(target, effectiveDate) {
|
|
1022
|
-
for (const { path: relativePath, record } of baselineRecordFiles(effectiveDate)) {
|
|
1026
|
+
export async function writeBaselineRecords(target, effectiveDate, starter = "security") {
|
|
1027
|
+
for (const { path: relativePath, record } of baselineRecordFiles(effectiveDate, starter)) {
|
|
1023
1028
|
const path = join(target, relativePath);
|
|
1024
1029
|
await mkdir(dirname(path), { recursive: true });
|
|
1025
1030
|
await writeFile(path, `${JSON.stringify(record, null, 2)}\n`, { encoding: "utf8", flag: "wx" });
|
package/src/index.js
CHANGED
|
@@ -4,33 +4,43 @@ import { basename, dirname, extname, join, relative, resolve } from "node:path";
|
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import { promisify } from "node:util";
|
|
6
6
|
import { createInterface } from "node:readline/promises";
|
|
7
|
-
import { baselineRecordPaths, writeBaselineRecords } from "./defaults.js";
|
|
7
|
+
import { baselineRecordFiles, baselineRecordPaths, writeBaselineRecords } from "./defaults.js";
|
|
8
8
|
|
|
9
9
|
const execute = promisify(execFile);
|
|
10
10
|
const packageRoot = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
11
11
|
const textExtensions = new Set(["", ".json", ".md", ".txt", ".yml", ".yaml", ".gitignore"]);
|
|
12
|
+
const STARTER_PROFILES = new Set(["foundation", "security"]);
|
|
13
|
+
const SECURITY_TEMPLATE_COLLECTIONS = new Set(["documents", "policies", "training"]);
|
|
12
14
|
|
|
13
15
|
export async function createFilegrc(options = {}) {
|
|
14
16
|
const parameterConfig = JSON.parse(await readFile(join(packageRoot, "template-parameters.json"), "utf8"));
|
|
15
17
|
const target = resolve(options.target ?? "filegrc-program");
|
|
18
|
+
const starter = normalizeStarterProfile(options.starter);
|
|
19
|
+
if (options.setup && options.install === false) {
|
|
20
|
+
throw new Error("Combined service setup requires installation. Remove --no-install or run filegrc setup after npm install.");
|
|
21
|
+
}
|
|
22
|
+
validateCombinedSetup(options.setup);
|
|
16
23
|
await assertWritableTarget(target, Boolean(options.force));
|
|
17
|
-
if (options.force) await assertNoTemplateCollisions(target);
|
|
24
|
+
if (options.force) await assertNoTemplateCollisions(target, starter);
|
|
18
25
|
|
|
19
26
|
const prompted = await resolvePromptValues(parameterConfig.parameters, options);
|
|
20
|
-
const
|
|
27
|
+
const engine = await resolveEngine(options);
|
|
28
|
+
const starterText = starterTemplateText(starter, prompted.company_name);
|
|
21
29
|
const values = {
|
|
22
30
|
...prompted,
|
|
23
31
|
effective_date: options.effectiveDate ?? new Date().toISOString().slice(0, 10),
|
|
24
32
|
project_name: normalizePackageName(basename(target)),
|
|
25
|
-
filegrc_version:
|
|
26
|
-
filegrc_version_range:
|
|
33
|
+
filegrc_version: engine.version,
|
|
34
|
+
filegrc_version_range: engine.dependency,
|
|
35
|
+
...starterText
|
|
27
36
|
};
|
|
28
37
|
|
|
29
38
|
await mkdir(target, { recursive: true });
|
|
30
|
-
await copyTemplate(target);
|
|
31
|
-
await renderTemplate(target, parameterConfig, values);
|
|
32
|
-
await writeBaselineRecords(target, values.effective_date);
|
|
33
|
-
|
|
39
|
+
await copyTemplate(target, starter);
|
|
40
|
+
await renderTemplate(target, parameterConfig, values, starter);
|
|
41
|
+
await writeBaselineRecords(target, values.effective_date, starter);
|
|
42
|
+
await applyStarterScope(target, starter, values.effective_date);
|
|
43
|
+
const initialResourceCounts = await summarizeResources(target);
|
|
34
44
|
|
|
35
45
|
const installed = options.install !== false;
|
|
36
46
|
if (installed) {
|
|
@@ -40,16 +50,33 @@ export async function createFilegrc(options = {}) {
|
|
|
40
50
|
}
|
|
41
51
|
const joinedExistingWorktree = await isInsideGitWorktree(target);
|
|
42
52
|
if (!joinedExistingWorktree) await run("git", ["init"], target);
|
|
53
|
+
const setup = options.setup ? await runCombinedSetup(target, options.setup) : null;
|
|
54
|
+
const resourceCounts = setup ? await summarizeResources(target) : initialResourceCounts;
|
|
43
55
|
return {
|
|
44
56
|
target,
|
|
45
57
|
values,
|
|
46
|
-
|
|
58
|
+
starter,
|
|
59
|
+
engineVersion: engine.version,
|
|
60
|
+
engineSource: engine.localPath ? "local" : "registry",
|
|
61
|
+
enginePackage: engine.localPath || null,
|
|
62
|
+
dependency: engine.dependency,
|
|
63
|
+
stages: starterStages(starter, initialResourceCounts),
|
|
47
64
|
resourceCounts,
|
|
65
|
+
setup,
|
|
48
66
|
install: installed ? "installed" : "skipped",
|
|
49
67
|
gitMode: joinedExistingWorktree ? "existing-worktree" : "initialized"
|
|
50
68
|
};
|
|
51
69
|
}
|
|
52
70
|
|
|
71
|
+
export function normalizeStarterProfile(value = "security") {
|
|
72
|
+
const profile = String(value || "security").trim().toLowerCase();
|
|
73
|
+
const normalized = profile === "empty" ? "foundation" : profile === "soc2-security" ? "security" : profile;
|
|
74
|
+
if (!STARTER_PROFILES.has(normalized)) {
|
|
75
|
+
throw new Error(`Starter profile must be one of ${[...STARTER_PROFILES].join(", ")}.`);
|
|
76
|
+
}
|
|
77
|
+
return normalized;
|
|
78
|
+
}
|
|
79
|
+
|
|
53
80
|
export async function resolveFilegrcVersion(explicitVersion) {
|
|
54
81
|
if (explicitVersion) return cleanVersion(explicitVersion);
|
|
55
82
|
try {
|
|
@@ -65,6 +92,28 @@ export async function resolveFilegrcVersion(explicitVersion) {
|
|
|
65
92
|
}
|
|
66
93
|
}
|
|
67
94
|
|
|
95
|
+
async function resolveEngine(options) {
|
|
96
|
+
if (options.filegrcVersion && options.filegrcPackage) {
|
|
97
|
+
throw new Error("Use either filegrcVersion or filegrcPackage, not both.");
|
|
98
|
+
}
|
|
99
|
+
if (!options.filegrcPackage) {
|
|
100
|
+
const version = await resolveFilegrcVersion(options.filegrcVersion);
|
|
101
|
+
return { version, dependency: `^${version}`, localPath: null };
|
|
102
|
+
}
|
|
103
|
+
const localPath = resolve(String(options.filegrcPackage));
|
|
104
|
+
let packageJson;
|
|
105
|
+
try {
|
|
106
|
+
packageJson = JSON.parse(await readFile(join(localPath, "package.json"), "utf8"));
|
|
107
|
+
} catch (error) {
|
|
108
|
+
throw new Error(`Could not read a local filegrc package at ${localPath}: ${error.message}`);
|
|
109
|
+
}
|
|
110
|
+
if (packageJson.name !== "filegrc") {
|
|
111
|
+
throw new Error(`Local package at ${localPath} is named "${packageJson.name || ""}", expected "filegrc".`);
|
|
112
|
+
}
|
|
113
|
+
const version = cleanVersion(packageJson.version);
|
|
114
|
+
return { version, dependency: `file:${localPath.replaceAll("\\", "/")}`, localPath };
|
|
115
|
+
}
|
|
116
|
+
|
|
68
117
|
async function resolvePromptValues(parameters, options) {
|
|
69
118
|
const mapped = {
|
|
70
119
|
company_name: options.companyName,
|
|
@@ -161,9 +210,9 @@ async function assertWritableTarget(target, force) {
|
|
|
161
210
|
}
|
|
162
211
|
}
|
|
163
212
|
|
|
164
|
-
async function assertNoTemplateCollisions(target) {
|
|
213
|
+
async function assertNoTemplateCollisions(target, starter) {
|
|
165
214
|
const collisions = [];
|
|
166
|
-
for (const destinationPath of [...await templateDestinationPaths(), ...baselineRecordPaths()]) {
|
|
215
|
+
for (const destinationPath of [...await templateDestinationPaths(starter), ...baselineRecordPaths(starter)]) {
|
|
167
216
|
await assertNoSymlinkComponents(target, destinationPath);
|
|
168
217
|
try {
|
|
169
218
|
await lstat(join(target, destinationPath));
|
|
@@ -192,20 +241,22 @@ async function assertNoSymlinkComponents(target, relativePath) {
|
|
|
192
241
|
}
|
|
193
242
|
}
|
|
194
243
|
|
|
195
|
-
async function renderTemplate(target, parameterConfig, values) {
|
|
244
|
+
async function renderTemplate(target, parameterConfig, values, starter) {
|
|
196
245
|
const declared = new Set([
|
|
197
246
|
...parameterConfig.parameters.map(({ key }) => key),
|
|
198
247
|
...parameterConfig.generated.map(({ key }) => key)
|
|
199
248
|
]);
|
|
200
|
-
const files = (await templateDestinationPaths()).map((path) => join(target, path));
|
|
249
|
+
const files = (await templateDestinationPaths(starter)).map((path) => join(target, path));
|
|
201
250
|
for (const path of files) {
|
|
202
251
|
if (!textExtensions.has(extname(path)) && basename(path) !== ".gitignore") continue;
|
|
203
252
|
let source = await readFile(path, "utf8");
|
|
204
253
|
const jsonFile = extname(path) === ".json";
|
|
205
|
-
source = source.replace(/\{\{([a-z0-9_]+)\}\}
|
|
254
|
+
source = source.replace(/\{\{([a-z0-9_]+)\}\}(\.)?/g, (match, token, sentencePeriod = "") => {
|
|
206
255
|
if (!declared.has(token)) throw new Error(`Unknown template token "{{${token}}}" in ${path}`);
|
|
207
256
|
if (values[token] === undefined) throw new Error(`No value resolved for template token "{{${token}}}"`);
|
|
208
|
-
|
|
257
|
+
const value = jsonFile ? jsonStringContents(values[token]) : String(values[token]);
|
|
258
|
+
const punctuation = !jsonFile && sentencePeriod && /[.!?]$/u.test(value) ? "" : sentencePeriod;
|
|
259
|
+
return value + punctuation;
|
|
209
260
|
});
|
|
210
261
|
const unresolved = /\{\{([a-z0-9_]+)\}\}/.exec(source);
|
|
211
262
|
if (unresolved) throw new Error(`Unresolved template token "{{${unresolved[1]}}}" in ${path}`);
|
|
@@ -213,20 +264,22 @@ async function renderTemplate(target, parameterConfig, values) {
|
|
|
213
264
|
}
|
|
214
265
|
}
|
|
215
266
|
|
|
216
|
-
async function templateDestinationPaths() {
|
|
267
|
+
async function templateDestinationPaths(starter = "security") {
|
|
217
268
|
const template = join(packageRoot, "template");
|
|
218
269
|
return (await collectFiles(template)).flatMap((source) => {
|
|
219
270
|
const templatePath = relative(template, source);
|
|
271
|
+
if (!includeTemplatePath(templatePath, starter)) return [];
|
|
220
272
|
if (templatePath === "README.md") return [];
|
|
221
273
|
if (templatePath === "WORKSPACE.md") return ["README.md"];
|
|
222
274
|
return [templatePath === "gitignore" ? ".gitignore" : templatePath];
|
|
223
275
|
});
|
|
224
276
|
}
|
|
225
277
|
|
|
226
|
-
async function copyTemplate(target) {
|
|
278
|
+
async function copyTemplate(target, starter = "security") {
|
|
227
279
|
const template = join(packageRoot, "template");
|
|
228
280
|
for (const source of await collectFiles(template)) {
|
|
229
281
|
const templatePath = relative(template, source);
|
|
282
|
+
if (!includeTemplatePath(templatePath, starter)) continue;
|
|
230
283
|
if (templatePath === "README.md") continue;
|
|
231
284
|
const destinationPath = templatePath === "WORKSPACE.md"
|
|
232
285
|
? "README.md"
|
|
@@ -242,6 +295,13 @@ async function copyTemplate(target) {
|
|
|
242
295
|
}
|
|
243
296
|
}
|
|
244
297
|
|
|
298
|
+
function includeTemplatePath(templatePath, starter) {
|
|
299
|
+
if (starter === "security") return true;
|
|
300
|
+
const segments = templatePath.split(/[\\/]/);
|
|
301
|
+
if (segments[0] !== "data" || !SECURITY_TEMPLATE_COLLECTIONS.has(segments[1])) return true;
|
|
302
|
+
return segments.at(-1) === "AGENTS.md";
|
|
303
|
+
}
|
|
304
|
+
|
|
245
305
|
async function collectFiles(directory) {
|
|
246
306
|
const result = [];
|
|
247
307
|
for (const item of await readdir(directory, { withFileTypes: true })) {
|
|
@@ -265,16 +325,137 @@ async function summarizeResources(target) {
|
|
|
265
325
|
return { total, byType: counts };
|
|
266
326
|
}
|
|
267
327
|
|
|
328
|
+
async function applyStarterScope(target, starter, effectiveDate) {
|
|
329
|
+
if (starter !== "security") return;
|
|
330
|
+
const records = baselineRecordFiles(effectiveDate, starter).map(({ record }) => record);
|
|
331
|
+
const workspacePath = join(target, "data", "workspace.json");
|
|
332
|
+
const workspace = JSON.parse(await readFile(workspacePath, "utf8"));
|
|
333
|
+
const ids = (type) => records.filter((record) => record.type === type).map((record) => record.id);
|
|
334
|
+
await writeFile(workspacePath, `${JSON.stringify({
|
|
335
|
+
...workspace,
|
|
336
|
+
frameworkIds: ids("framework"),
|
|
337
|
+
requirementIds: ids("requirement"),
|
|
338
|
+
controlIds: ids("control")
|
|
339
|
+
}, null, 2)}\n`, "utf8");
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function starterStages(starter, counts) {
|
|
343
|
+
const foundationTypes = new Set(["workspace", "renderer-settings", "person", "team", "system"]);
|
|
344
|
+
const foundation = Object.entries(counts.byType)
|
|
345
|
+
.filter(([type]) => foundationTypes.has(type))
|
|
346
|
+
.reduce((total, [, count]) => total + count, 0);
|
|
347
|
+
return [
|
|
348
|
+
{ id: "foundation", status: "created", records: foundation },
|
|
349
|
+
{
|
|
350
|
+
id: "soc2-security",
|
|
351
|
+
status: starter === "security" ? "created" : "skipped",
|
|
352
|
+
records: starter === "security" ? counts.total - foundation : 0
|
|
353
|
+
}
|
|
354
|
+
];
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function starterTemplateText(starter, companyName) {
|
|
358
|
+
if (starter === "foundation") {
|
|
359
|
+
return {
|
|
360
|
+
program_title: `${companyName} GRC Program`,
|
|
361
|
+
program_description: "Governance, risk, and compliance workspace without a preselected framework.",
|
|
362
|
+
program_summary: `This private workspace holds ${companyName}'s governance, risk, compliance, and audit-evidence records. JSON under \`data/\` stores structured records, Markdown stores long-form work, and Git records reviewed changes.`,
|
|
363
|
+
agent_title: "filegrc Workspace Instructions",
|
|
364
|
+
agent_purpose: `This repository is ${companyName}’s foundation filegrc workspace. Engineers and agents maintain the source records under \`data/\`. The \`filegrc\` package validates, searches, edits, and renders those files. No framework or assurance program has been selected yet.`,
|
|
365
|
+
starter_baseline: `## Foundation baseline
|
|
366
|
+
|
|
367
|
+
The generated workspace starts with five structural records:
|
|
368
|
+
|
|
369
|
+
- Workspace and renderer settings
|
|
370
|
+
- The initial active owner
|
|
371
|
+
- An inactive security and risk oversight team that still needs an independent chair
|
|
372
|
+
- The filegrc Git repository as a governance system of record
|
|
373
|
+
- A default 5x5 risk method and Public, Internal, Confidential, and Restricted data classifications
|
|
374
|
+
|
|
375
|
+
This profile does not include framework requirements, policies, governed documents, training, controls, obligations, or audit-management templates. Add and review those records for the selected framework before treating Program Readiness or Audit Readiness as meaningful. Do not infer that an absent control, policy, or schedule is unnecessary.`,
|
|
376
|
+
audit_preparation_guidance: "The foundation profile does not include the local SOC 2 management-document templates used by `prepare-audit`. Add reviewed templates and program scope before initializing audit work. Audit preparation must not invent missing policy, control, or evidence facts.",
|
|
377
|
+
starter_setup: `## Start the program
|
|
378
|
+
|
|
379
|
+
This foundation profile contains the workspace, initial owner, oversight team, renderer settings, and filegrc system of record. It does not select a framework or create proposed policies, controls, obligations, or evidence.
|
|
380
|
+
|
|
381
|
+
1. Run \`npx filegrc setup\` for guided service and goal setup, or use browser onboarding.
|
|
382
|
+
2. Use \`npx filegrc guide --json\` before creating framework requirements, policies, controls, obligations, and evidence sources.
|
|
383
|
+
3. Run \`npx filegrc validate\`, review the Git diff, and commit each reviewed program layer.`
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
return {
|
|
387
|
+
program_title: `${companyName} SOC 2 Program`,
|
|
388
|
+
program_description: "SOC 2 Security program based on the AICPA Trust Services Criteria.",
|
|
389
|
+
program_summary: `This private workspace holds ${companyName}'s SOC 2 program records and audit evidence. JSON under \`data/\` stores structured records, Markdown stores long-form work, and Git records reviewed changes.`,
|
|
390
|
+
agent_title: "filegrc SOC 2 Workspace Instructions",
|
|
391
|
+
agent_purpose: `This repository is ${companyName}’s filegrc workspace for its SOC 2 program. Engineers and agents maintain the source records under \`data/\`. The \`filegrc\` package validates, searches, edits, and renders those files.`,
|
|
392
|
+
starter_baseline: `## Starter baseline
|
|
393
|
+
|
|
394
|
+
The generated workspace starts with the SOC 2 Security category:
|
|
395
|
+
|
|
396
|
+
- Active framework records for the 2017 Trust Services Criteria with revised points of focus (2022) and the 2018 SOC 2 Description Criteria with revised implementation guidance (2022)
|
|
397
|
+
- The 33 Common Criteria reference IDs from CC1.1 through CC9.2, without the licensed criteria text
|
|
398
|
+
- The nine Description Criteria reference IDs from DC1 through DC9, without the licensed criteria text
|
|
399
|
+
- Planned controls mapped to those references and the included policies
|
|
400
|
+
- A security and risk oversight team chaired by an independent reviewer who may be internal or external
|
|
401
|
+
- Recurring obligations for the reviews, scans, tests, training, and meetings required by the included policies
|
|
402
|
+
- A default 5x5 risk method and Public, Internal, Confidential, and Restricted data classifications
|
|
403
|
+
|
|
404
|
+
Treat every planned control as a proposal until its owner, actual procedure in Record Markdown, system scope, cadence, authoritative evidence sources, implementation date, and mappings match actual practice. For a control linked to filegrc obligations, every non-retired Work Queue schedule must be enabled and its governing policies effective. Marking the control implemented starts eligible schedules. Do not mark a control implemented because a policy describes it. Add Availability, Processing Integrity, Confidentiality, or Privacy criteria only when they are in scope.
|
|
405
|
+
|
|
406
|
+
The recurring obligations mirror the fixed cadences in the starter policies. They remain proposals until every governing policy is active and effective and, when they name controls, at least one linked control is implemented. Update the policy, control, and obligation together when an approved cadence changes. Create separate completion records, such as meetings, reviews, scans, tests, exercises, and attestations, for each period.`,
|
|
407
|
+
audit_preparation_guidance: "Preparation creates a separate system description, management assertion, and management representation document for the engagement from the local starter templates. Type 2 preparation also creates a period completeness statement and one `audit-population` record for each standard population. It is safe to run again and does not approve documents, mark controls implemented, or create evidence. Do not reuse one completed management document across engagements.",
|
|
408
|
+
starter_setup: `## Finish initial setup
|
|
409
|
+
|
|
410
|
+
The starter policies, controls, and obligations are proposals. They do not state that ${companyName} operates the described controls.
|
|
411
|
+
|
|
412
|
+
1. Run \`npx filegrc setup\` for guided service and goal setup, or use browser onboarding. Then finish Step 1 by adding the real reviewers and operators, finishing the oversight team, and confirming applicable criteria, commitments, material vendors, and in-scope systems.
|
|
413
|
+
2. Review the starter policies, appoint a reviewer who is separate from the policy owner, and activate only the policies that match current practice. The reviewer will usually be another person in the organization, but may be external.
|
|
414
|
+
3. Review the starter control set, implement each applicable control with its actual procedure, scope, cadence, evidence sources, and implementation date, and confirm any linked Work Queue schedules are enabled. Marking a control implemented starts eligible schedules. Then record any complementary customer or subservice controls.
|
|
415
|
+
4. Preview External Evidence drafts with \`npx filegrc evidence-test-drafts --preview --json\`. Create them only after confirming applicable controls and authoritative source systems.
|
|
416
|
+
5. Run \`npx filegrc program-readiness --require-ready\`, record the management candidate period start when reliable evidence collection begins, maintain risk assessments and risks, update controls when needed, use Work Queue for scheduled work, and trigger Policy Events when changes create required actions.
|
|
417
|
+
6. Engage a CPA firm, record the separate firm-agreed period in an audit record, review filegrc Evidence and External Evidence, and prepare fieldwork.`
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
function validateCombinedSetup(setup) {
|
|
422
|
+
if (!setup) return;
|
|
423
|
+
if (Array.isArray(setup) || typeof setup !== "object") throw new Error("setup must be a JSON object.");
|
|
424
|
+
const required = ["serviceName", "boundary", "criticality", "dataClassification", "internetExposed"];
|
|
425
|
+
const missing = required.filter((name) => setup[name] === undefined || setup[name] === "");
|
|
426
|
+
if (missing.length) throw new Error(`Combined setup is missing: ${missing.join(", ")}.`);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
async function runCombinedSetup(target, input) {
|
|
430
|
+
const setup = { programGoal: "none", ownerId: "person-policy-owner", ...input };
|
|
431
|
+
const args = [
|
|
432
|
+
join(target, "node_modules", "filegrc", "bin", "filegrc.js"),
|
|
433
|
+
"setup",
|
|
434
|
+
"--service-name", String(setup.serviceName),
|
|
435
|
+
"--boundary", String(setup.boundary),
|
|
436
|
+
"--owner", String(setup.ownerId),
|
|
437
|
+
"--criticality", String(setup.criticality),
|
|
438
|
+
"--classification", String(setup.dataClassification),
|
|
439
|
+
"--internet-exposed", String(setup.internetExposed),
|
|
440
|
+
"--program-goal", String(setup.programGoal),
|
|
441
|
+
"--summary",
|
|
442
|
+
"--json"
|
|
443
|
+
];
|
|
444
|
+
if (setup.draft === true) args.push("--draft");
|
|
445
|
+
const result = await run(process.execPath, args, target);
|
|
446
|
+
return JSON.parse(result.stdout);
|
|
447
|
+
}
|
|
448
|
+
|
|
268
449
|
async function writeMinimalLockfile(target, name, versionRange) {
|
|
269
450
|
const lock = {
|
|
270
451
|
name,
|
|
271
|
-
version: "0.3.
|
|
452
|
+
version: "0.3.1",
|
|
272
453
|
lockfileVersion: 3,
|
|
273
454
|
requires: true,
|
|
274
455
|
packages: {
|
|
275
456
|
"": {
|
|
276
457
|
name,
|
|
277
|
-
version: "0.3.
|
|
458
|
+
version: "0.3.1",
|
|
278
459
|
dependencies: { filegrc: versionRange }
|
|
279
460
|
}
|
|
280
461
|
}
|
package/template/AGENTS.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
#
|
|
1
|
+
# {{agent_title}}
|
|
2
2
|
|
|
3
3
|
## Purpose
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
{{agent_purpose}}
|
|
6
6
|
|
|
7
7
|
Using this repository does not establish compliance by itself. Records must match actual practice and evidence must prove that controls operated during the audit period.
|
|
8
8
|
|
|
@@ -95,25 +95,11 @@ Headless agents get the same protection by exporting an edit payload with `fileg
|
|
|
95
95
|
|
|
96
96
|
`data/renderer.json` stores committed renderer preferences. New workspaces set `showOnboarding` to `true`. Completing or skipping onboarding sets it to `false`; the app does not commit that change.
|
|
97
97
|
|
|
98
|
-
Onboarding explains the file and Git workflow, the program path, policy obligations, and Policy Events before covering report types and the final audit stage. It then collects the initial service boundary, owner, business criticality, highest data classification, internet exposure, and optional program goal. It creates or updates
|
|
98
|
+
Onboarding explains the file and Git workflow, the program path, policy obligations, and Policy Events before covering report types and the final audit stage. It then collects the initial service boundary, owner, business criticality, highest data classification, internet exposure, and optional program goal. It creates or updates one `system` record and stores that selected system and the management goal on `workspace`. It does not select framework records, link controls to the service, or create evidence. Selecting Type 1 or Type 2 does not create an audit engagement. Completing onboarding opens the Step 1 overview so the user can add the real reviewers and operators, finish the oversight team, and confirm the criteria, commitments, vendors, and systems before approving policies.
|
|
99
99
|
|
|
100
100
|
The renderer is optional. Agents may set `showOnboarding` to `false` and maintain all records headlessly. Restart onboarding from Repository when useful. Read-only builds never run it.
|
|
101
101
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
The generated workspace starts with the SOC 2 Security category:
|
|
105
|
-
|
|
106
|
-
- Active framework records for the 2017 Trust Services Criteria with revised points of focus (2022) and the 2018 SOC 2 Description Criteria with revised implementation guidance (2022)
|
|
107
|
-
- The 33 Common Criteria reference IDs from CC1.1 through CC9.2, without the licensed criteria text
|
|
108
|
-
- The nine Description Criteria reference IDs from DC1 through DC9, without the licensed criteria text
|
|
109
|
-
- Planned controls mapped to those references and the included policies
|
|
110
|
-
- A security and risk oversight team chaired by an independent reviewer who may be internal or external
|
|
111
|
-
- Recurring obligations for the reviews, scans, tests, training, and meetings required by the included policies
|
|
112
|
-
- A default 5x5 risk method and Public, Internal, Confidential, and Restricted data classifications
|
|
113
|
-
|
|
114
|
-
Treat every planned control as a proposal until its owner, actual procedure in Record Markdown, system scope, cadence, authoritative evidence sources, implementation date, and mappings match actual practice. For a control linked to filegrc obligations, every non-retired Work Queue schedule must be enabled and its governing policies effective. Marking the control implemented starts eligible schedules. Do not mark a control implemented because a policy describes it. Add Availability, Processing Integrity, Confidentiality, or Privacy criteria only when they are in scope.
|
|
115
|
-
|
|
116
|
-
The recurring obligations mirror the fixed cadences in the starter policies. They remain proposals until every governing policy is active and effective and, when they name controls, at least one linked control is implemented. Update the policy, control, and obligation together when an approved cadence changes. Create separate completion records, such as meetings, reviews, scans, tests, exercises, and attestations, for each period.
|
|
102
|
+
{{starter_baseline}}
|
|
117
103
|
|
|
118
104
|
## Work Queue and Policy Events
|
|
119
105
|
|
|
@@ -180,7 +166,7 @@ The Evidence Ready gate requires:
|
|
|
180
166
|
4. Active authoritative systems with evidence source roles, access owners, and repeatable extraction instructions in Record Markdown.
|
|
181
167
|
5. A verified `test-export` or `test-capture` evidence record for each selected control family that relies on evidence from outside filegrc.
|
|
182
168
|
|
|
183
|
-
|
|
169
|
+
Onboarding does not create External Evidence records. After confirming the applicable controls and authoritative source Systems, run `npx filegrc evidence-test-drafts --preview --json` and review the proposed collection tests. Then run `npx filegrc evidence-test-drafts` to create the missing drafts. filegrc-managed records, such as risk assessments, meetings, vendor reviews, attestations, vulnerability scans, penetration tests, backup tests, exercises, exceptions, and findings, do not need a separate collection test. Put any fixed external artifact in an External Evidence record and link it from the operating record. For each created draft, choose its authoritative source System, attach or reference the real result, record its collector and classification, then have another person verify it.
|
|
184
170
|
|
|
185
171
|
When the gate passes, set `workspace.candidatePeriodStart` to the date reliable evidence collection begins. Do not backdate it. `candidatePeriodStart` and `candidatePeriodEnd` express management’s target. They do not establish the final report period.
|
|
186
172
|
|
|
@@ -200,7 +186,7 @@ npx filegrc audit-readiness audit-2026-type-2 --require-ready --json
|
|
|
200
186
|
|
|
201
187
|
The audit record’s `typeOneAsOf`, `periodStart`, and `periodEnd` are the dates agreed with the CPA firm. Keep the workspace candidate dates even when the formal period differs.
|
|
202
188
|
|
|
203
|
-
|
|
189
|
+
{{audit_preparation_guidance}}
|
|
204
190
|
|
|
205
191
|
Review both evidence paths against the exact firm-agreed date or period:
|
|
206
192
|
|
package/template/README.md
CHANGED
|
@@ -34,6 +34,8 @@ npm run validate
|
|
|
34
34
|
npm run serve
|
|
35
35
|
```
|
|
36
36
|
|
|
37
|
+
The default `security` starter builds the full proposed SOC 2 Security program. Use `--starter foundation` to create only the five structural records when you want to select a framework and program content later. Company and service values can also be supplied together through `create-filegrc --config setup.json`.
|
|
38
|
+
|
|
37
39
|
Setup asks for the legal organization name, the initial policy owner and their email, a security reporting address, and the program timezone. It initializes Git when needed. The first local run then defines the initial service boundary and an optional program goal. A Type 2 choice records management intent, not an audit engagement. Completing onboarding opens Step 1 so you can add the real reviewers and operators, finish the oversight team, and confirm the criteria, commitments, vendors, and systems before moving on.
|
|
38
40
|
|
|
39
41
|
Open the printed local URL. You can commit locally from Repository without configuring a remote. Add a remote when the team is ready to share the workspace, then the browser can pull with rebase and push reviewed commits.
|
|
@@ -45,7 +47,7 @@ The creation summary reports the resolved engine version, program timezone, star
|
|
|
45
47
|
1. Confirm the program’s people and oversight team, applicable criteria, commitments, material vendors, and in-scope systems.
|
|
46
48
|
2. Review and activate the policies with a separate management reviewer, who is usually internal and may be external.
|
|
47
49
|
3. Tailor the starter controls, add each owner, actual procedure, scope, cadence, evidence source, and implementation date, and confirm any linked Work Queue schedules are enabled. Marking a control implemented starts eligible schedules. Then record any complementary customer or subservice controls.
|
|
48
|
-
4.
|
|
50
|
+
4. After confirming the applicable controls and authoritative source Systems, preview the proposed External Evidence drafts with `npx filegrc evidence-test-drafts --preview --json`. Create only the relevant drafts, collect each named artifact, and have another person verify it.
|
|
49
51
|
5. Start the management candidate period, maintain risk assessments and risks, update controls when needed, work the filegrc queue, and preserve dated evidence.
|
|
50
52
|
6. Engage a CPA firm, record the separate firm-agreed period, review filegrc Evidence and External Evidence, prepare fieldwork, and generate the evidence packet.
|
|
51
53
|
|
package/template/WORKSPACE.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
# {{
|
|
1
|
+
# {{program_title}}
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
{{program_summary}}
|
|
4
4
|
|
|
5
|
-
The workspace uses filegrc {{filegrc_version}} through the dependency
|
|
5
|
+
The workspace uses filegrc {{filegrc_version}} through the dependency spec `{{filegrc_version_range}}`.
|
|
6
6
|
|
|
7
7
|
## Work locally
|
|
8
8
|
|
|
@@ -27,15 +27,6 @@ npx filegrc validate --json
|
|
|
27
27
|
|
|
28
28
|
Read `AGENTS.md` and `data/AGENTS.md` before broad changes.
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
The starter policies, controls, and obligations are proposals. They do not state that {{company_name}} operates the described controls.
|
|
33
|
-
|
|
34
|
-
1. Run `npx filegrc setup` for guided service and goal setup, or use browser onboarding. Then finish Step 1 by adding the real reviewers and operators, finishing the oversight team, and confirming applicable criteria, commitments, material vendors, and in-scope systems.
|
|
35
|
-
2. Review the starter policies, appoint a reviewer who is separate from the policy owner, and activate only the policies that match current practice. The reviewer will usually be another person in the organization, but may be external.
|
|
36
|
-
3. Review the starter control set, implement each applicable control with its actual procedure, scope, cadence, evidence sources, and implementation date, and confirm any linked Work Queue schedules are enabled. Marking a control implemented starts eligible schedules. Then record any complementary customer or subservice controls.
|
|
37
|
-
4. Open each generated External Evidence draft, choose its authoritative source System, collect the named artifact, and have another person verify it.
|
|
38
|
-
5. Run `npx filegrc program-readiness --require-ready`, record the management candidate period start when reliable evidence collection begins, maintain risk assessments and risks, update controls when needed, use Work Queue for scheduled work, and trigger Policy Events when changes create required actions. `npx filegrc obligations` previews every event task, owner, deadline, and requested proof before the trigger creates anything.
|
|
39
|
-
6. Engage a CPA firm, record the separate firm-agreed period in an audit record, review filegrc Evidence and External Evidence, and prepare fieldwork.
|
|
30
|
+
{{starter_setup}}
|
|
40
31
|
|
|
41
32
|
filegrc manages GRC records and audit evidence. It does not replace infrastructure logging, monitoring, identity, backup, endpoint, or incident-detection systems.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
An evidence record explains what a proof item is, where it came from, what period it supports, who collected it, and which records or controls it supports. The attachment alone is not enough.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Onboarding does not create collection tests. After confirming applicable controls and authoritative source Systems, preview proposed tests with `npx filegrc evidence-test-drafts --preview --json`, then explicitly create the missing drafts. Risk assessments, meetings, vendor reviews, attestations, vulnerability scans, penetration tests, backup tests, exercises, exceptions, and findings do not need a separate test. When one of those operating records needs a fixed external artifact, create or update an External Evidence record for the artifact and link its ID from the operating record. Keep a created collection test as `draft` until the artifact has actually been captured. Set it to `collected` only after selecting the source System, attaching or referencing the result, and recording the source, date, classification, and collector. Set it to `verified` only after another named person checks it.
|
|
6
6
|
|
|
7
7
|
## Create evidence
|
|
8
8
|
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
"dataModelVersion": "1",
|
|
4
4
|
"id": "workspace",
|
|
5
5
|
"type": "workspace",
|
|
6
|
-
"title": "{{
|
|
6
|
+
"title": "{{program_title}}",
|
|
7
7
|
"organizationName": "{{company_name}}",
|
|
8
8
|
"timezone": "{{timezone}}",
|
|
9
|
-
"description": "
|
|
9
|
+
"description": "{{program_description}}",
|
|
10
10
|
"riskMethodology": {
|
|
11
11
|
"method": "5x5 likelihood and impact",
|
|
12
12
|
"likelihoodScale": ["Rare", "Unlikely", "Possible", "Likely", "Almost certain"],
|
package/template/package.json
CHANGED
package/template-parameters.json
CHANGED
|
@@ -45,6 +45,38 @@
|
|
|
45
45
|
{
|
|
46
46
|
"key": "filegrc_version_range",
|
|
47
47
|
"source": "resolved-filegrc-version"
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"key": "program_title",
|
|
51
|
+
"source": "starter-profile"
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"key": "program_description",
|
|
55
|
+
"source": "starter-profile"
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"key": "program_summary",
|
|
59
|
+
"source": "starter-profile"
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"key": "starter_setup",
|
|
63
|
+
"source": "starter-profile"
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"key": "agent_title",
|
|
67
|
+
"source": "starter-profile"
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"key": "agent_purpose",
|
|
71
|
+
"source": "starter-profile"
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"key": "starter_baseline",
|
|
75
|
+
"source": "starter-profile"
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"key": "audit_preparation_guidance",
|
|
79
|
+
"source": "starter-profile"
|
|
48
80
|
}
|
|
49
81
|
]
|
|
50
82
|
}
|