@danceiny/gotry 0.0.1-rc.21 → 0.0.1-rc.24

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 (327) hide show
  1. package/README.md +102 -194
  2. package/README.zh-CN.md +115 -210
  3. package/bin/gotry-backend.js +53 -0
  4. package/bin/gotry-bootstrap.js +466 -35
  5. package/bin/gotry-inner.js +255 -36
  6. package/bin/gotry-process-liveness.d.ts +27 -0
  7. package/bin/gotry-process-liveness.js +97 -0
  8. package/bin/gotry-runtime-resolution.d.ts +1 -1
  9. package/bin/gotry-runtime-resolution.js +2 -2
  10. package/client/client.js +174 -0
  11. package/cordis.gotry-patch.yml +70 -93
  12. package/dist/capabilities/agent-reach-deep.js +45 -122
  13. package/dist/capabilities/agent-reach.js +63 -148
  14. package/dist/capabilities/anything.js +89 -75
  15. package/dist/capabilities/artifacts.js +123 -104
  16. package/dist/capabilities/channel-health.js +65 -45
  17. package/dist/capabilities/channel-registry.js +108 -87
  18. package/dist/capabilities/doctor.js +357 -235
  19. package/dist/capabilities/effect.js +223 -309
  20. package/dist/capabilities/fact-log.js +28 -14
  21. package/dist/capabilities/flyai.js +245 -156
  22. package/dist/capabilities/ground-transfer.js +509 -0
  23. package/dist/capabilities/hbcli.js +144 -148
  24. package/dist/capabilities/hotelbyte-transaction.js +194 -0
  25. package/dist/capabilities/incident-log.js +131 -61
  26. package/dist/capabilities/model-override.js +30 -10
  27. package/dist/capabilities/opensky.js +54 -43
  28. package/dist/capabilities/resilience.js +50 -32
  29. package/dist/capabilities/session/action-cache.js +50 -44
  30. package/dist/capabilities/session/adapters/ctrip-flight.js +119 -62
  31. package/dist/capabilities/session/adapters/ctrip-hotel.js +125 -110
  32. package/dist/capabilities/session/adapters/dida-portal.js +206 -0
  33. package/dist/capabilities/session/adapters/meituan-local.js +35 -54
  34. package/dist/capabilities/session/adapters/rail-12306.js +169 -112
  35. package/dist/capabilities/session/benchmark.js +85 -149
  36. package/dist/capabilities/session/extension-bridge.js +320 -259
  37. package/dist/capabilities/session/extension-channel.js +57 -76
  38. package/dist/capabilities/session/extension-distribution.js +115 -119
  39. package/dist/capabilities/session/extract.js +31 -21
  40. package/dist/capabilities/session/golden-score.js +55 -33
  41. package/dist/capabilities/session/health-watch.js +146 -154
  42. package/dist/capabilities/session/read-guard.js +55 -47
  43. package/dist/capabilities/session/static-flight-golden.js +49 -36
  44. package/dist/capabilities/session/transport.js +54 -61
  45. package/dist/capabilities/session/wizard.js +63 -93
  46. package/dist/capabilities/session-consent.js +143 -47
  47. package/dist/capabilities/session-login.js +95 -126
  48. package/dist/capabilities/session-search.js +391 -253
  49. package/dist/capabilities/sponsor-plugin.js +341 -0
  50. package/dist/capabilities/visa-policy.js +87 -73
  51. package/dist/capabilities/weather.js +163 -235
  52. package/dist/scripts/action-cache-tests.js +24 -59
  53. package/dist/scripts/agent-planning-turn-deadline-e2e.js +91 -142
  54. package/dist/scripts/agent-planning-turn-deadline-tests.js +80 -176
  55. package/dist/scripts/agent-reach-deep-tests.js +30 -40
  56. package/dist/scripts/agent-reach-tests.js +26 -18
  57. package/dist/scripts/agent-reach-wrapper-tests.js +46 -50
  58. package/dist/scripts/anything-tests.js +58 -62
  59. package/dist/scripts/artifact-client-contract-tests.js +69 -0
  60. package/dist/scripts/artifacts-capability-tests.js +235 -0
  61. package/dist/scripts/async-collect.js +26 -15
  62. package/dist/scripts/benchmark-environment-bridge-e2e.js +287 -684
  63. package/dist/scripts/benchmark-environment-bridge-tests.js +818 -3123
  64. package/dist/scripts/booking-copilot-availability-ledger-binding-tests.js +66 -279
  65. package/dist/scripts/booking-copilot-availability-policy-tests.js +153 -1257
  66. package/dist/scripts/booking-copilot-bin-proof-tests.js +22 -45
  67. package/dist/scripts/booking-copilot-crossrepo-fixture-server.js +64 -76
  68. package/dist/scripts/booking-copilot-dsh-core-proof-tests.js +69 -199
  69. package/dist/scripts/booking-copilot-dsh-planner-proof-tests.js +393 -759
  70. package/dist/scripts/booking-copilot-dsh-plugin-proof-tests.js +33 -75
  71. package/dist/scripts/booking-copilot-event-sequence-concurrency-proof-tests.js +60 -110
  72. package/dist/scripts/booking-copilot-gap-code-contract-proof-tests.js +28 -66
  73. package/dist/scripts/booking-copilot-operation-ledger-concurrency-proof-tests.js +72 -271
  74. package/dist/scripts/booking-copilot-receipt-ledger-concurrency-proof-tests.js +73 -275
  75. package/dist/scripts/booking-copilot-runtime-proof-tests.js +784 -3398
  76. package/dist/scripts/booking-copilot-server-proof-tests.js +63 -168
  77. package/dist/scripts/booking-copilot-startup-proof-tests.js +38 -124
  78. package/dist/scripts/booking-executor-tests.js +80 -0
  79. package/dist/scripts/booking-recovery-chain-tests.js +182 -0
  80. package/dist/scripts/booking-saga-tests.js +61 -123
  81. package/dist/scripts/booking-surface-contract-proof-tests.js +152 -1167
  82. package/dist/scripts/bootstrap-tests.js +1383 -128
  83. package/dist/scripts/build-changelog.js +89 -130
  84. package/dist/scripts/build-metrics-report.js +135 -143
  85. package/dist/scripts/changelog-tests.js +35 -82
  86. package/dist/scripts/channel-probe-tests.js +44 -116
  87. package/dist/scripts/channel-probe.js +84 -167
  88. package/dist/scripts/channel-registry-tests.js +97 -185
  89. package/dist/scripts/companion-tests.js +25 -70
  90. package/dist/scripts/dida-cookie-probe.js +87 -0
  91. package/dist/scripts/dida-sw-probe.js +114 -0
  92. package/dist/scripts/diff-test.js +17 -12
  93. package/dist/scripts/doctor-tests.js +428 -120
  94. package/dist/scripts/dsh-artifact-e2e.js +150 -0
  95. package/dist/scripts/dsh-artifact-web-e2e.js +443 -0
  96. package/dist/scripts/dsh-http-proxy-sse-proof.js +90 -0
  97. package/dist/scripts/dsh-runtime-closure-tests.js +110 -153
  98. package/dist/scripts/dsh-runtime-closure.js +75 -63
  99. package/dist/scripts/dsh-session-v3-migration-proof.js +106 -0
  100. package/dist/scripts/dsh-subprocess-local-proof.js +149 -0
  101. package/dist/scripts/dsh-target-closure-proof.js +178 -0
  102. package/dist/scripts/effect-tests.js +165 -552
  103. package/dist/scripts/engine-run.js +1 -3
  104. package/dist/scripts/engine-tests.js +16 -10
  105. package/dist/scripts/evaluation-cadence-tests.js +69 -242
  106. package/dist/scripts/evaluation-contract-tests.js +193 -499
  107. package/dist/scripts/extension-distribution-cli.js +19 -15
  108. package/dist/scripts/extension-distribution-tests.js +98 -211
  109. package/dist/scripts/extension-tests.js +366 -389
  110. package/dist/scripts/fact-gate-tests.js +725 -263
  111. package/dist/scripts/fixtures/extension-bridge-unref-child.mjs +21 -0
  112. package/dist/scripts/flight-hotel-anchor-363-registered-e2e.js +169 -0
  113. package/dist/scripts/flight-malformed-tests.js +159 -0
  114. package/dist/scripts/flyai-tests.js +325 -68
  115. package/dist/scripts/g5-guard-tests.js +131 -0
  116. package/dist/scripts/g5-guard.js +189 -0
  117. package/dist/scripts/gen-lunar-anchors.js +106 -0
  118. package/dist/scripts/gotry-backend-kernel-tests.js +79 -0
  119. package/dist/scripts/ground-transfer-tests.js +956 -0
  120. package/dist/scripts/hbcli-e2e-tests.js +71 -90
  121. package/dist/scripts/hbcli-live-optin-tests.js +96 -0
  122. package/dist/scripts/hbcli-release-contract-tests.js +122 -0
  123. package/dist/scripts/hbcli-tests.js +41 -115
  124. package/dist/scripts/health-watch-cli.js +42 -28
  125. package/dist/scripts/hotel-date-gate-tests.js +496 -0
  126. package/dist/scripts/hotelbyte-reconcile-tests.js +141 -0
  127. package/dist/scripts/hotelbyte-spawn-e2e-tests.js +373 -0
  128. package/dist/scripts/i18n-tests.js +16 -9
  129. package/dist/scripts/incident-tests.js +281 -87
  130. package/dist/scripts/issue-194-job-id-guard-tests.js +106 -0
  131. package/dist/scripts/issue-233-cancel-refund-commission-tests.js +392 -0
  132. package/dist/scripts/issue-271-liveness-tests.js +547 -0
  133. package/dist/scripts/issue-289-model-retry-real-tests.js +296 -0
  134. package/dist/scripts/issue-289-model-retry-tests.js +441 -0
  135. package/dist/scripts/issue-289-web-retry-e2e.js +630 -0
  136. package/dist/scripts/issue-338-home-city-e2e.js +577 -0
  137. package/dist/scripts/issue343-real-entry-e2e.js +160 -0
  138. package/dist/scripts/journey-tests.js +22 -27
  139. package/dist/scripts/kernel-manifest-evidence.js +109 -0
  140. package/dist/scripts/kernel-manifest-gate.js +126 -0
  141. package/dist/scripts/kernel-manifest-tests.js +212 -0
  142. package/dist/scripts/kernel-manifest-trace-entry.js +7 -0
  143. package/dist/scripts/kernel-manifest-trace-hook.mjs +28 -0
  144. package/dist/scripts/kernel-manifest.js +369 -0
  145. package/dist/scripts/ledger-repair-apply-tests.js +316 -0
  146. package/dist/scripts/ledger-repair-plan-tests.js +375 -0
  147. package/dist/scripts/ledger-tests.js +325 -340
  148. package/dist/scripts/ledger-workflow-crash.js +10 -20
  149. package/dist/scripts/map-tools-vendor-package-proof.js +206 -134
  150. package/dist/scripts/memory-capture-tests.js +242 -61
  151. package/dist/scripts/memory-decay-tests.js +18 -52
  152. package/dist/scripts/memory-lifecycle-tests.js +657 -0
  153. package/dist/scripts/memory-lifecycle.js +196 -0
  154. package/dist/scripts/memory-metrics.js +24 -12
  155. package/dist/scripts/memory-value-report.js +474 -222
  156. package/dist/scripts/memory-value-tests.js +328 -0
  157. package/dist/scripts/metrics-report-tests.js +62 -199
  158. package/dist/scripts/model-override-e2e.js +66 -105
  159. package/dist/scripts/nightly-evidence-tests.js +64 -89
  160. package/dist/scripts/nightly-evidence.js +108 -111
  161. package/dist/scripts/nudge-digest.js +39 -28
  162. package/dist/scripts/onboarding-tests.js +63 -88
  163. package/dist/scripts/opensky-check.js +18 -13
  164. package/dist/scripts/opensky-tests.js +28 -28
  165. package/dist/scripts/persona-surface-guard-tests.js +37 -12
  166. package/dist/scripts/pnpm-dsh-closure-proof.js +2 -5
  167. package/dist/scripts/policy-anchor-359-registered-e2e.js +214 -0
  168. package/dist/scripts/price-drift-tests.js +126 -227
  169. package/dist/scripts/price-drift-watch.js +209 -253
  170. package/dist/scripts/probe-poi-tests.js +52 -45
  171. package/dist/scripts/product-metrics.js +187 -273
  172. package/dist/scripts/publish-preverify.js +47 -41
  173. package/dist/scripts/realtime-pricing-tests.js +37 -76
  174. package/dist/scripts/replay-async.js +20 -18
  175. package/dist/scripts/replay-real.js +16 -18
  176. package/dist/scripts/replay.js +52 -82
  177. package/dist/scripts/session-attach-diagnose.js +16 -12
  178. package/dist/scripts/session-attach-poc.js +34 -37
  179. package/dist/scripts/session-benchmark.js +66 -147
  180. package/dist/scripts/session-dida-live-e2e.js +151 -0
  181. package/dist/scripts/session-extract-tests.js +14 -35
  182. package/dist/scripts/session-login.js +14 -13
  183. package/dist/scripts/session-tests.js +545 -583
  184. package/dist/scripts/sf-live-benchmark.js +92 -110
  185. package/dist/scripts/sf-live-cli-tests.js +2 -9
  186. package/dist/scripts/sf-soft-score-tests.js +19 -60
  187. package/dist/scripts/sf-summary-tests.js +149 -0
  188. package/dist/scripts/sf-summary.js +466 -84
  189. package/dist/scripts/skeleton-check.js +16 -14
  190. package/dist/scripts/skeleton-integration-test.js +11 -10
  191. package/dist/scripts/skills-contract-tests.js +51 -46
  192. package/dist/scripts/smoke-session-gate-tests.js +12 -20
  193. package/dist/scripts/smoke.js +541 -422
  194. package/dist/scripts/sponsor-reuse-tests.js +262 -0
  195. package/dist/scripts/state-cli-tests.js +226 -101
  196. package/dist/scripts/state-cli.js +549 -212
  197. package/dist/scripts/static-golden-tests.js +46 -180
  198. package/dist/scripts/time-eval-tests.js +210 -192
  199. package/dist/scripts/travel-timeline-tests.js +29 -93
  200. package/dist/scripts/turn-handoff-collect-tests.js +46 -93
  201. package/dist/scripts/turn-handoff-collect.js +71 -99
  202. package/dist/scripts/turn-policy-tests.js +28 -29
  203. package/dist/scripts/typed-contract-canary.js +77 -162
  204. package/dist/scripts/tz-resolver-sanity.js +50 -0
  205. package/dist/scripts/tz-resolver-tests.js +584 -0
  206. package/dist/scripts/unified-tests.js +23 -19
  207. package/dist/scripts/visa-policy-tests.js +31 -44
  208. package/dist/scripts/weather-tests.js +121 -543
  209. package/dist/scripts/wish-channel-gate-tests.js +29 -86
  210. package/dist/scripts/write-gate-tests.js +522 -0
  211. package/dist/scripts/z3-lifecycle-fault-tests.js +188 -0
  212. package/dist/scripts/z3-lifecycle-tests.js +170 -0
  213. package/dist/scripts/z3-race-repeat-tests.js +39 -0
  214. package/dist/scripts/z3-race-tests.js +18 -12
  215. package/dist/src/artifact-gate.js +652 -221
  216. package/dist/src/backend/kernel.js +91 -0
  217. package/dist/src/backend/modules/booking-copilot.js +55 -0
  218. package/dist/src/backend/modules/booking-executor.js +124 -0
  219. package/dist/src/backend/modules/session-search.js +194 -0
  220. package/dist/src/benchmark-agent-conformance.js +296 -262
  221. package/dist/src/benchmark-environment-bridge.js +298 -413
  222. package/dist/src/benchmark-headless-child-diagnostics.js +93 -100
  223. package/dist/src/benchmark-tool-isolation.js +94 -67
  224. package/dist/src/bookable-facts.js +299 -174
  225. package/dist/src/booking-saga.js +64 -120
  226. package/dist/src/booking-surface/availability-policy.js +443 -576
  227. package/dist/src/booking-surface/cancel-refund-commission.js +379 -0
  228. package/dist/src/booking-surface/contracts.js +24 -80
  229. package/dist/src/booking-surface/dsh-planner.js +510 -281
  230. package/dist/src/booking-surface/dsh-plugin.js +17 -1
  231. package/dist/src/booking-surface/error-codes.js +82 -10
  232. package/dist/src/booking-surface/index.js +0 -3
  233. package/dist/src/booking-surface/profile.js +10 -54
  234. package/dist/src/booking-surface/recovery-chain.js +367 -0
  235. package/dist/src/booking-surface/runtime.js +765 -891
  236. package/dist/src/booking-surface/server.js +200 -251
  237. package/dist/src/booking-surface/startup.js +58 -54
  238. package/dist/src/booking-surface/validation.js +150 -218
  239. package/dist/src/bridge.js +20 -13
  240. package/dist/src/companions.js +48 -74
  241. package/dist/src/contracts.js +21 -43
  242. package/dist/src/dsh-llm.js +82 -120
  243. package/dist/src/engine.js +253 -239
  244. package/dist/src/evaluation-cadence.js +72 -209
  245. package/dist/src/evaluation-contracts.js +220 -843
  246. package/dist/src/flight-pack-adapter.js +54 -0
  247. package/dist/src/flight-pack-contract.js +2 -0
  248. package/dist/src/gotry-backend.js +70 -0
  249. package/dist/src/hotel-date-gate.js +118 -0
  250. package/dist/src/i18n.js +18 -7
  251. package/dist/src/index.js +1425 -1629
  252. package/dist/src/journey.js +132 -81
  253. package/dist/src/ledger-repair-apply.js +538 -0
  254. package/dist/src/ledger-repair-plan.js +463 -0
  255. package/dist/src/loop.js +257 -166
  256. package/dist/src/memory-capture.js +180 -32
  257. package/dist/src/memory-decay.js +30 -14
  258. package/dist/src/memory-lifecycle.js +1310 -0
  259. package/dist/src/memory-utility.js +27 -40
  260. package/dist/src/mock-llm.js +36 -61
  261. package/dist/src/model.js +81 -36
  262. package/dist/src/realtime-pricing.js +63 -72
  263. package/dist/src/slot-spec.js +84 -106
  264. package/dist/src/state-ledger.js +310 -471
  265. package/dist/src/subagent-job-id-guard.js +58 -0
  266. package/dist/src/time-anchor.js +231 -33
  267. package/dist/src/tool-packet.js +20 -8
  268. package/dist/src/travel-slots.js +58 -35
  269. package/dist/src/travel-timeline.js +44 -63
  270. package/dist/src/turn-deadline.js +121 -127
  271. package/dist/src/turn-policy.js +61 -89
  272. package/dist/src/tz-resolver.js +143 -0
  273. package/dist/src/unified.js +504 -381
  274. package/dist/src/wish-pool.js +39 -14
  275. package/dist/src/write-gate.js +495 -0
  276. package/dist/src/z3-shared.js +273 -7
  277. package/extension/README.md +4 -3
  278. package/extension/background.js +125 -14
  279. package/extension/content-bridge.js +23 -1
  280. package/extension/content-main.js +15 -4
  281. package/extension/manifest.json +16 -8
  282. package/package.json +251 -220
  283. package/ts/capabilities/agent-reach.ts +4 -4
  284. package/ts/capabilities/anything.ts +1 -1
  285. package/ts/capabilities/artifacts.ts +46 -9
  286. package/ts/capabilities/flyai.ts +156 -37
  287. package/ts/capabilities/hbcli.ts +1 -1
  288. package/ts/capabilities/incident-log.ts +88 -59
  289. package/ts/capabilities/session/adapters/ctrip-flight.ts +99 -36
  290. package/ts/capabilities/session/adapters/dida-portal.ts +250 -0
  291. package/ts/capabilities/session/adapters/rail-12306.ts +123 -24
  292. package/ts/capabilities/session/extension-bridge.ts +226 -101
  293. package/ts/capabilities/session/extension-channel.ts +22 -9
  294. package/ts/capabilities/session/wizard.ts +2 -2
  295. package/ts/capabilities/session-consent.ts +98 -14
  296. package/ts/capabilities/session-login.ts +1 -0
  297. package/ts/capabilities/session-search.ts +315 -31
  298. package/ts/dsh-runtime/vendor/README.md +8 -4
  299. package/ts/dsh-runtime/vendor/dsh-map-tools/README.en.md +7 -1
  300. package/ts/dsh-runtime/vendor/dsh-map-tools/README.md +5 -3
  301. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/settings-ns.js +1 -1
  302. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/settings-ns.d.ts +1 -1
  303. package/ts/dsh-runtime/vendor/dsh-map-tools/package.json +6 -6
  304. package/ts/package.json +236 -16
  305. package/ts/scripts/map-tools-vendor-package-proof.ts +152 -31
  306. package/ts/scripts/state-cli.ts +418 -51
  307. package/ts/src/artifact-gate.ts +561 -15
  308. package/ts/src/benchmark-environment-bridge.ts +37 -20
  309. package/ts/src/bookable-facts.ts +183 -7
  310. package/ts/src/booking-surface/cancel-refund-commission.ts +488 -0
  311. package/ts/src/booking-surface/dsh-planner.ts +240 -32
  312. package/ts/src/booking-surface/dsh-plugin.js +17 -1
  313. package/ts/src/booking-surface/error-codes.ts +75 -0
  314. package/ts/src/booking-surface/recovery-chain.ts +409 -0
  315. package/ts/src/booking-surface/server.ts +84 -51
  316. package/ts/src/booking-surface/validation.ts +2 -0
  317. package/ts/src/contracts.ts +12 -2
  318. package/ts/src/dsh-llm.ts +4 -8
  319. package/ts/src/index.ts +609 -90
  320. package/ts/src/loop.ts +116 -7
  321. package/ts/src/memory-capture.ts +227 -3
  322. package/ts/src/mock-llm.ts +4 -8
  323. package/ts/src/model.ts +88 -2
  324. package/ts/src/state-ledger.ts +47 -29
  325. package/ts/src/subagent-job-id-guard.ts +72 -0
  326. package/ts/src/time-anchor.ts +217 -13
  327. package/ts/src/unified.ts +316 -54
@@ -1,207 +1,75 @@
1
1
  import { createHash } from 'node:crypto';
2
- export const BENCHMARK_IDS = [
3
- 'trek',
4
- 'travelplanner',
5
- 'chinatravel',
6
- 'travelbench',
7
- 'tau2',
8
- 'locomo',
9
- 'bfcl'
10
- ];
11
- export const FAILURE_CATEGORIES = [
12
- 'schema_or_format',
13
- 'grounding_or_provenance',
14
- 'constraint_or_feasibility',
15
- 'time_or_location_continuity',
16
- 'cost_or_cardinality',
17
- 'tool_selection_or_arguments',
18
- 'policy_or_write_safety',
19
- 'preference_elicitation',
20
- 'memory_retrieval_or_temporal_reasoning',
21
- 'reliability_cost_or_latency'
22
- ];
23
- function obj(value, label) {
24
- if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error(`${label} must be an object`);
25
- return value;
26
- }
27
- function exact(value, label, keys) {
28
- const unknown = Object.keys(value).filter((key)=>!keys.includes(key));
29
- const missing = keys.filter((key)=>!(key in value));
30
- if (unknown.length) throw new Error(`${label} contains undeclared fields: ${unknown.join(',')}`);
31
- if (missing.length) throw new Error(`${label} missing fields: ${missing.join(',')}`);
32
- }
33
- function text(value, label) {
34
- if (typeof value !== 'string' || value.length === 0) throw new Error(`${label} must be non-empty text`);
35
- return value;
36
- }
37
- function id(value, label) {
38
- const out = text(value, label);
39
- if (!/^[a-z0-9][a-z0-9:._-]+$/.test(out)) throw new Error(`${label} must be a stable identifier`);
40
- return out;
41
- }
42
- function lit(value, label, values) {
43
- const out = text(value, label);
44
- if (!values.includes(out)) throw new Error(`${label} invalid literal`);
45
- return out;
46
- }
47
- function boolean(value, label) {
48
- if (typeof value !== 'boolean') throw new Error(`${label} must be boolean`);
49
- return value;
50
- }
51
- function finite(value, label, min = 0) {
52
- if (typeof value !== 'number' || !Number.isFinite(value) || value < min) throw new Error(`${label} must be a finite number >= ${min}`);
53
- return value;
54
- }
55
- function integer(value, label, min = 0) {
56
- const out = finite(value, label, min);
57
- if (!Number.isInteger(out)) throw new Error(`${label} must be an integer`);
58
- return out;
59
- }
60
- function strings(value, label, empty = false) {
61
- if (!Array.isArray(value) || !empty && value.length === 0) throw new Error(`${label} must be an array`);
62
- const out = value.map((item, index)=>text(item, `${label}[${index}]`));
63
- if (new Set(out).size !== out.length) throw new Error(`${label} must be unique`);
64
- return out;
65
- }
66
- function digest(value, label) {
67
- const out = text(value, label);
68
- if (!/^[0-9a-f]{64}$/.test(out)) throw new Error(`${label} must be lowercase SHA-256`);
69
- return out;
70
- }
71
- function commit(value, label) {
72
- const out = text(value, label);
73
- if (!/^[0-9a-f]{40}$/.test(out)) throw new Error(`${label} must be a lowercase 40-character Git commit`);
74
- return out;
75
- }
76
- function url(value, label) {
77
- const out = text(value, label);
78
- let parsed;
79
- try {
80
- parsed = new URL(out);
81
- } catch {
82
- throw new Error(`${label} must be a valid URL`);
83
- }
84
- ;
85
- if (parsed.protocol !== 'https:') throw new Error(`${label} must use https`);
86
- if (parsed.username || parsed.password) throw new Error(`${label} must not contain credentials`);
87
- return out;
88
- }
89
- function instant(value, label) {
90
- const out = text(value, label);
91
- const date = new Date(out);
92
- if (!Number.isFinite(date.valueOf()) || date.toISOString() !== out) throw new Error(`${label} must be canonical ISO UTC with milliseconds`);
93
- return out;
94
- }
95
- function zone(value, label) {
96
- const out = text(value, label);
97
- if (out === 'UTC') return out;
98
- try {
99
- new Intl.DateTimeFormat('en', {
100
- timeZone: out
101
- }).format(new Date(0));
102
- } catch {
103
- throw new Error(`${label} must be UTC or an IANA timezone`);
104
- }
105
- ;
106
- if (!out.includes('/')) throw new Error(`${label} must be UTC or an IANA timezone`);
107
- return out;
108
- }
109
- function revision(value, label) {
110
- const root = obj(value, label);
111
- exact(root, label, [
112
- 'kind',
113
- 'value'
114
- ]);
115
- const kind = lit(root.kind, `${label}.kind`, [
116
- 'git_commit',
117
- 'content_sha256',
118
- 'not_separately_declared'
119
- ]);
120
- if (kind === 'not_separately_declared') {
121
- if (root.value !== null) throw new Error(`${label}.value must be null`);
122
- return {
123
- kind,
124
- value: null
125
- };
126
- }
127
- ;
128
- return {
129
- kind,
130
- value: kind === 'git_commit' ? commit(root.value, `${label}.value`) : digest(root.value, `${label}.value`)
131
- };
132
- }
133
- function pin(value, label) {
134
- const root = obj(value, label);
135
- exact(root, label, [
136
- 'url',
137
- 'revision',
138
- 'source_scope'
139
- ]);
140
- return {
141
- url: url(root.url, `${label}.url`),
142
- revision: revision(root.revision, `${label}.revision`),
143
- source_scope: text(root.source_scope, `${label}.source_scope`)
144
- };
145
- }
146
- function licenseDetermination(value, label) {
147
- const root = obj(value, label);
148
- exact(root, label, [
149
- 'value',
150
- 'determination',
151
- 'source_url'
152
- ]);
153
- const determination = lit(root.determination, `${label}.determination`, [
154
- 'declared',
155
- 'not_separately_declared'
156
- ]);
157
- const resolved = text(root.value, `${label}.value`);
158
- if (determination === 'not_separately_declared' && resolved !== 'not_separately_declared') throw new Error(`${label}.value must be not_separately_declared`);
159
- return {
160
- value: resolved,
161
- determination,
162
- source_url: url(root.source_url, `${label}.source_url`)
163
- };
164
- }
165
- export function stableEvaluationJson(value) {
166
- const sort = (item)=>Array.isArray(item) ? item.map(sort) : item && typeof item === 'object' ? Object.fromEntries(Object.entries(item).sort(([a], [b])=>a.localeCompare(b)).map(([key, child])=>[
167
- key,
168
- sort(child)
169
- ])) : item;
170
- return `${JSON.stringify(sort(value), null, 2)}\n`;
171
- }
172
- export function evaluationFingerprint(value) {
173
- return createHash('sha256').update(stableEvaluationJson(value)).digest('hex');
174
- }
175
- const sensitiveKeys = new Set([
176
- 'apikey',
177
- 'accesstoken',
178
- 'refreshtoken',
179
- 'authorization',
180
- 'password',
181
- 'clientsecret',
182
- 'credentials',
183
- 'cookie',
184
- 'rawprompt',
185
- 'goldanswer',
186
- 'oraclepayload',
187
- 'credential',
188
- 'rawanswer',
189
- 'privatepayload',
190
- 'secretpayload',
191
- 'trajectorypayload'
192
- ]);
2
+ export const BENCHMARK_IDS = ['trek', 'travelplanner', 'chinatravel', 'travelbench', 'tau2', 'locomo', 'bfcl'];
3
+ export const FAILURE_CATEGORIES = ['schema_or_format', 'grounding_or_provenance', 'constraint_or_feasibility', 'time_or_location_continuity', 'cost_or_cardinality', 'tool_selection_or_arguments', 'policy_or_write_safety', 'preference_elicitation', 'memory_retrieval_or_temporal_reasoning', 'reliability_cost_or_latency'];
4
+ function obj(value, label) { if (!value || typeof value !== 'object' || Array.isArray(value))
5
+ throw new Error(`${label} must be an object`); return value; }
6
+ function exact(value, label, keys) { const unknown = Object.keys(value).filter(key => !keys.includes(key)); const missing = keys.filter(key => !(key in value)); if (unknown.length)
7
+ throw new Error(`${label} contains undeclared fields: ${unknown.join(',')}`); if (missing.length)
8
+ throw new Error(`${label} missing fields: ${missing.join(',')}`); }
9
+ function text(value, label) { if (typeof value !== 'string' || value.length === 0)
10
+ throw new Error(`${label} must be non-empty text`); return value; }
11
+ function id(value, label) { const out = text(value, label); if (!/^[a-z0-9][a-z0-9:._-]+$/.test(out))
12
+ throw new Error(`${label} must be a stable identifier`); return out; }
13
+ function lit(value, label, values) { const out = text(value, label); if (!values.includes(out))
14
+ throw new Error(`${label} invalid literal`); return out; }
15
+ function boolean(value, label) { if (typeof value !== 'boolean')
16
+ throw new Error(`${label} must be boolean`); return value; }
17
+ function finite(value, label, min = 0) { if (typeof value !== 'number' || !Number.isFinite(value) || value < min)
18
+ throw new Error(`${label} must be a finite number >= ${min}`); return value; }
19
+ function integer(value, label, min = 0) { const out = finite(value, label, min); if (!Number.isInteger(out))
20
+ throw new Error(`${label} must be an integer`); return out; }
21
+ function strings(value, label, empty = false) { if (!Array.isArray(value) || (!empty && value.length === 0))
22
+ throw new Error(`${label} must be an array`); const out = value.map((item, index) => text(item, `${label}[${index}]`)); if (new Set(out).size !== out.length)
23
+ throw new Error(`${label} must be unique`); return out; }
24
+ function digest(value, label) { const out = text(value, label); if (!/^[0-9a-f]{64}$/.test(out))
25
+ throw new Error(`${label} must be lowercase SHA-256`); return out; }
26
+ function commit(value, label) { const out = text(value, label); if (!/^[0-9a-f]{40}$/.test(out))
27
+ throw new Error(`${label} must be a lowercase 40-character Git commit`); return out; }
28
+ function url(value, label) { const out = text(value, label); let parsed; try {
29
+ parsed = new URL(out);
30
+ }
31
+ catch {
32
+ throw new Error(`${label} must be a valid URL`);
33
+ } ; if (parsed.protocol !== 'https:')
34
+ throw new Error(`${label} must use https`); if (parsed.username || parsed.password)
35
+ throw new Error(`${label} must not contain credentials`); return out; }
36
+ function instant(value, label) { const out = text(value, label); const date = new Date(out); if (!Number.isFinite(date.valueOf()) || date.toISOString() !== out)
37
+ throw new Error(`${label} must be canonical ISO UTC with milliseconds`); return out; }
38
+ function zone(value, label) { const out = text(value, label); if (out === 'UTC')
39
+ return out; try {
40
+ new Intl.DateTimeFormat('en', { timeZone: out }).format(new Date(0));
41
+ }
42
+ catch {
43
+ throw new Error(`${label} must be UTC or an IANA timezone`);
44
+ } ; if (!out.includes('/'))
45
+ throw new Error(`${label} must be UTC or an IANA timezone`); return out; }
46
+ function revision(value, label) { const root = obj(value, label); exact(root, label, ['kind', 'value']); const kind = lit(root.kind, `${label}.kind`, ['git_commit', 'content_sha256', 'not_separately_declared']); if (kind === 'not_separately_declared') {
47
+ if (root.value !== null)
48
+ throw new Error(`${label}.value must be null`);
49
+ return { kind, value: null };
50
+ } ; return { kind, value: kind === 'git_commit' ? commit(root.value, `${label}.value`) : digest(root.value, `${label}.value`) }; }
51
+ function pin(value, label) { const root = obj(value, label); exact(root, label, ['url', 'revision', 'source_scope']); return { url: url(root.url, `${label}.url`), revision: revision(root.revision, `${label}.revision`), source_scope: text(root.source_scope, `${label}.source_scope`) }; }
52
+ function licenseDetermination(value, label) { const root = obj(value, label); exact(root, label, ['value', 'determination', 'source_url']); const determination = lit(root.determination, `${label}.determination`, ['declared', 'not_separately_declared']); const resolved = text(root.value, `${label}.value`); if (determination === 'not_separately_declared' && resolved !== 'not_separately_declared')
53
+ throw new Error(`${label}.value must be not_separately_declared`); return { value: resolved, determination, source_url: url(root.source_url, `${label}.source_url`) }; }
54
+ export function stableEvaluationJson(value) { const sort = (item) => Array.isArray(item) ? item.map(sort) : item && typeof item === 'object' ? Object.fromEntries(Object.entries(item).sort(([a], [b]) => a.localeCompare(b)).map(([key, child]) => [key, sort(child)])) : item; return `${JSON.stringify(sort(value), null, 2)}\n`; }
55
+ export function evaluationFingerprint(value) { return createHash('sha256').update(stableEvaluationJson(value)).digest('hex'); }
56
+ const sensitiveKeys = new Set(['apikey', 'accesstoken', 'refreshtoken', 'authorization', 'password', 'clientsecret', 'credentials', 'cookie', 'rawprompt', 'goldanswer', 'oraclepayload', 'credential', 'rawanswer', 'privatepayload', 'secretpayload', 'trajectorypayload']);
193
57
  export function assertPublicArtifactSafe(value, label = 'artifact') {
194
- const walk = (item, path)=>{
195
- if (Array.isArray(item)) return item.forEach((child, index)=>walk(child, `${path}[${index}]`));
58
+ const walk = (item, path) => {
59
+ if (Array.isArray(item))
60
+ return item.forEach((child, index) => walk(child, `${path}[${index}]`));
196
61
  if (typeof item === 'string') {
197
62
  const withoutHttpsUrls = item.replace(/https:\/\/[^\s]+/g, '');
198
63
  const highConfidenceSecret = /Bearer\s+[A-Za-z0-9._~-]{24,}|\bsk-[A-Za-z0-9_-]{20,}|\bgh[pousr]_[A-Za-z0-9_]{20,}|\bAKIA[0-9A-Z]{16}\b/;
199
- if (/(?:^|[^A-Za-z0-9])(?:\/|~\/)|[A-Za-z]:[\\/]|file:\/\//.test(withoutHttpsUrls) || highConfidenceSecret.test(item)) throw new Error(`${path} contains an absolute path or secret`);
64
+ if (/(?:^|[^A-Za-z0-9])(?:\/|~\/)|[A-Za-z]:[\\/]|file:\/\//.test(withoutHttpsUrls) || highConfidenceSecret.test(item))
65
+ throw new Error(`${path} contains an absolute path or secret`);
200
66
  return;
201
67
  }
202
- if (!item || typeof item !== 'object') return;
203
- for (const [key, child] of Object.entries(item)){
204
- if (sensitiveKeys.has(key.replace(/[^a-z0-9]/gi, '').toLowerCase()) && child !== '' && child !== null) throw new Error(`${path}.${key} contains credentials or raw sensitive payload`);
68
+ if (!item || typeof item !== 'object')
69
+ return;
70
+ for (const [key, child] of Object.entries(item)) {
71
+ if (sensitiveKeys.has(key.replace(/[^a-z0-9]/gi, '').toLowerCase()) && child !== '' && child !== null)
72
+ throw new Error(`${path}.${key} contains credentials or raw sensitive payload`);
205
73
  walk(child, `${path}.${key}`);
206
74
  }
207
75
  };
@@ -209,698 +77,207 @@ export function assertPublicArtifactSafe(value, label = 'artifact') {
209
77
  }
210
78
  function registryEntry(value, label) {
211
79
  const root = obj(value, label);
212
- exact(root, label, [
213
- 'schema_version',
214
- 'benchmark_id',
215
- 'provenance',
216
- 'license',
217
- 'task_scopes',
218
- 'native_metrics',
219
- 'source_fence',
220
- 'countability_default'
221
- ]);
80
+ exact(root, label, ['schema_version', 'benchmark_id', 'provenance', 'license', 'task_scopes', 'native_metrics', 'source_fence', 'countability_default']);
222
81
  const provenance = obj(root.provenance, `${label}.provenance`);
223
- exact(provenance, `${label}.provenance`, [
224
- 'official_entry',
225
- 'data',
226
- 'evaluator'
227
- ]);
82
+ exact(provenance, `${label}.provenance`, ['official_entry', 'data', 'evaluator']);
228
83
  const license = obj(root.license, `${label}.license`);
229
- exact(license, `${label}.license`, [
230
- 'upstream_rights',
231
- 'repo_storage_policy'
232
- ]);
84
+ exact(license, `${label}.license`, ['upstream_rights', 'repo_storage_policy']);
233
85
  const rights = obj(license.upstream_rights, `${label}.license.upstream_rights`);
234
- exact(rights, `${label}.license.upstream_rights`, [
235
- 'code',
236
- 'data',
237
- 'evaluator'
238
- ]);
86
+ exact(rights, `${label}.license.upstream_rights`, ['code', 'data', 'evaluator']);
239
87
  const metrics = obj(root.native_metrics, `${label}.native_metrics`);
240
- exact(metrics, `${label}.native_metrics`, [
241
- 'status',
242
- 'values'
243
- ]);
244
- if (!Array.isArray(metrics.values)) throw new Error(`${label}.native_metrics.values must be an array`);
245
- const status = lit(metrics.status, `${label}.native_metrics.status`, [
246
- 'declared',
247
- 'not_separately_declared'
248
- ]);
249
- const values = metrics.values.map((raw, index)=>{
250
- const metric = obj(raw, `${label}.metric[${index}]`);
251
- exact(metric, `${label}.metric[${index}]`, [
252
- 'receipt_key',
253
- 'upstream_label',
254
- 'scope',
255
- 'source_url'
256
- ]);
257
- return {
258
- receipt_key: id(metric.receipt_key, `${label}.metric.receipt_key`),
259
- upstream_label: text(metric.upstream_label, `${label}.metric.upstream_label`),
260
- scope: text(metric.scope, `${label}.metric.scope`),
261
- source_url: url(metric.source_url, `${label}.metric.source_url`)
262
- };
263
- });
264
- if (status === 'declared' && values.length === 0) throw new Error(`${label} declared metrics must be non-empty`);
265
- if (status === 'not_separately_declared' && values.length !== 0) throw new Error(`${label} undeclared metrics must be empty`);
88
+ exact(metrics, `${label}.native_metrics`, ['status', 'values']);
89
+ if (!Array.isArray(metrics.values))
90
+ throw new Error(`${label}.native_metrics.values must be an array`);
91
+ const status = lit(metrics.status, `${label}.native_metrics.status`, ['declared', 'not_separately_declared']);
92
+ const values = metrics.values.map((raw, index) => { const metric = obj(raw, `${label}.metric[${index}]`); exact(metric, `${label}.metric[${index}]`, ['receipt_key', 'upstream_label', 'scope', 'source_url']); return { receipt_key: id(metric.receipt_key, `${label}.metric.receipt_key`), upstream_label: text(metric.upstream_label, `${label}.metric.upstream_label`), scope: text(metric.scope, `${label}.metric.scope`), source_url: url(metric.source_url, `${label}.metric.source_url`) }; });
93
+ if (status === 'declared' && values.length === 0)
94
+ throw new Error(`${label} declared metrics must be non-empty`);
95
+ if (status === 'not_separately_declared' && values.length !== 0)
96
+ throw new Error(`${label} undeclared metrics must be empty`);
266
97
  const fence = obj(root.source_fence, `${label}.source_fence`);
267
- exact(fence, `${label}.source_fence`, [
268
- 'solver_allowed_field_classes',
269
- 'solver_forbidden_field_classes'
270
- ]);
271
- return {
272
- schema_version: lit(root.schema_version, `${label}.schema_version`, [
273
- 'gotry_benchmark_registry_entry_v0'
274
- ]),
275
- benchmark_id: lit(root.benchmark_id, `${label}.benchmark_id`, BENCHMARK_IDS),
276
- provenance: {
277
- official_entry: pin(provenance.official_entry, `${label}.official_entry`),
278
- data: pin(provenance.data, `${label}.data`),
279
- evaluator: pin(provenance.evaluator, `${label}.evaluator`)
280
- },
281
- license: {
282
- upstream_rights: {
283
- code: licenseDetermination(rights.code, `${label}.rights.code`),
284
- data: licenseDetermination(rights.data, `${label}.rights.data`),
285
- evaluator: licenseDetermination(rights.evaluator, `${label}.rights.evaluator`)
286
- },
287
- repo_storage_policy: lit(license.repo_storage_policy, `${label}.storage`, [
288
- 'metadata_only_no_upstream_payload'
289
- ])
290
- },
291
- task_scopes: strings(root.task_scopes, `${label}.task_scopes`),
292
- native_metrics: status === 'declared' ? {
293
- status,
294
- values
295
- } : {
296
- status,
297
- values: []
298
- },
299
- source_fence: {
300
- solver_allowed_field_classes: strings(fence.solver_allowed_field_classes, `${label}.allowed`),
301
- solver_forbidden_field_classes: strings(fence.solver_forbidden_field_classes, `${label}.forbidden`)
302
- },
303
- countability_default: lit(root.countability_default, `${label}.countability_default`, [
304
- 'countable_if_qualified',
305
- 'diagnostic_only'
306
- ])
307
- };
308
- }
309
- export function parseBenchmarkRegistry(value) {
310
- if (!Array.isArray(value) || value.length !== 7) throw new Error('registry must contain exactly seven entries');
311
- const out = value.map((item, index)=>registryEntry(item, `registry[${index}]`));
312
- if (out.some((item, index)=>item.benchmark_id !== BENCHMARK_IDS[index])) throw new Error('registry must use canonical unique order');
313
- return out;
98
+ exact(fence, `${label}.source_fence`, ['solver_allowed_field_classes', 'solver_forbidden_field_classes']);
99
+ return { schema_version: lit(root.schema_version, `${label}.schema_version`, ['gotry_benchmark_registry_entry_v0']), benchmark_id: lit(root.benchmark_id, `${label}.benchmark_id`, BENCHMARK_IDS), provenance: { official_entry: pin(provenance.official_entry, `${label}.official_entry`), data: pin(provenance.data, `${label}.data`), evaluator: pin(provenance.evaluator, `${label}.evaluator`) }, license: { upstream_rights: { code: licenseDetermination(rights.code, `${label}.rights.code`), data: licenseDetermination(rights.data, `${label}.rights.data`), evaluator: licenseDetermination(rights.evaluator, `${label}.rights.evaluator`) }, repo_storage_policy: lit(license.repo_storage_policy, `${label}.storage`, ['metadata_only_no_upstream_payload']) }, task_scopes: strings(root.task_scopes, `${label}.task_scopes`), native_metrics: status === 'declared' ? { status, values } : { status, values: [] }, source_fence: { solver_allowed_field_classes: strings(fence.solver_allowed_field_classes, `${label}.allowed`), solver_forbidden_field_classes: strings(fence.solver_forbidden_field_classes, `${label}.forbidden`) }, countability_default: lit(root.countability_default, `${label}.countability_default`, ['countable_if_qualified', 'diagnostic_only']) };
314
100
  }
101
+ export function parseBenchmarkRegistry(value) { if (!Array.isArray(value) || value.length !== 7)
102
+ throw new Error('registry must contain exactly seven entries'); const out = value.map((item, index) => registryEntry(item, `registry[${index}]`)); if (out.some((item, index) => item.benchmark_id !== BENCHMARK_IDS[index]))
103
+ throw new Error('registry must use canonical unique order'); return out; }
315
104
  export function parseEvalCase(value) {
316
105
  const root = obj(value, 'case');
317
- exact(root, 'case', [
318
- 'schema_version',
319
- 'case_id',
320
- 'benchmark_id',
321
- 'input_ref',
322
- 'isolation',
323
- 'clock',
324
- 'allowed_effects',
325
- 'forbidden_effects',
326
- 'budget',
327
- 'scorer_revision',
328
- 'public_safety'
329
- ]);
106
+ exact(root, 'case', ['schema_version', 'case_id', 'benchmark_id', 'input_ref', 'isolation', 'clock', 'allowed_effects', 'forbidden_effects', 'budget', 'scorer_revision', 'public_safety']);
330
107
  const input = obj(root.input_ref, 'case.input_ref');
331
- exact(input, 'case.input_ref', [
332
- 'kind',
333
- 'revision',
334
- 'digest_sha256'
335
- ]);
108
+ exact(input, 'case.input_ref', ['kind', 'revision', 'digest_sha256']);
336
109
  const isolation = obj(root.isolation, 'case.isolation');
337
- exact(isolation, 'case.isolation', [
338
- 'state_root',
339
- 'network',
340
- 'writes'
341
- ]);
110
+ exact(isolation, 'case.isolation', ['state_root', 'network', 'writes']);
342
111
  const clock = obj(root.clock, 'case.clock');
343
- exact(clock, 'case.clock', [
344
- 'now',
345
- 'timezone'
346
- ]);
112
+ exact(clock, 'case.clock', ['now', 'timezone']);
347
113
  const budget = obj(root.budget, 'case.budget');
348
- exact(budget, 'case.budget', [
349
- 'max_seconds',
350
- 'max_cost_usd',
351
- 'max_tool_calls',
352
- 'max_turns'
353
- ]);
114
+ exact(budget, 'case.budget', ['max_seconds', 'max_cost_usd', 'max_tool_calls', 'max_turns']);
354
115
  const safety = obj(root.public_safety, 'case.public_safety');
355
- exact(safety, 'case.public_safety', [
356
- 'contains_third_party_prompt',
357
- 'contains_gold',
358
- 'contains_oracle',
359
- 'contains_private_data'
360
- ]);
361
- for (const [key, flag] of Object.entries(safety))if (boolean(flag, `case.public_safety.${key}`) !== false) throw new Error(`case.public_safety.${key} must be false`);
362
- const out = {
363
- schema_version: lit(root.schema_version, 'case.schema_version', [
364
- 'gotry_eval_case_v0'
365
- ]),
366
- case_id: id(root.case_id, 'case.case_id'),
367
- benchmark_id: lit(root.benchmark_id, 'case.benchmark_id', BENCHMARK_IDS),
368
- input_ref: {
369
- kind: lit(input.kind, 'case.input_ref.kind', [
370
- 'gotry_owned_synthetic',
371
- 'external_opaque_reference'
372
- ]),
373
- revision: revision(input.revision, 'case.input_ref.revision'),
374
- digest_sha256: digest(input.digest_sha256, 'case.input_ref.digest_sha256')
375
- },
376
- isolation: {
377
- state_root: lit(isolation.state_root, 'case.state_root', [
378
- 'ephemeral'
379
- ]),
380
- network: lit(isolation.network, 'case.network', [
381
- 'denied',
382
- 'benchmark_declared'
383
- ]),
384
- writes: lit(isolation.writes, 'case.writes', [
385
- 'forbidden'
386
- ])
387
- },
388
- clock: {
389
- now: instant(clock.now, 'case.clock.now'),
390
- timezone: zone(clock.timezone, 'case.clock.timezone')
391
- },
392
- allowed_effects: strings(root.allowed_effects, 'case.allowed_effects', true),
393
- forbidden_effects: strings(root.forbidden_effects, 'case.forbidden_effects'),
394
- budget: {
395
- max_seconds: finite(budget.max_seconds, 'case.max_seconds'),
396
- max_cost_usd: finite(budget.max_cost_usd, 'case.max_cost_usd'),
397
- max_tool_calls: integer(budget.max_tool_calls, 'case.max_tool_calls'),
398
- max_turns: integer(budget.max_turns, 'case.max_turns')
399
- },
400
- scorer_revision: revision(root.scorer_revision, 'case.scorer_revision'),
401
- public_safety: {
402
- contains_third_party_prompt: false,
403
- contains_gold: false,
404
- contains_oracle: false,
405
- contains_private_data: false
406
- }
407
- };
116
+ exact(safety, 'case.public_safety', ['contains_third_party_prompt', 'contains_gold', 'contains_oracle', 'contains_private_data']);
117
+ for (const [key, flag] of Object.entries(safety))
118
+ if (boolean(flag, `case.public_safety.${key}`) !== false)
119
+ throw new Error(`case.public_safety.${key} must be false`);
120
+ const out = { schema_version: lit(root.schema_version, 'case.schema_version', ['gotry_eval_case_v0']), case_id: id(root.case_id, 'case.case_id'), benchmark_id: lit(root.benchmark_id, 'case.benchmark_id', BENCHMARK_IDS), input_ref: { kind: lit(input.kind, 'case.input_ref.kind', ['gotry_owned_synthetic', 'external_opaque_reference']), revision: revision(input.revision, 'case.input_ref.revision'), digest_sha256: digest(input.digest_sha256, 'case.input_ref.digest_sha256') }, isolation: { state_root: lit(isolation.state_root, 'case.state_root', ['ephemeral']), network: lit(isolation.network, 'case.network', ['denied', 'benchmark_declared']), writes: lit(isolation.writes, 'case.writes', ['forbidden']) }, clock: { now: instant(clock.now, 'case.clock.now'), timezone: zone(clock.timezone, 'case.clock.timezone') }, allowed_effects: strings(root.allowed_effects, 'case.allowed_effects', true), forbidden_effects: strings(root.forbidden_effects, 'case.forbidden_effects'), budget: { max_seconds: finite(budget.max_seconds, 'case.max_seconds'), max_cost_usd: finite(budget.max_cost_usd, 'case.max_cost_usd'), max_tool_calls: integer(budget.max_tool_calls, 'case.max_tool_calls'), max_turns: integer(budget.max_turns, 'case.max_turns') }, scorer_revision: revision(root.scorer_revision, 'case.scorer_revision'), public_safety: { contains_third_party_prompt: false, contains_gold: false, contains_oracle: false, contains_private_data: false } };
408
121
  assertPublicArtifactSafe(out, 'case');
409
122
  return out;
410
123
  }
411
- function optionalDigest(value, label) {
412
- return value === null ? null : digest(value, label);
413
- }
414
- function numberMap(value, label, integers = false) {
415
- const root = obj(value, label);
416
- return Object.fromEntries(Object.entries(root).map(([key, item])=>[
417
- id(key, `${label}.key`),
418
- integers ? integer(item, `${label}.${key}`) : finite(item, `${label}.${key}`, Number.NEGATIVE_INFINITY)
419
- ]));
420
- }
124
+ function optionalDigest(value, label) { return value === null ? null : digest(value, label); }
125
+ // The only schema-free object maps are native_metrics and evidence counts. Their leaves are numeric;
126
+ // native_metrics keys close exactly to registry receipt_key values in parseEvaluationFoundation.
127
+ function numberMap(value, label, integers = false) { const root = obj(value, label); return Object.fromEntries(Object.entries(root).map(([key, item]) => [id(key, `${label}.key`), integers ? integer(item, `${label}.${key}`) : finite(item, `${label}.${key}`, Number.NEGATIVE_INFINITY)])); }
421
128
  export function parseEvalRunReceipt(value) {
422
129
  const root = obj(value, 'run');
423
- exact(root, 'run', [
424
- 'schema_version',
425
- 'run_id',
426
- 'benchmark_id',
427
- 'case_id',
428
- 'evidence_kind',
429
- 'pairing',
430
- 'status',
431
- 'started_at',
432
- 'finished_at',
433
- 'gotry_sha',
434
- 'model',
435
- 'controls',
436
- 'qualification',
437
- 'experiment',
438
- 'native_metrics',
439
- 'guardrails',
440
- 'evidence_summary'
441
- ]);
130
+ exact(root, 'run', ['schema_version', 'run_id', 'benchmark_id', 'case_id', 'evidence_kind', 'pairing', 'status', 'started_at', 'finished_at', 'gotry_sha', 'model', 'controls', 'qualification', 'experiment', 'native_metrics', 'guardrails', 'evidence_summary']);
442
131
  const model = obj(root.model, 'run.model');
443
- exact(model, 'run.model', [
444
- 'provider',
445
- 'model'
446
- ]);
132
+ exact(model, 'run.model', ['provider', 'model']);
447
133
  const controls = obj(root.controls, 'run.controls');
448
- const controlKeys = [
449
- 'case_set_sha256',
450
- 'protocol_control_sha256',
451
- 'model_parameters_sha256',
452
- 'scorer_sha256',
453
- 'tool_snapshot_sha256',
454
- 'source_fence_sha256',
455
- 'integrity_sha256',
456
- 'official_evaluator_sha256'
457
- ];
134
+ const controlKeys = ['case_set_sha256', 'protocol_control_sha256', 'model_parameters_sha256', 'scorer_sha256', 'tool_snapshot_sha256', 'source_fence_sha256', 'integrity_sha256', 'official_evaluator_sha256'];
458
135
  exact(controls, 'run.controls', controlKeys);
459
136
  const qualification = obj(root.qualification, 'run.qualification');
460
- exact(qualification, 'run.qualification', [
461
- 'official_result',
462
- 'source_fence_passed',
463
- 'integrity_passed',
464
- 'evidence_receipts'
465
- ]);
137
+ exact(qualification, 'run.qualification', ['official_result', 'source_fence_passed', 'integrity_passed', 'evidence_receipts']);
466
138
  const receipts = obj(qualification.evidence_receipts, 'run.qualification.evidence_receipts');
467
- exact(receipts, 'run.qualification.evidence_receipts', [
468
- 'official_evaluator_output_sha256',
469
- 'source_fence_audit_sha256',
470
- 'integrity_audit_sha256'
471
- ]);
139
+ exact(receipts, 'run.qualification.evidence_receipts', ['official_evaluator_output_sha256', 'source_fence_audit_sha256', 'integrity_audit_sha256']);
472
140
  const experiment = obj(root.experiment, 'run.experiment');
473
- exact(experiment, 'run.experiment', [
474
- 'changed_variables',
475
- 'candidate_sha256'
476
- ]);
141
+ exact(experiment, 'run.experiment', ['changed_variables', 'candidate_sha256']);
477
142
  const guard = obj(root.guardrails, 'run.guardrails');
478
- exact(guard, 'run.guardrails', [
479
- 'hard_violation_count',
480
- 'forbidden_leakage_hits',
481
- 'latency_ms',
482
- 'cost_usd',
483
- 'tool_calls',
484
- 'turns'
485
- ]);
143
+ exact(guard, 'run.guardrails', ['hard_violation_count', 'forbidden_leakage_hits', 'latency_ms', 'cost_usd', 'tool_calls', 'turns']);
486
144
  const summary = obj(root.evidence_summary, 'run.evidence_summary');
487
- exact(summary, 'run.evidence_summary', [
488
- 'artifact_classification',
489
- 'sha256',
490
- 'counts',
491
- 'reason_codes',
492
- 'fixture_only',
493
- 'statement'
494
- ]);
145
+ exact(summary, 'run.evidence_summary', ['artifact_classification', 'sha256', 'counts', 'reason_codes', 'fixture_only', 'statement']);
495
146
  let pairing = null;
496
147
  if (root.pairing !== null) {
497
148
  const item = obj(root.pairing, 'run.pairing');
498
- exact(item, 'run.pairing', [
499
- 'pair_id',
500
- 'role',
501
- 'counterpart_run_id'
502
- ]);
503
- pairing = {
504
- pair_id: id(item.pair_id, 'run.pair_id'),
505
- role: lit(item.role, 'run.role', [
506
- 'baseline',
507
- 'treatment'
508
- ]),
509
- counterpart_run_id: id(item.counterpart_run_id, 'run.counterpart')
510
- };
149
+ exact(item, 'run.pairing', ['pair_id', 'role', 'counterpart_run_id']);
150
+ pairing = { pair_id: id(item.pair_id, 'run.pair_id'), role: lit(item.role, 'run.role', ['baseline', 'treatment']), counterpart_run_id: id(item.counterpart_run_id, 'run.counterpart') };
511
151
  }
512
- const evidence_kind = lit(root.evidence_kind, 'run.evidence_kind', [
513
- 'synthetic_fixture',
514
- 'observed_external'
515
- ]);
516
- const status = lit(root.status, 'run.status', [
517
- 'running',
518
- 'succeeded',
519
- 'failed',
520
- 'blocked'
521
- ]);
152
+ const evidence_kind = lit(root.evidence_kind, 'run.evidence_kind', ['synthetic_fixture', 'observed_external']);
153
+ const status = lit(root.status, 'run.status', ['running', 'succeeded', 'failed', 'blocked']);
522
154
  const finished_at = root.finished_at === null ? null : instant(root.finished_at, 'run.finished_at');
523
- if (status === 'running' !== (finished_at === null)) throw new Error('run.finished_at must be null only while running');
524
- const out = {
525
- schema_version: lit(root.schema_version, 'run.schema_version', [
526
- 'gotry_eval_run_receipt_v0'
527
- ]),
528
- run_id: id(root.run_id, 'run.run_id'),
529
- benchmark_id: lit(root.benchmark_id, 'run.benchmark_id', BENCHMARK_IDS),
530
- case_id: id(root.case_id, 'run.case_id'),
531
- evidence_kind,
532
- pairing,
533
- status,
534
- started_at: instant(root.started_at, 'run.started_at'),
535
- finished_at,
536
- gotry_sha: commit(root.gotry_sha, 'run.gotry_sha'),
537
- model: {
538
- provider: text(model.provider, 'run.provider'),
539
- model: text(model.model, 'run.model')
540
- },
541
- controls: Object.fromEntries(controlKeys.map((key)=>[
542
- key,
543
- digest(controls[key], `run.controls.${key}`)
544
- ])),
545
- qualification: {
546
- official_result: boolean(qualification.official_result, 'run.official_result'),
547
- source_fence_passed: boolean(qualification.source_fence_passed, 'run.source_fence_passed'),
548
- integrity_passed: boolean(qualification.integrity_passed, 'run.integrity_passed'),
549
- evidence_receipts: {
550
- official_evaluator_output_sha256: optionalDigest(receipts.official_evaluator_output_sha256, 'run.evaluator_receipt'),
551
- source_fence_audit_sha256: optionalDigest(receipts.source_fence_audit_sha256, 'run.fence_receipt'),
552
- integrity_audit_sha256: optionalDigest(receipts.integrity_audit_sha256, 'run.integrity_receipt')
553
- }
554
- },
555
- experiment: {
556
- changed_variables: strings(experiment.changed_variables, 'run.changed_variables', true),
557
- candidate_sha256: digest(experiment.candidate_sha256, 'run.candidate')
558
- },
559
- native_metrics: numberMap(root.native_metrics, 'run.native_metrics'),
560
- guardrails: {
561
- hard_violation_count: integer(guard.hard_violation_count, 'run.hard'),
562
- forbidden_leakage_hits: integer(guard.forbidden_leakage_hits, 'run.leakage'),
563
- latency_ms: finite(guard.latency_ms, 'run.latency'),
564
- cost_usd: finite(guard.cost_usd, 'run.cost'),
565
- tool_calls: integer(guard.tool_calls, 'run.tools'),
566
- turns: integer(guard.turns, 'run.turns')
567
- },
568
- evidence_summary: {
569
- artifact_classification: lit(summary.artifact_classification, 'run.classification', [
570
- 'public_safe'
571
- ]),
572
- sha256: strings(summary.sha256, 'run.sha256', true).map((item, index)=>digest(item, `run.sha256[${index}]`)),
573
- counts: numberMap(summary.counts, 'run.counts', true),
574
- reason_codes: strings(summary.reason_codes, 'run.reason_codes', true),
575
- fixture_only: boolean(summary.fixture_only, 'run.fixture_only'),
576
- statement: text(summary.statement, 'run.statement')
577
- }
578
- };
579
- if (out.finished_at !== null && new Date(out.finished_at).valueOf() < new Date(out.started_at).valueOf()) throw new Error('run.finished_at must not precede started_at');
580
- if (evidence_kind === 'synthetic_fixture' && (pairing !== null || out.qualification.official_result || out.qualification.source_fence_passed || out.qualification.integrity_passed || Object.values(out.qualification.evidence_receipts).some(Boolean) || !out.evidence_summary.fixture_only)) throw new Error('synthetic fixture must be unmatched, unqualified, receipt-free, and fixture-only');
581
- if (evidence_kind === 'observed_external' && out.evidence_summary.fixture_only) throw new Error('observed_external receipt must set fixture_only=false');
155
+ if ((status === 'running') !== (finished_at === null))
156
+ throw new Error('run.finished_at must be null only while running');
157
+ const out = { schema_version: lit(root.schema_version, 'run.schema_version', ['gotry_eval_run_receipt_v0']), run_id: id(root.run_id, 'run.run_id'), benchmark_id: lit(root.benchmark_id, 'run.benchmark_id', BENCHMARK_IDS), case_id: id(root.case_id, 'run.case_id'), evidence_kind, pairing, status, started_at: instant(root.started_at, 'run.started_at'), finished_at, gotry_sha: commit(root.gotry_sha, 'run.gotry_sha'), model: { provider: text(model.provider, 'run.provider'), model: text(model.model, 'run.model') }, controls: Object.fromEntries(controlKeys.map(key => [key, digest(controls[key], `run.controls.${key}`)])), qualification: { official_result: boolean(qualification.official_result, 'run.official_result'), source_fence_passed: boolean(qualification.source_fence_passed, 'run.source_fence_passed'), integrity_passed: boolean(qualification.integrity_passed, 'run.integrity_passed'), evidence_receipts: { official_evaluator_output_sha256: optionalDigest(receipts.official_evaluator_output_sha256, 'run.evaluator_receipt'), source_fence_audit_sha256: optionalDigest(receipts.source_fence_audit_sha256, 'run.fence_receipt'), integrity_audit_sha256: optionalDigest(receipts.integrity_audit_sha256, 'run.integrity_receipt') } }, experiment: { changed_variables: strings(experiment.changed_variables, 'run.changed_variables', true), candidate_sha256: digest(experiment.candidate_sha256, 'run.candidate') }, native_metrics: numberMap(root.native_metrics, 'run.native_metrics'), guardrails: { hard_violation_count: integer(guard.hard_violation_count, 'run.hard'), forbidden_leakage_hits: integer(guard.forbidden_leakage_hits, 'run.leakage'), latency_ms: finite(guard.latency_ms, 'run.latency'), cost_usd: finite(guard.cost_usd, 'run.cost'), tool_calls: integer(guard.tool_calls, 'run.tools'), turns: integer(guard.turns, 'run.turns') }, evidence_summary: { artifact_classification: lit(summary.artifact_classification, 'run.classification', ['public_safe']), sha256: strings(summary.sha256, 'run.sha256', true).map((item, index) => digest(item, `run.sha256[${index}]`)), counts: numberMap(summary.counts, 'run.counts', true), reason_codes: strings(summary.reason_codes, 'run.reason_codes', true), fixture_only: boolean(summary.fixture_only, 'run.fixture_only'), statement: text(summary.statement, 'run.statement') } };
158
+ if (out.finished_at !== null && new Date(out.finished_at).valueOf() < new Date(out.started_at).valueOf())
159
+ throw new Error('run.finished_at must not precede started_at');
160
+ if (evidence_kind === 'synthetic_fixture' && (pairing !== null || out.qualification.official_result || out.qualification.source_fence_passed || out.qualification.integrity_passed || Object.values(out.qualification.evidence_receipts).some(Boolean) || !out.evidence_summary.fixture_only))
161
+ throw new Error('synthetic fixture must be unmatched, unqualified, receipt-free, and fixture-only');
162
+ if (evidence_kind === 'observed_external' && out.evidence_summary.fixture_only)
163
+ throw new Error('observed_external receipt must set fixture_only=false');
582
164
  assertPublicArtifactSafe(out, 'run');
583
165
  return out;
584
166
  }
585
- export function parseEvalFailureCluster(value) {
586
- const root = obj(value, 'failure');
587
- exact(root, 'failure', [
588
- 'schema_version',
589
- 'cluster_id',
590
- 'category',
591
- 'severity',
592
- 'benchmark_ids',
593
- 'case_ids',
594
- 'run_ids',
595
- 'reproduction',
596
- 'falsifiable_hypothesis',
597
- 'gotry_regression_id',
598
- 'suggested_surface',
599
- 'state'
600
- ]);
601
- const reproduction = obj(root.reproduction, 'failure.reproduction');
602
- exact(reproduction, 'failure.reproduction', [
603
- 'condition',
604
- 'minimum_repetitions',
605
- 'observed_repetitions'
606
- ]);
607
- const out = {
608
- schema_version: lit(root.schema_version, 'failure.schema_version', [
609
- 'gotry_eval_failure_cluster_v0'
610
- ]),
611
- cluster_id: id(root.cluster_id, 'failure.cluster_id'),
612
- category: lit(root.category, 'failure.category', FAILURE_CATEGORIES),
613
- severity: lit(root.severity, 'failure.severity', [
614
- 'P0',
615
- 'P1',
616
- 'P2',
617
- 'P3'
618
- ]),
619
- benchmark_ids: strings(root.benchmark_ids, 'failure.benchmark_ids').map((item)=>lit(item, 'failure.benchmark', BENCHMARK_IDS)),
620
- case_ids: strings(root.case_ids, 'failure.case_ids'),
621
- run_ids: strings(root.run_ids, 'failure.run_ids'),
622
- reproduction: {
623
- condition: text(reproduction.condition, 'failure.condition'),
624
- minimum_repetitions: integer(reproduction.minimum_repetitions, 'failure.minimum', 1),
625
- observed_repetitions: integer(reproduction.observed_repetitions, 'failure.observed')
626
- },
627
- falsifiable_hypothesis: text(root.falsifiable_hypothesis, 'failure.hypothesis'),
628
- gotry_regression_id: id(root.gotry_regression_id, 'failure.regression'),
629
- suggested_surface: lit(root.suggested_surface, 'failure.surface', [
630
- 'agent',
631
- 'tool_policy',
632
- 'deterministic',
633
- 'evaluation'
634
- ]),
635
- state: lit(root.state, 'failure.state', [
636
- 'observed',
637
- 'confirmed',
638
- 'resolved',
639
- 'rejected'
640
- ])
641
- };
642
- if (out.state === 'confirmed' && out.reproduction.observed_repetitions < out.reproduction.minimum_repetitions) throw new Error('confirmed failure lacks repetitions');
643
- assertPublicArtifactSafe(out, 'failure');
644
- return out;
645
- }
167
+ export function parseEvalFailureCluster(value) { const root = obj(value, 'failure'); exact(root, 'failure', ['schema_version', 'cluster_id', 'category', 'severity', 'benchmark_ids', 'case_ids', 'run_ids', 'reproduction', 'falsifiable_hypothesis', 'gotry_regression_id', 'suggested_surface', 'state']); const reproduction = obj(root.reproduction, 'failure.reproduction'); exact(reproduction, 'failure.reproduction', ['condition', 'minimum_repetitions', 'observed_repetitions']); const out = { schema_version: lit(root.schema_version, 'failure.schema_version', ['gotry_eval_failure_cluster_v0']), cluster_id: id(root.cluster_id, 'failure.cluster_id'), category: lit(root.category, 'failure.category', FAILURE_CATEGORIES), severity: lit(root.severity, 'failure.severity', ['P0', 'P1', 'P2', 'P3']), benchmark_ids: strings(root.benchmark_ids, 'failure.benchmark_ids').map(item => lit(item, 'failure.benchmark', BENCHMARK_IDS)), case_ids: strings(root.case_ids, 'failure.case_ids'), run_ids: strings(root.run_ids, 'failure.run_ids'), reproduction: { condition: text(reproduction.condition, 'failure.condition'), minimum_repetitions: integer(reproduction.minimum_repetitions, 'failure.minimum', 1), observed_repetitions: integer(reproduction.observed_repetitions, 'failure.observed') }, falsifiable_hypothesis: text(root.falsifiable_hypothesis, 'failure.hypothesis'), gotry_regression_id: id(root.gotry_regression_id, 'failure.regression'), suggested_surface: lit(root.suggested_surface, 'failure.surface', ['agent', 'tool_policy', 'deterministic', 'evaluation']), state: lit(root.state, 'failure.state', ['observed', 'confirmed', 'resolved', 'rejected']) }; if (out.state === 'confirmed' && out.reproduction.observed_repetitions < out.reproduction.minimum_repetitions)
168
+ throw new Error('confirmed failure lacks repetitions'); assertPublicArtifactSafe(out, 'failure'); return out; }
646
169
  export function parseEvaluationFoundation(value) {
647
170
  const root = obj(value, 'foundation');
648
- exact(root, 'foundation', [
649
- 'registry',
650
- 'cases',
651
- 'run_receipts',
652
- 'failure_clusters'
653
- ]);
171
+ exact(root, 'foundation', ['registry', 'cases', 'run_receipts', 'failure_clusters']);
654
172
  const registry = parseBenchmarkRegistry(root.registry);
655
- if (!Array.isArray(root.cases) || !Array.isArray(root.run_receipts) || !Array.isArray(root.failure_clusters)) throw new Error('foundation collections must be arrays');
173
+ if (!Array.isArray(root.cases) || !Array.isArray(root.run_receipts) || !Array.isArray(root.failure_clusters))
174
+ throw new Error('foundation collections must be arrays');
656
175
  const cases = root.cases.map(parseEvalCase), runs = root.run_receipts.map(parseEvalRunReceipt), failures = root.failure_clusters.map(parseEvalFailureCluster);
657
- const unique = (values, label)=>{
658
- if (new Set(values).size !== values.length) throw new Error(`${label} ids must be unique`);
659
- };
660
- unique(cases.map((item)=>item.case_id), 'case');
661
- unique(runs.map((item)=>item.run_id), 'run');
662
- unique(failures.map((item)=>item.cluster_id), 'failure');
663
- const caseById = new Map(cases.map((item)=>[
664
- item.case_id,
665
- item
666
- ])), runById = new Map(runs.map((item)=>[
667
- item.run_id,
668
- item
669
- ])), registryById = new Map(registry.map((item)=>[
670
- item.benchmark_id,
671
- item
672
- ]));
673
- for (const run of runs){
176
+ const unique = (values, label) => { if (new Set(values).size !== values.length)
177
+ throw new Error(`${label} ids must be unique`); };
178
+ unique(cases.map(item => item.case_id), 'case');
179
+ unique(runs.map(item => item.run_id), 'run');
180
+ unique(failures.map(item => item.cluster_id), 'failure');
181
+ const caseById = new Map(cases.map(item => [item.case_id, item])), runById = new Map(runs.map(item => [item.run_id, item])), registryById = new Map(registry.map(item => [item.benchmark_id, item]));
182
+ for (const run of runs) {
674
183
  const linked = caseById.get(run.case_id), entry = registryById.get(run.benchmark_id);
675
- if (!linked || linked.benchmark_id !== run.benchmark_id || !entry) throw new Error(`run ${run.run_id} must close to case and registry`);
676
- const metricKeys = Object.keys(run.native_metrics).sort(), declared = entry.native_metrics.values.map((metric)=>metric.receipt_key).sort();
677
- if (JSON.stringify(metricKeys) !== JSON.stringify(declared)) throw new Error(`run ${run.run_id} metric keys must equal registry receipt keys`);
184
+ if (!linked || linked.benchmark_id !== run.benchmark_id || !entry)
185
+ throw new Error(`run ${run.run_id} must close to case and registry`);
186
+ const metricKeys = Object.keys(run.native_metrics).sort(), declared = entry.native_metrics.values.map(metric => metric.receipt_key).sort();
187
+ if (JSON.stringify(metricKeys) !== JSON.stringify(declared))
188
+ throw new Error(`run ${run.run_id} metric keys must equal registry receipt keys`);
678
189
  }
679
- for (const failure of failures){
680
- const linkedCases = failure.case_ids.map((key)=>caseById.get(key)), linkedRuns = failure.run_ids.map((key)=>runById.get(key));
681
- if (linkedCases.some((item)=>!item) || linkedRuns.some((item)=>!item)) throw new Error(`failure ${failure.cluster_id} has unclosed links`);
682
- const actual = new Set([
683
- ...linkedCases,
684
- ...linkedRuns
685
- ].map((item)=>item.benchmark_id));
686
- if (actual.size !== failure.benchmark_ids.length || failure.benchmark_ids.some((key)=>!actual.has(key))) throw new Error(`failure ${failure.cluster_id} benchmark closure must equal linked case and run benchmarks`);
190
+ for (const failure of failures) {
191
+ const linkedCases = failure.case_ids.map(key => caseById.get(key)), linkedRuns = failure.run_ids.map(key => runById.get(key));
192
+ if (linkedCases.some(item => !item) || linkedRuns.some(item => !item))
193
+ throw new Error(`failure ${failure.cluster_id} has unclosed links`);
194
+ const actual = new Set([...linkedCases, ...linkedRuns].map(item => item.benchmark_id));
195
+ if (actual.size !== failure.benchmark_ids.length || failure.benchmark_ids.some(key => !actual.has(key)))
196
+ throw new Error(`failure ${failure.cluster_id} benchmark closure must equal linked case and run benchmarks`);
687
197
  }
688
- return {
689
- registry,
690
- cases,
691
- run_receipts: runs,
692
- failure_clusters: failures
693
- };
694
- }
695
- function runBinding(run) {
696
- const { evidence_receipts: _receipts, ...qualification } = run.qualification;
697
- return evaluationFingerprint({
698
- ...run,
699
- qualification
700
- });
701
- }
702
- function resolveArtifact(resolver, run, kind, evalCase) {
703
- const expected = kind === 'official_evaluator' ? run.qualification.evidence_receipts.official_evaluator_output_sha256 : kind === 'source_fence_audit' ? run.qualification.evidence_receipts.source_fence_audit_sha256 : run.qualification.evidence_receipts.integrity_audit_sha256;
704
- if (!expected) throw new Error(`pair ${run.run_id} lacks qualification evidence receipts`);
705
- const value = obj(resolver.resolve(expected), `${kind} artifact`);
706
- const common = [
707
- 'schema_version',
708
- 'artifact_kind',
709
- 'run_id',
710
- 'benchmark_id',
711
- 'case_id',
712
- 'run_binding_sha256'
713
- ];
714
- const specific = kind === 'official_evaluator' ? [
715
- 'evaluator_sha256',
716
- 'native_metrics_sha256',
717
- 'native_metrics',
718
- 'official_result'
719
- ] : kind === 'source_fence_audit' ? [
720
- 'source_fence_sha256',
721
- 'input_digest_sha256',
722
- 'source_fence_passed',
723
- 'forbidden_field_hits'
724
- ] : [
725
- 'integrity_sha256',
726
- 'candidate_sha256',
727
- 'integrity_passed'
728
- ];
729
- exact(value, `${kind} artifact`, [
730
- ...common,
731
- ...specific
732
- ]);
733
- lit(value.schema_version, `${kind}.schema_version`, [
734
- 'gotry_eval_evidence_artifact_v0'
735
- ]);
736
- if (value.artifact_kind !== kind) throw new Error(`${kind} artifact kind mismatch`);
737
- if (typeof value.run_id !== 'string' || typeof value.benchmark_id !== 'string' || typeof value.case_id !== 'string' || typeof value.run_binding_sha256 !== 'string') throw new Error(`${kind} artifact schema types mismatch`);
738
- assertPublicArtifactSafe(value, ` artifact`);
739
- if (kind === 'official_evaluator' && (typeof value.official_result !== 'boolean' || typeof value.native_metrics_sha256 !== 'string')) throw new Error(`${kind} artifact schema types mismatch`);
740
- if (kind === 'source_fence_audit' && (typeof value.source_fence_passed !== 'boolean' || typeof value.forbidden_field_hits !== 'number' || !Number.isInteger(value.forbidden_field_hits))) throw new Error(`${kind} artifact schema types mismatch`);
741
- if (kind === 'integrity_audit' && typeof value.integrity_passed !== 'boolean') throw new Error(`${kind} artifact schema types mismatch`);
742
- if (evaluationFingerprint(value) !== expected) throw new Error(`pair ${run.run_id} ${kind} artifact fingerprint mismatch`);
743
- if (value.run_id !== run.run_id || value.benchmark_id !== run.benchmark_id || value.case_id !== run.case_id || value.run_binding_sha256 !== runBinding(run)) throw new Error(`pair ${run.run_id} ${kind} artifact binding mismatch`);
744
- if (kind === 'official_evaluator' && (value.evaluator_sha256 !== run.controls.official_evaluator_sha256 || value.native_metrics_sha256 !== evaluationFingerprint(run.native_metrics) || value.official_result !== true || JSON.stringify(value.native_metrics) !== JSON.stringify(run.native_metrics))) throw new Error(`pair ${run.run_id} evaluator artifact mismatch`);
745
- if (kind === 'source_fence_audit' && (value.source_fence_sha256 !== run.controls.source_fence_sha256 || value.source_fence_passed !== true || value.forbidden_field_hits !== 0 || evalCase !== undefined && value.input_digest_sha256 !== evalCase.input_ref.digest_sha256)) throw new Error(`pair ${run.run_id} source-fence artifact mismatch`);
746
- if (kind === 'integrity_audit' && (value.integrity_sha256 !== run.controls.integrity_sha256 || value.candidate_sha256 !== run.experiment.candidate_sha256 || value.integrity_passed !== true)) throw new Error(`pair ${run.run_id} integrity artifact mismatch`);
747
- return value;
748
- }
198
+ return { registry, cases, run_receipts: runs, failure_clusters: failures };
199
+ }
200
+ function runBinding(run) { const { evidence_receipts: _receipts, ...qualification } = run.qualification; return evaluationFingerprint({ ...run, qualification }); }
201
+ function resolveArtifact(resolver, run, kind, evalCase) { const expected = kind === 'official_evaluator' ? run.qualification.evidence_receipts.official_evaluator_output_sha256 : kind === 'source_fence_audit' ? run.qualification.evidence_receipts.source_fence_audit_sha256 : run.qualification.evidence_receipts.integrity_audit_sha256; if (!expected)
202
+ throw new Error(`pair ${run.run_id} lacks qualification evidence receipts`); const value = obj(resolver.resolve(expected), `${kind} artifact`); const common = ['schema_version', 'artifact_kind', 'run_id', 'benchmark_id', 'case_id', 'run_binding_sha256']; const specific = kind === 'official_evaluator' ? ['evaluator_sha256', 'native_metrics_sha256', 'native_metrics', 'official_result'] : kind === 'source_fence_audit' ? ['source_fence_sha256', 'input_digest_sha256', 'source_fence_passed', 'forbidden_field_hits'] : ['integrity_sha256', 'candidate_sha256', 'integrity_passed']; exact(value, `${kind} artifact`, [...common, ...specific]); lit(value.schema_version, `${kind}.schema_version`, ['gotry_eval_evidence_artifact_v0']); if (value.artifact_kind !== kind)
203
+ throw new Error(`${kind} artifact kind mismatch`); if (typeof value.run_id !== 'string' || typeof value.benchmark_id !== 'string' || typeof value.case_id !== 'string' || typeof value.run_binding_sha256 !== 'string')
204
+ throw new Error(`${kind} artifact schema types mismatch`); assertPublicArtifactSafe(value, ` artifact`); if (kind === 'official_evaluator' && (typeof value.official_result !== 'boolean' || typeof value.native_metrics_sha256 !== 'string'))
205
+ throw new Error(`${kind} artifact schema types mismatch`); if (kind === 'source_fence_audit' && (typeof value.source_fence_passed !== 'boolean' || typeof value.forbidden_field_hits !== 'number' || !Number.isInteger(value.forbidden_field_hits)))
206
+ throw new Error(`${kind} artifact schema types mismatch`); if (kind === 'integrity_audit' && typeof value.integrity_passed !== 'boolean')
207
+ throw new Error(`${kind} artifact schema types mismatch`); if (evaluationFingerprint(value) !== expected)
208
+ throw new Error(`pair ${run.run_id} ${kind} artifact fingerprint mismatch`); if (value.run_id !== run.run_id || value.benchmark_id !== run.benchmark_id || value.case_id !== run.case_id || value.run_binding_sha256 !== runBinding(run))
209
+ throw new Error(`pair ${run.run_id} ${kind} artifact binding mismatch`); if (kind === 'official_evaluator' && (value.evaluator_sha256 !== run.controls.official_evaluator_sha256 || value.native_metrics_sha256 !== evaluationFingerprint(run.native_metrics) || value.official_result !== true || JSON.stringify(value.native_metrics) !== JSON.stringify(run.native_metrics)))
210
+ throw new Error(`pair ${run.run_id} evaluator artifact mismatch`); if (kind === 'source_fence_audit' && (value.source_fence_sha256 !== run.controls.source_fence_sha256 || value.source_fence_passed !== true || value.forbidden_field_hits !== 0 || (evalCase !== undefined && value.input_digest_sha256 !== evalCase.input_ref.digest_sha256)))
211
+ throw new Error(`pair ${run.run_id} source-fence artifact mismatch`); if (kind === 'integrity_audit' && (value.integrity_sha256 !== run.controls.integrity_sha256 || value.candidate_sha256 !== run.experiment.candidate_sha256 || value.integrity_passed !== true))
212
+ throw new Error(`pair ${run.run_id} integrity artifact mismatch`); return value; }
749
213
  export function deriveMatchedPairs(foundation, resolver) {
750
214
  const groups = new Map();
751
- for (const run of foundation.run_receipts)if (run.pairing) groups.set(run.pairing.pair_id, [
752
- ...groups.get(run.pairing.pair_id) ?? [],
753
- run
754
- ]);
755
- const registry = new Map(foundation.registry.map((item)=>[
756
- item.benchmark_id,
757
- item
758
- ])), cases = new Map(foundation.cases.map((item)=>[
759
- item.case_id,
760
- item
761
- ]));
762
- return [
763
- ...groups.entries()
764
- ].sort(([a], [b])=>a.localeCompare(b)).map(([pairId, members])=>{
765
- if (members.length !== 2) throw new Error(`pair ${pairId} must contain exactly two receipts`);
766
- const baseline = members.find((item)=>item.pairing?.role === 'baseline'), treatment = members.find((item)=>item.pairing?.role === 'treatment');
767
- if (!baseline || !treatment) throw new Error(`pair ${pairId} must contain opposite baseline and treatment roles`);
768
- if (baseline.pairing.counterpart_run_id !== treatment.run_id || treatment.pairing.counterpart_run_id !== baseline.run_id) throw new Error(`pair ${pairId} counterpart references must be reciprocal`);
769
- if (baseline.benchmark_id !== treatment.benchmark_id || baseline.case_id !== treatment.case_id) throw new Error(`pair ${pairId} must share benchmark and case`);
215
+ for (const run of foundation.run_receipts)
216
+ if (run.pairing)
217
+ groups.set(run.pairing.pair_id, [...(groups.get(run.pairing.pair_id) ?? []), run]);
218
+ const registry = new Map(foundation.registry.map(item => [item.benchmark_id, item])), cases = new Map(foundation.cases.map(item => [item.case_id, item]));
219
+ return [...groups.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([pairId, members]) => {
220
+ if (members.length !== 2)
221
+ throw new Error(`pair ${pairId} must contain exactly two receipts`);
222
+ const baseline = members.find(item => item.pairing?.role === 'baseline'), treatment = members.find(item => item.pairing?.role === 'treatment');
223
+ if (!baseline || !treatment)
224
+ throw new Error(`pair ${pairId} must contain opposite baseline and treatment roles`);
225
+ if (baseline.pairing.counterpart_run_id !== treatment.run_id || treatment.pairing.counterpart_run_id !== baseline.run_id)
226
+ throw new Error(`pair ${pairId} counterpart references must be reciprocal`);
227
+ if (baseline.benchmark_id !== treatment.benchmark_id || baseline.case_id !== treatment.case_id)
228
+ throw new Error(`pair ${pairId} must share benchmark and case`);
770
229
  const entry = registry.get(baseline.benchmark_id), evalCase = cases.get(baseline.case_id);
771
- if (entry.countability_default !== 'countable_if_qualified') throw new Error(`pair ${pairId} benchmark is diagnostic only`);
772
- if (evalCase.input_ref.kind !== 'external_opaque_reference') throw new Error(`pair ${pairId} requires an external opaque case reference`);
773
- if ([
774
- baseline,
775
- treatment
776
- ].some((run)=>run.evidence_kind !== 'observed_external')) throw new Error(`pair ${pairId} requires observed_external evidence`);
777
- if (baseline.status !== 'succeeded' || treatment.status !== 'succeeded') throw new Error(`pair ${pairId} requires succeeded terminal receipts`);
778
- if (baseline.model.provider !== treatment.model.provider || baseline.model.model !== treatment.model.model) throw new Error(`pair ${pairId} model identity must match`);
779
- for (const key of [
780
- 'case_set_sha256',
781
- 'protocol_control_sha256',
782
- 'model_parameters_sha256',
783
- 'scorer_sha256',
784
- 'tool_snapshot_sha256',
785
- 'source_fence_sha256',
786
- 'integrity_sha256',
787
- 'official_evaluator_sha256'
788
- ])if (baseline.controls[key] !== treatment.controls[key]) throw new Error(`pair ${pairId} control mismatch: ${key}`);
789
- if (baseline.controls.scorer_sha256 !== evaluationFingerprint(evalCase.scorer_revision)) throw new Error(`pair ${pairId} scorer fingerprint does not close`);
790
- if (baseline.controls.case_set_sha256 !== evaluationFingerprint(foundation.cases.filter((item)=>item.benchmark_id === baseline.benchmark_id))) throw new Error(`pair ${pairId} case-set fingerprint does not close`);
791
- if (baseline.controls.source_fence_sha256 !== evaluationFingerprint(entry.source_fence)) throw new Error(`pair ${pairId} source-fence fingerprint does not close`);
792
- if (baseline.controls.official_evaluator_sha256 !== evaluationFingerprint(entry.provenance.evaluator)) throw new Error(`pair ${pairId} evaluator fingerprint does not close`);
793
- if (baseline.experiment.changed_variables.length !== 0 || JSON.stringify(treatment.experiment.changed_variables) !== JSON.stringify([
794
- 'gotry_sha'
795
- ])) throw new Error(`pair ${pairId} treatment variable must be exactly gotry_sha`);
796
- if (baseline.gotry_sha === treatment.gotry_sha) throw new Error(`pair ${pairId} baseline and treatment gotry_sha must differ`);
797
- if (baseline.guardrails.hard_violation_count !== 0 || treatment.guardrails.hard_violation_count !== 0 || baseline.guardrails.forbidden_leakage_hits !== 0 || treatment.guardrails.forbidden_leakage_hits !== 0) throw new Error(`pair ${pairId} hard violation or leakage`);
798
- for (const run of [
799
- baseline,
800
- treatment
801
- ])if (run.experiment.candidate_sha256 !== evaluationFingerprint({
802
- treatment_variable: 'gotry_sha',
803
- gotry_sha: run.gotry_sha
804
- })) throw new Error(`pair ${pairId} candidate fingerprint does not close to gotry_sha`);
805
- for (const run of [
806
- baseline,
807
- treatment
808
- ]){
230
+ if (entry.countability_default !== 'countable_if_qualified')
231
+ throw new Error(`pair ${pairId} benchmark is diagnostic only`);
232
+ if (evalCase.input_ref.kind !== 'external_opaque_reference')
233
+ throw new Error(`pair ${pairId} requires an external opaque case reference`);
234
+ if ([baseline, treatment].some(run => run.evidence_kind !== 'observed_external'))
235
+ throw new Error(`pair ${pairId} requires observed_external evidence`);
236
+ if (baseline.status !== 'succeeded' || treatment.status !== 'succeeded')
237
+ throw new Error(`pair ${pairId} requires succeeded terminal receipts`);
238
+ if (baseline.model.provider !== treatment.model.provider || baseline.model.model !== treatment.model.model)
239
+ throw new Error(`pair ${pairId} model identity must match`);
240
+ for (const key of ['case_set_sha256', 'protocol_control_sha256', 'model_parameters_sha256', 'scorer_sha256', 'tool_snapshot_sha256', 'source_fence_sha256', 'integrity_sha256', 'official_evaluator_sha256'])
241
+ if (baseline.controls[key] !== treatment.controls[key])
242
+ throw new Error(`pair ${pairId} control mismatch: ${key}`);
243
+ if (baseline.controls.scorer_sha256 !== evaluationFingerprint(evalCase.scorer_revision))
244
+ throw new Error(`pair ${pairId} scorer fingerprint does not close`);
245
+ if (baseline.controls.case_set_sha256 !== evaluationFingerprint(foundation.cases.filter(item => item.benchmark_id === baseline.benchmark_id)))
246
+ throw new Error(`pair ${pairId} case-set fingerprint does not close`);
247
+ if (baseline.controls.source_fence_sha256 !== evaluationFingerprint(entry.source_fence))
248
+ throw new Error(`pair ${pairId} source-fence fingerprint does not close`);
249
+ if (baseline.controls.official_evaluator_sha256 !== evaluationFingerprint(entry.provenance.evaluator))
250
+ throw new Error(`pair ${pairId} evaluator fingerprint does not close`);
251
+ if (baseline.experiment.changed_variables.length !== 0 || JSON.stringify(treatment.experiment.changed_variables) !== JSON.stringify(['gotry_sha']))
252
+ throw new Error(`pair ${pairId} treatment variable must be exactly gotry_sha`);
253
+ if (baseline.gotry_sha === treatment.gotry_sha)
254
+ throw new Error(`pair ${pairId} baseline and treatment gotry_sha must differ`);
255
+ if (baseline.guardrails.hard_violation_count !== 0 || treatment.guardrails.hard_violation_count !== 0 || baseline.guardrails.forbidden_leakage_hits !== 0 || treatment.guardrails.forbidden_leakage_hits !== 0)
256
+ throw new Error(`pair ${pairId} hard violation or leakage`);
257
+ for (const run of [baseline, treatment])
258
+ if (run.experiment.candidate_sha256 !== evaluationFingerprint({ treatment_variable: 'gotry_sha', gotry_sha: run.gotry_sha }))
259
+ throw new Error(`pair ${pairId} candidate fingerprint does not close to gotry_sha`);
260
+ for (const run of [baseline, treatment]) {
809
261
  const receipts = Object.values(run.qualification.evidence_receipts);
810
- if (!run.qualification.official_result || !run.qualification.source_fence_passed || !run.qualification.integrity_passed || receipts.some((item)=>item === null)) throw new Error(`pair ${pairId} lacks qualification evidence receipts`);
262
+ if (!run.qualification.official_result || !run.qualification.source_fence_passed || !run.qualification.integrity_passed || receipts.some(item => item === null))
263
+ throw new Error(`pair ${pairId} lacks qualification evidence receipts`);
811
264
  const evaluator = resolveArtifact(resolver, run, 'official_evaluator'), fence = resolveArtifact(resolver, run, 'source_fence_audit', evalCase), integrity = resolveArtifact(resolver, run, 'integrity_audit');
812
265
  }
813
- return {
814
- schema_version: 'gotry_eval_matched_pair_derived_v0',
815
- pair_id: pairId,
816
- benchmark_id: baseline.benchmark_id,
817
- case_id: baseline.case_id,
818
- baseline_run_id: baseline.run_id,
819
- treatment_run_id: treatment.run_id,
820
- treatment_variable: 'gotry_sha',
821
- matched_pair_countable: true
822
- };
266
+ return { schema_version: 'gotry_eval_matched_pair_derived_v0', pair_id: pairId, benchmark_id: baseline.benchmark_id, case_id: baseline.case_id, baseline_run_id: baseline.run_id, treatment_run_id: treatment.run_id, treatment_variable: 'gotry_sha', matched_pair_countable: true };
823
267
  });
824
268
  }
825
- export function parseMutationVectors(value) {
826
- if (!Array.isArray(value)) throw new Error('mutation vectors must be an array');
827
- return value.map((raw, index)=>{
828
- const root = obj(raw, `mutation[${index}]`), kind = lit(root.value_kind, 'mutation.value_kind', [
829
- 'literal',
830
- 'synthetic_absolute_path',
831
- 'nan',
832
- 'duplicate_treatment_receipt'
833
- ]), keys = kind === 'literal' ? [
834
- 'id',
835
- 'foundation_kind',
836
- 'target',
837
- 'operation',
838
- 'path',
839
- 'value_kind',
840
- 'value',
841
- 'expected_error'
842
- ] : [
843
- 'id',
844
- 'foundation_kind',
845
- 'target',
846
- 'operation',
847
- 'path',
848
- 'value_kind',
849
- 'expected_error'
850
- ];
851
- exact(root, `mutation[${index}]`, keys);
852
- return {
853
- id: id(root.id, 'mutation.id'),
854
- foundation_kind: lit(root.foundation_kind, 'mutation.foundation_kind', [
855
- 'diagnostic_repository',
856
- 'countable_test_only'
857
- ]),
858
- target: lit(root.target, 'mutation.target', [
859
- 'registry',
860
- 'case',
861
- 'run',
862
- 'failure',
863
- 'foundation'
864
- ]),
865
- operation: lit(root.operation, 'mutation.operation', [
866
- 'replace',
867
- 'remove',
868
- 'append'
869
- ]),
870
- path: text(root.path, 'mutation.path'),
871
- value_kind: kind,
872
- ...kind === 'literal' ? {
873
- value: root.value
874
- } : {},
875
- expected_error: text(root.expected_error, 'mutation.expected_error')
876
- };
877
- });
878
- }
879
- export function applyMutationVector(foundation, vector) {
880
- const cloned = JSON.parse(JSON.stringify(foundation));
881
- const selected = vector.target === 'foundation' ? cloned : vector.target === 'registry' ? cloned.registry : vector.target === 'case' ? cloned.cases[0] : vector.target === 'run' ? cloned.run_receipts[0] : cloned.failure_clusters[0];
882
- let value = vector.value;
883
- if (vector.value_kind === 'synthetic_absolute_path') value = [
884
- '',
885
- 'Users',
886
- 'fixture',
887
- 'private.json'
888
- ].join('/');
889
- if (vector.value_kind === 'nan') value = Number.NaN;
890
- if (vector.value_kind === 'duplicate_treatment_receipt') {
891
- const run = structuredClone(cloned.run_receipts[1]);
892
- run.run_id = 'run:trek:treatment-duplicate';
893
- value = run;
894
- }
895
- const parts = vector.path.split('.').filter(Boolean);
896
- let parent = selected;
897
- for (const part of parts.slice(0, -1))parent = parent[part];
898
- const leaf = parts.at(-1);
899
- if (vector.operation === 'remove') Array.isArray(parent) ? parent.splice(Number(leaf), 1) : delete parent[leaf];
900
- else if (vector.operation === 'append') parent[leaf].push(value);
901
- else parent[leaf] = value;
902
- return selected;
903
- }
904
-
905
-
906
- //# sourceURL=ts/src/evaluation-contracts.ts
269
+ export function parseMutationVectors(value) { if (!Array.isArray(value))
270
+ throw new Error('mutation vectors must be an array'); return value.map((raw, index) => { const root = obj(raw, `mutation[${index}]`), kind = lit(root.value_kind, 'mutation.value_kind', ['literal', 'synthetic_absolute_path', 'nan', 'duplicate_treatment_receipt']), keys = kind === 'literal' ? ['id', 'foundation_kind', 'target', 'operation', 'path', 'value_kind', 'value', 'expected_error'] : ['id', 'foundation_kind', 'target', 'operation', 'path', 'value_kind', 'expected_error']; exact(root, `mutation[${index}]`, keys); return { id: id(root.id, 'mutation.id'), foundation_kind: lit(root.foundation_kind, 'mutation.foundation_kind', ['diagnostic_repository', 'countable_test_only']), target: lit(root.target, 'mutation.target', ['registry', 'case', 'run', 'failure', 'foundation']), operation: lit(root.operation, 'mutation.operation', ['replace', 'remove', 'append']), path: text(root.path, 'mutation.path'), value_kind: kind, ...(kind === 'literal' ? { value: root.value } : {}), expected_error: text(root.expected_error, 'mutation.expected_error') }; }); }
271
+ export function applyMutationVector(foundation, vector) { const cloned = JSON.parse(JSON.stringify(foundation)); const selected = vector.target === 'foundation' ? cloned : vector.target === 'registry' ? cloned.registry : vector.target === 'case' ? cloned.cases[0] : vector.target === 'run' ? cloned.run_receipts[0] : cloned.failure_clusters[0]; let value = vector.value; if (vector.value_kind === 'synthetic_absolute_path')
272
+ value = ['', 'Users', 'fixture', 'private.json'].join('/'); if (vector.value_kind === 'nan')
273
+ value = Number.NaN; if (vector.value_kind === 'duplicate_treatment_receipt') {
274
+ const run = structuredClone(cloned.run_receipts[1]);
275
+ run.run_id = 'run:trek:treatment-duplicate';
276
+ value = run;
277
+ } const parts = vector.path.split('.').filter(Boolean); let parent = selected; for (const part of parts.slice(0, -1))
278
+ parent = parent[part]; const leaf = parts.at(-1); if (vector.operation === 'remove')
279
+ Array.isArray(parent) ? parent.splice(Number(leaf), 1) : delete parent[leaf];
280
+ else if (vector.operation === 'append')
281
+ parent[leaf].push(value);
282
+ else
283
+ parent[leaf] = value; return selected; }