filegrc 0.3.4 → 0.5.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 +24 -6
- package/model/index.js +41 -3
- package/model/v1.json +81 -47
- package/model/v2.json +8022 -0
- package/model/v3.json +9391 -0
- package/package.json +2 -2
- package/src/agent.js +89 -8
- package/src/appointments.js +19 -0
- package/src/audit-preparation.js +109 -65
- package/src/audit-transition.js +96 -0
- package/src/batch-review.js +109 -0
- package/src/cli.js +563 -148
- package/src/collection-review.js +185 -0
- package/src/coverage.js +50 -0
- package/src/evidence-packet.js +149 -77
- package/src/external-reviewer.js +165 -0
- package/src/files.js +267 -29
- package/src/git.js +239 -41
- package/src/index.js +41 -7
- package/src/model-docs.js +103 -7
- package/src/model-migration.js +1958 -0
- package/src/mutation.js +42 -0
- package/src/obligations.js +502 -95
- package/src/parties.js +17 -2
- package/src/program-lifecycle.js +1 -0
- package/src/program-path.js +70 -60
- package/src/program-readiness.js +470 -130
- package/src/reconciliation.js +277 -0
- package/src/resource-status.js +17 -0
- package/src/server.js +347 -48
- package/src/setup.js +57 -26
- package/src/source-coverage.js +61 -0
- package/src/state.js +122 -25
- package/src/timing.js +41 -0
- package/src/validate.js +707 -44
- package/src/web.js +1440 -304
- package/src/workflow.js +1595 -0
- package/src/workspace.js +15 -7
- package/src/evidence-tests.js +0 -69
package/src/model-docs.js
CHANGED
|
@@ -15,7 +15,7 @@ export function generateModelDocumentation(model) {
|
|
|
15
15
|
"",
|
|
16
16
|
"## Program path",
|
|
17
17
|
"",
|
|
18
|
-
"The renderer, CLI, generated agent instructions, and this reference use the same
|
|
18
|
+
"The renderer, CLI, generated agent instructions, and this reference use the same five-step lifecycle:",
|
|
19
19
|
"",
|
|
20
20
|
...programPath.flatMap((stage) => [
|
|
21
21
|
`### Step ${stage.number}. ${stage.title}`,
|
|
@@ -35,6 +35,62 @@ export function generateModelDocumentation(model) {
|
|
|
35
35
|
...stage.commands.map((command) => `- \`${command}\``),
|
|
36
36
|
""
|
|
37
37
|
]),
|
|
38
|
+
"## Relation groups",
|
|
39
|
+
"",
|
|
40
|
+
"Relationship fields use the named groups below. The registry expands each group to explicit resource types, so no relationship accepts an unrestricted wildcard.",
|
|
41
|
+
"",
|
|
42
|
+
"| Group | Resource types |",
|
|
43
|
+
"| --- | --- |",
|
|
44
|
+
...Object.entries(model.relationGroups || {}).map(([name, types]) => (
|
|
45
|
+
`| \`${name}\` | ${types.map((type) => `\`${type}\``).join(", ")} |`
|
|
46
|
+
)),
|
|
47
|
+
"",
|
|
48
|
+
"## Collection review confirmations",
|
|
49
|
+
"",
|
|
50
|
+
"FileGRC derives record issues, but it cannot infer that management reviewed an apparently complete or empty collection. Each configured collection review records the conclusion, reviewer, date, current scope revision, and exact collection revision. A record or material scope change makes the confirmation stale.",
|
|
51
|
+
"",
|
|
52
|
+
"| Resource type | Review | Allowed conclusions | What to review |",
|
|
53
|
+
"| --- | --- | --- | --- |",
|
|
54
|
+
...Object.entries(model.collectionReviews || {}).map(([type, review]) => (
|
|
55
|
+
`| \`${type}\` | ${escapeCell(review.title)} | ${review.decisions.map((decision) => `\`${decision}\``).join(", ")} | ${escapeCell(review.reviewPoints.join(" "))} |`
|
|
56
|
+
)),
|
|
57
|
+
"",
|
|
58
|
+
"## Relationship constraints",
|
|
59
|
+
"",
|
|
60
|
+
"Relationship constraints prevent cycles and duplicate active authority or access records.",
|
|
61
|
+
"",
|
|
62
|
+
...((model.relationshipConstraints?.acyclic || []).map((constraint) => (
|
|
63
|
+
`- \`${constraint.resourceType}.${constraint.field}\` must be acyclic.`
|
|
64
|
+
))),
|
|
65
|
+
...((model.relationshipConstraints?.unique || []).map((constraint) => (
|
|
66
|
+
`- \`${constraint.resourceType}\` records in ${constraint.statuses.map((status) => `\`${status}\``).join(", ")} must be unique by ${constraint.fields.map((field) => `\`${field}\``).join(", ")}.`
|
|
67
|
+
))),
|
|
68
|
+
"",
|
|
69
|
+
"## Obligation activities",
|
|
70
|
+
"",
|
|
71
|
+
"The model owns each activity name, allowed recurrence modes and scope types, its default completion record, and every record type that can prove completion.",
|
|
72
|
+
"",
|
|
73
|
+
"| Activity | Title | Recurrence | Scope resource types | Default completion | Accepted completion records |",
|
|
74
|
+
"| --- | --- | --- | --- | --- | --- |",
|
|
75
|
+
...Object.entries(model.obligationActivities || {}).map(([name, activity]) => (
|
|
76
|
+
`| \`${name}\` | ${escapeCell(activity.title)} | ${activity.recurrenceModes.map((mode) => `\`${mode}\``).join(", ")} | ${activity.scopeResourceTypes.map((type) => `\`${type}\``).join(", ")} | \`${activity.completionType}\` | ${activity.completionResourceTypes.map((type) => `\`${type}\``).join(", ")} |`
|
|
77
|
+
)),
|
|
78
|
+
"",
|
|
79
|
+
"## Policy events",
|
|
80
|
+
"",
|
|
81
|
+
"The model owns each event title and the minimum and maximum count for each subject resource type.",
|
|
82
|
+
"",
|
|
83
|
+
"| Event | Title | Subject rules |",
|
|
84
|
+
"| --- | --- | --- |",
|
|
85
|
+
...Object.entries(model.policyEvents || {}).map(([name, event]) => (
|
|
86
|
+
`| \`${name}\` | ${escapeCell(event.title)} | ${event.subjectRules.map(({ resourceType, minimum = 0, maximum }) => `\`${resourceType}\` ${minimum}..${Number.isInteger(maximum) ? maximum : "*"}`).join(", ")} |`
|
|
87
|
+
)),
|
|
88
|
+
"",
|
|
89
|
+
"## Nested object schemas",
|
|
90
|
+
"",
|
|
91
|
+
"Named object schemas reject unknown keys unless the schema explicitly allows a typed map or arbitrary JSON. Conditional properties are valid only for the selected discriminator.",
|
|
92
|
+
"",
|
|
93
|
+
...Object.entries(model.objectTypes || {}).flatMap(([name, schema]) => objectTypeDocumentation(name, schema)),
|
|
38
94
|
"## Common fields",
|
|
39
95
|
"",
|
|
40
96
|
"| Field | Type | Required | Meaning |",
|
|
@@ -53,7 +109,7 @@ export function generateModelDocumentation(model) {
|
|
|
53
109
|
"",
|
|
54
110
|
"## Program and audit readiness defaults",
|
|
55
111
|
"",
|
|
56
|
-
"Program Readiness checks management scope, policy adoption, control implementation,
|
|
112
|
+
"Program Readiness checks management scope, policy adoption, control implementation, and authoritative evidence mapping without requiring an audit record. Audit Readiness starts after a CPA firm is engaged and uses the defaults below to prepare Type 1 and Type 2 fieldwork.",
|
|
57
113
|
"",
|
|
58
114
|
"Management documents:",
|
|
59
115
|
"",
|
|
@@ -66,9 +122,9 @@ export function generateModelDocumentation(model) {
|
|
|
66
122
|
"Authoritative systems of record:",
|
|
67
123
|
"",
|
|
68
124
|
...model.evidenceSourceFamilies.map((item) => (
|
|
69
|
-
item.
|
|
70
|
-
? `- **${item.title}** (${item.sourceKinds.map((kind) => `\`${kind}\``).join(", ")}): ${item.description}
|
|
71
|
-
: `- **${item.title}** (${item.sourceKinds.map((kind) => `\`${kind}\``).join(", ")}): ${item.description}
|
|
125
|
+
item.filegrcManaged === true
|
|
126
|
+
? `- **${item.title}** (${item.sourceKinds.map((kind) => `\`${kind}\``).join(", ")}): ${item.description} FileGRC operating records: ${item.operationRecordTypes.map((type) => `\`${type}\``).join(", ")}. Expected evidence: ${item.evidencePrompt} ${item.timing}`
|
|
127
|
+
: `- **${item.title}** (${item.sourceKinds.map((kind) => `\`${kind}\``).join(", ")}): ${item.description} Expected evidence: ${item.evidencePrompt} ${item.timing}`
|
|
72
128
|
)),
|
|
73
129
|
"",
|
|
74
130
|
"## Resource groups",
|
|
@@ -84,6 +140,11 @@ export function generateModelDocumentation(model) {
|
|
|
84
140
|
lines.push(`Instructions: ${RESOURCE_INSTRUCTIONS[type] || resource.description}`, "");
|
|
85
141
|
lines.push(`Policy basis: ${resource.guidance.policyBasis}`, "");
|
|
86
142
|
lines.push(`Timing: ${resource.guidance.cadence}`, "");
|
|
143
|
+
if (resource.guidance.reviewPoints?.length) {
|
|
144
|
+
lines.push("When reviewing:", "");
|
|
145
|
+
for (const point of resource.guidance.reviewPoints) lines.push(`- ${point}`);
|
|
146
|
+
lines.push("");
|
|
147
|
+
}
|
|
87
148
|
if (resource.guidance.sourceResourceIds?.length) {
|
|
88
149
|
lines.push(`Default sources: ${resource.guidance.sourceResourceIds.map((id) => `\`${id}\``).join(", ")}`, "");
|
|
89
150
|
}
|
|
@@ -143,7 +204,12 @@ function choiceLabel(name) {
|
|
|
143
204
|
}
|
|
144
205
|
|
|
145
206
|
function fieldType(field) {
|
|
146
|
-
if (field.type === "array")
|
|
207
|
+
if (field.type === "array") {
|
|
208
|
+
return field.itemObjectType
|
|
209
|
+
? `array of object (\`${field.itemObjectType}\`)`
|
|
210
|
+
: `array of ${field.items ?? "values"}`;
|
|
211
|
+
}
|
|
212
|
+
if (field.type === "object" && field.objectType) return `object (\`${field.objectType}\`)`;
|
|
147
213
|
return field.format && field.format !== field.type ? `${field.type} (${field.format})` : field.type;
|
|
148
214
|
}
|
|
149
215
|
|
|
@@ -151,14 +217,44 @@ function fieldNotes(field) {
|
|
|
151
217
|
return [
|
|
152
218
|
field.label,
|
|
153
219
|
field.values ? `Values: ${field.values.map((item) => `\`${item}\``).join(", ")}` : "",
|
|
220
|
+
field.registry ? `Values come from the \`${field.registry}\` registry.` : "",
|
|
221
|
+
field.relationGroup ? `Relation group: \`${field.relationGroup}\`.` : "",
|
|
154
222
|
field.relation ? `References: ${field.relation.map((item) => `\`${item}\``).join(", ")}` : "",
|
|
155
223
|
field.minimum !== undefined ? `Minimum: \`${field.minimum}\`.` : "",
|
|
156
224
|
field.maximum !== undefined ? `Maximum: \`${field.maximum}\`.` : "",
|
|
225
|
+
field.managed ? "Managed by filegrc." : "",
|
|
157
226
|
field.disjointFrom ? `Must not overlap \`${field.disjointFrom}\`.` : "",
|
|
158
|
-
field.requiredWhen ? `Required when ${
|
|
227
|
+
field.requiredWhen ? `Required when ${conditionText(field.requiredWhen)}.` : "",
|
|
228
|
+
field.allowedWhen ? `Allowed when ${conditionText(field.allowedWhen)}.` : ""
|
|
159
229
|
].filter(Boolean).join(" ");
|
|
160
230
|
}
|
|
161
231
|
|
|
232
|
+
function objectTypeDocumentation(name, schema) {
|
|
233
|
+
const lines = [`### \`${name}\``, ""];
|
|
234
|
+
const properties = Object.entries(schema.properties || {});
|
|
235
|
+
if (properties.length) {
|
|
236
|
+
const required = new Set(schema.required || []);
|
|
237
|
+
lines.push("| Property | Type | Required | Notes |", "| --- | --- | --- | --- |");
|
|
238
|
+
for (const [propertyName, property] of properties) {
|
|
239
|
+
const requiredLabel = required.has(propertyName)
|
|
240
|
+
? "Yes"
|
|
241
|
+
: property.requiredWhen
|
|
242
|
+
? "Conditional"
|
|
243
|
+
: "No";
|
|
244
|
+
lines.push(`| \`${propertyName}\` | ${fieldType(property)} | ${requiredLabel} | ${escapeCell(fieldNotes(property))} |`);
|
|
245
|
+
}
|
|
246
|
+
} else if (schema.additionalProperties === true) {
|
|
247
|
+
lines.push("Allows arbitrary JSON properties.");
|
|
248
|
+
} else if (schema.additionalProperties) {
|
|
249
|
+
lines.push(`Allows dynamic keys whose values are ${fieldType(schema.additionalProperties)}.`);
|
|
250
|
+
} else {
|
|
251
|
+
lines.push("Does not allow properties.");
|
|
252
|
+
}
|
|
253
|
+
if (schema.keyFormat) lines.push("", `Key format: \`${schema.keyFormat}\`.`);
|
|
254
|
+
lines.push("");
|
|
255
|
+
return lines;
|
|
256
|
+
}
|
|
257
|
+
|
|
162
258
|
function escapeCell(value) {
|
|
163
259
|
return String(value).replaceAll("|", "\\|").replaceAll("\n", " ");
|
|
164
260
|
}
|