修复(08-28-production-arm-backup-transfer-incident): 纠正partial停止偏移分类

这个提交包含在:
2026-08-28 19:52:59 +08:00
父节点 d6a2b85dc2
当前提交 1bb066ddb7
共修改 5 个文件,包含 75 行新增和 7 行删除
@@ -2857,11 +2857,21 @@ current shared mapping -> fine-grained stages + mutation listeners
offsets or durations indicate a deterministic limit; materially different offsets or durations indicate a deterministic limit; materially different
offsets/durations favor intermittent timeout or network interruption. Do not offsets/durations favor intermittent timeout or network interruption. Do not
infer this split from service exit `1`, which is the wrapper's generic code. infer this split from service exit `1`, which is the wrapper's generic code.
- A stopping-offset pair is `similar` only when its absolute difference is no
more than both `50000 ppm` (5% of the larger aggregate) and `67108864` bytes
(64 MiB), equivalently `difference <= min(relative_limit, absolute_limit)`.
Using `max(...)` is forbidden: it misclassifies a small nonzero partial and a
zero-byte partial as similar merely because both are below 64 MiB.
- `last-deadman.json` proves the detector decision. `deadman-alert-state.json` - `last-deadman.json` proves the detector decision. `deadman-alert-state.json`
proves the deduplication state. A journal allowlist line with proves the deduplication state. A journal allowlist line with
`evaluated sent=true` proves the helper attempted and accepted one send; it `evaluated sent=true` proves the helper attempted and accepted one send; it
does not prove a human noticed the message. Repeated `sent=false` for the same does not prove a human noticed the message. Repeated `sent=false` for the same
failure is normal deduplication, not a monitoring blind spot. failure is normal deduplication, not a monitoring blind spot.
- Keep the two JSON schemas distinct: the dead-man summary contains
`status/category/findingCount/findings`, while the alert deduplication state
contains only `schemaVersion/status/category`. A probe must not request
`findingCount` from the alert state or report its contractual absence as an
invalid remote state.
- Read-only investigation output is an allowlist of status/category/count/age - Read-only investigation output is an allowlist of status/category/count/age
and capacity fields. Never print unit command lines, raw JSON, raw journal, and capacity fields. Never print unit command lines, raw JSON, raw journal,
remote identities, paths, run ids, webhook data or diagnostic stderr. remote identities, paths, run ids, webhook data or diagnostic stderr.
@@ -2961,6 +2971,8 @@ current shared mapping -> fine-grained stages + mutation listeners
rsync codes `23/30/12/11/255`; assert the incident summary preserves code and rsync codes `23/30/12/11/255`; assert the incident summary preserves code and
elapsed-time evidence separately from wrapper exit `1` and does not output elapsed-time evidence separately from wrapper exit `1` and does not output
file names. file names.
- Assert `2.68 MB` versus zero is `different`, a large pair within both 5% and
64 MiB is `similar`, and exceeding either threshold is `different`.
- Fixture one complete source batch, no complete source batch, checksum/size - Fixture one complete source batch, no complete source batch, checksum/size
corruption and malformed manifest. Assert the risk classification and corruption and malformed manifest. Assert the risk classification and
transition recommendation change without emitting paths, run ids, artifact transition recommendation change without emitting paths, run ids, artifact
@@ -2987,6 +2999,7 @@ state path test returned false -> state missing
partial count > 0 -> finalize failed partial count > 0 -> finalize failed
off-host stale -> assume one complete backup still exists on production off-host stale -> assume one complete backup still exists on production
service exit 1 -> call it the rsync exit code and use service duration service exit 1 -> call it the rsync exit code and use service duration
partial difference <= max(5% limit, 64 MiB) -> similar
``` ```
#### Correct #### Correct
@@ -3000,4 +3013,5 @@ derive unit identity from installer -> timer trigger + service Result/exit
-> safe scheduler/source/transfer/finalize/alert conclusion -> safe scheduler/source/transfer/finalize/alert conclusion
-> validate the source batch first and classify single-point vs no proven point -> validate the source batch first and classify single-point vs no proven point
-> bind retained diagnostics to the safe failure phase; unavailable clocks stay unavailable -> bind retained diagnostics to the safe failure phase; unavailable clocks stay unavailable
-> partial difference <= min(5% limit, 64 MiB) before calling it similar
``` ```
@@ -34,6 +34,7 @@
- 生产侧先用 manifest/三文件/身份/字节元数据枚举全部本地候选,只对最新一个元数据合格候选完整读取 database/storage 并计算 SHA-256;旧候选明确输出 `older_metadata_batch_hash_status=NOT_RUN`,不得为了计数连续读取数 GiB。最新候选只有通过完整 SHA-256 合同才输出 `complete_local_recovery_point=yes`;其哈希失败时不得现场回退扫描旧候选,而应保守标为“无已证明可用备份”。输出年龄、元数据合格数量和聚合大小,不输出 run id、文件名或路径。该结果先于传输根因分析,决定当前是“生产单点”还是“无已证明可用备份”。ARM 仍对正式归档执行完整哈希合同。 - 生产侧先用 manifest/三文件/身份/字节元数据枚举全部本地候选,只对最新一个元数据合格候选完整读取 database/storage 并计算 SHA-256;旧候选明确输出 `older_metadata_batch_hash_status=NOT_RUN`,不得为了计数连续读取数 GiB。最新候选只有通过完整 SHA-256 合同才输出 `complete_local_recovery_point=yes`;其哈希失败时不得现场回退扫描旧候选,而应保守标为“无已证明可用备份”。输出年龄、元数据合格数量和聚合大小,不输出 run id、文件名或路径。该结果先于传输根因分析,决定当前是“生产单点”还是“无已证明可用备份”。ARM 仍对正式归档执行完整哈希合同。
- 对成功与失败路径分别输出本地完整批次、生成期临时对象和保留策略的聚合结果,明确 `retained_after_failure`、`pruned_after_success` 和连续失败累积趋势;不从单次目录计数猜测脚本语义。 - 对成功与失败路径分别输出本地完整批次、生成期临时对象和保留策略的聚合结果,明确 `retained_after_failure`、`pruned_after_success` 和连续失败累积趋势;不从单次目录计数猜测脚本语义。
- partial 只输出每个匿名样本的总字节数、预期总字节数、安全比例和年龄;不输出名称、路径、run id 或 artifact 名称。 - partial 只输出每个匿名样本的总字节数、预期总字节数、安全比例和年龄;不输出名称、路径、run id 或 artifact 名称。
- partial 停止偏移的“接近”必须同时满足相对差异不超过 `50000 ppm`(5%)和绝对差异不超过 `67108864` bytes(64 MiB),等价于以两者较小值作为上限。不得用两者较大值,否则小样本与零字节样本会被绝对阈值错误吞并为 `similar`。
- 生产私有诊断只输出固定的 `transfer_exit_code`、`failure_category`、开始/结束 epoch 和持续秒数。若现有状态没有持久化真实 rsync 码或开始时刻,记录 `NOT_AVAILABLE`,需要本地设计可观测性修订并另行批准部署,不能现场改脚本。 - 生产私有诊断只输出固定的 `transfer_exit_code`、`failure_category`、开始/结束 epoch 和持续秒数。若现有状态没有持久化真实 rsync 码或开始时刻,记录 `NOT_AVAILABLE`,需要本地设计可观测性修订并另行批准部署,不能现场改脚本。
- 容量只输出 filesystem 使用率/可用 GiB、完整/不完整/临时批次数量和聚合字节,不列目录。 - 容量只输出 filesystem 使用率/可用 GiB、完整/不完整/临时批次数量和聚合字节,不列目录。
- 采集器先探测账号能力;无 sudo 时不得尝试临时扩权。需要 root-owned 状态时先提交固定 allowlist 入口设计并单独审批。 - 采集器先探测账号能力;无 sudo 时不得尝试临时扩权。需要 root-owned 状态时先提交固定 allowlist 入口设计并单独审批。
@@ -93,3 +94,12 @@ PLANNING
- 腾讯云生产和 ARM 地址、账号、绝对现场路径、完整 run id/digest、密钥、known_hosts、webhook、原始日志和业务内容不进入仓库或聊天。 - 腾讯云生产和 ARM 地址、账号、绝对现场路径、完整 run id/digest、密钥、known_hosts、webhook、原始日志和业务内容不进入仓库或聊天。
- 远端临时探针若获批,固定目标、SHA-256、语法、sudoers 和自清理均需机械校验,并由外层独立报告入口/暂存残留。 - 远端临时探针若获批,固定目标、SHA-256、语法、sudoers 和自清理均需机械校验,并由外层独立报告入口/暂存残留。
- timer `active`、partial 存在、单次备份成功或告警 `sent=true` 都是局部证据,不能扩写为链路整体正常。 - timer `active`、partial 存在、单次备份成功或告警 `sent=true` 都是局部证据,不能扩写为链路整体正常。
## 2026-08-28 只读调查裁决
- 生产本地存在一个通过完整哈希合同的最新可用批次,风险分类为 `production_single_point`,不是“当前无已证明可用备份”;元数据合格批次 7 个,失败传输后保留本地批次、异机成功后才 prune,过渡动作保持 `wait_natural`。
- ARM 原始调查输出曾将两个 partial 判为 `similar`。该输出保留作为原始证据;现场聚合字节为 `2682168` 与 `0`,按修正后的“双阈值同时满足”合同人工重算为 `different`。一个老 partial 已满足完整合同但未 finalize,另一个较新 partial 为零字节,两者不支持“固定相同偏移的确定性传输上限”,且可能来自不同失败类型。
- 现有状态未持久化本次失败的 rsync 原始退出码和传输专属时钟,均保持 `NOT_AVAILABLE`;只读证据不足以证明单一根因。若后续自然周期再次失败,按既定边界转为单独审批的一次受控观测性重传,不扩大只读读取范围来补造结论。
- 调查时 ARM 已出现约 12 小时 43 分的新鲜完整原子归档,dead-man 最近 service 为 `success/0` 且状态为 healthy,说明异机链已自然恢复一次;该事实可解除恢复演练的事件阻塞,但不满足本 P1 事件的两个自然周期结案条件。
- 私有探针输出的 `arm_deadman_alert_state_finding_count=invalid` 是表示缺陷,不是 ARM 状态损坏:`last-deadman.json` summary 才有 `findingCount`,`deadman-alert-state.json` 按稳定 helper 合同只保存 `schemaVersion/status/category`。后续探针不得对 alert state 索取不存在的 count 字段。
- ARM health service 同期为 `failed/1`,而生产公网 `/ready` 调查前后均为 PASS、12 位构建前缀一致。该 finding 指向健康监控执行、状态或通知链,不得扩写为 OA 当前不可用;进一步读取或处置需另行授权。
@@ -11,22 +11,40 @@
- [x] 从 `scripts/run-automatic-backup.sh`、receiver、dead-man 和 systemd installer 提取真实单位名、状态路径来源、阶段和退出码合同,禁止手写近似常量。 - [x] 从 `scripts/run-automatic-backup.sh`、receiver、dead-man 和 systemd installer 提取真实单位名、状态路径来源、阶段和退出码合同,禁止手写近似常量。
- [x] 实现只输出 allowlist 的跟踪只读采集器:生产本地完整恢复点合同、本地成功/失败清理保留语义、partial 匿名字节比例、rsync 原始码、无法取得的传输时钟、两端容量;timer/service 继续复用既有只读 systemd 聚合合同,不在 Python 中另写一套。 - [x] 实现只输出 allowlist 的跟踪只读采集器:生产本地完整恢复点合同、本地成功/失败清理保留语义、partial 匿名字节比例、rsync 原始码、无法取得的传输时钟、两端容量;timer/service 继续复用既有只读 systemd 聚合合同,不在 Python 中另写一套。
- [x] 为本地完整/无完整批次、成功清理/失败保留、相近/不同停止偏移、rsync `23/30/12/11/255`、缺失持续时间、权限拒绝、畸形 manifest 和敏感输出补合成回归。 - [x] 为本地完整/无完整批次、成功清理/失败保留、相近/不同停止偏移、rsync `23/30/12/11/255`、缺失持续时间、权限拒绝、畸形 manifest 和敏感输出补合成回归。
- [ ] 本地语法、合同、敏感扫描全部通过后,提交远端只读调查摘要与精确权限边界,单独申请执行批准。 - [x] 本地语法、合同、敏感扫描全部通过后,提交远端只读调查摘要与精确权限边界,单独申请执行批准。
### 本地准备结果(2026-08-28) ### 本地准备结果(2026-08-28)
- 跟踪调查器:`scripts/qa/inspect-backup-transfer-incident.py`;仅接受显式绝对路径参数,不回显参数值,未知异常也只输出稳定类别。 - 跟踪调查器:`scripts/qa/inspect-backup-transfer-incident.py`;仅接受显式绝对路径参数,不回显参数值,未知异常也只输出稳定类别。
- 可复跑合同:`scripts/qa/run-backup-transfer-incident-contract.sh`;19 项合成检查通过,fixture 与 Python cache 零残留;权限拒绝不会被误写成“无有效备份”,其他阶段的旧日志不会冒充本轮 rsync 诊断;生产多个批次时只哈希最新元数据合格候选,最新哈希失败不回退扫描旧批次。 - 可复跑合同:`scripts/qa/run-backup-transfer-incident-contract.sh`;初始 19 项合成检查通过;现场分类缺陷修复后扩展为 20 项,fixture 与 Python cache 零残留。权限拒绝不会被误写成“无有效备份”,其他阶段的旧日志不会冒充本轮 rsync 诊断;生产多个批次时只哈希最新元数据合格候选,最新哈希失败不回退扫描旧批次;停止偏移必须同时满足 5% 和 64 MiB 双阈值。
- 既有 `run-automatic-backup-contract.sh` 74 项通过;生产 wrapper 未改。现状没有持久化传输专属开始/结束时刻,故持续时间必须为 `NOT_AVAILABLE`。 - 既有 `run-automatic-backup-contract.sh` 74 项通过;生产 wrapper 未改。现状没有持久化传输专属开始/结束时刻,故持续时间必须为 `NOT_AVAILABLE`。
- rsync 原始码只在安全摘要明确为 `phase_offhost_transfer_failed` 时从 root 私有 `last-error.log` 固定正则提取,避免把 paired-backup/finalize 的旧诊断错误归因给传输。 - rsync 原始码只在安全摘要明确为 `phase_offhost_transfer_failed` 时从 root 私有 `last-error.log` 固定正则提取,避免把 paired-backup/finalize 的旧诊断错误归因给传输。
## Phase 2:获批的远端只读调查 ## Phase 2:获批的远端只读调查
- [ ] 在不修改生产/ARM 的前提下采集四类证据;每个临时入口在同轮精确清理并由外层独立复核零残留。 - [x] 在不修改生产/ARM 的前提下采集四类证据;每个临时入口在同轮精确清理并由外层独立复核零残留。
- [ ] 先判定“生产单点”或“无已证明可用备份”,再交叉判定主要根因、次要候选、清理/累积语义和容量预算;无法取得的字段标为 `NOT_AVAILABLE/NOT_MEASURED`。 - [x] 先判定“生产单点”或“无已证明可用备份”,再交叉判定主要根因、次要候选、清理/累积语义和容量预算;无法取得的字段标为 `NOT_AVAILABLE/NOT_MEASURED`。
- [ ] 记录过渡恢复点决策;只读证据无法收敛时停止扩读,提交一次受控观测性重传的独立审批方案,不直接执行。 - [x] 记录过渡恢复点决策;只读证据无法收敛时停止扩读,提交一次受控观测性重传的独立审批方案,不直接执行。
- [ ] 更新本 task 证据,给出单一最小修复方案、回滚方案和生产写操作清单;停止并请求用户审阅。 - [ ] 更新本 task 证据,给出单一最小修复方案、回滚方案和生产写操作清单;停止并请求用户审阅。
### 远端只读调查结果(2026-08-28,追加时间线)
- 公网 `/ready` 调查前后均 PASS,12 位构建前缀均为 `7b86ffb3c9ef`;生产固定入口与 staging 经外层独立枚举均为 0,ARM `arm_independent_residue=0`。调查未改变在线 OA、正式归档、timer 或业务数据。
- 生产:`complete_local_recovery_point=yes`、`backup_risk_classification=production_single_point`;完整哈希通过的最新批次 1 个、元数据完整批次 7 个,最新候选年龄 42885 秒、聚合 11400180 bytes;生产文件系统 82% 使用、10 GiB 可用。源码顺序证明传输失败保留完整本地批次、异机成功后 prune、生成临时对象在失败路径清理,故过渡动作是 `wait_natural`。
- 生产最近 daily/watchdog service 均为 `success/0`、timer active;历史失败摘要未保留可绑定到本轮的 rsync 原始码或传输专属时钟,因此退出码和时长均为 `NOT_AVAILABLE`,不得用 service exit、mtime 或对话时刻替代。
- ARM 原始输出:partial 2 个,聚合 2682168 bytes,样本总字节分别为 2682168 与 0;调查器当时输出 `arm_partial_stop_offset_class=similar`。该原始结果不回写。
- 分类缺陷修订:旧实现以 `max(64 MiB, 5%)` 为接近上限,使 2.68 MB 与 0 字节被 64 MiB 阈值错误吞并。按固定阈值必须同时满足的合同,人工重算为 `different`;一个约 32 日龄 partial 满足完整合同但未 finalize,另一个约 36 小时龄 partial 为零字节,不能据此声称两次都停在同一确定性偏移。
- ARM 正式原子归档 7 个、无无效归档,最新完整归档年龄 45756 秒;inbox/archive 文件系统 54% 使用、89 GiB 可用;dead-man service `success/0`、timer active、最近状态 `PASS_OFFHOST_DEADMAN_FRESH/healthy`。链路已自然恢复一个新鲜原子批次,可解除恢复演练的事件阻塞,但 P1 仍需第二个连续自然周期及容量验收后才能结案。
- `arm_deadman_alert_state_finding_count=invalid` 不表示远端状态损坏。仓库 helper 明确把 alert state 写成仅含 `schemaVersion/status/category` 的去抖状态;`findingCount` 属于 `last-deadman.json` summary。私有探针错误复用了 summary 读取模板,后续证据不得把这个表示缺陷升级为告警链 finding。
- 独立 finding:ARM health service 为 `failed/1`,但公网 `/ready` 实测前后一致且 PASS。这是健康监控执行/状态/通知链问题,不是 OA 当前不可用的证据;本次授权不覆盖进一步调查或修改。
- 根因裁决:历史单一 rsync 原始码和可靠传输时长不可得,两个 partial 的真实停止位置明显不同,现有证据不能收敛为单一根因。不得把自然恢复一次冒充“已实施修复”;后续若再失败,候选动作是另行批准的一次受控观测性重传。生产 10 GiB 余量仍需独立容量方案。
### 分类缺陷复盘
- 根因类别是“隐含假设 + 测试覆盖缺口”:实现用 `max(absolute, relative)` 把两个独立上限误写成二选一宽松上限;旧正例夹具只证明“绝对差异较小”,没有证明相对差异也在 5% 内,因而为错误实现背书。
- 修复机制已闭环:实现改为 `min(...)`;合同直接覆盖现场 `2682168/0`、同时满足双阈值的正例、分别越过相对/绝对阈值的负例;稳定 spec 写明公式、错误写法与断言点。仓库搜索确认该分类公式只有这一个实现位置,不存在需要同步的第二套逻辑。
- 原始远端 `similar` 保留,人工修订追加为 `different`;以后任何现场分类缺陷都按同样的不可变时间线处理,不回写旧证据。
## Phase 3:本地修复与变更审批 ## Phase 3:本地修复与变更审批
- [ ] 仅修改证据支持的脚本/installer/合同;不轮换已通过初始认证的密钥,不放宽 manifest/finalize 门禁。 - [ ] 仅修改证据支持的脚本/installer/合同;不轮换已通过初始认证的密钥,不放宽 manifest/finalize 门禁。
@@ -432,7 +432,8 @@ def stopping_class(samples: list[BatchResult]) -> str:
sizes = sorted(item.total_bytes for item in samples[-2:]) sizes = sorted(item.total_bytes for item in samples[-2:])
difference = sizes[1] - sizes[0] difference = sizes[1] - sizes[0]
relative_limit = sizes[1] * SIMILAR_RELATIVE_PPM // 1_000_000 relative_limit = sizes[1] * SIMILAR_RELATIVE_PPM // 1_000_000
return "similar" if difference <= max(SIMILAR_ABSOLUTE_BYTES, relative_limit) else "different" similar_limit = min(SIMILAR_ABSOLUTE_BYTES, relative_limit)
return "similar" if difference <= similar_limit else "different"
def arm(args: argparse.Namespace) -> None: def arm(args: argparse.Namespace) -> None:
@@ -239,7 +239,7 @@ pass "私有诊断只在安全摘要明确为 offhost_transfer 时绑定 rsync
ARM_SIMILAR="$TEST_ROOT/arm-similar" ARM_SIMILAR="$TEST_ROOT/arm-similar"
mkdir -p "$ARM_SIMILAR/inbox" "$ARM_SIMILAR/archive" mkdir -p "$ARM_SIMILAR/inbox" "$ARM_SIMILAR/archive"
make_batch "$ARM_SIMILAR/inbox/hyb-oa-backup-auto-20260827T000000Z.partial" "auto-20260827T000000Z" 1000 1000 make_batch "$ARM_SIMILAR/inbox/hyb-oa-backup-auto-20260827T000000Z.partial" "auto-20260827T000000Z" 1000 1000
make_batch "$ARM_SIMILAR/inbox/hyb-oa-backup-auto-20260828T000000Z.partial" "auto-20260828T000000Z" 1100 1000 make_batch "$ARM_SIMILAR/inbox/hyb-oa-backup-auto-20260828T000000Z.partial" "auto-20260828T000000Z" 1040 1000
rm "$ARM_SIMILAR/inbox/hyb-oa-backup-auto-20260827T000000Z.partial/storage.tar.gz" rm "$ARM_SIMILAR/inbox/hyb-oa-backup-auto-20260827T000000Z.partial/storage.tar.gz"
rm "$ARM_SIMILAR/inbox/hyb-oa-backup-auto-20260828T000000Z.partial/storage.tar.gz" rm "$ARM_SIMILAR/inbox/hyb-oa-backup-auto-20260828T000000Z.partial/storage.tar.gz"
python3 -B "$INSPECTOR" --now-epoch 1787875200 arm --inbox-root "$ARM_SIMILAR/inbox" --archive-root "$ARM_SIMILAR/archive" >"$TEST_ROOT/arm-similar.out" python3 -B "$INSPECTOR" --now-epoch 1787875200 arm --inbox-root "$ARM_SIMILAR/inbox" --archive-root "$ARM_SIMILAR/archive" >"$TEST_ROOT/arm-similar.out"
@@ -258,6 +258,31 @@ python3 -B "$INSPECTOR" --now-epoch 1787875200 arm --inbox-root "$ARM_DIFFERENT/
assert_line "$TEST_ROOT/arm-different.out" "arm_partial_stop_offset_class=different" assert_line "$TEST_ROOT/arm-different.out" "arm_partial_stop_offset_class=different"
pass "两个 partial 明显不同停止偏移分类为 different" pass "两个 partial 明显不同停止偏移分类为 different"
python3 -B - "$INSPECTOR" <<'PY'
import importlib.util
import sys
spec = importlib.util.spec_from_file_location("backup_transfer_incident", sys.argv[1])
module = importlib.util.module_from_spec(spec)
sys.modules[spec.name] = module
spec.loader.exec_module(module)
def sample(total_bytes):
return module.BatchResult(False, total_bytes, None, None, total_bytes)
cases = [
("现场量级的 2.68MB 与 0 字节必须不同", [sample(0), sample(2_682_168)], "different"),
("大文件差异同时落在 5% 与 64MiB 内才接近", [sample(1_000_000_000), sample(1_040_000_000)], "similar"),
("即使落在 64MiB 内但超过 5% 仍不同", [sample(10_000_000), sample(10_600_000)], "different"),
("即使落在 5% 内但超过 64MiB 仍不同", [sample(2_000_000_000), sample(2_080_000_000)], "different"),
]
for label, samples, expected in cases:
actual = module.stopping_class(samples)
if actual != expected:
raise SystemExit(f"{label}: expected {expected}, got {actual}")
PY
pass "停止偏移分类同时满足相对与绝对阈值并覆盖现场 2.68MB/0 字节回归"
ARM_COMPLETE="$TEST_ROOT/arm-complete" ARM_COMPLETE="$TEST_ROOT/arm-complete"
mkdir -p "$ARM_COMPLETE/inbox" "$ARM_COMPLETE/archive" mkdir -p "$ARM_COMPLETE/inbox" "$ARM_COMPLETE/archive"
make_batch "$ARM_COMPLETE/inbox/hyb-oa-backup-auto-20260828T000000Z.partial" "auto-20260828T000000Z" make_batch "$ARM_COMPLETE/inbox/hyb-oa-backup-auto-20260828T000000Z.partial" "auto-20260828T000000Z"