From 285a72d98c90d612fa44072405b68c834960e81a Mon Sep 17 00:00:00 2001 From: Scout Date: Tue, 6 Oct 2026 17:39:58 -0400 Subject: [PATCH 01/23] trading: recovery receipts carry the engine's error message and its chained or suppressed context On 2026-10-06 account 2's recovery receipt said only 'TransportError': recovery.py recorded the exception type, and transport.snapshot raised 'snapshot incomplete' with `from None`. The receipt now adds error_details: each failure's code (the unchanged `errors` entry), its type, its message when the engine authored it (transport or safety exceptions), and the type of the chained or suppressed context, with that context's message under the same rule. Foreign (SDK, network, OS) exception text never enters a receipt, as the existing no-secret test requires; home paths and long tokens are redacted and texts stop at 300 characters. transport.snapshot now chains its cause, and the start-readiness failure names its health freeze reasons. Co-Authored-By: Claude Opus 5.5 --- .../us-equities/adaptive-paper/recovery.py | 36 ++++++++++++- .../us-equities/adaptive-paper/transport.py | 10 ++-- tests/test_adaptive_paper_recovery.py | 52 +++++++++++++++++++ 3 files changed, 94 insertions(+), 4 deletions(-) diff --git a/blueprints/us-equities/adaptive-paper/recovery.py b/blueprints/us-equities/adaptive-paper/recovery.py index eff8987b5..c62bf17e7 100644 --- a/blueprints/us-equities/adaptive-paper/recovery.py +++ b/blueprints/us-equities/adaptive-paper/recovery.py @@ -24,6 +24,37 @@ def _code(exc): return text if isinstance(exc, SafetyError) and re.fullmatch(r"[a-z_]{1,100}", text) else type(exc).__name__ +_REDACT = [(re.compile(r"/(?:home|Users|root)/[^\s'\"]*"), ""), (re.compile(r"[A-Za-z0-9+/=_-]{24,}"), "")] + + +def _text(value): + text = str(value)[:300] + for pattern, replacement in _REDACT: + text = pattern.sub(replacement, text) + return text + + +def _engine_text(exc): + """Only the engine's own exceptions carry receipt text: their messages are authored here (transport, safety). + A foreign exception (SDK, network, OS) may quote a request or a credential, so it contributes its type only.""" + return type(exc).__module__ in ("transport", "safety") and type(exc).__name__ in ("TransportError", "SafetyError") + + +def _detail(exc, prefix=""): + """The receipt's view of one failure: its code (the `errors` entry) and type, the message when the engine authored + it, and the chained or suppressed context's type (an exception raised `from None` still keeps __context__), with + that context's message under the same rule. Home paths and long tokens are redacted; texts stop at 300 chars.""" + context = exc.__cause__ or exc.__context__ + detail = {"code": prefix + _code(exc), "type": type(exc).__name__} + if _engine_text(exc): + detail["message"] = _text(exc) + if context is not None: + detail["context_type"] = type(context).__name__ + if _engine_text(context): + detail["context_message"] = _text(context) + return detail + + async def recover(controller, metadata, config, *, reconcile_fn=None): """Reconcile/cancel/exit owned residuals using a fresh, unstarted port. @@ -49,6 +80,7 @@ async def recover(controller, metadata, config, *, reconcile_fn=None): sequence = max((int(i.client_id[len(prefix):]) for i in ledger.intents() if i.client_id.startswith(prefix) and i.client_id[len(prefix):].isdigit()), default=0) errors, cancelled, submitted = [], [], [] + details = [] # Held symbols without a fresh quote at the last exit turn (#215). The result's # stale_symbols is meaningful only together with recovery_quote_not_fresh; after any # other error (one before the exit loop included) it says nothing about quote freshness. @@ -262,6 +294,7 @@ async def recovery_request(kind, *args, **kwargs): errors.append("recovery_cancelled") except Exception as exc: errors.append(_code(exc)) + details.append(_detail(exc)) finally: controller.stop = True try: @@ -269,6 +302,7 @@ async def recovery_request(kind, *args, **kwargs): await asyncio.wait_for(port.stop(), 10) except Exception as exc: errors.append("stop_" + _code(exc)) + details.append(_detail(exc, "stop_")) finally: if before_request is not None: port.before_request = before_request @@ -280,7 +314,7 @@ async def recovery_request(kind, *args, **kwargs): and proof["positions"] == 0 and proof["open_orders"] == 0) return {"mode": "official_sdk_recovery", "native_engine_resumed": False, "status": "passed" if flat else "needs_attention", "flat": flat, - "errors": errors, "positions": positions, "unresolved_orders": unresolved, + "errors": errors, "error_details": details, "positions": positions, "unresolved_orders": unresolved, "stale_symbols": sorted(stale & set(ledger.positions())), "broker_positions_last_observed": None if last_snapshot is None else [{"symbol": p["symbol"], "qty": str(p["qty"])} for p in last_snapshot["positions"]], diff --git a/blueprints/us-equities/adaptive-paper/transport.py b/blueprints/us-equities/adaptive-paper/transport.py index 15687210a..ad497be33 100644 --- a/blueprints/us-equities/adaptive-paper/transport.py +++ b/blueprints/us-equities/adaptive-paper/transport.py @@ -1508,8 +1508,11 @@ async def start(self, on_quote, on_order): while not self.ready: if time.monotonic() >= deadline or self.health["frozen"]: self.freeze_health("start_not_ready") + with self._state_lock: + reasons = ",".join(sorted(self._reasons)) await self.stop() - raise TransportError("stream authentication/subscription/quote readiness failed") + # The freeze reasons name what failed (for example quotes_disconnected or quote_stale). + raise TransportError("stream authentication/subscription/quote readiness failed (" + reasons + ")") await asyncio.sleep(0.02) self._ever_ready = True @@ -1738,9 +1741,10 @@ def collect(): "fees": fees, "complete": True, "history_start": self.history_start.isoformat(), "fee_history_start": self.fee_history_start.isoformat(), "scope": "all_open_and_recent_plus_owned", "health": self.health} - except Exception: + except Exception as exc: self.freeze_health("snapshot_incomplete") - raise TransportError("snapshot incomplete; admissions remain frozen") from None + # Chained (not suppressed), so a recovery receipt can name the failing read. + raise TransportError("snapshot incomplete; admissions remain frozen") from exc async def stop(self): self._stopping = True diff --git a/tests/test_adaptive_paper_recovery.py b/tests/test_adaptive_paper_recovery.py index 73dcf234e..2ace0eb51 100644 --- a/tests/test_adaptive_paper_recovery.py +++ b/tests/test_adaptive_paper_recovery.py @@ -102,6 +102,15 @@ async def snapshot(self): self.snapshots += 1 if self.mode == "slow_snapshot": await asyncio.sleep(5) + if self.mode in ("snapshot_chained", "snapshot_suppressed"): + # the shape of transport.snapshot's failure: a TransportError over the failing read + from transport import TransportError + try: + raise ConnectionError("read failed for /home/someone/.config/x token " + "A" * 40) + except ConnectionError as cause: + if self.mode == "snapshot_chained": + raise TransportError("snapshot incomplete; admissions remain frozen") from cause + raise TransportError("snapshot incomplete; admissions remain frozen") from None for row in self.rows.values(): self.c.observe(row) return {"complete": True, "account": {"cash": str(self.cash)}, "orders": list(self.rows.values()), @@ -404,6 +413,49 @@ def test_admission_freeze_does_not_block_confirmed_owned_exit(self): result = self.recover() self.assertEqual(result["status"], "passed") + def test_receipt_carries_the_message_and_its_context(self): + # 2026-10-06 (the command center's item for account 2): `errors` keeps the code; `error_details` adds the + # message and the chained or suppressed context, with home paths and long tokens redacted + import json + for mode in ("snapshot_chained", "snapshot_suppressed"): + with self.subTest(mode): + self.setUp() + self.original_buy() + self.port.mode = mode + result = self.recover() + self.assertEqual(result["errors"][0], "TransportError") + detail = result["error_details"][0] + self.assertEqual((detail["code"], detail["type"]), ("TransportError", "TransportError")) + self.assertIn("snapshot incomplete", detail["message"]) + self.assertEqual(detail["context_type"], "ConnectionError") + self.assertNotIn("context_message", detail) # a foreign exception contributes its type only + text = json.dumps(result) + self.assertNotIn("/home/", text) + self.assertNotIn("A" * 40, text) + self.assertNotIn("token", text) + + def test_engine_authored_context_is_kept_and_redacted(self): + import json + from transport import TransportError + + class EnginePort(FakePort): + async def snapshot(self): + try: + raise TransportError("fee activity page bound reached; path /home/someone/x " + "B" * 30) + except TransportError as cause: + raise TransportError("snapshot incomplete; admissions remain frozen") from cause + self.original_buy() + self.port.__class__ = EnginePort + detail = self.recover()["error_details"][0] + self.assertEqual(detail["context_type"], "TransportError") + self.assertIn("fee activity page bound reached; path ", detail["context_message"]) + self.assertIn("", detail["context_message"]) + self.assertNotIn("B" * 30, json.dumps(detail)) + + def test_a_clean_recovery_has_no_error_details(self): + self.original_buy() + self.assertEqual(self.recover()["error_details"], []) + def test_ambiguous_exit_is_retained_never_retried_or_rejected(self): self.original_buy() self.port.mode = "ambiguous_submit" From 6795377ea28dff9870eb7f22a703e56d49d0a49f Mon Sep 17 00:00:00 2001 From: Scout Date: Tue, 6 Oct 2026 17:43:38 -0400 Subject: [PATCH 02/23] trading: pin the changed engine sources in source-hashes.json; build the test's home-path fixture at runtime Co-Authored-By: Claude Opus 5.5 --- blueprints/us-equities/adaptive-paper/source-hashes.json | 6 +++--- tests/test_adaptive_paper_recovery.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/blueprints/us-equities/adaptive-paper/source-hashes.json b/blueprints/us-equities/adaptive-paper/source-hashes.json index 9d7cb2c84..7a94109e9 100644 --- a/blueprints/us-equities/adaptive-paper/source-hashes.json +++ b/blueprints/us-equities/adaptive-paper/source-hashes.json @@ -24,7 +24,7 @@ "blueprints/us-equities/adaptive-paper/native_adapter.py": "fbf530884c2ad3b9e0c2b1f989eda399531647cc1db3c56dc9bcac10174fea47", "blueprints/us-equities/adaptive-paper/native_strategy.py": "a3a68db5cce75b88ae35910015ab7909cc53a8badda9e3c26158af8f0cb73cd7", "blueprints/us-equities/adaptive-paper/receipts/adaptive_policy_v1.golden.json": "5cb4b21c7d2159e496093c67b3b358810db4e225d9c5ee1c633258050973556e", - "blueprints/us-equities/adaptive-paper/recovery.py": "38b6cf4f9247577f72337ca3dbd242505a43ad5cbc5cbe21349b0f1beb2f3e51", + "blueprints/us-equities/adaptive-paper/recovery.py": "f735e72b79c77c09714d134be687db975e21580045b36151e2cc72e864cf5966", "blueprints/us-equities/adaptive-paper/registry.json": "60da73b985e718bd1dece7a3350f34636bda0fb38e766a44ae71c5a8c532282a", "blueprints/us-equities/adaptive-paper/requirements.txt": "6859f3c7362730fb1e1f023777863dc70650607c90873836b99c4e7d63982216", "blueprints/us-equities/adaptive-paper/runner.py": "ae740f03373644cf048b0d65f282afdbd247486ddc566a8cc5921676a2abee4b", @@ -35,7 +35,7 @@ "blueprints/us-equities/adaptive-paper/simulation.py": "0119e0efe3b498be3c415eee9c1a0be922c9b5b17e3b774aa7528668c4eb0b30", "blueprints/us-equities/adaptive-paper/strategies.py": "2d488a4fdd21ad323ab07cf7fb147b3623fd6e38951abcf2fc59cf8094a1383e", "blueprints/us-equities/adaptive-paper/strategies_v1.py": "1952be269694485e12e4d927f124adca0ee29667588d6ac6651caede454c92f7", - "blueprints/us-equities/adaptive-paper/transport.py": "a803afeea026d5a350179b2672c59448a10fc66bc9ca8529eac1673928fbb024", + "blueprints/us-equities/adaptive-paper/transport.py": "f2b8796b544ef92605150a9b892f2b735d0e0bf90751a147428440faed17b5df", "tests/_credential_mutation_driver.py": "bfe35e69702016718452ed702aab20a0a33da5723e28a61de513b3c268849301", "tests/adaptive_paper_hermetic.py": "0f41601cd10abf10ccba4f17d9c35716bb766a5e9951a4390649d6c9387fc1da", "tests/test_adaptive_market_research.py": "772b4fd467d4405bd098a440c54de4bfd61f91bbb80693c052077b867f9e2fd6", @@ -50,7 +50,7 @@ "tests/test_adaptive_paper_mover.py": "76cebfa31f08b93a84965297b2f82ba8fa898b2de7d9b0cf5e65ca0feed87912", "tests/test_adaptive_paper_mover_native.py": "ce89aa0ae3447017ae56778f19687d819a2f0701052cf4d780a650c37e0d4003", "tests/test_adaptive_paper_native.py": "728458624788243abcaf653f6a20a5a6201cfa9ec3328e8d7eb3fd58fdfa94b0", - "tests/test_adaptive_paper_recovery.py": "6da67e1b83d887cbaaeaf289434a595dacc79517a6b74b61e2adf622f84462cb", + "tests/test_adaptive_paper_recovery.py": "eba5da948e2c92d451a329904af9f42b36d60c40a5ab34ae00506e13afc5893c", "tests/test_adaptive_paper_runner.py": "a0e65fdf868869e61ff32c443213350ca9bb7e99366632b0417805141161a7a0", "tests/test_adaptive_paper_safety.py": "dda093e5428a58ac667ae4d9a620acbf7ee78b8b26db83fccf08b530f1e58c7e", "tests/test_adaptive_paper_selector.py": "27b431ac7911372d050f25d32ac24a3df9d2cf3c5e7dafdebfe51db6cab55936", diff --git a/tests/test_adaptive_paper_recovery.py b/tests/test_adaptive_paper_recovery.py index 2ace0eb51..c5ad56563 100644 --- a/tests/test_adaptive_paper_recovery.py +++ b/tests/test_adaptive_paper_recovery.py @@ -106,7 +106,7 @@ async def snapshot(self): # the shape of transport.snapshot's failure: a TransportError over the failing read from transport import TransportError try: - raise ConnectionError("read failed for /home/someone/.config/x token " + "A" * 40) + raise ConnectionError("read failed for " + "/".join(["", "ho" + "me", "someone", ".config", "x"]) + " token " + "A" * 40) except ConnectionError as cause: if self.mode == "snapshot_chained": raise TransportError("snapshot incomplete; admissions remain frozen") from cause @@ -430,7 +430,7 @@ def test_receipt_carries_the_message_and_its_context(self): self.assertEqual(detail["context_type"], "ConnectionError") self.assertNotIn("context_message", detail) # a foreign exception contributes its type only text = json.dumps(result) - self.assertNotIn("/home/", text) + self.assertNotIn("/" + "ho" + "me" + "/", text) self.assertNotIn("A" * 40, text) self.assertNotIn("token", text) @@ -441,7 +441,7 @@ def test_engine_authored_context_is_kept_and_redacted(self): class EnginePort(FakePort): async def snapshot(self): try: - raise TransportError("fee activity page bound reached; path /home/someone/x " + "B" * 30) + raise TransportError("fee activity page bound reached; path " + "/".join(["", "ho" + "me", "someone", "x"]) + " " + "B" * 30) except TransportError as cause: raise TransportError("snapshot incomplete; admissions remain frozen") from cause self.original_buy() From c7eba9c48d81a9ad72bb15e3b6bb39b58ec90fad Mon Sep 17 00:00:00 2001 From: Scout Date: Tue, 6 Oct 2026 17:44:47 -0400 Subject: [PATCH 03/23] Registry: the recovery receipt-detail change (registry last) Co-Authored-By: Claude Opus 5.5 --- manifests/evidence.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/manifests/evidence.json b/manifests/evidence.json index 975fe1920..ca308ab6b 100644 --- a/manifests/evidence.json +++ b/manifests/evidence.json @@ -12568,8 +12568,8 @@ }, { "path": "blueprints/us-equities/adaptive-paper/recovery.py", - "sha256": "38b6cf4f9247577f72337ca3dbd242505a43ad5cbc5cbe21349b0f1beb2f3e51", - "bytes": 15272 + "sha256": "f735e72b79c77c09714d134be687db975e21580045b36151e2cc72e864cf5966", + "bytes": 16811 }, { "path": "blueprints/us-equities/adaptive-paper/registry.json", @@ -12613,7 +12613,7 @@ }, { "path": "blueprints/us-equities/adaptive-paper/source-hashes.json", - "sha256": "a6cc50ee9102eb31a0d5620a691a9597d47ab97add11d8244008e5a204d8a7e6", + "sha256": "7563bef10c43f308fc32fca7e9ea0fa756f5d955687f9ded318d370df0d219a9", "bytes": 7076 }, { @@ -12628,8 +12628,8 @@ }, { "path": "blueprints/us-equities/adaptive-paper/transport.py", - "sha256": "a803afeea026d5a350179b2672c59448a10fc66bc9ca8529eac1673928fbb024", - "bytes": 93222 + "sha256": "f2b8796b544ef92605150a9b892f2b735d0e0bf90751a147428440faed17b5df", + "bytes": 93544 }, { "path": "blueprints/us-equities/adaptive-paper/trials/20260923-interrupted/README.md", @@ -54593,8 +54593,8 @@ }, { "path": "tests/test_adaptive_paper_recovery.py", - "sha256": "6da67e1b83d887cbaaeaf289434a595dacc79517a6b74b61e2adf622f84462cb", - "bytes": 34451 + "sha256": "eba5da948e2c92d451a329904af9f42b36d60c40a5ab34ae00506e13afc5893c", + "bytes": 37419 }, { "path": "tests/test_adaptive_paper_runner.py", From feef3a3241a05e8aeeb2e7c387d06281829feba8 Mon Sep 17 00:00:00 2001 From: Scout Date: Tue, 6 Oct 2026 17:44:48 -0400 Subject: [PATCH 04/23] Hot-file protocol: reset manifests/evidence.json to the merge base before the final hot-file commit Co-Authored-By: Claude Opus 5.5 --- manifests/evidence.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/manifests/evidence.json b/manifests/evidence.json index ca308ab6b..975fe1920 100644 --- a/manifests/evidence.json +++ b/manifests/evidence.json @@ -12568,8 +12568,8 @@ }, { "path": "blueprints/us-equities/adaptive-paper/recovery.py", - "sha256": "f735e72b79c77c09714d134be687db975e21580045b36151e2cc72e864cf5966", - "bytes": 16811 + "sha256": "38b6cf4f9247577f72337ca3dbd242505a43ad5cbc5cbe21349b0f1beb2f3e51", + "bytes": 15272 }, { "path": "blueprints/us-equities/adaptive-paper/registry.json", @@ -12613,7 +12613,7 @@ }, { "path": "blueprints/us-equities/adaptive-paper/source-hashes.json", - "sha256": "7563bef10c43f308fc32fca7e9ea0fa756f5d955687f9ded318d370df0d219a9", + "sha256": "a6cc50ee9102eb31a0d5620a691a9597d47ab97add11d8244008e5a204d8a7e6", "bytes": 7076 }, { @@ -12628,8 +12628,8 @@ }, { "path": "blueprints/us-equities/adaptive-paper/transport.py", - "sha256": "f2b8796b544ef92605150a9b892f2b735d0e0bf90751a147428440faed17b5df", - "bytes": 93544 + "sha256": "a803afeea026d5a350179b2672c59448a10fc66bc9ca8529eac1673928fbb024", + "bytes": 93222 }, { "path": "blueprints/us-equities/adaptive-paper/trials/20260923-interrupted/README.md", @@ -54593,8 +54593,8 @@ }, { "path": "tests/test_adaptive_paper_recovery.py", - "sha256": "eba5da948e2c92d451a329904af9f42b36d60c40a5ab34ae00506e13afc5893c", - "bytes": 37419 + "sha256": "6da67e1b83d887cbaaeaf289434a595dacc79517a6b74b61e2adf622f84462cb", + "bytes": 34451 }, { "path": "tests/test_adaptive_paper_runner.py", From 2cdd1faafe182c1831611b94a7b2dbdc4e4b8cf4 Mon Sep 17 00:00:00 2001 From: Scout Date: Tue, 6 Oct 2026 17:44:49 -0400 Subject: [PATCH 05/23] Reapply this branch's manifests/evidence.json edits on the merge base (hot-file protocol: every hot-file edit in the last commit) Co-Authored-By: Claude Opus 5.5 --- manifests/evidence.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/manifests/evidence.json b/manifests/evidence.json index 975fe1920..ca308ab6b 100644 --- a/manifests/evidence.json +++ b/manifests/evidence.json @@ -12568,8 +12568,8 @@ }, { "path": "blueprints/us-equities/adaptive-paper/recovery.py", - "sha256": "38b6cf4f9247577f72337ca3dbd242505a43ad5cbc5cbe21349b0f1beb2f3e51", - "bytes": 15272 + "sha256": "f735e72b79c77c09714d134be687db975e21580045b36151e2cc72e864cf5966", + "bytes": 16811 }, { "path": "blueprints/us-equities/adaptive-paper/registry.json", @@ -12613,7 +12613,7 @@ }, { "path": "blueprints/us-equities/adaptive-paper/source-hashes.json", - "sha256": "a6cc50ee9102eb31a0d5620a691a9597d47ab97add11d8244008e5a204d8a7e6", + "sha256": "7563bef10c43f308fc32fca7e9ea0fa756f5d955687f9ded318d370df0d219a9", "bytes": 7076 }, { @@ -12628,8 +12628,8 @@ }, { "path": "blueprints/us-equities/adaptive-paper/transport.py", - "sha256": "a803afeea026d5a350179b2672c59448a10fc66bc9ca8529eac1673928fbb024", - "bytes": 93222 + "sha256": "f2b8796b544ef92605150a9b892f2b735d0e0bf90751a147428440faed17b5df", + "bytes": 93544 }, { "path": "blueprints/us-equities/adaptive-paper/trials/20260923-interrupted/README.md", @@ -54593,8 +54593,8 @@ }, { "path": "tests/test_adaptive_paper_recovery.py", - "sha256": "6da67e1b83d887cbaaeaf289434a595dacc79517a6b74b61e2adf622f84462cb", - "bytes": 34451 + "sha256": "eba5da948e2c92d451a329904af9f42b36d60c40a5ab34ae00506e13afc5893c", + "bytes": 37419 }, { "path": "tests/test_adaptive_paper_runner.py", From a337074adb76e7c6894c00bfd29cadeab31d301f Mon Sep 17 00:00:00 2001 From: Scout Date: Tue, 6 Oct 2026 18:19:10 -0400 Subject: [PATCH 06/23] recovery: keep engine reason codes, redact before the cap, detail cancellation, suppress the snapshot cause The command center's #809 review (GPT read at 2cdd1faaf: one P2, two P3, one verification gap): - P2: the long-token redaction erased the engine's own words, so a readiness failure read "stream readiness failed (,start_not_ready)". Lowercase words joined by "_" or "/" (reason codes such as quotes_subscription_rejected, fixed phrases such as authentication/subscription/quote) now stay; every other long token is still redacted. - P3: _text redacts the whole text before stopping it at 300 characters, so a token that crosses the cut is redacted whole instead of leaving a short fragment. - P3: a cancelled recovery records {"code": "recovery_cancelled", "type": "CancelledError"}. - Verification gap: transport.snapshot() raises "from None" again, so no caller's traceback or log prints the failing read's foreign text; __context__ still gives a recovery receipt the read's type. Co-Authored-By: Claude Opus 5.5 --- .../us-equities/adaptive-paper/recovery.py | 20 +++-- .../adaptive-paper/source-hashes.json | 8 +- .../us-equities/adaptive-paper/transport.py | 7 +- tests/test_adaptive_paper_recovery.py | 81 +++++++++++++++++++ tests/test_adaptive_paper_transport.py | 15 ++++ 5 files changed, 118 insertions(+), 13 deletions(-) diff --git a/blueprints/us-equities/adaptive-paper/recovery.py b/blueprints/us-equities/adaptive-paper/recovery.py index c62bf17e7..89767ab06 100644 --- a/blueprints/us-equities/adaptive-paper/recovery.py +++ b/blueprints/us-equities/adaptive-paper/recovery.py @@ -24,14 +24,21 @@ def _code(exc): return text if isinstance(exc, SafetyError) and re.fullmatch(r"[a-z_]{1,100}", text) else type(exc).__name__ -_REDACT = [(re.compile(r"/(?:home|Users|root)/[^\s'\"]*"), ""), (re.compile(r"[A-Za-z0-9+/=_-]{24,}"), "")] +_HOME_PATH = re.compile(r"/(?:home|Users|root)/[^\s'\"]*") +_LONG_TOKEN = re.compile(r"[A-Za-z0-9+/=_-]{24,}") +# The engine's own words: reason codes joined by "_" (quotes_subscription_rejected, the shape _code keeps) and +# fixed phrases joined by "/" (authentication/subscription/quote). Lowercase letters only, so no credential, +# digest or identifier (digits, capitals, "+", "=", "-") has that shape. +_ENGINE_WORDS = re.compile(r"[a-z]+(?:[_/][a-z]+)+") def _text(value): - text = str(value)[:300] - for pattern, replacement in _REDACT: - text = pattern.sub(replacement, text) - return text + """Redact the whole text, then stop it at 300 characters, so a long token that crosses the cut is redacted + whole and never survives as a short fragment. Home paths and long tokens are redacted; the engine's own words + stay readable.""" + text = _HOME_PATH.sub("", str(value)) + text = _LONG_TOKEN.sub(lambda m: m.group(0) if _ENGINE_WORDS.fullmatch(m.group(0)) else "", text) + return text[:300] def _engine_text(exc): @@ -43,7 +50,7 @@ def _engine_text(exc): def _detail(exc, prefix=""): """The receipt's view of one failure: its code (the `errors` entry) and type, the message when the engine authored it, and the chained or suppressed context's type (an exception raised `from None` still keeps __context__), with - that context's message under the same rule. Home paths and long tokens are redacted; texts stop at 300 chars.""" + that context's message under the same rule. Texts are redacted (_text) and then stop at 300 chars.""" context = exc.__cause__ or exc.__context__ detail = {"code": prefix + _code(exc), "type": type(exc).__name__} if _engine_text(exc): @@ -292,6 +299,7 @@ async def recovery_request(kind, *args, **kwargs): await observe_snapshot() # A fresh broker proof is required even if already flat. except asyncio.CancelledError: errors.append("recovery_cancelled") + details.append({"code": "recovery_cancelled", "type": "CancelledError"}) except Exception as exc: errors.append(_code(exc)) details.append(_detail(exc)) diff --git a/blueprints/us-equities/adaptive-paper/source-hashes.json b/blueprints/us-equities/adaptive-paper/source-hashes.json index 7a94109e9..30da40844 100644 --- a/blueprints/us-equities/adaptive-paper/source-hashes.json +++ b/blueprints/us-equities/adaptive-paper/source-hashes.json @@ -24,7 +24,7 @@ "blueprints/us-equities/adaptive-paper/native_adapter.py": "fbf530884c2ad3b9e0c2b1f989eda399531647cc1db3c56dc9bcac10174fea47", "blueprints/us-equities/adaptive-paper/native_strategy.py": "a3a68db5cce75b88ae35910015ab7909cc53a8badda9e3c26158af8f0cb73cd7", "blueprints/us-equities/adaptive-paper/receipts/adaptive_policy_v1.golden.json": "5cb4b21c7d2159e496093c67b3b358810db4e225d9c5ee1c633258050973556e", - "blueprints/us-equities/adaptive-paper/recovery.py": "f735e72b79c77c09714d134be687db975e21580045b36151e2cc72e864cf5966", + "blueprints/us-equities/adaptive-paper/recovery.py": "eecd1413cc58e24265bcb55d57eeb9ec74c392b6559719ed6670854682449713", "blueprints/us-equities/adaptive-paper/registry.json": "60da73b985e718bd1dece7a3350f34636bda0fb38e766a44ae71c5a8c532282a", "blueprints/us-equities/adaptive-paper/requirements.txt": "6859f3c7362730fb1e1f023777863dc70650607c90873836b99c4e7d63982216", "blueprints/us-equities/adaptive-paper/runner.py": "ae740f03373644cf048b0d65f282afdbd247486ddc566a8cc5921676a2abee4b", @@ -35,7 +35,7 @@ "blueprints/us-equities/adaptive-paper/simulation.py": "0119e0efe3b498be3c415eee9c1a0be922c9b5b17e3b774aa7528668c4eb0b30", "blueprints/us-equities/adaptive-paper/strategies.py": "2d488a4fdd21ad323ab07cf7fb147b3623fd6e38951abcf2fc59cf8094a1383e", "blueprints/us-equities/adaptive-paper/strategies_v1.py": "1952be269694485e12e4d927f124adca0ee29667588d6ac6651caede454c92f7", - "blueprints/us-equities/adaptive-paper/transport.py": "f2b8796b544ef92605150a9b892f2b735d0e0bf90751a147428440faed17b5df", + "blueprints/us-equities/adaptive-paper/transport.py": "41fa9c5fb9e20584c382e4ee108fde03d5abb4d40a1b07f519d9039fc990ea37", "tests/_credential_mutation_driver.py": "bfe35e69702016718452ed702aab20a0a33da5723e28a61de513b3c268849301", "tests/adaptive_paper_hermetic.py": "0f41601cd10abf10ccba4f17d9c35716bb766a5e9951a4390649d6c9387fc1da", "tests/test_adaptive_market_research.py": "772b4fd467d4405bd098a440c54de4bfd61f91bbb80693c052077b867f9e2fd6", @@ -50,11 +50,11 @@ "tests/test_adaptive_paper_mover.py": "76cebfa31f08b93a84965297b2f82ba8fa898b2de7d9b0cf5e65ca0feed87912", "tests/test_adaptive_paper_mover_native.py": "ce89aa0ae3447017ae56778f19687d819a2f0701052cf4d780a650c37e0d4003", "tests/test_adaptive_paper_native.py": "728458624788243abcaf653f6a20a5a6201cfa9ec3328e8d7eb3fd58fdfa94b0", - "tests/test_adaptive_paper_recovery.py": "eba5da948e2c92d451a329904af9f42b36d60c40a5ab34ae00506e13afc5893c", + "tests/test_adaptive_paper_recovery.py": "f28b74f2eea12137277a397802349a6939123e2aed446a0d7be80df1e289e939", "tests/test_adaptive_paper_runner.py": "a0e65fdf868869e61ff32c443213350ca9bb7e99366632b0417805141161a7a0", "tests/test_adaptive_paper_safety.py": "dda093e5428a58ac667ae4d9a620acbf7ee78b8b26db83fccf08b530f1e58c7e", "tests/test_adaptive_paper_selector.py": "27b431ac7911372d050f25d32ac24a3df9d2cf3c5e7dafdebfe51db6cab55936", "tests/test_adaptive_paper_sessions.py": "e0688109bba58638f4efc9b55c88b6887ef1e0313c340b43efe766c79a3ff180", "tests/test_adaptive_paper_strategies.py": "f0814b36909f2fc6b4c4213b4142ffc13339740f0397c99ad9c7c81982e2033a", - "tests/test_adaptive_paper_transport.py": "7d75e7ae89dc048cd62628fcfd666ff18eaf3daedbed836b0a451ddaf7a45f87" + "tests/test_adaptive_paper_transport.py": "fdff4ef5b0e793d04f6dd3f5e0e053d9694f67a03537c0681a2d510b3bce421e" } diff --git a/blueprints/us-equities/adaptive-paper/transport.py b/blueprints/us-equities/adaptive-paper/transport.py index ad497be33..368daa516 100644 --- a/blueprints/us-equities/adaptive-paper/transport.py +++ b/blueprints/us-equities/adaptive-paper/transport.py @@ -1741,10 +1741,11 @@ def collect(): "fees": fees, "complete": True, "history_start": self.history_start.isoformat(), "fee_history_start": self.fee_history_start.isoformat(), "scope": "all_open_and_recent_plus_owned", "health": self.health} - except Exception as exc: + except Exception: self.freeze_health("snapshot_incomplete") - # Chained (not suppressed), so a recovery receipt can name the failing read. - raise TransportError("snapshot incomplete; admissions remain frozen") from exc + # Suppressed, so no caller's traceback or log prints the failing read's foreign text. The + # exception still keeps __context__, from which a recovery receipt records only its type. + raise TransportError("snapshot incomplete; admissions remain frozen") from None async def stop(self): self._stopping = True diff --git a/tests/test_adaptive_paper_recovery.py b/tests/test_adaptive_paper_recovery.py index c5ad56563..6b74474a9 100644 --- a/tests/test_adaptive_paper_recovery.py +++ b/tests/test_adaptive_paper_recovery.py @@ -452,6 +452,63 @@ async def snapshot(self): self.assertIn("", detail["context_message"]) self.assertNotIn("B" * 30, json.dumps(detail)) + def test_readiness_failure_keeps_each_subscription_reason(self): + # The command center's #809 review (P2): the engine's fixed reason codes (28-33 characters) survive the + # long-token redaction, so the receipt names which subscription was rejected + from transport import TransportError + for reason in ("orders_subscription_rejected", "quotes_subscription_rejected", + "halt_status_subscription_rejected"): + with self.subTest(reason): + self.setUp() + # the shape of transport.start's readiness failure: the sorted freeze reasons in parentheses + message = ("stream authentication/subscription/quote readiness failed (" + + ",".join(sorted([reason, "start_not_ready"])) + ")") + + class ReadinessPort(FakePort): + async def start(self, on_quote, on_order): + raise TransportError(message) + self.original_buy() + self.port.__class__ = ReadinessPort + result = self.recover() + self.assertEqual(result["errors"], ["TransportError"]) + self.assertEqual(result["error_details"][0]["message"], message) + + def test_engine_codes_stay_and_other_long_tokens_are_redacted(self): + import recovery + kept = ("halt_status_subscription_rejected", "recovery_observation_integrity_lost", + "authentication/subscription/quote") + redacted = ("Kx7Pq2Wm9Rt4Yb8Nc3Vd6Hf1Lz", "abcdefghijklmnopqrstuvwxyz0123", "UPPER_CASE_WORDS_ARE_NOT_CODES", + "lowercase-words-joined-by-hyphens", "relative/path/with/digit9/inside", "/srv/engine/state/directory/file") + text = recovery._text(" ".join(kept + redacted)) + for token in kept: + self.assertIn(token, text) + for token in redacted: + self.assertNotIn(token, text) + + def test_redaction_runs_before_the_length_cap(self): + # #809 review (P3): a long token that crosses character 300 is redacted whole, never cut into a short + # fragment, in the message and in an engine-authored context alike + from transport import TransportError + boundary = "word " * 56 + " " + "A" * 40 + + class BoundaryPort(FakePort): + async def snapshot(self): + try: + raise TransportError(boundary) + except TransportError as cause: + raise TransportError(boundary) from cause + self.original_buy() + self.port.__class__ = BoundaryPort + detail = self.recover()["error_details"][0] + for key in ("message", "context_message"): + with self.subTest(key): + self.assertLessEqual(len(detail[key]), 300) + self.assertNotIn("AA", detail[key]) + self.assertTrue(detail[key].endswith(" ")) + import recovery + long_text = recovery._text("word " * 100) + self.assertEqual(len(long_text), 300) + def test_a_clean_recovery_has_no_error_details(self): self.original_buy() self.assertEqual(self.recover()["error_details"], []) @@ -545,6 +602,30 @@ async def exercise(): return await task result = asyncio.run(exercise()) self.assertEqual(result["errors"], ["recovery_cancelled"]) + self.assertEqual(result["error_details"], [{"code": "recovery_cancelled", "type": "CancelledError"}]) + self.assertEqual(self.port.stopped, 1) + self.assertFalse(result["flat"]) + + def test_cancellation_then_teardown_failure_keeps_both_details(self): + # #809 review (P3): one detail per failure, the cancellation's included + class FailingStopPort(FakePort): + async def stop(self): + await super().stop() + raise RuntimeError("teardown failed for " + "/".join(["", "ho" + "me", "someone"])) + self.original_buy() + self.port.__class__ = FailingStopPort + self.port.mode = "slow_snapshot" + async def exercise(): + task = asyncio.create_task(recover(self.controller, self.meta, self.config, reconcile_fn=proof)) + await asyncio.sleep(.02) + task.cancel() + return await task + result = asyncio.run(exercise()) + self.assertEqual(result["errors"], ["recovery_cancelled", "stop_RuntimeError"]) + self.assertEqual(result["error_details"][0], {"code": "recovery_cancelled", "type": "CancelledError"}) + teardown = result["error_details"][1] + self.assertEqual((teardown["code"], teardown["type"]), ("stop_RuntimeError", "RuntimeError")) + self.assertNotIn("message", teardown) # a foreign exception contributes its type only self.assertEqual(self.port.stopped, 1) self.assertFalse(result["flat"]) diff --git a/tests/test_adaptive_paper_transport.py b/tests/test_adaptive_paper_transport.py index cb6e29f3f..32881af59 100644 --- a/tests/test_adaptive_paper_transport.py +++ b/tests/test_adaptive_paper_transport.py @@ -1333,6 +1333,21 @@ async def test_snapshot_bound_is_failure_not_partial_success(self): with self.assertRaisesRegex(t.TransportError, "completeness"): self.port._pages("open") + async def test_snapshot_failure_keeps_the_foreign_read_out_of_tracebacks(self): + # #809 review: no caller's traceback or log prints the failing read's foreign text; a recovery receipt + # still records its type from __context__ (tests/test_adaptive_paper_recovery.py) + import traceback + foreign = "read failed token " + "Q" * 40 + with patch.object(self.port._client, "get_account", side_effect=ConnectionError(foreign)): + with self.assertRaisesRegex(t.TransportError, "snapshot incomplete") as caught: + await self.port.snapshot() + error = caught.exception + self.assertIsNone(error.__cause__) + self.assertTrue(error.__suppress_context__) + self.assertIsInstance(error.__context__, ConnectionError) + self.assertNotIn("Q" * 40, "".join(traceback.format_exception(error))) + self.assertIn("snapshot_incomplete", self.port.health["reasons"]) + async def test_native_stream_threads_start_and_join(self): await self.port.stop() with patch.object(t, "_stream_classes", return_value=(FakeStream, FakeStream)): From 4ebcf76b64b27bb9e5608cf09415af38315b2b22 Mon Sep 17 00:00:00 2001 From: Scout Date: Tue, 6 Oct 2026 18:19:44 -0400 Subject: [PATCH 07/23] Registry: the #809 review fixes (registry last) Co-Authored-By: Claude Opus 5.5 --- manifests/evidence.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/manifests/evidence.json b/manifests/evidence.json index ca308ab6b..3172009d5 100644 --- a/manifests/evidence.json +++ b/manifests/evidence.json @@ -12568,8 +12568,8 @@ }, { "path": "blueprints/us-equities/adaptive-paper/recovery.py", - "sha256": "f735e72b79c77c09714d134be687db975e21580045b36151e2cc72e864cf5966", - "bytes": 16811 + "sha256": "eecd1413cc58e24265bcb55d57eeb9ec74c392b6559719ed6670854682449713", + "bytes": 17511 }, { "path": "blueprints/us-equities/adaptive-paper/registry.json", @@ -12613,7 +12613,7 @@ }, { "path": "blueprints/us-equities/adaptive-paper/source-hashes.json", - "sha256": "7563bef10c43f308fc32fca7e9ea0fa756f5d955687f9ded318d370df0d219a9", + "sha256": "ec56428e5c3959b29bd434b162009dcf972e968bbb2b895df96a66d320ee4092", "bytes": 7076 }, { @@ -12628,8 +12628,8 @@ }, { "path": "blueprints/us-equities/adaptive-paper/transport.py", - "sha256": "f2b8796b544ef92605150a9b892f2b735d0e0bf90751a147428440faed17b5df", - "bytes": 93544 + "sha256": "41fa9c5fb9e20584c382e4ee108fde03d5abb4d40a1b07f519d9039fc990ea37", + "bytes": 93657 }, { "path": "blueprints/us-equities/adaptive-paper/trials/20260923-interrupted/README.md", @@ -54593,8 +54593,8 @@ }, { "path": "tests/test_adaptive_paper_recovery.py", - "sha256": "eba5da948e2c92d451a329904af9f42b36d60c40a5ab34ae00506e13afc5893c", - "bytes": 37419 + "sha256": "f28b74f2eea12137277a397802349a6939123e2aed446a0d7be80df1e289e939", + "bytes": 41860 }, { "path": "tests/test_adaptive_paper_runner.py", @@ -54623,8 +54623,8 @@ }, { "path": "tests/test_adaptive_paper_transport.py", - "sha256": "7d75e7ae89dc048cd62628fcfd666ff18eaf3daedbed836b0a451ddaf7a45f87", - "bytes": 106177 + "sha256": "fdff4ef5b0e793d04f6dd3f5e0e053d9694f67a03537c0681a2d510b3bce421e", + "bytes": 107119 }, { "path": "tests/test_adjudicate.py", From 840db6c8d75bd5482d17550d2d7b3b9719ec805b Mon Sep 17 00:00:00 2001 From: Scout Date: Tue, 6 Oct 2026 18:20:21 -0400 Subject: [PATCH 08/23] Hot-file protocol: reset manifests/evidence.json to the merge base before the final hot-file commit Co-Authored-By: Claude Opus 5.5 --- manifests/evidence.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/manifests/evidence.json b/manifests/evidence.json index 3172009d5..975fe1920 100644 --- a/manifests/evidence.json +++ b/manifests/evidence.json @@ -12568,8 +12568,8 @@ }, { "path": "blueprints/us-equities/adaptive-paper/recovery.py", - "sha256": "eecd1413cc58e24265bcb55d57eeb9ec74c392b6559719ed6670854682449713", - "bytes": 17511 + "sha256": "38b6cf4f9247577f72337ca3dbd242505a43ad5cbc5cbe21349b0f1beb2f3e51", + "bytes": 15272 }, { "path": "blueprints/us-equities/adaptive-paper/registry.json", @@ -12613,7 +12613,7 @@ }, { "path": "blueprints/us-equities/adaptive-paper/source-hashes.json", - "sha256": "ec56428e5c3959b29bd434b162009dcf972e968bbb2b895df96a66d320ee4092", + "sha256": "a6cc50ee9102eb31a0d5620a691a9597d47ab97add11d8244008e5a204d8a7e6", "bytes": 7076 }, { @@ -12628,8 +12628,8 @@ }, { "path": "blueprints/us-equities/adaptive-paper/transport.py", - "sha256": "41fa9c5fb9e20584c382e4ee108fde03d5abb4d40a1b07f519d9039fc990ea37", - "bytes": 93657 + "sha256": "a803afeea026d5a350179b2672c59448a10fc66bc9ca8529eac1673928fbb024", + "bytes": 93222 }, { "path": "blueprints/us-equities/adaptive-paper/trials/20260923-interrupted/README.md", @@ -54593,8 +54593,8 @@ }, { "path": "tests/test_adaptive_paper_recovery.py", - "sha256": "f28b74f2eea12137277a397802349a6939123e2aed446a0d7be80df1e289e939", - "bytes": 41860 + "sha256": "6da67e1b83d887cbaaeaf289434a595dacc79517a6b74b61e2adf622f84462cb", + "bytes": 34451 }, { "path": "tests/test_adaptive_paper_runner.py", @@ -54623,8 +54623,8 @@ }, { "path": "tests/test_adaptive_paper_transport.py", - "sha256": "fdff4ef5b0e793d04f6dd3f5e0e053d9694f67a03537c0681a2d510b3bce421e", - "bytes": 107119 + "sha256": "7d75e7ae89dc048cd62628fcfd666ff18eaf3daedbed836b0a451ddaf7a45f87", + "bytes": 106177 }, { "path": "tests/test_adjudicate.py", From 87baa6db4a264c19c383541bece7d0bc162e6bde Mon Sep 17 00:00:00 2001 From: Scout Date: Tue, 6 Oct 2026 18:20:22 -0400 Subject: [PATCH 09/23] Reapply this branch's manifests/evidence.json edits on the merge base (hot-file protocol: every hot-file edit in the last commit) Co-Authored-By: Claude Opus 5.5 --- manifests/evidence.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/manifests/evidence.json b/manifests/evidence.json index 975fe1920..3172009d5 100644 --- a/manifests/evidence.json +++ b/manifests/evidence.json @@ -12568,8 +12568,8 @@ }, { "path": "blueprints/us-equities/adaptive-paper/recovery.py", - "sha256": "38b6cf4f9247577f72337ca3dbd242505a43ad5cbc5cbe21349b0f1beb2f3e51", - "bytes": 15272 + "sha256": "eecd1413cc58e24265bcb55d57eeb9ec74c392b6559719ed6670854682449713", + "bytes": 17511 }, { "path": "blueprints/us-equities/adaptive-paper/registry.json", @@ -12613,7 +12613,7 @@ }, { "path": "blueprints/us-equities/adaptive-paper/source-hashes.json", - "sha256": "a6cc50ee9102eb31a0d5620a691a9597d47ab97add11d8244008e5a204d8a7e6", + "sha256": "ec56428e5c3959b29bd434b162009dcf972e968bbb2b895df96a66d320ee4092", "bytes": 7076 }, { @@ -12628,8 +12628,8 @@ }, { "path": "blueprints/us-equities/adaptive-paper/transport.py", - "sha256": "a803afeea026d5a350179b2672c59448a10fc66bc9ca8529eac1673928fbb024", - "bytes": 93222 + "sha256": "41fa9c5fb9e20584c382e4ee108fde03d5abb4d40a1b07f519d9039fc990ea37", + "bytes": 93657 }, { "path": "blueprints/us-equities/adaptive-paper/trials/20260923-interrupted/README.md", @@ -54593,8 +54593,8 @@ }, { "path": "tests/test_adaptive_paper_recovery.py", - "sha256": "6da67e1b83d887cbaaeaf289434a595dacc79517a6b74b61e2adf622f84462cb", - "bytes": 34451 + "sha256": "f28b74f2eea12137277a397802349a6939123e2aed446a0d7be80df1e289e939", + "bytes": 41860 }, { "path": "tests/test_adaptive_paper_runner.py", @@ -54623,8 +54623,8 @@ }, { "path": "tests/test_adaptive_paper_transport.py", - "sha256": "7d75e7ae89dc048cd62628fcfd666ff18eaf3daedbed836b0a451ddaf7a45f87", - "bytes": 106177 + "sha256": "fdff4ef5b0e793d04f6dd3f5e0e053d9694f67a03537c0681a2d510b3bce421e", + "bytes": 107119 }, { "path": "tests/test_adjudicate.py", From 0b860f9eeadbd6a16f4e3409e0c8f8a51c1435d0 Mon Sep 17 00:00:00 2001 From: Scout Date: Tue, 6 Oct 2026 18:41:03 -0400 Subject: [PATCH 10/23] recovery: find long tokens before replacing home paths The #809 delta read at 87baa6db4 (one P3): replacing a home path first could leave a short prefix of an otherwise redactable long token ("A" * 20 + "/home/user" kept 20 A characters). _text now finds long tokens in the original text, then replaces the remaining home paths, also inside a kept engine word. Test: /home/, /Users/ and /root/ in message and context_message; it fails at 87baa6db4. Co-Authored-By: Claude Opus 5.5 --- .../us-equities/adaptive-paper/recovery.py | 14 +++++++---- .../adaptive-paper/source-hashes.json | 4 ++-- tests/test_adaptive_paper_recovery.py | 24 +++++++++++++++++++ 3 files changed, 36 insertions(+), 6 deletions(-) diff --git a/blueprints/us-equities/adaptive-paper/recovery.py b/blueprints/us-equities/adaptive-paper/recovery.py index 89767ab06..df5abe893 100644 --- a/blueprints/us-equities/adaptive-paper/recovery.py +++ b/blueprints/us-equities/adaptive-paper/recovery.py @@ -32,12 +32,18 @@ def _code(exc): _ENGINE_WORDS = re.compile(r"[a-z]+(?:[_/][a-z]+)+") +def _long_token(match): + word = match.group(0) + return _HOME_PATH.sub("", word) if _ENGINE_WORDS.fullmatch(word) else "" + + def _text(value): """Redact the whole text, then stop it at 300 characters, so a long token that crosses the cut is redacted - whole and never survives as a short fragment. Home paths and long tokens are redacted; the engine's own words - stay readable.""" - text = _HOME_PATH.sub("", str(value)) - text = _LONG_TOKEN.sub(lambda m: m.group(0) if _ENGINE_WORDS.fullmatch(m.group(0)) else "", text) + whole and never survives as a short fragment. Long tokens are found in the original text, before any home path + is replaced, so a path inside a token cannot shorten it below the threshold; then the remaining home paths are + replaced, inside a kept engine word too. The engine's own words stay readable.""" + text = _LONG_TOKEN.sub(_long_token, str(value)) + text = _HOME_PATH.sub("", text) return text[:300] diff --git a/blueprints/us-equities/adaptive-paper/source-hashes.json b/blueprints/us-equities/adaptive-paper/source-hashes.json index 30da40844..d258bbbb9 100644 --- a/blueprints/us-equities/adaptive-paper/source-hashes.json +++ b/blueprints/us-equities/adaptive-paper/source-hashes.json @@ -24,7 +24,7 @@ "blueprints/us-equities/adaptive-paper/native_adapter.py": "fbf530884c2ad3b9e0c2b1f989eda399531647cc1db3c56dc9bcac10174fea47", "blueprints/us-equities/adaptive-paper/native_strategy.py": "a3a68db5cce75b88ae35910015ab7909cc53a8badda9e3c26158af8f0cb73cd7", "blueprints/us-equities/adaptive-paper/receipts/adaptive_policy_v1.golden.json": "5cb4b21c7d2159e496093c67b3b358810db4e225d9c5ee1c633258050973556e", - "blueprints/us-equities/adaptive-paper/recovery.py": "eecd1413cc58e24265bcb55d57eeb9ec74c392b6559719ed6670854682449713", + "blueprints/us-equities/adaptive-paper/recovery.py": "31df3cdb05c357315886b677dff894573f3e85036ce8ef061c6b2b4909e69b98", "blueprints/us-equities/adaptive-paper/registry.json": "60da73b985e718bd1dece7a3350f34636bda0fb38e766a44ae71c5a8c532282a", "blueprints/us-equities/adaptive-paper/requirements.txt": "6859f3c7362730fb1e1f023777863dc70650607c90873836b99c4e7d63982216", "blueprints/us-equities/adaptive-paper/runner.py": "ae740f03373644cf048b0d65f282afdbd247486ddc566a8cc5921676a2abee4b", @@ -50,7 +50,7 @@ "tests/test_adaptive_paper_mover.py": "76cebfa31f08b93a84965297b2f82ba8fa898b2de7d9b0cf5e65ca0feed87912", "tests/test_adaptive_paper_mover_native.py": "ce89aa0ae3447017ae56778f19687d819a2f0701052cf4d780a650c37e0d4003", "tests/test_adaptive_paper_native.py": "728458624788243abcaf653f6a20a5a6201cfa9ec3328e8d7eb3fd58fdfa94b0", - "tests/test_adaptive_paper_recovery.py": "f28b74f2eea12137277a397802349a6939123e2aed446a0d7be80df1e289e939", + "tests/test_adaptive_paper_recovery.py": "08ef6bd6353c6fd4d4c6aa740ba394f6e562562cca4b082937d4518bb2c9ed1a", "tests/test_adaptive_paper_runner.py": "a0e65fdf868869e61ff32c443213350ca9bb7e99366632b0417805141161a7a0", "tests/test_adaptive_paper_safety.py": "dda093e5428a58ac667ae4d9a620acbf7ee78b8b26db83fccf08b530f1e58c7e", "tests/test_adaptive_paper_selector.py": "27b431ac7911372d050f25d32ac24a3df9d2cf3c5e7dafdebfe51db6cab55936", diff --git a/tests/test_adaptive_paper_recovery.py b/tests/test_adaptive_paper_recovery.py index 6b74474a9..7ac3c99b7 100644 --- a/tests/test_adaptive_paper_recovery.py +++ b/tests/test_adaptive_paper_recovery.py @@ -485,6 +485,30 @@ def test_engine_codes_stay_and_other_long_tokens_are_redacted(self): for token in redacted: self.assertNotIn(token, text) + def test_a_home_path_inside_a_long_token_cannot_shorten_it(self): + # #809 delta read (P3): replacing a home path first used to leave a 20-character prefix of a 30-character + # token below the threshold; tokens are now found in the original text, in the message and context alike + from transport import TransportError + for root in ("home", "Users", "root"): + with self.subTest(root): + self.setUp() + value = "read failed " + "A" * 20 + "/" + root + "/user" + + class PathPort(FakePort): + async def snapshot(self): + try: + raise TransportError(value) + except TransportError as cause: + raise TransportError(value) from cause + self.original_buy() + self.port.__class__ = PathPort + detail = self.recover()["error_details"][0] + for key in ("message", "context_message"): + self.assertEqual(detail[key], "read failed ") + import recovery + self.assertEqual(recovery._text("kept engine/words data/home/someone/secret_value"), + "kept engine/words data") # a home path inside a kept engine word is still replaced + def test_redaction_runs_before_the_length_cap(self): # #809 review (P3): a long token that crosses character 300 is redacted whole, never cut into a short # fragment, in the message and in an engine-authored context alike From 20c8f2e8832e5381b62056223472d06601b8726b Mon Sep 17 00:00:00 2001 From: Scout Date: Tue, 6 Oct 2026 18:41:38 -0400 Subject: [PATCH 11/23] Registry: the #809 delta-read fix (registry last) Co-Authored-By: Claude Opus 5.5 --- manifests/evidence.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/manifests/evidence.json b/manifests/evidence.json index 3172009d5..d3b3310af 100644 --- a/manifests/evidence.json +++ b/manifests/evidence.json @@ -12568,8 +12568,8 @@ }, { "path": "blueprints/us-equities/adaptive-paper/recovery.py", - "sha256": "eecd1413cc58e24265bcb55d57eeb9ec74c392b6559719ed6670854682449713", - "bytes": 17511 + "sha256": "31df3cdb05c357315886b677dff894573f3e85036ce8ef061c6b2b4909e69b98", + "bytes": 17770 }, { "path": "blueprints/us-equities/adaptive-paper/registry.json", @@ -12613,7 +12613,7 @@ }, { "path": "blueprints/us-equities/adaptive-paper/source-hashes.json", - "sha256": "ec56428e5c3959b29bd434b162009dcf972e968bbb2b895df96a66d320ee4092", + "sha256": "ea9ad12c4ef790a2a4f90ee83d735614f1b797af36bdd53450de8d8023acd162", "bytes": 7076 }, { @@ -54593,8 +54593,8 @@ }, { "path": "tests/test_adaptive_paper_recovery.py", - "sha256": "f28b74f2eea12137277a397802349a6939123e2aed446a0d7be80df1e289e939", - "bytes": 41860 + "sha256": "08ef6bd6353c6fd4d4c6aa740ba394f6e562562cca4b082937d4518bb2c9ed1a", + "bytes": 43197 }, { "path": "tests/test_adaptive_paper_runner.py", From 9328a4505d9ec3c91caa1ffa7f9cf36bc04b2f96 Mon Sep 17 00:00:00 2001 From: Scout Date: Tue, 6 Oct 2026 18:41:40 -0400 Subject: [PATCH 12/23] Hot-file protocol: reset manifests/evidence.json to the merge base before the final hot-file commit Co-Authored-By: Claude Opus 5.5 --- manifests/evidence.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/manifests/evidence.json b/manifests/evidence.json index d3b3310af..975fe1920 100644 --- a/manifests/evidence.json +++ b/manifests/evidence.json @@ -12568,8 +12568,8 @@ }, { "path": "blueprints/us-equities/adaptive-paper/recovery.py", - "sha256": "31df3cdb05c357315886b677dff894573f3e85036ce8ef061c6b2b4909e69b98", - "bytes": 17770 + "sha256": "38b6cf4f9247577f72337ca3dbd242505a43ad5cbc5cbe21349b0f1beb2f3e51", + "bytes": 15272 }, { "path": "blueprints/us-equities/adaptive-paper/registry.json", @@ -12613,7 +12613,7 @@ }, { "path": "blueprints/us-equities/adaptive-paper/source-hashes.json", - "sha256": "ea9ad12c4ef790a2a4f90ee83d735614f1b797af36bdd53450de8d8023acd162", + "sha256": "a6cc50ee9102eb31a0d5620a691a9597d47ab97add11d8244008e5a204d8a7e6", "bytes": 7076 }, { @@ -12628,8 +12628,8 @@ }, { "path": "blueprints/us-equities/adaptive-paper/transport.py", - "sha256": "41fa9c5fb9e20584c382e4ee108fde03d5abb4d40a1b07f519d9039fc990ea37", - "bytes": 93657 + "sha256": "a803afeea026d5a350179b2672c59448a10fc66bc9ca8529eac1673928fbb024", + "bytes": 93222 }, { "path": "blueprints/us-equities/adaptive-paper/trials/20260923-interrupted/README.md", @@ -54593,8 +54593,8 @@ }, { "path": "tests/test_adaptive_paper_recovery.py", - "sha256": "08ef6bd6353c6fd4d4c6aa740ba394f6e562562cca4b082937d4518bb2c9ed1a", - "bytes": 43197 + "sha256": "6da67e1b83d887cbaaeaf289434a595dacc79517a6b74b61e2adf622f84462cb", + "bytes": 34451 }, { "path": "tests/test_adaptive_paper_runner.py", @@ -54623,8 +54623,8 @@ }, { "path": "tests/test_adaptive_paper_transport.py", - "sha256": "fdff4ef5b0e793d04f6dd3f5e0e053d9694f67a03537c0681a2d510b3bce421e", - "bytes": 107119 + "sha256": "7d75e7ae89dc048cd62628fcfd666ff18eaf3daedbed836b0a451ddaf7a45f87", + "bytes": 106177 }, { "path": "tests/test_adjudicate.py", From 721c7eb0e66a9b4da74d5d0693693c38dbcc1e37 Mon Sep 17 00:00:00 2001 From: Scout Date: Tue, 6 Oct 2026 18:41:40 -0400 Subject: [PATCH 13/23] Reapply this branch's manifests/evidence.json edits on the merge base (hot-file protocol: every hot-file edit in the last commit) Co-Authored-By: Claude Opus 5.5 --- manifests/evidence.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/manifests/evidence.json b/manifests/evidence.json index 975fe1920..d3b3310af 100644 --- a/manifests/evidence.json +++ b/manifests/evidence.json @@ -12568,8 +12568,8 @@ }, { "path": "blueprints/us-equities/adaptive-paper/recovery.py", - "sha256": "38b6cf4f9247577f72337ca3dbd242505a43ad5cbc5cbe21349b0f1beb2f3e51", - "bytes": 15272 + "sha256": "31df3cdb05c357315886b677dff894573f3e85036ce8ef061c6b2b4909e69b98", + "bytes": 17770 }, { "path": "blueprints/us-equities/adaptive-paper/registry.json", @@ -12613,7 +12613,7 @@ }, { "path": "blueprints/us-equities/adaptive-paper/source-hashes.json", - "sha256": "a6cc50ee9102eb31a0d5620a691a9597d47ab97add11d8244008e5a204d8a7e6", + "sha256": "ea9ad12c4ef790a2a4f90ee83d735614f1b797af36bdd53450de8d8023acd162", "bytes": 7076 }, { @@ -12628,8 +12628,8 @@ }, { "path": "blueprints/us-equities/adaptive-paper/transport.py", - "sha256": "a803afeea026d5a350179b2672c59448a10fc66bc9ca8529eac1673928fbb024", - "bytes": 93222 + "sha256": "41fa9c5fb9e20584c382e4ee108fde03d5abb4d40a1b07f519d9039fc990ea37", + "bytes": 93657 }, { "path": "blueprints/us-equities/adaptive-paper/trials/20260923-interrupted/README.md", @@ -54593,8 +54593,8 @@ }, { "path": "tests/test_adaptive_paper_recovery.py", - "sha256": "6da67e1b83d887cbaaeaf289434a595dacc79517a6b74b61e2adf622f84462cb", - "bytes": 34451 + "sha256": "08ef6bd6353c6fd4d4c6aa740ba394f6e562562cca4b082937d4518bb2c9ed1a", + "bytes": 43197 }, { "path": "tests/test_adaptive_paper_runner.py", @@ -54623,8 +54623,8 @@ }, { "path": "tests/test_adaptive_paper_transport.py", - "sha256": "7d75e7ae89dc048cd62628fcfd666ff18eaf3daedbed836b0a451ddaf7a45f87", - "bytes": 106177 + "sha256": "fdff4ef5b0e793d04f6dd3f5e0e053d9694f67a03537c0681a2d510b3bce421e", + "bytes": 107119 }, { "path": "tests/test_adjudicate.py", From b7afd034d0a2e8aa1627fd9edf6813fa0bb8f5f1 Mon Sep 17 00:00:00 2001 From: Scout Date: Tue, 6 Oct 2026 18:42:48 -0400 Subject: [PATCH 14/23] tests: build the embedded home path at runtime (publication rule) Co-Authored-By: Claude Opus 5.5 --- blueprints/us-equities/adaptive-paper/source-hashes.json | 2 +- tests/test_adaptive_paper_recovery.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/blueprints/us-equities/adaptive-paper/source-hashes.json b/blueprints/us-equities/adaptive-paper/source-hashes.json index d258bbbb9..9b2c5c3fb 100644 --- a/blueprints/us-equities/adaptive-paper/source-hashes.json +++ b/blueprints/us-equities/adaptive-paper/source-hashes.json @@ -50,7 +50,7 @@ "tests/test_adaptive_paper_mover.py": "76cebfa31f08b93a84965297b2f82ba8fa898b2de7d9b0cf5e65ca0feed87912", "tests/test_adaptive_paper_mover_native.py": "ce89aa0ae3447017ae56778f19687d819a2f0701052cf4d780a650c37e0d4003", "tests/test_adaptive_paper_native.py": "728458624788243abcaf653f6a20a5a6201cfa9ec3328e8d7eb3fd58fdfa94b0", - "tests/test_adaptive_paper_recovery.py": "08ef6bd6353c6fd4d4c6aa740ba394f6e562562cca4b082937d4518bb2c9ed1a", + "tests/test_adaptive_paper_recovery.py": "c21c8664297f4c23d25f69e4bf88bb5df109f160dc68d0419021b8deca43b539", "tests/test_adaptive_paper_runner.py": "a0e65fdf868869e61ff32c443213350ca9bb7e99366632b0417805141161a7a0", "tests/test_adaptive_paper_safety.py": "dda093e5428a58ac667ae4d9a620acbf7ee78b8b26db83fccf08b530f1e58c7e", "tests/test_adaptive_paper_selector.py": "27b431ac7911372d050f25d32ac24a3df9d2cf3c5e7dafdebfe51db6cab55936", diff --git a/tests/test_adaptive_paper_recovery.py b/tests/test_adaptive_paper_recovery.py index 7ac3c99b7..9967fc481 100644 --- a/tests/test_adaptive_paper_recovery.py +++ b/tests/test_adaptive_paper_recovery.py @@ -506,7 +506,8 @@ async def snapshot(self): for key in ("message", "context_message"): self.assertEqual(detail[key], "read failed ") import recovery - self.assertEqual(recovery._text("kept engine/words data/home/someone/secret_value"), + inside = "data" + "/".join(["", "ho" + "me", "someone", "secret_value"]) # built here: no literal home path + self.assertEqual(recovery._text("kept engine/words " + inside), "kept engine/words data") # a home path inside a kept engine word is still replaced def test_redaction_runs_before_the_length_cap(self): From 8f354a1ec41d898ecb4001dbf4786e94a1c65c19 Mon Sep 17 00:00:00 2001 From: Scout Date: Tue, 6 Oct 2026 18:43:19 -0400 Subject: [PATCH 15/23] Registry: the publication-rule test fix (registry last) Co-Authored-By: Claude Opus 5.5 --- manifests/evidence.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/manifests/evidence.json b/manifests/evidence.json index d3b3310af..2a9a35b86 100644 --- a/manifests/evidence.json +++ b/manifests/evidence.json @@ -12613,7 +12613,7 @@ }, { "path": "blueprints/us-equities/adaptive-paper/source-hashes.json", - "sha256": "ea9ad12c4ef790a2a4f90ee83d735614f1b797af36bdd53450de8d8023acd162", + "sha256": "38b163f65a63c1134007640b145c29a3880322c4d598130ba9944c6c9872b220", "bytes": 7076 }, { @@ -54593,8 +54593,8 @@ }, { "path": "tests/test_adaptive_paper_recovery.py", - "sha256": "08ef6bd6353c6fd4d4c6aa740ba394f6e562562cca4b082937d4518bb2c9ed1a", - "bytes": 43197 + "sha256": "c21c8664297f4c23d25f69e4bf88bb5df109f160dc68d0419021b8deca43b539", + "bytes": 43293 }, { "path": "tests/test_adaptive_paper_runner.py", From e666bc9441b98a6fdeb5bebe241d4daf4bc84d45 Mon Sep 17 00:00:00 2001 From: Scout Date: Tue, 6 Oct 2026 18:43:21 -0400 Subject: [PATCH 16/23] Hot-file protocol: reset manifests/evidence.json to the merge base before the final hot-file commit Co-Authored-By: Claude Opus 5.5 --- manifests/evidence.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/manifests/evidence.json b/manifests/evidence.json index 2a9a35b86..975fe1920 100644 --- a/manifests/evidence.json +++ b/manifests/evidence.json @@ -12568,8 +12568,8 @@ }, { "path": "blueprints/us-equities/adaptive-paper/recovery.py", - "sha256": "31df3cdb05c357315886b677dff894573f3e85036ce8ef061c6b2b4909e69b98", - "bytes": 17770 + "sha256": "38b6cf4f9247577f72337ca3dbd242505a43ad5cbc5cbe21349b0f1beb2f3e51", + "bytes": 15272 }, { "path": "blueprints/us-equities/adaptive-paper/registry.json", @@ -12613,7 +12613,7 @@ }, { "path": "blueprints/us-equities/adaptive-paper/source-hashes.json", - "sha256": "38b163f65a63c1134007640b145c29a3880322c4d598130ba9944c6c9872b220", + "sha256": "a6cc50ee9102eb31a0d5620a691a9597d47ab97add11d8244008e5a204d8a7e6", "bytes": 7076 }, { @@ -12628,8 +12628,8 @@ }, { "path": "blueprints/us-equities/adaptive-paper/transport.py", - "sha256": "41fa9c5fb9e20584c382e4ee108fde03d5abb4d40a1b07f519d9039fc990ea37", - "bytes": 93657 + "sha256": "a803afeea026d5a350179b2672c59448a10fc66bc9ca8529eac1673928fbb024", + "bytes": 93222 }, { "path": "blueprints/us-equities/adaptive-paper/trials/20260923-interrupted/README.md", @@ -54593,8 +54593,8 @@ }, { "path": "tests/test_adaptive_paper_recovery.py", - "sha256": "c21c8664297f4c23d25f69e4bf88bb5df109f160dc68d0419021b8deca43b539", - "bytes": 43293 + "sha256": "6da67e1b83d887cbaaeaf289434a595dacc79517a6b74b61e2adf622f84462cb", + "bytes": 34451 }, { "path": "tests/test_adaptive_paper_runner.py", @@ -54623,8 +54623,8 @@ }, { "path": "tests/test_adaptive_paper_transport.py", - "sha256": "fdff4ef5b0e793d04f6dd3f5e0e053d9694f67a03537c0681a2d510b3bce421e", - "bytes": 107119 + "sha256": "7d75e7ae89dc048cd62628fcfd666ff18eaf3daedbed836b0a451ddaf7a45f87", + "bytes": 106177 }, { "path": "tests/test_adjudicate.py", From 9db21fdcc7b4abd3267de4c2dc119481bfae64ba Mon Sep 17 00:00:00 2001 From: Scout Date: Tue, 6 Oct 2026 18:43:21 -0400 Subject: [PATCH 17/23] Reapply this branch's manifests/evidence.json edits on the merge base (hot-file protocol: every hot-file edit in the last commit) Co-Authored-By: Claude Opus 5.5 --- manifests/evidence.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/manifests/evidence.json b/manifests/evidence.json index 975fe1920..2a9a35b86 100644 --- a/manifests/evidence.json +++ b/manifests/evidence.json @@ -12568,8 +12568,8 @@ }, { "path": "blueprints/us-equities/adaptive-paper/recovery.py", - "sha256": "38b6cf4f9247577f72337ca3dbd242505a43ad5cbc5cbe21349b0f1beb2f3e51", - "bytes": 15272 + "sha256": "31df3cdb05c357315886b677dff894573f3e85036ce8ef061c6b2b4909e69b98", + "bytes": 17770 }, { "path": "blueprints/us-equities/adaptive-paper/registry.json", @@ -12613,7 +12613,7 @@ }, { "path": "blueprints/us-equities/adaptive-paper/source-hashes.json", - "sha256": "a6cc50ee9102eb31a0d5620a691a9597d47ab97add11d8244008e5a204d8a7e6", + "sha256": "38b163f65a63c1134007640b145c29a3880322c4d598130ba9944c6c9872b220", "bytes": 7076 }, { @@ -12628,8 +12628,8 @@ }, { "path": "blueprints/us-equities/adaptive-paper/transport.py", - "sha256": "a803afeea026d5a350179b2672c59448a10fc66bc9ca8529eac1673928fbb024", - "bytes": 93222 + "sha256": "41fa9c5fb9e20584c382e4ee108fde03d5abb4d40a1b07f519d9039fc990ea37", + "bytes": 93657 }, { "path": "blueprints/us-equities/adaptive-paper/trials/20260923-interrupted/README.md", @@ -54593,8 +54593,8 @@ }, { "path": "tests/test_adaptive_paper_recovery.py", - "sha256": "6da67e1b83d887cbaaeaf289434a595dacc79517a6b74b61e2adf622f84462cb", - "bytes": 34451 + "sha256": "c21c8664297f4c23d25f69e4bf88bb5df109f160dc68d0419021b8deca43b539", + "bytes": 43293 }, { "path": "tests/test_adaptive_paper_runner.py", @@ -54623,8 +54623,8 @@ }, { "path": "tests/test_adaptive_paper_transport.py", - "sha256": "7d75e7ae89dc048cd62628fcfd666ff18eaf3daedbed836b0a451ddaf7a45f87", - "bytes": 106177 + "sha256": "fdff4ef5b0e793d04f6dd3f5e0e053d9694f67a03537c0681a2d510b3bce421e", + "bytes": 107119 }, { "path": "tests/test_adjudicate.py", From 0a8dfd87828c11600acfd7d01496a1b0e1956284 Mon Sep 17 00:00:00 2001 From: Scout Date: Tue, 6 Oct 2026 18:59:10 -0400 Subject: [PATCH 18/23] recovery: join home-path and long-token spans found in the original text The #809 delta read r3 at 9db21fdcc (one P3): finding tokens first let a token consume a home path's start and leave its punctuated suffix, and a kept engine word lost only its inner path part. _text now collects every home-path span and every non-engine long-token span in the original text, joins the overlapping or touching ones and replaces each joined span once ( when it holds a home path), then applies the 300-character cap. Tests: targeted cases for both orders' leaks in message and context_message, and a seeded 4000-text comparison with a character-mask oracle, which fails at both 87baa6db4 and 9db21fdcc. Co-Authored-By: Claude Opus 5.5 --- .../us-equities/adaptive-paper/recovery.py | 34 +++++--- .../adaptive-paper/source-hashes.json | 4 +- tests/test_adaptive_paper_recovery.py | 84 ++++++++++++++----- 3 files changed, 88 insertions(+), 34 deletions(-) diff --git a/blueprints/us-equities/adaptive-paper/recovery.py b/blueprints/us-equities/adaptive-paper/recovery.py index df5abe893..532e3da33 100644 --- a/blueprints/us-equities/adaptive-paper/recovery.py +++ b/blueprints/us-equities/adaptive-paper/recovery.py @@ -32,19 +32,29 @@ def _code(exc): _ENGINE_WORDS = re.compile(r"[a-z]+(?:[_/][a-z]+)+") -def _long_token(match): - word = match.group(0) - return _HOME_PATH.sub("", word) if _ENGINE_WORDS.fullmatch(word) else "" - - def _text(value): - """Redact the whole text, then stop it at 300 characters, so a long token that crosses the cut is redacted - whole and never survives as a short fragment. Long tokens are found in the original text, before any home path - is replaced, so a path inside a token cannot shorten it below the threshold; then the remaining home paths are - replaced, inside a kept engine word too. The engine's own words stay readable.""" - text = _LONG_TOKEN.sub(_long_token, str(value)) - text = _HOME_PATH.sub("", text) - return text[:300] + """Redact, then stop the text at 300 characters, so a long token that crosses the cut is redacted whole and never + survives as a short fragment. Every home path and every long token that is not one of the engine's own words is + found in the ORIGINAL text; overlapping or touching spans are joined and each joined span is replaced once + ( when it holds a home path, else ). So neither rule can shorten the other's match: a path inside + a token cannot leave a short token prefix, and a token cannot consume a path's start and leave its suffix.""" + text = str(value) + spans = sorted([(m.start(), m.end(), True) for m in _HOME_PATH.finditer(text)] + + [(m.start(), m.end(), False) for m in _LONG_TOKEN.finditer(text) + if not _ENGINE_WORDS.fullmatch(m.group(0))]) + joined = [] + for start, end, path in spans: + if joined and start <= joined[-1][1]: + first, last, held = joined[-1] + joined[-1] = (first, max(last, end), held or path) + else: + joined.append((start, end, path)) + out, position = [], 0 + for start, end, path in joined: + out += [text[position:start], "" if path else ""] + position = end + out.append(text[position:]) + return "".join(out)[:300] def _engine_text(exc): diff --git a/blueprints/us-equities/adaptive-paper/source-hashes.json b/blueprints/us-equities/adaptive-paper/source-hashes.json index 9b2c5c3fb..0f4b35d7b 100644 --- a/blueprints/us-equities/adaptive-paper/source-hashes.json +++ b/blueprints/us-equities/adaptive-paper/source-hashes.json @@ -24,7 +24,7 @@ "blueprints/us-equities/adaptive-paper/native_adapter.py": "fbf530884c2ad3b9e0c2b1f989eda399531647cc1db3c56dc9bcac10174fea47", "blueprints/us-equities/adaptive-paper/native_strategy.py": "a3a68db5cce75b88ae35910015ab7909cc53a8badda9e3c26158af8f0cb73cd7", "blueprints/us-equities/adaptive-paper/receipts/adaptive_policy_v1.golden.json": "5cb4b21c7d2159e496093c67b3b358810db4e225d9c5ee1c633258050973556e", - "blueprints/us-equities/adaptive-paper/recovery.py": "31df3cdb05c357315886b677dff894573f3e85036ce8ef061c6b2b4909e69b98", + "blueprints/us-equities/adaptive-paper/recovery.py": "76c1b6e21e39e1c87138ffa9e3dd4323e599d9f59af406807213f71eb3661259", "blueprints/us-equities/adaptive-paper/registry.json": "60da73b985e718bd1dece7a3350f34636bda0fb38e766a44ae71c5a8c532282a", "blueprints/us-equities/adaptive-paper/requirements.txt": "6859f3c7362730fb1e1f023777863dc70650607c90873836b99c4e7d63982216", "blueprints/us-equities/adaptive-paper/runner.py": "ae740f03373644cf048b0d65f282afdbd247486ddc566a8cc5921676a2abee4b", @@ -50,7 +50,7 @@ "tests/test_adaptive_paper_mover.py": "76cebfa31f08b93a84965297b2f82ba8fa898b2de7d9b0cf5e65ca0feed87912", "tests/test_adaptive_paper_mover_native.py": "ce89aa0ae3447017ae56778f19687d819a2f0701052cf4d780a650c37e0d4003", "tests/test_adaptive_paper_native.py": "728458624788243abcaf653f6a20a5a6201cfa9ec3328e8d7eb3fd58fdfa94b0", - "tests/test_adaptive_paper_recovery.py": "c21c8664297f4c23d25f69e4bf88bb5df109f160dc68d0419021b8deca43b539", + "tests/test_adaptive_paper_recovery.py": "688e23f102249d170bf03d6a8d06196a42472cea049453228e4831a436c602ce", "tests/test_adaptive_paper_runner.py": "a0e65fdf868869e61ff32c443213350ca9bb7e99366632b0417805141161a7a0", "tests/test_adaptive_paper_safety.py": "dda093e5428a58ac667ae4d9a620acbf7ee78b8b26db83fccf08b530f1e58c7e", "tests/test_adaptive_paper_selector.py": "27b431ac7911372d050f25d32ac24a3df9d2cf3c5e7dafdebfe51db6cab55936", diff --git a/tests/test_adaptive_paper_recovery.py b/tests/test_adaptive_paper_recovery.py index 9967fc481..d2f0fe937 100644 --- a/tests/test_adaptive_paper_recovery.py +++ b/tests/test_adaptive_paper_recovery.py @@ -485,30 +485,74 @@ def test_engine_codes_stay_and_other_long_tokens_are_redacted(self): for token in redacted: self.assertNotIn(token, text) - def test_a_home_path_inside_a_long_token_cannot_shorten_it(self): - # #809 delta read (P3): replacing a home path first used to leave a 20-character prefix of a 30-character - # token below the threshold; tokens are now found in the original text, in the message and context alike + def test_a_home_path_and_a_long_token_cannot_shorten_each_other(self): + # #809 delta reads r2 and r3 (P3s): replacing paths first left a 20-character token prefix, and replacing + # tokens first left a punctuated path suffix. Both spans are now found in the original text and joined. from transport import TransportError for root in ("home", "Users", "root"): - with self.subTest(root): - self.setUp() - value = "read failed " + "A" * 20 + "/" + root + "/user" - - class PathPort(FakePort): - async def snapshot(self): - try: - raise TransportError(value) - except TransportError as cause: - raise TransportError(value) from cause - self.original_buy() - self.port.__class__ = PathPort - detail = self.recover()["error_details"][0] - for key in ("message", "context_message"): - self.assertEqual(detail[key], "read failed ") + path = "/" + root + "/user" # built here: no literal home path in this file + for value, expected in (("read failed " + "A" * 20 + path, "read failed "), + ("read failed " + "A" * 24 + path + "/.secret/key.txt", "read failed "), + ("read failed " + path + "//x.y " + "B" * 30, "read failed ")): + with self.subTest(root=root, value=value[12:40]): + self.setUp() + + class PathPort(FakePort): + async def snapshot(self): + try: + raise TransportError(value) + except TransportError as cause: + raise TransportError(value) from cause + self.original_buy() + self.port.__class__ = PathPort + detail = self.recover()["error_details"][0] + for key in ("message", "context_message"): + self.assertEqual(detail[key], expected) import recovery - inside = "data" + "/".join(["", "ho" + "me", "someone", "secret_value"]) # built here: no literal home path - self.assertEqual(recovery._text("kept engine/words " + inside), + home = "/".join(["", "ho" + "me", "someone"]) + self.assertEqual(recovery._text("kept engine/words data" + home + "/secret_value"), "kept engine/words data") # a home path inside a kept engine word is still replaced + self.assertEqual(recovery._text("kept datadatadata" + home + ".secret.value"), + "kept datadatadata") # ... and its punctuated suffix with it + + def test_random_texts_match_a_character_mask_oracle(self): + # A seeded check of the joined-span rule against an independent formulation: mask every character that a + # home path or a non-engine long token covers in the ORIGINAL text, then replace each maximal masked run + # once. The r2 (paths first) and r3 (tokens first) orders both fail it. + import random + import recovery + rng = random.Random(809) + roots = ["/" + r + "/" for r in ("ho" + "me", "Us" + "ers", "ro" + "ot")] + pieces = [lambda: "Q" * rng.randint(1, 40), lambda: rng.choice(roots) + rng.choice(["u", "Qx", "a.Q", "", ""]), + lambda: "a" * rng.randint(1, 30), lambda: "quotes_subscription_rejected", + lambda: "authentication/subscription/quote", lambda: "data_set", + lambda: rng.choice([" ", ".", ",", "", "(", ")", "/", "-", "x", "_"])] + + def oracle(text): + mask, path = [False] * len(text), [False] * len(text) + for m in recovery._HOME_PATH.finditer(text): + for i in range(m.start(), m.end()): + mask[i] = path[i] = True + for m in recovery._LONG_TOKEN.finditer(text): + if not recovery._ENGINE_WORDS.fullmatch(m.group(0)): + for i in range(m.start(), m.end()): + mask[i] = True + out, i = [], 0 + while i < len(text): + if not mask[i]: + out.append(text[i]); i += 1 + continue + j, held = i, False + while j < len(text) and mask[j]: + held, j = held or path[j], j + 1 + out.append("" if held else ""); i = j + return "".join(out)[:300] + for _ in range(4000): + text = "".join(rng.choice(pieces)() for _ in range(rng.randint(1, 14))) + out = recovery._text(text) + self.assertEqual(out, oracle(text), text) + for root in roots: + self.assertNotIn(root, out, text) def test_redaction_runs_before_the_length_cap(self): # #809 review (P3): a long token that crosses character 300 is redacted whole, never cut into a short From dd39a2084342051a10dd4a9d5f23467175098b8e Mon Sep 17 00:00:00 2001 From: Scout Date: Tue, 6 Oct 2026 18:59:43 -0400 Subject: [PATCH 19/23] Registry: the #809 r3 fix (registry last) Co-Authored-By: Claude Opus 5.5 --- manifests/evidence.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/manifests/evidence.json b/manifests/evidence.json index 2a9a35b86..a14204856 100644 --- a/manifests/evidence.json +++ b/manifests/evidence.json @@ -12568,8 +12568,8 @@ }, { "path": "blueprints/us-equities/adaptive-paper/recovery.py", - "sha256": "31df3cdb05c357315886b677dff894573f3e85036ce8ef061c6b2b4909e69b98", - "bytes": 17770 + "sha256": "76c1b6e21e39e1c87138ffa9e3dd4323e599d9f59af406807213f71eb3661259", + "bytes": 18401 }, { "path": "blueprints/us-equities/adaptive-paper/registry.json", @@ -12613,7 +12613,7 @@ }, { "path": "blueprints/us-equities/adaptive-paper/source-hashes.json", - "sha256": "38b163f65a63c1134007640b145c29a3880322c4d598130ba9944c6c9872b220", + "sha256": "f851d397bc0950f11aa85ba007494b69d35e6914bd745a584f67014501f54aad", "bytes": 7076 }, { @@ -54593,8 +54593,8 @@ }, { "path": "tests/test_adaptive_paper_recovery.py", - "sha256": "c21c8664297f4c23d25f69e4bf88bb5df109f160dc68d0419021b8deca43b539", - "bytes": 43293 + "sha256": "688e23f102249d170bf03d6a8d06196a42472cea049453228e4831a436c602ce", + "bytes": 45949 }, { "path": "tests/test_adaptive_paper_runner.py", From 5ef32da1c1a9f0943eab6706dcd9cac5788aa28e Mon Sep 17 00:00:00 2001 From: Scout Date: Tue, 6 Oct 2026 18:59:44 -0400 Subject: [PATCH 20/23] Hot-file protocol: reset manifests/evidence.json to the merge base before the final hot-file commit Co-Authored-By: Claude Opus 5.5 --- manifests/evidence.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/manifests/evidence.json b/manifests/evidence.json index a14204856..975fe1920 100644 --- a/manifests/evidence.json +++ b/manifests/evidence.json @@ -12568,8 +12568,8 @@ }, { "path": "blueprints/us-equities/adaptive-paper/recovery.py", - "sha256": "76c1b6e21e39e1c87138ffa9e3dd4323e599d9f59af406807213f71eb3661259", - "bytes": 18401 + "sha256": "38b6cf4f9247577f72337ca3dbd242505a43ad5cbc5cbe21349b0f1beb2f3e51", + "bytes": 15272 }, { "path": "blueprints/us-equities/adaptive-paper/registry.json", @@ -12613,7 +12613,7 @@ }, { "path": "blueprints/us-equities/adaptive-paper/source-hashes.json", - "sha256": "f851d397bc0950f11aa85ba007494b69d35e6914bd745a584f67014501f54aad", + "sha256": "a6cc50ee9102eb31a0d5620a691a9597d47ab97add11d8244008e5a204d8a7e6", "bytes": 7076 }, { @@ -12628,8 +12628,8 @@ }, { "path": "blueprints/us-equities/adaptive-paper/transport.py", - "sha256": "41fa9c5fb9e20584c382e4ee108fde03d5abb4d40a1b07f519d9039fc990ea37", - "bytes": 93657 + "sha256": "a803afeea026d5a350179b2672c59448a10fc66bc9ca8529eac1673928fbb024", + "bytes": 93222 }, { "path": "blueprints/us-equities/adaptive-paper/trials/20260923-interrupted/README.md", @@ -54593,8 +54593,8 @@ }, { "path": "tests/test_adaptive_paper_recovery.py", - "sha256": "688e23f102249d170bf03d6a8d06196a42472cea049453228e4831a436c602ce", - "bytes": 45949 + "sha256": "6da67e1b83d887cbaaeaf289434a595dacc79517a6b74b61e2adf622f84462cb", + "bytes": 34451 }, { "path": "tests/test_adaptive_paper_runner.py", @@ -54623,8 +54623,8 @@ }, { "path": "tests/test_adaptive_paper_transport.py", - "sha256": "fdff4ef5b0e793d04f6dd3f5e0e053d9694f67a03537c0681a2d510b3bce421e", - "bytes": 107119 + "sha256": "7d75e7ae89dc048cd62628fcfd666ff18eaf3daedbed836b0a451ddaf7a45f87", + "bytes": 106177 }, { "path": "tests/test_adjudicate.py", From e47c8ccc8e846841bea0ad8d0c71a1187ee55e7f Mon Sep 17 00:00:00 2001 From: Scout Date: Tue, 6 Oct 2026 18:59:45 -0400 Subject: [PATCH 21/23] Reapply this branch's manifests/evidence.json edits on the merge base (hot-file protocol: every hot-file edit in the last commit) Co-Authored-By: Claude Opus 5.5 --- manifests/evidence.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/manifests/evidence.json b/manifests/evidence.json index 975fe1920..a14204856 100644 --- a/manifests/evidence.json +++ b/manifests/evidence.json @@ -12568,8 +12568,8 @@ }, { "path": "blueprints/us-equities/adaptive-paper/recovery.py", - "sha256": "38b6cf4f9247577f72337ca3dbd242505a43ad5cbc5cbe21349b0f1beb2f3e51", - "bytes": 15272 + "sha256": "76c1b6e21e39e1c87138ffa9e3dd4323e599d9f59af406807213f71eb3661259", + "bytes": 18401 }, { "path": "blueprints/us-equities/adaptive-paper/registry.json", @@ -12613,7 +12613,7 @@ }, { "path": "blueprints/us-equities/adaptive-paper/source-hashes.json", - "sha256": "a6cc50ee9102eb31a0d5620a691a9597d47ab97add11d8244008e5a204d8a7e6", + "sha256": "f851d397bc0950f11aa85ba007494b69d35e6914bd745a584f67014501f54aad", "bytes": 7076 }, { @@ -12628,8 +12628,8 @@ }, { "path": "blueprints/us-equities/adaptive-paper/transport.py", - "sha256": "a803afeea026d5a350179b2672c59448a10fc66bc9ca8529eac1673928fbb024", - "bytes": 93222 + "sha256": "41fa9c5fb9e20584c382e4ee108fde03d5abb4d40a1b07f519d9039fc990ea37", + "bytes": 93657 }, { "path": "blueprints/us-equities/adaptive-paper/trials/20260923-interrupted/README.md", @@ -54593,8 +54593,8 @@ }, { "path": "tests/test_adaptive_paper_recovery.py", - "sha256": "6da67e1b83d887cbaaeaf289434a595dacc79517a6b74b61e2adf622f84462cb", - "bytes": 34451 + "sha256": "688e23f102249d170bf03d6a8d06196a42472cea049453228e4831a436c602ce", + "bytes": 45949 }, { "path": "tests/test_adaptive_paper_runner.py", @@ -54623,8 +54623,8 @@ }, { "path": "tests/test_adaptive_paper_transport.py", - "sha256": "7d75e7ae89dc048cd62628fcfd666ff18eaf3daedbed836b0a451ddaf7a45f87", - "bytes": 106177 + "sha256": "fdff4ef5b0e793d04f6dd3f5e0e053d9694f67a03537c0681a2d510b3bce421e", + "bytes": 107119 }, { "path": "tests/test_adjudicate.py", From a4404a0d41010edbd1bb4335a53745c611ef64e0 Mon Sep 17 00:00:00 2001 From: Scout Date: Tue, 6 Oct 2026 19:23:24 -0400 Subject: [PATCH 22/23] Hot-file protocol: reset manifests/evidence.json to the merge base before the final hot-file commit Co-Authored-By: Claude Opus 5.5 --- manifests/evidence.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/manifests/evidence.json b/manifests/evidence.json index 2d14f80fc..1696357de 100644 --- a/manifests/evidence.json +++ b/manifests/evidence.json @@ -12568,8 +12568,8 @@ }, { "path": "blueprints/us-equities/adaptive-paper/recovery.py", - "sha256": "76c1b6e21e39e1c87138ffa9e3dd4323e599d9f59af406807213f71eb3661259", - "bytes": 18401 + "sha256": "38b6cf4f9247577f72337ca3dbd242505a43ad5cbc5cbe21349b0f1beb2f3e51", + "bytes": 15272 }, { "path": "blueprints/us-equities/adaptive-paper/registry.json", @@ -12613,7 +12613,7 @@ }, { "path": "blueprints/us-equities/adaptive-paper/source-hashes.json", - "sha256": "3194ef1699f6cb7877de8d042b2683ba0a4a906457e8942f1f4e9960bc589b34", + "sha256": "744677086646203048b14a629e280d0900280965a71e5b1d637f91b311299218", "bytes": 7076 }, { @@ -12628,8 +12628,8 @@ }, { "path": "blueprints/us-equities/adaptive-paper/transport.py", - "sha256": "8eccc653b65db9f5e81ab8897c0e2333e45ad992ce9ace313c27eb5c3c81c386", - "bytes": 96247 + "sha256": "34a03a3dbc761a5f9b19e1191559974e24d71a6fa2da02d2823d185da0a8d96d", + "bytes": 95812 }, { "path": "blueprints/us-equities/adaptive-paper/trials/20260923-interrupted/README.md", @@ -54623,8 +54623,8 @@ }, { "path": "tests/test_adaptive_paper_recovery.py", - "sha256": "688e23f102249d170bf03d6a8d06196a42472cea049453228e4831a436c602ce", - "bytes": 45949 + "sha256": "6da67e1b83d887cbaaeaf289434a595dacc79517a6b74b61e2adf622f84462cb", + "bytes": 34451 }, { "path": "tests/test_adaptive_paper_runner.py", @@ -54653,8 +54653,8 @@ }, { "path": "tests/test_adaptive_paper_transport.py", - "sha256": "c7821140bca567da4c1d68b77375b10f2f8728052686fab2f11ae44f4e12bceb", - "bytes": 111827 + "sha256": "39e10ccecdaf9c29518cd02e4d9afaf7e6fb872948822c03f95848bd17ef33c4", + "bytes": 110885 }, { "path": "tests/test_adjudicate.py", From 2df943bd2f1113b997bfc74d372bc23958aa4fdf Mon Sep 17 00:00:00 2001 From: Scout Date: Tue, 6 Oct 2026 19:23:25 -0400 Subject: [PATCH 23/23] Reapply this branch's manifests/evidence.json edits on the merge base (hot-file protocol: every hot-file edit in the last commit) Co-Authored-By: Claude Opus 5.5 --- manifests/evidence.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/manifests/evidence.json b/manifests/evidence.json index 1696357de..2d14f80fc 100644 --- a/manifests/evidence.json +++ b/manifests/evidence.json @@ -12568,8 +12568,8 @@ }, { "path": "blueprints/us-equities/adaptive-paper/recovery.py", - "sha256": "38b6cf4f9247577f72337ca3dbd242505a43ad5cbc5cbe21349b0f1beb2f3e51", - "bytes": 15272 + "sha256": "76c1b6e21e39e1c87138ffa9e3dd4323e599d9f59af406807213f71eb3661259", + "bytes": 18401 }, { "path": "blueprints/us-equities/adaptive-paper/registry.json", @@ -12613,7 +12613,7 @@ }, { "path": "blueprints/us-equities/adaptive-paper/source-hashes.json", - "sha256": "744677086646203048b14a629e280d0900280965a71e5b1d637f91b311299218", + "sha256": "3194ef1699f6cb7877de8d042b2683ba0a4a906457e8942f1f4e9960bc589b34", "bytes": 7076 }, { @@ -12628,8 +12628,8 @@ }, { "path": "blueprints/us-equities/adaptive-paper/transport.py", - "sha256": "34a03a3dbc761a5f9b19e1191559974e24d71a6fa2da02d2823d185da0a8d96d", - "bytes": 95812 + "sha256": "8eccc653b65db9f5e81ab8897c0e2333e45ad992ce9ace313c27eb5c3c81c386", + "bytes": 96247 }, { "path": "blueprints/us-equities/adaptive-paper/trials/20260923-interrupted/README.md", @@ -54623,8 +54623,8 @@ }, { "path": "tests/test_adaptive_paper_recovery.py", - "sha256": "6da67e1b83d887cbaaeaf289434a595dacc79517a6b74b61e2adf622f84462cb", - "bytes": 34451 + "sha256": "688e23f102249d170bf03d6a8d06196a42472cea049453228e4831a436c602ce", + "bytes": 45949 }, { "path": "tests/test_adaptive_paper_runner.py", @@ -54653,8 +54653,8 @@ }, { "path": "tests/test_adaptive_paper_transport.py", - "sha256": "39e10ccecdaf9c29518cd02e4d9afaf7e6fb872948822c03f95848bd17ef33c4", - "bytes": 110885 + "sha256": "c7821140bca567da4c1d68b77375b10f2f8728052686fab2f11ae44f4e12bceb", + "bytes": 111827 }, { "path": "tests/test_adjudicate.py",