dxcomplete 0.1.0 → 0.2.1

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 (50) hide show
  1. package/README.md +22 -22
  2. package/dist/init.js +19 -0
  3. package/dist/mcp/docs.d.ts +18 -2
  4. package/dist/mcp/docs.js +201 -13
  5. package/dist/mcp/server.js +761 -47
  6. package/dist/runtime/check.d.ts +1 -1
  7. package/dist/runtime/records.d.ts +95 -4
  8. package/dist/runtime/records.js +640 -11
  9. package/dist/validate.js +3 -1
  10. package/docs/codex-integration.md +45 -18
  11. package/docs/glossary.md +39 -7
  12. package/docs/index.md +2 -1
  13. package/docs/model.md +3 -3
  14. package/docs/operating-guide.md +116 -0
  15. package/docs/taxonomy.md +12 -6
  16. package/docs/workflows.md +5 -3
  17. package/package.json +21 -3
  18. package/scripts/{dogfood-work-order.mjs → runtime-work-order.mjs} +4 -4
  19. package/scripts/smoke-mcp-http.mjs +460 -6
  20. package/src/init.ts +35 -0
  21. package/src/mcp/docs.ts +234 -14
  22. package/src/mcp/server.ts +1138 -83
  23. package/src/runtime/records.ts +914 -12
  24. package/src/validate.ts +3 -1
  25. package/templates/AGENTS.md +30 -0
  26. package/templates/process/controls.yml +10 -6
  27. package/templates/process/diagrams/01-intake-triage.mmd +5 -5
  28. package/templates/process/diagrams/02-product-definition.mmd +1 -1
  29. package/templates/process/diagrams/06-change-release-control.mmd +5 -7
  30. package/templates/process/diagrams/07-deployment-operations.mmd +2 -2
  31. package/templates/process/diagrams/08-support-incident-management.mmd +5 -4
  32. package/templates/process/diagrams/09-problem-improvement.mmd +4 -3
  33. package/templates/process/diagrams/10-risk-control-management.mmd +6 -4
  34. package/templates/process/diagrams/11-audit-evidence-capture.mmd +1 -1
  35. package/templates/process/taxonomy.yml +91 -17
  36. package/templates/process/workflows.yml +10 -9
  37. package/website/flow.html +1 -0
  38. package/website/glossary.html +37 -8
  39. package/website/index.html +2 -1
  40. package/website/objects.html +68 -11
  41. package/website/operating-guide.html +165 -0
  42. package/website/outcomes.html +1 -0
  43. package/website/phase-build.html +1 -0
  44. package/website/phase-elicit.html +1 -0
  45. package/website/phase-go-live.html +2 -1
  46. package/website/phase-measure.html +1 -0
  47. package/website/phase-operate.html +1 -0
  48. package/website/phase-orient.html +1 -0
  49. package/website/phase-weigh.html +1 -0
  50. package/website/roles.html +1 -0
@@ -16,6 +16,11 @@ export const COLLECTION_NAMES = [
16
16
  "commitments",
17
17
  "deferrals",
18
18
  "changes",
19
+ "incidents",
20
+ "problems",
21
+ "maintenance_schedules",
22
+ "support_requests",
23
+ "value_realizations",
19
24
  "decisions",
20
25
  "risks"
21
26
  ] as const;
@@ -46,6 +51,11 @@ export const READABLE_ID_TYPE_CODES = {
46
51
  deferrals: "DFR",
47
52
  decisions: "DEC",
48
53
  changes: "CHG",
54
+ incidents: "INC",
55
+ problems: "PRB",
56
+ maintenance_schedules: "MNT",
57
+ support_requests: "SUP",
58
+ value_realizations: "VAL",
49
59
  risks: "RSK",
50
60
  estimates: "EST",
51
61
  benefits: "BFT"
@@ -90,6 +100,7 @@ export type DxcRecord = {
90
100
  createdBy: string;
91
101
  updatedAt: string;
92
102
  updatedBy: string;
103
+ derived?: Record<string, unknown>;
93
104
  };
94
105
 
95
106
  export type ReviewableRecordType = "expectations" | "requirements";
@@ -123,7 +134,6 @@ export type RecordVersionHistoryEntry = {
123
134
  export type ChangeEventType =
124
135
  | "notice_given"
125
136
  | "veto_recorded"
126
- | "emergency_declared"
127
137
  | "decision_recorded"
128
138
  | "result_reported"
129
139
  | "recovery_recorded"
@@ -185,6 +195,70 @@ export type TaskEntry = {
185
195
  status?: TaskStatus;
186
196
  };
187
197
 
198
+ export type RiskEntryType = "identified" | "assessment" | "treatment" | "monitor_note" | "closed" | "reopened";
199
+ export type RiskLevel = "low" | "medium" | "high";
200
+ export type RiskTreatment = "accept" | "mitigate" | "transfer" | "avoid";
201
+
202
+ export type RiskEntry = {
203
+ id: string;
204
+ entryType: RiskEntryType;
205
+ body: string;
206
+ createdAt: string;
207
+ createdBy: string;
208
+ likelihood?: RiskLevel;
209
+ impact?: RiskLevel;
210
+ treatment?: RiskTreatment;
211
+ treatmentRationale?: string;
212
+ };
213
+
214
+ export type IncidentEntryType = "detected" | "update" | "severity" | "resolved" | "reopened" | "note";
215
+ export type IncidentSeverity = "low" | "medium" | "high" | "critical";
216
+
217
+ export type IncidentEntry = {
218
+ id: string;
219
+ entryType: IncidentEntryType;
220
+ body: string;
221
+ createdAt: string;
222
+ createdBy: string;
223
+ severity?: IncidentSeverity;
224
+ };
225
+
226
+ export type ProblemEntryType =
227
+ | "identified"
228
+ | "investigation"
229
+ | "root_cause"
230
+ | "known_error"
231
+ | "resolved"
232
+ | "reopened"
233
+ | "note";
234
+
235
+ export type ProblemEntry = {
236
+ id: string;
237
+ entryType: ProblemEntryType;
238
+ body: string;
239
+ createdAt: string;
240
+ createdBy: string;
241
+ rootCause?: string;
242
+ };
243
+
244
+ export type SupportRequestEntryType =
245
+ | "raised"
246
+ | "triage"
247
+ | "update"
248
+ | "escalated"
249
+ | "resolved"
250
+ | "reopened"
251
+ | "note";
252
+
253
+ export type SupportRequestEntry = {
254
+ id: string;
255
+ entryType: SupportRequestEntryType;
256
+ body: string;
257
+ createdAt: string;
258
+ createdBy: string;
259
+ incidentId?: string;
260
+ };
261
+
188
262
  export type JournalEntryKind = "note" | "summary";
189
263
 
190
264
  export type AppendJournalNoteInput = {
@@ -282,6 +356,41 @@ export type AppendTaskEntryInput = {
282
356
  status?: TaskStatus;
283
357
  };
284
358
 
359
+ export type AppendRiskEntryInput = {
360
+ workspaceId: string;
361
+ riskId: string;
362
+ entryType: RiskEntryType;
363
+ body: string;
364
+ likelihood?: RiskLevel;
365
+ impact?: RiskLevel;
366
+ treatment?: RiskTreatment;
367
+ treatmentRationale?: string;
368
+ };
369
+
370
+ export type AppendIncidentEntryInput = {
371
+ workspaceId: string;
372
+ incidentId: string;
373
+ entryType: IncidentEntryType;
374
+ body: string;
375
+ severity?: IncidentSeverity;
376
+ };
377
+
378
+ export type AppendProblemEntryInput = {
379
+ workspaceId: string;
380
+ problemId: string;
381
+ entryType: ProblemEntryType;
382
+ body: string;
383
+ rootCause?: string;
384
+ };
385
+
386
+ export type AppendSupportRequestEntryInput = {
387
+ workspaceId: string;
388
+ supportRequestId: string;
389
+ entryType: SupportRequestEntryType;
390
+ body: string;
391
+ incidentId?: string;
392
+ };
393
+
285
394
  export type UnlinkRecordsInput = {
286
395
  fromType: CollectionName;
287
396
  fromId: string;
@@ -374,7 +483,9 @@ const VERSIONED_RECORD_TYPES = [
374
483
  "expectations",
375
484
  "requirements",
376
485
  "estimates",
377
- "benefits"
486
+ "benefits",
487
+ "maintenance_schedules",
488
+ "value_realizations"
378
489
  ] as const;
379
490
  const VERSION_HISTORY_FIELD = "versionHistory";
380
491
  const CHANGE_MANAGED_FIELDS = [
@@ -382,6 +493,11 @@ const CHANGE_MANAGED_FIELDS = [
382
493
  "executionSteps",
383
494
  "rollbackPlan",
384
495
  "riskImpact",
496
+ "changeType",
497
+ "impactGrade",
498
+ "emergencyImportance",
499
+ "emergencyImmediacy",
500
+ "emergencyRationaleGaps",
385
501
  "plannedFor",
386
502
  "events"
387
503
  ] as const;
@@ -399,6 +515,23 @@ const COMPONENT_MANAGED_FIELDS = [
399
515
  "secretPointers",
400
516
  "notes"
401
517
  ] as const;
518
+ const MAINTENANCE_SCHEDULE_MANAGED_FIELDS = [
519
+ "name",
520
+ "kind",
521
+ "cadence",
522
+ "startDate",
523
+ "rationale",
524
+ "notes"
525
+ ] as const;
526
+ const SUPPORT_REQUEST_LEDGER_MANAGED_FIELDS = [
527
+ "reporter",
528
+ "kind",
529
+ "reportedExperience",
530
+ "entries",
531
+ "currentStatus",
532
+ "status"
533
+ ] as const;
534
+ const VALUE_REALIZATION_MANAGED_FIELDS = ["metrics"] as const;
402
535
  const DECISION_LEDGER_MANAGED_FIELDS = [
403
536
  "matter",
404
537
  "entries",
@@ -420,6 +553,32 @@ const TASK_LEDGER_MANAGED_FIELDS = [
420
553
  "details",
421
554
  "status"
422
555
  ] as const;
556
+ const RISK_LEDGER_MANAGED_FIELDS = [
557
+ "topic",
558
+ "entries",
559
+ "currentStatus",
560
+ "currentAssessment",
561
+ "currentTreatment",
562
+ "likelihood",
563
+ "impact",
564
+ "mitigation"
565
+ ] as const;
566
+ const INCIDENT_LEDGER_MANAGED_FIELDS = [
567
+ "description",
568
+ "entries",
569
+ "currentStatus",
570
+ "currentSeverity",
571
+ "status",
572
+ "severity"
573
+ ] as const;
574
+ const PROBLEM_LEDGER_MANAGED_FIELDS = [
575
+ "description",
576
+ "entries",
577
+ "currentStatus",
578
+ "currentRootCause",
579
+ "status",
580
+ "rootCause"
581
+ ] as const;
423
582
  const DECISION_INPUT_MANAGED_FIELDS = ["informedBy", "informedByIds", "inputRecords"] as const;
424
583
  const JOURNAL_ENTRY_MANAGED_FIELDS = ["kind", "body", "tag", "covers", "coveredBySummaryId"] as const;
425
584
  const JOURNAL_COMPACTION_THRESHOLD = 200;
@@ -430,7 +589,9 @@ const VERSIONED_TYPED_FIELDS: Partial<Record<CollectionName, string[]>> = {
430
589
  estimates: ["lineItems", "rollup"],
431
590
  benefits: ["benefitItems", "rollup"],
432
591
  environments: ["name", "description"],
433
- components: ["name", "environmentId", "kind", "locator", "identifiers", "secretPointers", "notes"]
592
+ components: ["name", "environmentId", "kind", "locator", "identifiers", "secretPointers", "notes"],
593
+ maintenance_schedules: ["name", "kind", "cadence", "startDate", "rationale", "notes"],
594
+ value_realizations: ["metrics"]
434
595
  };
435
596
 
436
597
  const RESERVED_RELATIONSHIP_FIELDS: Partial<Record<CollectionName, string[]>> = {
@@ -439,6 +600,7 @@ const RESERVED_RELATIONSHIP_FIELDS: Partial<Record<CollectionName, string[]>> =
439
600
  deferrals: ["requirementIds", "expectationIds"],
440
601
  estimates: ["requirementIds", "expectationIds"],
441
602
  benefits: ["requirementIds", "expectationIds"],
603
+ value_realizations: ["requirementIds", "expectationIds", "commitmentIds"],
442
604
  expectations: ["statementId"],
443
605
  requirements: ["expectationId"],
444
606
  tasks: ["requirementId"]
@@ -503,11 +665,11 @@ export async function createRecord(
503
665
  } finally {
504
666
  await session.endSession();
505
667
  }
506
- return record;
668
+ return withDerivedRecordFields(db, record);
507
669
  }
508
670
 
509
671
  await db.collection<DxcRecord>(recordType).insertOne(record);
510
- return record;
672
+ return withDerivedRecordFields(db, record);
511
673
  }
512
674
 
513
675
  export async function listRecords(
@@ -526,12 +688,14 @@ export async function listRecords(
526
688
  filter.archivedAt = { $exists: false };
527
689
  }
528
690
 
529
- return db
691
+ const records = await db
530
692
  .collection<DxcRecord>(recordType)
531
693
  .find(filter)
532
694
  .sort({ createdAt: -1 })
533
695
  .limit(limit)
534
696
  .toArray();
697
+
698
+ return Promise.all(records.map((record) => withDerivedRecordFields(db, record)));
535
699
  }
536
700
 
537
701
  export async function getRecord(
@@ -550,7 +714,8 @@ export async function getRecord(
550
714
  filter.workspaceId = readRequiredWorkspaceId(options.workspaceId, recordType);
551
715
  }
552
716
 
553
- return db.collection<DxcRecord>(recordType).findOne(filter);
717
+ const record = await db.collection<DxcRecord>(recordType).findOne(filter);
718
+ return record ? withDerivedRecordFields(db, record) : null;
554
719
  }
555
720
 
556
721
  export async function listLinkedRecords(
@@ -648,6 +813,266 @@ export async function listLinkedRecords(
648
813
  return result;
649
814
  }
650
815
 
816
+ async function withDerivedRecordFields(db: Db, record: DxcRecord): Promise<DxcRecord> {
817
+ if (record.recordType === "maintenance_schedules") {
818
+ return {
819
+ ...record,
820
+ derived: await deriveMaintenanceScheduleState(db, record)
821
+ };
822
+ }
823
+
824
+ if (record.recordType === "value_realizations") {
825
+ return {
826
+ ...record,
827
+ derived: deriveValueRealizationState(record)
828
+ };
829
+ }
830
+
831
+ return record;
832
+ }
833
+
834
+ async function deriveMaintenanceScheduleState(db: Db, record: DxcRecord): Promise<Record<string, unknown>> {
835
+ const cadence = parseMaintenanceCadence(record.fields.cadence);
836
+ const startDate = parseDateString(record.fields.startDate);
837
+ const occurrences = await findMaintenanceOccurrences(db, record);
838
+ const latestOccurrence = occurrences[0];
839
+ const latestOccurrenceAt = latestOccurrence?.occurredAt;
840
+ const basis = latestOccurrenceAt ?? startDate?.toISOString();
841
+ const nextDueAt = cadence && basis ? addMaintenanceCadence(new Date(basis), cadence).toISOString() : undefined;
842
+
843
+ return compactRuntimeObject({
844
+ occurrenceCount: occurrences.length,
845
+ latestOccurrence,
846
+ cadence: cadence ?? undefined,
847
+ startDate: startDate?.toISOString(),
848
+ nextDueAt,
849
+ overdue: nextDueAt ? new Date() > new Date(nextDueAt) : undefined
850
+ });
851
+ }
852
+
853
+ async function findMaintenanceOccurrences(
854
+ db: Db,
855
+ schedule: DxcRecord
856
+ ): Promise<Array<{ recordType: "changes" | "tasks"; id: string; readableId?: string; occurredAt: string }>> {
857
+ const workspaceId = schedule.workspaceId;
858
+ if (!workspaceId) {
859
+ return [];
860
+ }
861
+
862
+ const linkFilter = {
863
+ workspaceId,
864
+ archivedAt: { $exists: false },
865
+ links: {
866
+ $elemMatch: {
867
+ toType: "maintenance_schedules",
868
+ toId: schedule._id,
869
+ relationship: "assigned_to"
870
+ }
871
+ }
872
+ };
873
+ const [changes, tasks] = await Promise.all([
874
+ db.collection<DxcRecord>("changes").find(linkFilter).toArray(),
875
+ db.collection<DxcRecord>("tasks").find(linkFilter).toArray()
876
+ ]);
877
+ const occurrences = [
878
+ ...changes.flatMap((record) => occurrenceFromChange(record)),
879
+ ...tasks.flatMap((record) => occurrenceFromTask(record))
880
+ ];
881
+
882
+ return occurrences.sort((left, right) => right.occurredAt.localeCompare(left.occurredAt));
883
+ }
884
+
885
+ function occurrenceFromChange(record: DxcRecord): Array<{ recordType: "changes"; id: string; readableId?: string; occurredAt: string }> {
886
+ const events = Array.isArray(record.fields.events) ? record.fields.events : [];
887
+ const event = [...events]
888
+ .reverse()
889
+ .find((entry): entry is Record<string, unknown> =>
890
+ Boolean(
891
+ entry &&
892
+ typeof entry === "object" &&
893
+ (entry.eventType === "result_reported" || entry.eventType === "recovery_recorded") &&
894
+ typeof entry.createdAt === "string"
895
+ )
896
+ );
897
+ const plannedFor = typeof record.fields.plannedFor === "string" ? record.fields.plannedFor : undefined;
898
+ const occurredAt = typeof event?.createdAt === "string" ? event.createdAt : plannedFor ?? record.createdAt;
899
+
900
+ return [{ recordType: "changes", id: record._id, readableId: record.readableId, occurredAt }];
901
+ }
902
+
903
+ function occurrenceFromTask(record: DxcRecord): Array<{ recordType: "tasks"; id: string; readableId?: string; occurredAt: string }> {
904
+ const entries = Array.isArray(record.fields.entries) ? record.fields.entries : [];
905
+ const doneEntry = [...entries]
906
+ .reverse()
907
+ .find((entry): entry is Record<string, unknown> =>
908
+ Boolean(
909
+ entry &&
910
+ typeof entry === "object" &&
911
+ entry.entryType === "status_change" &&
912
+ entry.status === "done" &&
913
+ typeof entry.createdAt === "string"
914
+ )
915
+ );
916
+
917
+ const occurredAt = typeof doneEntry?.createdAt === "string" ? doneEntry.createdAt : record.createdAt;
918
+ return [{ recordType: "tasks", id: record._id, readableId: record.readableId, occurredAt }];
919
+ }
920
+
921
+ type MaintenanceCadence = {
922
+ count: number;
923
+ unit: "day" | "week" | "month" | "quarter" | "year";
924
+ };
925
+
926
+ function parseMaintenanceCadence(value: unknown): MaintenanceCadence | undefined {
927
+ if (!value || typeof value !== "object") {
928
+ return undefined;
929
+ }
930
+ const cadence = value as Partial<MaintenanceCadence>;
931
+ if (
932
+ Number.isInteger(cadence.count) &&
933
+ (cadence.count ?? 0) > 0 &&
934
+ (cadence.unit === "day" ||
935
+ cadence.unit === "week" ||
936
+ cadence.unit === "month" ||
937
+ cadence.unit === "quarter" ||
938
+ cadence.unit === "year")
939
+ ) {
940
+ return { count: cadence.count as number, unit: cadence.unit };
941
+ }
942
+ return undefined;
943
+ }
944
+
945
+ function parseDateString(value: unknown): Date | undefined {
946
+ if (typeof value !== "string") {
947
+ return undefined;
948
+ }
949
+ const date = new Date(value);
950
+ return Number.isNaN(date.getTime()) ? undefined : date;
951
+ }
952
+
953
+ function addMaintenanceCadence(date: Date, cadence: MaintenanceCadence): Date {
954
+ const next = new Date(date);
955
+ switch (cadence.unit) {
956
+ case "day":
957
+ next.setUTCDate(next.getUTCDate() + cadence.count);
958
+ return next;
959
+ case "week":
960
+ next.setUTCDate(next.getUTCDate() + cadence.count * 7);
961
+ return next;
962
+ case "month":
963
+ next.setUTCMonth(next.getUTCMonth() + cadence.count);
964
+ return next;
965
+ case "quarter":
966
+ next.setUTCMonth(next.getUTCMonth() + cadence.count * 3);
967
+ return next;
968
+ case "year":
969
+ next.setUTCFullYear(next.getUTCFullYear() + cadence.count);
970
+ return next;
971
+ }
972
+ }
973
+
974
+ function deriveValueRealizationState(record: DxcRecord): Record<string, unknown> {
975
+ const metrics = normalizeValueRealizationMetrics(record.fields.metrics);
976
+ const comparisons = metrics.map((metric) => {
977
+ if (!metric.actual) {
978
+ return {
979
+ id: metric.id,
980
+ name: metric.name,
981
+ unit: metric.unit,
982
+ direction: metric.direction,
983
+ status: "open"
984
+ };
985
+ }
986
+
987
+ const absoluteChange = metric.actual.value - metric.baseline.value;
988
+ const ratio = metric.baseline.value === 0 ? null : metric.actual.value / metric.baseline.value;
989
+ const percentChange = ratio === null ? null : (ratio - 1) * 100;
990
+ const outcome =
991
+ absoluteChange === 0
992
+ ? "unchanged"
993
+ : metric.direction === "higher_is_better"
994
+ ? absoluteChange > 0
995
+ ? "improved"
996
+ : "regressed"
997
+ : absoluteChange < 0
998
+ ? "improved"
999
+ : "regressed";
1000
+
1001
+ return {
1002
+ id: metric.id,
1003
+ name: metric.name,
1004
+ unit: metric.unit,
1005
+ direction: metric.direction,
1006
+ status: "measured",
1007
+ absoluteChange,
1008
+ percentChange,
1009
+ ratio,
1010
+ outcome,
1011
+ baselineMeasuredAt: metric.baseline.measuredAt,
1012
+ actualMeasuredAt: metric.actual.measuredAt
1013
+ };
1014
+ });
1015
+
1016
+ return {
1017
+ metricCount: metrics.length,
1018
+ openMetricCount: comparisons.filter((comparison) => comparison.status === "open").length,
1019
+ measuredMetricCount: comparisons.filter((comparison) => comparison.status === "measured").length,
1020
+ comparisons
1021
+ };
1022
+ }
1023
+
1024
+ type ValueRealizationMetric = {
1025
+ id: string;
1026
+ name: string;
1027
+ unit: string;
1028
+ direction: "lower_is_better" | "higher_is_better";
1029
+ baseline: {
1030
+ value: number;
1031
+ measuredAt: string;
1032
+ };
1033
+ actual?: {
1034
+ value: number;
1035
+ measuredAt: string;
1036
+ };
1037
+ };
1038
+
1039
+ function normalizeValueRealizationMetrics(value: unknown): ValueRealizationMetric[] {
1040
+ if (!Array.isArray(value)) {
1041
+ return [];
1042
+ }
1043
+
1044
+ return value.filter(isValueRealizationMetric);
1045
+ }
1046
+
1047
+ function isValueRealizationMetric(value: unknown): value is ValueRealizationMetric {
1048
+ if (!value || typeof value !== "object") {
1049
+ return false;
1050
+ }
1051
+ const metric = value as ValueRealizationMetric;
1052
+ return (
1053
+ typeof metric.id === "string" &&
1054
+ typeof metric.name === "string" &&
1055
+ typeof metric.unit === "string" &&
1056
+ (metric.direction === "lower_is_better" || metric.direction === "higher_is_better") &&
1057
+ isMeasuredValue(metric.baseline) &&
1058
+ (metric.actual === undefined || isMeasuredValue(metric.actual))
1059
+ );
1060
+ }
1061
+
1062
+ function isMeasuredValue(value: unknown): value is { value: number; measuredAt: string } {
1063
+ return (
1064
+ Boolean(value) &&
1065
+ typeof value === "object" &&
1066
+ typeof (value as { value?: unknown }).value === "number" &&
1067
+ Number.isFinite((value as { value: number }).value) &&
1068
+ typeof (value as { measuredAt?: unknown }).measuredAt === "string"
1069
+ );
1070
+ }
1071
+
1072
+ function compactRuntimeObject(input: Record<string, unknown>): Record<string, unknown> {
1073
+ return Object.fromEntries(Object.entries(input).filter(([, value]) => value !== undefined));
1074
+ }
1075
+
651
1076
  export async function migrateLegacyIntakeItemsToDxcompleteTickets(db: Db): Promise<{ copied: number; skipped: number }> {
652
1077
  const legacyCollection = db.collection<DxcRecord>(LEGACY_INTAKE_COLLECTION_NAME);
653
1078
  const ticketCollection = db.collection<DxcRecord>(DXCOMPLETE_TICKET_COLLECTION_NAME);
@@ -1204,6 +1629,233 @@ export async function appendTaskEntry(
1204
1629
  return updated;
1205
1630
  }
1206
1631
 
1632
+ export async function appendRiskEntry(
1633
+ db: Db,
1634
+ input: AppendRiskEntryInput,
1635
+ actorId: string
1636
+ ): Promise<DxcRecord> {
1637
+ const collection = db.collection<DxcRecord>("risks");
1638
+ const filter = scopedRecordFilter("risks", input.riskId, input.workspaceId);
1639
+ const existing = await collection.findOne(filter);
1640
+
1641
+ if (!existing) {
1642
+ throw new Error(`Record not found: risks/${input.riskId}`);
1643
+ }
1644
+
1645
+ assertRiskEntryInput(input);
1646
+
1647
+ const now = new Date().toISOString();
1648
+ const entry: RiskEntry = {
1649
+ id: randomUUID(),
1650
+ entryType: input.entryType,
1651
+ body: input.body,
1652
+ createdAt: now,
1653
+ createdBy: actorId,
1654
+ ...(input.likelihood ? { likelihood: input.likelihood } : {}),
1655
+ ...(input.impact ? { impact: input.impact } : {}),
1656
+ ...(input.treatment ? { treatment: input.treatment } : {}),
1657
+ ...(input.treatmentRationale ? { treatmentRationale: input.treatmentRationale } : {})
1658
+ };
1659
+ const update: Record<string, unknown> = {
1660
+ $push: {
1661
+ "fields.entries": entry
1662
+ },
1663
+ $set: {
1664
+ updatedAt: now,
1665
+ updatedBy: actorId
1666
+ }
1667
+ };
1668
+
1669
+ if (entry.entryType === "identified" || entry.entryType === "reopened") {
1670
+ (update.$set as Record<string, unknown>)["fields.currentStatus"] = riskEntryToCurrentStatus(entry, "open");
1671
+ }
1672
+
1673
+ if (entry.entryType === "closed") {
1674
+ (update.$set as Record<string, unknown>)["fields.currentStatus"] = riskEntryToCurrentStatus(entry, "closed");
1675
+ }
1676
+
1677
+ if (entry.entryType === "assessment") {
1678
+ (update.$set as Record<string, unknown>)["fields.currentAssessment"] = riskEntryToCurrentAssessment(entry);
1679
+ }
1680
+
1681
+ if (entry.entryType === "treatment") {
1682
+ (update.$set as Record<string, unknown>)["fields.currentTreatment"] = riskEntryToCurrentTreatment(entry);
1683
+ }
1684
+
1685
+ await collection.updateOne(filter, update);
1686
+
1687
+ const updated = await getRecord(db, "risks", input.riskId, { workspaceId: input.workspaceId });
1688
+ if (!updated) {
1689
+ throw new Error(`Record not found after risk entry append: risks/${input.riskId}`);
1690
+ }
1691
+
1692
+ return updated;
1693
+ }
1694
+
1695
+ export async function appendIncidentEntry(
1696
+ db: Db,
1697
+ input: AppendIncidentEntryInput,
1698
+ actorId: string
1699
+ ): Promise<DxcRecord> {
1700
+ const collection = db.collection<DxcRecord>("incidents");
1701
+ const filter = scopedRecordFilter("incidents", input.incidentId, input.workspaceId);
1702
+ const existing = await collection.findOne(filter);
1703
+
1704
+ if (!existing) {
1705
+ throw new Error(`Record not found: incidents/${input.incidentId}`);
1706
+ }
1707
+
1708
+ assertIncidentEntryInput(input);
1709
+
1710
+ const now = new Date().toISOString();
1711
+ const entry: IncidentEntry = {
1712
+ id: randomUUID(),
1713
+ entryType: input.entryType,
1714
+ body: input.body,
1715
+ createdAt: now,
1716
+ createdBy: actorId,
1717
+ ...(input.severity ? { severity: input.severity } : {})
1718
+ };
1719
+ const update: Record<string, unknown> = {
1720
+ $push: {
1721
+ "fields.entries": entry
1722
+ },
1723
+ $set: {
1724
+ updatedAt: now,
1725
+ updatedBy: actorId
1726
+ }
1727
+ };
1728
+
1729
+ if (entry.entryType === "detected" || entry.entryType === "reopened") {
1730
+ (update.$set as Record<string, unknown>)["fields.currentStatus"] = incidentEntryToCurrentStatus(entry, "open");
1731
+ }
1732
+
1733
+ if (entry.entryType === "resolved") {
1734
+ (update.$set as Record<string, unknown>)["fields.currentStatus"] = incidentEntryToCurrentStatus(entry, "resolved");
1735
+ }
1736
+
1737
+ if (entry.entryType === "severity") {
1738
+ (update.$set as Record<string, unknown>)["fields.currentSeverity"] = incidentEntryToCurrentSeverity(entry);
1739
+ }
1740
+
1741
+ await collection.updateOne(filter, update);
1742
+
1743
+ const updated = await getRecord(db, "incidents", input.incidentId, { workspaceId: input.workspaceId });
1744
+ if (!updated) {
1745
+ throw new Error(`Record not found after incident entry append: incidents/${input.incidentId}`);
1746
+ }
1747
+
1748
+ return updated;
1749
+ }
1750
+
1751
+ export async function appendProblemEntry(
1752
+ db: Db,
1753
+ input: AppendProblemEntryInput,
1754
+ actorId: string
1755
+ ): Promise<DxcRecord> {
1756
+ const collection = db.collection<DxcRecord>("problems");
1757
+ const filter = scopedRecordFilter("problems", input.problemId, input.workspaceId);
1758
+ const existing = await collection.findOne(filter);
1759
+
1760
+ if (!existing) {
1761
+ throw new Error(`Record not found: problems/${input.problemId}`);
1762
+ }
1763
+
1764
+ assertProblemEntryInput(input);
1765
+
1766
+ const now = new Date().toISOString();
1767
+ const entry: ProblemEntry = {
1768
+ id: randomUUID(),
1769
+ entryType: input.entryType,
1770
+ body: input.body,
1771
+ createdAt: now,
1772
+ createdBy: actorId,
1773
+ ...(input.rootCause ? { rootCause: input.rootCause } : {})
1774
+ };
1775
+ const update: Record<string, unknown> = {
1776
+ $push: {
1777
+ "fields.entries": entry
1778
+ },
1779
+ $set: {
1780
+ updatedAt: now,
1781
+ updatedBy: actorId
1782
+ }
1783
+ };
1784
+
1785
+ if (entry.entryType === "identified" || entry.entryType === "reopened") {
1786
+ (update.$set as Record<string, unknown>)["fields.currentStatus"] = problemEntryToCurrentStatus(entry, "open");
1787
+ }
1788
+
1789
+ if (entry.entryType === "known_error") {
1790
+ (update.$set as Record<string, unknown>)["fields.currentStatus"] = problemEntryToCurrentStatus(entry, "known_error");
1791
+ }
1792
+
1793
+ if (entry.entryType === "resolved") {
1794
+ (update.$set as Record<string, unknown>)["fields.currentStatus"] = problemEntryToCurrentStatus(entry, "resolved");
1795
+ }
1796
+
1797
+ if (entry.entryType === "root_cause") {
1798
+ (update.$set as Record<string, unknown>)["fields.currentRootCause"] = problemEntryToCurrentRootCause(entry);
1799
+ }
1800
+
1801
+ await collection.updateOne(filter, update);
1802
+
1803
+ const updated = await getRecord(db, "problems", input.problemId, { workspaceId: input.workspaceId });
1804
+ if (!updated) {
1805
+ throw new Error(`Record not found after problem entry append: problems/${input.problemId}`);
1806
+ }
1807
+
1808
+ return updated;
1809
+ }
1810
+
1811
+ export async function appendSupportRequestEntry(
1812
+ db: Db,
1813
+ input: AppendSupportRequestEntryInput,
1814
+ actorId: string
1815
+ ): Promise<DxcRecord> {
1816
+ const collection = db.collection<DxcRecord>("support_requests");
1817
+ const filter = scopedRecordFilter("support_requests", input.supportRequestId, input.workspaceId);
1818
+ const existing = await collection.findOne(filter);
1819
+
1820
+ if (!existing) {
1821
+ throw new Error(`Record not found: support_requests/${input.supportRequestId}`);
1822
+ }
1823
+
1824
+ assertSupportRequestEntryInput(input);
1825
+
1826
+ const now = new Date().toISOString();
1827
+ const entry: SupportRequestEntry = {
1828
+ id: randomUUID(),
1829
+ entryType: input.entryType,
1830
+ body: input.body,
1831
+ createdAt: now,
1832
+ createdBy: actorId,
1833
+ ...(input.incidentId ? { incidentId: input.incidentId } : {})
1834
+ };
1835
+ const update: Record<string, unknown> = {
1836
+ $push: {
1837
+ "fields.entries": entry
1838
+ },
1839
+ $set: {
1840
+ updatedAt: now,
1841
+ updatedBy: actorId
1842
+ }
1843
+ };
1844
+ const currentStatus = supportRequestEntryToCurrentStatus(entry, readSupportRequestStatus(existing));
1845
+ if (currentStatus) {
1846
+ (update.$set as Record<string, unknown>)["fields.currentStatus"] = currentStatus;
1847
+ }
1848
+
1849
+ await collection.updateOne(filter, update);
1850
+
1851
+ const updated = await getRecord(db, "support_requests", input.supportRequestId, { workspaceId: input.workspaceId });
1852
+ if (!updated) {
1853
+ throw new Error(`Record not found after support request entry append: support_requests/${input.supportRequestId}`);
1854
+ }
1855
+
1856
+ return updated;
1857
+ }
1858
+
1207
1859
  export function decisionEntryToCurrentDecision(entry: DecisionEntry): Record<string, unknown> {
1208
1860
  return {
1209
1861
  entryId: entry.id,
@@ -1229,6 +1881,138 @@ export function taskEntryToCurrentStatus(entry: TaskEntry): Record<string, unkno
1229
1881
  };
1230
1882
  }
1231
1883
 
1884
+ export function riskEntryToCurrentStatus(entry: RiskEntry, status: "open" | "closed"): Record<string, unknown> {
1885
+ return {
1886
+ entryId: entry.id,
1887
+ status,
1888
+ body: entry.body,
1889
+ createdAt: entry.createdAt,
1890
+ createdBy: entry.createdBy
1891
+ };
1892
+ }
1893
+
1894
+ export function riskEntryToCurrentAssessment(entry: RiskEntry): Record<string, unknown> {
1895
+ if (entry.entryType !== "assessment" || !entry.likelihood || !entry.impact) {
1896
+ throw new Error("Risk current assessment can only derive from an assessment entry with likelihood and impact.");
1897
+ }
1898
+
1899
+ return {
1900
+ entryId: entry.id,
1901
+ likelihood: entry.likelihood,
1902
+ impact: entry.impact,
1903
+ body: entry.body,
1904
+ createdAt: entry.createdAt,
1905
+ createdBy: entry.createdBy
1906
+ };
1907
+ }
1908
+
1909
+ export function riskEntryToCurrentTreatment(entry: RiskEntry): Record<string, unknown> {
1910
+ if (entry.entryType !== "treatment" || !entry.treatment) {
1911
+ throw new Error("Risk current treatment can only derive from a treatment entry.");
1912
+ }
1913
+
1914
+ return {
1915
+ entryId: entry.id,
1916
+ treatment: entry.treatment,
1917
+ body: entry.body,
1918
+ createdAt: entry.createdAt,
1919
+ createdBy: entry.createdBy,
1920
+ ...(entry.treatmentRationale ? { treatmentRationale: entry.treatmentRationale } : {})
1921
+ };
1922
+ }
1923
+
1924
+ export function incidentEntryToCurrentStatus(entry: IncidentEntry, status: "open" | "resolved"): Record<string, unknown> {
1925
+ return {
1926
+ entryId: entry.id,
1927
+ status,
1928
+ body: entry.body,
1929
+ createdAt: entry.createdAt,
1930
+ createdBy: entry.createdBy
1931
+ };
1932
+ }
1933
+
1934
+ export function incidentEntryToCurrentSeverity(entry: IncidentEntry): Record<string, unknown> {
1935
+ if (entry.entryType !== "severity" || !entry.severity) {
1936
+ throw new Error("Incident current severity can only derive from a severity entry.");
1937
+ }
1938
+
1939
+ return {
1940
+ entryId: entry.id,
1941
+ severity: entry.severity,
1942
+ body: entry.body,
1943
+ createdAt: entry.createdAt,
1944
+ createdBy: entry.createdBy
1945
+ };
1946
+ }
1947
+
1948
+ export function problemEntryToCurrentStatus(
1949
+ entry: ProblemEntry,
1950
+ status: "open" | "known_error" | "resolved"
1951
+ ): Record<string, unknown> {
1952
+ return {
1953
+ entryId: entry.id,
1954
+ status,
1955
+ body: entry.body,
1956
+ createdAt: entry.createdAt,
1957
+ createdBy: entry.createdBy
1958
+ };
1959
+ }
1960
+
1961
+ export function problemEntryToCurrentRootCause(entry: ProblemEntry): Record<string, unknown> {
1962
+ if (entry.entryType !== "root_cause") {
1963
+ throw new Error("Problem current root cause can only derive from a root_cause entry.");
1964
+ }
1965
+
1966
+ return {
1967
+ entryId: entry.id,
1968
+ rootCause: entry.rootCause ?? entry.body,
1969
+ body: entry.body,
1970
+ createdAt: entry.createdAt,
1971
+ createdBy: entry.createdBy
1972
+ };
1973
+ }
1974
+
1975
+ export function supportRequestEntryToCurrentStatus(
1976
+ entry: SupportRequestEntry,
1977
+ previousStatus?: Record<string, unknown>
1978
+ ): Record<string, unknown> | undefined {
1979
+ const status = supportRequestStatusForEntryType(entry.entryType);
1980
+ if (!status) {
1981
+ return previousStatus;
1982
+ }
1983
+
1984
+ return {
1985
+ entryId: entry.id,
1986
+ status,
1987
+ body: entry.body,
1988
+ createdAt: entry.createdAt,
1989
+ createdBy: entry.createdBy
1990
+ };
1991
+ }
1992
+
1993
+ function supportRequestStatusForEntryType(entryType: SupportRequestEntryType): "open" | "triaged" | "escalated" | "resolved" | undefined {
1994
+ switch (entryType) {
1995
+ case "raised":
1996
+ case "reopened":
1997
+ return "open";
1998
+ case "triage":
1999
+ return "triaged";
2000
+ case "escalated":
2001
+ return "escalated";
2002
+ case "resolved":
2003
+ return "resolved";
2004
+ case "update":
2005
+ case "note":
2006
+ return undefined;
2007
+ }
2008
+ }
2009
+
2010
+ function readSupportRequestStatus(record: DxcRecord): Record<string, unknown> | undefined {
2011
+ return record.fields.currentStatus && typeof record.fields.currentStatus === "object"
2012
+ ? (record.fields.currentStatus as Record<string, unknown>)
2013
+ : undefined;
2014
+ }
2015
+
1232
2016
  function assertDecisionEntryInput(input: AppendDecisionEntryInput): void {
1233
2017
  if (input.entryType !== "decision" && (input.decidedBy || input.rationale)) {
1234
2018
  throw new Error("decidedBy and rationale are only valid on decision entries.");
@@ -1245,6 +2029,50 @@ function assertTaskEntryInput(input: AppendTaskEntryInput): void {
1245
2029
  }
1246
2030
  }
1247
2031
 
2032
+ function assertRiskEntryInput(input: AppendRiskEntryInput): void {
2033
+ if (input.entryType === "assessment" && (!input.likelihood || !input.impact)) {
2034
+ throw new Error("assessment entries require likelihood and impact.");
2035
+ }
2036
+
2037
+ if (input.entryType !== "assessment" && (input.likelihood || input.impact)) {
2038
+ throw new Error("likelihood and impact are only valid on assessment entries.");
2039
+ }
2040
+
2041
+ if (input.entryType === "treatment" && !input.treatment) {
2042
+ throw new Error("treatment entries require treatment.");
2043
+ }
2044
+
2045
+ if (input.entryType !== "treatment" && (input.treatment || input.treatmentRationale)) {
2046
+ throw new Error("treatment and treatmentRationale are only valid on treatment entries.");
2047
+ }
2048
+ }
2049
+
2050
+ function assertIncidentEntryInput(input: AppendIncidentEntryInput): void {
2051
+ if (input.entryType === "severity" && !input.severity) {
2052
+ throw new Error("severity entries require severity.");
2053
+ }
2054
+
2055
+ if (input.entryType !== "severity" && input.severity) {
2056
+ throw new Error("severity is only valid on severity entries.");
2057
+ }
2058
+ }
2059
+
2060
+ function assertProblemEntryInput(input: AppendProblemEntryInput): void {
2061
+ if (input.entryType !== "root_cause" && input.rootCause) {
2062
+ throw new Error("rootCause is only valid on root_cause entries.");
2063
+ }
2064
+ }
2065
+
2066
+ function assertSupportRequestEntryInput(input: AppendSupportRequestEntryInput): void {
2067
+ if (input.entryType === "escalated" && !input.incidentId) {
2068
+ throw new Error("escalated support request entries require incidentId.");
2069
+ }
2070
+
2071
+ if (input.entryType !== "escalated" && input.incidentId) {
2072
+ throw new Error("incidentId is only valid on escalated support request entries.");
2073
+ }
2074
+ }
2075
+
1248
2076
  function applyDeferralEventState(
1249
2077
  existing: DxcRecord,
1250
2078
  event: DeferralEvent,
@@ -1531,7 +2359,7 @@ export async function updateRecord(db: Db, input: UpdateRecordInput, actorId: st
1531
2359
  const existingVersionHistory = normalizeVersionHistory(existing.fields.versionHistory);
1532
2360
 
1533
2361
  if (changedFields.length === 0) {
1534
- return existing;
2362
+ return withDerivedRecordFields(db, existing);
1535
2363
  }
1536
2364
 
1537
2365
  set["fields.versionHistory"] = [
@@ -1556,7 +2384,7 @@ export async function updateRecord(db: Db, input: UpdateRecordInput, actorId: st
1556
2384
  throw new Error(`Updated record not found: ${input.recordType}/${input.id}`);
1557
2385
  }
1558
2386
 
1559
- return updated;
2387
+ return withDerivedRecordFields(db, updated);
1560
2388
  }
1561
2389
 
1562
2390
  function applyRecordUpdate(record: DxcRecord, input: UpdateRecordInput): DxcRecord {
@@ -1751,7 +2579,7 @@ export async function archiveRecord(db: Db, input: ArchiveRecordInput, actorId:
1751
2579
  throw new Error(`Archived record not found: ${input.recordType}/${input.id}`);
1752
2580
  }
1753
2581
 
1754
- return updated;
2582
+ return withDerivedRecordFields(db, updated);
1755
2583
  }
1756
2584
 
1757
2585
  export async function linkRecords(
@@ -1809,7 +2637,7 @@ export async function linkRecords(
1809
2637
  throw new Error(`Updated source record not found: ${input.fromType}/${input.fromId}`);
1810
2638
  }
1811
2639
 
1812
- return updated;
2640
+ return withDerivedRecordFields(db, updated);
1813
2641
  }
1814
2642
 
1815
2643
  export async function unlinkRecords(
@@ -1866,7 +2694,7 @@ export async function unlinkRecords(
1866
2694
  throw new Error(`Updated source record not found: ${input.fromType}/${input.fromId}`);
1867
2695
  }
1868
2696
 
1869
- return updated;
2697
+ return withDerivedRecordFields(db, updated);
1870
2698
  }
1871
2699
 
1872
2700
  function assertFieldName(key: string): void {
@@ -1926,8 +2754,14 @@ function assertNotManagedField(recordType: CollectionName, key: string): void {
1926
2754
  assertNotBenefitsManagedField(recordType, key);
1927
2755
  assertNotEnvironmentManagedField(recordType, key);
1928
2756
  assertNotComponentManagedField(recordType, key);
2757
+ assertNotMaintenanceScheduleManagedField(recordType, key);
2758
+ assertNotSupportRequestLedgerManagedField(recordType, key);
2759
+ assertNotValueRealizationManagedField(recordType, key);
1929
2760
  assertNotDecisionLedgerManagedField(recordType, key);
1930
2761
  assertNotTaskLedgerManagedField(recordType, key);
2762
+ assertNotRiskLedgerManagedField(recordType, key);
2763
+ assertNotIncidentLedgerManagedField(recordType, key);
2764
+ assertNotProblemLedgerManagedField(recordType, key);
1931
2765
  assertNotDecisionInputManagedField(recordType, key);
1932
2766
  assertNotJournalEntryManagedField(recordType, key);
1933
2767
  }
@@ -2030,6 +2864,36 @@ function assertNotComponentManagedField(recordType: CollectionName, key: string)
2030
2864
  );
2031
2865
  }
2032
2866
 
2867
+ function assertNotMaintenanceScheduleManagedField(recordType: CollectionName, key: string): void {
2868
+ if (recordType !== "maintenance_schedules" || !(MAINTENANCE_SCHEDULE_MANAGED_FIELDS as readonly string[]).includes(key)) {
2869
+ return;
2870
+ }
2871
+
2872
+ throw new Error(
2873
+ `fields.${key} is managed on maintenance schedules. Use create_maintenance_schedule or update_maintenance_schedule instead of setting or unsetting it directly.`
2874
+ );
2875
+ }
2876
+
2877
+ function assertNotSupportRequestLedgerManagedField(recordType: CollectionName, key: string): void {
2878
+ if (recordType !== "support_requests" || !(SUPPORT_REQUEST_LEDGER_MANAGED_FIELDS as readonly string[]).includes(key)) {
2879
+ return;
2880
+ }
2881
+
2882
+ throw new Error(
2883
+ `fields.${key} is managed on support requests. Use create_support_request or append_support_request_entry instead of setting or unsetting it directly.`
2884
+ );
2885
+ }
2886
+
2887
+ function assertNotValueRealizationManagedField(recordType: CollectionName, key: string): void {
2888
+ if (recordType !== "value_realizations" || !(VALUE_REALIZATION_MANAGED_FIELDS as readonly string[]).includes(key)) {
2889
+ return;
2890
+ }
2891
+
2892
+ throw new Error(
2893
+ `fields.${key} is managed on value realizations. Use create_value_realization or update_value_realization instead of setting or unsetting it directly.`
2894
+ );
2895
+ }
2896
+
2033
2897
  function assertNotDecisionLedgerManagedField(recordType: CollectionName, key: string): void {
2034
2898
  if (recordType !== "decisions" || !(DECISION_LEDGER_MANAGED_FIELDS as readonly string[]).includes(key)) {
2035
2899
  return;
@@ -2050,6 +2914,36 @@ function assertNotTaskLedgerManagedField(recordType: CollectionName, key: string
2050
2914
  );
2051
2915
  }
2052
2916
 
2917
+ function assertNotRiskLedgerManagedField(recordType: CollectionName, key: string): void {
2918
+ if (recordType !== "risks" || !(RISK_LEDGER_MANAGED_FIELDS as readonly string[]).includes(key)) {
2919
+ return;
2920
+ }
2921
+
2922
+ throw new Error(
2923
+ `fields.${key} is managed on risks. Use create_risk or append_risk_entry instead of setting or unsetting it directly.`
2924
+ );
2925
+ }
2926
+
2927
+ function assertNotIncidentLedgerManagedField(recordType: CollectionName, key: string): void {
2928
+ if (recordType !== "incidents" || !(INCIDENT_LEDGER_MANAGED_FIELDS as readonly string[]).includes(key)) {
2929
+ return;
2930
+ }
2931
+
2932
+ throw new Error(
2933
+ `fields.${key} is managed on incidents. Use create_incident or append_incident_entry instead of setting or unsetting it directly.`
2934
+ );
2935
+ }
2936
+
2937
+ function assertNotProblemLedgerManagedField(recordType: CollectionName, key: string): void {
2938
+ if (recordType !== "problems" || !(PROBLEM_LEDGER_MANAGED_FIELDS as readonly string[]).includes(key)) {
2939
+ return;
2940
+ }
2941
+
2942
+ throw new Error(
2943
+ `fields.${key} is managed on problems. Use create_problem or append_problem_entry instead of setting or unsetting it directly.`
2944
+ );
2945
+ }
2946
+
2053
2947
  function assertNotDecisionInputManagedField(recordType: CollectionName, key: string): void {
2054
2948
  if (recordType !== "decisions" || !(DECISION_INPUT_MANAGED_FIELDS as readonly string[]).includes(key)) {
2055
2949
  return;
@@ -2091,6 +2985,14 @@ function versionedUpdateToolName(recordType: CollectionName): string {
2091
2985
  return "update_component";
2092
2986
  }
2093
2987
 
2988
+ if (recordType === "maintenance_schedules") {
2989
+ return "update_maintenance_schedule";
2990
+ }
2991
+
2992
+ if (recordType === "value_realizations") {
2993
+ return "update_value_realization";
2994
+ }
2995
+
2094
2996
  if (recordType === "estimates") {
2095
2997
  return "update_estimate";
2096
2998
  }