diff --git a/.project-docs/30-worklog/tasks/20260922-arrangement-default-date-01a0b77e.md b/.project-docs/30-worklog/tasks/20260922-arrangement-default-date-01a0b77e.md index 1d4d0d9..2dd33bf 100644 --- a/.project-docs/30-worklog/tasks/20260922-arrangement-default-date-01a0b77e.md +++ b/.project-docs/30-worklog/tasks/20260922-arrangement-default-date-01a0b77e.md @@ -33,7 +33,7 @@ ## Delivery / 后续 -- 当前包:[0.5.189](../../../dist/ltjt-order-assistant-0.5.189.zip)。需要更新插件和平台最低版本并刷新 ERP 页面;无服务端运行逻辑变更、无数据库迁移。Skill、输入模板/DOCX、解析器和 Schema 结构不变。 +- 当前包:[0.5.189](../../../archive/releases/2026-09-24/hotel-batch-01a0858b/ltjt-order-assistant-0.5.189.zip)。需要更新插件和平台最低版本并刷新 ERP 页面;无服务端运行逻辑变更、无数据库迁移。Skill、输入模板/DOCX、解析器和 Schema 结构不变。 - 未提交/推送、未部署/重启/重载插件、未操作真实 ERP、未重放失败任务。真实 ERP 验收待部署方完成;截图中的已有安排不能凭此次失败自动重做。 ## Promotion Candidates diff --git a/.project-docs/30-worklog/tasks/20260924-hotel-batch-01a0858b.md b/.project-docs/30-worklog/tasks/20260924-hotel-batch-01a0858b.md new file mode 100644 index 0000000..ecc556a --- /dev/null +++ b/.project-docs/30-worklog/tasks/20260924-hotel-batch-01a0858b.md @@ -0,0 +1,44 @@ +# 同团批量新增酒店 + +## Scope + +- 用户授权按上一轮建议实现:一条消息中同一个团的多段酒店新增,兼容重复“团号”分段;保留单条输入。 +- 整批输入验证后逐条保存/回查,首次失败或不确定即停止;保留成功明细、未执行项,接入当前账号安排记录和三段式回执。 +- 不支持跨团、混合业务、批量变更;不自动重试已有写入。不部署、重启、扩展重载或真实 ERP 写入。 + +## Worktree gate + +- main,唯一 worktree `/Users/andy/IdeaProjects/LWLT-AIBOT`,base `4cfbefffe8b584c2e465cc862cf5ea6fcb5c9bc9`。 +- 起始只有 `.idea/` 与旧版 ZIP 未跟踪,源码无既有改动,Overlap: Clear。`.idea/` 保持原样;旧 ZIP 经逐字节核对原 manifest 的 SHA-256 确认为发布物后,按归档规则保留到 `archive/releases/2026-09-24/hotel-batch-01a0858b/`,未覆盖其内容。 +- Feature 模式,单线程,仅本任务项目记忆可写。 + +## Plan + +1. 增加同团酒店分段解析及批量契约、整批字段校验。 +2. 复用单条酒店执行路径,串行编排、持久化逐条结果并停止于首个非成功,保证中断/回查不重复写入。 +3. 逐条成功证据进入安排账本;生成完整/部分成功及未执行回执。 +4. 更新契约/运营模板和发布物,递增插件版本,执行相关回归与仓库检查。 + +## Validation / Results + +- 已完成同团 2—10 条酒店新增,重复团号分块、逐条完整字段,单条输入继续使用原契约。跨团、混合动作、重复条目、缺字段/无效日期整体阻断;连住多晚不拆分,同酒店不同日期独立。 +- 分段语法固定走 Program(v1.0.15),避免单条 Agent 契约在 AI/Shadow 或重解析时丢条目;决定记录保留实际引擎和配置模式。输入契约 0.5.137,标准 Schema 接收程序批量信封,外部 Agent 单条 Schema 保持。 +- 插件逐条复用原生 single lifecycle;首次失败/不确定停止,单条结果与成功前缀持久化。执行中的父结果保守声明可能写入,超时不推断未写入;不自动重放。只读回查仅收敛不确定项,未执行尾部不续写。 +- 控制面把结果逐条绑定原始 operation,验证同团、独立行 ID、日期、数量及原生成功/回查证据;每条成功独立写入当前账号安排账本和进度。部分完成不伪装整批成功,强制删除任务不删明细。 +- 成功回执包含逐条结果及现有三段式安排详情;中断回执列明成功、失败、待核验和未执行项,并提示禁止整批重发。 +- 插件/平台最低版本/mapping/ZIP 已同步 0.5.190;Docker runtime COPY 包含新共享验证模块。无新增数据库迁移,Agent Prompt 与五个 Skill 不变。 +- 指令 Markdown/DOCX 已同步 0.5.137;18 页全部渲染和视觉检查完成,文档示例可直接通过程序解析。 +- 校验:控制面 299/299、插件/兼容回归 447/447、干净源码副本仓库检查 10/10;类型检查、编译、Docker 运行布局隔离导入检查与 `git diff --check` 通过。 +- 原工作目录仓库检查仍有两个既有卫生失败:`.idea/`、`.DS_Store` 与历史日志;未清除用户本地内容。最终功能回归和发布物检查使用剔除这些既有杂项的同内容源码副本。 +- 旧 ZIP/DOCX 归档;按“移动后修复活动链接”要求,仅修复 20260922-arrangement-default-date 任务记录中的一条旧包链接到原字节归档位置,没有修改历史结论或共享 canonical 记忆。 +- [完整本地验证证据](../../../archive/evidence/2026-09-24/hotel-batch-01a0858b/README.md);[发布清单](../../../dist/release-manifest.json)。 + +## Follow-ups + +- 部署方同步控制面/平台与插件 0.5.190,刷新平台和 ERP 页,再在授权环境验收同团多酒店真实保存及部分失败回执。 +- 不新增迁移,不部署/重启、不写 ERP、不发外部消息。当前改动未提交 Git。 + +## Promotion Candidates + +- 目标:canonical 业务规则与当前状态快照。建议登记同团酒店批量信封、Program 固定分段解析、串行遇错即停及按条账本证据;用户已接受产品方向,本地验证见证据目录,真实 ERP 能力仅在授权验收后提升为已真实验证。 +- 目标:文件治理政策。发布归档与旧任务记录只读之间需要明确“只修复原字节文件路径链接”的边界;本次只做必要路径修复,未变更历史任务事实。 diff --git a/Dockerfile b/Dockerfile index 2e5d545..76d39d2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -20,8 +20,8 @@ RUN npm ci --omit=dev --no-audit --no-fund COPY --from=build /app/.build ./.build COPY --from=build /app/control-plane/migrations ./.build/control-plane/migrations COPY --from=build /app/LianSyn-platform ./LianSyn-platform -# Shared parsed original-date validation is also consumed by the browser plugin. -COPY chrome-extension/ltjt-order-assistant/operation-plans.js chrome-extension/ltjt-order-assistant/package.json ./chrome-extension/ltjt-order-assistant/ +# Shared parsed-date and hotel-batch evidence validation is also consumed by the browser plugin. +COPY chrome-extension/ltjt-order-assistant/operation-plans.js chrome-extension/ltjt-order-assistant/hotel-batch.js chrome-extension/ltjt-order-assistant/package.json ./chrome-extension/ltjt-order-assistant/ USER node EXPOSE 8786 diff --git a/LianSyn-platform/app.js b/LianSyn-platform/app.js index a67e118..54db9e5 100644 --- a/LianSyn-platform/app.js +++ b/LianSyn-platform/app.js @@ -103,7 +103,7 @@ const persistedExtensionResultVersions = new Map(); let taskCreateInProgress = false; const AUTO_HANDOFF_RETRY_MS = 30_000; -const REQUIRED_EXTENSION_VERSION = '0.5.189'; +const REQUIRED_EXTENSION_VERSION = '0.5.190'; const MANUAL_HANDOFF_LABEL = '确认并提交到 ERP 插件'; const RETRY_HANDOFF_LABEL = '继续提交到 ERP 插件'; const RECONCILE_LABEL = '只读回查 ERP 现有结果'; diff --git a/LianSyn-platform/external-agent-client.mjs b/LianSyn-platform/external-agent-client.mjs index e788ac5..9f5d26d 100644 --- a/LianSyn-platform/external-agent-client.mjs +++ b/LianSyn-platform/external-agent-client.mjs @@ -2391,6 +2391,12 @@ export function validateAgentOperation(operation) { // entry point so a Program-only operation can share the downstream standard // contract without becoming an Agent-recognized action. export function validateProgramOperation(operation) { + if (operation?.data && Object.hasOwn(operation.data, 'arrangements')) { + try { + return operationPlans.expandHotelBatch(operation).flatMap((item, index) => + validateAgentOperation(item).map(message => `第 ${index + 1} 条:${message}`)); + } catch (error) { return [error.message]; } + } if (operation?.action !== 'shared_child_order_batch_create') return validateAgentOperation(operation); const errors = []; if (!isPlainObject(operation)) return ['operation 必须是 JSON 对象。']; @@ -2794,6 +2800,7 @@ function validateCommonOperationData(data, errors) { } export function validateStandardOperation(operation) { + if (operation?.data && Object.hasOwn(operation.data, 'arrangements')) return validateProgramOperation(operation); const errors = []; if (!isPlainObject(operation)) return ['operation 必须是 JSON 对象。']; pushUnknownKeyError('operation', operation, STANDARD_OPERATION_KEYS, errors); diff --git a/agent设计规范/agentbus-reply-contract.md b/agent设计规范/agentbus-reply-contract.md index b1186c6..5951933 100644 --- a/agent设计规范/agentbus-reply-contract.md +++ b/agent设计规范/agentbus-reply-contract.md @@ -120,3 +120,10 @@ OUT {out_date_dd_mm_yyyy} AgentBus 回执不发送原始文件字节或 `content_base64`,也不发送后台鉴权下载地址。确认单必须使用 OSS 存储并生成可供渠道适配器访问的 HTTPS URL;数据库存储或 HTTP/后台登录 URL 的附件只保留在平台任务详情中,不投递到外部渠道。原始文件字节、base64、SHA-256、ERP URL、artifact ID 和执行状态不能进入用户文本;文件正文不写入任务 JSONB 或日志,耐久 outbox 只保存附件引用。 `visitor-list` 在平台任务中同时保留源归档 `.xls` 与移动端交付 `.xlsx`;其中源归档明确标记为不可投递,AgentBus/微信回执只允许携带真实 `.xlsx`。独立团/散拼子单游客名单来自 `did+tid`,散拼母团整团游客信息来自 `tid-only`,两种范围必须在 `existing_refs.kind` 中明确。若 `.xlsx` 生成失败,任务阻断且不把原始 `.xls` 发送给外部渠道。 + + +## 同团批量酒店回执 + +`hotel-batch-v1` 仅用于同团酒店新增。完整成功回执沿用三段式布局,本次结果列出总数、逐条入住/离店、酒店搜索词、间数和结果,当前安排展示服务器账本的实际安排明细。搜索词不冒充 ERP 实际选中名称。 + +部分成功、失败或不确定时返回逐条状态(已完成、失败、待只读回查、未执行)和失败原因,明确已完成记录保留且不可整批重复提交。成功前缀独立进入账号隔离账本,即使任务整体未成功也可用于后续安排详情。失败条目必须有明确未写入证据;没有证据按结果不确定处理,不自动重试。只读回查收敛该条后,未执行尾部仍不执行,不能把部分完成宣称为整批成功。 diff --git a/agent设计规范/business-adaptation-registry.md b/agent设计规范/business-adaptation-registry.md index ee40101..a1f2d69 100644 --- a/agent设计规范/business-adaptation-registry.md +++ b/agent设计规范/business-adaptation-registry.md @@ -34,6 +34,12 @@ 插件 `0.5.189` 起,这两类新增仅允许备用行的日期等于页面有效发团日期 `chufari`(兼容月份/日期补零差异),且数量为空/0/1 时,将该日期视为默认值;结算单位、资源 ID、说明、备注、费用、付款和审核等原有保护仍全部生效。其他日期、缺失/无效默认日期来源或非默认数量继续阻断;酒店、用车、导游及变更/清除不放宽。选中行填写用户指定日期,其余已有/备用行及独立备案字段保持原样。用户截图与本地合成回归支持此修复,真实 ERP 验收待部署方完成。见[任务记录](../.project-docs/30-worklog/tasks/20260922-arrangement-default-date-01a0b77e.md)。 +## 同团批量新增酒店 + +插件 `0.5.190`、程序解析器 `v1.0.15` 与运营模板 `0.5.137` 支持同团 2—10 个完整酒店块(每块重复团号),沿用 `arrangement_hotel_create` route。`data.arrangements[]` 是程序解析和插件编排信封;普通单条仍使用 `data.arrangement`,外部 Agent 单条 Schema 不接收批量信封。此分段语法始终由程序解析,记录实际 authoritative engine;避免 AI/Shadow 或重解析只保留一条。 + +整批字段先验证,跨团、混合动作、完全重复条目和不完整输入拒绝执行;运行时每条独立解析资源、原生保存并 fresh requery 后才继续,连住多晚不拆分,同酒店不同日期不合并。失败或不确定即停止,成功明细按当前账号与完整团号持久化,强制删除任务不删明细;回查只收敛不确定条目,不续写剩余条目。需更新服务端、平台和插件,无新增迁移,尚未执行真实 ERP 验收。见[任务记录](../.project-docs/30-worklog/tasks/20260924-hotel-batch-01a0858b.md)与[输入示例](templates/business-input-templates.md#同团批量安排示例)。 + ## 跨业务成功回执 各业务成功回执按“📢 本次结果与团队信息 → 📌 当前安排 → 📋 系统记录”展示当前账号、完整团号范围内的团队办理进度及五类当前有效安排明细,日期采用 `YYYY/MM/DD`;散拼子单分别记录,名单只展示最近成功导入人数,文件保留导出事实。该汇总只来源于本系统成功记录,强制删除任务不删除独立业务历史,旧回执保持生成时业务快照。具体规则见 [AgentBus 用户回复契约](agentbus-reply-contract.md#全业务团队进度与安排明细);实现位于 `control-plane/src/team-progress.ts`、`arrangement-ledger.ts` 与 `task-service.ts`,不改变 ERP 执行或用户指令。 diff --git a/agent设计规范/templates/business-input-templates.md b/agent设计规范/templates/business-input-templates.md index d629c35..13803d2 100644 --- a/agent设计规范/templates/business-input-templates.md +++ b/agent设计规范/templates/business-input-templates.md @@ -1,6 +1,6 @@ # 运营业务 AI 输入模板总表 -交付版本:0.5.136 +交付版本:0.5.137 适用范围:当前 23 条业务路由,按 19 项运营场景分组;执行能力以生命周期发布门槛为准。 本文件用于向系统提交 ERP 业务需求。复制对应模板填写;示例只供参考,不要直接用于生产业务。 @@ -291,6 +291,35 @@ 酒店搜索可填写酒店名,也可包含房型关键词;离店日期必须晚于入住日期。 +#### 同团批量安排示例 + +同一条消息可填写同团 2—10 条酒店安排,每条从团号开始,入住、离店、酒店搜索和间数均需完整填写。空行只用于方便阅读。 + +```text +安排酒店 +团号:LW-260926A-A +入住日期:2026-09-26 +离店日期:2026-09-27 +酒店搜索:昆明州际twn +间数:1 + +团号:LW-260926A-A +入住日期:2026-09-27 +离店日期:2026-09-28 +酒店搜索:万荣龙吟阁twn +间数:1 + +团号:LW-260926A-A +入住日期:2026-09-28 +离店日期:2026-09-30 +酒店搜索:拉邦星光twn +间数:1 +``` + +先校验全部输入,再按顺序逐条安排;连住多晚作为一条,相同酒店不同日期分别安排。缺字段、不同团号、完全重复条目或混入其他业务时整批不执行,请修正后重新发送完整内容。 + +某条失败或结果不确定时停止后续条目,已完成的安排保留,回执逐条展示结果。请勿整批重复发送;结果不确定的条目先只读回查,确认后仅处理尚未安排的条目。普通单条指令保持原用法。 + ### 11. 安排大交通 #### 模板 diff --git a/agent设计规范/test-fixtures/lwlt-lifecycle/release-gate.md b/agent设计规范/test-fixtures/lwlt-lifecycle/release-gate.md index 1e4c22e..f687475 100644 --- a/agent设计规范/test-fixtures/lwlt-lifecycle/release-gate.md +++ b/agent设计规范/test-fixtures/lwlt-lifecycle/release-gate.md @@ -2,14 +2,15 @@ ## 当前基线 -- Chrome 插件:`0.5.189` +- Chrome 插件:`0.5.190` +- `0.5.190` 同团批量酒店新增:2—10 个完整酒店块先整批验证,串行逐条复用原生保存/回查,首次失败或不确定停止,保留成功前缀和未执行清单;只读核验不自动续写。需同步控制面/平台和插件,无新增迁移;本地合成验证,真实 ERP 验收待部署方完成。见[验证证据](../../../archive/evidence/2026-09-24/hotel-batch-01a0858b/README.md)和[任务记录](../../../.project-docs/30-worklog/tasks/20260924-hotel-batch-01a0858b.md)。 - `0.5.189` 仅修复大交通与其他/备案新增的默认日期空行误判:行日期等于页面有效发团日期且数量为原生空/0/1 时允许进入原有空行校验,资源、说明、备注、费用、付款、审核与独立备案保护仍保留;其他安排、非默认日期和变更/清除不放宽。需更新插件、平台最低版本并刷新 ERP 页面,无服务端逻辑/迁移变更;真实 ERP 验收待部署方完成。见[本地验证](../../../archive/evidence/2026-09-22/arrangement-default-date-01a0b77e/README.md)及[任务记录](../../../.project-docs/30-worklog/tasks/20260922-arrangement-default-date-01a0b77e.md)。 - `0.5.188` 与程序解析器 `v1.0.14` 修复单个下单的产品格式匹配:比较双方统一 NFKC/空白,保留标点及业务限定词,唯一候选使用 ERP 原名联动;keyword 保留输入原始格式。初始失败跳过后续依赖校验,零/多产品候选提供独立中文原因。真实公共预检入口的合成回归通过,生产 ERP 验收待部署方完成;需同步服务端、插件和平台,无迁移。见[验证证据](../../../archive/evidence/2026-09-19/product-name-normalization-01a0b77e/README.md)及[任务记录](../../../.project-docs/30-worklog/tasks/20260919-product-name-normalization-01a0b77e.md)。 - `0.5.187` 与程序解析器 `v1.0.13` 支持独立团“房间数更改为 12 标+2 大”,DBL 映射 `frenshu3` 并参与原生合计及保存后 fresh GET 核验;语法、保存前阻断和保存后不一致的本地合成回归通过,真实 ERP 实写待部署方验收。服务端及插件需同步更新,无迁移。见[任务记录](../../../.project-docs/30-worklog/tasks/20260916-independent-dbl-update-01a0858b.md)。 - `0.5.186` 修复车票说明中车次与时刻的数字边界,明确供应商与说明联合唯一匹配及多候选提示;新增/变更本地合成回归通过,真实 ERP 验收待部署方完成。见[任务记录](../../../.project-docs/30-worklog/tasks/20260916-transport-description-match-01a0858b.md)。 - Agent Prompt:`ltjt-agent-prompt-v1.8-independent-headcount-categories` - 生命周期契约:`ltjt-lifecycle-v2.9-roster-leader-contact-2026-08` -- 五个 Skill:`0.5.129`;运营 DOCX:`0.5.136` +- 五个 Skill:`0.5.129`;运营 DOCX:`0.5.137` - 发布状态:核心窄能力已有真实证据;一笔名单附件 Program-only 任务已把 25 行及唯一领队联系人正确保存到 ERP,后续只读逐字段核验全等。两种写后逐行读取方式都曾在真实成功写入后产生假阴性;名单在全部写前门禁通过后,以父表单 ERP 明确成功响应作为终结证据,不再执行写后逐行回查或自动名单对账。`0.5.155` 规定确认覆盖的指定序号同值也写;`0.5.156` 把原生游客位不足统一映射为包含名单人数和 ERP 实际上限的业务提示,原技术 blocker 仅留技术详情。`0.5.157` 新增散拼母团“整团游客信息”的 `shared_plan + visitor-list + tid-only` 窄分支,静态契约与回归已通过,仍待授权后的新版运行态只读复测。`0.5.158` 把正式控制面 `https://lwlt.nianxx.cn` 同步加入 Popup、后台、Manifest 权限和 content script 精确白名单;其他 HTTPS origin 仍保持阻断。`0.5.159` 修复 MV3 后台恢复被中断后持久化 `running` 永久阻断插件派发的问题:仅在内存恢复 Promise 已不存在时收敛为 `interrupted`,真实活动恢复仍保持阻断,下一次成功只读保活把状态归零为 `idle`。`0.5.160` 为独立团单个下单写前漂移增加仅字段名诊断,并把数字后缀动态应收字段纳入保护。`0.5.161` 等待客户、跟单人和销售人原生 lookup 数据就绪后才开始单团预检;生产反馈证明仅检查 lookup 数据仍不足。`0.5.162` 进一步要求页面 jQuery Ajax 在预检前归零,并在 `GetProduct` 后等待原生 `ajaxStop` 再应用最终业务字段。`0.5.163` 按生产操作方明确要求恢复 `0.5.156` 的实际校验范围:核心订单字段继续写前阻断,带编号的动态 `ys_*` 应收行不再纳入保护哈希,允许 ERP 原生回调在预检后调整这些字段。`0.5.164` 新增平台账号、AgentBus 渠道、唯一云电脑 worker 与 ERP 登录身份四方一致门禁;旧未绑定渠道和历史未归属 AgentBus 任务默认不执行。`0.5.165` 让散拼新增计划和独立团批量下单在本地候选未命中时先使用表单原生非空产品检索,实测两类表单均能由完整关键词唯一返回目标产品;本轮未勾选或保存,自动选择与写入仍由原门禁保护。新增独立团房型/人数映射仍待实写;未验证宽能力继续失败关闭。 - `0.5.166` 在散拼母团列表入口加入立即检查、100ms 条件轮询和 15 秒总超时;每次探测都重新取得当前主 iframe document,按钮一旦可用便立即继续。该等待只发生在点击前,不固定拖慢快速页面,也不触发业务写入重试。 diff --git a/archive/evidence/2026-09-24/README.md b/archive/evidence/2026-09-24/README.md new file mode 100644 index 0000000..42d8986 --- /dev/null +++ b/archive/evidence/2026-09-24/README.md @@ -0,0 +1,5 @@ +# 2026-09-24 验证证据 + +以下为历史验证记录,不代表当前业务规则。 + +- [同团批量酒店本地验证](hotel-batch-01a0858b/README.md) diff --git a/archive/evidence/2026-09-24/hotel-batch-01a0858b/README.md b/archive/evidence/2026-09-24/hotel-batch-01a0858b/README.md new file mode 100644 index 0000000..fa2f87f --- /dev/null +++ b/archive/evidence/2026-09-24/hotel-batch-01a0858b/README.md @@ -0,0 +1,23 @@ +# 同团批量酒店本地验证 + +本目录保存 2026-09-24 此次版本验证,不作为当前规则来源;当前契约以活动源码和发布清单为准。本次未部署、重启、扩展重载、真实 ERP 写入或外部发消息。 + +## 结果 + +- 控制面测试:299/299 通过,见 [输出](control-plane-tests.txt)。 +- 插件及兼容回归:447/447 通过,见 [输出](legacy-tests.txt)。覆盖普通单条、五段输入、整批拒绝非法输入、串行执行、成功前缀持久化、失败/不确定停止、超时保存证据、只读收敛不续写、执行中父结果不误报未写入。 +- `node --run check`、`node --run build`:退出码 0;未手改编译输出。 +- `check:repo`:干净源码副本 10/10 通过,含版本/哈希、ZIP 与源码逐文件比对、DOCX 单一源和活动链接,见 [输出](repository-clean-tests.txt)。 +- 原工作目录保留既有 `.idea/`、`.DS_Store` 和 2026-09-14 历史 `.log`,因此其仓库卫生检查有两个既有失败,见 [原目录结果](repository-local-residue.txt)。副本来自当前受控源码和本次新文件,仅排除这些原有杂项、秘密配置及可重建输出;不修改检查断言来放行杂项。 +- 按 Docker runtime COPY 布局隔离加载编译产物与共享模块,程序解析两条酒店成功;没有 DB/ERP 连接。见 [运行布局验证](runtime-layout-smoke.txt)。没有执行完整 Docker 镜像构建。 + +## DOCX 全页视觉检查 + +- 由现有 Markdown 和生成器重建指令表 0.5.137,使用 bundled LibreOffice/Poppler 与中文字体配置。 +- 18 页 PNG 全部逐页打开检查,中文正常,无方框字、截断或重叠;酒店新增示例位于第 8—9 页,参见 [第 8 页](docx-page-8.png)、[第 9 页](docx-page-9.png)。沿用原文档布局和样式。 +- 已验证 DOCX SHA-256:`64846b40722d5c3c3807868f990c5781e77c746fc7f9f115ccfca1a74beb786e`。 +- 运营文档批量示例由测试直接读取并解析,验证三条安排及跨多晚日期没有被拆分。 + +## 验证边界 + +浏览器执行编排测试使用本地 VM 与合成 ERP 成功/失败回执;页面原生单条行为依赖既有适配器回归。真实团队多酒店保存、资源候选和页面速度需更新服务端/平台/插件后在授权环境验收。数据库 schema 未变,无新增迁移。 diff --git a/archive/evidence/2026-09-24/hotel-batch-01a0858b/control-plane-tests.txt b/archive/evidence/2026-09-24/hotel-batch-01a0858b/control-plane-tests.txt new file mode 100644 index 0000000..0bcf0d1 --- /dev/null +++ b/archive/evidence/2026-09-24/hotel-batch-01a0858b/control-plane-tests.txt @@ -0,0 +1,1875 @@ +TAP version 13 +# Subtest: account migration adds roles, actor attribution, and reversible archive without purging history +ok 1 - account migration adds roles, actor attribution, and reversible archive without purging history + --- + duration_ms: 5.283084 + type: 'test' + ... +# Subtest: team-lead migration adds the role and bounded dashboard indexes without a tenant concept +ok 2 - team-lead migration adds the role and bounded dashboard indexes without a tenant concept + --- + duration_ms: 1.439208 + type: 'test' + ... +# Subtest: business authorization migration adds a fail-closed per-user allowlist for all registered routes +ok 3 - business authorization migration adds a fail-closed per-user allowlist for all registered routes + --- + duration_ms: 1.355333 + type: 'test' + ... +# Subtest: AgentBus account-worker migration adds fail-closed channel, task, browser, and ERP identity ownership +ok 4 - AgentBus account-worker migration adds fail-closed channel, task, browser, and ERP identity ownership + --- + duration_ms: 1.090833 + type: 'test' + ... +# Subtest: administrator task-isolation migration removes legacy bindings and rejects future data-plane principals +ok 5 - administrator task-isolation migration removes legacy bindings and rejects future data-plane principals + --- + duration_ms: 3.316583 + type: 'test' + ... +# Subtest: AgentBus channel keys and owners are unique so one inbound identity cannot fan out to multiple employees +ok 6 - AgentBus channel keys and owners are unique so one inbound identity cannot fan out to multiple employees + --- + duration_ms: 2.126833 + type: 'test' + ... +# Subtest: account lifecycle is administrator-gated and protects passwords, sessions, and the last administrator +ok 7 - account lifecycle is administrator-gated and protects passwords, sessions, and the last administrator + --- + duration_ms: 4.886166 + type: 'test' + ... +# Subtest: administrators manage employee task-type grants while remaining outside manual intake +ok 8 - administrators manage employee task-type grants while remaining outside manual intake + --- + duration_ms: 7.531583 + type: 'test' + ... +# Subtest: operations dashboard is leadership-gated, cross-source, business-facing, and separate from normal task authority +ok 9 - operations dashboard is leadership-gated, cross-source, business-facing, and separate from normal task authority + --- + duration_ms: 259.16975 + type: 'test' + ... +# Subtest: ordinary task access is enforced across reads, mutations, artifacts, events, and browser connections +ok 10 - ordinary task access is enforced across reads, mutations, artifacts, events, and browser connections + --- + duration_ms: 12.309666 + type: 'test' + ... +# Subtest: administrators are excluded from task events and plugin result routing +ok 11 - administrators are excluded from task events and plugin result routing + --- + duration_ms: 56.874792 + type: 'test' + ... +# Subtest: ERP browser claims serialize only the assigned account queue +ok 12 - ERP browser claims serialize only the assigned account queue + --- + duration_ms: 49.517292 + type: 'test' + ... +# Subtest: force delete physically removes accessible tasks without the archive state gate +ok 13 - force delete physically removes accessible tasks without the archive state gate + --- + duration_ms: 102.626666 + type: 'test' + ... +# Subtest: operator UI exposes role-aware accounts, executive drill-through, archive, and explicit permanent deletion +ok 14 - operator UI exposes role-aware accounts, executive drill-through, archive, and explicit permanent deletion + --- + duration_ms: 47.601 + type: 'test' + ... +# Subtest: account authorization editor uses a scroll-safe open layout without overriding the closed account list +ok 15 - account authorization editor uses a scroll-safe open layout without overriding the closed account list + --- + duration_ms: 13.511834 + type: 'test' + ... +# Subtest: AgentBus configuration stays disabled until connection fields are supplied +ok 16 - AgentBus configuration stays disabled until connection fields are supplied + --- + duration_ms: 10.661125 + type: 'test' + ... +# Subtest: AgentBus accepted delivery payloads keep final ownership metadata server-only +ok 17 - AgentBus accepted delivery payloads keep final ownership metadata server-only + --- + duration_ms: 0.879417 + type: 'test' + ... +# Subtest: AgentBus auto configuration rejects partial credentials +ok 18 - AgentBus auto configuration rejects partial credentials + --- + duration_ms: 3.326875 + type: 'test' + ... +# Subtest: database-managed channels may use the global WebSocket URL without a global key +ok 19 - database-managed channels may use the global WebSocket URL without a global key + --- + duration_ms: 0.469875 + type: 'test' + ... +# Subtest: WeChat transport envelope exposes only its business text to the global parser +ok 20 - WeChat transport envelope exposes only its business text to the global parser + --- + duration_ms: 4.565292 + type: 'test' + ... +# Subtest: channel directory prefers the current listener over stale persisted disabled status +ok 21 - channel directory prefers the current listener over stale persisted disabled status + --- + duration_ms: 2.130292 + type: 'test' + ... +# Subtest: AgentBus protocol helpers preserve reply routing fields +ok 22 - AgentBus protocol helpers preserve reply routing fields + --- + duration_ms: 0.842167 + type: 'test' + ... +# Subtest: AgentBus result text uses the unified important message and preserves the confirmation gate +ok 23 - AgentBus result text uses the unified important message and preserves the confirmation gate + --- + duration_ms: 0.80425 + type: 'test' + ... +# Subtest: AgentBus attachment references omit archived visitor XLS and keep only XLSX delivery +ok 24 - AgentBus attachment references omit archived visitor XLS and keep only XLSX delivery + --- + duration_ms: 0.362041 + type: 'test' + ... +# Subtest: AgentBus listener connects with the documented Authorization header and returns one final result +ok 25 - AgentBus listener connects with the documented Authorization header and returns one final result + --- + duration_ms: 28.985834 + type: 'test' + ... +# Subtest: WeChat attachment placeholder without file metadata fails closed before task ingestion +ok 26 - WeChat attachment placeholder without file metadata fails closed before task ingestion + --- + duration_ms: 182.902167 + type: 'test' + ... +# Subtest: attachment validation rejection returns an immediate failed result without scheduling parsing +ok 27 - attachment validation rejection returns an immediate failed result without scheduling parsing + --- + duration_ms: 17.522375 + type: 'test' + ... +# Subtest: roster direct listener sends waiting, attachment-received, and one final reply across two frames +ok 28 - roster direct listener sends waiting, attachment-received, and one final reply across two frames + --- + duration_ms: 15.095292 + type: 'test' + ... +# Subtest: listener reload stop does not overwrite the channel status as disabled +ok 29 - listener reload stop does not overwrite the channel status as disabled + --- + duration_ms: 0.960709 + type: 'test' + ... +# Subtest: channel manager replays a reload requested while another reload is in flight +ok 30 - channel manager replays a reload requested while another reload is in flight + --- + duration_ms: 8.875834 + type: 'test' + ... +# Subtest: listener shutdown carries its session epoch for stale-writer protection +ok 31 - listener shutdown carries its session epoch for stale-writer protection + --- + duration_ms: 7.240167 + type: 'test' + ... +# Subtest: durable channel listener persists route and resends accepted/result deliveries +ok 32 - durable channel listener persists route and resends accepted/result deliveries + --- + duration_ms: 1039.299583 + type: 'test' + ... +# Subtest: durable roster lifecycle assigns the final result only to the attachment frame +ok 33 - durable roster lifecycle assigns the final result only to the attachment frame + --- + duration_ms: 1003.399833 + type: 'test' + ... +# Subtest: durable channel persists and sends an attachment rejection result while the task keeps waiting +ok 34 - durable channel persists and sends an attachment rejection result while the task keeps waiting + --- + duration_ms: 1006.428625 + type: 'test' + ... +# Subtest: successful arrangements capture actual linked fields and retain separate original input/evidence +ok 35 - successful arrangements capture actual linked fields and retain separate original input/evidence + --- + duration_ms: 3.615125 + type: 'test' + ... +# Subtest: parser, failed, queued, dry-run, uncertain and incomplete proof never change arrangement state +ok 36 - parser, failed, queued, dry-run, uncertain and incomplete proof never change arrangement state + --- + duration_ms: 0.473708 + type: 'test' + ... +# Subtest: full group identity is normalized without collapsing suffixes or accepting a different ERP group +ok 37 - full group identity is normalized without collapsing suffixes or accepting a different ERP group + --- + duration_ms: 0.697375 + type: 'test' + ... +# Subtest: five arrangement types have independent status and guides remain singleton after replacement +ok 38 - five arrangement types have independent status and guides remain singleton after replacement + --- + duration_ms: 17.344083 + type: 'test' + ... +# Subtest: update and clear target stable row IDs, preserving other rows regardless of slot renumbering +ok 39 - update and clear target stable row IDs, preserving other rows regardless of slot renumbering + --- + duration_ms: 2.329584 + type: 'test' + ... +# Subtest: late reconciliation replays by original execution time rather than overwriting newer arrangements +ok 40 - late reconciliation replays by original execution time rather than overwriting newer arrangements + --- + duration_ms: 0.122958 + type: 'test' + ... +# Subtest: narrow hotel updates retain existing resource/date details but honor a verified cleared remark +ok 41 - narrow hotel updates retain existing resource/date details but honor a verified cleared remark + --- + duration_ms: 0.207792 + type: 'test' + ... +# Subtest: clearing external/untracked rows does not erase this account recorded arrangements +ok 42 - clearing external/untracked rows does not erase this account recorded arrangements + --- + duration_ms: 0.084125 + type: 'test' + ... +# Subtest: receipt summary is a frozen snapshot and remains single when public receipts are rebuilt +ok 43 - receipt summary is a frozen snapshot and remains single when public receipts are rebuilt + --- + duration_ms: 1.898042 + type: 'test' + ... +# Subtest: ledger storage encrypts details, deduplicates and scopes reads to both organization and account +ok 44 - ledger storage encrypts details, deduplicates and scopes reads to both organization and account + --- + duration_ms: 2.233208 + type: 'test' + ... +# Subtest: backfill includes archived successes and skips failed/other-account tasks, preserving encrypted evidence +ok 45 - backfill includes archived successes and skips failed/other-account tasks, preserving encrypted evidence + --- + duration_ms: 0.6065 + type: 'test' + ... +# Subtest: ledger migration has no cascading source-task dependency and blocks admin ownership +ok 46 - ledger migration has no cascading source-task dependency and blocks admin ownership + --- + duration_ms: 14.116541 + type: 'test' + ... +# Subtest: real success-result transaction saves summary/history once and force delete preserves details +ok 47 - real success-result transaction saves summary/history once and force delete preserves details + --- + duration_ms: 9.200291 + type: 'test' + ... +# Subtest: original hotel dates never roll forward with the submission date or group number +ok 48 - original hotel dates never roll forward with the submission date or group number + --- + duration_ms: 284.753625 + type: 'test' + ... +# Subtest: original dates retain explicit years and allow cross-year, mixed-year and leap-day selectors +ok 49 - original dates retain explicit years and allow cross-year, mixed-year and leap-day selectors + --- + duration_ms: 111.879833 + type: 'test' + ... +# Subtest: vehicle, transport and other original dates also preserve month/day including filing date +ok 50 - vehicle, transport and other original dates also preserve month/day including filing date + --- + duration_ms: 93.392833 + type: 'test' + ... +# Subtest: hotel creation and legacy target-date changes retain their existing year semantics +ok 51 - hotel creation and legacy target-date changes retain their existing year semantics + --- + duration_ms: 49.848833 + type: 'test' + ... +# Subtest: vehicle type is an optional candidate filter on create and update, never an original date or direct item edit +ok 52 - vehicle type is an optional candidate filter on create and update, never an original date or direct item edit + --- + duration_ms: 434.782958 + type: 'test' + ... +# Subtest: guide update separates original dates from replacements +ok 53 - guide update separates original dates from replacements + --- + duration_ms: 1.30325 + type: 'test' + ... +# Subtest: vehicle update separates original dates from replacements +ok 54 - vehicle update separates original dates from replacements + --- + duration_ms: 0.533333 + type: 'test' + ... +# Subtest: transport update separates original dates from replacements +ok 55 - transport update separates original dates from replacements + --- + duration_ms: 6.584167 + type: 'test' + ... +# Subtest: other update separates original dates from replacements +ok 56 - other update separates original dates from replacements + --- + duration_ms: 2.121209 + type: 'test' + ... +# Subtest: updates require an original date and new resource; quantity remains optional only for other +ok 57 - updates require an original date and new resource; quantity remains optional only for other + --- + duration_ms: 12.247208 + type: 'test' + ... +# Subtest: ticket description is optional on transport create/update and stays separate from linked item and original dates +ok 58 - ticket description is optional on transport create/update and stays separate from linked item and original dates + --- + duration_ms: 198.057875 + type: 'test' + ... +# {"level":30,"time":1790223334548,"pid":7732,"hostname":"andydeMacBook-Pro.local","service":"ltjt-control-plane","environment":"test","deployment_revision":"unknown","diagnostic_event":"service.initialized","diagnostic_stage":"startup","host":"127.0.0.1","port":8786,"log_level":"info","database_schema":"public","database_ssl":"disable","artifact_storage_backend":"database","agentbus_enabled":false,"leader_summary_webhook_enabled":false,"leader_summary_webhook_configuration_error":null,"parser_loop_enabled":false,"data_retention_enabled":false,"raw_payload_logging":false,"msg":"control plane initialized"} +# Subtest: message routing starts a new session for a business directive, not for a supplement +ok 59 - message routing starts a new session for a business directive, not for a supplement + --- + duration_ms: 92.926958 + type: 'test' + ... +# Subtest: task access contract excludes administrators and isolates employee owners +ok 60 - task access contract excludes administrators and isolates employee owners + --- + duration_ms: 0.237958 + type: 'test' + ... +# Subtest: task data-plane HTTP classifier covers every task-bearing surface and excludes management APIs +ok 61 - task data-plane HTTP classifier covers every task-bearing surface and excludes management APIs + --- + duration_ms: 0.102084 + type: 'test' + ... +# Subtest: task service rejects administrator operations before touching task storage +ok 62 - task service rejects administrator operations before touching task storage + --- + duration_ms: 6.828417 + type: 'test' + ... +# Subtest: business route authorization is an explicit allowlist for team leads and ordinary users +ok 63 - business route authorization is an explicit allowlist for team leads and ordinary users + --- + duration_ms: 0.139875 + type: 'test' + ... +# Subtest: field encryption round-trips without storing plaintext +ok 64 - field encryption round-trips without storing plaintext + --- + duration_ms: 1.285959 + type: 'test' + ... +# Subtest: field encryption round-trips binary task attachments +ok 65 - field encryption round-trips binary task attachments + --- + duration_ms: 5.045083 + type: 'test' + ... +# Subtest: database artifact store sends encrypted bytes to the persistence boundary +ok 66 - database artifact store sends encrypted bytes to the persistence boundary + --- + duration_ms: 21.095417 + type: 'test' + ... +# Subtest: production OSS configuration requires provider credentials and derives the region +ok 67 - production OSS configuration requires provider credentials and derives the region + --- + duration_ms: 1.626042 + type: 'test' + ... +# Subtest: OSS artifact store writes metadata with an idempotent UUID execution path and cleans captured keys +ok 68 - OSS artifact store writes metadata with an idempotent UUID execution path and cleans captured keys + --- + duration_ms: 2.772459 + type: 'test' + ... +# Subtest: token hashes compare safely and do not expose the original token +ok 69 - token hashes compare safely and do not expose the original token + --- + duration_ms: 0.145416 + type: 'test' + ... +# Subtest: organization automation applies uniformly to every valid task source +ok 70 - organization automation applies uniformly to every valid task source + --- + duration_ms: 0.071334 + type: 'test' + ... +# Subtest: control plane exposes a live health endpoint without a database connection +ok 71 - control plane exposes a live health endpoint without a database connection + --- + duration_ms: 350.027375 + type: 'test' + ... +# {"level":30,"time":1790223334590,"pid":7732,"hostname":"andydeMacBook-Pro.local","service":"ltjt-control-plane","environment":"test","deployment_revision":"unknown","diagnostic_event":"service.initialized","diagnostic_stage":"startup","host":"127.0.0.1","port":8786,"log_level":"info","database_schema":"public","database_ssl":"disable","artifact_storage_backend":"database","agentbus_enabled":false,"leader_summary_webhook_enabled":false,"leader_summary_webhook_configuration_error":null,"parser_loop_enabled":false,"data_retention_enabled":false,"raw_payload_logging":false,"msg":"control plane initialized"} +# {"level":30,"time":1790223334585,"pid":7732,"hostname":"andydeMacBook-Pro.local","service":"ltjt-control-plane","environment":"test","deployment_revision":"unknown","diagnostic_event":"service.closing","diagnostic_stage":"shutdown","msg":"control plane closing"} +# {"level":40,"time":1790223334602,"pid":7732,"hostname":"andydeMacBook-Pro.local","service":"ltjt-control-plane","environment":"test","deployment_revision":"unknown","reqId":"1ff63e21-86c9-4969-ac3d-491b05ce3c9f","diagnostic_event":"http.request.rejected","diagnostic_stage":"http","request_id":"1ff63e21-86c9-4969-ac3d-491b05ce3c9f","status_code":403,"error_code":"task_access_forbidden","error_name":"AuthError","msg":"HTTP request rejected by application guard"} +# {"level":40,"time":1790223334602,"pid":7732,"hostname":"andydeMacBook-Pro.local","service":"ltjt-control-plane","environment":"test","deployment_revision":"unknown","reqId":"1ff63e21-86c9-4969-ac3d-491b05ce3c9f","diagnostic_event":"http.request.completed","diagnostic_stage":"http","request_id":"1ff63e21-86c9-4969-ac3d-491b05ce3c9f","method":"GET","path":"/api/tasks","status_code":403,"duration_ms":0.892,"msg":"HTTP request completed with client error"} +# {"level":30,"time":1790223334602,"pid":7732,"hostname":"andydeMacBook-Pro.local","service":"ltjt-control-plane","environment":"test","deployment_revision":"unknown","reqId":"37d9d901-eaca-4abf-9527-28f7515293d1","diagnostic_event":"http.request.started","diagnostic_stage":"http","request_id":"37d9d901-eaca-4abf-9527-28f7515293d1","method":"POST","path":"/api/tasks","content_length":27,"msg":"HTTP request started"} +# {"level":40,"time":1790223334606,"pid":7732,"hostname":"andydeMacBook-Pro.local","service":"ltjt-control-plane","environment":"test","deployment_revision":"unknown","reqId":"37d9d901-eaca-4abf-9527-28f7515293d1","diagnostic_event":"http.request.rejected","diagnostic_stage":"http","request_id":"37d9d901-eaca-4abf-9527-28f7515293d1","status_code":403,"error_code":"task_access_forbidden","error_name":"AuthError","msg":"HTTP request rejected by application guard"} +# {"level":40,"time":1790223334607,"pid":7732,"hostname":"andydeMacBook-Pro.local","service":"ltjt-control-plane","environment":"test","deployment_revision":"unknown","reqId":"37d9d901-eaca-4abf-9527-28f7515293d1","diagnostic_event":"http.request.completed","diagnostic_stage":"http","request_id":"37d9d901-eaca-4abf-9527-28f7515293d1","method":"POST","path":"/api/tasks","status_code":403,"duration_ms":4.174,"msg":"HTTP request completed with client error"} +# {"level":30,"time":1790223334608,"pid":7732,"hostname":"andydeMacBook-Pro.local","service":"ltjt-control-plane","environment":"test","deployment_revision":"unknown","reqId":"11f04c5d-2e10-4bad-8fd5-4434348afe62","diagnostic_event":"http.request.started","diagnostic_stage":"http","request_id":"11f04c5d-2e10-4bad-8fd5-4434348afe62","method":"POST","path":"/api/messages","content_length":26,"msg":"HTTP request started"} +# {"level":40,"time":1790223334609,"pid":7732,"hostname":"andydeMacBook-Pro.local","service":"ltjt-control-plane","environment":"test","deployment_revision":"unknown","reqId":"11f04c5d-2e10-4bad-8fd5-4434348afe62","diagnostic_event":"http.request.rejected","diagnostic_stage":"http","request_id":"11f04c5d-2e10-4bad-8fd5-4434348afe62","status_code":403,"error_code":"task_access_forbidden","error_name":"AuthError","msg":"HTTP request rejected by application guard"} +# {"level":40,"time":1790223334610,"pid":7732,"hostname":"andydeMacBook-Pro.local","service":"ltjt-control-plane","environment":"test","deployment_revision":"unknown","reqId":"11f04c5d-2e10-4bad-8fd5-4434348afe62","diagnostic_event":"http.request.completed","diagnostic_stage":"http","request_id":"11f04c5d-2e10-4bad-8fd5-4434348afe62","method":"POST","path":"/api/messages","status_code":403,"duration_ms":1.973,"msg":"HTTP request completed with client error"} +# {"level":30,"time":1790223334610,"pid":7732,"hostname":"andydeMacBook-Pro.local","service":"ltjt-control-plane","environment":"test","deployment_revision":"unknown","reqId":"6e828468-13bb-42e9-ad69-8edd5b23a295","diagnostic_event":"http.request.started","diagnostic_stage":"http","request_id":"6e828468-13bb-42e9-ad69-8edd5b23a295","method":"GET","path":"/api/tasks/TASK-1","msg":"HTTP request started"} +# {"level":40,"time":1790223334610,"pid":7732,"hostname":"andydeMacBook-Pro.local","service":"ltjt-control-plane","environment":"test","deployment_revision":"unknown","reqId":"6e828468-13bb-42e9-ad69-8edd5b23a295","diagnostic_event":"http.request.rejected","diagnostic_stage":"http","request_id":"6e828468-13bb-42e9-ad69-8edd5b23a295","task_id":"TASK-1","status_code":403,"error_code":"task_access_forbidden","error_name":"AuthError","msg":"HTTP request rejected by application guard"} +# {"level":40,"time":1790223334610,"pid":7732,"hostname":"andydeMacBook-Pro.local","service":"ltjt-control-plane","environment":"test","deployment_revision":"unknown","reqId":"6e828468-13bb-42e9-ad69-8edd5b23a295","diagnostic_event":"http.request.completed","diagnostic_stage":"http","request_id":"6e828468-13bb-42e9-ad69-8edd5b23a295","method":"GET","path":"/api/tasks/:taskId","status_code":403,"duration_ms":0.314,"msg":"HTTP request completed with client error"} +# {"level":30,"time":1790223334611,"pid":7732,"hostname":"andydeMacBook-Pro.local","service":"ltjt-control-plane","environment":"test","deployment_revision":"unknown","reqId":"a20df5b9-4b55-49a4-9de0-1ebf66b71e37","diagnostic_event":"http.request.started","diagnostic_stage":"http","request_id":"a20df5b9-4b55-49a4-9de0-1ebf66b71e37","method":"GET","path":"/api/tasks/TASK-1/input-history","msg":"HTTP request started"} +# {"level":40,"time":1790223334611,"pid":7732,"hostname":"andydeMacBook-Pro.local","service":"ltjt-control-plane","environment":"test","deployment_revision":"unknown","reqId":"a20df5b9-4b55-49a4-9de0-1ebf66b71e37","diagnostic_event":"http.request.rejected","diagnostic_stage":"http","request_id":"a20df5b9-4b55-49a4-9de0-1ebf66b71e37","task_id":"TASK-1","status_code":403,"error_code":"task_access_forbidden","error_name":"AuthError","msg":"HTTP request rejected by application guard"} +# {"level":40,"time":1790223334611,"pid":7732,"hostname":"andydeMacBook-Pro.local","service":"ltjt-control-plane","environment":"test","deployment_revision":"unknown","reqId":"a20df5b9-4b55-49a4-9de0-1ebf66b71e37","diagnostic_event":"http.request.completed","diagnostic_stage":"http","request_id":"a20df5b9-4b55-49a4-9de0-1ebf66b71e37","method":"GET","path":"/api/tasks/:taskId/input-history","status_code":403,"duration_ms":0.217,"msg":"HTTP request completed with client error"} +# {"level":30,"time":1790223334611,"pid":7732,"hostname":"andydeMacBook-Pro.local","service":"ltjt-control-plane","environment":"test","deployment_revision":"unknown","reqId":"3d10cddd-2f98-444d-af17-70a0e5e936b5","diagnostic_event":"http.request.started","diagnostic_stage":"http","request_id":"3d10cddd-2f98-444d-af17-70a0e5e936b5","method":"GET","path":"/api/tasks/TASK-1/artifacts/44444444-4444-4444-8444-444444444444","msg":"HTTP request started"} +# {"level":40,"time":1790223334611,"pid":7732,"hostname":"andydeMacBook-Pro.local","service":"ltjt-control-plane","environment":"test","deployment_revision":"unknown","reqId":"3d10cddd-2f98-444d-af17-70a0e5e936b5","diagnostic_event":"http.request.rejected","diagnostic_stage":"http","request_id":"3d10cddd-2f98-444d-af17-70a0e5e936b5","task_id":"TASK-1","status_code":403,"error_code":"task_access_forbidden","error_name":"AuthError","msg":"HTTP request rejected by application guard"} +# {"level":40,"time":1790223334611,"pid":7732,"hostname":"andydeMacBook-Pro.local","service":"ltjt-control-plane","environment":"test","deployment_revision":"unknown","reqId":"3d10cddd-2f98-444d-af17-70a0e5e936b5","diagnostic_event":"http.request.completed","diagnostic_stage":"http","request_id":"3d10cddd-2f98-444d-af17-70a0e5e936b5","method":"GET","path":"/api/tasks/:taskId/artifacts/:artifactId","status_code":403,"duration_ms":0.221,"msg":"HTTP request completed with client error"} +# {"level":30,"time":1790223334612,"pid":7732,"hostname":"andydeMacBook-Pro.local","service":"ltjt-control-plane","environment":"test","deployment_revision":"unknown","reqId":"f8a02d8e-67d5-4e0a-845c-bf990eee17fa","diagnostic_event":"http.request.started","diagnostic_stage":"http","request_id":"f8a02d8e-67d5-4e0a-845c-bf990eee17fa","method":"POST","path":"/api/tasks/TASK-1/confirm","content_length":2,"msg":"HTTP request started"} +# {"level":40,"time":1790223334640,"pid":7732,"hostname":"andydeMacBook-Pro.local","service":"ltjt-control-plane","environment":"test","deployment_revision":"unknown","reqId":"f8a02d8e-67d5-4e0a-845c-bf990eee17fa","diagnostic_event":"http.request.rejected","diagnostic_stage":"http","request_id":"f8a02d8e-67d5-4e0a-845c-bf990eee17fa","task_id":"TASK-1","status_code":403,"error_code":"task_access_forbidden","error_name":"AuthError","msg":"HTTP request rejected by application guard"} +# {"level":40,"time":1790223334670,"pid":7732,"hostname":"andydeMacBook-Pro.local","service":"ltjt-control-plane","environment":"test","deployment_revision":"unknown","reqId":"f8a02d8e-67d5-4e0a-845c-bf990eee17fa","diagnostic_event":"http.request.completed","diagnostic_stage":"http","request_id":"f8a02d8e-67d5-4e0a-845c-bf990eee17fa","method":"POST","path":"/api/tasks/:taskId/confirm","status_code":403,"duration_ms":58.712,"msg":"HTTP request completed with client error"} +# {"level":30,"time":1790223334672,"pid":7732,"hostname":"andydeMacBook-Pro.local","service":"ltjt-control-plane","environment":"test","deployment_revision":"unknown","reqId":"cb0e4abb-addc-4c41-a1c8-0b4f2a05d1f5","diagnostic_event":"http.request.started","diagnostic_stage":"http","request_id":"cb0e4abb-addc-4c41-a1c8-0b4f2a05d1f5","method":"POST","path":"/api/tasks/TASK-1/claim","content_length":53,"msg":"HTTP request started"} +# {"level":40,"time":1790223334672,"pid":7732,"hostname":"andydeMacBook-Pro.local","service":"ltjt-control-plane","environment":"test","deployment_revision":"unknown","reqId":"cb0e4abb-addc-4c41-a1c8-0b4f2a05d1f5","diagnostic_event":"http.request.rejected","diagnostic_stage":"http","request_id":"cb0e4abb-addc-4c41-a1c8-0b4f2a05d1f5","task_id":"TASK-1","status_code":403,"error_code":"task_access_forbidden","error_name":"AuthError","msg":"HTTP request rejected by application guard"} +# {"level":40,"time":1790223334672,"pid":7732,"hostname":"andydeMacBook-Pro.local","service":"ltjt-control-plane","environment":"test","deployment_revision":"unknown","reqId":"cb0e4abb-addc-4c41-a1c8-0b4f2a05d1f5","diagnostic_event":"http.request.completed","diagnostic_stage":"http","request_id":"cb0e4abb-addc-4c41-a1c8-0b4f2a05d1f5","method":"POST","path":"/api/tasks/:taskId/claim","status_code":403,"duration_ms":0.894,"msg":"HTTP request completed with client error"} +# {"level":30,"time":1790223334726,"pid":7732,"hostname":"andydeMacBook-Pro.local","service":"ltjt-control-plane","environment":"test","deployment_revision":"unknown","reqId":"6d339e40-79f5-4f61-a60b-4cfb80b4bb40","diagnostic_event":"http.request.started","diagnostic_stage":"http","request_id":"6d339e40-79f5-4f61-a60b-4cfb80b4bb40","method":"POST","path":"/api/tasks/TASK-1/result","content_length":119,"msg":"HTTP request started"} +# {"level":40,"time":1790223334727,"pid":7732,"hostname":"andydeMacBook-Pro.local","service":"ltjt-control-plane","environment":"test","deployment_revision":"unknown","reqId":"6d339e40-79f5-4f61-a60b-4cfb80b4bb40","diagnostic_event":"http.request.rejected","diagnostic_stage":"http","request_id":"6d339e40-79f5-4f61-a60b-4cfb80b4bb40","task_id":"TASK-1","status_code":403,"error_code":"task_access_forbidden","error_name":"AuthError","msg":"HTTP request rejected by application guard"} +# {"level":40,"time":1790223334730,"pid":7732,"hostname":"andydeMacBook-Pro.local","service":"ltjt-control-plane","environment":"test","deployment_revision":"unknown","reqId":"6d339e40-79f5-4f61-a60b-4cfb80b4bb40","diagnostic_event":"http.request.completed","diagnostic_stage":"http","request_id":"6d339e40-79f5-4f61-a60b-4cfb80b4bb40","method":"POST","path":"/api/tasks/:taskId/result","status_code":403,"duration_ms":4.11,"msg":"HTTP request completed with client error"} +# {"level":30,"time":1790223334731,"pid":7732,"hostname":"andydeMacBook-Pro.local","service":"ltjt-control-plane","environment":"test","deployment_revision":"unknown","reqId":"e2eee72c-27d0-4718-84e8-955cad92aeb3","diagnostic_event":"http.request.started","diagnostic_stage":"http","request_id":"e2eee72c-27d0-4718-84e8-955cad92aeb3","method":"POST","path":"/api/tasks/TASK-1/cancel","content_length":2,"msg":"HTTP request started"} +# {"level":40,"time":1790223334731,"pid":7732,"hostname":"andydeMacBook-Pro.local","service":"ltjt-control-plane","environment":"test","deployment_revision":"unknown","reqId":"e2eee72c-27d0-4718-84e8-955cad92aeb3","diagnostic_event":"http.request.rejected","diagnostic_stage":"http","request_id":"e2eee72c-27d0-4718-84e8-955cad92aeb3","task_id":"TASK-1","status_code":403,"error_code":"task_access_forbidden","error_name":"AuthError","msg":"HTTP request rejected by application guard"} +# {"level":40,"time":1790223334731,"pid":7732,"hostname":"andydeMacBook-Pro.local","service":"ltjt-control-plane","environment":"test","deployment_revision":"unknown","reqId":"e2eee72c-27d0-4718-84e8-955cad92aeb3","diagnostic_event":"http.request.completed","diagnostic_stage":"http","request_id":"e2eee72c-27d0-4718-84e8-955cad92aeb3","method":"POST","path":"/api/tasks/:taskId/cancel","status_code":403,"duration_ms":0.391,"msg":"HTTP request completed with client error"} +# {"level":30,"time":1790223334732,"pid":7732,"hostname":"andydeMacBook-Pro.local","service":"ltjt-control-plane","environment":"test","deployment_revision":"unknown","reqId":"ffbef311-df50-43b7-8659-aa1a3560a730","diagnostic_event":"http.request.started","diagnostic_stage":"http","request_id":"ffbef311-df50-43b7-8659-aa1a3560a730","method":"POST","path":"/api/tasks/bulk-delete","content_length":23,"msg":"HTTP request started"} +# {"level":40,"time":1790223334732,"pid":7732,"hostname":"andydeMacBook-Pro.local","service":"ltjt-control-plane","environment":"test","deployment_revision":"unknown","reqId":"ffbef311-df50-43b7-8659-aa1a3560a730","diagnostic_event":"http.request.rejected","diagnostic_stage":"http","request_id":"ffbef311-df50-43b7-8659-aa1a3560a730","status_code":403,"error_code":"task_access_forbidden","error_name":"AuthError","msg":"HTTP request rejected by application guard"} +# {"level":40,"time":1790223334732,"pid":7732,"hostname":"andydeMacBook-Pro.local","service":"ltjt-control-plane","environment":"test","deployment_revision":"unknown","reqId":"ffbef311-df50-43b7-8659-aa1a3560a730","diagnostic_event":"http.request.completed","diagnostic_stage":"http","request_id":"ffbef311-df50-43b7-8659-aa1a3560a730","method":"POST","path":"/api/tasks/bulk-delete","status_code":403,"duration_ms":0.373,"msg":"HTTP request completed with client error"} +# {"level":30,"time":1790223334739,"pid":7732,"hostname":"andydeMacBook-Pro.local","service":"ltjt-control-plane","environment":"test","deployment_revision":"unknown","reqId":"720bdde6-1bff-4969-912b-46a2db56a7ec","diagnostic_event":"http.request.started","diagnostic_stage":"http","request_id":"720bdde6-1bff-4969-912b-46a2db56a7ec","method":"POST","path":"/api/tasks/bulk-archive","content_length":23,"msg":"HTTP request started"} +# {"level":40,"time":1790223334739,"pid":7732,"hostname":"andydeMacBook-Pro.local","service":"ltjt-control-plane","environment":"test","deployment_revision":"unknown","reqId":"720bdde6-1bff-4969-912b-46a2db56a7ec","diagnostic_event":"http.request.rejected","diagnostic_stage":"http","request_id":"720bdde6-1bff-4969-912b-46a2db56a7ec","status_code":403,"error_code":"task_access_forbidden","error_name":"AuthError","msg":"HTTP request rejected by application guard"} +# {"level":40,"time":1790223334739,"pid":7732,"hostname":"andydeMacBook-Pro.local","service":"ltjt-control-plane","environment":"test","deployment_revision":"unknown","reqId":"720bdde6-1bff-4969-912b-46a2db56a7ec","diagnostic_event":"http.request.completed","diagnostic_stage":"http","request_id":"720bdde6-1bff-4969-912b-46a2db56a7ec","method":"POST","path":"/api/tasks/bulk-archive","status_code":403,"duration_ms":0.88,"msg":"HTTP request completed with client error"} +# {"level":30,"time":1790223334740,"pid":7732,"hostname":"andydeMacBook-Pro.local","service":"ltjt-control-plane","environment":"test","deployment_revision":"unknown","reqId":"c6837ed8-f3de-4e27-a71f-39a71b148923","diagnostic_event":"http.request.started","diagnostic_stage":"http","request_id":"c6837ed8-f3de-4e27-a71f-39a71b148923","method":"DELETE","path":"/api/tasks/TASK-1","msg":"HTTP request started"} +# {"level":40,"time":1790223334740,"pid":7732,"hostname":"andydeMacBook-Pro.local","service":"ltjt-control-plane","environment":"test","deployment_revision":"unknown","reqId":"c6837ed8-f3de-4e27-a71f-39a71b148923","diagnostic_event":"http.request.rejected","diagnostic_stage":"http","request_id":"c6837ed8-f3de-4e27-a71f-39a71b148923","task_id":"TASK-1","status_code":403,"error_code":"task_access_forbidden","error_name":"AuthError","msg":"HTTP request rejected by application guard"} +# {"level":40,"time":1790223334740,"pid":7732,"hostname":"andydeMacBook-Pro.local","service":"ltjt-control-plane","environment":"test","deployment_revision":"unknown","reqId":"c6837ed8-f3de-4e27-a71f-39a71b148923","diagnostic_event":"http.request.completed","diagnostic_stage":"http","request_id":"c6837ed8-f3de-4e27-a71f-39a71b148923","method":"DELETE","path":"/api/tasks/:taskId","status_code":403,"duration_ms":0.15,"msg":"HTTP request completed with client error"} +# {"level":30,"time":1790223334740,"pid":7732,"hostname":"andydeMacBook-Pro.local","service":"ltjt-control-plane","environment":"test","deployment_revision":"unknown","reqId":"b0fc3ded-8de5-4545-9c25-f71dfaef56df","diagnostic_event":"http.request.started","diagnostic_stage":"http","request_id":"b0fc3ded-8de5-4545-9c25-f71dfaef56df","method":"POST","path":"/api/tasks/TASK-1/archive","content_length":2,"msg":"HTTP request started"} +# {"level":40,"time":1790223334740,"pid":7732,"hostname":"andydeMacBook-Pro.local","service":"ltjt-control-plane","environment":"test","deployment_revision":"unknown","reqId":"b0fc3ded-8de5-4545-9c25-f71dfaef56df","diagnostic_event":"http.request.rejected","diagnostic_stage":"http","request_id":"b0fc3ded-8de5-4545-9c25-f71dfaef56df","task_id":"TASK-1","status_code":403,"error_code":"task_access_forbidden","error_name":"AuthError","msg":"HTTP request rejected by application guard"} +# {"level":40,"time":1790223334741,"pid":7732,"hostname":"andydeMacBook-Pro.local","service":"ltjt-control-plane","environment":"test","deployment_revision":"unknown","reqId":"b0fc3ded-8de5-4545-9c25-f71dfaef56df","diagnostic_event":"http.request.completed","diagnostic_stage":"http","request_id":"b0fc3ded-8de5-4545-9c25-f71dfaef56df","method":"POST","path":"/api/tasks/:taskId/archive","status_code":403,"duration_ms":0.513,"msg":"HTTP request completed with client error"} +# {"level":30,"time":1790223334741,"pid":7732,"hostname":"andydeMacBook-Pro.local","service":"ltjt-control-plane","environment":"test","deployment_revision":"unknown","reqId":"fd4281ec-a275-4887-a88c-172c15f15228","diagnostic_event":"http.request.started","diagnostic_stage":"http","request_id":"fd4281ec-a275-4887-a88c-172c15f15228","method":"POST","path":"/api/tasks/TASK-1/restore","content_length":2,"msg":"HTTP request started"} +# {"level":40,"time":1790223334741,"pid":7732,"hostname":"andydeMacBook-Pro.local","service":"ltjt-control-plane","environment":"test","deployment_revision":"unknown","reqId":"fd4281ec-a275-4887-a88c-172c15f15228","diagnostic_event":"http.request.rejected","diagnostic_stage":"http","request_id":"fd4281ec-a275-4887-a88c-172c15f15228","task_id":"TASK-1","status_code":403,"error_code":"task_access_forbidden","error_name":"AuthError","msg":"HTTP request rejected by application guard"} +# {"level":40,"time":1790223334741,"pid":7732,"hostname":"andydeMacBook-Pro.local","service":"ltjt-control-plane","environment":"test","deployment_revision":"unknown","reqId":"fd4281ec-a275-4887-a88c-172c15f15228","diagnostic_event":"http.request.completed","diagnostic_stage":"http","request_id":"fd4281ec-a275-4887-a88c-172c15f15228","method":"POST","path":"/api/tasks/:taskId/restore","status_code":403,"duration_ms":0.199,"msg":"HTTP request completed with client error"} +# {"level":40,"time":1790223334742,"pid":7732,"hostname":"andydeMacBook-Pro.local","service":"ltjt-control-plane","environment":"test","deployment_revision":"unknown","reqId":"8fc082d3-c83e-4d19-be23-d41b360a3b3a","diagnostic_event":"http.request.rejected","diagnostic_stage":"http","request_id":"8fc082d3-c83e-4d19-be23-d41b360a3b3a","status_code":403,"error_code":"task_access_forbidden","error_name":"AuthError","msg":"HTTP request rejected by application guard"} +# {"level":40,"time":1790223334743,"pid":7732,"hostname":"andydeMacBook-Pro.local","service":"ltjt-control-plane","environment":"test","deployment_revision":"unknown","reqId":"8fc082d3-c83e-4d19-be23-d41b360a3b3a","diagnostic_event":"http.request.completed","diagnostic_stage":"http","request_id":"8fc082d3-c83e-4d19-be23-d41b360a3b3a","method":"POST","path":"/api/connections/heartbeat","status_code":403,"duration_ms":1.809,"msg":"HTTP request completed with client error"} +# {"level":30,"time":1790223334743,"pid":7732,"hostname":"andydeMacBook-Pro.local","service":"ltjt-control-plane","environment":"test","deployment_revision":"unknown","reqId":"06123454-a6a4-421a-912c-0b2c86e64c6b","diagnostic_event":"http.request.started","diagnostic_stage":"http","request_id":"06123454-a6a4-421a-912c-0b2c86e64c6b","method":"GET","path":"/api/events","msg":"HTTP request started"} +# {"level":40,"time":1790223334743,"pid":7732,"hostname":"andydeMacBook-Pro.local","service":"ltjt-control-plane","environment":"test","deployment_revision":"unknown","reqId":"06123454-a6a4-421a-912c-0b2c86e64c6b","diagnostic_event":"http.request.rejected","diagnostic_stage":"http","request_id":"06123454-a6a4-421a-912c-0b2c86e64c6b","status_code":403,"error_code":"task_access_forbidden","error_name":"AuthError","msg":"HTTP request rejected by application guard"} +# {"level":40,"time":1790223334744,"pid":7732,"hostname":"andydeMacBook-Pro.local","service":"ltjt-control-plane","environment":"test","deployment_revision":"unknown","reqId":"06123454-a6a4-421a-912c-0b2c86e64c6b","diagnostic_event":"http.request.completed","diagnostic_stage":"http","request_id":"06123454-a6a4-421a-912c-0b2c86e64c6b","method":"GET","path":"/api/events","status_code":403,"duration_ms":0.359,"msg":"HTTP request completed with client error"} +# {"level":30,"time":1790223334744,"pid":7732,"hostname":"andydeMacBook-Pro.local","service":"ltjt-control-plane","environment":"test","deployment_revision":"unknown","reqId":"013d6261-c961-484d-a4f9-ef830150b58f","diagnostic_event":"http.request.started","diagnostic_stage":"http","request_id":"013d6261-c961-484d-a4f9-ef830150b58f","method":"GET","path":"/api/operations-dashboard","msg":"HTTP request started"} +# {"level":40,"time":1790223334744,"pid":7732,"hostname":"andydeMacBook-Pro.local","service":"ltjt-control-plane","environment":"test","deployment_revision":"unknown","reqId":"013d6261-c961-484d-a4f9-ef830150b58f","diagnostic_event":"http.request.rejected","diagnostic_stage":"http","request_id":"013d6261-c961-484d-a4f9-ef830150b58f","status_code":403,"error_code":"task_access_forbidden","error_name":"AuthError","msg":"HTTP request rejected by application guard"} +# {"level":40,"time":1790223334744,"pid":7732,"hostname":"andydeMacBook-Pro.local","service":"ltjt-control-plane","environment":"test","deployment_revision":"unknown","reqId":"013d6261-c961-484d-a4f9-ef830150b58f","diagnostic_event":"http.request.completed","diagnostic_stage":"http","request_id":"013d6261-c961-484d-a4f9-ef830150b58f","method":"GET","path":"/api/operations-dashboard","status_code":403,"duration_ms":0.323,"msg":"HTTP request completed with client error"} +# {"level":30,"time":1790223334745,"pid":7732,"hostname":"andydeMacBook-Pro.local","service":"ltjt-control-plane","environment":"test","deployment_revision":"unknown","reqId":"ae4c0ce9-4ae4-43be-99fd-8f4b14f6349d","diagnostic_event":"http.request.started","diagnostic_stage":"http","request_id":"ae4c0ce9-4ae4-43be-99fd-8f4b14f6349d","method":"GET","path":"/api/operations-dashboard/tasks/TASK-1","msg":"HTTP request started"} +# {"level":40,"time":1790223334745,"pid":7732,"hostname":"andydeMacBook-Pro.local","service":"ltjt-control-plane","environment":"test","deployment_revision":"unknown","reqId":"ae4c0ce9-4ae4-43be-99fd-8f4b14f6349d","diagnostic_event":"http.request.rejected","diagnostic_stage":"http","request_id":"ae4c0ce9-4ae4-43be-99fd-8f4b14f6349d","task_id":"TASK-1","status_code":403,"error_code":"task_access_forbidden","error_name":"AuthError","msg":"HTTP request rejected by application guard"} +# {"level":40,"time":1790223334745,"pid":7732,"hostname":"andydeMacBook-Pro.local","service":"ltjt-control-plane","environment":"test","deployment_revision":"unknown","reqId":"ae4c0ce9-4ae4-43be-99fd-8f4b14f6349d","diagnostic_event":"http.request.completed","diagnostic_stage":"http","request_id":"ae4c0ce9-4ae4-43be-99fd-8f4b14f6349d","method":"GET","path":"/api/operations-dashboard/tasks/:taskId","status_code":403,"duration_ms":0.207,"msg":"HTTP request completed with client error"} +# {"level":30,"time":1790223334745,"pid":7732,"hostname":"andydeMacBook-Pro.local","service":"ltjt-control-plane","environment":"test","deployment_revision":"unknown","reqId":"da2a1528-2b85-4bb7-815f-0de9bc16bcf5","diagnostic_event":"http.request.started","diagnostic_stage":"http","request_id":"da2a1528-2b85-4bb7-815f-0de9bc16bcf5","method":"POST","path":"/api/tasks/TASK-1/reparse","content_length":31,"msg":"HTTP request started"} +# {"level":40,"time":1790223334747,"pid":7732,"hostname":"andydeMacBook-Pro.local","service":"ltjt-control-plane","environment":"test","deployment_revision":"unknown","reqId":"da2a1528-2b85-4bb7-815f-0de9bc16bcf5","diagnostic_event":"http.request.rejected","diagnostic_stage":"http","request_id":"da2a1528-2b85-4bb7-815f-0de9bc16bcf5","task_id":"TASK-1","status_code":403,"error_code":"task_access_forbidden","error_name":"AuthError","msg":"HTTP request rejected by application guard"} +# {"level":40,"time":1790223334747,"pid":7732,"hostname":"andydeMacBook-Pro.local","service":"ltjt-control-plane","environment":"test","deployment_revision":"unknown","reqId":"da2a1528-2b85-4bb7-815f-0de9bc16bcf5","diagnostic_event":"http.request.completed","diagnostic_stage":"http","request_id":"da2a1528-2b85-4bb7-815f-0de9bc16bcf5","method":"POST","path":"/api/tasks/:taskId/reparse","status_code":403,"duration_ms":1.981,"msg":"HTTP request completed with client error"} +# {"level":30,"time":1790223334748,"pid":7732,"hostname":"andydeMacBook-Pro.local","service":"ltjt-control-plane","environment":"test","deployment_revision":"unknown","reqId":"191bab69-1f8a-4816-b762-d53512fb0380","diagnostic_event":"http.request.started","diagnostic_stage":"http","request_id":"191bab69-1f8a-4816-b762-d53512fb0380","method":"PUT","path":"/api/parser-decisions/44444444-4444-4444-8444-444444444444/review","content_length":24,"msg":"HTTP request started"} +# Subtest: administrator sessions receive 403 before every task data-plane handler +ok 72 - administrator sessions receive 403 before every task data-plane handler + --- + duration_ms: 163.90975 + type: 'test' + ... +# Subtest: AI primary connection state follows service reachability, not historical authentication evidence +ok 73 - AI primary connection state follows service reachability, not historical authentication evidence + --- + duration_ms: 0.220416 + type: 'test' + ... +# {"level":40,"time":1790223334748,"pid":7732,"hostname":"andydeMacBook-Pro.local","service":"ltjt-control-plane","environment":"test","deployment_revision":"unknown","reqId":"191bab69-1f8a-4816-b762-d53512fb0380","diagnostic_event":"http.request.rejected","diagnostic_stage":"http","request_id":"191bab69-1f8a-4816-b762-d53512fb0380","status_code":403,"error_code":"task_access_forbidden","error_name":"AuthError","msg":"HTTP request rejected by application guard"} +# {"level":40,"time":1790223334749,"pid":7732,"hostname":"andydeMacBook-Pro.local","service":"ltjt-control-plane","environment":"test","deployment_revision":"unknown","reqId":"191bab69-1f8a-4816-b762-d53512fb0380","diagnostic_event":"http.request.completed","diagnostic_stage":"http","request_id":"191bab69-1f8a-4816-b762-d53512fb0380","method":"PUT","path":"/api/parser-decisions/:decisionId/review","status_code":403,"duration_ms":0.986,"msg":"HTTP request completed with client error"} +# {"level":30,"time":1790223334749,"pid":7732,"hostname":"andydeMacBook-Pro.local","service":"ltjt-control-plane","environment":"test","deployment_revision":"unknown","diagnostic_event":"service.closing","diagnostic_stage":"shutdown","msg":"control plane closing"} +# Subtest: migration contains the durable state tables and safety fields +ok 74 - migration contains the durable state tables and safety fields + --- + duration_ms: 15.93625 + type: 'test' + ... +# Subtest: control plane requires the arrangement ledger migration before readiness +ok 75 - control plane requires the arrangement ledger migration before readiness + --- + duration_ms: 13.525458 + type: 'test' + ... +# Subtest: ERP execution results fail closed after a possible write +ok 76 - ERP execution results fail closed after a possible write + --- + duration_ms: 3.112041 + type: 'test' + ... +# Subtest: successful ERP executor results expose consistent lifecycle boundary facts +ok 77 - successful ERP executor results expose consistent lifecycle boundary facts + --- + duration_ms: 0.511666 + type: 'test' + ... +# Subtest: reconciliation can only be downgraded to no-write with explicit page evidence +ok 78 - reconciliation can only be downgraded to no-write with explicit page evidence + --- + duration_ms: 0.405834 + type: 'test' + ... +# Subtest: success receipts require verifiable ERP evidence +ok 79 - success receipts require verifiable ERP evidence + --- + duration_ms: 0.219834 + type: 'test' + ... +# Subtest: multi-date split-order probes require complete child facts and no manual review +ok 80 - multi-date split-order probes require complete child facts and no manual review + --- + duration_ms: 0.939709 + type: 'test' + ... +# Subtest: unified lifecycle response plus fresh requery is accepted as completion evidence +ok 81 - unified lifecycle response plus fresh requery is accepted as completion evidence + --- + duration_ms: 2.097292 + type: 'test' + ... +# Subtest: passenger list explicit server success does not require a post-save row requery +ok 82 - passenger list explicit server success does not require a post-save row requery + --- + duration_ms: 0.229292 + type: 'test' + ... +# Subtest: backend-attached export requires frozen hashes and explicit no-delivery evidence before it becomes a success receipt +ok 83 - backend-attached export requires frozen hashes and explicit no-delivery evidence before it becomes a success receipt + --- + duration_ms: 0.997917 + type: 'test' + ... +# Subtest: confirmation export archives visitor XLS, delivers real XLSX, and converts other types to PDF +ok 84 - confirmation export archives visitor XLS, delivers real XLSX, and converts other types to PDF + --- + duration_ms: 3122.840292 + type: 'test' + ... +# Subtest: visitor XLSX conversion failure blocks export delivery instead of returning raw XLS +ok 85 - visitor XLSX conversion failure blocks export delivery instead of returning raw XLS + --- + duration_ms: 30.949209 + type: 'test' + ... +# Subtest: pre-write ERP blockers remain explicit after plugin dispatch +ok 86 - pre-write ERP blockers remain explicit after plugin dispatch + --- + duration_ms: 0.188458 + type: 'test' + ... +# Subtest: write-capable running progress is not mislabeled as write-started +ok 87 - write-capable running progress is not mislabeled as write-started + --- + duration_ms: 0.067416 + type: 'test' + ... +# Subtest: successful parse events show Agent return and explicit pre-plugin no-write boundaries +ok 88 - successful parse events show Agent return and explicit pre-plugin no-write boundaries + --- + duration_ms: 0.132208 + type: 'test' + ... +# Subtest: lifecycle live-write authority is removed after parsing and injected only by manual confirmation +ok 89 - lifecycle live-write authority is removed after parsing and injected only by manual confirmation + --- + duration_ms: 1.0465 + type: 'test' + ... +# Subtest: normal lifecycle confirmation keeps platform metadata outside the business operation +ok 90 - normal lifecycle confirmation keeps platform metadata outside the business operation + --- + duration_ms: 0.226333 + type: 'test' + ... +# Subtest: lifecycle delete authorization rechecks the frozen evidence and allowlist at confirmation time +ok 91 - lifecycle delete authorization rechecks the frozen evidence and allowlist at confirmation time + --- + duration_ms: 0.403167 + type: 'test' + ... +# Subtest: uncertain ERP writes have a reconciliation-specific failure code +ok 92 - uncertain ERP writes have a reconciliation-specific failure code + --- + duration_ms: 0.651292 + type: 'test' + ... +# Subtest: successful parse lifecycle facts are not promoted to a task failure +ok 93 - successful parse lifecycle facts are not promoted to a task failure + --- + duration_ms: 0.32875 + type: 'test' + ... +# Subtest: expired plugin results fail safely and preserve reconciliation when the write boundary is unknown +ok 94 - expired plugin results fail safely and preserve reconciliation when the write boundary is unknown + --- + duration_ms: 0.496084 + type: 'test' + ... +# Subtest: ERP business-rule rejection preserves the native message for the user +ok 95 - ERP business-rule rejection preserves the native message for the user + --- + duration_ms: 0.13725 + type: 'test' + ... +# Subtest: passenger capacity rejection preserves the business message and retains technical evidence +ok 96 - passenger capacity rejection preserves the business message and retains technical evidence + --- + duration_ms: 0.06375 + type: 'test' + ... +# Subtest: nested ERP business feedback is promoted when the executor omitted failure metadata +ok 97 - nested ERP business feedback is promoted when the executor omitted failure metadata + --- + duration_ms: 0.062375 + type: 'test' + ... +# Subtest: ERP migration enforces one execution attempt per task +ok 98 - ERP migration enforces one execution attempt per task + --- + duration_ms: 37.828791 + type: 'test' + ... +# Subtest: task session migration binds one encrypted conversation to each task +ok 99 - task session migration binds one encrypted conversation to each task + --- + duration_ms: 2.650083 + type: 'test' + ... +# Subtest: task outcome migration stores independently timestamped success and error summaries +ok 100 - task outcome migration stores independently timestamped success and error summaries + --- + duration_ms: 2.67775 + type: 'test' + ... +# Subtest: task source migration distinguishes manual and AgentBus tasks +ok 101 - task source migration distinguishes manual and AgentBus tasks + --- + duration_ms: 1.1905 + type: 'test' + ... +# Subtest: organization automation migration persists the switch and task confirmation mode +ok 102 - organization automation migration persists the switch and task confirmation mode + --- + duration_ms: 0.861041 + type: 'test' + ... +# Subtest: task query optimization migration adds bounded list and event indexes +ok 103 - task query optimization migration adds bounded list and event indexes + --- + duration_ms: 0.895875 + type: 'test' + ... +# Subtest: task artifact migration keeps encrypted bytes scoped to the owning task +ok 104 - task artifact migration keeps encrypted bytes scoped to the owning task + --- + duration_ms: 1.465 + type: 'test' + ... +# Subtest: AgentBus channel migration scopes encrypted keys and durable replies +ok 105 - AgentBus channel migration scopes encrypted keys and durable replies + --- + duration_ms: 5.638084 + type: 'test' + ... +# Subtest: business parser migration snapshots modes and encrypts dual candidates +ok 106 - business parser migration snapshots modes and encrypts dual candidates + --- + duration_ms: 3.1295 + type: 'test' + ... +# Subtest: parser promotion uses immediate correctness checks without time, volume, or fallback-rate waits +ok 107 - parser promotion uses immediate correctness checks without time, volume, or fallback-rate waits + --- + duration_ms: 0.304375 + type: 'test' + ... +# Subtest: persistent-session migration removes automatic expiry for active sessions +ok 108 - persistent-session migration removes automatic expiry for active sessions + --- + duration_ms: 1.072625 + type: 'test' + ... +# Subtest: operator page has a login gate and uses the durable task API +ok 109 - operator page has a login gate and uses the durable task API + --- + duration_ms: 24.075291 + type: 'test' + ... +# Subtest: system task events persist a null actor instead of an invalid empty UUID +ok 110 - system task events persist a null actor instead of an invalid empty UUID + --- + duration_ms: 1.171458 + type: 'test' + ... +# Subtest: public task wrappers keep session IDs out of operation and transport metadata +ok 111 - public task wrappers keep session IDs out of operation and transport metadata + --- + duration_ms: 2.330042 + type: 'test' + ... +# Subtest: public task exposes persisted outcome timestamps and keeps the latest error summary +ok 112 - public task exposes persisted outcome timestamps and keeps the latest error summary + --- + duration_ms: 0.97775 + type: 'test' + ... +# Subtest: platform only synthesizes an error timeline entry for a currently failed task +ok 113 - platform only synthesizes an error timeline entry for a currently failed task + --- + duration_ms: 2.155292 + type: 'test' + ... +# Subtest: diagnostic errors preserve code and stack location without leaking the error message +ok 114 - diagnostic errors preserve code and stack location without leaking the error message + --- + duration_ms: 4.585708 + type: 'test' + ... +# Subtest: diagnostic request identifiers and paths are stable and query-safe +ok 115 - diagnostic request identifiers and paths are stable and query-safe + --- + duration_ms: 1.08775 + type: 'test' + ... +# Subtest: control-plane logger redacts credentials and includes deployment identity +ok 116 - control-plane logger redacts credentials and includes deployment identity + --- + duration_ms: 11.513208 + type: 'test' + ... +# Subtest: HTTP diagnostics reuse one request ID and never log query values +ok 117 - HTTP diagnostics reuse one request ID and never log query values + --- + duration_ms: 380.395708 + type: 'test' + ... +# Subtest: successful heartbeats, health probes and workbench polling stay silent while their handlers still run +ok 118 - successful heartbeats, health probes and workbench polling stay silent while their handlers still run + --- + duration_ms: 42.774375 + type: 'test' + ... +# Subtest: failed heartbeats and readiness probes retain severity, error codes and request correlation +ok 119 - failed heartbeats and readiness probes retain severity, error codes and request correlation + --- + duration_ms: 90.925167 + type: 'test' + ... +# Subtest: polling failures stay visible and task writes and detail reads keep access logs +ok 120 - polling failures stay visible and task writes and detail reads keep access logs + --- + duration_ms: 21.816833 + type: 'test' + ... +# Subtest: automation settings reads are quiet but failures and actual settings changes remain visible +ok 121 - automation settings reads are quiet but failures and actual settings changes remain visible + --- + duration_ms: 72.951 + type: 'test' + ... +# Subtest: task state and audit diagnostics record keys but never business values +ok 122 - task state and audit diagnostics record keys but never business values + --- + duration_ms: 0.5275 + type: 'test' + ... +# Subtest: production configuration rejects raw AgentBus payload logging +ok 123 - production configuration rejects raw AgentBus payload logging + --- + duration_ms: 0.639667 + type: 'test' + ... +# Subtest: same execution read-only failure resolves provisional dispatch uncertainty +ok 124 - same execution read-only failure resolves provisional dispatch uncertainty + --- + duration_ms: 26.326958 + type: 'test' + ... +# Subtest: maintenance-expired dispatch uncertainty still accepts the original explicit no-write result +ok 125 - maintenance-expired dispatch uncertainty still accepts the original explicit no-write result + --- + duration_ms: 0.225334 + type: 'test' + ... +# Subtest: late no-write resolution enforces execution, original connection and immutable state boundaries +ok 126 - late no-write resolution enforces execution, original connection and immutable state boundaries + --- + duration_ms: 0.223625 + type: 'test' + ... +# Subtest: true post-write uncertainty and prior native evidence cannot be overwritten +ok 127 - true post-write uncertainty and prior native evidence cannot be overwritten + --- + duration_ms: 0.369125 + type: 'test' + ... +# Subtest: bare no-write flags, contradictory write evidence and non-lookup results are rejected +ok 128 - bare no-write flags, contradictory write evidence and non-lookup results are rejected + --- + duration_ms: 1.197083 + type: 'test' + ... +# Subtest: page-level no-write evidence must be complete and internally consistent +ok 129 - page-level no-write evidence must be complete and internally consistent + --- + duration_ms: 0.7075 + type: 'test' + ... +# Subtest: provisional timeout cannot replace already-received plugin progress or a final outcome +ok 130 - provisional timeout cannot replace already-received plugin progress or a final outcome + --- + duration_ms: 0.480791 + type: 'test' + ... +# Subtest: real result-ingestion transaction closes only eligible dispatch failures and emits consistent facts +ok 131 - real result-ingestion transaction closes only eligible dispatch failures and emits consistent facts + --- + duration_ms: 25.335916 + type: 'test' + ... +# Subtest: PDF sources pass through with PDF metadata +ok 132 - PDF sources pass through with PDF metadata + --- + duration_ms: 88.060083 + type: 'test' + ... +# Subtest: conversion failure falls back to the source file +ok 133 - conversion failure falls back to the source file + --- + duration_ms: 116.105667 + type: 'test' + ... +# Subtest: PDF conversion selects Writer HTML import only for HTML documents + # Subtest: ERP Word HTML with vendor MIME + ok 1 - ERP Word HTML with vendor MIME + --- + duration_ms: 3392.425166 + type: 'test' + ... + # Subtest: HTML doctype in a Word file + ok 2 - HTML doctype in a Word file + --- + duration_ms: 16.898667 + type: 'test' + ... + # Subtest: legacy body-only Word HTML + ok 3 - legacy body-only Word HTML + --- + duration_ms: 18.885125 + type: 'test' + ... + # Subtest: HTML extension + ok 4 - HTML extension + --- + duration_ms: 118.176209 + type: 'test' + ... + # Subtest: HTML MIME without extension + ok 5 - HTML MIME without extension + --- + duration_ms: 32.115125 + type: 'test' + ... + # Subtest: binary Word + ok 6 - binary Word + --- + duration_ms: 15.629 + type: 'test' + ... + # Subtest: DOCX package + ok 7 - DOCX package + --- + duration_ms: 14.599834 + type: 'test' + ... + # Subtest: RTF + ok 8 - RTF + --- + duration_ms: 12.679375 + type: 'test' + ... + 1..8 +ok 134 - PDF conversion selects Writer HTML import only for HTML documents + --- + duration_ms: 3653.647125 + type: 'test' + ... +# Subtest: a valid converter output replaces the source with a PDF +ok 135 - a valid converter output replaces the source with a PDF + --- + duration_ms: 32.153875 + type: 'test' + ... +# Subtest: HTML-in-XLS visitor sources become real XLSX workbooks +ok 136 - HTML-in-XLS visitor sources become real XLSX workbooks + --- + duration_ms: 23.880667 + type: 'test' + ... +# Subtest: visitor XLSX conversion failure remains a source fallback +ok 137 - visitor XLSX conversion failure remains a source fallback + --- + duration_ms: 7.876583 + type: 'test' + ... +# Subtest: wide Word HTML tables are scaled to the printable width before conversion +ok 138 - wide Word HTML tables are scaled to the printable width before conversion + --- + duration_ms: 37.048625 + type: 'test' + ... +# Subtest: external webhook request uses the confirmed fixed contract exactly +ok 139 - external webhook request uses the confirmed fixed contract exactly + --- + duration_ms: 136.955333 + type: 'test' + ... +# Subtest: external webhook accepts only HTTP 200 with code zero and data true +ok 140 - external webhook accepts only HTTP 200 with code zero and data true + --- + duration_ms: 4.293958 + type: 'test' + ... +# {"level":30,"time":1790223335831,"pid":7991,"hostname":"andydeMacBook-Pro.local","service":"ltjt-control-plane","environment":"test","deployment_revision":"unknown","diagnostic_event":"service.initialized","diagnostic_stage":"startup","host":"127.0.0.1","port":8786,"log_level":"info","database_schema":"public","database_ssl":"disable","artifact_storage_backend":"database","agentbus_enabled":false,"leader_summary_webhook_enabled":false,"leader_summary_webhook_configuration_error":"webhook_configuration_incomplete","parser_loop_enabled":false,"data_retention_enabled":false,"raw_payload_logging":false,"msg":"control plane initialized"} +# Subtest: webhook configuration is fail-closed and independent from AgentBus configuration +ok 141 - webhook configuration is fail-closed and independent from AgentBus configuration + --- + duration_ms: 2.734625 + type: 'test' + ... +# Subtest: client rejects blank content and malformed tokens before making a request +ok 142 - client rejects blank content and malformed tokens before making a request + --- + duration_ms: 0.844041 + type: 'test' + ... +# {"level":30,"time":1790223335916,"pid":7991,"hostname":"andydeMacBook-Pro.local","service":"ltjt-control-plane","environment":"test","deployment_revision":"unknown","diagnostic_event":"service.closing","diagnostic_stage":"shutdown","msg":"control plane closing"} +# {"level":30,"time":1790223335918,"pid":7991,"hostname":"andydeMacBook-Pro.local","service":"ltjt-control-plane","environment":"test","deployment_revision":"unknown","diagnostic_event":"service.initialized","diagnostic_stage":"startup","host":"127.0.0.1","port":8786,"log_level":"info","database_schema":"public","database_ssl":"disable","artifact_storage_backend":"database","agentbus_enabled":false,"leader_summary_webhook_enabled":true,"leader_summary_webhook_configuration_error":null,"parser_loop_enabled":false,"data_retention_enabled":false,"raw_payload_logging":false,"msg":"control plane initialized"} +# {"level":40,"time":1790223335930,"pid":7991,"hostname":"andydeMacBook-Pro.local","service":"ltjt-control-plane","environment":"test","deployment_revision":"unknown","diagnostic_event":"leader_summary.webhook_initialization_failed","notification_event":"webhook_initialization_failed","error_code":"ECONNREFUSED","error_name":"Error","error_fingerprint":"e4959bf5faac892132ca58ee","error_stack":["at /Users//IdeaProjects/LWLT-AIBOT/node_modules/pg-pool/index.js:45:11","at process.processTicksAndRejections (node:internal/process/task_queues:103:5)","at async AuthService.getOrganization (/control-plane/src/auth.ts:214:20)","at async buildServer (/control-plane/src/server.ts:870:28)","at async TestContext. (/control-plane/test/external-webhook-client.test.ts:221:21)","at async Test.run (node:internal/test_runner/test:1054:7)","at async Test.processPendingSubtests (node:internal/test_runner/test:744:7)"],"error_errno":-61,"error_syscall":"connect","msg":"Leader summary webhook initialization failed; normal task services remain available"} +# Subtest: invalid configuration or webhook initialization failure does not block the normal HTTP service +ok 143 - invalid configuration or webhook initialization failure does not block the normal HTTP service + --- + duration_ms: 535.660625 + type: 'test' + ... +# {"level":30,"time":1790223336031,"pid":7991,"hostname":"andydeMacBook-Pro.local","service":"ltjt-control-plane","environment":"test","deployment_revision":"unknown","diagnostic_event":"service.closing","diagnostic_stage":"shutdown","msg":"control plane closing"} +# Subtest: hotel input splits five repeated group blocks and preserves multi-night stays and ordinary input +ok 144 - hotel input splits five repeated group blocks and preserves multi-night stays and ordinary input + --- + duration_ms: 204.746625 + type: 'test' + ... +# Subtest: the entire batch blocks on cross-team, missing field, duplicate, mixed action, invalid date and oversized input +ok 145 - the entire batch blocks on cross-team, missing field, duplicate, mixed action, invalid date and oversized input + --- + duration_ms: 22.086041 + type: 'test' + ... +# Subtest: all rows require bound, distinct write/requery evidence before whole-batch success +ok 146 - all rows require bound, distinct write/requery evidence before whole-batch success + --- + duration_ms: 7.222834 + type: 'test' + ... +# Subtest: partial stop retains distinct successful ledger events and uncertainty never counts as success +ok 147 - partial stop retains distinct successful ledger events and uncertainty never counts as success + --- + duration_ms: 5.328041 + type: 'test' + ... +# Subtest: published operator batch example parses to three complete ordered stays +ok 148 - published operator batch example parses to three complete ordered stays + --- + duration_ms: 5.164042 + type: 'test' + ... +# Subtest: dated hotel replacement parses old dates separately from the new resource and count +ok 149 - dated hotel replacement parses old dates separately from the new resource and count + --- + duration_ms: 70.935125 + type: 'test' + ... +# Subtest: hotel replacement rejects missing dates, invalid count, reversed dates and mixed update styles +ok 150 - hotel replacement rejects missing dates, invalid count, reversed dates and mixed update styles + --- + duration_ms: 34.348417 + type: 'test' + ... +# Subtest: legacy hotel departure and count changes remain supported +ok 151 - legacy hotel departure and count changes remain supported + --- + duration_ms: 0.901542 + type: 'test' + ... +# Subtest: manual roster attachment base64 is bounded and hash verified +ok 152 - manual roster attachment base64 is bounded and hash verified + --- + duration_ms: 2.934667 + type: 'test' + ... +# Subtest: manual roster attachment rejects unsupported names, invalid base64 and mismatched hash +ok 153 - manual roster attachment rejects unsupported names, invalid base64 and mismatched hash + --- + duration_ms: 0.464208 + type: 'test' + ... +# Subtest: AgentBus attachment URLs allow private HTTPS endpoints but reject unsafe URL syntax +ok 154 - AgentBus attachment URLs allow private HTTPS endpoints but reject unsafe URL syntax + --- + duration_ms: 0.538167 + type: 'test' + ... +# Subtest: AgentBus attachment metadata is normalized without exposing URL credentials +ok 155 - AgentBus attachment metadata is normalized without exposing URL credentials + --- + duration_ms: 0.127583 + type: 'test' + ... +# Subtest: AgentBus attachment DNS accepts internal names and private literal addresses +ok 156 - AgentBus attachment DNS accepts internal names and private literal addresses + --- + duration_ms: 154.527375 + type: 'test' + ... +# Subtest: AgentBus attachment DNS pinning follows the Node lookup callback shape +ok 157 - AgentBus attachment DNS pinning follows the Node lookup callback shape + --- + duration_ms: 0.253167 + type: 'test' + ... +# Subtest: AgentBus attachment diagnostics expose stages and codes without URL data +ok 158 - AgentBus attachment diagnostics expose stages and codes without URL data + --- + duration_ms: 0.591625 + type: 'test' + ... +# Subtest: input attachment migration stores only encrypted normalized data and waiting-task index +ok 159 - input attachment migration stores only encrypted normalized data and waiting-task index + --- + duration_ms: 4.340041 + type: 'test' + ... +# Subtest: migration retires only the legacy leader-summary transport and creates an encrypted webhook outbox +ok 160 - migration retires only the legacy leader-summary transport and creates an encrypted webhook outbox + --- + duration_ms: 15.826375 + type: 'test' + ... +# Subtest: webhook worker is independent from normal task execution and AgentBus delivery +ok 161 - webhook worker is independent from normal task execution and AgentBus delivery + --- + duration_ms: 35.734333 + type: 'test' + ... +# Subtest: projection is organization-scoped, future-only, role-safe, source-limited and encrypted +ok 162 - projection is organization-scoped, future-only, role-safe, source-limited and encrypted + --- + duration_ms: 6.624542 + type: 'test' + ... +# Subtest: delivery makes one attempt and preserves ambiguous outcomes instead of retrying +ok 163 - delivery makes one attempt and preserves ambiguous outcomes instead of retrying + --- + duration_ms: 2.519041 + type: 'test' + ... +# Subtest: HTTP exposes administrator-only status and no mutation or live test-send endpoint +ok 164 - HTTP exposes administrator-only status and no mutation or live test-send endpoint + --- + duration_ms: 2.410625 + type: 'test' + ... +# Subtest: administrator UI explains the isolated fixed webhook and accepted-not-delivered semantics +ok 165 - administrator UI explains the isolated fixed webhook and accepted-not-delivered semantics + --- + duration_ms: 8.260958 + type: 'test' + ... +# Subtest: notification shows the original multiline input and omits the generated task-number field +ok 166 - notification shows the original multiline input and omits the generated task-number field + --- + duration_ms: 54.69025 + type: 'test' + ... +# Subtest: original input is available even when parsing failed or the business route is unknown +ok 167 - original input is available even when parsing failed or the business route is unknown + --- + duration_ms: 0.659291 + type: 'test' + ... +# Subtest: result-update messages retain the original input and full result +ok 168 - result-update messages retain the original input and full result + --- + duration_ms: 0.272416 + type: 'test' + ... +# Subtest: input formatting preserves punctuation and tabs, normalizes newlines and removes nonprinting controls +ok 169 - input formatting preserves punctuation and tabs, normalizes newlines and removes nonprinting controls + --- + duration_ms: 0.131792 + type: 'test' + ... +# Subtest: missing input has an explicit fallback without inventing input from a receipt +ok 170 - missing input has an explicit fallback without inventing input from a receipt + --- + duration_ms: 0.400333 + type: 'test' + ... +# Subtest: UTF-8 budget retains fitting inputs and visibly truncates only overflowing input +ok 171 - UTF-8 budget retains fitting inputs and visibly truncates only overflowing input + --- + duration_ms: 0.741166 + type: 'test' + ... +# Subtest: very long Chinese and emoji input cannot break Unicode or displace the outcome +ok 172 - very long Chinese and emoji input cannot break Unicode or displace the outcome + --- + duration_ms: 0.76875 + type: 'test' + ... +# Subtest: worker reads the same encrypted original input for manual and AgentBus tasks +ok 173 - worker reads the same encrypted original input for manual and AgentBus tasks + --- + duration_ms: 3.845209 + type: 'test' + ... +# Subtest: missing, invalid or unreadable ciphertext does not prevent a notification +ok 174 - missing, invalid or unreadable ciphertext does not prevent a notification + --- + duration_ms: 72.7915 + type: 'test' + ... +# Subtest: leader summary projects only stable outcomes +ok 175 - leader summary projects only stable outcomes + --- + duration_ms: 0.367833 + type: 'test' + ... +# Subtest: completed leader summary exposes only whitelisted business identifiers +ok 176 - completed leader summary exposes only whitelisted business identifiers + --- + duration_ms: 0.271417 + type: 'test' + ... +# Subtest: failure, uncertainty, cancellation and dry-run use stable safe wording +ok 177 - failure, uncertainty, cancellation and dry-run use stable safe wording + --- + duration_ms: 0.299125 + type: 'test' + ... +# Subtest: a stable result after needs-review produces one result-update milestone +ok 178 - a stable result after needs-review produces one result-update milestone + --- + duration_ms: 0.110792 + type: 'test' + ... +# Subtest: Shanghai timestamp formatting is deterministic and invalid-safe +ok 179 - Shanghai timestamp formatting is deterministic and invalid-safe + --- + duration_ms: 0.878708 + type: 'test' + ... +# Subtest: control plane keeps only the operation timing allowlisted shape +ok 180 - control plane keeps only the operation timing allowlisted shape + --- + duration_ms: 4.55225 + type: 'test' + ... +# Subtest: control plane rejects unknown timing contracts +ok 181 - control plane rejects unknown timing contracts + --- + duration_ms: 0.121459 + type: 'test' + ... +# Subtest: control plane accepts every plugin page-action timing code +ok 182 - control plane accepts every plugin page-action timing code + --- + duration_ms: 0.637333 + type: 'test' + ... +# Subtest: control plane keeps the privacy-safe orchestration breakdown used to locate unattributed time +ok 183 - control plane keeps the privacy-safe orchestration breakdown used to locate unattributed time + --- + duration_ms: 0.295709 + type: 'test' + ... +# Subtest: only roster routes pause at intake until a workbook arrives +ok 184 - only roster routes pause at intake until a workbook arrives + --- + duration_ms: 5.014292 + type: 'test' + ... +# Subtest: attachment-only AgentBus frames are eligible inbound tasks +ok 185 - attachment-only AgentBus frames are eligible inbound tasks + --- + duration_ms: 0.309625 + type: 'test' + ... +# Subtest: roster attachment source contract gates claims and scrubs normalized plaintext +ok 186 - roster attachment source contract gates claims and scrubs normalized plaintext + --- + duration_ms: 27.511917 + type: 'test' + ... +# Subtest: a newly delivered message revalidates previously rejected attachment bytes +ok 187 - a newly delivered message revalidates previously rejected attachment bytes + --- + duration_ms: 4.965 + type: 'test' + ... +# Subtest: durable roster replies distinguish waiting and attachment phases and select one final owner +ok 188 - durable roster replies distinguish waiting and attachment phases and select one final owner + --- + duration_ms: 1.504125 + type: 'test' + ... +# Subtest: manual API accepts exactly one bounded inline roster attachment +ok 189 - manual API accepts exactly one bounded inline roster attachment + --- + duration_ms: 0.758083 + type: 'test' + ... +# Subtest: automatic roster target query uses newest creation time and preserves all scope filters +ok 190 - automatic roster target query uses newest creation time and preserves all scope filters + --- + duration_ms: 33.649709 + type: 'test' + ... +# Subtest: explicit target still selects an older task and validates channel and conversation +ok 191 - explicit target still selects an older task and validates channel and conversation + --- + duration_ms: 2.130833 + type: 'test' + ... +# Subtest: missing conversation or eligible target never falls back to an unrelated task +ok 192 - missing conversation or eligible target never falls back to an unrelated task + --- + duration_ms: 0.455917 + type: 'test' + ... +# Subtest: attachment transaction writes only its selected task and keeps race and replay protections +ok 193 - attachment transaction writes only its selected task and keeps race and replay protections + --- + duration_ms: 3.394875 + type: 'test' + ... +# Subtest: roster tail: 28 passengers survive stale template formulas and a merged footer unchanged +ok 194 - roster tail: 28 passengers survive stale template formulas and a merged footer unchanged + --- + duration_ms: 639.264208 + type: 'test' + ... +# Subtest: roster tail: sequence-only placeholders and separated trailing notes are not passengers +ok 195 - roster tail: sequence-only placeholders and separated trailing notes are not passengers + --- + duration_ms: 299.299208 + type: 'test' + ... +# Subtest: roster tail: recognition follows semantic columns after reordering +ok 196 - roster tail: recognition follows semantic columns after reordering + --- + duration_ms: 68.578166 + type: 'test' + ... +# Subtest: roster tail: any supplied passenger field keeps a row subject to strict formula validation +ok 197 - roster tail: any supplied passenger field keeps a row subject to strict formula validation + --- + duration_ms: 144.219458 + type: 'test' + ... +# Subtest: roster tail: unrecognized formulas and notes cannot be silently discarded +ok 198 - roster tail: unrecognized formulas and notes cannot be silently discarded + --- + duration_ms: 46.197625 + type: 'test' + ... +# Subtest: roster tail: placeholders or notes inside the roster never hide later passengers +ok 199 - roster tail: placeholders or notes inside the roster never hide later passengers + --- + duration_ms: 40.347 + type: 'test' + ... +# Subtest: roster tail: empty templates do not create phantom passengers +ok 200 - roster tail: empty templates do not create phantom passengers + --- + duration_ms: 7.663084 + type: 'test' + ... +# Subtest: roster tail: active content and hidden required data remain blocked across the whole sheet +ok 201 - roster tail: active content and hidden required data remain blocked across the whole sheet + --- + duration_ms: 42.657958 + type: 'test' + ... +# Subtest: XLSX is deterministically normalized to the canonical 13-column TSV +ok 202 - XLSX is deterministically normalized to the canonical 13-column TSV + --- + duration_ms: 384.15675 + type: 'test' + ... +# Subtest: an absent phone header produces blank phones without guessing unheaded values +ok 203 - an absent phone header produces blank phones without guessing unheaded values + --- + duration_ms: 272.860708 + type: 'test' + ... +# Subtest: phone-free rosters support reordered alias headers, merged remarks and XLS conversion +ok 204 - phone-free rosters support reordered alias headers, merged remarks and XLS conversion + --- + duration_ms: 91.812542 + type: 'test' + ... +# Subtest: phone is the only optional semantic and duplicate phone headers stay blocked +ok 205 - phone is the only optional semantic and duplicate phone headers stay blocked + --- + duration_ms: 166.560625 + type: 'test' + ... +# Subtest: present phone columns retain formula, value, hidden-data and row checks +ok 206 - present phone columns retain formula, value, hidden-data and row checks + --- + duration_ms: 36.093292 + type: 'test' + ... +# Subtest: a marked leader still requires a phone when the source phone column is absent +ok 207 - a marked leader still requires a phone when the source phone column is absent + --- + duration_ms: 8.3515 + type: 'test' + ... +# Subtest: a merged title error is skipped, while header and passenger errors stay blocked +ok 208 - a merged title error is skipped, while header and passenger errors stay blocked + --- + duration_ms: 39.177666 + type: 'test' + ... +# Subtest: same-row identity templates support 18 and 15 digit sources without exporting them +ok 209 - same-row identity templates support 18 and 15 digit sources without exporting them + --- + duration_ms: 13.413375 + type: 'test' + ... +# Subtest: identity references follow reordered columns, shared formulas and the workbook date system +ok 210 - identity references follow reordered columns, shared formulas and the workbook date system + --- + duration_ms: 7.130333 + type: 'test' + ... +# Subtest: identity formulas reject missing, stale or error results at the formula cell +ok 211 - identity formulas reject missing, stale or error results at the formula cell + --- + duration_ms: 33.909583 + type: 'test' + ... +# Subtest: identity dependencies must be unambiguous same-row template references +ok 212 - identity dependencies must be unambiguous same-row template references + --- + duration_ms: 32.858916 + type: 'test' + ... +# Subtest: invalid identity sources cannot validate a cached gender or birth date +ok 213 - invalid identity sources cannot validate a cached gender or birth date + --- + duration_ms: 46.643 + type: 'test' + ... +# Subtest: populated identity formula rows are not discarded as empty trailing templates +ok 214 - populated identity formula rows are not discarded as empty trailing templates + --- + duration_ms: 6.533959 + type: 'test' + ... +# Subtest: a 22-person template passes normalization and program parsing with the last row as leader +ok 215 - a 22-person template passes normalization and program parsing with the last row as leader + --- + duration_ms: 11.065458 + type: 'test' + ... +# Subtest: the ERP field row is auto-detected and source columns may be reordered +ok 216 - the ERP field row is auto-detected and source columns may be reordered + --- + duration_ms: 31.2185 + type: 'test' + ... +# Subtest: the approved attachment aliases and reverse issue-date formula are normalized +ok 217 - the approved attachment aliases and reverse issue-date formula are normalized + --- + duration_ms: 8.299459 + type: 'test' + ... +# Subtest: birthplace and expiry aliases preserve the canonical passenger fields and values +ok 218 - birthplace and expiry aliases preserve the canonical passenger fields and values + --- + duration_ms: 11.041416 + type: 'test' + ... +# Subtest: birthplace and expiry aliases cannot silently select duplicate semantic columns +ok 219 - birthplace and expiry aliases cannot silently select duplicate semantic columns + --- + duration_ms: 22.919917 + type: 'test' + ... +# Subtest: source header order may vary, but every required label must appear exactly once +ok 220 - source header order may vary, but every required label must appear exactly once + --- + duration_ms: 16.329917 + type: 'test' + ... +# Subtest: legacy XLS is accepted only through an injected fail-closed converter +ok 221 - legacy XLS is accepted only through an injected fail-closed converter + --- + duration_ms: 5.805083 + type: 'test' + ... +# Subtest: identity cards and other non-import columns are ignored without leaking values +ok 222 - identity cards and other non-import columns are ignored without leaking values + --- + duration_ms: 9.850209 + type: 'test' + ... +# Subtest: source document type is ignored and canonical ERP document type remains passport +ok 223 - source document type is ignored and canonical ERP document type remains passport + --- + duration_ms: 5.395542 + type: 'test' + ... +# Subtest: sequence numbers must be positive and unique +ok 224 - sequence numbers must be positive and unique + --- + duration_ms: 16.312292 + type: 'test' + ... +# Subtest: unsafe formulas stay blocked while ignored columns do not affect selected fields +ok 225 - unsafe formulas stay blocked while ignored columns do not affect selected fields + --- + duration_ms: 30.154792 + type: 'test' + ... +# Subtest: macros, external relationships and ambiguous sheets fail closed +ok 226 - macros, external relationships and ambiguous sheets fail closed + --- + duration_ms: 18.531792 + type: 'test' + ... +# Subtest: hidden non-empty rows or columns and duplicate headers fail closed +ok 227 - hidden non-empty rows or columns and duplicate headers fail closed + --- + duration_ms: 18.391333 + type: 'test' + ... +# Subtest: format, MIME, conversion output and technical limits are validated before parsing +ok 228 - format, MIME, conversion output and technical limits are validated before parsing + --- + duration_ms: 751.462417 + type: 'test' + ... +# Subtest: roster, shared-child batch and four arrangement update routes are hard Program-only in the machine registry +ok 229 - roster, shared-child batch and four arrangement update routes are hard Program-only in the machine registry + --- + duration_ms: 1.401292 + type: 'test' + ... +# Subtest: hard Program-only task snapshots stay Program with missing or hostile stored settings +ok 230 - hard Program-only task snapshots stay Program with missing or hostile stored settings + --- + duration_ms: 1.21325 + type: 'test' + ... +# Subtest: the public route mutation rejects AI, Shadow, and Auto before touching settings storage +ok 231 - the public route mutation rejects AI, Shadow, and Auto before touching settings storage + --- + duration_ms: 2.58325 + type: 'test' + ... +# Subtest: TaskService exposes and enforces the registry parser policy at every AI override boundary +ok 232 - TaskService exposes and enforces the registry parser policy at every AI override boundary + --- + duration_ms: 3.055333 + type: 'test' + ... +# Subtest: the machine registry contains the 23 independent routes exactly once +ok 233 - the machine registry contains the 23 independent routes exactly once + --- + duration_ms: 1.596958 + type: 'test' + ... +# Subtest: machine routes stay synchronized with their declared Program or Agent boundary +ok 234 - machine routes stay synchronized with their declared Program or Agent boundary + --- + duration_ms: 6.722125 + type: 'test' + ... +# Subtest: versioned sanitized gold corpus replays deterministically +ok 235 - versioned sanitized gold corpus replays deterministically + --- + duration_ms: 108.57775 + type: 'test' + ... +# Subtest: all 23 official-format examples produce contract-valid operations +ok 236 - all 23 official-format examples produce contract-valid operations + --- + duration_ms: 11.36125 + type: 'test' + ... +# Subtest: shared-child batch input compiles the inclusive cross-month range without exposing an Agent action +ok 237 - shared-child batch input compiles the inclusive cross-month range without exposing an Agent action + --- + duration_ms: 0.418417 + type: 'test' + ... +# Subtest: passenger TSV preserves 13 columns when the final passenger has empty optional fields +ok 238 - passenger TSV preserves 13 columns when the final passenger has empty optional fields + --- + duration_ms: 4.392458 + type: 'test' + ... +# Subtest: passenger TSV rejects actual 12-column and 14-column data rows +ok 239 - passenger TSV rejects actual 12-column and 14-column data rows + --- + duration_ms: 0.557959 + type: 'test' + ... +# Subtest: passenger TSV promotes only an exact leader remark into structured ERP contact data +ok 240 - passenger TSV promotes only an exact leader remark into structured ERP contact data + --- + duration_ms: 0.550708 + type: 'test' + ... +# Subtest: passenger TSV rejects duplicate leaders or a leader without both name and phone +ok 241 - passenger TSV rejects duplicate leaders or a leader without both name and phone + --- + duration_ms: 0.908417 + type: 'test' + ... +# Subtest: independent room updates accept DBL shorthand without dropping other intentions +ok 242 - independent room updates accept DBL shorthand without dropping other intentions + --- + duration_ms: 19.992625 + type: 'test' + ... +# Subtest: independent headcount distributions keep occupied and non-occupied children distinct +ok 243 - independent headcount distributions keep occupied and non-occupied children distinct + --- + duration_ms: 2.555958 + type: 'test' + ... +# Subtest: named references retain original fullwidth spelling and internal whitespace in keyword +ok 244 - named references retain original fullwidth spelling and internal whitespace in keyword + --- + duration_ms: 1.288625 + type: 'test' + ... +# Subtest: registered customer typo is normalized only in name while keyword preserves source +ok 245 - registered customer typo is normalized only in name while keyword preserves source + --- + duration_ms: 0.670334 + type: 'test' + ... +# Subtest: route and field labels allow only a unique one-character correction +ok 246 - route and field labels allow only a unique one-character correction + --- + duration_ms: 0.637917 + type: 'test' + ... +# Subtest: route resolution is source-agnostic for embedded directives and complete unique field signatures +ok 247 - route resolution is source-agnostic for embedded directives and complete unique field signatures + --- + duration_ms: 3.788458 + type: 'test' + ... +# Subtest: unknown labels are eligible for auto fallback, but invalid values and conflicts are not +ok 248 - unknown labels are eligible for auto fallback, but invalid values and conflicts are not + --- + duration_ms: 2.221208 + type: 'test' + ... +# Subtest: business-value errors stay on the deterministic path instead of becoming contract fallbacks +ok 249 - business-value errors stay on the deterministic path instead of becoming contract fallbacks + --- + duration_ms: 2.202625 + type: 'test' + ... +# Subtest: independent updates require an exact registered target name +ok 250 - independent updates require an exact registered target name + --- + duration_ms: 0.616458 + type: 'test' + ... +# Subtest: later turns overwrite prior fields while an unlabeled supplement fills exactly one missing field +ok 251 - later turns overwrite prior fields while an unlabeled supplement fills exactly one missing field + --- + duration_ms: 0.518833 + type: 'test' + ... +# Subtest: a second business directive cannot be merged into the same task +ok 252 - a second business directive cannot be merged into the same task + --- + duration_ms: 0.414667 + type: 'test' + ... +# Subtest: passenger TSV rejects duplicate sequence numbers without changing any value +ok 253 - passenger TSV rejects duplicate sequence numbers without changing any value + --- + duration_ms: 0.320666 + type: 'test' + ... +# Subtest: all export expands deterministically and pickup sign requires a visitor name +ok 254 - all export expands deterministically and pickup sign requires a visitor name + --- + duration_ms: 0.481291 + type: 'test' + ... +# Subtest: whole shared-plan visitor information stays distinct from order and child visitor lists +ok 255 - whole shared-plan visitor information stays distinct from order and child visitor lists + --- + duration_ms: 1.214875 + type: 'test' + ... +# Subtest: date normalization handles next occurrence, cross-year ranges, and leap days +ok 256 - date normalization handles next occurrence, cross-year ranges, and leap days + --- + duration_ms: 1.401958 + type: 'test' + ... +# Subtest: modification dates use the next occurrence and preserve explicit years +ok 257 - modification dates use the next occurrence and preserve explicit years + --- + duration_ms: 4.481375 + type: 'test' + ... +# Subtest: a yearless modification date supplied on a later turn uses that turn date context +ok 258 - a yearless modification date supplied on a later turn uses that turn date context + --- + duration_ms: 0.456875 + type: 'test' + ... +# Subtest: cancel and restore dates use the next occurrence and preserve explicit years +ok 259 - cancel and restore dates use the next occurrence and preserve explicit years + --- + duration_ms: 4.0695 + type: 'test' + ... +# Subtest: deterministic parser meets the ordinary and large-list local P95 budgets +ok 260 - deterministic parser meets the ordinary and large-list local P95 budgets + --- + duration_ms: 69.737209 + type: 'test' + ... +# Subtest: semantic comparator ignores safe ordering but reports changed operation fields +ok 261 - semantic comparator ignores safe ordering but reports changed operation fields + --- + duration_ms: 1.401625 + type: 'test' + ... +# Subtest: mode orchestration keeps AI authority in Shadow and enforces the Auto fallback allowlist +ok 262 - mode orchestration keeps AI authority in Shadow and enforces the Auto fallback allowlist + --- + duration_ms: 1.72625 + type: 'test' + ... +# Subtest: repeated hotel blocks always use their deterministic batch contract even with AI affinity +ok 263 - repeated hotel blocks always use their deterministic batch contract even with AI affinity + --- + duration_ms: 1.44575 + type: 'test' + ... +# Subtest: hotel arrangement uses the customer bilingual booking template +ok 264 - hotel arrangement uses the customer bilingual booking template + --- + duration_ms: 3.08925 + type: 'test' + ... +# Subtest: hotel clear and update replies stay business-readable when the booking template is not applicable +ok 265 - hotel clear and update replies stay business-readable when the booking template is not applicable + --- + duration_ms: 0.302416 + type: 'test' + ... +# Subtest: hotel replacement reply describes the new hotel and count while retaining the original dates +ok 266 - hotel replacement reply describes the new hotel and count while retaining the original dates + --- + duration_ms: 0.100208 + type: 'test' + ... +# Subtest: batch newbooking receipt preserves every group number +ok 267 - batch newbooking receipt preserves every group number + --- + duration_ms: 0.075542 + type: 'test' + ... +# Subtest: update and file replies expose business fields without technical evidence +ok 268 - update and file replies expose business fields without technical evidence + --- + duration_ms: 5.080208 + type: 'test' + ... +# Subtest: visitor export reply exposes only the mobile XLSX delivery artifact +ok 269 - visitor export reply exposes only the mobile XLSX delivery artifact + --- + duration_ms: 1.850708 + type: 'test' + ... +# Subtest: legacy ERP mojibake filenames are repaired when a stored receipt is read +ok 270 - legacy ERP mojibake filenames are repaired when a stored receipt is read + --- + duration_ms: 1.241458 + type: 'test' + ... +# Subtest: arrangement replacement receipts report the new resource and quantity with original dates +ok 271 - arrangement replacement receipts report the new resource and quantity with original dates + --- + duration_ms: 0.231416 + type: 'test' + ... +# Subtest: all business actions project accepted success without roster contents or arbitrary result fields +ok 272 - all business actions project accepted success without roster contents or arbitrary result fields + --- + duration_ms: 5.153792 + type: 'test' + ... +# Subtest: failed, uncertain, blocker and unproved results do not create completed business records +ok 273 - failed, uncertain, blocker and unproved results do not create completed business records + --- + duration_ms: 0.881125 + type: 'test' + ... +# Subtest: individual group conflicts are rejected and full suffixes remain distinct +ok 274 - individual group conflicts are rejected and full suffixes remain distinct + --- + duration_ms: 0.208666 + type: 'test' + ... +# Subtest: internal identifiers and conflicting child-parent evidence cannot become group associations +ok 275 - internal identifiers and conflicting child-parent evidence cannot become group associations + --- + duration_ms: 0.115042 + type: 'test' + ... +# Subtest: batch children are explicitly mapped to their respective parents, never zipped by array order +ok 276 - batch children are explicitly mapped to their respective parents, never zipped by array order + --- + duration_ms: 0.140834 + type: 'test' + ... +# Subtest: native child-create receipts carry the verified parent; missing parents remain unlinked +ok 277 - native child-create receipts carry the verified parent; missing parents remain unlinked + --- + duration_ms: 0.079916 + type: 'test' + ... +# Subtest: roster counts use the latest import per subject and never add repeated uploads +ok 278 - roster counts use the latest import per subject and never add repeated uploads + --- + duration_ms: 15.041 + type: 'test' + ... +# Subtest: shared-plan summaries separate each child and never claim every roster is complete +ok 279 - shared-plan summaries separate each child and never claim every roster is complete + --- + duration_ms: 0.557125 + type: 'test' + ... +# Subtest: cancel/restore updates current state without erasing arrangements or uncancelling children +ok 280 - cancel/restore updates current state without erasing arrangements or uncancelling children + --- + duration_ms: 0.584125 + type: 'test' + ... +# Subtest: verified parent cancellation propagates only proven child references +ok 281 - verified parent cancellation propagates only proven child references + --- + duration_ms: 0.35975 + type: 'test' + ... +# Subtest: all current arrangement rows render supported details; unknown legacy detail is explicit +ok 282 - all current arrangement rows render supported details; unknown legacy detail is explicit + --- + duration_ms: 0.1395 + type: 'test' + ... +# Subtest: exports are latest per file type and labelled stale after later changes without deleting records +ok 283 - exports are latest per file type and labelled stale after later changes without deleting records + --- + duration_ms: 0.493958 + type: 'test' + ... +# Subtest: receipts stay frozen and rebuilding never duplicates the progress block +ok 284 - receipts stay frozen and rebuilding never duplicates the progress block + --- + duration_ms: 0.94975 + type: 'test' + ... +# Subtest: encrypted ledger isolates accounts and organizations, deduplicates, and survives task deletion +ok 285 - encrypted ledger isolates accounts and organizations, deduplicates, and survives task deletion + --- + duration_ms: 1.379125 + type: 'test' + ... +# Subtest: unlinked child history joins only a uniquely known parent in the same account +ok 286 - unlinked child history joins only a uniquely known parent in the same account + --- + duration_ms: 0.724833 + type: 'test' + ... +# Subtest: history backfill preserves only the matching successful execution including archived tasks +ok 287 - history backfill preserves only the matching successful execution including archived tasks + --- + duration_ms: 0.656209 + type: 'test' + ... +# Subtest: migration preserves business records without task foreign keys and prevents administrator ownership +ok 288 - migration preserves business records without task foreign keys and prevents administrator ownership + --- + duration_ms: 11.126458 + type: 'test' + ... +# Subtest: partial batches retain only independently verified completed rows +ok 289 - partial batches retain only independently verified completed rows + --- + duration_ms: 1.495834 + type: 'test' + ... +# Subtest: batch team dates follow their own receipt rows and never copy a date onto all groups +ok 290 - batch team dates follow their own receipt rows and never copy a date onto all groups + --- + duration_ms: 0.145333 + type: 'test' + ... +# Subtest: channel length limits clearly disclose truncation while leaving short replies unchanged +ok 291 - channel length limits clearly disclose truncation while leaving short replies unchanged + --- + duration_ms: 0.938125 + type: 'test' + ... +1..291 +# tests 299 +# suites 0 +# pass 299 +# fail 0 +# cancelled 0 +# skipped 0 +# todo 0 +# duration_ms 8667.896584 diff --git a/archive/evidence/2026-09-24/hotel-batch-01a0858b/docx-page-8.png b/archive/evidence/2026-09-24/hotel-batch-01a0858b/docx-page-8.png new file mode 100644 index 0000000..9c73718 Binary files /dev/null and b/archive/evidence/2026-09-24/hotel-batch-01a0858b/docx-page-8.png differ diff --git a/archive/evidence/2026-09-24/hotel-batch-01a0858b/docx-page-9.png b/archive/evidence/2026-09-24/hotel-batch-01a0858b/docx-page-9.png new file mode 100644 index 0000000..5d23562 Binary files /dev/null and b/archive/evidence/2026-09-24/hotel-batch-01a0858b/docx-page-9.png differ diff --git a/archive/evidence/2026-09-24/hotel-batch-01a0858b/legacy-tests.txt b/archive/evidence/2026-09-24/hotel-batch-01a0858b/legacy-tests.txt new file mode 100644 index 0000000..59c903e --- /dev/null +++ b/archive/evidence/2026-09-24/hotel-batch-01a0858b/legacy-tests.txt @@ -0,0 +1,2714 @@ +TAP version 13 +# Subtest: account creation accepts any non-empty password and blocks an empty password before the request +ok 1 - account creation accepts any non-empty password and blocks an empty password before the request + --- + duration_ms: 4.966917 + type: 'test' + ... +# Subtest: account creation normalizes valid form values into the server contract +ok 2 - account creation normalizes valid form values into the server contract + --- + duration_ms: 0.486292 + type: 'test' + ... +# Subtest: account creation translates backend password validation into an actionable message +ok 3 - account creation translates backend password validation into an actionable message + --- + duration_ms: 0.422625 + type: 'test' + ... +# Subtest: account forms expose no length rule or first-login forced-password flow +ok 4 - account forms expose no length rule or first-login forced-password flow + --- + duration_ms: 0.435625 + type: 'test' + ... +# Subtest: login after an unauthenticated page load starts ongoing status recovery without reload +ok 5 - login after an unauthenticated page load starts ongoing status recovery without reload + --- + duration_ms: 255.065041 + type: 'test' + ... +# Subtest: a pending task-list refresh does not stop bridge health or subsequent heartbeat recovery +ok 6 - a pending task-list refresh does not stop bridge health or subsequent heartbeat recovery + --- + duration_ms: 2.489916 + type: 'test' + ... +# Subtest: a two-second worker/ERP response stays connected and reaches the server heartbeat +ok 7 - a two-second worker/ERP response stays connected and reaches the server heartbeat + --- + duration_ms: 1.444166 + type: 'test' + ... +# Subtest: simultaneous focus and manual checks coalesce into one account-bound probe +ok 8 - simultaneous focus and manual checks coalesce into one account-bound probe + --- + duration_ms: 1.899209 + type: 'test' + ... +# Subtest: late status replies after logout cannot publish a heartbeat or restore connected status +ok 9 - late status replies after logout cannot publish a heartbeat or restore connected status + --- + duration_ms: 1.792709 + type: 'test' + ... +# Subtest: bridge-ready announcement refreshes account-bound state and never accepts an unbound snapshot +ok 10 - bridge-ready announcement refreshes account-bound state and never accepts an unbound snapshot + --- + duration_ms: 1.276458 + type: 'test' + ... +# Subtest: an admin page never sends a plugin probe or worker heartbeat +ok 11 - an admin page never sends a plugin probe or worker heartbeat + --- + duration_ms: 1.504834 + type: 'test' + ... +# Subtest: repeated login retains one timer and one focus listener; logout makes polling inert +ok 12 - repeated login retains one timer and one focus listener; logout makes polling inert + --- + duration_ms: 18.347458 + type: 'test' + ... +# Subtest: server worker rejection remains a warning until a later verified heartbeat recovers +ok 13 - server worker rejection remains a warning until a later verified heartbeat recovers + --- + duration_ms: 154.06625 + type: 'test' + ... +# Subtest: an old session response cannot overwrite a newer session check or heartbeat identity +ok 14 - an old session response cannot overwrite a newer session check or heartbeat identity + --- + duration_ms: 8.632292 + type: 'test' + ... +# Subtest: five-second plugin acceptance no longer triggers the old four-second false timeout +ok 15 - five-second plugin acceptance no longer triggers the old four-second false timeout + --- + duration_ms: 7.049625 + type: 'test' + ... +# Subtest: a genuine acceptance timeout records uncertainty and never dispatches twice +ok 16 - a genuine acceptance timeout records uncertainty and never dispatches twice + --- + duration_ms: 1.708666 + type: 'test' + ... +# Subtest: dispatch uncertainty reads stored late blocked results with no ERP action or new claim +ok 17 - dispatch uncertainty reads stored late blocked results with no ERP action or new claim + --- + duration_ms: 3.765959 + type: 'test' + ... +# Subtest: late progress does not replace dispatch uncertainty or post another API result +ok 18 - late progress does not replace dispatch uncertainty or post another API result + --- + duration_ms: 6.711708 + type: 'test' + ... +# Subtest: a rejected stored result preserves the platform uncertainty state +ok 19 - a rejected stored result preserves the platform uncertainty state + --- + duration_ms: 1.365416 + type: 'test' + ... +# Subtest: post-write uncertainty keeps its existing manual reconciliation boundary +ok 20 - post-write uncertainty keeps its existing manual reconciliation boundary + --- + duration_ms: 3.322541 + type: 'test' + ... +# Subtest: maintenance-expired dispatch tasks can manually sync local plugin results without ERP reconciliation +ok 21 - maintenance-expired dispatch tasks can manually sync local plugin results without ERP reconciliation + --- + duration_ms: 1.688667 + type: 'test' + ... +# Subtest: other-account tasks cannot read, persist or dispatch plugin tasks +ok 22 - other-account tasks cannot read, persist or dispatch plugin tasks + --- + duration_ms: 1.498208 + type: 'test' + ... +# Subtest: dashboard summary cards are uniform display-only totals and leave filtering to the explicit controls +ok 23 - dashboard summary cards are uniform display-only totals and leave filtering to the explicit controls + --- + duration_ms: 2.348792 + type: 'test' + ... +# Subtest: waiting-for-input work is folded into in-progress instead of exposing a follow-up state +ok 24 - waiting-for-input work is folded into in-progress instead of exposing a follow-up state + --- + duration_ms: 0.268833 + type: 'test' + ... +# Subtest: dashboard route has a focused leadership header and phone-safe navigation shell +ok 25 - dashboard route has a focused leadership header and phone-safe navigation shell + --- + duration_ms: 0.237125 + type: 'test' + ... +# Subtest: phone dashboard turns metrics and task rows into touch-friendly cards +ok 26 - phone dashboard turns metrics and task rows into touch-friendly cards + --- + duration_ms: 26.4135 + type: 'test' + ... +# Subtest: table filters use a bounded single-connection read and default to 20 rows +ok 27 - table filters use a bounded single-connection read and default to 20 rows + --- + duration_ms: 5.347542 + type: 'test' + ... +# Subtest: workbench exposes one XLS/XLSX roster attachment without turning it into persistent state +ok 28 - workbench exposes one XLS/XLSX roster attachment without turning it into persistent state + --- + duration_ms: 5.45075 + type: 'test' + ... +# Subtest: new and awaiting roster tasks send a single in-memory base64 JSON attachment +ok 29 - new and awaiting roster tasks send a single in-memory base64 JSON attachment + --- + duration_ms: 0.285875 + type: 'test' + ... +# Subtest: awaiting_attachment has a dedicated attachment-only continuation UI +ok 30 - awaiting_attachment has a dedicated attachment-only continuation UI + --- + duration_ms: 0.155583 + type: 'test' + ... +# Subtest: parser routing visibly locks Program-only routes and keeps emergency wording accurate +ok 31 - parser routing visibly locks Program-only routes and keeps emergency wording accurate + --- + duration_ms: 0.089166 + type: 'test' + ... +# Subtest: external parser contract + # Subtest: validates canonical operation JSON locally without opening an Agent session + ok 1 - validates canonical operation JSON locally without opening an Agent session + --- + duration_ms: 91.811042 + type: 'test' + ... + # Subtest: rejects invalid canonical operation JSON locally without Agent repair + ok 2 - rejects invalid canonical operation JSON locally without Agent repair + --- + duration_ms: 1.4395 + type: 'test' + ... + # Subtest: creates a task-isolated session and sends the raw input through SSE + ok 3 - creates a task-isolated session and sends the raw input through SSE + --- + duration_ms: 31.847333 + type: 'test' + ... + # Subtest: repairs one contract-invalid response in the same session without dropping unknown fields + ok 4 - repairs one contract-invalid response in the same session without dropping unknown fields + --- + duration_ms: 114.90175 + type: 'test' + ... + # Subtest: repairs a stale other/filing item question in the same session + ok 5 - repairs a stale other/filing item question in the same session + --- + duration_ms: 10.111583 + type: 'test' + ... + # Subtest: repairs a stale modification year question in the same session + ok 6 - repairs a stale modification year question in the same session + --- + duration_ms: 73.845042 + type: 'test' + ... + # Subtest: repairs the reported stale restore year question in the same session + ok 7 - repairs the reported stale restore year question in the same session + --- + duration_ms: 2.424 + type: 'test' + ... + # Subtest: reports the concrete unknown operation.data keys + ok 8 - reports the concrete unknown operation.data keys + --- + duration_ms: 0.718042 + type: 'test' + ... + # Subtest: returns NEED_INPUT without an operation and continues the same session on the next turn + ok 9 - returns NEED_INPUT without an operation and continues the same session on the next turn + --- + duration_ms: 2.590125 + type: 'test' + ... + # Subtest: rebuilds an expired session once and replays stored turns before the current turn + ok 10 - rebuilds an expired session once and replays stored turns before the current turn + --- + duration_ms: 3.285166 + type: 'test' + ... + # Subtest: returns a blocked result without making a request when the server key is absent + ok 11 - returns a blocked result without making a request when the server key is absent + --- + duration_ms: 0.296 + type: 'test' + ... + # Subtest: probes the provider health endpoint without creating a session + ok 12 - probes the provider health endpoint without creating a session + --- + duration_ms: 0.68875 + type: 'test' + ... + # Subtest: reports an unhealthy provider endpoint as disconnected + ok 13 - reports an unhealthy provider endpoint as disconnected + --- + duration_ms: 0.2285 + type: 'test' + ... + # Subtest: maps external authorization and active-run errors without exposing the key + ok 14 - maps external authorization and active-run errors without exposing the key + --- + duration_ms: 0.52025 + type: 'test' + ... + # Subtest: rejects unsafe provider URLs and non-raw API keys + ok 15 - rejects unsafe provider URLs and non-raw API keys + --- + duration_ms: 0.114916 + type: 'test' + ... + # Subtest: retries one rate-limited request with the same stable idempotency key + ok 16 - retries one rate-limited request with the same stable idempotency key + --- + duration_ms: 1.495833 + type: 'test' + ... + # Subtest: uses one parse-wide rate-limit budget across session and stream + ok 17 - uses one parse-wide rate-limit budget across session and stream + --- + duration_ms: 0.725917 + type: 'test' + ... + # Subtest: redacts arbitrary provider errors, API keys and raw input + ok 18 - redacts arbitrary provider errors, API keys and raw input + --- + duration_ms: 0.248291 + type: 'test' + ... + # Subtest: returns a bounded cancellation result when an SSE body ignores cancel() + ok 19 - returns a bounded cancellation result when an SSE body ignores cancel() + --- + duration_ms: 1246.485083 + type: 'test' + ... + 1..19 +ok 32 - external parser contract + --- + duration_ms: 1586.557084 + type: 'suite' + ... +# Subtest: SSE and result helpers + # Subtest: parses CRLF SSE blocks and JSON objects wrapped in markdown + ok 1 - parses CRLF SSE blocks and JSON objects wrapped in markdown + --- + duration_ms: 0.780708 + type: 'test' + ... + # Subtest: returns after a terminal SSE event even if the provider keeps the stream open + ok 2 - returns after a terminal SSE event even if the provider keeps the stream open + --- + duration_ms: 2.509292 + type: 'test' + ... + # Subtest: accepts a structured final event and preserves the operation contract + ok 3 - accepts a structured final event and preserves the operation contract + --- + duration_ms: 1.109583 + type: 'test' + ... + # Subtest: rejects provider-supplied runtime metadata from natural-language results + ok 4 - rejects provider-supplied runtime metadata from natural-language results + --- + duration_ms: 0.366167 + type: 'test' + ... + # Subtest: collects assistant chunks from the documented messages event shape + ok 5 - collects assistant chunks from the documented messages event shape + --- + duration_ms: 0.389208 + type: 'test' + ... + # Subtest: handles UTF-8 byte boundaries and CRLF while requiring a terminal event + ok 6 - handles UTF-8 byte boundaries and CRLF while requiring a terminal event + --- + duration_ms: 0.359833 + type: 'test' + ... + # Subtest: fails closed on oversized, incomplete and conflicting SSE output + # Subtest: oversized event + ok 1 - oversized event + --- + duration_ms: 0.609542 + type: 'test' + ... + # Subtest: event count limit + ok 2 - event count limit + --- + duration_ms: 0.351917 + type: 'test' + ... + # Subtest: missing terminal event + ok 3 - missing terminal event + --- + duration_ms: 0.3365 + type: 'test' + ... + # Subtest: conflicting candidates + ok 4 - conflicting candidates + --- + duration_ms: 0.603333 + type: 'test' + ... + # Subtest: wrong session content type + ok 5 - wrong session content type + --- + duration_ms: 0.99725 + type: 'test' + ... + 1..5 + ok 7 - fails closed on oversized, incomplete and conflicting SSE output + --- + duration_ms: 3.332792 + type: 'test' + ... + # Subtest: blocks the currently published flat parser shape instead of adapting it + ok 8 - blocks the currently published flat parser shape instead of adapting it + --- + duration_ms: 0.090459 + type: 'test' + ... + # Subtest: blocks camelCase flat fields emitted by the external Profile + ok 9 - blocks camelCase flat fields emitted by the external Profile + --- + duration_ms: 0.063625 + type: 'test' + ... + # Subtest: blocks the malformed formal batch payload that previously reached execution + ok 10 - blocks the malformed formal batch payload that previously reached execution + --- + duration_ms: 0.191417 + type: 'test' + ... + # Subtest: rolls yearless newbooking dates to the next occurrence using the task date + ok 11 - rolls yearless newbooking dates to the next occurrence using the task date + --- + duration_ms: 0.340542 + type: 'test' + ... + # Subtest: uses the current year for a future yearless newbooking date + ok 12 - uses the current year for a future yearless newbooking date + --- + duration_ms: 0.296292 + type: 'test' + ... + # Subtest: does not rewrite explicitly year-qualified newbooking dates + ok 13 - does not rewrite explicitly year-qualified newbooking dates + --- + duration_ms: 0.095 + type: 'test' + ... + # Subtest: rolls yearless modification lookup dates to the next occurrence + ok 14 - rolls yearless modification lookup dates to the next occurrence + --- + duration_ms: 0.187 + type: 'test' + ... + # Subtest: normalizes the reported shared-child 11-15 lookup without asking for a year + ok 15 - normalizes the reported shared-child 11-15 lookup without asking for a year + --- + duration_ms: 0.236625 + type: 'test' + ... + # Subtest: normalizes modification target dates and preserves explicit years + ok 16 - normalizes modification target dates and preserves explicit years + --- + duration_ms: 0.242958 + type: 'test' + ... + # Subtest: normalizes a yearless hotel-change end date + ok 17 - normalizes a yearless hotel-change end date + --- + duration_ms: 0.148792 + type: 'test' + ... + # Subtest: normalizes cancel and restore lookup dates and preserves explicit years + ok 18 - normalizes cancel and restore lookup dates and preserves explicit years + --- + duration_ms: 0.630583 + type: 'test' + ... + # Subtest: rejects a stale year-only restore question so the client performs one contract repair + ok 19 - rejects a stale year-only restore question so the client performs one contract repair + --- + duration_ms: 0.090875 + type: 'test' + ... + # Subtest: rejects a stale year-only modification question so the client performs one contract repair + ok 20 - rejects a stale year-only modification question so the client performs one contract repair + --- + duration_ms: 0.099708 + type: 'test' + ... + # Subtest: uses original task history when a continuation only supplies a missing field + ok 21 - uses original task history when a continuation only supplies a missing field + --- + duration_ms: 0.157959 + type: 'test' + ... + # Subtest: preserves product/customer source-region context without semantic validation + ok 22 - preserves product/customer source-region context without semantic validation + --- + duration_ms: 0.087583 + type: 'test' + ... + # Subtest: accepts missing source-region metadata without blocking + ok 23 - accepts missing source-region metadata without blocking + --- + duration_ms: 0.072792 + type: 'test' + ... + # Subtest: keeps shared-child guidance at user-visible business fields + ok 24 - keeps shared-child guidance at user-visible business fields + --- + duration_ms: 0.16275 + type: 'test' + ... + # Subtest: keeps the versioned router in the profile and the per-task message minimal + ok 25 - keeps the versioned router in the profile and the per-task message minimal + --- + duration_ms: 0.150875 + type: 'test' + ... + # Subtest: keeps shared-child batch creation Program-only and outside the external Agent router + ok 26 - keeps shared-child batch creation Program-only and outside the external Agent router + --- + duration_ms: 0.234708 + type: 'test' + ... + # Subtest: pins the optional other/filing fields in the per-task guidance + ok 27 - pins the optional other/filing fields in the per-task guidance + --- + duration_ms: 0.075333 + type: 'test' + ... + # Subtest: accepts a shared-child operation with only the visible parent reference + ok 28 - accepts a shared-child operation with only the visible parent reference + --- + duration_ms: 0.10475 + type: 'test' + ... + # Subtest: rejects internal parent identifiers in Agent output + ok 29 - rejects internal parent identifiers in Agent output + --- + duration_ms: 0.087708 + type: 'test' + ... + # Subtest: accepts multi-date shared-plan child facts without execution metadata + ok 30 - accepts multi-date shared-plan child facts without execution metadata + --- + duration_ms: 0.131666 + type: 'test' + ... + # Subtest: normalizes legacy flat shared-plan child fields into split_order + ok 31 - normalizes legacy flat shared-plan child fields into split_order + --- + duration_ms: 0.117375 + type: 'test' + ... + # Subtest: accepts parser-state lifecycle facts without requiring execution-state fields + ok 32 - accepts parser-state lifecycle facts without requiring execution-state fields + --- + duration_ms: 0.084416 + type: 'test' + ... + # Subtest: treats ERP passenger rows as an initial baseline while retaining a 5000-row technical bound + ok 33 - treats ERP passenger rows as an initial baseline while retaining a 5000-row technical bound + --- + duration_ms: 1.084417 + type: 'test' + ... + # Subtest: accepts hotel arrangement input without a room type and leaves that field for ERP resolution + ok 34 - accepts hotel arrangement input without a room type and leaves that field for ERP resolution + --- + duration_ms: 0.685333 + type: 'test' + ... + # Subtest: accepts customer/date business lookup while keeping visible identifiers optional + ok 35 - accepts customer/date business lookup while keeping visible identifiers optional + --- + duration_ms: 0.830458 + type: 'test' + ... + # Subtest: accepts only visitor-list for a shared mother-plan export scope + ok 36 - accepts only visitor-list for a shared mother-plan export scope + --- + duration_ms: 0.218167 + type: 'test' + ... + # Subtest: allows a no-customer shared mother-plan capacity update when the departure date is present + ok 37 - allows a no-customer shared mother-plan capacity update when the departure date is present + --- + duration_ms: 0.36325 + type: 'test' + ... + # Subtest: preserves a complete independent passenger and room distribution as multiple update actions + ok 38 - preserves a complete independent passenger and room distribution as multiple update actions + --- + duration_ms: 0.582583 + type: 'test' + ... + # Subtest: passes Agent and plugin front-gate validation for every supported directive route + ok 39 - passes Agent and plugin front-gate validation for every supported directive route + --- + duration_ms: 2.054083 + type: 'test' + ... + # Subtest: blocks a generic Profile response instead of inventing an operation + ok 40 - blocks a generic Profile response instead of inventing an operation + --- + duration_ms: 0.089542 + type: 'test' + ... + # Subtest: turns a stale other/filing item question into a contract repair instead of asking the user + ok 41 - turns a stale other/filing item question into a contract repair instead of asking the user + --- + duration_ms: 0.096541 + type: 'test' + ... + # Subtest: derives a NEED_INPUT reply from validated question prompts when the Agent omitted the top-level reply + ok 42 - derives a NEED_INPUT reply from validated question prompts when the Agent omitted the top-level reply + --- + duration_ms: 0.113208 + type: 'test' + ... + # Subtest: still blocks NEED_INPUT when only internal missing fields exist without a displayable question + ok 43 - still blocks NEED_INPUT when only internal missing fields exist without a displayable question + --- + duration_ms: 0.059666 + type: 'test' + ... + 1..43 +ok 33 - SSE and result helpers + --- + duration_ms: 19.934333 + type: 'suite' + ... +# Subtest: business-system HTTP boundary + # Subtest: returns the real AI connectivity probe from /api/status + ok 1 - returns the real AI connectivity probe from /api/status + --- + duration_ms: 30.758625 + type: 'test' + ... + # Subtest: exposes /api/parse and no longer exposes /api/agent routes + ok 2 - exposes /api/parse and no longer exposes /api/agent routes + --- + duration_ms: 7.098792 + type: 'test' + ... + 1..2 +ok 34 - business-system HTTP boundary + --- + duration_ms: 38.11325 + type: 'suite' + ... +# Subtest: vehicle create recovers from a delayed linkage sample but never saves while sampling stays delayed +ok 35 - vehicle create recovers from a delayed linkage sample but never saves while sampling stays delayed + --- + duration_ms: 275.315375 + type: 'test' + ... +# Subtest: vehicle create preserves old rows and binds native selection, dates and quantity to empty rows 0/1/10 +ok 36 - vehicle create preserves old rows and binds native selection, dates and quantity to empty rows 0/1/10 + --- + duration_ms: 540.968625 + type: 'test' + ... +# Subtest: vehicle requery follows the new record id after sorting, including stored reconciliation evidence +ok 37 - vehicle requery follows the new record id after sorting, including stored reconciliation evidence + --- + duration_ms: 219.746833 + type: 'test' + ... +# Subtest: vehicle rejects no free row, drafts, financial state, missing controls and forced targets before POST +ok 38 - vehicle rejects no free row, drafts, financial state, missing controls and forced targets before POST + --- + duration_ms: 225.516625 + type: 'test' + ... +# Subtest: vehicle checks existing rows and new values after native linkage and again after validation +ok 39 - vehicle checks existing rows and new values after native linkage and again after validation + --- + duration_ms: 732.971875 + type: 'test' + ... +# Subtest: vehicle cannot claim completion if ERP changes an old row, misses or duplicates the new row +ok 40 - vehicle cannot claim completion if ERP changes an old row, misses or duplicates the new row + --- + duration_ms: 168.761167 + type: 'test' + ... +# Subtest: transport create recovers from a delayed linkage sample but never saves while sampling stays delayed +ok 41 - transport create recovers from a delayed linkage sample but never saves while sampling stays delayed + --- + duration_ms: 90.228542 + type: 'test' + ... +# Subtest: transport create preserves old rows and binds native selection, dates and quantity to empty rows 0/1/10 +ok 42 - transport create preserves old rows and binds native selection, dates and quantity to empty rows 0/1/10 + --- + duration_ms: 379.126875 + type: 'test' + ... +# Subtest: transport requery follows the new record id after sorting, including stored reconciliation evidence +ok 43 - transport requery follows the new record id after sorting, including stored reconciliation evidence + --- + duration_ms: 27.497625 + type: 'test' + ... +# Subtest: transport rejects no free row, drafts, financial state, missing controls and forced targets before POST +ok 44 - transport rejects no free row, drafts, financial state, missing controls and forced targets before POST + --- + duration_ms: 195.771416 + type: 'test' + ... +# Subtest: transport checks existing rows and new values after native linkage and again after validation +ok 45 - transport checks existing rows and new values after native linkage and again after validation + --- + duration_ms: 479.422084 + type: 'test' + ... +# Subtest: transport cannot claim completion if ERP changes an old row, misses or duplicates the new row +ok 46 - transport cannot claim completion if ERP changes an old row, misses or duplicates the new row + --- + duration_ms: 32.658 + type: 'test' + ... +# Subtest: other create recovers from a delayed linkage sample but never saves while sampling stays delayed +ok 47 - other create recovers from a delayed linkage sample but never saves while sampling stays delayed + --- + duration_ms: 29.498375 + type: 'test' + ... +# Subtest: other create preserves old rows and binds native selection, dates and quantity to empty rows 0/1/10 +ok 48 - other create preserves old rows and binds native selection, dates and quantity to empty rows 0/1/10 + --- + duration_ms: 263.800958 + type: 'test' + ... +# Subtest: other requery follows the new record id after sorting, including stored reconciliation evidence +ok 49 - other requery follows the new record id after sorting, including stored reconciliation evidence + --- + duration_ms: 13.540083 + type: 'test' + ... +# Subtest: other rejects no free row, drafts, financial state, missing controls and forced targets before POST +ok 50 - other rejects no free row, drafts, financial state, missing controls and forced targets before POST + --- + duration_ms: 170.911958 + type: 'test' + ... +# Subtest: other checks existing rows and new values after native linkage and again after validation +ok 51 - other checks existing rows and new values after native linkage and again after validation + --- + duration_ms: 329.4435 + type: 'test' + ... +# Subtest: other cannot claim completion if ERP changes an old row, misses or duplicates the new row +ok 52 - other cannot claim completion if ERP changes an old row, misses or duplicates the new row + --- + duration_ms: 100.3305 + type: 'test' + ... +# Subtest: transport creates in a departure-default row and preserves existing rows, spare rows and filing +ok 53 - transport creates in a departure-default row and preserves existing rows, spare rows and filing + --- + duration_ms: 1067.110375 + type: 'test' + ... +# Subtest: transport rejects drafts and unverified dates even with other default-date rows available +ok 54 - transport rejects drafts and unverified dates even with other default-date rows available + --- + duration_ms: 233.344667 + type: 'test' + ... +# Subtest: transport guards other default dates through native selection, validation and preflight changes +ok 55 - transport guards other default dates through native selection, validation and preflight changes + --- + duration_ms: 118.931791 + type: 'test' + ... +# Subtest: other creates in a departure-default row and preserves existing rows, spare rows and filing +ok 56 - other creates in a departure-default row and preserves existing rows, spare rows and filing + --- + duration_ms: 795.930917 + type: 'test' + ... +# Subtest: other rejects drafts and unverified dates even with other default-date rows available +ok 57 - other rejects drafts and unverified dates even with other default-date rows available + --- + duration_ms: 205.240458 + type: 'test' + ... +# Subtest: other guards other default dates through native selection, validation and preflight changes +ok 58 - other guards other default dates through native selection, validation and preflight changes + --- + duration_ms: 15.396791 + type: 'test' + ... +# Subtest: vehicle still rejects an unsaved date equal to departure +ok 59 - vehicle still rejects an unsaved date equal to departure + --- + duration_ms: 2.397625 + type: 'test' + ... +# Subtest: other create keeps omitted quantity and shared filing, and verifies them after sorted requery +ok 60 - other create keeps omitted quantity and shared filing, and verifies them after sorted requery + --- + duration_ms: 11.835334 + type: 'test' + ... +# Subtest: other preserves omitted zero quantity in a default-date row +ok 61 - other preserves omitted zero quantity in a default-date row + --- + duration_ms: 5.713959 + type: 'test' + ... +# Subtest: other create may fill blank filing fields, blocks conflicting filing and preserves realized state +ok 62 - other create may fill blank filing fields, blocks conflicting filing and preserves realized state + --- + duration_ms: 13.15875 + type: 'test' + ... +# Subtest: other native callbacks cannot alter omitted shared filing or the realized flag +ok 63 - other native callbacks cannot alter omitted shared filing or the realized flag + --- + duration_ms: 76.089875 + type: 'test' + ... +# Subtest: guide remains single-person: an occupied guide gives the change-command message without selecting or saving +ok 64 - guide remains single-person: an occupied guide gives the change-command message without selecting or saving + --- + duration_ms: 1.099083 + type: 'test' + ... +# Subtest: guide empty-slot guard still accepts the native single-row form +ok 65 - guide empty-slot guard still accepts the native single-row form + --- + duration_ms: 0.563083 + type: 'test' + ... +# Subtest: background preserves both create evidence types and maps the guide business error +ok 66 - background preserves both create evidence types and maps the guide business error + --- + duration_ms: 1.225875 + type: 'test' + ... +# Subtest: ordinary linkage requires multiple observations over 1.5 seconds and redacts values +ok 67 - ordinary linkage requires multiple observations over 1.5 seconds and redacts values + --- + duration_ms: 8.8595 + type: 'test' + ... +# Subtest: one delayed callback restarts observation instead of failing or counting the blind interval +ok 68 - one delayed callback restarts observation instead of failing or counting the blind interval + --- + duration_ms: 0.631084 + type: 'test' + ... +# Subtest: one-second sampling still proves stability; persistent long gaps fail boundedly +ok 69 - one-second sampling still proves stability; persistent long gaps fail boundedly + --- + duration_ms: 2.662125 + type: 'test' + ... +# Subtest: continuous value changes fail and report field names rather than their values +ok 70 - continuous value changes fail and report field names rather than their values + --- + duration_ms: 3.577416 + type: 'test' + ... +# Subtest: the last allowed sample is checked; a late change restarts stability +ok 71 - the last allowed sample is checked; a late change restarts stability + --- + duration_ms: 4.345292 + type: 'test' + ... +# Subtest: detached form, missing/replaced controls and pending native Ajax cannot pass +ok 72 - detached form, missing/replaced controls and pending native Ajax cannot pass + --- + duration_ms: 13.029417 + type: 'test' + ... +# Subtest: background reports the observed failure reason without classifying all failures as changing fields +ok 73 - background reports the observed failure reason without classifying all failures as changing fields + --- + duration_ms: 0.561792 + type: 'test' + ... +# Subtest: dated updates report missing MAIN-world dependency without changing native fields; guide stays independent +ok 74 - dated updates report missing MAIN-world dependency without changing native fields; guide stays independent + --- + duration_ms: 123.961875 + type: 'test' + ... +# Subtest: guide update resolves the original record and preserves dates and unspecified fields +ok 75 - guide update resolves the original record and preserves dates and unspecified fields + --- + duration_ms: 39.301125 + type: 'test' + ... +# Subtest: vehicle update resolves the original record and preserves dates and unspecified fields +ok 76 - vehicle update resolves the original record and preserves dates and unspecified fields + --- + duration_ms: 132.316708 + type: 'test' + ... +# Subtest: transport update resolves the original record and preserves dates and unspecified fields +ok 77 - transport update resolves the original record and preserves dates and unspecified fields + --- + duration_ms: 70.424 + type: 'test' + ... +# Subtest: other update resolves the original record and preserves dates and unspecified fields +ok 78 - other update resolves the original record and preserves dates and unspecified fields + --- + duration_ms: 62.687333 + type: 'test' + ... +# Subtest: missing or ambiguous original records never trigger a native selection +ok 79 - missing or ambiguous original records never trigger a native selection + --- + duration_ms: 22.550708 + type: 'test' + ... +# Subtest: old row identity, audit, payment and realized flags stop the update before mutation +ok 80 - old row identity, audit, payment and realized flags stop the update before mutation + --- + duration_ms: 64.237625 + type: 'test' + ... +# Subtest: other update merges filing changes, preserves filing date and omitted zero quantity +ok 81 - other update merges filing changes, preserves filing date and omitted zero quantity + --- + duration_ms: 26.203166 + type: 'test' + ... +# Subtest: date changes are rejected and native callbacks cannot change dates, IDs or other rows +ok 82 - date changes are rejected and native callbacks cannot change dates, IDs or other rows + --- + duration_ms: 415.537458 + type: 'test' + ... +# Subtest: vehicle create AND-filters type and supplier, preserves native description and original dates +ok 83 - vehicle create AND-filters type and supplier, preserves native description and original dates + --- + duration_ms: 26.309791 + type: 'test' + ... +# Subtest: vehicle update AND-filters type and supplier, preserves native description and original dates +ok 84 - vehicle update AND-filters type and supplier, preserves native description and original dates + --- + duration_ms: 8.107333 + type: 'test' + ... +# Subtest: vehicle filter never drops an unmatched type, picks an ambiguous row, or selects a different supplier +ok 85 - vehicle filter never drops an unmatched type, picks an ambiguous row, or selects a different supplier + --- + duration_ms: 57.652375 + type: 'test' + ... +# Subtest: changed vehicle candidates or type cannot reach form mutation after resolution +ok 86 - changed vehicle candidates or type cannot reach form mutation after resolution + --- + duration_ms: 13.383417 + type: 'test' + ... +# Subtest: vehicle filter is nonempty, vehicle-only and outside date selectors or direct changes in every contract +ok 87 - vehicle filter is nonempty, vehicle-only and outside date selectors or direct changes in every contract + --- + duration_ms: 113.689834 + type: 'test' + ... +# Subtest: transport create selects the unique supplier+ticket row through native linkage +ok 88 - transport create selects the unique supplier+ticket row through native linkage + --- + duration_ms: 9.418041 + type: 'test' + ... +# Subtest: transport update selects the unique supplier+ticket row through native linkage +ok 89 - transport update selects the unique supplier+ticket row through native linkage + --- + duration_ms: 6.07975 + type: 'test' + ... +# Subtest: explicit ticket filter never drops a keyword or defaults to the first ambiguous result +ok 90 - explicit ticket filter never drops a keyword or defaults to the first ambiguous result + --- + duration_ms: 98.312834 + type: 'test' + ... +# Subtest: transport create distinguishes route and train before an adjacent timetable +ok 91 - transport create distinguishes route and train before an adjacent timetable + --- + duration_ms: 76.431875 + type: 'test' + ... +# Subtest: transport update distinguishes route and train before an adjacent timetable +ok 92 - transport update distinguishes route and train before an adjacent timetable + --- + duration_ms: 60.034958 + type: 'test' + ... +# Subtest: description normalization preserves original numeric boundaries and existing formatting compatibility +ok 93 - description normalization preserves original numeric boundaries and existing formatting compatibility + --- + duration_ms: 1.797708 + type: 'test' + ... +# Subtest: route-only or train-only filters block with only the matching supplier candidates and no mutation +ok 94 - route-only or train-only filters block with only the matching supplier candidates and no mutation + --- + duration_ms: 94.257292 + type: 'test' + ... +# Subtest: ambiguous timetable candidates stay bounded and cannot default to an identical first row +ok 95 - ambiguous timetable candidates stay bounded and cannot default to an identical first row + --- + duration_ms: 8.795292 + type: 'test' + ... +# Subtest: transport route failures explain zero matches, unavailable data and bounded ambiguous candidates +ok 96 - transport route failures explain zero matches, unavailable data and bounded ambiguous candidates + --- + duration_ms: 24.718875 + type: 'test' + ... +# Subtest: changed ticket description or candidates block before any native call or mutation +ok 97 - changed ticket description or candidates block before any native call or mutation + --- + duration_ms: 69.666375 + type: 'test' + ... +# Subtest: ticket filter is nonempty, transport-only, and never a date selector or direct item update +ok 98 - ticket filter is nonempty, transport-only, and never a date selector or direct item update + --- + duration_ms: 51.281458 + type: 'test' + ... +# Subtest: native transport selection rechecks a filter even after preflight and never falls back to a different ID +ok 99 - native transport selection rechecks a filter even after preflight and never falls back to a different ID + --- + duration_ms: 6.887292 + type: 'test' + ... +# Subtest: vehicle month/day dates pass parsed contracts and resolve to a full-year execution snapshot +ok 100 - vehicle month/day dates pass parsed contracts and resolve to a full-year execution snapshot + --- + duration_ms: 7.288792 + type: 'test' + ... +# Subtest: transport month/day dates pass parsed contracts and resolve to a full-year execution snapshot +ok 101 - transport month/day dates pass parsed contracts and resolve to a full-year execution snapshot + --- + duration_ms: 2.195 + type: 'test' + ... +# Subtest: other month/day dates pass parsed contracts and resolve to a full-year execution snapshot +ok 102 - other month/day dates pass parsed contracts and resolve to a full-year execution snapshot + --- + duration_ms: 6.327333 + type: 'test' + ... +# Subtest: parsed date-selector validators agree on invalid calendars while execution still requires a full year +ok 103 - parsed date-selector validators agree on invalid calendars while execution still requires a full year + --- + duration_ms: 22.783208 + type: 'test' + ... +# Subtest: ambiguous original dates are surfaced in the route failure message without native ids +ok 104 - ambiguous original dates are surfaced in the route failure message without native ids + --- + duration_ms: 1.053125 + type: 'test' + ... +# Subtest: fresh replay output creates missing parents and refuses to overwrite a run +ok 105 - fresh replay output creates missing parents and refuses to overwrite a run + --- + duration_ms: 100.393375 + type: 'test' + ... +# Subtest: template generates the four creation routes and all contracts agree +ok 106 - template generates the four creation routes and all contracts agree + --- + duration_ms: 1234.458416 + type: 'test' + ... +# Subtest: dedicated third-run shared batch probe carries customer and 15+1 facts through all three contracts +ok 107 - dedicated third-run shared batch probe carries customer and 15+1 facts through all three contracts + --- + duration_ms: 735.713708 + type: 'test' + ... +# Subtest: resolved state generates passenger, supported narrow updates, arrangement and source phases +ok 108 - resolved state generates passenger, supported narrow updates, arrangement and source phases + --- + duration_ms: 423.461417 + type: 'test' + ... +# Subtest: clear, transition and delete phases require progressively stronger evidence gates +ok 109 - clear, transition and delete phases require progressively stronger evidence gates + --- + duration_ms: 413.587708 + type: 'test' + ... +# Subtest: dynamic C04 children are deleted before every parent and every parent needs an explicit child-free proof +ok 110 - dynamic C04 children are deleted before every parent and every parent needs an explicit child-free proof + --- + duration_ms: 20.649083 + type: 'test' + ... +# Subtest: state validation rejects wrong owners, non-September targets and premature delete authorization +ok 111 - state validation rejects wrong owners, non-September targets and premature delete authorization + --- + duration_ms: 1.678791 + type: 'test' + ... +# Subtest: schema permits ERP-derived group prefix with an explicit suffix-only order number +ok 112 - schema permits ERP-derived group prefix with an explicit suffix-only order number + --- + duration_ms: 489.277666 + type: 'test' + ... +# Subtest: catalog has exactly 19 ordered operations with adjacent template and example blocks +ok 113 - catalog has exactly 19 ordered operations with adjacent template and example blocks + --- + duration_ms: 4.27625 + type: 'test' + ... +# Subtest: main prompt remains a compact Skill router +ok 114 - main prompt remains a compact Skill router + --- + duration_ms: 1.660125 + type: 'test' + ... +# Subtest: all packaged Skill sources contain only parsing knowledge +ok 115 - all packaged Skill sources contain only parsing knowledge + --- + duration_ms: 1.36875 + type: 'test' + ... +# Subtest: Agent parse Schema accepts business facts and rejects execution metadata +ok 116 - Agent parse Schema accepts business facts and rejects execution metadata + --- + duration_ms: 1250.1075 + type: 'test' + ... +# Subtest: template fields are marked and examples contain only concrete values +ok 117 - template fields are marked and examples contain only concrete values + --- + duration_ms: 14.487959 + type: 'test' + ... +# Subtest: user templates never request ERP internal identifiers and use canonical customer label +ok 118 - user templates never request ERP internal identifiers and use canonical customer label + --- + duration_ms: 0.596708 + type: 'test' + ... +# Subtest: all Agent directives are routed to their Skill contract in the same two-block format +ok 119 - all Agent directives are routed to their Skill contract in the same two-block format + --- + duration_ms: 1.77475 + type: 'test' + ... +# Subtest: shared-child batch stays Program-only while remaining copyable from the operator catalog +ok 120 - shared-child batch stays Program-only while remaining copyable from the operator catalog + --- + duration_ms: 0.709083 + type: 'test' + ... +# Subtest: the four detailed business pages use adjacent template and example blocks +ok 121 - the four detailed business pages use adjacent template and example blocks + --- + duration_ms: 8.279917 + type: 'test' + ... +# Subtest: the two desktop-approved examples remain exactly copyable +ok 122 - the two desktop-approved examples remain exactly copyable + --- + duration_ms: 0.402042 + type: 'test' + ... +# Subtest: every operation preserves its explicit minimum-required field set +ok 123 - every operation preserves its explicit minimum-required field set + --- + duration_ms: 0.45475 + type: 'test' + ... +# Subtest: modification input asks only for targets and export stays minimal +ok 124 - modification input asks only for targets and export stays minimal + --- + duration_ms: 2.327041 + type: 'test' + ... +# Subtest: team-file export uses the business name while keeping the internal action stable +ok 125 - team-file export uses the business name while keeping the internal action stable + --- + duration_ms: 3.907958 + type: 'test' + ... +# Subtest: independent-order updates preserve broad parser targets while arrangement changes stay narrow +ok 126 - independent-order updates preserve broad parser targets while arrangement changes stay narrow + --- + duration_ms: 1.82325 + type: 'test' + ... +# Subtest: deferred items 20 to 22 are absent from current user and Skill input contracts +ok 127 - deferred items 20 to 22 are absent from current user and Skill input contracts + --- + duration_ms: 0.410875 + type: 'test' + ... +# Subtest: new business adaptation template enforces the two-block writing convention +ok 128 - new business adaptation template enforces the two-block writing convention + --- + duration_ms: 0.909625 + type: 'test' + ... +# Subtest: ERP account identity comparison accepts only normalized exact equality +ok 129 - ERP account identity comparison accepts only normalized exact equality + --- + duration_ms: 1.358958 + type: 'test' + ... +# Subtest: ERP account identity comparison rejects substrings and blank identities +ok 130 - ERP account identity comparison rejects substrings and blank identities + --- + duration_ms: 0.091917 + type: 'test' + ... +# Subtest: ERP session probe reads the unique login label and never matches against whole-page text +ok 131 - ERP session probe reads the unique login label and never matches against whole-page text + --- + duration_ms: 1.841625 + type: 'test' + ... +# Subtest: normal lookup does not wait; business no-match and ambiguous results never retry +ok 132 - normal lookup does not wait; business no-match and ambiguous results never retry + --- + duration_ms: 5.318166 + type: 'test' + ... +# Subtest: removed query frame waits for a stable current document then reinjects and queries once +ok 133 - removed query frame waits for a stable current document then reinjects and queries once + --- + duration_ms: 191.837958 + type: 'test' + ... +# Subtest: frame removal during injection also safely recovers and identifies injection as failed step +ok 134 - frame removal during injection also safely recovers and identifies injection as failed step + --- + duration_ms: 0.880375 + type: 'test' + ... +# Subtest: repeated frame removal stops after two lookups with actual resolution failure stage and no-write evidence +ok 135 - repeated frame removal stops after two lookups with actual resolution failure stage and no-write evidence + --- + duration_ms: 3.584333 + type: 'test' + ... +# Subtest: moving/loading/missing-identity documents cannot pass readiness and the wait is bounded +ok 136 - moving/loading/missing-identity documents cannot pass readiness and the wait is bounded + --- + duration_ms: 11.842 + type: 'test' + ... +# Subtest: permanent errors, closed tabs, and disallowed navigation do not rerun queries +ok 137 - permanent errors, closed tabs, and disallowed navigation do not rerun queries + --- + duration_ms: 167.991166 + type: 'test' + ... +# Subtest: retry requires the same running execution without a prior write boundary; cancellation propagates +ok 138 - retry requires the same running execution without a prior write boundary; cancellation propagates + --- + duration_ms: 47.283041 + type: 'test' + ... +# Subtest: exceptions before route, during route and during preflight retain the actual failure phase +ok 139 - exceptions before route, during route and during preflight retain the actual failure phase + --- + duration_ms: 2.349791 + type: 'test' + ... +# Subtest: frame removal at live submit is uncertain and never retried +ok 140 - frame removal at live submit is uncertain and never retried + --- + duration_ms: 1.125083 + type: 'test' + ... +# Subtest: production control-plane origin is recognized and bridge injection is permitted +ok 141 - production control-plane origin is recognized and bridge injection is permitted + --- + duration_ms: 5.051542 + type: 'test' + ... +# Subtest: ERP session keepalive is scheduled through alarms with a conservative default +ok 142 - ERP session keepalive is scheduled through alarms with a conservative default + --- + duration_ms: 102.144875 + type: 'test' + ... +# Subtest: ERP keepalive is an existing-tab read-only request and never a page reload or task write +ok 143 - ERP keepalive is an existing-tab read-only request and never a page reload or task write + --- + duration_ms: 22.701417 + type: 'test' + ... +# Subtest: ERP exception recovery refreshes the existing ERP tab once, then performs a read-only probe +ok 144 - ERP exception recovery refreshes the existing ERP tab once, then performs a read-only probe + --- + duration_ms: 4.21225 + type: 'test' + ... +# Subtest: an interrupted service worker recovery cannot leave ERP dispatch permanently blocked +ok 145 - an interrupted service worker recovery cannot leave ERP dispatch permanently blocked + --- + duration_ms: 1.942916 + type: 'test' + ... +# Subtest: keepalive setting and last probe state are visible without changing the business operation contract +ok 146 - keepalive setting and last probe state are visible without changing the business operation contract + --- + duration_ms: 36.319042 + type: 'test' + ... +# Subtest: a new ERP execution is accepted exactly once +ok 147 - a new ERP execution is accepted exactly once + --- + duration_ms: 1.725917 + type: 'test' + ... +# Subtest: a different execution id for the same task is blocked +ok 148 - a different execution id for the same task is blocked + --- + duration_ms: 0.075833 + type: 'test' + ... +# Subtest: failures after the write boundary become uncertain +ok 149 - failures after the write boundary become uncertain + --- + duration_ms: 0.080542 + type: 'test' + ... +# Subtest: an explicit network-before-submit block is safe to classify as no ERP write +ok 150 - an explicit network-before-submit block is safe to classify as no ERP write + --- + duration_ms: 0.123958 + type: 'test' + ... +# Subtest: local extension history stays bounded without pruning active or uncertain roots +ok 151 - local extension history stays bounded without pruning active or uncertain roots + --- + duration_ms: 0.466958 + type: 'test' + ... +# Subtest: dispatch and schema accept only create batches with all ordinary per-row fields valid +ok 152 - dispatch and schema accept only create batches with all ordinary per-row fields valid + --- + duration_ms: 815.047667 + type: 'test' + ... +# Subtest: successful rows execute serially without prematurely finalizing the root task +ok 153 - successful rows execute serially without prematurely finalizing the root task + --- + duration_ms: 76.678792 + type: 'test' + ... +# Subtest: prewrite failure stops subsequent rows, retains saved prefix and stays known partial failure +ok 154 - prewrite failure stops subsequent rows, retains saved prefix and stays known partial failure + --- + duration_ms: 2.617458 + type: 'test' + ... +# Subtest: uncertain or missing response stops without retry and preserves both prefix and pending tail +ok 155 - uncertain or missing response stops without retry and preserves both prefix and pending tail + --- + duration_ms: 7.057959 + type: 'test' + ... +# Subtest: read-only reconciliation resolves only the uncertain row and never starts the pending tail +ok 156 - read-only reconciliation resolves only the uncertain row and never starts the pending tail + --- + duration_ms: 9.112541 + type: 'test' + ... +# Subtest: timeout preserves an already-saved child and classifies a missing child as uncertain without replay +ok 157 - timeout preserves an already-saved child and classifies a missing child as uncertain without replay + --- + duration_ms: 2.310625 + type: 'test' + ... +# Subtest: production Docker runtime includes the shared batch evidence validator +ok 158 - production Docker runtime includes the shared batch evidence validator + --- + duration_ms: 0.288167 + type: 'test' + ... +# Subtest: a proven prewrite failure on the first row reports zero writes and leaves the rest untouched +ok 159 - a proven prewrite failure on the first row reports zero writes and leaves the rest untouched + --- + duration_ms: 1.263917 + type: 'test' + ... +# Subtest: hotel create binds the first empty row through preflight, native linkage, POST and fresh requery +ok 160 - hotel create binds the first empty row through preflight, native linkage, POST and fresh requery + --- + duration_ms: 411.944 + type: 'test' + ... +# Subtest: 6/12-room hotel create recovers after delayed sampling, with only one native selection and POST +ok 161 - 6/12-room hotel create recovers after delayed sampling, with only one native selection and POST + --- + duration_ms: 280.939709 + type: 'test' + ... +# Subtest: unstable or persistently delayed hotel linkage records diagnostics and never saves +ok 162 - unstable or persistently delayed hotel linkage records diagnostics and never saves + --- + duration_ms: 31.054084 + type: 'test' + ... +# Subtest: hotel create matches the new record after ERP sorts rows and uses persisted evidence on read-only reconciliation +ok 163 - hotel create matches the new record after ERP sorts rows and uses persisted evidence on read-only reconciliation + --- + duration_ms: 26.365708 + type: 'test' + ... +# Subtest: no free row, unsaved drafts, financial state and incomplete rows fail before any POST +ok 164 - no free row, unsaved drafts, financial state and incomplete rows fail before any POST + --- + duration_ms: 228.905167 + type: 'test' + ... +# Subtest: a caller cannot force a create target, and a changed preflight cannot silently choose another row +ok 165 - a caller cannot force a create target, and a changed preflight cannot silently choose another row + --- + duration_ms: 49.673667 + type: 'test' + ... +# Subtest: native callbacks cannot modify other rows, selected record id, or financial state +ok 166 - native callbacks cannot modify other rows, selected record id, or financial state + --- + duration_ms: 184.881417 + type: 'test' + ... +# Subtest: requery cannot report an old row, duplicate new rows, changed existing rows or wrong quantities as success +ok 167 - requery cannot report an old row, duplicate new rows, changed existing rows or wrong quantities as success + --- + duration_ms: 57.5295 + type: 'test' + ... +# Subtest: hotel create accepts a complete two-row 76-field form after one stable second +ok 168 - hotel create accepts a complete two-row 76-field form after one stable second + --- + duration_ms: 50.327709 + type: 'test' + ... +# Subtest: hotel create fails closed without a form +ok 169 - hotel create fails closed without a form + --- + duration_ms: 4.399709 + type: 'test' + ... +# Subtest: hotel create accepts complete variable-size forms without a serialized count threshold +ok 170 - hotel create accepts complete variable-size forms without a serialized count threshold + --- + duration_ms: 27.192625 + type: 'test' + ... +# Subtest: hotel create rejects every missing required field even with more than 195 fields +ok 171 - hotel create rejects every missing required field even with more than 195 fields + --- + duration_ms: 971.559333 + type: 'test' + ... +# Subtest: hotel create waits for visible loading to disappear and then stabilize +ok 172 - hotel create waits for visible loading to disappear and then stabilize + --- + duration_ms: 4.486625 + type: 'test' + ... +# Subtest: hotel create requires a full observed stable second, including the final timeout sample +ok 173 - hotel create requires a full observed stable second, including the final timeout sample + --- + duration_ms: 15.059834 + type: 'test' + ... +# Subtest: hotel update/clear and unrelated lifecycle actions retain their current count gates +ok 174 - hotel update/clear and unrelated lifecycle actions retain their current count gates + --- + duration_ms: 50.1475 + type: 'test' + ... +# Subtest: 76-field readiness still leaves occupied slots and payment/audit state blocked +ok 175 - 76-field readiness still leaves occupied slots and payment/audit state blocked + --- + duration_ms: 16.6995 + type: 'test' + ... +# Subtest: full hotel preflight preserves identity, resource and ownership gates after structural readiness +ok 176 - full hotel preflight preserves identity, resource and ownership gates after structural readiness + --- + duration_ms: 59.887833 + type: 'test' + ... +# Subtest: hotel create rejects missing controls in later rows even on a large form +ok 177 - hotel create rejects missing controls in later rows even on a large form + --- + duration_ms: 723.229959 + type: 'test' + ... +# Subtest: hotel create rejects invalid row markers, omitted rows and gaps +ok 178 - hotel create rejects invalid row markers, omitted rows and gaps + --- + duration_ms: 94.277208 + type: 'test' + ... +# Subtest: hotel create rejects disabled, duplicate and id-only required data controls +ok 179 - hotel create rejects disabled, duplicate and id-only required data controls + --- + duration_ms: 45.253583 + type: 'test' + ... +# Subtest: hotel create rejects detached or replaced forms and unfinished documents +ok 180 - hotel create rejects detached or replaced forms and unfinished documents + --- + duration_ms: 31.592625 + type: 'test' + ... +# Subtest: same-count control replacement and select-option changes restart stability +ok 181 - same-count control replacement and select-option changes restart stability + --- + duration_ms: 102.141625 + type: 'test' + ... +# Subtest: native pending ajax blocks hotel readiness even without a visible overlay +ok 182 - native pending ajax blocks hotel readiness even without a visible overlay + --- + duration_ms: 4.363083 + type: 'test' + ... +# Subtest: hotel replacement resolves a dated existing row and uses native linked selection on that row only +ok 183 - hotel replacement resolves a dated existing row and uses native linked selection on that row only + --- + duration_ms: 133.961125 + type: 'test' + ... +# Subtest: fresh ERP MAIN world resolves the reported 2027 hotel update using the actual injection list +ok 184 - fresh ERP MAIN world resolves the reported 2027 hotel update using the actual injection list + --- + duration_ms: 7.550458 + type: 'test' + ... +# Subtest: missing page date dependency is diagnosed before matching and never reported as zero ERP records +ok 185 - missing page date dependency is diagnosed before matching and never reported as zero ERP records + --- + duration_ms: 11.894791 + type: 'test' + ... +# Subtest: hotel replacement stops on ambiguous or missing dated rows before selecting a hotel +ok 186 - hotel replacement stops on ambiguous or missing dated rows before selecting a hotel + --- + duration_ms: 3.641125 + type: 'test' + ... +# Subtest: hotel replacement blocks stale row identity, payment, audit and confirmed status +ok 187 - hotel replacement blocks stale row identity, payment, audit and confirmed status + --- + duration_ms: 12.254833 + type: 'test' + ... +# Subtest: hotel replacement rejects native callbacks that change another row or change the record id +ok 188 - hotel replacement rejects native callbacks that change another row or change the record id + --- + duration_ms: 82.273708 + type: 'test' + ... +# Subtest: month/day hotel selector freezes the unique actual ERP year and preserves native dates on replacement +ok 189 - month/day hotel selector freezes the unique actual ERP year and preserves native dates on replacement + --- + duration_ms: 64.330708 + type: 'test' + ... +# Subtest: cross-year duplicate month/day rows remain ambiguous and report original dates before any mutation +ok 190 - cross-year duplicate month/day rows remain ambiguous and report original dates before any mutation + --- + duration_ms: 7.0125 + type: 'test' + ... +# Subtest: mixed TWN/DBL updates serialize both fields, preserve other rooms and verify a fresh GET +ok 191 - mixed TWN/DBL updates serialize both fields, preserve other rooms and verify a fresh GET + --- + duration_ms: 75.474792 + type: 'test' + ... +# Subtest: DBL zero is an explicit reset and identical room targets never resubmit +ok 192 - DBL zero is an explicit reset and identical room targets never resubmit + --- + duration_ms: 12.958792 + type: 'test' + ... +# Subtest: a successful POST cannot hide a missing or stale DBL value or total in fresh ERP data +ok 193 - a successful POST cannot hide a missing or stale DBL value or total in fresh ERP data + --- + duration_ms: 88.128917 + type: 'test' + ... +# Subtest: missing controls, invalid existing rooms and incorrect native totals stop before saving +ok 194 - missing controls, invalid existing rooms and incorrect native totals stop before saving + --- + duration_ms: 27.639958 + type: 'test' + ... +# Subtest: invalid DBL targets and unimplemented fields block the whole mixed operation +ok 195 - invalid DBL targets and unimplemented fields block the whole mixed operation + --- + duration_ms: 18.343875 + type: 'test' + ... +# Subtest: read-only reconciliation verifies the requested DBL field and never posts +ok 196 - read-only reconciliation verifies the requested DBL field and never posts + --- + duration_ms: 3.181041 + type: 'test' + ... +# Subtest: fixture uses the exact ERP TSV headers and 16 synthetic rows +ok 197 - fixture uses the exact ERP TSV headers and 16 synthetic rows + --- + duration_ms: 14.741125 + type: 'test' + ... +# Subtest: extension execution guard exposes the two queue maintenance deadlines +ok 198 - extension execution guard exposes the two queue maintenance deadlines + --- + duration_ms: 1.085958 + type: 'test' + ... +# Subtest: shared-child adapter waits for the async parent row and cross-checks the exact tid +ok 199 - shared-child adapter waits for the async parent row and cross-checks the exact tid + --- + duration_ms: 3.263125 + type: 'test' + ... +# Subtest: team-order customer restore accepts exact final values and rejects any protected-field drift +ok 200 - team-order customer restore accepts exact final values and rejects any protected-field drift + --- + duration_ms: 8.474167 + type: 'test' + ... +# Subtest: team-order protected-field drift matches the 0.5.156 core-field scope +ok 201 - team-order protected-field drift matches the 0.5.156 core-field scope + --- + duration_ms: 6.309583 + type: 'test' + ... +# Subtest: team-order live prewrite blocker exposes only changed protected field names +ok 202 - team-order live prewrite blocker exposes only changed protected field names + --- + duration_ms: 277.394708 + type: 'test' + ... +# Subtest: team-order live gate allows numbered receivable drift like 0.5.156 +ok 203 - team-order live gate allows numbered receivable drift like 0.5.156 + --- + duration_ms: 9.287958 + type: 'test' + ... +# Subtest: split-parent entry waits adaptively for the current iframe add button before clicking +ok 204 - split-parent entry waits adaptively for the current iframe add button before clicking + --- + duration_ms: 2.287458 + type: 'test' + ... +# Subtest: split-parent post-write verification retries read-only and falls back after any missed response group +ok 205 - split-parent post-write verification retries read-only and falls back after any missed response group + --- + duration_ms: 3.172791 + type: 'test' + ... +# Subtest: identifier-only native list searches receive a calendar-safe plus/minus one-year window +ok 206 - identifier-only native list searches receive a calendar-safe plus/minus one-year window + --- + duration_ms: 16.176292 + type: 'test' + ... +# Subtest: shared-child batch resolver expands the inclusive range and hard-matches concrete parent entry rows by product +ok 207 - shared-child batch resolver expands the inclusive range and hard-matches concrete parent entry rows by product + --- + duration_ms: 8.425833 + type: 'test' + ... +# Subtest: business lookup uses customer/date as the native primary lookup and keeps optional facts supplemental +ok 208 - business lookup uses customer/date as the native primary lookup and keeps optional facts supplemental + --- + duration_ms: 1.928042 + type: 'test' + ... +# Subtest: restore lookup explicitly visits the canceled native status tab +ok 209 - restore lookup explicitly visits the canceled native status tab + --- + duration_ms: 3.512709 + type: 'test' + ... +# Subtest: resolved cancellation uses verified list routes and derives one exact current status +ok 210 - resolved cancellation uses verified list routes and derives one exact current status + --- + duration_ms: 3.100208 + type: 'test' + ... +# Subtest: exact lifecycle edit identity accepts authoritative form refs and query-key variants +ok 211 - exact lifecycle edit identity accepts authoritative form refs and query-key variants + --- + duration_ms: 4.36375 + type: 'test' + ... +# Subtest: route preparation binds one opaque token only to the exact selected lifecycle document +ok 212 - route preparation binds one opaque token only to the exact selected lifecycle document + --- + duration_ms: 6.787834 + type: 'test' + ... +# Subtest: shared-child leader uses the ERP transfer SelectBox, not the mother-plan filter +ok 213 - shared-child leader uses the ERP transfer SelectBox, not the mother-plan filter + --- + duration_ms: 14.376 + type: 'test' + ... +# Subtest: team arrangement list search accepts the native PrintGridLists request +ok 214 - team arrangement list search accepts the native PrintGridLists request + --- + duration_ms: 8.789208 + type: 'test' + ... +# Subtest: guide arrangement waits for async guide linkage and coordinator lookup hydration +ok 215 - guide arrangement waits for async guide linkage and coordinator lookup hydration + --- + duration_ms: 4.582791 + type: 'test' + ... +# Subtest: guide arrangement derives daoguan from the selected guide candidate, not the current operator +ok 216 - guide arrangement derives daoguan from the selected guide candidate, not the current operator + --- + duration_ms: 2.23825 + type: 'test' + ... +# Subtest: guide arrangement blocks when the selected guide has no linked daoguan +ok 217 - guide arrangement blocks when the selected guide has no linked daoguan + --- + duration_ms: 2.614583 + type: 'test' + ... +# Subtest: shared-child passenger lookup keeps a visible customer result when ERP nests child rows +ok 218 - shared-child passenger lookup keeps a visible customer result when ERP nests child rows + --- + duration_ms: 5.488834 + type: 'test' + ... +# Subtest: independent lookup keeps ERP rows whose update action is attached to the row +ok 219 - independent lookup keeps ERP rows whose update action is attached to the row + --- + duration_ms: 5.808084 + type: 'test' + ... +# Subtest: customer lifecycle lookup matches non-contiguous Chinese keyword tokens +ok 220 - customer lifecycle lookup matches non-contiguous Chinese keyword tokens + --- + duration_ms: 3.699583 + type: 'test' + ... +# Subtest: shared-child passenger lookup does not attach a parent customer to every nested child +ok 221 - shared-child passenger lookup does not attach a parent customer to every nested child + --- + duration_ms: 3.713917 + type: 'test' + ... +# Subtest: optional lookup facts narrow visible candidates without hard-filtering sparse rows +ok 222 - optional lookup facts narrow visible candidates without hard-filtering sparse rows + --- + duration_ms: 4.191625 + type: 'test' + ... +# Subtest: extension reload reconnects the current platform port and missing ERP identifiers return an actionable error +ok 223 - extension reload reconnects the current platform port and missing ERP identifiers return an actionable error + --- + duration_ms: 2.075042 + type: 'test' + ... +# Subtest: passenger lifecycle prepares the exact native import route before adapter preflight +ok 224 - passenger lifecycle prepares the exact native import route before adapter preflight + --- + duration_ms: 7.410833 + type: 'test' + ... +# Subtest: passenger rows normalize to canonical TSV and treat current ERP rows as an initial baseline, not an import limit +ok 225 - passenger rows normalize to canonical TSV and treat current ERP rows as an initial baseline, not an import limit + --- + duration_ms: 4.262167 + type: 'test' + ... +# Subtest: passenger merge fills explicit blank rows, preserves others, and protects occupied rows +ok 226 - passenger merge fills explicit blank rows, preserves others, and protects occupied rows + --- + duration_ms: 7.919791 + type: 'test' + ... +# Subtest: passenger adapter mirrors only native persisted fields and reports implicit passport semantics +ok 227 - passenger adapter mirrors only native persisted fields and reports implicit passport semantics + --- + duration_ms: 132.807416 + type: 'test' + ... +# Subtest: passenger explicit server success is terminal without a post-save row requery +ok 228 - passenger explicit server success is terminal without a post-save row requery + --- + duration_ms: 5.059334 + type: 'test' + ... +# Subtest: uncertain lifecycle writes can only converge through a read-only plugin requery +ok 229 - uncertain lifecycle writes can only converge through a read-only plugin requery + --- + duration_ms: 5.347 + type: 'test' + ... +# Subtest: ERP business-rule rejections return the native message and stay out of reconciliation +ok 230 - ERP business-rule rejections return the native message and stay out of reconciliation + --- + duration_ms: 2.288333 + type: 'test' + ... +# Subtest: childless shared plans receive a precise hotel-arrangement ERP blocker +ok 231 - childless shared plans receive a precise hotel-arrangement ERP blocker + --- + duration_ms: 5.827917 + type: 'test' + ... +# Subtest: passenger native row capacity failures receive a precise business-facing ERP limit message +ok 232 - passenger native row capacity failures receive a precise business-facing ERP limit message + --- + duration_ms: 4.176458 + type: 'test' + ... +# Subtest: lifecycle execution keeps the MV3 worker alive until the durable task settles +ok 233 - lifecycle execution keeps the MV3 worker alive until the durable task settles + --- + duration_ms: 5.982542 + type: 'test' + ... +# Subtest: extension 0.5.190 keeps reload dormant unless its durable execution state is fully idle +ok 234 - extension 0.5.190 keeps reload dormant unless its durable execution state is fully idle + --- + duration_ms: 2.420917 + type: 'test' + ... +# Subtest: ERP browser injection is host-permission gated and fail-closed with diagnostics +ok 235 - ERP browser injection is host-permission gated and fail-closed with diagnostics + --- + duration_ms: 2.592208 + type: 'test' + ... +# Subtest: five arrangement actions require exact hidden-id references and action-specific fields +ok 236 - five arrangement actions require exact hidden-id references and action-specific fields + --- + duration_ms: 3.399625 + type: 'test' + ... +# Subtest: arrangement browser safety uses real form completeness and exact business-page candidate tuples +ok 237 - arrangement browser safety uses real form completeness and exact business-page candidate tuples + --- + duration_ms: 2.747667 + type: 'test' + ... +# Subtest: arrangement clear reuses the five native actions but requires an exact current-run row target +ok 238 - arrangement clear reuses the five native actions but requires an exact current-run row target + --- + duration_ms: 5.165666 + type: 'test' + ... +# Subtest: update actions expose validated writes and preserve unvalidated fields for manual review +ok 239 - update actions expose validated writes and preserve unvalidated fields for manual review + --- + duration_ms: 5.969959 + type: 'test' + ... +# Subtest: update adapters stop target-equals-current operations before native submit +ok 240 - update adapters stop target-equals-current operations before native submit + --- + duration_ms: 7.041417 + type: 'test' + ... +# Subtest: receivable fixture is restricted to the verified shared-child 0.01 add/clear contract +ok 241 - receivable fixture is restricted to the verified shared-child 0.01 add/clear contract + --- + duration_ms: 3.081625 + type: 'test' + ... +# Subtest: cancel requires cleared receivables and arrangements +ok 242 - cancel requires cleared receivables and arrangements + --- + duration_ms: 0.804125 + type: 'test' + ... +# Subtest: list restore is disabled and shared-plan edit restore requires 收客中 +ok 243 - list restore is disabled and shared-plan edit restore requires 收客中 + --- + duration_ms: 0.629875 + type: 'test' + ... +# Subtest: restore route reports no-op status before the ERP write gate +ok 244 - restore route reports no-op status before the ERP write gate + --- + duration_ms: 4.382084 + type: 'test' + ... +# Subtest: edit-mode transitions select one native radio option without mutating its value +ok 245 - edit-mode transitions select one native radio option without mutating its value + --- + duration_ms: 2.766875 + type: 'test' + ... +# Subtest: live write uses target September dates and does not depend on the current calendar month +ok 246 - live write uses target September dates and does not depend on the current calendar month + --- + duration_ms: 1.479959 + type: 'test' + ... +# Subtest: delete guard distinguishes independent, shared child, and shared parent routes +ok 247 - delete guard distinguishes independent, shared child, and shared parent routes + --- + duration_ms: 3.755708 + type: 'test' + ... +# Subtest: lifecycle ownership, transition boundary, and duplicate update rules fail closed +ok 248 - lifecycle ownership, transition boundary, and duplicate update rules fail closed + --- + duration_ms: 0.41625 + type: 'test' + ... +# Subtest: external parser validator accepts recognized lifecycle targets for later capability review +ok 249 - external parser validator accepts recognized lifecycle targets for later capability review + --- + duration_ms: 0.124708 + type: 'test' + ... +# Subtest: planner and external validator share the dedicated multi-date split-order probe gate +ok 250 - planner and external validator share the dedicated multi-date split-order probe gate + --- + duration_ms: 0.689083 + type: 'test' + ... +# Subtest: platform runner independently blocks a tampered multi-date split-order probe before browser access +ok 251 - platform runner independently blocks a tampered multi-date split-order probe before browser access + --- + duration_ms: 182.046042 + type: 'test' + ... +# Subtest: confirmation export is backend-attached and route-aware for child and narrow shared-parent visitor sources +ok 252 - confirmation export is backend-attached and route-aware for child and narrow shared-parent visitor sources + --- + duration_ms: 0.802542 + type: 'test' + ... +# Subtest: confirmation export keeps shared-child source lookup independent of lifecycle status +ok 253 - confirmation export keeps shared-child source lookup independent of lifecycle status + --- + duration_ms: 4.288 + type: 'test' + ... +# Subtest: unified lifecycle evidence requires explicit response and matching requery +ok 254 - unified lifecycle evidence requires explicit response and matching requery + --- + duration_ms: 0.4085 + type: 'test' + ... +# Subtest: schema and browser adapters contain the v2 safety fields and no confirm override +ok 255 - schema and browser adapters contain the v2 safety fields and no confirm override + --- + duration_ms: 21.310292 + type: 'test' + ... +# Subtest: JSON Schema compiles and separates production ERP resolution from historical test context +ok 256 - JSON Schema compiles and separates production ERP resolution from historical test context + --- + duration_ms: 675.745291 + type: 'test' + ... +# Subtest: ready native list searches once and retains exact request evidence +ok 257 - ready native list searches once and retains exact request evidence + --- + duration_ms: 69.981042 + type: 'test' + ... +# Subtest: a loading same-path Document replacement is reacquired before searching +ok 258 - a loading same-path Document replacement is reacquired before searching + --- + duration_ms: 1.371583 + type: 'test' + ... +# Subtest: replaced same-path document can finish loading later without using its predecessor +ok 259 - replaced same-path document can finish loading later without using its predecessor + --- + duration_ms: 0.818083 + type: 'test' + ... +# Subtest: native readiness stops without searching: unfinished page +ok 260 - native readiness stops without searching: unfinished page + --- + duration_ms: 1.213959 + type: 'test' + ... +# Subtest: native readiness stops without searching: missing loader +ok 261 - native readiness stops without searching: missing loader + --- + duration_ms: 13.635833 + type: 'test' + ... +# Subtest: native readiness stops without searching: disabled button +ok 262 - native readiness stops without searching: disabled button + --- + duration_ms: 16.595708 + type: 'test' + ... +# Subtest: native readiness stops without searching: detached button +ok 263 - native readiness stops without searching: detached button + --- + duration_ms: 48.38975 + type: 'test' + ... +# Subtest: native readiness stops without searching: wrong page +ok 264 - native readiness stops without searching: wrong page + --- + duration_ms: 1.176125 + type: 'test' + ... +# Subtest: native readiness stops without searching: missing document +ok 265 - native readiness stops without searching: missing document + --- + duration_ms: 32.581375 + type: 'test' + ... +# Subtest: native readiness stops without searching: removed frame +ok 266 - native readiness stops without searching: removed frame + --- + duration_ms: 1.190708 + type: 'test' + ... +# Subtest: native readiness stops without searching: inaccessible frame +ok 267 - native readiness stops without searching: inaccessible frame + --- + duration_ms: 12.496417 + type: 'test' + ... +# Subtest: document replacement during prior Ajax wait fails closed without searching either page +ok 268 - document replacement during prior Ajax wait fails closed without searching either page + --- + duration_ms: 2.229208 + type: 'test' + ... +# Subtest: missing button is diagnosed separately from a ready document and loader +ok 269 - missing button is diagnosed separately from a ready document and loader + --- + duration_ms: 10.262208 + type: 'test' + ... +# Subtest: a page change during the native search is rejected instead of using stale result rows +ok 270 - a page change during the native search is rejected instead of using stale result rows + --- + duration_ms: 0.665875 + type: 'test' + ... +# Subtest: the real resolution failure carries explicit no-write proof and value-free readiness details +ok 271 - the real resolution failure carries explicit no-write proof and value-free readiness details + --- + duration_ms: 6.650459 + type: 'test' + ... +# Subtest: the worker describes readiness failures accurately while preserving not-found errors +ok 272 - the worker describes readiness failures accurately while preserving not-found errors + --- + duration_ms: 1.371709 + type: 'test' + ... +# Subtest: team-single remains the guarded production team-order path +ok 273 - team-single remains the guarded production team-order path + --- + duration_ms: 1.658666 + type: 'test' + ... +# Subtest: accepts product/customer source-region context without semantic validation +ok 274 - accepts product/customer source-region context without semantic validation + --- + duration_ms: 0.290375 + type: 'test' + ... +# Subtest: accepts a team-single operation without source-region metadata +ok 275 - accepts a team-single operation without source-region metadata + --- + duration_ms: 0.091667 + type: 'test' + ... +# Subtest: formal team-batch uses the validated native batch submit path +ok 276 - formal team-batch uses the validated native batch submit path + --- + duration_ms: 0.917667 + type: 'test' + ... +# Subtest: order updates require an identifier and only run a no-write browser preview +ok 277 - order updates require an identifier and only run a no-write browser preview + --- + duration_ms: 0.220916 + type: 'test' + ... +# Subtest: untyped parent confirmation export is blocked before any browser operation +ok 278 - untyped parent confirmation export is blocked before any browser operation + --- + duration_ms: 0.161583 + type: 'test' + ... +# Subtest: confirmation export accepts canonical types and blocks unknown artifacts +ok 279 - confirmation export accepts canonical types and blocks unknown artifacts + --- + duration_ms: 0.452375 + type: 'test' + ... +# Subtest: unknown operations fail closed +ok 280 - unknown operations fail closed + --- + duration_ms: 0.061167 + type: 'test' + ... +# Subtest: rejects legacy create envelopes instead of normalizing them +ok 281 - rejects legacy create envelopes instead of normalizing them + --- + duration_ms: 0.271458 + type: 'test' + ... +# Subtest: routes the canonical split-parent operation without child information into the guarded production browser adapter +ok 282 - routes the canonical split-parent operation without child information into the guarded production browser adapter + --- + duration_ms: 2.366917 + type: 'test' + ... +# Subtest: accepts a canonical split-child parent reference without fabricating a product +ok 283 - accepts a canonical split-child parent reference without fabricating a product + --- + duration_ms: 1.261416 + type: 'test' + ... +# Subtest: shared-child leader lookup survives the strict execution gate +ok 284 - shared-child leader lookup survives the strict execution gate + --- + duration_ms: 1.542375 + type: 'test' + ... +# Subtest: shared-child batch dispatch is Program-only, resolves all parents, and enters the guarded batch adapter +ok 285 - shared-child batch dispatch is Program-only, resolves all parents, and enters the guarded batch adapter + --- + duration_ms: 0.62975 + type: 'test' + ... +# Subtest: shared-child batch execution rejects duplicate or out-of-range resolved parents +ok 286 - shared-child batch execution rejects duplicate or out-of-range resolved parents + --- + duration_ms: 0.119208 + type: 'test' + ... +# Subtest: blocks a marked split-child parent when the child remark loses the test marker +ok 287 - blocks a marked split-child parent when the child remark loses the test marker + --- + duration_ms: 0.08625 + type: 'test' + ... +# Subtest: routes a traveler-only handoff update into the traveler import business action +ok 288 - routes a traveler-only handoff update into the traveler import business action + --- + duration_ms: 0.875583 + type: 'test' + ... +# Subtest: applies room and remark updates to a browser-read snapshot without writing ERP +ok 289 - applies room and remark updates to a browser-read snapshot without writing ERP + --- + duration_ms: 0.363084 + type: 'test' + ... +# Subtest: builds export-only source paths and never emits a save action +ok 290 - builds export-only source paths and never emits a save action + --- + duration_ms: 0.237125 + type: 'test' + ... +# Subtest: accepts the production team-single output without manual prices or staff fields +ok 291 - accepts the production team-single output without manual prices or staff fields + --- + duration_ms: 0.0715 + type: 'test' + ... +# Subtest: requires the booking customer for independent-team single orders +ok 292 - requires the booking customer for independent-team single orders + --- + duration_ms: 0.0745 + type: 'test' + ... +# Subtest: blocks scalar named references, unsupported room keys, and manual staff/price overrides +ok 293 - blocks scalar named references, unsupported room keys, and manual staff/price overrides + --- + duration_ms: 0.119292 + type: 'test' + ... +# Subtest: accepts the corrected formal native batch contract used by the reported task +ok 294 - accepts the corrected formal native batch contract used by the reported task + --- + duration_ms: 0.065416 + type: 'test' + ... +# Subtest: keeps the formal native batch operation free of test markers +ok 295 - keeps the formal native batch operation free of test markers + --- + duration_ms: 0.057583 + type: 'test' + ... +# Subtest: accepts a split-parent recurrence without child customer or passenger fields +ok 296 - accepts a split-parent recurrence without child customer or passenger fields + --- + duration_ms: 0.056084 + type: 'test' + ... +# Subtest: accepts independently supplied optional split-parent customer and passenger fields +ok 297 - accepts independently supplied optional split-parent customer and passenger fields + --- + duration_ms: 0.071209 + type: 'test' + ... +# Subtest: accepts either optional split-parent field without requiring the other +ok 298 - accepts either optional split-parent field without requiring the other + --- + duration_ms: 0.058667 + type: 'test' + ... +# Subtest: blocks multi-date split facts outside the dedicated live test probe +ok 299 - blocks multi-date split facts outside the dedicated live test probe + --- + duration_ms: 0.066042 + type: 'test' + ... +# Subtest: allows multi-date split facts only inside the dedicated TEST-202609 probe +ok 300 - allows multi-date split facts only inside the dedicated TEST-202609 probe + --- + duration_ms: 0.125375 + type: 'test' + ... +# Subtest: blocks invalid values for an optional split-parent field +ok 301 - blocks invalid values for an optional split-parent field + --- + duration_ms: 0.049458 + type: 'test' + ... +# Subtest: normalizes legacy flat split-parent fields into the canonical optional envelope +ok 302 - normalizes legacy flat split-parent fields into the canonical optional envelope + --- + duration_ms: 0.148042 + type: 'test' + ... +# Subtest: accepts canonical confirmation types and snake-case recovery flags +ok 303 - accepts canonical confirmation types and snake-case recovery flags + --- + duration_ms: 0.07725 + type: 'test' + ... +# Subtest: accepts canonical confirmation export types without alias normalization +ok 304 - accepts canonical confirmation export types without alias normalization + --- + duration_ms: 0.092375 + type: 'test' + ... +# Subtest: shared-plan visitor export uses tid only and rejects child-scoped or mixed sources +ok 305 - shared-plan visitor export uses tid only and rejects child-scoped or mixed sources + --- + duration_ms: 0.23125 + type: 'test' + ... +# Subtest: rejects requested confirmation formats because platform processing is separate +ok 306 - rejects requested confirmation formats because platform processing is separate + --- + duration_ms: 0.088541 + type: 'test' + ... +# Subtest: rejects unverified confirmation language variants +ok 307 - rejects unverified confirmation language variants + --- + duration_ms: 0.108167 + type: 'test' + ... +# Subtest: dispatch gate accepts parse-state lifecycle input but strict gate waits for ERP refs +ok 308 - dispatch gate accepts parse-state lifecycle input but strict gate waits for ERP refs + --- + duration_ms: 0.233625 + type: 'test' + ... +# Subtest: dispatch gate accepts business-field lookup without changing action routing +ok 309 - dispatch gate accepts business-field lookup without changing action routing + --- + duration_ms: 1.016084 + type: 'test' + ... +# Subtest: strict lifecycle gate accepts only ERP-enriched production operation +ok 310 - strict lifecycle gate accepts only ERP-enriched production operation + --- + duration_ms: 0.188375 + type: 'test' + ... +# Subtest: execution operation rejects platform task, session, receipt and batch metadata +ok 311 - execution operation rejects platform task, session, receipt and batch metadata + --- + duration_ms: 0.465542 + type: 'test' + ... +# Subtest: dispatch gate keeps internal ids, status defaults, and side-effect policy out of user input +ok 312 - dispatch gate keeps internal ids, status defaults, and side-effect policy out of user input + --- + duration_ms: 0.119417 + type: 'test' + ... +# Subtest: dispatch export needs only a visible identifier and file type +ok 313 - dispatch export needs only a visible identifier and file type + --- + duration_ms: 0.111916 + type: 'test' + ... +# Subtest: dispatch keeps an explicit historical export fixture on its strict test-context path +ok 314 - dispatch keeps an explicit historical export fixture on its strict test-context path + --- + duration_ms: 0.061417 + type: 'test' + ... +# Subtest: strict gate accepts the ERP-enriched production lifecycle matrix without test metadata +ok 315 - strict gate accepts the ERP-enriched production lifecycle matrix without test metadata + --- + duration_ms: 1.962625 + type: 'test' + ... +# Subtest: operation timing closes the previous stage and attributes buffered page work to it +ok 316 - operation timing closes the previous stage and attributes buffered page work to it + --- + duration_ms: 2.251667 + type: 'test' + ... +# Subtest: operation timing survives state loss without exposing absolute timestamps +ok 317 - operation timing survives state loss without exposing absolute timestamps + --- + duration_ms: 0.277792 + type: 'test' + ... +# Subtest: operation timing terminal snapshot is bounded and strips arbitrary values +ok 318 - operation timing terminal snapshot is bounded and strips arbitrary values + --- + duration_ms: 1.064541 + type: 'test' + ... +# Subtest: a later reconciliation continuation does not count the human idle gap +ok 319 - a later reconciliation continuation does not count the human idle gap + --- + duration_ms: 0.652458 + type: 'test' + ... +# Subtest: background timing hook covers every executor through the shared result and page-action boundaries +ok 320 - background timing hook covers every executor through the shared result and page-action boundaries + --- + duration_ms: 12.163083 + type: 'test' + ... +# Subtest: team-order preflight waits on ERP completion events without page-timer polling +ok 321 - team-order preflight waits on ERP completion events without page-timer polling + --- + duration_ms: 3.174542 + type: 'test' + ... +# Subtest: team-order frame discovery targets the exact ERP iframe instead of injecting into every frame +ok 322 - team-order frame discovery targets the exact ERP iframe instead of injecting into every frame + --- + duration_ms: 3.64125 + type: 'test' + ... +# Subtest: team-order frame waits for native customer and staff lookup data before preflight +ok 323 - team-order frame waits for native customer and staff lookup data before preflight + --- + duration_ms: 8.648625 + type: 'test' + ... +# Subtest: team-order preflight waits for the native jQuery Ajax completion event +ok 324 - team-order preflight waits for the native jQuery Ajax completion event + --- + duration_ms: 13.669667 + type: 'test' + ... +# Subtest: extension result changes push immediately while polling remains as a fallback +ok 325 - extension result changes push immediately while polling remains as a fallback + --- + duration_ms: 3.41475 + type: 'test' + ... +# Subtest: platform exposes the sanitized timing breakdown without rendering raw network data +ok 326 - platform exposes the sanitized timing breakdown without rendering raw network data + --- + duration_ms: 181.080708 + type: 'test' + ... +# Subtest: passenger TSV keeps trailing empty columns through server and plugin dispatch +ok 327 - passenger TSV keeps trailing empty columns through server and plugin dispatch + --- + duration_ms: 21.408458 + type: 'test' + ... +# Subtest: passenger TSV still rejects missing or surplus columns and keeps leader validation +ok 328 - passenger TSV still rejects missing or surplus columns and keeps leader validation + --- + duration_ms: 1.008208 + type: 'test' + ... +# Subtest: passenger merge accepts empty final fields without repairing malformed column counts +ok 329 - passenger merge accepts empty final fields without repairing malformed column counts + --- + duration_ms: 61.8855 + type: 'test' + ... +# Subtest: matches the ERP product name and duration as separate fields +ok 330 - matches the ERP product name and duration as separate fields + --- + duration_ms: 11.553042 + type: 'test' + ... +# Subtest: does not confuse a reversed route fragment with the 江西 product +ok 331 - does not confuse a reversed route fragment with the 江西 product + --- + duration_ms: 65.344917 + type: 'test' + ... +# Subtest: matches punctuation-separated Chinese fragments and Chinese duration aliases +ok 332 - matches punctuation-separated Chinese fragments and Chinese duration aliases + --- + duration_ms: 4.007 + type: 'test' + ... +# Subtest: preserves numeric product names while matching a duration suffix +ok 333 - preserves numeric product names while matching a duration suffix + --- + duration_ms: 0.114333 + type: 'test' + ... +# Subtest: uses the complete ERP product name as the first native search query +ok 334 - uses the complete ERP product name as the first native search query + --- + duration_ms: 1.17475 + type: 'test' + ... +# Subtest: removes only a trailing numeric duration from the native name query +ok 335 - removes only a trailing numeric duration from the native name query + --- + duration_ms: 0.144666 + type: 'test' + ... +# Subtest: both S_chanpinming order adapters execute the planned native queries +ok 336 - both S_chanpinming order adapters execute the planned native queries + --- + duration_ms: 1.125125 + type: 'test' + ... +# Subtest: keeps unique-match safety when multiple candidates satisfy a shorthand +ok 337 - keeps unique-match safety when multiple candidates satisfy a shorthand + --- + duration_ms: 0.090959 + type: 'test' + ... +# Subtest: keeps source-region tokens as a customer-only fallback, not a product/customer gate +ok 338 - keeps source-region tokens as a customer-only fallback, not a product/customer gate + --- + duration_ms: 0.96825 + type: 'test' + ... +# Subtest: resolves a non-canonical customer phrase by all source-region tokens only when unique +ok 339 - resolves a non-canonical customer phrase by all source-region tokens only when unique + --- + duration_ms: 0.741125 + type: 'test' + ... +# Subtest: does not guess when source-region keywords match multiple customer masters +ok 340 - does not guess when source-region keywords match multiple customer masters + --- + duration_ms: 0.196917 + type: 'test' + ... +# Subtest: records every C04 parent and concrete child into state and canonical allowlist refs +ok 341 - records every C04 parent and concrete child into state and canonical allowlist refs + --- + duration_ms: 5.958209 + type: 'test' + ... +# Subtest: records parent-only facts for cleanup without declaring batch child creation successful +ok 342 - records parent-only facts for cleanup without declaring batch child creation successful + --- + duration_ms: 0.971125 + type: 'test' + ... +# Subtest: refuses to record partial facts or a mismatched independent run id +ok 343 - refuses to record partial facts or a mismatched independent run id + --- + duration_ms: 0.422333 + type: 'test' + ... +# Subtest: root contains only governed long-lived entries +ok 344 - root contains only governed long-lived entries + --- + duration_ms: 64.983875 + type: 'test' + ... +# Subtest: project docs are the sole active project memory +ok 345 - project docs are the sole active project memory + --- + duration_ms: 32.234542 + type: 'test' + ... +# Subtest: temporary residue and report outputs do not pollute the repository +ok 346 - temporary residue and report outputs do not pollute the repository + --- + duration_ms: 200.777125 + type: 'test' + ... +# Subtest: dist is an exact versioned release set with valid hashes +ok 347 - dist is an exact versioned release set with valid hashes + --- + duration_ms: 15.600875 + type: 'test' + ... +# Subtest: extension and release baselines agree +ok 348 - extension and release baselines agree + --- + duration_ms: 5.896792 + type: 'test' + ... +# Subtest: packaged Skills and extension runtime match their project sources +ok 349 - packaged Skills and extension runtime match their project sources + --- + duration_ms: 571.097583 + type: 'test' + ... +# Subtest: active Markdown links resolve and current docs do not depend on rolling history +ok 350 - active Markdown links resolve and current docs do not depend on rolling history + --- + duration_ms: 362.0565 + type: 'test' + ... +# Subtest: archive README indexes remain navigable +ok 351 - archive README indexes remain navigable + --- + duration_ms: 99.569584 + type: 'test' + ... +# Subtest: single-source and generated-output boundaries remain explicit +ok 352 - single-source and generated-output boundaries remain explicit + --- + duration_ms: 21.588083 + type: 'test' + ... +# Subtest: runtime image includes both LibreOffice document converters +ok 353 - runtime image includes both LibreOffice document converters + --- + duration_ms: 4.980916 + type: 'test' + ... +# Subtest: single-order preflight normalizes both sides and uses the exact ERP spelling through native linkage and intercepted submission +ok 354 - single-order preflight normalizes both sides and uses the exact ERP spelling through native linkage and intercepted submission + --- + duration_ms: 449.646209 + type: 'test' + ... +# Subtest: normalized exact match takes precedence over longer containing product names +ok 355 - normalized exact match takes precedence over longer containing product names + --- + duration_ms: 37.755458 + type: 'test' + ... +# Subtest: a unique shortened keyword still selects the ERP product +ok 356 - a unique shortened keyword still selects the ERP product + --- + duration_ms: 34.236875 + type: 'test' + ... +# Subtest: normalization collisions block even when one raw spelling matches exactly +ok 357 - normalization collisions block even when one raw spelling matches exactly + --- + duration_ms: 16.094666 + type: 'test' + ... +# Subtest: multiple shortened-keyword matches block instead of selecting the first +ok 358 - multiple shortened-keyword matches block instead of selecting the first + --- + duration_ms: 16.129208 + type: 'test' + ... +# Subtest: unmatched product stops with one useful error and preserves punctuation and qualifiers +ok 359 - unmatched product stops with one useful error and preserves punctuation and qualifiers + --- + duration_ms: 39.97225 + type: 'test' + ... +# Subtest: failed lookup transport or customer selection never advances into product linkage +ok 360 - failed lookup transport or customer selection never advances into product linkage + --- + duration_ms: 81.166708 + type: 'test' + ... +# Subtest: successful name resolution still requires complete native linkage before submission +ok 361 - successful name resolution still requires complete native linkage before submission + --- + duration_ms: 62.132584 + type: 'test' + ... +# Subtest: matches a persisted split parent row after an ERP error response +ok 362 - matches a persisted split parent row after an ERP error response + --- + duration_ms: 2.886084 + type: 'test' + ... +# Subtest: does not match a row from a missing requested date +ok 363 - does not match a row from a missing requested date + --- + duration_ms: 0.278833 + type: 'test' + ... +# Subtest: matches a mother-plan row without child customer or passenger criteria +ok 364 - matches a mother-plan row without child customer or passenger criteria + --- + duration_ms: 0.747167 + type: 'test' + ... +# Subtest: prefers a unique customer match when generic row criteria are ambiguous +ok 365 - prefers a unique customer match when generic row criteria are ambiguous + --- + duration_ms: 0.115625 + type: 'test' + ... +# Subtest: extracts the concrete child ddid instead of the parent tid +ok 366 - extracts the concrete child ddid instead of the parent tid + --- + duration_ms: 0.987292 + type: 'test' + ... +# Subtest: extracts only concrete shared children for the exact parent tid +ok 367 - extracts only concrete shared children for the exact parent tid + --- + duration_ms: 1.31875 + type: 'test' + ... +# Subtest: classifies a per-date split-order fact with a concrete child +ok 368 - classifies a per-date split-order fact with a concrete child + --- + duration_ms: 0.749583 + type: 'test' + ... +# Subtest: keeps a parent-only split-order result explicit instead of inventing a child +ok 369 - keeps a parent-only split-order result explicit instead of inventing a child + --- + duration_ms: 0.451583 + type: 'test' + ... +# Subtest: does not call a split-order fact determined without a parent tid or complete child scan +ok 370 - does not call a split-order fact determined without a parent tid or complete child scan + --- + duration_ms: 0.41675 + type: 'test' + ... +# Subtest: builds split-parent requery filters without retaining unrelated list state +ok 371 - builds split-parent requery filters without retaining unrelated list state + --- + duration_ms: 0.442708 + type: 'test' + ... +# Subtest: retries returned group-number queries without resubmitting and converges on visible rows +ok 372 - retries returned group-number queries without resubmitting and converges on visible rows + --- + duration_ms: 0.388875 + type: 'test' + ... +# Subtest: keeps a persistently missing returned group incomplete after the bounded retry budget +ok 373 - keeps a persistently missing returned group incomplete after the bounded retry budget + --- + duration_ms: 0.334375 + type: 'test' + ... +# Subtest: requires date reconciliation when any complete ERP response group remains unverified +ok 374 - requires date reconciliation when any complete ERP response group remains unverified + --- + duration_ms: 0.093792 + type: 'test' + ... +# Subtest: prefers a successful date fallback row over an earlier exact-query miss +ok 375 - prefers a successful date fallback row over an earlier exact-query miss + --- + duration_ms: 0.206291 + type: 'test' + ... +# Subtest: resolves a Chinese shorthand customer across multiple SelectBox columns +ok 376 - resolves a Chinese shorthand customer across multiple SelectBox columns + --- + duration_ms: 18.613709 + type: 'test' + ... +# Subtest: blocks ambiguous fuzzy customer matches instead of choosing the first row +ok 377 - blocks ambiguous fuzzy customer matches instead of choosing the first row + --- + duration_ms: 0.170875 + type: 'test' + ... +# Subtest: maps native weekday and odd/even shortcuts without changing business dates +ok 378 - maps native weekday and odd/even shortcuts without changing business dates + --- + duration_ms: 5.021417 + type: 'test' + ... +# Subtest: uses the same cycle selection plan for explicit dates and ERP shortcuts +ok 379 - uses the same cycle selection plan for explicit dates and ERP shortcuts + --- + duration_ms: 1.651209 + type: 'test' + ... +# Subtest: expands odd and even cycles by calendar day instead of weekday number +ok 380 - expands odd and even cycles by calendar day instead of weekday number + --- + duration_ms: 0.878208 + type: 'test' + ... +# Subtest: normalizes dates for the ERP range and keeps range containment deterministic +ok 381 - normalizes dates for the ERP range and keeps range containment deterministic + --- + duration_ms: 0.150375 + type: 'test' + ... +1..381 +# tests 447 +# suites 3 +# pass 447 +# fail 0 +# cancelled 0 +# skipped 0 +# todo 0 +# duration_ms 7284.735583 diff --git a/archive/evidence/2026-09-24/hotel-batch-01a0858b/repository-clean-tests.txt b/archive/evidence/2026-09-24/hotel-batch-01a0858b/repository-clean-tests.txt new file mode 100644 index 0000000..79e0d79 --- /dev/null +++ b/archive/evidence/2026-09-24/hotel-batch-01a0858b/repository-clean-tests.txt @@ -0,0 +1,70 @@ +TAP version 13 +# Subtest: root contains only governed long-lived entries +ok 1 - root contains only governed long-lived entries + --- + duration_ms: 56.801042 + type: 'test' + ... +# Subtest: project docs are the sole active project memory +ok 2 - project docs are the sole active project memory + --- + duration_ms: 18.133583 + type: 'test' + ... +# Subtest: temporary residue and report outputs do not pollute the repository +ok 3 - temporary residue and report outputs do not pollute the repository + --- + duration_ms: 286.1865 + type: 'test' + ... +# Subtest: dist is an exact versioned release set with valid hashes +ok 4 - dist is an exact versioned release set with valid hashes + --- + duration_ms: 17.027209 + type: 'test' + ... +# Subtest: extension and release baselines agree +ok 5 - extension and release baselines agree + --- + duration_ms: 217.44 + type: 'test' + ... +# Subtest: packaged Skills and extension runtime match their project sources +ok 6 - packaged Skills and extension runtime match their project sources + --- + duration_ms: 1279.993542 + type: 'test' + ... +# Subtest: active Markdown links resolve and current docs do not depend on rolling history +ok 7 - active Markdown links resolve and current docs do not depend on rolling history + --- + duration_ms: 465.839542 + type: 'test' + ... +# Subtest: archive README indexes remain navigable +ok 8 - archive README indexes remain navigable + --- + duration_ms: 375.876166 + type: 'test' + ... +# Subtest: single-source and generated-output boundaries remain explicit +ok 9 - single-source and generated-output boundaries remain explicit + --- + duration_ms: 12.060458 + type: 'test' + ... +# Subtest: runtime image includes both LibreOffice document converters +ok 10 - runtime image includes both LibreOffice document converters + --- + duration_ms: 0.917292 + type: 'test' + ... +1..10 +# tests 10 +# suites 0 +# pass 10 +# fail 0 +# cancelled 0 +# skipped 0 +# todo 0 +# duration_ms 3102.947542 diff --git a/archive/evidence/2026-09-24/hotel-batch-01a0858b/repository-local-residue.txt b/archive/evidence/2026-09-24/hotel-batch-01a0858b/repository-local-residue.txt new file mode 100644 index 0000000..218b7aa --- /dev/null +++ b/archive/evidence/2026-09-24/hotel-batch-01a0858b/repository-local-residue.txt @@ -0,0 +1,132 @@ +TAP version 13 +# Subtest: root contains only governed long-lived entries +not ok 1 - root contains only governed long-lived entries + --- + duration_ms: 5.167042 + type: 'test' + location: '/Users/andy/IdeaProjects/LWLT-AIBOT/tools/repository-hygiene.test.mjs:111:1' + failureType: 'testCodeFailure' + error: |- + Expected values to be strictly deep-equal: + + actual - expected + + + [ + + '.DS_Store', + + '.idea' + + ] + - [] + + code: 'ERR_ASSERTION' + name: 'AssertionError' + expected: + actual: + 0: '.DS_Store' + 1: '.idea' + operator: 'deepStrictEqual' + stack: |- + TestContext. (file:///Users/andy/IdeaProjects/LWLT-AIBOT/tools/repository-hygiene.test.mjs:114:10) + async Test.run (node:internal/test_runner/test:1054:7) + async startSubtestAfterBootstrap (node:internal/test_runner/harness:296:3) + ... +# Subtest: project docs are the sole active project memory +ok 2 - project docs are the sole active project memory + --- + duration_ms: 4.306292 + type: 'test' + ... +# Subtest: temporary residue and report outputs do not pollute the repository +not ok 3 - temporary residue and report outputs do not pollute the repository + --- + duration_ms: 346.891167 + type: 'test' + location: '/Users/andy/IdeaProjects/LWLT-AIBOT/tools/repository-hygiene.test.mjs:149:1' + failureType: 'testCodeFailure' + error: |- + Expected values to be strictly deep-equal: + + actual - expected + + + [ + + '.DS_Store', + + 'archive/evidence/2026-09-14/arrangement-original-date-year-01a0858b/build.log', + + 'archive/evidence/2026-09-14/arrangement-original-date-year-01a0858b/control-plane-tests.log', + + 'archive/evidence/2026-09-14/arrangement-original-date-year-01a0858b/focused-tests.log', + + 'archive/evidence/2026-09-14/arrangement-original-date-year-01a0858b/legacy-tests.log', + + 'archive/evidence/2026-09-14/arrangement-original-date-year-01a0858b/repository-clean.log', + + 'archive/evidence/2026-09-14/arrangement-original-date-year-01a0858b/repository-local-metadata.log', + + 'archive/evidence/2026-09-14/arrangement-original-date-year-01a0858b/runtime-layout-smoke.log', + + 'archive/evidence/2026-09-14/arrangement-original-date-year-01a0858b/typecheck.log', + + 'chrome-extension/.DS_Store' + + ] + - [] + + code: 'ERR_ASSERTION' + name: 'AssertionError' + expected: + actual: + 0: '.DS_Store' + 1: 'archive/evidence/2026-09-14/arrangement-original-date-year-01a0858b/build.log' + 2: 'archive/evidence/2026-09-14/arrangement-original-date-year-01a0858b/control-plane-tests.log' + 3: 'archive/evidence/2026-09-14/arrangement-original-date-year-01a0858b/focused-tests.log' + 4: 'archive/evidence/2026-09-14/arrangement-original-date-year-01a0858b/legacy-tests.log' + 5: 'archive/evidence/2026-09-14/arrangement-original-date-year-01a0858b/repository-clean.log' + 6: 'archive/evidence/2026-09-14/arrangement-original-date-year-01a0858b/repository-local-metadata.log' + 7: 'archive/evidence/2026-09-14/arrangement-original-date-year-01a0858b/runtime-layout-smoke.log' + 8: 'archive/evidence/2026-09-14/arrangement-original-date-year-01a0858b/typecheck.log' + 9: 'chrome-extension/.DS_Store' + operator: 'deepStrictEqual' + stack: |- + TestContext. (file:///Users/andy/IdeaProjects/LWLT-AIBOT/tools/repository-hygiene.test.mjs:153:10) + async Test.run (node:internal/test_runner/test:1054:7) + async Test.processPendingSubtests (node:internal/test_runner/test:744:7) + ... +# Subtest: dist is an exact versioned release set with valid hashes +ok 4 - dist is an exact versioned release set with valid hashes + --- + duration_ms: 113.312 + type: 'test' + ... +# Subtest: extension and release baselines agree +ok 5 - extension and release baselines agree + --- + duration_ms: 17.245958 + type: 'test' + ... +# Subtest: packaged Skills and extension runtime match their project sources +ok 6 - packaged Skills and extension runtime match their project sources + --- + duration_ms: 910.900667 + type: 'test' + ... +# Subtest: active Markdown links resolve and current docs do not depend on rolling history +ok 7 - active Markdown links resolve and current docs do not depend on rolling history + --- + duration_ms: 725.7125 + type: 'test' + ... +# Subtest: archive README indexes remain navigable +ok 8 - archive README indexes remain navigable + --- + duration_ms: 174.902458 + type: 'test' + ... +# Subtest: single-source and generated-output boundaries remain explicit +ok 9 - single-source and generated-output boundaries remain explicit + --- + duration_ms: 5.070916 + type: 'test' + ... +# Subtest: runtime image includes both LibreOffice document converters +ok 10 - runtime image includes both LibreOffice document converters + --- + duration_ms: 0.831625 + type: 'test' + ... +1..10 +# tests 10 +# suites 0 +# pass 8 +# fail 2 +# cancelled 0 +# skipped 0 +# todo 0 +# duration_ms 2544.066833 diff --git a/archive/evidence/2026-09-24/hotel-batch-01a0858b/runtime-layout-smoke.txt b/archive/evidence/2026-09-24/hotel-batch-01a0858b/runtime-layout-smoke.txt new file mode 100644 index 0000000..864fb1f --- /dev/null +++ b/archive/evidence/2026-09-24/hotel-batch-01a0858b/runtime-layout-smoke.txt @@ -0,0 +1 @@ +PASS: compiled control plane imports and batch parser work with only Docker runtime COPY dependencies; no DB or ERP connection. diff --git a/archive/releases/2026-09-24/README.md b/archive/releases/2026-09-24/README.md new file mode 100644 index 0000000..2a25757 --- /dev/null +++ b/archive/releases/2026-09-24/README.md @@ -0,0 +1,5 @@ +# 2026-09-24 发布归档 + +本目录只供历史追溯,不代表当前规则。 + +- [同团批量酒店发布前交付物](hotel-batch-01a0858b/README.md) diff --git a/archive/releases/2026-09-24/hotel-batch-01a0858b/README.md b/archive/releases/2026-09-24/hotel-batch-01a0858b/README.md new file mode 100644 index 0000000..c2e956e --- /dev/null +++ b/archive/releases/2026-09-24/hotel-batch-01a0858b/README.md @@ -0,0 +1,6 @@ +# 批量酒店发布前交付物归档 + +这些文件仅供历史追溯,不代表当前业务规则或发布版本。当前交付物以 [release-manifest.json](../../../../dist/release-manifest.json) 为准。 + +- [Chrome 插件 0.5.189](ltjt-order-assistant-0.5.189.zip):归档前逐字节核对原发布清单 SHA-256,无源码重建或覆盖。 +- [运营指令表 0.5.136](老挝联泰AI指令表-0.5.136.docx):保留新批量示例加入前的完整文档。 diff --git a/archive/releases/2026-09-24/hotel-batch-01a0858b/ltjt-order-assistant-0.5.189.zip b/archive/releases/2026-09-24/hotel-batch-01a0858b/ltjt-order-assistant-0.5.189.zip new file mode 100644 index 0000000..8ec2791 Binary files /dev/null and b/archive/releases/2026-09-24/hotel-batch-01a0858b/ltjt-order-assistant-0.5.189.zip differ diff --git a/dist/老挝联泰AI指令表-0.5.136.docx b/archive/releases/2026-09-24/hotel-batch-01a0858b/老挝联泰AI指令表-0.5.136.docx similarity index 100% rename from dist/老挝联泰AI指令表-0.5.136.docx rename to archive/releases/2026-09-24/hotel-batch-01a0858b/老挝联泰AI指令表-0.5.136.docx diff --git a/chrome-extension/ltjt-order-assistant/README.md b/chrome-extension/ltjt-order-assistant/README.md index aea6758..2918a40 100644 --- a/chrome-extension/ltjt-order-assistant/README.md +++ b/chrome-extension/ltjt-order-assistant/README.md @@ -11,9 +11,14 @@ Chrome Manifest V3 扩展,在用户已登录的 LTJT ERP 页面内执行经过 ## 当前版本 -当前源码版本为 `0.5.189`。版本化 ZIP、文件哈希和 Skill/DOCX 基线见 [`../../dist/release-manifest.json`](../../dist/release-manifest.json)。旧版本实现流水已冻结在 [`../../archive/project-history/2026-08-16/chrome-extension-README.pre-governance.md`](../../archive/project-history/2026-08-16/chrome-extension-README.pre-governance.md)。 +当前源码版本为 `0.5.190`。版本化 ZIP、文件哈希和 Skill/DOCX 基线见 [`../../dist/release-manifest.json`](../../dist/release-manifest.json)。旧版本实现流水已冻结在 [`../../archive/project-history/2026-08-16/chrome-extension-README.pre-governance.md`](../../archive/project-history/2026-08-16/chrome-extension-README.pre-governance.md)。 -0.5.189 当前重点: +0.5.190 当前重点: + +- 同团 2—10 条酒店新增由程序解析为批量信封,插件按输入顺序复用单条原生保存/回查;首次失败或不确定即停止,逐条保存成功前缀、失败/待核验项与未执行项。每条确认成功才进入账号隔离的安排账本和团队进度;整批成功回执包含逐条结果及当前安排。只读回查只核验不确定项,不自动继续剩余安排。禁止跨团、混合动作、批量变更、整批自动重试。 +- 需同步更新控制面、平台和插件 0.5.190,无新增数据库迁移。单条输入及原生资源匹配规则保持;本地合成验证通过,真实 ERP 验收待部署方完成。 + +继续保留 0.5.189 的默认日期空行修复: - 大交通、其他/备案新增支持带团队默认日期的备用空行:仅允许行日期等于页面有效发团日期 chufari(兼容补零格式),数量为空/0/1,且原有资源、备注、费用、付款、审核保护全部通过。其他日期或无法验证默认值仍阻断。只改选中行的目标日期,保留其他行与全局备案信息;酒店/用车/导游和变更/清除规则保持。本地合成回归通过,真实 ERP 验收待部署方完成;需更新插件至 0.5.189、同步平台最低版本并刷新 ERP 页面,无服务端逻辑变更或数据库迁移。 diff --git a/chrome-extension/ltjt-order-assistant/background.js b/chrome-extension/ltjt-order-assistant/background.js index 3746b0a..54ac7a5 100644 --- a/chrome-extension/ltjt-order-assistant/background.js +++ b/chrome-extension/ltjt-order-assistant/background.js @@ -1,7 +1,8 @@ 'use strict'; -importScripts('source-region.js', 'operation-plans.js', 'execution-guard.js', 'lifecycle-result.js', 'operation-timing.js', 'erp-account-identity.js'); +importScripts('source-region.js', 'operation-plans.js', 'execution-guard.js', 'lifecycle-result.js', 'operation-timing.js', 'erp-account-identity.js', 'hotel-batch.js'); +const hotelBatch = self.LTJTHotelBatch; const runningTasks = new Set(); const executionWorkerKeepalives = new Map(); const cancelledTasks = new Set(); @@ -471,7 +472,8 @@ async function cacheResolvedExecutionOperation(taskId, operation) { const saved = await chrome.storage.local.get('businessTasks'); const tasks = saved.businessTasks || {}; if (!tasks[rootId]) return; - tasks[rootId] = { ...tasks[rootId], operation }; + const key = taskId.includes('#hotel-') ? taskId : rootId; + tasks[key] = { ...tasks[rootId], task_id: key, operation }; await chrome.storage.local.set({ businessTasks: tasks }); }); } @@ -714,7 +716,7 @@ async function setResult(taskId, result, { allowCancelled = false } = {}) { const nextStatus = String(nextResult.status || ''); const terminalResult = /^(completed|dry_run|blocked|failed|cancelled|saved_unverified|execution_uncertain|reconciliation_pending)$/; const resolvesReconciliation = /^(?:saved_unverified|execution_uncertain|reconciliation_pending)$/.test(currentStatus) - && nextStatus === 'completed' + && (nextStatus === 'completed' || (nextStatus === 'blocked' && hotelBatch.inspect(nextResult.hotel_batch)?.knownStopped)) && nextResult.reconciliation_resolved === true && nextResult.write_attempted === true; if (terminalResult.test(currentStatus) && currentStatus !== nextStatus && !resolvesReconciliation) { @@ -734,7 +736,8 @@ async function setResult(taskId, result, { allowCancelled = false } = {}) { // happened before the ERP write boundary and must remain retryable as // a normal blocked task rather than being mislabeled uncertain. const knownNoWrite = nextResult.no_erp_write === true && nextResult.write_attempted !== true; - const failureState = knownNoWrite ? 'blocked' : executionGuard.failureState(execution); + const knownBatchStop = hotelBatch.inspect(nextResult.hotel_batch)?.knownStopped === true; + const failureState = knownNoWrite || knownBatchStop ? 'blocked' : executionGuard.failureState(execution); executions[rootId] = { ...execution, state: failureState, updated_at: nowIso() }; if (failureState === 'uncertain') { nextResult = { @@ -2623,7 +2626,9 @@ function lifecycleRouteFailure(operation, report = {}) { }; } -async function executeLifecycleOperation(taskId, operation, operationPlan) { +async function executeLifecycleOperation(taskId, operation, operationPlan, options = {}) { + const transitionExecution = (id, state, details) => options.batchItem && ['blocked', 'uncertain'].includes(state) + ? Promise.resolve() : transitionCurrentExecution(id, state, details); throwIfTaskCancelled(taskId); let executionOperation = operation; let executionPlan = operationPlan; @@ -2794,7 +2799,7 @@ async function executeLifecycleOperation(taskId, operation, operationPlan) { } const frameTarget = preflight.frame_id ? { frameIds: [preflight.frame_id] } : { allFrames: false }; - await transitionCurrentExecution(taskId, 'write_started', { + await transitionExecution(taskId, 'write_started', { write_started_at: nowIso(), current_task_id: taskId, browser_action: 'liveSubmitLifecycleOperation', @@ -2854,7 +2859,7 @@ async function executeLifecycleOperation(taskId, operation, operationPlan) { const message = businessFailure?.message || linkageFailureMessage || '生命周期适配器在提交前阻断,未写入 ERP。'; - await transitionCurrentExecution(taskId, 'blocked', { + await transitionExecution(taskId, 'blocked', { blocked_at: nowIso(), current_task_id: taskId }); @@ -2895,7 +2900,7 @@ async function executeLifecycleOperation(taskId, operation, operationPlan) { || live.server_response?.business_blocked === true; if (businessBlocked) { const message = businessMessage || 'ERP 业务规则阻止了本次操作。'; - await transitionCurrentExecution(taskId, 'blocked', { + await transitionExecution(taskId, 'blocked', { blocked_at: nowIso(), current_task_id: taskId, lifecycle_requery: live.requery || null @@ -2916,7 +2921,7 @@ async function executeLifecycleOperation(taskId, operation, operationPlan) { } if (lifecycleResult?.isVerifiedResult?.(live) === true) { - await transitionCurrentExecution(taskId, 'submitted', { + await transitionExecution(taskId, 'submitted', { submitted_at: nowIso(), current_task_id: taskId, lifecycle_requery: live.requery @@ -2934,7 +2939,7 @@ async function executeLifecycleOperation(taskId, operation, operationPlan) { return { ok: true, status: 'completed', task_id: taskId, report: live }; } - await transitionCurrentExecution(taskId, 'uncertain', { + await transitionExecution(taskId, 'uncertain', { uncertain_at: nowIso(), current_task_id: taskId, lifecycle_requery: live.requery || null @@ -3422,6 +3427,71 @@ async function executeTeamBatchNativeTask(taskId, operation) { }; } +async function publishHotelBatch(taskId, operation, batch, { final = false, reconciled = false, allowCancelled = false } = {}) { + const stats = hotelBatch.inspect(batch); + if (!stats) throw new Error('酒店批量结果结构不完整,禁止自动重试。'); + const status = !final ? 'running' : stats.allCompleted ? 'completed' : stats.knownStopped ? 'blocked' : 'execution_uncertain'; + // The parent is polled independently of child progress. While a child runs, + // it may already have crossed the save boundary before its final result arrives. + const writeAttempted = batch.items.some(item => item.result?.write_attempted === true || item.result?.no_erp_write === false || ['running', 'completed', 'uncertain'].includes(item.status)); + const title = stats.allCompleted ? '酒店批量安排成功' : stats.completed ? '酒店批量安排部分完成' : '酒店批量安排未完成'; + const message = `📢【${title}】\n\n团号:${batch.group_number}\n\n${hotelBatch.resultText(batch)}`; + return setResult(taskId, { status, stage: 'hotel_batch', hotel_batch: batch, + message: final ? message : `正在执行第 ${batch.items.findIndex(item => item.status !== 'completed') + 1} / ${stats.total} 条酒店安排。`, + group_number: batch.group_number, summary: { action: 'arrangement_hotel' }, + report: { status: stats.allCompleted ? 'hotel_batch_completed' : 'hotel_batch_progress' }, + blockers: [], no_erp_write: !writeAttempted, write_attempted: writeAttempted, + uncertain: status === 'execution_uncertain', manual_review_required: status === 'execution_uncertain', + error_code: final && !stats.allCompleted ? 'hotel_batch_stopped' : '', + failure_message: final && !stats.allCompleted ? message : '', + ...(reconciled ? { reconciliation_resolved: true, no_additional_erp_write: true } : {}) }, { allowCancelled }); +} + +async function stopHotelBatch(taskId, operation, message) { + const saved = await chrome.storage.local.get('businessTaskResults'); + const batch = saved.businessTaskResults?.[taskId]?.hotel_batch; + if (!batch) return false; + for (const item of batch.items) if (item.status === 'running') { + const result = saved.businessTaskResults?.[`${taskId}#hotel-${item.index + 1}`]; + item.result = result || { status: 'execution_uncertain', no_erp_write: false, write_attempted: true, message }; + item.status = hotelBatch.verified(result, batch.group_number) ? 'completed' + : result?.no_erp_write === true && result?.write_attempted !== true && result?.status === 'blocked' ? 'failed' : 'uncertain'; + } + batch.stopped = true; + await publishHotelBatch(taskId, operation, batch, { final: true, allowCancelled: true }); + return true; +} + +async function executeHotelBatch(taskId, operation) { + const operations = operationPlans.expandHotelBatch(operation); + const batch = { version: 1, group_number: operation.data.existing_refs.identifier, + items: operations.map((item, index) => ({ index, arrangement: item.data.arrangement, status: 'not_started' })) }; + for (const [index, item] of operations.entries()) { + throwIfTaskCancelled(taskId); + const childId = `${taskId}#hotel-${index + 1}`; + batch.items[index].status = 'running'; + await publishHotelBatch(taskId, operation, batch); + let result; + try { + await executeLifecycleOperation(childId, item, operationPlans.validateDispatchOperation(item), { batchItem: true }); + const saved = await chrome.storage.local.get('businessTaskResults'); + result = saved.businessTaskResults?.[childId]; + if (!result) throw new Error('未取得该条酒店安排结果。'); + } catch (error) { + if (error?.code === 'task_cancelled') throw error; + // A missing result is never proof of no write. Keep the completed prefix. + result = { status: 'execution_uncertain', no_erp_write: false, write_attempted: true, + message: error.message || String(error), report: {} }; + } + batch.items[index].result = result; + batch.items[index].status = hotelBatch.verified(result, batch.group_number) ? 'completed' + : result.no_erp_write === true && result.write_attempted !== true ? 'failed' : 'uncertain'; + const final = batch.items[index].status !== 'completed' || index === operations.length - 1; + await publishHotelBatch(taskId, operation, batch, { final }); + if (final) return { ok: batch.items.every(row => row.status === 'completed'), task_id: taskId, hotel_batch: batch }; + } +} + async function runAutoTask(taskId, executionId) { const rootId = executionGuard.rootTaskId(taskId); if (isTaskCancelled(taskId)) { @@ -3465,6 +3535,8 @@ async function runAutoTask(taskId, executionId) { return { ok: false, status: 'validation_blocked', blockers }; } + if (operationPlan.execution === 'browser_hotel_batch') return await executeHotelBatch(normalizedTaskId, operation); + if (operationPlan.execution === 'browser_team_batch_native') { return await executeTeamBatchNativeTask(normalizedTaskId, operation); } @@ -3567,6 +3639,9 @@ async function runAutoTask(taskId, executionId) { } return await executeTeamSingleTask(normalizedTaskId, operation); } catch (error) { + if (operation?.data?.arrangements && await stopHotelBatch(rootId, operation, error.message || String(error))) { + return { ok: false, task_id: rootId, status: 'hotel_batch_stopped' }; + } if (error?.code === 'task_cancelled') { await setResult(taskId, { stage: 'cancelled', @@ -3585,6 +3660,7 @@ async function runAutoTask(taskId, executionId) { async function timeoutAutoTask(taskId, executionId) { const rootId = executionGuard.rootTaskId(taskId); cancelledTasks.add(rootId); + if (await stopHotelBatch(rootId, null, '酒店批量执行超时,已停止后续安排。')) return; const execution = await getExecutionRecord(rootId); const disposition = executionGuard.timeoutDisposition(execution); const message = disposition.reconciliation_required @@ -3771,6 +3847,31 @@ async function runLifecycleReadOnlyVerification(tab, rootId, operation, priorPre }; } +async function reconcileHotelBatch(rootId, operation, existingResult, saved) { + const batch = existingResult.hotel_batch; + if (!hotelBatch.inspect(batch)) throw new Error('缺少完整批量结果,禁止重试写入。'); + const item = batch.items.find(row => row.status === 'uncertain'); + if (!item) throw new Error('没有需要回查的酒店条目。'); + const childId = `${rootId}#hotel-${item.index + 1}`; + const childOperation = saved.businessTasks?.[childId]?.operation; + const prior = item.result?.report; + if (!childOperation || prior?.server_response?.completed !== true) throw new Error('该条缺少已保存成功的原生响应,请人工核对 ERP;禁止重新提交整批。'); + const gate = operationPlans.validateOperation(childOperation); + if (!gate.ok || gate.execution !== 'browser_lifecycle') throw new Error('该条原始酒店 operation 校验未通过。'); + runningTasks.add(rootId); + try { + const tab = await findOrOpenErpTab(rootId); + const readOnly = await runLifecycleReadOnlyVerification(tab, rootId, childOperation, prior.preflight); + if (!readOnly.resolved) return { ok: false, status: 'reconciliation_pending', message: readOnly.message }; + item.result = { ...item.result, status: 'completed', blockers: [], uncertain: false, manual_review_required: false, + report: { ...prior, status: 'lifecycle_completed', blockers: [], manual_review_required: false, requery: readOnly.verification.requery } }; + item.status = 'completed'; + batch.stopped = true; + await publishHotelBatch(rootId, operation, batch, { final: true, reconciled: true, allowCancelled: true }); + return { ok: true, status: hotelBatch.inspect(batch)?.allCompleted ? 'completed' : 'blocked', task_id: rootId, no_additional_erp_write: true, reconciliation_resolved: true }; + } finally { runningTasks.delete(rootId); } +} + async function reconcileLifecycleTask(taskId) { const rootId = executionGuard.rootTaskId(String(taskId || '').trim()); if (!rootId) throw new Error('缺少待回查 task_id。'); @@ -3788,6 +3889,7 @@ async function reconcileLifecycleTask(taskId) { if (!/^(?:saved_unverified|execution_uncertain|reconciliation_pending)$/.test(String(existingResult.status || ''))) { throw new Error('只允许对已跨越 ERP 写入边界且结果不确定的生命周期任务做只读回查。'); } + if (operation.data?.arrangements) return reconcileHotelBatch(rootId, operation, existingResult, saved); const validation = operationPlans.validateOperation(operation); if (validation.execution !== 'browser_lifecycle' || validation.blockers.length) { throw new Error(`原始生命周期 operation 已不符合当前契约:${validation.blockers.join(';') || validation.execution}。`); diff --git a/chrome-extension/ltjt-order-assistant/hotel-batch.js b/chrome-extension/ltjt-order-assistant/hotel-batch.js new file mode 100644 index 0000000..6a3890f --- /dev/null +++ b/chrome-extension/ltjt-order-assistant/hotel-batch.js @@ -0,0 +1,72 @@ +(function (root, factory) { + const api = factory(); + if (typeof module === 'object' && module.exports) module.exports = api; + root.LTJTHotelBatch = api; +})(typeof self !== 'undefined' ? self : globalThis, function () { + 'use strict'; + const object = value => value && typeof value === 'object' && !Array.isArray(value) ? value : {}; + const group = value => String(value || '').normalize('NFKC').replace(/\s+/gu, '').toUpperCase(); + function verified(result, identifier) { + const report = object(result?.report), refs = object(report.resolved_refs); + return result?.status === 'completed' && report.status === 'lifecycle_completed' && report.server_response?.completed === true + && report.requery?.matched === true && report.requery?.skipped !== true + && !result.uncertain && !result.manual_review_required && !report.manual_review_required + && ![result.blockers, report.blockers].some(value => Array.isArray(value) && value.length) + && refs.kind !== 'shared_child_order' && group(refs.identifier || refs.group_no) === group(identifier); + } + function inspect(value) { + const batch = object(value), items = batch.items; + if (batch.version !== 1 || !group(batch.group_number) || !Array.isArray(items) + || items.length < 2 || items.length > 10) return null; + let stopped = false, pending = false, completed = 0, failed = 0, uncertain = 0; + const rowIds = new Set(); + for (let index = 0; index < items.length; index++) { + const item = object(items[index]); + if (item.index !== index || item.arrangement?.mode !== 'create') return null; + if (item.status === 'completed') { + if (stopped || pending || !verified(item.result, batch.group_number)) return null; + const subpage = object(item.result.report.requery.subpage), target = object(subpage.arrangement_target); + const rowId = String(target.row_id || ''); + if (!rowId || rowId === '0' || rowIds.has(rowId)) return null; + rowIds.add(rowId); + const normalizeDate = value => String(value || '').replace(/^(\d{4})-(\d{1,2})-(\d{1,2})$/, (_, y, m, d) => `${y}-${m.padStart(2, '0')}-${d.padStart(2, '0')}`); + const checks = Array.isArray(subpage.checks) ? subpage.checks : []; + const actual = pattern => checks.filter(check => check.matched === true && pattern.test(check.field)); + for (const [pattern, expected] of [[/^riqi\d+$/, item.arrangement.start_date], [/^riqis\d+$/, item.arrangement.end_date]]) { + const matches = actual(pattern); + if (matches.length !== 1 || normalizeDate(matches[0].actual) !== expected) return null; + } + if (Number(target.quantity_value) !== item.arrangement.room_count) return null; + completed++; + } else if (item.status === 'failed') { + if (stopped || pending || item.result?.no_erp_write !== true || item.result?.write_attempted === true) return null; + stopped = true; failed++; + } else if (item.status === 'uncertain') { + if (stopped || pending) return null; + stopped = true; uncertain++; + } else if (item.status === 'running') { + if (stopped || pending) return null; + pending = true; + } else if (item.status === 'not_started') pending = true; + else return null; + } + return { completed, failed, uncertain, total: items.length, + not_started: items.filter(item => item.status === 'not_started').length, + allCompleted: completed === items.length, knownStopped: (failed === 1 || (batch.stopped === true && completed > 0 && !items.some(item => item.status === 'running'))) && uncertain === 0 }; + } + function resultText(batch) { + const stats = inspect(batch); + if (!stats) return ''; + const lines = [`共 ${stats.total} 条:成功 ${stats.completed} 条,失败 ${stats.failed} 条,待核验 ${stats.uncertain} 条,未执行 ${stats.not_started} 条`]; + const labels = { completed: '已完成', failed: '失败', uncertain: '结果不确定,待只读回查', not_started: '未执行', running: '处理中' }; + batch.items.forEach(item => { + const a = item.arrangement; + lines.push('', `${item.index + 1}. ${String(a.start_date).replaceAll('-', '/')}—${String(a.end_date).replaceAll('-', '/')}`, + ` 酒店搜索:${a.resource?.keyword || a.resource?.name || ''};${a.room_count} 间`, ` ${labels[item.status]}`); + if (item.status === 'failed') lines.push(` 原因:${String(item.result?.failure_message || item.result?.message || '执行阻断').slice(0, 250)}`); + }); + if (!stats.allCompleted) lines.push('', '已完成的安排已保留,请勿整批重复提交。'); + return lines.join('\n'); + } + return { verified, inspect, resultText }; +}); diff --git a/chrome-extension/ltjt-order-assistant/inpage.js b/chrome-extension/ltjt-order-assistant/inpage.js index b52a09a..cb1d667 100644 --- a/chrome-extension/ltjt-order-assistant/inpage.js +++ b/chrome-extension/ltjt-order-assistant/inpage.js @@ -6677,7 +6677,7 @@ } window.LTJTOrderAssistant = { - version: '0.5.189', + version: '0.5.190', resolveNativeListSearchValues, lookupKeywordMatchesText, inspectLifecycleSearchCriteria: lifecycleSearchCriteria, diff --git a/chrome-extension/ltjt-order-assistant/manifest.json b/chrome-extension/ltjt-order-assistant/manifest.json index 13b0db3..c73d27e 100644 --- a/chrome-extension/ltjt-order-assistant/manifest.json +++ b/chrome-extension/ltjt-order-assistant/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "联泰下单助手", - "version": "0.5.189", + "version": "0.5.190", "description": "在已登录 LTJT ERP 页面内规划并受控执行联泰 ERP 业务操作。", "permissions": [ "activeTab", diff --git a/chrome-extension/ltjt-order-assistant/operation-plans.js b/chrome-extension/ltjt-order-assistant/operation-plans.js index 690a5a1..6d27e17 100644 --- a/chrome-extension/ltjt-order-assistant/operation-plans.js +++ b/chrome-extension/ltjt-order-assistant/operation-plans.js @@ -1315,8 +1315,38 @@ if (types.includes('pickup-sign') && (typeof data.visitor_name !== 'string' || !data.visitor_name.trim())) blockers.push('接机牌必须提供游客姓名。'); } + // A batch is an envelope of ordinary hotel creates, never an ERP form. + function expandHotelBatch(operation) { + const data = operation?.data; + if (!data || !Object.hasOwn(data, 'arrangements')) return null; + if (operation.action !== 'arrangement_hotel' || !Array.isArray(data.arrangements) + || data.arrangements.length < 2 || data.arrangements.length > 10 + || Object.keys(data).some(key => !['existing_refs', 'arrangements'].includes(key))) { + throw new Error('批量酒店只支持同团 2—10 条新增安排,不能混用其他字段。'); + } + const seen = new Set(); + return data.arrangements.map((arrangement, index) => { + if (!isPlainObject(arrangement) || arrangement.mode !== 'create') throw new Error(`第 ${index + 1} 条必须为酒店新增。`); + const key = [arrangement.start_date, arrangement.end_date, + String(arrangement.resource?.keyword || arrangement.resource?.name || '').normalize('NFKC').replace(/\s+/gu, '').toUpperCase(), arrangement.room_count].join('|'); + if (seen.has(key)) throw new Error(`第 ${index + 1} 条酒店安排重复,请核对后重新发送整批。`); + seen.add(key); + return { ...operation, data: { existing_refs: data.existing_refs, arrangement } }; + }); + } + + function validateHotelBatch(operation) { + let items; + try { items = expandHotelBatch(operation); } + catch (error) { return { ok: false, action: operation.action, execution: 'blocked', blockers: [error.message], warnings: [] }; } + const plans = items.map(validateDispatchOperation); + const blockers = plans.flatMap((plan, index) => plan.blockers.map(message => `第 ${index + 1} 条:${message}`)); + return { ...plans[0], ok: !blockers.length, execution: blockers.length ? 'blocked' : 'browser_hotel_batch', blockers }; + } + function validateDispatchOperation(operation = {}) { const normalized = normalizeOperation(operation); + if (normalized?.data && Object.hasOwn(normalized.data, 'arrangements')) return validateHotelBatch(normalized); const action = actionForOperation(normalized); const definition = OPERATION_DEFINITIONS[action] || null; if (!definition) return { @@ -1885,6 +1915,7 @@ targetDatesForLifecycle, LIFECYCLE_ACTIONS, ERP_RESOLUTION_ACTIONS, + expandHotelBatch, normalizeOperation, operationSummary, publicPlan, diff --git a/chrome-extension/ltjt-order-assistant/team-batch-inpage.js b/chrome-extension/ltjt-order-assistant/team-batch-inpage.js index 88dc35e..7845cbe 100644 --- a/chrome-extension/ltjt-order-assistant/team-batch-inpage.js +++ b/chrome-extension/ltjt-order-assistant/team-batch-inpage.js @@ -944,7 +944,7 @@ window.LTJTOrderAssistant = { ...(window.LTJTOrderAssistant || {}), - version: '0.5.189', + version: '0.5.190', openTeamBatchForm, pingTeamBatchFrame, preflightTeamBatchNative, diff --git a/control-plane/src/business-routes.ts b/control-plane/src/business-routes.ts index 17d68a4..a7b6aff 100644 --- a/control-plane/src/business-routes.ts +++ b/control-plane/src/business-routes.ts @@ -79,8 +79,8 @@ export function effectiveParserModeForRoute(route: BusinessRouteDefinition, conf return route.parserPolicy === 'program_only' ? 'program' : 'ai'; } -const PROGRAM_VERSION = 'ltjt-program-parser-v1.0.14'; -export const BUSINESS_INPUT_CONTRACT_VERSION = 'business-input-templates-0.5.136'; +const PROGRAM_VERSION = 'ltjt-program-parser-v1.0.15'; +export const BUSINESS_INPUT_CONTRACT_VERSION = 'business-input-templates-0.5.137'; const lookupFields = ({ customerRequired = true } = {}): ProgramFieldDefinition[] => [ { key: 'order_no', label: '单号', aliases: ['团号', '订单号'], kind: 'text' }, diff --git a/control-plane/src/hotel-batch.ts b/control-plane/src/hotel-batch.ts new file mode 100644 index 0000000..6576801 --- /dev/null +++ b/control-plane/src/hotel-batch.ts @@ -0,0 +1,23 @@ +import { createRequire } from 'node:module'; +import { resolve } from 'node:path'; +const require = createRequire(import.meta.url); +type Json = Record; +export const hotelBatch = require(resolve(process.cwd(), 'chrome-extension/ltjt-order-assistant/hotel-batch.js')) as { + inspect(value: unknown): null | { completed: number; failed: number; uncertain: number; total: number; not_started: number; allCompleted: boolean; knownStopped: boolean }; + resultText(batch: unknown): string; + verified(result: unknown, identifier: unknown): boolean; +}; +// Bind every row back to the server's immutable parsed operation, not a plugin-supplied plan. +export function hotelBatchMatchesOperation(operation: unknown, result: unknown): boolean { + const op = operation as Json, batch = (result as Json)?.hotel_batch; + if (op?.action !== 'arrangement_hotel' || !Array.isArray(op.data?.arrangements) || !hotelBatch.inspect(batch) + || batch.group_number !== op.data.existing_refs?.identifier || batch.items.length !== op.data.arrangements.length) return false; + const canonical = (value: any): any => Array.isArray(value) ? value.map(canonical) : value && typeof value === 'object' + ? Object.fromEntries(Object.keys(value).sort().map(key => [key, canonical(value[key])])) : value; + if (batch.items.some((item: Json, index: number) => JSON.stringify(canonical(item.arrangement)) !== JSON.stringify(canonical(op.data.arrangements[index])))) return false; + return true; +} + +export function verifiedHotelBatchItems(operation: unknown, result: unknown): Json[] { + return hotelBatchMatchesOperation(operation, result) ? (result as Json).hotel_batch.items.filter((item: Json) => item.status === 'completed') : []; +} diff --git a/control-plane/src/parser-orchestrator.ts b/control-plane/src/parser-orchestrator.ts index 4fe4a47..00e3b30 100644 --- a/control-plane/src/parser-orchestrator.ts +++ b/control-plane/src/parser-orchestrator.ts @@ -1,7 +1,7 @@ import { performance } from 'node:perf_hooks'; import type { BusinessRouteId, ParserMode } from './business-routes.js'; import { compareParserResults } from './parser-contract.js'; -import { parseProgramInput, programFallbackAllowed, programParserVersion } from './program-parser.js'; +import { parseProgramInput, isHotelBatchInput, programFallbackAllowed, programParserVersion } from './program-parser.js'; import type { ParseDecisionInput, ParseTaskClaim } from './task-service.js'; export interface AiParser { @@ -71,6 +71,15 @@ export class ParserOrchestrator { const configuredMode: ParserMode = claim.task.parser.configured_mode; const affinity = claim.task.parser.engine_affinity; + // Repeated hotel blocks have a deterministic relationship. Do not send + // them to the single-arrangement Agent contract, including on AI reparse. + if (routeId === 'arrangement_hotel_create' && [claim.messageText, ...claim.history.map(item => item.rawText)].some(isHotelBatchInput)) { + const started = performance.now(); + const result = await parseProgramInput(this.programInput(claim)); + return { result, decision: { routeId, configuredMode, authoritativeEngine: 'program', + programVersion: programVersion(result, routeId), programResult: result, programDurationMs: elapsed(started) } }; + } + // Unresolved/legacy traffic and tasks explicitly pinned to AI preserve the // original parser path. This is also the permanent affinity after an Auto // fallback or the legacy one-time task AI reparse. diff --git a/control-plane/src/program-parser.ts b/control-plane/src/program-parser.ts index 914b294..8133d35 100644 --- a/control-plane/src/program-parser.ts +++ b/control-plane/src/program-parser.ts @@ -1044,6 +1044,40 @@ function needsInput(route: BusinessRouteDefinition, code: string, fields: string } as ProgramParseResult; } +export function isHotelBatchInput(text: string): boolean { + return Boolean(hotelBlocks(text)); +} + +function hotelBlocks(text: string): string[] | null { + const lines = text.replace(/\r\n?/gu, '\n').split('\n'); + const starts = lines.flatMap((line, index) => /^\s*(?:团号|单号)\s*[::]/u.test(line) ? [index] : []); + if (starts.length < 2) return null; + if (lines.slice(0, starts[0]).some(line => line.trim() && line.trim() !== '安排酒店')) return []; + return starts.map((start, index) => ['安排酒店', ...lines.slice(start, starts[index + 1])].join('\n')); +} + +async function parseHotelBatch(input: ProgramParseInput, route: BusinessRouteDefinition, blocks: string[]): Promise { + if (blocks.length < 2 || blocks.length > 10) return blocked(route, 'program_hotel_batch_invalid', '同团批量酒店必须为 2—10 条,每条从团号开始且字段完整,请重新发送整批。'); + const arrangements: unknown[] = []; + let identifier = ''; + for (let index = 0; index < blocks.length; index++) { + const parsed = await parseProgramInputInner({ ...input, history: [], rawText: blocks[index], routeId: route.routeId }); + if (parsed.status !== 'agent_parse_passed' || !parsed.operation) return blocked(route, 'program_hotel_batch_invalid', + `第 ${index + 1} 条:${String(parsed.reply || '字段不完整')} 请修正后重新发送整批,尚未执行任何安排。`); + const data = parsed.operation.data as JsonRecord; + const group = String((data.existing_refs as JsonRecord).identifier).normalize('NFKC').replace(/\s+/gu, '').toUpperCase(); + if (identifier && identifier !== group) return blocked(route, 'program_hotel_batch_invalid', '批量安排酒店只支持同一个团号,请按团分别发送。'); + identifier = group; + arrangements.push(data.arrangement); + } + const operation = { action: 'arrangement_hotel', order_nature: 'formal', submit_mode: 'dry_run', + data: { existing_refs: { identifier }, arrangements } }; + const errors = await validateParsedOperation(operation); + if (errors.length) return blocked(route, 'program_hotel_batch_invalid', errors.join(';')); + return { ...baseEnvelope(route), status: 'agent_parse_passed', blockers: [], operation, + reply: `已识别同团 ${arrangements.length} 条酒店新增安排,将按输入顺序逐条执行。`, no_plugin_dispatch: true, no_erp_write: true } as ProgramParseResult; +} + async function parseProgramInputInner(input: ProgramParseInput): Promise { const suppliedRoute = input.routeId ? businessRouteById(input.routeId) : null; const routeMessage = input.rawText || input.history?.[0]?.text || ''; @@ -1063,6 +1097,12 @@ async function parseProgramInputInner(input: ProgramParseInput): Promise ({ text: String(turn.text || ''), receivedAt: turn.receivedAt })), { text: String(input.rawText || ''), receivedAt: input.receivedAt } ].filter((turn, index, all) => turn.text.trim() && !(index === all.length - 1 && index > 0 && turn.text === all[index - 1].text)); + if (route.routeId === 'arrangement_hotel_create') { + const batch = hotelBlocks(turns.at(-1)?.text || ''); + if (batch) return parseHotelBatch(input, route, batch); + if (turns.slice(0, -1).some(turn => hotelBlocks(turn.text))) return blocked(route, 'program_hotel_batch_invalid', + '批量酒店需要保留每条对应关系,请修正后重新发送完整整批内容。'); + } const extracted = extractFields(route, turns); if (extracted.multipleRoute) return blocked(route, 'program_multiple_actions', '同一任务补充中出现了另一个业务动作,不能合并解析。'); if (extracted.unknownFields.length) { diff --git a/control-plane/src/reply-contract.ts b/control-plane/src/reply-contract.ts index b43dd83..2c407e7 100644 --- a/control-plane/src/reply-contract.ts +++ b/control-plane/src/reply-contract.ts @@ -1,3 +1,4 @@ +import { hotelBatch } from './hotel-batch.js'; import { normalizeTaskArtifactFileName } from './artifact-name.js'; import { arrangementSummaryText, isArrangementAction } from './arrangement-ledger.js'; import { receiptDate, teamProgressText } from './team-progress.js'; @@ -573,6 +574,14 @@ export function buildCustomerSuccessReceipt( resultValue: unknown, operationValue: unknown ): Record { + const batch = object(resultValue).hotel_batch; + if (hotelBatch.inspect(batch)?.allCompleted) { + const resultText = hotelBatch.resultText(batch); + const title = '酒店批量安排成功'; + const progress = teamProgressText(baseReceipt.team_progress, { title, resultText }); + return { ...baseReceipt, reply: { contract: AGENTBUS_REPLY_CONTRACT_VERSION, profile: 'hotel-batch-v1', + text: progress || `📢【${title}】\n\n团号:${object(batch).group_number}\n\n${resultText}`, fields: { action: 'arrangement_hotel' } } }; + } const reply = buildCustomerSuccessReply(baseReceipt, resultValue, operationValue); if (!reply) return baseReceipt; const result = object(resultValue); diff --git a/control-plane/src/task-service.ts b/control-plane/src/task-service.ts index a2be5cb..1427b55 100644 --- a/control-plane/src/task-service.ts +++ b/control-plane/src/task-service.ts @@ -1,3 +1,4 @@ +import { hotelBatch, hotelBatchMatchesOperation, verifiedHotelBatchItems } from './hotel-batch.js'; import { EventEmitter } from 'node:events'; import { randomToken, encryptText, decryptText, sha256Bytes, sha256Text } from './crypto.js'; import type { AppConfig } from './config.js'; @@ -1226,6 +1227,11 @@ function splitOrderProbeReceiptVerified(result: Record, report: export function successReceiptFromResult(value: unknown): Record | null { const result = jsonObject(value); + if (result.hotel_batch) { + const stats = hotelBatch.inspect(result.hotel_batch); + return stats?.allCompleted ? { success: true, group_number: jsonObject(result.hotel_batch).group_number, + hotel_batch: result.hotel_batch, status: 'verified' } : null; + } const report = jsonObject(result.report); const verification = jsonObject(result.verification); const blockers = [result.blockers, report.blockers] @@ -2204,6 +2210,11 @@ const IMMUTABLE_EXECUTION_STATUSES = new Set([ export function classifyExecutionResult(value: unknown): ExecutionClassification { const result = jsonObject(value); const rawStatus = text(result.status || result.result_status || 'running').toLowerCase(); + if (result.hotel_batch) { + const stats = hotelBatch.inspect(result.hotel_batch); + if (!stats) return { rawStatus, status: 'reconciliation_pending', terminal: true, uncertain: true }; + if (stats.knownStopped && /blocked|failed/.test(rawStatus)) return { rawStatus, status: 'blocked', terminal: true, uncertain: false }; + } const noErpWrite = result.no_erp_write === true; const writeAttempted = result.write_attempted === true || /submit_started|write_started|submitted/i.test(text(result.execution_phase)); @@ -6590,6 +6601,13 @@ export class TaskService { resultObject = attachStoredArtifactMetadata(this.config, resultObject, taskId, stored); } + const operation = decryptedJson(this.config, row.operation_ciphertext) || row.operation || null; + if (resultObject.hotel_batch) { + const stats = hotelBatch.inspect(resultObject.hotel_batch); + if (!stats || !hotelBatchMatchesOperation(operation, resultObject)) { + throw new TaskError('hotel_batch_evidence_mismatch', '酒店批量结果与原始任务或逐条回查证据不一致。'); + } + } const classification = classifyExecutionResult(resultObject); const { rawStatus, status, uncertain, terminal } = classification; const stage = text(resultObject.stage || 'erp').slice(0, 160); @@ -6616,7 +6634,6 @@ export class TaskService { Object.assign(resultObject, lifecycle); const message = text(resultObject.message || (uncertain ? 'ERP 结果不确定,请勿重复提交同一任务,等待任务所属账号只读核验。' : '插件已返回状态。')).slice(0, 2_000); const executionFailure = failureSummary(resultObject, '', status, stage); - const operation = decryptedJson(this.config, row.operation_ciphertext) || row.operation || null; const baseSuccessReceipt = status === 'completed' ? successReceiptFromResult(resultObject) : null; // This snapshot is exclusively derived by the server, never by a plugin. if (baseSuccessReceipt) { @@ -6649,7 +6666,10 @@ export class TaskService { && hasPrewriteNoErpEvidence(previousExecutionResult) && hasPrewriteNoErpEvidence(resultObject) && sameConnection; - const canFinalizeTerminalReconciliation = canFinalizeReconciliation || canFinalizeNoWriteReconciliation || canFinalizeDispatchNoWrite; + const canFinalizeBatchReconciliation = (text(row.status) === 'reconciliation_pending' || failedReconciliation) + && text(attemptRow.status) === 'reconciliation_pending' && sameConnection && status === 'blocked' + && lifecycle.reconciliation_resolved && hotelBatch.inspect(resultObject.hotel_batch)?.knownStopped === true; + const canFinalizeTerminalReconciliation = canFinalizeReconciliation || canFinalizeNoWriteReconciliation || canFinalizeDispatchNoWrite || canFinalizeBatchReconciliation; if (executionStatusImmutable(text(row.status)) && !canFinalizeTerminalReconciliation) { throw new TaskError('result_after_terminal', `任务已处于不可变状态 ${row.status},迟到结果已忽略。`); } @@ -6663,15 +6683,22 @@ export class TaskService { taskId, executionId, operation, result: resultObject, receipt: baseSuccessReceipt, effectiveAt: attemptRow.started_at, recordedAt: new Date().toISOString() }) : null; - if (arrangementEvent) { + const batchEvents = verifiedHotelBatchItems(operation, resultObject).map(item => arrangementEventFromSuccess({ + taskId, executionId: `${executionId}:hotel:${item.index + 1}`, effectiveAt: attemptRow.started_at, + recordedAt: new Date().toISOString(), + operation: { ...jsonObject(operation), data: { existing_refs: jsonObject(jsonObject(operation).data).existing_refs, arrangement: item.arrangement } }, + result: item.result, receipt: successReceiptFromResult(item.result) + })).filter((event): event is NonNullable => Boolean(event)); + if (arrangementEvent || batchEvents.length) { const ledger = new ArrangementLedger(this.config); await ledger.prepare(client, context, successReceiptFromResult); - await ledger.insert(client, context, arrangementEvent); - const history = await ledger.history(client, context, arrangementEvent.group_number); + for (const event of arrangementEvent ? [arrangementEvent, ...batchEvents] : batchEvents) await ledger.insert(client, context, event); + const summaryEvent = arrangementEvent || batchEvents[0]; + const history = await ledger.history(client, context, summaryEvent.group_number); // Freeze the account/group summary with this receipt in the SAME transaction. - successReceipt = buildCustomerSuccessReceipt({ + if (successReceipt) successReceipt = buildCustomerSuccessReceipt({ ...successReceipt, - arrangement_summary: arrangementSummary(history, arrangementEvent.group_number, arrangementEvent.recorded_at) + arrangement_summary: arrangementSummary(history, summaryEvent.group_number, summaryEvent.recorded_at) }, resultObject, operation); } const progressInput = { taskId, executionId, operation, result: resultObject, diff --git a/control-plane/src/team-progress.ts b/control-plane/src/team-progress.ts index 4835354..c52700a 100644 --- a/control-plane/src/team-progress.ts +++ b/control-plane/src/team-progress.ts @@ -1,3 +1,4 @@ +import { verifiedHotelBatchItems } from './hotel-batch.js'; import type pg from 'pg'; import type { AppConfig } from './config.js'; import { decryptText, encryptText, sha256Text } from './crypto.js'; @@ -49,6 +50,7 @@ export interface ProgressInput { export function progressEventsFromSuccess(input: ProgressInput): ProgressEvent[] { const op = obj(input.operation), data = obj(op.data), result = obj(input.result), report = obj(result.report), receipt = obj(input.receipt); const action = txt(op.action), refs = obj(report.resolved_refs || result.resolved_refs); + if (Array.isArray(data.arrangements)) return []; if (!ACTIONS.has(action) || result.status !== 'completed' || !Object.keys(receipt).length || receipt.success === false || result.uncertain === true || result.manual_review_required === true || report.manual_review_required === true || [result.blockers, report.blockers].some(v => Array.isArray(v) && v.length)) return []; @@ -116,6 +118,10 @@ export function progressEventsFromSuccess(input: ProgressInput): ProgressEvent[] // An interrupted batch is not a successful task. Preserve only independently // confirmed rows from the existing adapter's per-item completion contract. export function partialBatchProgressEvents(input: ProgressInput): ProgressEvent[] { + if (obj(input.result).hotel_batch) return verifiedHotelBatchItems(input.operation, input.result).flatMap(item => + progressEventsFromSuccess({ ...input, executionId: `${input.executionId}:hotel:${item.index + 1}`, + operation: { ...obj(input.operation), data: { existing_refs: obj(obj(input.operation).data).existing_refs, arrangement: item.arrangement } }, + result: item.result, receipt: { success: true, group_number: obj(obj(obj(input.operation).data).existing_refs).identifier } })); const op = obj(input.operation), result = obj(input.result), report = obj(result.report); if (result.status === 'completed' || !['blocked', 'failed', 'reconciliation_pending'].includes(txt(result.status))) return []; const batches = list(result.batch_results).length ? list(result.batch_results) : list(report.batch_results); diff --git a/control-plane/test/hotel-batch.test.ts b/control-plane/test/hotel-batch.test.ts new file mode 100644 index 0000000..ee7d6f3 --- /dev/null +++ b/control-plane/test/hotel-batch.test.ts @@ -0,0 +1,93 @@ +import { readFileSync } from 'node:fs'; +import test from 'node:test'; +import assert from 'node:assert/strict'; +import { parseProgramInput } from '../src/program-parser.js'; +import { classifyExecutionResult, successReceiptFromResult } from '../src/task-service.js'; +import { hotelBatch, hotelBatchMatchesOperation, verifiedHotelBatchItems } from '../src/hotel-batch.js'; +import { partialBatchProgressEvents } from '../src/team-progress.js'; +import { arrangementEventFromSuccess } from '../src/arrangement-ledger.js'; +import { buildCustomerSuccessReceipt, replyTextFromReceipt } from '../src/reply-contract.js'; +const block = (day: number, group = 'LW-SAMPLE') => `团号:${group}\n入住日期:2026-09-${day}\n离店日期:2026-09-${day + 1}\n酒店搜索:示例酒店${day}TWN\n间数:1`; +const parse = (rawText: string) => parseProgramInput({ rawText, receivedAt: '2026-09-24T00:00:00Z' }); +type Json = Record; +async function fixture() { + const parsed = await parse(`安排酒店\n${block(26)}\n${block(27)}\n${block(28)}`); + assert.equal(parsed.status, 'agent_parse_passed', parsed.reply as string); + const operation = parsed.operation as Json; + const items = operation.data.arrangements.map((arrangement: Json, index: number) => ({ index, arrangement, status: 'completed', + result: { status: 'completed', write_attempted: true, no_erp_write: false, report: { status: 'lifecycle_completed', + server_response: { completed: true }, resolved_refs: { kind: 'independent_order', identifier: 'LW-SAMPLE', tid: '1' }, + requery: { matched: true, subpage: { arrangement_target: { row_id: String(index + 10), quantity_value: '1' }, + checks: [{ field: 'riqi0', actual: arrangement.start_date, matched: true }, { field: 'riqis0', actual: arrangement.end_date, matched: true }] } } + } } })); + return { operation, result: { status: 'completed', write_attempted: true, no_erp_write: false, + hotel_batch: { version: 1, group_number: 'LW-SAMPLE', items } } as Json }; +} +test('hotel input splits five repeated group blocks and preserves multi-night stays and ordinary input', async () => { + const result = await parse(`安排酒店\n${[25,26,27,28,29].map(day => block(day)).join('\n')}`); + assert.equal(result.status, 'agent_parse_passed'); + assert.equal((result.operation?.data as Json).arrangements.length, 5); + const single = await parse(`安排酒店\n${block(26).replace('2026-09-27', '2026-09-29')}`); + assert.equal((single.operation?.data as Json).arrangement.end_date, '2026-09-29'); + assert.equal((single.operation?.data as Json).arrangements, undefined); +}); +test('the entire batch blocks on cross-team, missing field, duplicate, mixed action, invalid date and oversized input', async () => { + for (const raw of [ + `${block(26)}\n${block(27, 'OTHER')}`, `${block(26)}\n${block(27).replace('间数:1','')}`, + `${block(26)}\n${block(26)}`, `${block(26)}\n安排用车\n${block(27)}`, + `${block(26)}\n${block(27).replace('2026-09-28','2026-09-26')}`, + Array.from({ length:11 },(_, i)=>block(i + 10)).join('\n') + ]) { + const result = await parse(`安排酒店\n${raw}`); + assert.notEqual(result.status, 'agent_parse_passed', raw); + assert.equal(result.operation, null); + assert.equal(result.no_erp_write, true); + } +}); +test('all rows require bound, distinct write/requery evidence before whole-batch success', async () => { + const { operation, result } = await fixture(); + assert.equal(classifyExecutionResult(result).status, 'completed'); + assert.ok(hotelBatchMatchesOperation(operation, result)); + const reply = replyTextFromReceipt(buildCustomerSuccessReceipt(successReceiptFromResult(result)!, result, operation)); + assert.match(reply, /酒店批量安排成功/); assert.match(reply, /成功 3 条/); assert.match(reply, /2026\/09\/28/); + for (const mutate of [ + (r: Json) => { r.hotel_batch.items[1].result.report.server_response.completed = false; }, + (r: Json) => { r.hotel_batch.items[1].result.report.requery.subpage.arrangement_target.row_id = '10'; }, + (r: Json) => { r.hotel_batch.items[1].result.report.requery.subpage.checks[0].actual = '2026-01-01'; } + ]) { const bad = structuredClone(result); mutate(bad); assert.equal(classifyExecutionResult(bad).status, 'reconciliation_pending'); } + const bad = structuredClone(result); bad.hotel_batch.items[1].arrangement.resource.keyword = 'different'; + assert.equal(hotelBatchMatchesOperation(operation, bad), false); +}); +test('partial stop retains distinct successful ledger events and uncertainty never counts as success', async () => { + const { operation, result } = await fixture(); + result.status = 'blocked'; + const last = result.hotel_batch.items[2]; + last.status = 'failed'; last.result = { status: 'blocked', no_erp_write: true, write_attempted: false, message: '未匹配酒店' }; + assert.equal(classifyExecutionResult(result).status, 'blocked'); + assert.equal(successReceiptFromResult(result), null); + const input = { taskId:'TASK', executionId:'EXEC', operation, result, receipt:null, effectiveAt:'2026-09-24T00:00:00Z', recordedAt:'2026-09-24T00:00:00Z' }; + const events = partialBatchProgressEvents(input); + assert.equal(events.length, 2); assert.notEqual(events[0].execution_id, events[1].execution_id); + const arrangements = verifiedHotelBatchItems(operation, result).map(item => arrangementEventFromSuccess({ ...input, + executionId:`EXEC:hotel:${item.index + 1}`, operation:{ action:'arrangement_hotel', data:{ existing_refs:operation.data.existing_refs, arrangement:item.arrangement } }, + result:item.result, receipt:successReceiptFromResult(item.result) })); + assert.ok(arrangements.every(Boolean)); assert.notEqual(arrangements[0]?.record_key, arrangements[1]?.record_key); + last.status = 'uncertain'; last.result = { status:'execution_uncertain', write_attempted:true, no_erp_write:false }; + result.status = 'execution_uncertain'; + assert.equal(classifyExecutionResult(result).status, 'reconciliation_pending'); + assert.equal(partialBatchProgressEvents(input).length, 2); + assert.match(hotelBatch.resultText(result.hotel_batch), /待核验 1 条/); +}); + + +test('published operator batch example parses to three complete ordered stays', async () => { + const catalog = readFileSync('agent设计规范/templates/business-input-templates.md','utf8'); + const section = catalog.split('#### 同团批量安排示例')[1].split('### 11.')[0]; + const rawText = section.match(/```text\n([\s\S]*?)```/)![1]; + const parsed = await parse(rawText); + assert.equal(parsed.status, 'agent_parse_passed'); + const arrangements = (parsed.operation!.data as Json).arrangements; + assert.equal(arrangements.length,3); + assert.equal(arrangements[2].start_date,'2026-09-28'); + assert.equal(arrangements[2].end_date,'2026-09-30'); +}); diff --git a/control-plane/test/program-parser.test.ts b/control-plane/test/program-parser.test.ts index d934749..b92840b 100644 --- a/control-plane/test/program-parser.test.ts +++ b/control-plane/test/program-parser.test.ts @@ -189,7 +189,7 @@ test('versioned sanitized gold corpus replays deterministically', async () => { const corpus = JSON.parse(await readFile( new URL('../../samples/business-parser-gold-corpus.json', import.meta.url), 'utf8' )); - assert.equal(corpus.version, 'ltjt-program-parser-v1.0.14'); + assert.equal(corpus.version, 'ltjt-program-parser-v1.0.15'); assert.ok(corpus.cases.length >= 28); for (const candidate of corpus.cases) { const parse = () => parseProgramInput({ @@ -863,3 +863,22 @@ test('mode orchestration keeps AI authority in Shadow and enforces the Auto fall assert.equal(affinity.decision.authoritativeEngine, 'ai'); assert.equal(aiCalls, 3); }); + + +test('repeated hotel blocks always use their deterministic batch contract even with AI affinity', async () => { + let aiCalls = 0; + const orchestrator = new ParserOrchestrator({ async parse() { aiCalls++; return {}; } }); + const rawText = '安排酒店\n' + [26,27].map(day => `团号:LW-SAMPLE\n入住日期:2026-09-${day}\n离店日期:2026-09-${day+1}\n酒店搜索:示例酒店TWN\n间数:1`).join('\n'); + for (const mode of ['ai','shadow','auto','program'] as const) { + const claim = orchestrationClaim(mode, rawText, 'ai'); + claim.task.parser.route_id = 'arrangement_hotel_create'; + const parsed = await orchestrator.parse(claim); + assert.equal(parsed.decision.authoritativeEngine, 'program'); + assert.equal((parsed.result as any).operation.data.arrangements.length, 2); + claim.messageText = '间数:2'; + claim.history = [{ turnNo: 1, rawText }]; + const incomplete = await orchestrator.parse(claim); + assert.equal((incomplete.result as any).error_code, 'program_hotel_batch_invalid'); + } + assert.equal(aiCalls, 0); +}); diff --git a/dist/ltjt-order-assistant-0.5.190.zip b/dist/ltjt-order-assistant-0.5.190.zip new file mode 100644 index 0000000..2747c69 Binary files /dev/null and b/dist/ltjt-order-assistant-0.5.190.zip differ diff --git a/dist/release-manifest.json b/dist/release-manifest.json index ed179c2..801d043 100644 --- a/dist/release-manifest.json +++ b/dist/release-manifest.json @@ -1,19 +1,19 @@ { "manifest_version": 1, - "generated_on": "2026-09-22", + "generated_on": "2026-09-24", "baselines": { - "chrome_extension": "0.5.189", + "chrome_extension": "0.5.190", "skills": "0.5.129", - "business_instruction_docx": "0.5.136", + "business_instruction_docx": "0.5.137", "agent_prompt": "ltjt-agent-prompt-v1.8-independent-headcount-categories" }, "artifacts": [ { "kind": "chrome_extension", - "version": "0.5.189", - "path": "dist/ltjt-order-assistant-0.5.189.zip", + "version": "0.5.190", + "path": "dist/ltjt-order-assistant-0.5.190.zip", "source": "chrome-extension/ltjt-order-assistant", - "sha256": "06a44b11e53c0c563ab11edb9e055e8a8e893c1bb804f4afd5b988f89a90db5b" + "sha256": "092838d79cf5864ec3894035b60b6dde5bbeb979c6bf9bce0ad656dc9b7c7f69" }, { "kind": "skill", @@ -57,12 +57,12 @@ }, { "kind": "business_instruction_docx", - "version": "0.5.136", - "path": "dist/老挝联泰AI指令表-0.5.136.docx", + "version": "0.5.137", + "path": "dist/老挝联泰AI指令表-0.5.137.docx", "source": "agent设计规范/templates/business-input-templates.md", "builder": "tools/build_business_instruction_docx.py", - "sha256": "e6a669ef6f9a5b2acb00fba20988869d2c7da2663bb24527cc813853470a622f", - "source_sha256": "ecd51e09b75f56f64773b43a9305fb0ad62fbb3c8d2a0f1ec289eea3d074bdb5", + "sha256": "64846b40722d5c3c3807868f990c5781e77c746fc7f9f115ccfca1a74beb786e", + "source_sha256": "259d913ee844a72ae2ba08415813cede7b7593c83de8a358c63c2cfdd492f569", "builder_sha256": "c162884210da22e3b78368749b66f1f177df5e9fc6155f4fd9954bd516d187f1" } ] diff --git a/dist/老挝联泰AI指令表-0.5.137.docx b/dist/老挝联泰AI指令表-0.5.137.docx new file mode 100644 index 0000000..75f9467 Binary files /dev/null and b/dist/老挝联泰AI指令表-0.5.137.docx differ diff --git a/mappings/lifecycle.mapping.json b/mappings/lifecycle.mapping.json index 8350977..5f67c3c 100644 --- a/mappings/lifecycle.mapping.json +++ b/mappings/lifecycle.mapping.json @@ -1,7 +1,7 @@ { "contract_version": "ltjt-lifecycle-v2.9-roster-leader-contact-2026-08", "current_agent_parse_prompt_version": "ltjt-agent-prompt-v1.8-independent-headcount-categories", - "current_extension_version": "0.5.189", + "current_extension_version": "0.5.190", "original_arrangement_date_selection": "Hotel, vehicle, transport and other/filing update selection accepts MM-DD or YYYY-MM-DD; omitted year matches original ERP month/day within the exact group only. Require exactly one persisted row, then freeze its actual full dates and id. Full year is exact; mixed/cross-year endpoints are matched independently with actual range order checked. No match or ambiguity blocks before mutation; show up to five original candidates and ask to resubmit complete dates. Creation and legacy target-date changes retain existing semantics.", "historical_test_marker": "TEST-202609", "scope": "/System/Business/", @@ -705,5 +705,17 @@ "forbidden_side_effects_observed": 0, "remaining_objects": 3, "cleanup_state": "hotel row cleared; twin_room_count restored; three inert independent test orders retained on exact allowlist" + }, + "hotel_batch": { + "since": "0.5.190", + "action": "arrangement_hotel", + "execution": "browser_hotel_batch", + "input": "data.existing_refs.identifier + data.arrangements[2..10]", + "mode": "create_only", + "group_scope": "one_group_per_batch", + "native_execution": "serial_single_lifecycle_save_and_fresh_requery", + "stop_on": "first_blocked_failed_or_uncertain", + "retry": "no_automatic_replay", + "reconciliation": "read_only_uncertain_item_only_no_tail_resume" } } diff --git a/samples/business-parser-gold-corpus.json b/samples/business-parser-gold-corpus.json index 80c00db..c37e393 100644 --- a/samples/business-parser-gold-corpus.json +++ b/samples/business-parser-gold-corpus.json @@ -1,5 +1,5 @@ { - "version": "ltjt-program-parser-v1.0.14", + "version": "ltjt-program-parser-v1.0.15", "received_at": "2026-08-25T09:00:00+08:00", "description": "脱敏的 19 路由确定性解析 gold corpus;只包含合成业务值。", "cases": [ diff --git a/schemas/standard_system_operation.schema.json b/schemas/standard_system_operation.schema.json index 7025afc..f5d0e13 100644 --- a/schemas/standard_system_operation.schema.json +++ b/schemas/standard_system_operation.schema.json @@ -54,7 +54,10 @@ "$ref": "#/$defs/operationData" } }, - "allOf": [ + "allOf": [{ + "if": { "properties": { "data": { "required": ["arrangements"] } }, "required": ["data"] }, + "then": { "$ref": "#/$defs/hotelBatch" }, + "else": { "allOf": [ { "if": { "properties": { "data": { "properties": { "arrangement": { "required": ["vehicle_type"] } }, "required": ["arrangement"] } }, "required": ["data"] }, "then": { "properties": { "action": { "const": "arrangement_vehicle" }, "data": { "properties": { "arrangement": { "properties": { "mode": { "enum": ["create", "update"] } } } } } } } @@ -1326,8 +1329,115 @@ } } } - ], + ] } }], "$defs": { + "hotelBatch": { + "title": "Program-only same-team hotel creation batch", + "type": "object", + "additionalProperties": false, + "required": [ + "action", + "order_nature", + "submit_mode", + "data" + ], + "properties": { + "action": { + "const": "arrangement_hotel" + }, + "order_nature": { + "const": "formal" + }, + "submit_mode": { + "const": "dry_run" + }, + "data": { + "type": "object", + "additionalProperties": false, + "required": [ + "existing_refs", + "arrangements" + ], + "properties": { + "existing_refs": { + "type": "object", + "additionalProperties": false, + "required": [ + "identifier" + ], + "properties": { + "identifier": { + "type": "string", + "minLength": 1 + }, + "kind": { + "enum": [ + "independent_order", + "shared_plan" + ] + } + } + }, + "arrangements": { + "type": "array", + "minItems": 2, + "maxItems": 10, + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "mode", + "resource", + "start_date", + "end_date", + "room_count" + ], + "properties": { + "mode": { + "const": "create" + }, + "resource": { + "type": "object", + "additionalProperties": false, + "required": ["name"], + "properties": { + "name": { + "type": "string", + "minLength": 1 + }, + "keyword": { + "type": "string", + "minLength": 1 + }, + "source_region": { + "type": "string", + "minLength": 1 + } + } + }, + "start_date": { + "type": "string", + "format": "date" + }, + "end_date": { + "type": "string", + "format": "date" + }, + "room_count": { + "type": "integer", + "minimum": 1 + }, + "remark": { + "type": "string", + "minLength": 1 + } + } + } + } + } + } + } + }, "operationData": { "type": "object", "additionalProperties": false, @@ -1416,6 +1526,7 @@ }, "additionalProperties": true }, + "arrangements": { "type": "array" }, "arrangement": { "$ref": "#/$defs/arrangement" }, "transition": { "$ref": "#/$defs/transition" }, "delete_guard": { "$ref": "#/$defs/deleteGuard" }, diff --git a/tools/business-input-templates.test.mjs b/tools/business-input-templates.test.mjs index a125359..6558f72 100644 --- a/tools/business-input-templates.test.mjs +++ b/tools/business-input-templates.test.mjs @@ -208,7 +208,7 @@ test('catalog has exactly 19 ordered operations with adjacent template and examp assert.deepEqual(operationSections.map((section) => section.directive), allDirectives); for (const pair of catalogPairs) { - assert.equal(pair.blocks.length, 2, `${pair.directive}: expected template and example`); + assert.equal(pair.blocks.length, pair.directive === '安排酒店' ? 3 : 2, `${pair.directive}: expected template and example (plus hotel batch example)`); assert.ok(pair.template, `${pair.directive}: missing template block`); assert.ok(pair.example, `${pair.directive}: missing example block`); assert.match(pair.template, /(必填):/u); @@ -220,7 +220,7 @@ test('catalog has exactly 19 ordered operations with adjacent template and examp assert.deepEqual(blocks.map((block) => block.split('\n')[0]), arrangementUpdateDirectives.flatMap((directive) => [directive, directive])); continue; } - assert.equal(textBlocks(section.body).length, 2); + assert.equal(textBlocks(section.body).length, section.directive === '安排酒店' ? 3 : 2); assert.ok(labeledBlock(section, '模板')); assert.ok(labeledBlock(section, '示例')); assert.ok(section.body.indexOf('#### 模板') < section.body.indexOf('#### 示例')); diff --git a/tools/hotel-batch.test.mjs b/tools/hotel-batch.test.mjs new file mode 100644 index 0000000..9e7d5bb --- /dev/null +++ b/tools/hotel-batch.test.mjs @@ -0,0 +1,129 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import vm from 'node:vm'; +import { readFileSync } from 'node:fs'; +import { createRequire } from 'node:module'; +import Ajv from 'ajv/dist/2020.js'; +import addFormats from 'ajv-formats'; +import { validateProgramOperation } from '../LianSyn-platform/external-agent-client.mjs'; +const require = createRequire(import.meta.url); +const plans = require('../chrome-extension/ltjt-order-assistant/operation-plans.js'); +const batch = require('../chrome-extension/ltjt-order-assistant/hotel-batch.js'); +const guard = require('../chrome-extension/ltjt-order-assistant/execution-guard.js'); +const timing = require('../chrome-extension/ltjt-order-assistant/operation-timing.js'); +const source = readFileSync(new URL('../chrome-extension/ltjt-order-assistant/background.js', import.meta.url), 'utf8'); +const slice = (a,b) => source.slice(source.indexOf(a),source.indexOf(b,source.indexOf(a))); +const operation = { action:'arrangement_hotel', order_nature:'formal', submit_mode:'dry_run', data:{ existing_refs:{ identifier:'LW-SAMPLE' }, + arrangements:[26,27,28].map(day => ({ mode:'create', resource:{ name:`酒店${day}TWN`, keyword:`酒店${day}TWN` }, start_date:`2026-09-${day}`, end_date:`2026-09-${day + 1}`, room_count:1 })) } }; +function result(item, index, status = 'completed') { + if (status === 'failed') return { status:'blocked', no_erp_write:true, write_attempted:false, message:'没有匹配候选' }; + if (status === 'uncertain') return { status:'execution_uncertain', no_erp_write:false, write_attempted:true }; + return { status:'completed', no_erp_write:false, write_attempted:true, report:{ status:'lifecycle_completed', resolved_refs:{ kind:'independent_order', identifier:'LW-SAMPLE' }, + server_response:{ completed:true }, requery:{ matched:true, subpage:{ arrangement_target:{ row_id:String(index + 1), quantity_value:'1' }, checks:[ + {field:'riqi0', actual:item.start_date, matched:true}, {field:'riqis0', actual:item.end_date, matched:true} ] } } } }; +} +function harness(failure = '', failureIndex = 1) { + const saved = { businessTaskExecutions:{ TASK:{ state:'running', execution_id:'EXEC' } }, businessTaskResults:{}, businessTaskTimingStates:{} }; + const calls = [], rootResults = []; + const context = { console, performance, Date, Map, Set, Promise, hotelBatch:batch, executionGuard:guard, operationTiming:timing, + operationPlans:plans, operationTimingDetailBuffers:new Map(), runningTasks:new Set(), + isTaskCancelled:()=>false, throwIfTaskCancelled:()=>{}, nowIso:()=>new Date().toISOString(), + withStorageMutation:fn=>fn(), queueTimedDetail:()=>{}, takeOperationTimingDetails:()=>[], + chrome:{ storage:{ local:{ get:async()=>structuredClone(saved), set:async patch=>{ + Object.assign(saved,structuredClone(patch)); + const root=saved.businessTaskResults.TASK; if(root)rootResults.push(structuredClone(root)); + } } } }, + executeLifecycleOperation:async(id, op) => { + calls.push(id); const index=calls.length-1; + const status=index===failureIndex && failure ? failure : 'completed'; + saved.businessTaskExecutions.TASK.state=status==='failed'?'submitted':'write_started'; + if(failure==='throw' && index===failureIndex) throw new Error('lost connection'); + await context.setResult(id,result(op.data.arrangement,index,status)); + if(status==='completed')saved.businessTaskExecutions.TASK.state='submitted'; + } + }; + vm.createContext(context); + vm.runInContext(slice('async function setResult(', 'async function publish(')+'\n'+slice('async function publishHotelBatch(', 'async function runAutoTask(')+'\n'+slice('async function reconcileHotelBatch(', 'async function reconcileLifecycleTask('),context); + return { context, saved, calls, rootResults }; +} +test('dispatch and schema accept only create batches with all ordinary per-row fields valid',()=>{ + const ajv=new Ajv({strict:false});addFormats(ajv); + const validate=ajv.compile(JSON.parse(readFileSync(new URL('../schemas/standard_system_operation.schema.json',import.meta.url)))); + assert.deepEqual(validateProgramOperation(operation),[]); + assert.equal(plans.validateDispatchOperation(operation).execution,'browser_hotel_batch'); + assert.equal(validate(operation),true,JSON.stringify(validate.errors)); + for(const mutate of [op=>{op.data.arrangements[2].room_count=0;},op=>{op.data.arrangements[1].mode='update';},op=>{op.data.arrangement=op.data.arrangements[0];},op=>{op.action='arrangement_vehicle';}]){ + const bad=structuredClone(operation);mutate(bad);assert.equal(plans.validateDispatchOperation(bad).ok,false);assert.equal(validate(bad),false); + } +}); +test('successful rows execute serially without prematurely finalizing the root task',async()=>{ + const h=harness();await h.context.executeHotelBatch('TASK',operation); + assert.deepEqual(h.calls,['TASK#hotel-1','TASK#hotel-2','TASK#hotel-3']); + assert.equal(h.saved.businessTaskExecutions.TASK.state,'completed'); + assert.equal(h.saved.businessTaskResults.TASK.status,'completed'); + assert.ok(h.rootResults.filter(row=>row.hotel_batch?.items.some(item=>item.status==='running')).every(row=>row.write_attempted===true && row.no_erp_write===false), 'a parent lease expiry must never infer no write from a still-running child'); + assert.ok(h.rootResults.filter(row=>row.status==='completed').every(row=>row.hotel_batch.items.every(item=>item.status==='completed'))); +}); +test('prewrite failure stops subsequent rows, retains saved prefix and stays known partial failure',async()=>{ + const h=harness('failed');await h.context.executeHotelBatch('TASK',operation); + assert.equal(h.calls.length,2);const root=h.saved.businessTaskResults.TASK; + assert.equal(root.status,'blocked');assert.equal(root.no_erp_write,false);assert.equal(root.write_attempted,true); + assert.deepEqual(root.hotel_batch.items.map(item=>item.status),['completed','failed','not_started']); + assert.equal(h.saved.businessTaskExecutions.TASK.state,'blocked');assert.match(root.message,/未执行 1 条/); +}); +test('uncertain or missing response stops without retry and preserves both prefix and pending tail',async()=>{ + for(const failure of ['uncertain','throw']){ + const h=harness(failure);await h.context.executeHotelBatch('TASK',operation); + assert.equal(h.calls.length,2);assert.equal(h.saved.businessTaskResults.TASK.status,'execution_uncertain'); + assert.equal(h.saved.businessTaskResults.TASK.hotel_batch.items[2].status,'not_started'); + } +}); + +test('read-only reconciliation resolves only the uncertain row and never starts the pending tail',async()=>{ + const h=harness('uncertain');await h.context.executeHotelBatch('TASK',operation); + const root=h.saved.businessTaskResults.TASK; + const item=root.hotel_batch.items[1]; + const proof=result(item.arrangement,1); + item.result.report={...proof.report,status:'saved_unverified',requery:{matched:false}}; + h.saved.businessTasks={'TASK#hotel-2':{operation:{...operation,data:{existing_refs:{identifier:'LW-SAMPLE'},arrangement:item.arrangement}}}}; + h.context.operationPlans={...plans,validateOperation:()=>({ok:true,execution:'browser_lifecycle'})}; + h.context.findOrOpenErpTab=async()=>({id:1}); + let reads=0; + h.context.runLifecycleReadOnlyVerification=async()=>{reads++;return {resolved:true,verification:{requery:proof.report.requery}};}; + const reply=await h.context.reconcileHotelBatch('TASK',operation,root,h.saved); + assert.equal(reply.status,'blocked');assert.equal(reply.reconciliation_resolved,true); + assert.equal(reads,1);assert.equal(h.calls.length,2); + assert.deepEqual(h.saved.businessTaskResults.TASK.hotel_batch.items.map(row=>row.status),['completed','completed','not_started']); + assert.equal(h.saved.businessTaskExecutions.TASK.state,'blocked'); +}); +test('timeout preserves an already-saved child and classifies a missing child as uncertain without replay',async()=>{ + for(const savedChild of [true,false]){ + const h=harness(); + const pending={version:1,group_number:'LW-SAMPLE',items:operation.data.arrangements.map((arrangement,index)=>({index,arrangement,status:index===0?'completed':index===1?'running':'not_started',...(index===0?{result:result(arrangement,0)}:{})}))}; + await h.context.publishHotelBatch('TASK',operation,pending); + if(savedChild)h.saved.businessTaskResults['TASK#hotel-2']=result(operation.data.arrangements[1],1); + assert.equal(await h.context.stopHotelBatch('TASK',operation,'timeout'),true); + const root=h.saved.businessTaskResults.TASK; + assert.equal(root.status,savedChild?'blocked':'execution_uncertain'); + assert.equal(root.hotel_batch.items[0].status,'completed'); + assert.equal(root.hotel_batch.items[1].status,savedChild?'completed':'uncertain'); + assert.equal(root.hotel_batch.items[2].status,'not_started');assert.equal(h.calls.length,0); + } +}); + + +test('production Docker runtime includes the shared batch evidence validator',()=>{ + const dockerfile=readFileSync(new URL('../Dockerfile',import.meta.url),'utf8'); + const runtime=dockerfile.split('AS runtime')[1]; + assert.match(runtime,/COPY[^\n]*chrome-extension\/ltjt-order-assistant\/hotel-batch\.js[^\n]*\.\/chrome-extension\/ltjt-order-assistant\//); +}); + + +test('a proven prewrite failure on the first row reports zero writes and leaves the rest untouched',async()=>{ + const h=harness('failed',0);await h.context.executeHotelBatch('TASK',operation); + const root=h.saved.businessTaskResults.TASK; + assert.equal(h.calls.length,1);assert.equal(root.status,'blocked'); + assert.equal(root.no_erp_write,true);assert.equal(root.write_attempted,false); + assert.deepEqual(root.hotel_batch.items.map(item=>item.status),['failed','not_started','not_started']); + assert.match(root.message,/未完成/); +}); diff --git a/tools/lifecycle-contract.test.mjs b/tools/lifecycle-contract.test.mjs index fb5ecee..180746d 100644 --- a/tools/lifecycle-contract.test.mjs +++ b/tools/lifecycle-contract.test.mjs @@ -1374,7 +1374,7 @@ test('passenger explicit server success is terminal without a post-save row requ assert.doesNotMatch(passengerBranch, /passengerRequery|verifyLifecycleOperation/); assert.doesNotMatch(background, /attemptAutomaticPassengerReconciliation/); assert.match(background, /名单已取得 ERP 明确成功响应,按业务规则确认录入成功/); - assert.match(platformApp, /REQUIRED_EXTENSION_VERSION = '0\.5\.189'/); + assert.match(platformApp, /REQUIRED_EXTENSION_VERSION = '0\.5\.190'/); }); test('uncertain lifecycle writes can only converge through a read-only plugin requery', async () => { @@ -1584,7 +1584,7 @@ test('lifecycle execution keeps the MV3 worker alive until the durable task sett assert.match(background, /arrangement_resource_candidate_data_missing/); }); -test('extension 0.5.189 keeps reload dormant unless its durable execution state is fully idle', async () => { +test('extension 0.5.190 keeps reload dormant unless its durable execution state is fully idle', async () => { const [background, bridge] = await Promise.all([ readFile(new URL('../chrome-extension/ltjt-order-assistant/background.js', import.meta.url), 'utf8'), readFile(new URL('../chrome-extension/ltjt-order-assistant/business-bridge.js', import.meta.url), 'utf8') @@ -2037,7 +2037,7 @@ test('delete guard distinguishes independent, shared child, and shared parent ro for (const operation of operations) assert.equal(plans.validateOperation(operation).ok, true, plans.validateOperation(operation).blockers.join('; ')); const mapping = JSON.parse(await readFile(new URL('../mappings/lifecycle.mapping.json', import.meta.url), 'utf8')); - assert.equal(mapping.current_extension_version, '0.5.189'); + assert.equal(mapping.current_extension_version, '0.5.190'); assert.equal( mapping.updates.order_update_independent.field_mapped_pending_live_validation['pax.child_no_bed'], 'ertrenshu' @@ -2421,10 +2421,10 @@ test('schema and browser adapters contain the v2 safety fields and no confirm ov assert.match(inpage, /`ys_danweiid\$\{index\}`, resolvedCustomerId/); assert.doesNotMatch(inpage, /product_customer_source_region|sourceRegionCheck|source_reference/); assert.doesNotMatch(teamBatchInpage, /product_customer_source_region|sourceRegionCheck/); - assert.equal(extensionManifest.version, '0.5.189'); - assert.match(inpage, /version: '0\.5\.189'/); - assert.match(teamBatchInpage, /version: '0\.5\.189'/); - assert.match(platformApp, /REQUIRED_EXTENSION_VERSION = '0\.5\.189'/); + assert.equal(extensionManifest.version, '0.5.190'); + assert.match(inpage, /version: '0\.5\.190'/); + assert.match(teamBatchInpage, /version: '0\.5\.190'/); + assert.match(platformApp, /REQUIRED_EXTENSION_VERSION = '0\.5\.190'/); assert.match(inpage, /function strictIsoDate\(value\)/); assert.match(inpage, /const startDate = strictIsoDate\(current\('riqi'\)\)/); assert.match(inpage, /const endDate = strictIsoDate\(current\('riqis'\)\)/); diff --git a/tools/repository-hygiene.test.mjs b/tools/repository-hygiene.test.mjs index d4fa84e..ac42f9c 100644 --- a/tools/repository-hygiene.test.mjs +++ b/tools/repository-hygiene.test.mjs @@ -304,7 +304,7 @@ test('single-source and generated-output boundaries remain explicit', async () = const diagnoseServer = await readFile(path.join(ROOT, 'infra/diagnose-server.sh'), 'utf8'); const gitignore = await readFile(path.join(ROOT, '.gitignore'), 'utf8'); - assert.match(template, /^交付版本:0\.5\.136\s*$/mu); + assert.match(template, /^交付版本:0\.5\.137\s*$/mu); assert.match(template, /^适用范围:当前 23 条业务路由,按 19 项运营场景分组;执行能力以生命周期发布门槛为准。\s*$/mu); assert.match(template, /^### 5\. 散拼团多个新增子单\s*$/mu); assert.doesNotMatch(template, /^事实基线:/mu);