@oscharko-dev/keiko-contracts 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 (227) hide show
  1. package/dist/.tsbuildinfo +1 -0
  2. package/dist/bff-wire.d.ts +661 -0
  3. package/dist/bff-wire.d.ts.map +1 -0
  4. package/dist/bff-wire.js +102 -0
  5. package/dist/bug-investigation-events.d.ts +92 -0
  6. package/dist/bug-investigation-events.d.ts.map +1 -0
  7. package/dist/bug-investigation-events.js +18 -0
  8. package/dist/coding-context.d.ts +76 -0
  9. package/dist/coding-context.d.ts.map +1 -0
  10. package/dist/coding-context.js +158 -0
  11. package/dist/connected-context.d.ts +174 -0
  12. package/dist/connected-context.d.ts.map +1 -0
  13. package/dist/connected-context.js +636 -0
  14. package/dist/conversation-budget.d.ts +37 -0
  15. package/dist/conversation-budget.d.ts.map +1 -0
  16. package/dist/conversation-budget.js +97 -0
  17. package/dist/editor-agent.d.ts +131 -0
  18. package/dist/editor-agent.d.ts.map +1 -0
  19. package/dist/editor-agent.js +197 -0
  20. package/dist/editor-completion.d.ts +62 -0
  21. package/dist/editor-completion.d.ts.map +1 -0
  22. package/dist/editor-completion.js +147 -0
  23. package/dist/editor-dirty-close.d.ts +17 -0
  24. package/dist/editor-dirty-close.d.ts.map +1 -0
  25. package/dist/editor-dirty-close.js +8 -0
  26. package/dist/editor-hot-exit.d.ts +18 -0
  27. package/dist/editor-hot-exit.d.ts.map +1 -0
  28. package/dist/editor-hot-exit.js +42 -0
  29. package/dist/editor-inline-completion.d.ts +70 -0
  30. package/dist/editor-inline-completion.d.ts.map +1 -0
  31. package/dist/editor-inline-completion.js +215 -0
  32. package/dist/editor-layout.d.ts +105 -0
  33. package/dist/editor-layout.d.ts.map +1 -0
  34. package/dist/editor-layout.js +479 -0
  35. package/dist/editor-patch-apply.d.ts +77 -0
  36. package/dist/editor-patch-apply.d.ts.map +1 -0
  37. package/dist/editor-patch-apply.js +122 -0
  38. package/dist/editor-session.d.ts +31 -0
  39. package/dist/editor-session.d.ts.map +1 -0
  40. package/dist/editor-session.js +75 -0
  41. package/dist/editor-test-generation.d.ts +104 -0
  42. package/dist/editor-test-generation.d.ts.map +1 -0
  43. package/dist/editor-test-generation.js +211 -0
  44. package/dist/evaluations.d.ts +75 -0
  45. package/dist/evaluations.d.ts.map +1 -0
  46. package/dist/evaluations.js +16 -0
  47. package/dist/evidence.d.ts +297 -0
  48. package/dist/evidence.d.ts.map +1 -0
  49. package/dist/evidence.js +9 -0
  50. package/dist/gateway.d.ts +129 -0
  51. package/dist/gateway.d.ts.map +1 -0
  52. package/dist/gateway.js +66 -0
  53. package/dist/harness.d.ts +274 -0
  54. package/dist/harness.d.ts.map +1 -0
  55. package/dist/harness.js +38 -0
  56. package/dist/index.d.ts +101 -0
  57. package/dist/index.d.ts.map +1 -0
  58. package/dist/index.js +83 -0
  59. package/dist/language-service.d.ts +145 -0
  60. package/dist/language-service.d.ts.map +1 -0
  61. package/dist/language-service.js +161 -0
  62. package/dist/local-knowledge-large-document-validation.d.ts +7 -0
  63. package/dist/local-knowledge-large-document-validation.d.ts.map +1 -0
  64. package/dist/local-knowledge-large-document-validation.js +161 -0
  65. package/dist/local-knowledge-large-document.d.ts +113 -0
  66. package/dist/local-knowledge-large-document.d.ts.map +1 -0
  67. package/dist/local-knowledge-large-document.js +142 -0
  68. package/dist/local-knowledge-paths.d.ts +3 -0
  69. package/dist/local-knowledge-paths.d.ts.map +1 -0
  70. package/dist/local-knowledge-paths.js +65 -0
  71. package/dist/local-knowledge-records.d.ts +190 -0
  72. package/dist/local-knowledge-records.d.ts.map +1 -0
  73. package/dist/local-knowledge-records.js +36 -0
  74. package/dist/local-knowledge-schema-validation.d.ts +19 -0
  75. package/dist/local-knowledge-schema-validation.d.ts.map +1 -0
  76. package/dist/local-knowledge-schema-validation.js +115 -0
  77. package/dist/local-knowledge-schema.d.ts +14 -0
  78. package/dist/local-knowledge-schema.d.ts.map +1 -0
  79. package/dist/local-knowledge-schema.js +715 -0
  80. package/dist/local-knowledge-validation.d.ts +20 -0
  81. package/dist/local-knowledge-validation.d.ts.map +1 -0
  82. package/dist/local-knowledge-validation.js +487 -0
  83. package/dist/local-knowledge.d.ts +158 -0
  84. package/dist/local-knowledge.d.ts.map +1 -0
  85. package/dist/local-knowledge.js +63 -0
  86. package/dist/memory-audit-events.d.ts +73 -0
  87. package/dist/memory-audit-events.d.ts.map +1 -0
  88. package/dist/memory-audit-events.js +44 -0
  89. package/dist/memory-audit-validation.d.ts +4 -0
  90. package/dist/memory-audit-validation.d.ts.map +1 -0
  91. package/dist/memory-audit-validation.js +151 -0
  92. package/dist/memory-barrel.d.ts +15 -0
  93. package/dist/memory-barrel.d.ts.map +1 -0
  94. package/dist/memory-barrel.js +20 -0
  95. package/dist/memory-internal.d.ts +26 -0
  96. package/dist/memory-internal.d.ts.map +1 -0
  97. package/dist/memory-internal.js +104 -0
  98. package/dist/memory-operations-validation.d.ts +12 -0
  99. package/dist/memory-operations-validation.d.ts.map +1 -0
  100. package/dist/memory-operations-validation.js +267 -0
  101. package/dist/memory-operations.d.ts +156 -0
  102. package/dist/memory-operations.d.ts.map +1 -0
  103. package/dist/memory-operations.js +29 -0
  104. package/dist/memory-record-validation.d.ts +10 -0
  105. package/dist/memory-record-validation.d.ts.map +1 -0
  106. package/dist/memory-record-validation.js +101 -0
  107. package/dist/memory-records.d.ts +66 -0
  108. package/dist/memory-records.d.ts.map +1 -0
  109. package/dist/memory-records.js +22 -0
  110. package/dist/memory-retrieval-validation.d.ts +6 -0
  111. package/dist/memory-retrieval-validation.d.ts.map +1 -0
  112. package/dist/memory-retrieval-validation.js +108 -0
  113. package/dist/memory-validation.d.ts +31 -0
  114. package/dist/memory-validation.d.ts.map +1 -0
  115. package/dist/memory-validation.js +318 -0
  116. package/dist/memory-workflow-port.d.ts +26 -0
  117. package/dist/memory-workflow-port.d.ts.map +1 -0
  118. package/dist/memory-workflow-port.js +13 -0
  119. package/dist/memory.d.ts +81 -0
  120. package/dist/memory.d.ts.map +1 -0
  121. package/dist/memory.js +104 -0
  122. package/dist/prompt-enhancer-analyzer.d.ts +7 -0
  123. package/dist/prompt-enhancer-analyzer.d.ts.map +1 -0
  124. package/dist/prompt-enhancer-analyzer.js +745 -0
  125. package/dist/prompt-enhancer-bff.d.ts +67 -0
  126. package/dist/prompt-enhancer-bff.d.ts.map +1 -0
  127. package/dist/prompt-enhancer-bff.js +156 -0
  128. package/dist/prompt-enhancer-critic.d.ts +46 -0
  129. package/dist/prompt-enhancer-critic.d.ts.map +1 -0
  130. package/dist/prompt-enhancer-critic.js +35 -0
  131. package/dist/prompt-enhancer-grounding.d.ts +19 -0
  132. package/dist/prompt-enhancer-grounding.d.ts.map +1 -0
  133. package/dist/prompt-enhancer-grounding.js +235 -0
  134. package/dist/prompt-enhancer-safety.d.ts +66 -0
  135. package/dist/prompt-enhancer-safety.d.ts.map +1 -0
  136. package/dist/prompt-enhancer-safety.js +446 -0
  137. package/dist/prompt-enhancer-validation.d.ts +28 -0
  138. package/dist/prompt-enhancer-validation.d.ts.map +1 -0
  139. package/dist/prompt-enhancer-validation.js +931 -0
  140. package/dist/prompt-enhancer.d.ts +184 -0
  141. package/dist/prompt-enhancer.d.ts.map +1 -0
  142. package/dist/prompt-enhancer.js +350 -0
  143. package/dist/qualityIntelligence/assertNever.d.ts +2 -0
  144. package/dist/qualityIntelligence/assertNever.d.ts.map +1 -0
  145. package/dist/qualityIntelligence/assertNever.js +7 -0
  146. package/dist/qualityIntelligence/auditSummary.d.ts +25 -0
  147. package/dist/qualityIntelligence/auditSummary.d.ts.map +1 -0
  148. package/dist/qualityIntelligence/auditSummary.js +7 -0
  149. package/dist/qualityIntelligence/bffWire.d.ts +356 -0
  150. package/dist/qualityIntelligence/bffWire.d.ts.map +1 -0
  151. package/dist/qualityIntelligence/bffWire.js +22 -0
  152. package/dist/qualityIntelligence/coverageMap.d.ts +21 -0
  153. package/dist/qualityIntelligence/coverageMap.d.ts.map +1 -0
  154. package/dist/qualityIntelligence/coverageMap.js +29 -0
  155. package/dist/qualityIntelligence/editableRevision.d.ts +21 -0
  156. package/dist/qualityIntelligence/editableRevision.d.ts.map +1 -0
  157. package/dist/qualityIntelligence/editableRevision.js +8 -0
  158. package/dist/qualityIntelligence/evidenceAtom.d.ts +35 -0
  159. package/dist/qualityIntelligence/evidenceAtom.d.ts.map +1 -0
  160. package/dist/qualityIntelligence/evidenceAtom.js +29 -0
  161. package/dist/qualityIntelligence/exportBundle.d.ts +28 -0
  162. package/dist/qualityIntelligence/exportBundle.d.ts.map +1 -0
  163. package/dist/qualityIntelligence/exportBundle.js +46 -0
  164. package/dist/qualityIntelligence/handoffEnvelope.d.ts +23 -0
  165. package/dist/qualityIntelligence/handoffEnvelope.d.ts.map +1 -0
  166. package/dist/qualityIntelligence/handoffEnvelope.js +8 -0
  167. package/dist/qualityIntelligence/ids.d.ts +58 -0
  168. package/dist/qualityIntelligence/ids.d.ts.map +1 -0
  169. package/dist/qualityIntelligence/ids.js +93 -0
  170. package/dist/qualityIntelligence/index.d.ts +29 -0
  171. package/dist/qualityIntelligence/index.d.ts.map +1 -0
  172. package/dist/qualityIntelligence/index.js +20 -0
  173. package/dist/qualityIntelligence/reviewRecord.d.ts +19 -0
  174. package/dist/qualityIntelligence/reviewRecord.d.ts.map +1 -0
  175. package/dist/qualityIntelligence/reviewRecord.js +20 -0
  176. package/dist/qualityIntelligence/runPlanAndEvents.d.ts +84 -0
  177. package/dist/qualityIntelligence/runPlanAndEvents.d.ts.map +1 -0
  178. package/dist/qualityIntelligence/runPlanAndEvents.js +51 -0
  179. package/dist/qualityIntelligence/sourceEnvelope.d.ts +77 -0
  180. package/dist/qualityIntelligence/sourceEnvelope.d.ts.map +1 -0
  181. package/dist/qualityIntelligence/sourceEnvelope.js +118 -0
  182. package/dist/qualityIntelligence/testCaseCandidate.d.ts +21 -0
  183. package/dist/qualityIntelligence/testCaseCandidate.d.ts.map +1 -0
  184. package/dist/qualityIntelligence/testCaseCandidate.js +21 -0
  185. package/dist/qualityIntelligence/testQualityRubric.d.ts +17 -0
  186. package/dist/qualityIntelligence/testQualityRubric.d.ts.map +1 -0
  187. package/dist/qualityIntelligence/testQualityRubric.js +32 -0
  188. package/dist/qualityIntelligence/validationFinding.d.ts +48 -0
  189. package/dist/qualityIntelligence/validationFinding.d.ts.map +1 -0
  190. package/dist/qualityIntelligence/validationFinding.js +36 -0
  191. package/dist/relationships-validation.d.ts +13 -0
  192. package/dist/relationships-validation.d.ts.map +1 -0
  193. package/dist/relationships-validation.js +422 -0
  194. package/dist/relationships.d.ts +79 -0
  195. package/dist/relationships.d.ts.map +1 -0
  196. package/dist/relationships.js +307 -0
  197. package/dist/text-safety.d.ts +7 -0
  198. package/dist/text-safety.d.ts.map +1 -0
  199. package/dist/text-safety.js +58 -0
  200. package/dist/tools.d.ts +153 -0
  201. package/dist/tools.d.ts.map +1 -0
  202. package/dist/tools.js +118 -0
  203. package/dist/unit-test-events.d.ts +87 -0
  204. package/dist/unit-test-events.d.ts.map +1 -0
  205. package/dist/unit-test-events.js +14 -0
  206. package/dist/verification-summary.d.ts +38 -0
  207. package/dist/verification-summary.d.ts.map +1 -0
  208. package/dist/verification-summary.js +5 -0
  209. package/dist/verification.d.ts +64 -0
  210. package/dist/verification.d.ts.map +1 -0
  211. package/dist/verification.js +13 -0
  212. package/dist/workflow-descriptor.d.ts +21 -0
  213. package/dist/workflow-descriptor.d.ts.map +1 -0
  214. package/dist/workflow-descriptor.js +8 -0
  215. package/dist/workflow-handoff.d.ts +69 -0
  216. package/dist/workflow-handoff.d.ts.map +1 -0
  217. package/dist/workflow-handoff.js +381 -0
  218. package/dist/workspace-descriptors.d.ts +21 -0
  219. package/dist/workspace-descriptors.d.ts.map +1 -0
  220. package/dist/workspace-descriptors.js +180 -0
  221. package/dist/workspace-ui.d.ts +119 -0
  222. package/dist/workspace-ui.d.ts.map +1 -0
  223. package/dist/workspace-ui.js +105 -0
  224. package/dist/workspace.d.ts +104 -0
  225. package/dist/workspace.d.ts.map +1 -0
  226. package/dist/workspace.js +27 -0
  227. package/package.json +71 -0
@@ -0,0 +1,931 @@
1
+ // Pure validators for the Prompt Enhancer contract surface (Issue #1309). Sibling of
2
+ // `prompt-enhancer.ts` and `prompt-enhancer-analyzer.ts`.
3
+ //
4
+ // Every validator is pure: no IO, no clock, no crypto, no randomness, no module-level side effects.
5
+ // Each returns a discriminated `{ ok: true; value } | { ok: false; errors }` so downstream code can
6
+ // branch without throwing. Error messages are short, deterministic, machine-readable, one per failed
7
+ // invariant, and never echo raw user input (safe-error discipline, ADR-0044 §5). Leaf-package rule
8
+ // (ADR-0019 direction 1): no `@oscharko-dev/keiko-*` imports.
9
+ import { stripUnsafeFormatChars } from "./text-safety.js";
10
+ import { CITATION_DISCIPLINES, CITATION_GRANULARITIES, CONTRADICTION_POLICIES, GROUNDING_DIRECTIVES, GROUNDING_NEED_KINDS, GROUNDING_SIGNALS, GROUNDING_SOURCE_KINDS, GROUNDING_STRATEGIES, MISSING_CONTEXT_TOPICS, MISSING_INFORMATION_STRATEGIES, NO_ANSWER_CONDITIONS, OUTPUT_FORMAT_HINTS, PROMPT_ANALYSIS_MAX_SCAN_CHARS, PROMPT_CRITICALITIES, PROMPT_DOMAINS, PROMPT_ENHANCEMENT_PROFILE_IDS, PROMPT_ENHANCER_SCHEMA_VERSION, PROMPT_OUTPUT_FORMATS, PROMPT_RISK_CLASSES, PROMPT_SIGNAL_DIMENSIONS, PROMPT_SIGNAL_STRENGTHS, PROMPT_TASK_CLASSES, RAG_EVALUATION_DIMENSIONS, RETRIEVAL_MODES, validatePromptEnhancerIdString, } from "./prompt-enhancer.js";
11
+ import { buildDirectives, buildSourcePriority, MULTI_SOURCE_STRATEGIES, RAG_HINT_TEMPLATES, RETRIEVAL_MODES_BY_STRATEGY, SCOPED_EVIDENCE_STRATEGIES, } from "./prompt-enhancer-grounding.js";
12
+ import { PROMPT_CRITIC_DIMENSIONS, isPromptCandidateRejectionReason, } from "./prompt-enhancer-critic.js";
13
+ import { validatePromptSafetyAssessment, } from "./prompt-enhancer-safety.js";
14
+ // Maximum length of a raw draft accepted by the request validator. Generous enough for large pasted
15
+ // drafts while ensuring analyzer risk classification cannot silently ignore unvalidated suffix text.
16
+ export const PROMPT_REQUEST_TEXT_MAX_CHARS = PROMPT_ANALYSIS_MAX_SCAN_CHARS;
17
+ const PROMPT_LOCALE_MAX_CHARS = 35;
18
+ const PROMPT_SIGNAL_CODE_MAX_CHARS = 128;
19
+ const ENHANCED_PROMPT_FIELD_MAX_CHARS = 20_000;
20
+ const ENHANCED_PROMPT_LIST_MAX = 256;
21
+ const STRUCTURED_OUTPUT_FORMATS = new Set(["json", "yaml", "csv", "table"]);
22
+ const REQUEST_KEYS = new Set([
23
+ "schemaVersion",
24
+ "requestId",
25
+ "input",
26
+ "missingInformationStrategy",
27
+ "profilePreference",
28
+ "locale",
29
+ ]);
30
+ const RAW_INPUT_KEYS = new Set([
31
+ "text",
32
+ "hasConnectedContext",
33
+ "attachmentCount",
34
+ ]);
35
+ const ANALYSIS_KEYS = new Set([
36
+ "schemaVersion",
37
+ "requestId",
38
+ "taskClass",
39
+ "taskClassConfidence",
40
+ "domain",
41
+ "criticality",
42
+ "groundingNeed",
43
+ "outputSchema",
44
+ "missingContext",
45
+ "riskFlags",
46
+ "recommendedProfile",
47
+ "normalizedInputLength",
48
+ "signals",
49
+ ]);
50
+ const GROUNDING_NEED_KEYS = new Set(["kind", "volatile", "signals"]);
51
+ const OUTPUT_SCHEMA_KEYS = new Set(["format", "structured", "hints"]);
52
+ const SIGNAL_KEYS = new Set(["dimension", "code"]);
53
+ const CLARIFICATION_KEYS = new Set(["kind", "topic", "question"]);
54
+ const ASSUMPTION_KEYS = new Set(["kind", "topic", "statement"]);
55
+ const ENHANCED_PROMPT_KEYS = new Set([
56
+ "schemaVersion",
57
+ "promptId",
58
+ "role",
59
+ "goal",
60
+ "context",
61
+ "input",
62
+ "taskDecomposition",
63
+ "constraints",
64
+ "groundingRules",
65
+ "groundingPlan",
66
+ "outputSchema",
67
+ "qualityCriteria",
68
+ "uncertaintyHandling",
69
+ "safetyRules",
70
+ ]);
71
+ const GROUNDING_PLAN_KEYS = new Set([
72
+ "strategy",
73
+ "required",
74
+ "allowedRetrievalModes",
75
+ "sourcePriority",
76
+ "citation",
77
+ "recency",
78
+ "contradictionPolicy",
79
+ "noAnswerConditions",
80
+ "directives",
81
+ "ragEvaluation",
82
+ "untrustedContent",
83
+ ]);
84
+ const SOURCE_POLICY_KEYS = new Set(["source", "priority", "required"]);
85
+ const CITATION_KEYS = new Set(["discipline", "granularity"]);
86
+ const RECENCY_KEYS = new Set([
87
+ "volatile",
88
+ "requireAsOfDate",
89
+ "flagPotentiallyStale",
90
+ ]);
91
+ const RAG_HINT_KEYS = new Set(["dimension", "instruction"]);
92
+ const RAG_HINT_INSTRUCTION_MAX_CHARS = 400;
93
+ const CLARIFICATION_TEMPLATES = {
94
+ subject: "What specific subject or task should this prompt address?",
95
+ scope: "Which part of the work should the task focus on?",
96
+ audience: "Who is the intended audience for the output?",
97
+ "output-format": "What output format is expected (for example JSON, a table, or prose)?",
98
+ constraints: "Are there language, framework, or length constraints to honor?",
99
+ "data-source": "Which files, documents, or sources should ground the answer?",
100
+ "success-criteria": "What defines a successful outcome for this task?",
101
+ };
102
+ const ASSUMPTION_TEMPLATES = {
103
+ subject: "Assuming the broadest reasonable interpretation of the requested subject.",
104
+ scope: "Assuming the task applies to the most relevant available scope.",
105
+ audience: "Assuming a general professional audience.",
106
+ "output-format": "Assuming a structured format appropriate to the task.",
107
+ constraints: "Assuming no constraints beyond standard best practices.",
108
+ "data-source": "Assuming the answer should rely only on supplied context, with gaps flagged.",
109
+ "success-criteria": "Assuming correctness and completeness are the primary success criteria.",
110
+ };
111
+ // ─── Pure predicates ─────────────────────────────────────────────────────────────
112
+ const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
113
+ const isMember = (value, allowed) => typeof value === "string" && allowed.includes(value);
114
+ // A bounded array whose every entry is a member of the allowed closed set.
115
+ const isMemberArray = (value, allowed) => Array.isArray(value) && value.every((item) => isMember(item, allowed));
116
+ const arraysEqual = (actual, expected) => actual.length === expected.length && actual.every((entry, index) => entry === expected[index]);
117
+ const isNonNegativeInteger = (value) => typeof value === "number" && Number.isInteger(value) && value >= 0;
118
+ const isPositiveInteger = (value) => typeof value === "number" && Number.isInteger(value) && value > 0;
119
+ // A bounded, control-free string (TAB/LF/CR permitted via the text-safety policy). Rejects bidi /
120
+ // zero-width / control code points and over-length values.
121
+ const isBoundedSafeText = (value, max) => typeof value === "string" && value.length <= max && stripUnsafeFormatChars(value) === value;
122
+ function validateExactKeys(value, allowed, label, errors) {
123
+ if (Object.keys(value).some((key) => !allowed.has(key))) {
124
+ errors.push(`${label} must not contain unknown fields`);
125
+ }
126
+ }
127
+ function pushIdError(value, field, kind, errors) {
128
+ const result = validatePromptEnhancerIdString(value, kind);
129
+ if (!result.ok)
130
+ errors.push(`${field}: ${result.reason}`);
131
+ }
132
+ // ─── Request validator ───────────────────────────────────────────────────────────
133
+ function validateRawInput(input, errors) {
134
+ if (!isRecord(input)) {
135
+ errors.push("request.input must be an object");
136
+ return;
137
+ }
138
+ validateExactKeys(input, RAW_INPUT_KEYS, "request.input", errors);
139
+ if (typeof input.text !== "string" || input.text.length > PROMPT_REQUEST_TEXT_MAX_CHARS) {
140
+ errors.push(`request.input.text must be a string of at most ${String(PROMPT_REQUEST_TEXT_MAX_CHARS)} characters`);
141
+ }
142
+ if (input.hasConnectedContext !== undefined && typeof input.hasConnectedContext !== "boolean") {
143
+ errors.push("request.input.hasConnectedContext must be a boolean when set");
144
+ }
145
+ if (input.attachmentCount !== undefined && !isNonNegativeInteger(input.attachmentCount)) {
146
+ errors.push("request.input.attachmentCount must be a non-negative integer when set");
147
+ }
148
+ }
149
+ export function validatePromptEnhancementRequest(input) {
150
+ if (!isRecord(input)) {
151
+ return { ok: false, errors: ["request must be an object"] };
152
+ }
153
+ const errors = [];
154
+ validateExactKeys(input, REQUEST_KEYS, "request", errors);
155
+ if (input.schemaVersion !== PROMPT_ENHANCER_SCHEMA_VERSION) {
156
+ errors.push(`request.schemaVersion must be "${PROMPT_ENHANCER_SCHEMA_VERSION}"`);
157
+ }
158
+ pushIdError(input.requestId, "request.requestId", "PromptEnhancementRequestId", errors);
159
+ validateRawInput(input.input, errors);
160
+ if (!isMember(input.missingInformationStrategy, MISSING_INFORMATION_STRATEGIES)) {
161
+ errors.push(`request.missingInformationStrategy must be one of ${MISSING_INFORMATION_STRATEGIES.join("|")}`);
162
+ }
163
+ if (input.profilePreference !== undefined &&
164
+ !isMember(input.profilePreference, PROMPT_ENHANCEMENT_PROFILE_IDS)) {
165
+ errors.push(`request.profilePreference must be one of ${PROMPT_ENHANCEMENT_PROFILE_IDS.join("|")} when set`);
166
+ }
167
+ if (input.locale !== undefined && !isBoundedSafeText(input.locale, PROMPT_LOCALE_MAX_CHARS)) {
168
+ errors.push("request.locale must be a bounded, control-free string when set");
169
+ }
170
+ if (errors.length > 0)
171
+ return { ok: false, errors };
172
+ return { ok: true, value: input };
173
+ }
174
+ // ─── Shared sub-shape validators ─────────────────────────────────────────────────
175
+ function validateGroundingNeed(value, errors) {
176
+ if (!isRecord(value)) {
177
+ errors.push("analysis.groundingNeed must be an object");
178
+ return;
179
+ }
180
+ validateExactKeys(value, GROUNDING_NEED_KEYS, "analysis.groundingNeed", errors);
181
+ if (!isMember(value.kind, GROUNDING_NEED_KINDS)) {
182
+ errors.push(`analysis.groundingNeed.kind must be one of ${GROUNDING_NEED_KINDS.join("|")}`);
183
+ }
184
+ if (typeof value.volatile !== "boolean") {
185
+ errors.push("analysis.groundingNeed.volatile must be a boolean");
186
+ }
187
+ if (!Array.isArray(value.signals) || value.signals.some((s) => !isMember(s, GROUNDING_SIGNALS))) {
188
+ errors.push("analysis.groundingNeed.signals must be an array of known grounding signals");
189
+ }
190
+ }
191
+ function validateOutputSchema(value, errors) {
192
+ if (!isRecord(value)) {
193
+ errors.push("outputSchema must be an object");
194
+ return;
195
+ }
196
+ validateExactKeys(value, OUTPUT_SCHEMA_KEYS, "outputSchema", errors);
197
+ if (!isMember(value.format, PROMPT_OUTPUT_FORMATS)) {
198
+ errors.push(`outputSchema.format must be one of ${PROMPT_OUTPUT_FORMATS.join("|")}`);
199
+ }
200
+ if (typeof value.structured !== "boolean") {
201
+ errors.push("outputSchema.structured must be a boolean");
202
+ }
203
+ else if (typeof value.format === "string" &&
204
+ PROMPT_OUTPUT_FORMATS.includes(value.format) &&
205
+ value.structured !== STRUCTURED_OUTPUT_FORMATS.has(value.format)) {
206
+ errors.push("outputSchema.structured must match the selected output format");
207
+ }
208
+ if (!Array.isArray(value.hints) || value.hints.some((h) => !isMember(h, OUTPUT_FORMAT_HINTS))) {
209
+ errors.push("outputSchema.hints must be an array of known output-format hints");
210
+ }
211
+ }
212
+ function isValidMissingContextItem(item) {
213
+ if (!isRecord(item) || !isMember(item.topic, MISSING_CONTEXT_TOPICS))
214
+ return false;
215
+ if (item.kind === "clarification") {
216
+ return (Object.keys(item).every((key) => CLARIFICATION_KEYS.has(key)) &&
217
+ item.question === CLARIFICATION_TEMPLATES[item.topic]);
218
+ }
219
+ if (item.kind === "assumption") {
220
+ return (Object.keys(item).every((key) => ASSUMPTION_KEYS.has(key)) &&
221
+ item.statement === ASSUMPTION_TEMPLATES[item.topic]);
222
+ }
223
+ return false;
224
+ }
225
+ function validateMissingContext(value, errors) {
226
+ if (!Array.isArray(value) || value.some((item) => !isValidMissingContextItem(item))) {
227
+ errors.push("analysis.missingContext must be an array of clarification or assumption items");
228
+ }
229
+ }
230
+ function validateSignals(value, errors) {
231
+ if (!Array.isArray(value) || value.some((s) => !isRecord(s) || !isValidSignal(s))) {
232
+ errors.push("analysis.signals must be an array of {dimension, code} records");
233
+ }
234
+ }
235
+ function isCodeWithMember(code, prefix, allowed) {
236
+ return code.startsWith(prefix) && allowed.includes(code.slice(prefix.length));
237
+ }
238
+ const SIGNAL_CODE_VALIDATORS = {
239
+ "task-class": (code) => isCodeWithMember(code, "class:", PROMPT_TASK_CLASSES),
240
+ domain: (code) => isCodeWithMember(code, "domain:", PROMPT_DOMAINS),
241
+ grounding: (code) => isCodeWithMember(code, "grounding:", GROUNDING_NEED_KINDS) ||
242
+ isCodeWithMember(code, "signal:", GROUNDING_SIGNALS),
243
+ output: (code) => isCodeWithMember(code, "format:", PROMPT_OUTPUT_FORMATS) ||
244
+ isCodeWithMember(code, "hint:", OUTPUT_FORMAT_HINTS),
245
+ risk: (code) => isCodeWithMember(code, "risk:", PROMPT_RISK_CLASSES),
246
+ "missing-context": (code) => isCodeWithMember(code, "topic:", MISSING_CONTEXT_TOPICS),
247
+ };
248
+ function isValidSignal(signal) {
249
+ if (!Object.keys(signal).every((key) => SIGNAL_KEYS.has(key)) ||
250
+ !isMember(signal.dimension, PROMPT_SIGNAL_DIMENSIONS) ||
251
+ !isBoundedSafeText(signal.code, PROMPT_SIGNAL_CODE_MAX_CHARS)) {
252
+ return false;
253
+ }
254
+ const validator = SIGNAL_CODE_VALIDATORS[signal.dimension];
255
+ return validator?.(signal.code) === true;
256
+ }
257
+ // ─── Analysis validator ──────────────────────────────────────────────────────────
258
+ function validateAnalysisEnums(value, errors) {
259
+ if (!isMember(value.taskClass, PROMPT_TASK_CLASSES))
260
+ errors.push("analysis.taskClass is not a known task class");
261
+ if (!isMember(value.taskClassConfidence, PROMPT_SIGNAL_STRENGTHS)) {
262
+ errors.push("analysis.taskClassConfidence is not a known signal strength");
263
+ }
264
+ if (!isMember(value.domain, PROMPT_DOMAINS))
265
+ errors.push("analysis.domain is not a known domain");
266
+ if (!isMember(value.criticality, PROMPT_CRITICALITIES))
267
+ errors.push("analysis.criticality is not a known criticality");
268
+ if (!isMember(value.recommendedProfile, PROMPT_ENHANCEMENT_PROFILE_IDS)) {
269
+ errors.push("analysis.recommendedProfile is not a known profile id");
270
+ }
271
+ if (!Array.isArray(value.riskFlags) ||
272
+ value.riskFlags.some((f) => !isMember(f, PROMPT_RISK_CLASSES))) {
273
+ errors.push("analysis.riskFlags must be an array of known risk classes");
274
+ }
275
+ }
276
+ export function validatePromptTaskAnalysis(input) {
277
+ if (!isRecord(input)) {
278
+ return { ok: false, errors: ["analysis must be an object"] };
279
+ }
280
+ const errors = [];
281
+ validateExactKeys(input, ANALYSIS_KEYS, "analysis", errors);
282
+ if (input.schemaVersion !== PROMPT_ENHANCER_SCHEMA_VERSION) {
283
+ errors.push(`analysis.schemaVersion must be "${PROMPT_ENHANCER_SCHEMA_VERSION}"`);
284
+ }
285
+ pushIdError(input.requestId, "analysis.requestId", "PromptEnhancementRequestId", errors);
286
+ validateAnalysisEnums(input, errors);
287
+ validateGroundingNeed(input.groundingNeed, errors);
288
+ validateOutputSchema(input.outputSchema, errors);
289
+ validateMissingContext(input.missingContext, errors);
290
+ validateSignals(input.signals, errors);
291
+ if (!isNonNegativeInteger(input.normalizedInputLength)) {
292
+ errors.push("analysis.normalizedInputLength must be a non-negative integer");
293
+ }
294
+ if (errors.length > 0)
295
+ return { ok: false, errors };
296
+ return { ok: true, value: input };
297
+ }
298
+ // ─── Grounding plan validator (Issue #1311) ────────────────────────────────────────
299
+ function validateGroundingPlanScalars(value, errors) {
300
+ if (!isMember(value.strategy, GROUNDING_STRATEGIES)) {
301
+ errors.push(`groundingPlan.strategy must be one of ${GROUNDING_STRATEGIES.join("|")}`);
302
+ }
303
+ if (typeof value.required !== "boolean") {
304
+ errors.push("groundingPlan.required must be a boolean");
305
+ }
306
+ if (!isMember(value.contradictionPolicy, CONTRADICTION_POLICIES)) {
307
+ errors.push(`groundingPlan.contradictionPolicy must be one of ${CONTRADICTION_POLICIES.join("|")}`);
308
+ }
309
+ if (value.untrustedContent !== true) {
310
+ errors.push("groundingPlan.untrustedContent must be true");
311
+ }
312
+ }
313
+ function validateGroundingPlanLists(value, errors) {
314
+ if (!isMemberArray(value.allowedRetrievalModes, RETRIEVAL_MODES)) {
315
+ errors.push("groundingPlan.allowedRetrievalModes must be an array of known retrieval modes");
316
+ }
317
+ if (!isMemberArray(value.noAnswerConditions, NO_ANSWER_CONDITIONS)) {
318
+ errors.push("groundingPlan.noAnswerConditions must be an array of known no-answer conditions");
319
+ }
320
+ if (!isMemberArray(value.directives, GROUNDING_DIRECTIVES)) {
321
+ errors.push("groundingPlan.directives must be an array of known grounding directives");
322
+ }
323
+ }
324
+ function isValidSourcePolicy(entry) {
325
+ return (isRecord(entry) &&
326
+ Object.keys(entry).every((key) => SOURCE_POLICY_KEYS.has(key)) &&
327
+ isMember(entry.source, GROUNDING_SOURCE_KINDS) &&
328
+ isPositiveInteger(entry.priority) &&
329
+ typeof entry.required === "boolean");
330
+ }
331
+ function validateSourcePriority(value, errors) {
332
+ if (!Array.isArray(value) || value.some((entry) => !isValidSourcePolicy(entry))) {
333
+ errors.push("groundingPlan.sourcePriority must be an array of {source, priority, required}");
334
+ }
335
+ }
336
+ function validateCitation(value, errors) {
337
+ if (!isRecord(value)) {
338
+ errors.push("groundingPlan.citation must be an object");
339
+ return;
340
+ }
341
+ validateExactKeys(value, CITATION_KEYS, "groundingPlan.citation", errors);
342
+ if (!isMember(value.discipline, CITATION_DISCIPLINES)) {
343
+ errors.push(`groundingPlan.citation.discipline must be one of ${CITATION_DISCIPLINES.join("|")}`);
344
+ }
345
+ if (!isMember(value.granularity, CITATION_GRANULARITIES)) {
346
+ errors.push(`groundingPlan.citation.granularity must be one of ${CITATION_GRANULARITIES.join("|")}`);
347
+ }
348
+ }
349
+ function validateRecency(value, errors) {
350
+ if (!isRecord(value)) {
351
+ errors.push("groundingPlan.recency must be an object");
352
+ return;
353
+ }
354
+ validateExactKeys(value, RECENCY_KEYS, "groundingPlan.recency", errors);
355
+ if (typeof value.volatile !== "boolean")
356
+ errors.push("groundingPlan.recency.volatile must be a boolean");
357
+ if (typeof value.requireAsOfDate !== "boolean") {
358
+ errors.push("groundingPlan.recency.requireAsOfDate must be a boolean");
359
+ }
360
+ if (typeof value.flagPotentiallyStale !== "boolean") {
361
+ errors.push("groundingPlan.recency.flagPotentiallyStale must be a boolean");
362
+ }
363
+ }
364
+ function isValidRagHint(entry) {
365
+ return (isRecord(entry) &&
366
+ Object.keys(entry).every((key) => RAG_HINT_KEYS.has(key)) &&
367
+ isMember(entry.dimension, RAG_EVALUATION_DIMENSIONS) &&
368
+ isBoundedSafeText(entry.instruction, RAG_HINT_INSTRUCTION_MAX_CHARS));
369
+ }
370
+ function validateRagEvaluation(value, errors) {
371
+ if (!Array.isArray(value) || value.some((entry) => !isValidRagHint(entry))) {
372
+ errors.push("groundingPlan.ragEvaluation must be an array of {dimension, instruction}");
373
+ }
374
+ }
375
+ function asGroundingStrategy(value) {
376
+ return isMember(value, GROUNDING_STRATEGIES) ? value : undefined;
377
+ }
378
+ function isValidSourcePriority(value) {
379
+ return Array.isArray(value) && value.every((entry) => isValidSourcePolicy(entry));
380
+ }
381
+ function sourcePoliciesEqual(actual, expected) {
382
+ return (actual.source === expected.source &&
383
+ actual.priority === expected.priority &&
384
+ actual.required === expected.required);
385
+ }
386
+ function isValidRecency(value) {
387
+ return (isRecord(value) &&
388
+ typeof value.volatile === "boolean" &&
389
+ typeof value.requireAsOfDate === "boolean" &&
390
+ typeof value.flagPotentiallyStale === "boolean");
391
+ }
392
+ function isValidRagEvaluation(value) {
393
+ return Array.isArray(value) && value.every((entry) => isValidRagHint(entry));
394
+ }
395
+ function validateAllowedRetrievalModeSemantics(value, strategy, errors) {
396
+ if (!isMemberArray(value, RETRIEVAL_MODES))
397
+ return;
398
+ if (!arraysEqual(value, RETRIEVAL_MODES_BY_STRATEGY[strategy])) {
399
+ errors.push("groundingPlan.allowedRetrievalModes must match the selected strategy");
400
+ }
401
+ }
402
+ function validateSourcePrioritySemantics(value, strategy, required, errors) {
403
+ if (!isValidSourcePriority(value))
404
+ return;
405
+ const expected = buildSourcePriority(strategy, required);
406
+ const matches = value.every((entry, index) => {
407
+ const expectedEntry = expected[index];
408
+ return expectedEntry === undefined ? false : sourcePoliciesEqual(entry, expectedEntry);
409
+ });
410
+ const uniquePriorities = new Set(value.map((entry) => entry.priority));
411
+ const uniqueSources = new Set(value.map((entry) => entry.source));
412
+ if (value.length !== expected.length ||
413
+ !matches ||
414
+ uniquePriorities.size !== value.length ||
415
+ uniqueSources.size !== value.length) {
416
+ errors.push("groundingPlan.sourcePriority must match strategy ordering, 1-based priorities, and required-source rules");
417
+ }
418
+ }
419
+ function isValidCitationShape(value) {
420
+ return (isRecord(value) &&
421
+ isMember(value.discipline, CITATION_DISCIPLINES) &&
422
+ isMember(value.granularity, CITATION_GRANULARITIES));
423
+ }
424
+ function validateNoGroundingCitationSemantics(value, strategy, errors) {
425
+ if (strategy === "no-grounding" &&
426
+ (value.discipline !== "not-required" || value.granularity !== "none")) {
427
+ errors.push("groundingPlan.citation must be not-required/none for no-grounding plans");
428
+ }
429
+ }
430
+ function validateGroundedCitationSemantics(value, strategy, errors) {
431
+ if (strategy !== "no-grounding" && value.discipline === "not-required") {
432
+ errors.push("groundingPlan.citation.discipline cannot be not-required for grounded plans");
433
+ }
434
+ }
435
+ function validateCitationGranularitySemantics(value, errors) {
436
+ if (value.discipline === "best-effort" && value.granularity !== "per-section") {
437
+ errors.push("groundingPlan.citation.granularity must be per-section for best-effort citation");
438
+ }
439
+ if (value.discipline !== "best-effort" &&
440
+ value.discipline !== "not-required" &&
441
+ value.granularity !== "per-claim") {
442
+ errors.push("groundingPlan.citation.granularity must be per-claim when citations are required");
443
+ }
444
+ }
445
+ function validateCitationSemantics(value, strategy, errors) {
446
+ if (!isValidCitationShape(value))
447
+ return;
448
+ validateNoGroundingCitationSemantics(value, strategy, errors);
449
+ validateGroundedCitationSemantics(value, strategy, errors);
450
+ validateCitationGranularitySemantics(value, errors);
451
+ }
452
+ function validateRecencySemantics(value, strategy, errors) {
453
+ if (!isValidRecency(value))
454
+ return;
455
+ const expectedStaleFlag = value.volatile || strategy === "external-research-required";
456
+ if (value.requireAsOfDate !== value.volatile ||
457
+ value.flagPotentiallyStale !== expectedStaleFlag) {
458
+ errors.push("groundingPlan.recency must pair volatile data with as-of dates and strategy-appropriate stale flags");
459
+ }
460
+ }
461
+ function expectedNoAnswerConditions(strategy, recency) {
462
+ if (strategy === "no-grounding") {
463
+ return [];
464
+ }
465
+ const expected = ["insufficient-evidence"];
466
+ if (MULTI_SOURCE_STRATEGIES.has(strategy)) {
467
+ expected.push("contradictory-evidence");
468
+ }
469
+ if (SCOPED_EVIDENCE_STRATEGIES.has(strategy)) {
470
+ expected.push("outside-evidence-scope");
471
+ }
472
+ if (recency.volatile || strategy === "external-research-required") {
473
+ expected.push("stale-or-unavailable-current-data");
474
+ }
475
+ return expected;
476
+ }
477
+ function validateNoAnswerSemantics(value, recency, strategy, errors) {
478
+ if (!isMemberArray(value, NO_ANSWER_CONDITIONS) || !isValidRecency(recency))
479
+ return;
480
+ if (!arraysEqual(value, expectedNoAnswerConditions(strategy, recency))) {
481
+ errors.push("groundingPlan.noAnswerConditions must match strategy and recency requirements");
482
+ }
483
+ }
484
+ function validateDirectiveSemantics(value, strategy, required, errors) {
485
+ if (!isMemberArray(value, GROUNDING_DIRECTIVES))
486
+ return;
487
+ const expected = buildDirectives(strategy, required);
488
+ if (!arraysEqual(value, expected)) {
489
+ errors.push("groundingPlan.directives must match the selected strategy and requirement level");
490
+ }
491
+ }
492
+ function validateRagEvaluationSemantics(value, errors) {
493
+ if (!isValidRagEvaluation(value) || value.length === 0)
494
+ return;
495
+ const matchesTemplates = value.length === RAG_EVALUATION_DIMENSIONS.length &&
496
+ value.every((entry, index) => {
497
+ const dimension = RAG_EVALUATION_DIMENSIONS[index];
498
+ return (dimension !== undefined &&
499
+ entry.dimension === dimension &&
500
+ entry.instruction === RAG_HINT_TEMPLATES[dimension]);
501
+ });
502
+ if (!matchesTemplates) {
503
+ errors.push("groundingPlan.ragEvaluation must use the fixed RAG hint templates");
504
+ }
505
+ }
506
+ function validateGroundingPlanSemantics(value, errors) {
507
+ const strategy = asGroundingStrategy(value.strategy);
508
+ const required = typeof value.required === "boolean" ? value.required : undefined;
509
+ if (strategy === undefined || required === undefined)
510
+ return;
511
+ validateAllowedRetrievalModeSemantics(value.allowedRetrievalModes, strategy, errors);
512
+ validateSourcePrioritySemantics(value.sourcePriority, strategy, required, errors);
513
+ validateCitationSemantics(value.citation, strategy, errors);
514
+ validateRecencySemantics(value.recency, strategy, errors);
515
+ validateNoAnswerSemantics(value.noAnswerConditions, value.recency, strategy, errors);
516
+ validateDirectiveSemantics(value.directives, strategy, required, errors);
517
+ validateRagEvaluationSemantics(value.ragEvaluation, errors);
518
+ }
519
+ function collectGroundingPlanErrors(value, errors) {
520
+ if (!isRecord(value)) {
521
+ errors.push("groundingPlan must be an object");
522
+ return;
523
+ }
524
+ validateExactKeys(value, GROUNDING_PLAN_KEYS, "groundingPlan", errors);
525
+ validateGroundingPlanScalars(value, errors);
526
+ validateGroundingPlanLists(value, errors);
527
+ validateSourcePriority(value.sourcePriority, errors);
528
+ validateCitation(value.citation, errors);
529
+ validateRecency(value.recency, errors);
530
+ validateRagEvaluation(value.ragEvaluation, errors);
531
+ validateGroundingPlanSemantics(value, errors);
532
+ }
533
+ export function validateGroundingPlan(input) {
534
+ const errors = [];
535
+ collectGroundingPlanErrors(input, errors);
536
+ if (errors.length > 0)
537
+ return { ok: false, errors };
538
+ return { ok: true, value: input };
539
+ }
540
+ // ─── Enhanced Prompt validator ───────────────────────────────────────────────────
541
+ function isBoundedStringList(value) {
542
+ return (Array.isArray(value) &&
543
+ value.length <= ENHANCED_PROMPT_LIST_MAX &&
544
+ value.every((entry) => isBoundedSafeText(entry, ENHANCED_PROMPT_FIELD_MAX_CHARS)));
545
+ }
546
+ const ENHANCED_PROMPT_STRING_FIELDS = ["role", "goal", "input"];
547
+ const ENHANCED_PROMPT_LIST_FIELDS = [
548
+ "context",
549
+ "taskDecomposition",
550
+ "constraints",
551
+ "groundingRules",
552
+ "qualityCriteria",
553
+ "uncertaintyHandling",
554
+ "safetyRules",
555
+ ];
556
+ export function validateEnhancedPrompt(input) {
557
+ if (!isRecord(input)) {
558
+ return { ok: false, errors: ["enhancedPrompt must be an object"] };
559
+ }
560
+ const errors = [];
561
+ validateExactKeys(input, ENHANCED_PROMPT_KEYS, "enhancedPrompt", errors);
562
+ if (input.schemaVersion !== PROMPT_ENHANCER_SCHEMA_VERSION) {
563
+ errors.push(`enhancedPrompt.schemaVersion must be "${PROMPT_ENHANCER_SCHEMA_VERSION}"`);
564
+ }
565
+ pushIdError(input.promptId, "enhancedPrompt.promptId", "EnhancedPromptId", errors);
566
+ for (const field of ENHANCED_PROMPT_STRING_FIELDS) {
567
+ if (!isBoundedSafeText(input[field], ENHANCED_PROMPT_FIELD_MAX_CHARS)) {
568
+ errors.push(`enhancedPrompt.${field} must be a bounded, control-free string`);
569
+ }
570
+ }
571
+ for (const field of ENHANCED_PROMPT_LIST_FIELDS) {
572
+ if (!isBoundedStringList(input[field])) {
573
+ errors.push(`enhancedPrompt.${field} must be a bounded array of control-free strings`);
574
+ }
575
+ }
576
+ validateOutputSchema(input.outputSchema, errors);
577
+ collectGroundingPlanErrors(input.groundingPlan, errors);
578
+ if (errors.length > 0)
579
+ return { ok: false, errors };
580
+ return { ok: true, value: input };
581
+ }
582
+ // ─── Candidate-critic validators (Issue #1312) ─────────────────────────────────────
583
+ const CRITIC_RATIONALE_MAX_CHARS = 2_000;
584
+ const CANDIDATE_ID_MAX_CHARS = 256;
585
+ const CANDIDATE_LIST_MAX = 64;
586
+ const SCORECARD_KEYS = new Set([
587
+ "schemaVersion",
588
+ "candidateId",
589
+ "profile",
590
+ "dimensionScores",
591
+ "aggregateScore",
592
+ "estimatedTokens",
593
+ ]);
594
+ const DIMENSION_SCORE_KEYS = new Set(["dimension", "score", "rationale"]);
595
+ const REJECTION_KEYS = new Set([
596
+ "candidateId",
597
+ "profile",
598
+ "aggregateScore",
599
+ "reason",
600
+ ]);
601
+ const SELECTION_KEYS = new Set([
602
+ "schemaVersion",
603
+ "winner",
604
+ "ranked",
605
+ "rankedPrompts",
606
+ "winnerSafetyAssessment",
607
+ "rankedSafetyAssessments",
608
+ "rejected",
609
+ "bounds",
610
+ "iterations",
611
+ "candidatesConsidered",
612
+ "tokensConsumed",
613
+ ]);
614
+ const BOUNDS_KEYS = new Set([
615
+ "candidateCount",
616
+ "tokenBudget",
617
+ "maxIterations",
618
+ ]);
619
+ // A finite number within the closed unit interval [0, 1] — the legal range for every score.
620
+ const isUnitInterval = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0 && value <= 1;
621
+ // Validate the per-dimension scores array: exactly one entry per critic dimension, in canonical order.
622
+ function collectDimensionScoreErrors(value, errors) {
623
+ if (!Array.isArray(value) || value.length !== PROMPT_CRITIC_DIMENSIONS.length) {
624
+ errors.push(`scorecard.dimensionScores must list all ${String(PROMPT_CRITIC_DIMENSIONS.length)} dimensions`);
625
+ return;
626
+ }
627
+ PROMPT_CRITIC_DIMENSIONS.forEach((dimension, index) => {
628
+ const entry = value[index];
629
+ if (!isRecord(entry)) {
630
+ errors.push(`scorecard.dimensionScores[${String(index)}] must be an object`);
631
+ return;
632
+ }
633
+ validateExactKeys(entry, DIMENSION_SCORE_KEYS, `scorecard.dimensionScores[${String(index)}]`, errors);
634
+ if (entry.dimension !== dimension) {
635
+ errors.push(`scorecard.dimensionScores[${String(index)}].dimension must be "${dimension}"`);
636
+ }
637
+ if (!isUnitInterval(entry.score)) {
638
+ errors.push(`scorecard.dimensionScores[${String(index)}].score must be a number in [0, 1]`);
639
+ }
640
+ if (!isBoundedSafeText(entry.rationale, CRITIC_RATIONALE_MAX_CHARS)) {
641
+ errors.push(`scorecard.dimensionScores[${String(index)}].rationale must be a bounded, control-free string`);
642
+ }
643
+ });
644
+ }
645
+ function collectScorecardErrors(input, label, errors) {
646
+ if (!isRecord(input)) {
647
+ errors.push(`${label} must be an object`);
648
+ return;
649
+ }
650
+ validateExactKeys(input, SCORECARD_KEYS, label, errors);
651
+ if (input.schemaVersion !== PROMPT_ENHANCER_SCHEMA_VERSION) {
652
+ errors.push(`${label}.schemaVersion must be "${PROMPT_ENHANCER_SCHEMA_VERSION}"`);
653
+ }
654
+ if (!isBoundedSafeText(input.candidateId, CANDIDATE_ID_MAX_CHARS)) {
655
+ errors.push(`${label}.candidateId must be a bounded, control-free string`);
656
+ }
657
+ if (!isMember(input.profile, PROMPT_ENHANCEMENT_PROFILE_IDS)) {
658
+ errors.push(`${label}.profile must be a known generation profile`);
659
+ }
660
+ collectDimensionScoreErrors(input.dimensionScores, errors);
661
+ if (!isUnitInterval(input.aggregateScore)) {
662
+ errors.push(`${label}.aggregateScore must be a number in [0, 1]`);
663
+ }
664
+ if (!isNonNegativeInteger(input.estimatedTokens)) {
665
+ errors.push(`${label}.estimatedTokens must be a non-negative integer`);
666
+ }
667
+ }
668
+ /**
669
+ * Validate a `PromptCandidateScorecard`. Pure; returns a discriminated result and never throws.
670
+ */
671
+ export function validatePromptCandidateScorecard(input) {
672
+ const errors = [];
673
+ collectScorecardErrors(input, "scorecard", errors);
674
+ if (errors.length > 0)
675
+ return { ok: false, errors };
676
+ return { ok: true, value: input };
677
+ }
678
+ function collectRejectionErrors(value, index, errors) {
679
+ const label = `selection.rejected[${String(index)}]`;
680
+ if (!isRecord(value)) {
681
+ errors.push(`${label} must be an object`);
682
+ return;
683
+ }
684
+ validateExactKeys(value, REJECTION_KEYS, label, errors);
685
+ if (!isBoundedSafeText(value.candidateId, CANDIDATE_ID_MAX_CHARS)) {
686
+ errors.push(`${label}.candidateId must be a bounded, control-free string`);
687
+ }
688
+ if (!isMember(value.profile, PROMPT_ENHANCEMENT_PROFILE_IDS)) {
689
+ errors.push(`${label}.profile must be a known generation profile`);
690
+ }
691
+ if (value.aggregateScore !== null && !isUnitInterval(value.aggregateScore)) {
692
+ errors.push(`${label}.aggregateScore must be null or a number in [0, 1]`);
693
+ }
694
+ if (!isPromptCandidateRejectionReason(value.reason)) {
695
+ errors.push(`${label}.reason must be a known rejection reason`);
696
+ }
697
+ }
698
+ function collectBoundsErrors(value, errors) {
699
+ if (!isRecord(value)) {
700
+ errors.push("selection.bounds must be an object");
701
+ return;
702
+ }
703
+ validateExactKeys(value, BOUNDS_KEYS, "selection.bounds", errors);
704
+ for (const field of ["candidateCount", "tokenBudget", "maxIterations"]) {
705
+ if (!isPositiveInteger(value[field])) {
706
+ errors.push(`selection.bounds.${field} must be a positive integer`);
707
+ }
708
+ }
709
+ }
710
+ function getDimensionScore(card, dimension) {
711
+ return card.dimensionScores.find((entry) => entry.dimension === dimension)?.score ?? 0;
712
+ }
713
+ function getProfileRank(card) {
714
+ const index = PROMPT_ENHANCEMENT_PROFILE_IDS.indexOf(card.profile);
715
+ return index < 0 ? PROMPT_ENHANCEMENT_PROFILE_IDS.length : index;
716
+ }
717
+ function compareRankedScorecards(a, b) {
718
+ const keys = [
719
+ b.aggregateScore - a.aggregateScore,
720
+ getDimensionScore(b, "safety") - getDimensionScore(a, "safety"),
721
+ getDimensionScore(b, "completeness") - getDimensionScore(a, "completeness"),
722
+ getDimensionScore(b, "token-efficiency") - getDimensionScore(a, "token-efficiency"),
723
+ getProfileRank(a) - getProfileRank(b),
724
+ ];
725
+ for (const key of keys) {
726
+ if (key !== 0)
727
+ return key < 0 ? -1 : 1;
728
+ }
729
+ return 0;
730
+ }
731
+ function sameDimensionScore(left, right) {
732
+ return (left.dimension === right.dimension &&
733
+ left.score === right.score &&
734
+ left.rationale === right.rationale);
735
+ }
736
+ function sameScorecard(left, right) {
737
+ return (left.candidateId === right.candidateId &&
738
+ left.profile === right.profile &&
739
+ left.aggregateScore === right.aggregateScore &&
740
+ left.estimatedTokens === right.estimatedTokens &&
741
+ left.dimensionScores.length === right.dimensionScores.length &&
742
+ left.dimensionScores.every((entry, index) => {
743
+ const other = right.dimensionScores[index];
744
+ return other !== undefined && sameDimensionScore(entry, other);
745
+ }));
746
+ }
747
+ function sameSafetyFinding(left, right) {
748
+ return (left.code === right.code &&
749
+ left.ruleId === right.ruleId &&
750
+ left.severity === right.severity &&
751
+ left.detail === right.detail);
752
+ }
753
+ function sameSafetyAssessment(left, right) {
754
+ return (left.promptId === right.promptId &&
755
+ left.decision === right.decision &&
756
+ left.requiresHumanReview === right.requiresHumanReview &&
757
+ left.verificationStatus === right.verificationStatus &&
758
+ arraysEqual(left.leastPrivilege, right.leastPrivilege) &&
759
+ left.findings.length === right.findings.length &&
760
+ left.findings.every((entry, index) => {
761
+ const other = right.findings[index];
762
+ return other !== undefined && sameSafetyFinding(entry, other);
763
+ }));
764
+ }
765
+ function toValidScorecard(value) {
766
+ const result = validatePromptCandidateScorecard(value);
767
+ return result.ok ? result.value : undefined;
768
+ }
769
+ function collectSafetyAssessmentErrors(value, label, errors) {
770
+ const result = validatePromptSafetyAssessment(value);
771
+ if (!result.ok) {
772
+ errors.push(`${label} must be a valid PromptSafetyAssessment`);
773
+ return undefined;
774
+ }
775
+ return result.value;
776
+ }
777
+ function collectRankedErrors(ranked, winner, errors) {
778
+ if (!Array.isArray(ranked) || ranked.length === 0) {
779
+ errors.push("selection.ranked must be a non-empty array");
780
+ return;
781
+ }
782
+ if (ranked.length > CANDIDATE_LIST_MAX) {
783
+ errors.push(`selection.ranked must contain at most ${String(CANDIDATE_LIST_MAX)} entries`);
784
+ return;
785
+ }
786
+ ranked.forEach((entry, index) => {
787
+ collectScorecardErrors(entry, `selection.ranked[${String(index)}]`, errors);
788
+ });
789
+ const winnerId = isRecord(winner) ? winner.candidateId : undefined;
790
+ const firstRanked = ranked[0];
791
+ if (isRecord(firstRanked) && firstRanked.candidateId !== winnerId) {
792
+ errors.push("selection.winner must be the first ranked candidate");
793
+ }
794
+ const firstScorecard = toValidScorecard(firstRanked);
795
+ const winnerScorecard = toValidScorecard(winner);
796
+ if (firstScorecard !== undefined &&
797
+ winnerScorecard !== undefined &&
798
+ !sameScorecard(winnerScorecard, firstScorecard)) {
799
+ errors.push("selection.winner must exactly match the first ranked candidate");
800
+ }
801
+ const scorecards = ranked.map((entry) => toValidScorecard(entry));
802
+ scorecards.forEach((entry, index) => {
803
+ const next = scorecards[index + 1];
804
+ if (entry !== undefined && next !== undefined && compareRankedScorecards(entry, next) > 0) {
805
+ errors.push("selection.ranked must be sorted in deterministic rank order");
806
+ }
807
+ });
808
+ }
809
+ function collectRankedPromptErrors(ranked, rankedPrompts, errors) {
810
+ if (!Array.isArray(rankedPrompts)) {
811
+ errors.push("selection.rankedPrompts must be an array");
812
+ return;
813
+ }
814
+ if (Array.isArray(ranked) && rankedPrompts.length !== ranked.length) {
815
+ errors.push("selection.rankedPrompts must match selection.ranked length");
816
+ }
817
+ rankedPrompts.forEach((entry, index) => {
818
+ const result = validateEnhancedPrompt(entry);
819
+ if (!result.ok) {
820
+ errors.push(`selection.rankedPrompts[${String(index)}] must be a valid EnhancedPrompt`);
821
+ return;
822
+ }
823
+ const rankedEntry = Array.isArray(ranked) ? toValidScorecard(ranked[index]) : undefined;
824
+ if (rankedEntry !== undefined && result.value.promptId !== rankedEntry.candidateId) {
825
+ errors.push(`selection.rankedPrompts[${String(index)}].promptId must match the ranked candidateId`);
826
+ }
827
+ });
828
+ }
829
+ function collectRankedSafetyAssessmentListErrors(ranked, assessments, errors) {
830
+ if (!Array.isArray(assessments)) {
831
+ errors.push("selection.rankedSafetyAssessments must be an array");
832
+ return [];
833
+ }
834
+ if (Array.isArray(ranked) && assessments.length !== ranked.length) {
835
+ errors.push("selection.rankedSafetyAssessments must match selection.ranked length");
836
+ }
837
+ const validatedAssessments = assessments.map((entry, index) => collectSafetyAssessmentErrors(entry, `selection.rankedSafetyAssessments[${String(index)}]`, errors));
838
+ validatedAssessments.forEach((assessment, index) => {
839
+ if (assessment === undefined)
840
+ return;
841
+ if (assessment.decision === "rejected") {
842
+ errors.push("selection.rankedSafetyAssessments must not include rejected assessments");
843
+ }
844
+ const rankedEntry = Array.isArray(ranked) ? toValidScorecard(ranked[index]) : undefined;
845
+ if (rankedEntry !== undefined && assessment.promptId !== rankedEntry.candidateId) {
846
+ errors.push(`selection.rankedSafetyAssessments[${String(index)}].promptId must match the ranked candidateId`);
847
+ }
848
+ });
849
+ return validatedAssessments;
850
+ }
851
+ function collectWinnerSafetyAssessmentErrors(input, firstAssessment, errors) {
852
+ const winnerAssessment = collectSafetyAssessmentErrors(input.winnerSafetyAssessment, "selection.winnerSafetyAssessment", errors);
853
+ if (winnerAssessment !== undefined &&
854
+ firstAssessment !== undefined &&
855
+ !sameSafetyAssessment(winnerAssessment, firstAssessment)) {
856
+ errors.push("selection.winnerSafetyAssessment must exactly match the first ranked assessment");
857
+ }
858
+ const winner = toValidScorecard(input.winner);
859
+ if (winner !== undefined &&
860
+ winnerAssessment !== undefined &&
861
+ winnerAssessment.promptId !== winner.candidateId) {
862
+ errors.push("selection.winnerSafetyAssessment.promptId must match selection.winner.candidateId");
863
+ }
864
+ }
865
+ function collectRankedSafetyAssessmentErrors(input, errors) {
866
+ const validatedAssessments = collectRankedSafetyAssessmentListErrors(input.ranked, input.rankedSafetyAssessments, errors);
867
+ collectWinnerSafetyAssessmentErrors(input, validatedAssessments[0], errors);
868
+ }
869
+ function collectRejectedListErrors(rejected, errors) {
870
+ if (!Array.isArray(rejected) || rejected.length > CANDIDATE_LIST_MAX) {
871
+ errors.push(`selection.rejected must be an array of at most ${String(CANDIDATE_LIST_MAX)} entries`);
872
+ return;
873
+ }
874
+ rejected.forEach((entry, index) => {
875
+ collectRejectionErrors(entry, index, errors);
876
+ });
877
+ }
878
+ function collectSelectionCounterErrors(input, errors) {
879
+ for (const field of ["iterations", "candidatesConsidered", "tokensConsumed"]) {
880
+ if (!isNonNegativeInteger(input[field])) {
881
+ errors.push(`selection.${field} must be a non-negative integer`);
882
+ }
883
+ }
884
+ }
885
+ function collectExceededBoundError(value, bound, message, errors) {
886
+ if (isNonNegativeInteger(value) && isPositiveInteger(bound) && value > bound) {
887
+ errors.push(message);
888
+ }
889
+ }
890
+ function collectSelectionBoundUsageErrors(input, errors) {
891
+ if (!isRecord(input.bounds))
892
+ return;
893
+ collectExceededBoundError(input.iterations, input.bounds.maxIterations, "selection.iterations must not exceed selection.bounds.maxIterations", errors);
894
+ collectExceededBoundError(input.candidatesConsidered, input.bounds.candidateCount, "selection.candidatesConsidered must not exceed selection.bounds.candidateCount", errors);
895
+ collectExceededBoundError(input.tokensConsumed, input.bounds.tokenBudget, "selection.tokensConsumed must not exceed selection.bounds.tokenBudget", errors);
896
+ }
897
+ function collectCandidatesConsideredErrors(input, errors) {
898
+ if (Array.isArray(input.ranked) &&
899
+ isNonNegativeInteger(input.candidatesConsidered) &&
900
+ input.candidatesConsidered !== input.ranked.length) {
901
+ errors.push("selection.candidatesConsidered must equal selection.ranked.length");
902
+ }
903
+ }
904
+ /**
905
+ * Validate a `PromptCandidateSelection`. Pure; returns a discriminated result and never throws. Checks
906
+ * structural well-formedness and the cross-field invariants that make the result auditable: the winner
907
+ * is the first ranked entry, every ranked scorecard is well-formed, and the considered/consumed totals
908
+ * are non-negative integers within the declared bounds.
909
+ */
910
+ export function validatePromptCandidateSelection(input) {
911
+ if (!isRecord(input)) {
912
+ return { ok: false, errors: ["selection must be an object"] };
913
+ }
914
+ const errors = [];
915
+ validateExactKeys(input, SELECTION_KEYS, "selection", errors);
916
+ if (input.schemaVersion !== PROMPT_ENHANCER_SCHEMA_VERSION) {
917
+ errors.push(`selection.schemaVersion must be "${PROMPT_ENHANCER_SCHEMA_VERSION}"`);
918
+ }
919
+ collectScorecardErrors(input.winner, "selection.winner", errors);
920
+ collectRankedErrors(input.ranked, input.winner, errors);
921
+ collectRankedPromptErrors(input.ranked, input.rankedPrompts, errors);
922
+ collectRankedSafetyAssessmentErrors(input, errors);
923
+ collectRejectedListErrors(input.rejected, errors);
924
+ collectBoundsErrors(input.bounds, errors);
925
+ collectSelectionCounterErrors(input, errors);
926
+ collectSelectionBoundUsageErrors(input, errors);
927
+ collectCandidatesConsideredErrors(input, errors);
928
+ if (errors.length > 0)
929
+ return { ok: false, errors };
930
+ return { ok: true, value: input };
931
+ }