From 29e5c052e5830ff03fdca81dac33316edd2ec3b6 Mon Sep 17 00:00:00 2001 From: homelab-runtime-publisher Date: Mon, 21 Sep 2026 03:50:42 +0000 Subject: [PATCH] runtime: publish SUPPORT-260921-HOMELAB-BACKUP-TARGETED-CONTINUATION-1047R1 --- ...B-BACKUP-TARGETED-CONTINUATION-1047R1.json | 38 ++++++++++++++ ...AB-BACKUP-TARGETED-CONTINUATION-1047R1.txt | 50 ++++++++++++++++++ runtime/latest.json | 36 ++++++------- runtime/latest.txt | 51 ++++++++++++------- 4 files changed, 139 insertions(+), 36 deletions(-) create mode 100644 runtime/history/SUPPORT-260921-HOMELAB-BACKUP-TARGETED-CONTINUATION-1047R1.json create mode 100644 runtime/history/SUPPORT-260921-HOMELAB-BACKUP-TARGETED-CONTINUATION-1047R1.txt diff --git a/runtime/history/SUPPORT-260921-HOMELAB-BACKUP-TARGETED-CONTINUATION-1047R1.json b/runtime/history/SUPPORT-260921-HOMELAB-BACKUP-TARGETED-CONTINUATION-1047R1.json new file mode 100644 index 00000000..ce1f7084 --- /dev/null +++ b/runtime/history/SUPPORT-260921-HOMELAB-BACKUP-TARGETED-CONTINUATION-1047R1.json @@ -0,0 +1,38 @@ +{ + "schema_version": 1, + "channel": "homelab-runtime", + "command_id": "SUPPORT-260921-HOMELAB-BACKUP-TARGETED-CONTINUATION-1047R1", + "status": "FAIL", + "rc": 1, + "host": "pve01", + "mode": "change", + "component": "homelab-backup-targeted-continuation", + "started_at_utc": "2026-09-21T03:50:39Z", + "finished_at_utc": "2026-09-21T03:50:41Z", + "reference_register_checked": true, + "reference_sha256": "5a3d8e5154c41cb582a4e0aca68090be1f0138918bf82131a948df326f9d8d66", + "error_register_checked": true, + "error_register_sha256": "3b09a553ec0f527ed3afeed4753f52a74ee3036045bbfb3c685e9f8af4ba7ba0", + "command_sha256": "82608ea4b9da22360373565f61912c6ab5f62cdf8ca0c7dc9020cfaba9117924", + "duplicate_failed_command_blocked": false, + "block_reason": null, + "execution_started": true, + "change_declared": true, + "result_contract_valid": true, + "result_contract_status": "FAIL", + "result_contract_error": null, + "command_rc": 1, + "changes_made": true, + "rollback_started": true, + "rollback_restored": true, + "mutation_outcome": "ROLLED_BACK", + "sanitized": true, + "secrets_included": false, + "private_addresses_included": false, + "raw_evidence_retained_locally": true, + "raw_evidence_sha256": "cc7d08ca13508be1d1a71150dfcf653da21a6bac9973cb87d778b15ba1655360", + "sanitized_output_sha256": "cc7d08ca13508be1d1a71150dfcf653da21a6bac9973cb87d778b15ba1655360", + "output_truncated_in_json": false, + "full_sanitized_output_url": "https://git.gram1.ru/.well-known/homelab-runtime/latest.txt", + "output": "WORKERS2_BACKUP_TARGETED_CONTINUATION_BEGIN=1\nCOMMAND_ID=SUPPORT-260921-HOMELAB-BACKUP-TARGETED-CONTINUATION-1047R1\nMODE=change\nCOMPONENT=homelab-backup-targeted-continuation\nMUTATION_BOUNDARY=PRESERVE_1045_SOURCE_AND_APP_GENERATIONS;TARGET_VM9130_VIA_PROVEN_DISPATCH_ONE;INSTALL_COMMITTED_APP_SCHEDULER_RETENTION_AUDIT;STABLE_RETENTION_PLAN_SAFE_GC;ENABLE_TIMERS;NO_MAIL_NO_ALERTMANAGER_CHANGE_NO_SOURCE_PUSH_NO_RESTORE_DRILL\nAPP_PRE_CONTINUATION_DECISIONS={\"filebrowser\":\"SKIP_FRESH\",\"hbarhub\":\"SKIP_FRESH\",\"immich\":\"SKIP_FRESH\",\"skladchik-reports-monitor\":\"SKIP_FRESH\"}\nVM9130_BEFORE={\"age_seconds\":109083,\"consecutive_failures\":0,\"failure_backoff_seconds\":0,\"generation\":\"v2-vm9130-20260919T210758Z-0aa987efac8c\",\"last_attempt_epoch\":1789853557,\"last_success_epoch\":1789853557,\"replica_count\":2,\"result\":\"SUCCESS\",\"sha256\":\"6d9193e25bcf369a8800b1bf5fb6768934cf4786a9cb94e88f6b6e95645b92db\"}\nFAILURE=VM9130_DISPATCH_ONE_RC_65\nVM_BACKUP_STARTED=true\nVM_BACKUP_FRESH=false\nRUNTIME_CHANGED=false\nRETENTION_GC_STARTED=false\nRETENTION_GC_SUCCESS=false\nROLLBACK_STARTED_INTERNAL=true\nROLLBACK_RESTORED_INTERNAL=true\nTASK_RESULT=FAIL_HOMELAB_BACKUP_TARGETED_CONTINUATION\nHOMELAB_RESULT_CONTRACT={\"changes_made\":true,\"command_id\":\"SUPPORT-260921-HOMELAB-BACKUP-TARGETED-CONTINUATION-1047R1\",\"rollback_restored\":true,\"rollback_started\":true,\"status\":\"FAIL\",\"version\":1}\nWORKERS2_BACKUP_TARGETED_CONTINUATION_END=1\n" +} diff --git a/runtime/history/SUPPORT-260921-HOMELAB-BACKUP-TARGETED-CONTINUATION-1047R1.txt b/runtime/history/SUPPORT-260921-HOMELAB-BACKUP-TARGETED-CONTINUATION-1047R1.txt new file mode 100644 index 00000000..a053a18f --- /dev/null +++ b/runtime/history/SUPPORT-260921-HOMELAB-BACKUP-TARGETED-CONTINUATION-1047R1.txt @@ -0,0 +1,50 @@ +CHAT_OUTPUT_BEGIN +COMMAND_ID=SUPPORT-260921-HOMELAB-BACKUP-TARGETED-CONTINUATION-1047R1 +STATUS=FAIL +RC=1 +HOST=pve01 +MODE=change +COMPONENT=homelab-backup-targeted-continuation +REFERENCE_REGISTER_CHECK=OK +REFERENCE_SHA256=5a3d8e5154c41cb582a4e0aca68090be1f0138918bf82131a948df326f9d8d66 +ERROR_REGISTER_CHECK=OK +ERROR_REGISTER_SHA256=3b09a553ec0f527ed3afeed4753f52a74ee3036045bbfb3c685e9f8af4ba7ba0 +COMMAND_SHA256=82608ea4b9da22360373565f61912c6ab5f62cdf8ca0c7dc9020cfaba9117924 +DUPLICATE_FAILED_COMMAND_BLOCKED=false +EXECUTION_STARTED=true +CHANGE_DECLARED=true +RESULT_CONTRACT_VALID=true +RESULT_CONTRACT_STATUS=FAIL +RESULT_CONTRACT_ERROR=NONE +COMMAND_RC=1 +CHANGES_MADE=true +ROLLBACK_STARTED=true +ROLLBACK_RESTORED=true +MUTATION_OUTCOME=ROLLED_BACK +SANITIZED=yes +SECRETS_INCLUDED=no +PRIVATE_ADDRESSES_INCLUDED=no +RAW_EVIDENCE_SHA256=cc7d08ca13508be1d1a71150dfcf653da21a6bac9973cb87d778b15ba1655360 +SANITIZED_OUTPUT_SHA256=cc7d08ca13508be1d1a71150dfcf653da21a6bac9973cb87d778b15ba1655360 +OUTPUT_BEGIN +WORKERS2_BACKUP_TARGETED_CONTINUATION_BEGIN=1 +COMMAND_ID=SUPPORT-260921-HOMELAB-BACKUP-TARGETED-CONTINUATION-1047R1 +MODE=change +COMPONENT=homelab-backup-targeted-continuation +MUTATION_BOUNDARY=PRESERVE_1045_SOURCE_AND_APP_GENERATIONS;TARGET_VM9130_VIA_PROVEN_DISPATCH_ONE;INSTALL_COMMITTED_APP_SCHEDULER_RETENTION_AUDIT;STABLE_RETENTION_PLAN_SAFE_GC;ENABLE_TIMERS;NO_MAIL_NO_ALERTMANAGER_CHANGE_NO_SOURCE_PUSH_NO_RESTORE_DRILL +APP_PRE_CONTINUATION_DECISIONS={"filebrowser":"SKIP_FRESH","hbarhub":"SKIP_FRESH","immich":"SKIP_FRESH","skladchik-reports-monitor":"SKIP_FRESH"} +VM9130_BEFORE={"age_seconds":109083,"consecutive_failures":0,"failure_backoff_seconds":0,"generation":"v2-vm9130-20260919T210758Z-0aa987efac8c","last_attempt_epoch":1789853557,"last_success_epoch":1789853557,"replica_count":2,"result":"SUCCESS","sha256":"6d9193e25bcf369a8800b1bf5fb6768934cf4786a9cb94e88f6b6e95645b92db"} +FAILURE=VM9130_DISPATCH_ONE_RC_65 +VM_BACKUP_STARTED=true +VM_BACKUP_FRESH=false +RUNTIME_CHANGED=false +RETENTION_GC_STARTED=false +RETENTION_GC_SUCCESS=false +ROLLBACK_STARTED_INTERNAL=true +ROLLBACK_RESTORED_INTERNAL=true +TASK_RESULT=FAIL_HOMELAB_BACKUP_TARGETED_CONTINUATION +HOMELAB_RESULT_CONTRACT={"changes_made":true,"command_id":"SUPPORT-260921-HOMELAB-BACKUP-TARGETED-CONTINUATION-1047R1","rollback_restored":true,"rollback_started":true,"status":"FAIL","version":1} +WORKERS2_BACKUP_TARGETED_CONTINUATION_END=1 + +OUTPUT_END +CHAT_OUTPUT_END diff --git a/runtime/latest.json b/runtime/latest.json index 514fd5f0..ce1f7084 100644 --- a/runtime/latest.json +++ b/runtime/latest.json @@ -1,38 +1,38 @@ { "schema_version": 1, "channel": "homelab-runtime", - "command_id": "SIGNALBOT-260921-ORDER-NORMALIZATION-TEST-RCA-052FIXRCA1", - "status": "OK", - "rc": 0, + "command_id": "SUPPORT-260921-HOMELAB-BACKUP-TARGETED-CONTINUATION-1047R1", + "status": "FAIL", + "rc": 1, "host": "pve01", - "mode": "read-only", - "component": "signalbot-order-normalization-test-rca", - "started_at_utc": "2026-09-21T03:49:02Z", - "finished_at_utc": "2026-09-21T03:49:02Z", + "mode": "change", + "component": "homelab-backup-targeted-continuation", + "started_at_utc": "2026-09-21T03:50:39Z", + "finished_at_utc": "2026-09-21T03:50:41Z", "reference_register_checked": true, "reference_sha256": "5a3d8e5154c41cb582a4e0aca68090be1f0138918bf82131a948df326f9d8d66", "error_register_checked": true, "error_register_sha256": "3b09a553ec0f527ed3afeed4753f52a74ee3036045bbfb3c685e9f8af4ba7ba0", - "command_sha256": "bb6b4fbaa3c7a735965adc6a9e217dc7242ed3097f157c63cb7d11fcba77a0fa", + "command_sha256": "82608ea4b9da22360373565f61912c6ab5f62cdf8ca0c7dc9020cfaba9117924", "duplicate_failed_command_blocked": false, "block_reason": null, "execution_started": true, - "change_declared": false, + "change_declared": true, "result_contract_valid": true, - "result_contract_status": null, + "result_contract_status": "FAIL", "result_contract_error": null, - "command_rc": 0, - "changes_made": false, - "rollback_started": false, - "rollback_restored": null, - "mutation_outcome": "NO_MUTATION", + "command_rc": 1, + "changes_made": true, + "rollback_started": true, + "rollback_restored": true, + "mutation_outcome": "ROLLED_BACK", "sanitized": true, "secrets_included": false, "private_addresses_included": false, "raw_evidence_retained_locally": true, - "raw_evidence_sha256": "6f146b4f6887dfacff94c79715d1e60e5cdb633c76d4b25a3f28feec05a11eab", - "sanitized_output_sha256": "6f146b4f6887dfacff94c79715d1e60e5cdb633c76d4b25a3f28feec05a11eab", + "raw_evidence_sha256": "cc7d08ca13508be1d1a71150dfcf653da21a6bac9973cb87d778b15ba1655360", + "sanitized_output_sha256": "cc7d08ca13508be1d1a71150dfcf653da21a6bac9973cb87d778b15ba1655360", "output_truncated_in_json": false, "full_sanitized_output_url": "https://git.gram1.ru/.well-known/homelab-runtime/latest.txt", - "output": "SIGNALBOT052FIXRCA1_RESULT={\"branch\":\"signalbot-260917-reconnect-reconciliation\",\"clean\":true,\"diff_empty\":true,\"head\":\"749073ae0f894debc06fb0ffbf7a84abf9da531f\",\"package_identity\":\"9dd3a15343a59007aa1c0484f9a52b5987c67478687f1a5d3e3c91c7b95a9073\",\"restored_exact\":true,\"source_lines_146_215\":\"146:\\n147: def fetch_trade_window(\\n148: self,\\n149: symbol: str,\\n150: *,\\n151: start_mts: datetime,\\n152: end_mts: datetime,\\n153: ) -> BitfinexTradeHistoryBatch:\\n154: if not isinstance(symbol, str) or not symbol.startswith(\\\"t\\\") or len(symbol) < 2:\\n155: raise DemonovError(\\\"Bitfinex trade history symbol invalid\\\")\\n156: start_mts = _utc(start_mts, \\\"start_mts\\\")\\n157: end_mts = _utc(end_mts, \\\"end_mts\\\")\\n158: if end_mts < start_mts:\\n159: raise DemonovError(\\\"Bitfinex trade history interval invalid\\\")\\n160: start_ms = int(start_mts.timestamp() * 1000)\\n161: end_ms = int(end_mts.timestamp() * 1000)\\n162: query = urlencode({\\\"start\\\": start_ms, \\\"end\\\": end_ms, \\\"sort\\\": 1, \\\"limit\\\": BITFINEX_PUBLIC_HISTORY_LIMIT})\\n163: url = f\\\"{BITFINEX_PUBLIC_TRADES_BASE}/{quote(symbol, safe='')}/hist?{query}\\\"\\n164: try:\\n165: payload = self._fetch_json(\\n166: url,\\n167: timeout=self.timeout,\\n168: max_bytes=self.max_bytes,\\n169: )\\n170: except DemonovError:\\n171: raise\\n172: except Exception as exc:\\n173: raise DemonovError(\\n174: \\\"Bitfinex public trade history request failed\\\"\\n175: ) from exc\\n176: if not isinstance(payload, list):\\n177: raise DemonovError(\\\"Bitfinex public trade history payload invalid\\\")\\n178:\\n179: normalized: list[BitfinexCanonicalTradeV1] = []\\n180: raw_rows: list[list[object]] = []\\n181: seen: dict[str, str] = {}\\n182: order_keys: list[tuple[datetime, int]] = []\\n183: for raw in payload:\\n184: if not isinstance(raw, list):\\n185: raise DemonovError(\\\"Bitfinex REST trade row malformed\\\")\\n186: trade = normalize_bitfinex_rest_trade(raw)\\n187: raw_rows.append(list(raw[:4]))\\n188: prior = seen.get(trade.trade_identity)\\n189: if prior is not None:\\n190: if prior != trade.semantic_hash:\\n191: raise DemonovError(\\\"Bitfinex REST trade semantic conflict\\\")\\n192: continue\\n193: seen[trade.trade_identity] = trade.semantic_hash\\n194: normalized.append(trade)\\n195: order_keys.append((trade.mts, trade.trade_id))\\n196: if order_keys != sorted(order_keys):\\n197: raise DemonovError(\\\"Bitfinex public trade history not ascending\\\")\\n198:\\n199: evidence_sha256 = canonical_hash(\\n200: {\\n201: \\\"schema\\\": \\\"CR0116_BITFINEX_RECONCILIATION_REST_EVIDENCE_V1\\\",\\n202: \\\"symbol\\\": symbol,\\n203: \\\"start_mts\\\": start_mts,\\n204: \\\"end_mts\\\": end_mts,\\n205: \\\"raw_row_count\\\": len(payload),\\n206: \\\"rows\\\": raw_rows,\\n207: }\\n208: )\\n209: return BitfinexTradeHistoryBatch(\\n210: symbol=symbol,\\n211: start_mts=start_mts,\\n212: end_mts=end_mts,\\n213: raw_row_count=len(payload),\\n214: trades=tuple(normalized),\\n215: evidence_sha256=evidence_sha256,\",\"source_sha256\":\"023ce0a041b50b866a48d9f752bd80479a34789d82f73c81d18246ed8871ef8e\",\"test_hits\":[\"126: journal.close()\\n127:\\n128:\\n129:def test_public_history_fetch_is_ascending_inclusive_and_deduplicates_exact_rows():\\n130: a, b, c = _trade(2001, 1), _trade(2002, 2), _trade(2003, 3)\\n131: calls = []\\n132: def fetch(url, *, timeout, max_bytes):\\n133: calls.append((url, timeout, max_bytes))\",\"133: calls.append((url, timeout, max_bytes))\\n134: return [_rest_row(a), _rest_row(b), _rest_row(b), _rest_row(c)]\\n135: client = BitfinexPublicTradeHistoryClient(fetch_json=fetch)\\n136: batch = client.fetch_trade_window(\\\"tBTCUSD\\\", start_mts=a.mts, end_mts=c.mts)\\n137: assert [t.trade_id for t in batch.trades] == [2001, 2002, 2003]\\n138: assert batch.raw_row_count == 4\\n139: assert \\\"sort=1\\\" in calls[0][0] and \\\"limit=10000\\\" in calls[0][0]\\n140: assert f\\\"start={int(a.mts.timestamp()*1000)}\\\" in calls[0][0]\",\"145: a, b = _trade(2051, 1), _trade(2052, 2)\\n146: left = BitfinexPublicTradeHistoryClient(\\n147: fetch_json=lambda *args, **kwargs: [_rest_row(a), _rest_row(a), _rest_row(b)]\\n148: ).fetch_trade_window(\\\"tBTCUSD\\\", start_mts=a.mts, end_mts=b.mts)\\n149: right = BitfinexPublicTradeHistoryClient(\\n150: fetch_json=lambda *args, **kwargs: [_rest_row(a), _rest_row(b), _rest_row(b)]\\n151: ).fetch_trade_window(\\\"tBTCUSD\\\", start_mts=a.mts, end_mts=b.mts)\\n152: assert [t.trade_id for t in left.trades] == [2051, 2052]\",\"148: ).fetch_trade_window(\\\"tBTCUSD\\\", start_mts=a.mts, end_mts=b.mts)\\n149: right = BitfinexPublicTradeHistoryClient(\\n150: fetch_json=lambda *args, **kwargs: [_rest_row(a), _rest_row(b), _rest_row(b)]\\n151: ).fetch_trade_window(\\\"tBTCUSD\\\", start_mts=a.mts, end_mts=b.mts)\\n152: assert [t.trade_id for t in left.trades] == [2051, 2052]\\n153: assert [t.trade_id for t in right.trades] == [2051, 2052]\\n154: assert left.raw_row_count == right.raw_row_count == 3\\n155: assert left.evidence_sha256 != right.evidence_sha256\",\"164: ]\\n165: for client in bad_clients:\\n166: with pytest.raises(DemonovError):\\n167: client.fetch_trade_window(\\\"tBTCUSD\\\", start_mts=a.mts, end_mts=b.mts)\\n168:\\n169:\\n170:def test_successful_reconciliation_recovers_middle_trade_and_is_idempotent(tmp_path):\\n171: journal, window, previous, next_trade = _journal_with_window(tmp_path)\",\"175: calls.append(url)\\n176: return [_rest_row(previous), _rest_row(middle), _rest_row(next_trade)]\\n177: client = BitfinexPublicTradeHistoryClient(fetch_json=fetch)\\n178: resolved = client.reconcile_window(\\n179: journal, window.window_id, symbol=\\\"tBTCUSD\\\", resolved_at=T0 + timedelta(seconds=20)\\n180: )\\n181: repeated = client.reconcile_window(\\n182: journal, window.window_id, symbol=\\\"tBTCUSD\\\", resolved_at=T0 + timedelta(seconds=21)\",\"178: resolved = client.reconcile_window(\\n179: journal, window.window_id, symbol=\\\"tBTCUSD\\\", resolved_at=T0 + timedelta(seconds=20)\\n180: )\\n181: repeated = client.reconcile_window(\\n182: journal, window.window_id, symbol=\\\"tBTCUSD\\\", resolved_at=T0 + timedelta(seconds=21)\\n183: )\\n184: assert resolved.status is ReconciliationStatus.RECONCILED\\n185: assert resolved.recovered_row_count == 1\",\"189: journal.close()\\n190:\\n191:\\n192:def test_http_or_validation_failure_marks_window_unresolved(tmp_path):\\n193: journal, window, _, _ = _journal_with_window(tmp_path)\\n194: client = BitfinexPublicTradeHistoryClient(\\n195: fetch_json=lambda *a, **k: (_ for _ in ()).throw(TimeoutError(\\\"simulated\\\"))\\n196: )\",\"194: client = BitfinexPublicTradeHistoryClient(\\n195: fetch_json=lambda *a, **k: (_ for _ in ()).throw(TimeoutError(\\\"simulated\\\"))\\n196: )\\n197: resolved = client.reconcile_window(\\n198: journal, window.window_id, symbol=\\\"tBTCUSD\\\", resolved_at=T0 + timedelta(seconds=20)\\n199: )\\n200: assert resolved.status is ReconciliationStatus.UNRESOLVED\\n201: assert resolved.failure_reason == \\\"REST_FETCH_OR_VALIDATION_FAILED\\\"\",\"198: journal, window.window_id, symbol=\\\"tBTCUSD\\\", resolved_at=T0 + timedelta(seconds=20)\\n199: )\\n200: assert resolved.status is ReconciliationStatus.UNRESOLVED\\n201: assert resolved.failure_reason == \\\"REST_FETCH_OR_VALIDATION_FAILED\\\"\\n202: journal.close()\\n203:\\n204:\\n205:def test_exact_row_limit_marks_window_unresolved(tmp_path):\",\"212: rows[-1] = _rest_row(next_trade)\\n213: rows.sort(key=lambda r: (r[1], r[0]))\\n214: client = BitfinexPublicTradeHistoryClient(fetch_json=lambda *a, **k: rows)\\n215: resolved = client.reconcile_window(\\n216: journal, window.window_id, symbol=\\\"tBTCUSD\\\", resolved_at=T0 + timedelta(seconds=20)\\n217: )\\n218: assert resolved.status is ReconciliationStatus.UNRESOLVED\\n219: assert resolved.failure_reason == \\\"REST_ROW_LIMIT_REACHED\\\"\",\"226: client = BitfinexPublicTradeHistoryClient(\\n227: fetch_json=lambda *a, **k: [_rest_row(previous), _rest_row(middle)]\\n228: )\\n229: resolved = client.reconcile_window(\\n230: journal, window.window_id, symbol=\\\"tBTCUSD\\\", resolved_at=T0 + timedelta(seconds=20)\\n231: )\\n232: assert resolved.status is ReconciliationStatus.UNRESOLVED\\n233: assert resolved.failure_reason == \\\"NEXT_BOUNDARY_MISSING\\\"\",\"243: client = BitfinexPublicTradeHistoryClient(\\n244: fetch_json=lambda *a, **k: [conflicting, _rest_row(next_trade)]\\n245: )\\n246: resolved = client.reconcile_window(\\n247: journal, window.window_id, symbol=\\\"tBTCUSD\\\", resolved_at=T0 + timedelta(seconds=20)\\n248: )\\n249: after = journal._db.execute(\\n250: \\\"SELECT COUNT(*) AS n FROM trade_revisions WHERE trade_identity=?\\\", (previous.trade_identity,)\"],\"test_lines_150_280\":\"150: fetch_json=lambda *args, **kwargs: [_rest_row(a), _rest_row(b), _rest_row(b)]\\n151: ).fetch_trade_window(\\\"tBTCUSD\\\", start_mts=a.mts, end_mts=b.mts)\\n152: assert [t.trade_id for t in left.trades] == [2051, 2052]\\n153: assert [t.trade_id for t in right.trades] == [2051, 2052]\\n154: assert left.raw_row_count == right.raw_row_count == 3\\n155: assert left.evidence_sha256 != right.evidence_sha256\\n156:\\n157:\\n158:def test_public_history_rejects_malformed_unordered_and_conflicting_duplicate_rows():\\n159: a, b = _trade(2101, 1), _trade(2102, 2)\\n160: bad_clients = [\\n161: BitfinexPublicTradeHistoryClient(fetch_json=lambda *a, **k: [[1, 2]]),\\n162: BitfinexPublicTradeHistoryClient(fetch_json=lambda *a, **k: [_rest_row(b), _rest_row(a)]),\\n163: BitfinexPublicTradeHistoryClient(fetch_json=lambda *a, **k: [_rest_row(a), [a.trade_id, int(a.mts.timestamp()*1000), \\\"0.5\\\", \\\"101\\\"]]),\\n164: ]\\n165: for client in bad_clients:\\n166: with pytest.raises(DemonovError):\\n167: client.fetch_trade_window(\\\"tBTCUSD\\\", start_mts=a.mts, end_mts=b.mts)\\n168:\\n169:\\n170:def test_successful_reconciliation_recovers_middle_trade_and_is_idempotent(tmp_path):\\n171: journal, window, previous, next_trade = _journal_with_window(tmp_path)\\n172: middle = _trade(1002, 7)\\n173: calls = []\\n174: def fetch(url, *, timeout, max_bytes):\\n175: calls.append(url)\\n176: return [_rest_row(previous), _rest_row(middle), _rest_row(next_trade)]\\n177: client = BitfinexPublicTradeHistoryClient(fetch_json=fetch)\\n178: resolved = client.reconcile_window(\\n179: journal, window.window_id, symbol=\\\"tBTCUSD\\\", resolved_at=T0 + timedelta(seconds=20)\\n180: )\\n181: repeated = client.reconcile_window(\\n182: journal, window.window_id, symbol=\\\"tBTCUSD\\\", resolved_at=T0 + timedelta(seconds=21)\\n183: )\\n184: assert resolved.status is ReconciliationStatus.RECONCILED\\n185: assert resolved.recovered_row_count == 1\\n186: assert journal.has_trade_identity(middle.trade_identity)\\n187: assert repeated == resolved\\n188: assert len(calls) == 1\\n189: journal.close()\\n190:\\n191:\\n192:def test_http_or_validation_failure_marks_window_unresolved(tmp_path):\\n193: journal, window, _, _ = _journal_with_window(tmp_path)\\n194: client = BitfinexPublicTradeHistoryClient(\\n195: fetch_json=lambda *a, **k: (_ for _ in ()).throw(TimeoutError(\\\"simulated\\\"))\\n196: )\\n197: resolved = client.reconcile_window(\\n198: journal, window.window_id, symbol=\\\"tBTCUSD\\\", resolved_at=T0 + timedelta(seconds=20)\\n199: )\\n200: assert resolved.status is ReconciliationStatus.UNRESOLVED\\n201: assert resolved.failure_reason == \\\"REST_FETCH_OR_VALIDATION_FAILED\\\"\\n202: journal.close()\\n203:\\n204:\\n205:def test_exact_row_limit_marks_window_unresolved(tmp_path):\\n206: journal, window, previous, next_trade = _journal_with_window(tmp_path)\\n207: rows = []\\n208: for i in range(BITFINEX_PUBLIC_HISTORY_LIMIT):\\n209: trade_id = 300000 + i\\n210: rows.append([trade_id, int((previous.mts + timedelta(milliseconds=i)).timestamp()*1000), \\\"0.5\\\", \\\"100\\\"])\\n211: rows[0] = _rest_row(previous)\\n212: rows[-1] = _rest_row(next_trade)\\n213: rows.sort(key=lambda r: (r[1], r[0]))\\n214: client = BitfinexPublicTradeHistoryClient(fetch_json=lambda *a, **k: rows)\\n215: resolved = client.reconcile_window(\\n216: journal, window.window_id, symbol=\\\"tBTCUSD\\\", resolved_at=T0 + timedelta(seconds=20)\\n217: )\\n218: assert resolved.status is ReconciliationStatus.UNRESOLVED\\n219: assert resolved.failure_reason == \\\"REST_ROW_LIMIT_REACHED\\\"\\n220: journal.close()\\n221:\\n222:\\n223:def test_missing_boundary_marks_window_unresolved(tmp_path):\\n224: journal, window, previous, _ = _journal_with_window(tmp_path, persist_next=False)\\n225: middle = _trade(1002, 7)\\n226: client = BitfinexPublicTradeHistoryClient(\\n227: fetch_json=lambda *a, **k: [_rest_row(previous), _rest_row(middle)]\\n228: )\\n229: resolved = client.reconcile_window(\\n230: journal, window.window_id, symbol=\\\"tBTCUSD\\\", resolved_at=T0 + timedelta(seconds=20)\\n231: )\\n232: assert resolved.status is ReconciliationStatus.UNRESOLVED\\n233: assert resolved.failure_reason == \\\"NEXT_BOUNDARY_MISSING\\\"\\n234: journal.close()\\n235:\\n236:\\n237:def test_semantic_conflict_marks_window_unresolved_without_new_revision(tmp_path):\\n238: journal, window, previous, next_trade = _journal_with_window(tmp_path)\\n239: conflicting = [previous.trade_id, int(previous.mts.timestamp()*1000), \\\"0.5\\\", \\\"101\\\"]\\n240: before = journal._db.execute(\\n241: \\\"SELECT COUNT(*) AS n FROM trade_revisions WHERE trade_identity=?\\\", (previous.trade_identity,)\\n242: ).fetchone()[\\\"n\\\"]\\n243: client = BitfinexPublicTradeHistoryClient(\\n244: fetch_json=lambda *a, **k: [conflicting, _rest_row(next_trade)]\\n245: )\\n246: resolved = client.reconcile_window(\\n247: journal, window.window_id, symbol=\\\"tBTCUSD\\\", resolved_at=T0 + timedelta(seconds=20)\\n248: )\\n249: after = journal._db.execute(\\n250: \\\"SELECT COUNT(*) AS n FROM trade_revisions WHERE trade_identity=?\\\", (previous.trade_identity,)\\n251: ).fetchone()[\\\"n\\\"]\\n252: assert resolved.status is ReconciliationStatus.UNRESOLVED\\n253: assert resolved.failure_reason == \\\"REST_SEMANTIC_CONFLICT\\\"\\n254: assert after == before\\n255: journal.close()\",\"test_sha256\":\"bebdd9c2875d2c63a0ac27d89eefa7f1c36c83c067aa8b472c0616ac470dd2a2\"}\nTASK_RESULT=PASS_SIGNALBOT_052FIXRCA1_ROLLBACK_AND_TEST_SEMANTICS_CAPTURED\nHOMELAB_RESULT_CONTRACT={\"version\":1,\"command_id\":\"SIGNALBOT-260921-ORDER-NORMALIZATION-TEST-RCA-052FIXRCA1\",\"status\":\"OK\",\"changes_made\":false,\"rollback_started\":false,\"rollback_restored\":null}\n" + "output": "WORKERS2_BACKUP_TARGETED_CONTINUATION_BEGIN=1\nCOMMAND_ID=SUPPORT-260921-HOMELAB-BACKUP-TARGETED-CONTINUATION-1047R1\nMODE=change\nCOMPONENT=homelab-backup-targeted-continuation\nMUTATION_BOUNDARY=PRESERVE_1045_SOURCE_AND_APP_GENERATIONS;TARGET_VM9130_VIA_PROVEN_DISPATCH_ONE;INSTALL_COMMITTED_APP_SCHEDULER_RETENTION_AUDIT;STABLE_RETENTION_PLAN_SAFE_GC;ENABLE_TIMERS;NO_MAIL_NO_ALERTMANAGER_CHANGE_NO_SOURCE_PUSH_NO_RESTORE_DRILL\nAPP_PRE_CONTINUATION_DECISIONS={\"filebrowser\":\"SKIP_FRESH\",\"hbarhub\":\"SKIP_FRESH\",\"immich\":\"SKIP_FRESH\",\"skladchik-reports-monitor\":\"SKIP_FRESH\"}\nVM9130_BEFORE={\"age_seconds\":109083,\"consecutive_failures\":0,\"failure_backoff_seconds\":0,\"generation\":\"v2-vm9130-20260919T210758Z-0aa987efac8c\",\"last_attempt_epoch\":1789853557,\"last_success_epoch\":1789853557,\"replica_count\":2,\"result\":\"SUCCESS\",\"sha256\":\"6d9193e25bcf369a8800b1bf5fb6768934cf4786a9cb94e88f6b6e95645b92db\"}\nFAILURE=VM9130_DISPATCH_ONE_RC_65\nVM_BACKUP_STARTED=true\nVM_BACKUP_FRESH=false\nRUNTIME_CHANGED=false\nRETENTION_GC_STARTED=false\nRETENTION_GC_SUCCESS=false\nROLLBACK_STARTED_INTERNAL=true\nROLLBACK_RESTORED_INTERNAL=true\nTASK_RESULT=FAIL_HOMELAB_BACKUP_TARGETED_CONTINUATION\nHOMELAB_RESULT_CONTRACT={\"changes_made\":true,\"command_id\":\"SUPPORT-260921-HOMELAB-BACKUP-TARGETED-CONTINUATION-1047R1\",\"rollback_restored\":true,\"rollback_started\":true,\"status\":\"FAIL\",\"version\":1}\nWORKERS2_BACKUP_TARGETED_CONTINUATION_END=1\n" } diff --git a/runtime/latest.txt b/runtime/latest.txt index c31d43a0..a053a18f 100644 --- a/runtime/latest.txt +++ b/runtime/latest.txt @@ -1,35 +1,50 @@ CHAT_OUTPUT_BEGIN -COMMAND_ID=SIGNALBOT-260921-ORDER-NORMALIZATION-TEST-RCA-052FIXRCA1 -STATUS=OK -RC=0 +COMMAND_ID=SUPPORT-260921-HOMELAB-BACKUP-TARGETED-CONTINUATION-1047R1 +STATUS=FAIL +RC=1 HOST=pve01 -MODE=read-only -COMPONENT=signalbot-order-normalization-test-rca +MODE=change +COMPONENT=homelab-backup-targeted-continuation REFERENCE_REGISTER_CHECK=OK REFERENCE_SHA256=5a3d8e5154c41cb582a4e0aca68090be1f0138918bf82131a948df326f9d8d66 ERROR_REGISTER_CHECK=OK ERROR_REGISTER_SHA256=3b09a553ec0f527ed3afeed4753f52a74ee3036045bbfb3c685e9f8af4ba7ba0 -COMMAND_SHA256=bb6b4fbaa3c7a735965adc6a9e217dc7242ed3097f157c63cb7d11fcba77a0fa +COMMAND_SHA256=82608ea4b9da22360373565f61912c6ab5f62cdf8ca0c7dc9020cfaba9117924 DUPLICATE_FAILED_COMMAND_BLOCKED=false EXECUTION_STARTED=true -CHANGE_DECLARED=false +CHANGE_DECLARED=true RESULT_CONTRACT_VALID=true -RESULT_CONTRACT_STATUS=NOT_APPLICABLE +RESULT_CONTRACT_STATUS=FAIL RESULT_CONTRACT_ERROR=NONE -COMMAND_RC=0 -CHANGES_MADE=false -ROLLBACK_STARTED=false -ROLLBACK_RESTORED=null -MUTATION_OUTCOME=NO_MUTATION +COMMAND_RC=1 +CHANGES_MADE=true +ROLLBACK_STARTED=true +ROLLBACK_RESTORED=true +MUTATION_OUTCOME=ROLLED_BACK SANITIZED=yes SECRETS_INCLUDED=no PRIVATE_ADDRESSES_INCLUDED=no -RAW_EVIDENCE_SHA256=6f146b4f6887dfacff94c79715d1e60e5cdb633c76d4b25a3f28feec05a11eab -SANITIZED_OUTPUT_SHA256=6f146b4f6887dfacff94c79715d1e60e5cdb633c76d4b25a3f28feec05a11eab +RAW_EVIDENCE_SHA256=cc7d08ca13508be1d1a71150dfcf653da21a6bac9973cb87d778b15ba1655360 +SANITIZED_OUTPUT_SHA256=cc7d08ca13508be1d1a71150dfcf653da21a6bac9973cb87d778b15ba1655360 OUTPUT_BEGIN -SIGNALBOT052FIXRCA1_RESULT={"branch":"signalbot-260917-reconnect-reconciliation","clean":true,"diff_empty":true,"head":"749073ae0f894debc06fb0ffbf7a84abf9da531f","package_identity":"9dd3a15343a59007aa1c0484f9a52b5987c67478687f1a5d3e3c91c7b95a9073","restored_exact":true,"source_lines_146_215":"146:\n147: def fetch_trade_window(\n148: self,\n149: symbol: str,\n150: *,\n151: start_mts: datetime,\n152: end_mts: datetime,\n153: ) -> BitfinexTradeHistoryBatch:\n154: if not isinstance(symbol, str) or not symbol.startswith(\"t\") or len(symbol) < 2:\n155: raise DemonovError(\"Bitfinex trade history symbol invalid\")\n156: start_mts = _utc(start_mts, \"start_mts\")\n157: end_mts = _utc(end_mts, \"end_mts\")\n158: if end_mts < start_mts:\n159: raise DemonovError(\"Bitfinex trade history interval invalid\")\n160: start_ms = int(start_mts.timestamp() * 1000)\n161: end_ms = int(end_mts.timestamp() * 1000)\n162: query = urlencode({\"start\": start_ms, \"end\": end_ms, \"sort\": 1, \"limit\": BITFINEX_PUBLIC_HISTORY_LIMIT})\n163: url = f\"{BITFINEX_PUBLIC_TRADES_BASE}/{quote(symbol, safe='')}/hist?{query}\"\n164: try:\n165: payload = self._fetch_json(\n166: url,\n167: timeout=self.timeout,\n168: max_bytes=self.max_bytes,\n169: )\n170: except DemonovError:\n171: raise\n172: except Exception as exc:\n173: raise DemonovError(\n174: \"Bitfinex public trade history request failed\"\n175: ) from exc\n176: if not isinstance(payload, list):\n177: raise DemonovError(\"Bitfinex public trade history payload invalid\")\n178:\n179: normalized: list[BitfinexCanonicalTradeV1] = []\n180: raw_rows: list[list[object]] = []\n181: seen: dict[str, str] = {}\n182: order_keys: list[tuple[datetime, int]] = []\n183: for raw in payload:\n184: if not isinstance(raw, list):\n185: raise DemonovError(\"Bitfinex REST trade row malformed\")\n186: trade = normalize_bitfinex_rest_trade(raw)\n187: raw_rows.append(list(raw[:4]))\n188: prior = seen.get(trade.trade_identity)\n189: if prior is not None:\n190: if prior != trade.semantic_hash:\n191: raise DemonovError(\"Bitfinex REST trade semantic conflict\")\n192: continue\n193: seen[trade.trade_identity] = trade.semantic_hash\n194: normalized.append(trade)\n195: order_keys.append((trade.mts, trade.trade_id))\n196: if order_keys != sorted(order_keys):\n197: raise DemonovError(\"Bitfinex public trade history not ascending\")\n198:\n199: evidence_sha256 = canonical_hash(\n200: {\n201: \"schema\": \"CR0116_BITFINEX_RECONCILIATION_REST_EVIDENCE_V1\",\n202: \"symbol\": symbol,\n203: \"start_mts\": start_mts,\n204: \"end_mts\": end_mts,\n205: \"raw_row_count\": len(payload),\n206: \"rows\": raw_rows,\n207: }\n208: )\n209: return BitfinexTradeHistoryBatch(\n210: symbol=symbol,\n211: start_mts=start_mts,\n212: end_mts=end_mts,\n213: raw_row_count=len(payload),\n214: trades=tuple(normalized),\n215: evidence_sha256=evidence_sha256,","source_sha256":"023ce0a041b50b866a48d9f752bd80479a34789d82f73c81d18246ed8871ef8e","test_hits":["126: journal.close()\n127:\n128:\n129:def test_public_history_fetch_is_ascending_inclusive_and_deduplicates_exact_rows():\n130: a, b, c = _trade(2001, 1), _trade(2002, 2), _trade(2003, 3)\n131: calls = []\n132: def fetch(url, *, timeout, max_bytes):\n133: calls.append((url, timeout, max_bytes))","133: calls.append((url, timeout, max_bytes))\n134: return [_rest_row(a), _rest_row(b), _rest_row(b), _rest_row(c)]\n135: client = BitfinexPublicTradeHistoryClient(fetch_json=fetch)\n136: batch = client.fetch_trade_window(\"tBTCUSD\", start_mts=a.mts, end_mts=c.mts)\n137: assert [t.trade_id for t in batch.trades] == [2001, 2002, 2003]\n138: assert batch.raw_row_count == 4\n139: assert \"sort=1\" in calls[0][0] and \"limit=10000\" in calls[0][0]\n140: assert f\"start={int(a.mts.timestamp()*1000)}\" in calls[0][0]","145: a, b = _trade(2051, 1), _trade(2052, 2)\n146: left = BitfinexPublicTradeHistoryClient(\n147: fetch_json=lambda *args, **kwargs: [_rest_row(a), _rest_row(a), _rest_row(b)]\n148: ).fetch_trade_window(\"tBTCUSD\", start_mts=a.mts, end_mts=b.mts)\n149: right = BitfinexPublicTradeHistoryClient(\n150: fetch_json=lambda *args, **kwargs: [_rest_row(a), _rest_row(b), _rest_row(b)]\n151: ).fetch_trade_window(\"tBTCUSD\", start_mts=a.mts, end_mts=b.mts)\n152: assert [t.trade_id for t in left.trades] == [2051, 2052]","148: ).fetch_trade_window(\"tBTCUSD\", start_mts=a.mts, end_mts=b.mts)\n149: right = BitfinexPublicTradeHistoryClient(\n150: fetch_json=lambda *args, **kwargs: [_rest_row(a), _rest_row(b), _rest_row(b)]\n151: ).fetch_trade_window(\"tBTCUSD\", start_mts=a.mts, end_mts=b.mts)\n152: assert [t.trade_id for t in left.trades] == [2051, 2052]\n153: assert [t.trade_id for t in right.trades] == [2051, 2052]\n154: assert left.raw_row_count == right.raw_row_count == 3\n155: assert left.evidence_sha256 != right.evidence_sha256","164: ]\n165: for client in bad_clients:\n166: with pytest.raises(DemonovError):\n167: client.fetch_trade_window(\"tBTCUSD\", start_mts=a.mts, end_mts=b.mts)\n168:\n169:\n170:def test_successful_reconciliation_recovers_middle_trade_and_is_idempotent(tmp_path):\n171: journal, window, previous, next_trade = _journal_with_window(tmp_path)","175: calls.append(url)\n176: return [_rest_row(previous), _rest_row(middle), _rest_row(next_trade)]\n177: client = BitfinexPublicTradeHistoryClient(fetch_json=fetch)\n178: resolved = client.reconcile_window(\n179: journal, window.window_id, symbol=\"tBTCUSD\", resolved_at=T0 + timedelta(seconds=20)\n180: )\n181: repeated = client.reconcile_window(\n182: journal, window.window_id, symbol=\"tBTCUSD\", resolved_at=T0 + timedelta(seconds=21)","178: resolved = client.reconcile_window(\n179: journal, window.window_id, symbol=\"tBTCUSD\", resolved_at=T0 + timedelta(seconds=20)\n180: )\n181: repeated = client.reconcile_window(\n182: journal, window.window_id, symbol=\"tBTCUSD\", resolved_at=T0 + timedelta(seconds=21)\n183: )\n184: assert resolved.status is ReconciliationStatus.RECONCILED\n185: assert resolved.recovered_row_count == 1","189: journal.close()\n190:\n191:\n192:def test_http_or_validation_failure_marks_window_unresolved(tmp_path):\n193: journal, window, _, _ = _journal_with_window(tmp_path)\n194: client = BitfinexPublicTradeHistoryClient(\n195: fetch_json=lambda *a, **k: (_ for _ in ()).throw(TimeoutError(\"simulated\"))\n196: )","194: client = BitfinexPublicTradeHistoryClient(\n195: fetch_json=lambda *a, **k: (_ for _ in ()).throw(TimeoutError(\"simulated\"))\n196: )\n197: resolved = client.reconcile_window(\n198: journal, window.window_id, symbol=\"tBTCUSD\", resolved_at=T0 + timedelta(seconds=20)\n199: )\n200: assert resolved.status is ReconciliationStatus.UNRESOLVED\n201: assert resolved.failure_reason == \"REST_FETCH_OR_VALIDATION_FAILED\"","198: journal, window.window_id, symbol=\"tBTCUSD\", resolved_at=T0 + timedelta(seconds=20)\n199: )\n200: assert resolved.status is ReconciliationStatus.UNRESOLVED\n201: assert resolved.failure_reason == \"REST_FETCH_OR_VALIDATION_FAILED\"\n202: journal.close()\n203:\n204:\n205:def test_exact_row_limit_marks_window_unresolved(tmp_path):","212: rows[-1] = _rest_row(next_trade)\n213: rows.sort(key=lambda r: (r[1], r[0]))\n214: client = BitfinexPublicTradeHistoryClient(fetch_json=lambda *a, **k: rows)\n215: resolved = client.reconcile_window(\n216: journal, window.window_id, symbol=\"tBTCUSD\", resolved_at=T0 + timedelta(seconds=20)\n217: )\n218: assert resolved.status is ReconciliationStatus.UNRESOLVED\n219: assert resolved.failure_reason == \"REST_ROW_LIMIT_REACHED\"","226: client = BitfinexPublicTradeHistoryClient(\n227: fetch_json=lambda *a, **k: [_rest_row(previous), _rest_row(middle)]\n228: )\n229: resolved = client.reconcile_window(\n230: journal, window.window_id, symbol=\"tBTCUSD\", resolved_at=T0 + timedelta(seconds=20)\n231: )\n232: assert resolved.status is ReconciliationStatus.UNRESOLVED\n233: assert resolved.failure_reason == \"NEXT_BOUNDARY_MISSING\"","243: client = BitfinexPublicTradeHistoryClient(\n244: fetch_json=lambda *a, **k: [conflicting, _rest_row(next_trade)]\n245: )\n246: resolved = client.reconcile_window(\n247: journal, window.window_id, symbol=\"tBTCUSD\", resolved_at=T0 + timedelta(seconds=20)\n248: )\n249: after = journal._db.execute(\n250: \"SELECT COUNT(*) AS n FROM trade_revisions WHERE trade_identity=?\", (previous.trade_identity,)"],"test_lines_150_280":"150: fetch_json=lambda *args, **kwargs: [_rest_row(a), _rest_row(b), _rest_row(b)]\n151: ).fetch_trade_window(\"tBTCUSD\", start_mts=a.mts, end_mts=b.mts)\n152: assert [t.trade_id for t in left.trades] == [2051, 2052]\n153: assert [t.trade_id for t in right.trades] == [2051, 2052]\n154: assert left.raw_row_count == right.raw_row_count == 3\n155: assert left.evidence_sha256 != right.evidence_sha256\n156:\n157:\n158:def test_public_history_rejects_malformed_unordered_and_conflicting_duplicate_rows():\n159: a, b = _trade(2101, 1), _trade(2102, 2)\n160: bad_clients = [\n161: BitfinexPublicTradeHistoryClient(fetch_json=lambda *a, **k: [[1, 2]]),\n162: BitfinexPublicTradeHistoryClient(fetch_json=lambda *a, **k: [_rest_row(b), _rest_row(a)]),\n163: BitfinexPublicTradeHistoryClient(fetch_json=lambda *a, **k: [_rest_row(a), [a.trade_id, int(a.mts.timestamp()*1000), \"0.5\", \"101\"]]),\n164: ]\n165: for client in bad_clients:\n166: with pytest.raises(DemonovError):\n167: client.fetch_trade_window(\"tBTCUSD\", start_mts=a.mts, end_mts=b.mts)\n168:\n169:\n170:def test_successful_reconciliation_recovers_middle_trade_and_is_idempotent(tmp_path):\n171: journal, window, previous, next_trade = _journal_with_window(tmp_path)\n172: middle = _trade(1002, 7)\n173: calls = []\n174: def fetch(url, *, timeout, max_bytes):\n175: calls.append(url)\n176: return [_rest_row(previous), _rest_row(middle), _rest_row(next_trade)]\n177: client = BitfinexPublicTradeHistoryClient(fetch_json=fetch)\n178: resolved = client.reconcile_window(\n179: journal, window.window_id, symbol=\"tBTCUSD\", resolved_at=T0 + timedelta(seconds=20)\n180: )\n181: repeated = client.reconcile_window(\n182: journal, window.window_id, symbol=\"tBTCUSD\", resolved_at=T0 + timedelta(seconds=21)\n183: )\n184: assert resolved.status is ReconciliationStatus.RECONCILED\n185: assert resolved.recovered_row_count == 1\n186: assert journal.has_trade_identity(middle.trade_identity)\n187: assert repeated == resolved\n188: assert len(calls) == 1\n189: journal.close()\n190:\n191:\n192:def test_http_or_validation_failure_marks_window_unresolved(tmp_path):\n193: journal, window, _, _ = _journal_with_window(tmp_path)\n194: client = BitfinexPublicTradeHistoryClient(\n195: fetch_json=lambda *a, **k: (_ for _ in ()).throw(TimeoutError(\"simulated\"))\n196: )\n197: resolved = client.reconcile_window(\n198: journal, window.window_id, symbol=\"tBTCUSD\", resolved_at=T0 + timedelta(seconds=20)\n199: )\n200: assert resolved.status is ReconciliationStatus.UNRESOLVED\n201: assert resolved.failure_reason == \"REST_FETCH_OR_VALIDATION_FAILED\"\n202: journal.close()\n203:\n204:\n205:def test_exact_row_limit_marks_window_unresolved(tmp_path):\n206: journal, window, previous, next_trade = _journal_with_window(tmp_path)\n207: rows = []\n208: for i in range(BITFINEX_PUBLIC_HISTORY_LIMIT):\n209: trade_id = 300000 + i\n210: rows.append([trade_id, int((previous.mts + timedelta(milliseconds=i)).timestamp()*1000), \"0.5\", \"100\"])\n211: rows[0] = _rest_row(previous)\n212: rows[-1] = _rest_row(next_trade)\n213: rows.sort(key=lambda r: (r[1], r[0]))\n214: client = BitfinexPublicTradeHistoryClient(fetch_json=lambda *a, **k: rows)\n215: resolved = client.reconcile_window(\n216: journal, window.window_id, symbol=\"tBTCUSD\", resolved_at=T0 + timedelta(seconds=20)\n217: )\n218: assert resolved.status is ReconciliationStatus.UNRESOLVED\n219: assert resolved.failure_reason == \"REST_ROW_LIMIT_REACHED\"\n220: journal.close()\n221:\n222:\n223:def test_missing_boundary_marks_window_unresolved(tmp_path):\n224: journal, window, previous, _ = _journal_with_window(tmp_path, persist_next=False)\n225: middle = _trade(1002, 7)\n226: client = BitfinexPublicTradeHistoryClient(\n227: fetch_json=lambda *a, **k: [_rest_row(previous), _rest_row(middle)]\n228: )\n229: resolved = client.reconcile_window(\n230: journal, window.window_id, symbol=\"tBTCUSD\", resolved_at=T0 + timedelta(seconds=20)\n231: )\n232: assert resolved.status is ReconciliationStatus.UNRESOLVED\n233: assert resolved.failure_reason == \"NEXT_BOUNDARY_MISSING\"\n234: journal.close()\n235:\n236:\n237:def test_semantic_conflict_marks_window_unresolved_without_new_revision(tmp_path):\n238: journal, window, previous, next_trade = _journal_with_window(tmp_path)\n239: conflicting = [previous.trade_id, int(previous.mts.timestamp()*1000), \"0.5\", \"101\"]\n240: before = journal._db.execute(\n241: \"SELECT COUNT(*) AS n FROM trade_revisions WHERE trade_identity=?\", (previous.trade_identity,)\n242: ).fetchone()[\"n\"]\n243: client = BitfinexPublicTradeHistoryClient(\n244: fetch_json=lambda *a, **k: [conflicting, _rest_row(next_trade)]\n245: )\n246: resolved = client.reconcile_window(\n247: journal, window.window_id, symbol=\"tBTCUSD\", resolved_at=T0 + timedelta(seconds=20)\n248: )\n249: after = journal._db.execute(\n250: \"SELECT COUNT(*) AS n FROM trade_revisions WHERE trade_identity=?\", (previous.trade_identity,)\n251: ).fetchone()[\"n\"]\n252: assert resolved.status is ReconciliationStatus.UNRESOLVED\n253: assert resolved.failure_reason == \"REST_SEMANTIC_CONFLICT\"\n254: assert after == before\n255: journal.close()","test_sha256":"bebdd9c2875d2c63a0ac27d89eefa7f1c36c83c067aa8b472c0616ac470dd2a2"} -TASK_RESULT=PASS_SIGNALBOT_052FIXRCA1_ROLLBACK_AND_TEST_SEMANTICS_CAPTURED -HOMELAB_RESULT_CONTRACT={"version":1,"command_id":"SIGNALBOT-260921-ORDER-NORMALIZATION-TEST-RCA-052FIXRCA1","status":"OK","changes_made":false,"rollback_started":false,"rollback_restored":null} +WORKERS2_BACKUP_TARGETED_CONTINUATION_BEGIN=1 +COMMAND_ID=SUPPORT-260921-HOMELAB-BACKUP-TARGETED-CONTINUATION-1047R1 +MODE=change +COMPONENT=homelab-backup-targeted-continuation +MUTATION_BOUNDARY=PRESERVE_1045_SOURCE_AND_APP_GENERATIONS;TARGET_VM9130_VIA_PROVEN_DISPATCH_ONE;INSTALL_COMMITTED_APP_SCHEDULER_RETENTION_AUDIT;STABLE_RETENTION_PLAN_SAFE_GC;ENABLE_TIMERS;NO_MAIL_NO_ALERTMANAGER_CHANGE_NO_SOURCE_PUSH_NO_RESTORE_DRILL +APP_PRE_CONTINUATION_DECISIONS={"filebrowser":"SKIP_FRESH","hbarhub":"SKIP_FRESH","immich":"SKIP_FRESH","skladchik-reports-monitor":"SKIP_FRESH"} +VM9130_BEFORE={"age_seconds":109083,"consecutive_failures":0,"failure_backoff_seconds":0,"generation":"v2-vm9130-20260919T210758Z-0aa987efac8c","last_attempt_epoch":1789853557,"last_success_epoch":1789853557,"replica_count":2,"result":"SUCCESS","sha256":"6d9193e25bcf369a8800b1bf5fb6768934cf4786a9cb94e88f6b6e95645b92db"} +FAILURE=VM9130_DISPATCH_ONE_RC_65 +VM_BACKUP_STARTED=true +VM_BACKUP_FRESH=false +RUNTIME_CHANGED=false +RETENTION_GC_STARTED=false +RETENTION_GC_SUCCESS=false +ROLLBACK_STARTED_INTERNAL=true +ROLLBACK_RESTORED_INTERNAL=true +TASK_RESULT=FAIL_HOMELAB_BACKUP_TARGETED_CONTINUATION +HOMELAB_RESULT_CONTRACT={"changes_made":true,"command_id":"SUPPORT-260921-HOMELAB-BACKUP-TARGETED-CONTINUATION-1047R1","rollback_restored":true,"rollback_started":true,"status":"FAIL","version":1} +WORKERS2_BACKUP_TARGETED_CONTINUATION_END=1 OUTPUT_END CHAT_OUTPUT_END