monora-ai 2.0.0 → 2.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (202) hide show
  1. package/README.md +441 -150
  2. package/dist/aims_governance.d.ts +238 -0
  3. package/dist/aims_governance.d.ts.map +1 -0
  4. package/dist/aims_governance.js +922 -0
  5. package/dist/alerts.d.ts +16 -0
  6. package/dist/alerts.d.ts.map +1 -1
  7. package/dist/alerts.js +16 -0
  8. package/dist/api.d.ts +6 -0
  9. package/dist/api.d.ts.map +1 -1
  10. package/dist/api.js +6 -0
  11. package/dist/assessment.d.ts +269 -0
  12. package/dist/assessment.d.ts.map +1 -0
  13. package/dist/assessment.js +1232 -0
  14. package/dist/attestation.js +23 -1
  15. package/dist/attribution.d.ts +349 -0
  16. package/dist/attribution.d.ts.map +1 -0
  17. package/dist/attribution.js +987 -0
  18. package/dist/autodetect.d.ts +69 -1
  19. package/dist/autodetect.d.ts.map +1 -1
  20. package/dist/autodetect.js +644 -1
  21. package/dist/bias.d.ts +130 -0
  22. package/dist/bias.d.ts.map +1 -0
  23. package/dist/bias.js +223 -0
  24. package/dist/circuit_breaker.js +3 -3
  25. package/dist/cli/diagnostics.d.ts +5 -1
  26. package/dist/cli/diagnostics.d.ts.map +1 -1
  27. package/dist/cli/diagnostics.js +31 -8
  28. package/dist/cli/doctor.d.ts +25 -0
  29. package/dist/cli/doctor.d.ts.map +1 -0
  30. package/dist/cli/doctor.js +381 -0
  31. package/dist/cli/fix.d.ts +16 -0
  32. package/dist/cli/fix.d.ts.map +1 -0
  33. package/dist/cli/fix.js +284 -0
  34. package/dist/cli/init.d.ts +57 -0
  35. package/dist/cli/init.d.ts.map +1 -0
  36. package/dist/cli/init.js +205 -0
  37. package/dist/cli.js +1611 -126
  38. package/dist/complianceTargets.d.ts +111 -0
  39. package/dist/complianceTargets.d.ts.map +1 -0
  40. package/dist/complianceTargets.js +521 -0
  41. package/dist/config.d.ts +301 -17
  42. package/dist/config.d.ts.map +1 -1
  43. package/dist/config.js +428 -36
  44. package/dist/config_migrations.d.ts +41 -0
  45. package/dist/config_migrations.d.ts.map +1 -1
  46. package/dist/config_migrations.js +205 -0
  47. package/dist/config_schema.d.ts +2900 -731
  48. package/dist/config_schema.d.ts.map +1 -1
  49. package/dist/config_schema.js +257 -55
  50. package/dist/context.d.ts +34 -0
  51. package/dist/context.d.ts.map +1 -1
  52. package/dist/context.js +118 -7
  53. package/dist/control_backbone.d.ts +122 -0
  54. package/dist/control_backbone.d.ts.map +1 -0
  55. package/dist/control_backbone.js +698 -0
  56. package/dist/data-governance.d.ts +187 -0
  57. package/dist/data-governance.d.ts.map +1 -0
  58. package/dist/data-governance.js +424 -0
  59. package/dist/dataResidency.d.ts +44 -0
  60. package/dist/dataResidency.d.ts.map +1 -0
  61. package/dist/dataResidency.js +203 -0
  62. package/dist/dispatcher.d.ts +32 -0
  63. package/dist/dispatcher.d.ts.map +1 -1
  64. package/dist/dispatcher.js +91 -4
  65. package/dist/events.d.ts.map +1 -1
  66. package/dist/events.js +38 -0
  67. package/dist/evidence_store.d.ts +103 -0
  68. package/dist/evidence_store.d.ts.map +1 -0
  69. package/dist/evidence_store.js +459 -0
  70. package/dist/executiveSummary.d.ts +65 -8
  71. package/dist/executiveSummary.d.ts.map +1 -1
  72. package/dist/executiveSummary.js +289 -26
  73. package/dist/identity.d.ts +143 -0
  74. package/dist/identity.d.ts.map +1 -0
  75. package/dist/identity.js +231 -0
  76. package/dist/impact-assessment.d.ts +350 -0
  77. package/dist/impact-assessment.d.ts.map +1 -0
  78. package/dist/impact-assessment.js +580 -0
  79. package/dist/index.d.ts +25 -5
  80. package/dist/index.d.ts.map +1 -1
  81. package/dist/index.js +300 -4
  82. package/dist/instrumentation.d.ts +1 -1
  83. package/dist/instrumentation.d.ts.map +1 -1
  84. package/dist/instrumentation.js +243 -27
  85. package/dist/integrations/anthropic.d.ts +3 -0
  86. package/dist/integrations/anthropic.d.ts.map +1 -1
  87. package/dist/integrations/anthropic.js +284 -79
  88. package/dist/integrations/governance.d.ts +33 -0
  89. package/dist/integrations/governance.d.ts.map +1 -0
  90. package/dist/integrations/governance.js +208 -0
  91. package/dist/integrations/langchain.d.ts +7 -0
  92. package/dist/integrations/langchain.d.ts.map +1 -1
  93. package/dist/integrations/langchain.js +387 -143
  94. package/dist/integrations/openai.d.ts +9 -0
  95. package/dist/integrations/openai.d.ts.map +1 -1
  96. package/dist/integrations/openai.js +673 -73
  97. package/dist/iso42001_consolidation.d.ts +16 -0
  98. package/dist/iso42001_consolidation.d.ts.map +1 -0
  99. package/dist/iso42001_consolidation.js +413 -0
  100. package/dist/iso42001_workflows.d.ts +263 -0
  101. package/dist/iso42001_workflows.d.ts.map +1 -0
  102. package/dist/iso42001_workflows.js +781 -0
  103. package/dist/lifecycle.d.ts +299 -0
  104. package/dist/lifecycle.d.ts.map +1 -0
  105. package/dist/lifecycle.js +624 -0
  106. package/dist/lineage.d.ts +2 -2
  107. package/dist/lineage.d.ts.map +1 -1
  108. package/dist/lineage.js +12 -17
  109. package/dist/middleware/express.d.ts.map +1 -1
  110. package/dist/middleware/express.js +33 -3
  111. package/dist/middleware/nextjs.d.ts.map +1 -1
  112. package/dist/middleware/nextjs.js +42 -68
  113. package/dist/model.d.ts +143 -0
  114. package/dist/model.d.ts.map +1 -0
  115. package/dist/model.js +371 -0
  116. package/dist/onboarding.d.ts +42 -0
  117. package/dist/onboarding.d.ts.map +1 -0
  118. package/dist/onboarding.js +1022 -0
  119. package/dist/oversight.d.ts +264 -0
  120. package/dist/oversight.d.ts.map +1 -0
  121. package/dist/oversight.js +497 -0
  122. package/dist/pdf_report.d.ts.map +1 -1
  123. package/dist/pdf_report.js +42 -21
  124. package/dist/presets.d.ts +88 -0
  125. package/dist/presets.d.ts.map +1 -0
  126. package/dist/presets.js +520 -0
  127. package/dist/propagation.d.ts.map +1 -1
  128. package/dist/propagation.js +34 -2
  129. package/dist/quotas.d.ts +171 -0
  130. package/dist/quotas.d.ts.map +1 -0
  131. package/dist/quotas.js +259 -0
  132. package/dist/register.d.ts +13 -0
  133. package/dist/register.d.ts.map +1 -0
  134. package/dist/register.js +99 -0
  135. package/dist/registry.d.ts +1 -0
  136. package/dist/registry.d.ts.map +1 -1
  137. package/dist/registry.js +7 -0
  138. package/dist/registryData.json +43 -6
  139. package/dist/report.d.ts +2 -1
  140. package/dist/report.d.ts.map +1 -1
  141. package/dist/report.js +189 -2
  142. package/dist/reporting.d.ts +125 -0
  143. package/dist/reporting.d.ts.map +1 -1
  144. package/dist/reporting.js +196 -5
  145. package/dist/resources.d.ts +285 -0
  146. package/dist/resources.d.ts.map +1 -0
  147. package/dist/resources.js +643 -0
  148. package/dist/risk.d.ts +120 -0
  149. package/dist/risk.d.ts.map +1 -0
  150. package/dist/risk.js +220 -0
  151. package/dist/runtime.d.ts +74 -1
  152. package/dist/runtime.d.ts.map +1 -1
  153. package/dist/runtime.js +598 -22
  154. package/dist/schemaInference.d.ts +92 -0
  155. package/dist/schemaInference.d.ts.map +1 -0
  156. package/dist/schemaInference.js +466 -0
  157. package/dist/schema_validation.js +2 -2
  158. package/dist/schemas/config.schema.json +169 -6
  159. package/dist/schemas/event.schema.json +4 -0
  160. package/dist/security_report.js +4 -4
  161. package/dist/signing.d.ts +1 -1
  162. package/dist/signing.d.ts.map +1 -1
  163. package/dist/signing.js +4 -0
  164. package/dist/sinks/file.d.ts +19 -1
  165. package/dist/sinks/file.d.ts.map +1 -1
  166. package/dist/sinks/file.js +82 -13
  167. package/dist/sinks/https.d.ts +10 -0
  168. package/dist/sinks/https.d.ts.map +1 -1
  169. package/dist/sinks/https.js +76 -16
  170. package/dist/sinks/stdout.d.ts +1 -0
  171. package/dist/sinks/stdout.d.ts.map +1 -1
  172. package/dist/sinks/stdout.js +12 -1
  173. package/dist/spec.d.ts +159 -0
  174. package/dist/spec.d.ts.map +1 -0
  175. package/dist/spec.js +391 -0
  176. package/dist/stakeholders.d.ts +199 -0
  177. package/dist/stakeholders.d.ts.map +1 -0
  178. package/dist/stakeholders.js +398 -0
  179. package/dist/standards.d.ts.map +1 -1
  180. package/dist/standards.js +160 -2
  181. package/dist/standards_ingest.d.ts +2 -2
  182. package/dist/standards_ingest.d.ts.map +1 -1
  183. package/dist/standards_ingest.js +105 -23
  184. package/dist/streaming.d.ts.map +1 -1
  185. package/dist/streaming.js +7 -2
  186. package/dist/telemetry.d.ts +16 -2
  187. package/dist/telemetry.d.ts.map +1 -1
  188. package/dist/telemetry.js +79 -14
  189. package/dist/templates/controls/iso42001_control_catalog.json +1443 -0
  190. package/dist/traced_emitter.d.ts +3 -0
  191. package/dist/traced_emitter.d.ts.map +1 -1
  192. package/dist/traced_emitter.js +142 -25
  193. package/dist/trust_package.d.ts +21 -1
  194. package/dist/trust_package.d.ts.map +1 -1
  195. package/dist/trust_package.js +101 -4
  196. package/dist/verify.d.ts.map +1 -1
  197. package/dist/verify.js +9 -2
  198. package/dist/wal.d.ts.map +1 -1
  199. package/dist/wal.js +2 -1
  200. package/package.json +14 -1
  201. package/scripts/postinstall.js +119 -97
  202. package/templates/controls/iso42001_control_catalog.json +1443 -0
@@ -0,0 +1,922 @@
1
+ "use strict";
2
+ /**
3
+ * AIMS governance workflows for ISO 42001 policy, roles, and concerns.
4
+ *
5
+ * This module focuses on:
6
+ * - A.2 policy lifecycle (versioning, review cadence, approvals)
7
+ * - A.3 governance roles (RACI) and concern reporting
8
+ * - Clause 4/5/7 evidence tracking for context, leadership, and competence
9
+ */
10
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ var desc = Object.getOwnPropertyDescriptor(m, k);
13
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
14
+ desc = { enumerable: true, get: function() { return m[k]; } };
15
+ }
16
+ Object.defineProperty(o, k2, desc);
17
+ }) : (function(o, m, k, k2) {
18
+ if (k2 === undefined) k2 = k;
19
+ o[k2] = m[k];
20
+ }));
21
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
22
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
23
+ }) : function(o, v) {
24
+ o["default"] = v;
25
+ });
26
+ var __importStar = (this && this.__importStar) || (function () {
27
+ var ownKeys = function(o) {
28
+ ownKeys = Object.getOwnPropertyNames || function (o) {
29
+ var ar = [];
30
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
31
+ return ar;
32
+ };
33
+ return ownKeys(o);
34
+ };
35
+ return function (mod) {
36
+ if (mod && mod.__esModule) return mod;
37
+ var result = {};
38
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
39
+ __setModuleDefault(result, mod);
40
+ return result;
41
+ };
42
+ })();
43
+ Object.defineProperty(exports, "__esModule", { value: true });
44
+ exports.clearAimsGovernanceData = clearAimsGovernanceData;
45
+ exports.createPolicyDocument = createPolicyDocument;
46
+ exports.addPolicyVersion = addPolicyVersion;
47
+ exports.submitPolicyVersion = submitPolicyVersion;
48
+ exports.approvePolicyVersion = approvePolicyVersion;
49
+ exports.rejectPolicyVersion = rejectPolicyVersion;
50
+ exports.recordPolicyReview = recordPolicyReview;
51
+ exports.setPolicyReviewCadence = setPolicyReviewCadence;
52
+ exports.getPolicyDocument = getPolicyDocument;
53
+ exports.listPolicyDocuments = listPolicyDocuments;
54
+ exports.getDuePolicyReviews = getDuePolicyReviews;
55
+ exports.recordAimsContext = recordAimsContext;
56
+ exports.getAimsContext = getAimsContext;
57
+ exports.registerRaciEntry = registerRaciEntry;
58
+ exports.listRaciEntries = listRaciEntries;
59
+ exports.recordGovernanceTraining = recordGovernanceTraining;
60
+ exports.listGovernanceTrainingRecords = listGovernanceTrainingRecords;
61
+ exports.fileConcernReport = fileConcernReport;
62
+ exports.updateConcernReport = updateConcernReport;
63
+ exports.resolveConcernReport = resolveConcernReport;
64
+ exports.getConcernReport = getConcernReport;
65
+ exports.listConcernReports = listConcernReports;
66
+ exports.getOpenConcerns = getOpenConcerns;
67
+ exports.getAimsGovernanceSummary = getAimsGovernanceSummary;
68
+ exports.buildAimsGovernanceStatePayload = buildAimsGovernanceStatePayload;
69
+ exports.exportAimsGovernanceState = exportAimsGovernanceState;
70
+ exports.loadAimsGovernanceState = loadAimsGovernanceState;
71
+ const crypto = __importStar(require("crypto"));
72
+ const fs = __importStar(require("fs"));
73
+ const path = __importStar(require("path"));
74
+ const control_backbone_1 = require("./control_backbone");
75
+ const POLICY_STATUS_SET = new Set(['draft', 'in_review', 'approved', 'superseded', 'rejected']);
76
+ const CONCERN_STATUS_SET = new Set(['open', 'in_review', 'resolved', 'closed', 'rejected']);
77
+ const CONCERN_SEVERITY_SET = new Set(['low', 'medium', 'high', 'critical']);
78
+ const DEFAULT_POLICY_CONTROL_IDS = ['A.2.2', 'A.2.3', 'Clause5'];
79
+ const DEFAULT_CONTEXT_CONTROL_IDS = ['Clause4'];
80
+ const DEFAULT_RACI_CONTROL_IDS = ['A.3.2', 'Clause7'];
81
+ const DEFAULT_CONCERN_CONTROL_IDS = ['A.3.3'];
82
+ const DEFAULT_TRAINING_CONTROL_IDS = ['Clause7', 'A.4.6'];
83
+ const policies = new Map();
84
+ let contextRecord = null;
85
+ const raciEntries = new Map();
86
+ const concernReports = new Map();
87
+ const trainingRecords = new Map();
88
+ function utcNow() {
89
+ return new Date().toISOString();
90
+ }
91
+ function clone(value) {
92
+ return JSON.parse(JSON.stringify(value));
93
+ }
94
+ function normalizeList(value) {
95
+ if (value === null || value === undefined) {
96
+ return [];
97
+ }
98
+ if (Array.isArray(value)) {
99
+ return value
100
+ .map((item) => String(item).trim())
101
+ .filter(Boolean);
102
+ }
103
+ return String(value)
104
+ .replace(/;/g, ',')
105
+ .split(',')
106
+ .map((item) => item.trim())
107
+ .filter(Boolean);
108
+ }
109
+ function asPolicyStatus(value, fallback = 'draft') {
110
+ const text = String(value || '').trim();
111
+ return POLICY_STATUS_SET.has(text) ? text : fallback;
112
+ }
113
+ function asConcernStatus(value, fallback = 'open') {
114
+ const text = String(value || '').trim();
115
+ return CONCERN_STATUS_SET.has(text) ? text : fallback;
116
+ }
117
+ function asConcernSeverity(value, fallback = 'medium') {
118
+ const text = String(value || '').trim();
119
+ return CONCERN_SEVERITY_SET.has(text) ? text : fallback;
120
+ }
121
+ function buildId(prefix, ...parts) {
122
+ const seed = `${prefix}:${parts.join(':')}:${utcNow()}`;
123
+ const digest = crypto.createHash('sha256').update(seed).digest('hex').slice(0, 12);
124
+ return `${prefix}_${digest}`;
125
+ }
126
+ function parseIso(value) {
127
+ if (!value) {
128
+ return null;
129
+ }
130
+ const date = new Date(value);
131
+ if (Number.isNaN(date.getTime())) {
132
+ return null;
133
+ }
134
+ return date;
135
+ }
136
+ function getPolicy(policyId) {
137
+ const policy = policies.get(policyId);
138
+ if (!policy) {
139
+ throw new Error(`Policy not found: ${policyId}`);
140
+ }
141
+ return policy;
142
+ }
143
+ function getPolicyVersion(policy, versionId) {
144
+ const version = policy.versions.find((item) => item.version_id === versionId);
145
+ if (!version) {
146
+ throw new Error(`Policy version not found: ${policy.policy_id}:${versionId}`);
147
+ }
148
+ return version;
149
+ }
150
+ function hashPolicyContent(content, artifactPath, fallbackText = '') {
151
+ if (artifactPath && fs.existsSync(artifactPath)) {
152
+ const digest = crypto.createHash('sha256').update(fs.readFileSync(artifactPath)).digest('hex');
153
+ return `sha256:${digest}`;
154
+ }
155
+ const digest = crypto.createHash('sha256').update(content || fallbackText).digest('hex');
156
+ return `sha256:${digest}`;
157
+ }
158
+ function emitGovernanceEvidence(options) {
159
+ const normalizedEvidenceTypes = Array.from(new Set(normalizeList(options.evidenceTypes).concat([options.evidenceType, 'documented_information']))).sort();
160
+ const evidence = {
161
+ evidence_id: buildId('evd', options.evidenceType, options.title),
162
+ title: options.title,
163
+ source: 'aims_governance',
164
+ category: options.category,
165
+ collected_at: utcNow(),
166
+ collection_method: 'workflow',
167
+ system: 'aims_governance',
168
+ system_id: options.systemId,
169
+ control_ids: normalizeList(options.controlIds),
170
+ status: options.status || 'approved',
171
+ metadata: {
172
+ ...options.metadata,
173
+ evidence_type: options.evidenceType,
174
+ evidence_types: normalizedEvidenceTypes,
175
+ },
176
+ tags: options.tags,
177
+ };
178
+ let updated = clone(evidence);
179
+ const workflowIds = options.workflowIds || {};
180
+ for (const controlId of normalizeList(options.controlIds)) {
181
+ updated = (0, control_backbone_1.attachControlEvidence)({
182
+ controlId,
183
+ evidence: updated,
184
+ workflowId: workflowIds[controlId],
185
+ evidenceType: options.evidenceType,
186
+ note: 'aims_governance_auto_attach',
187
+ });
188
+ }
189
+ return updated;
190
+ }
191
+ function findExistingWorkflow(controlId, policyId) {
192
+ const workflows = (0, control_backbone_1.listWorkflowTasks)({ controlId });
193
+ if (policyId) {
194
+ const match = workflows.find((workflow) => workflow.metadata?.policy_id === policyId);
195
+ if (match) {
196
+ return match.workflow_id;
197
+ }
198
+ }
199
+ return workflows.length > 0 ? workflows[0].workflow_id : null;
200
+ }
201
+ function ensurePolicyWorkflows(policy) {
202
+ const workflowIds = { ...(policy.workflow_ids || {}) };
203
+ const controls = Array.from(new Set(normalizeList(policy.control_ids))).sort();
204
+ for (const controlId of controls) {
205
+ const existingId = workflowIds[controlId];
206
+ if (existingId && (0, control_backbone_1.getWorkflowTask)(existingId)) {
207
+ continue;
208
+ }
209
+ const found = findExistingWorkflow(controlId, policy.policy_id);
210
+ if (found) {
211
+ workflowIds[controlId] = found;
212
+ continue;
213
+ }
214
+ const dueAt = policy.next_review_due_at
215
+ || new Date(Date.now() + policy.review_frequency_days * 86400 * 1000).toISOString();
216
+ const workflow = (0, control_backbone_1.createWorkflowTask)({
217
+ controlId,
218
+ title: `${controlId} - ${policy.title}`,
219
+ owner: policy.owner,
220
+ dueAt,
221
+ metadata: { policy_id: policy.policy_id, policy_title: policy.title, workflow_kind: 'policy' },
222
+ });
223
+ workflowIds[controlId] = workflow.workflow_id;
224
+ }
225
+ policy.workflow_ids = workflowIds;
226
+ return workflowIds;
227
+ }
228
+ function syncPolicyWorkflowStatus(workflowIds, options) {
229
+ for (const workflowId of Object.values(workflowIds)) {
230
+ const workflow = (0, control_backbone_1.getWorkflowTask)(workflowId);
231
+ if (!workflow) {
232
+ continue;
233
+ }
234
+ const current = workflow.status;
235
+ if (options.targetStatus === 'in_review') {
236
+ if (current === 'in_review') {
237
+ continue;
238
+ }
239
+ if (current === 'draft' || current === 'approved' || current === 'expired') {
240
+ (0, control_backbone_1.submitWorkflowTask)(workflowId, { actor: options.actor, note: options.note });
241
+ }
242
+ }
243
+ else if (options.targetStatus === 'approved') {
244
+ if (current === 'approved') {
245
+ continue;
246
+ }
247
+ (0, control_backbone_1.approveControlEvidence)(workflowId, { actor: options.actor, note: options.note });
248
+ }
249
+ else if (options.targetStatus === 'draft') {
250
+ if (current !== 'in_review') {
251
+ continue;
252
+ }
253
+ (0, control_backbone_1.transitionWorkflowTask)(workflowId, { toStatus: 'draft', actor: options.actor, note: options.note });
254
+ }
255
+ }
256
+ }
257
+ function clearAimsGovernanceData() {
258
+ policies.clear();
259
+ raciEntries.clear();
260
+ concernReports.clear();
261
+ trainingRecords.clear();
262
+ contextRecord = null;
263
+ }
264
+ function createPolicyDocument(options) {
265
+ if (policies.has(options.policyId)) {
266
+ throw new Error(`Policy already exists: ${options.policyId}`);
267
+ }
268
+ const policy = {
269
+ policy_id: options.policyId,
270
+ title: options.title,
271
+ owner: options.owner,
272
+ review_frequency_days: Math.max(1, options.reviewFrequencyDays ?? 365),
273
+ control_ids: Array.from(new Set(normalizeList(options.controlIds).concat(DEFAULT_POLICY_CONTROL_IDS))).sort(),
274
+ created_at: utcNow(),
275
+ updated_at: utcNow(),
276
+ versions: [],
277
+ metadata: options.metadata || {},
278
+ };
279
+ ensurePolicyWorkflows(policy);
280
+ policies.set(policy.policy_id, clone(policy));
281
+ return clone(policy);
282
+ }
283
+ function addPolicyVersion(options) {
284
+ const policy = getPolicy(options.policyId);
285
+ const versionNumber = policy.versions.length + 1;
286
+ const version = {
287
+ version_id: `v${versionNumber}`,
288
+ version_number: versionNumber,
289
+ change_summary: options.changeSummary,
290
+ content_hash: hashPolicyContent(options.content, options.artifactPath, `${options.policyId}:${versionNumber}:${options.changeSummary}`),
291
+ created_by: options.createdBy,
292
+ created_at: utcNow(),
293
+ status: 'draft',
294
+ artifact_path: options.artifactPath,
295
+ required_reviewers: Array.from(new Set(normalizeList(options.requiredReviewers))).sort(),
296
+ approvals: [],
297
+ review_history: [],
298
+ metadata: options.metadata || {},
299
+ };
300
+ policy.versions.push(version);
301
+ policy.updated_at = utcNow();
302
+ policies.set(policy.policy_id, clone(policy));
303
+ return clone(version);
304
+ }
305
+ function submitPolicyVersion(options) {
306
+ const policy = getPolicy(options.policyId);
307
+ const version = getPolicyVersion(policy, options.versionId);
308
+ if (version.status !== 'draft' && version.status !== 'rejected') {
309
+ throw new Error(`Policy version cannot be submitted from status=${version.status}`);
310
+ }
311
+ const reviewers = Array.from(new Set(normalizeList(options.reviewerChain))).sort();
312
+ if (reviewers.length > 0) {
313
+ version.required_reviewers = reviewers;
314
+ }
315
+ else if (version.required_reviewers.length === 0) {
316
+ version.required_reviewers = [policy.owner];
317
+ }
318
+ version.status = 'in_review';
319
+ version.submitted_at = utcNow();
320
+ version.review_history.push({
321
+ timestamp: utcNow(),
322
+ actor: options.submittedBy,
323
+ action: 'submitted_for_review',
324
+ note: options.note,
325
+ });
326
+ policy.updated_at = utcNow();
327
+ const workflowIds = ensurePolicyWorkflows(policy);
328
+ syncPolicyWorkflowStatus(workflowIds, {
329
+ targetStatus: 'in_review',
330
+ actor: options.submittedBy,
331
+ note: options.note,
332
+ });
333
+ policies.set(policy.policy_id, clone(policy));
334
+ return clone(version);
335
+ }
336
+ function approvePolicyVersion(options) {
337
+ const policy = getPolicy(options.policyId);
338
+ const version = getPolicyVersion(policy, options.versionId);
339
+ if (version.status !== 'draft' && version.status !== 'in_review') {
340
+ throw new Error(`Policy version cannot be approved from status=${version.status}`);
341
+ }
342
+ if (version.status === 'draft') {
343
+ version.status = 'in_review';
344
+ version.submitted_at = utcNow();
345
+ }
346
+ if (version.required_reviewers.length > 0 &&
347
+ !options.force &&
348
+ !version.required_reviewers.includes(options.approver)) {
349
+ throw new Error(`Approver '${options.approver}' is not in required_reviewers=${version.required_reviewers.join(', ')}`);
350
+ }
351
+ if (!version.approvals.includes(options.approver)) {
352
+ version.approvals.push(options.approver);
353
+ }
354
+ version.review_history.push({
355
+ timestamp: utcNow(),
356
+ actor: options.approver,
357
+ action: 'approved_step',
358
+ note: options.note,
359
+ });
360
+ const required = new Set(version.required_reviewers);
361
+ const approvals = new Set(version.approvals);
362
+ const chainComplete = required.size === 0
363
+ || Array.from(required).every((reviewer) => approvals.has(reviewer))
364
+ || Boolean(options.force);
365
+ if (!chainComplete) {
366
+ policy.updated_at = utcNow();
367
+ policies.set(policy.policy_id, clone(policy));
368
+ return clone(version);
369
+ }
370
+ const approvedAt = utcNow();
371
+ version.status = 'approved';
372
+ version.approved_at = approvedAt;
373
+ version.approved_by = options.approver;
374
+ version.effective_at = approvedAt;
375
+ version.review_history.push({
376
+ timestamp: approvedAt,
377
+ actor: options.approver,
378
+ action: 'approval_chain_complete',
379
+ note: options.note,
380
+ });
381
+ for (const other of policy.versions) {
382
+ if (other.version_id !== version.version_id && other.status === 'approved') {
383
+ other.status = 'superseded';
384
+ }
385
+ }
386
+ policy.active_version_id = version.version_id;
387
+ policy.updated_at = utcNow();
388
+ policy.last_reviewed_at = approvedAt;
389
+ policy.next_review_due_at = new Date(Date.now() + policy.review_frequency_days * 86400 * 1000).toISOString();
390
+ const workflowIds = ensurePolicyWorkflows(policy);
391
+ emitGovernanceEvidence({
392
+ title: `Policy approved: ${policy.title} (${version.version_id})`,
393
+ evidenceType: 'policy_document',
394
+ category: 'policy_document',
395
+ controlIds: policy.control_ids,
396
+ systemId: policy.policy_id,
397
+ workflowIds,
398
+ metadata: {
399
+ policy_id: policy.policy_id,
400
+ title: policy.title,
401
+ version_id: version.version_id,
402
+ version_number: version.version_number,
403
+ approved_by: options.approver,
404
+ approved_at: approvedAt,
405
+ required_reviewers: version.required_reviewers,
406
+ approvals: version.approvals,
407
+ content_hash: version.content_hash,
408
+ },
409
+ tags: ['aims', 'policy', 'approval_chain'],
410
+ });
411
+ syncPolicyWorkflowStatus(workflowIds, {
412
+ targetStatus: 'approved',
413
+ actor: options.approver,
414
+ note: options.note,
415
+ });
416
+ policies.set(policy.policy_id, clone(policy));
417
+ return clone(version);
418
+ }
419
+ function rejectPolicyVersion(options) {
420
+ const policy = getPolicy(options.policyId);
421
+ const version = getPolicyVersion(policy, options.versionId);
422
+ if (version.status !== 'draft' && version.status !== 'in_review') {
423
+ throw new Error(`Policy version cannot be rejected from status=${version.status}`);
424
+ }
425
+ version.status = 'rejected';
426
+ version.review_history.push({
427
+ timestamp: utcNow(),
428
+ actor: options.reviewer,
429
+ action: 'rejected',
430
+ note: options.note,
431
+ });
432
+ policy.updated_at = utcNow();
433
+ const workflowIds = ensurePolicyWorkflows(policy);
434
+ syncPolicyWorkflowStatus(workflowIds, {
435
+ targetStatus: 'draft',
436
+ actor: options.reviewer,
437
+ note: options.note,
438
+ });
439
+ policies.set(policy.policy_id, clone(policy));
440
+ return clone(version);
441
+ }
442
+ function recordPolicyReview(options) {
443
+ const policy = getPolicy(options.policyId);
444
+ const version = getPolicyVersion(policy, options.versionId);
445
+ if (version.status !== 'approved' && version.status !== 'in_review') {
446
+ throw new Error(`Policy review not allowed from status=${version.status}`);
447
+ }
448
+ const outcome = options.outcome || 'accepted';
449
+ version.review_history.push({
450
+ timestamp: utcNow(),
451
+ actor: options.reviewer,
452
+ action: 'periodic_review',
453
+ note: `${outcome}: ${options.summary}`,
454
+ });
455
+ if (outcome === 'changes_required') {
456
+ version.status = 'in_review';
457
+ }
458
+ const reviewedAt = utcNow();
459
+ policy.last_reviewed_at = reviewedAt;
460
+ policy.updated_at = reviewedAt;
461
+ policy.next_review_due_at = new Date(Date.now() + policy.review_frequency_days * 86400 * 1000).toISOString();
462
+ const reviewControls = ['A.2.4', 'Clause9'];
463
+ const reviewWorkflows = {};
464
+ for (const controlId of reviewControls) {
465
+ const found = findExistingWorkflow(controlId, policy.policy_id);
466
+ if (found) {
467
+ reviewWorkflows[controlId] = found;
468
+ continue;
469
+ }
470
+ const workflow = (0, control_backbone_1.createWorkflowTask)({
471
+ controlId,
472
+ title: `${controlId} - ${policy.title} review`,
473
+ owner: policy.owner,
474
+ dueAt: policy.next_review_due_at,
475
+ metadata: { policy_id: policy.policy_id, policy_title: policy.title, workflow_kind: 'policy_review' },
476
+ });
477
+ reviewWorkflows[controlId] = workflow.workflow_id;
478
+ }
479
+ emitGovernanceEvidence({
480
+ title: `Policy review: ${policy.title} (${version.version_id})`,
481
+ evidenceType: 'review_minutes',
482
+ category: 'review_minutes',
483
+ controlIds: reviewControls,
484
+ systemId: policy.policy_id,
485
+ workflowIds: reviewWorkflows,
486
+ metadata: {
487
+ policy_id: policy.policy_id,
488
+ version_id: version.version_id,
489
+ reviewer: options.reviewer,
490
+ summary: options.summary,
491
+ outcome,
492
+ reviewed_at: reviewedAt,
493
+ },
494
+ tags: ['aims', 'policy', 'review_cadence'],
495
+ });
496
+ syncPolicyWorkflowStatus(reviewWorkflows, {
497
+ targetStatus: 'approved',
498
+ actor: options.reviewer,
499
+ note: options.summary,
500
+ });
501
+ policies.set(policy.policy_id, clone(policy));
502
+ return clone(version);
503
+ }
504
+ function setPolicyReviewCadence(policyId, reviewFrequencyDays) {
505
+ const policy = getPolicy(policyId);
506
+ policy.review_frequency_days = Math.max(1, reviewFrequencyDays);
507
+ policy.updated_at = utcNow();
508
+ policies.set(policy.policy_id, clone(policy));
509
+ return clone(policy);
510
+ }
511
+ function getPolicyDocument(policyId) {
512
+ const policy = policies.get(policyId);
513
+ return policy ? clone(policy) : null;
514
+ }
515
+ function listPolicyDocuments() {
516
+ return Array.from(policies.values()).map((policy) => clone(policy));
517
+ }
518
+ function getDuePolicyReviews(options) {
519
+ const now = parseIso(options?.referenceTime) || new Date();
520
+ const due = [];
521
+ for (const policy of policies.values()) {
522
+ const dueAt = parseIso(policy.next_review_due_at);
523
+ if (!dueAt || dueAt > now) {
524
+ continue;
525
+ }
526
+ const overdueDays = Math.max(0, Math.floor((now.getTime() - dueAt.getTime()) / (86400 * 1000)));
527
+ due.push({
528
+ policy_id: policy.policy_id,
529
+ title: policy.title,
530
+ owner: policy.owner,
531
+ active_version_id: policy.active_version_id,
532
+ next_review_due_at: policy.next_review_due_at || dueAt.toISOString(),
533
+ overdue_days: overdueDays,
534
+ });
535
+ }
536
+ return due.sort((a, b) => a.next_review_due_at.localeCompare(b.next_review_due_at));
537
+ }
538
+ function recordAimsContext(options) {
539
+ const version = contextRecord ? contextRecord.version + 1 : 1;
540
+ const reviewFrequencyDays = Math.max(1, options.reviewFrequencyDays ?? 365);
541
+ const record = {
542
+ context_id: 'aims_context',
543
+ scope: options.scope,
544
+ approved_by: options.approvedBy,
545
+ version,
546
+ interested_parties: normalizeList(options.interestedParties),
547
+ internal_issues: normalizeList(options.internalIssues),
548
+ external_issues: normalizeList(options.externalIssues),
549
+ objectives: normalizeList(options.objectives),
550
+ reviewed_at: utcNow(),
551
+ next_review_due_at: new Date(Date.now() + reviewFrequencyDays * 86400 * 1000).toISOString(),
552
+ review_frequency_days: reviewFrequencyDays,
553
+ metadata: options.metadata || {},
554
+ };
555
+ contextRecord = clone(record);
556
+ emitGovernanceEvidence({
557
+ title: 'AIMS context and scope review',
558
+ evidenceType: 'organizational_context_assessment',
559
+ category: 'organizational_context_assessment',
560
+ controlIds: DEFAULT_CONTEXT_CONTROL_IDS,
561
+ systemId: record.context_id,
562
+ metadata: record,
563
+ tags: ['aims', 'context', 'clause4'],
564
+ });
565
+ return clone(record);
566
+ }
567
+ function getAimsContext() {
568
+ return contextRecord ? clone(contextRecord) : null;
569
+ }
570
+ function registerRaciEntry(options) {
571
+ const entry = {
572
+ entry_id: options.entryId || buildId('raci', options.functionName),
573
+ function_name: options.functionName,
574
+ owner: options.owner,
575
+ accountable: Array.from(new Set(normalizeList(options.accountable))).sort(),
576
+ responsible: Array.from(new Set(normalizeList(options.responsible))).sort(),
577
+ consulted: Array.from(new Set(normalizeList(options.consulted))).sort(),
578
+ informed: Array.from(new Set(normalizeList(options.informed))).sort(),
579
+ control_ids: Array.from(new Set(normalizeList(options.controlIds).concat(DEFAULT_RACI_CONTROL_IDS))).sort(),
580
+ last_updated: utcNow(),
581
+ notes: options.notes,
582
+ metadata: options.metadata || {},
583
+ };
584
+ raciEntries.set(entry.entry_id, clone(entry));
585
+ emitGovernanceEvidence({
586
+ title: `RACI update: ${entry.function_name}`,
587
+ evidenceType: 'role_responsibility_matrix',
588
+ category: 'role_responsibility_matrix',
589
+ controlIds: entry.control_ids,
590
+ systemId: entry.entry_id,
591
+ metadata: entry,
592
+ tags: ['aims', 'raci', 'roles'],
593
+ });
594
+ return clone(entry);
595
+ }
596
+ function listRaciEntries() {
597
+ return Array.from(raciEntries.values()).map((entry) => clone(entry));
598
+ }
599
+ function recordGovernanceTraining(options) {
600
+ const record = {
601
+ record_id: buildId('train', options.userId, options.topic),
602
+ user_id: options.userId,
603
+ topic: options.topic,
604
+ completed_at: options.completedAt || utcNow(),
605
+ recorded_by: options.recordedBy,
606
+ control_ids: Array.from(new Set(normalizeList(options.controlIds).concat(DEFAULT_TRAINING_CONTROL_IDS))).sort(),
607
+ notes: options.notes,
608
+ metadata: options.metadata || {},
609
+ };
610
+ trainingRecords.set(record.record_id, clone(record));
611
+ emitGovernanceEvidence({
612
+ title: `Governance training completion: ${record.topic}`,
613
+ evidenceType: 'training_and_competency_records',
614
+ category: 'training_and_competency_records',
615
+ controlIds: record.control_ids,
616
+ systemId: record.record_id,
617
+ metadata: record,
618
+ tags: ['aims', 'training', 'competence'],
619
+ });
620
+ return clone(record);
621
+ }
622
+ function listGovernanceTrainingRecords() {
623
+ return Array.from(trainingRecords.values()).map((record) => clone(record));
624
+ }
625
+ function fileConcernReport(options) {
626
+ const concernId = options.concernId || buildId('concern', options.title);
627
+ if (concernReports.has(concernId)) {
628
+ throw new Error(`Concern already exists: ${concernId}`);
629
+ }
630
+ const report = {
631
+ concern_id: concernId,
632
+ title: options.title,
633
+ description: options.description,
634
+ reported_by: options.reportedBy,
635
+ severity: asConcernSeverity(options.severity, 'medium'),
636
+ status: 'open',
637
+ submitted_at: utcNow(),
638
+ updated_at: utcNow(),
639
+ assigned_to: options.assignedTo,
640
+ control_ids: Array.from(new Set(normalizeList(options.controlIds).concat(DEFAULT_CONCERN_CONTROL_IDS))).sort(),
641
+ tags: Array.from(new Set(normalizeList(options.tags))).sort(),
642
+ history: [
643
+ {
644
+ timestamp: utcNow(),
645
+ actor: options.reportedBy,
646
+ status: 'open',
647
+ note: 'concern_reported',
648
+ },
649
+ ],
650
+ metadata: options.metadata || {},
651
+ };
652
+ concernReports.set(concernId, clone(report));
653
+ emitGovernanceEvidence({
654
+ title: `Concern reported: ${report.title}`,
655
+ evidenceType: 'concern_reporting_records',
656
+ category: 'concern_reporting_records',
657
+ controlIds: report.control_ids,
658
+ systemId: report.concern_id,
659
+ metadata: report,
660
+ tags: ['aims', 'concern', 'intake'],
661
+ status: 'collected',
662
+ });
663
+ return clone(report);
664
+ }
665
+ function updateConcernReport(options) {
666
+ const report = concernReports.get(options.concernId);
667
+ if (!report) {
668
+ throw new Error(`Concern not found: ${options.concernId}`);
669
+ }
670
+ report.status = asConcernStatus(options.status, report.status);
671
+ if (options.assignedTo !== undefined) {
672
+ report.assigned_to = options.assignedTo;
673
+ }
674
+ report.updated_at = utcNow();
675
+ if (report.status === 'resolved' || report.status === 'closed') {
676
+ report.resolved_at = utcNow();
677
+ }
678
+ report.history.push({
679
+ timestamp: utcNow(),
680
+ actor: options.actor,
681
+ status: report.status,
682
+ note: options.note,
683
+ });
684
+ concernReports.set(report.concern_id, clone(report));
685
+ if (report.status === 'resolved' || report.status === 'closed') {
686
+ emitGovernanceEvidence({
687
+ title: `Concern resolved: ${report.title}`,
688
+ evidenceType: 'concern_reporting_records',
689
+ category: 'concern_reporting_records',
690
+ controlIds: report.control_ids,
691
+ systemId: report.concern_id,
692
+ metadata: report,
693
+ tags: ['aims', 'concern', 'resolution'],
694
+ });
695
+ }
696
+ return clone(report);
697
+ }
698
+ function resolveConcernReport(options) {
699
+ return updateConcernReport({
700
+ concernId: options.concernId,
701
+ actor: options.resolvedBy,
702
+ status: options.close === false ? 'resolved' : 'closed',
703
+ note: options.resolutionSummary,
704
+ });
705
+ }
706
+ function getConcernReport(concernId) {
707
+ const report = concernReports.get(concernId);
708
+ return report ? clone(report) : null;
709
+ }
710
+ function listConcernReports(options) {
711
+ const items = Array.from(concernReports.values())
712
+ .filter((item) => !options?.status || item.status === options.status)
713
+ .map((item) => clone(item));
714
+ return items;
715
+ }
716
+ function getOpenConcerns() {
717
+ return listConcernReports({ status: 'open' });
718
+ }
719
+ function getAimsGovernanceSummary() {
720
+ let policyInReview = 0;
721
+ let policyApproved = 0;
722
+ for (const policy of policies.values()) {
723
+ for (const version of policy.versions) {
724
+ if (version.status === 'in_review') {
725
+ policyInReview += 1;
726
+ }
727
+ if (version.status === 'approved') {
728
+ policyApproved += 1;
729
+ }
730
+ }
731
+ }
732
+ const openConcerns = Array.from(concernReports.values()).filter((item) => item.status === 'open' || item.status === 'in_review').length;
733
+ const closedConcerns = Array.from(concernReports.values()).filter((item) => item.status === 'resolved' || item.status === 'closed').length;
734
+ return {
735
+ policy_documents: policies.size,
736
+ policy_versions_in_review: policyInReview,
737
+ policy_versions_approved: policyApproved,
738
+ due_policy_reviews: getDuePolicyReviews().length,
739
+ raci_entries: raciEntries.size,
740
+ concerns_total: concernReports.size,
741
+ concerns_open_or_in_review: openConcerns,
742
+ concerns_resolved_or_closed: closedConcerns,
743
+ aims_context_version: contextRecord ? contextRecord.version : 0,
744
+ training_records: trainingRecords.size,
745
+ };
746
+ }
747
+ function buildAimsGovernanceStatePayload() {
748
+ return {
749
+ state_type: 'aims_governance_state',
750
+ generated_at: utcNow(),
751
+ policies: Array.from(policies.values()),
752
+ context_record: contextRecord ? clone(contextRecord) : null,
753
+ raci_entries: Array.from(raciEntries.values()),
754
+ concern_reports: Array.from(concernReports.values()),
755
+ training_records: Array.from(trainingRecords.values()),
756
+ };
757
+ }
758
+ function exportAimsGovernanceState(outputPath) {
759
+ const payload = buildAimsGovernanceStatePayload();
760
+ fs.mkdirSync(path.dirname(outputPath), { recursive: true });
761
+ fs.writeFileSync(outputPath, JSON.stringify(payload, null, 2), 'utf-8');
762
+ return clone(payload);
763
+ }
764
+ function loadAimsGovernanceState(inputPath, options) {
765
+ const payload = JSON.parse(fs.readFileSync(inputPath, 'utf-8'));
766
+ if (!payload || typeof payload !== 'object') {
767
+ throw new Error('Invalid AIMS governance state payload');
768
+ }
769
+ const replaceRuntime = options?.replaceRuntime !== false;
770
+ if (!replaceRuntime) {
771
+ return payload;
772
+ }
773
+ clearAimsGovernanceData();
774
+ for (const rawPolicy of Array.isArray(payload.policies) ? payload.policies : []) {
775
+ if (!rawPolicy || typeof rawPolicy !== 'object') {
776
+ continue;
777
+ }
778
+ const versions = [];
779
+ for (const rawVersion of Array.isArray(rawPolicy.versions) ? rawPolicy.versions : []) {
780
+ if (!rawVersion || typeof rawVersion !== 'object') {
781
+ continue;
782
+ }
783
+ versions.push({
784
+ version_id: String(rawVersion.version_id || buildId('ver')),
785
+ version_number: Number(rawVersion.version_number || 1),
786
+ change_summary: String(rawVersion.change_summary || ''),
787
+ content_hash: String(rawVersion.content_hash || ''),
788
+ created_by: String(rawVersion.created_by || 'unknown'),
789
+ created_at: String(rawVersion.created_at || utcNow()),
790
+ status: asPolicyStatus(rawVersion.status),
791
+ artifact_path: rawVersion.artifact_path || undefined,
792
+ submitted_at: rawVersion.submitted_at || undefined,
793
+ approved_at: rawVersion.approved_at || undefined,
794
+ approved_by: rawVersion.approved_by || undefined,
795
+ effective_at: rawVersion.effective_at || undefined,
796
+ required_reviewers: normalizeList(rawVersion.required_reviewers),
797
+ approvals: normalizeList(rawVersion.approvals),
798
+ review_history: (Array.isArray(rawVersion.review_history) ? rawVersion.review_history : [])
799
+ .filter((item) => item && typeof item === 'object')
800
+ .map((item) => ({
801
+ timestamp: String(item.timestamp || utcNow()),
802
+ actor: String(item.actor || 'unknown'),
803
+ action: String(item.action || 'event'),
804
+ note: item.note || undefined,
805
+ })),
806
+ metadata: rawVersion.metadata && typeof rawVersion.metadata === 'object'
807
+ ? rawVersion.metadata
808
+ : {},
809
+ });
810
+ }
811
+ const policy = {
812
+ policy_id: String(rawPolicy.policy_id || buildId('policy')),
813
+ title: String(rawPolicy.title || 'policy'),
814
+ owner: String(rawPolicy.owner || 'owner'),
815
+ review_frequency_days: Math.max(1, Number(rawPolicy.review_frequency_days || 365)),
816
+ control_ids: Array.from(new Set(normalizeList(rawPolicy.control_ids).concat(DEFAULT_POLICY_CONTROL_IDS))).sort(),
817
+ created_at: String(rawPolicy.created_at || utcNow()),
818
+ updated_at: String(rawPolicy.updated_at || utcNow()),
819
+ active_version_id: rawPolicy.active_version_id || undefined,
820
+ last_reviewed_at: rawPolicy.last_reviewed_at || undefined,
821
+ next_review_due_at: rawPolicy.next_review_due_at || undefined,
822
+ workflow_ids: rawPolicy.workflow_ids && typeof rawPolicy.workflow_ids === 'object'
823
+ ? rawPolicy.workflow_ids
824
+ : undefined,
825
+ versions,
826
+ metadata: rawPolicy.metadata && typeof rawPolicy.metadata === 'object'
827
+ ? rawPolicy.metadata
828
+ : {},
829
+ };
830
+ policies.set(policy.policy_id, clone(policy));
831
+ }
832
+ if (payload.context_record && typeof payload.context_record === 'object') {
833
+ const rawContext = payload.context_record;
834
+ contextRecord = {
835
+ context_id: String(rawContext.context_id || 'aims_context'),
836
+ scope: String(rawContext.scope || ''),
837
+ approved_by: String(rawContext.approved_by || 'unknown'),
838
+ version: Math.max(1, Number(rawContext.version || 1)),
839
+ interested_parties: normalizeList(rawContext.interested_parties),
840
+ internal_issues: normalizeList(rawContext.internal_issues),
841
+ external_issues: normalizeList(rawContext.external_issues),
842
+ objectives: normalizeList(rawContext.objectives),
843
+ reviewed_at: String(rawContext.reviewed_at || utcNow()),
844
+ next_review_due_at: rawContext.next_review_due_at || undefined,
845
+ review_frequency_days: Math.max(1, Number(rawContext.review_frequency_days || 365)),
846
+ metadata: rawContext.metadata && typeof rawContext.metadata === 'object'
847
+ ? rawContext.metadata
848
+ : {},
849
+ };
850
+ }
851
+ for (const rawEntry of Array.isArray(payload.raci_entries) ? payload.raci_entries : []) {
852
+ if (!rawEntry || typeof rawEntry !== 'object') {
853
+ continue;
854
+ }
855
+ const entry = {
856
+ entry_id: String(rawEntry.entry_id || buildId('raci', 'entry')),
857
+ function_name: String(rawEntry.function_name || 'function'),
858
+ owner: String(rawEntry.owner || 'owner'),
859
+ accountable: normalizeList(rawEntry.accountable),
860
+ responsible: normalizeList(rawEntry.responsible),
861
+ consulted: normalizeList(rawEntry.consulted),
862
+ informed: normalizeList(rawEntry.informed),
863
+ control_ids: Array.from(new Set(normalizeList(rawEntry.control_ids).concat(DEFAULT_RACI_CONTROL_IDS))).sort(),
864
+ last_updated: String(rawEntry.last_updated || utcNow()),
865
+ notes: rawEntry.notes || undefined,
866
+ metadata: rawEntry.metadata && typeof rawEntry.metadata === 'object'
867
+ ? rawEntry.metadata
868
+ : {},
869
+ };
870
+ raciEntries.set(entry.entry_id, clone(entry));
871
+ }
872
+ for (const rawConcern of Array.isArray(payload.concern_reports) ? payload.concern_reports : []) {
873
+ if (!rawConcern || typeof rawConcern !== 'object') {
874
+ continue;
875
+ }
876
+ const report = {
877
+ concern_id: String(rawConcern.concern_id || buildId('concern')),
878
+ title: String(rawConcern.title || 'concern'),
879
+ description: String(rawConcern.description || ''),
880
+ reported_by: String(rawConcern.reported_by || 'unknown'),
881
+ severity: asConcernSeverity(rawConcern.severity, 'medium'),
882
+ status: asConcernStatus(rawConcern.status, 'open'),
883
+ submitted_at: String(rawConcern.submitted_at || utcNow()),
884
+ updated_at: String(rawConcern.updated_at || utcNow()),
885
+ assigned_to: rawConcern.assigned_to || undefined,
886
+ resolved_at: rawConcern.resolved_at || undefined,
887
+ control_ids: Array.from(new Set(normalizeList(rawConcern.control_ids).concat(DEFAULT_CONCERN_CONTROL_IDS))).sort(),
888
+ tags: Array.from(new Set(normalizeList(rawConcern.tags))).sort(),
889
+ history: (Array.isArray(rawConcern.history) ? rawConcern.history : [])
890
+ .filter((item) => item && typeof item === 'object')
891
+ .map((item) => ({
892
+ timestamp: String(item.timestamp || utcNow()),
893
+ actor: String(item.actor || 'unknown'),
894
+ status: asConcernStatus(item.status, 'open'),
895
+ note: item.note || undefined,
896
+ })),
897
+ metadata: rawConcern.metadata && typeof rawConcern.metadata === 'object'
898
+ ? rawConcern.metadata
899
+ : {},
900
+ };
901
+ concernReports.set(report.concern_id, clone(report));
902
+ }
903
+ for (const rawTraining of Array.isArray(payload.training_records) ? payload.training_records : []) {
904
+ if (!rawTraining || typeof rawTraining !== 'object') {
905
+ continue;
906
+ }
907
+ const record = {
908
+ record_id: String(rawTraining.record_id || buildId('train', 'record')),
909
+ user_id: String(rawTraining.user_id || 'unknown'),
910
+ topic: String(rawTraining.topic || 'topic'),
911
+ completed_at: String(rawTraining.completed_at || utcNow()),
912
+ recorded_by: String(rawTraining.recorded_by || 'unknown'),
913
+ control_ids: Array.from(new Set(normalizeList(rawTraining.control_ids).concat(DEFAULT_TRAINING_CONTROL_IDS))).sort(),
914
+ notes: rawTraining.notes || undefined,
915
+ metadata: rawTraining.metadata && typeof rawTraining.metadata === 'object'
916
+ ? rawTraining.metadata
917
+ : {},
918
+ };
919
+ trainingRecords.set(record.record_id, clone(record));
920
+ }
921
+ return payload;
922
+ }