filegrc 0.9.1 → 0.9.2

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "filegrc",
3
- "version": "0.9.1",
3
+ "version": "0.9.2",
4
4
  "description": "Zero-dependency Git-native GRC engine",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -1,6 +1,7 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { readFileSync } from "node:fs";
3
3
  import {
4
+ authoritativeSourceRevisionValue,
4
5
  collectionRevisionInputs,
5
6
  collectionScopeRevisionFacts
6
7
  } from "./collection-scope.js";
@@ -16,15 +17,19 @@ export function collectionRevision(loaded, resourceType, options = {}) {
16
17
  .map((input) => [input.record.id, input]));
17
18
  const authoritativeSource = loaded.resources.find(({ id }) => id === options.authoritativeSourceId);
18
19
  if (authoritativeSource) {
19
- inputs.set(authoritativeSource.id, { record: authoritativeSource, value: authoritativeSource });
20
+ inputs.set(authoritativeSource.id, {
21
+ record: authoritativeSource,
22
+ value: authoritativeSourceRevisionValue(authoritativeSource),
23
+ includeContent: true
24
+ });
20
25
  }
21
26
  const records = [...inputs.values()]
22
- .map(({ record, value }) => ({
27
+ .map(({ record, value, includeContent }) => ({
23
28
  id: record.id,
24
29
  revision: createHash("sha256")
25
30
  .update(JSON.stringify(canonicalRecordValue(loaded.model, record.type, value)))
26
31
  .digest("hex"),
27
- contentRevisions: markdownEntries(loaded.model, record).flatMap(({ path }) => {
32
+ contentRevisions: (includeContent ? markdownEntries(loaded.model, record) : []).flatMap(({ path }) => {
28
33
  try {
29
34
  const content = readFileSync(resolveDataPath(loaded.root, path), "utf8");
30
35
  return [{ path, revision: createHash("sha256").update(content).digest("hex") }];
@@ -39,7 +39,7 @@ export function scopedCollectionRecords(loaded, resourceType, program) {
39
39
  export function collectionRevisionInputs(loaded, resourceType, program) {
40
40
  const reviewed = scopedCollectionRecords(loaded, resourceType, program);
41
41
  if (!modelSupports(loaded.model, "program-scope")) {
42
- return reviewed.map((record) => ({ record, value: record }));
42
+ return reviewed.map((record) => ({ record, value: record, includeContent: true }));
43
43
  }
44
44
  const byId = new Map(loaded.resources.map((record) => [record.id, record]));
45
45
  const records = new Map(reviewed.map((record) => [record.id, record]));
@@ -90,7 +90,9 @@ export function collectionRevisionInputs(loaded, resourceType, program) {
90
90
  record,
91
91
  value: reviewedIds.has(record.id)
92
92
  ? record
93
- : dependencyRevisionValue(resourceType, record)
93
+ : dependencyRevisionValue(resourceType, record),
94
+ includeContent: reviewedIds.has(record.id)
95
+ || dependencyContentAffectsRevision(resourceType, record.type)
94
96
  }));
95
97
  }
96
98
 
@@ -138,9 +140,20 @@ export function collectionScopeRevisionFacts(loaded, resourceType, program) {
138
140
  return common;
139
141
  }
140
142
 
143
+ export function authoritativeSourceRevisionValue(record) {
144
+ return Object.fromEntries(Object.entries(record).filter(([field]) => (
145
+ !authoritativeSourceBookkeepingFields.has(field)
146
+ )));
147
+ }
148
+
149
+ const authoritativeSourceBookkeepingFields = new Set([
150
+ "tags",
151
+ "statusTransition"
152
+ ]);
153
+
141
154
  const dependencyFields = {
142
155
  framework: {
143
- system: ["id", "type", "status", "purpose", "servicesProvided", "boundary", "exclusions", "informationTypeIds", "classificationId", "internetExposed"],
156
+ system: ["id", "type", "status", "purpose", "servicesProvided", "boundary", "exclusions", "criticality", "informationTypeIds", "classificationId", "internetExposed", "continuityObjectives"],
144
157
  requirement: ["id", "type", "title", "frameworkId", "reference", "description", "parentRequirementId"]
145
158
  },
146
159
  vendor: {
@@ -154,22 +167,29 @@ const dependencyFields = {
154
167
  },
155
168
  component: {
156
169
  system: ["id", "type", "status", "purpose", "servicesProvided", "boundary", "exclusions", "criticality", "informationTypeIds", "classificationId", "internetExposed", "continuityObjectives"],
157
- control: ["id", "type", "status", "statement", "activity", "controlType", "operationMode", "operationPattern", "systemIds", "componentIds", "evidenceSourceComponentIds"],
158
- vendor: ["id", "type", "status", "category", "criticality", "description", "standardAgreement", "agreementDocumentId", "startDate", "endDate", "classificationId", "informationTypeIds"],
170
+ control: ["id", "type", "status", "systemIds", "componentIds", "evidenceSourceComponentIds"],
171
+ vendor: ["id", "type", "status", "category", "criticality", "description", "startDate", "endDate"],
159
172
  classification: ["id", "type", "status", "rank", "description", "handlingRequirements"],
160
173
  "information-type": ["id", "type", "status", "classificationId", "description"]
161
174
  },
162
175
  "complementary-control": {
163
- system: ["id", "type", "status", "purpose", "servicesProvided", "boundary", "exclusions", "informationTypeIds", "classificationId", "internetExposed"],
164
- control: ["id", "type", "status", "statement", "activity", "controlType", "operationMode", "operationPattern", "systemIds", "componentIds", "evidenceSourceComponentIds"],
176
+ system: ["id", "type", "status", "purpose", "servicesProvided", "boundary", "exclusions", "criticality", "informationTypeIds", "classificationId", "internetExposed", "continuityObjectives"],
177
+ control: ["id", "type", "status", "statement", "activity", "systemIds", "componentIds", "evidenceSourceComponentIds"],
165
178
  vendor: ["id", "type", "status", "category", "criticality", "description", "standardAgreement", "agreementDocumentId", "startDate", "endDate", "classificationId", "informationTypeIds"],
166
179
  requirement: ["id", "type", "title", "frameworkId", "reference", "description", "parentRequirementId"],
167
180
  commitment: ["id", "type", "status", "commitmentKind", "statement", "systemIds", "requirementIds", "controlIds", "customerFacing", "effectiveOn"],
168
181
  document: ["id", "type", "status", "documentKind", "version", "effectiveOn", "approvedOn", "approvedContentRevisions", "systemIds", "controlIds", "componentIds", "classificationId"],
169
- component: ["id", "type", "status", "componentKind", "description", "vendorId", "systemUses", "informationUses", "internetExposed"]
182
+ component: ["id", "type", "status", "componentKind", "description", "criticality", "vendorId", "systemUses", "informationUses", "internetExposed", "classificationId", "continuityObjectives"]
170
183
  }
171
184
  };
172
185
 
186
+ const dependencyContentTypes = {
187
+ framework: new Set(["system"]),
188
+ vendor: new Set(["document"]),
189
+ component: new Set(["system"]),
190
+ "complementary-control": new Set(["system", "control", "document", "component"])
191
+ };
192
+
173
193
  function dependencyRevisionValue(resourceType, record) {
174
194
  const fields = dependencyFields[resourceType]?.[record.type];
175
195
  if (!fields) return { id: record.id, type: record.type };
@@ -178,6 +198,10 @@ function dependencyRevisionValue(resourceType, record) {
178
198
  .map((field) => [field, record[field]]));
179
199
  }
180
200
 
201
+ function dependencyContentAffectsRevision(resourceType, dependencyType) {
202
+ return dependencyContentTypes[resourceType]?.has(dependencyType) || false;
203
+ }
204
+
181
205
  function sorted(values) {
182
206
  return [...(values || [])].sort();
183
207
  }