@danceiny/gotry 0.2.0-rc.31 → 0.2.0-rc.33

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. package/README.md +2 -2
  2. package/README.zh-CN.md +2 -2
  3. package/bin/gotry-bootstrap.js +63 -53
  4. package/bin/gotry-hbcli-release.d.ts +3 -0
  5. package/bin/gotry-hbcli-release.js +4 -0
  6. package/client/client.js +66 -21
  7. package/dist/capabilities/doctor.js +7 -8
  8. package/dist/capabilities/effect.js +45 -12
  9. package/dist/capabilities/flyai-config.js +12 -10
  10. package/dist/capabilities/flyai.js +87 -56
  11. package/dist/capabilities/hbcli-config.js +156 -0
  12. package/dist/capabilities/hbcli.js +29 -2
  13. package/dist/capabilities/resilience.js +6 -6
  14. package/dist/capabilities/session/adapters/ctrip-flight.js +42 -7
  15. package/dist/capabilities/session/adapters/ctrip-hotel.js +52 -3
  16. package/dist/capabilities/session/bridge-ledger.js +168 -67
  17. package/dist/capabilities/session/ctrip-city.js +186 -0
  18. package/dist/capabilities/session/extension-bridge.js +148 -47
  19. package/dist/capabilities/session/gui-flight.js +949 -0
  20. package/dist/capabilities/session/gui-hotel.js +470 -0
  21. package/dist/capabilities/session-search.js +102 -14
  22. package/dist/scripts/booking-copilot-dsh-planner-proof-tests.js +129 -2
  23. package/dist/scripts/booking-copilot-dsh-readiness-proof.js +53 -5
  24. package/dist/scripts/booking-copilot-gateway-chat-adapter-tests.js +233 -0
  25. package/dist/scripts/booking-copilot-planner-live-probe.js +1 -0
  26. package/dist/scripts/booking-copilot-startup-proof-tests.js +1 -1
  27. package/dist/scripts/bootstrap-tests.js +23 -6
  28. package/dist/scripts/bridge-ledger-tests.js +20 -18
  29. package/dist/scripts/ctrip-city-tests.js +80 -0
  30. package/dist/scripts/effect-tests.js +602 -504
  31. package/dist/scripts/extension-tests.js +14 -8
  32. package/dist/scripts/fixtures/tenant-runaway-worker-fixture.mjs +73 -0
  33. package/dist/scripts/flight-malformed-tests.js +1 -1
  34. package/dist/scripts/flyai-setup-tests.js +19 -4
  35. package/dist/scripts/flyai-tests.js +768 -653
  36. package/dist/scripts/gotry-web-api-tests.js +5 -0
  37. package/dist/scripts/gotry-web-client-tests.js +20 -1
  38. package/dist/scripts/gui-flight-live.js +27 -0
  39. package/dist/scripts/hbcli-tests.js +1 -1
  40. package/dist/scripts/hbcli-upgrade-tests.js +107 -0
  41. package/dist/scripts/hbcli-web-setup-tests.js +114 -0
  42. package/dist/scripts/hotel-date-gate-tests.js +1 -1
  43. package/dist/scripts/hotel-result-tests.js +169 -0
  44. package/dist/scripts/issue-271-liveness-tests.js +1 -1
  45. package/dist/scripts/mutation-check.js +153 -0
  46. package/dist/scripts/nightly-evidence.js +4 -111
  47. package/dist/scripts/session-tests.js +211 -6
  48. package/dist/scripts/tenant-bridge-org-tests.js +1264 -0
  49. package/dist/scripts/tenant-capacity-probe.js +263 -0
  50. package/dist/scripts/tenant-compute-isolation-tests.js +707 -0
  51. package/dist/scripts/tenant-egress-credentials-tests.js +822 -0
  52. package/dist/scripts/tenant-executor-binding-tests.js +203 -0
  53. package/dist/scripts/tenant-prompt-partition-tests.js +265 -0
  54. package/dist/scripts/tenant-seam-adversarial-tests.js +957 -0
  55. package/dist/scripts/tenant-seam-quota-tests.js +383 -0
  56. package/dist/scripts/tenant-seam-test-kit.js +292 -0
  57. package/dist/scripts/tenant-state-copies-tests.js +607 -0
  58. package/dist/scripts/tenant-state-drill-tests.js +298 -0
  59. package/dist/scripts/tenant-state-drill-worker.js +164 -0
  60. package/dist/scripts/tenant-state-durable-conformance.js +101 -0
  61. package/dist/scripts/tenant-state-handoff-tests.js +854 -0
  62. package/dist/scripts/tenant-state-lease-conformance.js +142 -0
  63. package/dist/scripts/tenant-state-lease-worker.js +28 -0
  64. package/dist/scripts/tenant-state-router-tests.js +464 -0
  65. package/dist/scripts/tenant-state-test-kit.js +92 -0
  66. package/dist/scripts/tenant-state-tests.js +564 -0
  67. package/dist/src/backend/modules/booking-executor.js +61 -6
  68. package/dist/src/backend/modules/session-search.js +136 -39
  69. package/dist/src/booking-surface/dsh-llm-gateway-chat.js +434 -0
  70. package/dist/src/booking-surface/dsh-planner.js +150 -19
  71. package/dist/src/booking-surface/index.js +1 -0
  72. package/dist/src/booking-surface/managed-dsh-run-port.js +121 -7
  73. package/dist/src/booking-surface/server.js +193 -80
  74. package/dist/src/booking-surface/startup.js +153 -0
  75. package/dist/src/booking-surface/tenancy.js +188 -0
  76. package/dist/src/gotry-web-api.js +64 -2
  77. package/dist/src/index.js +70 -18
  78. package/dist/src/llm-price-table.js +148 -0
  79. package/dist/src/tenant-seam/index.js +8 -0
  80. package/dist/src/tenant-seam/llm-broker.js +508 -0
  81. package/dist/src/tenant-seam/process-governor.js +281 -0
  82. package/dist/src/tenant-seam/quotas.js +360 -0
  83. package/dist/src/tenant-seam/seam.js +92 -0
  84. package/dist/src/tenant-state/durable-store.js +158 -0
  85. package/dist/src/tenant-state/epoch-copies.js +422 -0
  86. package/dist/src/tenant-state/errors.js +108 -0
  87. package/dist/src/tenant-state/ids.js +101 -0
  88. package/dist/src/tenant-state/index.js +11 -0
  89. package/dist/src/tenant-state/lease-store.js +203 -0
  90. package/dist/src/tenant-state/tenant-lease.js +150 -0
  91. package/dist/src/tenant-state/tenant-router.js +650 -0
  92. package/extension/manifest.json +2 -2
  93. package/package.json +5 -1
  94. package/ts/capabilities/flyai-config.ts +12 -10
  95. package/ts/capabilities/flyai.ts +91 -50
  96. package/ts/capabilities/hbcli-config.ts +128 -0
  97. package/ts/capabilities/hbcli.ts +30 -3
  98. package/ts/capabilities/session/adapters/ctrip-flight.ts +43 -7
  99. package/ts/capabilities/session/adapters/ctrip-hotel.ts +51 -4
  100. package/ts/capabilities/session/bridge-ledger.ts +192 -78
  101. package/ts/capabilities/session/ctrip-city.ts +158 -0
  102. package/ts/capabilities/session/extension-bridge.ts +185 -67
  103. package/ts/capabilities/session/gui-flight.ts +1022 -0
  104. package/ts/capabilities/session/gui-hotel.ts +505 -0
  105. package/ts/capabilities/session-search.ts +130 -18
  106. package/ts/src/booking-surface/dsh-llm-gateway-chat.js +434 -0
  107. package/ts/src/booking-surface/dsh-planner.ts +169 -19
  108. package/ts/src/booking-surface/index.ts +1 -0
  109. package/ts/src/booking-surface/managed-dsh-run-port.ts +150 -7
  110. package/ts/src/booking-surface/server.ts +215 -84
  111. package/ts/src/booking-surface/startup.ts +155 -0
  112. package/ts/src/booking-surface/tenancy.ts +242 -0
  113. package/ts/src/gotry-web-api.ts +51 -2
  114. package/ts/src/index.ts +67 -19
  115. package/ts/src/llm-price-table.ts +180 -0
  116. package/ts/src/tenant-seam/index.ts +9 -0
  117. package/ts/src/tenant-seam/llm-broker.ts +520 -0
  118. package/ts/src/tenant-seam/process-governor.ts +343 -0
  119. package/ts/src/tenant-seam/quotas.ts +433 -0
  120. package/ts/src/tenant-seam/seam.ts +142 -0
  121. package/ts/src/tenant-state/durable-store.ts +171 -0
  122. package/ts/src/tenant-state/epoch-copies.ts +491 -0
  123. package/ts/src/tenant-state/errors.ts +128 -0
  124. package/ts/src/tenant-state/ids.ts +108 -0
  125. package/ts/src/tenant-state/index.ts +12 -0
  126. package/ts/src/tenant-state/lease-store.ts +266 -0
  127. package/ts/src/tenant-state/tenant-lease.ts +175 -0
  128. package/ts/src/tenant-state/tenant-router.ts +743 -0
@@ -0,0 +1,175 @@
1
+ /**
2
+ * The holder side of a tenant lease (docs/rfc/aaas-rfc.md §4.2, items 2, 4 and 6).
3
+ *
4
+ * A replica owns a tenant between a successful `acquire` and the first of: an
5
+ * explicit `release`, a refused `renew`, or the local fence deadline. The deadline
6
+ * is measured on the replica's monotonic clock from BEFORE the request was sent,
7
+ * so the round trip, the store's clock and any skew between the two clocks only
8
+ * make the holder stop earlier:
9
+ *
10
+ * fence = sentAt + grantedTtl - margin
11
+ *
12
+ * `assertHeld()` is the synchronous gate of the write path. A write is
13
+ * acknowledged only if the gate passes before it and after it.
14
+ *
15
+ * What the local fence cannot see is a frozen clock: a process paused between its
16
+ * check and its acknowledgement. That case is covered by the durable layer, whose
17
+ * copies are scoped by epoch, not by this class.
18
+ */
19
+
20
+ import { performance } from 'node:perf_hooks'
21
+ import { FencedError, LeaseLostError, type FenceReason } from './errors.ts'
22
+ import { assertLeaseTtl, type LeaseRecord, type LeaseStore } from './lease-store.ts'
23
+
24
+ /**
25
+ * A monotonic clock owned by the replica. Production uses `performance.now()`, which
26
+ * does not jump with wall-clock changes. It stops while a host is suspended, which
27
+ * is the frozen-clock case above.
28
+ */
29
+ export interface MonotonicClock {
30
+ nowMs(): number
31
+ }
32
+
33
+ export const systemMonotonicClock: MonotonicClock = { nowMs: () => performance.now() }
34
+
35
+ export interface TenantLeaseOptions {
36
+ readonly store: LeaseStore
37
+ readonly key: string
38
+ /** Unique per process incarnation: a restarted replica is a different holder and waits for expiry or release. */
39
+ readonly replicaId: string
40
+ readonly ttlMs: number
41
+ /** The agreed clock-skew and scheduling bound; the holder stops this long before the store would admit anyone else. */
42
+ readonly marginMs: number
43
+ readonly clock?: MonotonicClock
44
+ }
45
+
46
+ type HolderState = 'held' | 'released' | 'lost'
47
+
48
+ export class TenantLease {
49
+ readonly key: string
50
+ readonly replicaId: string
51
+ readonly epoch: number
52
+ /** Durable copies of epochs up to this one are dead; fixed for the whole tenure, because only the holder can raise it. */
53
+ readonly forgottenAtEpoch: number
54
+ private readonly store: LeaseStore
55
+ private readonly clock: MonotonicClock
56
+ private readonly ttlMs: number
57
+ private readonly marginMs: number
58
+ private fenceAtMs: number
59
+ private state: HolderState = 'held'
60
+ private tail: Promise<void> = Promise.resolve()
61
+
62
+ private constructor(options: TenantLeaseOptions, clock: MonotonicClock, record: LeaseRecord, fenceAtMs: number) {
63
+ this.key = options.key
64
+ this.replicaId = options.replicaId
65
+ this.epoch = record.epoch
66
+ this.forgottenAtEpoch = record.forgottenAtEpoch
67
+ this.store = options.store
68
+ this.clock = clock
69
+ this.ttlMs = options.ttlMs
70
+ this.marginMs = options.marginMs
71
+ this.fenceAtMs = fenceAtMs
72
+ }
73
+
74
+ /**
75
+ * @throws LeaseHeldError when another holder has an unexpired lease
76
+ * @throws LeaseStoreUnavailableError when the store cannot be reached
77
+ */
78
+ static async acquire(options: TenantLeaseOptions): Promise<TenantLease> {
79
+ assertLeaseTtl(options.ttlMs)
80
+ if (!Number.isInteger(options.marginMs) || options.marginMs < 0 || options.marginMs >= options.ttlMs) {
81
+ throw new RangeError(`lease margin must be an integer within [0, ttl), got ${String(options.marginMs)} for ttl ${options.ttlMs}`)
82
+ }
83
+ const clock = options.clock ?? systemMonotonicClock
84
+ const sentAt = clock.nowMs()
85
+ const grant = await options.store.acquire(options.key, options.replicaId, options.ttlMs)
86
+ if (grant.record.owner !== options.replicaId || grant.record.key !== options.key) {
87
+ throw new LeaseLostError(`${options.key}: the store granted the lease to ${grant.record.owner}`)
88
+ }
89
+ const lease = new TenantLease(options, clock, grant.record, sentAt + grant.ttlMs - options.marginMs)
90
+ if (lease.fenceAtMs <= clock.nowMs()) {
91
+ // The round trip consumed the usable part of the lease; hand it back instead of holding an already fenced epoch.
92
+ await lease.release().catch(() => undefined)
93
+ throw new LeaseLostError(`${options.key}: the grant of epoch ${grant.record.epoch} was consumed by the round trip`)
94
+ }
95
+ return lease
96
+ }
97
+
98
+ /** The synchronous gate of the write path. */
99
+ assertHeld(): void {
100
+ if (this.state === 'released') throw this.fenced('lease_released')
101
+ if (this.state === 'lost') throw this.fenced('lease_lost')
102
+ if (this.clock.nowMs() >= this.fenceAtMs) throw this.fenced('lease_expired_locally')
103
+ }
104
+
105
+ isHeld(): boolean {
106
+ try {
107
+ this.assertHeld()
108
+ return true
109
+ } catch {
110
+ return false
111
+ }
112
+ }
113
+
114
+ /** Milliseconds until the local fence; zero or negative once fenced. */
115
+ remainingMs(): number {
116
+ return this.fenceAtMs - this.clock.nowMs()
117
+ }
118
+
119
+ /**
120
+ * Extends the lease; the epoch stays. A refusal is terminal for this holder. Any other
121
+ * failure leaves the deadline untouched: the holder fails closed when it passes, and a
122
+ * later successful renewal resumes it, because an unchanged epoch means nobody else held it.
123
+ */
124
+ renew(): Promise<void> {
125
+ return this.serialize(async () => {
126
+ if (this.state !== 'held') throw this.fenced(this.state === 'released' ? 'lease_released' : 'lease_lost')
127
+ const sentAt = this.clock.nowMs()
128
+ try {
129
+ const grant = await this.store.renew(this.key, this.replicaId, this.epoch, this.ttlMs)
130
+ if (grant.record.epoch !== this.epoch || grant.record.owner !== this.replicaId) {
131
+ throw new LeaseLostError(`${this.key}: renewal answered for epoch ${grant.record.epoch}`)
132
+ }
133
+ this.fenceAtMs = Math.max(this.fenceAtMs, sentAt + grant.ttlMs - this.marginMs)
134
+ } catch (error) {
135
+ if (error instanceof LeaseLostError) this.state = 'lost'
136
+ throw error
137
+ }
138
+ })
139
+ }
140
+
141
+ /**
142
+ * Graceful handoff step: stop acknowledging first, then tell the store, so the next owner
143
+ * can start at once. If the store cannot be reached the lease simply expires; the error is
144
+ * rethrown so the caller knows the handoff degraded to waiting out the TTL.
145
+ */
146
+ release(): Promise<void> {
147
+ return this.serialize(async () => {
148
+ if (this.state !== 'held') return
149
+ this.state = 'released'
150
+ try {
151
+ await this.store.release(this.key, this.replicaId, this.epoch)
152
+ } catch (error) {
153
+ if (error instanceof LeaseLostError) return
154
+ throw error
155
+ }
156
+ })
157
+ }
158
+
159
+ /** Marks every durable copy up to this epoch dead (forget). Requires a lease that is provably held. */
160
+ async markForgotten(): Promise<LeaseRecord> {
161
+ this.assertHeld()
162
+ return this.store.markForgotten(this.key, this.replicaId, this.epoch)
163
+ }
164
+
165
+ private fenced(reason: FenceReason): FencedError {
166
+ return new FencedError(reason, this.key, this.epoch)
167
+ }
168
+
169
+ /** Renewals and the release of one holder run one at a time, in call order. */
170
+ private serialize<T>(step: () => Promise<T>): Promise<T> {
171
+ const run = this.tail.then(step, step)
172
+ this.tail = run.then(() => undefined, () => undefined)
173
+ return run
174
+ }
175
+ }