@danceiny/gotry 0.0.1-rc.9 → 0.2.0-rc.27
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.
- package/README.md +172 -219
- package/README.zh-CN.md +222 -0
- package/bin/gotry-backend.js +53 -0
- package/bin/gotry-booking-copilot.js +53 -0
- package/bin/gotry-bootstrap.js +1744 -0
- package/bin/gotry-inner.js +733 -83
- package/bin/gotry-process-liveness.d.ts +27 -0
- package/bin/gotry-process-liveness.js +97 -0
- package/bin/gotry-runtime-resolution.d.ts +27 -0
- package/bin/gotry-runtime-resolution.js +49 -0
- package/bin/gotry.js +1 -1
- package/client/client.js +203 -0
- package/cordis.gotry-patch.yml +80 -56
- package/data/airline-airports.json +39 -0
- package/data/stations-12306-verify.json +524 -0
- package/dist/capabilities/agent-reach-deep.js +45 -122
- package/dist/capabilities/agent-reach.js +101 -146
- package/dist/capabilities/anything.js +135 -94
- package/dist/capabilities/artifacts.js +370 -0
- package/dist/capabilities/channel-health.js +131 -0
- package/dist/capabilities/channel-registry.js +256 -0
- package/dist/capabilities/doctor.js +546 -0
- package/dist/capabilities/effect.js +394 -0
- package/dist/capabilities/external-event.js +149 -0
- package/dist/capabilities/fact-log.js +55 -0
- package/dist/capabilities/flyai-config.js +337 -0
- package/dist/capabilities/flyai.js +947 -110
- package/dist/capabilities/fx-contract.js +293 -0
- package/dist/capabilities/geo-atlas.js +246 -0
- package/dist/capabilities/ground-transfer.js +509 -0
- package/dist/capabilities/hbcli.js +260 -123
- package/dist/capabilities/hotelbyte-transaction.js +194 -0
- package/dist/capabilities/incident-log.js +131 -61
- package/dist/capabilities/itinerary-artifact.js +190 -0
- package/dist/capabilities/itinerary-deck-artifact.js +194 -0
- package/dist/capabilities/itinerary-deck-export.js +339 -0
- package/dist/capabilities/lavish-local.js +924 -0
- package/dist/capabilities/model-override.js +38 -0
- package/dist/capabilities/opensky.js +54 -43
- package/dist/capabilities/resilience.js +191 -0
- package/dist/capabilities/session/action-cache.js +50 -44
- package/dist/capabilities/session/adapters/ctrip-flight.js +120 -62
- package/dist/capabilities/session/adapters/ctrip-hotel.js +222 -0
- package/dist/capabilities/session/adapters/dida-portal.js +274 -0
- package/dist/capabilities/session/adapters/meituan-local.js +35 -54
- package/dist/capabilities/session/adapters/rail-12306.js +342 -0
- package/dist/capabilities/session/benchmark.js +189 -0
- package/dist/capabilities/session/bridge-ledger.js +224 -0
- package/dist/capabilities/session/extension-bridge.js +636 -0
- package/dist/capabilities/session/extension-channel.js +109 -0
- package/dist/capabilities/session/extension-distribution.js +230 -0
- package/dist/capabilities/session/extract.js +31 -21
- package/dist/capabilities/session/golden-score.js +114 -0
- package/dist/capabilities/session/health-watch.js +215 -0
- package/dist/capabilities/session/read-guard.js +75 -24
- package/dist/capabilities/session/static-flight-golden.js +150 -0
- package/dist/capabilities/session/transport.js +97 -83
- package/dist/capabilities/session/wizard.js +117 -0
- package/dist/capabilities/session-consent.js +178 -0
- package/dist/capabilities/session-login.js +170 -0
- package/dist/capabilities/session-search.js +635 -44
- package/dist/capabilities/spawn-bounded.js +203 -0
- package/dist/capabilities/sponsor-plugin.js +341 -0
- package/dist/capabilities/visa-policy.js +140 -0
- package/dist/capabilities/weather.js +243 -138
- package/dist/data/session-golden-20.json +25 -0
- package/dist/data/sf-golden-manifest.json +103 -0
- package/dist/data/sf-static-routes.json +91 -0
- package/dist/scripts/action-cache-tests.js +24 -59
- package/dist/scripts/agent-planning-turn-deadline-e2e.js +242 -0
- package/dist/scripts/agent-planning-turn-deadline-tests.js +185 -0
- package/dist/scripts/agent-reach-deep-tests.js +30 -40
- package/dist/scripts/agent-reach-error-tests.js +354 -0
- package/dist/scripts/agent-reach-tests.js +26 -18
- package/dist/scripts/agent-reach-wrapper-tests.js +50 -50
- package/dist/scripts/anything-tests.js +116 -60
- package/dist/scripts/artifact-client-contract-tests.js +220 -0
- package/dist/scripts/artifacts-capability-tests.js +727 -0
- package/dist/scripts/async-collect.js +50 -11
- package/dist/scripts/benchmark-environment-bridge-e2e.js +741 -0
- package/dist/scripts/benchmark-environment-bridge-tests.js +1452 -0
- package/dist/scripts/booking-copilot-availability-ledger-binding-tests.js +126 -0
- package/dist/scripts/booking-copilot-availability-policy-tests.js +265 -0
- package/dist/scripts/booking-copilot-bin-proof-tests.js +66 -0
- package/dist/scripts/booking-copilot-cleanup-proof-helper.js +76 -0
- package/dist/scripts/booking-copilot-crossrepo-fixture-server.js +144 -0
- package/dist/scripts/booking-copilot-dsh-core-proof-tests.js +424 -0
- package/dist/scripts/booking-copilot-dsh-planner-proof-tests.js +2839 -0
- package/dist/scripts/booking-copilot-dsh-plugin-proof-tests.js +309 -0
- package/dist/scripts/booking-copilot-dsh-readiness-proof.js +238 -0
- package/dist/scripts/booking-copilot-dsh-warmer-proof.js +123 -0
- package/dist/scripts/booking-copilot-event-sequence-concurrency-proof-tests.js +156 -0
- package/dist/scripts/booking-copilot-gap-code-contract-proof-tests.js +70 -0
- package/dist/scripts/booking-copilot-intent-proof-tests.js +422 -0
- package/dist/scripts/booking-copilot-operation-ledger-concurrency-proof-tests.js +184 -0
- package/dist/scripts/booking-copilot-planner-live-probe.js +95 -0
- package/dist/scripts/booking-copilot-proof-runtime.js +42 -0
- package/dist/scripts/booking-copilot-receipt-ledger-concurrency-proof-tests.js +210 -0
- package/dist/scripts/booking-copilot-runtime-proof-tests.js +2349 -0
- package/dist/scripts/booking-copilot-server-proof-tests.js +784 -0
- package/dist/scripts/booking-copilot-startup-proof-tests.js +94 -0
- package/dist/scripts/booking-executor-tests.js +80 -0
- package/dist/scripts/booking-recovery-chain-tests.js +182 -0
- package/dist/scripts/booking-saga-tests.js +125 -0
- package/dist/scripts/booking-surface-contract-proof-tests.js +212 -0
- package/dist/scripts/bootstrap-tests.js +1479 -0
- package/dist/scripts/bridge-events-tests.js +239 -0
- package/dist/scripts/bridge-ledger-tests.js +428 -0
- package/dist/scripts/build-changelog.js +238 -0
- package/dist/scripts/build-metrics-report.js +331 -0
- package/dist/scripts/changelog-tests.js +118 -0
- package/dist/scripts/channel-probe-tests.js +103 -0
- package/dist/scripts/channel-probe.js +169 -0
- package/dist/scripts/channel-registry-tests.js +208 -0
- package/dist/scripts/companion-tests.js +25 -70
- package/dist/scripts/dida-cookie-probe.js +87 -0
- package/dist/scripts/dida-runner-stop-tests.js +311 -0
- package/dist/scripts/dida-sw-probe.js +114 -0
- package/dist/scripts/diff-test.js +17 -12
- package/dist/scripts/doctor-tests.js +564 -0
- package/dist/scripts/dsh-artifact-e2e.js +279 -0
- package/dist/scripts/dsh-artifact-web-e2e.js +675 -0
- package/dist/scripts/dsh-http-proxy-sse-proof.js +90 -0
- package/dist/scripts/dsh-runtime-closure-tests.js +182 -0
- package/dist/scripts/dsh-runtime-closure.js +162 -0
- package/dist/scripts/dsh-session-v3-migration-proof.js +109 -0
- package/dist/scripts/dsh-subprocess-local-proof.js +150 -0
- package/dist/scripts/dsh-target-closure-proof.js +178 -0
- package/dist/scripts/effect-tests.js +539 -0
- package/dist/scripts/engine-run.js +1 -3
- package/dist/scripts/engine-tests.js +16 -10
- package/dist/scripts/evaluation-cadence-tests.js +174 -0
- package/dist/scripts/evaluation-contract-tests.js +268 -0
- package/dist/scripts/extension-distribution-cli.js +39 -0
- package/dist/scripts/extension-distribution-tests.js +443 -0
- package/dist/scripts/extension-tests.js +988 -0
- package/dist/scripts/external-event-tests.js +350 -0
- package/dist/scripts/fact-gate-tests.js +869 -0
- package/dist/scripts/fixtures/extension-bridge-unref-child.mjs +21 -0
- package/dist/scripts/flight-hotel-anchor-363-registered-e2e.js +169 -0
- package/dist/scripts/flight-malformed-tests.js +159 -0
- package/dist/scripts/flyai-setup-tests.js +228 -0
- package/dist/scripts/flyai-setup-tool-tests.js +54 -0
- package/dist/scripts/flyai-tests.js +692 -0
- package/dist/scripts/flyai-tool-registration-contract-tests.js +91 -0
- package/dist/scripts/fx-contract-tests.js +201 -0
- package/dist/scripts/g5-guard-tests.js +131 -0
- package/dist/scripts/g5-guard.js +189 -0
- package/dist/scripts/gen-lunar-anchors.js +106 -0
- package/dist/scripts/geo-atlas-tests.js +283 -0
- package/dist/scripts/gotry-backend-kernel-tests.js +206 -0
- package/dist/scripts/gotry-try-demo-tests.js +79 -0
- package/dist/scripts/gotry-try-demo.js +49 -0
- package/dist/scripts/ground-transfer-tests.js +956 -0
- package/dist/scripts/hbcli-e2e-tests.js +159 -0
- package/dist/scripts/hbcli-live-optin-tests.js +96 -0
- package/dist/scripts/hbcli-release-contract-tests.js +122 -0
- package/dist/scripts/hbcli-tests.js +121 -69
- package/dist/scripts/health-watch-cli.js +73 -0
- package/dist/scripts/hotel-date-gate-tests.js +496 -0
- package/dist/scripts/hotelbyte-reconcile-tests.js +141 -0
- package/dist/scripts/hotelbyte-spawn-e2e-tests.js +373 -0
- package/dist/scripts/i18n-tests.js +41 -0
- package/dist/scripts/incident-tests.js +281 -87
- package/dist/scripts/issue-194-job-id-guard-tests.js +106 -0
- package/dist/scripts/issue-233-cancel-refund-commission-tests.js +392 -0
- package/dist/scripts/issue-271-liveness-tests.js +569 -0
- package/dist/scripts/issue-289-model-retry-real-tests.js +296 -0
- package/dist/scripts/issue-289-model-retry-tests.js +441 -0
- package/dist/scripts/issue-289-web-retry-e2e.js +630 -0
- package/dist/scripts/issue-338-home-city-e2e.js +577 -0
- package/dist/scripts/issue343-real-entry-e2e.js +160 -0
- package/dist/scripts/issue436-persisted-routing-e2e.js +247 -0
- package/dist/scripts/itinerary-artifact-tests.js +368 -0
- package/dist/scripts/itinerary-deck-artifact-tests.js +416 -0
- package/dist/scripts/itinerary-deck-export-tests.js +552 -0
- package/dist/scripts/itinerary-deck-tests.js +435 -0
- package/dist/scripts/itinerary-html-tests.js +426 -0
- package/dist/scripts/journey-tests.js +22 -27
- package/dist/scripts/kernel-manifest-evidence.js +109 -0
- package/dist/scripts/kernel-manifest-gate.js +126 -0
- package/dist/scripts/kernel-manifest-tests.js +212 -0
- package/dist/scripts/kernel-manifest-trace-entry.js +7 -0
- package/dist/scripts/kernel-manifest-trace-hook.mjs +28 -0
- package/dist/scripts/kernel-manifest.js +369 -0
- package/dist/scripts/lavish-local-tests.js +1226 -0
- package/dist/scripts/lavish-product-tools-tests.js +537 -0
- package/dist/scripts/ledger-repair-apply-tests.js +316 -0
- package/dist/scripts/ledger-repair-plan-tests.js +375 -0
- package/dist/scripts/ledger-tests.js +382 -246
- package/dist/scripts/ledger-workflow-crash.js +10 -20
- package/dist/scripts/managed-dsh-cleanup-diagnostic-proof.js +184 -0
- package/dist/scripts/managed-dsh-close-failure-proof.js +172 -0
- package/dist/scripts/managed-dsh-run-port-proof.js +153 -0
- package/dist/scripts/managed-dsh-terminal-proof.js +252 -0
- package/dist/scripts/map-tools-vendor-package-proof.js +365 -0
- package/dist/scripts/memory-capture-tests.js +242 -61
- package/dist/scripts/memory-decay-tests.js +18 -52
- package/dist/scripts/memory-lifecycle-tests.js +657 -0
- package/dist/scripts/memory-lifecycle.js +196 -0
- package/dist/scripts/memory-metrics.js +24 -12
- package/dist/scripts/memory-value-report.js +631 -0
- package/dist/scripts/memory-value-tests.js +328 -0
- package/dist/scripts/metrics-report-tests.js +151 -0
- package/dist/scripts/model-override-e2e.js +137 -0
- package/dist/scripts/nightly-evidence-tests.js +161 -0
- package/dist/scripts/nightly-evidence.js +286 -0
- package/dist/scripts/nudge-digest.js +39 -28
- package/dist/scripts/observability-tests.js +381 -0
- package/dist/scripts/onboarding-tests.js +161 -0
- package/dist/scripts/opensky-check.js +18 -13
- package/dist/scripts/opensky-tests.js +28 -28
- package/dist/scripts/persona-surface-guard-tests.js +49 -0
- package/dist/scripts/pnpm-dsh-closure-proof.js +17 -0
- package/dist/scripts/policy-anchor-359-registered-e2e.js +214 -0
- package/dist/scripts/price-drift-tests.js +340 -0
- package/dist/scripts/price-drift-watch.js +447 -0
- package/dist/scripts/probe-poi-tests.js +53 -45
- package/dist/scripts/product-metrics.js +483 -0
- package/dist/scripts/publish-preverify.js +110 -0
- package/dist/scripts/realtime-pricing-tests.js +76 -0
- package/dist/scripts/recall-tests.js +513 -0
- package/dist/scripts/replay-async.js +20 -18
- package/dist/scripts/replay-real.js +18 -18
- package/dist/scripts/replay.js +52 -82
- package/dist/scripts/session-attach-diagnose.js +20 -20
- package/dist/scripts/session-attach-poc.js +34 -37
- package/dist/scripts/session-benchmark.js +180 -0
- package/dist/scripts/session-dida-live-e2e.js +199 -0
- package/dist/scripts/session-extract-tests.js +14 -35
- package/dist/scripts/session-link-tests.js +478 -0
- package/dist/scripts/session-login.js +27 -46
- package/dist/scripts/session-tests.js +796 -96
- package/dist/scripts/session-zones-tests.js +358 -0
- package/dist/scripts/sf-live-benchmark.js +367 -0
- package/dist/scripts/sf-live-challenge-stop-tests.js +453 -0
- package/dist/scripts/sf-live-cli-tests.js +92 -0
- package/dist/scripts/sf-live-options.js +46 -0
- package/dist/scripts/sf-manifest-tests.js +30 -0
- package/dist/scripts/sf-manifest.js +33 -0
- package/dist/scripts/sf-soft-score-tests.js +67 -0
- package/dist/scripts/sf-summary-tests.js +324 -0
- package/dist/scripts/sf-summary.js +525 -0
- package/dist/scripts/share-tests.js +432 -0
- package/dist/scripts/skeleton-check.js +16 -14
- package/dist/scripts/skeleton-integration-test.js +28 -11
- package/dist/scripts/skills-contract-tests.js +51 -46
- package/dist/scripts/smoke-session-gate-tests.js +21 -0
- package/dist/scripts/smoke.js +690 -246
- package/dist/scripts/spawn-bounded-abort-tests.js +241 -0
- package/dist/scripts/sponsor-reuse-tests.js +262 -0
- package/dist/scripts/state-cli-tests.js +226 -101
- package/dist/scripts/state-cli.js +550 -199
- package/dist/scripts/state-repair-cli.js +66 -0
- package/dist/scripts/state-repair-tests.js +140 -0
- package/dist/scripts/static-golden-tests.js +165 -0
- package/dist/scripts/time-eval-tests.js +210 -192
- package/dist/scripts/travel-timeline-tests.js +29 -93
- package/dist/scripts/turn-handoff-collect-tests.js +169 -0
- package/dist/scripts/turn-handoff-collect.js +161 -0
- package/dist/scripts/turn-policy-tests.js +55 -0
- package/dist/scripts/typed-contract-canary.js +176 -0
- package/dist/scripts/tz-resolver-sanity.js +50 -0
- package/dist/scripts/tz-resolver-tests.js +584 -0
- package/dist/scripts/unified-tests.js +23 -19
- package/dist/scripts/visa-policy-tests.js +62 -0
- package/dist/scripts/weather-tests.js +276 -41
- package/dist/scripts/wish-channel-gate-tests.js +54 -0
- package/dist/scripts/write-gate-tests.js +522 -0
- package/dist/scripts/z3-lifecycle-fault-tests.js +188 -0
- package/dist/scripts/z3-lifecycle-tests.js +170 -0
- package/dist/scripts/z3-race-repeat-tests.js +39 -0
- package/dist/scripts/z3-race-tests.js +41 -0
- package/dist/src/artifact-gate.js +829 -0
- package/dist/src/backend/kernel.js +91 -0
- package/dist/src/backend/modules/booking-copilot.js +92 -0
- package/dist/src/backend/modules/booking-executor.js +124 -0
- package/dist/src/backend/modules/session-search.js +551 -0
- package/dist/src/benchmark-agent-conformance.js +578 -0
- package/dist/src/benchmark-environment-bridge.js +515 -0
- package/dist/src/benchmark-headless-child-diagnostics.js +166 -0
- package/dist/src/benchmark-tool-isolation.js +151 -0
- package/dist/src/bookable-facts.js +533 -0
- package/dist/src/booking-saga.js +97 -0
- package/dist/src/booking-surface/availability-policy.js +697 -0
- package/dist/src/booking-surface/booking-intent.js +43 -0
- package/dist/src/booking-surface/cancel-refund-commission.js +379 -0
- package/dist/src/booking-surface/canonical-schema.js +116 -0
- package/dist/src/booking-surface/contracts.js +75 -0
- package/dist/src/booking-surface/dsh-planner.js +1854 -0
- package/dist/src/booking-surface/dsh-plugin.js +378 -0
- package/dist/src/booking-surface/dsh-worker.js +98 -0
- package/dist/src/booking-surface/error-codes.js +231 -0
- package/dist/src/booking-surface/index.js +9 -0
- package/dist/src/booking-surface/managed-dsh-cleanup-diagnostic.js +97 -0
- package/dist/src/booking-surface/managed-dsh-run-port.js +244 -0
- package/dist/src/booking-surface/profile.js +24 -0
- package/dist/src/booking-surface/recovery-chain.js +367 -0
- package/dist/src/booking-surface/runtime.js +1968 -0
- package/dist/src/booking-surface/server.js +674 -0
- package/dist/src/booking-surface/startup.js +168 -0
- package/dist/src/booking-surface/validation.js +261 -0
- package/dist/src/bridge.js +20 -13
- package/dist/src/companions.js +48 -74
- package/dist/src/contracts.js +21 -43
- package/dist/src/dsh-llm.js +97 -111
- package/dist/src/engine.js +276 -193
- package/dist/src/evaluation-cadence.js +97 -0
- package/dist/src/evaluation-contracts.js +283 -0
- package/dist/src/flight-pack-adapter.js +54 -0
- package/dist/src/flight-pack-contract.js +2 -0
- package/dist/src/flyai-setup-tool.js +70 -0
- package/dist/src/gotry-backend.js +125 -0
- package/dist/src/hotel-date-gate.js +118 -0
- package/dist/src/i18n.js +88 -0
- package/dist/src/index.js +2227 -882
- package/dist/src/itinerary-deck.js +139 -0
- package/dist/src/itinerary-doc-shared.js +661 -0
- package/dist/src/itinerary-html.js +131 -0
- package/dist/src/journey.js +155 -108
- package/dist/src/lavish-tools.js +400 -0
- package/dist/src/ledger-repair-apply.js +538 -0
- package/dist/src/ledger-repair-plan.js +463 -0
- package/dist/src/loop.js +319 -160
- package/dist/src/memory-capture.js +180 -32
- package/dist/src/memory-decay.js +30 -14
- package/dist/src/memory-lifecycle.js +1310 -0
- package/dist/src/memory-utility.js +27 -40
- package/dist/src/mock-llm.js +36 -61
- package/dist/src/model.js +81 -36
- package/dist/src/realtime-pricing.js +103 -0
- package/dist/src/recall/card.js +74 -0
- package/dist/src/recall/evaluator.js +140 -0
- package/dist/src/recall/integration.js +17 -0
- package/dist/src/recall/tick.js +166 -0
- package/dist/src/session-link/plan-it-action.js +48 -0
- package/dist/src/session-link/session-link.js +248 -0
- package/dist/src/session-zones.js +507 -0
- package/dist/src/share/adapters.js +51 -0
- package/dist/src/share/share-consent.js +76 -0
- package/dist/src/share/share-deck.js +58 -0
- package/dist/src/share/share-token.js +146 -0
- package/dist/src/slot-spec.js +84 -106
- package/dist/src/state-ledger.js +435 -516
- package/dist/src/state-repair.js +299 -0
- package/dist/src/subagent-job-id-guard.js +58 -0
- package/dist/src/time-anchor.js +231 -33
- package/dist/src/tool-packet.js +20 -8
- package/dist/src/travel-slots.js +58 -35
- package/dist/src/travel-timeline.js +44 -63
- package/dist/src/turn-deadline.js +287 -0
- package/dist/src/turn-policy.js +118 -0
- package/dist/src/tz-resolver.js +143 -0
- package/dist/src/unified.js +535 -359
- package/dist/src/wish-pool.js +42 -12
- package/dist/src/write-gate.js +495 -0
- package/dist/src/z3-shared.js +284 -0
- package/extension/README.md +21 -0
- package/extension/README.zh-CN.md +21 -0
- package/extension/background.js +463 -0
- package/extension/content-bridge.js +270 -0
- package/extension/content-main.js +111 -0
- package/extension/manifest.json +60 -0
- package/package.json +407 -14
- package/schemas/booking.intent.schema.json +193 -0
- package/schemas/booking.surface.schema.json +2622 -0
- package/ts/capabilities/agent-reach-deep.ts +114 -0
- package/ts/capabilities/agent-reach.ts +198 -0
- package/ts/capabilities/anything.ts +255 -0
- package/ts/capabilities/artifacts.ts +428 -0
- package/ts/capabilities/fact-log.ts +54 -0
- package/ts/capabilities/flyai-config.ts +388 -0
- package/ts/capabilities/flyai.ts +1274 -0
- package/ts/capabilities/hbcli.ts +253 -71
- package/ts/capabilities/incident-log.ts +88 -59
- package/ts/capabilities/itinerary-artifact.ts +214 -0
- package/ts/capabilities/itinerary-deck-artifact.ts +218 -0
- package/ts/capabilities/itinerary-deck-export.ts +378 -0
- package/ts/capabilities/opensky.ts +121 -0
- package/ts/capabilities/session/action-cache.ts +148 -0
- package/ts/capabilities/session/adapters/ctrip-flight.ts +181 -0
- package/ts/capabilities/session/adapters/ctrip-hotel.ts +238 -0
- package/ts/capabilities/session/adapters/dida-portal.ts +338 -0
- package/ts/capabilities/session/adapters/meituan-local.ts +70 -0
- package/ts/capabilities/session/adapters/rail-12306.ts +392 -0
- package/ts/capabilities/session/benchmark.ts +276 -0
- package/ts/capabilities/session/bridge-ledger.ts +333 -0
- package/ts/capabilities/session/extension-bridge.ts +794 -0
- package/ts/capabilities/session/extension-channel.ts +144 -0
- package/ts/capabilities/session/extension-distribution.ts +266 -0
- package/ts/capabilities/session/extract.ts +54 -0
- package/ts/capabilities/session/golden-score.ts +139 -0
- package/ts/capabilities/session/health-watch.ts +264 -0
- package/ts/capabilities/session/read-guard.ts +141 -0
- package/ts/capabilities/session/static-flight-golden.ts +209 -0
- package/ts/capabilities/session/transport.ts +146 -0
- package/ts/capabilities/session/wizard.ts +140 -0
- package/ts/capabilities/session-consent.ts +211 -0
- package/ts/capabilities/session-login.ts +208 -0
- package/ts/capabilities/session-search.ts +893 -0
- package/ts/capabilities/weather.ts +330 -0
- package/ts/dsh-runtime/vendor/README.md +68 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/LICENSE +21 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/README.en.md +405 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/README.md +404 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/client/client.js +1584 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/cordis.patch.yml +5 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/icon.svg +5 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/amap.js +595 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/nominatim.js +63 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/osrm.js +58 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/photon.js +55 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/config-file.js +87 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/config-route.js +131 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/config.js +16 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/geo.js +232 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/index.js +120 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/settings-ns.js +40 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/staticmap-route.js +115 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/tools/geocode.js +127 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/tools/poi.js +84 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/tools/routes.js +267 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/amap.d.ts +90 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/nominatim.d.ts +17 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/osrm.d.ts +15 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/photon.d.ts +22 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/config-file.d.ts +33 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/config-route.d.ts +23 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/config.d.ts +23 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/geo.d.ts +61 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/index.d.ts +18 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/settings-ns.d.ts +14 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/staticmap-route.d.ts +31 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/tools/geocode.d.ts +11 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/tools/poi.d.ts +9 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/tools/routes.d.ts +75 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/types.d.ts +68 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types.js +16 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/package.json +97 -0
- package/ts/package.json +293 -3
- package/ts/scripts/async-collect.ts +91 -0
- package/ts/scripts/map-tools-vendor-package-proof.ts +404 -0
- package/ts/scripts/skeleton-check.ts +1 -1
- package/ts/scripts/skeleton-integration-test.ts +23 -1
- package/ts/scripts/state-cli.ts +568 -0
- package/ts/scripts/turn-handoff-collect.ts +178 -0
- package/ts/src/artifact-gate.ts +991 -0
- package/ts/src/benchmark-agent-conformance.ts +619 -0
- package/ts/src/benchmark-environment-bridge.ts +538 -0
- package/ts/src/benchmark-headless-child-diagnostics.ts +184 -0
- package/ts/src/benchmark-tool-isolation.ts +166 -0
- package/ts/src/bookable-facts.ts +837 -0
- package/ts/src/booking-saga.ts +133 -0
- package/ts/src/booking-surface/availability-policy.ts +522 -0
- package/ts/src/booking-surface/booking-intent.ts +86 -0
- package/ts/src/booking-surface/cancel-refund-commission.ts +488 -0
- package/ts/src/booking-surface/canonical-schema.js +116 -0
- package/ts/src/booking-surface/contracts.ts +475 -0
- package/ts/src/booking-surface/dsh-planner.ts +1949 -0
- package/ts/src/booking-surface/dsh-plugin.js +378 -0
- package/ts/src/booking-surface/dsh-worker.ts +110 -0
- package/ts/src/booking-surface/error-codes.ts +241 -0
- package/ts/src/booking-surface/index.ts +9 -0
- package/ts/src/booking-surface/managed-dsh-cleanup-diagnostic.ts +151 -0
- package/ts/src/booking-surface/managed-dsh-run-port.ts +273 -0
- package/ts/src/booking-surface/profile.ts +42 -0
- package/ts/src/booking-surface/recovery-chain.ts +409 -0
- package/ts/src/booking-surface/runtime.ts +1793 -0
- package/ts/src/booking-surface/server.ts +733 -0
- package/ts/src/booking-surface/startup.ts +201 -0
- package/ts/src/booking-surface/validation.ts +217 -0
- package/ts/src/companions.ts +113 -0
- package/ts/src/contracts.ts +13 -3
- package/ts/src/dsh-llm.ts +44 -11
- package/ts/src/flyai-setup-tool.ts +77 -0
- package/ts/src/index.ts +1948 -198
- package/ts/src/itinerary-deck.ts +189 -0
- package/ts/src/itinerary-doc-shared.ts +741 -0
- package/ts/src/itinerary-html.ts +171 -0
- package/ts/src/loop.ts +223 -25
- package/ts/src/memory-capture.ts +283 -0
- package/ts/src/memory-decay.ts +55 -0
- package/ts/src/memory-utility.ts +70 -0
- package/ts/src/mock-llm.ts +4 -8
- package/ts/src/model.ts +88 -2
- package/ts/src/recall/card.ts +106 -0
- package/ts/src/recall/evaluator.ts +170 -0
- package/ts/src/recall/integration.ts +35 -0
- package/ts/src/recall/tick.ts +213 -0
- package/ts/src/session-link/plan-it-action.ts +78 -0
- package/ts/src/session-link/session-link.ts +275 -0
- package/ts/src/share/adapters.ts +93 -0
- package/ts/src/share/share-consent.ts +105 -0
- package/ts/src/share/share-deck.ts +85 -0
- package/ts/src/share/share-token.ts +166 -0
- package/ts/src/slot-spec.ts +172 -0
- package/ts/src/state-ledger.ts +912 -0
- package/ts/src/subagent-job-id-guard.ts +72 -0
- package/ts/src/time-anchor.ts +338 -0
- package/ts/src/tool-packet.ts +32 -0
- package/ts/src/travel-slots.ts +219 -0
- package/ts/src/travel-timeline.ts +83 -0
- package/ts/src/turn-deadline.ts +366 -0
- package/ts/src/turn-policy.ts +154 -0
- package/ts/src/unified.ts +354 -88
- package/ts/src/wish-pool.ts +79 -0
- package/dist/scripts/session-attach-wait.js +0 -41
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* sf live benchmark 挑战截断离线 E2E(issue #411,RFC §3.5 挑战红线)。
|
|
3
|
+
*
|
|
4
|
+
* 真实 CLI 路径:spawn 真 runner(scripts/sf-live-benchmark.ts,Node24 原生 type stripping),只在 /tmp
|
|
5
|
+
* overlay 副本里把 capabilities/session-search.ts 替换为脚本化确定性响应——
|
|
6
|
+
* challenge@all 首条即 challenged → 期望整批在 1 次调用后截断;
|
|
7
|
+
* challenge@2 首条 hit、第二条 challenged → 期望 2 次调用后截断;
|
|
8
|
+
* challenge@8 前七条 hit、第八条 challenged → 8 条证据齐全但仍非完整批次;
|
|
9
|
+
* hit@all 普通 8 条批次 → 期望跑满且 sf-summary 仍标完整/有效校准。
|
|
10
|
+
* comparator(flyai CLI)经 PATH shim(npx 即败 + 计数)证明「停止后不再调用」;
|
|
11
|
+
* globalThis.fetch 陷阱兜底断言零网络。HOME/evidence root 全部临时,零真实
|
|
12
|
+
* 网站/浏览器/供应商请求,不触碰共享 founder state。
|
|
13
|
+
*
|
|
14
|
+
* 同时断言:challenged 的结构化语义(session.verdict / doubleSource=challenge_stop /
|
|
15
|
+
* no_spend_stop)不被改写;部分批次落盘 stop_reason + attempted/not_attempted 清单;
|
|
16
|
+
* sf-summary 对挑战批次 status=fail_closed(即使 8 条证据齐全也不标完整/有效校准)。
|
|
17
|
+
*/
|
|
18
|
+
import assert from 'node:assert/strict';
|
|
19
|
+
import { execFileSync, spawnSync } from 'node:child_process';
|
|
20
|
+
import { chmodSync, cpSync, existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, realpathSync, rmSync, symlinkSync, writeFileSync } from 'node:fs';
|
|
21
|
+
import { tmpdir } from 'node:os';
|
|
22
|
+
import { join, relative } from 'node:path';
|
|
23
|
+
const REAL_TS = join(import.meta.dirname, '..');
|
|
24
|
+
// macOS 的 /tmp 是 /private/tmp 符号链接:sf-summary 以 argv[1]===import.meta.url 判定
|
|
25
|
+
// 直跑入口,overlay 路径必须先规范化,否则 argv[1](/tmp/...)与模块 URL(/private/tmp/...)不等,main() 会被静默跳过
|
|
26
|
+
const TMP_ROOT = realpathSync(tmpdir());
|
|
27
|
+
const EXPECTED_QUERY_IDS = Array.from({ length: 8 }, (_, index) => `sf-${(index + 1).toString().padStart(2, '0')}`);
|
|
28
|
+
const IDS_AFTER_FIRST = EXPECTED_QUERY_IDS.slice(1);
|
|
29
|
+
function readLines(path) {
|
|
30
|
+
try {
|
|
31
|
+
const text = readFileSync(path, 'utf8');
|
|
32
|
+
return text.length === 0 ? [] : text.split('\n').filter((line) => line.length > 0);
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return [];
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/** overlay = 仓库 ts 源副本(脚本/能力/数据)+ 真 node_modules 符号链接 + 确定性 session 模块 */
|
|
39
|
+
function buildOverlay(tag) {
|
|
40
|
+
const base = mkdtempSync(join(TMP_ROOT, `gotry-sf411-${tag}-`));
|
|
41
|
+
const home = join(base, 'home');
|
|
42
|
+
const overlayTs = join(base, 'ts');
|
|
43
|
+
mkdirSync(home, { recursive: true });
|
|
44
|
+
for (const entry of ['scripts', 'capabilities', 'data']) {
|
|
45
|
+
cpSync(join(REAL_TS, entry), join(overlayTs, entry), { recursive: true });
|
|
46
|
+
}
|
|
47
|
+
cpSync(join(REAL_TS, 'package.json'), join(overlayTs, 'package.json'));
|
|
48
|
+
cpSync(join(REAL_TS, 'tsconfig.json'), join(overlayTs, 'tsconfig.json'));
|
|
49
|
+
symlinkSync(join(REAL_TS, 'node_modules'), join(overlayTs, 'node_modules'), 'dir');
|
|
50
|
+
// Keep the explicit destination outside HOME so the test proves that the
|
|
51
|
+
// new flag, rather than the default, owns every benchmark artifact.
|
|
52
|
+
const explicitEvidenceRoot = join(base, 'explicit evidence root');
|
|
53
|
+
const defaultEvidenceRoot = join(home, '.gotry', 'evidence', 'session');
|
|
54
|
+
// Optional local baseline mode lets the harness prove that main@20d728e
|
|
55
|
+
// fails the explicit-root contract without touching the live worktree.
|
|
56
|
+
if (process.env.SF503_BASELINE === '1') {
|
|
57
|
+
const baseline = execFileSync('git', [
|
|
58
|
+
'show',
|
|
59
|
+
'20d728e3668d302821bad0f8687ca8ef55d29ed5:ts/scripts/sf-live-benchmark.ts',
|
|
60
|
+
], { cwd: join(REAL_TS, '..'), encoding: 'utf8' });
|
|
61
|
+
writeFileSync(join(overlayTs, 'scripts', 'sf-live-benchmark.ts'), baseline);
|
|
62
|
+
}
|
|
63
|
+
// 确定性 session 模块:每次调用计数并按 SF411_SCRIPT 返回 hit/challenged
|
|
64
|
+
writeFileSync(join(overlayTs, 'capabilities', 'session-search.ts'), `// sf411 E2E 确定性 session 模块(仅存在于临时 overlay;真模块不参与本测试)
|
|
65
|
+
import { appendFileSync } from 'node:fs'
|
|
66
|
+
|
|
67
|
+
export type SessionVerdict = 'hit' | 'miss' | 'error' | 'challenged' | 'cooldown' | 'needs-login' | 'needs-attach' | 'needs-extension'
|
|
68
|
+
|
|
69
|
+
export interface SessionSearchResult {
|
|
70
|
+
ok: boolean
|
|
71
|
+
via: 'session-ctrip-flight' | 'session-ctrip-flight-error'
|
|
72
|
+
evidence: string
|
|
73
|
+
latencyMs: number
|
|
74
|
+
verdict: SessionVerdict
|
|
75
|
+
options?: Array<{ depDateTime: string; arrDateTime: string; flightNo: string; price: number }>
|
|
76
|
+
error?: string
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface SessionFlightQuery { from: string; to: string; date: string }
|
|
80
|
+
|
|
81
|
+
let calls = 0
|
|
82
|
+
export async function sessionFlightSearch(q: SessionFlightQuery): Promise<SessionSearchResult> {
|
|
83
|
+
calls += 1
|
|
84
|
+
const counter = process.env.SF411_CALL_COUNTER
|
|
85
|
+
if (counter) appendFileSync(counter, JSON.stringify({ call: calls, query_id: q.from + '->' + q.to, date: q.date }) + '\\n')
|
|
86
|
+
const script = process.env.SF411_SCRIPT ?? 'challenge@all'
|
|
87
|
+
const challengeFrom = script === 'challenge@all'
|
|
88
|
+
? 1
|
|
89
|
+
: script.startsWith('challenge@')
|
|
90
|
+
? Number(script.slice('challenge@'.length))
|
|
91
|
+
: Number.POSITIVE_INFINITY
|
|
92
|
+
if (Number.isInteger(challengeFrom) && challengeFrom >= 1 && calls >= challengeFrom) {
|
|
93
|
+
return {
|
|
94
|
+
ok: false,
|
|
95
|
+
via: 'session-ctrip-flight-error',
|
|
96
|
+
evidence: '[stub] challenged #' + String(calls),
|
|
97
|
+
latencyMs: 1,
|
|
98
|
+
verdict: 'challenged',
|
|
99
|
+
error: '风控/验证码命中(stub);按红线不重试不绕过,交还用户',
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
ok: true,
|
|
104
|
+
via: 'session-ctrip-flight',
|
|
105
|
+
evidence: '[stub] hit #' + String(calls),
|
|
106
|
+
latencyMs: 1,
|
|
107
|
+
verdict: 'hit',
|
|
108
|
+
options: [{ depDateTime: '2026-10-01T08:30:00+08:00', arrDateTime: '2026-10-01T11:40:00+08:00', flightNo: 'MU9999', price: 1234 }],
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
`);
|
|
112
|
+
// comparator 计数 shim:npx 即败(退出 97),计数即 comparator 尝试数
|
|
113
|
+
const shimDir = join(base, 'shim');
|
|
114
|
+
mkdirSync(shimDir, { recursive: true });
|
|
115
|
+
const npxShim = join(shimDir, 'npx');
|
|
116
|
+
writeFileSync(npxShim, `#!/bin/sh
|
|
117
|
+
printf '1\\n' >> "$SF411_NPX_COUNTER"
|
|
118
|
+
echo 'sf411 shim: flyai CLI must not run in offline proof' >&2
|
|
119
|
+
exit 97
|
|
120
|
+
`);
|
|
121
|
+
chmodSync(npxShim, 0o755);
|
|
122
|
+
// 零网络兜底:fetch 陷阱(记录并抛错)
|
|
123
|
+
const netTrap = join(base, 'net-trap.mjs');
|
|
124
|
+
writeFileSync(netTrap, `import { appendFileSync } from 'node:fs'
|
|
125
|
+
globalThis.fetch = async (...args) => {
|
|
126
|
+
if (process.env.SF411_NET_COUNTER) appendFileSync(process.env.SF411_NET_COUNTER, JSON.stringify(String(args[0])) + '\\n')
|
|
127
|
+
throw new Error('blocked network in sf411 offline proof')
|
|
128
|
+
}
|
|
129
|
+
`);
|
|
130
|
+
const timerPreload = join(base, 'timer-preload.mjs');
|
|
131
|
+
writeFileSync(timerPreload, `const nativeSetTimeout = globalThis.setTimeout
|
|
132
|
+
globalThis.setTimeout = (callback, delay, ...args) => {
|
|
133
|
+
if (delay === 35000) return nativeSetTimeout(callback, 0, ...args)
|
|
134
|
+
return nativeSetTimeout(callback, delay, ...args)
|
|
135
|
+
}
|
|
136
|
+
`);
|
|
137
|
+
return {
|
|
138
|
+
base,
|
|
139
|
+
home,
|
|
140
|
+
ts: overlayTs,
|
|
141
|
+
evidenceRoot: explicitEvidenceRoot,
|
|
142
|
+
defaultEvidenceRoot,
|
|
143
|
+
sessionCallCounter: join(base, 'session-calls.jsonl'),
|
|
144
|
+
npxCounter: join(base, 'npx-calls.txt'),
|
|
145
|
+
netCounter: join(base, 'net-attempts.jsonl'),
|
|
146
|
+
timerPreload,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
function runRunner(overlay, golden, script, rootMode = 'split-absolute') {
|
|
150
|
+
const rootArgs = rootMode === 'equals-relative'
|
|
151
|
+
? [`--evidence-root=${relative(overlay.ts, overlay.evidenceRoot)}`]
|
|
152
|
+
: rootMode === 'split-absolute'
|
|
153
|
+
? ['--evidence-root', overlay.evidenceRoot]
|
|
154
|
+
: [];
|
|
155
|
+
const result = spawnSync(process.execPath, [
|
|
156
|
+
join(overlay.ts, 'scripts', 'sf-live-benchmark.ts'),
|
|
157
|
+
`--golden=${golden}`,
|
|
158
|
+
...rootArgs,
|
|
159
|
+
], {
|
|
160
|
+
cwd: overlay.ts,
|
|
161
|
+
encoding: 'utf8',
|
|
162
|
+
timeout: 180_000,
|
|
163
|
+
env: {
|
|
164
|
+
...process.env,
|
|
165
|
+
HOME: overlay.home,
|
|
166
|
+
PATH: `${join(overlay.base, 'shim')}:${process.env.PATH ?? ''}`,
|
|
167
|
+
NODE_OPTIONS: `--import=${join(overlay.base, 'net-trap.mjs')} --import=${overlay.timerPreload}`,
|
|
168
|
+
GOTRY_SESSION_LIVE: '0',
|
|
169
|
+
GOTRY_HBCLI_LIVE: '0',
|
|
170
|
+
SF411_SCRIPT: script,
|
|
171
|
+
SF411_CALL_COUNTER: overlay.sessionCallCounter,
|
|
172
|
+
SF411_NPX_COUNTER: overlay.npxCounter,
|
|
173
|
+
SF411_NET_COUNTER: overlay.netCounter,
|
|
174
|
+
},
|
|
175
|
+
});
|
|
176
|
+
return { status: result.status, stdout: result.stdout ?? '', stderr: result.stderr ?? '', overlay };
|
|
177
|
+
}
|
|
178
|
+
function readRecord(overlay, queryId, evidenceRoot = overlay.evidenceRoot) {
|
|
179
|
+
const dir = join(evidenceRoot, queryId);
|
|
180
|
+
const files = readdirSync(dir).filter((name) => name.endsWith('.json'));
|
|
181
|
+
assert.equal(files.length, 1, `${queryId} 应恰有一个证据文件`);
|
|
182
|
+
return JSON.parse(readFileSync(join(dir, files[0]), 'utf8'));
|
|
183
|
+
}
|
|
184
|
+
/** 跑 overlay 里的真 sf-summary,返回(stdout, 解析后的 summary JSON) */
|
|
185
|
+
function runSfSummary(overlay, evidenceRoot = overlay.evidenceRoot) {
|
|
186
|
+
const result = spawnSync(process.execPath, [
|
|
187
|
+
join(overlay.ts, 'scripts', 'sf-summary.ts'),
|
|
188
|
+
'--evidence-root', evidenceRoot,
|
|
189
|
+
], {
|
|
190
|
+
cwd: overlay.ts,
|
|
191
|
+
encoding: 'utf8',
|
|
192
|
+
timeout: 120_000,
|
|
193
|
+
env: { ...process.env, HOME: overlay.home, GOTRY_SESSION_LIVE: '0', GOTRY_HBCLI_LIVE: '0' },
|
|
194
|
+
});
|
|
195
|
+
const match = /summary: (.+\.json)/.exec(result.stdout ?? '');
|
|
196
|
+
assert.notEqual(match, null, `sf-summary 应输出 summary 路径,stdout=${result.stdout}`);
|
|
197
|
+
const summaryPath = match[1];
|
|
198
|
+
assert.equal(summaryPath.startsWith(`${join(evidenceRoot, 'sf-summary')}/`), true, 'sf-summary 输出必须落在传入 evidence root');
|
|
199
|
+
const summary = JSON.parse(readFileSync(summaryPath, 'utf8'));
|
|
200
|
+
return { status: result.status, stdout: result.stdout ?? '', summary, summaryPath };
|
|
201
|
+
}
|
|
202
|
+
function assertZeroNetwork(overlay) {
|
|
203
|
+
assert.deepEqual(readLines(overlay.netCounter), [], '零网络断言失败:fetch 陷阱被触发');
|
|
204
|
+
}
|
|
205
|
+
function assertBatchArtifacts(overlay, evidenceRoot = overlay.evidenceRoot) {
|
|
206
|
+
const summaryDir = join(evidenceRoot, 'sf-summary');
|
|
207
|
+
const summaryFiles = readdirSync(summaryDir).filter((name) => name.endsWith('.json'));
|
|
208
|
+
assert.equal(summaryFiles.length, 1, `benchmark raw summary 应唯一,实际 ${summaryFiles.join(',')}`);
|
|
209
|
+
const summaryFile = summaryFiles[0];
|
|
210
|
+
const stem = summaryFile.slice(0, -'.json'.length);
|
|
211
|
+
const identity = /^(\d{4}-\d{2}-\d{2}T\d{2})-(\d{2})-(\d{2})-(\d{3})Z$/.exec(stem);
|
|
212
|
+
assert.notEqual(identity, null, 'benchmark raw summary 必须使用 canonical run filename');
|
|
213
|
+
const captureAt = `${identity[1]}:${identity[2]}:${identity[3]}.${identity[4]}Z`;
|
|
214
|
+
const raw = JSON.parse(readFileSync(join(summaryDir, summaryFile), 'utf8'));
|
|
215
|
+
assert.equal(raw.started_at, captureAt, 'raw summary.started_at 必须与 canonical run filename 同一 run identity');
|
|
216
|
+
const rawRecords = raw.records;
|
|
217
|
+
assert.equal(rawRecords.length, raw.total, 'raw summary total 必须与 records 一致');
|
|
218
|
+
assert.ok(rawRecords.every((record) => record.sessionTransportShape === 'not_observed'), '脚本化 session stub 不能冒充真实传输形状');
|
|
219
|
+
assert.deepEqual(rawRecords.map((record) => record.query_id), raw.attempted_query_ids, 'raw summary records 必须按 attempted 顺序落账');
|
|
220
|
+
for (const queryId of raw.attempted_query_ids) {
|
|
221
|
+
const queryDir = join(evidenceRoot, queryId);
|
|
222
|
+
const files = readdirSync(queryDir).filter((name) => name.endsWith('.json'));
|
|
223
|
+
assert.deepEqual(files, [summaryFile], `${queryId} 必须使用同一 canonical run stem`);
|
|
224
|
+
const record = JSON.parse(readFileSync(join(queryDir, summaryFile), 'utf8'));
|
|
225
|
+
const rawRecord = rawRecords.find((candidate) => candidate.query_id === queryId);
|
|
226
|
+
assert.deepEqual(record, rawRecord, `${queryId} 文件必须与 raw summary.records 深相等`);
|
|
227
|
+
}
|
|
228
|
+
return { raw, stem };
|
|
229
|
+
}
|
|
230
|
+
function assertDefaultRootAbsent(overlay) {
|
|
231
|
+
assert.equal(existsSync(overlay.defaultEvidenceRoot), false, '显式 root 场景不得写入 HOME 默认 evidence root');
|
|
232
|
+
}
|
|
233
|
+
function assertExplicitRootAbsent(overlay) {
|
|
234
|
+
assert.equal(existsSync(overlay.evidenceRoot), false, '默认兼容场景不得创建显式 evidence root');
|
|
235
|
+
}
|
|
236
|
+
function assertRebuiltBatch(summary, raw, stem) {
|
|
237
|
+
const selected = summary.selected_batch;
|
|
238
|
+
assert.equal(selected.batch_id, stem, 'sf-summary 必须回读 benchmark canonical filename batch');
|
|
239
|
+
assert.equal(selected.identity_source, 'canonical_filename');
|
|
240
|
+
assert.equal(selected.record_count, raw.records.length);
|
|
241
|
+
assert.deepEqual(selected.query_ids, raw.records.map((record) => record.query_id));
|
|
242
|
+
assert.equal(summary.records.length, selected.record_count);
|
|
243
|
+
for (const record of summary.records)
|
|
244
|
+
assert.equal(record.batch_id, stem);
|
|
245
|
+
}
|
|
246
|
+
function cleanupOverlay(overlay) {
|
|
247
|
+
rmSync(overlay.base, { recursive: true, force: true });
|
|
248
|
+
assert.equal(existsSync(overlay.base), false, 'overlay 必须在场景结束时清理');
|
|
249
|
+
}
|
|
250
|
+
function scenario(name) {
|
|
251
|
+
const overlay = buildOverlay(name);
|
|
252
|
+
console.log(`\n── scenario ${name} ──`);
|
|
253
|
+
return overlay;
|
|
254
|
+
}
|
|
255
|
+
// ── A. 首条 challenge:1 次调用即截断,语义保持 challenge_stop/no_spend_stop ──
|
|
256
|
+
{
|
|
257
|
+
const overlay = scenario('first-challenge');
|
|
258
|
+
try {
|
|
259
|
+
const run = runRunner(overlay, 'flyai', 'challenge@all', 'equals-relative');
|
|
260
|
+
assert.equal(run.status, 0, `挑战截断应正常收尾(exit 0),stderr=${run.stderr}`);
|
|
261
|
+
assert.match(run.stdout, /stop reason: challenge_stop/);
|
|
262
|
+
const artifacts = assertBatchArtifacts(overlay);
|
|
263
|
+
assertDefaultRootAbsent(overlay);
|
|
264
|
+
const sessionCalls = readLines(overlay.sessionCallCounter);
|
|
265
|
+
assert.equal(sessionCalls.length, 1, `首条 challenged 后必须停止,期望 1 次 session 调用,实际 ${sessionCalls.length}`);
|
|
266
|
+
const npxCalls = readLines(overlay.npxCounter);
|
|
267
|
+
assert.equal(npxCalls.length, 1, `comparator 不得为未尝试 query 调用,期望 1 次,实际 ${npxCalls.length}`);
|
|
268
|
+
assertZeroNetwork(overlay);
|
|
269
|
+
const record = readRecord(overlay, 'sf-01');
|
|
270
|
+
assert.equal(record.session.verdict, 'challenged', 'challenged 结构化语义不得被改写成 error');
|
|
271
|
+
assert.equal(record.sessionVerdict, 'challenged');
|
|
272
|
+
assert.equal(record.doubleSource.state, 'challenge_stop');
|
|
273
|
+
assert.equal(record.doubleSource.quota_disposition, 'no_spend_stop');
|
|
274
|
+
const summary = artifacts.raw;
|
|
275
|
+
assert.equal(summary.total, 1);
|
|
276
|
+
assert.equal(summary.batch_complete, false);
|
|
277
|
+
assert.equal(summary.stop_reason, 'challenge_stop');
|
|
278
|
+
assert.deepEqual(summary.attempted_query_ids, ['sf-01']);
|
|
279
|
+
assert.deepEqual(summary.not_attempted_query_ids, IDS_AFTER_FIRST);
|
|
280
|
+
// sf-summary:缺七条的批次绝不标完整/有效校准
|
|
281
|
+
const sfSummary = runSfSummary(overlay);
|
|
282
|
+
assert.equal(sfSummary.status, 1, '部分批次必须 fail_closed');
|
|
283
|
+
assert.equal(sfSummary.summary.status, 'fail_closed');
|
|
284
|
+
assert.equal(sfSummary.summary.challenge_stop_detected, true);
|
|
285
|
+
assert.equal(sfSummary.summary.total, 1);
|
|
286
|
+
assert.equal(sfSummary.summary.missing_query_ids.length, 7);
|
|
287
|
+
assertRebuiltBatch(sfSummary.summary, artifacts.raw, artifacts.stem);
|
|
288
|
+
assertDefaultRootAbsent(overlay);
|
|
289
|
+
console.log('A. 首条 challenge 截断 OK(1 次调用/comparator 1 次/fail_closed/challenge_stop 语义保留)');
|
|
290
|
+
}
|
|
291
|
+
finally {
|
|
292
|
+
cleanupOverlay(overlay);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
// ── B. 中途 challenge:首条 hit,第二条 challenged → 2 次调用后截断 ──
|
|
296
|
+
{
|
|
297
|
+
const overlay = scenario('mid-challenge');
|
|
298
|
+
try {
|
|
299
|
+
const run = runRunner(overlay, 'flyai', 'challenge@2');
|
|
300
|
+
assert.equal(run.status, 0, `挑战截断应正常收尾(exit 0),stderr=${run.stderr}`);
|
|
301
|
+
assert.match(run.stdout, /stop reason: challenge_stop/);
|
|
302
|
+
const artifacts = assertBatchArtifacts(overlay);
|
|
303
|
+
assertDefaultRootAbsent(overlay);
|
|
304
|
+
const sessionCalls = readLines(overlay.sessionCallCounter);
|
|
305
|
+
assert.equal(sessionCalls.length, 2, `中途 challenged 后必须停止,期望 2 次 session 调用,实际 ${sessionCalls.length}`);
|
|
306
|
+
const npxCalls = readLines(overlay.npxCounter);
|
|
307
|
+
assert.equal(npxCalls.length, 2, `comparator 调用数须等于已尝试 query 数,期望 2 次,实际 ${npxCalls.length}`);
|
|
308
|
+
assertZeroNetwork(overlay);
|
|
309
|
+
const hitRecord = readRecord(overlay, 'sf-01');
|
|
310
|
+
assert.equal(hitRecord.session.verdict, 'hit', '截断前已尝试的 query 证据不受影响');
|
|
311
|
+
const challengedRecord = readRecord(overlay, 'sf-02');
|
|
312
|
+
assert.equal(challengedRecord.session.verdict, 'challenged');
|
|
313
|
+
assert.equal(challengedRecord.doubleSource.state, 'challenge_stop');
|
|
314
|
+
assert.equal(challengedRecord.doubleSource.quota_disposition, 'no_spend_stop');
|
|
315
|
+
assert.equal(existsSync(join(overlay.evidenceRoot, 'sf-03')), false, '未尝试 query 不得产生证据');
|
|
316
|
+
const summary = artifacts.raw;
|
|
317
|
+
assert.equal(summary.total, 2);
|
|
318
|
+
assert.equal(summary.batch_complete, false);
|
|
319
|
+
assert.equal(summary.stop_reason, 'challenge_stop');
|
|
320
|
+
assert.deepEqual(summary.attempted_query_ids, ['sf-01', 'sf-02']);
|
|
321
|
+
assert.deepEqual(summary.not_attempted_query_ids, EXPECTED_QUERY_IDS.slice(2));
|
|
322
|
+
const sfSummary = runSfSummary(overlay);
|
|
323
|
+
assertRebuiltBatch(sfSummary.summary, artifacts.raw, artifacts.stem);
|
|
324
|
+
assertDefaultRootAbsent(overlay);
|
|
325
|
+
console.log('B. 中途 challenge 截断 OK(2 次调用;sf-01 hit 证据保留;sf-03 起零证据)');
|
|
326
|
+
}
|
|
327
|
+
finally {
|
|
328
|
+
cleanupOverlay(overlay);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
// ── C. 末条 challenge:八条证据齐全,仍必须 batch_complete=false 且 summary fail_closed ──
|
|
332
|
+
{
|
|
333
|
+
const overlay = scenario('last-challenge');
|
|
334
|
+
try {
|
|
335
|
+
const run = runRunner(overlay, 'flyai', 'challenge@8');
|
|
336
|
+
assert.equal(run.status, 0, `末条挑战应正常收尾(exit 0),stderr=${run.stderr}`);
|
|
337
|
+
assert.match(run.stdout, /stop reason: challenge_stop/);
|
|
338
|
+
const artifacts = assertBatchArtifacts(overlay);
|
|
339
|
+
assertDefaultRootAbsent(overlay);
|
|
340
|
+
assert.match(run.stdout, /attempted=\[sf-01,sf-02,sf-03,sf-04,sf-05,sf-06,sf-07,sf-08\]/);
|
|
341
|
+
assert.match(run.stdout, /not_attempted=\[\]/);
|
|
342
|
+
assert.equal(readLines(overlay.sessionCallCounter).length, 8, '末条 challenged 后 session 应恰调用 8 次');
|
|
343
|
+
assert.equal(readLines(overlay.npxCounter).length, 8, '末条 challenged 后 comparator 应恰调用 8 次');
|
|
344
|
+
assertZeroNetwork(overlay);
|
|
345
|
+
const challengedRecord = readRecord(overlay, 'sf-08');
|
|
346
|
+
assert.equal(challengedRecord.session.verdict, 'challenged');
|
|
347
|
+
assert.equal(challengedRecord.sessionVerdict, 'challenged');
|
|
348
|
+
assert.equal(challengedRecord.doubleSource.state, 'challenge_stop');
|
|
349
|
+
assert.equal(challengedRecord.doubleSource.quota_disposition, 'no_spend_stop');
|
|
350
|
+
const summary = artifacts.raw;
|
|
351
|
+
assert.equal(summary.total, 8);
|
|
352
|
+
assert.equal(summary.batch_complete, false);
|
|
353
|
+
assert.equal(summary.stop_reason, 'challenge_stop');
|
|
354
|
+
assert.deepEqual(summary.attempted_query_ids, EXPECTED_QUERY_IDS);
|
|
355
|
+
assert.deepEqual(summary.not_attempted_query_ids, []);
|
|
356
|
+
const sfSummary = runSfSummary(overlay);
|
|
357
|
+
assert.equal(sfSummary.status, 1, `完整证据但挑战批次必须 fail_closed,stdout=${sfSummary.stdout}`);
|
|
358
|
+
assert.equal(sfSummary.summary.status, 'fail_closed');
|
|
359
|
+
assert.equal(sfSummary.summary.total, 8);
|
|
360
|
+
assert.deepEqual(sfSummary.summary.missing_query_ids, []);
|
|
361
|
+
assert.equal(sfSummary.summary.challenge_stop_detected, true);
|
|
362
|
+
assert.ok(sfSummary.summary.errors.some((error) => error.includes('challenge/guard stop evidence')));
|
|
363
|
+
assertRebuiltBatch(sfSummary.summary, artifacts.raw, artifacts.stem);
|
|
364
|
+
assertDefaultRootAbsent(overlay);
|
|
365
|
+
console.log('C. 末条 challenge 截断 OK(8 条证据/0 未尝试/8+8 请求/fail_closed)');
|
|
366
|
+
}
|
|
367
|
+
finally {
|
|
368
|
+
cleanupOverlay(overlay);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
// ── D. 普通八条批次:全 hit → 跑满,sf-summary 仍标完整/有效校准(既有契约) ──
|
|
372
|
+
{
|
|
373
|
+
const overlay = scenario('normal-batch');
|
|
374
|
+
try {
|
|
375
|
+
const run = runRunner(overlay, 'manual', 'hit@all');
|
|
376
|
+
assert.equal(run.status, 0, `普通批次应跑满,stderr=${run.stderr}`);
|
|
377
|
+
assert.match(run.stdout, /stop reason: completed/);
|
|
378
|
+
const artifacts = assertBatchArtifacts(overlay);
|
|
379
|
+
assertDefaultRootAbsent(overlay);
|
|
380
|
+
const sessionCalls = readLines(overlay.sessionCallCounter);
|
|
381
|
+
assert.equal(sessionCalls.length, 8, `普通批次应完整跑 8 条,实际 ${sessionCalls.length}`);
|
|
382
|
+
assert.equal(readLines(overlay.npxCounter).length, 0, 'manual golden 不经 comparator 进程');
|
|
383
|
+
assertZeroNetwork(overlay);
|
|
384
|
+
const summary = artifacts.raw;
|
|
385
|
+
assert.equal(summary.total, 8);
|
|
386
|
+
assert.equal(summary.batch_complete, true);
|
|
387
|
+
assert.equal(summary.stop_reason, 'completed');
|
|
388
|
+
assert.equal(artifacts.raw.comparator_evidence_scope, 'deterministic_only');
|
|
389
|
+
assert.equal(artifacts.raw.live_provider_comparable, 0);
|
|
390
|
+
assert.deepEqual(summary.attempted_query_ids, EXPECTED_QUERY_IDS);
|
|
391
|
+
assert.deepEqual(summary.not_attempted_query_ids, []);
|
|
392
|
+
const sfSummary = runSfSummary(overlay);
|
|
393
|
+
assert.equal(sfSummary.status, 0, `完整八条批次须保持 ok 契约,stdout=${sfSummary.stdout}`);
|
|
394
|
+
assert.equal(sfSummary.summary.status, 'ok');
|
|
395
|
+
assert.equal(sfSummary.summary.status_scope, 'batch_integrity_only');
|
|
396
|
+
assert.equal(sfSummary.summary.total, 8);
|
|
397
|
+
assert.equal(sfSummary.summary.challenge_stop_detected, false);
|
|
398
|
+
assertRebuiltBatch(sfSummary.summary, artifacts.raw, artifacts.stem);
|
|
399
|
+
assertDefaultRootAbsent(overlay);
|
|
400
|
+
console.log('D. 普通八条批次保留 OK(sf-summary 仍 ok/完整)');
|
|
401
|
+
}
|
|
402
|
+
finally {
|
|
403
|
+
cleanupOverlay(overlay);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
// ── E. 默认目录兼容:仅允许写入隔离 HOME,不得触碰显式 root ──
|
|
407
|
+
{
|
|
408
|
+
const overlay = scenario('default-root-compat');
|
|
409
|
+
try {
|
|
410
|
+
const run = runRunner(overlay, 'manual', 'hit@all', 'default');
|
|
411
|
+
assert.equal(run.status, 0, `默认 root 批次应跑满,stderr=${run.stderr}`);
|
|
412
|
+
assert.match(run.stdout, /stop reason: completed/);
|
|
413
|
+
assert.equal(readLines(overlay.sessionCallCounter).length, 8, '默认路径保持完整八条查询');
|
|
414
|
+
assert.equal(readLines(overlay.npxCounter).length, 0, 'manual golden 不调用外部 comparator');
|
|
415
|
+
assertZeroNetwork(overlay);
|
|
416
|
+
const artifacts = assertBatchArtifacts(overlay, overlay.defaultEvidenceRoot);
|
|
417
|
+
assert.equal(existsSync(overlay.defaultEvidenceRoot), true);
|
|
418
|
+
assertExplicitRootAbsent(overlay);
|
|
419
|
+
const sfSummary = runSfSummary(overlay, overlay.defaultEvidenceRoot);
|
|
420
|
+
assert.equal(sfSummary.status, 0, `默认 root summary 应保持 ok,stdout=${sfSummary.stdout}`);
|
|
421
|
+
assert.equal(sfSummary.summary.status, 'ok');
|
|
422
|
+
assertRebuiltBatch(sfSummary.summary, artifacts.raw, artifacts.stem);
|
|
423
|
+
assertExplicitRootAbsent(overlay);
|
|
424
|
+
console.log('E. 默认 root 兼容 OK(仅临时 HOME/显式 root 未创建)');
|
|
425
|
+
}
|
|
426
|
+
finally {
|
|
427
|
+
cleanupOverlay(overlay);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
// ── F. 静态对照可形成可比记录,但不得贡献实时提供方可比数 ──
|
|
431
|
+
{
|
|
432
|
+
const overlay = scenario('static-scope');
|
|
433
|
+
try {
|
|
434
|
+
const run = runRunner(overlay, 'static', 'hit@all');
|
|
435
|
+
assert.equal(run.status, 0, `静态批次应跑满,stderr=${run.stderr}`);
|
|
436
|
+
assert.match(run.stdout, /comparator evidence scope = deterministic_only/);
|
|
437
|
+
const artifacts = assertBatchArtifacts(overlay);
|
|
438
|
+
assert.equal(artifacts.raw.comparator_evidence_scope, 'deterministic_only');
|
|
439
|
+
assert.equal(artifacts.raw.live_provider_comparable, 0);
|
|
440
|
+
assert.ok(artifacts.raw.records.every((record) => record.comparator_evidence_scope === 'deterministic_only'));
|
|
441
|
+
const rebuilt = runSfSummary(overlay);
|
|
442
|
+
assert.equal(rebuilt.summary.status_scope, 'batch_integrity_only');
|
|
443
|
+
assert.equal(rebuilt.summary.live_provider_comparable, 0);
|
|
444
|
+
assert.ok(rebuilt.summary.records.every((record) => record.comparator_evidence_scope === 'deterministic_only'));
|
|
445
|
+
assertZeroNetwork(overlay);
|
|
446
|
+
assertDefaultRootAbsent(overlay);
|
|
447
|
+
console.log('F. 静态对照仅计确定性证据 OK');
|
|
448
|
+
}
|
|
449
|
+
finally {
|
|
450
|
+
cleanupOverlay(overlay);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
console.log('\nSF LIVE CHALLENGE STOP TESTS: 6 scenarios OK (first-challenge / mid-challenge / last-challenge / normal-batch / default-root-compat / static-scope; offline, explicit roots, request counts and summary readback asserted)');
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { spawnSync } from 'node:child_process';
|
|
3
|
+
import { existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
const tsRoot = join(import.meta.dirname, '..');
|
|
7
|
+
const tsxCli = join(tsRoot, 'node_modules', 'tsx', 'dist', 'cli.mjs');
|
|
8
|
+
const runner = join(import.meta.dirname, 'sf-live-benchmark.ts');
|
|
9
|
+
const root = mkdtempSync(join(tmpdir(), 'gotry-sf503-cli-'));
|
|
10
|
+
const home = join(root, 'home');
|
|
11
|
+
const explicitRoot = join(root, 'explicit evidence root');
|
|
12
|
+
const defaultRoot = join(home, '.gotry', 'evidence', 'session');
|
|
13
|
+
const fetchMarker = join(root, 'fetch-attempts');
|
|
14
|
+
const fetchTrap = join(root, 'fetch-trap.mjs');
|
|
15
|
+
mkdirSync(home, { recursive: true });
|
|
16
|
+
writeFileSync(fetchTrap, `import { appendFileSync } from 'node:fs'
|
|
17
|
+
globalThis.fetch = async (...args) => {
|
|
18
|
+
appendFileSync(${JSON.stringify(fetchMarker)}, JSON.stringify(String(args[0])) + '\\n')
|
|
19
|
+
throw new Error('blocked network in sf503 CLI argument proof')
|
|
20
|
+
}
|
|
21
|
+
`);
|
|
22
|
+
function run(args) {
|
|
23
|
+
const result = spawnSync(process.execPath, [tsxCli, runner, ...args], {
|
|
24
|
+
cwd: tsRoot,
|
|
25
|
+
encoding: 'utf8',
|
|
26
|
+
timeout: 10_000,
|
|
27
|
+
env: {
|
|
28
|
+
...process.env,
|
|
29
|
+
HOME: home,
|
|
30
|
+
NODE_OPTIONS: `--import=${fetchTrap}`,
|
|
31
|
+
GOTRY_SESSION_LIVE: '0',
|
|
32
|
+
GOTRY_HBCLI_LIVE: '0',
|
|
33
|
+
GOTRY_HOTELBYTE_SKILLS_LIVE: '0',
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
return {
|
|
37
|
+
status: result.status,
|
|
38
|
+
output: `${result.stdout ?? ''}${result.stderr ?? ''}`,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function assertNoQueryOrNetwork(result, label) {
|
|
42
|
+
assert.equal(result.output.includes('[sf-live-benchmark] sf-01'), false, `${label}: fail-closed 前不得启动 query`);
|
|
43
|
+
assert.equal(existsSync(fetchMarker), false, `${label}: 参数/目标失败前不得触发网络`);
|
|
44
|
+
}
|
|
45
|
+
try {
|
|
46
|
+
for (const helpArg of ['--help', '-h']) {
|
|
47
|
+
const result = run([helpArg]);
|
|
48
|
+
assert.equal(result.status, 0, `${helpArg} 应正常退出: ${result.output}`);
|
|
49
|
+
assert.match(result.output, /Usage: sf-live-benchmark\.ts/);
|
|
50
|
+
assertNoQueryOrNetwork(result, helpArg);
|
|
51
|
+
assert.equal(existsSync(defaultRoot), false, `${helpArg} 不得创建默认 evidence root`);
|
|
52
|
+
assert.equal(existsSync(explicitRoot), false, `${helpArg} 不得创建显式 evidence root`);
|
|
53
|
+
}
|
|
54
|
+
const invalidCases = [
|
|
55
|
+
{ name: 'unknown golden', args: ['--golden=ctrip-open'], message: /不支持的 golden vendor: ctrip-open/ },
|
|
56
|
+
{ name: 'missing root value', args: ['--evidence-root'], message: /--evidence-root requires a path/ },
|
|
57
|
+
{ name: 'empty equals root', args: ['--evidence-root='], message: /--evidence-root requires a path/ },
|
|
58
|
+
{ name: 'empty split root', args: ['--evidence-root', ''], message: /--evidence-root requires a path/ },
|
|
59
|
+
{ name: 'whitespace root', args: ['--evidence-root', ' '], message: /--evidence-root requires a path/ },
|
|
60
|
+
{ name: 'next flag as root value', args: ['--evidence-root', '--golden=manual'], message: /--evidence-root requires a path/ },
|
|
61
|
+
{ name: 'duplicate root', args: ['--evidence-root', explicitRoot, '--evidence-root', join(root, 'other root')], message: /--evidence-root must be specified once/ },
|
|
62
|
+
{ name: 'duplicate golden', args: ['--golden=manual', '--golden=static'], message: /--golden must be specified once/ },
|
|
63
|
+
{ name: 'unknown argument', args: ['--definitely-invalid'], message: /unknown argument: --definitely-invalid/ },
|
|
64
|
+
{ name: 'separate golden flag', args: ['--golden'], message: /unknown argument: --golden/ },
|
|
65
|
+
];
|
|
66
|
+
for (const testCase of invalidCases) {
|
|
67
|
+
const result = run(testCase.args);
|
|
68
|
+
assert.equal(result.status, 1, `${testCase.name} 应 exit 1: ${result.output}`);
|
|
69
|
+
assert.match(result.output, testCase.message);
|
|
70
|
+
assertNoQueryOrNetwork(result, testCase.name);
|
|
71
|
+
assert.equal(existsSync(defaultRoot), false, `${testCase.name}: 不得创建默认 evidence root`);
|
|
72
|
+
assert.equal(existsSync(explicitRoot), false, `${testCase.name}: 不得提前创建显式 evidence root`);
|
|
73
|
+
assert.equal(existsSync(join(root, 'other root')), false, `${testCase.name}: 不得创建重复参数中的目录`);
|
|
74
|
+
}
|
|
75
|
+
const fileTarget = join(root, 'evidence target file');
|
|
76
|
+
writeFileSync(fileTarget, 'not a directory');
|
|
77
|
+
const filesystemCases = [
|
|
78
|
+
['file target', ['--golden=manual', '--evidence-root', fileTarget]],
|
|
79
|
+
['child under file', ['--golden=manual', '--evidence-root', join(fileTarget, 'child')]],
|
|
80
|
+
];
|
|
81
|
+
for (const [name, args] of filesystemCases) {
|
|
82
|
+
const result = run([...args]);
|
|
83
|
+
assert.equal(result.status, 1, `${name} 应在 supplier/search 前失败: ${result.output}`);
|
|
84
|
+
assertNoQueryOrNetwork(result, name);
|
|
85
|
+
assert.equal(existsSync(defaultRoot), false, `${name}: 不得回落写入默认 evidence root`);
|
|
86
|
+
}
|
|
87
|
+
console.log('SF LIVE CLI TESTS: 14 cases OK (2 help, 10 invalid arguments, 2 filesystem failures; no query output or fetch attempts)');
|
|
88
|
+
}
|
|
89
|
+
finally {
|
|
90
|
+
rmSync(root, { recursive: true, force: true });
|
|
91
|
+
assert.equal(existsSync(root), false, 'CLI argument test root must be cleaned up');
|
|
92
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { homedir } from 'node:os';
|
|
2
|
+
import { join, resolve } from 'node:path';
|
|
3
|
+
import { parseGoldenSource } from '../capabilities/session/static-flight-golden.js';
|
|
4
|
+
export const SF_LIVE_HELP = `Usage: sf-live-benchmark.ts [--golden=manual|static|flyai] [--evidence-root PATH]
|
|
5
|
+
|
|
6
|
+
--evidence-root PATH Write query records and the batch summary to this directory.
|
|
7
|
+
Also accepts --evidence-root=PATH; relative paths use the current directory.
|
|
8
|
+
Default: ~/.gotry/evidence/session. Automated tests must use an isolated root.
|
|
9
|
+
--golden=SOURCE Comparator source (default: manual). Session searches still use live access.
|
|
10
|
+
--help, -h Show this help without starting searches or writing evidence.
|
|
11
|
+
|
|
12
|
+
Rebuild a batch with: sf-summary.ts --evidence-root PATH
|
|
13
|
+
`;
|
|
14
|
+
export function parseSfLiveOptions(args) {
|
|
15
|
+
let evidenceRoot;
|
|
16
|
+
let goldenSeen = false;
|
|
17
|
+
let help = false;
|
|
18
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
19
|
+
const arg = args[index];
|
|
20
|
+
if (arg === '--help' || arg === '-h') {
|
|
21
|
+
help = true;
|
|
22
|
+
}
|
|
23
|
+
else if (arg === '--evidence-root' || arg.startsWith('--evidence-root=')) {
|
|
24
|
+
if (evidenceRoot !== undefined)
|
|
25
|
+
throw new Error('--evidence-root must be specified once');
|
|
26
|
+
const value = arg === '--evidence-root' ? args[++index] : arg.slice('--evidence-root='.length);
|
|
27
|
+
if (value === undefined || value.trim().length === 0 || value.startsWith('-')) {
|
|
28
|
+
throw new Error('--evidence-root requires a path');
|
|
29
|
+
}
|
|
30
|
+
evidenceRoot = value;
|
|
31
|
+
}
|
|
32
|
+
else if (arg.startsWith('--golden=')) {
|
|
33
|
+
if (goldenSeen)
|
|
34
|
+
throw new Error('--golden must be specified once');
|
|
35
|
+
goldenSeen = true;
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
throw new Error(`unknown argument: ${arg}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
evidenceRoot: resolve(evidenceRoot ?? join(homedir(), '.gotry', 'evidence', 'session')),
|
|
43
|
+
source: parseGoldenSource(args),
|
|
44
|
+
help,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { validateSfManifest } from './sf-manifest.js';
|
|
5
|
+
const dataRoot = join(import.meta.dirname, '..', 'data');
|
|
6
|
+
const queries = JSON.parse(readFileSync(join(dataRoot, 'session-golden-20.json'), 'utf8'));
|
|
7
|
+
const comparator = JSON.parse(readFileSync(join(dataRoot, 'sf-golden-manifest.json'), 'utf8'));
|
|
8
|
+
const expectedIds = Array.from({ length: 8 }, (_, i) => `sf-${String(i + 1).padStart(2, '0')}`);
|
|
9
|
+
const frozen = validateSfManifest(queries, comparator);
|
|
10
|
+
assert.deepEqual(frozen.map((q) => q.id), expectedIds);
|
|
11
|
+
assert.equal(comparator.query_manifest_sha256, '3be197830e5a6aa6e1309684a1fce4a736ad2a845c67b6f2f1b8a4ad3ccf977a');
|
|
12
|
+
function changedQuery(mutator, reason) {
|
|
13
|
+
const copy = structuredClone(queries);
|
|
14
|
+
mutator(copy);
|
|
15
|
+
assert.throws(() => validateSfManifest(copy, comparator), reason);
|
|
16
|
+
}
|
|
17
|
+
changedQuery((copy) => { copy.queries[0].to = '昆明'; }, /frozen query digest/);
|
|
18
|
+
changedQuery((copy) => { copy.queries.splice(7, 1); }, /sf-01..08/);
|
|
19
|
+
changedQuery((copy) => { copy.queries.splice(8, 0, { id: 'sf-09', kind: 'flight', from: '上海', to: '北京', date: '2026-12-01' }); }, /sf-01..08/);
|
|
20
|
+
changedQuery((copy) => { [copy.queries[0], copy.queries[1]] = [copy.queries[1], copy.queries[0]]; }, /sf-01..08/);
|
|
21
|
+
const wrongComparator = structuredClone(comparator);
|
|
22
|
+
wrongComparator.matches[0].to = '昆明';
|
|
23
|
+
assert.throws(() => validateSfManifest(queries, wrongComparator), /comparator query mismatch/);
|
|
24
|
+
const duplicateComparator = structuredClone(comparator);
|
|
25
|
+
duplicateComparator.matches[7].query_id = 'sf-07';
|
|
26
|
+
assert.throws(() => validateSfManifest(queries, duplicateComparator), /comparator query mismatch/);
|
|
27
|
+
const wrongDigest = { ...comparator, query_manifest_sha256: '0'.repeat(64) };
|
|
28
|
+
assert.throws(() => validateSfManifest(queries, wrongDigest), /frozen query digest/);
|
|
29
|
+
assert.throws(() => validateSfManifest(queries, { ...comparator, query_manifest_sha256: undefined }), /frozen query digest/);
|
|
30
|
+
console.log('SF MANIFEST FREEZE: eight exact queries, digest, comparator parity, and drift rejection OK');
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/** Frozen sf-01..08 live-benchmark input; no supplier or filesystem effects. */
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
const IDS = Array.from({ length: 8 }, (_, i) => `sf-${String(i + 1).padStart(2, '0')}`);
|
|
4
|
+
export function validateSfManifest(queryFile, comparatorFile) {
|
|
5
|
+
const queryRecord = queryFile;
|
|
6
|
+
const comparator = comparatorFile;
|
|
7
|
+
if (!queryRecord || !Array.isArray(queryRecord.queries))
|
|
8
|
+
throw new Error('sf-01..08 query manifest is missing');
|
|
9
|
+
if (!comparator || !Array.isArray(comparator.matches))
|
|
10
|
+
throw new Error('sf-01..08 comparator manifest is missing');
|
|
11
|
+
const flights = queryRecord.queries.filter((q) => q != null && typeof q === 'object' && typeof q.id === 'string'
|
|
12
|
+
&& q.id.startsWith('sf-'));
|
|
13
|
+
if (flights.length !== IDS.length || flights.some((q, i) => q.id !== IDS[i])) {
|
|
14
|
+
throw new Error('sf-01..08 query ids or order drifted');
|
|
15
|
+
}
|
|
16
|
+
const queries = flights.map((q) => {
|
|
17
|
+
if (q.kind !== 'flight' || typeof q.from !== 'string' || !q.from || typeof q.to !== 'string' || !q.to
|
|
18
|
+
|| typeof q.date !== 'string' || !/^\d{4}-\d{2}-\d{2}$/.test(q.date)) {
|
|
19
|
+
throw new Error(`sf-01..08 query shape drifted: ${String(q.id)}`);
|
|
20
|
+
}
|
|
21
|
+
return { id: q.id, kind: q.kind, from: q.from, to: q.to, date: q.date };
|
|
22
|
+
});
|
|
23
|
+
const digest = createHash('sha256').update(JSON.stringify(queries)).digest('hex');
|
|
24
|
+
if (comparator.query_manifest_sha256 !== digest)
|
|
25
|
+
throw new Error('sf-01..08 frozen query digest mismatch');
|
|
26
|
+
if (comparator.matches.length !== IDS.length || comparator.matches.some((m, i) => {
|
|
27
|
+
const row = m;
|
|
28
|
+
const q = queries[i];
|
|
29
|
+
return !row || row.query_id !== q.id || row.from !== q.from || row.to !== q.to || row.date !== q.date;
|
|
30
|
+
}))
|
|
31
|
+
throw new Error('sf-01..08 comparator query mismatch');
|
|
32
|
+
return queries;
|
|
33
|
+
}
|