filegrc 0.16.1 → 0.16.3
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 +1 -1
- package/package.json +1 -1
- package/src/cli.js +5 -1
- package/src/collection-scope.js +18 -0
- package/src/obligations.js +6 -2
- package/src/program-readiness.js +48 -1
- package/src/web.js +42 -45
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ filegrc is a zero-dependency Node.js engine for Git-native GRC workspaces. It va
|
|
|
4
4
|
|
|
5
5
|
Program Readiness checks management-owned scope, policy adoption, control implementation, and authoritative evidence mapping without requiring an audit record. Audit Readiness starts after CPA engagement and checks the firm-agreed date or period, engagement-specific management documents, operating evidence, and Type 2 population completeness.
|
|
6
6
|
|
|
7
|
-
Controls
|
|
7
|
+
The Controls list focuses on implementation and its next action. The Obligations list shows schedule state separately, including proposals, approved schedules, and Policies awaiting activation or their effective date. Enabled work starts when its governing content is effective and a linked Control is implemented.
|
|
8
8
|
|
|
9
9
|
Most users should create a complete workspace:
|
|
10
10
|
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -1846,7 +1846,11 @@ function buildProgramPathResult(model, readiness, auditReadiness) {
|
|
|
1846
1846
|
nextActions: (current?.items || []).filter((item) => item.status === "action")
|
|
1847
1847
|
};
|
|
1848
1848
|
});
|
|
1849
|
-
const
|
|
1849
|
+
const recommendedStage = readiness.stages.find((stage) => stage.items.includes(readiness.firstAction));
|
|
1850
|
+
const recommendedId = recommendedStage?.id === "operation" ? "run" : recommendedStage?.id;
|
|
1851
|
+
const currentStep = stages.find(({ id }) => id === recommendedId)
|
|
1852
|
+
|| stages.find((stage) => !["complete", "operating", "management-ready"].includes(stage.status))
|
|
1853
|
+
|| stages.at(-1);
|
|
1850
1854
|
return {
|
|
1851
1855
|
schemaVersion: 1,
|
|
1852
1856
|
dataModelVersion: String(model.modelVersion),
|
package/src/collection-scope.js
CHANGED
|
@@ -65,6 +65,24 @@ export function scopedCollectionRecords(loaded, resourceType, program) {
|
|
|
65
65
|
));
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
// These records can change which members enter a collection even when they
|
|
69
|
+
// are not part of its current revision inputs yet.
|
|
70
|
+
const COLLECTION_MEMBERSHIP_SOURCES = {
|
|
71
|
+
person: ["workspace", "program", "appointment", "team", "system", "component", "vendor", "control", "policy", "document", "obligation"],
|
|
72
|
+
vendor: ["program", "system", "component", "audit"],
|
|
73
|
+
component: ["program", "system", "control"],
|
|
74
|
+
"complementary-control": ["program", "system", "component", "control"],
|
|
75
|
+
"information-type": ["program", "system", "component", "vendor"],
|
|
76
|
+
"retention-schedule-item": ["program", "system", "component", "vendor", "control", "information-type", "source-coverage"],
|
|
77
|
+
framework: ["program"],
|
|
78
|
+
system: ["program"],
|
|
79
|
+
control: ["program"]
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export function collectionMembershipSourceTypes(resourceType) {
|
|
83
|
+
return COLLECTION_MEMBERSHIP_SOURCES[resourceType] || [];
|
|
84
|
+
}
|
|
85
|
+
|
|
68
86
|
export function retentionScheduleReviewScope(loaded) {
|
|
69
87
|
const programs = loaded.resources.filter((record) => (
|
|
70
88
|
record.type === "program" && record.status !== "retired"
|
package/src/obligations.js
CHANGED
|
@@ -101,6 +101,9 @@ export function planObligations(resources, options = {}) {
|
|
|
101
101
|
&& ["active", "proposed"].includes(record.status)
|
|
102
102
|
&& obligationBelongsToProgram(record, obligationProgram, model)
|
|
103
103
|
));
|
|
104
|
+
const programStatusByObligationId = new Map(obligations.map((obligation) => [
|
|
105
|
+
obligation.id, obligationProgramStatus(obligation, byId, asOf, model)
|
|
106
|
+
]));
|
|
104
107
|
const obligationIds = new Set(obligations.map(({ id }) => id));
|
|
105
108
|
if (obligations.length > MAX_PLANNED_ITEMS) {
|
|
106
109
|
throw new Error(`The obligation query must be narrowed; it includes more than ${MAX_PLANNED_ITEMS.toLocaleString("en-US")} active obligations.`);
|
|
@@ -123,7 +126,7 @@ export function planObligations(resources, options = {}) {
|
|
|
123
126
|
const schedule = rule || obligation;
|
|
124
127
|
const activity = obligationActivity(model, obligation);
|
|
125
128
|
const expectedCompletionTypes = activity.completionResourceTypes;
|
|
126
|
-
const programStatus =
|
|
129
|
+
const programStatus = programStatusByObligationId.get(obligation.id);
|
|
127
130
|
const programBlocker = programStatus === "proposed" ? obligationProgramBlocker(obligation, byId, asOf) : null;
|
|
128
131
|
if (schedule.recurrence?.mode === "event" && schedule.recurrence.eventType) {
|
|
129
132
|
const eventType = schedule.recurrence.eventType;
|
|
@@ -294,7 +297,7 @@ export function planObligations(resources, options = {}) {
|
|
|
294
297
|
if (!dueWindowStart || !dueWindowEnd || dueWindowStart > through) continue;
|
|
295
298
|
const overdueOn = addCalendarDays(dueWindowEnd, 1);
|
|
296
299
|
const window = { dueWindowStart, dueWindowEnd, overdueOn };
|
|
297
|
-
const programStatus =
|
|
300
|
+
const programStatus = programStatusByObligationId.get(obligation.id);
|
|
298
301
|
const programBlocker = programStatus === "proposed" ? obligationProgramBlocker(obligation, byId, asOf) : null;
|
|
299
302
|
const activity = obligationActivity(model, obligation);
|
|
300
303
|
const completedMemberIds = (occurrence.members || [])
|
|
@@ -402,6 +405,7 @@ export function planObligations(resources, options = {}) {
|
|
|
402
405
|
asOf,
|
|
403
406
|
through,
|
|
404
407
|
from: requestedFrom,
|
|
408
|
+
programStatuses: Object.fromEntries(programStatusByObligationId),
|
|
405
409
|
counts,
|
|
406
410
|
items,
|
|
407
411
|
calendarItems,
|
package/src/program-readiness.js
CHANGED
|
@@ -3,6 +3,7 @@ import { modelSupports } from "../model/index.js";
|
|
|
3
3
|
import { applicabilityReviewIsCurrent } from "./applicability-scope.js";
|
|
4
4
|
import { assessRequiredAppointments } from "./appointments.js";
|
|
5
5
|
import { assessCollectionReviews } from "./collection-review.js";
|
|
6
|
+
import { collectionMembershipSourceTypes, collectionRevisionInputs, collectionScopeRevisionFacts } from "./collection-scope.js";
|
|
6
7
|
import { openPlaceholderCount, substantiveMarkdown } from "./content-readiness.js";
|
|
7
8
|
import { coverageEnd, coverageStart } from "./coverage.js";
|
|
8
9
|
import { planObligations } from "./obligations.js";
|
|
@@ -233,6 +234,7 @@ export async function assessProgramReadiness(input, options = {}) {
|
|
|
233
234
|
policyStage,
|
|
234
235
|
controlStage
|
|
235
236
|
];
|
|
237
|
+
const prioritizedSetupItems = prioritizeReviewDependencies(evidenceGateStages, loaded, program);
|
|
236
238
|
for (const current of evidenceGateStages) finalizeStage(current);
|
|
237
239
|
const evidenceReady = evidenceGateStages.every((current) => current.counts.action === 0);
|
|
238
240
|
const stages = [
|
|
@@ -260,7 +262,8 @@ export async function assessProgramReadiness(input, options = {}) {
|
|
|
260
262
|
const items = stages.flatMap((current) => current.items);
|
|
261
263
|
const managedItems = items.filter((current) => !["info", "later"].includes(current.status));
|
|
262
264
|
const complete = managedItems.filter((current) => current.status === "complete").length;
|
|
263
|
-
const firstAction =
|
|
265
|
+
const firstAction = [...prioritizedSetupItems, ...stages.at(-1).items]
|
|
266
|
+
.find((current) => current.status === "action") || null;
|
|
264
267
|
const target = {
|
|
265
268
|
programId: program?.id || null,
|
|
266
269
|
goal: program?.assuranceGoal || "none",
|
|
@@ -1966,6 +1969,50 @@ function finalizeStage(current) {
|
|
|
1966
1969
|
current.status = current.counts.action ? "action" : current.counts.later ? "later" : "complete";
|
|
1967
1970
|
}
|
|
1968
1971
|
|
|
1972
|
+
// A collection confirmation can be performed early, but an unfinished input
|
|
1973
|
+
// would make it stale again. Derive the ordering from the same revision inputs
|
|
1974
|
+
// used to decide whether that confirmation is current.
|
|
1975
|
+
export function prioritizeReviewDependencies(stages, loaded, program) {
|
|
1976
|
+
const items = stages.flatMap(({ items }) => items);
|
|
1977
|
+
const edges = new Map(items.map((_, index) => [index, new Set()]));
|
|
1978
|
+
const indegree = items.map(() => 0);
|
|
1979
|
+
for (const [reviewIndex, review] of items.entries()) {
|
|
1980
|
+
if (review.status !== "action" || !review.id.startsWith("collection-review-")) continue;
|
|
1981
|
+
const resourceType = review.id.slice("collection-review-".length);
|
|
1982
|
+
if (!loaded.model.collectionReviews?.[resourceType]) continue;
|
|
1983
|
+
const inputTypes = new Set(collectionRevisionInputs(loaded, resourceType, program)
|
|
1984
|
+
.map(({ record }) => record.type));
|
|
1985
|
+
for (const type of collectionMembershipSourceTypes(resourceType)) inputTypes.add(type);
|
|
1986
|
+
const scopeFacts = collectionScopeRevisionFacts(loaded, resourceType, program);
|
|
1987
|
+
if (Object.keys(scopeFacts).some((key) => key !== "programId")) inputTypes.add("program");
|
|
1988
|
+
for (const key of Object.keys(scopeFacts)) {
|
|
1989
|
+
if (!key.endsWith("Ids")) continue;
|
|
1990
|
+
const type = key.slice(0, -3).replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
|
|
1991
|
+
if (loaded.model.resources?.[type]) inputTypes.add(type);
|
|
1992
|
+
}
|
|
1993
|
+
for (const [sourceIndex, source] of items.entries()) {
|
|
1994
|
+
if (sourceIndex === reviewIndex || source.status !== "action"
|
|
1995
|
+
|| source.id.startsWith("collection-review-")
|
|
1996
|
+
|| !inputTypes.has(source.resourceType)) continue;
|
|
1997
|
+
edges.get(sourceIndex).add(reviewIndex);
|
|
1998
|
+
indegree[reviewIndex]++;
|
|
1999
|
+
}
|
|
2000
|
+
}
|
|
2001
|
+
const remaining = new Set(items.map((_, index) => index));
|
|
2002
|
+
const ordered = [];
|
|
2003
|
+
while (remaining.size) {
|
|
2004
|
+
const next = [...remaining].find((index) => indegree[index] === 0);
|
|
2005
|
+
if (next === undefined) break;
|
|
2006
|
+
remaining.delete(next);
|
|
2007
|
+
ordered.push(items[next]);
|
|
2008
|
+
for (const dependent of edges.get(next)) indegree[dependent]--;
|
|
2009
|
+
}
|
|
2010
|
+
if (ordered.length !== items.length) return items;
|
|
2011
|
+
const rank = new Map(ordered.map((item, index) => [item, index]));
|
|
2012
|
+
for (const stage of stages) stage.items.sort((left, right) => rank.get(left) - rank.get(right));
|
|
2013
|
+
return ordered;
|
|
2014
|
+
}
|
|
2015
|
+
|
|
1969
2016
|
function item(id, status, title, message, resource = {}, details = {}) {
|
|
1970
2017
|
return {
|
|
1971
2018
|
id,
|
package/src/web.js
CHANGED
|
@@ -246,6 +246,7 @@ function normalizeAppState(next) {
|
|
|
246
246
|
function blockingStateSections(route) {
|
|
247
247
|
if (route.name === "home") return ["program", "obligations", "workflow"];
|
|
248
248
|
if (route.name === "repository") return ["repository"];
|
|
249
|
+
if (route.name === "list" && route.type === "obligation") return ["obligations"];
|
|
249
250
|
if (route.name === "obligations" || route.name === "stage" && route.stageId === "run") return ["program", "obligations"];
|
|
250
251
|
if (route.name === "audit-packet") return ["repository", "program", "obligations", "audits"];
|
|
251
252
|
if (route.name === "evidence-sources") return ["program", "workflow"];
|
|
@@ -589,6 +590,13 @@ function readinessOverview() {
|
|
|
589
590
|
}
|
|
590
591
|
|
|
591
592
|
function nextProgramStageHref() {
|
|
593
|
+
const firstAction = state.programReadiness?.firstAction;
|
|
594
|
+
const recommendedStage = state.programReadiness?.stages?.find((stage) => stage.items.some((item) => (
|
|
595
|
+
item.id === firstAction?.id && item.resourceId === firstAction?.resourceId
|
|
596
|
+
)));
|
|
597
|
+
if (recommendedStage) {
|
|
598
|
+
return "#/stage/" + encodeURIComponent(recommendedStage.id === "operation" ? "run" : recommendedStage.id);
|
|
599
|
+
}
|
|
592
600
|
const nextStage = READINESS_STAGES.find((stage) => {
|
|
593
601
|
const progress = stageProgress(stage);
|
|
594
602
|
return progress.complete < progress.total;
|
|
@@ -3081,7 +3089,6 @@ function renderList(main, type, params = new URLSearchParams()) {
|
|
|
3081
3089
|
const fields = [...new Set([
|
|
3082
3090
|
"title",
|
|
3083
3091
|
...(definition.listFields || []),
|
|
3084
|
-
...(type === "control" ? ["$operationTracking"] : []),
|
|
3085
3092
|
...(type === "obligation" ? ["$workQueueStatus"] : [])
|
|
3086
3093
|
])].filter((name) => name !== "title");
|
|
3087
3094
|
const modelFields = { ...state.model.commonFields, ...definition.fields };
|
|
@@ -3124,7 +3131,7 @@ function renderList(main, type, params = new URLSearchParams()) {
|
|
|
3124
3131
|
const start = (pageNumber - 1) * LIST_PAGE_SIZE;
|
|
3125
3132
|
const visible = filtered.slice(start, start + LIST_PAGE_SIZE);
|
|
3126
3133
|
main.querySelector("#result-count").textContent = filtered.length + (filtered.length === 1 ? " record" : " records");
|
|
3127
|
-
main.querySelector("#record-rows").innerHTML = filtered.length ? visible.map((entry) => '<tr><td data-label="' + esc(fieldLabel(type, "title")) + '" data-primary-field><a class="record-title" href="#/resource/' + encodeURIComponent(type) + '/' + encodeURIComponent(entry.record.id) + detailContext + '">' + esc(entry.record.title) + '</a></td>' + fields.map((name) => '<td data-label="' + esc(fieldLabel(type, name)) + '">' + (name === "$
|
|
3134
|
+
main.querySelector("#record-rows").innerHTML = filtered.length ? visible.map((entry) => '<tr><td data-label="' + esc(fieldLabel(type, "title")) + '" data-primary-field><a class="record-title" href="#/resource/' + encodeURIComponent(type) + '/' + encodeURIComponent(entry.record.id) + detailContext + '">' + esc(entry.record.title) + '</a></td>' + fields.map((name) => '<td data-label="' + esc(fieldLabel(type, name)) + '">' + (name === "$workQueueStatus" ? obligationWorkQueueStatus(entry.record) : formatValue(name === "status" ? displayStatus(entry.record) : entry.record[name], name, type, true)) + '</td>').join("") + '<td data-label="Next action">' + recordWorkflowCell(type, entry) + '</td><td data-label="Git file"><code>' + esc(entry.relativePath.replace(/^data\//, "")) + '</code></td></tr>').join("") : '<tr><td colspan="' + (fields.length + 3) + '">' + empty(entries.length ? "No records match this filter." : collectionEmptyState(type, definition)) + '</td></tr>';
|
|
3128
3135
|
pagination.hidden = totalPages === 1;
|
|
3129
3136
|
previous.disabled = pageNumber === 1;
|
|
3130
3137
|
next.disabled = pageNumber === totalPages;
|
|
@@ -5965,7 +5972,6 @@ function setNavigationGroupOpen(group, open) {
|
|
|
5965
5972
|
function groupTitle(id) { return state.model.groups.find((group) => group.id === id)?.title || "Program"; }
|
|
5966
5973
|
function fieldDefinition(type, name) { return state.model.resources[type]?.fields?.[name] || state.model.commonFields[name]; }
|
|
5967
5974
|
function fieldLabel(type, name) {
|
|
5968
|
-
if (name === "$operationTracking") return "Operation tracking";
|
|
5969
5975
|
if (name === "$workQueueStatus") return "Work Queue";
|
|
5970
5976
|
if (type === "obligation" && name === "status") return "Configuration";
|
|
5971
5977
|
if (name === "title") return state.model.resources[type]?.titleLabel || state.model.commonFields.title.label;
|
|
@@ -6076,58 +6082,49 @@ function formatObjectArray(items, objectType, compact = false) {
|
|
|
6076
6082
|
return '<div class="object-value"><span class="object-value-facts">' + facts.join("") + '</span>' + (!compact && notes.length ? '<small>' + esc(notes.join(" ")) + '</small>' : "") + '</div>';
|
|
6077
6083
|
}).join("") + '</div>';
|
|
6078
6084
|
}
|
|
6079
|
-
function
|
|
6080
|
-
|
|
6081
|
-
|
|
6082
|
-
|
|
6083
|
-
|
|
6084
|
-
return
|
|
6085
|
-
}
|
|
6086
|
-
const counts = obligations.reduce((result, obligation) => {
|
|
6087
|
-
const status = workQueueScheduleStatus(obligation, control);
|
|
6088
|
-
result[status] = (result[status] || 0) + 1;
|
|
6089
|
-
return result;
|
|
6090
|
-
}, {});
|
|
6091
|
-
const scheduleCount = obligations.length + " " + pluralize("schedule", obligations.length);
|
|
6092
|
-
if (counts.running === obligations.length) {
|
|
6093
|
-
return '<a class="operation-tracking running" href="#/stage/run"><strong>Running in Work Queue</strong><small>' + esc(scheduleCount) + '</small></a>';
|
|
6094
|
-
}
|
|
6095
|
-
if (counts["waiting-policy"] === obligations.length) {
|
|
6096
|
-
return '<a class="operation-tracking waiting" href="#/stage/run"><strong>Waiting for policy approval</strong><small>' + esc(scheduleCount) + ' enabled</small></a>';
|
|
6097
|
-
}
|
|
6098
|
-
if (counts["waiting-control"] === obligations.length) {
|
|
6099
|
-
return '<a class="operation-tracking waiting" href="#/stage/run"><strong>Ready when implemented</strong><small>' + esc(scheduleCount) + ' enabled</small></a>';
|
|
6100
|
-
}
|
|
6101
|
-
if (counts.paused === obligations.length) {
|
|
6102
|
-
return '<a class="operation-tracking paused" href="#/stage/run"><strong>Work Queue paused</strong><small>' + esc(scheduleCount) + '</small></a>';
|
|
6103
|
-
}
|
|
6104
|
-
const summary = [
|
|
6105
|
-
counts.running ? counts.running + " running" : "",
|
|
6106
|
-
counts["waiting-policy"] ? counts["waiting-policy"] + " waiting for policy" : "",
|
|
6107
|
-
counts["waiting-control"] ? counts["waiting-control"] + " waiting for implementation" : "",
|
|
6108
|
-
counts.paused ? counts.paused + " paused" : ""
|
|
6109
|
-
].filter(Boolean).join(" · ");
|
|
6110
|
-
return '<a class="operation-tracking mixed" href="#/stage/run"><strong>Work Queue needs attention</strong><small>' + esc(summary) + '</small></a>';
|
|
6111
|
-
}
|
|
6112
|
-
function workQueueScheduleStatus(obligation, control = null) {
|
|
6085
|
+
function workQueueScheduleStatus(obligation) {
|
|
6086
|
+
if (obligation.status === "proposed") {
|
|
6087
|
+
const pendingRules = resourcesOfType("obligation-rule")
|
|
6088
|
+
.map(({ record }) => record)
|
|
6089
|
+
.filter((record) => record.obligationId === obligation.id && ["proposed", "approved"].includes(record.status));
|
|
6090
|
+
return pendingRules.length === 1 && pendingRules[0].status === "approved" ? "approved" : "proposed";
|
|
6091
|
+
}
|
|
6113
6092
|
if (obligation.status === "paused") return "paused";
|
|
6114
6093
|
if (obligation.status !== "active") return obligation.status;
|
|
6094
|
+
const programStatus = state.obligations?.programStatuses?.[obligation.id];
|
|
6095
|
+
if (!programStatus) return "not-evaluated";
|
|
6096
|
+
const rule = state.resources.find(({ record }) => record.id === obligation.activeRuleId)?.record;
|
|
6097
|
+
if (obligation.scheduleMode === "rule" && rule?.status !== "active") {
|
|
6098
|
+
return "proposed";
|
|
6099
|
+
}
|
|
6100
|
+
if (rule?.effectiveAt && new Date(rule.effectiveAt) > new Date()) {
|
|
6101
|
+
const prior = state.resources.find(({ record }) => record.id === rule.supersedesId)?.record;
|
|
6102
|
+
if (prior?.obligationId !== obligation.id || !["active", "retired"].includes(prior.status)) {
|
|
6103
|
+
return "waiting-schedule-effective";
|
|
6104
|
+
}
|
|
6105
|
+
}
|
|
6115
6106
|
const asOf = state.obligations?.asOf || new Date().toISOString().slice(0, 10);
|
|
6116
6107
|
const policies = (obligation.policyIds || []).map((id) => state.resources.find(({ record }) => record.id === id)?.record);
|
|
6117
|
-
if (
|
|
6118
|
-
|
|
6119
|
-
|
|
6108
|
+
if (policies.some((policy) => policy?.type !== "policy" || !["approved", "active"].includes(policy.status))) return "waiting-policy-approval";
|
|
6109
|
+
if (policies.some((policy) => policy.status !== "active")) return "waiting-policy-activation";
|
|
6110
|
+
if (policies.some((policy) => !policy.effectiveOn || policy.effectiveOn > asOf)) return "waiting-policy-effective";
|
|
6120
6111
|
const linkedControls = (obligation.controlIds || []).map((id) => state.resources.find(({ record }) => record.id === id)?.record);
|
|
6121
|
-
const implemented = control
|
|
6122
|
-
|
|
6123
|
-
|
|
6124
|
-
return implemented ? "running" : "waiting-control";
|
|
6112
|
+
const implemented = !linkedControls.length || linkedControls.some((record) => record?.type === "control" && record.status === "implemented");
|
|
6113
|
+
if (!implemented) return "waiting-control";
|
|
6114
|
+
return programStatus === "accepted" ? "running" : "waiting-prerequisites";
|
|
6125
6115
|
}
|
|
6126
6116
|
function obligationWorkQueueStatus(obligation) {
|
|
6127
6117
|
const status = workQueueScheduleStatus(obligation);
|
|
6118
|
+
if (status === "proposed") return '<span class="operation-tracking"><strong>Proposed</strong><small>Review schedule before activation</small></span>';
|
|
6119
|
+
if (status === "approved") return '<span class="operation-tracking"><strong>Approved</strong><small>Activate schedule before operation</small></span>';
|
|
6128
6120
|
if (status === "running") return '<span class="operation-tracking running"><strong>Running</strong><small>Enabled and policy effective</small></span>';
|
|
6129
|
-
if (status === "waiting-policy") return '<span class="operation-tracking waiting"><strong>Waiting for policy</strong><small>Schedule enabled</small></span>';
|
|
6121
|
+
if (status === "waiting-policy-approval") return '<span class="operation-tracking waiting"><strong>Waiting for policy approval</strong><small>Schedule enabled</small></span>';
|
|
6122
|
+
if (status === "waiting-policy-activation") return '<span class="operation-tracking waiting"><strong>Waiting for policy activation</strong><small>Policy approved</small></span>';
|
|
6123
|
+
if (status === "waiting-policy-effective") return '<span class="operation-tracking waiting"><strong>Waiting for effective date</strong><small>Policy active</small></span>';
|
|
6124
|
+
if (status === "waiting-schedule-effective") return '<span class="operation-tracking waiting"><strong>Waiting for schedule start</strong><small>Approved rule activates at its effective time</small></span>';
|
|
6130
6125
|
if (status === "waiting-control") return '<span class="operation-tracking waiting"><strong>Waiting for control</strong><small>Implement a linked control</small></span>';
|
|
6126
|
+
if (status === "waiting-prerequisites") return '<span class="operation-tracking waiting"><strong>Waiting for prerequisites</strong><small>Check owner and governed content</small></span>';
|
|
6127
|
+
if (status === "not-evaluated") return '<span class="operation-tracking"><strong>Not assessed here</strong><small>Outside the selected program</small></span>';
|
|
6131
6128
|
if (status === "paused") return '<span class="operation-tracking paused"><strong>Paused</strong><small>Schedule disabled</small></span>';
|
|
6132
6129
|
return '<span class="operation-tracking"><strong>' + esc(properCase(status)) + '</strong></span>';
|
|
6133
6130
|
}
|