@xemahq/kernel-contracts 0.30.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 (91) 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/question/index.d.ts +5 -0
  14. package/dist/question/index.d.ts.map +1 -0
  15. package/dist/question/index.js +21 -0
  16. package/dist/question/index.js.map +1 -0
  17. package/dist/question/lib/question-action-context.d.ts +7 -0
  18. package/dist/question/lib/question-action-context.d.ts.map +1 -0
  19. package/dist/question/lib/question-action-context.js +3 -0
  20. package/dist/question/lib/question-action-context.js.map +1 -0
  21. package/dist/question/lib/question-dto.d.ts +54 -0
  22. package/dist/question/lib/question-dto.d.ts.map +1 -0
  23. package/dist/question/lib/question-dto.js +3 -0
  24. package/dist/question/lib/question-dto.js.map +1 -0
  25. package/dist/question/lib/question-enums.d.ts +20 -0
  26. package/dist/question/lib/question-enums.d.ts.map +1 -0
  27. package/dist/question/lib/question-enums.js +32 -0
  28. package/dist/question/lib/question-enums.js.map +1 -0
  29. package/dist/question/lib/question-escalation.d.ts +8 -0
  30. package/dist/question/lib/question-escalation.d.ts.map +1 -0
  31. package/dist/question/lib/question-escalation.js +3 -0
  32. package/dist/question/lib/question-escalation.js.map +1 -0
  33. package/dist/review-inquiry/index.d.ts +3 -0
  34. package/dist/review-inquiry/index.d.ts.map +1 -0
  35. package/dist/review-inquiry/index.js +19 -0
  36. package/dist/review-inquiry/index.js.map +1 -0
  37. package/dist/review-inquiry/lib/review-inquiry.d.ts +138 -0
  38. package/dist/review-inquiry/lib/review-inquiry.d.ts.map +1 -0
  39. package/dist/review-inquiry/lib/review-inquiry.js +20 -0
  40. package/dist/review-inquiry/lib/review-inquiry.js.map +1 -0
  41. package/dist/review-inquiry/lib/web-annotation.d.ts +195 -0
  42. package/dist/review-inquiry/lib/web-annotation.d.ts.map +1 -0
  43. package/dist/review-inquiry/lib/web-annotation.js +59 -0
  44. package/dist/review-inquiry/lib/web-annotation.js.map +1 -0
  45. package/dist/run-detail/index.d.ts +7 -0
  46. package/dist/run-detail/index.d.ts.map +1 -0
  47. package/dist/run-detail/index.js +23 -0
  48. package/dist/run-detail/index.js.map +1 -0
  49. package/dist/run-detail/lib/run-detail-dto.d.ts +34 -0
  50. package/dist/run-detail/lib/run-detail-dto.d.ts.map +1 -0
  51. package/dist/run-detail/lib/run-detail-dto.js +3 -0
  52. package/dist/run-detail/lib/run-detail-dto.js.map +1 -0
  53. package/dist/run-detail/lib/run-mode.d.ts +14 -0
  54. package/dist/run-detail/lib/run-mode.d.ts.map +1 -0
  55. package/dist/run-detail/lib/run-mode.js +25 -0
  56. package/dist/run-detail/lib/run-mode.js.map +1 -0
  57. package/dist/run-detail/lib/run-outcome.d.ts +28 -0
  58. package/dist/run-detail/lib/run-outcome.d.ts.map +1 -0
  59. package/dist/run-detail/lib/run-outcome.js +27 -0
  60. package/dist/run-detail/lib/run-outcome.js.map +1 -0
  61. package/dist/run-detail/lib/run-phase-rail.d.ts +21 -0
  62. package/dist/run-detail/lib/run-phase-rail.d.ts.map +1 -0
  63. package/dist/run-detail/lib/run-phase-rail.js +14 -0
  64. package/dist/run-detail/lib/run-phase-rail.js.map +1 -0
  65. package/dist/run-detail/lib/run-revision-state.d.ts +47 -0
  66. package/dist/run-detail/lib/run-revision-state.d.ts.map +1 -0
  67. package/dist/run-detail/lib/run-revision-state.js +11 -0
  68. package/dist/run-detail/lib/run-revision-state.js.map +1 -0
  69. package/dist/run-detail/lib/wait-windows.d.ts +15 -0
  70. package/dist/run-detail/lib/wait-windows.d.ts.map +1 -0
  71. package/dist/run-detail/lib/wait-windows.js +39 -0
  72. package/dist/run-detail/lib/wait-windows.js.map +1 -0
  73. package/package.json +1 -1
  74. package/src/decision-gate/index.ts +13 -0
  75. package/src/decision-gate/lib/decision-gate.ts +113 -0
  76. package/src/decision-gate/lib/pinned-subject-artifact.ts +29 -0
  77. package/src/question/index.ts +24 -0
  78. package/src/question/lib/question-action-context.ts +26 -0
  79. package/src/question/lib/question-dto.ts +143 -0
  80. package/src/question/lib/question-enums.ts +67 -0
  81. package/src/question/lib/question-escalation.ts +22 -0
  82. package/src/review-inquiry/index.ts +19 -0
  83. package/src/review-inquiry/lib/review-inquiry.ts +76 -0
  84. package/src/review-inquiry/lib/web-annotation.ts +136 -0
  85. package/src/run-detail/index.ts +22 -0
  86. package/src/run-detail/lib/run-detail-dto.ts +84 -0
  87. package/src/run-detail/lib/run-mode.ts +65 -0
  88. package/src/run-detail/lib/run-outcome.ts +66 -0
  89. package/src/run-detail/lib/run-phase-rail.ts +43 -0
  90. package/src/run-detail/lib/run-revision-state.ts +83 -0
  91. package/src/run-detail/lib/wait-windows.ts +70 -0
@@ -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
+
@@ -0,0 +1,84 @@
1
+ import { WorkflowRunStatus } from '../../workflow';
2
+
3
+ import { RunOutcomeDto } from './run-outcome';
4
+ import { RunPhaseRailDto } from './run-phase-rail';
5
+ import { RunRevisionPayload } from './run-revision-state';
6
+ import { WaitWindow } from './wait-windows';
7
+
8
+ /**
9
+ * The single payload `GET /workflows/:slug/runs/:id/full` returns. Replaces
10
+ * the legacy `useRunSummary` + `useRunDescribe` + first-page `useRunHistory`
11
+ * + `useRunJobs` quad-query with one coalesced call.
12
+ *
13
+ * Conventions:
14
+ * - Every nested list is bounded — the FE never asks for "all events".
15
+ * Detail/history surfaces page through the timeline endpoint.
16
+ * - Every closed-domain field uses an enum from this package or
17
+ * `../../workflow`. No free-form strings except labels.
18
+ * - The payload is shaped for the user-visible view, not internal
19
+ * state. Internal Temporal IDs are exposed only in DEVELOPER mode.
20
+ *
21
+ * Wire envelope: this DTO is the `data` member of the standard envelope
22
+ *. The FE's envelope-assert util validates the
23
+ * shape end-to-end.
24
+ */
25
+ export interface RunDetailDto {
26
+ readonly id: string;
27
+ readonly workflowSlug: string;
28
+ readonly workflowDisplayName: string;
29
+ readonly status: WorkflowRunStatus;
30
+ /** Initiator displayName — `null` for system-triggered runs (schedules, webhooks). */
31
+ readonly initiatorDisplayName: string | null;
32
+ readonly startedAt: string;
33
+ /** ISO-8601 when the run reached a terminal state, else `null`. */
34
+ readonly endedAt: string | null;
35
+ /** Engine ETA for in-flight runs. `null` when terminal or unknown. */
36
+ readonly etaAt: string | null;
37
+
38
+ readonly phaseRail: RunPhaseRailDto;
39
+ readonly waitWindows: readonly WaitWindow[];
40
+
41
+ /** What's actively running right now — feeds the Overview "Activity now" strip. */
42
+ readonly activityNow: readonly RunActivityNowDto[];
43
+
44
+ /** Last 6 events (coarse milestones). Click-through navigates to Developer mode. */
45
+ readonly recentTail: readonly RunRecentEventDto[];
46
+
47
+ /**
48
+ * Revision-state discriminator + payload (see {@link RunRevisionPayload}).
49
+ * Drives whether/which of the 3 revision UIs renders.
50
+ */
51
+ readonly revision: RunRevisionPayload;
52
+
53
+ /** Outcome strip for terminal runs. `null` for in-flight. */
54
+ readonly outcome: RunOutcomeDto | null;
55
+
56
+ /**
57
+ * One-line "what's blocking" callout for in-flight runs. Server-computed
58
+ * from waitWindows + escalation state. `null` when nothing is blocking.
59
+ */
60
+ readonly blockingSummary: string | null;
61
+ }
62
+
63
+ /**
64
+ * One row in the Overview "Activity now" strip — coarse "who is doing
65
+ * what right now". Updated via SSE incremental patches; one sentence
66
+ * per active job, no timestamps (they're current).
67
+ */
68
+ export interface RunActivityNowDto {
69
+ readonly jobKey: string;
70
+ readonly agentDisplayName: string | null;
71
+ readonly summary: string;
72
+ }
73
+
74
+ /**
75
+ * One row in the Overview "Recent" tail — the last 6 coarse milestones
76
+ * (phase entered, deliverable emitted, agent paused, decision needed).
77
+ */
78
+ export interface RunRecentEventDto {
79
+ readonly id: string;
80
+ readonly at: string;
81
+ readonly summary: string;
82
+ /** Optional deep-link anchor for the Developer-mode timeline. */
83
+ readonly developerAnchor: string | null;
84
+ }
@@ -0,0 +1,65 @@
1
+ import { WorkflowRunStatus, TERMINAL_WORKFLOW_RUN_STATUSES } from '../../workflow';
2
+
3
+ /**
4
+ * The calm modes that replace the legacy 10-tab run detail.
5
+ *
6
+ * - `OVERVIEW` — coarse status + latest deliverables (links) + last 6
7
+ * events. Default for running and terminal runs.
8
+ * - `WORK` — the question canvas + reply form. Default when the run has
9
+ * a question waiting on the current user.
10
+ * - `OUTPUTS` — full deliverables surface. One sub-tab per artifact;
11
+ * each sub-tab renders the artifact's CONTENT (markdown rendered as
12
+ * an A4 page, code diff via the diff viewer, diagram via mermaid,
13
+ * etc.) with a `Show raw` toggle that reveals the underlying artifact
14
+ * JSON. Never the default — opened from the Overview deliverables
15
+ * strip or directly via the mode switcher.
16
+ * - `DEVELOPER` — full activity console + Temporal history + Mounts /
17
+ * Pending / Query diagnostic cards. Behind a user-pref + role gate.
18
+ *
19
+ * Mode-default selection is deterministic — see {@link selectDefaultRunMode}.
20
+ * Free-text strings are forbidden; consumers route on this enum.
21
+ */
22
+ export enum RunMode {
23
+ OVERVIEW = 'overview',
24
+ WORK = 'work',
25
+ OUTPUTS = 'outputs',
26
+ DEVELOPER = 'developer',
27
+ }
28
+
29
+ /**
30
+ * Inputs the deterministic mode-default selector consumes. Keeping this
31
+ * narrow (no full RunDetailDto reference) lets callers pre-compute it
32
+ * without holding the whole run payload in scope.
33
+ */
34
+ export interface RunModeSelectionInputs {
35
+ readonly status: WorkflowRunStatus;
36
+ readonly hasQuestionForCurrentUser: boolean;
37
+ /** User-pref override; wins if set. */
38
+ readonly userPreferredMode?: RunMode;
39
+ }
40
+
41
+ /**
42
+ * Deterministic mode-default rules:
43
+ *
44
+ * status == waiting_on_human && hasQuestionForMe(runId) → WORK
45
+ * status terminal → OVERVIEW
46
+ * otherwise → OVERVIEW
47
+ * user pref override → wins
48
+ *
49
+ * No timing, no probabilities, no fuzzy matching. The FE calls this once
50
+ * per render and routes accordingly; reverse calls are not supported.
51
+ */
52
+ export function selectDefaultRunMode(input: RunModeSelectionInputs): RunMode {
53
+ if (input.userPreferredMode !== undefined) {
54
+ return input.userPreferredMode;
55
+ }
56
+ const isTerminal = TERMINAL_WORKFLOW_RUN_STATUSES.includes(input.status);
57
+ if (
58
+ !isTerminal &&
59
+ input.status === WorkflowRunStatus.WAITING_ON_HUMAN &&
60
+ input.hasQuestionForCurrentUser
61
+ ) {
62
+ return RunMode.WORK;
63
+ }
64
+ return RunMode.OVERVIEW;
65
+ }
@@ -0,0 +1,66 @@
1
+ import { WorkflowRunStatus } from '../../workflow';
2
+
3
+ /**
4
+ * Branch the FE outcome strip renders for a TERMINAL run. Mirrors the
5
+ * branch matrix for terminal runs.
6
+ *
7
+ * Every terminal run lands in exactly one branch — the engine sets it on
8
+ * the {@link RunDetailDto.outcome} field at run-end so the FE never has
9
+ * to re-derive from `status` + reason text.
10
+ */
11
+ export enum RunOutcomeBranch {
12
+ SUCCEEDED = 'succeeded',
13
+ FAILED = 'failed',
14
+ CANCELLED = 'cancelled',
15
+ ESCALATION_EXHAUSTED = 'escalation_exhausted',
16
+ }
17
+
18
+ /**
19
+ * What the user sees in the outcome card. Each branch's `actions` list
20
+ * is computed server-side (only valid CTAs leak into the wire) so the
21
+ * FE renders without policy logic.
22
+ *
23
+ * `summary` is one human sentence — never JSON. The FE displays it
24
+ * verbatim under the status header.
25
+ */
26
+ export interface RunOutcomeDto {
27
+ readonly branch: RunOutcomeBranch;
28
+ readonly summary: string;
29
+ readonly actions: readonly RunOutcomeActionDto[];
30
+ }
31
+
32
+ /**
33
+ * A button rendered under the outcome card. Closed CTA enum — the FE
34
+ * fails-fast on unknown values rather than rendering a placeholder.
35
+ */
36
+ export interface RunOutcomeActionDto {
37
+ readonly kind: RunOutcomeActionKind;
38
+ readonly label: string;
39
+ /** Optional href; if absent the action is dispatched to the engine via SDK. */
40
+ readonly href?: string;
41
+ /** Optional opaque payload the engine echoes on dispatch. */
42
+ readonly payload?: Readonly<Record<string, unknown>>;
43
+ }
44
+
45
+ export enum RunOutcomeActionKind {
46
+ RUN_AGAIN = 'run_again',
47
+ OPEN_IN_KB = 'open_in_kb',
48
+ REDO_FROM_PHASE = 'redo_from_phase',
49
+ OPEN_LAST_SUCCESSFUL_ARTIFACT = 'open_last_successful_artifact',
50
+ RESTART_WITH_SAME_INPUTS = 'restart_with_same_inputs',
51
+ ADJUST_ESCALATION_CHAIN = 'adjust_escalation_chain',
52
+ }
53
+
54
+ /**
55
+ * True iff a {@link WorkflowRunStatus} represents a terminal state that
56
+ * carries an outcome. Mirrors {@link TERMINAL_WORKFLOW_RUN_STATUSES} but
57
+ * rejects `ABORTED_FOR_MIGRATION` because that branch has no user-facing
58
+ * outcome card (it's an internal lifecycle event).
59
+ */
60
+ export function isOutcomeBearingStatus(status: WorkflowRunStatus): boolean {
61
+ return (
62
+ status === WorkflowRunStatus.SUCCEEDED ||
63
+ status === WorkflowRunStatus.FAILED ||
64
+ status === WorkflowRunStatus.CANCELLED
65
+ );
66
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * The phase rail shown at the top of every run-detail mode. Click any
3
+ * dot to filter the activity panel to that phase. The rail is a stable,
4
+ * compile-time-known sequence per workflow definition — the engine
5
+ * computes it once at run dispatch and freezes it on the run row so the
6
+ * FE doesn't have to re-derive from runtime activity.
7
+ */
8
+ export interface RunPhaseRailDto {
9
+ readonly phases: readonly RunPhaseRailEntryDto[];
10
+ /**
11
+ * Index into `phases` of the currently-active phase. `null` when the
12
+ * run is terminal AND the last phase already completed (the rail
13
+ * renders fully filled).
14
+ */
15
+ readonly currentIndex: number | null;
16
+ }
17
+
18
+ export interface RunPhaseRailEntryDto {
19
+ readonly stageKey: string;
20
+ /** Display label — e.g. "Implementation", "Architecture review". */
21
+ readonly label: string;
22
+ readonly status: RunPhaseRailStatus;
23
+ /** ISO-8601 when the phase entered RUNNING. `null` if not yet started. */
24
+ readonly startedAt: string | null;
25
+ /** ISO-8601 when the phase reached a terminal state. `null` if still in flight. */
26
+ readonly endedAt: string | null;
27
+ }
28
+
29
+ /**
30
+ * Closed phase-rail status set. Distinct from {@link JobRunStatus} — a
31
+ * single phase rolls up many jobs and adds the WAITING / SKIPPED rail
32
+ * affordances the user sees. Maps from job status server-side; FE
33
+ * renders the icon+color directly off this enum.
34
+ */
35
+ export enum RunPhaseRailStatus {
36
+ PENDING = 'pending',
37
+ RUNNING = 'running',
38
+ WAITING = 'waiting',
39
+ SUCCEEDED = 'succeeded',
40
+ FAILED = 'failed',
41
+ CANCELLED = 'cancelled',
42
+ SKIPPED = 'skipped',
43
+ }
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Three deliberately distinct revision UIs.
3
+ * The engine emits exactly one of these on the run detail payload; the
4
+ * FE renders the matching component (or nothing for `NONE`).
5
+ *
6
+ * - `REVISE` — purple checkpoint card embedded in the doc flow.
7
+ * CTAs: "Approve & continue" | "Revise with feedback".
8
+ * - `MANUAL_OVERRIDE` — warning banner shown after auto-retries are
9
+ * exhausted. CTAs: "Extend retries" | "Force approve" | "Reject run".
10
+ * - `REDO_FROM_PHASE` — terminal-only dropdown that re-dispatches from
11
+ * a chosen earlier phase.
12
+ * - `NONE` — no revision UI; the run is progressing normally.
13
+ *
14
+ * Auto-retry status surfaces as a status pill in the run header, NOT
15
+ * its own revision-state value. Don't extend this enum to cover
16
+ * non-revision states.
17
+ */
18
+ export enum RunRevisionState {
19
+ NONE = 'none',
20
+ REVISE = 'revise',
21
+ MANUAL_OVERRIDE = 'manual_override',
22
+ REDO_FROM_PHASE = 'redo_from_phase',
23
+ }
24
+
25
+ /**
26
+ * Per-state payload the FE needs to render the matching component. The
27
+ * engine includes whichever variant matches `state` and omits the rest.
28
+ *
29
+ * `NONE` carries no payload — the discriminator alone suffices.
30
+ */
31
+ export type RunRevisionPayload =
32
+ | { readonly state: RunRevisionState.NONE }
33
+ | { readonly state: RunRevisionState.REVISE; readonly checkpoint: RunReviseCheckpointDto }
34
+ | {
35
+ readonly state: RunRevisionState.MANUAL_OVERRIDE;
36
+ readonly override: RunManualOverrideDto;
37
+ }
38
+ | {
39
+ readonly state: RunRevisionState.REDO_FROM_PHASE;
40
+ readonly redo: RunRedoFromPhaseDto;
41
+ };
42
+
43
+ /** Payload for the purple checkpoint card. */
44
+ export interface RunReviseCheckpointDto {
45
+ readonly checkpointId: string;
46
+ readonly stageKey: string;
47
+ readonly title: string;
48
+ /** Human-readable summary of what's being reviewed. */
49
+ readonly summary: string;
50
+ /** Reviewer findings to render inline alongside the canvas. */
51
+ readonly findings: readonly RunReviseFindingDto[];
52
+ }
53
+
54
+ export interface RunReviseFindingDto {
55
+ readonly id: string;
56
+ readonly severity: 'info' | 'minor' | 'major' | 'critical';
57
+ /** Optional file:line anchor — `null` if the finding is run-level. */
58
+ readonly anchor: string | null;
59
+ readonly message: string;
60
+ }
61
+
62
+ /** Payload for the manual-override banner (auto-retries exhausted). */
63
+ export interface RunManualOverrideDto {
64
+ readonly retryCount: number;
65
+ readonly retryLimit: number;
66
+ readonly lastFailureReason: string;
67
+ /** When the engine is willing to attempt one more retry on demand. */
68
+ readonly canExtendRetries: boolean;
69
+ }
70
+
71
+ /** Payload for the redo-from-phase dropdown (terminal runs only). */
72
+ export interface RunRedoFromPhaseDto {
73
+ readonly availablePhases: readonly RunRedoPhaseChoiceDto[];
74
+ }
75
+
76
+ export interface RunRedoPhaseChoiceDto {
77
+ readonly stageKey: string;
78
+ readonly label: string;
79
+ /** Whether the engine has the inputs needed to re-dispatch from here. */
80
+ readonly available: boolean;
81
+ /** Human-readable reason when `available === false`. */
82
+ readonly unavailableReason?: string;
83
+ }
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Single source of truth for "the run is waiting on X" math used by the
3
+ * RunHeaderTimebar, the run-list "what's blocking" inline text, and the
4
+ * Overview blocking callout.
5
+ *
6
+ * Three different surfaces used to compute these windows independently
7
+ * and could disagree by the
8
+ * second. They now share this util.
9
+ *
10
+ * All times are ISO-8601 strings on the wire; the helpers parse + return
11
+ * primitive ms numbers so callers stay framework-agnostic.
12
+ */
13
+
14
+ export interface WaitWindow {
15
+ /** Inclusive start ISO-8601. */
16
+ readonly startedAt: string;
17
+ /** Exclusive end ISO-8601, or `null` if the wait is still open. */
18
+ readonly endedAt: string | null;
19
+ readonly reason: WaitReason;
20
+ }
21
+
22
+ /**
23
+ * Closed reason set. The user-visible label is rendered by the FE from
24
+ * this discriminator — never accept a free-form `reason` string from
25
+ * the wire.
26
+ */
27
+ export enum WaitReason {
28
+ WAITING_ON_HUMAN = 'waiting_on_human',
29
+ WAITING_ON_AGENT = 'waiting_on_agent',
30
+ WAITING_ON_EXTERNAL = 'waiting_on_external',
31
+ WAITING_ON_DEPENDENCY = 'waiting_on_dependency',
32
+ WAITING_ON_QUOTA = 'waiting_on_quota',
33
+ }
34
+
35
+ /**
36
+ * Total wait duration (ms) summed across windows. Open windows count
37
+ * up to `nowMs`. Caller passes `nowMs` (test-injectable) — the lib
38
+ * never reads `Date.now()` on its own.
39
+ */
40
+ export function totalWaitMs(windows: readonly WaitWindow[], nowMs: number): number {
41
+ let total = 0;
42
+ for (const w of windows) {
43
+ const start = Date.parse(w.startedAt);
44
+ const end = w.endedAt !== null ? Date.parse(w.endedAt) : nowMs;
45
+ if (Number.isFinite(start) && Number.isFinite(end) && end > start) {
46
+ total += end - start;
47
+ }
48
+ }
49
+ return total;
50
+ }
51
+
52
+ /**
53
+ * The currently-open wait window, if any. Returns the most recent open
54
+ * window (there should never be more than one open at a time, but if
55
+ * the engine emits overlapping waits we surface the most recent so the
56
+ * FE doesn't pick a stale reason).
57
+ */
58
+ export function currentOpenWait(windows: readonly WaitWindow[]): WaitWindow | null {
59
+ let bestStart = -Infinity;
60
+ let bestWindow: WaitWindow | null = null;
61
+ for (const w of windows) {
62
+ if (w.endedAt !== null) continue;
63
+ const start = Date.parse(w.startedAt);
64
+ if (Number.isFinite(start) && start > bestStart) {
65
+ bestStart = start;
66
+ bestWindow = w;
67
+ }
68
+ }
69
+ return bestWindow;
70
+ }