filegrc 0.16.9 → 0.16.10

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.16.9",
3
+ "version": "0.16.10",
4
4
  "description": "Zero-dependency Git-native GRC engine",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -117,13 +117,16 @@ export function applicabilityScopeRevision(record, program, resources, model, op
117
117
  policies: resources
118
118
  .filter(({ type, id }) => type === "policy" && selectedPolicyIds.has(id))
119
119
  .sort(compareRecordIds)
120
- .map((policy) => options.legacyPolicyStatus
120
+ .map((policy) => options.legacyPolicyStatus || options.legacyPolicyOverrides
121
121
  ? pick({
122
122
  ...policy,
123
- status: options.legacyPolicyStatus === "approved" && ["in-review", "active"].includes(policy.status)
124
- ? "approved" : policy.status,
125
- effectiveOn: options.legacyPolicyStatus === "approved" && policy.status === "active"
126
- ? undefined : policy.effectiveOn
123
+ status: (options.legacyPolicyOverrides?.[policy.id]?.status || options.legacyPolicyStatus || "current") === "current"
124
+ ? policy.status : options.legacyPolicyOverrides?.[policy.id]?.status || options.legacyPolicyStatus,
125
+ effectiveOn: (options.legacyPolicyOverrides?.[policy.id]?.withoutEffectiveOn
126
+ ?? options.legacyPolicyWithoutEffectiveOn) ? undefined : policy.effectiveOn,
127
+ approvedContentRevisions: (options.legacyPolicyOverrides?.[policy.id]?.withoutApprovalBinding
128
+ ?? options.legacyPolicyWithoutApprovalBinding)
129
+ ? undefined : policy.approvedContentRevisions
127
130
  }, [
128
131
  "id", "status", "policyKind", "version", "programRole", "effectiveOn",
129
132
  "requirementIds", "audience", "acknowledgementRequired",
@@ -168,11 +171,50 @@ export function applicabilityReviewIsCurrent(review, record, program, resources,
168
171
  if (!review?.scopeRevision) return false;
169
172
  const matches = (revision) => revisionsMatch("applicability-scope", review.scopeRevision, revision);
170
173
  if (matches(applicabilityScopeRevision(record, program, resources, model))) return true;
171
- // Reviews written before the scope fix included Policy lifecycle fields. Accept
172
- // that earlier fingerprint only when its material inputs still match.
173
- if (matches(applicabilityScopeRevision(record, program, resources, model, { legacyPolicyStatus: "current" }))) return true;
174
- return resources.some(({ type, status }) => type === "policy" && ["in-review", "active"].includes(status))
175
- && matches(applicabilityScopeRevision(record, program, resources, model, { legacyPolicyStatus: "approved" }));
174
+ // Older reviews hashed Policy approval metadata alongside scope facts.
175
+ // Reconstruct each Policy independently because other Policies may already
176
+ // have been approved when one Policy changes. Material fields stay current.
177
+ for (const legacyPolicyStatus of ["current", "draft", "in-review", "approved", "active"]) {
178
+ for (const legacyPolicyWithoutApprovalBinding of [false, true]) {
179
+ for (const legacyPolicyWithoutEffectiveOn of [false, true]) {
180
+ if (matches(applicabilityScopeRevision(record, program, resources, model, {
181
+ legacyPolicyStatus,
182
+ legacyPolicyWithoutApprovalBinding,
183
+ legacyPolicyWithoutEffectiveOn
184
+ }))) return true;
185
+ }
186
+ }
187
+ }
188
+ const selectedControlIds = new Set(program?.controlIds || []);
189
+ const selectedPolicyIds = new Set(resources
190
+ .filter(({ type, id }) => type === "control" && selectedControlIds.has(id))
191
+ .flatMap(({ policyIds }) => policyIds || []));
192
+ for (const id of record.policyIds || []) selectedPolicyIds.add(id);
193
+ const policies = resources.filter(({ type, id }) => type === "policy" && selectedPolicyIds.has(id));
194
+ // One Policy may advance while the others retain different approval states.
195
+ // Reconstruct that Policy's former lifecycle fields without changing any
196
+ // material scope fact or searching combinations of unrelated Policies.
197
+ for (const policy of policies) {
198
+ for (const value of legacyPolicyAlternatives(policy)) {
199
+ if (matches(applicabilityScopeRevision(record, program, resources, model, {
200
+ legacyPolicyOverrides: { [policy.id]: value }
201
+ }))) return true;
202
+ }
203
+ }
204
+ return false;
205
+ }
206
+
207
+ function legacyPolicyAlternatives(policy) {
208
+ const prior = {
209
+ active: ["approved", "in-review", "draft"],
210
+ approved: ["in-review", "draft"],
211
+ "in-review": ["draft"]
212
+ }[policy.status] || [];
213
+ return prior.map((status) => ({
214
+ status,
215
+ withoutApprovalBinding: status === "draft" || status === "in-review",
216
+ withoutEffectiveOn: true
217
+ }));
176
218
  }
177
219
 
178
220
  function pick(record, fields, model, objectType = null) {