Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
285a72d
trading: recovery receipts carry the engine's error message and its c…
Oct 6, 2026
6795377
trading: pin the changed engine sources in source-hashes.json; build …
Oct 6, 2026
c7eba9c
Registry: the recovery receipt-detail change (registry last)
Oct 6, 2026
feef3a3
Hot-file protocol: reset manifests/evidence.json to the merge base be…
Oct 6, 2026
2cdd1fa
Reapply this branch's manifests/evidence.json edits on the merge base…
Oct 6, 2026
a337074
recovery: keep engine reason codes, redact before the cap, detail can…
Oct 6, 2026
4ebcf76
Registry: the #809 review fixes (registry last)
Oct 6, 2026
840db6c
Hot-file protocol: reset manifests/evidence.json to the merge base be…
Oct 6, 2026
87baa6d
Reapply this branch's manifests/evidence.json edits on the merge base…
Oct 6, 2026
0b860f9
recovery: find long tokens before replacing home paths
Oct 6, 2026
20c8f2e
Registry: the #809 delta-read fix (registry last)
Oct 6, 2026
9328a45
Hot-file protocol: reset manifests/evidence.json to the merge base be…
Oct 6, 2026
721c7eb
Reapply this branch's manifests/evidence.json edits on the merge base…
Oct 6, 2026
b7afd03
tests: build the embedded home path at runtime (publication rule)
Oct 6, 2026
8f354a1
Registry: the publication-rule test fix (registry last)
Oct 6, 2026
e666bc9
Hot-file protocol: reset manifests/evidence.json to the merge base be…
Oct 6, 2026
9db21fd
Reapply this branch's manifests/evidence.json edits on the merge base…
Oct 6, 2026
0a8dfd8
recovery: join home-path and long-token spans found in the original text
Oct 6, 2026
dd39a20
Registry: the #809 r3 fix (registry last)
Oct 6, 2026
5ef32da
Hot-file protocol: reset manifests/evidence.json to the merge base be…
Oct 6, 2026
e47c8cc
Reapply this branch's manifests/evidence.json edits on the merge base…
Oct 6, 2026
33189a6
Merge main b4e1fa6df (#812) into #809: content refresh
Oct 6, 2026
a4404a0
Hot-file protocol: reset manifests/evidence.json to the merge base be…
Oct 6, 2026
2df943b
Reapply this branch's manifests/evidence.json edits on the merge base…
Oct 6, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 59 additions & 1 deletion blueprints/us-equities/adaptive-paper/recovery.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,60 @@ def _code(exc):
return text if isinstance(exc, SafetyError) and re.fullmatch(r"[a-z_]{1,100}", text) else type(exc).__name__


_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):
"""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
(<path> when it holds a home path, else <redacted>). 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], "<path>" if path else "<redacted>"]
position = end
out.append(text[position:])
return "".join(out)[:300]


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. 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):
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.

Expand All @@ -49,6 +103,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.
Expand Down Expand Up @@ -260,15 +315,18 @@ 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))
Comment thread
seathatflowsinourveins marked this conversation as resolved.
finally:
controller.stop = True
try:
# Teardown can outlive the order deadline, but cannot authorize orders.
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
Expand All @@ -280,7 +338,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"]],
Expand Down
8 changes: 4 additions & 4 deletions blueprints/us-equities/adaptive-paper/source-hashes.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": "76c1b6e21e39e1c87138ffa9e3dd4323e599d9f59af406807213f71eb3661259",
"blueprints/us-equities/adaptive-paper/registry.json": "60da73b985e718bd1dece7a3350f34636bda0fb38e766a44ae71c5a8c532282a",
"blueprints/us-equities/adaptive-paper/requirements.txt": "6859f3c7362730fb1e1f023777863dc70650607c90873836b99c4e7d63982216",
"blueprints/us-equities/adaptive-paper/runner.py": "b0536be7c027bf5e21d41af8ec67a6ac301d4e40e568cf52551631a59085ebc9",
Expand All @@ -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": "34a03a3dbc761a5f9b19e1191559974e24d71a6fa2da02d2823d185da0a8d96d",
"blueprints/us-equities/adaptive-paper/transport.py": "8eccc653b65db9f5e81ab8897c0e2333e45ad992ce9ace313c27eb5c3c81c386",
"tests/_credential_mutation_driver.py": "bfe35e69702016718452ed702aab20a0a33da5723e28a61de513b3c268849301",
"tests/adaptive_paper_hermetic.py": "0f41601cd10abf10ccba4f17d9c35716bb766a5e9951a4390649d6c9387fc1da",
"tests/test_adaptive_market_research.py": "772b4fd467d4405bd098a440c54de4bfd61f91bbb80693c052077b867f9e2fd6",
Expand All @@ -50,11 +50,11 @@
"tests/test_adaptive_paper_mover.py": "55ad458b609367fee7db6bc0583da39e11dea69388d6c11252d367df8dc32ac7",
"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": "688e23f102249d170bf03d6a8d06196a42472cea049453228e4831a436c602ce",
"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": "39e10ccecdaf9c29518cd02e4d9afaf7e6fb872948822c03f95848bd17ef33c4"
"tests/test_adaptive_paper_transport.py": "c7821140bca567da4c1d68b77375b10f2f8728052686fab2f11ae44f4e12bceb"
}
7 changes: 6 additions & 1 deletion blueprints/us-equities/adaptive-paper/transport.py
Original file line number Diff line number Diff line change
Expand Up @@ -1541,8 +1541,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

Expand Down Expand Up @@ -1773,6 +1776,8 @@ def collect():
"scope": "all_open_and_recent_plus_owned", "health": self.health}
except Exception:
self.freeze_health("snapshot_incomplete")
# 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):
Expand Down
18 changes: 9 additions & 9 deletions manifests/evidence.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -12613,7 +12613,7 @@
},
{
"path": "blueprints/us-equities/adaptive-paper/source-hashes.json",
"sha256": "744677086646203048b14a629e280d0900280965a71e5b1d637f91b311299218",
"sha256": "3194ef1699f6cb7877de8d042b2683ba0a4a906457e8942f1f4e9960bc589b34",
"bytes": 7076
},
{
Expand All @@ -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",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -54653,8 +54653,8 @@
},
{
"path": "tests/test_adaptive_paper_transport.py",
"sha256": "39e10ccecdaf9c29518cd02e4d9afaf7e6fb872948822c03f95848bd17ef33c4",
"bytes": 110885
"sha256": "c7821140bca567da4c1d68b77375b10f2f8728052686fab2f11ae44f4e12bceb",
"bytes": 111827
},
{
"path": "tests/test_adjudicate.py",
Expand Down
Loading
Loading