@danceiny/gotry 0.0.1-rc.9 → 0.2.0-rc.28
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 +1788 -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 +161 -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 +553 -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 +676 -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/provider-detail-sanitize.js +64 -0
- package/dist/capabilities/resilience.js +191 -0
- package/dist/capabilities/route-provider-conformance.js +341 -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-path-measure-tests.js +133 -0
- package/dist/scripts/anything-path-measure.js +150 -0
- package/dist/scripts/anything-tests.js +250 -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 +126 -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 +1549 -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/city-scenario-tier-tests.js +234 -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 +600 -0
- package/dist/scripts/drill-multiuser-ledger-tests.js +1076 -0
- package/dist/scripts/drill-sdk-descendant-cleanup-tests.js +606 -0
- package/dist/scripts/drill-w2a-sensor-tests.js +788 -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-llm-tests.js +437 -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 +150 -0
- package/dist/scripts/g5-guard.js +193 -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/m3-cohort-tests.js +714 -0
- package/dist/scripts/m3-cohort.js +310 -0
- 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 +203 -0
- package/dist/scripts/nightly-evidence.js +303 -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/outcome-projection-tests.js +427 -0
- package/dist/scripts/persona-sim-tests.js +603 -0
- package/dist/scripts/persona-sim.js +1081 -0
- 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 +22 -18
- package/dist/scripts/replay-real.js +18 -18
- package/dist/scripts/replay.js +54 -82
- package/dist/scripts/route-provider-conformance-tests.js +391 -0
- 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-zone-crash.js +49 -0
- package/dist/scripts/session-zone-ledger-tests.js +432 -0
- package/dist/scripts/session-zone-observation-tests.js +492 -0
- package/dist/scripts/session-zone-observe.js +135 -0
- package/dist/scripts/session-zone-wiring-tests.js +566 -0
- package/dist/scripts/session-zones-tests.js +374 -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 +244 -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 +565 -198
- 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 +212 -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 +60 -20
- 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 +46 -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/city-scenario-tier.js +378 -0
- package/dist/src/companions.js +48 -74
- package/dist/src/contracts.js +21 -43
- package/dist/src/dsh-llm.js +171 -115
- 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 +2385 -881
- 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 +331 -161
- package/dist/src/m3-cohort.js +1577 -0
- 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/outcome-projection.js +746 -0
- 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-zone-ledger.js +312 -0
- package/dist/src/session-zone-observation.js +354 -0
- package/dist/src/session-zone-wiring.js +658 -0
- package/dist/src/session-zones.js +510 -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 +696 -524
- 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 +600 -360
- 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 +282 -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 +586 -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 +17 -3
- package/ts/src/dsh-llm.ts +152 -17
- package/ts/src/flyai-setup-tool.ts +77 -0
- package/ts/src/index.ts +2123 -200
- 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 +234 -26
- 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 +1166 -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 +444 -105
- package/ts/src/wish-pool.ts +79 -0
- package/dist/scripts/session-attach-wait.js +0 -41
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
/**
|
|
3
|
+
* Explicit opt-in M3 seed-cohort capture CLI (issue #22).
|
|
4
|
+
*
|
|
5
|
+
* Produces the records `scripts/product-metrics.ts` consumes. The scorer is the only
|
|
6
|
+
* component that divides anything; this CLI only records funnel facts and then hands the
|
|
7
|
+
* written evidence root straight back to the scorer for verification.
|
|
8
|
+
*
|
|
9
|
+
* The production CLI intentionally has no `--at` flag: timestamps come from the local
|
|
10
|
+
* clock. Tests inject clocks through `../src/m3-cohort.ts`.
|
|
11
|
+
*
|
|
12
|
+
* HMAC key custody: `GOTRY_M3_COHORT_HMAC_KEY` (>= 32 chars) lives outside the repository
|
|
13
|
+
* and is never printed by this CLI. Losing it makes the store unreadable by design, and
|
|
14
|
+
* `verify` needs it too — the export attestation is keyed, not a public digest.
|
|
15
|
+
*
|
|
16
|
+
* Argv exposure (accepted, documented): `--participant`, `--plan` and `--consent` carry raw
|
|
17
|
+
* labels on the command line, so they are visible to `ps` and land in shell history. Only
|
|
18
|
+
* their HMAC refs are ever persisted, but the invocation itself is not secret. For a real
|
|
19
|
+
* cohort, drive this CLI from a script whose history is not shared, or export the labels
|
|
20
|
+
* from a file the shell does not record; never pass anything you would not put in `ps`.
|
|
21
|
+
*
|
|
22
|
+
* npx tsx scripts/m3-cohort.ts init --state-root "$ROOT" --consent "$C" \
|
|
23
|
+
* --cohort m3-seed --evidence-kind real_seed_cohort \
|
|
24
|
+
* --window-start 2026-10-01T00:00:00.000Z --window-end 2026-12-31T23:59:59.000Z \
|
|
25
|
+
* --timezone Asia/Shanghai --attribution gotry_primary
|
|
26
|
+
* npx tsx scripts/m3-cohort.ts enroll ... --participant <handle> --invited --participant-consent
|
|
27
|
+
* npx tsx scripts/m3-cohort.ts deliver ... --participant <handle> --plan <handle> --attribution gotry_primary
|
|
28
|
+
* npx tsx scripts/m3-cohort.ts finalize ... --participant <handle> --plan <handle>
|
|
29
|
+
* npx tsx scripts/m3-cohort.ts nps ... --participant <handle> --plan <handle> --score 9
|
|
30
|
+
* npx tsx scripts/m3-cohort.ts poi-lock ... --participant <handle> --plan <handle> --locked-claims 12 --invalid-claims 0
|
|
31
|
+
* npx tsx scripts/m3-cohort.ts export ... --evidence-root <dir>
|
|
32
|
+
* npx tsx scripts/m3-cohort.ts verify --evidence-root <dir> # needs the HMAC key
|
|
33
|
+
* npx tsx scripts/m3-cohort.ts lock-status --state-root "$ROOT" # read-only
|
|
34
|
+
* npx tsx scripts/m3-cohort.ts unlock --state-root "$ROOT" # dead writer only
|
|
35
|
+
*/
|
|
36
|
+
import { resolve } from 'node:path';
|
|
37
|
+
import { pathToFileURL } from 'node:url';
|
|
38
|
+
import { enrollParticipant, exportM3Cohort, initM3Cohort, inspectStoreLock, M3CohortError, M3_SIMULATION_PROVENANCE_SCHEMA, recordNps, recordPlanDelivered, recordPlanFinalized, recordPoiLock, unlockStore, verifyExportedEvidence, } from '../src/m3-cohort.js';
|
|
39
|
+
import { parseCohortRecord, parseManifest, scoreProductMetrics } from './product-metrics.js';
|
|
40
|
+
const ERROR_CODES = [
|
|
41
|
+
'bad_args',
|
|
42
|
+
'cohort_already_initialized',
|
|
43
|
+
'cohort_consent_mismatch',
|
|
44
|
+
'cohort_key_mismatch',
|
|
45
|
+
'cohort_not_initialized',
|
|
46
|
+
'evidence_kind_mismatch',
|
|
47
|
+
'evidence_root_conflict',
|
|
48
|
+
'export_validation_failed',
|
|
49
|
+
'internal_error',
|
|
50
|
+
'invalid_hmac_key',
|
|
51
|
+
'invalid_input',
|
|
52
|
+
'invalid_store',
|
|
53
|
+
'invalid_transition',
|
|
54
|
+
'lock_busy',
|
|
55
|
+
'missing_consent',
|
|
56
|
+
'missing_hmac_key',
|
|
57
|
+
'missing_state_root',
|
|
58
|
+
'output_exists',
|
|
59
|
+
'simulation_forbidden_in_real_cohort',
|
|
60
|
+
'simulation_label_required',
|
|
61
|
+
'unsafe_state_root',
|
|
62
|
+
];
|
|
63
|
+
function parseFlags(args) {
|
|
64
|
+
const [command, ...rest] = args;
|
|
65
|
+
if (!command)
|
|
66
|
+
throw new M3CohortError('bad_args');
|
|
67
|
+
const flags = {};
|
|
68
|
+
for (let index = 0; index < rest.length; index += 1) {
|
|
69
|
+
const token = rest[index];
|
|
70
|
+
if (!token.startsWith('--'))
|
|
71
|
+
throw new M3CohortError('bad_args');
|
|
72
|
+
const key = token.slice(2);
|
|
73
|
+
if (!key)
|
|
74
|
+
throw new M3CohortError('bad_args');
|
|
75
|
+
const next = rest[index + 1];
|
|
76
|
+
const value = next === undefined || next.startsWith('--') ? true : (index += 1, next);
|
|
77
|
+
if (key in flags) {
|
|
78
|
+
const current = flags[key];
|
|
79
|
+
flags[key] = Array.isArray(current) ? [...current, value] : [current, value];
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
flags[key] = value;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return { command, flags };
|
|
86
|
+
}
|
|
87
|
+
function one(flags, key, required = true) {
|
|
88
|
+
const value = flags[key];
|
|
89
|
+
if (Array.isArray(value) || value === true)
|
|
90
|
+
throw new M3CohortError('bad_args');
|
|
91
|
+
if (value === undefined && required)
|
|
92
|
+
throw new M3CohortError(key === 'state-root' ? 'missing_state_root' : 'invalid_input');
|
|
93
|
+
return value;
|
|
94
|
+
}
|
|
95
|
+
function many(flags, key) {
|
|
96
|
+
const value = flags[key];
|
|
97
|
+
if (value === undefined)
|
|
98
|
+
return [];
|
|
99
|
+
if (value === true)
|
|
100
|
+
throw new M3CohortError('bad_args');
|
|
101
|
+
const list = Array.isArray(value) ? value : [value];
|
|
102
|
+
return list.map(item => {
|
|
103
|
+
if (item === true)
|
|
104
|
+
throw new M3CohortError('bad_args');
|
|
105
|
+
return item;
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
function boolFlag(flags, key) {
|
|
109
|
+
const value = flags[key];
|
|
110
|
+
if (value === undefined)
|
|
111
|
+
return false;
|
|
112
|
+
if (value !== true)
|
|
113
|
+
throw new M3CohortError('bad_args');
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
function exclusiveBool(flags, trueKey, falseKey) {
|
|
117
|
+
const positive = boolFlag(flags, trueKey);
|
|
118
|
+
const negative = boolFlag(flags, falseKey);
|
|
119
|
+
if (positive === negative)
|
|
120
|
+
throw new M3CohortError('invalid_input');
|
|
121
|
+
return positive;
|
|
122
|
+
}
|
|
123
|
+
function integerFlag(flags, key) {
|
|
124
|
+
const raw = one(flags, key);
|
|
125
|
+
if (!/^\d{1,9}$/.test(raw))
|
|
126
|
+
throw new M3CohortError('invalid_input');
|
|
127
|
+
return Number(raw);
|
|
128
|
+
}
|
|
129
|
+
function common(flags) {
|
|
130
|
+
const stateRoot = one(flags, 'state-root');
|
|
131
|
+
const consent = one(flags, 'consent', false);
|
|
132
|
+
if (!consent)
|
|
133
|
+
throw new M3CohortError('missing_consent');
|
|
134
|
+
return { stateRoot: stateRoot, consent, hmacKey: process.env['GOTRY_M3_COHORT_HMAC_KEY'] };
|
|
135
|
+
}
|
|
136
|
+
function ensureNoUnknownFlags(flags, allowed) {
|
|
137
|
+
const allowedSet = new Set(allowed);
|
|
138
|
+
for (const key of Object.keys(flags)) {
|
|
139
|
+
if (!allowedSet.has(key))
|
|
140
|
+
throw new M3CohortError('bad_args');
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
function attributionFlag(flags) {
|
|
144
|
+
const value = one(flags, 'attribution');
|
|
145
|
+
if (value !== 'gotry_primary' && value !== 'gotry_assisted')
|
|
146
|
+
throw new M3CohortError('invalid_input');
|
|
147
|
+
return value;
|
|
148
|
+
}
|
|
149
|
+
function attributionList(flags) {
|
|
150
|
+
const values = many(flags, 'attribution');
|
|
151
|
+
if (values.length === 0)
|
|
152
|
+
throw new M3CohortError('invalid_input');
|
|
153
|
+
return values.map(value => {
|
|
154
|
+
if (value !== 'gotry_primary' && value !== 'gotry_assisted')
|
|
155
|
+
throw new M3CohortError('invalid_input');
|
|
156
|
+
return value;
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
function simulationFlags(flags) {
|
|
160
|
+
const simulated = boolFlag(flags, 'simulated');
|
|
161
|
+
const personaId = one(flags, 'persona-id', false);
|
|
162
|
+
const productModel = one(flags, 'product-model', false);
|
|
163
|
+
const personaModel = one(flags, 'persona-model', false);
|
|
164
|
+
const promptDigest = one(flags, 'prompt-digest', false);
|
|
165
|
+
const provided = [personaId, productModel, personaModel, promptDigest];
|
|
166
|
+
if (!simulated) {
|
|
167
|
+
if (provided.some(value => value !== undefined))
|
|
168
|
+
throw new M3CohortError('invalid_input');
|
|
169
|
+
return undefined;
|
|
170
|
+
}
|
|
171
|
+
if (provided.some(value => value === undefined))
|
|
172
|
+
throw new M3CohortError('invalid_input');
|
|
173
|
+
return {
|
|
174
|
+
schema_version: M3_SIMULATION_PROVENANCE_SCHEMA,
|
|
175
|
+
kind: 'llm_persona',
|
|
176
|
+
persona_id: personaId,
|
|
177
|
+
product_model: productModel,
|
|
178
|
+
persona_model: personaModel,
|
|
179
|
+
prompt_digest: promptDigest,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
function output(value) {
|
|
183
|
+
process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
|
|
184
|
+
}
|
|
185
|
+
/** Consumer-side verification: the exact parsers and scorer the M3 gate uses. */
|
|
186
|
+
export function scorerValidator(payload) {
|
|
187
|
+
const manifest = parseManifest(JSON.parse(JSON.stringify(payload.manifest)));
|
|
188
|
+
const cohort = payload.cohort.map((record, index) => parseCohortRecord(JSON.parse(JSON.stringify(record)), index));
|
|
189
|
+
const summary = scoreProductMetrics(manifest, cohort, []);
|
|
190
|
+
if (manifest.evidence_kind === 'synthetic_fixture' && summary.business_pass) {
|
|
191
|
+
throw new M3CohortError('export_validation_failed');
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
function run(command, flags) {
|
|
195
|
+
switch (command) {
|
|
196
|
+
case 'init': {
|
|
197
|
+
ensureNoUnknownFlags(flags, ['state-root', 'consent', 'cohort', 'evidence-kind', 'window-start', 'window-end', 'timezone', 'attribution']);
|
|
198
|
+
const evidenceKind = one(flags, 'evidence-kind');
|
|
199
|
+
if (evidenceKind !== 'real_seed_cohort' && evidenceKind !== 'synthetic_fixture')
|
|
200
|
+
throw new M3CohortError('invalid_input');
|
|
201
|
+
output(initM3Cohort({
|
|
202
|
+
...common(flags),
|
|
203
|
+
cohort: one(flags, 'cohort'),
|
|
204
|
+
evidenceKind,
|
|
205
|
+
windowStartAt: one(flags, 'window-start'),
|
|
206
|
+
windowEndAt: one(flags, 'window-end'),
|
|
207
|
+
timezone: one(flags, 'timezone'),
|
|
208
|
+
allowedAttribution: attributionList(flags),
|
|
209
|
+
}));
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
case 'enroll': {
|
|
213
|
+
ensureNoUnknownFlags(flags, [
|
|
214
|
+
'state-root', 'consent', 'participant', 'invited', 'not-invited', 'participant-consent',
|
|
215
|
+
'participant-consent-withdrawn', 'test-or-staff', 'simulated', 'persona-id', 'product-model',
|
|
216
|
+
'persona-model', 'prompt-digest',
|
|
217
|
+
]);
|
|
218
|
+
output(enrollParticipant({
|
|
219
|
+
...common(flags),
|
|
220
|
+
participant: one(flags, 'participant'),
|
|
221
|
+
invited: exclusiveBool(flags, 'invited', 'not-invited'),
|
|
222
|
+
participantConsent: exclusiveBool(flags, 'participant-consent', 'participant-consent-withdrawn'),
|
|
223
|
+
testOrStaff: boolFlag(flags, 'test-or-staff'),
|
|
224
|
+
simulation: simulationFlags(flags),
|
|
225
|
+
}));
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
case 'deliver': {
|
|
229
|
+
ensureNoUnknownFlags(flags, ['state-root', 'consent', 'participant', 'plan', 'attribution']);
|
|
230
|
+
output(recordPlanDelivered({
|
|
231
|
+
...common(flags),
|
|
232
|
+
participant: one(flags, 'participant'),
|
|
233
|
+
plan: one(flags, 'plan'),
|
|
234
|
+
attribution: attributionFlag(flags),
|
|
235
|
+
}));
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
case 'finalize': {
|
|
239
|
+
ensureNoUnknownFlags(flags, ['state-root', 'consent', 'participant', 'plan']);
|
|
240
|
+
output(recordPlanFinalized({ ...common(flags), participant: one(flags, 'participant'), plan: one(flags, 'plan') }));
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
case 'nps': {
|
|
244
|
+
ensureNoUnknownFlags(flags, ['state-root', 'consent', 'participant', 'plan', 'score']);
|
|
245
|
+
output(recordNps({
|
|
246
|
+
...common(flags),
|
|
247
|
+
participant: one(flags, 'participant'),
|
|
248
|
+
plan: one(flags, 'plan'),
|
|
249
|
+
score: integerFlag(flags, 'score'),
|
|
250
|
+
}));
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
case 'poi-lock': {
|
|
254
|
+
ensureNoUnknownFlags(flags, ['state-root', 'consent', 'participant', 'plan', 'locked-claims', 'invalid-claims']);
|
|
255
|
+
output(recordPoiLock({
|
|
256
|
+
...common(flags),
|
|
257
|
+
participant: one(flags, 'participant'),
|
|
258
|
+
plan: one(flags, 'plan'),
|
|
259
|
+
lockedClaims: integerFlag(flags, 'locked-claims'),
|
|
260
|
+
invalidClaims: integerFlag(flags, 'invalid-claims'),
|
|
261
|
+
}));
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
case 'export': {
|
|
265
|
+
ensureNoUnknownFlags(flags, ['state-root', 'consent', 'evidence-root']);
|
|
266
|
+
const evidenceRoot = one(flags, 'evidence-root');
|
|
267
|
+
const shared = common(flags);
|
|
268
|
+
const { payload, result } = exportM3Cohort({ ...shared, evidenceRoot, validate: scorerValidator });
|
|
269
|
+
// Verify the bytes that actually landed: re-read the evidence root through the real
|
|
270
|
+
// scorer parsers plus the keyed attestation check.
|
|
271
|
+
const manifest = parseManifest(JSON.parse(JSON.stringify(payload.manifest)));
|
|
272
|
+
const cohort = payload.cohort.map((record, index) => parseCohortRecord(JSON.parse(JSON.stringify(record)), index));
|
|
273
|
+
const summary = scoreProductMetrics(manifest, cohort, []);
|
|
274
|
+
const verification = verifyExportedEvidence(evidenceRoot, shared.hmacKey);
|
|
275
|
+
output({ ...result, verification, scorer_summary: summary });
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
case 'verify': {
|
|
279
|
+
ensureNoUnknownFlags(flags, ['evidence-root']);
|
|
280
|
+
output(verifyExportedEvidence(one(flags, 'evidence-root'), process.env['GOTRY_M3_COHORT_HMAC_KEY']));
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
case 'lock-status': {
|
|
284
|
+
ensureNoUnknownFlags(flags, ['state-root']);
|
|
285
|
+
output(inspectStoreLock(one(flags, 'state-root')));
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
case 'unlock': {
|
|
289
|
+
ensureNoUnknownFlags(flags, ['state-root']);
|
|
290
|
+
output(unlockStore(one(flags, 'state-root')));
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
default:
|
|
294
|
+
throw new M3CohortError('bad_args');
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
function main() {
|
|
298
|
+
try {
|
|
299
|
+
const { command, flags } = parseFlags(process.argv.slice(2));
|
|
300
|
+
run(command, flags);
|
|
301
|
+
}
|
|
302
|
+
catch (error) {
|
|
303
|
+
const code = error instanceof M3CohortError && ERROR_CODES.includes(error.code) ? error.code : 'internal_error';
|
|
304
|
+
process.stderr.write(`m3_cohort_error:${code}\n`);
|
|
305
|
+
process.exitCode = 2;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
const invokedPath = process.argv[1] ? pathToFileURL(resolve(process.argv[1])).href : '';
|
|
309
|
+
if (import.meta.url === invokedPath)
|
|
310
|
+
main();
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { ManagedDshRunPort } from '../src/booking-surface/managed-dsh-run-port.js';
|
|
5
|
+
import { ManagedDshCleanupError, managedDshGroupIsQuiescent, parseManagedDshGroupSnapshot, readManagedDshWorkerStart, snapshotManagedDshGroup, } from '../src/booking-surface/managed-dsh-cleanup-diagnostic.js';
|
|
6
|
+
const workerPath = fileURLToPath(new URL('./fixtures/managed-dsh-worker-fixture.mjs', import.meta.url));
|
|
7
|
+
async function proveFailedObservation(kind) {
|
|
8
|
+
const port = new ManagedDshRunPort({ workerPath, graceMs: 50, cleanupRole: kind === 'timeout' ? 'task' : 'warmer' });
|
|
9
|
+
// This seam injects an observation failure, not a real process-cleanup bug.
|
|
10
|
+
// Launch and termination still use the installed runtime and real child tree.
|
|
11
|
+
const handle = port.handle;
|
|
12
|
+
const waitForExit = handle.waitForExit.bind(handle);
|
|
13
|
+
try {
|
|
14
|
+
const result = await port.run('fixture', { sessionId: 'cleanup-diagnostic-proof' });
|
|
15
|
+
const descendant = result.events[0].grandchildPid;
|
|
16
|
+
handle.waitForExit = async () => {
|
|
17
|
+
if (kind === 'rejected')
|
|
18
|
+
throw new Error('PRIVATE_PROVIDER_TOKEN_DO_NOT_EXPOSE');
|
|
19
|
+
return false;
|
|
20
|
+
};
|
|
21
|
+
const closing = port.close();
|
|
22
|
+
assert.strictEqual(port.close(), closing, 'failure remains sticky for every close caller');
|
|
23
|
+
const error = await closing.then(() => undefined, (reason) => reason);
|
|
24
|
+
assert.ok(error instanceof ManagedDshCleanupError, 'failure carries a typed safe diagnostic');
|
|
25
|
+
const diagnostic = error.diagnostic;
|
|
26
|
+
assert.equal(diagnostic.role, kind === 'timeout' ? 'task' : 'warmer');
|
|
27
|
+
assert.equal(diagnostic.schemaVersion, 'managed-dsh-cleanup.v2');
|
|
28
|
+
if (process.platform === 'darwin')
|
|
29
|
+
assert.ok(Number.isSafeInteger(diagnostic.workerPid) && diagnostic.workerPid > 0, 'direct fallback receipt identifies the worker');
|
|
30
|
+
assert.equal(diagnostic.deadlineMs, 1_000, 'existing minimum teardown deadline is preserved');
|
|
31
|
+
assert.equal(diagnostic.graceMs, 50);
|
|
32
|
+
assert.ok(diagnostic.elapsedMs >= 0);
|
|
33
|
+
assert.ok(['pending', 'exited', 'failed'].includes(diagnostic.workerOutcome.state));
|
|
34
|
+
assert.equal(diagnostic.processGroup.expectedPgid, process.platform === 'darwin' ? diagnostic.workerPid : null);
|
|
35
|
+
assert.equal(JSON.stringify(error).includes('PRIVATE_'), false, 'provider observation errors are not forwarded');
|
|
36
|
+
const log = spawnSync(process.execPath, ['--input-type=module', '-e',
|
|
37
|
+
'let error = new Error(process.argv[1]); for (let i = 0; i < 3; i++) error = new AggregateError([error], "nested cleanup"); throw error',
|
|
38
|
+
error.message], { encoding: 'utf8', timeout: 5_000 });
|
|
39
|
+
assert.equal(log.status, 1, 'nested uncaught error child fails as intended');
|
|
40
|
+
assert.ok(log.stderr.includes(`diagnostic=${JSON.stringify(diagnostic)}`), 'default nested error log retains every safe diagnostic value');
|
|
41
|
+
assert.equal(log.stderr.includes('PRIVATE_'), false, 'nested log does not expose provider errors');
|
|
42
|
+
for (const member of diagnostic.processGroup.members) {
|
|
43
|
+
assert.equal(member.pgid, diagnostic.workerPid, 'only the owned process group is returned');
|
|
44
|
+
assert.deepEqual(Object.keys(member).sort(), ['pgid', 'pid', 'ppid', 'startedAt', 'state', 'zombie']);
|
|
45
|
+
assert.equal(member.zombie, member.state.startsWith('Z'), 'zombie flag matches the observed state letter');
|
|
46
|
+
}
|
|
47
|
+
if (kind === 'timeout' && diagnostic.processGroup.status === 'observed') {
|
|
48
|
+
// Right after terminate() the real fixture tree still holds live members,
|
|
49
|
+
// so the safe snapshot must classify it as not quiescent.
|
|
50
|
+
assert.equal(diagnostic.processGroup.classification, 'has-live-members');
|
|
51
|
+
assert.ok(diagnostic.processGroup.liveMemberCount >= 1);
|
|
52
|
+
assert.equal(managedDshGroupIsQuiescent(diagnostic.processGroup), false);
|
|
53
|
+
}
|
|
54
|
+
assert.equal(await waitForExit(AbortSignal.timeout(2_000)), true, 'actual runtime still reaps the worker tree');
|
|
55
|
+
if (process.platform !== 'win32')
|
|
56
|
+
assert.throws(() => process.kill(descendant, 0), 'grandchild is reaped');
|
|
57
|
+
console.log(`MANAGED DSH CLEANUP DIAGNOSTIC: ${kind} observation preserves ${diagnostic.role} identity and actual tree teardown`);
|
|
58
|
+
}
|
|
59
|
+
finally {
|
|
60
|
+
handle.waitForExit = waitForExit;
|
|
61
|
+
handle.terminate();
|
|
62
|
+
assert.equal(await waitForExit(AbortSignal.timeout(2_000)), true, 'proof cleans its own processes even after an assertion failure');
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/** Classify crafted ps tables without signalling anything. */
|
|
66
|
+
async function proveSyntheticClassification() {
|
|
67
|
+
const anchor = 'Sun Sep 20 19:59:16 2026';
|
|
68
|
+
const zombieOnly = parseManagedDshGroupSnapshot(' 4242 1 4242 Z Sun Sep 20 19:59:16 2026', 4242, anchor);
|
|
69
|
+
assert.equal(zombieOnly.status, 'observed');
|
|
70
|
+
assert.equal(zombieOnly.classification, 'zombie-only');
|
|
71
|
+
assert.deepEqual([zombieOnly.liveMemberCount, zombieOnly.zombieMemberCount], [0, 1]);
|
|
72
|
+
assert.equal(zombieOnly.workerIdentity, 'match');
|
|
73
|
+
assert.equal(managedDshGroupIsQuiescent(zombieOnly), true, 'a group holding only un-reaped zombies is quiescent');
|
|
74
|
+
const reused = parseManagedDshGroupSnapshot(' 4242 1 4242 Z Mon Dec 1 08:00:00 2025', 4242, anchor);
|
|
75
|
+
assert.equal(reused.workerIdentity, 'mismatch', 'a start time that differs from the live anchor proves PID reuse');
|
|
76
|
+
const live = parseManagedDshGroupSnapshot(' 4242 1 4242 Ss Sun Sep 20 19:59:16 2026\n 4243 4242 4242 R Sun Sep 20 19:59:17 2026', 4242, anchor);
|
|
77
|
+
assert.equal(live.classification, 'has-live-members');
|
|
78
|
+
assert.deepEqual([live.liveMemberCount, live.zombieMemberCount], [2, 0]);
|
|
79
|
+
assert.equal(live.workerIdentity, 'match');
|
|
80
|
+
assert.equal(managedDshGroupIsQuiescent(live), false);
|
|
81
|
+
const mixed = parseManagedDshGroupSnapshot(' 4242 1 4242 Z Sun Sep 20 19:59:16 2026\n 4243 4242 4242 U Sun Sep 20 19:59:17 2026', 4242, null);
|
|
82
|
+
assert.equal(mixed.classification, 'has-live-members', 'an uninterruptible member is live, never zombie');
|
|
83
|
+
assert.equal(mixed.workerIdentity, 'unknown', 'without a live anchor identity stays unknown');
|
|
84
|
+
const empty = parseManagedDshGroupSnapshot('', 4242, anchor);
|
|
85
|
+
assert.equal(empty.classification, 'empty');
|
|
86
|
+
assert.equal(managedDshGroupIsQuiescent(empty), true);
|
|
87
|
+
assert.equal(parseManagedDshGroupSnapshot('4243 1 999 Z Sun Sep 20 19:59:16 2026', 4242).classification, 'empty', 'foreign groups are excluded');
|
|
88
|
+
console.log('MANAGED DSH CLEANUP DIAGNOSTIC: synthetic tables classify zombie-only, live, uninterruptible, empty and reused identities');
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* A live process that never reaps its child keeps a real zombie inside its own
|
|
92
|
+
* process group for as long as it lives: `sh -c 'sleep 0.3 & exec sleep 600'`
|
|
93
|
+
* leaves the exited background job owned by the exec'd sleep.
|
|
94
|
+
*/
|
|
95
|
+
async function proveRealGroupClassification() {
|
|
96
|
+
if (process.platform !== 'darwin') {
|
|
97
|
+
console.log('MANAGED DSH CLEANUP DIAGNOSTIC: real group classification is Darwin-only, skipped');
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
const holder = spawn('/bin/sh', ['-c', 'sleep 0.3 & exec sleep 600'], { detached: true, stdio: 'ignore' });
|
|
101
|
+
try {
|
|
102
|
+
const anchor = await readManagedDshWorkerStart(holder.pid);
|
|
103
|
+
assert.ok(anchor, 'live holder reports its start-time anchor');
|
|
104
|
+
const deadline = Date.now() + 2_000;
|
|
105
|
+
let group;
|
|
106
|
+
while (Date.now() < deadline) {
|
|
107
|
+
group = await snapshotManagedDshGroup(holder.pid, anchor);
|
|
108
|
+
if (group.status === 'observed' && group.zombieMemberCount >= 1)
|
|
109
|
+
break;
|
|
110
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
111
|
+
}
|
|
112
|
+
assert.equal(group.status, 'observed');
|
|
113
|
+
assert.equal(group.classification, 'has-live-members', 'a live member holding a zombie is not zombie-only');
|
|
114
|
+
assert.ok(group.liveMemberCount >= 1 && group.zombieMemberCount >= 1, 'the real group holds one live holder and one real zombie');
|
|
115
|
+
for (const member of group.members) {
|
|
116
|
+
assert.ok(member.startedAt, 'every real member carries a start time');
|
|
117
|
+
assert.equal(member.zombie, member.state.startsWith('Z'));
|
|
118
|
+
}
|
|
119
|
+
assert.equal(group.workerIdentity, 'match', 'the live anchor matches the observed holder identity');
|
|
120
|
+
assert.equal(managedDshGroupIsQuiescent(group), false);
|
|
121
|
+
console.log(`MANAGED DSH CLEANUP DIAGNOSTIC: real group ${holder.pid} classified with live holder and real zombie member`);
|
|
122
|
+
}
|
|
123
|
+
finally {
|
|
124
|
+
try {
|
|
125
|
+
process.kill(-holder.pid, 'SIGKILL');
|
|
126
|
+
}
|
|
127
|
+
catch { /* already gone */ }
|
|
128
|
+
const reaped = Date.now() + 2_000;
|
|
129
|
+
while (Date.now() < reaped) {
|
|
130
|
+
try {
|
|
131
|
+
process.kill(-holder.pid, 0);
|
|
132
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
/** The close verdict: an observation deadline plus a quiescent group is a successful cleanup, not a timeout. */
|
|
141
|
+
async function proveCloseVerdict(classification) {
|
|
142
|
+
let observedPgid = null;
|
|
143
|
+
const port = new ManagedDshRunPort({
|
|
144
|
+
workerPath, graceMs: 50, cleanupRole: 'task',
|
|
145
|
+
groupObserver: async (pid) => {
|
|
146
|
+
observedPgid = pid;
|
|
147
|
+
const table = classification === 'empty' ? '' : ` ${pid} 1 ${pid} ${classification === 'zombie-only' ? 'Z' : 'Ss'} Sun Sep 20 19:59:16 2026`;
|
|
148
|
+
return parseManagedDshGroupSnapshot(table, pid, null);
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
const handle = port.handle;
|
|
152
|
+
const waitForExit = handle.waitForExit.bind(handle);
|
|
153
|
+
try {
|
|
154
|
+
await port.run('fixture', { sessionId: 'cleanup-verdict-proof' });
|
|
155
|
+
handle.waitForExit = async () => false;
|
|
156
|
+
const closing = port.close();
|
|
157
|
+
if (classification === 'has-live-members') {
|
|
158
|
+
const error = await closing.then(() => undefined, (reason) => reason);
|
|
159
|
+
assert.ok(error instanceof ManagedDshCleanupError, 'a live member still converts the deadline into a failure');
|
|
160
|
+
assert.equal(error.message.split(';')[0], 'managed DSH process tree cleanup timeout');
|
|
161
|
+
assert.equal(error.diagnostic.processGroup.classification, 'has-live-members');
|
|
162
|
+
assert.equal(error.diagnostic.processGroup.liveMemberCount, 1);
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
await closing;
|
|
166
|
+
}
|
|
167
|
+
assert.ok(Number.isInteger(observedPgid) && observedPgid > 0, 'the verdict observer receives the owned worker identity');
|
|
168
|
+
assert.equal(await waitForExit(AbortSignal.timeout(2_000)), true, 'the real runtime still reaps the tree in every verdict case');
|
|
169
|
+
}
|
|
170
|
+
finally {
|
|
171
|
+
handle.waitForExit = waitForExit;
|
|
172
|
+
handle.terminate();
|
|
173
|
+
assert.equal(await waitForExit(AbortSignal.timeout(2_000)), true, 'proof cleans its own processes even after an assertion failure');
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
await proveFailedObservation('timeout');
|
|
177
|
+
await proveFailedObservation('rejected');
|
|
178
|
+
await proveSyntheticClassification();
|
|
179
|
+
await proveRealGroupClassification();
|
|
180
|
+
await proveCloseVerdict('zombie-only');
|
|
181
|
+
await proveCloseVerdict('empty');
|
|
182
|
+
await proveCloseVerdict('has-live-members');
|
|
183
|
+
assert.equal((await snapshotManagedDshGroup(null)).status, 'unknown_worker');
|
|
184
|
+
console.log('MANAGED DSH CLEANUP DIAGNOSTIC PROOF: observation failures, zombie classification, identity anchors and verdict-by-quiescence OK');
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { spawn as nodeSpawn } from 'node:child_process';
|
|
3
|
+
import { mkdtempSync, rmSync } from 'node:fs';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
import LocalSubprocessRuntime from '@deepseek-ai/dsh-subprocess-local';
|
|
8
|
+
import { ManagedDshRunPort } from '../src/booking-surface/managed-dsh-run-port.js';
|
|
9
|
+
import { ManagedDshCleanupError } from '../src/booking-surface/managed-dsh-cleanup-diagnostic.js';
|
|
10
|
+
const workerPath = fileURLToPath(new URL('./fixtures/managed-dsh-pending-worker-fixture.mjs', import.meta.url));
|
|
11
|
+
function observe(promise) {
|
|
12
|
+
return promise.then((value) => ({ status: 'fulfilled', value }), (reason) => ({ status: 'rejected', reason }));
|
|
13
|
+
}
|
|
14
|
+
async function bounded(promise, timeoutMs, label) {
|
|
15
|
+
let timer;
|
|
16
|
+
try {
|
|
17
|
+
return await Promise.race([
|
|
18
|
+
promise,
|
|
19
|
+
new Promise((_, reject) => {
|
|
20
|
+
timer = setTimeout(() => reject(new Error(label)), timeoutMs);
|
|
21
|
+
}),
|
|
22
|
+
]);
|
|
23
|
+
}
|
|
24
|
+
finally {
|
|
25
|
+
clearTimeout(timer);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function reasonMessage(reason) {
|
|
29
|
+
return reason instanceof Error ? reason.message : String(reason);
|
|
30
|
+
}
|
|
31
|
+
async function assertRejected(promise, expected, label) {
|
|
32
|
+
const outcome = await bounded(promise, 2_000, `${label}: close did not settle`);
|
|
33
|
+
assert.equal(outcome.status, 'rejected', `${label}: close must reject`);
|
|
34
|
+
const reason = outcome.reason;
|
|
35
|
+
// Since #518 close failures carry the typed safe diagnostic; the stable
|
|
36
|
+
// public meaning lives before its appended diagnostic payload.
|
|
37
|
+
assert.ok(reason instanceof ManagedDshCleanupError, `${label}: close failure carries the typed cleanup diagnostic`);
|
|
38
|
+
assert.equal(reason.message.split(';')[0], expected, `${label}: stable close error`);
|
|
39
|
+
}
|
|
40
|
+
async function settleInjectedHandle(port, originalWaitForExit, doneOutcome) {
|
|
41
|
+
const handle = port.handle;
|
|
42
|
+
handle.waitForExit = originalWaitForExit;
|
|
43
|
+
// The pinned fallback owner makes the real observation deterministic: the
|
|
44
|
+
// spawn never produced a child (no pid), so the owner's liveness probe is
|
|
45
|
+
// immediately false and confirmed-empty arrives without any provider-side
|
|
46
|
+
// wait. The bounded window stays as harness safety, not as a contract.
|
|
47
|
+
const rangeEmpty = await bounded(originalWaitForExit(AbortSignal.timeout(12_000)), 13_500, 'real handle cleanup did not settle');
|
|
48
|
+
assert.equal(rangeEmpty, true, 'real handle cleanup must confirm the range empty (deterministic fallback owner)');
|
|
49
|
+
const done = await bounded(doneOutcome, 2_500, 'real handle done did not settle');
|
|
50
|
+
assert.equal(done.status, 'rejected', 'real spawn-failure handle.done rejects');
|
|
51
|
+
await bounded(observe(port.close()), 2_500, 'sticky close promise did not settle');
|
|
52
|
+
}
|
|
53
|
+
const runtimeProto = LocalSubprocessRuntime.prototype;
|
|
54
|
+
/**
|
|
55
|
+
* Construct the port with the runtime's PUBLIC test hooks pinned to the POSIX
|
|
56
|
+
* fallback owner for the one synchronous spawn the constructor performs.
|
|
57
|
+
* `platform: 'darwin'` routes selectContainmentMode away from the linux-scope
|
|
58
|
+
* owner whose uncancellable shared wait can hang the runner; the recorded
|
|
59
|
+
* `spawn` hook delegates to the real node spawn so the missing-cwd failure is
|
|
60
|
+
* genuine. Prototype and per-instance internals are restored before any
|
|
61
|
+
* asynchronous assertion can observe them.
|
|
62
|
+
*/
|
|
63
|
+
function constructPortWithFallbackOwner(options) {
|
|
64
|
+
const spawnCalls = [];
|
|
65
|
+
let pinnedInternals = null;
|
|
66
|
+
const originalProtoSpawn = runtimeProto.spawn;
|
|
67
|
+
runtimeProto.spawn = function (spec) {
|
|
68
|
+
pinnedInternals = this.internals;
|
|
69
|
+
const hadPlatform = Object.hasOwn(this.internals, 'platform');
|
|
70
|
+
const previousPlatform = this.internals.platform;
|
|
71
|
+
const previousSpawn = this.internals.spawn;
|
|
72
|
+
this.internals.platform = 'darwin';
|
|
73
|
+
this.internals.spawn = (program, args, opts) => {
|
|
74
|
+
spawnCalls.push({ program, args: [...args], options: opts });
|
|
75
|
+
return (previousSpawn ?? nodeSpawn)(program, args, opts);
|
|
76
|
+
};
|
|
77
|
+
try {
|
|
78
|
+
return originalProtoSpawn.call(this, spec);
|
|
79
|
+
}
|
|
80
|
+
finally {
|
|
81
|
+
if (previousSpawn !== undefined)
|
|
82
|
+
this.internals.spawn = previousSpawn;
|
|
83
|
+
else
|
|
84
|
+
delete this.internals.spawn;
|
|
85
|
+
if (hadPlatform)
|
|
86
|
+
this.internals.platform = previousPlatform;
|
|
87
|
+
else
|
|
88
|
+
delete this.internals.platform;
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
let port;
|
|
92
|
+
try {
|
|
93
|
+
port = new ManagedDshRunPort(options);
|
|
94
|
+
}
|
|
95
|
+
finally {
|
|
96
|
+
runtimeProto.spawn = originalProtoSpawn;
|
|
97
|
+
}
|
|
98
|
+
return { port, spawnCalls, pinnedInternals, prototypeRestored: runtimeProto.spawn === originalProtoSpawn };
|
|
99
|
+
}
|
|
100
|
+
async function proveCloseFailure(mode, expected, unhandled) {
|
|
101
|
+
const root = mkdtempSync(join(tmpdir(), `gotry-510-close-${mode}-`));
|
|
102
|
+
const missingCwd = join(root, 'missing-cwd');
|
|
103
|
+
const { port, spawnCalls, pinnedInternals, prototypeRestored } = constructPortWithFallbackOwner({
|
|
104
|
+
cwd: missingCwd,
|
|
105
|
+
workerPath,
|
|
106
|
+
graceMs: 50,
|
|
107
|
+
});
|
|
108
|
+
// Containment-selection coverage: the pinned platform hook must have routed
|
|
109
|
+
// this spawn through the POSIX fallback owner — the ordinary spawn hook
|
|
110
|
+
// fired exactly once for the real Node executable with the real missing
|
|
111
|
+
// cwd and detached POSIX options (the native linux-scope branch would have
|
|
112
|
+
// exec'd systemd-run instead).
|
|
113
|
+
assert.equal(spawnCalls.length, 1, `${mode}: ordinary spawn fired exactly once (fallback owner selected)`);
|
|
114
|
+
assert.equal(spawnCalls[0].program, process.execPath, `${mode}: real worker executable spawned`);
|
|
115
|
+
assert.equal(spawnCalls[0].args.at(-1), workerPath, `${mode}: real worker path spawned`);
|
|
116
|
+
assert.equal(spawnCalls[0].options.cwd, missingCwd, `${mode}: real missing cwd passed to the real spawn`);
|
|
117
|
+
assert.equal(spawnCalls[0].options.detached, true, `${mode}: detached POSIX fallback owner selected`);
|
|
118
|
+
// Hook restoration coverage: nothing observable of the test hooks may
|
|
119
|
+
// outlive the synchronous construction.
|
|
120
|
+
assert.ok(prototypeRestored, `${mode}: runtime prototype spawn restored`);
|
|
121
|
+
assert.ok(pinnedInternals !== null && !Object.hasOwn(pinnedInternals, 'platform') && !Object.hasOwn(pinnedInternals, 'spawn'), `${mode}: internals test hooks restored`);
|
|
122
|
+
const handle = port.handle;
|
|
123
|
+
const originalWaitForExit = handle.waitForExit.bind(handle);
|
|
124
|
+
const doneOutcome = observe(handle.done);
|
|
125
|
+
const unhandledAtStart = unhandled.length;
|
|
126
|
+
const injected = mode === 'reject'
|
|
127
|
+
? () => Promise.reject(new Error('fault-injected observation failure'))
|
|
128
|
+
: () => Promise.resolve(false);
|
|
129
|
+
handle.waitForExit = injected;
|
|
130
|
+
try {
|
|
131
|
+
const closePromise = port.close();
|
|
132
|
+
const repeatedClosePromise = port.close();
|
|
133
|
+
assert.strictEqual(repeatedClosePromise, closePromise, `${mode}: repeated close returns one Promise`);
|
|
134
|
+
await assertRejected(observe(closePromise), expected, mode);
|
|
135
|
+
await assertRejected(observe(repeatedClosePromise), expected, `${mode} repeated close`);
|
|
136
|
+
await settleInjectedHandle(port, originalWaitForExit, doneOutcome);
|
|
137
|
+
// Give the real done rejection a turn to expose an unhandled derived
|
|
138
|
+
// outcome Promise. The proof itself observes every public close Promise.
|
|
139
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
140
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
141
|
+
assert.equal(unhandled.length, unhandledAtStart, `${mode}: no unhandled rejection from close cleanup (${unhandled.slice(unhandledAtStart).map(reasonMessage).join('; ')})`);
|
|
142
|
+
}
|
|
143
|
+
finally {
|
|
144
|
+
handle.waitForExit = originalWaitForExit;
|
|
145
|
+
await bounded(observe(port.close()), 2_500, `${mode}: final cleanup did not settle`);
|
|
146
|
+
rmSync(root, { recursive: true, force: true });
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
const unhandled = [];
|
|
150
|
+
const onUnhandled = (reason) => { unhandled.push(reason); };
|
|
151
|
+
process.on('unhandledRejection', onUnhandled);
|
|
152
|
+
try {
|
|
153
|
+
const failures = [];
|
|
154
|
+
for (const [mode, expected] of [
|
|
155
|
+
['reject', 'managed DSH process tree observation failed'],
|
|
156
|
+
['false', 'managed DSH process tree cleanup timeout'],
|
|
157
|
+
]) {
|
|
158
|
+
try {
|
|
159
|
+
await proveCloseFailure(mode, expected, unhandled);
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
failures.push(error);
|
|
163
|
+
console.error(`MANAGED DSH CLOSE FAILURE PROOF RED (${mode}):`, error);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
if (failures.length > 0)
|
|
167
|
+
throw new AggregateError(failures, 'managed DSH close failure proof failed');
|
|
168
|
+
console.log('MANAGED DSH CLOSE FAILURE PROOF: observation reject/timeout are bounded, idempotent, and handled');
|
|
169
|
+
}
|
|
170
|
+
finally {
|
|
171
|
+
process.off('unhandledRejection', onUnhandled);
|
|
172
|
+
}
|