Skip to content

Commit f2292f9

Browse files
committed
Add decision workspace contracts
- Add schema and IPC types for decision cases and workspace - Wire new decision WebSocket methods and update channel events
1 parent e0a7269 commit f2292f9

4 files changed

Lines changed: 357 additions & 0 deletions

File tree

packages/contracts/src/decision.ts

Lines changed: 300 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,300 @@
1+
import { Schema } from "effect";
2+
import {
3+
IsoDateTime,
4+
NonNegativeInt,
5+
ProjectId,
6+
ThreadId,
7+
TrimmedNonEmptyString,
8+
} from "./baseSchemas";
9+
import { OrchestrationThread, ProviderUserInputAnswers } from "./orchestration";
10+
import {
11+
PrConflictAnalysis,
12+
PrConflictApplyResult,
13+
PrReviewConfig,
14+
PrReviewDashboardResult,
15+
PrReviewPatchResult,
16+
PrReviewSummary,
17+
PrWorkflowStepResolution,
18+
} from "./prReview";
19+
20+
export const DECISION_WS_METHODS = {
21+
listCases: "decision.listCases",
22+
getWorkspace: "decision.getWorkspace",
23+
reanalyze: "decision.reanalyze",
24+
requestConsultation: "decision.requestConsultation",
25+
respondConsultation: "decision.respondConsultation",
26+
executeRecommendation: "decision.executeRecommendation",
27+
} as const;
28+
29+
export const DECISION_WS_CHANNELS = {
30+
updated: "decision.updated",
31+
} as const;
32+
33+
export const DecisionCaseId = TrimmedNonEmptyString;
34+
export type DecisionCaseId = typeof DecisionCaseId.Type;
35+
36+
export const DecisionConsultationId = TrimmedNonEmptyString;
37+
export type DecisionConsultationId = typeof DecisionConsultationId.Type;
38+
39+
export const DecisionRecommendationId = TrimmedNonEmptyString;
40+
export type DecisionRecommendationId = typeof DecisionRecommendationId.Type;
41+
42+
export const DecisionSource = Schema.Literals(["pr", "thread", "manual_brief"]);
43+
export type DecisionSource = typeof DecisionSource.Type;
44+
45+
export const DecisionConflictKind = Schema.Literals([
46+
"merge_conflict",
47+
"workflow_blocker",
48+
"review_conflict",
49+
"intent_ambiguity",
50+
"policy_conflict",
51+
]);
52+
export type DecisionConflictKind = typeof DecisionConflictKind.Type;
53+
54+
export const DecisionConsultationTarget = Schema.Literals(["operator", "orchestrator"]);
55+
export type DecisionConsultationTarget = typeof DecisionConsultationTarget.Type;
56+
57+
export const DecisionConsultationStatus = Schema.Literals([
58+
"not_requested",
59+
"awaiting_operator",
60+
"awaiting_orchestrator",
61+
"resolved",
62+
"superseded",
63+
]);
64+
export type DecisionConsultationStatus = typeof DecisionConsultationStatus.Type;
65+
66+
export const DecisionRiskTier = Schema.Literals(["low", "medium", "high"]);
67+
export type DecisionRiskTier = typeof DecisionRiskTier.Type;
68+
69+
export const DecisionFactorId = Schema.Literals([
70+
"contextCompleteness",
71+
"evidenceQuality",
72+
"sourceAgreement",
73+
"policyAlignment",
74+
"safetyAndReversibility",
75+
"executionReadiness",
76+
]);
77+
export type DecisionFactorId = typeof DecisionFactorId.Type;
78+
79+
export const DecisionEvidenceSource = Schema.Struct({
80+
id: TrimmedNonEmptyString,
81+
label: TrimmedNonEmptyString,
82+
source: DecisionSource,
83+
kind: TrimmedNonEmptyString,
84+
capturedAt: IsoDateTime,
85+
freshness: Schema.Literals(["fresh", "stale", "derived"]),
86+
usedInDecision: Schema.Boolean,
87+
});
88+
export type DecisionEvidenceSource = typeof DecisionEvidenceSource.Type;
89+
90+
export const DecisionContextRequirement = Schema.Struct({
91+
id: TrimmedNonEmptyString,
92+
label: TrimmedNonEmptyString,
93+
satisfied: Schema.Boolean,
94+
whyItMatters: TrimmedNonEmptyString,
95+
howToProvideIt: TrimmedNonEmptyString,
96+
evidenceIds: Schema.Array(TrimmedNonEmptyString),
97+
});
98+
export type DecisionContextRequirement = typeof DecisionContextRequirement.Type;
99+
100+
export const DecisionPrincipleResult = Schema.Struct({
101+
id: TrimmedNonEmptyString,
102+
label: TrimmedNonEmptyString,
103+
passed: Schema.Boolean,
104+
blocking: Schema.Boolean,
105+
rationale: TrimmedNonEmptyString,
106+
evidenceIds: Schema.Array(TrimmedNonEmptyString),
107+
});
108+
export type DecisionPrincipleResult = typeof DecisionPrincipleResult.Type;
109+
110+
export const DecisionConfidenceFactor = Schema.Struct({
111+
id: DecisionFactorId,
112+
label: TrimmedNonEmptyString,
113+
score: NonNegativeInt.pipe(Schema.clamp(0, 100)),
114+
weight: Schema.Number,
115+
weightedPoints: Schema.Number,
116+
why: TrimmedNonEmptyString,
117+
missingInputs: Schema.Array(TrimmedNonEmptyString),
118+
evidenceIds: Schema.Array(TrimmedNonEmptyString),
119+
});
120+
export type DecisionConfidenceFactor = typeof DecisionConfidenceFactor.Type;
121+
122+
export const DecisionNextContextHint = Schema.Struct({
123+
label: TrimmedNonEmptyString,
124+
whyItMatters: TrimmedNonEmptyString,
125+
howToProvideIt: TrimmedNonEmptyString,
126+
estimatedScoreGain: NonNegativeInt,
127+
appliesTo: Schema.Array(DecisionFactorId),
128+
});
129+
export type DecisionNextContextHint = typeof DecisionNextContextHint.Type;
130+
131+
export const DecisionConfidenceAnalysis = Schema.Struct({
132+
score: NonNegativeInt.pipe(Schema.clamp(0, 100)),
133+
riskTier: DecisionRiskTier,
134+
autoExecuteEligible: Schema.Boolean,
135+
scoreDelta: Schema.Int,
136+
contextCoverageNumerator: NonNegativeInt,
137+
contextCoverageDenominator: NonNegativeInt,
138+
sourceAgreementNumerator: NonNegativeInt,
139+
sourceAgreementDenominator: NonNegativeInt,
140+
policyPassNumerator: NonNegativeInt,
141+
policyPassDenominator: NonNegativeInt,
142+
factors: Schema.Array(DecisionConfidenceFactor),
143+
nextContextHints: Schema.Array(DecisionNextContextHint),
144+
});
145+
export type DecisionConfidenceAnalysis = typeof DecisionConfidenceAnalysis.Type;
146+
147+
export const DecisionRecommendation = Schema.Struct({
148+
id: DecisionRecommendationId,
149+
label: TrimmedNonEmptyString,
150+
rationale: TrimmedNonEmptyString,
151+
executable: Schema.Boolean,
152+
blockedReason: Schema.NullOr(TrimmedNonEmptyString),
153+
preview: Schema.String,
154+
source: DecisionSource,
155+
});
156+
export type DecisionRecommendation = typeof DecisionRecommendation.Type;
157+
158+
export const DecisionConsultationQuestionOption = Schema.Struct({
159+
label: TrimmedNonEmptyString,
160+
description: TrimmedNonEmptyString,
161+
});
162+
export type DecisionConsultationQuestionOption = typeof DecisionConsultationQuestionOption.Type;
163+
164+
export const DecisionConsultationQuestion = Schema.Struct({
165+
id: TrimmedNonEmptyString,
166+
header: TrimmedNonEmptyString,
167+
question: TrimmedNonEmptyString,
168+
options: Schema.Array(DecisionConsultationQuestionOption),
169+
});
170+
export type DecisionConsultationQuestion = typeof DecisionConsultationQuestion.Type;
171+
172+
export const DecisionConsultation = Schema.Struct({
173+
id: DecisionConsultationId,
174+
caseId: DecisionCaseId,
175+
target: DecisionConsultationTarget,
176+
status: DecisionConsultationStatus,
177+
reason: TrimmedNonEmptyString,
178+
questions: Schema.Array(DecisionConsultationQuestion),
179+
responseSummary: Schema.NullOr(Schema.String),
180+
linkedThreadId: Schema.NullOr(ThreadId),
181+
createdAt: IsoDateTime,
182+
updatedAt: IsoDateTime,
183+
resolvedAt: Schema.NullOr(IsoDateTime),
184+
});
185+
export type DecisionConsultation = typeof DecisionConsultation.Type;
186+
187+
export const DecisionCaseSummary = Schema.Struct({
188+
id: DecisionCaseId,
189+
projectId: ProjectId,
190+
cwd: TrimmedNonEmptyString,
191+
sourceKind: DecisionSource,
192+
sourceId: TrimmedNonEmptyString,
193+
title: TrimmedNonEmptyString,
194+
subtitle: Schema.String,
195+
conflictKind: DecisionConflictKind,
196+
score: NonNegativeInt.pipe(Schema.clamp(0, 100)),
197+
riskTier: DecisionRiskTier,
198+
consultationStatus: DecisionConsultationStatus,
199+
updatedAt: IsoDateTime,
200+
});
201+
export type DecisionCaseSummary = typeof DecisionCaseSummary.Type;
202+
203+
export const DecisionContextPack = Schema.Struct({
204+
sourceKind: DecisionSource,
205+
sourceId: TrimmedNonEmptyString,
206+
prSummary: Schema.optional(PrReviewSummary),
207+
prDashboard: Schema.optional(PrReviewDashboardResult),
208+
prPatch: Schema.optional(PrReviewPatchResult),
209+
prConfig: Schema.optional(PrReviewConfig),
210+
prConflicts: Schema.optional(PrConflictAnalysis),
211+
workflowSteps: Schema.optional(Schema.Array(PrWorkflowStepResolution)),
212+
thread: Schema.optional(OrchestrationThread),
213+
manualBrief: Schema.optional(Schema.String),
214+
evidence: Schema.Array(DecisionEvidenceSource),
215+
contextRequirements: Schema.Array(DecisionContextRequirement),
216+
linkedThreadId: Schema.NullOr(ThreadId),
217+
});
218+
export type DecisionContextPack = typeof DecisionContextPack.Type;
219+
220+
export const DecisionCase = Schema.Struct({
221+
id: DecisionCaseId,
222+
projectId: ProjectId,
223+
cwd: TrimmedNonEmptyString,
224+
sourceKind: DecisionSource,
225+
sourceId: TrimmedNonEmptyString,
226+
title: TrimmedNonEmptyString,
227+
conflictKind: DecisionConflictKind,
228+
linkedThreadId: Schema.NullOr(ThreadId),
229+
createdAt: IsoDateTime,
230+
updatedAt: IsoDateTime,
231+
});
232+
export type DecisionCase = typeof DecisionCase.Type;
233+
234+
export const DecisionWorkspace = Schema.Struct({
235+
case: DecisionCase,
236+
summary: DecisionCaseSummary,
237+
contextPack: DecisionContextPack,
238+
principles: Schema.Array(DecisionPrincipleResult),
239+
confidence: DecisionConfidenceAnalysis,
240+
recommendations: Schema.Array(DecisionRecommendation),
241+
consultations: Schema.Array(DecisionConsultation),
242+
});
243+
export type DecisionWorkspace = typeof DecisionWorkspace.Type;
244+
245+
export const DecisionExecutionResult = Schema.Struct({
246+
caseId: DecisionCaseId,
247+
recommendationId: DecisionRecommendationId,
248+
executed: Schema.Boolean,
249+
summary: TrimmedNonEmptyString,
250+
appliedConflictResult: Schema.optional(PrConflictApplyResult),
251+
updatedAt: IsoDateTime,
252+
});
253+
export type DecisionExecutionResult = typeof DecisionExecutionResult.Type;
254+
255+
export const DecisionListCasesInput = Schema.Struct({
256+
cwd: TrimmedNonEmptyString,
257+
sourceKind: Schema.optional(DecisionSource),
258+
sourceId: Schema.optional(TrimmedNonEmptyString),
259+
});
260+
export type DecisionListCasesInput = typeof DecisionListCasesInput.Type;
261+
262+
export const DecisionGetWorkspaceInput = Schema.Struct({
263+
cwd: TrimmedNonEmptyString,
264+
caseId: DecisionCaseId,
265+
});
266+
export type DecisionGetWorkspaceInput = typeof DecisionGetWorkspaceInput.Type;
267+
268+
export const DecisionReanalyzeInput = DecisionGetWorkspaceInput;
269+
export type DecisionReanalyzeInput = typeof DecisionReanalyzeInput.Type;
270+
271+
export const DecisionRequestConsultationInput = Schema.Struct({
272+
cwd: TrimmedNonEmptyString,
273+
caseId: DecisionCaseId,
274+
target: DecisionConsultationTarget,
275+
reason: TrimmedNonEmptyString,
276+
questions: Schema.optional(Schema.Array(DecisionConsultationQuestion)),
277+
});
278+
export type DecisionRequestConsultationInput = typeof DecisionRequestConsultationInput.Type;
279+
280+
export const DecisionRespondConsultationInput = Schema.Struct({
281+
cwd: TrimmedNonEmptyString,
282+
consultationId: DecisionConsultationId,
283+
answers: ProviderUserInputAnswers,
284+
resolution: TrimmedNonEmptyString,
285+
});
286+
export type DecisionRespondConsultationInput = typeof DecisionRespondConsultationInput.Type;
287+
288+
export const DecisionExecuteRecommendationInput = Schema.Struct({
289+
cwd: TrimmedNonEmptyString,
290+
caseId: DecisionCaseId,
291+
recommendationId: DecisionRecommendationId,
292+
});
293+
export type DecisionExecuteRecommendationInput = typeof DecisionExecuteRecommendationInput.Type;
294+
295+
export const DecisionUpdatedPayload = Schema.Struct({
296+
cwd: TrimmedNonEmptyString,
297+
caseId: DecisionCaseId,
298+
updatedAt: IsoDateTime,
299+
});
300+
export type DecisionUpdatedPayload = typeof DecisionUpdatedPayload.Type;

packages/contracts/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,4 @@ export * from "./environment";
1818
export * from "./skill";
1919
export * from "./skillCatalog";
2020
export * from "./sme";
21+
export * from "./decision";

packages/contracts/src/ipc.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,17 @@ import type {
123123
OrchestrationOverviewSnapshot,
124124
OrchestrationThread,
125125
} from "./orchestration";
126+
import type {
127+
DecisionCaseSummary,
128+
DecisionExecuteRecommendationInput,
129+
DecisionExecutionResult,
130+
DecisionGetWorkspaceInput,
131+
DecisionListCasesInput,
132+
DecisionRequestConsultationInput,
133+
DecisionRespondConsultationInput,
134+
DecisionUpdatedPayload,
135+
DecisionWorkspace,
136+
} from "./decision";
126137
import type {
127138
SmeConversation,
128139
SmeCreateConversationInput,
@@ -422,6 +433,17 @@ export interface NativeApi {
422433
callback: (payload: PrReviewRepoConfigUpdatedPayload) => void,
423434
) => () => void;
424435
};
436+
decision: {
437+
listCases: (input: DecisionListCasesInput) => Promise<ReadonlyArray<DecisionCaseSummary>>;
438+
getWorkspace: (input: DecisionGetWorkspaceInput) => Promise<DecisionWorkspace>;
439+
reanalyze: (input: DecisionGetWorkspaceInput) => Promise<DecisionWorkspace>;
440+
requestConsultation: (input: DecisionRequestConsultationInput) => Promise<DecisionWorkspace>;
441+
respondConsultation: (input: DecisionRespondConsultationInput) => Promise<DecisionWorkspace>;
442+
executeRecommendation: (
443+
input: DecisionExecuteRecommendationInput,
444+
) => Promise<DecisionExecutionResult>;
445+
onUpdated: (callback: (payload: DecisionUpdatedPayload) => void) => () => void;
446+
};
425447
skills: {
426448
list: (input?: SkillListInput) => Promise<SkillListResult>;
427449
catalog: (input?: SkillCatalogInput) => Promise<SkillCatalogResult>;

0 commit comments

Comments
 (0)