@danceiny/gotry 0.0.1-rc.17 → 0.0.1-rc.19

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 (137) hide show
  1. package/README.md +185 -148
  2. package/README.zh-CN.md +195 -154
  3. package/bin/gotry-bootstrap.js +246 -26
  4. package/bin/gotry-inner.js +68 -38
  5. package/bin/gotry-runtime-resolution.d.ts +1 -1
  6. package/bin/gotry-runtime-resolution.js +7 -8
  7. package/cordis.gotry-patch.yml +15 -5
  8. package/data/stations-12306-verify.json +524 -0
  9. package/dist/capabilities/agent-reach.js +4 -4
  10. package/dist/capabilities/channel-health.js +103 -0
  11. package/dist/capabilities/channel-registry.js +213 -0
  12. package/dist/capabilities/doctor.js +285 -0
  13. package/dist/capabilities/effect.js +178 -5
  14. package/dist/capabilities/flyai.js +13 -0
  15. package/dist/capabilities/session/adapters/ctrip-hotel.js +207 -0
  16. package/dist/capabilities/session/adapters/rail-12306.js +285 -0
  17. package/dist/capabilities/session/extension-bridge.js +14 -1
  18. package/dist/capabilities/session/extension-channel.js +4 -2
  19. package/dist/capabilities/session/extension-distribution.js +1 -1
  20. package/dist/capabilities/session-search.js +311 -0
  21. package/dist/capabilities/visa-policy.js +126 -0
  22. package/dist/scripts/agent-planning-turn-deadline-e2e.js +304 -0
  23. package/dist/scripts/agent-planning-turn-deadline-tests.js +281 -0
  24. package/dist/scripts/benchmark-environment-bridge-e2e.js +5 -10
  25. package/dist/scripts/benchmark-environment-bridge-tests.js +148 -142
  26. package/dist/scripts/booking-copilot-availability-ledger-binding-tests.js +26 -26
  27. package/dist/scripts/{booking-copilot-availability-policy-v2-tests.js → booking-copilot-availability-policy-tests.js} +115 -115
  28. package/dist/scripts/booking-copilot-crossrepo-fixture-server.js +41 -1
  29. package/dist/scripts/booking-copilot-dsh-core-proof-tests.js +75 -120
  30. package/dist/scripts/booking-copilot-dsh-planner-proof-tests.js +370 -61
  31. package/dist/scripts/booking-copilot-dsh-plugin-proof-tests.js +2 -2
  32. package/dist/scripts/booking-copilot-event-sequence-concurrency-proof-tests.js +7 -3
  33. package/dist/scripts/booking-copilot-gap-code-contract-proof-tests.js +15 -9
  34. package/dist/scripts/booking-copilot-operation-ledger-concurrency-proof-tests.js +19 -11
  35. package/dist/scripts/booking-copilot-receipt-ledger-concurrency-proof-tests.js +16 -52
  36. package/dist/scripts/booking-copilot-runtime-proof-tests.js +3953 -155
  37. package/dist/scripts/booking-copilot-server-proof-tests.js +58 -23
  38. package/dist/scripts/booking-copilot-startup-proof-tests.js +10 -119
  39. package/dist/scripts/booking-surface-contract-proof-tests.js +1154 -326
  40. package/dist/scripts/bootstrap-tests.js +115 -44
  41. package/dist/scripts/build-metrics-report.js +339 -0
  42. package/dist/scripts/channel-probe-tests.js +148 -0
  43. package/dist/scripts/channel-probe.js +252 -0
  44. package/dist/scripts/channel-registry-tests.js +262 -0
  45. package/dist/scripts/doctor-tests.js +121 -0
  46. package/dist/scripts/effect-tests.js +274 -1
  47. package/dist/scripts/extension-tests.js +129 -11
  48. package/dist/scripts/fact-gate-tests.js +91 -2
  49. package/dist/scripts/flyai-tests.js +17 -1
  50. package/dist/scripts/hbcli-e2e-tests.js +1 -3
  51. package/dist/scripts/metrics-report-tests.js +288 -0
  52. package/dist/scripts/persona-surface-guard-tests.js +19 -0
  53. package/dist/scripts/session-tests.js +401 -1
  54. package/dist/scripts/smoke.js +100 -83
  55. package/dist/scripts/turn-handoff-collect-tests.js +216 -0
  56. package/dist/scripts/turn-handoff-collect.js +189 -0
  57. package/dist/scripts/turn-policy-tests.js +56 -0
  58. package/dist/scripts/typed-contract-canary.js +261 -0
  59. package/dist/scripts/visa-policy-tests.js +75 -0
  60. package/dist/scripts/wish-channel-gate-tests.js +111 -0
  61. package/dist/src/artifact-gate.js +91 -3
  62. package/dist/src/benchmark-agent-conformance.js +7 -4
  63. package/dist/src/benchmark-environment-bridge.js +68 -7
  64. package/dist/src/bookable-facts.js +62 -3
  65. package/dist/src/booking-surface/{availability-policy-v2.js → availability-policy.js} +89 -89
  66. package/dist/src/booking-surface/canonical-schema.js +8 -22
  67. package/dist/src/booking-surface/contracts.js +70 -19
  68. package/dist/src/booking-surface/dsh-planner.js +310 -177
  69. package/dist/src/booking-surface/dsh-plugin.js +3 -3
  70. package/dist/src/booking-surface/index.js +1 -5
  71. package/dist/src/booking-surface/profile.js +1 -1
  72. package/dist/src/booking-surface/runtime.js +1682 -247
  73. package/dist/src/booking-surface/server.js +371 -185
  74. package/dist/src/booking-surface/startup.js +17 -27
  75. package/dist/src/booking-surface/validation.js +270 -760
  76. package/dist/src/index.js +735 -149
  77. package/dist/src/turn-deadline.js +292 -0
  78. package/dist/src/turn-policy.js +146 -0
  79. package/dist/src/wish-pool.js +5 -0
  80. package/extension/background.js +23 -4
  81. package/extension/content-main.js +47 -9
  82. package/extension/manifest.json +27 -9
  83. package/package.json +6 -26
  84. package/schemas/booking.surface.schema.json +2593 -0
  85. package/ts/capabilities/agent-reach.ts +4 -4
  86. package/ts/capabilities/flyai.ts +21 -3
  87. package/ts/capabilities/session/action-cache.ts +1 -1
  88. package/ts/capabilities/session/adapters/ctrip-hotel.ts +238 -0
  89. package/ts/capabilities/session/adapters/rail-12306.ts +293 -0
  90. package/ts/capabilities/session/extension-bridge.ts +32 -2
  91. package/ts/capabilities/session/extension-channel.ts +9 -8
  92. package/ts/capabilities/session/extension-distribution.ts +3 -1
  93. package/ts/capabilities/session/health-watch.ts +1 -1
  94. package/ts/capabilities/session/read-guard.ts +1 -1
  95. package/ts/capabilities/session/wizard.ts +1 -1
  96. package/ts/capabilities/session-search.ts +323 -1
  97. package/ts/package.json +1 -0
  98. package/ts/scripts/turn-handoff-collect.ts +178 -0
  99. package/ts/src/artifact-gate.ts +82 -2
  100. package/ts/src/benchmark-agent-conformance.ts +14 -6
  101. package/ts/src/benchmark-environment-bridge.ts +29 -10
  102. package/ts/src/bookable-facts.ts +109 -5
  103. package/ts/src/booking-saga.ts +1 -1
  104. package/ts/src/booking-surface/{availability-policy-v2.ts → availability-policy.ts} +124 -125
  105. package/ts/src/booking-surface/canonical-schema.js +8 -22
  106. package/ts/src/booking-surface/contracts.ts +278 -239
  107. package/ts/src/booking-surface/dsh-planner.ts +333 -194
  108. package/ts/src/booking-surface/dsh-plugin.js +3 -3
  109. package/ts/src/booking-surface/index.ts +1 -5
  110. package/ts/src/booking-surface/profile.ts +11 -11
  111. package/ts/src/booking-surface/runtime.ts +1351 -351
  112. package/ts/src/booking-surface/server.ts +282 -175
  113. package/ts/src/booking-surface/startup.ts +36 -48
  114. package/ts/src/booking-surface/validation.ts +194 -442
  115. package/ts/src/contracts.ts +1 -1
  116. package/ts/src/index.ts +489 -171
  117. package/ts/src/loop.ts +1 -1
  118. package/ts/src/state-ledger.ts +1 -1
  119. package/ts/src/turn-deadline.ts +361 -0
  120. package/ts/src/turn-policy.ts +154 -0
  121. package/ts/src/wish-pool.ts +17 -4
  122. package/dist/scripts/agent-planning-budget-e2e.js +0 -227
  123. package/dist/scripts/agent-planning-budget-tests.js +0 -173
  124. package/dist/scripts/booking-copilot-v2-runtime-proof-tests.js +0 -3935
  125. package/dist/scripts/booking-surface-v2-contract-proof-tests.js +0 -1174
  126. package/dist/src/booking-surface/contracts-v2.js +0 -89
  127. package/dist/src/booking-surface/runtime-v2.js +0 -1771
  128. package/dist/src/booking-surface/server-v2.js +0 -334
  129. package/dist/src/booking-surface/validation-v2.js +0 -319
  130. package/dist/src/tool-budget.js +0 -136
  131. package/schemas/booking.surface.v1.schema.json +0 -927
  132. package/schemas/booking.surface.v2.schema.json +0 -61
  133. package/ts/src/booking-surface/contracts-v2.ts +0 -118
  134. package/ts/src/booking-surface/runtime-v2.ts +0 -1466
  135. package/ts/src/booking-surface/server-v2.ts +0 -247
  136. package/ts/src/booking-surface/validation-v2.ts +0 -205
  137. package/ts/src/tool-budget.ts +0 -165
@@ -1,6 +1,6 @@
1
1
  import { readFileSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
- import { BOOKABLE_FACT_SCHEMA, checkSameDayDeparture, dedupeFacts, detectBacktrack, factsFromFlyai, flightClaimVerdict, itineraryInvariants, latestFactsForRouteDate, negativeFact, renderConnection, renderFlightFact, renderNightsLine, renderPolicyFact } from '../src/bookable-facts.js';
3
+ import { BOOKABLE_FACT_SCHEMA, checkSameDayDeparture, dedupeFacts, detectBacktrack, factsFromFlyai, factsFromHotel, flightClaimVerdict, itineraryInvariants, latestFactsForRouteDate, negativeFact, renderConnection, renderFlightFact, renderHotelFact, renderNightsLine, renderPolicyFact } from '../src/bookable-facts.js';
4
4
  import { ARTIFACT_GATE_SCHEMA, gateArtifact } from '../src/artifact-gate.js';
5
5
  let pass = 0;
6
6
  let fail = 0;
@@ -311,7 +311,96 @@ const goodReport = gateArtifact(goodArtifact, registry, map, {
311
311
  });
312
312
  assert(goodReport.verdict === 'pass' && goodReport.presentation === 'verified_itinerary_allowed' && goodReport.traceable === 6 && goodReport.violations.length === 0, `good artifact:6/6 航班 claim 全回溯,闸 pass(违例 ${goodReport.violations.length})`);
313
313
  assert(goodFlights.every((f)=>f.bookability === 'bookable_exact_date' && f.query_id.includes('flyai:flight:')), 'good artifact 的每个可下单事实均可回溯到 tool result/query id(验收⑧)');
314
- console.log(`\nFACT GATE TESTS: ${pass} pass, ${fail} fail`);
314
+ {
315
+ const fetchedAt = '2026-09-04T00:00:00.000Z';
316
+ assert(factsFromHotel({
317
+ source: 'flyai-hotel',
318
+ destination: '大理',
319
+ verdict: 'hit',
320
+ options: 9,
321
+ evidence: 'e',
322
+ fetchedAt
323
+ }).length === 0, '摸底(无档期)酒店检索不落账(无 exact-date 语义)');
324
+ assert(factsFromHotel({
325
+ source: 'flyai-hotel',
326
+ destination: '大理',
327
+ checkIn: '2026-10-01',
328
+ checkOut: '2026-10-03',
329
+ verdict: 'needs-setup',
330
+ options: 0,
331
+ evidence: 'e',
332
+ fetchedAt
333
+ }).length === 0 && factsFromHotel({
334
+ source: 'flyai-hotel',
335
+ destination: '大理',
336
+ checkIn: '2026-10-01',
337
+ checkOut: '2026-10-03',
338
+ verdict: 'error',
339
+ options: 0,
340
+ evidence: 'e',
341
+ fetchedAt
342
+ }).length === 0, 'needs-setup/error 传输失败永不落负事实(ADR-19 不变量)');
343
+ const hitFacts = factsFromHotel({
344
+ source: 'flyai-hotel',
345
+ destination: '大理',
346
+ checkIn: '2026-10-01',
347
+ checkOut: '2026-10-03',
348
+ verdict: 'hit',
349
+ options: 9,
350
+ evidence: 'e',
351
+ fetchedAt
352
+ });
353
+ const missFacts = factsFromHotel({
354
+ source: 'session:ctrip-hotel',
355
+ destination: '大理',
356
+ checkIn: '2026-10-01',
357
+ checkOut: '2026-10-03',
358
+ verdict: 'miss',
359
+ options: 0,
360
+ evidence: 'e',
361
+ fetchedAt
362
+ });
363
+ assert(hitFacts.length === 1 && hitFacts[0].bookability === 'bookable_exact_date' && hitFacts[0].options_masked === 9, 'exact-date hit → 正事实(在架家数,无数字价字段)');
364
+ assert(!('price' in hitFacts[0]), '酒店事实无 price 字段(打码价保真,不落数字价)');
365
+ assert(missFacts.length === 1 && missFacts[0].bookability === 'unavailable_exact_date', 'exact-date miss → 负事实');
366
+ const hotelLine = renderHotelFact(hitFacts[0]);
367
+ assert(hotelLine.includes(`fact:${hitFacts[0].fact_id}`), 'renderHotelFact 行内嵌 fact 锚点');
368
+ const anchoredArtifact = [
369
+ '## 住宿',
370
+ hotelLine
371
+ ].join('\n');
372
+ const anchoredReport = gateArtifact(anchoredArtifact, [
373
+ ...hitFacts
374
+ ], map, {
375
+ trip_year: 2026
376
+ });
377
+ assert(anchoredReport.verdict === 'pass' && anchoredReport.traceable === 1, `锚点酒店行 → 锚点确定性回溯 pass(实际 ${anchoredReport.verdict}/traceable=${anchoredReport.traceable})`);
378
+ const forged = anchoredArtifact.replace(hitFacts[0].fact_id, '0123456789abcdef');
379
+ const forgedReport = gateArtifact(forged, [
380
+ ...hitFacts
381
+ ], map, {
382
+ trip_year: 2026
383
+ });
384
+ assert(forgedReport.violations.some((v)=>v.kind === 'fact_anchor_unknown'), '手改/伪造锚点 → fact_anchor_unknown');
385
+ const claimHit = gateArtifact('## 住宿\n- 大理 2026-10-01→2026-10-03 洱海民宿有房可订', [
386
+ ...hitFacts
387
+ ], map, {
388
+ trip_year: 2026
389
+ });
390
+ assert(claimHit.verdict === 'pass' && claimHit.traceable === 1, '启发式酒店 claim 命中在册 hit → traceable');
391
+ const claimMiss = gateArtifact('## 住宿\n- 大理 2026-10-01→2026-10-03 洱海民宿有房可订 ✓', [
392
+ ...missFacts
393
+ ], map, {
394
+ trip_year: 2026
395
+ });
396
+ assert(claimMiss.violations.some((v)=>v.kind === 'not_in_source'), 'exact-date miss 在册却写有房 → not_in_source');
397
+ assert(claimMiss.violations.some((v)=>v.kind === 'unconditional_check'), '对未核验酒店用 ✓ → unconditional_check');
398
+ const noCtx = gateArtifact('## 住宿\n- 住宿:湖景房有房可订 ✓', [], map, {
399
+ trip_year: 2026
400
+ });
401
+ assert(noCtx.violations.some((v)=>v.kind === 'unverifiable_hotel_claim'), '缺目的地上下文 → unverifiable_hotel_claim(fail closed)');
402
+ console.log(` ok - §10 酒店事实闸(D-26)八断言完成`);
403
+ }console.log(`\nFACT GATE TESTS: ${pass} pass, ${fail} fail`);
315
404
  if (fail > 0) process.exit(1);
316
405
 
317
406
 
@@ -85,11 +85,27 @@ const f = await flyaiSearch({
85
85
  assert.equal(f.ok, false);
86
86
  assert.equal(f.verdict, 'error', '非零退出应判 error');
87
87
  console.log('4. 非零退出 → error OK');
88
+ const trialBin = join(tmp, 'flyai-trial');
89
+ await writeFile(trialBin, `#!/bin/sh\necho 'search-hotel: MCP HTTP 429: Body: {"jsonrpc":"2.0","id":"1","error":{"code":-32603,"message":"Trial limit reached. Please visit the console at flyai.open.fliggy.com to get a formal API Key"}}' >&2\nexit 1\n`, {
90
+ mode: 0o755
91
+ });
92
+ const t = await flyaiSearch({
93
+ ...base,
94
+ cliBin: trialBin,
95
+ timeoutMs: 5000
96
+ });
97
+ assert.equal(t.ok, false);
98
+ assert.equal(t.verdict, 'needs-setup', `429 达限应判 needs-setup,实际 ${t.verdict}`);
99
+ assert.match(t.setup ?? '', /FLYAI_API_KEY/, 'setup 指引带 FLYAI_API_KEY 配置路径');
100
+ assert.match(t.setup ?? '', /flyai\.open\.fliggy\.com/, 'setup 指引带控制台入口');
101
+ assert.match(t.setup ?? '', /请勿重试|勿重试|不要重试/, 'setup 明示本会话勿重试');
102
+ assert.match(t.error ?? '', /429|Trial limit/i, 'error 保留上游 429 原话');
103
+ assert.match(t.evidence, /\[实时API:flyai@error@/, '证据链标注');
88
104
  await rm(tmp, {
89
105
  recursive: true,
90
106
  force: true
91
107
  });
92
- console.log('FLYAI TESTS: 4/4 OK(离线假 CLI:Sentinel→error / 空 itemList→miss / 命中→hit / exit≠0→error)');
108
+ console.log('FLYAI TESTS: 5/5 OK(离线假 CLI:Sentinel→error / 空 itemList→miss / 命中→hit / exit≠0→error / 429 达限→needs-setup)');
93
109
 
94
110
 
95
111
  //# sourceURL=ts/scripts/flyai-tests.ts
@@ -144,9 +144,7 @@ async function main() {
144
144
  const tool = registered.find((t)=>t.name === 'gotry_hotel_search');
145
145
  assert.ok(tool, 'gotry_hotel_search 应已注册');
146
146
  const obs = await tool.execute({
147
- query: {
148
- destination: '深圳'
149
- }
147
+ destination: '深圳'
150
148
  }, null);
151
149
  assert.equal(obs.ok, true, '工具面应返回 ok 包络(永不抛错契约)');
152
150
  assert.ok(obs.via === 'hbcli-error' || obs.via === 'hbcli-realtime', `工具面 via 应与渠道一致,实际 ${obs.via}`);
@@ -0,0 +1,288 @@
1
+ import { strict as assert } from 'node:assert';
2
+ import { mkdtempSync, rmSync, mkdirSync, writeFileSync, readdirSync, readFileSync, existsSync } from 'node:fs';
3
+ import { tmpdir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ import { aggregateFacts, aggregateChannels, aggregateIncidents, aggregateLatency, collectMetrics, renderMetricsReport, main } from './build-metrics-report.js';
6
+ const NOW = new Date('2026-09-05T12:00:00Z');
7
+ const DAY = 86_400_000;
8
+ const iso = (daysAgo)=>new Date(NOW.getTime() - daysAgo * DAY).toISOString();
9
+ let checkCount = 0;
10
+ function check(name, fn) {
11
+ fn();
12
+ checkCount += 1;
13
+ console.log(` ok ${checkCount} ${name}`);
14
+ }
15
+ async function withRoot(fn) {
16
+ const root = mkdtempSync(join(tmpdir(), 'gotry-metrics-'));
17
+ const stateDir = join(root, 'gotry-state');
18
+ mkdirSync(stateDir, {
19
+ recursive: true
20
+ });
21
+ try {
22
+ return await fn(root, stateDir);
23
+ } finally{
24
+ rmSync(root, {
25
+ recursive: true,
26
+ force: true
27
+ });
28
+ }
29
+ }
30
+ function writeJsonl(dir, name, lines) {
31
+ writeFileSync(join(dir, name), lines.map((l)=>typeof l === 'string' ? l : JSON.stringify(l)).join('\n') + '\n', 'utf-8');
32
+ }
33
+ check('事实闸聚合:去重/blocked 率/坏行容忍', ()=>{
34
+ const stats = aggregateFacts([
35
+ {
36
+ schema: 'gotry_bookable_fact.v1',
37
+ fact_id: 'f1',
38
+ kind: 'flight_like',
39
+ verdict: 'hit'
40
+ },
41
+ 'NOT JSON',
42
+ {
43
+ schema: 'gotry_bookable_fact.v1',
44
+ fact_id: 'f1',
45
+ kind: 'flight_like',
46
+ verdict: 'error'
47
+ },
48
+ {
49
+ schema: 'gotry_bookable_fact.v1',
50
+ fact_id: 'f2',
51
+ kind: 'flight_like',
52
+ verdict: 'hit'
53
+ },
54
+ {
55
+ schema: 'gotry_bookable_fact.v1',
56
+ fact_id: 'f3',
57
+ kind: 'hotel',
58
+ verdict: 'needs-setup'
59
+ },
60
+ {
61
+ schema: 'gotry_bookable_fact.v1',
62
+ fact_id: 'f4',
63
+ kind: 'hotel',
64
+ verdict: 'miss'
65
+ },
66
+ {
67
+ fact_id: 'f5',
68
+ kind: 'hotel'
69
+ }
70
+ ]);
71
+ assert.equal(stats.lines, 7);
72
+ assert.equal(stats.deduped, 5);
73
+ const flight = stats.byKind.flight_like;
74
+ assert.equal(flight.total, 2);
75
+ assert.equal(flight.verdicts.error, 1);
76
+ assert.equal(flight.verdicts.hit, 1);
77
+ assert.equal(flight.blocked, 1);
78
+ const hotel = stats.byKind.hotel;
79
+ assert.equal(hotel.total, 3);
80
+ assert.equal(hotel.blocked, 1);
81
+ assert.equal(hotel.verdicts['n/a'], 1);
82
+ });
83
+ check('通道健康聚合:30 天窗/最新事件/计数', ()=>{
84
+ const stats = aggregateChannels([
85
+ {
86
+ channel: 'flyai',
87
+ state: 'down',
88
+ reason: 'trial-exhausted',
89
+ at: iso(40)
90
+ },
91
+ {
92
+ channel: 'flyai',
93
+ state: 'down',
94
+ reason: '429',
95
+ at: iso(2)
96
+ },
97
+ {
98
+ channel: 'flyai',
99
+ state: 'cooldown',
100
+ reason: 'retry-after',
101
+ at: iso(1)
102
+ },
103
+ {
104
+ channel: 'session:ctrip-hotel',
105
+ state: 'down',
106
+ reason: 'needs-login',
107
+ at: iso(5)
108
+ },
109
+ 'BROKEN'
110
+ ], {
111
+ now: NOW
112
+ });
113
+ assert.equal(stats.latest.length, 2);
114
+ const flyai = stats.latest.find((c)=>c.channel === 'flyai');
115
+ assert.equal(flyai?.state, 'cooldown');
116
+ assert.equal(flyai?.events, 2);
117
+ const ctrip = stats.latest.find((c)=>c.channel === 'session:ctrip-hotel');
118
+ assert.equal(ctrip?.events, 1);
119
+ });
120
+ check('事故面聚合:窗口/kind 计数/最近一条/top 来源', ()=>{
121
+ const stats = aggregateIncidents([
122
+ {
123
+ ts: iso(10),
124
+ kind: 'uncaughtException',
125
+ message: '太旧不计',
126
+ source: 'old-plugin'
127
+ },
128
+ {
129
+ ts: iso(1),
130
+ kind: 'plugin_error',
131
+ message: 'z3 wasm boom',
132
+ source: 'z3'
133
+ },
134
+ {
135
+ ts: iso(0.2),
136
+ kind: 'plugin_error',
137
+ message: 'second boom',
138
+ source: 'z3'
139
+ },
140
+ {
141
+ ts: iso(0.1),
142
+ kind: 'tool_execute_error',
143
+ message: 'flyai 429',
144
+ source: 'flyai'
145
+ }
146
+ ], {
147
+ now: NOW,
148
+ windowDays: 7
149
+ });
150
+ assert.equal(stats.totalInWindow, 3);
151
+ assert.equal(stats.byKind.plugin_error.count, 2);
152
+ assert.equal(stats.byKind.plugin_error.lastMessage, 'second boom');
153
+ assert.deepEqual(stats.topSources[0], {
154
+ source: 'z3',
155
+ count: 2
156
+ });
157
+ });
158
+ check('桥延迟聚合:p50/p95/max/超预算', ()=>{
159
+ const stats = aggregateLatency([
160
+ {
161
+ ts: iso(1),
162
+ kind: 'hotel_search:hbcli',
163
+ latencyMs: 100
164
+ },
165
+ {
166
+ ts: iso(1),
167
+ kind: 'hotel_search:hbcli',
168
+ latencyMs: 200
169
+ },
170
+ {
171
+ ts: iso(0.5),
172
+ kind: 'weather:open-meteo',
173
+ latencyMs: 300
174
+ },
175
+ {
176
+ ts: iso(0.4),
177
+ kind: 'weather:open-meteo',
178
+ latencyMs: 400
179
+ },
180
+ {
181
+ ts: iso(0.3),
182
+ kind: 'hotel_search:session',
183
+ latencyMs: 501
184
+ },
185
+ {
186
+ ts: iso(9),
187
+ kind: 'old',
188
+ latencyMs: 999_999
189
+ },
190
+ {
191
+ ts: iso(0.2),
192
+ kind: 'bad',
193
+ latencyMs: Number.NaN
194
+ }
195
+ ], {
196
+ now: NOW,
197
+ windowDays: 7
198
+ });
199
+ assert.equal(stats.count, 5);
200
+ assert.equal(stats.p50, 300);
201
+ assert.equal(stats.p95, 501);
202
+ assert.equal(stats.max, 501);
203
+ assert.equal(stats.overBudget, 1);
204
+ assert.deepEqual(stats.topKinds[0], {
205
+ kind: 'hotel_search:hbcli',
206
+ count: 2
207
+ });
208
+ });
209
+ await withRoot(async (root)=>{
210
+ const before = readdirSync(root).sort();
211
+ const snap = await collectMetrics(root, {
212
+ now: NOW
213
+ });
214
+ const md = renderMetricsReport(snap);
215
+ assert.match(md, /事实闸 verdict 分布/);
216
+ assert.match(md, /通道健康/);
217
+ assert.match(md, /事故面/);
218
+ assert.match(md, /桥延迟/);
219
+ assert.match(md, /\(空:窗口内无事故\)/);
220
+ assert.match(md, /不存在\(该 stateRoot 无账本\)/);
221
+ assert.deepEqual(readdirSync(root).sort(), before, 'collect 不得在 stateRoot 新建任何文件');
222
+ checkCount += 1;
223
+ console.log(` ok ${checkCount} 空 stateRoot:报告成型且零写入`);
224
+ });
225
+ await withRoot(async (root, stateDir)=>{
226
+ writeJsonl(stateDir, 'bookable-facts.jsonl', [
227
+ {
228
+ schema: 'gotry_bookable_fact.v1',
229
+ fact_id: 'a',
230
+ kind: 'hotel',
231
+ verdict: 'hit'
232
+ },
233
+ {
234
+ schema: 'gotry_bookable_fact.v1',
235
+ fact_id: 'b',
236
+ kind: 'hotel',
237
+ verdict: 'error'
238
+ }
239
+ ]);
240
+ writeJsonl(stateDir, 'channel-health.jsonl', [
241
+ {
242
+ channel: 'flyai',
243
+ state: 'down',
244
+ reason: 'trial-exhausted',
245
+ at: iso(1)
246
+ }
247
+ ]);
248
+ writeJsonl(stateDir, 'incidents.jsonl', [
249
+ {
250
+ ts: iso(1),
251
+ kind: 'plugin_error',
252
+ message: 'boom',
253
+ source: 'z3'
254
+ }
255
+ ]);
256
+ writeJsonl(stateDir, 'bridge-latency.jsonl', [
257
+ {
258
+ ts: iso(1),
259
+ kind: 'hotel_search:hbcli',
260
+ latencyMs: 120
261
+ }
262
+ ]);
263
+ writeFileSync(join(stateDir, 'doctor-report.md'), '# doctor\n', 'utf-8');
264
+ const before = readdirSync(stateDir).sort();
265
+ const outPath = join(root, 'report.md');
266
+ await main([
267
+ '--state-root',
268
+ root,
269
+ '--out',
270
+ outPath,
271
+ '--days',
272
+ '7'
273
+ ]);
274
+ const md = readFileSync(outPath, 'utf-8');
275
+ assert.match(md, /hotel \| 2 \| hit:1 \/ error:1 \| 1\/2\(50%\)/);
276
+ assert.match(md, /\| flyai \| down \| trial-exhausted \|/);
277
+ assert.match(md, /plugin_error \| 1 /);
278
+ assert.match(md, /p50=120ms/);
279
+ assert.match(md, /doctor 报告 doctor-report\.md:存在/);
280
+ assert.deepEqual(readdirSync(stateDir).sort(), before, '报告生成不得改动 gotry-state 目录');
281
+ assert.ok(existsSync(outPath));
282
+ checkCount += 1;
283
+ console.log(` ok ${checkCount} 端到端:侧车聚合→--out 落盘,stateRoot 只读`);
284
+ });
285
+ console.log(`\nMETRICS REPORT TESTS OK(${checkCount} checks)`);
286
+
287
+
288
+ //# sourceURL=ts/scripts/metrics-report-tests.ts
@@ -0,0 +1,19 @@
1
+ import assert from 'node:assert/strict';
2
+ import { readFileSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ const patch = readFileSync(join(process.cwd(), '..', 'cordis.gotry-patch.yml'), 'utf-8');
5
+ const guard = '表层规则:gotry 的全部能力一律是【工具调用】';
6
+ assert.equal((patch.match(new RegExp(guard, 'g')) ?? []).length, 1, '表层规则句应恰好出现一次');
7
+ assert.ok(patch.includes('绝不把 gotry_* 名字传给 skill 加载器'), '应显式禁止 gotry_* 进 skill 加载器');
8
+ const markers = new Set([
9
+ ...patch.matchAll(/\((\d{1,2})\)/g)
10
+ ].map((m)=>Number(m[1])));
11
+ for(let i = 1; i <= 22; i += 1){
12
+ assert.ok(markers.has(i), `行为契约 (${i}) 应存在`);
13
+ }
14
+ assert.equal(markers.size, 22, `契约条目应恰为 22 条,实际 ${markers.size}`);
15
+ assert.ok(patch.includes('改回 tool call'), '应指引失败后改回 tool call');
16
+ console.log('PERSONA SURFACE GUARD TESTS: 3/3 OK(表层规则句 / 22 条契约完整 / skill 失败行为指引)');
17
+
18
+
19
+ //# sourceURL=ts/scripts/persona-surface-guard-tests.ts