ai-capability-framework 1.0.3 → 1.1.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 (246) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/README.md +38 -30
  3. package/ROADMAP.md +14 -0
  4. package/dist/cli.d.ts.map +1 -1
  5. package/dist/cli.js +32 -1
  6. package/dist/discovery/bindings.d.ts +7 -0
  7. package/dist/discovery/bindings.d.ts.map +1 -0
  8. package/dist/discovery/bindings.js +24 -0
  9. package/dist/discovery/budget.d.ts +17 -0
  10. package/dist/discovery/budget.d.ts.map +1 -0
  11. package/dist/discovery/budget.js +77 -0
  12. package/dist/discovery/configuration.d.ts +10 -0
  13. package/dist/discovery/configuration.d.ts.map +1 -0
  14. package/dist/discovery/configuration.js +63 -0
  15. package/dist/discovery/corpus.d.ts +8 -0
  16. package/dist/discovery/corpus.d.ts.map +1 -0
  17. package/dist/discovery/corpus.js +64 -0
  18. package/dist/discovery/diagnostics.d.ts +15 -0
  19. package/dist/discovery/diagnostics.d.ts.map +1 -0
  20. package/dist/discovery/diagnostics.js +11 -0
  21. package/dist/discovery/discoverer.d.ts +23 -0
  22. package/dist/discovery/discoverer.d.ts.map +1 -0
  23. package/dist/discovery/discoverer.js +261 -0
  24. package/dist/discovery/embedding-configuration.d.ts +12 -0
  25. package/dist/discovery/embedding-configuration.d.ts.map +1 -0
  26. package/dist/discovery/embedding-configuration.js +81 -0
  27. package/dist/discovery/embedding-types.d.ts +75 -0
  28. package/dist/discovery/embedding-types.d.ts.map +1 -0
  29. package/dist/discovery/embedding-types.js +1 -0
  30. package/dist/discovery/embeddings.d.ts +38 -0
  31. package/dist/discovery/embeddings.d.ts.map +1 -0
  32. package/dist/discovery/embeddings.js +71 -0
  33. package/dist/discovery/evaluation-validation.d.ts +8 -0
  34. package/dist/discovery/evaluation-validation.d.ts.map +1 -0
  35. package/dist/discovery/evaluation-validation.js +19 -0
  36. package/dist/discovery/evaluation.d.ts +84 -0
  37. package/dist/discovery/evaluation.d.ts.map +1 -0
  38. package/dist/discovery/evaluation.js +310 -0
  39. package/dist/discovery/export.d.ts +14 -0
  40. package/dist/discovery/export.d.ts.map +1 -0
  41. package/dist/discovery/export.js +81 -0
  42. package/dist/discovery/facade.d.ts +82 -0
  43. package/dist/discovery/facade.d.ts.map +1 -0
  44. package/dist/discovery/facade.js +266 -0
  45. package/dist/discovery/fingerprint.d.ts +71 -0
  46. package/dist/discovery/fingerprint.d.ts.map +1 -0
  47. package/dist/discovery/fingerprint.js +67 -0
  48. package/dist/discovery/index-builder.d.ts +3 -0
  49. package/dist/discovery/index-builder.d.ts.map +1 -0
  50. package/dist/discovery/index-builder.js +129 -0
  51. package/dist/discovery/index-validation.d.ts +12 -0
  52. package/dist/discovery/index-validation.d.ts.map +1 -0
  53. package/dist/discovery/index-validation.js +117 -0
  54. package/dist/discovery/index.d.ts +32 -0
  55. package/dist/discovery/index.d.ts.map +1 -0
  56. package/dist/discovery/index.js +18 -0
  57. package/dist/discovery/json-snapshot.d.ts +3 -0
  58. package/dist/discovery/json-snapshot.d.ts.map +1 -0
  59. package/dist/discovery/json-snapshot.js +11 -0
  60. package/dist/discovery/lexical.d.ts +12 -0
  61. package/dist/discovery/lexical.d.ts.map +1 -0
  62. package/dist/discovery/lexical.js +26 -0
  63. package/dist/discovery/metadata.d.ts +4 -0
  64. package/dist/discovery/metadata.d.ts.map +1 -0
  65. package/dist/discovery/metadata.js +33 -0
  66. package/dist/discovery/ranking.d.ts +7 -0
  67. package/dist/discovery/ranking.d.ts.map +1 -0
  68. package/dist/discovery/ranking.js +68 -0
  69. package/dist/discovery/replay.d.ts +50 -0
  70. package/dist/discovery/replay.d.ts.map +1 -0
  71. package/dist/discovery/replay.js +175 -0
  72. package/dist/discovery/retrieval-policy.d.ts +5 -0
  73. package/dist/discovery/retrieval-policy.d.ts.map +1 -0
  74. package/dist/discovery/retrieval-policy.js +10 -0
  75. package/dist/discovery/router.d.ts +39 -0
  76. package/dist/discovery/router.d.ts.map +1 -0
  77. package/dist/discovery/router.js +98 -0
  78. package/dist/discovery/selection-attempt.d.ts +20 -0
  79. package/dist/discovery/selection-attempt.d.ts.map +1 -0
  80. package/dist/discovery/selection-attempt.js +39 -0
  81. package/dist/discovery/selection-cards.d.ts +12 -0
  82. package/dist/discovery/selection-cards.d.ts.map +1 -0
  83. package/dist/discovery/selection-cards.js +79 -0
  84. package/dist/discovery/selection-configuration.d.ts +18 -0
  85. package/dist/discovery/selection-configuration.d.ts.map +1 -0
  86. package/dist/discovery/selection-configuration.js +71 -0
  87. package/dist/discovery/selection-response.d.ts +13 -0
  88. package/dist/discovery/selection-response.d.ts.map +1 -0
  89. package/dist/discovery/selection-response.js +47 -0
  90. package/dist/discovery/selection-types.d.ts +18 -0
  91. package/dist/discovery/selection-types.d.ts.map +1 -0
  92. package/dist/discovery/selection-types.js +1 -0
  93. package/dist/discovery/selection.d.ts +6 -0
  94. package/dist/discovery/selection.d.ts.map +1 -0
  95. package/dist/discovery/selection.js +145 -0
  96. package/dist/discovery/types.d.ts +291 -0
  97. package/dist/discovery/types.d.ts.map +1 -0
  98. package/dist/discovery/types.js +1 -0
  99. package/dist/discovery/usage.d.ts +2 -0
  100. package/dist/discovery/usage.d.ts.map +1 -0
  101. package/dist/discovery/usage.js +1 -0
  102. package/dist/discovery/vectors.d.ts +5 -0
  103. package/dist/discovery/vectors.d.ts.map +1 -0
  104. package/dist/discovery/vectors.js +43 -0
  105. package/dist/discovery/versions.d.ts +37 -0
  106. package/dist/discovery/versions.d.ts.map +1 -0
  107. package/dist/discovery/versions.js +36 -0
  108. package/dist/evals-live/live-eval-runner.d.ts +1 -0
  109. package/dist/evals-live/live-eval-runner.d.ts.map +1 -1
  110. package/dist/evals-live/live-eval-runner.js +12 -1
  111. package/dist/evals-live/types.d.ts +3 -0
  112. package/dist/evals-live/types.d.ts.map +1 -1
  113. package/dist/generated/manifest-types.d.ts +3655 -0
  114. package/dist/generated/manifest-types.d.ts.map +1 -1
  115. package/dist/loader.d.ts.map +1 -1
  116. package/dist/loader.js +8 -0
  117. package/dist/mcp-server/server.d.ts.map +1 -1
  118. package/dist/mcp-server/server.js +11 -2
  119. package/dist/mcp-server/types.d.ts +4 -0
  120. package/dist/mcp-server/types.d.ts.map +1 -1
  121. package/dist/observability/redaction.d.ts.map +1 -1
  122. package/dist/observability/redaction.js +6 -2
  123. package/dist/observability/types.d.ts +1 -1
  124. package/dist/observability/types.d.ts.map +1 -1
  125. package/dist/openai/index.d.ts +2 -0
  126. package/dist/openai/index.d.ts.map +1 -1
  127. package/dist/openai/index.js +1 -0
  128. package/dist/openai/responses-runner.d.ts +1 -1
  129. package/dist/openai/responses-runner.d.ts.map +1 -1
  130. package/dist/openai/responses-runner.js +134 -7
  131. package/dist/openai/selection-client.d.ts +28 -0
  132. package/dist/openai/selection-client.d.ts.map +1 -0
  133. package/dist/openai/selection-client.js +95 -0
  134. package/dist/openai/tool-loop.js +1 -1
  135. package/dist/openai/types.d.ts +19 -2
  136. package/dist/openai/types.d.ts.map +1 -1
  137. package/dist/replay/discovery-snapshot.d.ts +10 -0
  138. package/dist/replay/discovery-snapshot.d.ts.map +1 -0
  139. package/dist/replay/discovery-snapshot.js +17 -0
  140. package/dist/replay/recorder.d.ts.map +1 -1
  141. package/dist/replay/recorder.js +4 -0
  142. package/dist/replay/redaction.js +5 -1
  143. package/dist/replay/replayer.d.ts.map +1 -1
  144. package/dist/replay/replayer.js +14 -1
  145. package/dist/replay/types.d.ts +3 -0
  146. package/dist/replay/types.d.ts.map +1 -1
  147. package/dist/replay/validation.d.ts.map +1 -1
  148. package/dist/replay/validation.js +6 -0
  149. package/dist/runtime/bounded-json.d.ts +4 -0
  150. package/dist/runtime/bounded-json.d.ts.map +1 -0
  151. package/dist/runtime/bounded-json.js +74 -0
  152. package/dist/runtime/eligibility.d.ts +4 -0
  153. package/dist/runtime/eligibility.d.ts.map +1 -0
  154. package/dist/runtime/eligibility.js +128 -0
  155. package/dist/runtime/index.d.ts +5 -1
  156. package/dist/runtime/index.d.ts.map +1 -1
  157. package/dist/runtime/index.js +2 -0
  158. package/dist/runtime/provider-attempt.d.ts +11 -0
  159. package/dist/runtime/provider-attempt.d.ts.map +1 -0
  160. package/dist/runtime/provider-attempt.js +221 -0
  161. package/dist/runtime/provider-run.d.ts +10 -0
  162. package/dist/runtime/provider-run.d.ts.map +1 -0
  163. package/dist/runtime/provider-run.js +123 -0
  164. package/dist/runtime/provider-stop.d.ts +9 -0
  165. package/dist/runtime/provider-stop.d.ts.map +1 -0
  166. package/dist/runtime/provider-stop.js +28 -0
  167. package/dist/runtime/provider-types.d.ts +189 -0
  168. package/dist/runtime/provider-types.d.ts.map +1 -0
  169. package/dist/runtime/provider-types.js +1 -0
  170. package/dist/runtime/provider-usage.d.ts +9 -0
  171. package/dist/runtime/provider-usage.d.ts.map +1 -0
  172. package/dist/runtime/provider-usage.js +69 -0
  173. package/dist/runtime/router.d.ts.map +1 -1
  174. package/dist/runtime/router.js +3 -118
  175. package/dist/runtime/types.d.ts +9 -0
  176. package/dist/runtime/types.d.ts.map +1 -1
  177. package/dist/types.d.ts +1 -1
  178. package/dist/types.d.ts.map +1 -1
  179. package/dist/validator.d.ts +3 -0
  180. package/dist/validator.d.ts.map +1 -1
  181. package/dist/validator.js +32 -0
  182. package/docs/api/public-api-policy.md +10 -0
  183. package/docs/api/stable-workflow.md +58 -0
  184. package/docs/api.md +7 -0
  185. package/docs/capability-manifests.md +24 -0
  186. package/docs/development.md +22 -0
  187. package/docs/discovery.md +186 -0
  188. package/docs/evals/discovery-live.md +199 -0
  189. package/docs/evals/discovery-qualification.md +158 -0
  190. package/docs/evals/overview.md +9 -0
  191. package/docs/getting-started/installation.md +11 -12
  192. package/docs/getting-started/openai-quickstart.md +16 -5
  193. package/docs/getting-started/provider-neutral-quickstart.md +247 -88
  194. package/docs/getting-started/quickstart.md +8 -30
  195. package/docs/index.md +10 -0
  196. package/docs/release.md +29 -5
  197. package/docs/runtime.md +27 -0
  198. package/docs/start-here.md +23 -134
  199. package/examples/12-capability-discovery-selection/README.md +346 -0
  200. package/examples/12-capability-discovery-selection/customer-cases.mjs +52 -0
  201. package/examples/12-capability-discovery-selection/customer-harness.mjs +91 -0
  202. package/examples/12-capability-discovery-selection/customer-request-example.mjs +320 -0
  203. package/examples/12-capability-discovery-selection/discovery-example.mjs +175 -0
  204. package/examples/12-capability-discovery-selection/discovery-fixtures/three-domains.suite.json +7887 -0
  205. package/examples/12-capability-discovery-selection/discovery-fixtures/three-domains.v2.suite.json +8167 -0
  206. package/examples/12-capability-discovery-selection/discovery-fixtures/three-domains.v3.suite.json +8342 -0
  207. package/examples/12-capability-discovery-selection/evaluation/application.mjs +305 -0
  208. package/examples/12-capability-discovery-selection/evaluation/campaign.mjs +248 -0
  209. package/examples/12-capability-discovery-selection/evaluation/clients.mjs +433 -0
  210. package/examples/12-capability-discovery-selection/evaluation/continuation.mjs +208 -0
  211. package/examples/12-capability-discovery-selection/evaluation/durable-budget.mjs +420 -0
  212. package/examples/12-capability-discovery-selection/evaluation/freeze.mjs +9 -0
  213. package/examples/12-capability-discovery-selection/evaluation/harness.mjs +618 -0
  214. package/examples/12-capability-discovery-selection/evaluation/mock-host.mjs +260 -0
  215. package/examples/12-capability-discovery-selection/evaluation/preflight.mjs +115 -0
  216. package/examples/12-capability-discovery-selection/evaluation/qualification-workflows.mjs +87 -0
  217. package/examples/12-capability-discovery-selection/evaluation/regrade.mjs +281 -0
  218. package/examples/12-capability-discovery-selection/evaluation/report.mjs +517 -0
  219. package/examples/12-capability-discovery-selection/evaluation/runner.mjs +613 -0
  220. package/examples/12-capability-discovery-selection/evaluation/saved-campaign.mjs +385 -0
  221. package/examples/12-capability-discovery-selection/evaluation/wording.mjs +115 -0
  222. package/examples/12-capability-discovery-selection/evaluation/workflow-application.mjs +378 -0
  223. package/examples/12-capability-discovery-selection/evaluation/workflow-cases.mjs +90 -0
  224. package/examples/12-capability-discovery-selection/evaluation-example.mjs +154 -0
  225. package/examples/12-capability-discovery-selection/hybrid-example.mjs +174 -0
  226. package/examples/12-capability-discovery-selection/mock-embeddings.mjs +101 -0
  227. package/examples/12-capability-discovery-selection/mock-selection.mjs +78 -0
  228. package/examples/12-capability-discovery-selection/pipeline-example.mjs +496 -0
  229. package/examples/12-capability-discovery-selection/retrieval-cases.mjs +31 -0
  230. package/examples/12-capability-discovery-selection/run-campaign-mock.mjs +40 -0
  231. package/examples/12-capability-discovery-selection/run-customer-mock.mjs +15 -0
  232. package/examples/12-capability-discovery-selection/run-evaluation-live.mjs +10 -0
  233. package/examples/12-capability-discovery-selection/run-evaluation-mock.mjs +3 -0
  234. package/examples/12-capability-discovery-selection/run-evaluation-regrade.mjs +71 -0
  235. package/examples/12-capability-discovery-selection/run-hybrid-mock.mjs +3 -0
  236. package/examples/12-capability-discovery-selection/run-mock.mjs +3 -0
  237. package/examples/12-capability-discovery-selection/run-pipeline-mock.mjs +2 -0
  238. package/examples/12-capability-discovery-selection/run-selector-mock.mjs +3 -0
  239. package/examples/12-capability-discovery-selection/selection-example.mjs +88 -0
  240. package/package.json +17 -4
  241. package/schemas/discovery/embedding-index.schema.json +71 -0
  242. package/schemas/discovery/eval-observations.schema.json +409 -0
  243. package/schemas/discovery/eval-suite.schema.json +375 -0
  244. package/schemas/discovery/metadata.schema.json +30 -0
  245. package/schemas/discovery/selection-response.schema.json +47 -0
  246. package/schemas/replay/discovery-snapshot.schema.json +335 -0
@@ -0,0 +1,385 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { createHash } from "node:crypto";
4
+ import {
5
+ buildCapabilitySearchCorpus,
6
+ hashDiscoveryContent,
7
+ fingerprintDiscoveryEvaluationSuite,
8
+ validateCapabilityEmbeddingIndex,
9
+ DISCOVERY_STANDARD_PROFILE
10
+ } from "ai-capability-framework/discovery";
11
+ import {
12
+ campaignBinding,
13
+ loadSuite,
14
+ registryFor,
15
+ trialSchedule,
16
+ MODES,
17
+ CAPS
18
+ } from "./campaign.mjs";
19
+ import { QUALIFICATION_WORKFLOWS } from "./qualification-workflows.mjs";
20
+ import { FROZEN_V3, FROZEN_QUALIFICATION_WORKFLOWS } from "./freeze.mjs";
21
+
22
+ export const byteHash = (bytes) =>
23
+ `sha256:${createHash("sha256").update(bytes).digest("hex")}`;
24
+ const requireEvidence = (condition) => {
25
+ if (!condition)
26
+ throw new Error("Saved campaign evidence is incomplete or incompatible.");
27
+ };
28
+ export function readBounded(filename, maximum) {
29
+ requireEvidence(
30
+ fs.statSync(filename).isFile() && fs.statSync(filename).size <= maximum
31
+ );
32
+ const bytes = fs.readFileSync(filename);
33
+ requireEvidence(bytes.length <= maximum);
34
+ return bytes;
35
+ }
36
+ const parse = (filename, maximum) =>
37
+ JSON.parse(readBounded(filename, maximum).toString("utf8"));
38
+ const fingerprint = (value) =>
39
+ typeof value === "string" && /^sha256:[a-f0-9]{64}$/.test(value);
40
+ const quantities = ["providerCalls", "inputTokens", "outputTokens", "estimatedCost"];
41
+ const same = (left, right) =>
42
+ hashDiscoveryContent(left) === hashDiscoveryContent(right);
43
+ const close = (left, right) =>
44
+ typeof right === "number" &&
45
+ Math.abs(left - right) <= Math.max(1e-10, Math.abs(left) * 1e-10);
46
+
47
+ export function readSavedArtifact(directory, reference) {
48
+ requireEvidence(
49
+ reference &&
50
+ /^[a-zA-Z0-9_.-]+\.json$/.test(reference.name) &&
51
+ fingerprint(reference.hash)
52
+ );
53
+ const filename = path.join(directory, reference.name);
54
+ requireEvidence(
55
+ path.dirname(fs.realpathSync(filename)) === fs.realpathSync(directory)
56
+ );
57
+ const bytes = readBounded(filename, 16 * 1024 * 1024),
58
+ value = JSON.parse(bytes.toString("utf8"));
59
+ requireEvidence(hashDiscoveryContent(value) === reference.hash);
60
+ return { value, bytesHash: byteHash(bytes) };
61
+ }
62
+
63
+ export function validateSavedSchedule(records, trials, suite) {
64
+ const scheduled = [
65
+ "administrative-index",
66
+ ...["train", "baseline", "qualify"].flatMap((stage) =>
67
+ trialSchedule(suite, stage).map((t) => t.key)
68
+ ),
69
+ ...QUALIFICATION_WORKFLOWS.flatMap((w) =>
70
+ MODES.map((mode) => `workflow.${w.id}.${mode.replace("/", ".")}`)
71
+ )
72
+ ];
73
+ requireEvidence(
74
+ same([...trials.keys()], scheduled) && [...trials.values()].every((t) => !t.pending)
75
+ );
76
+ requireEvidence(
77
+ same(
78
+ records.filter((r) => r.type === "stage.complete").map((r) => r.data),
79
+ [
80
+ ...["train", "baseline", "qualify"].map((stage) => ({
81
+ stage,
82
+ hash: hashDiscoveryContent(trialSchedule(suite, stage)),
83
+ count: trialSchedule(suite, stage).length
84
+ })),
85
+ { stage: "workflows", count: 48 }
86
+ ]
87
+ ) &&
88
+ records.filter((r) => r.type === "profile.freeze").length === 1 &&
89
+ records.find((r) => r.type === "profile.freeze").data.k === 8
90
+ );
91
+ return scheduled;
92
+ }
93
+
94
+ /** Reads immutable evidence only. Never opens a campaign ledger or host configuration. */
95
+ export function readSavedCampaign(inputPath) {
96
+ const manifest = parse(inputPath, 64_000);
97
+ requireEvidence(
98
+ Object.keys(manifest).sort().join() ===
99
+ "campaignDirectory,campaignFingerprint,journalHash,reportHash,reportPath,schemaVersion"
100
+ );
101
+ requireEvidence(
102
+ manifest.schemaVersion === "1.0" &&
103
+ [manifest.campaignFingerprint, manifest.journalHash, manifest.reportHash].every(
104
+ fingerprint
105
+ ) &&
106
+ [manifest.campaignDirectory, manifest.reportPath].every(
107
+ (p) => typeof p === "string" && p.trim()
108
+ )
109
+ );
110
+ const directory = fs.realpathSync(
111
+ path.resolve(path.dirname(inputPath), manifest.campaignDirectory)
112
+ );
113
+ const reportPath = fs.realpathSync(
114
+ path.resolve(path.dirname(inputPath), manifest.reportPath)
115
+ );
116
+ requireEvidence(!fs.existsSync(path.join(directory, "campaign.lock")));
117
+ const journal = readBounded(path.join(directory, "journal.jsonl"), 64 * 1024 * 1024);
118
+ const reportBytes = readBounded(reportPath, 16 * 1024 * 1024);
119
+ requireEvidence(
120
+ byteHash(journal) === manifest.journalHash &&
121
+ byteHash(reportBytes) === manifest.reportHash &&
122
+ journal.at(-1) === 10
123
+ );
124
+ const original = JSON.parse(reportBytes.toString("utf8"));
125
+ requireEvidence(
126
+ original.status === "evaluation_completed" &&
127
+ original.report?.complete &&
128
+ original.campaignFingerprint === manifest.campaignFingerprint
129
+ );
130
+ const suite = loadSuite(),
131
+ registry = registryFor(suite),
132
+ corpus = buildCapabilitySearchCorpus(registry);
133
+ requireEvidence(
134
+ fingerprintDiscoveryEvaluationSuite(suite) === FROZEN_V3 &&
135
+ hashDiscoveryContent(QUALIFICATION_WORKFLOWS) === FROZEN_QUALIFICATION_WORKFLOWS
136
+ );
137
+ const records = journal
138
+ .toString("utf8")
139
+ .trimEnd()
140
+ .split("\n")
141
+ .map((line) => {
142
+ requireEvidence(line.length <= 128_000);
143
+ return JSON.parse(line);
144
+ });
145
+ const head = records[0]?.data;
146
+ requireEvidence(
147
+ records[0]?.type === "campaign" &&
148
+ head?.fingerprint === manifest.campaignFingerprint &&
149
+ hashDiscoveryContent({ binding: head.binding, caps: head.caps }) ===
150
+ head.fingerprint
151
+ );
152
+ const { predecessor, ...binding } = head.binding;
153
+ requireEvidence(
154
+ same(
155
+ binding,
156
+ campaignBinding(
157
+ suite,
158
+ QUALIFICATION_WORKFLOWS,
159
+ binding.sourceFingerprint,
160
+ binding.hostIdentity
161
+ )
162
+ ) &&
163
+ fingerprint(binding.sourceFingerprint) &&
164
+ binding.hostIdentity.mock === false &&
165
+ predecessor &&
166
+ Object.keys(CAPS).every(
167
+ (key) =>
168
+ Number.isFinite(head.caps[key]) &&
169
+ head.caps[key] >= 0 &&
170
+ head.caps[key] <= CAPS[key]
171
+ )
172
+ );
173
+ const artifacts = [];
174
+ const readArtifact = (reference) => {
175
+ const { value, bytesHash } = readSavedArtifact(directory, reference);
176
+ artifacts.push({
177
+ name: reference.name,
178
+ hash: reference.hash,
179
+ bytesHash
180
+ });
181
+ return value;
182
+ };
183
+ const { values, trials, charged, httpRequests, index } = verifySavedJournal(
184
+ records,
185
+ readArtifact
186
+ );
187
+ const scheduled = validateSavedSchedule(records, trials, suite);
188
+ const ledger = original.report.ledger;
189
+ requireEvidence(
190
+ !ledger.halted &&
191
+ ledger.pendingReservations === 0 &&
192
+ ledger.pendingHttp === 0 &&
193
+ ledger.pendingTrials === 0 &&
194
+ ledger.completedTrials === scheduled.length &&
195
+ ledger.httpRequests === httpRequests &&
196
+ ledger.fingerprint === head.fingerprint &&
197
+ same(ledger.caps, head.caps) &&
198
+ quantities.every(
199
+ (key) =>
200
+ close(charged[key], ledger.charged[key]) && charged[key] <= head.caps[key]
201
+ ) &&
202
+ httpRequests <= head.caps.httpRequests
203
+ );
204
+ const identity = hashDiscoveryContent({
205
+ execution: binding.execution,
206
+ tokenizerIdentity: binding.hostIdentity.tokenizerIdentity,
207
+ pricing: binding.hostIdentity.pricing,
208
+ mock: false
209
+ });
210
+ requireEvidence(
211
+ index &&
212
+ index.indexFingerprint === original.indexFingerprint &&
213
+ validateCapabilityEmbeddingIndex(index, {
214
+ corpus,
215
+ configuration: {
216
+ provider: "openai",
217
+ model: binding.execution.embeddingModel,
218
+ dimensions: 1536,
219
+ configurationId: `evaluation.embedding.${identity.slice(7, 23)}`
220
+ },
221
+ mock: false,
222
+ limits: DISCOVERY_STANDARD_PROFILE.indexLimits
223
+ }).valid
224
+ );
225
+ const expectedTrials = ["train", "baseline", "qualify"].flatMap((stage) =>
226
+ trialSchedule(suite, stage)
227
+ );
228
+ const taskValues = values.filter((v) => v.observation),
229
+ workflows = values.filter((v) => v.workflowId);
230
+ requireEvidence(
231
+ same(
232
+ taskValues.map((v) => v.trial),
233
+ expectedTrials
234
+ ) &&
235
+ workflows.length === 48 &&
236
+ values.length === scheduled.length
237
+ );
238
+ for (const value of taskValues)
239
+ requireEvidence(
240
+ trials.get(value.trial.key).binding ===
241
+ hashDiscoveryContent({
242
+ trial: value.trial,
243
+ campaign: head.fingerprint,
244
+ index: value.trial.mode.startsWith("hybrid") ? index.indexFingerprint : null
245
+ })
246
+ );
247
+ for (const scenario of QUALIFICATION_WORKFLOWS)
248
+ for (const mode of MODES) {
249
+ const matching = workflows.filter(
250
+ (w) => w.workflowId === scenario.id && w.mode === mode
251
+ );
252
+ requireEvidence(
253
+ matching.length === 1 &&
254
+ matching[0].kind === scenario.kind &&
255
+ trials.get(`workflow.${scenario.id}.${mode.replace("/", ".")}`).binding ===
256
+ hashDiscoveryContent({
257
+ workflow: hashDiscoveryContent(scenario),
258
+ mode,
259
+ k: 8,
260
+ campaign: head.fingerprint
261
+ })
262
+ );
263
+ }
264
+ return {
265
+ manifest,
266
+ original,
267
+ suite,
268
+ registry,
269
+ taskValues,
270
+ workflows,
271
+ ledger,
272
+ artifacts,
273
+ directory,
274
+ reportPath,
275
+ binding,
276
+ trialArtifacts: Object.fromEntries(
277
+ [...trials].map(([key, value]) => [key, value.artifact])
278
+ ),
279
+ journalRecords: records.length
280
+ };
281
+ }
282
+
283
+ /** Pure journal reconciliation, also used by synthetic integrity regressions. */
284
+ export function verifySavedJournal(records, readArtifact) {
285
+ requireEvidence(records[0]?.type === "campaign");
286
+ const values = [],
287
+ trials = new Map(),
288
+ reservations = new Map(),
289
+ settled = new Set(),
290
+ http = new Set(),
291
+ httpSeen = new Set();
292
+ const charged = Object.fromEntries(quantities.map((key) => [key, 0]));
293
+ let previous = null,
294
+ httpRequests = 0,
295
+ index = null;
296
+ for (const [offset, { hash, ...record }] of records.entries()) {
297
+ const d = record.data;
298
+ requireEvidence(
299
+ record.sequence === offset + 1 &&
300
+ record.previous === previous &&
301
+ hashDiscoveryContent(record) === hash
302
+ );
303
+ previous = hash;
304
+ requireEvidence(
305
+ [
306
+ "campaign",
307
+ "capacity.preflight",
308
+ "reservation",
309
+ "settlement",
310
+ "http.start",
311
+ "http.end",
312
+ "trial.start",
313
+ "trial.end",
314
+ "index.ready",
315
+ "schedule",
316
+ "stage.complete",
317
+ "profile.freeze",
318
+ "halt"
319
+ ].includes(record.type)
320
+ );
321
+ if (record.type === "campaign") requireEvidence(offset === 0);
322
+ if (record.type === "halt") requireEvidence(false);
323
+ if (record.type === "reservation") {
324
+ requireEvidence(!reservations.has(d.id) && !settled.has(d.id));
325
+ reservations.set(d.id, d);
326
+ }
327
+ if (record.type === "settlement") {
328
+ const reservation = reservations.get(d.id),
329
+ attempts = Math.max(d.providerAttempts, d.completedResponses);
330
+ requireEvidence(
331
+ reservation &&
332
+ reservation.runId === d.runId &&
333
+ [d.providerAttempts, d.completedResponses].every(
334
+ (n) => Number.isSafeInteger(n) && n >= 0 && n <= 1
335
+ ) &&
336
+ Object.values(d.usage).every((n) => Number.isFinite(n) && n >= 0)
337
+ );
338
+ for (const key of quantities) {
339
+ const amount =
340
+ key === "providerCalls"
341
+ ? attempts
342
+ : (d.usage[key] ?? (attempts ? reservation.amounts[key] : 0));
343
+ requireEvidence(
344
+ Number.isFinite(amount) &&
345
+ amount >= 0 &&
346
+ d.charged[key] === amount &&
347
+ amount <= reservation.amounts[key]
348
+ );
349
+ charged[key] += amount;
350
+ }
351
+ reservations.delete(d.id);
352
+ settled.add(d.id);
353
+ }
354
+ if (record.type === "http.start") {
355
+ requireEvidence(!httpSeen.has(d.id) && Number.isFinite(d.fee) && d.fee >= 0);
356
+ httpSeen.add(d.id);
357
+ http.add(d.id);
358
+ httpRequests++;
359
+ charged.estimatedCost += d.fee;
360
+ }
361
+ if (record.type === "http.end") requireEvidence(http.delete(d.id));
362
+ if (record.type === "trial.start") {
363
+ requireEvidence(!trials.has(d.key));
364
+ trials.set(d.key, { binding: d.binding, pending: true });
365
+ }
366
+ if (record.type === "trial.end") {
367
+ const trial = trials.get(d.key);
368
+ requireEvidence(trial?.pending && trial.binding === d.binding);
369
+ trial.pending = false;
370
+ trial.artifact = d.artifact;
371
+ const value = readArtifact(d.artifact);
372
+ values.push(value);
373
+ if (d.key === "administrative-index") {
374
+ requireEvidence(value.status === "built");
375
+ index = value.index;
376
+ }
377
+ }
378
+ if (record.type === "index.ready")
379
+ requireEvidence(same(readArtifact(d.artifact).index, index));
380
+ }
381
+ requireEvidence(
382
+ !reservations.size && !http.size && [...trials.values()].every((t) => !t.pending)
383
+ );
384
+ return { values, trials, charged, httpRequests, index };
385
+ }
@@ -0,0 +1,115 @@
1
+ // Bounded English wording support for the synthetic application, not a prose verifier.
2
+ export const WORDING_CHECKER_VERSION = "synthetic-wording.v2";
3
+
4
+ const months = [
5
+ "January",
6
+ "February",
7
+ "March",
8
+ "April",
9
+ "May",
10
+ "June",
11
+ "July",
12
+ "August",
13
+ "September",
14
+ "October",
15
+ "November",
16
+ "December"
17
+ ];
18
+ const monthPattern = months
19
+ .map((month) => `${month}|${month.slice(0, 3)}\\.?`)
20
+ .join("|");
21
+ const namedDate = `(?:${monthPattern})\\s+\\d{1,2},?\\s+\\d{4}|\\d{1,2}\\s+(?:${monthPattern}),?\\s+\\d{4}`;
22
+ const values = new RegExp(
23
+ `(?:[\\w.+-]+@example\\.com|\\b(?:\\d{4}-\\d{2}-\\d{2}(?:T\\d{2}:\\d{2}:\\d{2}Z)?|${namedDate}|\\d{1,2}/\\d{1,2}/\\d{4}|(?:TCK|SKU|WH|USR|ORD|EVT)-[A-Z0-9]+|\\d{2}:\\d{2}|\\d+(?:\\.\\d+)?)\\b)`,
24
+ "gi"
25
+ );
26
+
27
+ function dateKey(year, month, day) {
28
+ if (![year, month, day].every(Number.isInteger)) return null;
29
+ if (year < 1 || year > 9999 || month < 1 || month > 12 || day < 1) return null;
30
+ const leap = year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
31
+ const days = [31, leap ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
32
+ if (day > days[month - 1]) return null;
33
+ return `${String(year).padStart(4, "0")}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
34
+ }
35
+
36
+ function calendarValue(value) {
37
+ const iso = /^(\d{4})-(\d{2})-(\d{2})(?:T(\d{2}):(\d{2}):(\d{2})Z)?$/.exec(value);
38
+ if (iso) {
39
+ if (iso[4] !== undefined && (+iso[4] > 23 || +iso[5] > 59 || +iso[6] > 59))
40
+ return null;
41
+ return dateKey(+iso[1], +iso[2], +iso[3]);
42
+ }
43
+ const parts = value.replace(/[.,]/g, "").toLowerCase().split(/\s+/);
44
+ if (parts.length !== 3) return null;
45
+ const dayFirst = /^\d+$/.test(parts[0]);
46
+ const name = parts[dayFirst ? 1 : 0];
47
+ const month =
48
+ months.findIndex((entry) =>
49
+ [entry.toLowerCase(), entry.slice(0, 3).toLowerCase()].includes(name)
50
+ ) + 1;
51
+ return dateKey(+parts[2], month, +parts[dayFirst ? 0 : 1]);
52
+ }
53
+
54
+ export function hasUnsupportedWordingValue(summary, facts) {
55
+ const permitted = new Set(),
56
+ dates = new Set();
57
+ const collect = (value) => {
58
+ if (value && typeof value === "object") {
59
+ for (const child of Object.values(value)) collect(child);
60
+ } else if (typeof value === "string" || typeof value === "number") {
61
+ const authored = String(value);
62
+ permitted.add(authored);
63
+ const date = calendarValue(authored);
64
+ if (date) {
65
+ dates.add(date);
66
+ if (/^\d{4}-\d{2}-\d{2}T/.test(authored)) {
67
+ permitted.add(date);
68
+ permitted.add(authored.slice(11, 16));
69
+ }
70
+ }
71
+ }
72
+ };
73
+ collect(facts);
74
+ for (const match of summary.matchAll(values)) {
75
+ const value = match[0];
76
+ if (/^\d{4}-\d{2}-\d{2}T/.test(value)) {
77
+ if (!calendarValue(value) || !permitted.has(value)) return true;
78
+ } else if (
79
+ /^\d{4}-\d{2}-\d{2}$/.test(value) ||
80
+ (/[a-z]/i.test(value) && /\s/.test(value))
81
+ ) {
82
+ const date = calendarValue(value);
83
+ if (!date || !dates.has(date)) return true;
84
+ } else if (
85
+ !permitted.has(value) &&
86
+ !(/^\d+(?:\.\d+)?$/.test(value) && permitted.has(String(Number(value))))
87
+ )
88
+ return true;
89
+ }
90
+ return false;
91
+ }
92
+
93
+ export function positiveCompletionText(summary) {
94
+ const expanded = summary.replace(
95
+ /\b(has|have|had|was|were|is|are)n['’]t\b/gi,
96
+ "$1 not"
97
+ );
98
+ const actions =
99
+ /\b(?:sent|paid|refunded|committed|applied|reassigned|transferred|completed|executed|delivered|approved|prepared|drafted|cancelled)\b/gi;
100
+ return expanded.replace(actions, (word, offset) => {
101
+ // Only the action span is masked. A later affirmative clause still gets checked.
102
+ const prefix = expanded
103
+ .slice(Math.max(0, offset - 120), offset)
104
+ .split(/[.!?;,\n]|\b(?:but|however|and)\b/i)
105
+ .at(-1);
106
+ const negated =
107
+ /\b(?:not|never)\s+(?:(?:been|be|already|yet)\s+){0,2}$/i.test(prefix) ||
108
+ /\b(?:no (?:action|refund|payment|adjustment|invitation|invite)|nothing)\s+(?:(?:has|have|had|was|were|is|are)\s+)?(?:been\s+)?$/i.test(
109
+ prefix
110
+ );
111
+ const prospective =
112
+ /\b(?:can|could|may|might|will|would|shall)\s+(?:only\s+)?be\s+$/i.test(prefix);
113
+ return negated || prospective ? " ".repeat(word.length) : word;
114
+ });
115
+ }