/** * E04 关系覆盖与关键人经营的包内自测 —— **只准 import @sdk / @contracts / @sfeel**。 * 案例活动没有 `run`,自测不跑回合;测的是清单本身与四处对齐 * (判据谓词 / 台账声明的三级占位 / 决策卡循环的骨架 / 报告模版的读写面)。 */ import { describe, expect, it } from 'vitest'; import { activityManifestSchema } from '@contracts'; import { evaluateCondition } from '@sfeel'; import { createMockHost } from '@sdk'; import { manifest } from '../src/manifest.ts'; import activity from '../src/index.ts'; describe('清单本身合规', () => { it('过 @contracts 的 schema', () => { expect(() => activityManifestSchema.parse(manifest)).not.toThrow(); }); it('没有悬空监听(E04 本轮没有静态监听声明)', () => { expect(manifest.declarations.listeners).toEqual([]); }); it('L1/L2 判据都给了可机检的谓词 —— 验收等级不得谎报', () => { for (const c of manifest.contract.acceptanceCriteria) { if (c.level === 'L1' || c.level === 'L2') expect(c.predicate ?? c.modelRef, c.key).toBeDefined(); } }); it('禁区短而硬,授权边界给了调人预算', () => { expect(manifest.contract.prohibitions.length).toBeGreaterThan(0); expect(manifest.contract.authorization.humanCallBudget).toBeGreaterThan(0); }); it('每件必交付物都有台账地址', () => { for (const d of manifest.contract.requiredDeliverables) { expect(d.ledger, d.key).toBeDefined(); } }); /* * **人环预算必须严格大于「最后一次拍板」时的实际调人次数**(2026-08-20 实测栽过): * 基座按 `used >= cap` 判耗尽,若 cap 恰等于完成放行所需的调人数,「裁决:pass」 * 那一次回复本身就把 used 顶到 cap,harnessCheck 在 land() 之前就判 exhausted —— * 验收卡显示全部达标、人也点了通过,任务却终态 exhausted,草稿从没合并进正式版。 * 基线四次(S2/S4通过/S5/T_release)+ 两轮决策卡循环(每轮 +2)= 8,留够余量。 */ it('人环预算留够余量:基线四次 + 两轮决策卡循环仍在 cap 之内', () => { const baseline = 4; const twoRejectRounds = baseline + 4; expect(manifest.contract.authorization.humanCallBudget).toBeGreaterThan(twoRejectRounds); expect(manifest.contract.resources.budget.humanCalls).toBeGreaterThan(twoRejectRounds); }); it('起单只必填 trigger,eventType/customers/note 都可选 —— 两级下拉+客户数组的形状', () => { expect(manifest.contract.input.data['required']).toEqual(['trigger']); const properties = manifest.contract.input.data['properties'] as Record< string, { enum?: unknown[]; showWhen?: { field?: string; equals?: string } } >; for (const key of ['trigger', 'eventType', 'customers', 'note']) { expect(properties[key], `入参少了 ${key}`).toBeDefined(); } expect(properties['trigger']?.enum).toEqual(['monthly', 'event']); expect(properties['eventType']?.enum).toEqual(['new_customer', 'key_person_change', 'major_deal', 't04_project']); // eventType 只在 trigger:'event' 时该露面 —— 测试台靠这个字段做级联下拉 expect(properties['eventType']?.showWhen).toEqual({ field: 'trigger', equals: 'event' }); }); it('五条常备样例形状真不同:月度例行 1 条 + 事件驱动四种事件类型各 1 条', () => { expect(manifest.samples.length).toBe(5); const [majorDeal, monthly, keyPerson, newCustomer, t04] = manifest.samples; type Input = { trigger?: string; eventType?: string; customers?: unknown[]; note?: string }; expect((monthly?.input as Input).trigger).toBe('monthly'); expect((monthly?.input as Input).customers).toBeUndefined(); expect((majorDeal?.input as Input).eventType).toBe('major_deal'); expect(typeof (majorDeal?.input as Input).customers?.[0]).toBe('string'); expect((keyPerson?.input as Input).eventType).toBe('key_person_change'); expect((keyPerson?.input as Input).customers).not.toEqual((majorDeal?.input as Input).customers); // 新客户样例:customers 里是对象(新建 L-02 档案的字段),不是客户编码字符串 expect((newCustomer?.input as Input).eventType).toBe('new_customer'); const newCustomerItem = (newCustomer?.input as Input).customers?.[0] as { name?: string } | undefined; expect(typeof newCustomerItem).toBe('object'); expect(newCustomerItem?.name).toBeDefined(); // 攻坚项目(T04)立项:四种事件类型里最后一种,点名已有客户(字符串形状) expect((t04?.input as Input).eventType).toBe('t04_project'); expect((t04?.input as Input).customers).toEqual(['CU-0142']); // 四种事件类型两两不同,凑齐 enum 的全部取值 const eventTypes = new Set([majorDeal, keyPerson, newCustomer, t04].map((s) => (s?.input as Input).eventType)); expect(eventTypes).toEqual(new Set(['major_deal', 'key_person_change', 'new_customer', 't04_project'])); for (const s of manifest.samples) expect(s.expect.ports).toEqual(['coverage-map']); }); /* * **落到这个活动上就该看见一份填好的入参**(2026-08-23 用户:「询价受理起单时左边 * 都有预填好的活动输入,E04 也要」)。测试台自动铺的是 `samples[0]`,所以第一条 * 必须四个字段全有值 —— 从前第一条是月度例行、只有 trigger 一个键,表单看着像没预填。 * 每一条也都至少要有背景说明:整屏只有一个下拉有值的样例,人看不出这活动吃什么。 */ it('样例 0 是满载的那条(四个字段全有值)—— 测试台落地即预填', () => { const first = manifest.samples[0]?.input as Record; for (const key of ['trigger', 'eventType', 'customers', 'note']) { expect(first[key], `样例 0 缺 ${key}`).toBeDefined(); } expect((first['customers'] as unknown[]).length).toBeGreaterThan(0); }); it('每条样例都带本轮说明,且样例名 ≤14 字(超了测试台只显示「样例 N」)', () => { for (const s of manifest.samples) { expect((s.input as { note?: string }).note, `样例「${s.name}」没有 note`).toBeTruthy(); expect(s.name.length, `样例名「${s.name}」超过 14 字`).toBeLessThanOrEqual(14); } }); it('主数据追加列全部显式标 kind:master —— 与平台声明一致,不然静态审会拒', () => { for (const object of ['profile', 'contact']) { const decl = manifest.declarations.ledger.find((d) => d.domain === 'customer' && d.object === object); expect(decl?.kind, `customer.${object}`).toBe('master'); } }); it('influence / priority / completion_status 三个 tally 列的取值域全是 ASCII', () => { for (const d of manifest.declarations.ledger) { for (const f of d.fields) { for (const value of f.tally ?? []) { expect(/^[a-z0-9_]+$/.test(value), `${d.domain}.${d.object}.${f.name} 的取值 ${value} 不是纯 ASCII —— data-class-of 会把它清空成空 class`).toBe(true); } } } }); it('customer.profile 新增 cust_type:L-02 文档明列的字段,expect 而非 required,不带 tally', () => { const profile = manifest.declarations.ledger.find((d) => d.domain === 'customer' && d.object === 'profile'); const custType = profile?.fields.find((f) => f.name === 'cust_type'); expect(custType, 'customer.profile 缺 cust_type').toBeDefined(); expect(custType?.expect).toBe(true); expect(custType?.required).toBeUndefined(); expect(custType?.tally).toBeUndefined(); }); it('crm_coverage.verdict 新增 event_type 列(人读标签,月度例行留空)', () => { const verdict = manifest.declarations.ledger.find((d) => d.object === 'verdict'); expect(verdict?.fields.find((f) => f.name === 'event_type')).toBeDefined(); }); it('我方追加的批注列(change_log)不标 expect —— 标了会让 _gaps 永远大于零', () => { const contact = manifest.declarations.ledger.find((d) => d.domain === 'customer' && d.object === 'contact'); expect(contact?.fields.find((f) => f.name === 'change_log')?.expect).toBeUndefined(); }); it('候选客户清单的客户编码/客户名称都标 required —— 业务说明明文两列必填', () => { const candidate = manifest.declarations.ledger.find((d) => d.object === 'candidate'); expect(candidate?.fields.find((f) => f.name === 'customer_code')?.required).toBe(true); expect(candidate?.fields.find((f) => f.name === 'customer_name')?.required).toBe(true); }); it('receipt 的完成状态/结果摘要/关键人变动三列必填,客户反馈选填', () => { const receipt = manifest.declarations.ledger.find((d) => d.object === 'receipt'); for (const col of ['completion_status', 'result_summary', 'key_person_change']) { expect(receipt?.fields.find((f) => f.name === col)?.required, col).toBe(true); } expect(receipt?.fields.find((f) => f.name === 'customer_feedback')?.required).toBeUndefined(); }); it('两个工具都是纯工具(pure),批量入参 customers/contacts 优先于单条', () => { const declared = new Set(manifest.declarations.tools.map((t) => t.name)); expect(declared.has('crm-relationship-coverage.recompute-score')).toBe(true); expect(declared.has('crm-relationship-coverage.rank-key-persons')).toBe(true); }); }); describe('案例模型 —— 双表示同版本', () => { const model = manifest.assets.businessModel; const cmmn = parseCmmnLite(model.cmmnXml); it('CMMN 与说明书同在,不允许分叉', () => { expect(model.cmmnXml).toContain(' { expect(items(model.cmmnXml)).toEqual([ 'S1_identify_candidates', 'S2_scope_confirm', 'S3_plan', 'S4_plan_approval', 'S5_execute_receipt', 'S6_writeback', 'S6_ledger_update', 'T_release', ]); }); it('四处人的门都标了人工激活:核对范围 / 审批 / 执行回执 / 放行(识别/制定计划/写台账是查表或AI起草,不标)', () => { expect(cmmn.manualActivation).toEqual(['S2_scope_confirm', 'S4_plan_approval', 'S5_execute_receipt', 'T_release']); }); it('不用阶段 / 里程碑 / 裁量项', () => { for (const tag of [' { const manual = new Set(cmmn.manualActivation); for (const step of model.steps ?? []) { if (manual.has(step.item)) expect(step.doneWhen, `${step.item} 不该有 doneWhen`).toBeUndefined(); } }); it('AI 起草的三项都给了 doneWhen,且不是「未绑定名字恒真」那种坑', () => { const manual = new Set(cmmn.manualActivation); for (const step of model.steps ?? []) { if (manual.has(step.item)) continue; expect(step.doneWhen, `${step.item} 缺 doneWhen`).toBeDefined(); expect(step.doneWhen, `${step.item} 的 doneWhen 不许用 != null`).not.toContain('!= null'); } }); /* * **放行门的入场哨兵认「办完了没有」,四处全读基座数**(2026-08-02 那类「只有 onPart * 没有 ifPart 等于没有门」的坑,与「写台账要给行 rows 为空」的哨兵松绑同一类)。 */ it('放行门哨兵带 ifPart,四个条件全是基座读数或本步显式记账的旗', () => { const condition = sentryIf(model.cmmnXml, 's_ledger_updated'); expect(condition).toContain('ledger_update_confirmed_total'); expect(condition).toContain('crm_coverage_ledger_update_rows'); expect(condition).toContain('crm_coverage_ledger_update_missing'); expect(condition).toContain('customer_profile_missing'); expect(condition).toContain('customer_contact_missing'); const done = { ledger_update_confirmed_total: 1, crm_coverage_ledger_update_rows: 4, crm_coverage_ledger_update_missing: 0, customer_profile_missing: 0, customer_contact_missing: 0, }; expect(safeEval(condition, done)).toBe(true); // 写完了但主数据还缺必填 → 门不开 expect(safeEval(condition, { ...done, customer_profile_missing: 1 })).toBe(false); // 写过一笔但还没显式记账(旗还没置一)→ 门不开(踩过「只有 onPart 没有 ifPart」的坑) expect(safeEval(condition, { ...done, ledger_update_confirmed_total: 0 })).toBe(false); }); it('S3 制定计划的入场哨兵认「人核对过了」,不认「AI 起草过」', () => { const condition = sentryIf(model.cmmnXml, 's_scope_confirmed'); expect(condition).toContain('scope_confirmed_total'); expect(safeEval(condition, { scope_confirmed_total: 1 })).toBe(true); expect(safeEval(condition, {})).toBe(false); }); it('每个计划项都有剧本,guide 有实质内容', () => { const ids = items(model.cmmnXml); expect((model.steps ?? []).map((s) => s.item)).toEqual(ids); for (const step of model.steps ?? []) { expect(step.guide.length, `${step.item} 的做法`).toBeGreaterThan(80); expect(step.intent.length, `${step.item} 的意图`).toBeGreaterThan(6); } }); it('说明书不点名不存在的计划项', () => { const named = [...model.narrative.matchAll(/\b(?:S\d+_[A-Za-z_]+|T_[a-z]+)\b/g)].map((m) => m[0]); const ids = new Set(items(model.cmmnXml)); for (const name of named) { expect(ids.has(name), `说明书提到的 ${name} 不在案例模型里`).toBe(true); } }); /* * **决策卡循环三处写死的东西也进说明书**(照 A6 T_haggle 立的规矩): * itemId / 三个 value / allowCustom 断点那几拍模型手上只剩这份说明书。 */ it('说明书写死了决策卡的 itemId / 三个 value / allowCustom', () => { expect(model.narrative).toContain('S4_plan_approval'); for (const value of ['approve', 'reject', 'custom:']) { expect(model.narrative, `说明书少了 value ${value}`).toContain(value); } expect(model.narrative).toContain('allowCustom'); }); /* * **重发判断走在「问理由」之前**(2026-08-20 实测逼出来的修复):固定按钮点出来的 * 驳回永远没有 note,再问一遍是把人指向一扇打不开的门。S4 的 guide 必须写死 * 「不许再发卡追问理由」,而不是 A6 T_haggle 那种「找不到答复才发卡」的单发模式 * (E04 的决策卡允许多轮循环,与 A6 恰好相反,两处不能弄混)。 */ it('S4 的 guide 写死「reject 不许追问理由」,不是无脑重问', () => { const s4 = (model.steps ?? []).find((s) => s.item === 'S4_plan_approval'); expect(s4?.guide).toContain('不许再发卡追问理由'); }); /* * **新客户建档的数据录入卡骨架也写死进说明书**(与 S4 决策卡同一条纪律): * "断点"那几拍模型手上只保证摊开 NARRATIVE,S1 自己的 guide 不一定摊开。 * 照抄 A1 quote-intake 的 S1_customer 机制——`customer_code` 从不给 AI 编, * 必须由人在卡上现场给。 */ it('说明书写死了新客户录入卡的骨架:entry / customer.profile / customer_code 现场给', () => { expect(model.narrative).toContain('"kind":"entry"'); expect(model.narrative).toContain('"ledger":"customer.profile"'); expect(model.narrative).toContain('customer_code'); expect(model.narrative, '不许自己编客户编码').toContain('不能自己编一个'); }); it('S1 的 guide 写死「人确认后不许再 write 一遍 customer.profile/.contact」', () => { const s1 = (model.steps ?? []).find((s) => s.item === 'S1_identify_candidates'); expect(s1?.guide).toContain('不许你再'); expect(s1?.guide).toContain('customer.profile'); expect(s1?.guide).toContain('customer.contact'); }); }); describe('声明与契约对得上', () => { const columns = new Set( manifest.declarations.ledger.flatMap((d) => [`${d.domain}.${d.object}`, ...d.fields.map((f) => f.name)]), ); it('必交付物的台账地址都声明过', () => { for (const d of manifest.contract.requiredDeliverables) { expect(columns.has(d.ledger ?? ''), `${d.key} → ${d.ledger}`).toBe(true); } }); it('四件必交付物 editableAtRelease 全是空数组 —— 验收工单只读展示,不可修改', () => { for (const d of manifest.contract.requiredDeliverables) { expect(d.editableAtRelease, d.key).toEqual([]); } }); const rowReadings = new Set([ ...manifest.declarations.ledger.map((d) => `${d.domain}_${d.object}_rows`), ...manifest.declarations.ledger .filter((d) => d.fields.some((f) => f.required === true || f.requiredWhen !== undefined)) .map((d) => `${d.domain}_${d.object}_missing`), ...manifest.declarations.ledger.flatMap((d) => d.fields.flatMap((f) => (f.tally ?? []).map((v) => `${d.domain}_${d.object}_${f.name}_${v.toLowerCase().replace(/[^a-z0-9_]/g, '')}`), ), ), ]); it('L1/L2 谓词读的名字都有承载(台账列或基座读数)', () => { for (const c of manifest.contract.acceptanceCriteria) { if (c.level !== 'L1' && c.level !== 'L2') continue; for (const identifier of (c.predicate ?? '').match(/[a-z_][a-z0-9_]*/g) ?? []) { if (['and', 'or', 'not', 'true', 'false', 'null', 'in', 'between'].includes(identifier)) continue; if (rowReadings.has(identifier)) continue; if (identifier === 'plan_approved_total' || identifier === 'scope_confirmed_total') continue; // 一次性记账旗,直接是 caseFileItem expect(columns.has(identifier), `${c.key} 的谓词引用了 ${identifier}`).toBe(true); } } }); it('每条 L1/L2 都至少读一个基座读数或本步记账旗 —— 不许两边都由活动自报凑数', () => { for (const c of manifest.contract.acceptanceCriteria) { if (c.level !== 'L1' && c.level !== 'L2') continue; const names = (c.predicate ?? '').match(/[a-z_][a-z0-9_]*/g) ?? []; const grounded = names.some((n) => rowReadings.has(n) || n.endsWith('_total')); expect(grounded, `${c.key} 一个基座读数/记账旗都没读`).toBe(true); } }); it('单一必发口 coverage-map,无选择组(E04 全程线性,无分支)', () => { expect(manifest.contract.output.mandatory.map((p) => p.name)).toEqual(['coverage-map']); expect(manifest.contract.output.selective).toEqual([]); }); it('coverage-map 出口标量字段都有台账列承载', () => { const snake = (name: string) => name.replace(/[A-Z]/g, (m) => `_${m.toLowerCase()}`); const port = manifest.contract.output.mandatory[0]; const properties = (port?.payload as { properties?: Record }).properties ?? {}; for (const [field, spec] of Object.entries(properties)) { if (spec.type === 'array') continue; expect(columns.has(snake(field)), `${port?.name}.${field} → ${snake(field)}`).toBe(true); } }); it('L4 判据裁量语句整句出现在放行剧本里 —— judge 才对得上名', () => { const release = manifest.assets.businessModel.steps?.find((s) => s.item === 'T_release'); for (const c of manifest.contract.acceptanceCriteria) { if (c.level !== 'L4') continue; expect(release?.guide, `T_release 的 guide 要整句抄 ${c.key}`).toContain(c.label); } }); }); describe('报告模版 —— 活动持有,容器无脚本执行权', () => { it('四个局面各一张:候选清单 / 经营计划 / 执行回执 / 台账更新明细', () => { expect(manifest.assets.viewTemplates.map((t) => t.name)).toEqual([ 'coverage-scope', 'coverage-plan', 'coverage-receipt', 'coverage-ledger-update', ]); }); it('剧本每一步的 view 都在模版名单里', () => { const names = new Set(manifest.assets.viewTemplates.map((t) => t.name)); for (const step of manifest.assets.businessModel.steps ?? []) { expect(names.has(step.view ?? ''), `${step.item} 的 view=${step.view}`).toBe(true); } }); it('放行那一步摆的是台账更新明细 —— 最后摆上去的那张就是归档件', () => { const release = manifest.assets.businessModel.steps?.find((s) => s.item === 'T_release'); expect(release?.view).toBe('coverage-ledger-update'); }); it('模版里没有脚本、没有事件处理器、没有外链 —— 样式自带', () => { for (const template of manifest.assets.viewTemplates) { expect(template.html, template.name).not.toMatch(/'); } }); it('候选清单可编辑:data-edit 指向 crm_coverage.candidate,主键客户编码挂了 data-col', () => { const html = manifest.assets.viewTemplates.find((t) => t.name === 'coverage-scope')?.html ?? ''; expect(html).toContain('data-edit="crm_coverage.candidate"'); expect(html).toContain('data-col="customer_code"'); }); it('经营计划只读:没有任何 data-edit / data-col —— 人只审不改', () => { const html = manifest.assets.viewTemplates.find((t) => t.name === 'coverage-plan')?.html ?? ''; expect(html).not.toContain('data-edit='); expect(html).not.toContain('data-col='); }); it('台账更新明细只读:没有任何 data-edit / data-col —— 归档件不给改', () => { const html = manifest.assets.viewTemplates.find((t) => t.name === 'coverage-ledger-update')?.html ?? ''; expect(html).not.toContain('data-edit='); expect(html).not.toContain('data-col='); }); it('执行回执混排:主键联系人编码挂 data-col,四个可填列各自挂了 data-col', () => { const html = manifest.assets.viewTemplates.find((t) => t.name === 'coverage-receipt')?.html ?? ''; expect(html).toContain('data-edit="crm_coverage.receipt"'); const cols = new Set([...html.matchAll(/data-col="([^"]+)"/g)].map((m) => m[1])); expect(cols).toEqual( new Set(['contact_code', 'completion_status', 'result_summary', 'customer_feedback', 'key_person_change']), ); }); it('每个可编辑格子的 data-col 都在对应台账对象的声明里 —— 编辑面不能超出声明的字段', () => { const editableViews: Array<[string, string]> = [ ['coverage-scope', 'candidate'], ['coverage-receipt', 'receipt'], ]; for (const [view, object] of editableViews) { const html = manifest.assets.viewTemplates.find((t) => t.name === view)?.html ?? ''; const declared = new Set(manifest.declarations.ledger.find((d) => d.object === object)?.fields.map((f) => f.name)); for (const col of [...html.matchAll(/data-col="([^"]+)"/g)].map((m) => m[1])) { expect(declared.has(col ?? ''), `${view} 的 data-col="${col}" 不在 crm_coverage.${object} 的声明里`).toBe(true); } } }); it('缺必填红带与提交闸同源 —— 可编辑件上摆的是基座的 `_missing` 与主键名单', () => { for (const [name, object] of [ ['coverage-scope', 'candidate'], ['coverage-receipt', 'receipt'], ] as const) { const html = manifest.assets.viewTemplates.find((t) => t.name === name)?.html ?? ''; expect(html, name).toContain(`crm_coverage_${object}_missing`); expect(html, name).toContain(`crm_coverage_${object}_missing_keys`); } }); it('三个状态徽(候选状态/优先级/完成状态)只用 data-class-of 投影,不写 data-slot 重复暴露原值', () => { const scope = manifest.assets.viewTemplates.find((t) => t.name === 'coverage-scope')?.html ?? ''; expect(scope).toContain(''); const plan = manifest.assets.viewTemplates.find((t) => t.name === 'coverage-plan')?.html ?? ''; expect(plan).toContain(''); const receipt = manifest.assets.viewTemplates.find((t) => t.name === 'coverage-receipt')?.html ?? ''; expect(receipt).toContain(''); }); it('宿主拒推含脚本的模版', async () => { const host = createMockHost({ contract: CONTRACT }); await expect(host.base.interaction.pushHtml('
')).rejects.toThrow(/无脚本执行权/); }); }); const CONTRACT = { activityId: 'crm-relationship-coverage', version: '1.0.0', data: {}, message: undefined, outputPorts: { mandatory: ['coverage-map'], selective: [] }, prohibitions: [], discretion: [], } as const; describe('代码入口 —— 案例活动没有 run,不是残包', () => { it('默认导出是一个有名字的活动定义', () => { expect(activity.name).toBe('crm-relationship-coverage'); }); it('mock 宿主如实报「没有 run」,不静默返回一个假的 outcome', async () => { const host = createMockHost({ contract: CONTRACT }); await expect(host.run(activity)).rejects.toThrow(/没有 run/); }); }); function items(xml: string): string[] { return [...xml.matchAll(/<(?:humanTask|decisionTask|processTask|caseTask) id="([^"]+)"/g)].map((m) => m[1] ?? ''); } /** * 有的 humanTask 是自闭合标签(``,如没有人工激活的 S1)—— * `[\s\S]*?<\/humanTask>` 这种非贪婪匹配会跳过自闭合标签、错吞下一个 humanTask * 的内容(把 S2 的 manualActivationRule 记到 S1 头上)。两种收尾分别匹配。 */ function parseCmmnLite(xml: string): { manualActivation: string[]; repetition: string[] } { const manualActivation: string[] = []; const repetition: string[] = []; for (const m of xml.matchAll(/]*?(?:\/>|>[\s\S]*?<\/humanTask>)/g)) { if (m[0].includes('manualActivationRule')) manualActivation.push(m[1] ?? ''); if (m[0].includes('repetitionRule')) repetition.push(m[1] ?? ''); } return { manualActivation, repetition }; } function sentryIf(xml: string, sentryId: string): string { const block = xml.slice(xml.indexOf(`([\s\S]*?)<\/condition>/.exec(block.slice(0, block.indexOf('')))?.[1] ?? ''; return raw.replaceAll('>', '>').replaceAll('<', '<').replaceAll('&', '&'); } function safeEval(expression: string, scope: Record): boolean { try { return evaluateCondition(expression, scope); } catch { return false; } }