dxcomplete 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -10
- package/dist/init.js +19 -0
- package/dist/mcp/docs.d.ts +18 -2
- package/dist/mcp/docs.js +201 -13
- package/dist/mcp/server.js +761 -47
- package/dist/runtime/check.d.ts +1 -1
- package/dist/runtime/records.d.ts +95 -4
- package/dist/runtime/records.js +640 -11
- package/dist/validate.js +2 -0
- package/docs/codex-integration.md +45 -18
- package/docs/glossary.md +39 -7
- package/docs/index.md +2 -1
- package/docs/model.md +3 -3
- package/docs/operating-guide.md +116 -0
- package/docs/taxonomy.md +12 -6
- package/docs/workflows.md +5 -3
- package/package.json +20 -2
- package/scripts/smoke-mcp-http.mjs +460 -6
- package/src/init.ts +35 -0
- package/src/mcp/docs.ts +234 -14
- package/src/mcp/server.ts +1138 -83
- package/src/runtime/records.ts +914 -12
- package/src/validate.ts +2 -0
- package/templates/AGENTS.md +30 -0
- package/templates/process/controls.yml +10 -6
- package/templates/process/diagrams/01-intake-triage.mmd +5 -5
- package/templates/process/diagrams/02-product-definition.mmd +1 -1
- package/templates/process/diagrams/06-change-release-control.mmd +5 -7
- package/templates/process/diagrams/07-deployment-operations.mmd +2 -2
- package/templates/process/diagrams/08-support-incident-management.mmd +5 -4
- package/templates/process/diagrams/09-problem-improvement.mmd +4 -3
- package/templates/process/diagrams/10-risk-control-management.mmd +6 -4
- package/templates/process/diagrams/11-audit-evidence-capture.mmd +1 -1
- package/templates/process/taxonomy.yml +91 -17
- package/templates/process/workflows.yml +10 -9
- package/website/flow.html +1 -0
- package/website/glossary.html +37 -8
- package/website/index.html +2 -1
- package/website/objects.html +68 -11
- package/website/operating-guide.html +165 -0
- package/website/outcomes.html +1 -0
- package/website/phase-build.html +1 -0
- package/website/phase-elicit.html +1 -0
- package/website/phase-go-live.html +2 -1
- package/website/phase-measure.html +1 -0
- package/website/phase-operate.html +1 -0
- package/website/phase-orient.html +1 -0
- package/website/phase-weigh.html +1 -0
- package/website/roles.html +1 -0
package/dist/mcp/server.js
CHANGED
|
@@ -4,7 +4,7 @@ import * as z from "zod/v4";
|
|
|
4
4
|
import { DOC_PAGE_IDS, DOC_REFERENCE, getDocReference } from "./docs.js";
|
|
5
5
|
import { createLocalActorContext } from "../runtime/actor.js";
|
|
6
6
|
import { DXCOMPLETE_PACKAGE_VERSION, MCP_SURFACE_ID, WORKSPACE_COMPATIBILITY_VERSION } from "../version.js";
|
|
7
|
-
import { COLLECTION_NAMES, RUNTIME_ACTOR_ID, appendChangeEvent, appendDecisionEntry, appendDeferralEvent, appendJournalNote, appendJournalSummary, appendReviewNote, appendTaskEntry, archiveRecord, appendDxcompleteTicket, archiveDxcompleteTicket, decisionEntryToCurrentDecision, createDxcompleteTicket, createRecord, getJournalEntry, getRecord, linkRecords, listDxcompleteTickets, listLinkedRecords, listUnreadDxcompleteTicketReplies, listRecords, readJournal, readDxcompleteTicket, taskEntryToCurrentStatus, unlinkRecords, updateRecord } from "../runtime/records.js";
|
|
7
|
+
import { COLLECTION_NAMES, RUNTIME_ACTOR_ID, appendChangeEvent, appendDecisionEntry, appendDeferralEvent, appendIncidentEntry, appendJournalNote, appendJournalSummary, appendProblemEntry, appendReviewNote, appendRiskEntry, appendSupportRequestEntry, appendTaskEntry, archiveRecord, appendDxcompleteTicket, archiveDxcompleteTicket, decisionEntryToCurrentDecision, createDxcompleteTicket, createRecord, getJournalEntry, getRecord, incidentEntryToCurrentSeverity, incidentEntryToCurrentStatus, linkRecords, listDxcompleteTickets, listLinkedRecords, listUnreadDxcompleteTicketReplies, listRecords, readJournal, readDxcompleteTicket, problemEntryToCurrentStatus, riskEntryToCurrentAssessment, riskEntryToCurrentStatus, riskEntryToCurrentTreatment, supportRequestEntryToCurrentStatus, taskEntryToCurrentStatus, unlinkRecords, updateRecord } from "../runtime/records.js";
|
|
8
8
|
const collectionSchema = z.enum(COLLECTION_NAMES);
|
|
9
9
|
const workspaceScopedCollectionNames = COLLECTION_NAMES.filter((name) => name !== "workspaces");
|
|
10
10
|
const hostedCollectionSchema = z.enum(workspaceScopedCollectionNames);
|
|
@@ -19,6 +19,37 @@ const requirementStatusSchema = z.enum(["draft", "ready", "approved", "supersede
|
|
|
19
19
|
const taskStatusSchema = z.enum(["open", "in_progress", "blocked", "done"]);
|
|
20
20
|
const decisionEntryTypeSchema = z.enum(["argument", "decision", "note"]);
|
|
21
21
|
const taskEntryTypeSchema = z.enum(["comment", "status_change", "note"]);
|
|
22
|
+
const riskEntryTypeSchema = z.enum(["identified", "assessment", "treatment", "monitor_note", "closed", "reopened"]);
|
|
23
|
+
const riskLevelSchema = z.enum(["low", "medium", "high"]);
|
|
24
|
+
const riskTreatmentSchema = z.enum(["accept", "mitigate", "transfer", "avoid"]);
|
|
25
|
+
const incidentEntryTypeSchema = z.enum(["detected", "update", "severity", "resolved", "reopened", "note"]);
|
|
26
|
+
const incidentSeveritySchema = z.enum(["low", "medium", "high", "critical"]);
|
|
27
|
+
const problemEntryTypeSchema = z.enum([
|
|
28
|
+
"identified",
|
|
29
|
+
"investigation",
|
|
30
|
+
"root_cause",
|
|
31
|
+
"known_error",
|
|
32
|
+
"resolved",
|
|
33
|
+
"reopened",
|
|
34
|
+
"note"
|
|
35
|
+
]);
|
|
36
|
+
const supportRequestEntryTypeSchema = z.enum(["raised", "triage", "update", "escalated", "resolved", "reopened", "note"]);
|
|
37
|
+
const maintenanceCadenceSchema = z.object({
|
|
38
|
+
count: z.number().int().min(1),
|
|
39
|
+
unit: z.enum(["day", "week", "month", "quarter", "year"])
|
|
40
|
+
}).strict();
|
|
41
|
+
const valueMetricMeasurementSchema = z.object({
|
|
42
|
+
value: z.number().finite(),
|
|
43
|
+
measuredAt: z.string().min(1)
|
|
44
|
+
}).strict();
|
|
45
|
+
const valueMetricSchema = z.object({
|
|
46
|
+
id: z.string().min(1).optional(),
|
|
47
|
+
name: z.string().min(1),
|
|
48
|
+
unit: z.string().min(1),
|
|
49
|
+
direction: z.enum(["lower_is_better", "higher_is_better"]),
|
|
50
|
+
baseline: valueMetricMeasurementSchema,
|
|
51
|
+
actual: valueMetricMeasurementSchema.optional()
|
|
52
|
+
}).strict();
|
|
22
53
|
const reviewableRecordTypeSchema = z.enum(["expectations", "requirements"]);
|
|
23
54
|
const decisionInputRecordTypes = [
|
|
24
55
|
"expectations",
|
|
@@ -30,24 +61,35 @@ const decisionInputRecordTypes = [
|
|
|
30
61
|
"components",
|
|
31
62
|
"estimates",
|
|
32
63
|
"benefits",
|
|
64
|
+
"maintenance_schedules",
|
|
65
|
+
"support_requests",
|
|
66
|
+
"value_realizations",
|
|
33
67
|
"risks",
|
|
34
68
|
"changes",
|
|
69
|
+
"incidents",
|
|
70
|
+
"problems",
|
|
35
71
|
"decisions"
|
|
36
72
|
];
|
|
37
73
|
const decisionInputRecordTypeSchema = z.enum(decisionInputRecordTypes);
|
|
38
74
|
const changeEventTypeSchema = z.enum([
|
|
39
75
|
"notice_given",
|
|
40
76
|
"veto_recorded",
|
|
41
|
-
"emergency_declared",
|
|
42
77
|
"decision_recorded",
|
|
43
78
|
"result_reported",
|
|
44
79
|
"recovery_recorded",
|
|
45
80
|
"plan_revised",
|
|
46
81
|
"note_added"
|
|
47
82
|
]);
|
|
83
|
+
const changeTypeSchema = z.enum(["standard", "normal", "emergency"]);
|
|
84
|
+
const changeImpactGradeSchema = z.enum(["minor", "significant", "major"]);
|
|
48
85
|
const changeVetoRoleSchema = z.enum(["Owner", "Engineer"]);
|
|
49
86
|
const changeDecisionSchema = z.enum(["proceed", "defer", "cancel"]);
|
|
50
87
|
const changeResultSchema = z.enum(["completed", "failed", "rolled_back"]);
|
|
88
|
+
const gitCommitReferenceSchema = z.object({
|
|
89
|
+
commit: z.string().min(1),
|
|
90
|
+
repository: z.string().min(1).optional(),
|
|
91
|
+
url: z.string().min(1).optional()
|
|
92
|
+
});
|
|
51
93
|
const deferralEventTypeSchema = z.enum([
|
|
52
94
|
"condition_addressed",
|
|
53
95
|
"condition_reopened",
|
|
@@ -130,6 +172,11 @@ const CHANGE_TYPED_FIELDS = [
|
|
|
130
172
|
"executionSteps",
|
|
131
173
|
"rollbackPlan",
|
|
132
174
|
"riskImpact",
|
|
175
|
+
"changeType",
|
|
176
|
+
"impactGrade",
|
|
177
|
+
"emergencyImportance",
|
|
178
|
+
"emergencyImmediacy",
|
|
179
|
+
"emergencyRationaleGaps",
|
|
133
180
|
"plannedFor",
|
|
134
181
|
"events"
|
|
135
182
|
];
|
|
@@ -148,6 +195,25 @@ const COMPONENT_TYPED_FIELDS = [
|
|
|
148
195
|
"notes",
|
|
149
196
|
"versionHistory"
|
|
150
197
|
];
|
|
198
|
+
const MAINTENANCE_SCHEDULE_TYPED_FIELDS = [
|
|
199
|
+
"name",
|
|
200
|
+
"kind",
|
|
201
|
+
"cadence",
|
|
202
|
+
"startDate",
|
|
203
|
+
"rationale",
|
|
204
|
+
"notes",
|
|
205
|
+
"versionHistory"
|
|
206
|
+
];
|
|
207
|
+
const SUPPORT_REQUEST_TYPED_FIELDS = [
|
|
208
|
+
"workspaceId",
|
|
209
|
+
"reporter",
|
|
210
|
+
"kind",
|
|
211
|
+
"reportedExperience",
|
|
212
|
+
"entries",
|
|
213
|
+
"currentStatus",
|
|
214
|
+
"status"
|
|
215
|
+
];
|
|
216
|
+
const VALUE_REALIZATION_TYPED_FIELDS = ["metrics", "versionHistory"];
|
|
151
217
|
const OBSOLETE_EXPECTATION_FIELDS = ["confirmationState", "ratifiedBy", "ratifiedAt"];
|
|
152
218
|
const DECISION_INPUT_FIELDS = ["informedBy", "informedByIds", "inputRecords"];
|
|
153
219
|
const DECISION_TYPED_FIELDS = [
|
|
@@ -164,6 +230,19 @@ const DECISION_TYPED_FIELDS = [
|
|
|
164
230
|
"status"
|
|
165
231
|
];
|
|
166
232
|
const TASK_TYPED_FIELDS = ["workspaceId", "description", "assignee", "assignor", "entries", "currentStatus", "details", "status"];
|
|
233
|
+
const RISK_TYPED_FIELDS = [
|
|
234
|
+
"workspaceId",
|
|
235
|
+
"topic",
|
|
236
|
+
"entries",
|
|
237
|
+
"currentStatus",
|
|
238
|
+
"currentAssessment",
|
|
239
|
+
"currentTreatment",
|
|
240
|
+
"likelihood",
|
|
241
|
+
"impact",
|
|
242
|
+
"mitigation"
|
|
243
|
+
];
|
|
244
|
+
const INCIDENT_TYPED_FIELDS = ["workspaceId", "description", "entries", "currentStatus", "currentSeverity", "status", "severity"];
|
|
245
|
+
const PROBLEM_TYPED_FIELDS = ["workspaceId", "description", "entries", "currentStatus", "currentRootCause", "status", "rootCause"];
|
|
167
246
|
const PROCESS_GUIDE = {
|
|
168
247
|
name: "DX Complete process guide",
|
|
169
248
|
status: "Current operating guidance for working inside DX Complete. It is a lean guide for MCP clients; it is not the full reference, not a final model, and not a validation rule.",
|
|
@@ -183,6 +262,10 @@ const PROCESS_GUIDE = {
|
|
|
183
262
|
{
|
|
184
263
|
record: "Decision",
|
|
185
264
|
use: "A recorded choice that can appear in any phase when a meaningful decision needs to remain legible."
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
record: "Support Request",
|
|
268
|
+
use: "Shared user-facing support follow-up for a reported experience, question, request, or issue."
|
|
186
269
|
}
|
|
187
270
|
],
|
|
188
271
|
recordRoutingGuidance: {
|
|
@@ -202,13 +285,41 @@ const PROCESS_GUIDE = {
|
|
|
202
285
|
use: "Use Task."
|
|
203
286
|
},
|
|
204
287
|
{
|
|
205
|
-
when: "The information is
|
|
206
|
-
use: "Use Change
|
|
288
|
+
when: "The information is a discrete alteration to the running service.",
|
|
289
|
+
use: "Use Change."
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
when: "The information is a specific service-impacting occurrence that needs response.",
|
|
293
|
+
use: "Use Incident."
|
|
294
|
+
},
|
|
295
|
+
{
|
|
296
|
+
when: "The information is an underlying or recurring cause behind one or more incidents.",
|
|
297
|
+
use: "Use Problem."
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
when: "The information is uncertainty or exposure.",
|
|
301
|
+
use: "Use Risk."
|
|
207
302
|
},
|
|
208
303
|
{
|
|
209
304
|
when: "The information is operational infrastructure state.",
|
|
210
305
|
use: "Use Environment and Component records in the Operational Registry; do not use Journal as the long-term home."
|
|
211
306
|
},
|
|
307
|
+
{
|
|
308
|
+
when: "The information is recurring operational hygiene, such as a scheduled review, rotation, backup check, or maintenance duty.",
|
|
309
|
+
use: "Use Maintenance Schedule."
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
when: "The information is a user-facing question, request, or issue that needs shared support follow-up.",
|
|
313
|
+
use: "Use Support Request."
|
|
314
|
+
},
|
|
315
|
+
{
|
|
316
|
+
when: "The information is measured before/after value for an approved outcome or commitment.",
|
|
317
|
+
use: "Use Value Realization."
|
|
318
|
+
},
|
|
319
|
+
{
|
|
320
|
+
when: "The information is a private question, report, request, correction, or follow-up with DX Complete.",
|
|
321
|
+
use: "Use DX Complete Ticket."
|
|
322
|
+
},
|
|
212
323
|
{
|
|
213
324
|
when: "The information is relevant context with no better home.",
|
|
214
325
|
use: "Use Journal."
|
|
@@ -216,6 +327,42 @@ const PROCESS_GUIDE = {
|
|
|
216
327
|
],
|
|
217
328
|
promotion: "If Journal content becomes load-bearing, promote it to the appropriate dedicated record and link back where useful."
|
|
218
329
|
},
|
|
330
|
+
roleOperatingGuidance: [
|
|
331
|
+
{
|
|
332
|
+
role: "Owner",
|
|
333
|
+
guidance: "Use Statement, Expectation, Benefits, Commitment, Deferral, Decision, Value Realization, and Risk records for direction, expected value, measured value, commitment, deferral, formal risk acceptance, and authority decisions."
|
|
334
|
+
},
|
|
335
|
+
{
|
|
336
|
+
role: "Engineer",
|
|
337
|
+
guidance: "Default to Requirement -> Task for implementation work. Use Decision for meaningful choices, Risk for uncertainty, Journal for relevant context with no better home, and Change only when the work becomes a run-side alteration."
|
|
338
|
+
},
|
|
339
|
+
{
|
|
340
|
+
role: "Codex assistance",
|
|
341
|
+
guidance: "Codex is a coding-capable tool used by the Engineer, not a DX Complete role. A fresh Codex instance should orient from get_process_guide, get_doc({ page: \"operating_guide\" }), and the installed Codex guidance before choosing records."
|
|
342
|
+
},
|
|
343
|
+
{
|
|
344
|
+
role: "Tester",
|
|
345
|
+
guidance: "Use Task entries, review notes, Risk, Decision, or Journal to keep verification evidence visible. Do not create Change records merely because testing is happening."
|
|
346
|
+
},
|
|
347
|
+
{
|
|
348
|
+
role: "Operator",
|
|
349
|
+
guidance: "Use Change for discrete run-side alterations, Incident for specific service-impacting occurrences, Problem for underlying or recurring causes, Environment and Component for operational inventory, Maintenance Schedule for recurring operational hygiene, and Risk or Decision where operations need accountability. Administration duties such as users, permissions, settings, provisioning, and run-side security live here."
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
role: "Support Agent",
|
|
353
|
+
guidance: "Use Support Request for shared user-facing support follow-up. Use DX Complete Ticket only for private questions, reports, requests, corrections, and follow-ups with DX Complete itself. Promote to Statement, Requirement, Task, Incident, Problem, Risk, Decision, Change, or Journal only when the signal has that meaning."
|
|
354
|
+
},
|
|
355
|
+
{
|
|
356
|
+
role: "End User",
|
|
357
|
+
guidance: "End User is not a DX Complete operator and does not approve records. Their input is captured by another role as Statement, report, request, correction, feedback signal, or ticket."
|
|
358
|
+
}
|
|
359
|
+
],
|
|
360
|
+
itsmGuidance: [
|
|
361
|
+
"Change is the current first-class run-side control record.",
|
|
362
|
+
"Incident is the current first-class record for a specific service-impacting or potentially service-impacting occurrence.",
|
|
363
|
+
"Problem is the current first-class record for an underlying or recurring cause evidenced by one or more incidents.",
|
|
364
|
+
"Do not create ITSM-style records merely because work is happening; use Change, Incident, Problem, and Risk only when the record meaning fits."
|
|
365
|
+
],
|
|
219
366
|
phases: [
|
|
220
367
|
{
|
|
221
368
|
id: "orient",
|
|
@@ -382,10 +529,11 @@ const PROCESS_GUIDE = {
|
|
|
382
529
|
clientRole: "Help the Operator record the intended service change, readiness basis, execution path, rollback path, and resulting events.",
|
|
383
530
|
conductRules: [
|
|
384
531
|
"Use Change for a discrete alteration to the running service; reserve Operations Plan for a future standing operating model.",
|
|
385
|
-
"Record the original change plan, execution steps, rollback plan, and risk
|
|
386
|
-
"
|
|
532
|
+
"Record the original change plan, execution steps, rollback plan, and risk, impact, and downstream impact as the baseline.",
|
|
533
|
+
"Classify the Change as standard, normal, or emergency, then use append-only Change events for notice, veto, decision, result, recovery, notes, and plan revisions.",
|
|
534
|
+
"When result or recovery events have Git commits behind them, record optional Git commit references for that execution attempt or rollback.",
|
|
387
535
|
"Check readiness before putting the change into use.",
|
|
388
|
-
"Make release, rollback, notice, veto,
|
|
536
|
+
"Make release, rollback, notice, veto, change type, and communication risks visible.",
|
|
389
537
|
"Do not treat vetoes or readiness checks as mechanical blockers; DX Complete records accountability and does not perform or enforce the operation.",
|
|
390
538
|
"Record open readiness risk when a readiness concern remains open."
|
|
391
539
|
],
|
|
@@ -393,13 +541,15 @@ const PROCESS_GUIDE = {
|
|
|
393
541
|
"What is changing, why, and when is it planned?",
|
|
394
542
|
"What steps will carry out the change?",
|
|
395
543
|
"What rollback or recovery path exists if something goes wrong?",
|
|
544
|
+
"What else may be affected, and what depends on what is changing?",
|
|
396
545
|
"Who needs to know about the change?",
|
|
397
546
|
"Has anyone recorded a veto or concern?",
|
|
398
547
|
"What readiness concerns are still open?"
|
|
399
548
|
],
|
|
400
549
|
expectedOutput: [
|
|
401
|
-
"Change record with change plan, execution steps, rollback plan, and risk
|
|
402
|
-
"Append-only Change events for notice, veto,
|
|
550
|
+
"Change record with change plan, execution steps, rollback plan, and risk, impact, and downstream-impact notes.",
|
|
551
|
+
"Append-only Change events for notice, veto, decision, result, recovery, notes, or revisions where they occur.",
|
|
552
|
+
"Optional Git commit references on result or recovery events where they help trace the execution attempt.",
|
|
403
553
|
"Readiness basis.",
|
|
404
554
|
"Release or service-change decision context.",
|
|
405
555
|
"Visible open risks and any Owner risk-acceptance decisions."
|
|
@@ -408,7 +558,7 @@ const PROCESS_GUIDE = {
|
|
|
408
558
|
checkpointNotes: [
|
|
409
559
|
"Readiness checks reduce launch risk.",
|
|
410
560
|
"A veto by Owner or Engineer is a serious recorded event, not a mechanical stop.",
|
|
411
|
-
"Emergency changes
|
|
561
|
+
"Emergency changes should record both importance and immediacy where available; missing rationale remains visible but does not block the record.",
|
|
412
562
|
"Open readiness concerns can move forward only when the risk remains visible or is formally accepted by the Owner."
|
|
413
563
|
]
|
|
414
564
|
}
|
|
@@ -417,14 +567,16 @@ const PROCESS_GUIDE = {
|
|
|
417
567
|
id: "operate",
|
|
418
568
|
name: "Operate",
|
|
419
569
|
purpose: "Run the service, help users, and respond when something goes wrong.",
|
|
420
|
-
commonRecords: ["Change", "DX Complete Ticket", "Risk", "Decision", "Task"],
|
|
570
|
+
commonRecords: ["Incident", "Problem", "Support Request", "Change", "Environment", "Component", "Maintenance Schedule", "DX Complete Ticket", "Risk", "Decision", "Task"],
|
|
421
571
|
handoff: "Operational signals are handled or sent into measurement, improvement, or new work.",
|
|
422
572
|
operatingGuidance: {
|
|
423
573
|
clientRole: "Help keep the service legible while it is running and route operational signals to the right follow-up.",
|
|
424
574
|
conductRules: [
|
|
425
575
|
"Separate immediate user-facing help from deeper improvement work.",
|
|
426
|
-
"Record
|
|
576
|
+
"Record Incident for a specific service-impacting occurrence, Problem for an underlying or recurring cause, and Risk, Decision, or Task when those meanings fit.",
|
|
577
|
+
"Use Support Request for shared user-facing support follow-up.",
|
|
427
578
|
"Use Change when the follow-up is a discrete alteration to the running service.",
|
|
579
|
+
"Use Maintenance Schedule for recurring operational hygiene and link completed Changes or Tasks to it when the work is performed.",
|
|
428
580
|
"Do not assume the service is finished just because there is no active build.",
|
|
429
581
|
"Surface recurring signals for later improvement or measurement."
|
|
430
582
|
],
|
|
@@ -432,11 +584,12 @@ const PROCESS_GUIDE = {
|
|
|
432
584
|
"Is the service running as intended?",
|
|
433
585
|
"What user-facing issue, operational issue, or risk needs attention?",
|
|
434
586
|
"Does this signal need immediate response, a task, a decision, or later improvement?",
|
|
587
|
+
"Is this a shared support request, an incident, a problem, or recurring maintenance?",
|
|
435
588
|
"What operational follow-up remains open?"
|
|
436
589
|
],
|
|
437
590
|
expectedOutput: [
|
|
438
591
|
"Handled operational signal or routed follow-up.",
|
|
439
|
-
"Visible incidents, risks, decisions, or tasks where needed.",
|
|
592
|
+
"Visible support requests, incidents, problems, maintenance schedules, risks, decisions, or tasks where needed.",
|
|
440
593
|
"Ongoing operating state."
|
|
441
594
|
],
|
|
442
595
|
exitCheck: "Move to Measure when cost, benefit, reliability, or other operating data is available for comparison or learning.",
|
|
@@ -450,12 +603,13 @@ const PROCESS_GUIDE = {
|
|
|
450
603
|
id: "measure",
|
|
451
604
|
name: "Measure",
|
|
452
605
|
purpose: "Compare expected and actual cost or benefit when data is available.",
|
|
453
|
-
commonRecords: ["Estimate", "Benefits", "Decision", "Risk"],
|
|
606
|
+
commonRecords: ["Value Realization", "Estimate", "Benefits", "Decision", "Risk"],
|
|
454
607
|
handoff: "Learning from actuals is available for future estimates and decisions.",
|
|
455
608
|
operatingGuidance: {
|
|
456
609
|
clientRole: "Help compare estimates to actuals where data exists and feed learning into future decisions.",
|
|
457
610
|
conductRules: [
|
|
458
611
|
"Capture actual cost or benefit observations when available.",
|
|
612
|
+
"Use Value Realization for before/after metrics tied to expectations, requirements, or commitments.",
|
|
459
613
|
"Do not block closure or continued operation because actuals are unavailable.",
|
|
460
614
|
"Compare measured results to earlier estimates where possible.",
|
|
461
615
|
"Turn meaningful differences into learning, risk, decision context, or future work."
|
|
@@ -463,11 +617,13 @@ const PROCESS_GUIDE = {
|
|
|
463
617
|
questionsToAsk: [
|
|
464
618
|
"What actual cost or benefit data is available?",
|
|
465
619
|
"How does it compare with the estimate?",
|
|
620
|
+
"Which value metric has a baseline, an actual measurement, or an open measurement gap?",
|
|
466
621
|
"What should future estimates or decisions learn from this?",
|
|
467
622
|
"Is new work, risk, or a decision needed because of the measurement?"
|
|
468
623
|
],
|
|
469
624
|
expectedOutput: [
|
|
470
625
|
"Actual cost or benefit observations where available.",
|
|
626
|
+
"Value Realization records for measured or still-open value metrics where useful.",
|
|
471
627
|
"Comparison to estimates where possible.",
|
|
472
628
|
"Learning for future estimates and decisions."
|
|
473
629
|
],
|
|
@@ -508,7 +664,7 @@ const PROCESS_GUIDE = {
|
|
|
508
664
|
"Formal risk acceptance is an Owner-level decision; domain actors may proceed, but proceeding does not close or accept the risk.",
|
|
509
665
|
"Use Risk and Decision records to capture Owner risk acceptance or proceeding past an open checkpoint until dedicated checkpoint fields or tools are proven necessary.",
|
|
510
666
|
"Use Decision input links to preserve which records informed important choices.",
|
|
511
|
-
"Use Change records for run-side service changes where notice, veto,
|
|
667
|
+
"Use Change records for run-side service changes where change type, notice, veto, execution, rollback, result, and optional Git commit references need an ordered audit trail."
|
|
512
668
|
],
|
|
513
669
|
nextStepGuidance: {
|
|
514
670
|
responsibility: "The MCP client derives the next step from the current records, phase handoffs, operating guidance, open risks, and unanswered questions. DX Complete does not compute or prescribe a single server-side next step.",
|
|
@@ -564,7 +720,15 @@ const PROCESS_GUIDE = {
|
|
|
564
720
|
},
|
|
565
721
|
{
|
|
566
722
|
record: "Change",
|
|
567
|
-
use: "A discrete service change record with baseline plan sections and append-only events for notice, veto,
|
|
723
|
+
use: "A discrete service change record with baseline change type, plan sections, and append-only events for notice, veto, decision, result, recovery, notes, and revisions."
|
|
724
|
+
},
|
|
725
|
+
{
|
|
726
|
+
record: "Incident",
|
|
727
|
+
use: "A specific service-impacting or potentially service-impacting occurrence with an append-only entry log; current status and severity come from the latest relevant entries."
|
|
728
|
+
},
|
|
729
|
+
{
|
|
730
|
+
record: "Problem",
|
|
731
|
+
use: "An underlying or recurring cause evidenced by one or more Incidents, with an append-only entry log and current root cause derived from entries."
|
|
568
732
|
},
|
|
569
733
|
{
|
|
570
734
|
record: "Environment",
|
|
@@ -574,6 +738,10 @@ const PROCESS_GUIDE = {
|
|
|
574
738
|
record: "Component",
|
|
575
739
|
use: "One environment-specific operational component with kind, structured locator, identifiers, secret pointers, notes, and version history."
|
|
576
740
|
},
|
|
741
|
+
{
|
|
742
|
+
record: "Maintenance Schedule",
|
|
743
|
+
use: "A recurring operational hygiene record with cadence, start date, rationale, version history, and due state derived from linked completed Changes or Tasks."
|
|
744
|
+
},
|
|
577
745
|
{
|
|
578
746
|
record: "Estimate",
|
|
579
747
|
use: "An Engineer cost input for Weigh, with itemized line items, scope links, cost-only roll-up totals, and version history."
|
|
@@ -582,6 +750,14 @@ const PROCESS_GUIDE = {
|
|
|
582
750
|
record: "Benefits",
|
|
583
751
|
use: "An Owner-authored benefit input for Weigh, with qualitative or quantified benefit items, scope links, quantified roll-up totals where amounts exist, and version history."
|
|
584
752
|
},
|
|
753
|
+
{
|
|
754
|
+
record: "Value Realization",
|
|
755
|
+
use: "A measured-value record for before/after metrics tied to expectations, requirements, or commitments, with comparisons derived from baseline and actual values."
|
|
756
|
+
},
|
|
757
|
+
{
|
|
758
|
+
record: "Support Request",
|
|
759
|
+
use: "A shared support ledger for a user-facing reported experience, question, request, or issue; current status comes from ordered entries."
|
|
760
|
+
},
|
|
585
761
|
{
|
|
586
762
|
record: "Decision",
|
|
587
763
|
use: "A revisitable choice record with an append-only entry log; current decision comes from the latest decision entry while earlier arguments and decisions remain visible."
|
|
@@ -589,9 +765,10 @@ const PROCESS_GUIDE = {
|
|
|
589
765
|
],
|
|
590
766
|
clientGuidance: [
|
|
591
767
|
"Use get_process_guide when you need the current DX Complete phase language.",
|
|
592
|
-
"Use get_doc for the fuller reference on outcomes, flow, records, roles, and glossary terms.",
|
|
768
|
+
"Use get_doc for the fuller reference on outcomes, flow, records, roles, operating guide, and glossary terms.",
|
|
769
|
+
"Use get_doc({ page: \"operating_guide\" }) when a fresh client or agent needs role-by-role record routing.",
|
|
593
770
|
"Use DX Complete Ticket tools for private questions, reports, requests, corrections, or follow-ups.",
|
|
594
|
-
"Use Journal tools only after checking whether the information belongs in Statement, Expectation, Requirement, Decision, Task, Change, Risk, Environment, Component, or another dedicated record.",
|
|
771
|
+
"Use Journal tools only after checking whether the information belongs in Statement, Expectation, Requirement, Decision, Task, Change, Incident, Problem, Support Request, Risk, Environment, Component, Maintenance Schedule, Value Realization, or another dedicated record.",
|
|
595
772
|
"Use workspace-scoped records for shared process work.",
|
|
596
773
|
"Use readableId values such as REQ-0001 as human-facing references when a tool accepts an existing record id; UUID remains the primary key and link target.",
|
|
597
774
|
"Hosted MCP access derives workspace scope from installed repo config and actor scope from OAuth.",
|
|
@@ -599,6 +776,8 @@ const PROCESS_GUIDE = {
|
|
|
599
776
|
"Use append_decision_entry and append_task_entry for Decision and Task history; do not rewrite their current decision or status directly.",
|
|
600
777
|
"Use link_decision_input when a record informed a Decision; use list_linked_records with relationship informed_by to review the decision inputs.",
|
|
601
778
|
"Use Benefits for Owner-authored benefit lists; use Estimate for Engineer cost estimates.",
|
|
779
|
+
"Use Value Realization for measured before/after value, not for expected benefits.",
|
|
780
|
+
"Use Support Request for shared user-facing support follow-up; use DX Complete Ticket for private communication with DX Complete.",
|
|
602
781
|
"Use link_records and unlink_records for other relationships between records."
|
|
603
782
|
]
|
|
604
783
|
};
|
|
@@ -716,7 +895,7 @@ export function createMcpServer(runtime, options = {}) {
|
|
|
716
895
|
});
|
|
717
896
|
});
|
|
718
897
|
registerDxcTool("get_doc", {
|
|
719
|
-
description: "Get an on-demand DX Complete reference page for MCP clients. Use this for fuller outcomes, flow, records, roles, and glossary content without loading all documentation into the process guide.",
|
|
898
|
+
description: "Get an on-demand DX Complete reference page for MCP clients. Use this for fuller outcomes, flow, records, roles, operating guide, and glossary content without loading all documentation into the process guide.",
|
|
720
899
|
inputSchema: {
|
|
721
900
|
page: z.enum(DOC_PAGE_IDS),
|
|
722
901
|
term: z.string().min(1).optional()
|
|
@@ -948,6 +1127,37 @@ export function createMcpServer(runtime, options = {}) {
|
|
|
948
1127
|
revisionNote: z.string().min(1).optional()
|
|
949
1128
|
}
|
|
950
1129
|
}, async (input) => jsonResult(await updateComponent(runtime, input, recordActorId)));
|
|
1130
|
+
registerDxcTool("create_maintenance_schedule", {
|
|
1131
|
+
description: "Create a Maintenance Schedule for recurring operational hygiene. Due state is derived from cadence and linked completed Changes or Tasks.",
|
|
1132
|
+
inputSchema: {
|
|
1133
|
+
workspaceId: workspaceIdSchema,
|
|
1134
|
+
name: z.string().min(1),
|
|
1135
|
+
kind: z.string().min(1),
|
|
1136
|
+
cadence: maintenanceCadenceSchema,
|
|
1137
|
+
startDate: z.string().min(1),
|
|
1138
|
+
summary: z.string().min(1).optional(),
|
|
1139
|
+
rationale: z.string().min(1).optional(),
|
|
1140
|
+
notes: z.string().min(1).optional(),
|
|
1141
|
+
fields: recordFieldsSchema
|
|
1142
|
+
}
|
|
1143
|
+
}, async (input) => jsonResult(await createMaintenanceSchedule(runtime, input, recordActorId)));
|
|
1144
|
+
registerDxcTool("update_maintenance_schedule", {
|
|
1145
|
+
description: "Update a Maintenance Schedule while preserving prior versions.",
|
|
1146
|
+
inputSchema: {
|
|
1147
|
+
workspaceId: workspaceIdSchema,
|
|
1148
|
+
id: z.string().min(1),
|
|
1149
|
+
name: z.string().min(1).optional(),
|
|
1150
|
+
kind: z.string().min(1).optional(),
|
|
1151
|
+
cadence: maintenanceCadenceSchema.optional(),
|
|
1152
|
+
startDate: z.string().min(1).optional(),
|
|
1153
|
+
summary: z.string().min(1).optional(),
|
|
1154
|
+
rationale: z.string().min(1).optional(),
|
|
1155
|
+
notes: z.string().min(1).optional(),
|
|
1156
|
+
fields: recordFieldsSchema,
|
|
1157
|
+
unsetFields: z.array(fieldNameSchema).optional(),
|
|
1158
|
+
revisionNote: z.string().min(1).optional()
|
|
1159
|
+
}
|
|
1160
|
+
}, async (input) => jsonResult(await updateMaintenanceSchedule(runtime, input, recordActorId)));
|
|
951
1161
|
registerDxcTool("create_estimate", {
|
|
952
1162
|
description: "Create an itemized Engineer cost estimate for Weigh.",
|
|
953
1163
|
inputSchema: {
|
|
@@ -998,6 +1208,32 @@ export function createMcpServer(runtime, options = {}) {
|
|
|
998
1208
|
revisionNote: z.string().min(1).optional()
|
|
999
1209
|
}
|
|
1000
1210
|
}, async (input) => jsonResult(await updateBenefits(runtime, input, recordActorId)));
|
|
1211
|
+
registerDxcTool("create_value_realization", {
|
|
1212
|
+
description: "Create a Value Realization record for measured before/after value. Comparisons are derived from baseline and actual metric values.",
|
|
1213
|
+
inputSchema: {
|
|
1214
|
+
workspaceId: workspaceIdSchema,
|
|
1215
|
+
title: z.string().min(1),
|
|
1216
|
+
summary: z.string().min(1).optional(),
|
|
1217
|
+
metrics: z.array(valueMetricSchema).min(1),
|
|
1218
|
+
requirementIds: z.array(z.string().min(1)).optional(),
|
|
1219
|
+
expectationIds: z.array(z.string().min(1)).optional(),
|
|
1220
|
+
commitmentIds: z.array(z.string().min(1)).optional(),
|
|
1221
|
+
fields: recordFieldsSchema
|
|
1222
|
+
}
|
|
1223
|
+
}, async (input) => jsonResult(await createValueRealization(runtime, input, recordActorId)));
|
|
1224
|
+
registerDxcTool("update_value_realization", {
|
|
1225
|
+
description: "Update a Value Realization record while preserving prior metric versions.",
|
|
1226
|
+
inputSchema: {
|
|
1227
|
+
workspaceId: workspaceIdSchema,
|
|
1228
|
+
id: z.string().min(1),
|
|
1229
|
+
title: z.string().min(1).optional(),
|
|
1230
|
+
summary: z.string().min(1).optional(),
|
|
1231
|
+
metrics: z.array(valueMetricSchema).min(1).optional(),
|
|
1232
|
+
fields: recordFieldsSchema,
|
|
1233
|
+
unsetFields: z.array(fieldNameSchema).optional(),
|
|
1234
|
+
revisionNote: z.string().min(1).optional()
|
|
1235
|
+
}
|
|
1236
|
+
}, async (input) => jsonResult(await updateValueRealization(runtime, input, recordActorId)));
|
|
1001
1237
|
registerDxcTool("create_expectation", {
|
|
1002
1238
|
description: "Create an expectation record.",
|
|
1003
1239
|
inputSchema: {
|
|
@@ -1101,22 +1337,29 @@ export function createMcpServer(runtime, options = {}) {
|
|
|
1101
1337
|
}
|
|
1102
1338
|
}, async (input) => jsonResult(await appendDeferralEventForTool(runtime, input, recordActorId)));
|
|
1103
1339
|
registerDxcTool("create_change", {
|
|
1104
|
-
description: "Create a service change record with baseline plan, execution, rollback, and risk/impact sections.",
|
|
1340
|
+
description: "Create a service change record with baseline type, plan, execution, rollback, and risk/impact sections. Use riskImpact to include downstream impact or blast radius when known.",
|
|
1105
1341
|
inputSchema: {
|
|
1106
1342
|
workspaceId: workspaceIdSchema,
|
|
1107
1343
|
title: z.string().min(1),
|
|
1108
1344
|
summary: z.string().min(1).optional(),
|
|
1345
|
+
changeType: changeTypeSchema.optional(),
|
|
1346
|
+
impactGrade: changeImpactGradeSchema.optional(),
|
|
1347
|
+
emergencyImportance: z.string().min(1).optional(),
|
|
1348
|
+
emergencyImmediacy: z.string().min(1).optional(),
|
|
1109
1349
|
changePlan: z.string().min(1),
|
|
1110
1350
|
executionSteps: z.array(z.string().min(1)).min(1),
|
|
1111
1351
|
rollbackPlan: z.string().min(1),
|
|
1112
|
-
riskImpact: z
|
|
1352
|
+
riskImpact: z
|
|
1353
|
+
.string()
|
|
1354
|
+
.min(1)
|
|
1355
|
+
.describe("Risk, impact, open concerns, and downstream impact for this Change. Include what else may be affected, what depends on what is changing, and the blast-radius conclusion identified by the Engineer or their tool when available. This is documentary and absence of analysis is visible; it is not a separate blocker."),
|
|
1113
1356
|
plannedFor: z.string().min(1).optional(),
|
|
1114
1357
|
requirementId: z.string().min(1).optional(),
|
|
1115
1358
|
fields: recordFieldsSchema
|
|
1116
1359
|
}
|
|
1117
1360
|
}, async (input) => jsonResult(await createChange(runtime, input, recordActorId)));
|
|
1118
1361
|
registerDxcTool("append_change_event", {
|
|
1119
|
-
description: "Append an immutable event to a Change record. Use this for notice, veto,
|
|
1362
|
+
description: "Append an immutable event to a Change record. Use this for notice, veto, decision, result, recovery, plan revisions, and notes. Result and recovery events may include optional Git commit references as documentary traceability.",
|
|
1120
1363
|
inputSchema: {
|
|
1121
1364
|
workspaceId: workspaceIdSchema,
|
|
1122
1365
|
changeId: z.string().min(1),
|
|
@@ -1126,12 +1369,19 @@ export function createMcpServer(runtime, options = {}) {
|
|
|
1126
1369
|
effectiveAt: z.string().min(1).optional(),
|
|
1127
1370
|
vetoByRole: changeVetoRoleSchema.optional(),
|
|
1128
1371
|
reason: z.string().min(1).optional(),
|
|
1129
|
-
importance: z.string().min(1).optional(),
|
|
1130
|
-
immediacy: z.string().min(1).optional(),
|
|
1131
1372
|
decision: changeDecisionSchema.optional(),
|
|
1132
1373
|
result: changeResultSchema.optional(),
|
|
1374
|
+
gitCommitReferences: z
|
|
1375
|
+
.array(gitCommitReferenceSchema)
|
|
1376
|
+
.min(1)
|
|
1377
|
+
.optional()
|
|
1378
|
+
.describe("Optional Git commit references for result or recovery events. Each reference stores a commit identifier plus optional repository and URL. DX Complete records these as provided and does not validate or inspect Git."),
|
|
1133
1379
|
summary: z.string().min(1).optional(),
|
|
1134
1380
|
note: z.string().min(1).optional(),
|
|
1381
|
+
revisedChangeType: changeTypeSchema.optional(),
|
|
1382
|
+
revisedImpactGrade: changeImpactGradeSchema.optional(),
|
|
1383
|
+
revisedEmergencyImportance: z.string().min(1).optional(),
|
|
1384
|
+
revisedEmergencyImmediacy: z.string().min(1).optional(),
|
|
1135
1385
|
revisedChangePlan: z.string().min(1).optional(),
|
|
1136
1386
|
revisedExecutionSteps: z.array(z.string().min(1)).optional(),
|
|
1137
1387
|
revisedRollbackPlan: z.string().min(1).optional(),
|
|
@@ -1171,27 +1421,102 @@ export function createMcpServer(runtime, options = {}) {
|
|
|
1171
1421
|
}
|
|
1172
1422
|
}, async (input) => jsonResult(await appendDecisionEntry(runtime.db, input, recordActorId)));
|
|
1173
1423
|
registerDxcTool("create_risk", {
|
|
1174
|
-
description: "Create a
|
|
1424
|
+
description: "Create a Risk ledger for uncertainty or exposure. Current status, assessment, and treatment derive from entries.",
|
|
1175
1425
|
inputSchema: {
|
|
1176
1426
|
workspaceId: workspaceIdSchema,
|
|
1177
1427
|
title: z.string().min(1),
|
|
1428
|
+
topic: z.string().min(1),
|
|
1178
1429
|
summary: z.string().min(1).optional(),
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1430
|
+
body: z.string().min(1).optional(),
|
|
1431
|
+
likelihood: riskLevelSchema.optional(),
|
|
1432
|
+
impact: riskLevelSchema.optional(),
|
|
1433
|
+
treatment: riskTreatmentSchema.optional(),
|
|
1434
|
+
treatmentRationale: z.string().min(1).optional(),
|
|
1182
1435
|
fields: recordFieldsSchema
|
|
1183
1436
|
}
|
|
1184
|
-
}, async (
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1437
|
+
}, async (input) => jsonResult(await createRisk(runtime, input, recordActorId, options.workspaceRoles)));
|
|
1438
|
+
registerDxcTool("append_risk_entry", {
|
|
1439
|
+
description: "Append an immutable entry to a Risk. Use assessment entries for likelihood/impact and treatment entries for accept, mitigate, transfer, or avoid.",
|
|
1440
|
+
inputSchema: {
|
|
1441
|
+
workspaceId: workspaceIdSchema,
|
|
1442
|
+
riskId: z.string().min(1),
|
|
1443
|
+
entryType: riskEntryTypeSchema,
|
|
1444
|
+
body: z.string().min(1),
|
|
1445
|
+
likelihood: riskLevelSchema.optional(),
|
|
1446
|
+
impact: riskLevelSchema.optional(),
|
|
1447
|
+
treatment: riskTreatmentSchema.optional(),
|
|
1448
|
+
treatmentRationale: z.string().min(1).optional()
|
|
1449
|
+
}
|
|
1450
|
+
}, async (input) => {
|
|
1451
|
+
assertRiskAcceptanceAccess(input.treatment, options.workspaceRoles);
|
|
1452
|
+
return jsonResult(await appendRiskEntry(runtime.db, input, recordActorId));
|
|
1453
|
+
});
|
|
1454
|
+
registerDxcTool("create_incident", {
|
|
1455
|
+
description: "Create an Incident ledger for a specific service-impacting or potentially service-impacting occurrence.",
|
|
1456
|
+
inputSchema: {
|
|
1457
|
+
workspaceId: workspaceIdSchema,
|
|
1458
|
+
title: z.string().min(1),
|
|
1459
|
+
description: z.string().min(1),
|
|
1460
|
+
summary: z.string().min(1).optional(),
|
|
1461
|
+
severity: incidentSeveritySchema.optional(),
|
|
1462
|
+
componentIds: z.array(z.string().min(1)).optional(),
|
|
1463
|
+
fields: recordFieldsSchema
|
|
1193
1464
|
}
|
|
1194
|
-
}, recordActorId)));
|
|
1465
|
+
}, async (input) => jsonResult(await createIncident(runtime, input, recordActorId)));
|
|
1466
|
+
registerDxcTool("append_incident_entry", {
|
|
1467
|
+
description: "Append an immutable entry to an Incident. Current status and severity derive from the latest relevant entries.",
|
|
1468
|
+
inputSchema: {
|
|
1469
|
+
workspaceId: workspaceIdSchema,
|
|
1470
|
+
incidentId: z.string().min(1),
|
|
1471
|
+
entryType: incidentEntryTypeSchema,
|
|
1472
|
+
body: z.string().min(1),
|
|
1473
|
+
severity: incidentSeveritySchema.optional()
|
|
1474
|
+
}
|
|
1475
|
+
}, async (input) => jsonResult(await appendIncidentEntry(runtime.db, input, recordActorId)));
|
|
1476
|
+
registerDxcTool("create_support_request", {
|
|
1477
|
+
description: "Create a shared Support Request ledger for a reported user experience, question, request, or issue. This is distinct from a private DX Complete Ticket.",
|
|
1478
|
+
inputSchema: {
|
|
1479
|
+
workspaceId: workspaceIdSchema,
|
|
1480
|
+
title: z.string().min(1),
|
|
1481
|
+
reporter: z.string().min(1),
|
|
1482
|
+
kind: z.string().min(1),
|
|
1483
|
+
reportedExperience: z.string().min(1),
|
|
1484
|
+
summary: z.string().min(1).optional(),
|
|
1485
|
+
fields: recordFieldsSchema
|
|
1486
|
+
}
|
|
1487
|
+
}, async (input) => jsonResult(await createSupportRequest(runtime, input, recordActorId)));
|
|
1488
|
+
registerDxcTool("append_support_request_entry", {
|
|
1489
|
+
description: "Append an immutable entry to a Support Request. Escalated entries require an Incident link; current status derives from the latest status-bearing entry.",
|
|
1490
|
+
inputSchema: {
|
|
1491
|
+
workspaceId: workspaceIdSchema,
|
|
1492
|
+
supportRequestId: z.string().min(1),
|
|
1493
|
+
entryType: supportRequestEntryTypeSchema,
|
|
1494
|
+
body: z.string().min(1),
|
|
1495
|
+
incidentId: z.string().min(1).optional()
|
|
1496
|
+
}
|
|
1497
|
+
}, async (input) => jsonResult(await appendSupportRequestEntryForTool(runtime, input, recordActorId)));
|
|
1498
|
+
registerDxcTool("create_problem", {
|
|
1499
|
+
description: "Create a Problem ledger for an underlying or recurring cause evidenced by one or more Incidents.",
|
|
1500
|
+
inputSchema: {
|
|
1501
|
+
workspaceId: workspaceIdSchema,
|
|
1502
|
+
title: z.string().min(1),
|
|
1503
|
+
description: z.string().min(1),
|
|
1504
|
+
summary: z.string().min(1).optional(),
|
|
1505
|
+
incidentIds: z.array(z.string().min(1)).min(1),
|
|
1506
|
+
componentIds: z.array(z.string().min(1)).optional(),
|
|
1507
|
+
fields: recordFieldsSchema
|
|
1508
|
+
}
|
|
1509
|
+
}, async (input) => jsonResult(await createProblem(runtime, input, recordActorId)));
|
|
1510
|
+
registerDxcTool("append_problem_entry", {
|
|
1511
|
+
description: "Append an immutable entry to a Problem. Current status and root cause derive from the latest relevant entries.",
|
|
1512
|
+
inputSchema: {
|
|
1513
|
+
workspaceId: workspaceIdSchema,
|
|
1514
|
+
problemId: z.string().min(1),
|
|
1515
|
+
entryType: problemEntryTypeSchema,
|
|
1516
|
+
body: z.string().min(1),
|
|
1517
|
+
rootCause: z.string().min(1).optional()
|
|
1518
|
+
}
|
|
1519
|
+
}, async (input) => jsonResult(await appendProblemEntry(runtime.db, input, recordActorId)));
|
|
1195
1520
|
registerDxcTool("list_records", {
|
|
1196
1521
|
description: "List records from a DX Complete collection.",
|
|
1197
1522
|
inputSchema: {
|
|
@@ -1351,12 +1676,18 @@ function assertToolRoleAccess(toolName, roles, input) {
|
|
|
1351
1676
|
if (roles.includes("engineer") && engineerAllowedTools.has(toolName)) {
|
|
1352
1677
|
return;
|
|
1353
1678
|
}
|
|
1679
|
+
if (roles.includes("tester") && testerAllowedTools.has(toolName)) {
|
|
1680
|
+
return;
|
|
1681
|
+
}
|
|
1354
1682
|
if (roles.includes("operator") && operatorAllowedTools.has(toolName)) {
|
|
1355
1683
|
return;
|
|
1356
1684
|
}
|
|
1685
|
+
if (roles.includes("support_agent") && supportAgentAllowedTools.has(toolName)) {
|
|
1686
|
+
return;
|
|
1687
|
+
}
|
|
1357
1688
|
if (roles.includes("operator") &&
|
|
1358
1689
|
toolName === "archive_record" &&
|
|
1359
|
-
(input.recordType === "environments" || input.recordType === "components")) {
|
|
1690
|
+
(input.recordType === "environments" || input.recordType === "components" || input.recordType === "maintenance_schedules")) {
|
|
1360
1691
|
return;
|
|
1361
1692
|
}
|
|
1362
1693
|
throw new Error(`Workspace role access denied for ${toolName}.`);
|
|
@@ -1386,15 +1717,36 @@ const engineerAllowedTools = new Set([
|
|
|
1386
1717
|
"update_estimate",
|
|
1387
1718
|
"create_task",
|
|
1388
1719
|
"append_task_entry",
|
|
1720
|
+
"create_risk",
|
|
1721
|
+
"append_risk_entry",
|
|
1722
|
+
"append_review_note"
|
|
1723
|
+
]);
|
|
1724
|
+
const testerAllowedTools = new Set([
|
|
1725
|
+
"create_risk",
|
|
1726
|
+
"append_risk_entry",
|
|
1389
1727
|
"append_review_note"
|
|
1390
1728
|
]);
|
|
1391
1729
|
const operatorAllowedTools = new Set([
|
|
1392
1730
|
"create_change",
|
|
1393
1731
|
"append_change_event",
|
|
1732
|
+
"create_risk",
|
|
1733
|
+
"append_risk_entry",
|
|
1734
|
+
"create_incident",
|
|
1735
|
+
"append_incident_entry",
|
|
1736
|
+
"create_problem",
|
|
1737
|
+
"append_problem_entry",
|
|
1394
1738
|
"create_environment",
|
|
1395
1739
|
"update_environment",
|
|
1396
1740
|
"create_component",
|
|
1397
|
-
"update_component"
|
|
1741
|
+
"update_component",
|
|
1742
|
+
"create_maintenance_schedule",
|
|
1743
|
+
"update_maintenance_schedule"
|
|
1744
|
+
]);
|
|
1745
|
+
const supportAgentAllowedTools = new Set([
|
|
1746
|
+
"create_incident",
|
|
1747
|
+
"append_incident_entry",
|
|
1748
|
+
"create_support_request",
|
|
1749
|
+
"append_support_request_entry"
|
|
1398
1750
|
]);
|
|
1399
1751
|
function jsonResult(value) {
|
|
1400
1752
|
return {
|
|
@@ -1571,6 +1923,47 @@ async function updateComponent(runtime, input, actorId) {
|
|
|
1571
1923
|
revisionNote: input.revisionNote
|
|
1572
1924
|
}, actorId);
|
|
1573
1925
|
}
|
|
1926
|
+
async function createMaintenanceSchedule(runtime, input, actorId) {
|
|
1927
|
+
assertNoTypedFields(input.fields, "create_maintenance_schedule", MAINTENANCE_SCHEDULE_TYPED_FIELDS);
|
|
1928
|
+
return createRecord(runtime.db, "maintenance_schedules", {
|
|
1929
|
+
workspaceId: input.workspaceId,
|
|
1930
|
+
title: input.name,
|
|
1931
|
+
summary: input.summary ?? input.rationale,
|
|
1932
|
+
allowManagedFields: true,
|
|
1933
|
+
fields: {
|
|
1934
|
+
...(input.fields ?? {}),
|
|
1935
|
+
name: input.name,
|
|
1936
|
+
kind: input.kind,
|
|
1937
|
+
cadence: input.cadence,
|
|
1938
|
+
startDate: input.startDate,
|
|
1939
|
+
...(input.rationale !== undefined ? { rationale: input.rationale } : {}),
|
|
1940
|
+
...(input.notes !== undefined ? { notes: input.notes } : {})
|
|
1941
|
+
}
|
|
1942
|
+
}, actorId);
|
|
1943
|
+
}
|
|
1944
|
+
async function updateMaintenanceSchedule(runtime, input, actorId) {
|
|
1945
|
+
assertNoTypedFields(input.fields, "update_maintenance_schedule", MAINTENANCE_SCHEDULE_TYPED_FIELDS);
|
|
1946
|
+
assertNoTypedUnsetFields(input.unsetFields, "update_maintenance_schedule", MAINTENANCE_SCHEDULE_TYPED_FIELDS);
|
|
1947
|
+
return updateRecord(runtime.db, {
|
|
1948
|
+
recordType: "maintenance_schedules",
|
|
1949
|
+
workspaceId: input.workspaceId,
|
|
1950
|
+
id: input.id,
|
|
1951
|
+
title: input.name,
|
|
1952
|
+
summary: input.summary,
|
|
1953
|
+
fields: {
|
|
1954
|
+
...(input.fields ?? {}),
|
|
1955
|
+
...(input.name !== undefined ? { name: input.name } : {}),
|
|
1956
|
+
...(input.kind !== undefined ? { kind: input.kind } : {}),
|
|
1957
|
+
...(input.cadence !== undefined ? { cadence: input.cadence } : {}),
|
|
1958
|
+
...(input.startDate !== undefined ? { startDate: input.startDate } : {}),
|
|
1959
|
+
...(input.rationale !== undefined ? { rationale: input.rationale } : {}),
|
|
1960
|
+
...(input.notes !== undefined ? { notes: input.notes } : {})
|
|
1961
|
+
},
|
|
1962
|
+
unsetFields: input.unsetFields,
|
|
1963
|
+
allowManagedFields: true,
|
|
1964
|
+
revisionNote: input.revisionNote
|
|
1965
|
+
}, actorId);
|
|
1966
|
+
}
|
|
1574
1967
|
async function createExpectation(runtime, input, actorId) {
|
|
1575
1968
|
assertNoTypedFields(input.fields, "create_expectation", EXPECTATION_TYPED_FIELDS);
|
|
1576
1969
|
assertNoObsoleteExpectationFields(input.fields, "create_expectation");
|
|
@@ -1717,6 +2110,69 @@ async function updateBenefits(runtime, input, actorId) {
|
|
|
1717
2110
|
revisionNote: input.revisionNote
|
|
1718
2111
|
}, actorId);
|
|
1719
2112
|
}
|
|
2113
|
+
async function createValueRealization(runtime, input, actorId) {
|
|
2114
|
+
assertNoReservedFields(input.fields, ["workspaceId", "requirementIds", "expectationIds", "commitmentIds"]);
|
|
2115
|
+
assertNoTypedFields(input.fields, "create_value_realization", VALUE_REALIZATION_TYPED_FIELDS);
|
|
2116
|
+
assertAtLeastOneValueRealizationTarget(input.requirementIds, input.expectationIds, input.commitmentIds);
|
|
2117
|
+
await assertRuntimeRecordsExist(runtime, input.workspaceId, "requirements", uniqueIds(input.requirementIds ?? []));
|
|
2118
|
+
await assertRuntimeRecordsExist(runtime, input.workspaceId, "expectations", uniqueIds(input.expectationIds ?? []));
|
|
2119
|
+
await assertRuntimeRecordsExist(runtime, input.workspaceId, "commitments", uniqueIds(input.commitmentIds ?? []));
|
|
2120
|
+
let valueRealization = await createRecord(runtime.db, "value_realizations", {
|
|
2121
|
+
workspaceId: input.workspaceId,
|
|
2122
|
+
title: input.title,
|
|
2123
|
+
summary: input.summary,
|
|
2124
|
+
allowManagedFields: true,
|
|
2125
|
+
fields: {
|
|
2126
|
+
...(input.fields ?? {}),
|
|
2127
|
+
metrics: normalizeValueMetrics(input.metrics)
|
|
2128
|
+
}
|
|
2129
|
+
}, actorId);
|
|
2130
|
+
valueRealization = await linkManyRecords(runtime, valueRealization, "requirements", input.requirementIds ?? [], "realizes_value_for", actorId);
|
|
2131
|
+
valueRealization = await linkManyRecords(runtime, valueRealization, "expectations", input.expectationIds ?? [], "realizes_value_for", actorId);
|
|
2132
|
+
valueRealization = await linkManyRecords(runtime, valueRealization, "commitments", input.commitmentIds ?? [], "realizes_value_for", actorId);
|
|
2133
|
+
return valueRealization;
|
|
2134
|
+
}
|
|
2135
|
+
async function updateValueRealization(runtime, input, actorId) {
|
|
2136
|
+
assertNoTypedFields(input.fields, "update_value_realization", VALUE_REALIZATION_TYPED_FIELDS);
|
|
2137
|
+
assertNoTypedUnsetFields(input.unsetFields, "update_value_realization", VALUE_REALIZATION_TYPED_FIELDS);
|
|
2138
|
+
return updateRecord(runtime.db, {
|
|
2139
|
+
recordType: "value_realizations",
|
|
2140
|
+
workspaceId: input.workspaceId,
|
|
2141
|
+
id: input.id,
|
|
2142
|
+
title: input.title,
|
|
2143
|
+
summary: input.summary,
|
|
2144
|
+
fields: {
|
|
2145
|
+
...(input.fields ?? {}),
|
|
2146
|
+
...(input.metrics ? { metrics: normalizeValueMetrics(input.metrics) } : {})
|
|
2147
|
+
},
|
|
2148
|
+
unsetFields: input.unsetFields,
|
|
2149
|
+
allowManagedFields: true,
|
|
2150
|
+
revisionNote: input.revisionNote
|
|
2151
|
+
}, actorId);
|
|
2152
|
+
}
|
|
2153
|
+
function normalizeValueMetrics(metrics) {
|
|
2154
|
+
const seenIds = new Set();
|
|
2155
|
+
return metrics.map((metric, index) => {
|
|
2156
|
+
const id = metric.id ?? randomUUID();
|
|
2157
|
+
if (seenIds.has(id)) {
|
|
2158
|
+
throw new Error(`Value metric id must be unique: ${id}.`);
|
|
2159
|
+
}
|
|
2160
|
+
seenIds.add(id);
|
|
2161
|
+
assertMeasuredAtDate(metric.baseline.measuredAt, `Value metric ${index + 1} baseline`);
|
|
2162
|
+
if (metric.actual) {
|
|
2163
|
+
assertMeasuredAtDate(metric.actual.measuredAt, `Value metric ${index + 1} actual`);
|
|
2164
|
+
}
|
|
2165
|
+
return {
|
|
2166
|
+
...metric,
|
|
2167
|
+
id
|
|
2168
|
+
};
|
|
2169
|
+
});
|
|
2170
|
+
}
|
|
2171
|
+
function assertMeasuredAtDate(value, label) {
|
|
2172
|
+
if (Number.isNaN(new Date(value).getTime())) {
|
|
2173
|
+
throw new Error(`${label} measuredAt must be a valid date string.`);
|
|
2174
|
+
}
|
|
2175
|
+
}
|
|
1720
2176
|
function normalizeBenefitItems(benefitItems) {
|
|
1721
2177
|
const seenIds = new Set();
|
|
1722
2178
|
return benefitItems.map((item, index) => {
|
|
@@ -2089,6 +2545,12 @@ function assertAtLeastOneBenefitsTarget(requirementIds, expectationIds) {
|
|
|
2089
2545
|
}
|
|
2090
2546
|
throw new Error("create_benefits requires at least one requirementId or expectationId.");
|
|
2091
2547
|
}
|
|
2548
|
+
function assertAtLeastOneValueRealizationTarget(requirementIds, expectationIds, commitmentIds) {
|
|
2549
|
+
if ((requirementIds?.length ?? 0) > 0 || (expectationIds?.length ?? 0) > 0 || (commitmentIds?.length ?? 0) > 0) {
|
|
2550
|
+
return;
|
|
2551
|
+
}
|
|
2552
|
+
throw new Error("create_value_realization requires at least one requirementId, expectationId, or commitmentId.");
|
|
2553
|
+
}
|
|
2092
2554
|
async function assertRecordsExist(runtime, recordType, ids, workspaceId) {
|
|
2093
2555
|
for (const id of new Set(ids)) {
|
|
2094
2556
|
const record = await getRecord(runtime.db, recordType, id, { workspaceId });
|
|
@@ -2134,9 +2596,228 @@ async function ensureLinkRecords(runtime, input, actorId) {
|
|
|
2134
2596
|
}
|
|
2135
2597
|
return linkRecords(runtime.db, input, actorId);
|
|
2136
2598
|
}
|
|
2599
|
+
async function createRisk(runtime, input, actorId, roles) {
|
|
2600
|
+
assertNoReservedFields(input.fields, ["workspaceId"]);
|
|
2601
|
+
assertNoTypedFields(input.fields, "create_risk", RISK_TYPED_FIELDS);
|
|
2602
|
+
assertRiskAcceptanceAccess(input.treatment, roles);
|
|
2603
|
+
if ((input.likelihood && !input.impact) || (!input.likelihood && input.impact)) {
|
|
2604
|
+
throw new Error("create_risk requires both likelihood and impact when creating an initial assessment.");
|
|
2605
|
+
}
|
|
2606
|
+
const now = new Date().toISOString();
|
|
2607
|
+
const identifiedEntry = {
|
|
2608
|
+
id: randomUUID(),
|
|
2609
|
+
entryType: "identified",
|
|
2610
|
+
body: input.body ?? input.topic,
|
|
2611
|
+
createdAt: now,
|
|
2612
|
+
createdBy: actorId
|
|
2613
|
+
};
|
|
2614
|
+
const entries = [identifiedEntry];
|
|
2615
|
+
const fields = {
|
|
2616
|
+
...(input.fields ?? {}),
|
|
2617
|
+
topic: input.topic,
|
|
2618
|
+
entries,
|
|
2619
|
+
currentStatus: riskEntryToCurrentStatus(identifiedEntry, "open")
|
|
2620
|
+
};
|
|
2621
|
+
if (input.likelihood && input.impact) {
|
|
2622
|
+
const assessmentEntry = {
|
|
2623
|
+
id: randomUUID(),
|
|
2624
|
+
entryType: "assessment",
|
|
2625
|
+
body: "Initial risk assessment.",
|
|
2626
|
+
likelihood: input.likelihood,
|
|
2627
|
+
impact: input.impact,
|
|
2628
|
+
createdAt: now,
|
|
2629
|
+
createdBy: actorId
|
|
2630
|
+
};
|
|
2631
|
+
entries.push(assessmentEntry);
|
|
2632
|
+
fields.currentAssessment = riskEntryToCurrentAssessment(assessmentEntry);
|
|
2633
|
+
}
|
|
2634
|
+
if (input.treatment) {
|
|
2635
|
+
const treatmentEntry = {
|
|
2636
|
+
id: randomUUID(),
|
|
2637
|
+
entryType: "treatment",
|
|
2638
|
+
body: input.treatmentRationale ?? `Initial risk treatment: ${input.treatment}.`,
|
|
2639
|
+
treatment: input.treatment,
|
|
2640
|
+
...(input.treatmentRationale ? { treatmentRationale: input.treatmentRationale } : {}),
|
|
2641
|
+
createdAt: now,
|
|
2642
|
+
createdBy: actorId
|
|
2643
|
+
};
|
|
2644
|
+
entries.push(treatmentEntry);
|
|
2645
|
+
fields.currentTreatment = riskEntryToCurrentTreatment(treatmentEntry);
|
|
2646
|
+
}
|
|
2647
|
+
return createRecord(runtime.db, "risks", {
|
|
2648
|
+
workspaceId: input.workspaceId,
|
|
2649
|
+
title: input.title,
|
|
2650
|
+
summary: input.summary ?? input.topic,
|
|
2651
|
+
allowManagedFields: true,
|
|
2652
|
+
fields
|
|
2653
|
+
}, actorId);
|
|
2654
|
+
}
|
|
2655
|
+
async function createIncident(runtime, input, actorId) {
|
|
2656
|
+
assertNoReservedFields(input.fields, ["workspaceId", "componentIds"]);
|
|
2657
|
+
assertNoTypedFields(input.fields, "create_incident", INCIDENT_TYPED_FIELDS);
|
|
2658
|
+
const componentIds = uniqueIds(input.componentIds ?? []);
|
|
2659
|
+
await assertRuntimeRecordsExist(runtime, input.workspaceId, "components", componentIds);
|
|
2660
|
+
const now = new Date().toISOString();
|
|
2661
|
+
const detectedEntry = {
|
|
2662
|
+
id: randomUUID(),
|
|
2663
|
+
entryType: "detected",
|
|
2664
|
+
body: input.description,
|
|
2665
|
+
createdAt: now,
|
|
2666
|
+
createdBy: actorId
|
|
2667
|
+
};
|
|
2668
|
+
const entries = [detectedEntry];
|
|
2669
|
+
const fields = {
|
|
2670
|
+
...(input.fields ?? {}),
|
|
2671
|
+
description: input.description,
|
|
2672
|
+
entries,
|
|
2673
|
+
currentStatus: incidentEntryToCurrentStatus(detectedEntry, "open")
|
|
2674
|
+
};
|
|
2675
|
+
if (input.severity) {
|
|
2676
|
+
const severityEntry = {
|
|
2677
|
+
id: randomUUID(),
|
|
2678
|
+
entryType: "severity",
|
|
2679
|
+
body: `Initial severity: ${input.severity}.`,
|
|
2680
|
+
severity: input.severity,
|
|
2681
|
+
createdAt: now,
|
|
2682
|
+
createdBy: actorId
|
|
2683
|
+
};
|
|
2684
|
+
entries.push(severityEntry);
|
|
2685
|
+
fields.currentSeverity = incidentEntryToCurrentSeverity(severityEntry);
|
|
2686
|
+
}
|
|
2687
|
+
let incident = await createRecord(runtime.db, "incidents", {
|
|
2688
|
+
workspaceId: input.workspaceId,
|
|
2689
|
+
title: input.title,
|
|
2690
|
+
summary: input.summary ?? input.description,
|
|
2691
|
+
allowManagedFields: true,
|
|
2692
|
+
fields
|
|
2693
|
+
}, actorId);
|
|
2694
|
+
for (const componentId of componentIds) {
|
|
2695
|
+
incident = await ensureLinkRecords(runtime, {
|
|
2696
|
+
workspaceId: input.workspaceId,
|
|
2697
|
+
fromType: "incidents",
|
|
2698
|
+
fromId: incident._id,
|
|
2699
|
+
toType: "components",
|
|
2700
|
+
toId: componentId,
|
|
2701
|
+
relationship: "affects"
|
|
2702
|
+
}, actorId);
|
|
2703
|
+
}
|
|
2704
|
+
return incident;
|
|
2705
|
+
}
|
|
2706
|
+
async function createSupportRequest(runtime, input, actorId) {
|
|
2707
|
+
assertNoReservedFields(input.fields, ["workspaceId"]);
|
|
2708
|
+
assertNoTypedFields(input.fields, "create_support_request", SUPPORT_REQUEST_TYPED_FIELDS);
|
|
2709
|
+
const now = new Date().toISOString();
|
|
2710
|
+
const raisedEntry = {
|
|
2711
|
+
id: randomUUID(),
|
|
2712
|
+
entryType: "raised",
|
|
2713
|
+
body: input.reportedExperience,
|
|
2714
|
+
createdAt: now,
|
|
2715
|
+
createdBy: actorId
|
|
2716
|
+
};
|
|
2717
|
+
return createRecord(runtime.db, "support_requests", {
|
|
2718
|
+
workspaceId: input.workspaceId,
|
|
2719
|
+
title: input.title,
|
|
2720
|
+
summary: input.summary ?? input.reportedExperience,
|
|
2721
|
+
allowManagedFields: true,
|
|
2722
|
+
fields: {
|
|
2723
|
+
...(input.fields ?? {}),
|
|
2724
|
+
reporter: input.reporter,
|
|
2725
|
+
kind: input.kind,
|
|
2726
|
+
reportedExperience: input.reportedExperience,
|
|
2727
|
+
entries: [raisedEntry],
|
|
2728
|
+
currentStatus: supportRequestEntryToCurrentStatus(raisedEntry)
|
|
2729
|
+
}
|
|
2730
|
+
}, actorId);
|
|
2731
|
+
}
|
|
2732
|
+
async function appendSupportRequestEntryForTool(runtime, input, actorId) {
|
|
2733
|
+
if (input.incidentId) {
|
|
2734
|
+
const incident = await getRecord(runtime.db, "incidents", input.incidentId, {
|
|
2735
|
+
workspaceId: input.workspaceId
|
|
2736
|
+
});
|
|
2737
|
+
if (!incident) {
|
|
2738
|
+
throw new Error(`Incident not found: incidents/${input.incidentId}`);
|
|
2739
|
+
}
|
|
2740
|
+
}
|
|
2741
|
+
const supportRequest = await appendSupportRequestEntry(runtime.db, input, actorId);
|
|
2742
|
+
if (!input.incidentId) {
|
|
2743
|
+
return supportRequest;
|
|
2744
|
+
}
|
|
2745
|
+
return ensureLinkRecords(runtime, {
|
|
2746
|
+
workspaceId: input.workspaceId,
|
|
2747
|
+
fromType: "support_requests",
|
|
2748
|
+
fromId: supportRequest._id,
|
|
2749
|
+
toType: "incidents",
|
|
2750
|
+
toId: input.incidentId,
|
|
2751
|
+
relationship: "spawned_incident"
|
|
2752
|
+
}, actorId);
|
|
2753
|
+
}
|
|
2754
|
+
async function createProblem(runtime, input, actorId) {
|
|
2755
|
+
assertNoReservedFields(input.fields, ["workspaceId", "incidentIds", "componentIds"]);
|
|
2756
|
+
assertNoTypedFields(input.fields, "create_problem", PROBLEM_TYPED_FIELDS);
|
|
2757
|
+
const incidentIds = uniqueIds(input.incidentIds);
|
|
2758
|
+
const componentIds = uniqueIds(input.componentIds ?? []);
|
|
2759
|
+
if (incidentIds.length === 0) {
|
|
2760
|
+
throw new Error("create_problem requires at least one incidentId.");
|
|
2761
|
+
}
|
|
2762
|
+
await assertRuntimeRecordsExist(runtime, input.workspaceId, "incidents", incidentIds);
|
|
2763
|
+
await assertRuntimeRecordsExist(runtime, input.workspaceId, "components", componentIds);
|
|
2764
|
+
const now = new Date().toISOString();
|
|
2765
|
+
const identifiedEntry = {
|
|
2766
|
+
id: randomUUID(),
|
|
2767
|
+
entryType: "identified",
|
|
2768
|
+
body: input.description,
|
|
2769
|
+
createdAt: now,
|
|
2770
|
+
createdBy: actorId
|
|
2771
|
+
};
|
|
2772
|
+
let problem = await createRecord(runtime.db, "problems", {
|
|
2773
|
+
workspaceId: input.workspaceId,
|
|
2774
|
+
title: input.title,
|
|
2775
|
+
summary: input.summary ?? input.description,
|
|
2776
|
+
allowManagedFields: true,
|
|
2777
|
+
fields: {
|
|
2778
|
+
...(input.fields ?? {}),
|
|
2779
|
+
description: input.description,
|
|
2780
|
+
entries: [identifiedEntry],
|
|
2781
|
+
currentStatus: problemEntryToCurrentStatus(identifiedEntry, "open")
|
|
2782
|
+
}
|
|
2783
|
+
}, actorId);
|
|
2784
|
+
for (const incidentId of incidentIds) {
|
|
2785
|
+
problem = await ensureLinkRecords(runtime, {
|
|
2786
|
+
workspaceId: input.workspaceId,
|
|
2787
|
+
fromType: "problems",
|
|
2788
|
+
fromId: problem._id,
|
|
2789
|
+
toType: "incidents",
|
|
2790
|
+
toId: incidentId,
|
|
2791
|
+
relationship: "evidenced_by"
|
|
2792
|
+
}, actorId);
|
|
2793
|
+
}
|
|
2794
|
+
for (const componentId of componentIds) {
|
|
2795
|
+
problem = await ensureLinkRecords(runtime, {
|
|
2796
|
+
workspaceId: input.workspaceId,
|
|
2797
|
+
fromType: "problems",
|
|
2798
|
+
fromId: problem._id,
|
|
2799
|
+
toType: "components",
|
|
2800
|
+
toId: componentId,
|
|
2801
|
+
relationship: "affects"
|
|
2802
|
+
}, actorId);
|
|
2803
|
+
}
|
|
2804
|
+
return problem;
|
|
2805
|
+
}
|
|
2806
|
+
function uniqueIds(ids) {
|
|
2807
|
+
return [...new Set(ids.map((id) => id.trim()).filter(Boolean))];
|
|
2808
|
+
}
|
|
2809
|
+
async function assertRuntimeRecordsExist(runtime, workspaceId, recordType, ids) {
|
|
2810
|
+
for (const id of ids) {
|
|
2811
|
+
const record = await getRecord(runtime.db, recordType, id, { workspaceId });
|
|
2812
|
+
if (!record) {
|
|
2813
|
+
throw new Error(`Record not found: ${recordType}/${id}`);
|
|
2814
|
+
}
|
|
2815
|
+
}
|
|
2816
|
+
}
|
|
2137
2817
|
async function createChange(runtime, input, actorId) {
|
|
2138
2818
|
assertNoReservedFields(input.fields, ["workspaceId", "initiativeId", "requirementId"]);
|
|
2139
2819
|
assertNoTypedFields(input.fields, "create_change", CHANGE_TYPED_FIELDS);
|
|
2820
|
+
assertChangeTypeInput(input);
|
|
2140
2821
|
if (input.requirementId) {
|
|
2141
2822
|
const requirement = await getRecord(runtime.db, "requirements", input.requirementId, {
|
|
2142
2823
|
workspaceId: input.workspaceId
|
|
@@ -2152,6 +2833,11 @@ async function createChange(runtime, input, actorId) {
|
|
|
2152
2833
|
allowManagedFields: true,
|
|
2153
2834
|
fields: {
|
|
2154
2835
|
...(input.fields ?? {}),
|
|
2836
|
+
changeType: input.changeType ?? "normal",
|
|
2837
|
+
...(input.impactGrade !== undefined ? { impactGrade: input.impactGrade } : {}),
|
|
2838
|
+
...(input.emergencyImportance !== undefined ? { emergencyImportance: input.emergencyImportance } : {}),
|
|
2839
|
+
...(input.emergencyImmediacy !== undefined ? { emergencyImmediacy: input.emergencyImmediacy } : {}),
|
|
2840
|
+
...emergencyRationaleGapFields(input),
|
|
2155
2841
|
changePlan: input.changePlan,
|
|
2156
2842
|
executionSteps: input.executionSteps,
|
|
2157
2843
|
rollbackPlan: input.rollbackPlan,
|
|
@@ -2172,6 +2858,9 @@ async function createChange(runtime, input, actorId) {
|
|
|
2172
2858
|
return change;
|
|
2173
2859
|
}
|
|
2174
2860
|
function buildChangeEventContent(input) {
|
|
2861
|
+
if (input.gitCommitReferences && input.eventType !== "result_reported" && input.eventType !== "recovery_recorded") {
|
|
2862
|
+
throw new Error("gitCommitReferences are only valid on result_reported or recovery_recorded Change events.");
|
|
2863
|
+
}
|
|
2175
2864
|
switch (input.eventType) {
|
|
2176
2865
|
case "notice_given":
|
|
2177
2866
|
return compactObject({
|
|
@@ -2185,12 +2874,6 @@ function buildChangeEventContent(input) {
|
|
|
2185
2874
|
reason: input.reason,
|
|
2186
2875
|
summary: input.summary
|
|
2187
2876
|
});
|
|
2188
|
-
case "emergency_declared":
|
|
2189
|
-
return compactObject({
|
|
2190
|
-
importance: requireChangeEventField(input.importance, input.eventType, "importance"),
|
|
2191
|
-
immediacy: requireChangeEventField(input.immediacy, input.eventType, "immediacy"),
|
|
2192
|
-
summary: input.summary
|
|
2193
|
-
});
|
|
2194
2877
|
case "decision_recorded":
|
|
2195
2878
|
return compactObject({
|
|
2196
2879
|
decision: requireChangeEventField(input.decision, input.eventType, "decision"),
|
|
@@ -2200,15 +2883,21 @@ function buildChangeEventContent(input) {
|
|
|
2200
2883
|
case "result_reported":
|
|
2201
2884
|
return compactObject({
|
|
2202
2885
|
result: requireChangeEventField(input.result, input.eventType, "result"),
|
|
2886
|
+
gitCommitReferences: input.gitCommitReferences,
|
|
2203
2887
|
summary: input.summary
|
|
2204
2888
|
});
|
|
2205
2889
|
case "recovery_recorded":
|
|
2206
2890
|
return compactObject({
|
|
2207
|
-
summary: requireChangeEventField(input.summary, input.eventType, "summary")
|
|
2891
|
+
summary: requireChangeEventField(input.summary, input.eventType, "summary"),
|
|
2892
|
+
gitCommitReferences: input.gitCommitReferences
|
|
2208
2893
|
});
|
|
2209
2894
|
case "plan_revised": {
|
|
2210
2895
|
const event = compactObject({
|
|
2211
2896
|
summary: input.summary,
|
|
2897
|
+
revisedChangeType: input.revisedChangeType,
|
|
2898
|
+
revisedImpactGrade: input.revisedImpactGrade,
|
|
2899
|
+
revisedEmergencyImportance: input.revisedEmergencyImportance,
|
|
2900
|
+
revisedEmergencyImmediacy: input.revisedEmergencyImmediacy,
|
|
2212
2901
|
revisedChangePlan: input.revisedChangePlan,
|
|
2213
2902
|
revisedExecutionSteps: input.revisedExecutionSteps,
|
|
2214
2903
|
revisedRollbackPlan: input.revisedRollbackPlan,
|
|
@@ -2232,6 +2921,31 @@ function requireChangeEventField(value, eventType, fieldName) {
|
|
|
2232
2921
|
}
|
|
2233
2922
|
return value;
|
|
2234
2923
|
}
|
|
2924
|
+
function assertRiskAcceptanceAccess(treatment, roles) {
|
|
2925
|
+
if (treatment !== "accept" || !roles || roles.includes("owner")) {
|
|
2926
|
+
return;
|
|
2927
|
+
}
|
|
2928
|
+
throw new Error("Formal risk acceptance requires the Owner role. Other roles may record non-acceptance treatment or proceed with visible open risk.");
|
|
2929
|
+
}
|
|
2930
|
+
function assertChangeTypeInput(input) {
|
|
2931
|
+
const changeType = input.changeType ?? "normal";
|
|
2932
|
+
if (input.impactGrade && changeType !== "normal") {
|
|
2933
|
+
throw new Error("impactGrade is only valid for normal changes.");
|
|
2934
|
+
}
|
|
2935
|
+
if ((input.emergencyImportance || input.emergencyImmediacy) && changeType !== "emergency") {
|
|
2936
|
+
throw new Error("emergencyImportance and emergencyImmediacy are only valid for emergency changes.");
|
|
2937
|
+
}
|
|
2938
|
+
}
|
|
2939
|
+
function emergencyRationaleGapFields(input) {
|
|
2940
|
+
if ((input.changeType ?? "normal") !== "emergency") {
|
|
2941
|
+
return {};
|
|
2942
|
+
}
|
|
2943
|
+
const missing = [
|
|
2944
|
+
...(input.emergencyImportance ? [] : ["emergencyImportance"]),
|
|
2945
|
+
...(input.emergencyImmediacy ? [] : ["emergencyImmediacy"])
|
|
2946
|
+
];
|
|
2947
|
+
return missing.length > 0 ? { emergencyRationaleGaps: missing } : {};
|
|
2948
|
+
}
|
|
2235
2949
|
function compactObject(input) {
|
|
2236
2950
|
return Object.fromEntries(Object.entries(input).filter(([, value]) => value !== undefined));
|
|
2237
2951
|
}
|