Skip to content

[WARP•R00T] Custody Lane 3 — Signer-Plane Scaffold (Lane 3 stays OPEN)#2068

Merged
bayuewalker merged 4 commits into
mainfrom
WARP/root-custody-lane3-signer-isolation
Jun 21, 2026
Merged

[WARP•R00T] Custody Lane 3 — Signer-Plane Scaffold (Lane 3 stays OPEN)#2068
bayuewalker merged 4 commits into
mainfrom
WARP/root-custody-lane3-signer-isolation

Conversation

@bayuewalker

@bayuewalker bayuewalker commented Jun 21, 2026

Copy link
Copy Markdown
Owner

Custody Lane 3 — Signer-Plane Scaffold (MAJOR / owner-routed WARP•R00T)

Refs #2067this PR does NOT close #2067. It is a signer-plane scaffold / foundation, NOT completed signer isolation. Verdict: OWNER_ACTION_REQUIRED. Lane 3 / #2067 remains OPEN for the continuation lane 3B — Remote/KMS Signing Plane Cutover.

Scope honesty (CMD HOLD on #2068 — applied)

  • The LOCAL plane is transitional same-host: it still decrypts in-process and still returns the decrypted key to the main app via SignerGrant.material. That is a reduced, authorized, audited call surface — not signer isolation.
  • True isolation (main app stops receiving raw key; out-of-process / KMS / HSM; remaining shared/on-chain/withdrawal cutovers) is OWNER_ACTION_REQUIRED, tracked as Lane 3B.
  • PROJECT_STATE / WORKTODO / CHANGELOG / report all state Lane 3 is NOT complete and [WARP•R00T] Custody Lane 3 — Signer Isolation #2067 stays open.

What this scaffold delivers

  • wallet/signing_plane.py — narrow, fail-closed, audited signer-acquisition boundary. Grant allowlist restricted to implemented intents (per_user_clob_signer only; every other declared intent is intent_not_enabled-denied). Implemented intents require non-empty target + guard_context (missing_target / missing_guard_context denials). Key material is repr-suppressed and never logged/audited. Pluggable (set_signing_plane) so a future remote/KMS plane drops in with no caller change.
  • Per-user CLOB signer cutoveruser_client._build_for_user acquires its signer through the plane (with honest build-context target/guard_context); the direct wallet.vault.get_decrypted_pk import is removed.

Fold-in hardening (achievable Path-B, per CMD directive)

  • Target + guard context enforced on implemented intents (above).
  • Shared/omnibus + complete-set bypass removed: the live.execute shared_transitional path and complete_set_exec.book_complete_set_live shared path now route their signer through the plane with SHARED_CLOB_SIGNER — which is declared-but-not-enabled — so they fail closed (shared_signer_not_available → paper / live_blocked_shared_signer_not_available → skipped). ALLOW_SHARED_LIVE_EXECUTION can no longer obtain a signer outside the boundary.

Safety posture (unchanged constraints)

No live order, no fund movement, no DB write, no Fly secret change, no ENABLE_LIVE_TRADING flip; no live-guard / USE_REAL_CLOB / kill-switch / CLOB-readiness / balance-cap / idempotency / dedup bypass; no Kelly/sizing/risk-control change (full Kelly forbidden); no secrets or real signature fixtures in code/tests/reports/state/logs.

Tests & CI

  • tests/test_custody_lane3_signing_plane.py — authorization (identity / declared-intent / intent_not_enabled / route / missing_target / missing_guard_context), fail-closed on signer-source error, no-material empty grant, secret-hygiene, pluggability, AST-based architectural cutover checks.
  • test_live_per_user_wiring.py + test_complete_set_live.py updated: shared opt-in now fails closed at the plane.
  • In-session: plane pure logic passes; ruff + py_compile clean. Heavy-dependency suite (CI-only py_clob_client_v2) gates in CI.

Verdict: OWNER_ACTION_REQUIRED. Next: Lane 3B — Remote/KMS Signing Plane Cutover (#2067 stays open).

Report: projects/polymarket/crusaderbot/reports/root/custody-lane3-signer-isolation.md

🤖 Generated with Claude Code

https://claude.ai/code/session_01BxPHVVcpuJzkm4v6qZpheZ

New wallet/signing_plane.py — allowlisted, fail-closed, audited signer
acquisition boundary (intents, request/grant, identity+intent authorization,
repr-suppressed key material, pluggable for a future remote/KMS plane). Cut the
live per-user CLOB signer (user_client._build_for_user) over to it; raw vault
decrypt import removed. LOCAL plane is transitional same-host — true
out-of-process/KMS isolation is OWNER_ACTION_REQUIRED. V1 stays
custodial-transparent; no live order/fund/guard/Kelly/ENABLE_LIVE_TRADING
change; no secrets.

refs #2067

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BxPHVVcpuJzkm4v6qZpheZ
Copilot AI review requested due to automatic review settings June 21, 2026 19:35

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements Custody Lane 3: Signer Isolation, introducing a new signing-plane boundary (wallet/signing_plane.py) to manage signer-material acquisition and cutting over the live per-user CLOB signer to this interface. The review feedback focuses on enhancing robustness and test reliability. Key recommendations include converting string UUIDs to UUID objects in SignerRequest, adding defensive checks for null values in attribution(), and using Python's ast module in tests to replace fragile substring checks when verifying that raw vault decrypt helpers are not imported or referenced directly.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +98 to +112
@dataclass(frozen=True)
class SignerRequest:
"""A request for a signing capability. Carries NO secret material.

``target`` holds market / token / destination metadata (keys only are
surfaced in attribution). ``guard_context`` is an upstream-resolved summary
for audit — the plane does NOT re-decide live guards (that would be a hidden
bypass); guard enforcement stays in the gate/router/executor.
"""

user_id: UUID
intent: SigningIntent
route: str
target: dict = field(default_factory=dict)
guard_context: dict = field(default_factory=dict)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To prevent fragile type-check failures when user_id is passed as a string representation of a UUID (which is common when parsing external payloads or CLI arguments), consider automatically converting valid UUID strings to UUID objects in __post_init__. This ensures robust runtime behavior while maintaining strict type safety.

@dataclass(frozen=True)
class SignerRequest:
    """A request for a signing capability. Carries NO secret material.

    target holds market / token / destination metadata (keys only are
    surfaced in attribution). guard_context is an upstream-resolved summary
    for audit - the plane does NOT re-decide live guards (that would be a hidden
    bypass); guard enforcement stays in the gate/router/executor.
    """

    user_id: UUID
    intent: SigningIntent
    route: str
    target: dict = field(default_factory=dict)
    guard_context: dict = field(default_factory=dict)

    def __post_init__(self) -> None:
        if isinstance(self.user_id, str):
            try:
                object.__setattr__(self, "user_id", UUID(self.user_id))
            except ValueError:
                pass

Comment on lines +114 to +123
def attribution(self) -> dict[str, object]:
"""Operator-facing attribution dict — NEVER includes secrets."""
return {
"user_id": str(self.user_id),
"intent": self.intent.value if isinstance(self.intent, SigningIntent) else str(self.intent),
"route": self.route,
"target_keys": sorted(self.target),
"guard_context": dict(self.guard_context),
"plane_version": PLANE_VERSION,
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Enforce defensive programming by safely handling cases where target or guard_context might be passed as None (e.g., from nullable variables). Directly calling sorted(self.target) or dict(self.guard_context) on None will raise a TypeError, which would suppress the audit log or crash the signing plane.

Suggested change
def attribution(self) -> dict[str, object]:
"""Operator-facing attribution dict NEVER includes secrets."""
return {
"user_id": str(self.user_id),
"intent": self.intent.value if isinstance(self.intent, SigningIntent) else str(self.intent),
"route": self.route,
"target_keys": sorted(self.target),
"guard_context": dict(self.guard_context),
"plane_version": PLANE_VERSION,
}
def attribution(self) -> dict[str, object]:
"""Operator-facing attribution dict - NEVER includes secrets."""
return {
"user_id": str(self.user_id),
"intent": self.intent.value if isinstance(self.intent, SigningIntent) else str(self.intent),
"route": self.route,
"target_keys": sorted(self.target) if self.target else [],
"guard_context": dict(self.guard_context) if self.guard_context else {},
"plane_version": PLANE_VERSION,
}
References
  1. Maintain defensive fallback checks (such as checking for 'None' values) in critical execution paths even if current upstream logic guarantees non-null values.

Comment on lines +181 to +190
def test_live_execution_modules_do_not_decrypt_directly() -> None:
import projects.polymarket.crusaderbot.domain.execution.complete_set_exec as cse
import projects.polymarket.crusaderbot.domain.execution.live as live
import projects.polymarket.crusaderbot.domain.execution.per_user_clob as puc

for mod in (live, cse, puc):
src = Path(inspect.getfile(mod)).read_text(encoding="utf-8")
assert "get_decrypted_pk" not in src, (
f"{mod.__name__} must not call the raw vault decrypt helper directly"
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using a simple substring check like "get_decrypted_pk" not in src is fragile because any developer adding a comment or docstring referencing get_decrypted_pk in the future will break this test. Consider using Python's ast module to parse the source code and robustly verify that get_decrypted_pk is neither imported nor called, completely ignoring comments and docstrings.

Suggested change
def test_live_execution_modules_do_not_decrypt_directly() -> None:
import projects.polymarket.crusaderbot.domain.execution.complete_set_exec as cse
import projects.polymarket.crusaderbot.domain.execution.live as live
import projects.polymarket.crusaderbot.domain.execution.per_user_clob as puc
for mod in (live, cse, puc):
src = Path(inspect.getfile(mod)).read_text(encoding="utf-8")
assert "get_decrypted_pk" not in src, (
f"{mod.__name__} must not call the raw vault decrypt helper directly"
)
def test_live_execution_modules_do_not_decrypt_directly() -> None:
import ast
import projects.polymarket.crusaderbot.domain.execution.complete_set_exec as cse
import projects.polymarket.crusaderbot.domain.execution.live as live
import projects.polymarket.crusaderbot.domain.execution.per_user_clob as puc
for mod in (live, cse, puc):
src = Path(inspect.getfile(mod)).read_text(encoding="utf-8")
tree = ast.parse(src)
for node in ast.walk(tree):
if isinstance(node, ast.Name) and node.id == "get_decrypted_pk":
raise AssertionError(f"{mod.__name__} references get_decrypted_pk directly")
if isinstance(node, ast.ImportFrom) and any(alias.name == "get_decrypted_pk" for alias in node.names):
raise AssertionError(f"{mod.__name__} imports get_decrypted_pk directly")
References
  1. In Python test files, absolute imports starting with the project's root package (e.g., 'projects.polymarket.crusaderbot.*') are acceptable and should be used for consistency if that is the repository's convention.

Comment on lines +169 to +179
def test_user_client_uses_signing_plane_not_raw_vault() -> None:
from projects.polymarket.crusaderbot.integrations.clob import user_client

src = Path(inspect.getfile(user_client)).read_text(encoding="utf-8")
assert "signing_plane" in src and "acquire_signer" in src, (
"per-user build must acquire its signer through the signing plane"
)
assert "from ...wallet.vault import get_decrypted_pk" not in src, (
"per-user build must NOT import the raw vault decrypt helper directly"
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Similarly, checking for raw string imports like "from ...wallet.vault import get_decrypted_pk" not in src is prone to false positives if the import is mentioned in comments or docstrings. Using an AST-based check is much more robust and maintainable.

def test_user_client_uses_signing_plane_not_raw_vault() -> None:
    import ast
    from projects.polymarket.crusaderbot.integrations.clob import user_client

    src = Path(inspect.getfile(user_client)).read_text(encoding="utf-8")
    tree = ast.parse(src)
    
    has_signing_plane = False
    has_acquire_signer = False
    
    for node in ast.walk(tree):
        if isinstance(node, ast.Name):
            if node.id == "signing_plane":
                has_signing_plane = True
            elif node.id == "acquire_signer":
                has_acquire_signer = True
            elif node.id == "get_decrypted_pk":
                raise AssertionError("user_client references get_decrypted_pk directly")
        elif isinstance(node, ast.ImportFrom):
            if any(alias.name == "get_decrypted_pk" for alias in node.names):
                raise AssertionError("user_client imports get_decrypted_pk directly")

    assert has_signing_plane and has_acquire_signer, (
        "per-user build must acquire its signer through the signing plane"
    )
References
  1. In Python test files, absolute imports starting with the project's root package (e.g., 'projects.polymarket.crusaderbot.*') are acceptable and should be used for consistency if that is the repository's convention.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adopted the AST approach (c0ce0ee) — agreed the substring check is fragile. One correction: the suggested snippet checks acquire_signer and signing_plane as ast.Name nodes, but in this module acquire_signer is an attribute access (get_signing_plane().acquire_signer(...)ast.Attribute) and signing_plane only appears in the ImportFrom.module (from ...wallet.signing_plane import ...) — so the as-written version would assert-fail. I applied a corrected helper that inspects ast.Name, ast.Attribute, and ast.ImportFrom names/modules: it denies any get_decrypted_pk import/name/attr and confirms the plane is used via an ImportFrom module ending in signing_plane or an acquire_signer attribute. Same fix applied to test_live_execution_modules_do_not_decrypt_directly. The UUID-coercion and None-safe attribution() suggestions were also applied.


Generated by Claude Code

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 546ac884e5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

RECOVERY = "recovery"


_ALLOWLISTED_INTENTS: frozenset[SigningIntent] = frozenset(SigningIntent)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Restrict signer grants to implemented intents

In a live process, any code that can import this new plane can request ONCHAIN_TRANSFER, ONCHAIN_SWEEP, SHARED_CLOB_SIGNER, etc. with any non-empty route and _authorize will pass because the allowlist is frozenset(SigningIntent); LocalSigningPlane then decrypts and returns the user's key. I checked the wired caller is only user_client._build_for_user with PER_USER_CLOB_SIGNER, while the report/WORKTODO leave shared and on-chain cutovers for later, so pre-authorizing every declared intent weakens the fail-closed custody boundary. Keep the allowlist to currently implemented (intent, route) pairs and add entries only as those lanes land.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strong catch — applied in c0ce0ee. The allowlist now splits into _DECLARED_INTENTS (the stable enum surface future lanes target) and _IMPLEMENTED_INTENTS = {PER_USER_CLOB_SIGNER} (only what's wired this lane). _authorize denies any declared-but-unwired intent (ONCHAIN_TRANSFER, SHARED_CLOB_SIGNER, REDEMPTION, CLOSE_UNWIND, …) with reason_code="intent_not_enabled" before any decrypt, so no importer can obtain a user's key under an unimplemented intent. Lane 4+ add their (intent) to the implemented set as each callsite cutover lands. New test test_denies_declared_but_unimplemented_intent covers it.


Generated by Claude Code

Codex P2: the signer-grant allowlist was every declared intent, so any importer
could request a user's key under an unwired intent. Restrict grants to
_IMPLEMENTED_INTENTS (per_user_clob_signer only this lane); declared-but-unwired
intents are intent_not_enabled-denied until their callsite cutover lands.
Gemini: coerce valid UUID strings in SignerRequest, None-safe attribution(),
and AST-based architectural tests (corrected — acquire_signer is an attribute,
not a Name).

refs #2067

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BxPHVVcpuJzkm4v6qZpheZ
@bayuewalker

Copy link
Copy Markdown
Owner Author

DECISION: HOLD

MAJOR signer/capital/custody gate. CI is green and the local signing-plane scaffold is mechanically clean, but this PR cannot merge as written because the PR scope/closure overclaims Lane 3.

Blockers:

  1. PR says Closes #2067, but the implementation explicitly does not satisfy the Lane 3 objective. The report states the LOCAL plane is transitional same-host, still decrypts in-process, and returns key material through SignerGrant.material to the main app. That is a useful boundary scaffold, but it is not signer isolation and should not close the Lane 3 issue as completed.

  2. Issue [WARP•R00T] Custody Lane 3 — Signer Isolation #2067 required the main app to no longer directly decrypt or access private key material on normal signing paths. This PR removes the direct wallet.vault.get_decrypted_pk import from integrations/clob/user_client.py, but the main app still receives the decrypted private key via grant.material. That is a reduced call surface, not a completed isolation boundary.

  3. Issue [WARP•R00T] Custody Lane 3 — Signer Isolation #2067 required the signing plane to enforce target metadata and guard context. Current SignerRequest allows empty target and guard_context, and _build_for_user sends only user_id, intent, and route. The plane authorizes identity/intent/route, but not the target/guard context required by the lane.

  4. Issue [WARP•R00T] Custody Lane 3 — Signer Isolation #2067 required coverage/cutover for shared_transitional and complete-set live paths where they can sign/order live. The report correctly says shared/global get_clob_client and complete_set_exec remain transitional/follow-up. Because ALLOW_SHARED_LIVE_EXECUTION can explicitly enable those paths, they remain signer-plane bypasses when opted in.

Acceptable fix paths:

A. Narrow this PR honestly to a scaffold PR: remove Closes #2067, set verdict to LANE3_BLOCKED_BY_INFRA_REQUIRED or OWNER_ACTION_REQUIRED, update state/WORKTODO so Lane 3 remains open/incomplete, and open a follow-up issue/PR for true out-of-process/KMS isolation + remaining callsite cutovers.

B. Complete the Lane 3 contract in this PR: keep Closes #2067 only if the main app no longer receives raw key material on the covered normal paths, required target/guard context is enforced, and shared/complete-set signer bypasses are cut over or fail-closed under the same boundary.

No merge until one of those paths is applied. No runtime/live/funds issue found; this is a custody-boundary correctness blocker.

@bayuewalker

Copy link
Copy Markdown
Owner Author

CMD PROCESS DIRECTIVE — WARP•R00T CONTINUATION

Boss directive received: all required fixes, scope corrections, or follow-up instructions must be handled directly in this PR thread. Do not wait for side-channel clarification when the gate finding is clear.

For this PR, proceed with one of the two fix paths already posted. CMD recommendation: use Fix Path A unless you can truly complete signer isolation in-repo without overclaiming.

Recommended current action:

  1. Narrow [WARP•R00T] Custody Lane 3 — Signer-Plane Scaffold (Lane 3 stays OPEN) #2068 to a signer-plane scaffold/foundation PR.
  2. Replace Closes #2067 with Refs #2067 so Lane 3 remains open.
  3. Set verdict to LANE3_BLOCKED_BY_INFRA_REQUIRED or OWNER_ACTION_REQUIRED.
  4. Update PROJECT_STATE / WORKTODO / CHANGELOG / report to say:
    • signing-plane boundary introduced;
    • live per-user CLOB signer callsite cut over to the boundary;
    • local plane is same-host transitional and still returns key material to main app;
    • true signer isolation is not complete;
    • [WARP•R00T] Custody Lane 3 — Signer Isolation #2067 remains open for true out-of-process / KMS / HSM signing isolation and remaining callsite cutovers.
  5. Keep all hard constraints: no live order, no funds, no DB writes, no Fly secrets, no ENABLE flip, no guard/Kelly/sizing/risk/balance-cap bypass, no secrets in logs/tests/state.

After a clean merge, CMD will include the next task issue link in the merge comment so WARP•R00T can continue directly into the next work lane without a fresh owner briefing.

Expected next task after scaffold merge, unless true Lane 3 is completed here:
[WARP•R00T] Custody Lane 3B — Remote/KMS Signing Plane Cutover

Goal for 3B: make the main app stop receiving raw key material, move signing out of process or behind an owner-approved KMS/HSM boundary, and cut over remaining shared/complete-set/on-chain signer callsites or fail-close them under the signing plane.

@bayuewalker

Copy link
Copy Markdown
Owner Author

CMD OWNER-PATH DECISION

Proceed with: A + fold in achievable hardening.

Meaning:

Required changes before CMD can merge:

  1. Scope / closure honesty
  • Replace Closes #2067 with Refs #2067.
  • Verdict must be OWNER_ACTION_REQUIRED or LANE3_BLOCKED_BY_INFRA_REQUIRED.
  • PROJECT_STATE / WORKTODO / CHANGELOG / report must say:
    • signing-plane boundary scaffold introduced;
    • live per-user CLOB signer callsite cut over to the boundary;
    • local same-host plane still decrypts in-process and still returns key material to main app;
    • true signer isolation is NOT complete;
    • [WARP•R00T] Custody Lane 3 — Signer Isolation #2067 remains open for out-of-process / KMS / HSM isolation and remaining callsite completion.
  1. Enforce target + guard context now
  • SignerRequest / _authorize must reject missing required target metadata and missing required guard_context for implemented signing intents.
  • The per-user CLOB signer request from user_client._build_for_user must pass real target/guard context where available, or explicit non-empty placeholder metadata that is safe and honest for the current callsite.
  • Add tests for denial on missing target and missing guard context.
  1. Remove shared / complete-set bypass now
  • Shared_transitional and complete-set live paths must not be able to bypass the signing-plane boundary when explicitly enabled.
  • Either route those signer acquisitions through the plane with explicit intent + target + guard_context, or fail-closed with a distinct reason until the signer-plane path is implemented.
  • Do not leave ALLOW_SHARED_LIVE_EXECUTION as a route that can obtain a signer outside the plane.
  1. Keep hard constraints intact
  • No live order.
  • No fund movement.
  • No production DB writes.
  • No Fly secret changes.
  • No ENABLE_LIVE_TRADING flip.
  • No live guard / USE_REAL_CLOB / kill switch / CLOB readiness / balance-cap / idempotency / dedup bypass.
  • No Kelly/sizing/risk-control loosening.
  • Full Kelly remains forbidden.
  • No secrets or real signature fixtures in code/tests/reports/state/logs.

After this lands cleanly with CI green, CMD can merge the scaffold PR and create/link the next continuation task in the merge comment:
[WARP•R00T] Custody Lane 3B — Remote/KMS Signing Plane Cutover.

Goal for 3B: make the main app stop receiving raw key material, move signing out of process or behind owner-approved KMS/HSM boundary, and complete remaining signer callsite cutovers.

CMD HOLD on #2068: re-scope to a signer-plane scaffold (Refs #2067, NOT
Closes; verdict OWNER_ACTION_REQUIRED; Lane 3 stays OPEN; state/report/WORKTODO/
CHANGELOG say true isolation is NOT complete — main app still receives the key
via SignerGrant.material). Folded in achievable Path-B hardening: implemented
intents require non-empty target+guard_context (missing_target/
missing_guard_context denials); the shared_transitional + complete-set live
signer paths now route through the plane and FAIL CLOSED (SHARED_CLOB_SIGNER not
enabled) so ALLOW_SHARED_LIVE_EXECUTION can no longer obtain a signer outside the
boundary. Tests updated accordingly. No live order/fund/guard/Kelly/
ENABLE_LIVE_TRADING change; no secrets.

refs #2067

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BxPHVVcpuJzkm4v6qZpheZ
@bayuewalker bayuewalker changed the title [WARP•R00T] Custody Lane 3 — Signer Isolation [WARP•R00T] Custody Lane 3 — Signer-Plane Scaffold (Lane 3 stays OPEN) Jun 21, 2026

Copy link
Copy Markdown
Owner Author

WARP•R00T — CMD HOLD directive applied (path: A + fold in achievable hardening). Pushed b6f8641.

1. Scope / closure honesty

  • PR Closes #2067Refs #2067 (title + body updated); [WARP•R00T] Custody Lane 3 — Signer Isolation #2067 stays OPEN.
  • Verdict → OWNER_ACTION_REQUIRED.
  • PROJECT_STATE (moved to [IN PROGRESS]), WORKTODO (Lane 3 marked OPEN/incomplete, true-isolation item unchecked), CHANGELOG, and the report now all state: scaffold introduced; per-user signer cut over; LOCAL plane still decrypts in-process and still returns the key to the main app via SignerGrant.material; true isolation NOT complete; [WARP•R00T] Custody Lane 3 — Signer Isolation #2067 open for out-of-process/KMS/HSM + remaining cutovers = continuation lane 3B.

2. Enforce target + guard context_authorize now denies missing_target / missing_guard_context for implemented intents; user_client._build_for_user passes honest non-empty build-context metadata. Tests added.

3. Remove shared / complete-set bypass — both the live.execute shared_transitional path and complete_set_exec shared path now route their signer through the plane (SHARED_CLOB_SIGNER, declared-but-not-enabled) and fail closed (shared_signer_not_available → paper / live_blocked_shared_signer_not_available → skipped). ALLOW_SHARED_LIVE_EXECUTION can no longer obtain a signer outside the boundary. Tests updated to assert fail-closed.

4. Hard constraints intact — no live order / fund / DB write / Fly secret / ENABLE_LIVE_TRADING flip; no guard/USE_REAL_CLOB/kill-switch/readiness/balance-cap/idempotency/dedup bypass; no Kelly/sizing change (full Kelly forbidden); no secrets or signature fixtures.

CI re-running on b6f8641. Ready for CMD merge as the scaffold once green; I'll stand up Lane 3B — Remote/KMS Signing Plane Cutover against the still-open #2067 when you give the go.


Generated by Claude Code

The Lane 3 shared signer fence fires before get_clob_client, so the two
TestExceptionClassification cases (real-CLOB shared open, get_clob_client
patched to raise) now hit shared_signer_not_available instead of the
get_clob_client construction-error reclassification. Updated both to assert the
Lane 3 fail-closed reality (LivePreSubmitError shared_signer_not_available,
get_clob_client never reached) — the router-safe pre-submit property is
preserved; the ClobConfigError/ClobAuthError reclassification remains in code
for the per-user/non-real paths.

refs #2067

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BxPHVVcpuJzkm4v6qZpheZ
@bayuewalker

Copy link
Copy Markdown
Owner Author

DECISION: HOLD — CI FAILURE AFTER SYNC

The scope fix is directionally correct now:

  • PR title/body no longer claims Lane 3 complete.
  • Refs #2067, not Closes #2067.
  • Verdict is OWNER_ACTION_REQUIRED.
  • Lane 3 / [WARP•R00T] Custody Lane 3 — Signer Isolation #2067 remains open for true remote/KMS/HSM signer isolation.
  • Implemented-intent target + guard_context enforcement is present.
  • Shared_transitional live and complete-set shared paths are intended to fail closed through SHARED_CLOB_SIGNER / intent_not_enabled instead of bypassing the plane.

Current blocker: CI is red.

Failing check:

Passing checks:

  • Sidecar build (Rust)
  • notify

Required action:

  1. Pull the failing Lint + Test annotations/logs from run 27916043238.
  2. Patch the failure on the same branch.
  3. Keep the current scope honesty intact: PR remains scaffold/foundation, [WARP•R00T] Custody Lane 3 — Signer Isolation #2067 stays open, verdict remains OWNER_ACTION_REQUIRED unless true remote isolation is implemented.
  4. Keep hard constraints unchanged: no live order, funds, prod DB writes, Fly secrets, ENABLE flip, guard/Kelly/sizing/risk/balance-cap bypass, or secrets in code/tests/state/logs.

CMD will re-gate after the next synchronize event. No merge while CI is red.

Copy link
Copy Markdown
Owner Author

WARP•R00T — CI red resolved. The b6f8641 failure was already patched on the same branch before this HOLD landed; head is now 0815c41 and CI is GREEN (Lint + Test ✅, Sidecar build ✅, notify ✅).

Root cause + fix: the 2 failures were test_live_execution_rewire.py::TestExceptionClassification::test_clob_{config,auth}_error_*. They drive a real-CLOB shared open (get_clob_client patched to raise) and expected the old CLOB client config error reclassification. Under the Lane 3 hardening, the shared signer now fails closed at the signing plane before get_clob_client (SHARED_CLOB_SIGNERintent_not_enabledshared_signer_not_available), so get_clob_client is never reached on that path — that's the intended bypass-removal you directed. Updated both tests to assert the Lane 3 reality (LivePreSubmitError + shared_signer_not_available, get_clob_client.assert_not_called()). The router-safe pre-submit property is preserved; the ClobConfigError/ClobAuthError reclassification remains in code for the per-user / non-real-CLOB paths.

Scope honesty intact (unchanged): PR is scaffold/foundation, Refs #2067 (not Closes), verdict OWNER_ACTION_REQUIRED, #2067 stays OPEN for true remote/KMS/HSM isolation (Lane 3B). Hard constraints intact: no live order / funds / prod DB write / Fly secret / ENABLE_LIVE_TRADING flip / guard·Kelly·sizing·balance-cap bypass / secrets.

Ready for CMD re-gate on the 0815c41 synchronize.


Generated by Claude Code

@bayuewalker bayuewalker merged commit 595b58b into main Jun 21, 2026
5 checks passed
@bayuewalker

Copy link
Copy Markdown
Owner Author

DECISION: MERGE — executed.

Merged PR #2068 as a scaffold/foundation PR, NOT completed Lane 3.

Merge SHA: 595b58be324e9720879dd404e8a30fdaaa08f462
Merged head SHA: 0815c41160546f51f21b475e786f3db9117e4285

Gate summary:

  • Scope honesty fixed: Refs #2067, not Closes #2067.
  • Verdict remains OWNER_ACTION_REQUIRED.
  • Local same-host signing plane is explicitly transitional and still returns key material via SignerGrant.material.
  • Target + guard_context enforcement added.
  • Shared_transitional and complete-set signer bypasses now fail closed through SHARED_CLOB_SIGNER / intent_not_enabled.
  • CI green: Lint + Test, Sidecar, notify.
  • No live order, funds, prod DB write, Fly secret, ENABLE_LIVE_TRADING flip, guard/Kelly/sizing/risk/balance-cap bypass, or secret exposure.

GitHub auto-closed #2067 on merge despite the PR body saying Refs; CMD reopened #2067 because true signer isolation is not complete.

Next continuation issue for WARP•R00T:
#2071 — [WARP•R00T] Custody Lane 3B — Remote/KMS Signing Plane Cutover
#2071

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[WARP•R00T] Custody Lane 3 — Signer Isolation

3 participants