@danceiny/gotry 0.0.1-rc.15 → 0.0.1-rc.16

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 (139) hide show
  1. package/README.md +10 -6
  2. package/README.zh-CN.md +10 -6
  3. package/bin/gotry-bootstrap.js +294 -5
  4. package/cordis.gotry-patch.yml +10 -0
  5. package/data/airline-airports.json +39 -0
  6. package/dist/capabilities/agent-reach-deep.js +1 -1
  7. package/dist/capabilities/agent-reach.js +1 -1
  8. package/dist/capabilities/anything.js +1 -1
  9. package/dist/capabilities/artifacts.js +1 -1
  10. package/dist/capabilities/effect.js +293 -0
  11. package/dist/capabilities/fact-log.js +41 -0
  12. package/dist/capabilities/flyai.js +1 -1
  13. package/dist/capabilities/hbcli.js +43 -1
  14. package/dist/capabilities/incident-log.js +1 -1
  15. package/dist/capabilities/opensky.js +1 -1
  16. package/dist/capabilities/resilience.js +90 -0
  17. package/dist/capabilities/session/action-cache.js +1 -1
  18. package/dist/capabilities/session/adapters/ctrip-flight.js +1 -1
  19. package/dist/capabilities/session/adapters/meituan-local.js +1 -1
  20. package/dist/capabilities/session/benchmark.js +2 -1
  21. package/dist/capabilities/session/extension-bridge.js +345 -0
  22. package/dist/capabilities/session/extension-channel.js +93 -0
  23. package/dist/capabilities/session/extract.js +1 -1
  24. package/dist/capabilities/session/health-watch.js +216 -0
  25. package/dist/capabilities/session/read-guard.js +1 -1
  26. package/dist/capabilities/session/transport.js +1 -1
  27. package/dist/capabilities/session/wizard.js +377 -0
  28. package/dist/capabilities/session-consent.js +1 -1
  29. package/dist/capabilities/session-login.js +77 -1
  30. package/dist/capabilities/session-search.js +69 -2
  31. package/dist/capabilities/weather.js +1 -1
  32. package/dist/scripts/action-cache-tests.js +1 -1
  33. package/dist/scripts/agent-reach-deep-tests.js +1 -1
  34. package/dist/scripts/agent-reach-tests.js +1 -1
  35. package/dist/scripts/agent-reach-wrapper-tests.js +1 -1
  36. package/dist/scripts/anything-tests.js +1 -1
  37. package/dist/scripts/async-collect.js +1 -1
  38. package/dist/scripts/booking-saga-tests.js +1 -1
  39. package/dist/scripts/bootstrap-tests.js +39 -3
  40. package/dist/scripts/build-changelog.js +279 -0
  41. package/dist/scripts/changelog-tests.js +165 -0
  42. package/dist/scripts/companion-tests.js +1 -1
  43. package/dist/scripts/diff-test.js +1 -1
  44. package/dist/scripts/effect-tests.js +474 -0
  45. package/dist/scripts/engine-run.js +1 -1
  46. package/dist/scripts/engine-tests.js +1 -1
  47. package/dist/scripts/extension-tests.js +468 -0
  48. package/dist/scripts/fact-gate-tests.js +318 -0
  49. package/dist/scripts/flyai-tests.js +1 -1
  50. package/dist/scripts/hbcli-e2e-tests.js +180 -0
  51. package/dist/scripts/hbcli-tests.js +1 -1
  52. package/dist/scripts/health-watch-cli.js +51 -0
  53. package/dist/scripts/i18n-tests.js +1 -1
  54. package/dist/scripts/incident-tests.js +1 -1
  55. package/dist/scripts/journey-tests.js +1 -1
  56. package/dist/scripts/ledger-tests.js +1 -1
  57. package/dist/scripts/ledger-workflow-crash.js +1 -1
  58. package/dist/scripts/memory-capture-tests.js +1 -1
  59. package/dist/scripts/memory-decay-tests.js +1 -1
  60. package/dist/scripts/memory-metrics.js +1 -1
  61. package/dist/scripts/memory-value-report.js +1 -1
  62. package/dist/scripts/nightly-evidence-tests.js +67 -4
  63. package/dist/scripts/nightly-evidence.js +59 -3
  64. package/dist/scripts/nudge-digest.js +1 -1
  65. package/dist/scripts/onboarding-tests.js +218 -0
  66. package/dist/scripts/opensky-check.js +1 -1
  67. package/dist/scripts/opensky-tests.js +1 -1
  68. package/dist/scripts/price-drift-tests.js +441 -0
  69. package/dist/scripts/price-drift-watch.js +491 -0
  70. package/dist/scripts/probe-poi-tests.js +1 -1
  71. package/dist/scripts/product-metrics.js +1 -1
  72. package/dist/scripts/publish-preverify.js +1 -1
  73. package/dist/scripts/realtime-pricing-tests.js +1 -1
  74. package/dist/scripts/replay-async.js +1 -1
  75. package/dist/scripts/replay-real.js +1 -1
  76. package/dist/scripts/replay.js +1 -1
  77. package/dist/scripts/session-attach-diagnose.js +1 -1
  78. package/dist/scripts/session-attach-poc.js +1 -1
  79. package/dist/scripts/session-benchmark.js +1 -1
  80. package/dist/scripts/session-extract-tests.js +1 -1
  81. package/dist/scripts/session-login.js +1 -1
  82. package/dist/scripts/session-tests.js +1 -1
  83. package/dist/scripts/skeleton-check.js +1 -1
  84. package/dist/scripts/skeleton-integration-test.js +1 -1
  85. package/dist/scripts/skills-contract-tests.js +1 -1
  86. package/dist/scripts/smoke.js +76 -1
  87. package/dist/scripts/state-cli-tests.js +1 -1
  88. package/dist/scripts/state-cli.js +1 -1
  89. package/dist/scripts/time-eval-tests.js +1 -1
  90. package/dist/scripts/travel-timeline-tests.js +1 -1
  91. package/dist/scripts/unified-tests.js +1 -1
  92. package/dist/scripts/weather-tests.js +1 -1
  93. package/dist/scripts/wizard-bootstrap.js +32 -0
  94. package/dist/scripts/z3-race-tests.js +1 -1
  95. package/dist/src/artifact-gate.js +310 -0
  96. package/dist/src/bookable-facts.js +349 -0
  97. package/dist/src/booking-saga.js +1 -1
  98. package/dist/src/bridge.js +1 -1
  99. package/dist/src/companions.js +1 -1
  100. package/dist/src/contracts.js +1 -1
  101. package/dist/src/dsh-llm.js +1 -1
  102. package/dist/src/engine.js +1 -1
  103. package/dist/src/i18n.js +1 -1
  104. package/dist/src/index.js +189 -46
  105. package/dist/src/journey.js +1 -1
  106. package/dist/src/loop.js +1 -1
  107. package/dist/src/memory-capture.js +1 -1
  108. package/dist/src/memory-decay.js +1 -1
  109. package/dist/src/memory-utility.js +1 -1
  110. package/dist/src/mock-llm.js +1 -1
  111. package/dist/src/model.js +1 -1
  112. package/dist/src/realtime-pricing.js +14 -7
  113. package/dist/src/slot-spec.js +1 -1
  114. package/dist/src/state-ledger.js +1 -1
  115. package/dist/src/time-anchor.js +1 -1
  116. package/dist/src/tool-packet.js +1 -1
  117. package/dist/src/travel-slots.js +1 -1
  118. package/dist/src/travel-timeline.js +1 -1
  119. package/dist/src/unified.js +1 -1
  120. package/dist/src/wish-pool.js +1 -1
  121. package/dist/src/z3-shared.js +1 -1
  122. package/extension/README.md +26 -0
  123. package/extension/background.js +160 -0
  124. package/extension/content-bridge.js +41 -0
  125. package/extension/content-main.js +62 -0
  126. package/extension/manifest.json +32 -0
  127. package/package.json +6 -2
  128. package/ts/capabilities/fact-log.ts +54 -0
  129. package/ts/capabilities/hbcli.ts +46 -0
  130. package/ts/capabilities/session/benchmark.ts +4 -1
  131. package/ts/capabilities/session/extension-bridge.ts +354 -0
  132. package/ts/capabilities/session/extension-channel.ts +92 -0
  133. package/ts/capabilities/session/health-watch.ts +257 -0
  134. package/ts/capabilities/session/wizard.ts +282 -0
  135. package/ts/capabilities/session-login.ts +53 -2
  136. package/ts/capabilities/session-search.ts +68 -3
  137. package/ts/src/artifact-gate.ts +365 -0
  138. package/ts/src/bookable-facts.ts +557 -0
  139. package/ts/src/index.ts +102 -8
@@ -493,6 +493,81 @@ async function main() {
493
493
  });
494
494
  console.log(`artifacts: ledger run + cwd md discovered; read window(${r2.ok ? r2.lines.length : '?'} lines @10) + read card; path/ext guardrails hold`);
495
495
  }
496
+ {
497
+ const { appendFacts, loadFactRegistry } = await import('../capabilities/fact-log.js');
498
+ const { factsFromFlyai } = await import('../src/bookable-facts.js');
499
+ const avMap = JSON.parse(await readFile(join(import.meta.dirname, '..', '..', 'data', 'airline-airports.json'), 'utf-8'));
500
+ const fetchedAt = '2026-08-29T00:00:00.000Z';
501
+ await appendFacts(smokeRoot, [
502
+ ...factsFromFlyai({
503
+ kind: 'flight',
504
+ origin: '香港',
505
+ destination: '普吉',
506
+ date: '2027-07-18'
507
+ }, {
508
+ verdict: 'hit',
509
+ options: [
510
+ {
511
+ no: 'CX771',
512
+ name: '国泰航空',
513
+ depDateTime: '2027-07-18T08:05:00+08:00',
514
+ arrDateTime: '2027-07-18T10:35:00+07:00',
515
+ price: 1234
516
+ }
517
+ ],
518
+ evidence: ''
519
+ }, fetchedAt, avMap.city_alias),
520
+ ...factsFromFlyai({
521
+ kind: 'flight',
522
+ origin: '深圳',
523
+ destination: '普吉',
524
+ date: '2027-07-16'
525
+ }, {
526
+ verdict: 'miss',
527
+ options: [],
528
+ evidence: ''
529
+ }, fetchedAt, avMap.city_alias)
530
+ ]);
531
+ const registry = await loadFactRegistry(smokeRoot);
532
+ const probeFacts = registry.filter((f)=>f.kind !== 'policy' && (f.query_id.endsWith(':2027-07-16') || f.query_id.endsWith(':2027-07-18')));
533
+ if (probeFacts.length !== 2 || !probeFacts.some((f)=>f.bookability === 'unavailable_exact_date') || !probeFacts.some((f)=>f.bookability === 'bookable_exact_date')) {
534
+ throw new Error(`FAIL: 事实侧车应落 1 正 1 负两条,实际:${JSON.stringify(probeFacts.map((f)=>f.bookability))}`);
535
+ }
536
+ const gate = byName('gotry_fact_gate');
537
+ const passMd = [
538
+ '# 行程片段',
539
+ '## D3 7.18 香港 → 普吉',
540
+ '- 国泰航空 CX771 08:05→10:35(已按 exact-date 检索记录)'
541
+ ].join('\n');
542
+ const pass = await gate.execute({
543
+ query: {
544
+ markdown: passMd,
545
+ tripYear: 2027
546
+ }
547
+ }, null);
548
+ if (pass.verdict !== 'pass' || pass.presentation !== 'verified_itinerary_allowed') {
549
+ throw new Error(`FAIL: 可回溯产物应 pass,实际:${JSON.stringify(pass).slice(0, 300)}`);
550
+ }
551
+ const badMd = [
552
+ '# 行程片段',
553
+ '## D1 7.16 深圳 → 普吉',
554
+ '- 香港快运 UO784 10:05→12:40 直飞 ✓'
555
+ ].join('\n');
556
+ const blocked = await gate.execute({
557
+ query: {
558
+ markdown: badMd,
559
+ tripYear: 2027
560
+ }
561
+ }, null);
562
+ if (blocked.verdict !== 'blocked' || !blocked.violations?.some((v)=>v.kind === 'not_in_source')) {
563
+ throw new Error(`FAIL: exact-date miss 被 UO784 填充必须 blocked(not_in_source),实际:${JSON.stringify(blocked).slice(0, 300)}`);
564
+ }
565
+ const view = gate.presentResult?.({
566
+ query: {}
567
+ }, blocked);
568
+ if (!view?.title?.includes('blocked')) throw new Error(`FAIL: blocked 呈现卡标题应含 blocked,实际:${view?.title}`);
569
+ console.log(`fact gate: registry 1+1(hit/miss);verified 措辞 pass;miss 填充 UO784 blocked(not_in_source) + 呈现卡`);
570
+ }
496
571
  rmSync(smokeRoot, {
497
572
  recursive: true,
498
573
  force: true
@@ -506,4 +581,4 @@ main().catch((e)=>{
506
581
  });
507
582
 
508
583
 
509
- //# sourceURL=/Users/bytedance/work/gotry/ts/scripts/smoke.ts
584
+ //# sourceURL=/Users/bytedance/work/gotry-publish/ts/scripts/smoke.ts
@@ -133,4 +133,4 @@ console.log(`\nSTATE-CLI TESTS: ${pass} ok, ${fail} fail`);
133
133
  if (fail > 0) process.exit(1);
134
134
 
135
135
 
136
- //# sourceURL=/Users/bytedance/work/gotry/ts/scripts/state-cli-tests.ts
136
+ //# sourceURL=/Users/bytedance/work/gotry-publish/ts/scripts/state-cli-tests.ts
@@ -245,4 +245,4 @@ switch(cmd){
245
245
  }
246
246
 
247
247
 
248
- //# sourceURL=/Users/bytedance/work/gotry/ts/scripts/state-cli.ts
248
+ //# sourceURL=/Users/bytedance/work/gotry-publish/ts/scripts/state-cli.ts
@@ -315,4 +315,4 @@ if (process.argv.includes('--real')) {
315
315
  }
316
316
 
317
317
 
318
- //# sourceURL=/Users/bytedance/work/gotry/ts/scripts/time-eval-tests.ts
318
+ //# sourceURL=/Users/bytedance/work/gotry-publish/ts/scripts/time-eval-tests.ts
@@ -120,4 +120,4 @@ pass('trip_id 语义派生:确定性可重放', ()=>{
120
120
  console.log(`\nTRAVEL TIMELINE TESTS: ${n}/7 OK(memory-design P1 守门面)`);
121
121
 
122
122
 
123
- //# sourceURL=/Users/bytedance/work/gotry/ts/scripts/travel-timeline-tests.ts
123
+ //# sourceURL=/Users/bytedance/work/gotry-publish/ts/scripts/travel-timeline-tests.ts
@@ -46,4 +46,4 @@ assert.deepEqual(r4.work_window_exclusions, []);
46
46
  console.log(`TS UNIFIED TESTS: 4/4 OK(工作窗口生效,f2=VZ303,¥${r1.money_cny})`);
47
47
 
48
48
 
49
- //# sourceURL=/Users/bytedance/work/gotry/ts/scripts/unified-tests.ts
49
+ //# sourceURL=/Users/bytedance/work/gotry-publish/ts/scripts/unified-tests.ts
@@ -52,4 +52,4 @@ console.log('5. WMO 码映射 OK');
52
52
  console.log('\nWEATHER TESTS: 6/6 OK(Open-Meteo 真实 API,免费无 key;普吉岛走 Nominatim 兜底层)');
53
53
 
54
54
 
55
- //# sourceURL=/Users/bytedance/work/gotry/ts/scripts/weather-tests.ts
55
+ //# sourceURL=/Users/bytedance/work/gotry-publish/ts/scripts/weather-tests.ts
@@ -0,0 +1,32 @@
1
+ import process from 'node:process';
2
+ import { runOnboardingWizard } from '../capabilities/session/wizard.js';
3
+ function parseArgs(argv) {
4
+ const args = {};
5
+ for(let i = 0; i < argv.length; i += 1){
6
+ const cur = argv[i];
7
+ if (cur === '--extension-dir') args.extensionDir = argv[++i];
8
+ else if (cur === '--source-dir') args.sourceDir = argv[++i];
9
+ }
10
+ return args;
11
+ }
12
+ async function main() {
13
+ const args = parseArgs(process.argv.slice(2));
14
+ const result = await runOnboardingWizard({
15
+ dryRun: false,
16
+ ...args.extensionDir ? {
17
+ extensionDir: args.extensionDir
18
+ } : {},
19
+ ...args.sourceDir ? {
20
+ sourceDir: args.sourceDir
21
+ } : {}
22
+ });
23
+ process.stdout.write(JSON.stringify(result) + '\n');
24
+ process.exit(0);
25
+ }
26
+ main().catch((e)=>{
27
+ process.stderr.write(`[wizard-bootstrap] error: ${e instanceof Error ? e.message : String(e)}\n`);
28
+ process.exit(2);
29
+ });
30
+
31
+
32
+ //# sourceURL=/Users/bytedance/work/gotry-publish/ts/scripts/wizard-bootstrap.ts
@@ -32,4 +32,4 @@ for(let round = 0; round < ROUNDS; round++){
32
32
  console.log(`Z3 RACE TESTS OK(进程内三形态并发 ×${ROUNDS}:判定与顺序基线一致,零 WASM 降级)`);
33
33
 
34
34
 
35
- //# sourceURL=/Users/bytedance/work/gotry/ts/scripts/z3-race-tests.ts
35
+ //# sourceURL=/Users/bytedance/work/gotry-publish/ts/scripts/z3-race-tests.ts
@@ -0,0 +1,310 @@
1
+ import { flightClaimVerdict, itineraryInvariants, latestFactsForRouteDate } from './bookable-facts.js';
2
+ export const ARTIFACT_GATE_SCHEMA = 'gotry_artifact_gate.v1';
3
+ const FLIGHT_NO = /(?<![A-Za-z0-9])([A-Z0-9]{2}\d{3,4})(?![\d])/g;
4
+ const CARRIER_DIRECT = /(?<![A-Za-z0-9])([A-Z0-9]{2})(?!\d)(?:\s|[一-龥]){0,6}直飞/g;
5
+ const CARRIER_ZH = {
6
+ 东航: 'MU',
7
+ 南航: 'CZ',
8
+ 国航: 'CA',
9
+ 厦航: 'MF',
10
+ 春秋: '9C',
11
+ 祥鹏: '8L',
12
+ 亚航: 'FD',
13
+ 越捷: 'VZ',
14
+ 国泰: 'CX',
15
+ 港航: 'HX',
16
+ 香港快运: 'UO',
17
+ 阿联酋: 'EK',
18
+ 泰航: 'TG'
19
+ };
20
+ const POLICY_WORD = /免签|落地签|签证|入境申报|过境免/;
21
+ const AS_OF_WORD = /截至\s*\d{4}[-/年]\d{1,2}|as[_ ]?of\s*\d{4}/i;
22
+ const CHECK_MARK = /[✓✅]/;
23
+ function hasDirectAssertion(line) {
24
+ const idx = line.indexOf('直飞');
25
+ if (idx < 0) return false;
26
+ const before = line.slice(Math.max(0, idx - 3), idx);
27
+ return !(before.endsWith('无') || before.endsWith('不') || before.endsWith('没有') || before.endsWith('未见'));
28
+ }
29
+ function routeCtxOf(text, map, defaultYear) {
30
+ const occ = [];
31
+ for (const [city, iata] of Object.entries(map.city_alias)){
32
+ let pos = text.indexOf(city);
33
+ while(pos >= 0){
34
+ occ.push({
35
+ city,
36
+ iata,
37
+ pos
38
+ });
39
+ pos = text.indexOf(city, pos + city.length);
40
+ }
41
+ }
42
+ occ.sort((a, b)=>a.pos - b.pos);
43
+ const ctx = {};
44
+ outer: for(let i = 0; i < occ.length; i++){
45
+ for(let j = i + 1; j < occ.length; j++){
46
+ if (occ[j].iata === occ[i].iata) continue;
47
+ const between = text.slice(occ[i].pos + occ[i].city.length, occ[j].pos);
48
+ if (/→|⇀|->/.test(between)) {
49
+ ctx.origin = occ[i].iata;
50
+ ctx.destination = occ[j].iata;
51
+ break outer;
52
+ }
53
+ }
54
+ }
55
+ if (!ctx.origin && occ.length >= 2) {
56
+ ctx.origin = occ[0].iata;
57
+ ctx.destination = occ[1].iata;
58
+ }
59
+ if (defaultYear) {
60
+ const dm = text.match(/(?<!\d)(\d{1,2})\.(\d{1,2})(?!\d)/) ?? text.match(/(?<!\d)(\d{2})-(\d{2})(?!\d)/);
61
+ if (dm) {
62
+ const mm = Number(dm[1]), dd = Number(dm[2]);
63
+ if (mm >= 1 && mm <= 12 && dd >= 1 && dd <= 31) {
64
+ ctx.date = `${defaultYear}-${String(mm).padStart(2, '0')}-${String(dd).padStart(2, '0')}`;
65
+ }
66
+ }
67
+ }
68
+ return ctx;
69
+ }
70
+ export function extractClaims(markdown, map, opts) {
71
+ const claims = {
72
+ flights: [],
73
+ policies: [],
74
+ airports: [],
75
+ direct_lines: []
76
+ };
77
+ const lines = markdown.split('\n');
78
+ let section = {};
79
+ for(let i = 0; i < lines.length; i++){
80
+ const line = lines[i];
81
+ const lineNo = i + 1;
82
+ if (/^#{1,4}\s/.test(line)) {
83
+ const ctx = routeCtxOf(line, map, opts?.trip_year);
84
+ section = {
85
+ origin: ctx.origin,
86
+ destination: ctx.destination,
87
+ date: ctx.date ?? section.date
88
+ };
89
+ continue;
90
+ }
91
+ const own = routeCtxOf(line, map, opts?.trip_year);
92
+ const origin = own.origin ?? section.origin;
93
+ const destination = own.destination ?? section.destination;
94
+ const date = own.date ?? section.date;
95
+ for (const m of line.matchAll(FLIGHT_NO)){
96
+ claims.flights.push({
97
+ line: lineNo,
98
+ text: line.trim().slice(0, 120),
99
+ flight_no: m[1].toUpperCase(),
100
+ origin,
101
+ destination,
102
+ date
103
+ });
104
+ }
105
+ for (const m of line.matchAll(CARRIER_DIRECT)){
106
+ const carrier = m[1].toUpperCase();
107
+ if (!/[A-Z]/.test(carrier)) continue;
108
+ if (new RegExp(`${carrier}\\d`).test(line)) continue;
109
+ claims.flights.push({
110
+ line: lineNo,
111
+ text: line.trim().slice(0, 120),
112
+ flight_no: '',
113
+ carrier_only: carrier,
114
+ origin,
115
+ destination,
116
+ date
117
+ });
118
+ }
119
+ if (hasDirectAssertion(line) && (origin || destination)) {
120
+ claims.direct_lines.push({
121
+ line: lineNo,
122
+ text: line.trim().slice(0, 120),
123
+ origin,
124
+ destination,
125
+ date
126
+ });
127
+ for (const [zh, code] of Object.entries(CARRIER_ZH)){
128
+ if (new RegExp(`(?<![一-龥])${zh}`).test(line) && !new RegExp(`${code}\\d`).test(line) && !claims.flights.some((c)=>c.line === lineNo && c.carrier_only === code)) {
129
+ claims.flights.push({
130
+ line: lineNo,
131
+ text: line.trim().slice(0, 120),
132
+ flight_no: '',
133
+ carrier_only: code,
134
+ origin,
135
+ destination,
136
+ date
137
+ });
138
+ }
139
+ }
140
+ }
141
+ if (POLICY_WORD.test(line)) {
142
+ claims.policies.push({
143
+ line: lineNo,
144
+ text: line.trim().slice(0, 160),
145
+ has_as_of: AS_OF_WORD.test(line)
146
+ });
147
+ }
148
+ for (const [city, carriers] of Object.entries(map.carrier_airport)){
149
+ const airports = [
150
+ ...new Set(Object.values(carriers))
151
+ ];
152
+ if (!airports.some((a)=>line.includes(a))) continue;
153
+ const mentioned = Object.keys(carriers).filter((c)=>new RegExp(`(?<![A-Za-z0-9])${c}(?![A-Za-z0-9])`).test(line));
154
+ if (mentioned.length === 0) continue;
155
+ const airport = airports.find((a)=>line.includes(a));
156
+ if (airport) claims.airports.push({
157
+ line: lineNo,
158
+ text: line.trim().slice(0, 120),
159
+ carriers: mentioned,
160
+ airport,
161
+ city
162
+ });
163
+ }
164
+ }
165
+ return claims;
166
+ }
167
+ function lineTimesContradict(text, dep, arr) {
168
+ if (!dep && !arr) return false;
169
+ const times = [
170
+ ...text.matchAll(/(?<!\d)(\d{1,2}:\d{2})(?!\d)/g)
171
+ ].map((m)=>m[1]);
172
+ if (times.length === 0) return false;
173
+ return !times.some((t)=>t === dep || t === arr);
174
+ }
175
+ export function gateArtifact(markdown, facts, map, opts) {
176
+ const claims = extractClaims(markdown, map, opts);
177
+ const violations = [];
178
+ let traceable = 0;
179
+ for (const c of claims.flights){
180
+ if (c.carrier_only) {
181
+ const hasCarrier = facts.some((f)=>f.kind === 'flight' && f.bookability === 'bookable_exact_date' && f.nonstop !== false && f.flight_no.startsWith(c.carrier_only) && (!c.origin || f.route.origin === c.origin || f.route.origin_airport === c.origin) && (!c.destination || f.route.destination === c.destination || f.route.dest_airport === c.destination) && (!c.date || f.date === c.date));
182
+ if (!hasCarrier) {
183
+ const scoped = c.origin && c.destination && c.date ? latestFactsForRouteDate(facts, c.origin, c.destination, c.date) : [];
184
+ violations.push({
185
+ kind: scoped.length > 0 ? 'not_in_source' : 'carrier_direct_unverified',
186
+ line: c.line,
187
+ detail: `承运级直飞断言「${c.carrier_only} 直飞」无 exact-date 证据(${c.origin ?? '?'}→${c.destination ?? '?'} ${c.date ?? '?'})` + (scoped.length > 0 && scoped.every((f)=>f.bookability === 'bookable_exact_date' && f.nonstop === false) ? '——该日可售仅有中转' : '——「可考虑/也许有」不得写入决策树')
188
+ });
189
+ }
190
+ continue;
191
+ }
192
+ const r = flightClaimVerdict(facts, c);
193
+ if (r.verdict === 'traceable') {
194
+ if (lineTimesContradict(c.text, r.fact?.dep_local, r.fact?.arr_local)) {
195
+ violations.push({
196
+ kind: 'contradicted',
197
+ line: c.line,
198
+ detail: `${c.flight_no} 时刻与 exact-date 快照不符(快照 ${r.fact?.dep_local}→${r.fact?.arr_local} ${r.fact?.date})——渲染层不得改写工具返回的时刻`
199
+ });
200
+ continue;
201
+ }
202
+ traceable++;
203
+ continue;
204
+ }
205
+ violations.push({
206
+ kind: r.verdict,
207
+ line: c.line,
208
+ detail: `${c.flight_no}: ${r.reason}`
209
+ });
210
+ if (CHECK_MARK.test(c.text)) {
211
+ violations.push({
212
+ kind: 'unconditional_check',
213
+ line: c.line,
214
+ detail: `对未核验航班 ${c.flight_no} 使用无条件 ✓/✅——只有 bookable_exact_date 才允许确定性标记`
215
+ });
216
+ }
217
+ }
218
+ for (const d of claims.direct_lines){
219
+ if (claims.flights.some((c)=>c.line === d.line)) continue;
220
+ if (!d.origin || !d.destination || !d.date) {
221
+ violations.push({
222
+ kind: 'route_unqueried',
223
+ line: d.line,
224
+ detail: `直飞断言缺 route/date 上下文,无法回溯——fail closed 按未核验处理`
225
+ });
226
+ continue;
227
+ }
228
+ const scoped = latestFactsForRouteDate(facts, d.origin, d.destination, d.date);
229
+ if (scoped.length === 0) {
230
+ violations.push({
231
+ kind: 'route_unqueried',
232
+ line: d.line,
233
+ detail: `${d.origin}→${d.destination} ${d.date} 的直飞断言从未经 exact-date 源核验`
234
+ });
235
+ continue;
236
+ }
237
+ const nonstopCount = scoped.filter((f)=>f.bookability === 'bookable_exact_date' && f.nonstop !== false).length;
238
+ if (nonstopCount === 0) {
239
+ violations.push({
240
+ kind: 'not_in_source',
241
+ line: d.line,
242
+ detail: `${d.origin}→${d.destination} ${d.date} exact-date 无任何在架直飞(仅中转或 0 条)——不得写直飞`
243
+ });
244
+ }
245
+ }
246
+ for (const a of claims.airports){
247
+ const table = map.carrier_airport[a.city] ?? {};
248
+ for (const carrier of a.carriers){
249
+ const expected = table[carrier];
250
+ if (expected && expected !== a.airport) {
251
+ violations.push({
252
+ kind: 'airport_mapping_conflict',
253
+ line: a.line,
254
+ detail: `${carrier} 在${a.city}落 ${expected}(映射快照 as_of ${map.meta.as_of}),产物写 ${a.airport}——双机场不互换,航司不合并映射`
255
+ });
256
+ }
257
+ }
258
+ }
259
+ for (const p of claims.policies){
260
+ if (!p.has_as_of) {
261
+ violations.push({
262
+ kind: 'policy_without_as_of',
263
+ line: p.line,
264
+ detail: `政策断言缺时间边界:动态政策只能表述为「截至 YYYY-MM-DD 的现行政策」并附复核日期`
265
+ });
266
+ }
267
+ if (CHECK_MARK.test(p.text) && !p.has_as_of) {
268
+ violations.push({
269
+ kind: 'unconditional_check',
270
+ line: p.line,
271
+ detail: `政策行使用无条件 ✓/✅`
272
+ });
273
+ }
274
+ }
275
+ markdown.split('\n').forEach((line, i)=>{
276
+ if (!line.includes('联程')) return;
277
+ const nos = [
278
+ ...line.matchAll(FLIGHT_NO)
279
+ ].map((m)=>m[1].toUpperCase());
280
+ const matched = facts.filter((f)=>f.kind !== 'policy' && nos.includes(f.flight_no.toUpperCase()));
281
+ const allProtected = nos.length > 0 && matched.length === nos.length && matched.every((f)=>f.protected_connection === true);
282
+ if (!allProtected) {
283
+ violations.push({
284
+ kind: 'self_transfer_called_through',
285
+ line: i + 1,
286
+ detail: '「联程」措辞无 protected_connection=true 证据——两段接得上不等于同票联程;分票必须标红自助转机(无行李直挂/误机无保护)'
287
+ });
288
+ }
289
+ });
290
+ if (opts?.itinerary) {
291
+ for (const v of itineraryInvariants(opts.itinerary)){
292
+ violations.push({
293
+ kind: v.kind,
294
+ line: 0,
295
+ detail: v.detail
296
+ });
297
+ }
298
+ }
299
+ return {
300
+ schema: ARTIFACT_GATE_SCHEMA,
301
+ verdict: violations.length > 0 ? 'blocked' : 'pass',
302
+ claims_checked: claims.flights.length + claims.policies.length + claims.airports.length,
303
+ traceable,
304
+ violations,
305
+ presentation: violations.length > 0 ? 'verified_label_forbidden' : 'verified_itinerary_allowed'
306
+ };
307
+ }
308
+
309
+
310
+ //# sourceURL=/Users/bytedance/work/gotry-publish/ts/src/artifact-gate.ts