@xemahq/kernel-contracts 0.29.0 → 0.31.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 (221) hide show
  1. package/dist/decision-gate/index.d.ts +3 -0
  2. package/dist/decision-gate/index.d.ts.map +1 -0
  3. package/dist/decision-gate/index.js +19 -0
  4. package/dist/decision-gate/index.js.map +1 -0
  5. package/dist/decision-gate/lib/decision-gate.d.ts +55 -0
  6. package/dist/decision-gate/lib/decision-gate.d.ts.map +1 -0
  7. package/dist/decision-gate/lib/decision-gate.js +56 -0
  8. package/dist/decision-gate/lib/decision-gate.js.map +1 -0
  9. package/dist/decision-gate/lib/pinned-subject-artifact.d.ts +9 -0
  10. package/dist/decision-gate/lib/pinned-subject-artifact.d.ts.map +1 -0
  11. package/dist/decision-gate/lib/pinned-subject-artifact.js +11 -0
  12. package/dist/decision-gate/lib/pinned-subject-artifact.js.map +1 -0
  13. package/dist/document-patch/index.d.ts +4 -0
  14. package/dist/document-patch/index.d.ts.map +1 -0
  15. package/dist/document-patch/index.js +20 -0
  16. package/dist/document-patch/index.js.map +1 -0
  17. package/dist/document-patch/lib/patch-anchor.d.ts +7 -0
  18. package/dist/document-patch/lib/patch-anchor.d.ts.map +1 -0
  19. package/dist/document-patch/lib/patch-anchor.js +14 -0
  20. package/dist/document-patch/lib/patch-anchor.js.map +1 -0
  21. package/dist/document-patch/lib/patch-enums.d.ts +11 -0
  22. package/dist/document-patch/lib/patch-enums.d.ts.map +1 -0
  23. package/dist/document-patch/lib/patch-enums.js +16 -0
  24. package/dist/document-patch/lib/patch-enums.js.map +1 -0
  25. package/dist/document-patch/lib/pending-patch.d.ts +22 -0
  26. package/dist/document-patch/lib/pending-patch.d.ts.map +1 -0
  27. package/dist/document-patch/lib/pending-patch.js +3 -0
  28. package/dist/document-patch/lib/pending-patch.js.map +1 -0
  29. package/dist/output/index.d.ts +7 -0
  30. package/dist/output/index.d.ts.map +1 -0
  31. package/dist/output/index.js +23 -0
  32. package/dist/output/index.js.map +1 -0
  33. package/dist/output/lib/output-deliverable-expectation.d.ts +9 -0
  34. package/dist/output/lib/output-deliverable-expectation.d.ts.map +1 -0
  35. package/dist/output/lib/output-deliverable-expectation.js +3 -0
  36. package/dist/output/lib/output-deliverable-expectation.js.map +1 -0
  37. package/dist/output/lib/output-item-dto.d.ts +25 -0
  38. package/dist/output/lib/output-item-dto.d.ts.map +1 -0
  39. package/dist/output/lib/output-item-dto.js +3 -0
  40. package/dist/output/lib/output-item-dto.js.map +1 -0
  41. package/dist/output/lib/output-kind.d.ts +14 -0
  42. package/dist/output/lib/output-kind.d.ts.map +1 -0
  43. package/dist/output/lib/output-kind.js +18 -0
  44. package/dist/output/lib/output-kind.js.map +1 -0
  45. package/dist/output/lib/output-render-hint.d.ts +11 -0
  46. package/dist/output/lib/output-render-hint.d.ts.map +1 -0
  47. package/dist/output/lib/output-render-hint.js +10 -0
  48. package/dist/output/lib/output-render-hint.js.map +1 -0
  49. package/dist/output/lib/output-version-dto.d.ts +10 -0
  50. package/dist/output/lib/output-version-dto.d.ts.map +1 -0
  51. package/dist/output/lib/output-version-dto.js +3 -0
  52. package/dist/output/lib/output-version-dto.js.map +1 -0
  53. package/dist/output/lib/payload-schemas/code-diff.d.ts +7 -0
  54. package/dist/output/lib/payload-schemas/code-diff.d.ts.map +1 -0
  55. package/dist/output/lib/payload-schemas/code-diff.js +11 -0
  56. package/dist/output/lib/payload-schemas/code-diff.js.map +1 -0
  57. package/dist/output/lib/payload-schemas/decision-record.d.ts +19 -0
  58. package/dist/output/lib/payload-schemas/decision-record.d.ts.map +1 -0
  59. package/dist/output/lib/payload-schemas/decision-record.js +39 -0
  60. package/dist/output/lib/payload-schemas/decision-record.js.map +1 -0
  61. package/dist/output/lib/payload-schemas/diagram.d.ts +12 -0
  62. package/dist/output/lib/payload-schemas/diagram.d.ts.map +1 -0
  63. package/dist/output/lib/payload-schemas/diagram.js +34 -0
  64. package/dist/output/lib/payload-schemas/diagram.js.map +1 -0
  65. package/dist/output/lib/payload-schemas/external-blob.d.ts +9 -0
  66. package/dist/output/lib/payload-schemas/external-blob.d.ts.map +1 -0
  67. package/dist/output/lib/payload-schemas/external-blob.js +15 -0
  68. package/dist/output/lib/payload-schemas/external-blob.js.map +1 -0
  69. package/dist/output/lib/payload-schemas/index.d.ts +17 -0
  70. package/dist/output/lib/payload-schemas/index.d.ts.map +1 -0
  71. package/dist/output/lib/payload-schemas/index.js +94 -0
  72. package/dist/output/lib/payload-schemas/index.js.map +1 -0
  73. package/dist/output/lib/payload-schemas/json-payload.d.ts +4 -0
  74. package/dist/output/lib/payload-schemas/json-payload.d.ts.map +1 -0
  75. package/dist/output/lib/payload-schemas/json-payload.js +9 -0
  76. package/dist/output/lib/payload-schemas/json-payload.js.map +1 -0
  77. package/dist/output/lib/payload-schemas/manifest.d.ts +37 -0
  78. package/dist/output/lib/payload-schemas/manifest.d.ts.map +1 -0
  79. package/dist/output/lib/payload-schemas/manifest.js +43 -0
  80. package/dist/output/lib/payload-schemas/manifest.js.map +1 -0
  81. package/dist/output/lib/payload-schemas/markdown-doc.d.ts +8 -0
  82. package/dist/output/lib/payload-schemas/markdown-doc.d.ts.map +1 -0
  83. package/dist/output/lib/payload-schemas/markdown-doc.js +12 -0
  84. package/dist/output/lib/payload-schemas/markdown-doc.js.map +1 -0
  85. package/dist/output/lib/payload-schemas/review-findings.d.ts +30 -0
  86. package/dist/output/lib/payload-schemas/review-findings.d.ts.map +1 -0
  87. package/dist/output/lib/payload-schemas/review-findings.js +39 -0
  88. package/dist/output/lib/payload-schemas/review-findings.js.map +1 -0
  89. package/dist/output/lib/payload-schemas/schema.d.ts +4 -0
  90. package/dist/output/lib/payload-schemas/schema.d.ts.map +1 -0
  91. package/dist/output/lib/payload-schemas/schema.js +10 -0
  92. package/dist/output/lib/payload-schemas/schema.js.map +1 -0
  93. package/dist/output/lib/payload-schemas/test-report.d.ts +33 -0
  94. package/dist/output/lib/payload-schemas/test-report.d.ts.map +1 -0
  95. package/dist/output/lib/payload-schemas/test-report.js +46 -0
  96. package/dist/output/lib/payload-schemas/test-report.js.map +1 -0
  97. package/dist/question/index.d.ts +5 -0
  98. package/dist/question/index.d.ts.map +1 -0
  99. package/dist/question/index.js +21 -0
  100. package/dist/question/index.js.map +1 -0
  101. package/dist/question/lib/question-action-context.d.ts +7 -0
  102. package/dist/question/lib/question-action-context.d.ts.map +1 -0
  103. package/dist/question/lib/question-action-context.js +3 -0
  104. package/dist/question/lib/question-action-context.js.map +1 -0
  105. package/dist/question/lib/question-dto.d.ts +54 -0
  106. package/dist/question/lib/question-dto.d.ts.map +1 -0
  107. package/dist/question/lib/question-dto.js +3 -0
  108. package/dist/question/lib/question-dto.js.map +1 -0
  109. package/dist/question/lib/question-enums.d.ts +20 -0
  110. package/dist/question/lib/question-enums.d.ts.map +1 -0
  111. package/dist/question/lib/question-enums.js +32 -0
  112. package/dist/question/lib/question-enums.js.map +1 -0
  113. package/dist/question/lib/question-escalation.d.ts +8 -0
  114. package/dist/question/lib/question-escalation.d.ts.map +1 -0
  115. package/dist/question/lib/question-escalation.js +3 -0
  116. package/dist/question/lib/question-escalation.js.map +1 -0
  117. package/dist/review-inquiry/index.d.ts +3 -0
  118. package/dist/review-inquiry/index.d.ts.map +1 -0
  119. package/dist/review-inquiry/index.js +19 -0
  120. package/dist/review-inquiry/index.js.map +1 -0
  121. package/dist/review-inquiry/lib/review-inquiry.d.ts +138 -0
  122. package/dist/review-inquiry/lib/review-inquiry.d.ts.map +1 -0
  123. package/dist/review-inquiry/lib/review-inquiry.js +20 -0
  124. package/dist/review-inquiry/lib/review-inquiry.js.map +1 -0
  125. package/dist/review-inquiry/lib/web-annotation.d.ts +195 -0
  126. package/dist/review-inquiry/lib/web-annotation.d.ts.map +1 -0
  127. package/dist/review-inquiry/lib/web-annotation.js +59 -0
  128. package/dist/review-inquiry/lib/web-annotation.js.map +1 -0
  129. package/dist/run-detail/index.d.ts +7 -0
  130. package/dist/run-detail/index.d.ts.map +1 -0
  131. package/dist/run-detail/index.js +23 -0
  132. package/dist/run-detail/index.js.map +1 -0
  133. package/dist/run-detail/lib/run-detail-dto.d.ts +34 -0
  134. package/dist/run-detail/lib/run-detail-dto.d.ts.map +1 -0
  135. package/dist/run-detail/lib/run-detail-dto.js +3 -0
  136. package/dist/run-detail/lib/run-detail-dto.js.map +1 -0
  137. package/dist/run-detail/lib/run-mode.d.ts +14 -0
  138. package/dist/run-detail/lib/run-mode.d.ts.map +1 -0
  139. package/dist/run-detail/lib/run-mode.js +25 -0
  140. package/dist/run-detail/lib/run-mode.js.map +1 -0
  141. package/dist/run-detail/lib/run-outcome.d.ts +28 -0
  142. package/dist/run-detail/lib/run-outcome.d.ts.map +1 -0
  143. package/dist/run-detail/lib/run-outcome.js +27 -0
  144. package/dist/run-detail/lib/run-outcome.js.map +1 -0
  145. package/dist/run-detail/lib/run-phase-rail.d.ts +21 -0
  146. package/dist/run-detail/lib/run-phase-rail.d.ts.map +1 -0
  147. package/dist/run-detail/lib/run-phase-rail.js +14 -0
  148. package/dist/run-detail/lib/run-phase-rail.js.map +1 -0
  149. package/dist/run-detail/lib/run-revision-state.d.ts +47 -0
  150. package/dist/run-detail/lib/run-revision-state.d.ts.map +1 -0
  151. package/dist/run-detail/lib/run-revision-state.js +11 -0
  152. package/dist/run-detail/lib/run-revision-state.js.map +1 -0
  153. package/dist/run-detail/lib/wait-windows.d.ts +15 -0
  154. package/dist/run-detail/lib/wait-windows.d.ts.map +1 -0
  155. package/dist/run-detail/lib/wait-windows.js +39 -0
  156. package/dist/run-detail/lib/wait-windows.js.map +1 -0
  157. package/dist/workspace-image/index.d.ts +5 -0
  158. package/dist/workspace-image/index.d.ts.map +1 -0
  159. package/dist/workspace-image/index.js +21 -0
  160. package/dist/workspace-image/index.js.map +1 -0
  161. package/dist/workspace-image/lib/enums.d.ts +17 -0
  162. package/dist/workspace-image/lib/enums.d.ts.map +1 -0
  163. package/dist/workspace-image/lib/enums.js +22 -0
  164. package/dist/workspace-image/lib/enums.js.map +1 -0
  165. package/dist/workspace-image/lib/hash.d.ts +3 -0
  166. package/dist/workspace-image/lib/hash.d.ts.map +1 -0
  167. package/dist/workspace-image/lib/hash.js +50 -0
  168. package/dist/workspace-image/lib/hash.js.map +1 -0
  169. package/dist/workspace-image/lib/schemas.d.ts +103 -0
  170. package/dist/workspace-image/lib/schemas.d.ts.map +1 -0
  171. package/dist/workspace-image/lib/schemas.js +53 -0
  172. package/dist/workspace-image/lib/schemas.js.map +1 -0
  173. package/dist/workspace-image/lib/types.d.ts +50 -0
  174. package/dist/workspace-image/lib/types.d.ts.map +1 -0
  175. package/dist/workspace-image/lib/types.js +3 -0
  176. package/dist/workspace-image/lib/types.js.map +1 -0
  177. package/package.json +1 -1
  178. package/src/decision-gate/index.ts +13 -0
  179. package/src/decision-gate/lib/decision-gate.ts +113 -0
  180. package/src/decision-gate/lib/pinned-subject-artifact.ts +29 -0
  181. package/src/document-patch/index.ts +25 -0
  182. package/src/document-patch/lib/patch-anchor.ts +22 -0
  183. package/src/document-patch/lib/patch-enums.ts +36 -0
  184. package/src/document-patch/lib/pending-patch.ts +31 -0
  185. package/src/output/index.ts +20 -0
  186. package/src/output/lib/output-deliverable-expectation.ts +25 -0
  187. package/src/output/lib/output-item-dto.ts +54 -0
  188. package/src/output/lib/output-kind.ts +44 -0
  189. package/src/output/lib/output-render-hint.ts +28 -0
  190. package/src/output/lib/output-version-dto.ts +21 -0
  191. package/src/output/lib/payload-schemas/code-diff.ts +20 -0
  192. package/src/output/lib/payload-schemas/decision-record.ts +71 -0
  193. package/src/output/lib/payload-schemas/diagram.ts +54 -0
  194. package/src/output/lib/payload-schemas/external-blob.ts +36 -0
  195. package/src/output/lib/payload-schemas/index.ts +133 -0
  196. package/src/output/lib/payload-schemas/json-payload.ts +19 -0
  197. package/src/output/lib/payload-schemas/manifest.ts +81 -0
  198. package/src/output/lib/payload-schemas/markdown-doc.ts +33 -0
  199. package/src/output/lib/payload-schemas/review-findings.ts +75 -0
  200. package/src/output/lib/payload-schemas/schema.ts +22 -0
  201. package/src/output/lib/payload-schemas/test-report.ts +69 -0
  202. package/src/question/index.ts +24 -0
  203. package/src/question/lib/question-action-context.ts +26 -0
  204. package/src/question/lib/question-dto.ts +143 -0
  205. package/src/question/lib/question-enums.ts +67 -0
  206. package/src/question/lib/question-escalation.ts +22 -0
  207. package/src/review-inquiry/index.ts +19 -0
  208. package/src/review-inquiry/lib/review-inquiry.ts +76 -0
  209. package/src/review-inquiry/lib/web-annotation.ts +136 -0
  210. package/src/run-detail/index.ts +22 -0
  211. package/src/run-detail/lib/run-detail-dto.ts +84 -0
  212. package/src/run-detail/lib/run-mode.ts +65 -0
  213. package/src/run-detail/lib/run-outcome.ts +66 -0
  214. package/src/run-detail/lib/run-phase-rail.ts +43 -0
  215. package/src/run-detail/lib/run-revision-state.ts +83 -0
  216. package/src/run-detail/lib/wait-windows.ts +70 -0
  217. package/src/workspace-image/index.ts +13 -0
  218. package/src/workspace-image/lib/enums.ts +38 -0
  219. package/src/workspace-image/lib/hash.ts +95 -0
  220. package/src/workspace-image/lib/schemas.ts +63 -0
  221. package/src/workspace-image/lib/types.ts +79 -0
@@ -0,0 +1,69 @@
1
+ import { z } from 'zod';
2
+
3
+ /**
4
+ * Outcome of a single test in a test-report payload. Closed enum
5
+ * because the FE renders each state with distinctive iconography.
6
+ */
7
+ export enum TestOutcome {
8
+ PASSED = 'passed',
9
+ FAILED = 'failed',
10
+ SKIPPED = 'skipped',
11
+ ERRORED = 'errored',
12
+ }
13
+
14
+ export const TestOutcomeSchema = z.nativeEnum(TestOutcome);
15
+
16
+ export const TestCaseSchema = z
17
+ .object({
18
+ name: z.string().min(1),
19
+ outcome: TestOutcomeSchema,
20
+ /** Duration in milliseconds; producers without timing data omit. */
21
+ durationMs: z.number().nonnegative().optional(),
22
+ /** Failure / error message for FAILED + ERRORED outcomes. */
23
+ message: z.string().optional(),
24
+ /** Optional path to the file under test (e.g. `src/auth.test.ts`). */
25
+ file: z.string().optional(),
26
+ })
27
+ .passthrough();
28
+
29
+ export type TestCase = z.infer<typeof TestCaseSchema>;
30
+
31
+ /**
32
+ * Payload shape for `OutputKind.TEST_REPORT` artifacts.
33
+ *
34
+ * `tests[]` is the per-case detail; the rollup counters
35
+ * (`passed`, `failed`, `skipped`, `errored`) are derived from
36
+ * `tests[]` when present but may be supplied directly when the
37
+ * producer only has aggregate numbers.
38
+ *
39
+ * `body` is the legacy markdown fallback (same pattern as other
40
+ * markdown-bearing kinds).
41
+ */
42
+ export const TestReportPayloadSchema = z
43
+ .object({
44
+ tests: z.array(TestCaseSchema).optional(),
45
+ passed: z.number().nonnegative().int().optional(),
46
+ failed: z.number().nonnegative().int().optional(),
47
+ skipped: z.number().nonnegative().int().optional(),
48
+ errored: z.number().nonnegative().int().optional(),
49
+ summary: z.string().optional(),
50
+ body: z.string().optional(),
51
+ })
52
+ .passthrough()
53
+ .superRefine((value, ctx) => {
54
+ const hasTests = Array.isArray(value.tests) && value.tests.length > 0;
55
+ const hasCounters =
56
+ typeof value.passed === 'number' ||
57
+ typeof value.failed === 'number' ||
58
+ typeof value.skipped === 'number';
59
+ const hasBody = typeof value.body === 'string' && value.body.trim().length > 0;
60
+ if (!hasTests && !hasCounters && !hasBody) {
61
+ ctx.addIssue({
62
+ code: 'custom',
63
+ message:
64
+ 'test_report payload must include at least one of `tests[]`, the rollup counters, or `body`.',
65
+ });
66
+ }
67
+ });
68
+
69
+ export type TestReportPayload = z.infer<typeof TestReportPayloadSchema>;
@@ -0,0 +1,24 @@
1
+ // ═══════════════════════════════════════════════════════════════════════════
2
+ // @xemahq/question-contracts — Layer 1 / Kernel
3
+ //
4
+ // User-facing wire shapes for the unified "Question" surface. The platform
5
+ // internally has three overlapping primitives (decision_gate inquiries,
6
+ // agent_tool_inquiry inquiries, human-recipient review jobs); the FE
7
+ // collapses them into ONE concept the user sees: a "Question".
8
+ //
9
+ // Per the "one concept" model: never say
10
+ // "approval", "review", "decision gate", "inquiry", or "task" in the UI.
11
+ // Internal terms stay in code, schemas, logs, telemetry. The reply form
12
+ // renders by `kind`, not by which backend primitive backs the row.
13
+ //
14
+ // Endpoints powered by this package:
15
+ // GET /questions → QuestionDto[] (paginated envelope)
16
+ // GET /questions/badge → { count: number }
17
+ // GET /questions/:id → QuestionDto (single envelope)
18
+ // POST /questions/:id/reply (body shape lives in user-hub-api DTOs)
19
+ // ═══════════════════════════════════════════════════════════════════════════
20
+
21
+ export * from './lib/question-enums';
22
+ export * from './lib/question-dto';
23
+ export * from './lib/question-action-context';
24
+ export * from './lib/question-escalation';
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Every QuestionDto carries a stable
3
+ * "action context" that the FE's reply form, the bell dropdown's
4
+ * deep-links, and the email channel adapter all read.
5
+ *
6
+ * The contract: `runId` + `taskAnchor` are ALWAYS present. Without them
7
+ * the bell can't deep-link to the run+Work mode and the email link
8
+ * can't restore context — so the engine refuses to materialize a
9
+ * question without both fields. Fail-fast at the boundary, not in the
10
+ * FE.
11
+ */
12
+ export interface QuestionActionContext {
13
+ /** The workflow run this question belongs to. */
14
+ readonly runId: string;
15
+ /** The workflow slug — used to construct the deep-link path. */
16
+ readonly workflowSlug: string;
17
+ /**
18
+ * A stable anchor for scrolling to the question's canvas section.
19
+ * The FE uses this as the URL fragment so refresh restores scroll
20
+ * position. Format: `q-<inquiryId>` or `q-<inquiryId>-r<recipientId>`
21
+ * for per-recipient framing.
22
+ */
23
+ readonly taskAnchor: string;
24
+ /** Optional checkpoint key when the question gates a phase boundary. */
25
+ readonly stageKey: string | null;
26
+ }
@@ -0,0 +1,143 @@
1
+ import { QuestionActionContext } from './question-action-context';
2
+ import { QuestionEscalationLevel } from './question-escalation';
3
+ import { QuestionKind, QuestionSeverity, QuestionStatus } from './question-enums';
4
+
5
+ /**
6
+ * The wire shape powering the global Questions bell, the run-detail
7
+ * Work-mode question canvas, and the question-detail standalone route.
8
+ *
9
+ * One row per (recipient, inquiry) pair. The same inquiry may produce
10
+ * N QuestionDto rows when it has N recipients — each recipient sees
11
+ * THEIR question with their own status. The aggregation policy lives
12
+ * server-side; the FE renders one row at a time.
13
+ *
14
+ * `kind` discriminates which Reply form variant renders. `kindPayload`
15
+ * carries the kind-specific render hints (choice options, default
16
+ * approval reason templates, etc). `subjects` carries the artifacts
17
+ * the question is about so the canvas can render them inline via the
18
+ * existing OutputCard renderer registry — no separate fetch needed
19
+ * beyond the run's Outputs list. `extras` is a closed-set bag for
20
+ * orchestrator-injected hints (review iteration, reviewer findings,
21
+ * past-iteration history) that the canvas renders conditionally.
22
+ */
23
+ export interface QuestionDto {
24
+ readonly id: string;
25
+ readonly kind: QuestionKind;
26
+ readonly status: QuestionStatus;
27
+ readonly severity: QuestionSeverity;
28
+
29
+ /** The user-facing question — one sentence, never JSON. */
30
+ readonly title: string;
31
+ /** Optional detail line shown under the title. */
32
+ readonly detail: string | null;
33
+ /**
34
+ * Long-form context shown above the subjects in the canvas. Markdown.
35
+ * Distinct from `detail` (one-liner under the title) — `description`
36
+ * is the human-readable framing the engine pulled from the inquiry's
37
+ * prompt (e.g. DECISION_GATE.description, agent_tool_inquiry.context).
38
+ */
39
+ readonly description: string | null;
40
+
41
+ /**
42
+ * Engine-side `InquiryRecipient.id` PK for THIS recipient. Required by
43
+ * the FE reply-submit call; populated from the inquiry CloudEvent's
44
+ * `recipients[]` projection during materialization. Empty string for
45
+ * legacy rows that pre-date the wiring.
46
+ */
47
+ readonly recipientId: string;
48
+
49
+ /**
50
+ * Artifact references the question is asking about (the documents
51
+ * being reviewed, the proposed change, etc.). The FE renders each via
52
+ * the existing OutputCard renderer registry by matching `artifactId`
53
+ * against the run's Outputs list. Empty for questions without subject
54
+ * artifacts (e.g. agent_tool_inquiry asking "what should I do next?").
55
+ */
56
+ readonly subjects: readonly QuestionSubjectRef[];
57
+
58
+ /**
59
+ * Closed-set bag of orchestrator-injected render hints. Today the FE
60
+ * recognises:
61
+ * - `reviewIteration` (number)
62
+ * - `reviewerFindings` (array of {severity, message, location?})
63
+ * - `pastIterations` (array of {iteration, verdict, submittedAtIso})
64
+ * - `reviewSubjects` (array of `ReviewableSubject` entries from
65
+ * `narrowReviewableContent` — post-unification this is the
66
+ * closed set `{ kind: 'artifact-ref', … } | { kind: 'string',
67
+ * value }`. The kernel narrower throws fail-fast on any other
68
+ * shape, so consumers can `switch (subject.kind)` exhaustively.
69
+ * Distinct from `subjects[]` above, which carries the strict
70
+ * approval-time artifact pins consumed by `publish-kb`.)
71
+ * Unknown keys are ignored. The engine surfaces these from
72
+ * `prompt.passthrough()` extras authored by orchestrators like
73
+ * `xema/review@v3`.
74
+ */
75
+ readonly extras: Readonly<Record<string, unknown>>;
76
+
77
+ readonly action: QuestionActionContext;
78
+
79
+ /** Empty array when no escalation has fired. */
80
+ readonly escalationHistory: readonly QuestionEscalationLevel[];
81
+
82
+ /** ISO-8601 when the question became visible to the recipient. */
83
+ readonly occurredAt: string;
84
+ /** ISO-8601 deadline; `null` for non-timing-bound questions. */
85
+ readonly expiresAt: string | null;
86
+
87
+ /**
88
+ * Kind-specific render hints. Variant-typed by `kind`. The FE narrows
89
+ * via discriminated union before reading.
90
+ */
91
+ readonly kindPayload: QuestionKindPayload;
92
+ }
93
+
94
+ /**
95
+ * A pinned reference to an artifact this question is about. Mirrors
96
+ * `PinnedSubjectArtifact` from `@xemahq/decision-gate-contracts` —
97
+ * carried through verbatim from the inquiry prompt.
98
+ */
99
+ export interface QuestionSubjectRef {
100
+ readonly artifactId: string;
101
+ readonly version: number;
102
+ /** Engine-resolved kind of the underlying artifact (markdown_doc,
103
+ * code_diff, decision_record, …). Optional — absent when the inquiry
104
+ * was created before the engine started pinning the kind. */
105
+ readonly kind?: string;
106
+ /** Engine-resolved title at pin time. Optional for the same reason. */
107
+ readonly title?: string;
108
+ }
109
+
110
+ /** Discriminated union over `kind`. */
111
+ export type QuestionKindPayload =
112
+ | { readonly kind: QuestionKind.APPROVAL; readonly approval: ApprovalKindPayload }
113
+ | { readonly kind: QuestionKind.CHOICE; readonly choice: ChoiceKindPayload }
114
+ | { readonly kind: QuestionKind.FREEFORM; readonly freeform: FreeformKindPayload };
115
+
116
+ export interface ApprovalKindPayload {
117
+ /** Whether the reply form should expose a "Revise with note" CTA. */
118
+ readonly allowRevise: boolean;
119
+ /**
120
+ * Optional reasons the FE pre-populates in the reject dropdown.
121
+ * Empty array → reason is free-text only.
122
+ */
123
+ readonly suggestedRejectReasons: readonly string[];
124
+ }
125
+
126
+ export interface ChoiceKindPayload {
127
+ readonly options: readonly QuestionChoiceOptionDto[];
128
+ /** True if the recipient can pick more than one option. */
129
+ readonly multiSelect: boolean;
130
+ }
131
+
132
+ export interface QuestionChoiceOptionDto {
133
+ readonly value: string;
134
+ readonly label: string;
135
+ readonly description: string | null;
136
+ }
137
+
138
+ export interface FreeformKindPayload {
139
+ /** Hint text rendered as the textarea placeholder. */
140
+ readonly placeholder: string | null;
141
+ /** Optional cap; FE enforces, server re-validates. */
142
+ readonly maxLength: number | null;
143
+ }
@@ -0,0 +1,67 @@
1
+ /**
2
+ * The three reply-shape families a Question can take. Drives which
3
+ * Reply form the FE renders below the canvas.
4
+ *
5
+ * - `APPROVAL` — Approve / Reject (+ optional reason). Renders the
6
+ * 3-button row used by checkpoint reviews and decision gates.
7
+ * - `CHOICE` — pick one of N predefined options. Renders a radio list.
8
+ * - `FREEFORM` — open-ended textarea. Renders a single textarea +
9
+ * submit. Used for agent_tool_inquiry "what should I do next?".
10
+ *
11
+ * The reply shape is enforced server-side by the inquiry kind's Zod
12
+ * schema (per `@xemahq/inquiry-contracts` `KindSchemaRegistry`); this
13
+ * enum is the FE-facing projection.
14
+ */
15
+ export enum QuestionKind {
16
+ APPROVAL = 'approval',
17
+ CHOICE = 'choice',
18
+ FREEFORM = 'freeform',
19
+ }
20
+
21
+ /**
22
+ * Lifecycle status of a question from the recipient's perspective.
23
+ *
24
+ * - `PENDING` — recipient must reply; counts toward badge.
25
+ * - `RESPONDED` — this recipient replied but the inquiry as a whole
26
+ * hasn't yet aggregated to a verdict. Stays in the list (to show
27
+ * what was answered) but does NOT count toward badge.
28
+ * - `RESOLVED` — terminal, verdict reached. Hidden from the bell.
29
+ * - `EXPIRED` — terminal via timeout (no verdict reached, escalation
30
+ * chain exhausted).
31
+ * - `CANCELLED` — terminal via run cancel or admin override.
32
+ *
33
+ * Maps server-side from inquiry-recipient status + inquiry status.
34
+ * The FE never derives terminality on its own.
35
+ */
36
+ export enum QuestionStatus {
37
+ PENDING = 'pending',
38
+ RESPONDED = 'responded',
39
+ RESOLVED = 'resolved',
40
+ EXPIRED = 'expired',
41
+ CANCELLED = 'cancelled',
42
+ }
43
+
44
+ /** Statuses that are TERMINAL — no further transitions accepted. */
45
+ export const TERMINAL_QUESTION_STATUSES: readonly QuestionStatus[] = [
46
+ QuestionStatus.RESOLVED,
47
+ QuestionStatus.EXPIRED,
48
+ QuestionStatus.CANCELLED,
49
+ ] as const;
50
+
51
+ /** Statuses that count toward the badge in the global Questions header button. */
52
+ export const BADGE_COUNTING_QUESTION_STATUSES: readonly QuestionStatus[] = [
53
+ QuestionStatus.PENDING,
54
+ ] as const;
55
+
56
+ /**
57
+ * Severity of a question — used to sort the bell + decide whether to
58
+ * pop a toast. The engine maps from inquiry policy + escalation level.
59
+ *
60
+ * `URGENT` toasts immediately; `NORMAL` toasts on first arrival only;
61
+ * `INFO` never toasts (badge-only).
62
+ */
63
+ export enum QuestionSeverity {
64
+ URGENT = 'urgent',
65
+ NORMAL = 'normal',
66
+ INFO = 'info',
67
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Escalation breadcrumb shown under the question header in Work mode
3
+ * when escalation has fired at least once.
4
+ *
5
+ * Rendered as a level chain, for example:
6
+ * "Level 1 (Alice) · expired · → Level 2 (Bob)"
7
+ *
8
+ * The engine emits the full level history; the FE renders the chain
9
+ * left-to-right with a strikethrough on expired levels. The CURRENT
10
+ * level (where the recipient sits) is the last entry with `expired:
11
+ * false`.
12
+ */
13
+ export interface QuestionEscalationLevel {
14
+ readonly level: number;
15
+ readonly recipientDisplayName: string;
16
+ /** True when this level timed out and advanced to the next. */
17
+ readonly expired: boolean;
18
+ /** ISO-8601 when the level entered. */
19
+ readonly enteredAt: string;
20
+ /** ISO-8601 when the level expired (only set if `expired === true`). */
21
+ readonly expiredAt: string | null;
22
+ }
@@ -0,0 +1,19 @@
1
+ // ═══════════════════════════════════════════════════════════════════════════
2
+ // @xemahq/review-inquiry-contracts — Kernel layer
3
+ //
4
+ // Abstract types for the REVIEW InquiryKind: a comment-only review surface
5
+ // that pins one or more artifact versions and asks recipients to leave
6
+ // free-form commentary. Distinct from DECISION_GATE — there is no
7
+ // approve/reject verdict and no policy aggregation. Reviewers are heard;
8
+ // the workflow continues regardless of what they say.
9
+ //
10
+ // Compose with DECISION_GATE when both review-and-gate semantics are
11
+ // needed: emit a REVIEW first (collect comments), then a DECISION_GATE
12
+ // over the same `subjectArtifacts` (collect verdicts). The two stay
13
+ // orthogonal at the contract level.
14
+ //
15
+ // Pure types + Zod. Zero runtime adapter knowledge.
16
+ // ═══════════════════════════════════════════════════════════════════════════
17
+
18
+ export * from './lib/review-inquiry';
19
+ export * from './lib/web-annotation';
@@ -0,0 +1,76 @@
1
+ import { z } from 'zod';
2
+
3
+ import { PinnedSubjectArtifactSchema } from '../../decision-gate';
4
+
5
+ import { WebAnnotationTargetSchema } from './web-annotation';
6
+
7
+ /**
8
+ * Prompt payload for a REVIEW inquiry.
9
+ *
10
+ * A REVIEW inquiry is a comment-only review surface: it pins one or more
11
+ * artifact versions and asks recipients to leave free-form comments. It is
12
+ * intentionally distinct from DECISION_GATE — there is NO approve/reject
13
+ * verdict, no policy aggregation, no terminal "approved/rejected" outcome.
14
+ * Reviewers are heard; the workflow continues regardless of what they say.
15
+ *
16
+ * `subjectArtifacts` mirrors the DECISION_GATE prompt and uses the same
17
+ * pinned-reference primitive so downstream consumers (KB ingest, audit)
18
+ * can resolve "what was reviewed at v3" via the same machinery they use
19
+ * for approval gates. `subjectArtifacts` may be empty: a "freeform review
20
+ * thread" on the run as a whole is a legitimate use case.
21
+ *
22
+ * `passthrough()` keeps unknown keys on the parsed object so orchestrators
23
+ * (e.g. `xema/review-thread@v1`) can inject extra render context (past
24
+ * iteration links, reviewer rubrics) that the FE picks up by field
25
+ * presence without contract drift.
26
+ */
27
+ export const ReviewInquiryPromptSchema = z
28
+ .object({
29
+ subjectArtifacts: z.array(PinnedSubjectArtifactSchema),
30
+ description: z.string().optional(),
31
+ })
32
+ .passthrough();
33
+ export type ReviewInquiryPrompt = z.infer<typeof ReviewInquiryPromptSchema>;
34
+
35
+ /**
36
+ * One comment submitted as part of a REVIEW reply.
37
+ *
38
+ * - `body` is required and non-empty — a "review" with no content is a
39
+ * no-op and should be suppressed at the UI rather than persisted.
40
+ *
41
+ * - `anchor` is an optional W3C Web Annotation target
42
+ * (https://www.w3.org/TR/annotation-model/) pinning the comment to a
43
+ * specific artifact version and optionally a sub-region of it via a
44
+ * selector (text quote, text position, fragment, range, JSON pointer).
45
+ * We standardise on Web Annotation so downstream tools — including
46
+ * redraft agents and SCM adapters — receive a vocabulary they already
47
+ * know, instead of a Xema-bespoke string format. The engine
48
+ * round-trips the structured value opaquely; only the FE and the
49
+ * redraft agent need to interpret selector kinds.
50
+ *
51
+ * When the reviewer leaves a document-level remark (no specific span
52
+ * highlighted), `anchor` is omitted entirely — the LLM briefing
53
+ * translator groups those as "general notes" without needing a
54
+ * second sentinel field.
55
+ */
56
+ export const ReviewInquiryCommentSchema = z.object({
57
+ body: z.string().trim().min(1),
58
+ anchor: WebAnnotationTargetSchema.optional(),
59
+ });
60
+ export type ReviewInquiryComment = z.infer<typeof ReviewInquiryCommentSchema>;
61
+
62
+ /**
63
+ * Reply payload for a REVIEW inquiry. At least one comment is required —
64
+ * an empty review is a no-op and should be modelled as "didn't reply"
65
+ * instead.
66
+ *
67
+ * NOTE: no `verdict` field. The whole point of the REVIEW kind is that
68
+ * reviewers leave commentary without being forced into an approve/reject
69
+ * binary. If a workflow needs both ("review then gate"), it composes a
70
+ * REVIEW inquiry followed by a DECISION_GATE that pins the same
71
+ * `subjectArtifacts`.
72
+ */
73
+ export const ReviewInquiryReplySchema = z.object({
74
+ comments: z.array(ReviewInquiryCommentSchema).min(1),
75
+ });
76
+ export type ReviewInquiryReply = z.infer<typeof ReviewInquiryReplySchema>;
@@ -0,0 +1,136 @@
1
+ import { z } from 'zod';
2
+
3
+ /**
4
+ * W3C Web Annotation Data Model — Zod schemas for the subset we use as
5
+ * anchor references on REVIEW inquiry comments.
6
+ *
7
+ * Spec: https://www.w3.org/TR/annotation-model/ (W3C Recommendation,
8
+ * 2017). The standard models commentary on web resources via a
9
+ * { body, target, selector } triple and is the de-facto substrate for
10
+ * Hypothesis-style anchored comments.
11
+ *
12
+ * We adopt the standard rather than inventing a custom shape so the
13
+ * platform can integrate with annotation-aware tools (Hypothesis,
14
+ * Apache Annotator, web-annotation-js) without translation, and so a
15
+ * downstream LLM redraft agent receives a vocabulary it already knows.
16
+ *
17
+ * The selector vocabulary below carries every anchor kind a markdown /
18
+ * code / JSON document reasonably needs:
19
+ *
20
+ * - `TextQuoteSelector` — anchor by literal text + prefix/suffix
21
+ * context. Robust against minor edits.
22
+ * - `TextPositionSelector` — char-offset range. Precise but fragile.
23
+ * - `FragmentSelector` — anchor by fragment id (heading slug).
24
+ * - `RangeSelector` — composite (start + end selectors).
25
+ * - `xema:JsonPointerSelector` — RFC 6901 pointer for structured JSON
26
+ * artifacts. Namespaced per W3C extension
27
+ * rules (custom selector `type` strings
28
+ * MUST be IRIs or namespaced identifiers).
29
+ *
30
+ * All selectors carry a discriminating `type` field so the FE / agent
31
+ * can branch on shape without sniffing other fields — standard
32
+ * pattern for the W3C JSON serialisation.
33
+ */
34
+
35
+ export const TextQuoteSelectorSchema = z.object({
36
+ type: z.literal('TextQuoteSelector'),
37
+ exact: z.string().min(1),
38
+ prefix: z.string().optional(),
39
+ suffix: z.string().optional(),
40
+ });
41
+ export type TextQuoteSelector = z.infer<typeof TextQuoteSelectorSchema>;
42
+
43
+ export const TextPositionSelectorSchema = z
44
+ .object({
45
+ type: z.literal('TextPositionSelector'),
46
+ start: z.number().int().min(0),
47
+ end: z.number().int().min(0),
48
+ })
49
+ .refine((v) => v.end >= v.start, {
50
+ message: 'TextPositionSelector requires end >= start',
51
+ path: ['end'],
52
+ });
53
+ export type TextPositionSelector = z.infer<typeof TextPositionSelectorSchema>;
54
+
55
+ export const FragmentSelectorSchema = z.object({
56
+ type: z.literal('FragmentSelector'),
57
+ value: z.string().min(1),
58
+ /**
59
+ * Optional `conformsTo` per W3C We only emit two values
60
+ * today: the RFC 3986 IRI for generic fragments and a Xema-specific
61
+ * marker for GFM heading slugs (so consumers know to slugify
62
+ * candidate headings the same way).
63
+ */
64
+ conformsTo: z
65
+ .union([
66
+ z.literal('https://tools.ietf.org/html/rfc3986'),
67
+ z.literal('gfm-heading-slug'),
68
+ ])
69
+ .optional(),
70
+ });
71
+ export type FragmentSelector = z.infer<typeof FragmentSelectorSchema>;
72
+
73
+ export const JsonPointerSelectorSchema = z.object({
74
+ /**
75
+ * Xema-namespaced custom selector type. NOT a core W3C type — but
76
+ * the spec explicitly permits extension types as long as `type`
77
+ * is an IRI or namespaced identifier. Documenting the
78
+ * extension URI is on us.
79
+ */
80
+ type: z.literal('xema:JsonPointerSelector'),
81
+ /** RFC 6901-compliant JSON Pointer (e.g. `/decisions/2/rationale`). */
82
+ pointer: z.string().min(1),
83
+ });
84
+ export type JsonPointerSelector = z.infer<typeof JsonPointerSelectorSchema>;
85
+
86
+ /**
87
+ * Non-recursive selector — everything except RangeSelector. Exported
88
+ * so RangeSelector can use it as its start/end child without inviting
89
+ * arbitrary nesting (a Range of Ranges is technically valid per the
90
+ * W3C spec but adds no expressive power for our use case).
91
+ */
92
+ export const NonRangeSelectorSchema = z.discriminatedUnion('type', [
93
+ TextQuoteSelectorSchema,
94
+ TextPositionSelectorSchema,
95
+ FragmentSelectorSchema,
96
+ JsonPointerSelectorSchema,
97
+ ]);
98
+ export type NonRangeSelector = z.infer<typeof NonRangeSelectorSchema>;
99
+
100
+ export const RangeSelectorSchema = z.object({
101
+ type: z.literal('RangeSelector'),
102
+ startSelector: NonRangeSelectorSchema,
103
+ endSelector: NonRangeSelectorSchema,
104
+ });
105
+ export type RangeSelector = z.infer<typeof RangeSelectorSchema>;
106
+
107
+ export const WebAnnotationSelectorSchema = z.discriminatedUnion('type', [
108
+ TextQuoteSelectorSchema,
109
+ TextPositionSelectorSchema,
110
+ FragmentSelectorSchema,
111
+ JsonPointerSelectorSchema,
112
+ RangeSelectorSchema,
113
+ ]);
114
+ export type WebAnnotationSelector = z.infer<typeof WebAnnotationSelectorSchema>;
115
+
116
+ /**
117
+ * W3C Web Annotation Target.
118
+ *
119
+ * Identifies WHICH artifact version the comment refers to and
120
+ * (optionally) WHERE inside it. We model the W3C `source` IRI as the
121
+ * concrete identity triple `{artifactId, versionId, version}` because
122
+ * the platform's artifact-store identity is what downstream consumers
123
+ * actually need — emitting a synthetic URI here would just push
124
+ * parsing complexity onto every reader.
125
+ *
126
+ * `selector` is optional: a whole-document anchor (the reviewer
127
+ * commented on the artifact in general, not a specific span) is a
128
+ * legitimate shape.
129
+ */
130
+ export const WebAnnotationTargetSchema = z.object({
131
+ artifactId: z.string().min(1),
132
+ versionId: z.string().min(1),
133
+ version: z.number().int().min(1),
134
+ selector: WebAnnotationSelectorSchema.optional(),
135
+ });
136
+ export type WebAnnotationTarget = z.infer<typeof WebAnnotationTargetSchema>;
@@ -0,0 +1,22 @@
1
+ // ═══════════════════════════════════════════════════════════════════════════
2
+ // @xemahq/run-detail-contracts — Layer 1 / Kernel
3
+ //
4
+ // Wire shapes for the workflow run detail surface (FE) and the engine's
5
+ // `GET /workflows/:slug/runs/:id/full` endpoint that powers it.
6
+ //
7
+ // The run detail page collapses into
8
+ // THREE modes — Overview / Work / Developer. The contracts here define what
9
+ // each mode reads, the deterministic mode-default rules, the phase rail, the
10
+ // outcome strip for terminal runs, and the revision-state discriminator that
11
+ // tells the FE which (if any) of the three revision UIs to render.
12
+ //
13
+ // Runtime-agnostic. Pure types. Zero NestJS / Prisma / framework imports.
14
+ // ═══════════════════════════════════════════════════════════════════════════
15
+
16
+ export * from './lib/run-mode';
17
+ export * from './lib/run-outcome';
18
+ export * from './lib/run-revision-state';
19
+ export * from './lib/run-phase-rail';
20
+ export * from './lib/run-detail-dto';
21
+ export * from './lib/wait-windows';
22
+