filegrc 0.16.2 → 0.16.4
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/agent.js +2 -1
- package/src/audit-preparation.js +6 -1
- package/src/cli.js +69 -31
- package/src/index.js +1 -0
- package/src/obligations.js +6 -2
- package/src/program-path.js +141 -75
- package/src/program-readiness.js +95 -11
- package/src/state.js +1 -0
- package/src/web.js +87 -62
- package/src/workflow.js +1 -0
package/src/web.js
CHANGED
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
utcCalendarDate,
|
|
11
11
|
validCalendarRecurrence
|
|
12
12
|
} from "./recurrence.js";
|
|
13
|
-
import { PROGRAM_PATH, programPathForModel, RESOURCE_INSTRUCTIONS, RESOURCE_PAGE_SUMMARIES } from "./program-path.js";
|
|
13
|
+
import { PROGRAM_PATH, programPathForModel, RESOURCE_INSTRUCTIONS, RESOURCE_OUTPUTS, RESOURCE_PAGE_SUMMARIES } from "./program-path.js";
|
|
14
14
|
import { formatCalendarDate, formatLocalDateTime } from "./time.js";
|
|
15
15
|
|
|
16
16
|
export function dashboardProgramReadiness(programReadiness = {}) {
|
|
@@ -96,6 +96,7 @@ let READINESS_STAGES = SHARED_PROGRAM_STAGES.map((stage) => ({
|
|
|
96
96
|
number: String(stage.number)
|
|
97
97
|
}));
|
|
98
98
|
const RESOURCE_GUIDE_INSTRUCTIONS = ${JSON.stringify(RESOURCE_INSTRUCTIONS)};
|
|
99
|
+
const RESOURCE_GUIDE_OUTPUTS = ${JSON.stringify(RESOURCE_OUTPUTS)};
|
|
99
100
|
const dashboardProgramReadiness = ${dashboardProgramReadiness.toString()};
|
|
100
101
|
const workflowHrefBelongsToPage = ${workflowHrefBelongsToPage.toString()};
|
|
101
102
|
const pageActionIdentity = ${pageActionIdentity.toString()};
|
|
@@ -246,6 +247,8 @@ function normalizeAppState(next) {
|
|
|
246
247
|
function blockingStateSections(route) {
|
|
247
248
|
if (route.name === "home") return ["program", "obligations", "workflow"];
|
|
248
249
|
if (route.name === "repository") return ["repository"];
|
|
250
|
+
if (route.name === "list" && route.type === "control") return ["program"];
|
|
251
|
+
if (route.name === "list" && route.type === "obligation") return ["obligations"];
|
|
249
252
|
if (route.name === "obligations" || route.name === "stage" && route.stageId === "run") return ["program", "obligations"];
|
|
250
253
|
if (route.name === "audit-packet") return ["repository", "program", "obligations", "audits"];
|
|
251
254
|
if (route.name === "evidence-sources") return ["program", "workflow"];
|
|
@@ -612,7 +615,7 @@ function renderStageOverview(main, stageId, params = new URLSearchParams()) {
|
|
|
612
615
|
const progress = stageProgress(stage);
|
|
613
616
|
main.innerHTML = '<div class="page stage-overview-page"><nav class="breadcrumbs"><a href="#/">Overview</a><span>/</span><span>' + esc(stage.title) + '</span></nav>' +
|
|
614
617
|
'<section class="stage-overview-hero"><div><p class="kicker">Step ' + esc(stage.number) + ' of 5</p><h2>' + esc(stage.title) + '</h2><p>' + esc(stage.summary) + '</p></div>' + stageProgressCard(progress) + '</section>' +
|
|
615
|
-
renderStagePageIndex(stage) + (stage.id === "controls" ? renderFinishStepThree() : "") + (stage.id === "audit" ? renderAuditDocumentActivationAssessments() : "") + '</div>';
|
|
618
|
+
renderStageInstructions(stage) + renderStagePageIndex(stage) + (stage.id === "controls" ? renderFinishStepThree() : "") + (stage.id === "audit" ? renderAuditDocumentActivationAssessments() : "") + '</div>';
|
|
616
619
|
main.querySelector("[data-show-evidence-families]")?.addEventListener("click", (event) => {
|
|
617
620
|
main.querySelectorAll("[data-evidence-family-extra]").forEach((card) => { card.hidden = false; });
|
|
618
621
|
event.currentTarget.remove();
|
|
@@ -633,7 +636,8 @@ function renderPoliciesPage(main, params = new URLSearchParams()) {
|
|
|
633
636
|
}
|
|
634
637
|
|
|
635
638
|
function policyContentGuide() {
|
|
636
|
-
|
|
639
|
+
const section = READINESS_STAGES.find(({ id }) => id === "policies")?.sections.find(({ id }) => id === "policy-content");
|
|
640
|
+
return '<section class="page-guide resource-guide-popover" id="resource-guide" role="dialog" aria-label="How to use Policies" hidden><div><span>Do now</span><p>' + esc(section?.instructions || "Review and approve the program's governing content.") + '</p></div><div><span>Output</span><p>' + esc(section?.output || "Approved content bound to the reviewed revisions.") + '</p></div></section>';
|
|
637
641
|
}
|
|
638
642
|
|
|
639
643
|
function renderRetentionSchedulePage(main, params = new URLSearchParams()) {
|
|
@@ -845,7 +849,7 @@ function renderPolicyApprovalGuidance() {
|
|
|
845
849
|
: "";
|
|
846
850
|
return '<article><strong>' + esc(proposal.title) + '</strong><p>' + esc(proposal.message) + '</p>' + review + '<div class="evidence-map-references">' + proposal.policyIds.map((id) => formatReference(id)).join("") + '</div></article>';
|
|
847
851
|
}).join("");
|
|
848
|
-
return '<section class="policy-lifecycle-note panel ' + (proposalRows ? "" : "single") + '"><div><p class="kicker">Step 2 approval</p><h3>Approve the governed content</h3><p>
|
|
852
|
+
return '<section class="policy-lifecycle-note panel ' + (proposalRows ? "" : "single") + '"><div><p class="kicker">Step 2 approval</p><h3>Approve the governed content</h3><p>Replace placeholders and confirm the intended values. Have a separate approver review the exact Markdown revision and record the approval date.</p></div>' + (proposalRows ? '<div class="policy-library-proposals">' + proposalRows + '</div>' : "") + '</section>';
|
|
849
853
|
}
|
|
850
854
|
|
|
851
855
|
function renderPoliciesTable(params = new URLSearchParams()) {
|
|
@@ -962,7 +966,8 @@ function renderDocumentActivationAssessments() {
|
|
|
962
966
|
}
|
|
963
967
|
|
|
964
968
|
function renderAuditDocumentActivationAssessments() {
|
|
965
|
-
const audits = resourcesOfType("audit").map(({ record }) => record)
|
|
969
|
+
const audits = resourcesOfType("audit").map(({ record }) => record)
|
|
970
|
+
.filter((record) => !state.selectedProgramId || record.programId === state.selectedProgramId);
|
|
966
971
|
const audit = selectDefaultAudit(audits, state.programReadiness?.asOf || currentDate());
|
|
967
972
|
const preparation = audit ? state.auditPreparations?.[audit.id] : null;
|
|
968
973
|
const assessments = preparation?.documentActivations || [];
|
|
@@ -1181,7 +1186,10 @@ function workflowGuidance(options = {}) {
|
|
|
1181
1186
|
const visible = items.slice(0, 6);
|
|
1182
1187
|
const renderRow = (item) => {
|
|
1183
1188
|
const href = workflowItemHref(item);
|
|
1184
|
-
const
|
|
1189
|
+
const guidance = options.type === "control" && item.nextSteps?.length
|
|
1190
|
+
? '<ol class="control-next-steps">' + item.nextSteps.map((step) => '<li>' + esc(step) + '</li>').join("") + '</ol>'
|
|
1191
|
+
: '<small>' + esc(item.message || workflowItemDetail(item)) + '</small>';
|
|
1192
|
+
const body = '<span class="workflow-finding-status ' + esc(item.state) + '">' + esc(properCase(item.state)) + '</span><span><strong>' + esc(item.title) + '</strong>' + guidance + '</span>';
|
|
1185
1193
|
return href ? '<a href="' + href + '">' + body + '</a>' : '<div>' + body + '</div>';
|
|
1186
1194
|
};
|
|
1187
1195
|
const rows = visible.map(renderRow).join("");
|
|
@@ -1749,6 +1757,12 @@ function renderStagePageIndex(stage) {
|
|
|
1749
1757
|
return '<section class="stage-pages" aria-label="Step ' + esc(stage.number) + ' pages"><div class="stage-page-grid">' + cards + '</div></section>';
|
|
1750
1758
|
}
|
|
1751
1759
|
|
|
1760
|
+
function renderStageInstructions(stage) {
|
|
1761
|
+
return '<details class="stage-instructions"><summary>How to complete Step ' + esc(stage.number) + '</summary><div class="stage-instructions-body">' + stage.sections.map((section) => (
|
|
1762
|
+
'<section><h3>' + esc(section.title) + '</h3><ol>' + section.steps.map((step) => '<li>' + esc(step) + '</li>').join("") + '</ol></section>'
|
|
1763
|
+
)).join("") + '</div></details>';
|
|
1764
|
+
}
|
|
1765
|
+
|
|
1752
1766
|
function stagePageDestinations(stage) {
|
|
1753
1767
|
return stage.sections.flatMap((section) => sectionDestinations(section)
|
|
1754
1768
|
.filter((destination) => destination.utility !== "obligation-board")
|
|
@@ -1819,7 +1833,7 @@ function derivedStagePageState(stage, destination) {
|
|
|
1819
1833
|
if (
|
|
1820
1834
|
stage.id === "audit"
|
|
1821
1835
|
&& state.workflow?.assessments?.auditReadiness?.status === "not-started"
|
|
1822
|
-
&& (destination.type === "audit" || destination.utility === "audit-packet")
|
|
1836
|
+
&& (destination.type === "audit" || destination.type === "document" && destination.href.includes("documentScope=audit") || destination.utility === "audit-packet")
|
|
1823
1837
|
) {
|
|
1824
1838
|
return { complete: false, label: "No engagement" };
|
|
1825
1839
|
}
|
|
@@ -1854,6 +1868,11 @@ function derivedStagePageState(stage, destination) {
|
|
|
1854
1868
|
? { complete: true, label: "Reviewed" }
|
|
1855
1869
|
: { complete: false, label: "Review scope" };
|
|
1856
1870
|
}
|
|
1871
|
+
if (stage.id === "audit" && destination.type === "document" && destination.href.includes("documentScope=audit")) {
|
|
1872
|
+
if (!resourcesOfType("document").some(({ record }) => auditSpecificDocument(record))) {
|
|
1873
|
+
return { complete: false, countsTowardProgress: false, label: "Only if needed" };
|
|
1874
|
+
}
|
|
1875
|
+
}
|
|
1857
1876
|
if (destination.type && resourcesOfType(destination.type).length === 0) {
|
|
1858
1877
|
return { complete: false, countsTowardProgress: false, label: "Only if needed" };
|
|
1859
1878
|
}
|
|
@@ -2004,7 +2023,7 @@ function sectionDestinations(section) {
|
|
|
2004
2023
|
destinations.push({ type, kind: "Record page", label: titleCase(definition.pluralTitle), href: "#/resources/" + encodeURIComponent(type), description: definition.description });
|
|
2005
2024
|
}
|
|
2006
2025
|
for (const link of section.relatedLinks || []) {
|
|
2007
|
-
destinations.push({ type: link.type, kind: "Record page", label: link.label, href: link.href, description: section.description });
|
|
2026
|
+
destinations.push({ type: link.type, kind: "Record page", label: link.label, href: link.href, description: link.summary || section.description });
|
|
2008
2027
|
}
|
|
2009
2028
|
if (section.utility === "obligation-board") destinations.push({ utility: section.utility, kind: "Working page", label: "Work Queue", href: "#/stage/run", description: "Complete recurring work, Policy Event tasks, and assigned follow-up with its due windows and linked proof." });
|
|
2010
2029
|
if (section.utility === "evidence-sources") destinations.push({ utility: section.utility, kind: "Coverage page", label: "Evidence Sources", href: "#/evidence-sources", description: section.description });
|
|
@@ -2082,6 +2101,7 @@ function renderObligations(main, params = new URLSearchParams()) {
|
|
|
2082
2101
|
: "";
|
|
2083
2102
|
main.innerHTML = '<div class="page obligation-board-page stage-overview-page"><nav class="breadcrumbs"><a href="#/">Overview</a><span>/</span><span>' + esc(stage.title) + '</span></nav>' +
|
|
2084
2103
|
'<section class="stage-overview-hero"><div><p class="kicker">Step ' + esc(stage.number) + ' of 5</p><h2>' + esc(stage.title) + '</h2><p>' + esc(stage.summary) + '</p></div>' + stageProgressCard(stageProgress(stage)) + '</section>' +
|
|
2104
|
+
renderStageInstructions(stage) +
|
|
2085
2105
|
feedback +
|
|
2086
2106
|
operationGate +
|
|
2087
2107
|
activeOperation +
|
|
@@ -2743,6 +2763,13 @@ function openNextAuditCycleDialog(prior) {
|
|
|
2743
2763
|
});
|
|
2744
2764
|
}
|
|
2745
2765
|
|
|
2766
|
+
function controlApplicabilityPending(record) {
|
|
2767
|
+
if (!record.applicabilityReview) return true;
|
|
2768
|
+
const item = state.programReadiness?.stages?.find(({ id }) => id === "controls")?.items
|
|
2769
|
+
.find(({ resourceType, resourceId }) => resourceType === "control" && resourceId === record.id);
|
|
2770
|
+
return item?.checks?.applicability === false;
|
|
2771
|
+
}
|
|
2772
|
+
|
|
2746
2773
|
function openApplicabilityReviewDialog(type, entries) {
|
|
2747
2774
|
const definition = state.model.resources[type];
|
|
2748
2775
|
const reviewPoints = definition?.guidance?.reviewPoints || [];
|
|
@@ -2751,8 +2778,9 @@ function openApplicabilityReviewDialog(type, entries) {
|
|
|
2751
2778
|
const pending = entries.filter(({ record }) => (
|
|
2752
2779
|
type === "requirement" && modelSupports("program-scope")
|
|
2753
2780
|
? !reviewedRequirements.has(record.id)
|
|
2754
|
-
:
|
|
2755
|
-
|
|
2781
|
+
: type === "control"
|
|
2782
|
+
? controlApplicabilityPending(record)
|
|
2783
|
+
: !record.applicabilityReview || type === "requirement" && record.applicability === "undetermined"
|
|
2756
2784
|
));
|
|
2757
2785
|
const dialog = document.createElement("dialog");
|
|
2758
2786
|
dialog.className = "commit-dialog applicability-dialog";
|
|
@@ -3088,7 +3116,6 @@ function renderList(main, type, params = new URLSearchParams()) {
|
|
|
3088
3116
|
const fields = [...new Set([
|
|
3089
3117
|
"title",
|
|
3090
3118
|
...(definition.listFields || []),
|
|
3091
|
-
...(type === "control" ? ["$operationTracking"] : []),
|
|
3092
3119
|
...(type === "obligation" ? ["$workQueueStatus"] : [])
|
|
3093
3120
|
])].filter((name) => name !== "title");
|
|
3094
3121
|
const modelFields = { ...state.model.commonFields, ...definition.fields };
|
|
@@ -3102,7 +3129,9 @@ function renderList(main, type, params = new URLSearchParams()) {
|
|
|
3102
3129
|
const hasPendingApplicability = entries.some(({ record }) => (
|
|
3103
3130
|
type === "requirement" && modelSupports("program-scope")
|
|
3104
3131
|
? !reviewedRequirements.has(record.id)
|
|
3105
|
-
:
|
|
3132
|
+
: type === "control"
|
|
3133
|
+
? controlApplicabilityPending(record)
|
|
3134
|
+
: !record.applicabilityReview || type === "requirement" && record.applicability === "undetermined"
|
|
3106
3135
|
));
|
|
3107
3136
|
const applicabilityButton = !state.readOnly
|
|
3108
3137
|
&& ["requirement", "control", "commitment", "complementary-control"].includes(type)
|
|
@@ -3112,8 +3141,11 @@ function renderList(main, type, params = new URLSearchParams()) {
|
|
|
3112
3141
|
const guideTrigger = '<button class="guide-trigger" id="resource-guide-trigger" type="button" aria-label="About ' + esc(definition.pluralTitle) + '" aria-haspopup="dialog" aria-controls="resource-guide" aria-expanded="false"><svg viewBox="0 0 20 20" aria-hidden="true"><circle cx="10" cy="10" r="8"></circle><path d="M7.8 7.5a2.4 2.4 0 1 1 3.25 2.25c-.7.31-1.05.72-1.05 1.5v.25M10 14.5v.1"></path></svg></button>';
|
|
3113
3142
|
const listTools = '<div class="list-tools list-header-tools"><label><span class="sr-only">Filter list</span><input id="list-search" type="search" placeholder="Filter ' + esc(definition.pluralTitle.toLowerCase()) + '"></label>' +
|
|
3114
3143
|
filters.map(({ name, label, values }) => '<select class="field-filter" data-field="' + esc(name) + '" aria-label="Filter by ' + esc(label.toLowerCase()) + '"><option value="">Any ' + esc(properCase(label)) + '</option>' + values.map((value) => '<option value="' + esc(value) + '">' + esc(filterOptionLabel(value)) + '</option>').join("") + '</select>').join("") + '<span id="result-count" aria-live="polite">' + entries.length + ' records</span>' + applicabilityButton + createButton + '</div>';
|
|
3115
|
-
const
|
|
3116
|
-
|
|
3144
|
+
const relatedGuidance = type === "document" && documentScope === "audit"
|
|
3145
|
+
? listStage?.sections.flatMap((section) => section.relatedLinks || []).find((link) => link.type === "document" && link.instructions)
|
|
3146
|
+
: null;
|
|
3147
|
+
const pageSummary = relatedGuidance?.summary || STAGE_PAGE_SUMMARIES[type] || definition.description;
|
|
3148
|
+
main.innerHTML = '<div class="page"><div class="page-intro"><div><p class="kicker">' + esc(listStage?.title || groupTitle(definition.group)) + '</p><div class="page-title-line"><h2>' + esc(titleCase(listTitle)) + '</h2>' + guideTrigger + '</div><p>' + esc(pageSummary) + '</p></div>' + listTools + '</div>' + resourceGuide(type, relatedGuidance) +
|
|
3117
3149
|
collectionReviewPanel(type) +
|
|
3118
3150
|
'<section class="record-table-wrap"><table class="record-table"><thead><tr><th>' + esc(fieldLabel(type, "title")) + '</th>' + fields.map((name) => '<th>' + esc(fieldLabel(type, name)) + '</th>').join("") + '<th>Next action</th><th>Git file</th></tr></thead><tbody id="record-rows"></tbody></table></section>' +
|
|
3119
3151
|
'<nav class="pagination list-pagination" aria-label="' + esc(definition.pluralTitle) + ' pages" hidden><button class="button" type="button" data-page="previous">Previous</button><span class="page-status" aria-live="polite"></span><button class="button" type="button" data-page="next">Next</button></nav></div>';
|
|
@@ -3131,7 +3163,7 @@ function renderList(main, type, params = new URLSearchParams()) {
|
|
|
3131
3163
|
const start = (pageNumber - 1) * LIST_PAGE_SIZE;
|
|
3132
3164
|
const visible = filtered.slice(start, start + LIST_PAGE_SIZE);
|
|
3133
3165
|
main.querySelector("#result-count").textContent = filtered.length + (filtered.length === 1 ? " record" : " records");
|
|
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 === "$
|
|
3166
|
+
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>';
|
|
3135
3167
|
pagination.hidden = totalPages === 1;
|
|
3136
3168
|
previous.disabled = pageNumber === 1;
|
|
3137
3169
|
next.disabled = pageNumber === totalPages;
|
|
@@ -3316,7 +3348,7 @@ function renderDetail(main, type, id, params = new URLSearchParams()) {
|
|
|
3316
3348
|
});
|
|
3317
3349
|
const mayDelete = !state.readOnly && !routeSetLocked && !definition.singleton;
|
|
3318
3350
|
const recordMenu = '<details class="record-overflow"><summary aria-label="More record actions" title="More record actions"><svg viewBox="0 0 18 4" aria-hidden="true"><circle cx="2" cy="2" r="2"></circle><circle cx="9" cy="2" r="2"></circle><circle cx="16" cy="2" r="2"></circle></svg></summary><div class="record-overflow-menu" aria-label="Record actions"><button type="button" data-record-modal="info">Record info</button><button type="button" data-record-modal="connections">Connections</button><button type="button" data-record-modal="history"' + (entry.historyLoaded === false ? ' disabled' : '') + '>File history' + (entry.historyLoaded === false ? ' (loading)' : '') + '</button>' + (mayDelete ? '<div class="record-overflow-separator"></div><button class="danger-text" type="button" data-delete-resource>Delete record</button>' : '') + '</div></details>';
|
|
3319
|
-
main.innerHTML = '<div class="page"><div class="detail-head"><div><div class="breadcrumbs header-breadcrumbs"><a href="' + collectionHref + '">' + esc(collectionLabel) + '</a><span>/</span><span>' + esc(entry.record.title) + '</span></div><h2>' + esc(titleCase(entry.record.title)) + '</h2></div><div class="actions">' + reportingRouteSetActions + auditCycleAction + auditPopulationCorrectionAction + (type === "audit" ? '<a class="button primary" href="#/audit-packet?auditId=' + encodeURIComponent(entry.record.id) + '">Audit Evidence & Packet</a>' : "") + governanceActions + lifecycleActions + issueActions + addRecordContentAction + (!state.readOnly && !routeSetLocked ? '<button class="button" id="edit-resource">Edit</button>' : "") + recordMenu + '</div></div><div class="detail-grid ' + (hasRecordBody ? "" : "detail-grid-structured") + '">' + detailMain + supportPanels + '</div></div>';
|
|
3351
|
+
main.innerHTML = '<div class="page"><div class="detail-head"><div><div class="breadcrumbs header-breadcrumbs"><a href="' + collectionHref + '">' + esc(collectionLabel) + '</a><span>/</span><span>' + esc(entry.record.title) + '</span></div><h2>' + esc(titleCase(entry.record.title)) + '</h2></div><div class="actions">' + reportingRouteSetActions + auditCycleAction + auditPopulationCorrectionAction + (type === "audit" ? '<a class="button primary" href="#/audit-packet?auditId=' + encodeURIComponent(entry.record.id) + '">Audit Evidence & Packet</a>' : "") + governanceActions + lifecycleActions + issueActions + addRecordContentAction + (!state.readOnly && !routeSetLocked ? '<button class="button" id="edit-resource">Edit</button>' : "") + recordMenu + '</div></div><div class="detail-grid ' + (hasRecordBody ? "" : "detail-grid-structured") + (type === "control" ? " detail-control" : "") + '">' + detailMain + supportPanels + '</div></div>';
|
|
3320
3352
|
main.querySelectorAll("[data-completion-requirements]").forEach((button) => button.addEventListener("click", () => openCompletionRequirementsDialog(button.dataset.completionRequirements)));
|
|
3321
3353
|
main.querySelector("#edit-resource")?.addEventListener("click", () => openEditor(type, entry));
|
|
3322
3354
|
main.querySelectorAll("[data-record-modal]").forEach((button) => button.addEventListener("click", () => {
|
|
@@ -4251,11 +4283,11 @@ function zonedTimestampFromLocal(value, timezone) {
|
|
|
4251
4283
|
return value + offset;
|
|
4252
4284
|
}
|
|
4253
4285
|
|
|
4254
|
-
function resourceGuide(type) {
|
|
4286
|
+
function resourceGuide(type, pageGuidance = null) {
|
|
4255
4287
|
const definition = state.model.resources[type];
|
|
4256
4288
|
const guidance = definition?.guidance;
|
|
4257
4289
|
if (!definition || !guidance) return "";
|
|
4258
|
-
const instructions = RESOURCE_GUIDE_INSTRUCTIONS[type] || definition.description;
|
|
4290
|
+
const instructions = pageGuidance?.instructions || RESOURCE_GUIDE_INSTRUCTIONS[type] || definition.description;
|
|
4259
4291
|
const sources = (guidance.sourceResourceIds || [])
|
|
4260
4292
|
.map((id) => state.resources.find(({ record }) => record.id === id))
|
|
4261
4293
|
.filter(Boolean);
|
|
@@ -4266,7 +4298,8 @@ function resourceGuide(type) {
|
|
|
4266
4298
|
const reviewGuide = reviewPoints.length
|
|
4267
4299
|
? '<div class="guide-review"><span>When reviewing</span><ul>' + reviewPoints.map((point) => '<li>' + esc(point) + '</li>').join("") + '</ul></div>'
|
|
4268
4300
|
: "";
|
|
4269
|
-
|
|
4301
|
+
const output = pageGuidance?.output || RESOURCE_GUIDE_OUTPUTS[type] || 'A validated ' + definition.title + ' record.';
|
|
4302
|
+
return '<section class="page-guide resource-guide-popover" id="resource-guide" role="dialog" aria-label="How to use ' + esc(definition.pluralTitle) + '" hidden><div><span>Do now</span><p>' + esc(instructions) + '</p></div><div><span>Output</span><p>' + esc(output) + '</p></div><div><span>Policy basis</span><p>' + esc(guidance.policyBasis) + '</p>' + sourceLinks + '</div>' + reviewGuide + '</section>';
|
|
4270
4303
|
}
|
|
4271
4304
|
|
|
4272
4305
|
function setupResourceGuide(main) {
|
|
@@ -5972,7 +6005,6 @@ function setNavigationGroupOpen(group, open) {
|
|
|
5972
6005
|
function groupTitle(id) { return state.model.groups.find((group) => group.id === id)?.title || "Program"; }
|
|
5973
6006
|
function fieldDefinition(type, name) { return state.model.resources[type]?.fields?.[name] || state.model.commonFields[name]; }
|
|
5974
6007
|
function fieldLabel(type, name) {
|
|
5975
|
-
if (name === "$operationTracking") return "Operation tracking";
|
|
5976
6008
|
if (name === "$workQueueStatus") return "Work Queue";
|
|
5977
6009
|
if (type === "obligation" && name === "status") return "Configuration";
|
|
5978
6010
|
if (name === "title") return state.model.resources[type]?.titleLabel || state.model.commonFields.title.label;
|
|
@@ -6083,58 +6115,49 @@ function formatObjectArray(items, objectType, compact = false) {
|
|
|
6083
6115
|
return '<div class="object-value"><span class="object-value-facts">' + facts.join("") + '</span>' + (!compact && notes.length ? '<small>' + esc(notes.join(" ")) + '</small>' : "") + '</div>';
|
|
6084
6116
|
}).join("") + '</div>';
|
|
6085
6117
|
}
|
|
6086
|
-
function
|
|
6087
|
-
|
|
6088
|
-
|
|
6089
|
-
|
|
6090
|
-
|
|
6091
|
-
return
|
|
6092
|
-
}
|
|
6093
|
-
const counts = obligations.reduce((result, obligation) => {
|
|
6094
|
-
const status = workQueueScheduleStatus(obligation, control);
|
|
6095
|
-
result[status] = (result[status] || 0) + 1;
|
|
6096
|
-
return result;
|
|
6097
|
-
}, {});
|
|
6098
|
-
const scheduleCount = obligations.length + " " + pluralize("schedule", obligations.length);
|
|
6099
|
-
if (counts.running === obligations.length) {
|
|
6100
|
-
return '<a class="operation-tracking running" href="#/stage/run"><strong>Running in Work Queue</strong><small>' + esc(scheduleCount) + '</small></a>';
|
|
6101
|
-
}
|
|
6102
|
-
if (counts["waiting-policy"] === obligations.length) {
|
|
6103
|
-
return '<a class="operation-tracking waiting" href="#/stage/run"><strong>Waiting for policy approval</strong><small>' + esc(scheduleCount) + ' enabled</small></a>';
|
|
6104
|
-
}
|
|
6105
|
-
if (counts["waiting-control"] === obligations.length) {
|
|
6106
|
-
return '<a class="operation-tracking waiting" href="#/stage/run"><strong>Ready when implemented</strong><small>' + esc(scheduleCount) + ' enabled</small></a>';
|
|
6107
|
-
}
|
|
6108
|
-
if (counts.paused === obligations.length) {
|
|
6109
|
-
return '<a class="operation-tracking paused" href="#/stage/run"><strong>Work Queue paused</strong><small>' + esc(scheduleCount) + '</small></a>';
|
|
6110
|
-
}
|
|
6111
|
-
const summary = [
|
|
6112
|
-
counts.running ? counts.running + " running" : "",
|
|
6113
|
-
counts["waiting-policy"] ? counts["waiting-policy"] + " waiting for policy" : "",
|
|
6114
|
-
counts["waiting-control"] ? counts["waiting-control"] + " waiting for implementation" : "",
|
|
6115
|
-
counts.paused ? counts.paused + " paused" : ""
|
|
6116
|
-
].filter(Boolean).join(" · ");
|
|
6117
|
-
return '<a class="operation-tracking mixed" href="#/stage/run"><strong>Work Queue needs attention</strong><small>' + esc(summary) + '</small></a>';
|
|
6118
|
-
}
|
|
6119
|
-
function workQueueScheduleStatus(obligation, control = null) {
|
|
6118
|
+
function workQueueScheduleStatus(obligation) {
|
|
6119
|
+
if (obligation.status === "proposed") {
|
|
6120
|
+
const pendingRules = resourcesOfType("obligation-rule")
|
|
6121
|
+
.map(({ record }) => record)
|
|
6122
|
+
.filter((record) => record.obligationId === obligation.id && ["proposed", "approved"].includes(record.status));
|
|
6123
|
+
return pendingRules.length === 1 && pendingRules[0].status === "approved" ? "approved" : "proposed";
|
|
6124
|
+
}
|
|
6120
6125
|
if (obligation.status === "paused") return "paused";
|
|
6121
6126
|
if (obligation.status !== "active") return obligation.status;
|
|
6127
|
+
const programStatus = state.obligations?.programStatuses?.[obligation.id];
|
|
6128
|
+
if (!programStatus) return "not-evaluated";
|
|
6129
|
+
const rule = state.resources.find(({ record }) => record.id === obligation.activeRuleId)?.record;
|
|
6130
|
+
if (obligation.scheduleMode === "rule" && rule?.status !== "active") {
|
|
6131
|
+
return "proposed";
|
|
6132
|
+
}
|
|
6133
|
+
if (rule?.effectiveAt && new Date(rule.effectiveAt) > new Date()) {
|
|
6134
|
+
const prior = state.resources.find(({ record }) => record.id === rule.supersedesId)?.record;
|
|
6135
|
+
if (prior?.obligationId !== obligation.id || !["active", "retired"].includes(prior.status)) {
|
|
6136
|
+
return "waiting-schedule-effective";
|
|
6137
|
+
}
|
|
6138
|
+
}
|
|
6122
6139
|
const asOf = state.obligations?.asOf || new Date().toISOString().slice(0, 10);
|
|
6123
6140
|
const policies = (obligation.policyIds || []).map((id) => state.resources.find(({ record }) => record.id === id)?.record);
|
|
6124
|
-
if (
|
|
6125
|
-
|
|
6126
|
-
|
|
6141
|
+
if (policies.some((policy) => policy?.type !== "policy" || !["approved", "active"].includes(policy.status))) return "waiting-policy-approval";
|
|
6142
|
+
if (policies.some((policy) => policy.status !== "active")) return "waiting-policy-activation";
|
|
6143
|
+
if (policies.some((policy) => !policy.effectiveOn || policy.effectiveOn > asOf)) return "waiting-policy-effective";
|
|
6127
6144
|
const linkedControls = (obligation.controlIds || []).map((id) => state.resources.find(({ record }) => record.id === id)?.record);
|
|
6128
|
-
const implemented = control
|
|
6129
|
-
|
|
6130
|
-
|
|
6131
|
-
return implemented ? "running" : "waiting-control";
|
|
6145
|
+
const implemented = !linkedControls.length || linkedControls.some((record) => record?.type === "control" && record.status === "implemented");
|
|
6146
|
+
if (!implemented) return "waiting-control";
|
|
6147
|
+
return programStatus === "accepted" ? "running" : "waiting-prerequisites";
|
|
6132
6148
|
}
|
|
6133
6149
|
function obligationWorkQueueStatus(obligation) {
|
|
6134
6150
|
const status = workQueueScheduleStatus(obligation);
|
|
6151
|
+
if (status === "proposed") return '<span class="operation-tracking"><strong>Proposed</strong><small>Review schedule before activation</small></span>';
|
|
6152
|
+
if (status === "approved") return '<span class="operation-tracking"><strong>Approved</strong><small>Activate schedule before operation</small></span>';
|
|
6135
6153
|
if (status === "running") return '<span class="operation-tracking running"><strong>Running</strong><small>Enabled and policy effective</small></span>';
|
|
6136
|
-
if (status === "waiting-policy") return '<span class="operation-tracking waiting"><strong>Waiting for policy</strong><small>Schedule enabled</small></span>';
|
|
6154
|
+
if (status === "waiting-policy-approval") return '<span class="operation-tracking waiting"><strong>Waiting for policy approval</strong><small>Schedule enabled</small></span>';
|
|
6155
|
+
if (status === "waiting-policy-activation") return '<span class="operation-tracking waiting"><strong>Waiting for policy activation</strong><small>Policy approved</small></span>';
|
|
6156
|
+
if (status === "waiting-policy-effective") return '<span class="operation-tracking waiting"><strong>Waiting for effective date</strong><small>Policy active</small></span>';
|
|
6157
|
+
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>';
|
|
6137
6158
|
if (status === "waiting-control") return '<span class="operation-tracking waiting"><strong>Waiting for control</strong><small>Implement a linked control</small></span>';
|
|
6159
|
+
if (status === "waiting-prerequisites") return '<span class="operation-tracking waiting"><strong>Waiting for prerequisites</strong><small>Check owner and governed content</small></span>';
|
|
6160
|
+
if (status === "not-evaluated") return '<span class="operation-tracking"><strong>Not assessed here</strong><small>Outside the selected program</small></span>';
|
|
6138
6161
|
if (status === "paused") return '<span class="operation-tracking paused"><strong>Paused</strong><small>Schedule disabled</small></span>';
|
|
6139
6162
|
return '<span class="operation-tracking"><strong>' + esc(properCase(status)) + '</strong></span>';
|
|
6140
6163
|
}
|
|
@@ -6496,6 +6519,7 @@ html,body{height:100%;overflow:hidden}.shell{grid-template-columns:248px minmax(
|
|
|
6496
6519
|
.brand{flex:0 0 auto;margin-bottom:18px}.sidebar-nav{--nav-control-width:14px;flex:1;min-height:0;overflow-x:hidden;overflow-y:auto;overscroll-behavior:contain;padding-right:1px}.nav-home{margin-bottom:10px}.nav-stage>.nav-heading{display:grid;grid-template-columns:24px minmax(0,1fr) var(--nav-control-width);gap:6px;align-items:center;padding:7px 6px;border-radius:7px;color:#d5d9ed;text-align:left;text-transform:none;letter-spacing:0}.nav-stage>.nav-heading:hover{background:rgba(255,255,255,.08);color:#fff}.nav-stage-number{display:grid;place-items:center;width:22px;height:22px;border:1px solid rgba(255,255,255,.26);border-radius:50%;font-size:9.6px}.nav-stage-copy,.nav-stage-copy strong,.nav-stage-copy small{display:block;min-width:0}.nav-stage-copy strong{font-size:12px;line-height:1.25}.nav-stage-copy small{margin-top:2px;color:#aeb6d8;font-size:9.6px;line-height:1.25;font-weight:500}.nav-stage>.nav-items{margin:2px 0 8px 17px;padding:1px 0 5px 12px;border-left:1px solid rgba(255,255,255,.14)}.nav-subgroup>.nav-subheading,.nav-subgroup>.nav-items a{width:100%;display:grid;grid-template-columns:minmax(0,1fr) var(--nav-control-width);gap:6px;align-items:center;padding-right:6px}.nav-subgroup>.nav-subheading{border:0;background:none;padding-top:7px;padding-bottom:4px;padding-left:7px;color:#919bc4;text-align:left;text-transform:uppercase;letter-spacing:.09em;font-size:9.6px;font-weight:780;cursor:pointer}.nav-control,.nav-control-slot{justify-self:end;width:var(--nav-control-width);text-align:right}.nav-subgroup>.nav-items{padding:1px 0 4px 3px}.nav-subgroup>.nav-items a{padding-top:6px;padding-bottom:6px;padding-left:8px;font-size:13.2px}.nav-group.open>.nav-heading>.chevron:before,.nav-group.open>.nav-subheading>.chevron:before{transform:rotate(45deg)}.nav-stage.open>.nav-items>.nav-subgroup:not(.open)>.nav-items{display:none}.sidebar-footer{flex:0 0 auto;margin:10px -18px -18px;padding:10px 18px 14px;background:#000024;border-top:1px solid rgba(255,255,255,.14)}.organization-nav{display:grid;grid-template-columns:32px minmax(0,1fr) 12px;gap:9px;align-items:center;padding:8px;border-radius:8px;color:#eef1ff;text-decoration:none}.organization-nav:hover,.organization-nav.current{background:rgba(255,255,255,.11)}.organization-mark{display:grid;place-items:center;width:32px;height:32px;border:1px solid rgba(255,255,255,.25);border-radius:50%;background:rgba(255,255,255,.08);font-size:13.2px;font-weight:800}.organization-nav strong,.organization-nav small{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.organization-nav strong{font-size:12px}.organization-nav small{margin-top:2px;color:#aeb6d8;font-size:9.6px}.organization-arrow{color:#aeb6d8;font-size:19.2px}.organization-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:14px}.organization-links{display:grid}.organization-links a{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:12px 0;border-top:1px solid var(--line);text-decoration:none}.organization-links a:first-child{padding-top:0;border-top:0}.organization-links strong,.organization-links small{display:block}.organization-links strong{font-size:13.2px}.organization-links small{margin-top:3px;color:var(--muted);font-size:10.8px;line-height:1.4}.organization-links b{color:var(--accent);font-size:13.2px}.organization-links a:hover strong{color:var(--accent)}
|
|
6497
6520
|
.nav-stage>.nav-items>a.nav-direct{display:grid;grid-template-columns:minmax(0,1fr) var(--nav-control-width);gap:6px;align-items:center;width:100%;padding:6px 6px 6px 7px;font-size:13.2px}
|
|
6498
6521
|
.nav-heading-row{display:grid;grid-template-columns:minmax(0,1fr) 24px;gap:2px;align-items:stretch}.nav-heading-row>.nav-heading{display:grid;grid-template-columns:24px minmax(0,1fr);gap:6px;align-items:center;width:100%;padding:7px 6px;border-radius:7px;color:#d5d9ed;text-align:left;text-transform:none;letter-spacing:0;text-decoration:none}.nav-heading-row>.nav-heading:hover,.nav-heading-row>.nav-heading.current{background:rgba(255,255,255,.1);color:#fff}.nav-toggle{display:grid;place-items:center;width:24px;height:auto;min-height:100%;padding:0;border:0;border-radius:6px;background:none;color:#aeb6d8;cursor:pointer}.nav-toggle:hover{background:rgba(255,255,255,.1);color:#fff}.nav-chevron{display:block;width:12px;height:12px;place-self:center;fill:none;stroke:currentColor;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;transition:transform .15s}.nav-subheading-row{display:grid;grid-template-columns:minmax(0,1fr) 22px;gap:2px;align-items:center;width:100%;padding:0;border:0;border-radius:6px;background:none;color:#919bc4;cursor:pointer}.nav-subheading-row:hover{background:rgba(255,255,255,.1);color:#fff}.nav-subheading-row>.nav-subheading{display:flex;align-items:center;min-width:0;padding:7px;color:inherit;text-align:left;text-transform:uppercase;letter-spacing:.09em;font-size:9.6px;font-weight:780}.nav-group.open>.nav-heading-row .nav-chevron,.nav-group.open>.nav-subheading-row .nav-chevron{transform:rotate(90deg)}
|
|
6522
|
+
.stage-instructions{margin-top:14px;padding:0 18px;background:var(--panel);border:1px solid var(--line);border-radius:10px}.stage-instructions>summary{padding:14px 0;cursor:pointer;font-size:12px;font-weight:700}.stage-instructions-body{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:18px;padding:2px 0 18px}.stage-instructions h3{margin:0 0 7px;font-size:12px}.stage-instructions ol{margin:0;padding-left:20px}.stage-instructions li{margin:0 0 6px;color:var(--muted);font-size:11px;line-height:1.5}
|
|
6499
6523
|
.stage-overview-hero{display:grid;grid-template-columns:minmax(0,1fr) 300px;gap:28px;align-items:center;padding:26px 28px;background:var(--panel);border:1px solid var(--line);border-radius:12px;box-shadow:var(--shadow)}.stage-overview-hero h2,.group-overview-head h2{font:500 37.2px Georgia,serif;margin:7px 0}.stage-overview-hero>div>p:not(.kicker),.group-overview-head>div>p:not(.kicker){max-width:760px;color:var(--muted);font-size:14.4px;line-height:1.55;margin:0}.stage-progress-card{padding:15px 17px;background:var(--paper);border:1px solid var(--line);border-radius:9px}.stage-progress-card>div:first-child{display:flex;align-items:center;justify-content:space-between;margin-bottom:11px}.stage-progress-card>div:first-child>strong{font:500 33.6px Georgia,serif}.stage-progress-card p{color:var(--muted);font-size:10.8px;line-height:1.45;margin:9px 0 0}.badge.neutral{background:#e5e8f2;color:#555e73}.stage-overview-layout{display:grid;grid-template-columns:320px minmax(0,1fr);gap:15px;margin-top:15px;align-items:start}.stage-plan ol,.group-plan ol{display:grid;gap:12px;padding-left:20px;margin:0}.stage-plan li,.group-plan li{padding-left:4px;color:var(--ink);font-size:13.2px;line-height:1.5}.stage-groups{min-width:0}.stage-groups>.section-head{margin:4px 0 13px}.stage-group-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px}.stage-group-card{position:relative;display:block;min-height:138px;padding:18px 38px 17px 18px;background:var(--panel);border:1px solid var(--line);border-radius:10px;text-decoration:none;box-shadow:0 2px 8px rgba(21,40,33,.025)}.stage-group-card:hover{border-color:var(--accent-light)}.stage-group-card h3{font-size:15.6px;margin:0 0 7px}.stage-group-card p{color:var(--muted);font-size:12px;line-height:1.5;margin:0}.stage-group-card small{display:block;color:var(--accent);font-size:9.6px;font-weight:700;margin-top:12px}.stage-group-arrow{position:absolute;right:16px;top:16px;color:var(--accent);font-size:24px}.group-overview-head{display:flex;justify-content:space-between;align-items:end;gap:25px;margin-bottom:15px}.stage-status-link{display:grid;grid-template-columns:auto auto;align-items:center;gap:4px 12px;min-width:155px;padding:12px 14px;background:var(--panel);border:1px solid var(--line);border-radius:9px;text-decoration:none}.stage-status-link>strong{font:500 28.8px Georgia,serif;text-align:right}.stage-status-link>small{grid-column:1/-1;color:var(--muted);font-size:9.6px;text-align:right}.relationship-note{display:grid;grid-template-columns:250px minmax(0,1fr);gap:20px;align-items:center;margin-bottom:15px;padding:17px 20px;background:var(--accent-soft);border:1px solid #cbd3ff;border-radius:10px}.relationship-note h3{font-size:15.6px;margin:5px 0 0}.relationship-note>p{color:var(--muted);font-size:12px;line-height:1.55;margin:0}.relationship-note code{font-size:10.8px}.group-plan{margin-bottom:24px}.group-related-links{display:flex;align-items:center;gap:8px;margin-top:18px;padding-top:14px;border-top:1px solid var(--line)}.group-related-links>span{color:var(--muted);font-size:10.8px;margin-right:auto}.group-destinations>.section-head{margin-bottom:12px}.group-destination-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:11px}.group-destination-card{display:grid;grid-template-columns:minmax(0,1fr) 100px;gap:16px;min-height:150px;padding:18px;background:var(--panel);border:1px solid var(--line);border-radius:10px;text-decoration:none;box-shadow:0 2px 8px rgba(21,40,33,.025)}.group-destination-card:hover{border-color:var(--accent-light)}.group-destination-card h3{font-size:15.6px;margin:5px 0 7px}.group-destination-card p:not(.kicker){color:var(--muted);font-size:10.8px;line-height:1.5;margin:0}.destination-rollup{align-self:center;text-align:right}.destination-rollup strong,.destination-rollup small{display:block}.destination-rollup strong{font:500 32.4px Georgia,serif}.destination-rollup small{color:var(--muted);font-size:9.6px;line-height:1.35;margin-top:3px}
|
|
6500
6524
|
.collection-review-panel{margin:16px 0 22px}.collection-review-panel.required{border-color:#d8bd78}.collection-review-panel.current{border-color:#b9dac6}.collection-review-head{display:flex;align-items:flex-start;justify-content:space-between;gap:24px}.collection-review-head h3{margin:4px 0 6px}.collection-review-head p:not(.kicker){max-width:900px;margin:0;color:var(--muted);font-size:11px;line-height:1.5}.collection-review-panel details{margin-top:13px;padding:10px 12px;border:1px solid var(--line);border-radius:7px;background:var(--surface-soft)}.collection-review-panel summary{cursor:pointer;font-size:11px;font-weight:750}.collection-review-panel ul,.collection-review-checks ul{margin:9px 0 0;padding-left:20px;color:var(--muted);font-size:10.4px;line-height:1.55}.collection-review-foot{display:flex;align-items:center;justify-content:space-between;gap:20px;margin-top:13px}.collection-review-result{display:flex;align-items:baseline;gap:8px;margin:0}.collection-review-result strong{font-size:11px}.collection-review-result span{color:var(--muted);font-size:10.4px}.collection-review-checks{margin:13px 0;padding:11px 13px;border:1px solid var(--line);border-radius:7px;background:var(--surface-soft)}.collection-review-checks>strong{font-size:11px}.event-dialog-steps.collection-review-checks{margin:15px 0 0;padding:10px;border:0}.collection-review-dialog textarea{box-sizing:border-box;width:100%;resize:vertical}.review-save-status{margin-right:auto;color:var(--muted);font-size:11px}.resource-review-criteria{margin:16px 0 22px;padding:13px 15px;border:1px solid var(--line);border-radius:8px;background:var(--panel)}.resource-review-criteria>strong{font-size:11px}.resource-review-criteria ul{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:5px 28px;margin:9px 0 0;padding-left:20px;color:var(--muted);font-size:10.4px;line-height:1.5}.commit-dialog .resource-review-criteria{margin:12px 0;background:var(--surface-soft)}.record-workflow-action{display:grid;grid-template-columns:auto minmax(140px,1fr);gap:7px;align-items:start;min-width:240px;text-decoration:none}.record-workflow-action strong,.record-workflow-action small{display:block}.record-workflow-action strong{font-size:10.4px}.record-workflow-action small{margin-top:2px;color:var(--muted);font-size:9.2px;line-height:1.35}.record-workflow-clear{color:var(--muted);font-size:10px;white-space:nowrap}.workflow-guidance{margin:16px 0 22px}.workflow-guidance .panel-head{align-items:flex-start;margin-bottom:12px}.workflow-guidance .panel-head h3{margin:4px 0}.workflow-guidance .panel-head p:not(.kicker){margin:4px 0 0;color:var(--muted);font-size:11px}.workflow-findings{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:7px}.workflow-findings>a,.workflow-findings>div{display:grid;grid-template-columns:auto minmax(0,1fr);gap:9px;align-items:start;padding:10px 11px;border:1px solid var(--line);border-radius:7px;background:var(--surface-soft);text-decoration:none}.workflow-findings strong,.workflow-findings small{display:block}.workflow-findings strong{font-size:11px}.workflow-findings small{margin-top:3px;color:var(--muted);font-size:9.6px;line-height:1.4}.workflow-finding-status{min-width:62px;padding:3px 5px;border-radius:99px;background:var(--accent-soft);color:var(--accent);font-size:8.4px;font-weight:750;text-align:center;text-transform:uppercase;letter-spacing:.04em}.workflow-finding-status.overdue,.workflow-finding-status.blocked{background:#f5ded9;color:#8d352c}.workflow-finding-status.ready,.workflow-finding-status.due,.workflow-finding-status.open{background:#f7e9cf;color:#855717}.workflow-finding-status.complete{background:#ddefe5;color:#176143}.workflow-guidance-more{margin:10px 0 0;color:var(--muted);font-size:9.6px}.workflow-guidance-more>summary{cursor:pointer;font-weight:750}.workflow-findings-more{margin-top:9px}.workflow-guidance-more>p{margin:8px 0 0}.context-workflow{display:flex;align-items:center;justify-content:space-between;gap:24px;margin:-8px 0 18px;padding:17px 19px;background:var(--accent-soft);border:1px solid #cbd3ff;border-radius:10px}.context-workflow h3{font-size:16px;margin:4px 0 5px}.context-workflow p:not(.kicker){color:var(--muted);font-size:12px;line-height:1.5;margin:0;max-width:850px}.context-workflow .button{white-space:nowrap}.policy-lifecycle-note{display:grid;grid-template-columns:minmax(0,1.5fr) minmax(260px,1fr);gap:24px;margin-top:18px}.policy-lifecycle-note h3{margin:5px 0 8px}.policy-lifecycle-note p:not(.kicker),.policy-library-proposals p{color:var(--muted);font-size:11px;line-height:1.55}.policy-library-proposals{display:grid;gap:8px}.policy-library-proposals article{padding:11px;border:1px solid var(--line);border-radius:8px;background:var(--surface-soft)}.policy-library-proposals p{margin:5px 0 8px}.policy-activation{display:grid;gap:12px;margin-top:22px}.policy-activation-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px}.policy-activation-card{padding:17px 18px;border:1px solid #d8bd78;border-radius:10px;background:var(--panel)}.policy-activation-card.active-and-operating{border-color:#b9dac6}.policy-activation-card h3{margin:7px 0 0}.policy-activation-gaps{display:grid;gap:9px;margin-top:13px}.policy-activation-gaps>div{padding-top:8px;border-top:1px solid var(--line)}.policy-activation-gaps small{display:block;margin-bottom:6px;color:var(--muted);font-size:9.6px;font-weight:700;text-transform:uppercase;letter-spacing:.05em}.policy-activation-warning{margin:11px 0 0;padding:9px 10px;border-radius:7px;background:#f7e9cf;color:#6d4917;font-size:10.4px;line-height:1.45}.evidence-map{display:grid;gap:12px;margin-top:18px}.evidence-map-head{display:flex;align-items:center;justify-content:space-between;gap:24px;padding:20px 22px;background:var(--accent-soft);border:1px solid #cbd3ff;border-radius:11px}.evidence-map-head>div:first-child{max-width:760px}.evidence-map-head h2{font:500 24px Georgia,serif;margin:5px 0 7px}.evidence-map-head p:not(.kicker){color:var(--muted);font-size:12px;line-height:1.55;margin:0}.evidence-map-actions{display:flex;gap:8px}.evidence-map-card{padding:18px 20px;background:var(--panel);border:1px solid var(--line);border-radius:10px;box-shadow:0 2px 8px rgba(21,40,33,.025)}.evidence-map-card.complete{border-color:#b9dac6}.evidence-map-card-head{display:flex;align-items:start;justify-content:space-between;gap:20px}.evidence-map-card-head h3{font-size:16px;margin:7px 0 0}.evidence-map-card-head>small{color:var(--muted);font-size:10px;text-align:right}.evidence-map-card>p{color:var(--muted);font-size:12px;line-height:1.55;margin:12px 0}.evidence-map-expectation{display:grid;grid-template-columns:120px minmax(0,1fr);gap:12px;padding:8px 0;border-top:1px solid var(--line);font-size:11px;line-height:1.5}.evidence-map-expectation strong{color:var(--muted);font-size:9.6px;text-transform:uppercase;letter-spacing:.06em}.evidence-map-expectation code{background:var(--paper);border-radius:4px;padding:2px 5px}.evidence-map-links{display:grid;grid-template-columns:minmax(220px,1fr) minmax(280px,1.2fr);gap:20px;margin-top:13px;padding-top:13px;border-top:1px solid var(--line)}.evidence-map-links>div>small{display:block;color:var(--muted);font-size:9.6px;font-weight:700;text-transform:uppercase;letter-spacing:.06em;margin-bottom:7px}.evidence-map-references,.evidence-map-sources{display:flex;flex-wrap:wrap;gap:5px}.evidence-map-source{display:flex;align-items:center;gap:7px;padding:7px 9px;background:var(--paper);border:1px solid var(--line);border-radius:7px;font-size:11px;text-decoration:none}.evidence-map-source.complete{border-color:#b9dac6;background:#edf7f1}.evidence-map-source small{color:var(--muted);font-size:9px}.evidence-map-status{padding:9px 11px;background:var(--paper);border-radius:7px}.evidence-map-empty{padding:24px;background:var(--panel);border:1px solid var(--line);border-radius:10px}.evidence-map-empty h3{margin:6px 0}.evidence-map-empty p:not(.kicker){color:var(--muted);font-size:12px;margin:0 0 14px}.stage-pages{margin-top:24px}.stage-page-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px}.stage-page-card{position:relative;display:flex;flex-direction:column;min-width:0;padding:18px;background:var(--panel);border:1px solid var(--line);border-radius:10px;box-shadow:0 2px 8px rgba(21,40,33,.025);transition:border-color .15s,box-shadow .15s}.stage-page-card:hover{border-color:var(--accent-light);box-shadow:0 5px 16px rgba(21,40,33,.07)}.stage-page-card.complete{border-color:#b9dac6}.stage-page-card-head{display:flex;align-items:center;justify-content:space-between;gap:18px}.stage-page-card-head h3{font-size:15.6px;line-height:1.35;margin:4px 0 0}.stage-page-card-head>div>small{display:block;color:var(--accent);font-size:9.6px;font-weight:700}.stage-page-card>p{color:var(--muted);font-size:12px;line-height:1.5;margin:13px 0 0}.stage-page-tasks{position:relative;z-index:2;display:grid;gap:6px;margin-top:13px}.stage-page-tasks>a{display:grid;grid-template-columns:auto minmax(0,1fr);gap:8px;align-items:start;padding:9px;border:1px solid var(--line);border-radius:7px;background:var(--surface-soft);text-decoration:none}.stage-page-tasks>a:hover{border-color:var(--accent-light)}.stage-page-tasks strong,.stage-page-tasks small{display:block}.stage-page-tasks strong{font-size:10.8px}.stage-page-tasks small{margin-top:2px;color:var(--muted);font-size:9.4px;line-height:1.35}.stage-page-tasks-more{color:var(--muted);font-size:9.4px}.stage-page-rollup{display:flex;flex:0 0 104px;flex-direction:column;justify-content:center;text-align:right}.stage-page-rollup strong,.stage-page-rollup small{display:block}.stage-page-rollup strong{font:500 24px Georgia,serif}.stage-page-rollup small{color:var(--muted);font-size:9.6px;line-height:1.35;margin-top:2px}.stage-page-card-foot{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-top:auto;padding-top:15px}.stage-page-completion-state{color:var(--muted);font-size:10.8px}.stage-page-completion-state.complete{color:#176143;font-weight:700}.stage-page-open{color:var(--accent);font-size:12px;font-weight:700}.work-queue-section{margin-top:28px}.work-queue-section>.section-head{align-items:end}
|
|
6501
6525
|
.stage-page-card-actions{display:flex;align-items:center;gap:10px}.stage-page-card-actions .stage-page-open{white-space:nowrap}.stage-page-open::after{content:"";position:absolute;inset:0;z-index:1;border-radius:10px}.stage-page-open:focus-visible::after{outline:2px solid var(--focus);outline-offset:2px}.stage-page-open>span{position:relative;z-index:2}
|
|
@@ -6679,6 +6703,7 @@ dialog::backdrop{background:rgba(0,0,24,.62)}
|
|
|
6679
6703
|
@media(max-width:760px){.record-table td:has(.relation){min-width:0}.record-table td[data-label]>.relation{grid-column:2}}
|
|
6680
6704
|
@media(max-width:760px){.diagnostics>div{grid-template-columns:max-content minmax(0,1fr)}}
|
|
6681
6705
|
.workflow-guidance .workflow-status{margin:4px 0 12px;color:var(--muted);font-size:10px}.workflow-completion-requirements{display:inline;appearance:none;-webkit-appearance:none;white-space:nowrap;padding:0;border:0;background:none;color:var(--accent);font:inherit;font-weight:750;cursor:pointer;text-decoration:underline;text-underline-offset:2px}.workflow-completion-requirements:focus-visible{outline:2px solid var(--focus);outline-offset:2px}.completion-requirements-dialog ul{margin:14px 0 0;padding-left:20px;color:var(--muted);font-size:12px;line-height:1.55}
|
|
6706
|
+
.control-next-steps{margin:8px 0 9px;padding-left:18px;font-size:12px;line-height:1.5}.control-next-steps li+li{margin-top:7px}.detail-control aside .detail-workflow-panel{max-height:none;overflow:visible}
|
|
6682
6707
|
.record-completion-state{background:var(--surface-soft)}.record-completion-state h3{margin:4px 0 5px;font-size:16.8px}.record-completion-state p:last-child{margin:0;color:var(--muted);font-size:11px;line-height:1.45}
|
|
6683
6708
|
`;
|
|
6684
6709
|
|
package/src/workflow.js
CHANGED
|
@@ -1128,6 +1128,7 @@ function normalizeFinding(code, item, context) {
|
|
|
1128
1128
|
severity: findingSeverity(state, context.assessment),
|
|
1129
1129
|
title: item.title,
|
|
1130
1130
|
message: item.message,
|
|
1131
|
+
...(item.nextSteps?.length ? { nextSteps: item.nextSteps } : {}),
|
|
1131
1132
|
...(item.resourceId ? { resourceId: item.resourceId } : {}),
|
|
1132
1133
|
...(item.createResourceType ? { createResourceType: item.createResourceType } : {}),
|
|
1133
1134
|
...(item.sourceResourceIds ? { sourceResourceIds: item.sourceResourceIds } : {}),
|