filegrc 0.13.3 → 0.13.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/model/v10.json +12 -12
- package/package.json +1 -1
- package/src/audit-preparation.js +6 -0
- package/src/policy-library/data-retention-schedule-v2.md +2 -2
- package/src/policy-library.js +24 -9
- package/src/web.js +55 -17
- package/src/workflow.js +33 -17
package/model/v10.json
CHANGED
|
@@ -1436,9 +1436,9 @@
|
|
|
1436
1436
|
"policyBasis": "Policies rely on governed documents for detailed plans, schedules, procedures, charters, and reports. Approval accepts the intended values and exact content. Activation records that the linked requirements were implemented and the approved Document was put into use.",
|
|
1437
1437
|
"cadence": "Complete required program Documents in Step 2, obtain approval from a reviewer who is separate from the owner, implement their linked requirements in Step 3, then activate the unchanged approved revision. Follow the linked review Obligation after activation. Prepare Audit Documents in Step 5, then approve and activate each one there as separate revision-bound updates.",
|
|
1438
1438
|
"reviewPoints": [
|
|
1439
|
-
"
|
|
1440
|
-
"Replace every bracketed prompt with a reviewed fact before approval, activation,
|
|
1441
|
-
"
|
|
1439
|
+
"Keep companion Markdown to the policy, procedure, evidence, or narrative the Document records. Put FileGRC instructions and derived actions in the UI or guides unless FileGRC is the subject.",
|
|
1440
|
+
"Replace every bracketed prompt with a reviewed fact before approval, activation, signing, or delivery.",
|
|
1441
|
+
"Use ordinary business language; keep resource relationships in this record and its supporting records."
|
|
1442
1442
|
],
|
|
1443
1443
|
"sourceResourceIds": [
|
|
1444
1444
|
"policy-information-security",
|
|
@@ -1771,9 +1771,9 @@
|
|
|
1771
1771
|
"cadence": "Create an Evidence Artifact only for a real retained artifact or approved reference. Identify its source Component or source records, Controls, collection and verification facts, coverage, revision, and classification.",
|
|
1772
1772
|
"emptyState": "No Evidence Artifacts have been collected. Add one only when a real retained artifact or approved external reference exists.",
|
|
1773
1773
|
"reviewPoints": [
|
|
1774
|
-
"
|
|
1775
|
-
"For
|
|
1776
|
-
"
|
|
1774
|
+
"For a signed management representation, use artifactKind signed-record and artifactSubtype signed-management-representation.",
|
|
1775
|
+
"For that representation, record the signing time in businessEventAt; use collectedOn for when FileGRC received it.",
|
|
1776
|
+
"For a final CPA report, use artifactKind third-party-report and artifactSubtype soc2-report, then record its issuance time in sourceGeneratedAt."
|
|
1777
1777
|
],
|
|
1778
1778
|
"sourceResourceIds": [
|
|
1779
1779
|
"policy-information-security"
|
|
@@ -2865,9 +2865,9 @@
|
|
|
2865
2865
|
"policyBasis": "Controls translate approved Policies and applicable Requirements into owned procedures that management can operate and prove. FileGRC does not infer technical implementation from policy prose. Configuration facts belong in Controls, Components, Systems, Obligations, and Evidence.",
|
|
2866
2866
|
"cadence": "A Control may be implemented while its governing Policy, required program Document, or Training is approved but inactive. Before marking it implemented, record its owner, actual procedure in Record Markdown, bounded System scope, operation pattern, authoritative evidence-source Components, implementation date, and enabled calendar or event Obligations. Confirm each source Component is active, has the evidence role required by the Control family and current access owners, and includes repeatable retrieval instructions in Record Markdown. Enabled Obligations remain dormant until all of their governing content is active and effective.",
|
|
2867
2867
|
"reviewPoints": [
|
|
2868
|
-
"Confirm the Control is needed for the applicable Requirements,
|
|
2869
|
-
"
|
|
2870
|
-
"Use
|
|
2868
|
+
"Confirm the Control is needed for the applicable Requirements, Commitments, and service boundary.",
|
|
2869
|
+
"Describe the actual procedure. Put starter-selection and FileGRC record-entry instructions in the UI or guides, unless FileGRC is an operating Component or evidence source.",
|
|
2870
|
+
"Use not applicable, external, or zero population only when it is true for the current scope."
|
|
2871
2871
|
],
|
|
2872
2872
|
"sourceResourceIds": [
|
|
2873
2873
|
"policy-information-security"
|
|
@@ -4389,9 +4389,9 @@
|
|
|
4389
4389
|
"policyBasis": "The information security policy requires security training and added role-specific instruction when a person’s responsibilities or data access warrant it.",
|
|
4390
4390
|
"cadence": "Approve the exact training content in Step 2. During Step 3, finish the linked Controls and Obligations, then activate the unchanged approved revision. Assign training through Obligations at onboarding, annually, and after relevant material changes or incidents.",
|
|
4391
4391
|
"reviewPoints": [
|
|
4392
|
-
"
|
|
4393
|
-
"
|
|
4394
|
-
"
|
|
4392
|
+
"Keep workforce material focused on required behavior. Put FileGRC commands, record-entry instructions, and data-model terms in guides and assignments.",
|
|
4393
|
+
"Do not imply every user needs a paid product or that customer MFA applies without an approved requirement.",
|
|
4394
|
+
"Bind each completion to the exact revision the person reviewed."
|
|
4395
4395
|
],
|
|
4396
4396
|
"sourceResourceIds": [
|
|
4397
4397
|
"policy-information-security"
|
package/package.json
CHANGED
package/src/audit-preparation.js
CHANGED
|
@@ -1599,6 +1599,12 @@ function containsOpenPlaceholder(source) {
|
|
|
1599
1599
|
function managementDocumentContentIssues(source, definition, audit) {
|
|
1600
1600
|
if (!source) return ["Add the required Markdown content."];
|
|
1601
1601
|
if (containsOpenPlaceholder(source)) return ["Replace every bracketed preparation placeholder before approval."];
|
|
1602
|
+
if (definition.kind === "soc2-management-assertion" && audit) {
|
|
1603
|
+
const incompatibleKind = audit.auditKind === "soc-2-type-1" ? "Type 2" : "Type 1";
|
|
1604
|
+
if (new RegExp(`^#{1,6}\\s+${incompatibleKind} Assertion(?: Draft)?\\s*$`, "imu").test(source)) {
|
|
1605
|
+
return [`Remove the ${incompatibleKind} assertion from this ${audit.auditKind === "soc-2-type-1" ? "Type 1" : "Type 2"} engagement document.`];
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1602
1608
|
const words = source.match(/[\p{L}\p{N}][\p{L}\p{N}'’-]*/gu) || [];
|
|
1603
1609
|
if (definition.minimumWords && words.length < definition.minimumWords) {
|
|
1604
1610
|
return [`Add substantive content; this document has ${words.length} words and the preparation check expects at least ${definition.minimumWords}.`];
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## Use
|
|
4
4
|
|
|
5
|
-
This schedule records how long {{company_name}} keeps important record classes and what happens when each period ends.
|
|
5
|
+
This schedule records how long {{company_name}} keeps important record classes and what happens when each period ends.
|
|
6
6
|
|
|
7
7
|
Retention periods may come from law, contract, tax, audit, security, or a documented business need. Use the longest applicable period, but do not keep data indefinitely without a reason.
|
|
8
8
|
|
|
@@ -10,7 +10,7 @@ Retention periods may come from law, contract, tax, audit, security, or a docume
|
|
|
10
10
|
|
|
11
11
|
The structured Retention Schedule Items linked to this document are its schedule rows. Each approved item must name the covered Information Types and operational scope, owner, cutoff, period, disposition action, instructions, and authority. Planned items are review prompts and are not approved retention behavior.
|
|
12
12
|
|
|
13
|
-
Management must cover important information used by Systems, Components, and Vendors, including security logs, backups or alternate recovery copies, governance records, audit evidence, customer and service records, and incident records when those classes exist.
|
|
13
|
+
Management must cover important information used by Systems, Components, and Vendors, including security logs, backups or alternate recovery copies, governance records, audit evidence, customer and service records, and incident records when those classes exist. A proposed period or disposition action is not an approved organization value.
|
|
14
14
|
|
|
15
15
|
## Holds and exceptions
|
|
16
16
|
|
package/src/policy-library.js
CHANGED
|
@@ -55,9 +55,10 @@ const DOCUMENT_CONTENT_UPDATES = [
|
|
|
55
55
|
priorRevision: "45a408e8139bd57f42dda5ca5ae5c8cd4480b4e7bf08834f60058148a3a63475",
|
|
56
56
|
additionalPriorRevisions: new Set([
|
|
57
57
|
"d80b99ce53d1012cc169bbbc2afab8d0597bfbe9f30ac0812a8d5bbeb2ed9f90",
|
|
58
|
-
"dd11857ae7d881f176bd93947ef3031c33c75ee41e3c0435198fd60c67a94cf7"
|
|
58
|
+
"dd11857ae7d881f176bd93947ef3031c33c75ee41e3c0435198fd60c67a94cf7",
|
|
59
|
+
"4a48c15a4e20e4f29028cf2ff8597315eb51878814120125b5268356b923c9db"
|
|
59
60
|
]),
|
|
60
|
-
currentRevision: "
|
|
61
|
+
currentRevision: "1738d29786cd6a027c9a2bb339ce36ce3e15c858a55ec503f4e690e65273598b",
|
|
61
62
|
currentSourcePath: "./policy-library/data-retention-schedule-v2.md",
|
|
62
63
|
replacements: [
|
|
63
64
|
["FileGRC detects the bracketed prompts as approval blockers. Remove each prompt only after replacing it with a reviewed fact.", "Remove each bracketed prompt only after replacing it with a reviewed fact."],
|
|
@@ -73,9 +74,10 @@ const DOCUMENT_CONTENT_UPDATES = [
|
|
|
73
74
|
additionalPriorRevisions: new Set([
|
|
74
75
|
"51a19e22f3e0b31196371676297bfa843f96295d2f6c72e81a969ce7bafc36ae",
|
|
75
76
|
"5107f5c6015089a7c64e87692976aa48f727b8c03988f93f71e8f3e5084a05ac",
|
|
76
|
-
"970b97c4f594ff54fab554460f3cf44f80ee8079e996f4d5dc1427290f8aa714"
|
|
77
|
+
"970b97c4f594ff54fab554460f3cf44f80ee8079e996f4d5dc1427290f8aa714",
|
|
78
|
+
"ff8b5764ba3073d7ab6678c7da607850de5a82de4383cb54417cd74d82c05305"
|
|
77
79
|
]),
|
|
78
|
-
currentRevision: "
|
|
80
|
+
currentRevision: "72a6049f2eed25525417fc3eb6263c231e44193d3c5cbbf6675bb98459374d0c",
|
|
79
81
|
replacements: [
|
|
80
82
|
["## Reporting routes", "## Reporting channels"],
|
|
81
83
|
["The primary reporting route is {{security_contact_email}}.", "Use the current approved primary security Reporting Route. The Reporting Route record is the source of truth for its channel, destination, owner, priority, and effective period."],
|
|
@@ -90,7 +92,14 @@ const DOCUMENT_CONTENT_UPDATES = [
|
|
|
90
92
|
["- Restore-validation method and governed schedule", "- Restore-validation method and approved schedule"],
|
|
91
93
|
["- Dependencies, fallback paths, and validation steps", "- Dependencies, fallback paths, and validation steps\n- Any recovery targets required by an approved commitment, included Availability criterion, or risk decision"],
|
|
92
94
|
["[Confirm or replace before activation: The starter proposal for important production data is a daily backup, 30-day retention period, and annual restore validation. Record the approved choice for every important System in its Control, Component, System, Retention Schedule, and Obligation records.]", "[Confirm or replace before activation: The proposed starting point for important production data is a daily backup, 30-day retention period, and annual restore validation. Document the approved choice for every important System in its recovery procedures and the Data Retention Schedule.]"],
|
|
93
|
-
["on the approved governed schedule", "on the approved schedule"]
|
|
95
|
+
["on the approved governed schedule", "on the approved schedule"],
|
|
96
|
+
["[Complete before activation: Describe how workers can find the fallback reporting channel when the normal email, identity, or collaboration System is unavailable, compromised, or involved in the concern. Do not copy the destination here or include plaintext credentials, private keys, tokens, or recovery codes.]", "[Describe how workers find the fallback reporting channel when the normal email, identity, or collaboration System is unavailable, compromised, or involved in the concern. Do not copy the destination here or include plaintext credentials, private keys, tokens, or recovery codes.]"],
|
|
97
|
+
["The Policy Owner maintains this plan and ensures that the organization assigns these duties before activation:", "The Policy Owner maintains this plan and ensures that the organization assigns these duties:"],
|
|
98
|
+
["[Complete before activation: Record the emergency contact arrangement, its owner, alternate communication channel, protected storage location, and review schedule.]", "[Describe the emergency contact arrangement, its owner, alternate communication channel, protected storage location, and review schedule.]"],
|
|
99
|
+
["[Complete before activation: Identify every important System's recovery priority, dependencies, owner, backup or alternate recovery approach, and critical customer commitments. Record numeric recovery targets only when an approved commitment, included Availability criterion, or risk decision requires them.]", "[Identify every important System's recovery priority, dependencies, owner, backup or alternate recovery approach, and critical customer commitments. Record numeric recovery targets only when an approved commitment, included Availability criterion, or risk decision requires them.]"],
|
|
100
|
+
["[Confirm or replace before activation: The proposed starting point for important production data is a daily backup, 30-day retention period, and annual restore validation. Document the approved choice for every important System in its recovery procedures and the Data Retention Schedule.]", "[Document the management-approved recovery approach for every important System in its recovery procedures and the Data Retention Schedule. A daily backup, 30-day retention period, and annual restore validation are examples, not approved organization values.]"],
|
|
101
|
+
["[Complete before activation: Record the protected alternate location and access method responders will use when the primary identity, source-control, or collaboration Systems are unavailable. Confirm that authorized responders can retrieve the plan without exposing plaintext secrets or decryption keys.]", "[Describe the protected alternate location and access method responders use when the primary identity, source-control, or collaboration Systems are unavailable. Confirm that authorized responders can retrieve the plan without exposing plaintext secrets or decryption keys.]"],
|
|
102
|
+
["- Any recovery targets required by an approved commitment, included Availability criterion, or risk decision\n- Any recovery targets required by an approved commitment, included Availability criterion, or risk decision", "- Any recovery targets required by an approved commitment, included Availability criterion, or risk decision"],
|
|
94
103
|
],
|
|
95
104
|
summary: "Express incident and recovery requirements as a standalone plan, keep FileGRC record-entry guidance outside it, and make numeric recovery targets conditional."
|
|
96
105
|
},
|
|
@@ -98,9 +107,12 @@ const DOCUMENT_CONTENT_UPDATES = [
|
|
|
98
107
|
id: "document-soc2-management-representation",
|
|
99
108
|
path: "documents/document-soc2-management-representation.md",
|
|
100
109
|
priorRevision: "59e7e5929eeb24fa6bc23e1ed1553206901c33ba249b372ad0f42282ab7a64c7",
|
|
101
|
-
|
|
110
|
+
additionalPriorRevisions: new Set(["83de736025d07d8085fba93a212a98e7b3e951aded9894b933eab1e77c3a0d29"]),
|
|
111
|
+
currentRevision: "45fcfa1bfa280fe96c65220df48d55b2bd0f238c54a6dc18e8f2d9ccd38a7f96",
|
|
102
112
|
replacements: [
|
|
103
|
-
["Signed letter evidence record: [Evidence ID]", "Signed letter reference: [Approved storage location or evidence reference]"]
|
|
113
|
+
["Signed letter evidence record: [Evidence ID]", "Signed letter reference: [Approved storage location or evidence reference]"],
|
|
114
|
+
["> The service auditor normally supplies the required representation-letter wording near the end of fieldwork. Replace this preparation note with the agreed final letter, obtain the required management signature, and retain the signed fixed-format copy as linked evidence.", "> [Insert the final management representation agreed with the service auditor.]"],
|
|
115
|
+
["Before signing, reconcile the auditor's letter to:", "The signer confirms that the letter agrees with:"]
|
|
104
116
|
],
|
|
105
117
|
summary: "Keep the representation-letter template independent of FileGRC relationship fields."
|
|
106
118
|
},
|
|
@@ -121,11 +133,14 @@ const DOCUMENT_CONTENT_UPDATES = [
|
|
|
121
133
|
id: "document-soc2-system-description",
|
|
122
134
|
path: "documents/document-soc2-system-description.md",
|
|
123
135
|
priorRevision: "dc15bc2141be0d63a832eb6f7734bc7381e77582030534bcaf636138782f8e84",
|
|
124
|
-
|
|
136
|
+
additionalPriorRevisions: new Set(["929a7cd87ca78a2d9caa349cad0ae23583ad79184f26ba50c1694c62549aab9e"]),
|
|
137
|
+
currentRevision: "0e2e0252e0e0a5be821851de1d168fbcf22bfcca41d80836c657583ab38ba154",
|
|
125
138
|
replacements: [
|
|
126
139
|
["reconcile it to the filegrc records", "reconcile it to management's authoritative records"],
|
|
127
140
|
["Link the filegrc commitment records.", "Reconcile the summary to supporting commitment records."],
|
|
128
|
-
["Reference the selected criteria and control matrix generated by filegrc.", "Reference the selected criteria and management's control matrix."]
|
|
141
|
+
["Reference the selected criteria and control matrix generated by filegrc.", "Reference the selected criteria and management's control matrix."],
|
|
142
|
+
["> Draft preparation document. Complete every bracketed item, reconcile it to management's authoritative records, and have the service auditor review the final presentation.", "> [Describe the service as it existed for the stated reporting date or period.]"],
|
|
143
|
+
["[Summarize customer commitments, contractual security promises, internal objectives, and the system requirements needed to meet them. Reconcile the summary to supporting commitment records.]", "[Summarize customer commitments, contractual security promises, internal objectives, and the system requirements needed to meet them.]"]
|
|
129
144
|
],
|
|
130
145
|
summary: "Keep the SOC 2 System Description as a generic management deliverable."
|
|
131
146
|
}
|
package/src/web.js
CHANGED
|
@@ -847,13 +847,13 @@ function openPolicyActivationDialog() {
|
|
|
847
847
|
}
|
|
848
848
|
|
|
849
849
|
function workflowGuidance(options = {}) {
|
|
850
|
-
const workflow = state.workflow;
|
|
850
|
+
const workflow = options.workflow || state.workflow;
|
|
851
851
|
if (!workflow) return "";
|
|
852
852
|
const matches = (item) => {
|
|
853
853
|
if (options.type === "audit" && options.id && item.auditId === options.id) return true;
|
|
854
854
|
if (options.stageId && item.stage !== options.stageId) return false;
|
|
855
|
-
if (options.type && item.subject?.type !== options.type
|
|
856
|
-
if (options.id && item.subject?.id !== options.id
|
|
855
|
+
if (options.type && item.subject?.type !== options.type) return false;
|
|
856
|
+
if (options.id && item.subject?.id !== options.id) return false;
|
|
857
857
|
return true;
|
|
858
858
|
};
|
|
859
859
|
const activeStates = new Set(["blocked", "due", "open", "overdue", "ready", "scheduled", "upcoming", "waiting-external"]);
|
|
@@ -880,9 +880,16 @@ function workflowGuidance(options = {}) {
|
|
|
880
880
|
const rows = visible.map(renderRow).join("");
|
|
881
881
|
const remaining = items.slice(visible.length);
|
|
882
882
|
const more = remaining.length
|
|
883
|
-
? '<details class="workflow-guidance-more"><summary>Show ' + remaining.length + ' more ' + pluralize("item", remaining.length) + '</summary><div class="workflow-findings workflow-findings-more">' + remaining.map(renderRow).join("") + '</div><p><code>filegrc workflow --json</code> returns
|
|
883
|
+
? '<details class="workflow-guidance-more"><summary>Show ' + remaining.length + ' more ' + pluralize("item", remaining.length) + '</summary><div class="workflow-findings workflow-findings-more">' + remaining.map(renderRow).join("") + '</div><p><code>filegrc workflow --json</code> returns this same checklist for CLI and agent use.</p></details>'
|
|
884
884
|
: "";
|
|
885
|
-
|
|
885
|
+
const completionRequirements = options.type && resourceReviewCriteria(options.type, { trigger: true });
|
|
886
|
+
return '<section class="workflow-guidance panel detail-support-panel detail-workflow-panel"><div class="panel-head"><div><p class="kicker">To-do</p><h3>' + esc(options.title || "Checklist") + '</h3></div><span class="badge ' + (blocking.length ? "warn" : "good") + '">' + (blocking.length ? "Needs work" : "Current") + '</span></div><p class="workflow-status">' + esc(status) + (completionRequirements ? '. ' + completionRequirements : "") + '</p><div class="workflow-findings">' + rows + '</div>' + more + '</section>';
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
function recordCompletionState(record) {
|
|
890
|
+
const completeStatuses = new Set(["approved", "active", "complete", "completed", "closed", "done", "reconciled"]);
|
|
891
|
+
if (!completeStatuses.has(record.status)) return "";
|
|
892
|
+
return '<section class="record-completion-state panel detail-support-panel"><p class="kicker">Record status</p><h3>' + esc(properCase(record.status)) + '</h3><p>This record has no direct next step.</p></section>';
|
|
886
893
|
}
|
|
887
894
|
|
|
888
895
|
function collectionReviewPanel(type) {
|
|
@@ -899,14 +906,21 @@ function collectionReviewPanel(type) {
|
|
|
899
906
|
const reviewSummary = current
|
|
900
907
|
? '<p class="collection-review-result"><strong>' + esc(properCase(assessment.review.decision)) + '</strong><span>Reviewed ' + esc(formatCalendarDate(assessment.review.reviewedOn)) + (reviewerNames.length ? " by " + esc(reviewerNames.join(", ")) : "") + "." + reviewNote + '</span></p>'
|
|
901
908
|
: '<p class="collection-review-result"><strong>' + (needsFirstRecord ? "Records required" : assessment.status === "stale" ? "Review again" : "Review required") + '</strong><span>' + esc(assessment.message) + '</span></p>';
|
|
909
|
+
const compactReviewSummary = current
|
|
910
|
+
? '<p class="collection-review-result"><strong>' + esc(properCase(assessment.review.decision)) + '</strong><span>Reviewed ' + esc(formatCalendarDate(assessment.review.reviewedOn)) + (reviewerNames.length ? " by " + esc(reviewerNames.join(", ")) : "") + ".</span></p>"
|
|
911
|
+
: reviewSummary;
|
|
902
912
|
const action = state.readOnly
|
|
903
913
|
? ""
|
|
904
914
|
: needsFirstRecord
|
|
905
915
|
? '<a class="button primary" href="#/resources/' + encodeURIComponent(type) + '?new=1">Add first ' + esc(state.model.resources[type].title.toLowerCase()) + '</a>'
|
|
906
916
|
: '<button class="button ' + (current ? "" : "primary") + '" type="button" data-review-collection="' + esc(type) + '">' + (current ? "Review again" : "Review and confirm") + '</button>';
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
917
|
+
const status = current ? "Reviewed" : needsFirstRecord ? "Records required" : assessment.status === "stale" ? "Stale" : "Review required";
|
|
918
|
+
const details = '<details ' + (current ? 'class="collection-review-details"' : "open") + '><summary>' + (current ? "Show scope confirmation" : "What to review") + '</summary><div class="collection-review-detail-content"><div class="collection-review-head"><div><p class="kicker">Scope confirmation</p><h3>' + esc(configuration.title) + '</h3><p>' + esc(configuration.description) + '</p></div><span class="badge ' + (current ? "good" : "warn") + '">' + status + '</span></div><ul>' + configuration.reviewPoints.map((point) => '<li>' + esc(point) + '</li>').join("") + '</ul>' + (current ? reviewSummary : "") + '</div></details>';
|
|
919
|
+
const summary = current
|
|
920
|
+
? '<div class="collection-review-current-row"><div class="collection-review-complete-summary"><span class="kicker">Scope confirmation</span>' + compactReviewSummary + '</div>' + details + action + '</div>'
|
|
921
|
+
: details;
|
|
922
|
+
return '<section class="collection-review-panel panel ' + (current ? "current" : "required") + '">' + summary +
|
|
923
|
+
(current ? "" : '<div class="collection-review-foot">' + reviewSummary + action + '</div>') + '</section>';
|
|
910
924
|
}
|
|
911
925
|
|
|
912
926
|
function collectionNeedsFirstRecord(type) {
|
|
@@ -931,13 +945,31 @@ function collectionEmptyState(type, definition) {
|
|
|
931
945
|
: "No records exist. Use the scope confirmation above to record an allowed empty-collection conclusion, or add the records management identified.";
|
|
932
946
|
}
|
|
933
947
|
|
|
934
|
-
function resourceReviewCriteria(type,
|
|
948
|
+
function resourceReviewCriteria(type, options = {}) {
|
|
949
|
+
const collapsed = options === true || options.collapsed;
|
|
950
|
+
const trigger = typeof options === "object" && options.trigger;
|
|
951
|
+
const title = typeof options === "object" && options.title ? options.title : "Review criteria";
|
|
935
952
|
const reviewPoints = state.model.resources[type]?.guidance?.reviewPoints || [];
|
|
936
953
|
if (!reviewPoints.length) return "";
|
|
954
|
+
if (trigger) return '<button type="button" class="workflow-completion-requirements" data-completion-requirements="' + esc(type) + '">Completion requirements</button>';
|
|
937
955
|
const points = '<ul>' + reviewPoints.map((point) => '<li>' + esc(point) + '</li>').join("") + '</ul>';
|
|
938
956
|
return collapsed
|
|
939
|
-
? '<details class="resource-review-criteria compact"><summary>
|
|
940
|
-
: '<section class="resource-review-criteria panel detail-support-panel detail-review-panel"><div class="panel-head"><h3>
|
|
957
|
+
? '<details class="resource-review-criteria compact"><summary>' + esc(title) + '</summary>' + points + '</details>'
|
|
958
|
+
: '<section class="resource-review-criteria panel detail-support-panel detail-review-panel"><div class="panel-head"><h3>' + esc(title) + '</h3></div>' + points + '</section>';
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
function openCompletionRequirementsDialog(type) {
|
|
962
|
+
const definition = state.model.resources[type];
|
|
963
|
+
const reviewPoints = definition?.guidance?.reviewPoints || [];
|
|
964
|
+
if (!reviewPoints.length) return;
|
|
965
|
+
const dialog = document.createElement("dialog");
|
|
966
|
+
dialog.className = "alert-dialog completion-requirements-dialog";
|
|
967
|
+
dialog.innerHTML = '<div class="dialog-head"><div><p class="kicker">Completion requirements</p><h2>' + esc(titleCase(definition.title)) + '</h2></div><button type="button" class="icon-button" aria-label="Close">×</button></div><p>Check these before considering this record complete.</p><ul>' + reviewPoints.map((point) => '<li>' + esc(point) + '</li>').join("") + '</ul><div class="dialog-actions"><button type="button" class="button primary">Close</button></div>';
|
|
968
|
+
document.body.append(dialog);
|
|
969
|
+
const close = () => dialog.close();
|
|
970
|
+
dialog.querySelectorAll("button").forEach((button) => button.addEventListener("click", close));
|
|
971
|
+
dialog.addEventListener("close", () => dialog.remove(), { once: true });
|
|
972
|
+
dialog.showModal();
|
|
941
973
|
}
|
|
942
974
|
|
|
943
975
|
function recordWorkflowItems(type, id) {
|
|
@@ -2841,8 +2873,7 @@ function renderDetail(main, type, id, params = new URLSearchParams()) {
|
|
|
2841
2873
|
: personParticipation(entry);
|
|
2842
2874
|
const supportPanels = renderDetailSupport({
|
|
2843
2875
|
hasRecordBody,
|
|
2844
|
-
workflowPanel: workflowGuidance({ type, id, title: "Next steps" }),
|
|
2845
|
-
reviewPanel: resourceReviewCriteria(type),
|
|
2876
|
+
workflowPanel: workflowGuidance({ type, id, title: "Next steps", workflow: entry.workflow }) || recordCompletionState(entry.record),
|
|
2846
2877
|
metadataPanel: '<section class="panel detail-support-panel detail-metadata-panel"><div class="panel-head"><h3>Record details</h3></div><dl class="metadata">' + sourceMetadata + visible.map(([name, value]) => '<div><dt>' + esc(fields[name]?.label || humanize(name)) + '</dt><dd>' + formatValue(name === "status" ? displayStatus(entry.record) : value, name, type) + '</dd></div>').join("") + '</dl></section>',
|
|
2847
2878
|
attachmentPanel,
|
|
2848
2879
|
participationPanel,
|
|
@@ -2850,6 +2881,7 @@ function renderDetail(main, type, id, params = new URLSearchParams()) {
|
|
|
2850
2881
|
historyPanel
|
|
2851
2882
|
});
|
|
2852
2883
|
main.innerHTML = '<div class="page"><div class="detail-head"><div><div class="breadcrumbs header-breadcrumbs"><a href="#/resources/' + encodeURIComponent(type) + '">' + esc(titleCase(definition.pluralTitle)) + '</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>' + (!definition.singleton ? '<button class="button danger" id="delete-resource">Delete</button>' : "") : "") + '</div></div><div class="detail-grid ' + (hasRecordBody ? "" : "detail-grid-structured") + '">' + detailMain + supportPanels + '</div></div>';
|
|
2884
|
+
main.querySelectorAll("[data-completion-requirements]").forEach((button) => button.addEventListener("click", () => openCompletionRequirementsDialog(button.dataset.completionRequirements)));
|
|
2853
2885
|
main.querySelector("#edit-resource")?.addEventListener("click", () => openEditor(type, entry));
|
|
2854
2886
|
main.querySelector("[data-external-reviewer-governance]")?.addEventListener("click", openExternalReviewerGovernanceDialog);
|
|
2855
2887
|
main.querySelector("[data-next-audit-cycle]")?.addEventListener("click", () => openNextAuditCycleDialog(entry.record));
|
|
@@ -2987,11 +3019,11 @@ function renderDetail(main, type, id, params = new URLSearchParams()) {
|
|
|
2987
3019
|
}
|
|
2988
3020
|
}
|
|
2989
3021
|
|
|
2990
|
-
function renderDetailSupport({ hasRecordBody, workflowPanel,
|
|
2991
|
-
const panels = workflowPanel +
|
|
3022
|
+
function renderDetailSupport({ hasRecordBody, workflowPanel, metadataPanel, attachmentPanel, participationPanel, connectionsPanel, historyPanel }) {
|
|
3023
|
+
const panels = workflowPanel + metadataPanel + attachmentPanel + participationPanel + connectionsPanel + historyPanel;
|
|
2992
3024
|
if (hasRecordBody) return '<aside>' + panels + '</aside>';
|
|
2993
3025
|
const stacks = [
|
|
2994
|
-
{ name: "guidance", content: workflowPanel
|
|
3026
|
+
{ name: "guidance", content: workflowPanel },
|
|
2995
3027
|
{ name: "record", content: metadataPanel + attachmentPanel + historyPanel },
|
|
2996
3028
|
{ name: "relationships", content: participationPanel + connectionsPanel }
|
|
2997
3029
|
].filter(({ content }) => content);
|
|
@@ -3023,7 +3055,7 @@ async function loadResourceDetail(type, id) {
|
|
|
3023
3055
|
let token = state.stateToken;
|
|
3024
3056
|
let detail = null;
|
|
3025
3057
|
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
3026
|
-
const tokenQuery = token ? "?token=" + encodeURIComponent(token) + "&history=false" : "?history=false";
|
|
3058
|
+
const tokenQuery = token ? "?token=" + encodeURIComponent(token) + "&history=false&workflow=true" : "?history=false&workflow=true";
|
|
3027
3059
|
const response = await localFetch("/api/resource/" + encodeURIComponent(type) + "/" + encodeURIComponent(id) + tokenQuery);
|
|
3028
3060
|
if (response.status === 409 && token) {
|
|
3029
3061
|
await refreshExpiredAppState(token);
|
|
@@ -5971,6 +6003,9 @@ html,body{height:100%;overflow:hidden}.shell{grid-template-columns:248px minmax(
|
|
|
5971
6003
|
.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}
|
|
5972
6004
|
.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}
|
|
5973
6005
|
.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}
|
|
6006
|
+
.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}
|
|
6007
|
+
.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}
|
|
6008
|
+
@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}}
|
|
5974
6009
|
.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)}
|
|
5975
6010
|
.policy-lifecycle-note.single{grid-template-columns:1fr}
|
|
5976
6011
|
.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-count{margin:9px 2px 0;color:var(--muted);font-size:10.8px;text-align:right}
|
|
@@ -6142,6 +6177,9 @@ dialog::backdrop{background:rgba(0,0,24,.62)}
|
|
|
6142
6177
|
.record-table td:has(.relation){min-width:150px}
|
|
6143
6178
|
.record-table .relation{border-radius:7px;line-height:1.35;overflow-wrap:anywhere;word-break:normal}
|
|
6144
6179
|
@media(max-width:760px){.record-table td:has(.relation){min-width:0}.record-table td[data-label]>.relation{grid-column:2}}
|
|
6180
|
+
@media(max-width:760px){.diagnostics>div{grid-template-columns:max-content minmax(0,1fr)}}
|
|
6181
|
+
.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}
|
|
6182
|
+
.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}
|
|
6145
6183
|
`;
|
|
6146
6184
|
|
|
6147
6185
|
function safeJson(value) {
|
package/src/workflow.js
CHANGED
|
@@ -217,18 +217,34 @@ export function buildWorkflowDelta(before, after) {
|
|
|
217
217
|
}
|
|
218
218
|
|
|
219
219
|
export function workflowForResource(workflow, type, id) {
|
|
220
|
-
if (!workflow) return {
|
|
220
|
+
if (!workflow) return {
|
|
221
|
+
findings: [],
|
|
222
|
+
workItems: [],
|
|
223
|
+
related: { findings: [], workItems: [], recommended: null },
|
|
224
|
+
recommended: null
|
|
225
|
+
};
|
|
221
226
|
const matches = (item) => item.subject?.type === type && item.subject?.id === id;
|
|
222
|
-
const
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
)
|
|
227
|
+
const related = (item) => (
|
|
228
|
+
item.source?.type === type
|
|
229
|
+
&& item.source?.id === id
|
|
230
|
+
&& !matches(item)
|
|
231
|
+
);
|
|
232
|
+
const directFindings = workflow.findings.filter(matches);
|
|
233
|
+
const directWorkItems = workflow.workItems.filter(matches);
|
|
234
|
+
const relatedFindings = workflow.findings.filter(related);
|
|
235
|
+
const relatedWorkItems = workflow.workItems.filter(related);
|
|
236
|
+
const findings = [...directFindings, ...relatedFindings];
|
|
237
|
+
const workItems = [...directWorkItems, ...relatedWorkItems];
|
|
227
238
|
return {
|
|
228
239
|
contractVersion: workflow.contractVersion,
|
|
229
240
|
assessments: workflow.assessments,
|
|
230
241
|
findings,
|
|
231
242
|
workItems,
|
|
243
|
+
related: {
|
|
244
|
+
findings: relatedFindings,
|
|
245
|
+
workItems: relatedWorkItems,
|
|
246
|
+
recommended: [...relatedFindings, ...relatedWorkItems].sort(compareRecommended)[0] || null
|
|
247
|
+
},
|
|
232
248
|
recommended: [...findings, ...workItems].sort(compareRecommended)[0] || null
|
|
233
249
|
};
|
|
234
250
|
}
|
|
@@ -430,7 +446,7 @@ function recordIncompleteReason(record, loaded, program) {
|
|
|
430
446
|
return {
|
|
431
447
|
state: "ready",
|
|
432
448
|
requiredness: "required",
|
|
433
|
-
message: "Review this criterion against the current service scope
|
|
449
|
+
message: "Review this criterion against the current service scope, then record the applicability decision."
|
|
434
450
|
};
|
|
435
451
|
}
|
|
436
452
|
if (record.type === "appointment" && record.status === "planned") {
|
|
@@ -440,16 +456,16 @@ function recordIncompleteReason(record, loaded, program) {
|
|
|
440
456
|
return {
|
|
441
457
|
state: "ready",
|
|
442
458
|
requiredness: appointmentRequiredness(record, loaded.model),
|
|
443
|
-
message: "Assign a holder, confirm the authority
|
|
459
|
+
message: "Assign a holder, confirm the authority and independence needed, then activate this Appointment on its actual start date."
|
|
444
460
|
};
|
|
445
461
|
}
|
|
446
462
|
const messages = {
|
|
447
|
-
draft: "Complete the record, its relationships, and required Markdown before
|
|
448
|
-
planned: "
|
|
449
|
-
proposed: "Review the proposed work, owner, schedule, and completion
|
|
463
|
+
draft: "Complete the record, its relationships, and required Markdown before review.",
|
|
464
|
+
planned: "Check this planned record against the actual program, then complete its finalization checks.",
|
|
465
|
+
proposed: "Review the proposed work, owner, schedule, and completion requirements before activation.",
|
|
450
466
|
"in-review": "Complete independent review and bind the approval to the exact content revision.",
|
|
451
467
|
open: "Complete or formally dispose of this open work with the required proof.",
|
|
452
|
-
"in-progress": "Finish the work, record
|
|
468
|
+
"in-progress": "Finish the work, record the result, and link the completion proof.",
|
|
453
469
|
blocked: "Resolve the recorded blockers before completing this work.",
|
|
454
470
|
"partially-implemented": "Finish the remaining control design, operation, source, and scheduling work."
|
|
455
471
|
};
|
|
@@ -517,7 +533,7 @@ function finalizationFields(record, model, resources, program) {
|
|
|
517
533
|
fields.push({
|
|
518
534
|
field: "applicabilityReview",
|
|
519
535
|
requiredness: "required",
|
|
520
|
-
message: "Record or refresh the
|
|
536
|
+
message: "Record or refresh the applicability decision, rationale, reviewer, and date. FileGRC records the current material scope automatically."
|
|
521
537
|
});
|
|
522
538
|
}
|
|
523
539
|
if (record.type === "policy" && model.resources.policy?.fields?.programRole && !record.programRole) {
|
|
@@ -570,7 +586,7 @@ function finalizationFields(record, model, resources, program) {
|
|
|
570
586
|
fields.push({
|
|
571
587
|
field,
|
|
572
588
|
requiredness: "conditional",
|
|
573
|
-
message: `
|
|
589
|
+
message: `Add ${fieldLabel(field)} before treating this ${fieldLabel(record.type).toLowerCase()} as complete.`
|
|
574
590
|
});
|
|
575
591
|
}
|
|
576
592
|
return fields;
|
|
@@ -676,9 +692,9 @@ async function sourceCoverageFindings(loaded, program) {
|
|
|
676
692
|
? "The source family has a reviewed authoritative path, valid coverage dates, retrieval ownership, and reconciliation method."
|
|
677
693
|
: record
|
|
678
694
|
? record.status === "active"
|
|
679
|
-
? "
|
|
680
|
-
: "Finish the
|
|
681
|
-
: "Create a source-
|
|
695
|
+
? "Add missing source details and link a passed retrieval test after setting a candidate period."
|
|
696
|
+
: "Finish the source decision, retrieval method, retention, validity dates, and pre-period dry run after setting a candidate period."
|
|
697
|
+
: "Create a source-coverage record and choose FileGRC, an external authoritative System, zero population, or not applicable.",
|
|
682
698
|
subject: { type: "source-coverage", ...(record ? { id: record.id } : {}) },
|
|
683
699
|
dependencies: [],
|
|
684
700
|
actions: record
|