filegrc 0.15.2 → 0.15.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/package.json +1 -1
- package/src/audit-preparation.js +18 -12
- package/src/obligation-members.js +31 -0
- package/src/obligations.js +39 -6
- package/src/program-readiness.js +23 -14
- package/src/retention.js +1 -0
- package/src/validate.js +2 -12
- package/src/web.js +14 -5
- package/src/workflow.js +25 -18
package/package.json
CHANGED
package/src/audit-preparation.js
CHANGED
|
@@ -997,24 +997,30 @@ function occurrenceContinuityStage(audit, records, model, asOf) {
|
|
|
997
997
|
export function occurrenceProgressUnits(plan, { selectedControlIds, periodStart, periodThrough }) {
|
|
998
998
|
return [
|
|
999
999
|
...plan.calendarItems.flatMap((occurrence) => {
|
|
1000
|
-
const
|
|
1000
|
+
const resolvedMemberIds = new Set(occurrence.resolvedMemberIds || occurrence.completedMemberIds || []);
|
|
1001
|
+
const membersComplete = occurrence.membershipFinal && (occurrence.expectedMemberIds || [])
|
|
1002
|
+
.every((resourceId) => resolvedMemberIds.has(resourceId));
|
|
1001
1003
|
const conclusionComplete = occurrence.reconciliationStatus === "reconciled"
|
|
1002
|
-
&& ["complete", "complete-with-exceptions", "zero-population"].includes(occurrence.operatingResult)
|
|
1004
|
+
&& ["complete", "complete-with-exceptions", "zero-population"].includes(occurrence.operatingResult)
|
|
1005
|
+
&& membersComplete
|
|
1006
|
+
&& occurrence.conclusionValid !== false;
|
|
1003
1007
|
const occurrenceComplete = conclusionComplete || occurrence.legacySchedule && occurrence.status === "complete";
|
|
1004
|
-
const
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
return
|
|
1011
|
-
|
|
1008
|
+
const workComplete = occurrenceComplete || membersComplete;
|
|
1009
|
+
const workUnit = {
|
|
1010
|
+
id: `occurrence-${occurrence.occurrenceKey}-work`,
|
|
1011
|
+
status: workComplete ? "complete" : "action",
|
|
1012
|
+
title: `${occurrence.title} occurrence work`
|
|
1013
|
+
};
|
|
1014
|
+
return occurrence.legacySchedule
|
|
1015
|
+
? [workUnit]
|
|
1016
|
+
: [
|
|
1017
|
+
workUnit,
|
|
1012
1018
|
{
|
|
1013
1019
|
id: `occurrence-${occurrence.occurrenceKey}-reconciliation`,
|
|
1014
|
-
status: occurrenceComplete ? "complete" :
|
|
1020
|
+
status: occurrenceComplete ? "complete" : workComplete && occurrence.membershipFinal ? "action" : "blocked",
|
|
1015
1021
|
title: `${occurrence.title} reconciliation`
|
|
1016
1022
|
}
|
|
1017
|
-
|
|
1023
|
+
];
|
|
1018
1024
|
}),
|
|
1019
1025
|
...plan.eventRuns
|
|
1020
1026
|
.filter((run) => run.status !== "canceled" && run.occurredOn >= periodStart && run.occurredOn <= periodThrough)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export function occurrenceMemberExceptionIsValid(member, occurrence, byId) {
|
|
2
|
+
const exception = byId.get(member.exceptionId);
|
|
3
|
+
const occurrenceStart = occurrence.coverage?.kind === "as-of"
|
|
4
|
+
? occurrence.coverage.on
|
|
5
|
+
: occurrence.coverage?.startsOn;
|
|
6
|
+
const occurrenceEnd = occurrence.coverage?.kind === "as-of"
|
|
7
|
+
? occurrence.coverage.on
|
|
8
|
+
: occurrence.coverage?.endsOn;
|
|
9
|
+
const exceptionCoversMember = exception?.scopeResourceIds?.includes(member.resourceId)
|
|
10
|
+
|| exception?.scopeResourceIds?.includes(occurrence.obligationId);
|
|
11
|
+
const statusCoveredOccurrence = exception?.status === "approved"
|
|
12
|
+
|| ["revoked", "closed"].includes(exception?.status)
|
|
13
|
+
&& exception.resolution?.resolvedOn > occurrenceEnd;
|
|
14
|
+
return Boolean(
|
|
15
|
+
exception?.type === "exception"
|
|
16
|
+
&& statusCoveredOccurrence
|
|
17
|
+
&& exceptionCoversMember
|
|
18
|
+
&& exception.approval?.approvedOn <= occurrenceStart
|
|
19
|
+
&& exception.approval?.expiresOn >= occurrenceEnd
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function occurrenceMemberIsResolved(member, occurrence, byId, expectedCompletionIsValid = () => false) {
|
|
24
|
+
if (member.disposition === "expected") {
|
|
25
|
+
return member.result === "passed"
|
|
26
|
+
&& (member.completionResourceIds || []).some((id) => expectedCompletionIsValid(id, member));
|
|
27
|
+
}
|
|
28
|
+
if (member.disposition === "exception") return occurrenceMemberExceptionIsValid(member, occurrence, byId);
|
|
29
|
+
if (member.disposition === "not-applicable") return Boolean(String(member.rationale || "").trim());
|
|
30
|
+
return false;
|
|
31
|
+
}
|
package/src/obligations.js
CHANGED
|
@@ -29,6 +29,7 @@ import { serializeWorkspaceMutation } from "./mutation.js";
|
|
|
29
29
|
import { collectionReviewRevision, historicalCollectionReviewSnapshot } from "./collection-review-integrity.js";
|
|
30
30
|
import { bindAttestationReportingRouteSet, reportingRouteRevision } from "./reporting-route-integrity.js";
|
|
31
31
|
import { selectScopedCollectionRecords } from "./collection-scope.js";
|
|
32
|
+
import { occurrenceMemberIsResolved } from "./obligation-members.js";
|
|
32
33
|
|
|
33
34
|
const COMPLETION_DATE_FIELDS = [
|
|
34
35
|
"completedOn",
|
|
@@ -214,8 +215,22 @@ export function planObligations(resources, options = {}) {
|
|
|
214
215
|
const completedMemberIds = new Set((reconciliation?.members || [])
|
|
215
216
|
.filter((member) => member.result === "passed" && member.disposition === "expected")
|
|
216
217
|
.map((member) => member.resourceId));
|
|
218
|
+
const resolvedMemberIds = new Set((reconciliation?.members || [])
|
|
219
|
+
.filter((member) => occurrenceMemberIsResolved(member, reconciliation, byId, (completionId) => {
|
|
220
|
+
const completion = byId.get(completionId);
|
|
221
|
+
return completionTypeMatches(completion, expectedCompletionTypes)
|
|
222
|
+
&& completionMatchesMember(completion, activity.aggregate?.completionMemberField, member.resourceId)
|
|
223
|
+
&& completionFallsInWindow(completion, window, rule?.timezone || workspace?.timezone || "UTC")
|
|
224
|
+
&& completionPassesActivity(completion, activity);
|
|
225
|
+
}))
|
|
226
|
+
.map((member) => member.resourceId));
|
|
227
|
+
const resolutionsComplete = membershipFinal
|
|
228
|
+
&& expectedMemberIds.every((resourceId) => resolvedMemberIds.has(resourceId));
|
|
229
|
+
const conclusionValid = ["complete", "complete-with-exceptions", "zero-population"].includes(reconciliation?.conclusion)
|
|
230
|
+
&& resolutionsComplete;
|
|
217
231
|
const successful = reconciliation
|
|
218
|
-
? reconciliation.status === "reconciled"
|
|
232
|
+
? reconciliation.status === "reconciled"
|
|
233
|
+
&& conclusionValid
|
|
219
234
|
: legacyCompletions.length > 0;
|
|
220
235
|
const timingStatus = occurrenceStatus(window, asOf, successful);
|
|
221
236
|
const status = timingStatus === "complete" || programStatus === "accepted" ? timingStatus : "proposed";
|
|
@@ -244,6 +259,9 @@ export function planObligations(resources, options = {}) {
|
|
|
244
259
|
completionResourceIds,
|
|
245
260
|
expectedMemberIds,
|
|
246
261
|
completedMemberIds: [...completedMemberIds],
|
|
262
|
+
resolvedMemberIds: [...resolvedMemberIds],
|
|
263
|
+
resolutionsComplete,
|
|
264
|
+
conclusionValid,
|
|
247
265
|
expectedCount: reconciliation?.status === "open" && !membershipFinal
|
|
248
266
|
? expectedMemberIds.length
|
|
249
267
|
: reconciliation?.expectedCount ?? expectedMemberIds.length,
|
|
@@ -282,8 +300,20 @@ export function planObligations(resources, options = {}) {
|
|
|
282
300
|
const completedMemberIds = (occurrence.members || [])
|
|
283
301
|
.filter(({ disposition, result }) => disposition === "expected" && result === "passed")
|
|
284
302
|
.map(({ resourceId }) => resourceId);
|
|
303
|
+
const resolvedMemberIds = (occurrence.members || [])
|
|
304
|
+
.filter((member) => occurrenceMemberIsResolved(member, occurrence, byId, (completionId) => {
|
|
305
|
+
const completion = byId.get(completionId);
|
|
306
|
+
return completionTypeMatches(completion, activity.completionResourceTypes)
|
|
307
|
+
&& completionMatchesMember(completion, activity.aggregate?.completionMemberField, member.resourceId)
|
|
308
|
+
&& completionFallsInWindow(completion, window, rule.timezone || workspace?.timezone || "UTC")
|
|
309
|
+
&& completionPassesActivity(completion, activity);
|
|
310
|
+
}))
|
|
311
|
+
.map(({ resourceId }) => resourceId);
|
|
312
|
+
const resolutionsComplete = (occurrence.members || []).every(({ resourceId }) => resolvedMemberIds.includes(resourceId));
|
|
313
|
+
const conclusionValid = ["complete", "complete-with-exceptions", "zero-population"].includes(occurrence.conclusion)
|
|
314
|
+
&& resolutionsComplete;
|
|
285
315
|
const successful = occurrence.status === "reconciled"
|
|
286
|
-
&&
|
|
316
|
+
&& conclusionValid;
|
|
287
317
|
const timingStatus = occurrenceStatus(window, asOf, successful);
|
|
288
318
|
if (timingStatus === "complete" && !options.includeComplete) continue;
|
|
289
319
|
calendarItems.push({
|
|
@@ -307,6 +337,9 @@ export function planObligations(resources, options = {}) {
|
|
|
307
337
|
completionResourceIds: [...new Set((occurrence.members || []).flatMap(({ completionResourceIds = [] }) => completionResourceIds))],
|
|
308
338
|
expectedMemberIds: (occurrence.members || []).map(({ resourceId }) => resourceId),
|
|
309
339
|
completedMemberIds,
|
|
340
|
+
resolvedMemberIds,
|
|
341
|
+
resolutionsComplete,
|
|
342
|
+
conclusionValid,
|
|
310
343
|
expectedCount: occurrence.expectedCount,
|
|
311
344
|
completedCount: occurrence.completedCount,
|
|
312
345
|
membershipFinal: true,
|
|
@@ -1750,8 +1783,8 @@ function plannedCompletionWindow(window) {
|
|
|
1750
1783
|
};
|
|
1751
1784
|
}
|
|
1752
1785
|
|
|
1753
|
-
function completionFallsInWindow(record, window) {
|
|
1754
|
-
const date = completionDate(record);
|
|
1786
|
+
function completionFallsInWindow(record, window, timezone = "UTC") {
|
|
1787
|
+
const date = completionDate(record, timezone);
|
|
1755
1788
|
return Boolean(date && date >= window.dueWindowStart && date <= window.dueWindowEnd);
|
|
1756
1789
|
}
|
|
1757
1790
|
|
|
@@ -1765,11 +1798,11 @@ function completionTypeMatches(record, expectedTypes = []) {
|
|
|
1765
1798
|
return Boolean(record && (expectedTypes.length === 0 || expectedTypes.includes(record.type)));
|
|
1766
1799
|
}
|
|
1767
1800
|
|
|
1768
|
-
function completionDate(record) {
|
|
1801
|
+
function completionDate(record, timezone = "UTC") {
|
|
1769
1802
|
const coverageDate = coverageEnd(record.coverage);
|
|
1770
1803
|
if (parseCalendarDate(coverageDate)) return coverageDate;
|
|
1771
1804
|
for (const field of COMPLETION_TIMESTAMP_FIELDS) {
|
|
1772
|
-
if (isRfc3339Timestamp(record[field])) return record[field]
|
|
1805
|
+
if (isRfc3339Timestamp(record[field])) return currentCalendarDate(timezone, new Date(record[field]));
|
|
1773
1806
|
}
|
|
1774
1807
|
for (const field of COMPLETION_DATE_FIELDS) {
|
|
1775
1808
|
if (parseCalendarDate(record[field])) return record[field];
|
package/src/program-readiness.js
CHANGED
|
@@ -707,15 +707,34 @@ function reportingRouteItem(records, byId, asOf, timezone = "UTC") {
|
|
|
707
707
|
);
|
|
708
708
|
}
|
|
709
709
|
|
|
710
|
-
function collectionReviewReadinessItem(assessment) {
|
|
710
|
+
export function collectionReviewReadinessItem(assessment) {
|
|
711
711
|
const proposalUnits = (assessment.recordProposals || []).map((proposal) => ({
|
|
712
712
|
id: `${assessment.resourceType}-proposal-${proposal.resourceId}`,
|
|
713
713
|
status: proposal.complete ? "complete" : "action",
|
|
714
714
|
title: `${proposal.title} proposal`
|
|
715
715
|
}));
|
|
716
|
+
const proposalBatchComplete = proposalUnits.every(({ status }) => status === "complete");
|
|
717
|
+
const scheduleApprovalBlocked = assessment.resourceType === "retention-schedule-item"
|
|
718
|
+
&& proposalUnits.some(({ status }) => status !== "complete");
|
|
719
|
+
const batchProgressUnits = assessment.resourceType !== "retention-schedule-item" && proposalUnits.length
|
|
720
|
+
? [
|
|
721
|
+
{
|
|
722
|
+
id: `${assessment.resourceType}-proposal-batch`,
|
|
723
|
+
status: proposalBatchComplete ? "complete" : "action",
|
|
724
|
+
title: `${assessment.configuration.title} proposals`
|
|
725
|
+
},
|
|
726
|
+
{
|
|
727
|
+
id: `${assessment.resourceType}-collection-review`,
|
|
728
|
+
status: proposalBatchComplete
|
|
729
|
+
? assessment.complete ? "complete" : "action"
|
|
730
|
+
: "blocked",
|
|
731
|
+
title: `${assessment.configuration.title} collection review`
|
|
732
|
+
}
|
|
733
|
+
]
|
|
734
|
+
: null;
|
|
716
735
|
return item(
|
|
717
736
|
`collection-review-${assessment.resourceType}`,
|
|
718
|
-
assessment.complete ? "complete" : "action",
|
|
737
|
+
assessment.complete ? "complete" : scheduleApprovalBlocked ? "blocked" : "action",
|
|
719
738
|
assessment.status === "stale"
|
|
720
739
|
? `Review ${assessment.configuration.title.toLowerCase()} again`
|
|
721
740
|
: `Review ${assessment.configuration.title.toLowerCase()}`,
|
|
@@ -724,18 +743,7 @@ function collectionReviewReadinessItem(assessment) {
|
|
|
724
743
|
{
|
|
725
744
|
resourceType: assessment.resourceType,
|
|
726
745
|
reviewPoints: assessment.configuration.reviewPoints,
|
|
727
|
-
...(
|
|
728
|
-
progressUnits: [
|
|
729
|
-
...proposalUnits,
|
|
730
|
-
{
|
|
731
|
-
id: `${assessment.resourceType}-collection-review`,
|
|
732
|
-
status: proposalUnits.every(({ status }) => status === "complete")
|
|
733
|
-
? assessment.complete ? "complete" : "action"
|
|
734
|
-
: "blocked",
|
|
735
|
-
title: `${assessment.configuration.title} collection review`
|
|
736
|
-
}
|
|
737
|
-
]
|
|
738
|
-
} : {}),
|
|
746
|
+
...(batchProgressUnits ? { progressUnits: batchProgressUnits } : {}),
|
|
739
747
|
commands: [
|
|
740
748
|
`npx filegrc review-collection ${assessment.resourceType} --scaffold`,
|
|
741
749
|
`npx filegrc review-collection ${assessment.resourceType} REVIEW.json --preview --json`
|
|
@@ -1646,6 +1654,7 @@ async function evidenceSourcesStage(scope, byId, model, readMarkdown) {
|
|
|
1646
1654
|
: `${coveredControls.length} of ${family.controls.length} selected controls have an active authoritative ${componentSources ? "Component" : "System"} with the required source role, access owners, and extraction instructions.`,
|
|
1647
1655
|
completeSources[0] || selectedSources[0] || { type: sourceType },
|
|
1648
1656
|
{
|
|
1657
|
+
progressUnit: false,
|
|
1649
1658
|
familyId: family.id,
|
|
1650
1659
|
sourceKinds: family.sourceKinds,
|
|
1651
1660
|
controlIds: family.controls.map((control) => control.id),
|
package/src/retention.js
CHANGED
|
@@ -137,6 +137,7 @@ export async function assessRetentionReadiness(loaded, program, options = {}) {
|
|
|
137
137
|
: `${duplicates.length} similar pair${duplicates.length === 1 ? " needs" : "s need"} management review. FileGRC will not merge records or rewrite relationships automatically.`,
|
|
138
138
|
{ type: "information-type" },
|
|
139
139
|
{
|
|
140
|
+
progressUnit: false,
|
|
140
141
|
candidates: duplicates,
|
|
141
142
|
commands: [
|
|
142
143
|
"npx filegrc list information-type --workflow --json",
|
package/src/validate.js
CHANGED
|
@@ -44,6 +44,7 @@ import {
|
|
|
44
44
|
recordsAtRevision
|
|
45
45
|
} from "./reporting-route-integrity.js";
|
|
46
46
|
import { validateWorkflowHistoryIntegrity } from "./workflow-history-integrity.js";
|
|
47
|
+
import { occurrenceMemberExceptionIsValid } from "./obligation-members.js";
|
|
47
48
|
|
|
48
49
|
let fingerprintFileReadObserver = null;
|
|
49
50
|
|
|
@@ -667,18 +668,7 @@ function validateObligationOccurrence(record, model, resources, entries, root, w
|
|
|
667
668
|
));
|
|
668
669
|
}
|
|
669
670
|
if (member.disposition === "exception") {
|
|
670
|
-
|
|
671
|
-
const occurrenceStart = record.coverage?.kind === "as-of" ? record.coverage.on : record.coverage?.startsOn;
|
|
672
|
-
const occurrenceEnd = record.coverage?.kind === "as-of" ? record.coverage.on : record.coverage?.endsOn;
|
|
673
|
-
const exceptionCoversMember = exception?.scopeResourceIds?.includes(member.resourceId)
|
|
674
|
-
|| exception?.scopeResourceIds?.includes(record.obligationId);
|
|
675
|
-
if (
|
|
676
|
-
exception?.type !== "exception"
|
|
677
|
-
|| exception.status !== "approved"
|
|
678
|
-
|| !exceptionCoversMember
|
|
679
|
-
|| exception.approval?.approvedOn > occurrenceStart
|
|
680
|
-
|| exception.approval?.expiresOn < occurrenceEnd
|
|
681
|
-
) {
|
|
671
|
+
if (!occurrenceMemberExceptionIsValid(member, record, byId)) {
|
|
682
672
|
diagnostics.push(error(
|
|
683
673
|
"invalid-member-exception",
|
|
684
674
|
path,
|
package/src/web.js
CHANGED
|
@@ -670,7 +670,8 @@ function renderRetentionSchedulePage(main, params = new URLSearchParams()) {
|
|
|
670
670
|
const guideTrigger = '<button class="guide-trigger" id="resource-guide-trigger" type="button" aria-label="About Data Retention Schedule" 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>';
|
|
671
671
|
const initialFilter = params.get("q") || "";
|
|
672
672
|
const showHistory = params.get("history") === "1";
|
|
673
|
-
const
|
|
673
|
+
const retiredRows = rows.filter(({ record }) => ["superseded", "retired"].includes(record.status)).length;
|
|
674
|
+
const tableTools = '<div class="retention-table-tools"><label><span class="sr-only">Filter schedule rows</span><input type="search" data-retention-search placeholder="Filter schedule rows" value="' + esc(initialFilter) + '"></label>' + (retiredRows ? '<label class="history-toggle"><input type="checkbox" data-retention-history ' + (showHistory ? "checked" : "") + '> Show ' + retiredRows + ' retired ' + pluralize("row", retiredRows) + '</label>' : '<input type="checkbox" data-retention-history hidden>') + '</div>';
|
|
674
675
|
main.innerHTML = '<div class="page retention-schedule-page"><nav class="breadcrumbs"><a href="#/stage/policies">Step 2</a><span>/</span><span>Data Retention Schedule</span></nav>' +
|
|
675
676
|
'<div class="page-intro"><div><p class="kicker">Step 2</p><div class="page-title-line"><h2>Data Retention Schedule</h2>' + guideTrigger + '</div><p>Owners finish each proposed retention row first. A separate reviewer then approves the complete document and all proposed rows once.</p></div><div class="actions">' + documentAction + add + '</div></div>' + resourceGuide("retention-schedule-item") +
|
|
676
677
|
'<div class="retention-summary"><div><span class="retention-summary-label">Governing document</span>' + documentSummary + '</div><div><span class="retention-summary-label">Schedule revision</span><span><strong>' + proposedRows + ' completed ' + pluralize("proposal", proposedRows) + '</strong><small>Final approval follows every row proposal</small></span><span class="badge ' + (state.collectionReviews?.["retention-schedule-item"]?.status === "current" ? "good" : "warn") + '">' + (state.collectionReviews?.["retention-schedule-item"]?.status === "current" ? "Approved" : "Approval needed") + '</span></div></div>' +
|
|
@@ -746,7 +747,13 @@ function wireRetentionScheduleTable(total, initialPage = 1) {
|
|
|
746
747
|
noResults.hidden = filtered.length !== 0 || total === 0;
|
|
747
748
|
const firstVisible = filtered.length ? start + 1 : 0;
|
|
748
749
|
const lastVisible = Math.min(start + LIST_PAGE_SIZE, filtered.length);
|
|
749
|
-
|
|
750
|
+
const hiddenByHistory = historyToggle.checked ? 0 : [...table.querySelectorAll('[data-retention-row][data-history="true"]')]
|
|
751
|
+
.filter((row) => !query || row.textContent.toLowerCase().includes(query)).length;
|
|
752
|
+
const matching = filtered.length + hiddenByHistory;
|
|
753
|
+
count.textContent = firstVisible + "–" + lastVisible + " of " + filtered.length + (historyToggle.checked ? " shown " : " active ") + pluralize("row", filtered.length)
|
|
754
|
+
+ (hiddenByHistory ? " · " + hiddenByHistory + " retired hidden" : "")
|
|
755
|
+
+ (query ? " · " + matching + " matching" : "")
|
|
756
|
+
+ " · " + total + " total";
|
|
750
757
|
pagination.hidden = totalPages === 1;
|
|
751
758
|
pageStatus.textContent = "Page " + pageNumber + " of " + totalPages;
|
|
752
759
|
previous.disabled = pageNumber === 1;
|
|
@@ -1765,7 +1772,7 @@ function stagePageCard(stage, destination, index) {
|
|
|
1765
1772
|
return '<a href="' + href + '"><span class="workflow-finding-status ' + esc(item.state) + '">' + esc(properCase(item.state)) + '</span><span><strong>' + esc(item.title) + '</strong><small>' + esc(stagePageItemDetail(item)) + '</small></span></a>';
|
|
1766
1773
|
}).join("") + (items.length > 3 ? '<small class="stage-page-tasks-more">+' + (items.length - 3) + ' more on this page</small>' : "") + '</div>'
|
|
1767
1774
|
: "";
|
|
1768
|
-
return '<article class="stage-page-card ' + (complete ? "complete" : "") + '"><
|
|
1775
|
+
return '<article class="stage-page-card ' + (complete ? "complete" : "") + '"><div class="stage-page-card-head"><div><small>' + esc(stepLabel) + '</small><h3>' + esc(destination.label) + '</h3></div><div class="stage-page-card-actions">' + completionState + '<a class="stage-page-open" href="' + destination.href + '" aria-label="Open ' + esc(destination.label) + '"><span>Open ›</span></a></div></div><p>' + esc(summary) + '</p>' + taskPreview + '</article>';
|
|
1769
1776
|
}
|
|
1770
1777
|
|
|
1771
1778
|
function stagePageItemDetail(item) {
|
|
@@ -1891,7 +1898,7 @@ function stagePageItems(stage, destination) {
|
|
|
1891
1898
|
}
|
|
1892
1899
|
|
|
1893
1900
|
function workflowUiStage(stageId) {
|
|
1894
|
-
return
|
|
1901
|
+
return ["operate", "operation"].includes(stageId) ? "run" : stageId;
|
|
1895
1902
|
}
|
|
1896
1903
|
|
|
1897
1904
|
function operationProgress() {
|
|
@@ -6483,11 +6490,13 @@ html,body{height:100%;overflow:hidden}.shell{grid-template-columns:248px minmax(
|
|
|
6483
6490
|
.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}
|
|
6484
6491
|
.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)}
|
|
6485
6492
|
.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}
|
|
6486
|
-
.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-link{position:absolute;inset:0;z-index:1;border-radius:10px}.stage-page-card-link:focus-visible{outline:2px solid var(--focus);outline-offset:2px}.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}
|
|
6493
|
+
.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}
|
|
6494
|
+
.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}
|
|
6487
6495
|
.workflow-findings>a:hover{border-color:var(--accent-light);box-shadow:0 3px 9px rgba(21,40,33,.05)}.workflow-findings>a:focus-visible{outline:2px solid var(--focus);outline-offset:2px}.stage-page-card-head{align-items:flex-start}.stage-page-completion-state{flex:0 0 auto;max-width:150px;padding:5px 8px;border-radius:99px;background:#f7e9cf;color:#855717;font-size:9.6px;font-weight:750;line-height:1.25;text-align:right}.stage-page-completion-state.complete{background:#ddefe5;color:#176143}.obligation-card.workflow-target{outline:2px solid var(--focus);outline-offset:3px}.obligation-card-foot{flex-wrap:wrap}.obligation-card-foot .obligation-links{flex:1 1 120px}
|
|
6488
6496
|
.collection-review-panel.current details{margin-top:8px}.collection-review-detail-content{margin-top:10px}.collection-review-complete-summary{display:flex;align-items:baseline;gap:10px}.collection-review-complete-summary>.kicker{margin:0;white-space:nowrap}.collection-review-panel.current .collection-review-foot{justify-content:flex-end;margin-top:8px}
|
|
6489
6497
|
.collection-review-current-row{display:flex;align-items:center;gap:10px;flex-wrap:wrap}.collection-review-panel.current .collection-review-details{flex:0 0 auto;margin:0;padding:0;border:0;background:transparent}.collection-review-panel.current .collection-review-details>summary{color:var(--accent);white-space:nowrap}.collection-review-panel.current .collection-review-details[open]{flex-basis:100%;order:3;padding:10px 12px;border:1px solid var(--line);border-radius:7px;background:var(--surface-soft)}.collection-review-panel.current .collection-review-details[open]>summary{margin-bottom:10px}.collection-review-panel.current .collection-review-details[open] .collection-review-detail-content{margin-top:0}.collection-review-panel.current .button{margin-left:auto}
|
|
6490
6498
|
@media(max-width:520px){.collection-review-complete-summary{flex:1 1 180px;min-width:0;flex-wrap:wrap}.collection-review-complete-summary .collection-review-result{min-width:0;flex-wrap:wrap}.collection-review-complete-summary .collection-review-result span{overflow-wrap:anywhere}.collection-review-panel.current .button{margin-left:0}}
|
|
6499
|
+
@media(max-width:520px){.stage-page-card-head{align-items:stretch;flex-direction:column;gap:10px}.stage-page-card-actions{justify-content:space-between}}
|
|
6491
6500
|
.evidence-attachments .panel-head{align-items:flex-start}.evidence-attachments .panel-head h3{margin:0}.evidence-attachments .panel-head p{margin:4px 0 0;color:var(--muted);font-size:10px}.evidence-attachments ul{list-style:none;margin:0;padding:0}.evidence-attachments li{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:9px 0;border-top:1px solid var(--line)}.evidence-attachments li:first-child{border-top:0}.evidence-attachments strong,.evidence-attachments small{display:block}.evidence-attachments strong{font-size:11px}.evidence-attachments small{margin-top:2px;color:var(--muted);font-size:9px;overflow-wrap:anywhere}.evidence-attachments .attachment-empty{display:block;color:var(--muted);font-size:10px;line-height:1.45}.danger-text{color:var(--red)}
|
|
6492
6501
|
.policy-lifecycle-note.single{grid-template-columns:1fr}
|
|
6493
6502
|
.policy-content-table{margin-top:22px}.policy-content-table>.section-head{align-items:end}.policy-content-table>.section-head h2{font:500 27.6px Georgia,serif;margin:5px 0 6px}.policy-content-table>.section-head p:not(.kicker){max-width:720px;margin:0;color:var(--muted);font-size:12px;line-height:1.5}.policy-content-tools{display:flex;align-items:center;justify-content:flex-end;gap:9px;flex-wrap:wrap}.policy-content-tools input[type="search"]{min-width:210px;border:1px solid var(--line);border-radius:7px;background:var(--field);padding:9px 11px}.history-toggle{display:flex;align-items:center;gap:6px;color:var(--muted);font-size:11px}.policy-content-add{position:relative}.policy-content-add>summary{list-style:none}.policy-content-add>summary::-webkit-details-marker{display:none}.policy-content-add>div{position:absolute;right:0;z-index:5;display:grid;min-width:150px;margin-top:6px;padding:5px;border:1px solid var(--line);border-radius:8px;background:var(--panel);box-shadow:var(--shadow)}.policy-content-add button{border:0;border-radius:5px;background:transparent;padding:8px 10px;color:var(--ink);text-align:left;cursor:pointer}.policy-content-add button:hover{background:var(--accent-soft)}.policy-content-type span,.policy-content-approval span{display:block}.policy-content-type small,.policy-content-approval small{display:block;margin-top:3px;color:var(--muted);font-size:9.6px}.policy-content-filter-empty{margin:10px 0 0;padding:16px;border:1px dashed var(--line);border-radius:8px;color:var(--muted);font-size:12px;text-align:center}.policy-content-count{margin:9px 2px 0;color:var(--muted);font-size:10.8px;text-align:right}
|
package/src/workflow.js
CHANGED
|
@@ -27,6 +27,8 @@ import { resolveProgram } from "./program.js";
|
|
|
27
27
|
import { resourceReviewRevisions, retentionReviewResourceIds, retentionRuleIsCurrent } from "./retention.js";
|
|
28
28
|
import { signatoryAppointmentIssue, soc2ReportEvidenceIssue, subsequentEventsReviewIssue } from "./soc2.js";
|
|
29
29
|
import { planReconciliation } from "./reconciliation.js";
|
|
30
|
+
import { documentIsAuditSpecific } from "./program-lifecycle.js";
|
|
31
|
+
import { resourceProgramContext } from "./program-path.js";
|
|
30
32
|
import { currentCalendarDate } from "./time.js";
|
|
31
33
|
import { measureTiming } from "./timing.js";
|
|
32
34
|
import { validateWorkspace } from "./validate.js";
|
|
@@ -388,10 +390,10 @@ function recordFinalizationFindings(loaded, program) {
|
|
|
388
390
|
if (["workspace", "renderer-settings"].includes(record.type)) continue;
|
|
389
391
|
const incomplete = recordIncompleteReason(record, loaded, program);
|
|
390
392
|
if (incomplete) {
|
|
391
|
-
findings.push(finalizationFinding(record, incomplete));
|
|
393
|
+
findings.push(finalizationFinding(record, incomplete, loaded.model));
|
|
392
394
|
}
|
|
393
395
|
for (const missing of finalizationFields(record, loaded.model, loaded.resources, program)) {
|
|
394
|
-
findings.push(fieldFinding(record, missing));
|
|
396
|
+
findings.push(fieldFinding(record, missing, loaded.model));
|
|
395
397
|
}
|
|
396
398
|
}
|
|
397
399
|
return findings;
|
|
@@ -591,13 +593,13 @@ function finalizationFields(record, model, resources, program) {
|
|
|
591
593
|
return fields;
|
|
592
594
|
}
|
|
593
595
|
|
|
594
|
-
function finalizationFinding(record, details) {
|
|
596
|
+
function finalizationFinding(record, details, model) {
|
|
595
597
|
const code = `record.${record.type}.${record.id}.finalize`;
|
|
596
598
|
return {
|
|
597
599
|
key: code,
|
|
598
600
|
code,
|
|
599
|
-
assessment: recordAssessment(record
|
|
600
|
-
stage: recordStage(record
|
|
601
|
+
assessment: recordAssessment(record, model),
|
|
602
|
+
stage: recordStage(record, model),
|
|
601
603
|
state: details.state,
|
|
602
604
|
severity: details.state === "blocked" ? "error" : "warning",
|
|
603
605
|
requiredness: details.requiredness,
|
|
@@ -609,13 +611,13 @@ function finalizationFinding(record, details) {
|
|
|
609
611
|
};
|
|
610
612
|
}
|
|
611
613
|
|
|
612
|
-
function fieldFinding(record, missing) {
|
|
614
|
+
function fieldFinding(record, missing, model) {
|
|
613
615
|
const code = `record.${record.type}.${record.id}.field.${missing.field}`;
|
|
614
616
|
return {
|
|
615
617
|
key: code,
|
|
616
618
|
code,
|
|
617
|
-
assessment: recordAssessment(record
|
|
618
|
-
stage: recordStage(record
|
|
619
|
+
assessment: recordAssessment(record, model),
|
|
620
|
+
stage: recordStage(record, model),
|
|
619
621
|
state: "ready",
|
|
620
622
|
severity: "warning",
|
|
621
623
|
requiredness: missing.requiredness,
|
|
@@ -1732,22 +1734,27 @@ function shellArgument(value) {
|
|
|
1732
1734
|
: `'${text.replaceAll("'", "'\\''")}'`;
|
|
1733
1735
|
}
|
|
1734
1736
|
|
|
1735
|
-
function recordAssessment(
|
|
1736
|
-
|
|
1737
|
-
if (
|
|
1737
|
+
function recordAssessment(record, model) {
|
|
1738
|
+
const stage = workflowStageForRecord(record, model);
|
|
1739
|
+
if (stage === "audit") return "audit-readiness";
|
|
1740
|
+
if (stage === "operate") return "period-health";
|
|
1738
1741
|
return "program-configuration";
|
|
1739
1742
|
}
|
|
1740
1743
|
|
|
1741
|
-
function
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
if (
|
|
1747
|
-
if (
|
|
1744
|
+
export function workflowStageForRecord(record, model) {
|
|
1745
|
+
const type = record.type;
|
|
1746
|
+
if (documentIsAuditSpecific(record, model)) return "audit";
|
|
1747
|
+
if (type === "source-coverage") return "controls";
|
|
1748
|
+
const stage = resourceProgramContext(type, model)?.id;
|
|
1749
|
+
if (stage === "run") return "operate";
|
|
1750
|
+
if (stage) return stage;
|
|
1748
1751
|
return "operate";
|
|
1749
1752
|
}
|
|
1750
1753
|
|
|
1754
|
+
function recordStage(record, model) {
|
|
1755
|
+
return workflowStageForRecord(record, model);
|
|
1756
|
+
}
|
|
1757
|
+
|
|
1751
1758
|
function appointmentRequiredness(record, model) {
|
|
1752
1759
|
return model.appointmentTemplates?.[record.appointmentKind]?.requiredness || "conditional";
|
|
1753
1760
|
}
|