@oscharko-dev/keiko-workflows 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (191) hide show
  1. package/dist/.tsbuildinfo +1 -0
  2. package/dist/bug-investigation/context.d.ts +7 -0
  3. package/dist/bug-investigation/context.d.ts.map +1 -0
  4. package/dist/bug-investigation/context.js +119 -0
  5. package/dist/bug-investigation/descriptor.d.ts +4 -0
  6. package/dist/bug-investigation/descriptor.d.ts.map +1 -0
  7. package/dist/bug-investigation/descriptor.js +46 -0
  8. package/dist/bug-investigation/emit.d.ts +13 -0
  9. package/dist/bug-investigation/emit.d.ts.map +1 -0
  10. package/dist/bug-investigation/emit.js +35 -0
  11. package/dist/bug-investigation/events.d.ts +2 -0
  12. package/dist/bug-investigation/events.d.ts.map +1 -0
  13. package/dist/bug-investigation/events.js +6 -0
  14. package/dist/bug-investigation/failure-parse.d.ts +4 -0
  15. package/dist/bug-investigation/failure-parse.d.ts.map +1 -0
  16. package/dist/bug-investigation/failure-parse.js +154 -0
  17. package/dist/bug-investigation/guard.d.ts +3 -0
  18. package/dist/bug-investigation/guard.d.ts.map +1 -0
  19. package/dist/bug-investigation/guard.js +69 -0
  20. package/dist/bug-investigation/index.d.ts +8 -0
  21. package/dist/bug-investigation/index.d.ts.map +1 -0
  22. package/dist/bug-investigation/index.js +13 -0
  23. package/dist/bug-investigation/internal.d.ts +39 -0
  24. package/dist/bug-investigation/internal.d.ts.map +1 -0
  25. package/dist/bug-investigation/internal.js +65 -0
  26. package/dist/bug-investigation/memory.d.ts +5 -0
  27. package/dist/bug-investigation/memory.d.ts.map +1 -0
  28. package/dist/bug-investigation/memory.js +91 -0
  29. package/dist/bug-investigation/model-loop.d.ts +5 -0
  30. package/dist/bug-investigation/model-loop.d.ts.map +1 -0
  31. package/dist/bug-investigation/model-loop.js +225 -0
  32. package/dist/bug-investigation/parse.d.ts +4 -0
  33. package/dist/bug-investigation/parse.d.ts.map +1 -0
  34. package/dist/bug-investigation/parse.js +125 -0
  35. package/dist/bug-investigation/prompt.d.ts +5 -0
  36. package/dist/bug-investigation/prompt.d.ts.map +1 -0
  37. package/dist/bug-investigation/prompt.js +122 -0
  38. package/dist/bug-investigation/report.d.ts +24 -0
  39. package/dist/bug-investigation/report.d.ts.map +1 -0
  40. package/dist/bug-investigation/report.js +151 -0
  41. package/dist/bug-investigation/stages.d.ts +14 -0
  42. package/dist/bug-investigation/stages.d.ts.map +1 -0
  43. package/dist/bug-investigation/stages.js +247 -0
  44. package/dist/bug-investigation/types.d.ts +88 -0
  45. package/dist/bug-investigation/types.d.ts.map +1 -0
  46. package/dist/bug-investigation/types.js +6 -0
  47. package/dist/bug-investigation/verify-stage.d.ts +11 -0
  48. package/dist/bug-investigation/verify-stage.d.ts.map +1 -0
  49. package/dist/bug-investigation/verify-stage.js +91 -0
  50. package/dist/bug-investigation/workflow.d.ts +3 -0
  51. package/dist/bug-investigation/workflow.d.ts.map +1 -0
  52. package/dist/bug-investigation/workflow.js +85 -0
  53. package/dist/contextpack/assemble.d.ts +35 -0
  54. package/dist/contextpack/assemble.d.ts.map +1 -0
  55. package/dist/contextpack/assemble.js +431 -0
  56. package/dist/contextpack/compaction.d.ts +23 -0
  57. package/dist/contextpack/compaction.d.ts.map +1 -0
  58. package/dist/contextpack/compaction.js +68 -0
  59. package/dist/contextpack/index.d.ts +9 -0
  60. package/dist/contextpack/index.d.ts.map +1 -0
  61. package/dist/contextpack/index.js +8 -0
  62. package/dist/contextpack/microIndex.d.ts +29 -0
  63. package/dist/contextpack/microIndex.d.ts.map +1 -0
  64. package/dist/contextpack/microIndex.js +98 -0
  65. package/dist/contextpack/reranker.d.ts +15 -0
  66. package/dist/contextpack/reranker.d.ts.map +1 -0
  67. package/dist/contextpack/reranker.js +31 -0
  68. package/dist/descriptor.d.ts +2 -0
  69. package/dist/descriptor.d.ts.map +1 -0
  70. package/dist/descriptor.js +1 -0
  71. package/dist/governed-handoff.d.ts +6 -0
  72. package/dist/governed-handoff.d.ts.map +1 -0
  73. package/dist/governed-handoff.js +86 -0
  74. package/dist/index.d.ts +9 -0
  75. package/dist/index.d.ts.map +1 -0
  76. package/dist/index.js +13 -0
  77. package/dist/planner/anchors.d.ts +17 -0
  78. package/dist/planner/anchors.d.ts.map +1 -0
  79. package/dist/planner/anchors.js +291 -0
  80. package/dist/planner/explorationPlanner.d.ts +9 -0
  81. package/dist/planner/explorationPlanner.d.ts.map +1 -0
  82. package/dist/planner/explorationPlanner.js +15 -0
  83. package/dist/planner/governor.d.ts +16 -0
  84. package/dist/planner/governor.d.ts.map +1 -0
  85. package/dist/planner/governor.js +106 -0
  86. package/dist/planner/index.d.ts +11 -0
  87. package/dist/planner/index.d.ts.map +1 -0
  88. package/dist/planner/index.js +8 -0
  89. package/dist/planner/intent.d.ts +8 -0
  90. package/dist/planner/intent.d.ts.map +1 -0
  91. package/dist/planner/intent.js +140 -0
  92. package/dist/planner/plan.d.ts +43 -0
  93. package/dist/planner/plan.d.ts.map +1 -0
  94. package/dist/planner/plan.js +237 -0
  95. package/dist/promptEnhancer/index.d.ts +23 -0
  96. package/dist/promptEnhancer/index.d.ts.map +1 -0
  97. package/dist/promptEnhancer/index.js +282 -0
  98. package/dist/qualityIntelligence/__tests__/fixtures/runEntryFixtures.d.ts +30 -0
  99. package/dist/qualityIntelligence/__tests__/fixtures/runEntryFixtures.d.ts.map +1 -0
  100. package/dist/qualityIntelligence/__tests__/fixtures/runEntryFixtures.js +114 -0
  101. package/dist/qualityIntelligence/cancellation.d.ts +20 -0
  102. package/dist/qualityIntelligence/cancellation.d.ts.map +1 -0
  103. package/dist/qualityIntelligence/cancellation.js +55 -0
  104. package/dist/qualityIntelligence/descriptors.d.ts +41 -0
  105. package/dist/qualityIntelligence/descriptors.d.ts.map +1 -0
  106. package/dist/qualityIntelligence/descriptors.js +105 -0
  107. package/dist/qualityIntelligence/index.d.ts +11 -0
  108. package/dist/qualityIntelligence/index.d.ts.map +1 -0
  109. package/dist/qualityIntelligence/index.js +11 -0
  110. package/dist/qualityIntelligence/modelRoutedTestDesign.d.ts +100 -0
  111. package/dist/qualityIntelligence/modelRoutedTestDesign.d.ts.map +1 -0
  112. package/dist/qualityIntelligence/modelRoutedTestDesign.js +620 -0
  113. package/dist/qualityIntelligence/runEntries.d.ts +60 -0
  114. package/dist/qualityIntelligence/runEntries.d.ts.map +1 -0
  115. package/dist/qualityIntelligence/runEntries.js +243 -0
  116. package/dist/qualityIntelligence/runtimeCommon.d.ts +106 -0
  117. package/dist/qualityIntelligence/runtimeCommon.d.ts.map +1 -0
  118. package/dist/qualityIntelligence/runtimeCommon.js +258 -0
  119. package/dist/qualityIntelligence/scopedRegeneration.d.ts +26 -0
  120. package/dist/qualityIntelligence/scopedRegeneration.d.ts.map +1 -0
  121. package/dist/qualityIntelligence/scopedRegeneration.js +35 -0
  122. package/dist/ranking/filter.d.ts +20 -0
  123. package/dist/ranking/filter.d.ts.map +1 -0
  124. package/dist/ranking/filter.js +99 -0
  125. package/dist/ranking/index.d.ts +9 -0
  126. package/dist/ranking/index.d.ts.map +1 -0
  127. package/dist/ranking/index.js +8 -0
  128. package/dist/ranking/rank.d.ts +21 -0
  129. package/dist/ranking/rank.d.ts.map +1 -0
  130. package/dist/ranking/rank.js +160 -0
  131. package/dist/ranking/scoring.d.ts +13 -0
  132. package/dist/ranking/scoring.d.ts.map +1 -0
  133. package/dist/ranking/scoring.js +39 -0
  134. package/dist/ranking/signals.d.ts +20 -0
  135. package/dist/ranking/signals.d.ts.map +1 -0
  136. package/dist/ranking/signals.js +145 -0
  137. package/dist/unit-tests/context.d.ts +7 -0
  138. package/dist/unit-tests/context.d.ts.map +1 -0
  139. package/dist/unit-tests/context.js +129 -0
  140. package/dist/unit-tests/conventions.d.ts +5 -0
  141. package/dist/unit-tests/conventions.d.ts.map +1 -0
  142. package/dist/unit-tests/conventions.js +87 -0
  143. package/dist/unit-tests/descriptor.d.ts +5 -0
  144. package/dist/unit-tests/descriptor.d.ts.map +1 -0
  145. package/dist/unit-tests/descriptor.js +43 -0
  146. package/dist/unit-tests/emit.d.ts +13 -0
  147. package/dist/unit-tests/emit.d.ts.map +1 -0
  148. package/dist/unit-tests/emit.js +35 -0
  149. package/dist/unit-tests/events.d.ts +2 -0
  150. package/dist/unit-tests/events.d.ts.map +1 -0
  151. package/dist/unit-tests/events.js +6 -0
  152. package/dist/unit-tests/frontend.d.ts +42 -0
  153. package/dist/unit-tests/frontend.d.ts.map +1 -0
  154. package/dist/unit-tests/frontend.js +281 -0
  155. package/dist/unit-tests/index.d.ts +9 -0
  156. package/dist/unit-tests/index.d.ts.map +1 -0
  157. package/dist/unit-tests/index.js +15 -0
  158. package/dist/unit-tests/internal.d.ts +36 -0
  159. package/dist/unit-tests/internal.d.ts.map +1 -0
  160. package/dist/unit-tests/internal.js +43 -0
  161. package/dist/unit-tests/model-loop.d.ts +6 -0
  162. package/dist/unit-tests/model-loop.d.ts.map +1 -0
  163. package/dist/unit-tests/model-loop.js +98 -0
  164. package/dist/unit-tests/parse.d.ts +7 -0
  165. package/dist/unit-tests/parse.d.ts.map +1 -0
  166. package/dist/unit-tests/parse.js +68 -0
  167. package/dist/unit-tests/prompt.d.ts +6 -0
  168. package/dist/unit-tests/prompt.d.ts.map +1 -0
  169. package/dist/unit-tests/prompt.js +139 -0
  170. package/dist/unit-tests/report.d.ts +26 -0
  171. package/dist/unit-tests/report.d.ts.map +1 -0
  172. package/dist/unit-tests/report.js +104 -0
  173. package/dist/unit-tests/stages.d.ts +12 -0
  174. package/dist/unit-tests/stages.d.ts.map +1 -0
  175. package/dist/unit-tests/stages.js +202 -0
  176. package/dist/unit-tests/strategy.d.ts +6 -0
  177. package/dist/unit-tests/strategy.d.ts.map +1 -0
  178. package/dist/unit-tests/strategy.js +36 -0
  179. package/dist/unit-tests/target-guard.d.ts +5 -0
  180. package/dist/unit-tests/target-guard.d.ts.map +1 -0
  181. package/dist/unit-tests/target-guard.js +29 -0
  182. package/dist/unit-tests/types.d.ts +74 -0
  183. package/dist/unit-tests/types.d.ts.map +1 -0
  184. package/dist/unit-tests/types.js +6 -0
  185. package/dist/unit-tests/verify-stage.d.ts +10 -0
  186. package/dist/unit-tests/verify-stage.d.ts.map +1 -0
  187. package/dist/unit-tests/verify-stage.js +56 -0
  188. package/dist/unit-tests/workflow.d.ts +3 -0
  189. package/dist/unit-tests/workflow.d.ts.map +1 -0
  190. package/dist/unit-tests/workflow.js +69 -0
  191. package/package.json +38 -0
@@ -0,0 +1,282 @@
1
+ // Prompt Enhancer workflow authority (Epic #1307, Issue #1314; ADR-0044 §1/§3/§4).
2
+ //
3
+ // This module owns the governed `analyze → plan → optimize → validate → evidence-record-input`
4
+ // lifecycle. It composes the deterministic, provider-neutral primitives shipped by #1309–#1313 into a
5
+ // single content-light wire response. The core run remains pure apart from SHA-256 hashing; callers
6
+ // that persist evidence supply the clock and store at their boundary.
7
+ //
8
+ // Model-Gateway routing (AC3). The enhancer never calls a live model — every primitive is
9
+ // deterministic and the Enhanced Prompt is provider-neutral. The optional `modelId` the caller intends
10
+ // to dispatch the result to downstream is resolved against the Model-Gateway config (a configured
11
+ // provider check); when no gateway config is present or the model is not a configured provider, the
12
+ // result still succeeds and `modelRouting` reports the degraded state (graceful handling). The enhancer
13
+ // itself is never blocked by an unavailable model.
14
+ import { analyzePrompt, asEnhancedPromptId, asPromptEnhancementRequestId, normalizePromptDraft, validatePromptEnhancementRequest, PROMPT_ENHANCEMENT_DEFAULT_CANDIDATE_COUNT, PROMPT_ENHANCER_SCHEMA_VERSION, } from "@oscharko-dev/keiko-contracts";
15
+ import { findConfiguredCapability, PromptEnhancer, } from "@oscharko-dev/keiko-model-gateway";
16
+ import { sha256Hex } from "@oscharko-dev/keiko-security";
17
+ // Thrown when the (server-built) domain request fails the #1309 structural validator. Carries only the
18
+ // deterministic, content-free validator messages (they never echo raw user input). The route maps it
19
+ // to a 400. Distinct from the wire-request validator so the route can report the originating gate.
20
+ export class PromptEnhancementInputError extends Error {
21
+ errors;
22
+ constructor(errors) {
23
+ super("Prompt enhancement request failed domain validation.");
24
+ this.name = "PromptEnhancementInputError";
25
+ this.errors = errors;
26
+ }
27
+ }
28
+ // Thrown when the caller disconnects before the enhancement completes. Carries no payload; the route
29
+ // maps it to a 499 (client closed request). Lets the deterministic pipeline cooperate with cancellation
30
+ // even though it never blocks on a model (AC: cancellation).
31
+ export class PromptEnhancementCancelledError extends Error {
32
+ constructor() {
33
+ super("Prompt enhancement request was cancelled.");
34
+ this.name = "PromptEnhancementCancelledError";
35
+ }
36
+ }
37
+ export function promptEnhancementGatewayRoutingConfig(config) {
38
+ if (config === undefined) {
39
+ return undefined;
40
+ }
41
+ return {
42
+ providers: config.providers.map((provider) => ({ modelId: provider.modelId })),
43
+ ...(config.capabilities === undefined ? {} : { capabilities: config.capabilities }),
44
+ };
45
+ }
46
+ const throwIfCancelled = (signal) => {
47
+ if (signal?.aborted === true) {
48
+ throw new PromptEnhancementCancelledError();
49
+ }
50
+ };
51
+ // Clamp the requested candidate count into [1, gateway MAX_CANDIDATE_COUNT]. The wire validator already
52
+ // bounded the value to [1, PROMPT_ENHANCEMENT_MAX_CANDIDATE_COUNT]; this additionally honors the
53
+ // gateway's own bound so the surface can never widen the optimization envelope.
54
+ const clampCandidateCount = (requested) => {
55
+ const base = requested === undefined || !Number.isInteger(requested) || requested < 1
56
+ ? PROMPT_ENHANCEMENT_DEFAULT_CANDIDATE_COUNT
57
+ : requested;
58
+ return Math.min(base, PromptEnhancer.MAX_CANDIDATE_COUNT);
59
+ };
60
+ // A stable, content-free request id derived from the normalized draft + the request shaping options. A
61
+ // hash keeps the pipeline deterministic (identical request → identical id) and never embeds raw input,
62
+ // a path-traversal fragment, or a control character, so it passes the branded-id validator.
63
+ const deriveRequestId = (normalized, missingInformationStrategy, profilePreference, locale, hasConnectedContext, attachmentCount) => `pe-req-${sha256Hex([
64
+ normalized,
65
+ missingInformationStrategy,
66
+ profilePreference,
67
+ locale,
68
+ hasConnectedContext === undefined ? "" : String(hasConnectedContext),
69
+ attachmentCount === undefined ? "" : String(attachmentCount),
70
+ ].join("\u0000")).slice(0, 48)}`;
71
+ const resolveModelRouting = (request, config) => {
72
+ const requestedModelId = request.modelId;
73
+ if (requestedModelId === undefined) {
74
+ return { availability: "not-requested", reason: "no-model-requested" };
75
+ }
76
+ if (config === undefined) {
77
+ return { availability: "unavailable", reason: "no-gateway-config", requestedModelId };
78
+ }
79
+ const isConfiguredProvider = config.providers.some((provider) => provider.modelId === requestedModelId);
80
+ if (!isConfiguredProvider) {
81
+ return { availability: "unavailable", reason: "model-not-configured", requestedModelId };
82
+ }
83
+ const resolved = findConfiguredCapability(config, requestedModelId);
84
+ if (resolved?.kind !== "chat") {
85
+ return { availability: "unavailable", reason: "model-not-chat-capable", requestedModelId };
86
+ }
87
+ return {
88
+ availability: "available",
89
+ reason: "model-available",
90
+ requestedModelId,
91
+ resolvedModelId: requestedModelId,
92
+ costClass: resolved.costClass,
93
+ };
94
+ };
95
+ const resolveGroundingReadiness = (request, required) => {
96
+ if (!required) {
97
+ return { status: "not-required", reason: "no-grounding-required" };
98
+ }
99
+ if (request.hasConnectedContext === true || (request.attachmentCount ?? 0) > 0) {
100
+ return { status: "ready", reason: "connected-context-present" };
101
+ }
102
+ return {
103
+ status: "unavailable",
104
+ reason: "missing-concrete-scope",
105
+ notice: "This prompt requires grounding, but no connected workspace scope or attachment was supplied.",
106
+ };
107
+ };
108
+ const NOT_RECORDED_EVIDENCE = {
109
+ status: "not-recorded",
110
+ reason: "evidence-store-not-configured",
111
+ };
112
+ const NO_SCORED_CANDIDATE_ERROR_MESSAGE = "Prompt candidate optimization produced no scored candidate.";
113
+ // Normalize + guard the draft, mint the branded ids, build and re-validate the domain request, and run
114
+ // the deterministic analyzer. Throws PromptEnhancementInputError on an empty draft or domain-validation
115
+ // failure. Factored out of runPromptEnhancement to keep each function within the complexity/size bounds.
116
+ function prepareEnhancement(request) {
117
+ const missingInformationStrategy = request.missingInformationStrategy ?? "clarify";
118
+ const normalized = normalizePromptDraft(request.text);
119
+ // Self-contained guard so the reusable orchestrator never analyzes an empty draft even if a future
120
+ // caller skips the wire validator. A draft empty after control-stripping + NFKC carries no signal.
121
+ if (normalized.trim().length === 0) {
122
+ throw new PromptEnhancementInputError(["request text must not be empty after normalization"]);
123
+ }
124
+ const inputFingerprintSha256 = sha256Hex(normalized);
125
+ const requestId = asPromptEnhancementRequestId(deriveRequestId(normalized, missingInformationStrategy, request.profilePreference ?? "", request.locale ?? "", request.hasConnectedContext, request.attachmentCount));
126
+ const input = {
127
+ text: request.text,
128
+ ...(request.hasConnectedContext === undefined
129
+ ? {}
130
+ : { hasConnectedContext: request.hasConnectedContext }),
131
+ ...(request.attachmentCount === undefined ? {} : { attachmentCount: request.attachmentCount }),
132
+ };
133
+ const domainRequest = {
134
+ schemaVersion: PROMPT_ENHANCER_SCHEMA_VERSION,
135
+ requestId,
136
+ input,
137
+ missingInformationStrategy,
138
+ ...(request.profilePreference === undefined
139
+ ? {}
140
+ : { profilePreference: request.profilePreference }),
141
+ ...(request.locale === undefined ? {} : { locale: request.locale }),
142
+ };
143
+ const validation = validatePromptEnhancementRequest(domainRequest);
144
+ if (!validation.ok) {
145
+ throw new PromptEnhancementInputError(validation.errors);
146
+ }
147
+ return { analysis: analyzePrompt(validation.value), input, inputFingerprintSha256 };
148
+ }
149
+ const isNoScoredCandidateError = (error) => error instanceof Error && error.message === NO_SCORED_CANDIDATE_ERROR_MESSAGE;
150
+ function buildRejectedFailSafeSelection(prepared, request, originalError) {
151
+ const { analysis, input } = prepared;
152
+ const plan = PromptEnhancer.planPromptEnhancement(analysis, {
153
+ ...(request.profilePreference === undefined
154
+ ? {}
155
+ : { profilePreference: request.profilePreference }),
156
+ });
157
+ const candidateId = asEnhancedPromptId(`${analysis.requestId}-safety-fail-safe`);
158
+ const prompt = PromptEnhancer.generateEnhancedPrompt({
159
+ promptId: candidateId,
160
+ analysis,
161
+ plan,
162
+ input,
163
+ });
164
+ const scorecard = PromptEnhancer.scorePromptCandidate({
165
+ candidateId,
166
+ profile: plan.selectedProfile,
167
+ prompt,
168
+ plan,
169
+ analysis,
170
+ });
171
+ const safety = PromptEnhancer.assessPromptSafety({ prompt, analysis, input });
172
+ if (safety.decision !== "rejected") {
173
+ throw originalError;
174
+ }
175
+ return {
176
+ winner: scorecard,
177
+ ranked: [scorecard],
178
+ rankedPrompts: [prompt],
179
+ winnerSafetyAssessment: safety,
180
+ rejected: [],
181
+ };
182
+ }
183
+ function selectPromptCandidate(prepared, request) {
184
+ const { analysis, input } = prepared;
185
+ try {
186
+ return PromptEnhancer.optimizePromptCandidates({
187
+ analysis,
188
+ input,
189
+ bounds: { candidateCount: clampCandidateCount(request.candidateCount) },
190
+ ...(request.profilePreference === undefined
191
+ ? {}
192
+ : { profilePreference: request.profilePreference }),
193
+ });
194
+ }
195
+ catch (error) {
196
+ if (!isNoScoredCandidateError(error)) {
197
+ throw error;
198
+ }
199
+ return buildRejectedFailSafeSelection(prepared, request, error);
200
+ }
201
+ }
202
+ export function runPromptEnhancement(request, deps) {
203
+ throwIfCancelled(deps.signal);
204
+ const prepared = prepareEnhancement(request);
205
+ const { analysis, inputFingerprintSha256 } = prepared;
206
+ throwIfCancelled(deps.signal);
207
+ const selection = selectPromptCandidate(prepared, request);
208
+ throwIfCancelled(deps.signal);
209
+ const enhancedPrompt = selection.rankedPrompts[0];
210
+ if (enhancedPrompt === undefined) {
211
+ throw new Error("Prompt enhancement optimization produced no prompt.");
212
+ }
213
+ const renderedPrompt = PromptEnhancer.renderEnhancedPromptText(enhancedPrompt);
214
+ return {
215
+ schemaVersion: PROMPT_ENHANCER_SCHEMA_VERSION,
216
+ promptId: enhancedPrompt.promptId,
217
+ inputFingerprintSha256,
218
+ analysis,
219
+ enhancedPrompt,
220
+ renderedPrompt,
221
+ candidates: {
222
+ winnerCandidateId: selection.winner.candidateId,
223
+ scorecards: selection.ranked,
224
+ rejected: selection.rejected,
225
+ },
226
+ safety: selection.winnerSafetyAssessment,
227
+ modelRouting: resolveModelRouting(request, deps.gatewayRoutingConfig),
228
+ groundingReadiness: resolveGroundingReadiness(request, enhancedPrompt.groundingPlan.required),
229
+ evidence: NOT_RECORDED_EVIDENCE,
230
+ };
231
+ }
232
+ function evidenceStatus(decision) {
233
+ if (decision === "accepted")
234
+ return "validated";
235
+ if (decision === "rejected")
236
+ return "rejected";
237
+ return "requires-human-review";
238
+ }
239
+ function evidenceCandidateRows(candidates) {
240
+ return candidates.scorecards.map((scorecard) => ({
241
+ candidateId: scorecard.candidateId,
242
+ profile: scorecard.profile,
243
+ aggregateScore: scorecard.aggregateScore,
244
+ estimatedTokens: scorecard.estimatedTokens,
245
+ selected: scorecard.candidateId === candidates.winnerCandidateId,
246
+ }));
247
+ }
248
+ function evidenceModelMetadata(routing, winnerProfile) {
249
+ return {
250
+ deterministic: true,
251
+ ...(routing.resolvedModelId === undefined ? {} : { modelId: routing.resolvedModelId }),
252
+ ...(winnerProfile === undefined ? {} : { profile: winnerProfile }),
253
+ };
254
+ }
255
+ function runIdForEvidence(result, recordedAt) {
256
+ return `pe-run-${sha256Hex([result.analysis.requestId, result.promptId, result.inputFingerprintSha256, recordedAt].join("\u0000")).slice(0, 32)}`;
257
+ }
258
+ export function buildPromptEnhancementRecordInput(options) {
259
+ const { rawInput, result, recordedAt } = options;
260
+ const winnerProfile = result.candidates.scorecards.find((scorecard) => scorecard.candidateId === result.candidates.winnerCandidateId)?.profile;
261
+ return {
262
+ runId: runIdForEvidence(result, recordedAt),
263
+ recordedAt,
264
+ requestId: result.analysis.requestId,
265
+ status: evidenceStatus(result.safety.decision),
266
+ originalInput: rawInput,
267
+ enhancedPromptId: result.promptId,
268
+ enhancedPromptText: result.renderedPrompt,
269
+ appliedSafetyRules: result.enhancedPrompt.safetyRules,
270
+ appliedGroundingDirectives: result.enhancedPrompt.groundingPlan.directives,
271
+ assumptions: result.analysis.missingContext.flatMap((item) => item.kind === "assumption" ? [item.statement] : []),
272
+ candidateScores: evidenceCandidateRows(result.candidates),
273
+ safety: {
274
+ decision: result.safety.decision,
275
+ verificationStatus: result.safety.verificationStatus,
276
+ requiresHumanReview: result.safety.requiresHumanReview,
277
+ findingCodes: result.safety.findings.map((finding) => finding.code),
278
+ leastPrivilege: result.safety.leastPrivilege,
279
+ },
280
+ modelMetadata: evidenceModelMetadata(result.modelRouting, winnerProfile),
281
+ };
282
+ }
@@ -0,0 +1,30 @@
1
+ import { type QualityIntelligence as QI } from "@oscharko-dev/keiko-contracts";
2
+ import { createInMemoryQualityIntelligenceLocalStore, type QualityIntelligenceEvidenceManifest, type QualityIntelligenceLocalStore } from "@oscharko-dev/keiko-evidence";
3
+ import type { QualityIntelligenceClock, QualityIntelligenceRunEventSink } from "../../runtimeCommon.js";
4
+ import type { QualityIntelligenceModelRoutedTestDesignDeps } from "../../modelRoutedTestDesign.js";
5
+ export declare const CLOCK: QualityIntelligenceClock;
6
+ export declare const PROVENANCE: {
7
+ readonly envelopeIds: readonly ["env-1"];
8
+ readonly auditSummaryId: QualityIntelligenceEvidenceManifest["provenanceRefs"]["auditSummaryId"];
9
+ };
10
+ export declare const ENVELOPE: QI.QualityIntelligenceSourceEnvelope;
11
+ export declare function makePlan(id: string): QI.QualityIntelligenceRunPlan;
12
+ export declare function makeAtom(id: string): QI.QualityIntelligenceEvidenceAtom;
13
+ export declare function ingestedAtomsFixture(): readonly {
14
+ atom: QI.QualityIntelligenceEvidenceAtom;
15
+ canonicalText: string;
16
+ }[];
17
+ export declare function candidatesFor(runId: string, atoms: readonly QI.QualityIntelligenceEvidenceAtom[]): readonly QI.QualityIntelligenceTestCaseCandidate[];
18
+ export interface RecordingSink {
19
+ readonly sink: QualityIntelligenceRunEventSink;
20
+ readonly events: () => readonly QI.QualityIntelligenceRunEvent[];
21
+ readonly kinds: () => readonly string[];
22
+ readonly stageNames: () => readonly string[];
23
+ /** Ordered "kind" / "kind:stageName" tokens — the human-readable lifecycle trace. */
24
+ readonly trace: () => readonly string[];
25
+ }
26
+ export declare function recordingSink(): RecordingSink;
27
+ export declare const MODEL_OUTPUT_TWO: string;
28
+ export declare function modelRoutedDeps(store: QualityIntelligenceLocalStore, sink: QualityIntelligenceRunEventSink): QualityIntelligenceModelRoutedTestDesignDeps;
29
+ export { createInMemoryQualityIntelligenceLocalStore };
30
+ //# sourceMappingURL=runEntryFixtures.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runEntryFixtures.d.ts","sourceRoot":"","sources":["../../../../src/qualityIntelligence/__tests__/fixtures/runEntryFixtures.ts"],"names":[],"mappings":"AAOA,OAAO,EAAuB,KAAK,mBAAmB,IAAI,EAAE,EAAE,MAAM,+BAA+B,CAAC;AACpG,OAAO,EACL,2CAA2C,EAC3C,KAAK,mCAAmC,EACxC,KAAK,6BAA6B,EACnC,MAAM,8BAA8B,CAAC;AAMtC,OAAO,KAAK,EACV,wBAAwB,EACxB,+BAA+B,EAChC,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EAAE,4CAA4C,EAAE,MAAM,gCAAgC,CAAC;AAGnG,eAAO,MAAM,KAAK,EAAE,wBAElB,CAAC;AAEH,eAAO,MAAM,UAAU;;6BAGC,mCAAmC,CAAC,gBAAgB,CAAC,CAAC,gBAAgB,CAAC;CACrF,CAAC;AAEX,eAAO,MAAM,QAAQ,EAAE,EAAE,CAAC,iCAUxB,CAAC;AAEH,wBAAgB,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,EAAE,CAAC,0BAA0B,CAOlE;AAED,wBAAgB,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,EAAE,CAAC,+BAA+B,CASvE;AAED,wBAAgB,oBAAoB,IAAI,SAAS;IAC/C,IAAI,EAAE,EAAE,CAAC,+BAA+B,CAAC;IACzC,aAAa,EAAE,MAAM,CAAC;CACvB,EAAE,CAQF;AAID,wBAAgB,aAAa,CAC3B,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,SAAS,EAAE,CAAC,+BAA+B,EAAE,GACnD,SAAS,EAAE,CAAC,oCAAoC,EAAE,CAQpD;AAGD,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE,+BAA+B,CAAC;IAC/C,QAAQ,CAAC,MAAM,EAAE,MAAM,SAAS,EAAE,CAAC,2BAA2B,EAAE,CAAC;IACjE,QAAQ,CAAC,KAAK,EAAE,MAAM,SAAS,MAAM,EAAE,CAAC;IACxC,QAAQ,CAAC,UAAU,EAAE,MAAM,SAAS,MAAM,EAAE,CAAC;IAC7C,qFAAqF;IACrF,QAAQ,CAAC,KAAK,EAAE,MAAM,SAAS,MAAM,EAAE,CAAC;CACzC;AAED,wBAAgB,aAAa,IAAI,aAAa,CAoB7C;AAID,eAAO,MAAM,gBAAgB,QAiB3B,CAAC;AAEH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,6BAA6B,EACpC,IAAI,EAAE,+BAA+B,GACpC,4CAA4C,CAW9C;AAED,OAAO,EAAE,2CAA2C,EAAE,CAAC"}
@@ -0,0 +1,114 @@
1
+ // Shared fixtures + harness for the QI run-entry regression suite (Epic #270, Issue #273).
2
+ //
3
+ // Mirrors the captureSink / PLAN / PROVENANCE shapes used by runLifecycleCancellation.test.ts and
4
+ // modelRoutedTestDesign.test.ts so the new lifecycle / contract / cancellation tests reuse one
5
+ // canonical set of fixtures instead of reinventing per file. NOTE: this file is a TEST helper, not
6
+ // package source — it is intentionally NOT exported from any package index.ts.
7
+ import { QualityIntelligence } from "@oscharko-dev/keiko-contracts";
8
+ import { createInMemoryQualityIntelligenceLocalStore, } from "@oscharko-dev/keiko-evidence";
9
+ import { deriveIntent, designTestCaseCandidates, regressionDefault, } from "@oscharko-dev/keiko-quality-intelligence";
10
+ // Deterministic clock so persisted completedAt / event timestamps never depend on wall time.
11
+ export const CLOCK = Object.freeze({
12
+ nowIso: () => "2026-06-08T00:01:00.000Z",
13
+ });
14
+ export const PROVENANCE = {
15
+ envelopeIds: ["env-1"],
16
+ auditSummaryId: "audit-273-test",
17
+ };
18
+ export const ENVELOPE = Object.freeze({
19
+ id: QualityIntelligence.asQualityIntelligenceSourceEnvelopeId("env-1"),
20
+ kind: "human-context",
21
+ displayLabel: "Audit source",
22
+ localRef: "env-1",
23
+ provenance: {
24
+ origin: "requirements",
25
+ registeredAt: "2026-06-08T00:00:00.000Z",
26
+ integrityHashSha256Hex: "b".repeat(64),
27
+ },
28
+ });
29
+ export function makePlan(id) {
30
+ return {
31
+ id: QualityIntelligence.asQualityIntelligenceRunId(id),
32
+ requestedAt: "2026-06-08T00:00:00.000Z",
33
+ plannerKind: "model-routed",
34
+ stages: [],
35
+ };
36
+ }
37
+ export function makeAtom(id) {
38
+ return {
39
+ id: QualityIntelligence.asQualityIntelligenceEvidenceAtomId(id),
40
+ kind: "requirement",
41
+ sourceEnvelopeId: QualityIntelligence.asQualityIntelligenceSourceEnvelopeId("env-1"),
42
+ canonicalHashSha256Hex: "a".repeat(64),
43
+ redactionStatus: "not-required",
44
+ lifecycleStatus: "draft",
45
+ };
46
+ }
47
+ export function ingestedAtomsFixture() {
48
+ return [
49
+ {
50
+ atom: makeAtom("atom-1"),
51
+ canonicalText: "REQ-1: Lock the account after five failed logins.",
52
+ },
53
+ { atom: makeAtom("atom-2"), canonicalText: "REQ-2: Reset the counter after a success." },
54
+ ];
55
+ }
56
+ // Real domain candidates derived from atoms (status "proposed"), so coverage-review / validation /
57
+ // refinement entries receive schema-valid candidates without hand-rolling the shape.
58
+ export function candidatesFor(runId, atoms) {
59
+ const intent = deriveIntent([ENVELOPE], regressionDefault);
60
+ return designTestCaseCandidates({
61
+ runId: QualityIntelligence.asQualityIntelligenceRunId(runId),
62
+ intent,
63
+ atoms,
64
+ profile: regressionDefault,
65
+ });
66
+ }
67
+ export function recordingSink() {
68
+ const events = [];
69
+ return {
70
+ sink: { emit: (e) => void events.push(e) },
71
+ events: () => events,
72
+ kinds: () => events.map((e) => e.payload.kind),
73
+ stageNames: () => events
74
+ .map((e) => e.payload)
75
+ .filter((p) => "stageName" in p)
76
+ .map((p) => p.stageName),
77
+ trace: () => events.map((e) => {
78
+ const p = e.payload;
79
+ return "stageName" in p ? `${p.kind}:${p.stageName}` : p.kind;
80
+ }),
81
+ };
82
+ }
83
+ // Deterministic model output (two candidates) for the model-routed entry — copied shape from
84
+ // modelRoutedTestDesign.test.ts so the two suites agree on the fixture.
85
+ export const MODEL_OUTPUT_TWO = JSON.stringify([
86
+ {
87
+ title: "Test atom 1 behavior",
88
+ steps: ["Navigate to the feature", "Trigger the atom-1 action"],
89
+ expectedResults: ["The atom-1 behavior occurs"],
90
+ priority: "P2",
91
+ riskClass: "regression",
92
+ derivedFromEvidenceIndexes: [1],
93
+ },
94
+ {
95
+ title: "Test atom 2 behavior",
96
+ steps: ["Navigate to the feature", "Trigger the atom-2 action"],
97
+ expectedResults: ["The atom-2 behavior occurs"],
98
+ priority: "P2",
99
+ riskClass: "regression",
100
+ derivedFromEvidenceIndexes: [2],
101
+ },
102
+ ]);
103
+ export function modelRoutedDeps(store, sink) {
104
+ return {
105
+ sink,
106
+ evidenceStore: store,
107
+ candidatesSink: { record: () => undefined },
108
+ generate: {
109
+ generate: () => Promise.resolve({ rawText: MODEL_OUTPUT_TWO, modelCallCount: 1, modelId: "test-model" }),
110
+ },
111
+ clock: CLOCK,
112
+ };
113
+ }
114
+ export { createInMemoryQualityIntelligenceLocalStore };
@@ -0,0 +1,20 @@
1
+ export interface QualityIntelligenceStageCancellationHandle {
2
+ readonly signal: AbortSignal;
3
+ readonly cancel: (reason?: string) => void;
4
+ readonly dispose: () => void;
5
+ }
6
+ /**
7
+ * Compose a parent cancellation signal with a freshly minted stage controller.
8
+ * If the parent is already aborted, the returned signal is aborted immediately.
9
+ * The handle's `dispose()` MUST be called once the stage finishes so the parent
10
+ * listener is removed (otherwise long-running runs leak DOM listeners across
11
+ * stages on the same parent signal).
12
+ */
13
+ export declare function composeStageCancellation(parent: AbortSignal | undefined): QualityIntelligenceStageCancellationHandle;
14
+ /**
15
+ * Returns true when the supplied signal has been aborted. Pure helper around
16
+ * the standard DOM property so the run entries do not sprinkle the property
17
+ * access at every cooperative cancellation check.
18
+ */
19
+ export declare function isCancelled(signal: AbortSignal | undefined): boolean;
20
+ //# sourceMappingURL=cancellation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cancellation.d.ts","sourceRoot":"","sources":["../../src/qualityIntelligence/cancellation.ts"],"names":[],"mappings":"AASA,MAAM,WAAW,0CAA0C;IACzD,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,CAAC,MAAM,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3C,QAAQ,CAAC,OAAO,EAAE,MAAM,IAAI,CAAC;CAC9B;AAED;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,WAAW,GAAG,SAAS,GAC9B,0CAA0C,CA8B5C;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,WAAW,GAAG,SAAS,GAAG,OAAO,CAEpE"}
@@ -0,0 +1,55 @@
1
+ // Cancellation bridge between the harness AbortSignal world and the QI dispatcher
2
+ // (Epic #270, Issue #273, ADR-0023 D6).
3
+ //
4
+ // The harness threads a single AbortSignal through workflows. The QI dispatcher
5
+ // (#279) accepts an optional AbortSignal too. This adapter is a thin helper that
6
+ // composes a parent signal with a per-stage AbortController so an individual
7
+ // stage can be cancelled without disturbing the rest of the run. No timers; no
8
+ // IO; pure DOM/Node primitives only.
9
+ /**
10
+ * Compose a parent cancellation signal with a freshly minted stage controller.
11
+ * If the parent is already aborted, the returned signal is aborted immediately.
12
+ * The handle's `dispose()` MUST be called once the stage finishes so the parent
13
+ * listener is removed (otherwise long-running runs leak DOM listeners across
14
+ * stages on the same parent signal).
15
+ */
16
+ export function composeStageCancellation(parent) {
17
+ const controller = new AbortController();
18
+ if (parent !== undefined) {
19
+ if (parent.aborted) {
20
+ controller.abort(parent.reason);
21
+ }
22
+ else {
23
+ const onParentAbort = () => {
24
+ controller.abort(parent.reason);
25
+ };
26
+ parent.addEventListener("abort", onParentAbort, { once: true });
27
+ return Object.freeze({
28
+ signal: controller.signal,
29
+ cancel: (reason) => {
30
+ controller.abort(reason);
31
+ },
32
+ dispose: () => {
33
+ parent.removeEventListener("abort", onParentAbort);
34
+ },
35
+ });
36
+ }
37
+ }
38
+ return Object.freeze({
39
+ signal: controller.signal,
40
+ cancel: (reason) => {
41
+ controller.abort(reason);
42
+ },
43
+ dispose: () => {
44
+ /* no parent listener was attached */
45
+ },
46
+ });
47
+ }
48
+ /**
49
+ * Returns true when the supplied signal has been aborted. Pure helper around
50
+ * the standard DOM property so the run entries do not sprinkle the property
51
+ * access at every cooperative cancellation check.
52
+ */
53
+ export function isCancelled(signal) {
54
+ return signal?.aborted ?? false;
55
+ }
@@ -0,0 +1,41 @@
1
+ import type { QualityIntelligence } from "@oscharko-dev/keiko-contracts";
2
+ export type QualityIntelligenceWorkflowId = "qi:test-design" | "qi:coverage-review" | "qi:validation" | "qi:artifact-refinement";
3
+ export interface QualityIntelligenceWorkflowLimits {
4
+ /** Wall-clock soft limit per stage (advisory, used to bound dispatcher waits). */
5
+ readonly stageTimeoutMs: number;
6
+ /** Total candidates a single test-design run may emit (truncated past). */
7
+ readonly maxCandidatesPerRun: number;
8
+ /** Total findings a single validation run may emit (truncated past). */
9
+ readonly maxFindingsPerRun: number;
10
+ /**
11
+ * Maximum model gateway dispatches the GENERATION stage(s) may make per run. The adversarial
12
+ * test-quality judge (Epic #736) is governed by its own `maxJudgeCallsPerRun` budget instead,
13
+ * because it makes one dispatch per candidate and must score every candidate (Issue #747).
14
+ */
15
+ readonly maxModelCallsPerRun: number;
16
+ /**
17
+ * Maximum model gateway dispatches the adversarial test-quality judge may make per run
18
+ * (Epic #736, Issue #747). One dispatch per candidate; defaults to the candidate cap so a
19
+ * realistic run is judged in full while a pathological run can never dispatch an unbounded
20
+ * number of judge calls. Candidates beyond this ceiling receive deterministic weak judge findings,
21
+ * so the run still accounts for every candidate without dispatching unbounded calls.
22
+ */
23
+ readonly maxJudgeCallsPerRun: number;
24
+ }
25
+ export declare const QUALITY_INTELLIGENCE_DEFAULT_WORKFLOW_LIMITS: QualityIntelligenceWorkflowLimits;
26
+ export interface QualityIntelligenceWorkflowDescriptor {
27
+ readonly workflowId: QualityIntelligenceWorkflowId;
28
+ readonly displayName: string;
29
+ readonly description: string;
30
+ readonly stageNames: readonly string[];
31
+ readonly emittedEventKinds: readonly QualityIntelligence.QualityIntelligenceRunEventKind[];
32
+ readonly defaultLimits: QualityIntelligenceWorkflowLimits;
33
+ readonly preferredCostClass: "low" | "medium" | "high";
34
+ }
35
+ export declare const QI_TEST_DESIGN_WORKFLOW_DESCRIPTOR: QualityIntelligenceWorkflowDescriptor;
36
+ export declare const QI_COVERAGE_REVIEW_WORKFLOW_DESCRIPTOR: QualityIntelligenceWorkflowDescriptor;
37
+ export declare const QI_VALIDATION_WORKFLOW_DESCRIPTOR: QualityIntelligenceWorkflowDescriptor;
38
+ export declare const QI_ARTIFACT_REFINEMENT_WORKFLOW_DESCRIPTOR: QualityIntelligenceWorkflowDescriptor;
39
+ export declare const QUALITY_INTELLIGENCE_WORKFLOW_DESCRIPTORS: readonly QualityIntelligenceWorkflowDescriptor[];
40
+ export declare function findQualityIntelligenceWorkflowDescriptor(workflowId: QualityIntelligenceWorkflowId): QualityIntelligenceWorkflowDescriptor;
41
+ //# sourceMappingURL=descriptors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"descriptors.d.ts","sourceRoot":"","sources":["../../src/qualityIntelligence/descriptors.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAC;AAEzE,MAAM,MAAM,6BAA6B,GACrC,gBAAgB,GAChB,oBAAoB,GACpB,eAAe,GACf,wBAAwB,CAAC;AAE7B,MAAM,WAAW,iCAAiC;IAChD,kFAAkF;IAClF,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,2EAA2E;IAC3E,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;IACrC,wEAAwE;IACxE,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC;;;;OAIG;IACH,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;IACrC;;;;;;OAMG;IACH,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;CACtC;AAED,eAAO,MAAM,4CAA4C,EAAE,iCAOvD,CAAC;AAEL,MAAM,WAAW,qCAAqC;IACpD,QAAQ,CAAC,UAAU,EAAE,6BAA6B,CAAC;IACnD,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;IACvC,QAAQ,CAAC,iBAAiB,EAAE,SAAS,mBAAmB,CAAC,+BAA+B,EAAE,CAAC;IAC3F,QAAQ,CAAC,aAAa,EAAE,iCAAiC,CAAC;IAC1D,QAAQ,CAAC,kBAAkB,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;CACxD;AAsCD,eAAO,MAAM,kCAAkC,EAAE,qCAW7C,CAAC;AAEL,eAAO,MAAM,sCAAsC,EAAE,qCAWjD,CAAC;AAEL,eAAO,MAAM,iCAAiC,EAAE,qCAW5C,CAAC;AAEL,eAAO,MAAM,0CAA0C,EAAE,qCAWrD,CAAC;AAEL,eAAO,MAAM,yCAAyC,EAAE,SAAS,qCAAqC,EAMlG,CAAC;AAEL,wBAAgB,yCAAyC,CACvD,UAAU,EAAE,6BAA6B,GACxC,qCAAqC,CAOvC"}
@@ -0,0 +1,105 @@
1
+ // Quality Intelligence workflow descriptors (Epic #270, Issue #273, ADR-0023 D6).
2
+ //
3
+ // Four reviewable QI workflows executed through `@oscharko-dev/keiko-harness` and
4
+ // `@oscharko-dev/keiko-workflows`. Each descriptor enumerates the deterministic
5
+ // stage sequence, the emitted event kinds, and the typed limits the run entry
6
+ // honours. NO model SDK reaches this module: stage runtime composes the gateway
7
+ // dispatcher seam shipped in #279.
8
+ //
9
+ // Structurally inspired by Test Intelligence reference (TI) workflow staging,
10
+ // but the Keiko port stays envelope-shaped, descriptor-driven, and free of
11
+ // any TI runtime / IR.
12
+ //
13
+ // The descriptors here are pure value records — they intentionally do NOT
14
+ // instantiate the WorkflowDescriptor shape from `../descriptor.js` because that
15
+ // shape is tuned to the apply/dry-run developer-assist workflows (#8/#9) and
16
+ // would force fictional input slots onto QI runs. The QI surface re-exports
17
+ // `QualityIntelligenceWorkflowDescriptor` from this file directly.
18
+ export const QUALITY_INTELLIGENCE_DEFAULT_WORKFLOW_LIMITS = Object.freeze({
19
+ stageTimeoutMs: 45_000,
20
+ maxCandidatesPerRun: 256,
21
+ maxFindingsPerRun: 512,
22
+ maxModelCallsPerRun: 32,
23
+ maxJudgeCallsPerRun: 256,
24
+ });
25
+ const FROZEN_DEFAULT_LIMITS = QUALITY_INTELLIGENCE_DEFAULT_WORKFLOW_LIMITS;
26
+ function freezeStringArray(values) {
27
+ return Object.freeze([...values]);
28
+ }
29
+ function freezeEventKinds(values) {
30
+ return Object.freeze([...values]);
31
+ }
32
+ function freezeDescriptor(descriptor) {
33
+ return Object.freeze({
34
+ ...descriptor,
35
+ stageNames: freezeStringArray(descriptor.stageNames),
36
+ emittedEventKinds: freezeEventKinds(descriptor.emittedEventKinds),
37
+ defaultLimits: descriptor.defaultLimits,
38
+ });
39
+ }
40
+ // Shared run-lifecycle envelope event kinds. Every QI workflow emits this set;
41
+ // stage-specific extra kinds are added per descriptor below.
42
+ const LIFECYCLE_EVENT_KINDS = [
43
+ "run:queued",
44
+ "run:started",
45
+ "stage:started",
46
+ "stage:completed",
47
+ "stage:failed",
48
+ "run:succeeded",
49
+ "run:failed",
50
+ "run:cancelled",
51
+ ];
52
+ export const QI_TEST_DESIGN_WORKFLOW_DESCRIPTOR = freezeDescriptor({
53
+ workflowId: "qi:test-design",
54
+ displayName: "Quality Intelligence — Test Design",
55
+ description: "Draft test-case candidates, adversarially judge their quality, build coverage map, validate, " +
56
+ "persist evidence. Stages: plan, candidates, judge, coverage, validate, finalize.",
57
+ stageNames: ["plan", "candidates", "judge", "coverage", "validate", "finalize"],
58
+ emittedEventKinds: [...LIFECYCLE_EVENT_KINDS, "candidate:proposed", "finding:recorded"],
59
+ defaultLimits: FROZEN_DEFAULT_LIMITS,
60
+ preferredCostClass: "medium",
61
+ });
62
+ export const QI_COVERAGE_REVIEW_WORKFLOW_DESCRIPTOR = freezeDescriptor({
63
+ workflowId: "qi:coverage-review",
64
+ displayName: "Quality Intelligence — Coverage Review",
65
+ description: "Build and report a coverage map across existing candidates and atoms. " +
66
+ "Stages: plan, analyse, report.",
67
+ stageNames: ["plan", "analyse", "report"],
68
+ emittedEventKinds: [...LIFECYCLE_EVENT_KINDS],
69
+ defaultLimits: FROZEN_DEFAULT_LIMITS,
70
+ preferredCostClass: "low",
71
+ });
72
+ export const QI_VALIDATION_WORKFLOW_DESCRIPTOR = freezeDescriptor({
73
+ workflowId: "qi:validation",
74
+ displayName: "Quality Intelligence — Validation",
75
+ description: "Run schema/logic validators and optional model judges over existing candidates, reconcile, persist. " +
76
+ "Stages: plan, run-judges, reconcile, report.",
77
+ stageNames: ["plan", "run-judges", "reconcile", "report"],
78
+ emittedEventKinds: [...LIFECYCLE_EVENT_KINDS, "finding:recorded"],
79
+ defaultLimits: FROZEN_DEFAULT_LIMITS,
80
+ preferredCostClass: "high",
81
+ });
82
+ export const QI_ARTIFACT_REFINEMENT_WORKFLOW_DESCRIPTOR = freezeDescriptor({
83
+ workflowId: "qi:artifact-refinement",
84
+ displayName: "Quality Intelligence — Artifact Refinement",
85
+ description: "Refine existing candidates by re-applying the policy profile and deduplication, then validate. " +
86
+ "Stages: plan, refine, validate, report.",
87
+ stageNames: ["plan", "refine", "validate", "report"],
88
+ emittedEventKinds: [...LIFECYCLE_EVENT_KINDS, "candidate:proposed", "finding:recorded"],
89
+ defaultLimits: FROZEN_DEFAULT_LIMITS,
90
+ preferredCostClass: "medium",
91
+ });
92
+ export const QUALITY_INTELLIGENCE_WORKFLOW_DESCRIPTORS = Object.freeze([
93
+ QI_TEST_DESIGN_WORKFLOW_DESCRIPTOR,
94
+ QI_COVERAGE_REVIEW_WORKFLOW_DESCRIPTOR,
95
+ QI_VALIDATION_WORKFLOW_DESCRIPTOR,
96
+ QI_ARTIFACT_REFINEMENT_WORKFLOW_DESCRIPTOR,
97
+ ]);
98
+ export function findQualityIntelligenceWorkflowDescriptor(workflowId) {
99
+ for (const descriptor of QUALITY_INTELLIGENCE_WORKFLOW_DESCRIPTORS) {
100
+ if (descriptor.workflowId === workflowId) {
101
+ return descriptor;
102
+ }
103
+ }
104
+ throw new Error(`Unknown Quality Intelligence workflow id: ${workflowId}`);
105
+ }