@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,93 +1,475 @@
1
- import { mkdirSync, writeFileSync, readdirSync, readFileSync } from 'node:fs';
1
+ /**
2
+ * Rebuild the offline sf-01..08 evidence summary from one coherent batch.
3
+ *
4
+ * The default root is retained for humans, while tests and proofs must pass
5
+ * --evidence-root explicitly so no shared founder state is read or written.
6
+ */
7
+ import { mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
2
8
  import { homedir } from 'node:os';
3
- import { join } from 'node:path';
9
+ import { dirname, join, resolve } from 'node:path';
10
+ import { fileURLToPath } from 'node:url';
4
11
  import { SESSION_FIELD_ACCURACY_THRESHOLD } from '../capabilities/session/benchmark.js';
5
- const evidenceRoot = join(homedir(), '.gotry', 'evidence', 'session');
6
- function main() {
7
- const out = {
8
- started_at: new Date().toISOString(),
9
- total: 0,
10
- threshold: SESSION_FIELD_ACCURACY_THRESHOLD,
11
- accuracy_pass: 0,
12
- accuracy_eligible: 0,
13
- comparable: 0,
14
- hit: 0,
15
- challenge: 0,
16
- live_under_15s: 0,
17
- sources: {
18
- manual_golden_query_ids: [],
19
- flyai_query_ids: []
20
- },
21
- records: []
12
+ export const EXPECTED_QUERY_IDS = Array.from({ length: 8 }, (_, index) => `sf-${(index + 1).toString().padStart(2, '0')}`);
13
+ function isObject(value) {
14
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
15
+ }
16
+ function stringValue(value) {
17
+ return typeof value === 'string' && value.length > 0 ? value : null;
18
+ }
19
+ function nestedString(value, ...keys) {
20
+ let current = value;
21
+ for (const key of keys) {
22
+ if (!isObject(current))
23
+ return null;
24
+ current = current[key];
25
+ }
26
+ return stringValue(current);
27
+ }
28
+ function firstString(record, paths) {
29
+ for (const path of paths) {
30
+ const value = path.length === 1 ? record[path[0]] : nestedString(record, ...path);
31
+ const result = stringValue(value);
32
+ if (result !== null)
33
+ return result;
34
+ }
35
+ return null;
36
+ }
37
+ function parseTime(value) {
38
+ if (value === null)
39
+ return Number.NEGATIVE_INFINITY;
40
+ const parsed = Date.parse(value);
41
+ return Number.isFinite(parsed) ? parsed : Number.NEGATIVE_INFINITY;
42
+ }
43
+ function canonicalFilename(file) {
44
+ // sf-live-benchmark uses runStartedAt.replace(/[:.]/g, '-') for every
45
+ // evidence filename. Only that exact ISO-with-safe-time-punctuation form
46
+ // is a legacy batch identity; arbitrary filenames remain unknown.
47
+ const match = /^(\d{4}-\d{2}-\d{2}T\d{2})-(\d{2})-(\d{2})-(\d{3})Z\.json$/.exec(file);
48
+ if (match === null)
49
+ return null;
50
+ const captureAt = `${match[1]}:${match[2]}:${match[3]}.${match[4]}Z`;
51
+ const chronologyMs = parseTime(captureAt);
52
+ if (!Number.isFinite(chronologyMs))
53
+ return null;
54
+ return { stem: file.slice(0, -'.json'.length), captureAt, chronologyMs };
55
+ }
56
+ function captureAt(record) {
57
+ return firstString(record, [
58
+ ['evidence_captured_at'],
59
+ ['captured_at'],
60
+ ['capture_time'],
61
+ ['evidence_capture_time'],
62
+ ['official_captured_at'],
63
+ ['evidence', 'captured_at'],
64
+ ]);
65
+ }
66
+ function startedAt(record) {
67
+ return firstString(record, [
68
+ ['started_at'],
69
+ ['evidence', 'started_at'],
70
+ ]);
71
+ }
72
+ function sessionFetchedAt(record) {
73
+ return nestedString(record.session, 'fetched_at');
74
+ }
75
+ function explicitBatch(record) {
76
+ const values = [
77
+ stringValue(record.batch_id),
78
+ stringValue(record.batchId),
79
+ stringValue(record.run_id),
80
+ stringValue(record.runId),
81
+ nestedString(record.batch, 'id'),
82
+ nestedString(record.batch, 'batch_id'),
83
+ ].filter((value) => value !== null);
84
+ const unique = Array.from(new Set(values));
85
+ const capture = firstString(record, [
86
+ ['batch_captured_at'],
87
+ ['batch_capture_at'],
88
+ ['batch_started_at'],
89
+ ['run_started_at'],
90
+ ['batch', 'captured_at'],
91
+ ['batch', 'started_at'],
92
+ ]);
93
+ const validCapture = capture !== null && Number.isFinite(parseTime(capture)) ? capture : null;
94
+ return {
95
+ id: unique[0] ?? null,
96
+ captureAt: validCapture,
97
+ conflict: unique.length > 1 ? `conflicting explicit batch IDs: ${unique.join(', ')}` : null,
22
98
  };
23
- for(let i = 1; i <= 8; i += 1){
24
- const q = `sf-${i.toString().padStart(2, '0')}`;
25
- const qDir = join(evidenceRoot, q);
99
+ }
100
+ function recordChronology(record, captured, started) {
101
+ return Math.max(parseTime(captured), parseTime(started), parseTime(sessionFetchedAt(record)));
102
+ }
103
+ function relativePath(root, path) {
104
+ return path.startsWith(`${root}/`) ? path.slice(root.length + 1) : path;
105
+ }
106
+ function inventoryFiles(evidenceRoot) {
107
+ const files = [];
108
+ const invalidRecords = [];
109
+ const canonicalGroups = new Map();
110
+ for (const queryId of EXPECTED_QUERY_IDS) {
111
+ const queryDir = join(evidenceRoot, queryId);
112
+ let names;
26
113
  try {
27
- const files = readdirSync(qDir).sort();
28
- if (files.length === 0) continue;
29
- const manualGoldens = files.filter((f)=>{
30
- try {
31
- const r = JSON.parse(readFileSync(join(qDir, f), 'utf8'));
32
- return r.official_source === 'manual-golden' || r.official?.source === 'manual-golden';
33
- } catch {
34
- return false;
35
- }
36
- });
37
- const fileName = manualGoldens.length > 0 ? manualGoldens[manualGoldens.length - 1] : files[files.length - 1];
38
- const r = JSON.parse(readFileSync(join(qDir, fileName), 'utf8'));
39
- out.total += 1;
40
- if (r.doubleSource?.state === 'comparable') out.comparable += 1;
41
- if (r.session?.verdict === 'hit') out.hit += 1;
42
- if (r.session?.verdict === 'challenged') out.challenge += 1;
43
- if (r.softScore) {
44
- out.accuracy_eligible += 1;
45
- if (r.softScore.pass) out.accuracy_pass += 1;
114
+ if (!statSync(queryDir).isDirectory()) {
115
+ invalidRecords.push({ query_id: queryId, file: queryId, reason: 'query path is not a directory' });
116
+ continue;
117
+ }
118
+ names = readdirSync(queryDir).filter((name) => name.endsWith('.json')).sort();
119
+ }
120
+ catch (error) {
121
+ if (error.code === 'ENOENT')
122
+ continue;
123
+ invalidRecords.push({ query_id: queryId, file: queryId, reason: error instanceof Error ? error.message : String(error) });
124
+ continue;
125
+ }
126
+ for (const name of names) {
127
+ const entry = {
128
+ queryId,
129
+ file: relativePath(evidenceRoot, join(queryDir, name)),
130
+ absolutePath: join(queryDir, name),
131
+ canonical: canonicalFilename(name),
132
+ };
133
+ files.push(entry);
134
+ if (entry.canonical !== null) {
135
+ const group = canonicalGroups.get(entry.canonical.stem) ?? [];
136
+ group.push(entry);
137
+ canonicalGroups.set(entry.canonical.stem, group);
46
138
  }
47
- if (r.sessionLatencyMs !== null && r.session?.verdict === 'hit' && r.sessionLatencyMs < 15_000) out.live_under_15s += 1;
48
- const seg0 = r.session?.verdict === 'hit' && r.session.route_segments?.length ? r.session.route_segments[0] : null;
49
- const sessionFlight = seg0 ? {
50
- flight_no: seg0.transport_number,
51
- dep: seg0.departure_at,
52
- arr: seg0.arrival_at,
53
- price: r.session.price
54
- } : null;
55
- out.records.push({
56
- query_id: q,
57
- expected: r.expected,
58
- official_source: r.official_source,
59
- session_verdict: r.session?.verdict,
60
- session_flight: sessionFlight,
61
- soft_score: r.softScore ? {
62
- accuracy: r.softScore.accuracy,
63
- pass: r.softScore.pass,
64
- missing: r.softScore.missing,
65
- incorrect: r.softScore.incorrect
66
- } : null,
67
- session_latency_ms: r.sessionLatencyMs,
68
- session_error: r.sessionError
139
+ }
140
+ }
141
+ const candidates = Array.from(canonicalGroups.entries()).map(([stem, group]) => {
142
+ const canonical = group.find((entry) => entry.canonical !== null)?.canonical ?? null;
143
+ return {
144
+ id: stem,
145
+ identitySource: 'canonical_filename',
146
+ captureAt: canonical?.captureAt ?? null,
147
+ chronologyMs: canonical?.chronologyMs ?? Number.NEGATIVE_INFINITY,
148
+ files: group,
149
+ records: [],
150
+ conflictReason: null,
151
+ };
152
+ });
153
+ return { files, candidates, invalidRecords };
154
+ }
155
+ function readRecords(inventory) {
156
+ const records = [];
157
+ const malformedRecords = [];
158
+ const invalidRecords = [];
159
+ const unknownBatchRecords = [];
160
+ for (const file of inventory) {
161
+ let parsed;
162
+ try {
163
+ parsed = JSON.parse(readFileSync(file.absolutePath, 'utf8'));
164
+ }
165
+ catch (error) {
166
+ malformedRecords.push({ query_id: file.queryId, file: file.file, reason: error instanceof Error ? error.message : String(error) });
167
+ continue;
168
+ }
169
+ if (!isObject(parsed)) {
170
+ invalidRecords.push({ query_id: file.queryId, file: file.file, reason: 'record must be a JSON object' });
171
+ continue;
172
+ }
173
+ if (parsed.query_id !== file.queryId) {
174
+ invalidRecords.push({ query_id: file.queryId, file: file.file, reason: `query_id must be ${file.queryId}` });
175
+ continue;
176
+ }
177
+ const explicit = explicitBatch(parsed);
178
+ if (explicit.conflict !== null) {
179
+ invalidRecords.push({ query_id: file.queryId, file: file.file, reason: explicit.conflict });
180
+ continue;
181
+ }
182
+ const captured = captureAt(parsed);
183
+ const started = startedAt(parsed);
184
+ const loaded = {
185
+ ...file,
186
+ record: parsed,
187
+ explicitBatchId: explicit.id,
188
+ explicitBatchCaptureAt: explicit.captureAt,
189
+ captureAt: captured,
190
+ startedAt: started,
191
+ chronologyMs: recordChronology(parsed, captured, started),
192
+ };
193
+ records.push(loaded);
194
+ if (file.canonical === null && (explicit.id === null || explicit.captureAt === null)) {
195
+ unknownBatchRecords.push({
196
+ query_id: file.queryId,
197
+ file: file.file,
198
+ reason: explicit.id === null ? 'missing defensible batch identity' : 'explicit batch ID lacks a valid batch capture timestamp',
69
199
  });
70
- } catch {}
200
+ }
201
+ }
202
+ return { records, malformedRecords, invalidRecords, unknownBatchRecords };
203
+ }
204
+ function completeCandidates(initial, records) {
205
+ const candidates = initial.map((candidate) => {
206
+ const members = records.filter((record) => record.canonical?.stem === candidate.id);
207
+ const explicitIds = Array.from(new Set(members.map((record) => record.explicitBatchId).filter((value) => value !== null)));
208
+ const conflictReason = explicitIds.length > 1 ? `conflicting explicit batch IDs in filename batch: ${explicitIds.join(', ')}` : null;
209
+ const explicitCaptureTimes = Array.from(new Set(members.map((record) => record.explicitBatchCaptureAt).filter((value) => value !== null)));
210
+ const captureAt = candidate.captureAt;
211
+ return {
212
+ ...candidate,
213
+ id: conflictReason === null && explicitIds.length === 1 ? explicitIds[0] : candidate.id,
214
+ identitySource: conflictReason === null && explicitIds.length === 1 ? 'explicit_batch_id+canonical_filename' : candidate.identitySource,
215
+ captureAt,
216
+ records: members,
217
+ conflictReason: conflictReason ?? (explicitCaptureTimes.length > 1 ? `conflicting explicit batch capture times: ${explicitCaptureTimes.join(', ')}` : null),
218
+ };
219
+ });
220
+ const canonicalStems = new Set(initial.flatMap((candidate) => candidate.files.map((file) => file.canonical?.stem).filter((value) => value !== undefined)));
221
+ const explicitOnly = new Map();
222
+ for (const record of records) {
223
+ if (record.canonical !== null || record.explicitBatchId === null || record.explicitBatchCaptureAt === null)
224
+ continue;
225
+ const group = explicitOnly.get(record.explicitBatchId) ?? [];
226
+ group.push(record);
227
+ explicitOnly.set(record.explicitBatchId, group);
228
+ }
229
+ for (const [id, members] of explicitOnly) {
230
+ if (canonicalStems.has(members[0].canonical?.stem ?? ''))
231
+ continue;
232
+ const captureTimes = Array.from(new Set(members.map((record) => record.explicitBatchCaptureAt).filter((value) => value !== null)));
233
+ const captureAt = captureTimes[0] ?? null;
234
+ candidates.push({
235
+ id,
236
+ identitySource: 'explicit_batch_id',
237
+ captureAt,
238
+ chronologyMs: parseTime(captureAt),
239
+ files: members,
240
+ records: members,
241
+ conflictReason: captureTimes.length > 1 ? `conflicting explicit batch capture times: ${captureTimes.join(', ')}` : null,
242
+ });
71
243
  }
72
- for (const r of out.records){
73
- if (r.official_source === 'manual-golden') out.sources.manual_golden_query_ids.push(r.query_id);
74
- else out.sources.flyai_query_ids.push(r.query_id);
244
+ return candidates;
245
+ }
246
+ function chooseBatch(candidates) {
247
+ return candidates.slice().sort((left, right) => {
248
+ if (right.chronologyMs !== left.chronologyMs)
249
+ return right.chronologyMs - left.chronologyMs;
250
+ if (right.files.length !== left.files.length)
251
+ return right.files.length - left.files.length;
252
+ return right.id.localeCompare(left.id);
253
+ })[0] ?? null;
254
+ }
255
+ function selectBatchRecords(batch) {
256
+ const selected = new Map();
257
+ for (const record of batch.records) {
258
+ const previous = selected.get(record.queryId);
259
+ if (previous === undefined || record.chronologyMs > previous.chronologyMs || (record.chronologyMs === previous.chronologyMs && record.file > previous.file))
260
+ selected.set(record.queryId, record);
261
+ }
262
+ return selected;
263
+ }
264
+ function numberValue(value) {
265
+ return typeof value === 'number' && Number.isFinite(value) ? value : null;
266
+ }
267
+ function sourceFields(record) {
268
+ const requested = stringValue(record.requested_source);
269
+ const effective = stringValue(record.effective_source) ?? stringValue(record.official_source) ?? nestedString(record.official, 'source');
270
+ return {
271
+ official: stringValue(record.official_source) ?? effective,
272
+ requested,
273
+ effective,
274
+ fallback: stringValue(record.fallback_reason),
275
+ };
276
+ }
277
+ function sourceBucket(record) {
278
+ const normalizedRequested = record.requested_source?.toLowerCase();
279
+ const normalizedEffective = record.effective_source?.toLowerCase() ?? '';
280
+ if (normalizedRequested === 'static' || normalizedEffective.startsWith('static'))
281
+ return 'static_query_ids';
282
+ if (normalizedEffective === 'manual-golden' || normalizedEffective === 'manual')
283
+ return 'manual_golden_query_ids';
284
+ if (normalizedEffective === 'flyai' || normalizedEffective.startsWith('flyai'))
285
+ return 'flyai_query_ids';
286
+ return 'unknown_query_ids';
287
+ }
288
+ function toSummaryRecord(item, batchId, batchIdentitySource) {
289
+ const record = item.record;
290
+ const fields = sourceFields(record);
291
+ const session = isObject(record.session) ? record.session : null;
292
+ const soft = isObject(record.softScore) ? record.softScore : null;
293
+ const segments = session && Array.isArray(session.route_segments) ? session.route_segments : [];
294
+ const firstSegment = isObject(segments[0]) ? segments[0] : null;
295
+ const verdict = stringValue(session?.verdict) ?? stringValue(record.sessionVerdict);
296
+ const price = numberValue(session?.price);
297
+ const sessionFlight = verdict === 'hit' && firstSegment !== null
298
+ ? {
299
+ flight_no: stringValue(firstSegment.transport_number) ?? '',
300
+ dep: stringValue(firstSegment.departure_at) ?? '',
301
+ arr: stringValue(firstSegment.arrival_at) ?? '',
302
+ price: price ?? 0,
303
+ }
304
+ : null;
305
+ const sessionFetched = sessionFetchedAt(record);
306
+ const softScore = soft === null ? null : {
307
+ accuracy: numberValue(soft.accuracy) ?? 0,
308
+ pass: soft.pass === true,
309
+ missing: Array.isArray(soft.missing) ? soft.missing.filter((value) => typeof value === 'string') : [],
310
+ incorrect: Array.isArray(soft.incorrect) ? soft.incorrect.filter((value) => typeof value === 'string') : [],
311
+ };
312
+ return {
313
+ query_id: item.queryId,
314
+ expected: record.expected ?? null,
315
+ official_source: fields.official,
316
+ requested_source: fields.requested,
317
+ effective_source: fields.effective,
318
+ fallback_reason: fields.fallback,
319
+ batch_id: batchId,
320
+ batch_identity_source: batchIdentitySource,
321
+ evidence_captured_at: item.captureAt,
322
+ started_at: item.startedAt,
323
+ capture: {
324
+ evidence_captured_at: item.captureAt,
325
+ started_at: item.startedAt,
326
+ session_fetched_at: sessionFetched,
327
+ },
328
+ official_provenance: record.official_provenance ?? null,
329
+ manifest_provenance: record.manifest_provenance ?? record.manifest ?? null,
330
+ manifest_sha256: stringValue(record.manifest_sha256) ?? nestedString(record.manifest_provenance, 'sha256'),
331
+ manifest_hash: stringValue(record.manifest_hash) ?? nestedString(record.manifest_provenance, 'hash'),
332
+ batch_provenance: record.batch_provenance ?? null,
333
+ session_verdict: verdict,
334
+ session_flight: sessionFlight,
335
+ soft_score: softScore,
336
+ session_latency_ms: numberValue(record.sessionLatencyMs),
337
+ session_error: stringValue(record.sessionError),
338
+ };
339
+ }
340
+ function buildErrors(summary, conflictReason) {
341
+ const errors = [];
342
+ if (summary.selected_batch.batch_id === null)
343
+ errors.push('no defensible batch identity found');
344
+ if (conflictReason !== null)
345
+ errors.push(conflictReason);
346
+ if (summary.missing_query_ids.length > 0)
347
+ errors.push(`missing query IDs: ${summary.missing_query_ids.join(', ')}`);
348
+ if (summary.selected_malformed_records.length > 0)
349
+ errors.push(`malformed JSON: ${summary.selected_malformed_records.map((item) => item.file).join(', ')}`);
350
+ if (summary.selected_invalid_records.length > 0)
351
+ errors.push(`invalid records: ${summary.selected_invalid_records.map((item) => item.file).join(', ')}`);
352
+ return errors;
353
+ }
354
+ export function parseCliArgs(args) {
355
+ let evidenceRoot = null;
356
+ for (let index = 0; index < args.length; index += 1) {
357
+ const arg = args[index];
358
+ if (arg === '--evidence-root') {
359
+ const value = args[index + 1];
360
+ if (value === undefined || value.startsWith('--'))
361
+ throw new Error('--evidence-root requires a path');
362
+ evidenceRoot = value;
363
+ index += 1;
364
+ }
365
+ else if (arg.startsWith('--evidence-root=')) {
366
+ const value = arg.slice('--evidence-root='.length);
367
+ if (value.length === 0)
368
+ throw new Error('--evidence-root requires a path');
369
+ evidenceRoot = value;
370
+ }
371
+ else {
372
+ throw new Error(`unknown argument: ${arg}`);
373
+ }
75
374
  }
76
- const summaryPath = join(evidenceRoot, 'sf-summary', `${out.started_at.replace(/[:.]/g, '-')}.json`);
77
- mkdirSync(join(evidenceRoot, 'sf-summary'), {
78
- recursive: true
375
+ return { evidenceRoot: resolve(evidenceRoot ?? join(homedir(), '.gotry', 'evidence', 'session')) };
376
+ }
377
+ export function buildSummary(evidenceRoot, generatedAt = new Date().toISOString()) {
378
+ const root = resolve(evidenceRoot);
379
+ const inventory = inventoryFiles(root);
380
+ const loaded = readRecords(inventory.files);
381
+ const candidates = completeCandidates(inventory.candidates, loaded.records);
382
+ const batch = chooseBatch(candidates);
383
+ const selected = batch === null ? new Map() : selectBatchRecords(batch);
384
+ const selectedRecords = EXPECTED_QUERY_IDS.flatMap((queryId) => {
385
+ const item = selected.get(queryId);
386
+ return item === undefined || batch === null ? [] : [toSummaryRecord(item, batch.id, batch.identitySource)];
79
387
  });
80
- writeFileSync(summaryPath, JSON.stringify(out, null, 2));
81
- console.log(`──── sf-summary (unified,8 query) ────`);
82
- console.log(`total: ${out.total}`);
83
- console.log(`verdict=hit: ${out.hit}/${out.total}`);
84
- console.log(`soft score ≥${out.threshold * 100}%: ${out.accuracy_pass}/${out.accuracy_eligible}`);
85
- console.log(`live <15s (hit): ${out.live_under_15s}/${out.hit}`);
86
- console.log(`manual golden query_ids: ${out.sources.manual_golden_query_ids.join(', ') || '(none)'}`);
87
- console.log(`flyai query_ids: ${out.sources.flyai_query_ids.join(', ') || '(none)'}`);
88
- console.log(`summary: ${summaryPath}`);
89
- }
90
- main();
91
-
92
-
93
- //# sourceURL=ts/scripts/sf-summary.ts
388
+ const selectedFiles = new Set(batch?.files.map((file) => file.file) ?? []);
389
+ const selectedMalformed = loaded.malformedRecords.filter((item) => selectedFiles.has(item.file));
390
+ const selectedInvalid = [...inventory.invalidRecords, ...loaded.invalidRecords].filter((item) => selectedFiles.has(item.file));
391
+ const missingQueryIds = EXPECTED_QUERY_IDS.filter((queryId) => !selected.has(queryId));
392
+ const batchCaptureAt = batch?.captureAt ?? null;
393
+ const summary = {
394
+ schema_version: 'gotry_sf_summary_v2',
395
+ generated_at: generatedAt,
396
+ started_at: batchCaptureAt,
397
+ batch_capture_at: batchCaptureAt,
398
+ selected_batch: {
399
+ batch_id: batch?.id ?? null,
400
+ identity_source: batch?.identitySource ?? null,
401
+ started_at: batchCaptureAt,
402
+ record_count: selectedRecords.length,
403
+ query_ids: selectedRecords.map((record) => record.query_id),
404
+ },
405
+ status: 'fail_closed',
406
+ total: selectedRecords.length,
407
+ threshold: SESSION_FIELD_ACCURACY_THRESHOLD,
408
+ accuracy_pass: selectedRecords.filter((record) => record.soft_score?.pass === true).length,
409
+ accuracy_eligible: selectedRecords.filter((record) => record.soft_score !== null).length,
410
+ comparable: selectedRecords.filter((record) => {
411
+ const item = selected.get(record.query_id);
412
+ return item !== undefined && isObject(item.record.doubleSource) && item.record.doubleSource.state === 'comparable';
413
+ }).length,
414
+ hit: selectedRecords.filter((record) => record.session_verdict === 'hit').length,
415
+ challenge: selectedRecords.filter((record) => record.session_verdict === 'challenged').length,
416
+ live_under_15s: selectedRecords.filter((record) => record.session_verdict === 'hit' && record.session_latency_ms !== null && record.session_latency_ms < 15_000).length,
417
+ missing_query_ids: missingQueryIds,
418
+ malformed_records: loaded.malformedRecords,
419
+ invalid_records: [...inventory.invalidRecords, ...loaded.invalidRecords],
420
+ selected_malformed_records: selectedMalformed,
421
+ selected_invalid_records: selectedInvalid,
422
+ unknown_batch_records: loaded.unknownBatchRecords,
423
+ sources: {
424
+ manual_golden_query_ids: [],
425
+ static_query_ids: [],
426
+ flyai_query_ids: [],
427
+ unknown_query_ids: [],
428
+ },
429
+ records: selectedRecords,
430
+ errors: [],
431
+ };
432
+ for (const record of summary.records)
433
+ summary.sources[sourceBucket(record)].push(record.query_id);
434
+ summary.errors = buildErrors(summary, batch?.conflictReason ?? (batch === null ? null : null));
435
+ summary.status = summary.errors.length === 0 ? 'ok' : 'fail_closed';
436
+ const summaryPath = join(root, 'sf-summary', `${generatedAt.replace(/[:.]/g, '-')}.json`);
437
+ mkdirSync(dirname(summaryPath), { recursive: true });
438
+ writeFileSync(summaryPath, JSON.stringify(summary, null, 2));
439
+ return { summary, summaryPath };
440
+ }
441
+ export function main(args = process.argv.slice(2)) {
442
+ try {
443
+ const options = parseCliArgs(args);
444
+ const { summary, summaryPath } = buildSummary(options.evidenceRoot);
445
+ console.log('──── sf-summary (coherent offline batch) ────');
446
+ console.log(`status: ${summary.status}`);
447
+ console.log(`selected batch: ${summary.selected_batch.batch_id ?? '(none)'}`);
448
+ console.log(`batch capture: ${summary.batch_capture_at ?? '(unknown)'}`);
449
+ console.log(`generated at: ${summary.generated_at}`);
450
+ console.log(`total: ${summary.total}/${EXPECTED_QUERY_IDS.length}`);
451
+ console.log(`verdict=hit: ${summary.hit}/${summary.total}`);
452
+ console.log(`soft score ≥${summary.threshold * 100}%: ${summary.accuracy_pass}/${summary.accuracy_eligible}`);
453
+ console.log(`manual golden query_ids: ${summary.sources.manual_golden_query_ids.join(', ') || '(none)'}`);
454
+ console.log(`static query_ids: ${summary.sources.static_query_ids.join(', ') || '(none)'}`);
455
+ console.log(`flyai query_ids: ${summary.sources.flyai_query_ids.join(', ') || '(none)'}`);
456
+ console.log(`unknown source query_ids: ${summary.sources.unknown_query_ids.join(', ') || '(none)'}`);
457
+ if (summary.missing_query_ids.length > 0)
458
+ console.error(`missing query IDs: ${summary.missing_query_ids.join(', ')}`);
459
+ if (summary.selected_malformed_records.length > 0)
460
+ console.error(`malformed JSON: ${summary.selected_malformed_records.map((item) => item.file).join(', ')}`);
461
+ if (summary.selected_invalid_records.length > 0)
462
+ console.error(`invalid records: ${summary.selected_invalid_records.map((item) => item.file).join(', ')}`);
463
+ if (summary.malformed_records.length > summary.selected_malformed_records.length)
464
+ console.error(`diagnostic malformed JSON outside selected batch: ${summary.malformed_records.length - summary.selected_malformed_records.length}`);
465
+ console.log(`summary: ${summaryPath}`);
466
+ return summary.status === 'ok' ? 0 : 1;
467
+ }
468
+ catch (error) {
469
+ console.error(`[sf-summary] fail-closed: ${error instanceof Error ? error.message : String(error)}`);
470
+ return 1;
471
+ }
472
+ }
473
+ const invokedPath = process.argv[1] === undefined ? null : resolve(process.argv[1]);
474
+ if (invokedPath !== null && invokedPath === fileURLToPath(import.meta.url))
475
+ process.exitCode = main();
@@ -1,3 +1,9 @@
1
+ /**
2
+ * OpenFlights 骨架校验(M2 段 3,§7-1 骨架层):引擎候选集的通航性合法性校验。
3
+ * 数据源:data/openflights-skeleton.json(OpenFlights ODbL,枢纽间 168 对)。
4
+ * 语义:「查不到≠不可达」(骨架只含枢纽间),但「查到」= 候选集强证据。
5
+ * 运行:cd ts && npx tsx scripts/skeleton-check.ts HKG HKT
6
+ */
1
7
  import { readFile } from 'node:fs/promises';
2
8
  import { join } from 'node:path';
3
9
  let cached = null;
@@ -9,29 +15,25 @@ async function load() {
9
15
  }
10
16
  export async function checkConnectivity(a, b) {
11
17
  const skeleton = await load();
12
- const key = [
13
- a.toUpperCase(),
14
- b.toUpperCase()
15
- ].sort().join('-');
18
+ const key = [a.toUpperCase(), b.toUpperCase()].sort().join('-');
16
19
  const airlines = skeleton.pairs[key];
17
20
  if (airlines) {
18
- return {
19
- connected: true,
20
- airlines,
21
- evidence: `[骨架:openflights] ✅ ${a}↔${b} 直飞(${airlines.length}+ 航司:${airlines.join(',')})`
22
- };
21
+ return { connected: true, airlines, evidence: `[骨架:openflights] ✅ ${a}↔${b} 直飞(${airlines.length}+ 航司:${airlines.join(',')})` };
23
22
  }
24
- const inHub = skeleton.meta.hub_filter.includes(a.toUpperCase()) && skeleton.meta.hub_filter.includes(b.toUpperCase());
23
+ const inHub = skeleton.meta.hub_filter.includes(a.toUpperCase())
24
+ && skeleton.meta.hub_filter.includes(b.toUpperCase());
25
25
  return {
26
26
  connected: false,
27
- evidence: inHub ? `[骨架:openflights] ❌ ${a}↔${b} 枢纽间无直飞记录——引擎应将此候选降权或要求中转` : `[骨架:openflights] ○ ${a}或${b}不在枢纽集,骨架不覆盖(不作否定结论)`
27
+ evidence: inHub
28
+ ? `[骨架:openflights] ❌ ${a}↔${b} 枢纽间无直飞记录——引擎应将此候选降权或要求中转`
29
+ : `[骨架:openflights] ○ ${a}或${b}不在枢纽集,骨架不覆盖(不作否定结论)`,
28
30
  };
29
31
  }
32
+ // CLI 直跑
33
+ // 仅作为直接 CLI 入口时执行(被 import 时不触发:此前 dsh 子进程的
34
+ // argv[2]/[3](--profile headless)恰好真值,每次启动白跑一次探测还污染 stdout)
30
35
  import { pathToFileURL } from 'node:url';
31
36
  const isMain = process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href;
32
37
  if (isMain && process.argv[2] && process.argv[3]) {
33
38
  console.log((await checkConnectivity(process.argv[2], process.argv[3])).evidence);
34
39
  }
35
-
36
-
37
- //# sourceURL=ts/scripts/skeleton-check.ts
@@ -1,3 +1,8 @@
1
+ /**
2
+ * 骨架层集成验证(§7-1 消费面):开 skeletonHub + 带 route 的段 → 结果带三值标注;
3
+ * 枢纽间否定只标注不排除——EK329(数据集滞后的新航线)必须存活。
4
+ * 运行(在 ts/ 下):npx tsx scripts/skeleton-integration-test.ts
5
+ */
1
6
  import assert from 'node:assert/strict';
2
7
  import { readFile } from 'node:fs/promises';
3
8
  import { join } from 'node:path';
@@ -6,17 +11,13 @@ const pack = JSON.parse(await readFile(join('..', 'data', 'flights_2026.json'),
6
11
  const spec = parseFlightPackToSpec(pack);
7
12
  spec.budgetCny = 9000;
8
13
  spec.skeletonHub = true;
9
- spec.segments.forEach((s)=>{
10
- if (s.id === 'f5') s.route = 'SZX->DXB';
11
- if (s.id === 'f1') s.route = 'HKG->HKT';
12
- });
14
+ spec.segments.forEach(s => { if (s.id === 'f5')
15
+ s.route = 'SZX->DXB'; if (s.id === 'f1')
16
+ s.route = 'HKG->HKT'; });
13
17
  const r = await solveUnified(spec);
14
18
  assert.equal(r.feasible, true, '骨架否定不排除——EK329 必须仍在');
15
- const f5 = r.legs.find((l)=>l.leg === 'f5');
19
+ const f5 = r.legs.find(l => l.leg === 'f5');
16
20
  assert.equal(f5?.service, 'EK329', 'EK329 存活(骨架滞后容错)');
17
- assert.ok((r.skeleton_notes ?? []).some((n)=>n.includes('SZX') && n.includes('❌')), `枢纽否定标注在: ${r.skeleton_notes}`);
18
- assert.ok((r.skeleton_notes ?? []).some((n)=>n.includes('HKG') && n.includes('✅')), '正向标注在');
21
+ assert.ok((r.skeleton_notes ?? []).some(n => n.includes('SZX') && n.includes('❌')), `枢纽否定标注在: ${r.skeleton_notes}`);
22
+ assert.ok((r.skeleton_notes ?? []).some(n => n.includes('HKG') && n.includes('✅')), '正向标注在');
19
23
  console.log(`骨架集成 OK:EK329 在枢纽否定下存活,标注=[${r.skeleton_notes?.join(' | ')}]`);
20
-
21
-
22
- //# sourceURL=ts/scripts/skeleton-integration-test.ts