filegrc 0.11.0 → 0.12.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/model/index.js +9 -5
- package/model/v10.json +12122 -0
- package/model/v9.json +11647 -0
- package/package.json +1 -1
- package/src/agent.js +3 -0
- package/src/audit-populations.js +88 -0
- package/src/audit-preparation.js +7 -2
- package/src/cli.js +186 -13
- package/src/collection-review-integrity.js +118 -0
- package/src/collection-review.js +71 -7
- package/src/collection-scope.js +8 -0
- package/src/evidence-packet.js +328 -46
- package/src/files.js +364 -6
- package/src/git.js +1064 -149
- package/src/index.js +17 -1
- package/src/model-migration.js +221 -5
- package/src/obligations.js +834 -66
- package/src/policy-library/information-security-policy-v2.md +1 -1
- package/src/policy-library.js +85 -8
- package/src/program-path.js +11 -5
- package/src/program-readiness.js +84 -6
- package/src/reconciliation.js +332 -81
- package/src/reporting-route-integrity.js +542 -0
- package/src/reporting-route-sets.js +745 -0
- package/src/server.js +160 -47
- package/src/state.js +30 -8
- package/src/time.js +55 -0
- package/src/validate.js +978 -4
- package/src/web.js +491 -49
- package/src/workflow-history-integrity.js +872 -0
- package/src/workflow.js +36 -10
package/src/collection-review.js
CHANGED
|
@@ -5,10 +5,12 @@ import {
|
|
|
5
5
|
collectionRevisionMatches
|
|
6
6
|
} from "./collection-revision.js";
|
|
7
7
|
import { scopedCollectionRecords } from "./collection-scope.js";
|
|
8
|
-
import { applyResourceBatch } from "./files.js";
|
|
8
|
+
import { applyResourceBatch, INTERNAL_WORKFLOW_CAPABILITIES } from "./files.js";
|
|
9
9
|
import { getGitSummary } from "./git.js";
|
|
10
|
+
import { createResourceId } from "./id.js";
|
|
10
11
|
import { loadWorkspace } from "./workspace.js";
|
|
11
12
|
import { resolveProgram } from "./program.js";
|
|
13
|
+
import { currentCalendarDate } from "./time.js";
|
|
12
14
|
|
|
13
15
|
export { collectionRevision };
|
|
14
16
|
|
|
@@ -41,6 +43,13 @@ export function assessCollectionReview(loaded, resourceType, options = {}) {
|
|
|
41
43
|
authoritativeSourceId
|
|
42
44
|
});
|
|
43
45
|
const allowedDecisions = configuration.decisions || ["complete"];
|
|
46
|
+
const temporalReview = !modelSupports(loaded.model, "temporal-collection-reviews") || Boolean(
|
|
47
|
+
review?.coverage?.kind === "as-of"
|
|
48
|
+
&& review.coverage.on === review.reviewedOn
|
|
49
|
+
&& review.knowledgeCutoffAt
|
|
50
|
+
&& Array.isArray(review.populationResourceIds)
|
|
51
|
+
&& sameIds(review.populationResourceIds, records.map(({ id }) => id))
|
|
52
|
+
);
|
|
44
53
|
const allowsEmptyCollection = allowedDecisions.some((decision) => (
|
|
45
54
|
decision === "zero-population" || decision === "externally-managed"
|
|
46
55
|
));
|
|
@@ -58,6 +67,7 @@ export function assessCollectionReview(loaded, resourceType, options = {}) {
|
|
|
58
67
|
review?.status === "active"
|
|
59
68
|
&& allowedDecisions.includes(review.decision)
|
|
60
69
|
&& revisionMatches
|
|
70
|
+
&& temporalReview
|
|
61
71
|
);
|
|
62
72
|
const stale = Boolean(
|
|
63
73
|
review?.status === "active"
|
|
@@ -116,7 +126,17 @@ export async function planCollectionReview(input = process.cwd(), options = {})
|
|
|
116
126
|
const rationale = String(options.rationale || "").trim();
|
|
117
127
|
const reviewedByIds = [...new Set((options.reviewedByIds || []).map(String).filter(Boolean))];
|
|
118
128
|
const reviewedOn = String(options.reviewedOn || "").trim();
|
|
119
|
-
const
|
|
129
|
+
const temporalReviews = modelSupports(loaded.model, "temporal-collection-reviews");
|
|
130
|
+
const now = options.now ? new Date(options.now) : new Date();
|
|
131
|
+
if (Number.isNaN(now.getTime())) throw new Error("A valid Collection Review time is required.");
|
|
132
|
+
const today = currentCalendarDate(loaded.workspace.timezone, now);
|
|
133
|
+
const gitSummary = getGitSummary(loaded.root);
|
|
134
|
+
if (temporalReviews && (!gitSummary.available || !gitSummary.clean || !gitSummary.commit)) {
|
|
135
|
+
throw new Error("Commit the current workspace before recording a Collection Review so its scope revision is retrievable from Git.");
|
|
136
|
+
}
|
|
137
|
+
const scopeRevision = temporalReviews
|
|
138
|
+
? gitSummary.commit
|
|
139
|
+
: String(options.scopeRevision || gitSummary.commit || "uncommitted").trim();
|
|
120
140
|
const v4 = modelSupports(loaded.model, "program-scope");
|
|
121
141
|
const authoritativeSourceId = String(v4 ? options.authoritativeComponentId : options.authoritativeSystemId || "").trim();
|
|
122
142
|
if (!(configuration.decisions || ["complete"]).includes(decision)) {
|
|
@@ -138,6 +158,14 @@ export async function planCollectionReview(input = process.cwd(), options = {})
|
|
|
138
158
|
if (!rationale || !reviewedByIds.length || !reviewedOn) {
|
|
139
159
|
throw new Error(`${configuration.title} review needs review notes, a reviewer, and a review date.`);
|
|
140
160
|
}
|
|
161
|
+
if (temporalReviews) {
|
|
162
|
+
if (reviewedOn !== today) {
|
|
163
|
+
throw new Error("A current Collection Review must use today's workspace-local review date. Reconstruct an older population from Git history instead of backdating today's files.");
|
|
164
|
+
}
|
|
165
|
+
if (options.scopeRevision && options.scopeRevision !== scopeRevision) {
|
|
166
|
+
throw new Error("Collection Review scope revision is derived from Git and cannot be supplied.");
|
|
167
|
+
}
|
|
168
|
+
}
|
|
141
169
|
if (decision === "externally-managed") {
|
|
142
170
|
const system = loaded.resources.find((record) => (
|
|
143
171
|
record.type === (v4 ? "component" : "system")
|
|
@@ -151,14 +179,29 @@ export async function planCollectionReview(input = process.cwd(), options = {})
|
|
|
151
179
|
authoritativeSourceId: decision === "externally-managed" ? authoritativeSourceId : null
|
|
152
180
|
});
|
|
153
181
|
const existing = assessment.review;
|
|
182
|
+
const preservesReviewHistory = temporalReviews;
|
|
154
183
|
const record = {
|
|
155
|
-
...(existing
|
|
156
|
-
id:
|
|
184
|
+
...(!existing ? {
|
|
185
|
+
id: modelSupports(loaded.model, "program-scope")
|
|
186
|
+
? createResourceId(
|
|
187
|
+
"collection-review",
|
|
188
|
+
`${program.id} ${configuration.title} review`,
|
|
189
|
+
loaded.resources.map(({ id }) => id)
|
|
190
|
+
)
|
|
191
|
+
: `collection-review-${resourceType}`,
|
|
157
192
|
type: "collection-review",
|
|
158
193
|
title: `${configuration.title} review`,
|
|
159
194
|
resourceType,
|
|
160
195
|
scopeResourceIds: [program.id]
|
|
161
|
-
}
|
|
196
|
+
} : preservesReviewHistory ? {
|
|
197
|
+
...existing,
|
|
198
|
+
id: createResourceId(
|
|
199
|
+
"collection-review",
|
|
200
|
+
`${configuration.title} review ${reviewedOn}`,
|
|
201
|
+
loaded.resources.map(({ id }) => id)
|
|
202
|
+
),
|
|
203
|
+
supersedesId: existing.id
|
|
204
|
+
} : existing),
|
|
162
205
|
status: "active",
|
|
163
206
|
decision,
|
|
164
207
|
rationale,
|
|
@@ -166,6 +209,11 @@ export async function planCollectionReview(input = process.cwd(), options = {})
|
|
|
166
209
|
reviewedOn,
|
|
167
210
|
collectionRevision: currentRevision,
|
|
168
211
|
scopeRevision,
|
|
212
|
+
...(temporalReviews ? {
|
|
213
|
+
coverage: { kind: "as-of", on: reviewedOn },
|
|
214
|
+
knowledgeCutoffAt: now.toISOString(),
|
|
215
|
+
populationResourceIds: assessment.records.map(({ id }) => id).sort()
|
|
216
|
+
} : {}),
|
|
169
217
|
...(decision === "externally-managed"
|
|
170
218
|
? { [v4 ? "authoritativeComponentId" : "authoritativeSystemId"]: authoritativeSourceId }
|
|
171
219
|
: {})
|
|
@@ -179,17 +227,33 @@ export async function planCollectionReview(input = process.cwd(), options = {})
|
|
|
179
227
|
resourceType,
|
|
180
228
|
assessment,
|
|
181
229
|
changes: {
|
|
182
|
-
...(existing ? {
|
|
230
|
+
...(!existing || preservesReviewHistory ? { create: [record] } : { update: [record] }),
|
|
231
|
+
...(existing && preservesReviewHistory ? {
|
|
232
|
+
update: [{
|
|
233
|
+
...existing,
|
|
234
|
+
status: "retired",
|
|
235
|
+
statusTransition: {
|
|
236
|
+
changedByIds: reviewedByIds,
|
|
237
|
+
changedOn: reviewedOn,
|
|
238
|
+
reason: `Superseded by ${record.id}.`
|
|
239
|
+
}
|
|
240
|
+
}]
|
|
241
|
+
} : {}),
|
|
183
242
|
...(existing ? {
|
|
184
243
|
expectedRevisions: {
|
|
185
244
|
[existing.id]: options.expectedRevision || assessment.reviewRevision
|
|
186
245
|
}
|
|
187
246
|
} : {}),
|
|
188
|
-
validateWholeWorkspace: true
|
|
247
|
+
validateWholeWorkspace: true,
|
|
248
|
+
workflowCapability: INTERNAL_WORKFLOW_CAPABILITIES.collectionReviewReassessment
|
|
189
249
|
}
|
|
190
250
|
};
|
|
191
251
|
}
|
|
192
252
|
|
|
253
|
+
function sameIds(left, right) {
|
|
254
|
+
return JSON.stringify([...left].sort()) === JSON.stringify([...right].sort());
|
|
255
|
+
}
|
|
256
|
+
|
|
193
257
|
export async function applyCollectionReview(input = process.cwd(), options = {}) {
|
|
194
258
|
if (options.confirmed !== true) {
|
|
195
259
|
throw new Error("Preview the collection review and confirm the write.");
|
package/src/collection-scope.js
CHANGED
|
@@ -55,6 +55,14 @@ export function scopedCollectionRecords(loaded, resourceType, program) {
|
|
|
55
55
|
));
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
export function selectScopedCollectionRecords(loaded, selector, program) {
|
|
59
|
+
if (!selector?.resourceType) return [];
|
|
60
|
+
return scopedCollectionRecords(loaded, selector.resourceType, program).filter((record) => (
|
|
61
|
+
(!selector.statuses?.length || selector.statuses.includes(record.status))
|
|
62
|
+
&& (!selector.criticalities?.length || selector.criticalities.includes(record.criticality))
|
|
63
|
+
));
|
|
64
|
+
}
|
|
65
|
+
|
|
58
66
|
function scopedProgramPeople(loaded, program = {}) {
|
|
59
67
|
const personIds = new Set(loaded.resources.filter(({ type }) => type === "person").map(({ id }) => id));
|
|
60
68
|
const systemIds = new Set(program.systemIds || []);
|