aiki-cli 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +99 -7
- package/README.md +109 -30
- package/dist/bench/arms.js +10 -9
- package/dist/bench/harness.js +13 -10
- package/dist/bench/idea-lane-rotation.js +237 -0
- package/dist/bench/idea-v3-bench.js +506 -0
- package/dist/bench/idea-v3-rating.js +582 -0
- package/dist/bench/results.js +10 -3
- package/dist/bench/scoring/decision-insights.js +112 -0
- package/dist/bench/scoring/seeded-bugs.js +4 -0
- package/dist/cli/bench.js +180 -3
- package/dist/cli/doctor.js +56 -24
- package/dist/cli/index.js +32 -7
- package/dist/cli/resolve.js +7 -6
- package/dist/cli/resume.js +18 -0
- package/dist/cli/run.js +63 -8
- package/dist/cli/version.js +8 -0
- package/dist/council/view.js +378 -109
- package/dist/orchestration/calculations.js +97 -0
- package/dist/orchestration/context.js +37 -9
- package/dist/orchestration/decision-dossier.js +262 -0
- package/dist/orchestration/decision-graph.js +256 -0
- package/dist/orchestration/engine.js +5 -2
- package/dist/orchestration/evidence-pack.js +46 -0
- package/dist/orchestration/idea-lanes.js +20 -0
- package/dist/orchestration/jsonStage.js +72 -0
- package/dist/orchestration/legacy-idea-adapter.js +102 -0
- package/dist/orchestration/modes.js +33 -0
- package/dist/orchestration/preflight.js +183 -0
- package/dist/orchestration/quick-analysis.js +81 -0
- package/dist/orchestration/stages/cr-ladder.js +80 -0
- package/dist/orchestration/stages/s10-render.js +562 -150
- package/dist/orchestration/stages/s4-analyze.js +12 -7
- package/dist/orchestration/stages/s5-drift.js +9 -9
- package/dist/orchestration/stages/s6-positions.js +10 -0
- package/dist/orchestration/stages/s7-decision-graph.js +76 -0
- package/dist/orchestration/stages/s8-verify.js +153 -46
- package/dist/orchestration/stages/s8b-rebuttal.js +208 -0
- package/dist/orchestration/stages/s9-judge.js +329 -108
- package/dist/orchestration/stages/s9b-plan.js +85 -75
- package/dist/providers/codex.js +2 -1
- package/dist/providers/spawn.js +5 -0
- package/dist/schemas/index.js +572 -13
- package/dist/skills/idea-refinement/analyst.md +18 -14
- package/dist/skills/idea-refinement/economics-delivery.md +7 -0
- package/dist/skills/idea-refinement/market-adoption.md +7 -0
- package/dist/storage/runs.js +11 -4
- package/dist/tui/app.js +37 -13
- package/dist/tui/format.js +4 -5
- package/dist/tui/smart-entry.js +17 -1
- package/dist/tui/timeline.js +2 -4
- package/dist/workflows/code-review.js +4 -2
- package/dist/workflows/idea-refinement.js +110 -46
- package/package.json +12 -4
- package/dist/orchestration/stages/s0-grill.js +0 -79
- package/dist/orchestration/stages/s1-intent.js +0 -25
- package/dist/orchestration/stages/s2-misread.js +0 -76
- package/dist/orchestration/stages/s3-prompts.js +0 -55
- package/dist/orchestration/stages/s6-claims.js +0 -56
- package/dist/orchestration/stages/s7-disagreement.js +0 -134
package/dist/schemas/index.js
CHANGED
|
@@ -17,6 +17,15 @@ export const ProviderIdSchema = z.enum(['claude', 'codex', 'agy']);
|
|
|
17
17
|
export const TaskTypeSchema = z.enum(['idea-refinement', 'code-review', 'other']);
|
|
18
18
|
/** The two runnable v1 workflows (§12). Discriminates RoleOutput and tags RunMeta. */
|
|
19
19
|
export const WorkflowIdSchema = z.enum(['idea-refinement', 'code-review']);
|
|
20
|
+
/** Explicit user-selected idea protocol. There is deliberately no learned mode router. */
|
|
21
|
+
export const IdeaModeSchema = z.enum(['quick', 'council', 'research']);
|
|
22
|
+
export const DomainDimension = z
|
|
23
|
+
.object({
|
|
24
|
+
id: z.string().regex(/^D[1-5]$/),
|
|
25
|
+
label: z.string().min(1),
|
|
26
|
+
rationale: z.string().min(1),
|
|
27
|
+
})
|
|
28
|
+
.strict();
|
|
20
29
|
// ── S1: IntentContract (§13) ────────────────────────────────────────────────
|
|
21
30
|
export const IntentContract = z
|
|
22
31
|
.object({
|
|
@@ -25,8 +34,21 @@ export const IntentContract = z
|
|
|
25
34
|
constraints: z.array(z.string()), // explicit constraints the user stated (may be empty)
|
|
26
35
|
unknowns: z.array(z.string()), // things the request leaves unspecified
|
|
27
36
|
success_criteria: z.array(z.string()), // what a good final output must contain
|
|
37
|
+
domain_dimensions: z.array(DomainDimension).min(3).max(5).optional(), // required by the idea preflight; optional for old/code-review contracts
|
|
28
38
|
})
|
|
29
39
|
.strict();
|
|
40
|
+
/** R6 decision contract: the two-view preflight's single downstream boundary. */
|
|
41
|
+
export const DecisionContract = IntentContract.extend({
|
|
42
|
+
alternatives: z.array(z.string().min(1)).max(8),
|
|
43
|
+
success_bar: z.string().min(1),
|
|
44
|
+
evidence_supplied: z.array(z.string().min(1)).max(12),
|
|
45
|
+
missing_evidence: z.array(z.string().min(1)).max(12),
|
|
46
|
+
core_rubric: z.array(z.string().min(1)).min(1),
|
|
47
|
+
user_confirmed: z.boolean(),
|
|
48
|
+
confirmation: z.enum(['user-confirmed', 'headless-defaulted']),
|
|
49
|
+
}).strict();
|
|
50
|
+
/** Code review and old idea runs keep the smaller v1 contract. */
|
|
51
|
+
export const DecisionContractArtifact = z.union([DecisionContract, IntentContract]);
|
|
30
52
|
// ── S2: Interpretation — per provider (§13) ─────────────────────────────────
|
|
31
53
|
export const Interpretation = z
|
|
32
54
|
.object({
|
|
@@ -65,6 +87,7 @@ const RunBriefDraftBase = z
|
|
|
65
87
|
claims_to_test: z.array(z.string().min(1)).max(8),
|
|
66
88
|
evidence_supplied: z.array(z.string().min(1)).max(8),
|
|
67
89
|
missing_axes: z.array(z.string().min(1)).max(8),
|
|
90
|
+
domain_dimensions: z.array(DomainDimension).min(3).max(5),
|
|
68
91
|
questions: z.array(RunBriefQuestion).min(3).max(4),
|
|
69
92
|
})
|
|
70
93
|
.strict();
|
|
@@ -77,7 +100,19 @@ function checkQuestionIds(questions, ctx) {
|
|
|
77
100
|
seen.add(q.id);
|
|
78
101
|
}
|
|
79
102
|
}
|
|
80
|
-
|
|
103
|
+
function checkDomainDimensionIds(dimensions, ctx) {
|
|
104
|
+
const seen = new Set();
|
|
105
|
+
for (const dimension of dimensions) {
|
|
106
|
+
if (seen.has(dimension.id)) {
|
|
107
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ['domain_dimensions'], message: `duplicate domain dimension id: ${dimension.id}` });
|
|
108
|
+
}
|
|
109
|
+
seen.add(dimension.id);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
export const RunBriefDraft = RunBriefDraftBase.superRefine((brief, ctx) => {
|
|
113
|
+
checkQuestionIds(brief.questions, ctx);
|
|
114
|
+
checkDomainDimensionIds(brief.domain_dimensions, ctx);
|
|
115
|
+
});
|
|
81
116
|
export const GrillAnswer = z
|
|
82
117
|
.object({
|
|
83
118
|
question_id: z.string().min(1),
|
|
@@ -89,6 +124,7 @@ export const RunBrief = RunBriefDraftBase.extend({
|
|
|
89
124
|
answers: z.array(GrillAnswer).min(3).max(4),
|
|
90
125
|
}).superRefine((brief, ctx) => {
|
|
91
126
|
checkQuestionIds(brief.questions, ctx);
|
|
127
|
+
checkDomainDimensionIds(brief.domain_dimensions, ctx);
|
|
92
128
|
const questionIds = new Set(brief.questions.map((q) => q.id));
|
|
93
129
|
const answerIds = new Set();
|
|
94
130
|
for (const answer of brief.answers) {
|
|
@@ -106,6 +142,34 @@ export const RunBrief = RunBriefDraftBase.extend({
|
|
|
106
142
|
}
|
|
107
143
|
}
|
|
108
144
|
});
|
|
145
|
+
/** One of the two independent R6 preflight readings. */
|
|
146
|
+
export const PreflightReading = z.object({
|
|
147
|
+
subject: z.string().min(1),
|
|
148
|
+
interpretation: z.string().min(1),
|
|
149
|
+
normalized_decision: z.string().min(1),
|
|
150
|
+
alternatives: z.array(z.string().min(1)).max(8),
|
|
151
|
+
target_user: z.string().min(1).nullable(),
|
|
152
|
+
constraints: z.array(z.string().min(1)).max(10),
|
|
153
|
+
success_bar: z.string().min(1),
|
|
154
|
+
success_criteria: z.array(z.string().min(1)).max(8),
|
|
155
|
+
claims_to_test: z.array(z.string().min(1)).max(8),
|
|
156
|
+
evidence_supplied: z.array(z.string().min(1)).max(8),
|
|
157
|
+
missing_evidence: z.array(z.string().min(1)).max(8),
|
|
158
|
+
domain_dimensions: z.array(DomainDimension).min(3).max(5),
|
|
159
|
+
questions: z.array(RunBriefQuestion).min(3).max(4),
|
|
160
|
+
}).strict().superRefine((reading, ctx) => {
|
|
161
|
+
checkQuestionIds(reading.questions, ctx);
|
|
162
|
+
checkDomainDimensionIds(reading.domain_dimensions, ctx);
|
|
163
|
+
});
|
|
164
|
+
export const PreflightArtifact = z.object({
|
|
165
|
+
readings: z.array(z.object({ provider: ProviderIdSchema, reading: PreflightReading }).strict()).min(1).max(2),
|
|
166
|
+
clusters: z.array(z.object({ members: z.array(z.string()), representative: z.string().min(1) }).strict()),
|
|
167
|
+
chosen: z.object({
|
|
168
|
+
interpretation: z.string().min(1),
|
|
169
|
+
how: z.enum(['single-cluster', 'majority-cluster', 'user-selected', 'user-combined', 'user-typed']),
|
|
170
|
+
}).strict(),
|
|
171
|
+
dropped: z.array(z.object({ provider: ProviderIdSchema, error: z.string().min(1) }).strict()),
|
|
172
|
+
}).strict();
|
|
109
173
|
// ── S4: RoleOutput — workflow-discriminated union (§12, §13) ─────────────────
|
|
110
174
|
//
|
|
111
175
|
// The model output (§13) does NOT carry a `workflow` field; the engine injects the discriminator
|
|
@@ -126,7 +190,7 @@ const Attack = z
|
|
|
126
190
|
severity: z.enum(['HIGH', 'MED', 'LOW']),
|
|
127
191
|
})
|
|
128
192
|
.strict();
|
|
129
|
-
export const
|
|
193
|
+
export const LegacyIdeaRoleOutput = z
|
|
130
194
|
.object({
|
|
131
195
|
workflow: z.literal('idea-refinement'),
|
|
132
196
|
task_echo: z.string().min(1), // ≤2 sentence restatement (drift check, S5)
|
|
@@ -136,6 +200,174 @@ export const IdeaRoleOutput = z
|
|
|
136
200
|
open_questions: z.array(z.string()).max(5),
|
|
137
201
|
})
|
|
138
202
|
.strict();
|
|
203
|
+
export const ClaimPosition = z
|
|
204
|
+
.object({
|
|
205
|
+
local_id: z.string().min(1),
|
|
206
|
+
proposition: z.string().min(1),
|
|
207
|
+
dimension_id: z.string().min(1),
|
|
208
|
+
stance: z.enum(['SUPPORT', 'OPPOSE', 'MIXED', 'UNKNOWN']),
|
|
209
|
+
basis: z.enum(['EVIDENCE', 'INFERENCE', 'ASSUMPTION']),
|
|
210
|
+
load_bearing: z.boolean(),
|
|
211
|
+
if_false: z.enum(['STOP', 'PIVOT', 'CONDITION', 'MINOR']),
|
|
212
|
+
reasoning: z.string().min(1),
|
|
213
|
+
evidence_ids: z.array(z.string().min(1)),
|
|
214
|
+
depends_on: z.array(z.string().min(1)),
|
|
215
|
+
})
|
|
216
|
+
.strict();
|
|
217
|
+
const EvidenceCardBase = z
|
|
218
|
+
.object({
|
|
219
|
+
id: z.string().min(1),
|
|
220
|
+
claim_supported: z.string().min(1),
|
|
221
|
+
source_kind: z.enum(['USER', 'PRIMARY', 'SECONDARY', 'MODEL_KNOWLEDGE']),
|
|
222
|
+
title: z.string().min(1).optional(),
|
|
223
|
+
url: z.string().url().optional(),
|
|
224
|
+
published_at: z.string().min(1).optional(),
|
|
225
|
+
accessed_at: z.string().min(1).optional(),
|
|
226
|
+
locator: z.string().min(1).optional(),
|
|
227
|
+
support: z.enum(['SUPPORTS', 'CONTRADICTS', 'CONTEXT_ONLY']),
|
|
228
|
+
freshness: z.enum(['CURRENT', 'DATED', 'UNKNOWN']),
|
|
229
|
+
})
|
|
230
|
+
.strict();
|
|
231
|
+
function checkEvidenceCard(card, ctx) {
|
|
232
|
+
const external = card.source_kind === 'PRIMARY' || card.source_kind === 'SECONDARY';
|
|
233
|
+
if (external && !card.url && !card.locator) {
|
|
234
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ['locator'], message: `${card.source_kind} evidence requires a URL or locator` });
|
|
235
|
+
}
|
|
236
|
+
if (external && card.freshness === 'CURRENT' && !card.accessed_at) {
|
|
237
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ['accessed_at'], message: 'current external evidence requires accessed_at' });
|
|
238
|
+
}
|
|
239
|
+
if (card.source_kind === 'MODEL_KNOWLEDGE' && card.freshness === 'CURRENT') {
|
|
240
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ['freshness'], message: 'model knowledge cannot claim current freshness' });
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
export const EvidenceCard = EvidenceCardBase.superRefine(checkEvidenceCard);
|
|
244
|
+
export const CalculationInput = z.object({
|
|
245
|
+
id: z.string().min(1),
|
|
246
|
+
name: z.string().min(1),
|
|
247
|
+
value: z.number().finite(),
|
|
248
|
+
unit: z.string().min(1),
|
|
249
|
+
evidence_ids: z.array(z.string().min(1)).min(1),
|
|
250
|
+
}).strict();
|
|
251
|
+
export const CalculationStep = z.object({
|
|
252
|
+
id: z.string().min(1),
|
|
253
|
+
operation: z.enum(['ADD', 'SUBTRACT', 'MULTIPLY', 'DIVIDE']),
|
|
254
|
+
left: z.string().min(1),
|
|
255
|
+
right: z.string().min(1),
|
|
256
|
+
result: z.number().finite(),
|
|
257
|
+
unit: z.string().min(1),
|
|
258
|
+
}).strict();
|
|
259
|
+
export const CalculationLedger = z.object({
|
|
260
|
+
id: z.string().min(1),
|
|
261
|
+
claim_id: z.string().min(1),
|
|
262
|
+
inputs: z.array(CalculationInput).min(1).max(12),
|
|
263
|
+
steps: z.array(CalculationStep).min(1).max(12),
|
|
264
|
+
result_step: z.string().min(1),
|
|
265
|
+
}).strict();
|
|
266
|
+
// Live 20260714-2142 killed both seats on shape ceremony the prompt never promised: rationale on
|
|
267
|
+
// COVERED entries and a question id have no consumer, so the schema now matches the documented
|
|
268
|
+
// either/or shape (NOT_APPLICABLE still requires its rationale).
|
|
269
|
+
const CoverageEntryBase = z
|
|
270
|
+
.object({
|
|
271
|
+
dimension_id: z.string().min(1),
|
|
272
|
+
status: z.enum(['COVERED', 'NOT_APPLICABLE']),
|
|
273
|
+
position_ids: z.array(z.string().min(1)).default([]),
|
|
274
|
+
rationale: z.string().min(1).optional(),
|
|
275
|
+
})
|
|
276
|
+
.strict();
|
|
277
|
+
function checkCoverageEntry(entry, ctx) {
|
|
278
|
+
if (entry.status === 'NOT_APPLICABLE' && !entry.rationale) {
|
|
279
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ['rationale'], message: 'NOT_APPLICABLE coverage requires a rationale' });
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
export const CoverageEntry = CoverageEntryBase.superRefine(checkCoverageEntry);
|
|
283
|
+
export const DecisionQuestion = z
|
|
284
|
+
.object({
|
|
285
|
+
id: z.string().min(1).optional(),
|
|
286
|
+
question: z.string().min(1),
|
|
287
|
+
claim_ids: z.array(z.string().min(1)),
|
|
288
|
+
})
|
|
289
|
+
.strict();
|
|
290
|
+
const IdeaRoleOutputBase = z
|
|
291
|
+
.object({
|
|
292
|
+
workflow: z.literal('idea-refinement'),
|
|
293
|
+
task_echo: z.string().min(1),
|
|
294
|
+
strongest_version: z.string().min(1),
|
|
295
|
+
positions: z.array(ClaimPosition).max(12),
|
|
296
|
+
evidence: z.array(EvidenceCard).max(20),
|
|
297
|
+
calculations: z.array(CalculationLedger).max(8).default([]),
|
|
298
|
+
coverage: z.array(CoverageEntry).max(18), // 13 core + up to 5 preflight domain dimensions
|
|
299
|
+
decision_questions: z.array(DecisionQuestion).max(8),
|
|
300
|
+
})
|
|
301
|
+
.strict();
|
|
302
|
+
function checkSubmissionRefs(submission, ctx) {
|
|
303
|
+
const positionIds = new Set();
|
|
304
|
+
for (const [index, position] of submission.positions.entries()) {
|
|
305
|
+
if (positionIds.has(position.local_id))
|
|
306
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ['positions', index, 'local_id'], message: `duplicate position id: ${position.local_id}` });
|
|
307
|
+
positionIds.add(position.local_id);
|
|
308
|
+
}
|
|
309
|
+
const evidenceIds = new Set();
|
|
310
|
+
for (const [index, evidence] of submission.evidence.entries()) {
|
|
311
|
+
if (evidenceIds.has(evidence.id))
|
|
312
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ['evidence', index, 'id'], message: `duplicate evidence id: ${evidence.id}` });
|
|
313
|
+
evidenceIds.add(evidence.id);
|
|
314
|
+
}
|
|
315
|
+
const calculationIds = new Set();
|
|
316
|
+
for (const [index, calculation] of submission.calculations.entries()) {
|
|
317
|
+
if (calculationIds.has(calculation.id))
|
|
318
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ['calculations', index, 'id'], message: `duplicate calculation id: ${calculation.id}` });
|
|
319
|
+
calculationIds.add(calculation.id);
|
|
320
|
+
if (!positionIds.has(calculation.claim_id))
|
|
321
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ['calculations', index, 'claim_id'], message: `unknown position id: ${calculation.claim_id}` });
|
|
322
|
+
const refs = new Set(calculation.inputs.map((input) => input.id));
|
|
323
|
+
const inputIds = new Set();
|
|
324
|
+
const stepIds = new Set();
|
|
325
|
+
for (const [inputIndex, input] of calculation.inputs.entries()) {
|
|
326
|
+
if (inputIds.has(input.id))
|
|
327
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ['calculations', index, 'inputs', inputIndex, 'id'], message: `duplicate calculation input: ${input.id}` });
|
|
328
|
+
inputIds.add(input.id);
|
|
329
|
+
for (const evidenceId of input.evidence_ids) {
|
|
330
|
+
if (!evidenceIds.has(evidenceId))
|
|
331
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ['calculations', index, 'inputs', inputIndex, 'evidence_ids'], message: `unknown evidence id: ${evidenceId}` });
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
for (const [stepIndex, step] of calculation.steps.entries()) {
|
|
335
|
+
if (!refs.has(step.left))
|
|
336
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ['calculations', index, 'steps', stepIndex, 'left'], message: `unknown or forward calculation reference: ${step.left}` });
|
|
337
|
+
if (!refs.has(step.right))
|
|
338
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ['calculations', index, 'steps', stepIndex, 'right'], message: `unknown or forward calculation reference: ${step.right}` });
|
|
339
|
+
if (refs.has(step.id))
|
|
340
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ['calculations', index, 'steps', stepIndex, 'id'], message: `duplicate calculation reference: ${step.id}` });
|
|
341
|
+
refs.add(step.id);
|
|
342
|
+
stepIds.add(step.id);
|
|
343
|
+
}
|
|
344
|
+
if (!stepIds.has(calculation.result_step))
|
|
345
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ['calculations', index, 'result_step'], message: `unknown result step: ${calculation.result_step}` });
|
|
346
|
+
}
|
|
347
|
+
for (const [index, position] of submission.positions.entries()) {
|
|
348
|
+
for (const id of position.evidence_ids) {
|
|
349
|
+
if (!evidenceIds.has(id))
|
|
350
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ['positions', index, 'evidence_ids'], message: `unknown evidence id: ${id}` });
|
|
351
|
+
}
|
|
352
|
+
for (const id of position.depends_on) {
|
|
353
|
+
if (!positionIds.has(id))
|
|
354
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ['positions', index, 'depends_on'], message: `unknown position id: ${id}` });
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
for (const [index, entry] of submission.coverage.entries()) {
|
|
358
|
+
for (const id of entry.position_ids) {
|
|
359
|
+
if (!positionIds.has(id))
|
|
360
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ['coverage', index, 'position_ids'], message: `unknown position id: ${id}` });
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
for (const [index, question] of submission.decision_questions.entries()) {
|
|
364
|
+
for (const id of question.claim_ids) {
|
|
365
|
+
if (!positionIds.has(id))
|
|
366
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ['decision_questions', index, 'claim_ids'], message: `unknown position id: ${id}` });
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
export const IdeaRoleOutput = IdeaRoleOutputBase.superRefine(checkSubmissionRefs);
|
|
139
371
|
/** Defect categories a finding (and a seeded bug, T11) can carry. BENCHMARK.md's "defect class" match
|
|
140
372
|
* is equality on this enum (off-by-one→CORRECTNESS, race→CONCURRENCY, unhandled-rejection→ERROR_HANDLING,
|
|
141
373
|
* auth-gap→SECURITY, N+1→PERF). */
|
|
@@ -161,16 +393,123 @@ export const CodeReviewRoleOutput = z
|
|
|
161
393
|
findings: z.array(Finding).max(12),
|
|
162
394
|
})
|
|
163
395
|
.strict();
|
|
164
|
-
export const RoleOutput = z.
|
|
396
|
+
export const RoleOutput = z.union([IdeaRoleOutput, CodeReviewRoleOutput]);
|
|
165
397
|
/** The exact JSON a code-review S4 reviewer returns: `CodeReviewRoleOutput` WITHOUT the `workflow`
|
|
166
398
|
* discriminator (§13 — model output carries no `workflow`). Mirrors `IdeaRoleOutputModel` (T6); S4
|
|
167
399
|
* validates the raw call against this, injects `workflow`, then persists as `RoleOutput` (T10). */
|
|
168
400
|
export const CodeReviewRoleOutputModel = CodeReviewRoleOutput.omit({ workflow: true });
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
401
|
+
const StrictIdeaRoleOutputModel = IdeaRoleOutputBase.omit({ workflow: true }).superRefine((submission, ctx) => checkSubmissionRefs({ workflow: 'idea-refinement', ...submission }, ctx));
|
|
402
|
+
/** Case-insensitive match to an exact canonical enum word (plus known aliases); prose never matches. */
|
|
403
|
+
function canonicalEnum(value, canon, aliases = {}) {
|
|
404
|
+
if (typeof value !== 'string')
|
|
405
|
+
return value;
|
|
406
|
+
const upper = value.toUpperCase();
|
|
407
|
+
const mapped = aliases[upper] ?? upper;
|
|
408
|
+
return canon.includes(mapped) ? mapped : value;
|
|
409
|
+
}
|
|
410
|
+
/** Match a leading canonical/alias token with trailing prose ("OPPOSES: <reason>"), the observed
|
|
411
|
+
* 20260714-2142 codex vocabulary. The FIRST word must itself be the token; free prose whose first
|
|
412
|
+
* word is not a known token still never matches. */
|
|
413
|
+
function canonicalEnumLeadingToken(value, canon, aliases = {}) {
|
|
414
|
+
if (typeof value !== 'string')
|
|
415
|
+
return value;
|
|
416
|
+
const token = value.trim().match(/^[A-Za-z_]+/)?.[0]?.toUpperCase();
|
|
417
|
+
if (!token)
|
|
418
|
+
return value;
|
|
419
|
+
const mapped = aliases[token] ?? token;
|
|
420
|
+
return canon.includes(mapped) ? mapped : value;
|
|
421
|
+
}
|
|
422
|
+
/** Canonicalize enum spellings observed in live provider repairs (SUPPORT, current, Current);
|
|
423
|
+
* anything that is not the exact word in some casing stays invalid. */
|
|
424
|
+
function canonicalizeIdeaRoleOutputModel(input) {
|
|
425
|
+
if (!input || typeof input !== 'object' || Array.isArray(input))
|
|
426
|
+
return input;
|
|
427
|
+
const output = input;
|
|
428
|
+
if (!Array.isArray(output.evidence))
|
|
429
|
+
return input;
|
|
430
|
+
return {
|
|
431
|
+
...output,
|
|
432
|
+
evidence: output.evidence.map((item) => {
|
|
433
|
+
if (!item || typeof item !== 'object' || Array.isArray(item))
|
|
434
|
+
return item;
|
|
435
|
+
const evidence = item;
|
|
436
|
+
return {
|
|
437
|
+
...evidence,
|
|
438
|
+
support: canonicalEnumLeadingToken(evidence.support, ['SUPPORTS', 'CONTRADICTS', 'CONTEXT_ONLY'], { SUPPORT: 'SUPPORTS', OPPOSES: 'CONTRADICTS', OPPOSE: 'CONTRADICTS' }),
|
|
439
|
+
freshness: canonicalEnum(evidence.freshness, ['CURRENT', 'DATED', 'UNKNOWN']),
|
|
440
|
+
};
|
|
441
|
+
}),
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
/** Deterministic last resort after a failed §14 repair. Three live failures shaped it:
|
|
445
|
+
* run 20260712-0011 wrote evidence prose into `support`; run 20260713-1503 added an unknown `content`
|
|
446
|
+
* key to every card and leaked `MODEL_KNOWLEDGE` into a position's `basis`; run 20260714-2142 killed
|
|
447
|
+
* BOTH seats via an over-cap calculation ledger and coverage/question entries the old salvage never
|
|
448
|
+
* touched. Policy, strictly deterministic (never invents a value):
|
|
449
|
+
* - unknown extra keys are stripped (zod `.strip()` re-parse);
|
|
450
|
+
* - an evidence card that still fails is dropped, and its id scrubbed from positions;
|
|
451
|
+
* - a position that still fails is dropped, and its id scrubbed from depends_on / coverage /
|
|
452
|
+
* decision_questions — one bad position costs one position, not the whole seat;
|
|
453
|
+
* - a calculation that still fails, or whose position/evidence anchors were dropped, is dropped;
|
|
454
|
+
* the survivors are truncated to the schema cap in order (one bad ledger costs one ledger);
|
|
455
|
+
* - a coverage entry or decision question that still fails is dropped (a dropped coverage entry
|
|
456
|
+
* becomes a visible structural hole downstream, never a dead seat);
|
|
457
|
+
* - a seat with NO surviving position stays a hard failure (nothing to analyze). */
|
|
458
|
+
export function salvageIdeaRoleOutputModel(input) {
|
|
459
|
+
const canonical = canonicalizeIdeaRoleOutputModel(input);
|
|
460
|
+
if (!canonical || typeof canonical !== 'object' || Array.isArray(canonical))
|
|
461
|
+
return canonical;
|
|
462
|
+
const output = canonical;
|
|
463
|
+
if (!Array.isArray(output.evidence) || !Array.isArray(output.positions))
|
|
464
|
+
return canonical;
|
|
465
|
+
const evidence = output.evidence
|
|
466
|
+
.map((item) => EvidenceCardBase.strip().superRefine(checkEvidenceCard).safeParse(item))
|
|
467
|
+
.flatMap((result) => (result.success ? [result.data] : []));
|
|
468
|
+
const keptEvidence = new Set(evidence.map((card) => card.id));
|
|
469
|
+
const parsedPositions = output.positions
|
|
470
|
+
.map((item) => ClaimPosition.strip().safeParse(item))
|
|
471
|
+
.flatMap((result) => (result.success ? [result.data] : []));
|
|
472
|
+
if (parsedPositions.length === 0)
|
|
473
|
+
return canonical; // empty claim set → let strict validation fail it
|
|
474
|
+
const keptPositions = new Set(parsedPositions.map((position) => position.local_id));
|
|
475
|
+
const positions = parsedPositions.map((position) => ({
|
|
476
|
+
...position,
|
|
477
|
+
evidence_ids: position.evidence_ids.filter((id) => keptEvidence.has(id)),
|
|
478
|
+
depends_on: position.depends_on.filter((id) => keptPositions.has(id)),
|
|
479
|
+
}));
|
|
480
|
+
const scrubIds = (value, key) => {
|
|
481
|
+
if (!Array.isArray(value))
|
|
482
|
+
return value;
|
|
483
|
+
return value.map((item) => {
|
|
484
|
+
if (!item || typeof item !== 'object' || Array.isArray(item))
|
|
485
|
+
return item;
|
|
486
|
+
const entry = item;
|
|
487
|
+
if (!Array.isArray(entry[key]))
|
|
488
|
+
return item;
|
|
489
|
+
return { ...entry, [key]: entry[key].filter((id) => keptPositions.has(id)) };
|
|
490
|
+
});
|
|
491
|
+
};
|
|
492
|
+
const parseOrDrop = (value, parse) => Array.isArray(value)
|
|
493
|
+
? value.map((item) => parse(item)).flatMap((result) => (result.success ? [result.data] : []))
|
|
494
|
+
: value;
|
|
495
|
+
const calculations = Array.isArray(output.calculations)
|
|
496
|
+
? parseOrDrop(output.calculations, (item) => CalculationLedger.strip().safeParse(item))
|
|
497
|
+
.filter((calc) => keptPositions.has(calc.claim_id)
|
|
498
|
+
&& calc.inputs.every((input) => input.evidence_ids.every((id) => keptEvidence.has(id))))
|
|
499
|
+
.slice(0, 8)
|
|
500
|
+
: output.calculations;
|
|
501
|
+
return {
|
|
502
|
+
...output,
|
|
503
|
+
evidence,
|
|
504
|
+
positions,
|
|
505
|
+
calculations,
|
|
506
|
+
coverage: parseOrDrop(scrubIds(output.coverage, 'position_ids'), (item) => CoverageEntryBase.strip().superRefine(checkCoverageEntry).safeParse(item)),
|
|
507
|
+
decision_questions: parseOrDrop(scrubIds(output.decision_questions, 'claim_ids'), (item) => DecisionQuestion.strip().safeParse(item)),
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
/** The model-facing S4 shape. Exact known enum aliases are canonicalized, then the strict schema validates
|
|
511
|
+
* the full output; persisted `IdeaRoleOutput` remains canonical-only. */
|
|
512
|
+
export const IdeaRoleOutputModel = z.preprocess(canonicalizeIdeaRoleOutputModel, StrictIdeaRoleOutputModel);
|
|
174
513
|
// ── S3: StagePrompts (§9, §13) ──────────────────────────────────────────────
|
|
175
514
|
//
|
|
176
515
|
// S3 output: the role-specific S4 prompts with every {{SLOT}} filled. Deterministic validator
|
|
@@ -191,10 +530,59 @@ export const ClaimGroups = z
|
|
|
191
530
|
groups: z.array(z.array(z.string().min(1)).min(2)),
|
|
192
531
|
})
|
|
193
532
|
.strict();
|
|
533
|
+
// ── R2: typed decision graph ────────────────────────────────────────────────
|
|
534
|
+
const GraphPosition = ClaimPosition.extend({
|
|
535
|
+
id: z.string().min(1),
|
|
536
|
+
provider: ProviderIdSchema,
|
|
537
|
+
source_id: z.string().min(1),
|
|
538
|
+
});
|
|
539
|
+
const GraphEvidence = EvidenceCardBase.extend({
|
|
540
|
+
id: z.string().min(1),
|
|
541
|
+
provider: ProviderIdSchema,
|
|
542
|
+
source_id: z.string().min(1),
|
|
543
|
+
}).superRefine(checkEvidenceCard);
|
|
544
|
+
const GraphCalculation = CalculationLedger.extend({
|
|
545
|
+
id: z.string().min(1),
|
|
546
|
+
claim_id: z.string().min(1),
|
|
547
|
+
provider: ProviderIdSchema,
|
|
548
|
+
source_id: z.string().min(1),
|
|
549
|
+
});
|
|
550
|
+
export const CalculationCheck = z.object({
|
|
551
|
+
calculation_id: z.string().min(1),
|
|
552
|
+
claim_id: z.string().min(1),
|
|
553
|
+
status: z.enum(['PASS', 'FAIL']),
|
|
554
|
+
issues: z.array(z.string()),
|
|
555
|
+
}).strict();
|
|
556
|
+
export const DecisionClaim = z.object({
|
|
557
|
+
id: z.string().min(1),
|
|
558
|
+
proposition: z.string().min(1),
|
|
559
|
+
position_ids: z.array(z.string().min(1)).min(1),
|
|
560
|
+
state: z.enum(['CONSENSUS', 'SHARED_CONCERN', 'DISAGREEMENT', 'UNIQUE', 'UNCERTAIN']),
|
|
561
|
+
evidence_state: z.enum(['SUPPORTED', 'CONFLICTED', 'UNVERIFIED']),
|
|
562
|
+
load_bearing: z.boolean(),
|
|
563
|
+
if_false: z.enum(['STOP', 'PIVOT', 'CONDITION', 'MINOR']),
|
|
564
|
+
sensitivity: z.enum(['DECISIVE', 'MATERIAL', 'LOW']),
|
|
565
|
+
});
|
|
566
|
+
export const DecisionGraph = z.object({
|
|
567
|
+
positions: z.array(GraphPosition),
|
|
568
|
+
evidence: z.array(GraphEvidence),
|
|
569
|
+
calculations: z.array(GraphCalculation).default([]),
|
|
570
|
+
calculation_checks: z.array(CalculationCheck).default([]),
|
|
571
|
+
claims: z.array(DecisionClaim),
|
|
572
|
+
edges: z.array(z.object({
|
|
573
|
+
from: z.string().min(1),
|
|
574
|
+
to: z.string().min(1),
|
|
575
|
+
type: z.enum(['DEPENDS_ON', 'SUPPORTS', 'ATTACKS', 'CONTRADICTS']),
|
|
576
|
+
})),
|
|
577
|
+
holes: z.object({
|
|
578
|
+
coverage: z.array(z.object({ dimension_id: z.string().min(1), label: z.string().min(1) })),
|
|
579
|
+
evidence: z.array(z.object({ claim_id: z.string().min(1), reason: z.string().min(1) })),
|
|
580
|
+
}),
|
|
581
|
+
});
|
|
194
582
|
// ── S8: Verification (§13) ──────────────────────────────────────────────────
|
|
195
583
|
//
|
|
196
584
|
// `Verification` is the per-item verdict (§9 "per-item Verification"). `VerificationSet` is the
|
|
197
|
-
// actual S8 stage output
|
|
585
|
+
// actual S8 stage output. Each item is judged independently; no verdict distribution is required.
|
|
198
586
|
export const Verification = z
|
|
199
587
|
.object({
|
|
200
588
|
target_id: z.string().min(1),
|
|
@@ -206,10 +594,65 @@ export const Verification = z
|
|
|
206
594
|
export const VerificationSet = z
|
|
207
595
|
.object({
|
|
208
596
|
verifications: z.array(Verification),
|
|
209
|
-
//
|
|
597
|
+
// Accepted for backward compatibility with pre-R1 artifacts; the R1 prompt no longer requests it.
|
|
210
598
|
all_confirmed_justification: z.string().optional(),
|
|
211
599
|
})
|
|
212
600
|
.strict();
|
|
601
|
+
export const ClaimVerification = z.object({
|
|
602
|
+
claim_id: z.string().min(1),
|
|
603
|
+
status: z.enum(['VERIFIED', 'PARTIAL', 'CONTRADICTED', 'UNVERIFIABLE']),
|
|
604
|
+
reasoning: z.string().min(1),
|
|
605
|
+
evidence_ids: z.array(z.string().min(1)),
|
|
606
|
+
calculation_check: z.enum(['PASS', 'FAIL', 'NOT_APPLICABLE']).optional(),
|
|
607
|
+
missing_evidence: z.array(z.string().min(1)),
|
|
608
|
+
}).strict();
|
|
609
|
+
export const ClaimVerificationSet = z.object({
|
|
610
|
+
verifications: z.array(ClaimVerification),
|
|
611
|
+
}).strict();
|
|
612
|
+
// ── R5: bounded, append-only rebuttal events ───────────────────────────────
|
|
613
|
+
const RebuttalResponseBase = z.object({
|
|
614
|
+
claim_id: z.string().min(1),
|
|
615
|
+
response: z.enum(['CONCEDE', 'COUNTER', 'NARROW', 'UNRESOLVED']),
|
|
616
|
+
reasoning: z.string().min(1),
|
|
617
|
+
evidence_ids: z.array(z.string().min(1)),
|
|
618
|
+
narrowed_proposition: z.string().min(1).optional(),
|
|
619
|
+
}).strict();
|
|
620
|
+
const checkNarrowedRebuttal = (event, ctx) => {
|
|
621
|
+
if (event.response === 'NARROW' && !event.narrowed_proposition) {
|
|
622
|
+
ctx.addIssue({
|
|
623
|
+
code: z.ZodIssueCode.custom,
|
|
624
|
+
path: ['narrowed_proposition'],
|
|
625
|
+
message: 'NARROW requires narrowed_proposition',
|
|
626
|
+
});
|
|
627
|
+
}
|
|
628
|
+
};
|
|
629
|
+
export const RebuttalResponse = RebuttalResponseBase.superRefine(checkNarrowedRebuttal);
|
|
630
|
+
/** Exact model output for one scout's single rebuttal round. */
|
|
631
|
+
export const RebuttalResponseSet = z.object({
|
|
632
|
+
events: z.array(RebuttalResponse).max(3),
|
|
633
|
+
}).strict();
|
|
634
|
+
export const RebuttalEvent = RebuttalResponseBase.extend({
|
|
635
|
+
id: z.string().min(1),
|
|
636
|
+
round: z.literal(1),
|
|
637
|
+
responder: ProviderIdSchema,
|
|
638
|
+
target_position_ids: z.array(z.string().min(1)),
|
|
639
|
+
}).superRefine(checkNarrowedRebuttal);
|
|
640
|
+
/** Persisted separately from DecisionGraph so original claims/evidence remain immutable. */
|
|
641
|
+
export const RebuttalEventSet = z.object({
|
|
642
|
+
round: z.literal(1),
|
|
643
|
+
selected_claim_ids: z.array(z.string().min(1)).max(3),
|
|
644
|
+
events: z.array(RebuttalEvent).max(6),
|
|
645
|
+
stop_reason: z.enum([
|
|
646
|
+
'NO_ESCALATIONS',
|
|
647
|
+
'NO_ELIGIBLE_SCOUT',
|
|
648
|
+
'BUDGET_RESERVED',
|
|
649
|
+
'ROUND_COMPLETE',
|
|
650
|
+
'NO_NEW_EVIDENCE',
|
|
651
|
+
'CALL_CAP_REACHED',
|
|
652
|
+
]),
|
|
653
|
+
}).strict();
|
|
654
|
+
/** Shared artifact slot: code review keeps the v1 cross-exam shape; idea refinement uses R4 claims. */
|
|
655
|
+
export const VerificationArtifact = z.union([VerificationSet, ClaimVerificationSet]);
|
|
213
656
|
// ── S7: DisagreementMap (§7, §9) ────────────────────────────────────────────
|
|
214
657
|
//
|
|
215
658
|
// The plan (§7/§9) names only the four arrays. `Claim` shape is from §6. NOTE two under-specified
|
|
@@ -280,16 +723,69 @@ const Adjudication = z
|
|
|
280
723
|
id: z.string().min(1), // disputed item id
|
|
281
724
|
ruling: z.enum(['UPHOLD', 'REJECT', 'UNRESOLVED']),
|
|
282
725
|
reasoning: z.string().min(1), // ≤3 sentences
|
|
283
|
-
evidence_cited: z.string().min(1),
|
|
726
|
+
evidence_cited: z.string().min(1).optional(), // code-review / legacy idea artifacts
|
|
727
|
+
evidence_ids: z.array(z.string().min(1)).optional(), // R4 idea chair citations, validated by reference
|
|
728
|
+
effect_on_decision: z.string().min(1).optional(), // R5 idea chair; optional for legacy/code-review artifacts
|
|
729
|
+
what_would_change_it: z.string().min(1).optional(), // required by S9 when an idea ruling is UNRESOLVED
|
|
284
730
|
})
|
|
285
|
-
.strict()
|
|
731
|
+
.strict()
|
|
732
|
+
.refine((item) => item.evidence_cited || item.evidence_ids?.length, { message: 'adjudication requires evidence' });
|
|
286
733
|
export const Recommendation = z.enum(['PROCEED', 'PROCEED_WITH_CONDITIONS', 'PIVOT', 'STOP']);
|
|
734
|
+
const DecisionSnapshotItem = z.object({
|
|
735
|
+
label: z.string().min(1),
|
|
736
|
+
value: z.string().min(1),
|
|
737
|
+
meaning: z.string().min(1),
|
|
738
|
+
claim_ids: z.array(z.string().min(1)).min(1).max(4),
|
|
739
|
+
}).strict();
|
|
740
|
+
const DecisionOption = z.object({
|
|
741
|
+
label: z.string().min(1),
|
|
742
|
+
commitment: z.string().min(1),
|
|
743
|
+
commitment_kind: z.enum(['KNOWN', 'TARGET_CAP', 'UNKNOWN']),
|
|
744
|
+
tradeoff: z.string().min(1),
|
|
745
|
+
claim_ids: z.array(z.string().min(1)).max(4),
|
|
746
|
+
}).strict().superRefine((option, ctx) => {
|
|
747
|
+
if (option.commitment_kind !== 'UNKNOWN' && option.claim_ids.length === 0) {
|
|
748
|
+
ctx.addIssue({
|
|
749
|
+
code: z.ZodIssueCode.custom,
|
|
750
|
+
path: ['claim_ids'],
|
|
751
|
+
message: 'known commitments and target caps require a graph claim anchor',
|
|
752
|
+
});
|
|
753
|
+
}
|
|
754
|
+
});
|
|
755
|
+
/** Reader-first, graph-anchored numeric comparison for decision tasks where amounts or thresholds matter. */
|
|
756
|
+
export const DecisionSnapshot = z.object({
|
|
757
|
+
decisive_numbers: z.array(DecisionSnapshotItem).min(1).max(5),
|
|
758
|
+
payback: z.object({
|
|
759
|
+
status: z.enum(['ACHIEVED', 'NOT_ACHIEVED', 'NOT_COMPUTABLE']),
|
|
760
|
+
result: z.string().min(1),
|
|
761
|
+
basis: z.string().min(1),
|
|
762
|
+
claim_ids: z.array(z.string().min(1)).min(1).max(4),
|
|
763
|
+
}).strict().optional(),
|
|
764
|
+
options: z.array(DecisionOption).min(2).max(4),
|
|
765
|
+
tripwire: z.object({
|
|
766
|
+
metric: z.string().min(1),
|
|
767
|
+
threshold: z.string().min(1),
|
|
768
|
+
decision_rule: z.string().min(1),
|
|
769
|
+
claim_ids: z.array(z.string().min(1)).min(1).max(4),
|
|
770
|
+
}).strict().optional(),
|
|
771
|
+
}).strict();
|
|
287
772
|
const JudgeReportBase = z
|
|
288
773
|
.object({
|
|
289
774
|
adjudications: z.array(Adjudication),
|
|
290
775
|
verdict: z.string().min(1), // the recommendation + core reason (idea: 2-5 sentences; grounded in adjudicated + consensus claims)
|
|
291
776
|
recommendation: Recommendation.optional(), // idea workflow; code-review omits it
|
|
292
777
|
conditions: z.array(z.string().min(1)).max(6).optional(), // present only for PROCEED_WITH_CONDITIONS
|
|
778
|
+
recommendation_claim_ids: z.array(z.string().min(1)).min(1).max(8).optional(),
|
|
779
|
+
condition_claim_ids: z.array(z.string().min(1)).min(1).max(8).optional(),
|
|
780
|
+
pivot: z.object({
|
|
781
|
+
changed_claim_id: z.string().min(1),
|
|
782
|
+
new_risk_claim_id: z.string().min(1),
|
|
783
|
+
}).strict().optional(),
|
|
784
|
+
strongest_counter_case: z.object({
|
|
785
|
+
claim_ids: z.array(z.string().min(1)).min(1).max(4),
|
|
786
|
+
reasoning: z.string().min(1),
|
|
787
|
+
}).strict().optional(),
|
|
788
|
+
decision_snapshot: DecisionSnapshot.optional(),
|
|
293
789
|
key_points: z.array(z.string()).max(10).optional(), // chairman's bulleted reasoning (idea workflow); code-review omits it
|
|
294
790
|
dissent: z.array(z.string()).min(1), // ≥1 — empty dissent is invalid (§9); strongest counter-argument
|
|
295
791
|
confidence_notes: z.string().min(1), // which conclusions are HIGH/MEDIUM/LOW and why
|
|
@@ -319,6 +815,27 @@ export const JudgeReport = JudgeReportBase.superRefine((r, ctx) => {
|
|
|
319
815
|
export const JudgeReportModel = JudgeReportBase.extend({
|
|
320
816
|
dissent: z.array(z.string()),
|
|
321
817
|
});
|
|
818
|
+
/** R5 idea-chair boundary. Persisted JudgeReport keeps its legacy adjudication names so code-review
|
|
819
|
+
* and old run readers stay compatible; S9 translates only after this exact model shape validates. */
|
|
820
|
+
export const IdeaChairRuling = z.object({
|
|
821
|
+
claim_id: z.string().min(1),
|
|
822
|
+
ruling: z.enum(['HOLDS', 'FAILS', 'UNRESOLVED']),
|
|
823
|
+
reasoning: z.string().min(1),
|
|
824
|
+
evidence_ids: z.array(z.string().min(1)).min(1),
|
|
825
|
+
effect_on_decision: z.string().min(1),
|
|
826
|
+
what_would_change_it: z.string().min(1).optional(),
|
|
827
|
+
}).strict().superRefine((ruling, ctx) => {
|
|
828
|
+
if (ruling.ruling === 'UNRESOLVED' && !ruling.what_would_change_it) {
|
|
829
|
+
ctx.addIssue({
|
|
830
|
+
code: z.ZodIssueCode.custom,
|
|
831
|
+
path: ['what_would_change_it'],
|
|
832
|
+
message: 'UNRESOLVED requires what_would_change_it',
|
|
833
|
+
});
|
|
834
|
+
}
|
|
835
|
+
});
|
|
836
|
+
export const IdeaChairReportModel = JudgeReportModel.omit({ adjudications: true }).extend({
|
|
837
|
+
adjudications: z.array(IdeaChairRuling),
|
|
838
|
+
});
|
|
322
839
|
// ── S9b: ActionPlan (idea-refinement report v3) ─────────────────────────────
|
|
323
840
|
export const ActionPlan = z
|
|
324
841
|
.object({
|
|
@@ -338,6 +855,31 @@ export const ActionPlan = z
|
|
|
338
855
|
sequencing_note: z.string().min(1),
|
|
339
856
|
})
|
|
340
857
|
.strict();
|
|
858
|
+
export const PlannerUnavailable = z.object({
|
|
859
|
+
kind: z.literal('PlannerUnavailable'),
|
|
860
|
+
reason: z.enum(['budget_exhausted', 'planner_failed']),
|
|
861
|
+
unresolved_questions: z.array(z.string().min(1)).min(1).max(10),
|
|
862
|
+
}).strict();
|
|
863
|
+
export const ActionPlanArtifact = z.union([ActionPlan, PlannerUnavailable]);
|
|
864
|
+
/** R6 quick mode: one strong analyst produces the analysis, recommendation, and plan in one call. */
|
|
865
|
+
export const QuickDecisionModel = z.object({
|
|
866
|
+
analysis: IdeaRoleOutputModel,
|
|
867
|
+
verdict: z.string().min(1),
|
|
868
|
+
recommendation: Recommendation,
|
|
869
|
+
conditions: z.array(z.string().min(1)).max(6),
|
|
870
|
+
key_points: z.array(z.string().min(1)).min(2).max(8),
|
|
871
|
+
dissent: z.array(z.string().min(1)).min(1).max(4),
|
|
872
|
+
confidence_notes: z.string().min(1),
|
|
873
|
+
action_plan: ActionPlan,
|
|
874
|
+
}).strict().superRefine((decision, ctx) => {
|
|
875
|
+
const hasConditions = decision.conditions.length > 0;
|
|
876
|
+
if (decision.recommendation === 'PROCEED_WITH_CONDITIONS' && !hasConditions) {
|
|
877
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ['conditions'], message: 'conditions are required for PROCEED_WITH_CONDITIONS' });
|
|
878
|
+
}
|
|
879
|
+
if (decision.recommendation !== 'PROCEED_WITH_CONDITIONS' && hasConditions) {
|
|
880
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ['conditions'], message: 'conditions are only valid for PROCEED_WITH_CONDITIONS' });
|
|
881
|
+
}
|
|
882
|
+
});
|
|
341
883
|
// ── RunMeta (§15, §16) ──────────────────────────────────────────────────────
|
|
342
884
|
//
|
|
343
885
|
// Written by the artifact writer; assembled by the engine's RunCtx (T5). Internal → not strict.
|
|
@@ -345,6 +887,7 @@ export const ActionPlan = z
|
|
|
345
887
|
export const CallRecord = z.object({
|
|
346
888
|
provider: ProviderIdSchema,
|
|
347
889
|
stage: z.string(), // e.g. "S4", "S1"
|
|
890
|
+
category: z.enum(['discovery', 'verification', 'repair', 'planning']).optional(),
|
|
348
891
|
durationMs: z.number().nonnegative(),
|
|
349
892
|
error: z.enum(['NOT_FOUND', 'AUTH', 'QUOTA', 'TIMEOUT', 'BAD_OUTPUT', 'CRASH']).optional(),
|
|
350
893
|
});
|
|
@@ -360,6 +903,7 @@ const FlagProfileSchema = z.object({
|
|
|
360
903
|
export const RunMeta = z.object({
|
|
361
904
|
run_id: z.string().min(1), // encodes the timestamp (e.g. 20260702-1412-idea-refinement-a3f9)
|
|
362
905
|
workflow: WorkflowIdSchema,
|
|
906
|
+
mode: IdeaModeSchema.optional(),
|
|
363
907
|
provider_versions: z.record(ProviderIdSchema, z.string()), // detected `--version` strings
|
|
364
908
|
flag_profiles: z.record(ProviderIdSchema, FlagProfileSchema),
|
|
365
909
|
roles: z.record(z.string(), ProviderIdSchema), // role name → assigned provider
|
|
@@ -367,8 +911,23 @@ export const RunMeta = z.object({
|
|
|
367
911
|
calls: z.array(CallRecord),
|
|
368
912
|
call_count: z.number().int().nonnegative(),
|
|
369
913
|
budget: z.object({ limit: z.number().int().positive(), used: z.number().int().nonnegative() }),
|
|
914
|
+
receipt: z.object({
|
|
915
|
+
discovery: z.number().int().nonnegative(),
|
|
916
|
+
verification: z.number().int().nonnegative(),
|
|
917
|
+
repair: z.number().int().nonnegative(),
|
|
918
|
+
planning: z.number().int().nonnegative(),
|
|
919
|
+
}).optional(),
|
|
370
920
|
exit_status: z.enum(['ok', 'failed', 'aborted', 'partial']),
|
|
371
921
|
aborted: z.boolean(), // §16: Ctrl+C finalizes meta with aborted:true
|
|
372
922
|
// §16 report-header flags; absent = none.
|
|
373
|
-
flags: z.array(z.enum([
|
|
923
|
+
flags: z.array(z.enum([
|
|
924
|
+
'synthesis_suspect',
|
|
925
|
+
'low_diversity',
|
|
926
|
+
'plan_skipped',
|
|
927
|
+
'plan_fallback',
|
|
928
|
+
'headless_intent',
|
|
929
|
+
'verification_skipped',
|
|
930
|
+
'research_ungrounded',
|
|
931
|
+
'single_model',
|
|
932
|
+
])).optional(),
|
|
374
933
|
});
|