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.
Files changed (49) hide show
  1. package/README.md +18 -10
  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 +2 -0
  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 +20 -2
  18. package/scripts/smoke-mcp-http.mjs +460 -6
  19. package/src/init.ts +35 -0
  20. package/src/mcp/docs.ts +234 -14
  21. package/src/mcp/server.ts +1138 -83
  22. package/src/runtime/records.ts +914 -12
  23. package/src/validate.ts +2 -0
  24. package/templates/AGENTS.md +30 -0
  25. package/templates/process/controls.yml +10 -6
  26. package/templates/process/diagrams/01-intake-triage.mmd +5 -5
  27. package/templates/process/diagrams/02-product-definition.mmd +1 -1
  28. package/templates/process/diagrams/06-change-release-control.mmd +5 -7
  29. package/templates/process/diagrams/07-deployment-operations.mmd +2 -2
  30. package/templates/process/diagrams/08-support-incident-management.mmd +5 -4
  31. package/templates/process/diagrams/09-problem-improvement.mmd +4 -3
  32. package/templates/process/diagrams/10-risk-control-management.mmd +6 -4
  33. package/templates/process/diagrams/11-audit-evidence-capture.mmd +1 -1
  34. package/templates/process/taxonomy.yml +91 -17
  35. package/templates/process/workflows.yml +10 -9
  36. package/website/flow.html +1 -0
  37. package/website/glossary.html +37 -8
  38. package/website/index.html +2 -1
  39. package/website/objects.html +68 -11
  40. package/website/operating-guide.html +165 -0
  41. package/website/outcomes.html +1 -0
  42. package/website/phase-build.html +1 -0
  43. package/website/phase-elicit.html +1 -0
  44. package/website/phase-go-live.html +2 -1
  45. package/website/phase-measure.html +1 -0
  46. package/website/phase-operate.html +1 -0
  47. package/website/phase-orient.html +1 -0
  48. package/website/phase-weigh.html +1 -0
  49. package/website/roles.html +1 -0
@@ -25,7 +25,7 @@ import { loadWorkspaceConfig } from "../dist/runtime/workspace.js";
25
25
 
26
26
  const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
27
27
  const runId = `smoke-http-${new Date().toISOString().replace(/[:.]/g, "-")}-${randomUUID().slice(0, 8)}`;
28
- const expectedHostedToolCount = 49;
28
+ const expectedHostedToolCount = 60;
29
29
  const smokeTimeoutMs = 60_000;
30
30
  const smokeCleanupActorId = "dxcomplete-http-smoke";
31
31
  const smokeStartedAt = Date.now();
@@ -44,6 +44,11 @@ const smokeRecordTypes = [
44
44
  "commitments",
45
45
  "deferrals",
46
46
  "changes",
47
+ "incidents",
48
+ "problems",
49
+ "maintenance_schedules",
50
+ "support_requests",
51
+ "value_realizations",
47
52
  "decisions",
48
53
  "risks"
49
54
  ];
@@ -69,6 +74,11 @@ let createdBenefitsId;
69
74
  let createdCommitmentId;
70
75
  let createdDeferralId;
71
76
  let createdChangeId;
77
+ let createdChangeEnvironmentId;
78
+ let createdChangeComponentId;
79
+ let createdIncidentId;
80
+ let createdProblemId;
81
+ let createdRiskId;
72
82
  let createdDecisionId;
73
83
  let createdServiceClientId;
74
84
 
@@ -346,6 +356,16 @@ try {
346
356
  assert(status.server.tools.includes("append_deferral_event"), "Hosted surface is missing append_deferral_event.");
347
357
  assert(status.server.tools.includes("create_change"), "Hosted surface is missing create_change.");
348
358
  assert(status.server.tools.includes("append_change_event"), "Hosted surface is missing append_change_event.");
359
+ for (const itsmToolName of [
360
+ "create_risk",
361
+ "append_risk_entry",
362
+ "create_incident",
363
+ "append_incident_entry",
364
+ "create_problem",
365
+ "append_problem_entry"
366
+ ]) {
367
+ assert(status.server.tools.includes(itsmToolName), `Hosted surface is missing ${itsmToolName}.`);
368
+ }
349
369
  assert(status.server.tools.includes("link_decision_input"), "Hosted surface is missing link_decision_input.");
350
370
  assert(status.server.tools.includes("unlink_records"), "Hosted surface is missing unlink_records.");
351
371
  assert(status.server.tools.includes("append_decision_entry"), "Hosted surface is missing append_decision_entry.");
@@ -605,6 +625,11 @@ try {
605
625
  processGuide.runtimeRecordTypes?.includes("components"),
606
626
  "environments and components should appear as current runtime record types."
607
627
  );
628
+ assert(
629
+ processGuide.runtimeRecordTypes?.includes("incidents") &&
630
+ processGuide.runtimeRecordTypes?.includes("problems"),
631
+ "incidents and problems should appear as current runtime record types."
632
+ );
608
633
  for (const removedRuntimeRecordType of [
609
634
  "service_charters",
610
635
  "business_cases",
@@ -644,6 +669,20 @@ try {
644
669
  processGuideText.includes("component"),
645
670
  "Hosted process guide should describe Operational Registry routing."
646
671
  );
672
+ const roleOperatingGuidanceText = JSON.stringify(processGuide.roleOperatingGuidance ?? []);
673
+ assert(
674
+ roleOperatingGuidanceText.includes("Default to Requirement -> Task") &&
675
+ roleOperatingGuidanceText.includes("Use Change for discrete run-side alterations") &&
676
+ roleOperatingGuidanceText.includes("DX Complete Ticket"),
677
+ "Hosted process guide should include compact role operating guidance."
678
+ );
679
+ const itsmGuidanceText = JSON.stringify(processGuide.itsmGuidance ?? []);
680
+ assert(
681
+ itsmGuidanceText.includes("Incident is the current first-class record") &&
682
+ itsmGuidanceText.includes("Problem is the current first-class record") &&
683
+ itsmGuidanceText.includes("Do not create ITSM-style records merely because work is happening"),
684
+ "Hosted process guide should describe current Incident/Problem records with restraint guidance."
685
+ );
647
686
 
648
687
  const rolesDoc = await callJsonTool("get_doc", { page: "roles" });
649
688
  assert(rolesDoc.surfaceVersion === status.server.surfaceVersion, "Hosted get_doc surface version did not match runtime_status.");
@@ -673,6 +712,21 @@ try {
673
712
  for (const oldRoleName of ["Director", "Product Lead", "Product", "Engineering", "Coder", "QA", "Operations", "Support", "Admin", "Client/User"]) {
674
713
  assert(!actualRoleNames.includes(oldRoleName), `Hosted get_doc roles should not include old role ${oldRoleName}.`);
675
714
  }
715
+ const operatingGuideDoc = await callJsonTool("get_doc", { page: "operating_guide" });
716
+ assert(
717
+ operatingGuideDoc.surfaceFingerprint === status.server.surfaceFingerprint,
718
+ "Hosted get_doc operating guide fingerprint did not match runtime_status."
719
+ );
720
+ const operatingGuideText = JSON.stringify(operatingGuideDoc);
721
+ assert(
722
+ operatingGuideDoc.roles?.some(
723
+ (role) => role.name === "Engineer and Codex assistance" && role.use.includes("Requirement to Task")
724
+ ) &&
725
+ operatingGuideDoc.roles?.some((role) => role.name === "Operator and administration" && role.defaultRecords.includes("Incident") && role.defaultRecords.includes("Problem")) &&
726
+ operatingGuideText.includes("specific service-impacting") &&
727
+ operatingGuideText.includes("DX Complete Ticket"),
728
+ "Hosted get_doc operating guide should include role routing, Change boundary, tickets, and Incident/Problem guidance."
729
+ );
676
730
 
677
731
  const glossaryDoc = await callJsonTool("get_doc", { page: "glossary" });
678
732
  assert(
@@ -1757,9 +1811,71 @@ try {
1757
1811
  "append_deferral_event"
1758
1812
  );
1759
1813
 
1814
+ const risk = await callJsonTool("create_risk", {
1815
+ title: `DX Complete hosted MCP smoke risk ${runId}`,
1816
+ topic: "Smoke uncertainty.",
1817
+ likelihood: "low",
1818
+ impact: "low",
1819
+ treatment: "mitigate",
1820
+ treatmentRationale: "Smoke risk is mitigated by cleanup."
1821
+ });
1822
+ createdRiskId = risk._id;
1823
+ assert(risk.recordType === "risks", "Hosted create_risk created the wrong record type.");
1824
+ assert(risk.readableId?.startsWith("RSK-"), "Hosted Risk did not receive an RSK readable ID.");
1825
+ assert(risk.fields.currentStatus?.status === "open", "Hosted Risk did not derive current status.");
1826
+ assert(risk.fields.currentAssessment?.likelihood === "low", "Hosted Risk did not derive current assessment.");
1827
+ assert(risk.fields.currentTreatment?.treatment === "mitigate", "Hosted Risk did not derive current treatment.");
1828
+ const reassessedRisk = await callJsonTool("append_risk_entry", {
1829
+ riskId: risk._id,
1830
+ entryType: "assessment",
1831
+ body: "Smoke reassessment.",
1832
+ likelihood: "medium",
1833
+ impact: "high"
1834
+ });
1835
+ assert(reassessedRisk.fields.currentAssessment?.impact === "high", "Hosted Risk assessment did not derive from latest assessment entry.");
1836
+ const closedRisk = await callJsonTool("append_risk_entry", {
1837
+ riskId: risk._id,
1838
+ entryType: "closed",
1839
+ body: "Smoke risk closed."
1840
+ });
1841
+ assert(closedRisk.fields.currentStatus?.status === "closed", "Hosted Risk status did not derive from closed entry.");
1842
+ await expectToolError(
1843
+ "create_record",
1844
+ {
1845
+ recordType: "risks",
1846
+ title: `DX Complete hosted MCP direct risk ${runId}`,
1847
+ fields: { entries: [] }
1848
+ },
1849
+ "append_risk_entry"
1850
+ );
1851
+ await expectToolError(
1852
+ "update_record",
1853
+ {
1854
+ recordType: "risks",
1855
+ id: risk._id,
1856
+ unsetFields: ["currentStatus"]
1857
+ },
1858
+ "append_risk_entry"
1859
+ );
1860
+
1861
+ const changeEnvironment = await callJsonTool("create_environment", {
1862
+ name: `DX Complete hosted MCP smoke change environment ${runId}`,
1863
+ summary: "Change smoke environment."
1864
+ });
1865
+ createdChangeEnvironmentId = changeEnvironment._id;
1866
+ const changeComponent = await callJsonTool("create_component", {
1867
+ name: `DX Complete hosted MCP smoke change component ${runId}`,
1868
+ environmentId: changeEnvironment._id,
1869
+ kind: "smoke component",
1870
+ locator: { route: "/smoke-change" }
1871
+ });
1872
+ createdChangeComponentId = changeComponent._id;
1873
+
1760
1874
  const change = await callJsonTool("create_change", {
1761
1875
  title: `DX Complete hosted MCP smoke change ${runId}`,
1762
1876
  summary: "Change used by the hosted MCP smoke test.",
1877
+ changeType: "emergency",
1878
+ emergencyImportance: "Smoke change importance is only test coverage.",
1763
1879
  changePlan: "Record a harmless smoke-test service change.",
1764
1880
  executionSteps: ["Create the Change record.", "Append the smoke-test events."],
1765
1881
  rollbackPlan: "Archive the smoke-test records during cleanup.",
@@ -1774,6 +1890,8 @@ try {
1774
1890
  assert(Array.isArray(change.fields.executionSteps) && change.fields.executionSteps.length === 2, "Hosted Change did not store executionSteps.");
1775
1891
  assert(change.fields.rollbackPlan?.includes("Archive"), "Hosted Change did not store rollbackPlan.");
1776
1892
  assert(change.fields.riskImpact?.includes("No service impact"), "Hosted Change did not store riskImpact.");
1893
+ assert(change.fields.changeType === "emergency", "Hosted Change did not store changeType.");
1894
+ assert(change.fields.emergencyRationaleGaps?.includes("emergencyImmediacy"), "Hosted emergency Change did not expose missing immediacy rationale.");
1777
1895
  assert(
1778
1896
  change.links.some((link) => link.toType === "requirements" && link.toId === requirement._id && link.relationship === "for_requirement"),
1779
1897
  "Hosted create_change did not link to the requirement."
@@ -1802,14 +1920,53 @@ try {
1802
1920
  "Hosted Change veto event did not store the veto role."
1803
1921
  );
1804
1922
 
1923
+ const changeWithResultCommit = await callJsonTool("append_change_event", {
1924
+ changeId: change._id,
1925
+ eventType: "result_reported",
1926
+ result: "completed",
1927
+ summary: "Smoke result with Git reference.",
1928
+ gitCommitReferences: [
1929
+ {
1930
+ commit: "abc1234",
1931
+ repository: "smoke-repo",
1932
+ url: "https://example.test/smoke-repo/commit/abc1234"
1933
+ }
1934
+ ]
1935
+ });
1936
+ const resultEvent = changeWithResultCommit.fields.events?.at(-1);
1937
+ assert(resultEvent?.gitCommitReferences?.[0]?.commit === "abc1234", "Hosted Change result event did not store Git commit reference.");
1938
+ assert(resultEvent?.gitCommitReferences?.[0]?.repository === "smoke-repo", "Hosted Change result event did not store Git repository reference.");
1939
+
1940
+ const changeWithRecoveryCommit = await callJsonTool("append_change_event", {
1941
+ changeId: change._id,
1942
+ eventType: "recovery_recorded",
1943
+ summary: "Smoke recovery with rollback commit reference.",
1944
+ gitCommitReferences: [{ commit: "def5678" }]
1945
+ });
1946
+ assert(
1947
+ changeWithRecoveryCommit.fields.events?.at(-1)?.gitCommitReferences?.[0]?.commit === "def5678",
1948
+ "Hosted Change recovery event did not store Git commit reference."
1949
+ );
1950
+
1805
1951
  await expectToolError(
1806
1952
  "append_change_event",
1807
1953
  {
1808
1954
  changeId: change._id,
1809
1955
  eventType: "emergency_declared",
1810
- importance: "Smoke test importance without immediacy."
1956
+ note: "Emergency is modeled through changeType, not an event."
1957
+ },
1958
+ "Invalid option"
1959
+ );
1960
+
1961
+ await expectToolError(
1962
+ "append_change_event",
1963
+ {
1964
+ changeId: change._id,
1965
+ eventType: "notice_given",
1966
+ notice: "Smoke notice with invalid Git reference.",
1967
+ gitCommitReferences: [{ commit: "abc1234" }]
1811
1968
  },
1812
- "emergency_declared requires immediacy"
1969
+ "gitCommitReferences"
1813
1970
  );
1814
1971
 
1815
1972
  await expectToolError(
@@ -1853,6 +2010,111 @@ try {
1853
2010
  "Hosted list_linked_records did not return the linked requirement for Change."
1854
2011
  );
1855
2012
 
2013
+ const incident = await callJsonTool("create_incident", {
2014
+ title: `DX Complete hosted MCP smoke incident ${runId}`,
2015
+ description: "Smoke Incident for Change linkage.",
2016
+ severity: "low",
2017
+ componentIds: [changeComponent._id]
2018
+ });
2019
+ createdIncidentId = incident._id;
2020
+ assert(incident.recordType === "incidents", "Hosted create_incident created the wrong record type.");
2021
+ assert(incident.readableId?.startsWith("INC-"), "Hosted Incident did not receive an INC readable ID.");
2022
+ assert(incident.fields.currentStatus?.status === "open", "Hosted Incident did not derive current status.");
2023
+ assert(incident.fields.currentSeverity?.severity === "low", "Hosted Incident did not derive current severity.");
2024
+ assert(
2025
+ incident.links.some((link) => link.toType === "components" && link.toId === changeComponent._id && link.relationship === "affects"),
2026
+ "Hosted Incident did not link affected Component."
2027
+ );
2028
+
2029
+ const resolvedIncident = await callJsonTool("append_incident_entry", {
2030
+ incidentId: incident._id,
2031
+ entryType: "resolved",
2032
+ body: "Smoke Incident resolved."
2033
+ });
2034
+ assert(resolvedIncident.fields.currentStatus?.status === "resolved", "Hosted Incident status did not derive from resolved entry.");
2035
+ await expectToolError(
2036
+ "create_record",
2037
+ {
2038
+ recordType: "incidents",
2039
+ title: `DX Complete hosted MCP direct incident ${runId}`,
2040
+ fields: { entries: [] }
2041
+ },
2042
+ "append_incident_entry"
2043
+ );
2044
+
2045
+ const problem = await callJsonTool("create_problem", {
2046
+ title: `DX Complete hosted MCP smoke problem ${runId}`,
2047
+ description: "Smoke Problem evidenced by the smoke Incident.",
2048
+ incidentIds: [incident._id],
2049
+ componentIds: [changeComponent._id]
2050
+ });
2051
+ createdProblemId = problem._id;
2052
+ assert(problem.recordType === "problems", "Hosted create_problem created the wrong record type.");
2053
+ assert(problem.readableId?.startsWith("PRB-"), "Hosted Problem did not receive a PRB readable ID.");
2054
+ assert(problem.fields.currentStatus?.status === "open", "Hosted Problem did not derive current status.");
2055
+ assert(
2056
+ problem.links.some((link) => link.toType === "incidents" && link.toId === incident._id && link.relationship === "evidenced_by"),
2057
+ "Hosted Problem did not link to the evidencing Incident."
2058
+ );
2059
+
2060
+ const problemWithRootCause = await callJsonTool("append_problem_entry", {
2061
+ problemId: problem._id,
2062
+ entryType: "root_cause",
2063
+ body: "Smoke root cause.",
2064
+ rootCause: "Smoke root cause."
2065
+ });
2066
+ assert(problemWithRootCause.fields.currentRootCause?.rootCause === "Smoke root cause.", "Hosted Problem root cause did not derive from entry.");
2067
+ await expectToolError(
2068
+ "update_record",
2069
+ {
2070
+ recordType: "problems",
2071
+ id: problem._id,
2072
+ unsetFields: ["entries"]
2073
+ },
2074
+ "append_problem_entry"
2075
+ );
2076
+
2077
+ await callJsonTool("link_records", {
2078
+ fromType: "changes",
2079
+ fromId: change._id,
2080
+ toType: "components",
2081
+ toId: changeComponent._id,
2082
+ relationship: "affects"
2083
+ });
2084
+ await callJsonTool("link_records", {
2085
+ fromType: "changes",
2086
+ fromId: change._id,
2087
+ toType: "incidents",
2088
+ toId: incident._id,
2089
+ relationship: "caused"
2090
+ });
2091
+ await callJsonTool("link_records", {
2092
+ fromType: "changes",
2093
+ fromId: change._id,
2094
+ toType: "incidents",
2095
+ toId: incident._id,
2096
+ relationship: "resolved"
2097
+ });
2098
+ const changeComponentLinks = await callJsonTool("list_linked_records", {
2099
+ recordType: "changes",
2100
+ id: change._id,
2101
+ relationship: "affects"
2102
+ });
2103
+ assert(
2104
+ changeComponentLinks.outbound.some((entry) => entry.record._id === changeComponent._id),
2105
+ "Hosted Change did not link to affected Component."
2106
+ );
2107
+ const changeIncidentLinks = await callJsonTool("list_linked_records", {
2108
+ recordType: "incidents",
2109
+ id: incident._id,
2110
+ direction: "inbound",
2111
+ relationship: "resolved"
2112
+ });
2113
+ assert(
2114
+ changeIncidentLinks.inbound.some((entry) => entry.record._id === change._id),
2115
+ "Hosted inbound traversal did not find Change resolved Incident link."
2116
+ );
2117
+
1856
2118
  const referencedRequirement = await callJsonTool("link_records", {
1857
2119
  fromType: "requirements",
1858
2120
  fromId: requirement._id,
@@ -2243,7 +2505,12 @@ try {
2243
2505
  await archiveCreatedRecord("requirements", createdRequirementId);
2244
2506
  await archiveCreatedRecord("estimates", createdEstimateId);
2245
2507
  await archiveCreatedRecord("benefits", createdBenefitsId);
2508
+ await archiveCreatedRecord("problems", createdProblemId);
2509
+ await archiveCreatedRecord("incidents", createdIncidentId);
2246
2510
  await archiveCreatedRecord("changes", createdChangeId);
2511
+ await archiveCreatedRecord("components", createdChangeComponentId);
2512
+ await archiveCreatedRecord("environments", createdChangeEnvironmentId);
2513
+ await archiveCreatedRecord("risks", createdRiskId);
2247
2514
  await archiveCreatedRecord("commitments", createdCommitmentId);
2248
2515
  await archiveCreatedRecord("deferrals", createdDeferralId);
2249
2516
  await archiveCreatedRecord("expectations", createdExpectationId);
@@ -2568,6 +2835,12 @@ async function runSurfaceSection({ mcpUrl, actor }) {
2568
2835
  "create_estimate",
2569
2836
  "create_benefits",
2570
2837
  "create_change",
2838
+ "create_incident",
2839
+ "append_incident_entry",
2840
+ "create_problem",
2841
+ "append_problem_entry",
2842
+ "create_risk",
2843
+ "append_risk_entry",
2571
2844
  "create_decision",
2572
2845
  "append_decision_entry",
2573
2846
  "link_decision_input",
@@ -2598,7 +2871,7 @@ async function runSurfaceSection({ mcpUrl, actor }) {
2598
2871
  assert(!status.server.tools.includes(removedToolName), `Hosted runtime should not expose removed tool ${removedToolName}.`);
2599
2872
  }
2600
2873
  assert(manifestByName.get("get_doc")?.inputFields.join(",") === "page,term", "Hosted get_doc should expose page and optional term inputs.");
2601
- for (const toolName of ["get_doc", "create_statement", "update_statement", "create_expectation", "create_task", "append_task_entry", "create_environment", "update_environment", "list_environments", "create_component", "update_component", "list_components", "create_estimate", "create_benefits", "create_change", "create_decision", "append_decision_entry", "link_decision_input", "append_journal_note", "read_journal", "get_journal_entry", "append_journal_summary"]) {
2874
+ for (const toolName of ["get_doc", "create_statement", "update_statement", "create_expectation", "create_task", "append_task_entry", "create_environment", "update_environment", "list_environments", "create_component", "update_component", "list_components", "create_estimate", "create_benefits", "create_change", "create_incident", "append_incident_entry", "create_problem", "append_problem_entry", "create_risk", "append_risk_entry", "create_decision", "append_decision_entry", "link_decision_input", "append_journal_note", "read_journal", "get_journal_entry", "append_journal_summary"]) {
2602
2875
  assert(!manifestByName.get(toolName)?.inputFields.includes("workspaceId"), `${toolName} should not expose workspaceId on the hosted surface.`);
2603
2876
  }
2604
2877
 
@@ -2668,12 +2941,31 @@ async function runDocsSection() {
2668
2941
  processGuide.recordRoutingGuidance?.sharpTest?.includes("Will anything reference or depend on this"),
2669
2942
  "Process guide should include compact record-routing guidance."
2670
2943
  );
2944
+ const roleOperatingGuidanceText = JSON.stringify(processGuide.roleOperatingGuidance ?? []);
2945
+ assert(
2946
+ roleOperatingGuidanceText.includes("Default to Requirement -> Task") &&
2947
+ roleOperatingGuidanceText.includes("Use Change for discrete run-side alterations") &&
2948
+ roleOperatingGuidanceText.includes("DX Complete Ticket"),
2949
+ "Process guide should include compact role operating guidance."
2950
+ );
2951
+ const itsmGuidanceText = JSON.stringify(processGuide.itsmGuidance ?? []);
2952
+ assert(
2953
+ itsmGuidanceText.includes("Incident is the current first-class record") &&
2954
+ itsmGuidanceText.includes("Problem is the current first-class record") &&
2955
+ itsmGuidanceText.includes("Do not create ITSM-style records merely because work is happening"),
2956
+ "Process guide should describe current Incident/Problem records with restraint guidance."
2957
+ );
2671
2958
  assert(processGuide.phases.map((phase) => phase.id).join(",") === "orient,elicit,weigh,build,go_live,operate,measure", "Process guide returned an unexpected phase order.");
2672
2959
  assert(processGuide.runtimeRecordTypes?.includes("statements"), "Process guide should include statements.");
2673
2960
  assert(processGuide.runtimeRecordTypes?.includes("journal_entries"), "Process guide should include journal_entries.");
2674
2961
  assert(processGuide.runtimeRecordTypes?.includes("environments"), "Process guide should include environments.");
2675
2962
  assert(processGuide.runtimeRecordTypes?.includes("components"), "Process guide should include components.");
2963
+ assert(processGuide.runtimeRecordTypes?.includes("maintenance_schedules"), "Process guide should include maintenance_schedules.");
2676
2964
  assert(processGuide.runtimeRecordTypes?.includes("benefits"), "Process guide should include benefits.");
2965
+ assert(processGuide.runtimeRecordTypes?.includes("value_realizations"), "Process guide should include value_realizations.");
2966
+ assert(processGuide.runtimeRecordTypes?.includes("incidents"), "Process guide should include incidents.");
2967
+ assert(processGuide.runtimeRecordTypes?.includes("problems"), "Process guide should include problems.");
2968
+ assert(processGuide.runtimeRecordTypes?.includes("support_requests"), "Process guide should include support_requests.");
2677
2969
  assert(!processGuide.runtimeRecordTypes?.includes("initiatives"), "Process guide should not include initiatives.");
2678
2970
  for (const removedRuntimeRecordType of ["service_charters", "cost_baselines", "cost_actuals", "benefit_measurements"]) {
2679
2971
  assert(!processGuide.runtimeRecordTypes?.includes(removedRuntimeRecordType), `Process guide should not include ${removedRuntimeRecordType}.`);
@@ -2682,6 +2974,19 @@ async function runDocsSection() {
2682
2974
  const rolesDoc = await callJsonTool("get_doc", { page: "roles" });
2683
2975
  assert(rolesDoc.surfaceFingerprint === status.server.surfaceFingerprint, "Roles doc fingerprint did not match runtime_status.");
2684
2976
  assert(rolesDoc.roles?.map((role) => role.name).join(",") === "Owner,Engineer,Tester,Operator,Support Agent,End User", "Roles doc did not return the locked six-role model.");
2977
+ const operatingGuideDoc = await callJsonTool("get_doc", { page: "operating_guide" });
2978
+ assert(operatingGuideDoc.surfaceFingerprint === status.server.surfaceFingerprint, "Operating guide doc fingerprint did not match runtime_status.");
2979
+ const operatingGuideText = JSON.stringify(operatingGuideDoc);
2980
+ assert(
2981
+ operatingGuideDoc.roles?.some(
2982
+ (role) => role.name === "Engineer and Codex assistance" && role.use.includes("Requirement to Task")
2983
+ ) &&
2984
+ operatingGuideDoc.roles?.some((role) => role.name === "Operator and administration" && role.defaultRecords.includes("Incident") && role.defaultRecords.includes("Problem") && role.defaultRecords.includes("Maintenance Schedule")) &&
2985
+ operatingGuideDoc.roles?.some((role) => role.name === "Support Agent" && role.defaultRecords.includes("Support Request")) &&
2986
+ operatingGuideText.includes("specific service-impacting") &&
2987
+ operatingGuideText.includes("DX Complete Ticket"),
2988
+ "Operating guide doc should include role routing, Change boundary, tickets, and Incident/Problem guidance."
2989
+ );
2685
2990
 
2686
2991
  const recordsDoc = await callJsonTool("get_doc", { page: "records" });
2687
2992
  const recordNames = recordsDoc.groups?.flatMap((group) => group.records.map((record) => record.name)) ?? [];
@@ -2690,9 +2995,14 @@ async function runDocsSection() {
2690
2995
  recordNames.includes("Journal") &&
2691
2996
  recordNames.includes("Environment") &&
2692
2997
  recordNames.includes("Component") &&
2998
+ recordNames.includes("Maintenance Schedule") &&
2693
2999
  recordNames.includes("Estimate") &&
2694
- recordNames.includes("Benefits"),
2695
- "Records doc should include Statement, Journal, Environment, Component, Estimate, and Benefits."
3000
+ recordNames.includes("Benefits") &&
3001
+ recordNames.includes("Value Realization") &&
3002
+ recordNames.includes("Incident") &&
3003
+ recordNames.includes("Problem") &&
3004
+ recordNames.includes("Support Request"),
3005
+ "Records doc should include Statement, Journal, Environment, Component, Maintenance Schedule, Estimate, Benefits, Value Realization, Incident, Problem, and Support Request."
2696
3006
  );
2697
3007
  assert(
2698
3008
  recordsDoc.routingGuidance?.sharpTest?.includes("Will anything reference or depend on this") &&
@@ -2719,8 +3029,26 @@ async function runDocsSection() {
2719
3029
  registryDoc.term?.definition.includes("not monitoring"),
2720
3030
  "Operational Registry glossary term should describe inventory-only boundaries."
2721
3031
  );
3032
+ const incidentDoc = await callJsonTool("get_doc", { page: "glossary", term: "incident" });
3033
+ assert(
3034
+ incidentDoc.term?.definition.includes("specific service-impacting") &&
3035
+ incidentDoc.term?.definition.includes("ordered entries"),
3036
+ "Incident glossary term should describe the current Incident ledger record."
3037
+ );
3038
+ const problemDoc = await callJsonTool("get_doc", { page: "glossary", term: "problem" });
3039
+ assert(
3040
+ problemDoc.term?.definition.includes("underlying or recurring cause") &&
3041
+ problemDoc.term?.definition.includes("Incidents"),
3042
+ "Problem glossary term should describe the current Problem ledger record."
3043
+ );
2722
3044
  const componentDoc = await callJsonTool("get_doc", { page: "glossary", term: "component" });
2723
3045
  assert(componentDoc.term?.definition.includes("one Environment"), "Component glossary term should describe one-environment scope.");
3046
+ const maintenanceDoc = await callJsonTool("get_doc", { page: "glossary", term: "maintenance schedule" });
3047
+ assert(maintenanceDoc.term?.definition.includes("cadence"), "Maintenance Schedule glossary term should describe cadence.");
3048
+ const supportRequestDoc = await callJsonTool("get_doc", { page: "glossary", term: "support request" });
3049
+ assert(supportRequestDoc.term?.definition.includes("workspace-visible follow-up"), "Support Request glossary term should describe shared follow-up.");
3050
+ const valueRealizationDoc = await callJsonTool("get_doc", { page: "glossary", term: "value realization" });
3051
+ assert(valueRealizationDoc.term?.definition.includes("baseline") && valueRealizationDoc.term?.definition.includes("actual"), "Value Realization glossary term should describe baseline and actual metrics.");
2724
3052
  const secretPointerDoc = await callJsonTool("get_doc", { page: "glossary", term: "secret pointer" });
2725
3053
  assert(secretPointerDoc.term?.definition.includes("should not contain the secret value"), "Secret Pointer glossary term should warn against secret values.");
2726
3054
  await expectToolError("get_doc", { page: "glossary", term: "voice" }, "Glossary term not found");
@@ -2841,6 +3169,50 @@ async function runRecordsSection(cleanupRecords) {
2841
3169
  body: "Invalid task comment.",
2842
3170
  status: "done"
2843
3171
  }, "status is only valid");
3172
+ const maintenanceSchedule = await callJsonTool("create_maintenance_schedule", {
3173
+ name: `DX Complete hosted MCP smoke maintenance ${runId}`,
3174
+ kind: "temporary smoke maintenance",
3175
+ cadence: { count: 1, unit: "day" },
3176
+ startDate: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000).toISOString(),
3177
+ rationale: "Exercise recurring operational hygiene tracking."
3178
+ });
3179
+ cleanupRecords.push(["maintenance_schedules", maintenanceSchedule._id]);
3180
+ assert(maintenanceSchedule.recordType === "maintenance_schedules", "Records smoke Maintenance Schedule had the wrong record type.");
3181
+ assert(/^MNT-\d{4,}$/.test(maintenanceSchedule.readableId ?? ""), "Maintenance Schedule did not receive an MNT readable ID.");
3182
+ assert(maintenanceSchedule.derived?.overdue === true, "Maintenance Schedule should be overdue before a linked completion.");
3183
+ await callJsonTool("link_records", {
3184
+ fromType: "tasks",
3185
+ fromId: task._id,
3186
+ toType: "maintenance_schedules",
3187
+ toId: maintenanceSchedule._id,
3188
+ relationship: "assigned_to"
3189
+ });
3190
+ await callJsonTool("append_task_entry", {
3191
+ taskId: task._id,
3192
+ entryType: "status_change",
3193
+ body: "Maintenance task completed.",
3194
+ status: "done"
3195
+ });
3196
+ const maintenanceAfterCompletion = await callJsonTool("get_record", {
3197
+ recordType: "maintenance_schedules",
3198
+ id: maintenanceSchedule._id
3199
+ });
3200
+ assert(
3201
+ maintenanceAfterCompletion.derived?.occurrenceCount >= 1 &&
3202
+ maintenanceAfterCompletion.derived?.latestOccurrence?.recordType === "tasks",
3203
+ "Maintenance Schedule did not derive occurrence state from a linked completed Task."
3204
+ );
3205
+ const updatedMaintenanceSchedule = await callJsonTool("update_maintenance_schedule", {
3206
+ id: maintenanceSchedule._id,
3207
+ notes: "Updated selected records smoke maintenance schedule.",
3208
+ revisionNote: "Selected records smoke maintenance revision."
3209
+ });
3210
+ assert(updatedMaintenanceSchedule.fields.versionHistory?.length === 1, "Maintenance Schedule update did not preserve version history.");
3211
+ await expectToolError("update_record", {
3212
+ recordType: "maintenance_schedules",
3213
+ id: maintenanceSchedule._id,
3214
+ fields: { cadence: { count: 1, unit: "week" } }
3215
+ }, "update_maintenance_schedule");
2844
3216
  await expectToolError("update_record", {
2845
3217
  recordType: "tasks",
2846
3218
  id: task._id,
@@ -3016,6 +3388,38 @@ async function runRecordsSection(cleanupRecords) {
3016
3388
  id: component._id,
3017
3389
  unsetFields: ["versionHistory"]
3018
3390
  }, "versionHistory");
3391
+ const supportRequest = await callJsonTool("create_support_request", {
3392
+ title: `DX Complete hosted MCP smoke support request ${runId}`,
3393
+ reporter: "Smoke End User",
3394
+ kind: "question",
3395
+ reportedExperience: "The smoke user asked for help with a temporary workflow."
3396
+ });
3397
+ cleanupRecords.push(["support_requests", supportRequest._id]);
3398
+ assert(supportRequest.recordType === "support_requests", "Records smoke Support Request had the wrong record type.");
3399
+ assert(/^SUP-\d{4,}$/.test(supportRequest.readableId ?? ""), "Support Request did not receive an SUP readable ID.");
3400
+ assert(supportRequest.fields.currentStatus?.status === "open", "Support Request did not derive initial open status.");
3401
+ const triagedSupportRequest = await callJsonTool("append_support_request_entry", {
3402
+ supportRequestId: supportRequest._id,
3403
+ entryType: "triage",
3404
+ body: "Support request triaged."
3405
+ });
3406
+ assert(triagedSupportRequest.fields.currentStatus?.status === "triaged", "Support Request triage did not derive current status.");
3407
+ const resolvedSupportRequest = await callJsonTool("append_support_request_entry", {
3408
+ supportRequestId: supportRequest._id,
3409
+ entryType: "resolved",
3410
+ body: "Support request resolved."
3411
+ });
3412
+ assert(resolvedSupportRequest.fields.currentStatus?.status === "resolved", "Support Request resolution did not derive current status.");
3413
+ await expectToolError("create_record", {
3414
+ recordType: "support_requests",
3415
+ title: `DX Complete hosted MCP direct support request entries ${runId}`,
3416
+ fields: { entries: [] }
3417
+ }, "append_support_request_entry");
3418
+ await expectToolError("append_support_request_entry", {
3419
+ supportRequestId: supportRequest._id,
3420
+ entryType: "escalated",
3421
+ body: "Missing incident."
3422
+ }, "incidentId");
3019
3423
  await assertReadableIdsContiguous();
3020
3424
  await expectToolError("update_record", {
3021
3425
  recordType: "requirements",
@@ -3085,6 +3489,54 @@ async function runWeighSection(cleanupRecords) {
3085
3489
  relationship: "informed_by"
3086
3490
  });
3087
3491
  assert(commitmentWithBenefits.links.some((link) => link.toType === "benefits" && link.relationship === "informed_by"), "Weigh smoke did not link Commitment to Benefits.");
3492
+ const valueRealization = await callJsonTool("create_value_realization", {
3493
+ title: `DX Complete hosted MCP smoke value realization ${runId}`,
3494
+ requirementIds: [requirement._id],
3495
+ commitmentIds: [commitment._id],
3496
+ metrics: [
3497
+ {
3498
+ name: "Cycle time",
3499
+ unit: "hours",
3500
+ direction: "lower_is_better",
3501
+ baseline: { value: 10, measuredAt: "2026-06-01T00:00:00.000Z" },
3502
+ actual: { value: 7, measuredAt: "2026-06-02T00:00:00.000Z" }
3503
+ },
3504
+ {
3505
+ name: "Satisfaction signal",
3506
+ unit: "score",
3507
+ direction: "higher_is_better",
3508
+ baseline: { value: 3, measuredAt: "2026-06-01T00:00:00.000Z" }
3509
+ }
3510
+ ]
3511
+ });
3512
+ cleanupRecords.push(["value_realizations", valueRealization._id]);
3513
+ assert(valueRealization.recordType === "value_realizations", "Weigh smoke Value Realization had the wrong record type.");
3514
+ assert(/^VAL-\d{4,}$/.test(valueRealization.readableId ?? ""), "Value Realization did not receive a VAL readable ID.");
3515
+ assert(
3516
+ valueRealization.derived?.comparisons?.some((comparison) => comparison.name === "Cycle time" && comparison.outcome === "improved") &&
3517
+ valueRealization.derived?.comparisons?.some((comparison) => comparison.name === "Satisfaction signal" && comparison.status === "open"),
3518
+ "Value Realization did not derive measured/open comparisons."
3519
+ );
3520
+ const updatedValueRealization = await callJsonTool("update_value_realization", {
3521
+ id: valueRealization._id,
3522
+ metrics: [
3523
+ {
3524
+ id: valueRealization.fields.metrics[0].id,
3525
+ name: "Cycle time",
3526
+ unit: "hours",
3527
+ direction: "lower_is_better",
3528
+ baseline: { value: 10, measuredAt: "2026-06-01T00:00:00.000Z" },
3529
+ actual: { value: 6, measuredAt: "2026-06-03T00:00:00.000Z" }
3530
+ }
3531
+ ],
3532
+ revisionNote: "Weigh smoke value realization revision."
3533
+ });
3534
+ assert(updatedValueRealization.fields.versionHistory?.length === 1, "Value Realization update did not preserve version history.");
3535
+ await expectToolError("update_record", {
3536
+ recordType: "value_realizations",
3537
+ id: valueRealization._id,
3538
+ fields: { metrics: [] }
3539
+ }, "update_value_realization");
3088
3540
  }
3089
3541
 
3090
3542
  async function runChangeSection(cleanupRecords) {
@@ -3342,6 +3794,8 @@ async function assertReadableIdsContiguous() {
3342
3794
  deferrals: "DFR",
3343
3795
  decisions: "DEC",
3344
3796
  changes: "CHG",
3797
+ incidents: "INC",
3798
+ problems: "PRB",
3345
3799
  risks: "RSK",
3346
3800
  estimates: "EST",
3347
3801
  benefits: "BFT"