Skip to content

fix(#4): hold V0 swaps for operator review and retire legacy client signing - #610

Merged
RonildoBraga merged 11 commits into
mainfrom
claude/4-legacy-client-retirement
Sep 15, 2026
Merged

RonildoBraga merged 11 commits into
mainfrom
claude/4-legacy-client-retirement

Conversation

@RonildoBraga

@RonildoBraga RonildoBraga commented Sep 15, 2026 •

Copy link
Copy Markdown
Collaborator

Refs #4

This is the client slice of #4 that Codex released to Claude (5679427490). It is extended with the swap-list protocol field, released in 5681196805, under Claude's claim 5679480965, with the extension acknowledged in 5681218265. It follows the owner's decision (5679089129): keep legacy V0 swap history, hold it for operator attribution, and allow no new approval, signature or execution from V0. Codex's server prerequisite, #611, merged as 7b2c09e5 and refuses V0 actions on the server. This PR brings the clients and the list into line with it.

What changes

Backend: one field on the swap list

  • SwapOrderListSerializer now returns each swap's recorded settlement_protocol_version, read-only. Detail responses and V1 signing responses are unchanged. There is no migration.
  • A new test, backend/tokens/tests/test_swap_list_protocol.py:
    • Fixtures: it builds real historical swaps by rolling the tokens schema back to 0038 with migrate_to. V0 therefore comes from the migration default. The expired V0 row gets its status through the 0038 historical model, before fix(#4): hold legacy swaps for operator attribution #611's 0056_hold_legacy_swaps exists. No model guard or hold is bypassed. The test then restores the schema and captures a current V1 swap.
    • What it proves, through the authenticated GET /api/v1/trading/swaps/: V0 rows return 0, V1 rows return 1, and another tenant's swaps stay invisible. A missing key is never read as V0, and an expired V0 swap leaves the list.
  • Regenerated files: backend/schema/openapi.json and packages/shared/src/generated/api.ts. The list schema gains only this field.

Dashboard and mobile

  • Explicit version 0: the row shows "Held for operator review" with its recorded history and role, and no approval, signature, broadcast or provider action. The wording implies no cancellation, refund, revoked allowance or signature, and no known chain outcome.
  • Absent or malformed version context: it stays on the V1 route, which refuses before sending any request, as before.
  • The V1 journey: unchanged.

Deleted after searching every consumer

  • dashboard/src/pages/trading/components/SwapSigningFlow.tsx
  • dashboard/src/pages/trading/hooks/useAtomicSwapSigning.ts
  • mobile/src/screens/trading/components/SoftwareSignMessage.tsx
  • mobile/src/screens/trading/components/SwapSigningModal.legacy.test.tsx
  • mobile/src/screens/trading/components/SwapSigningModal.tsx
  • mobile/src/screens/trading/useAtomicSwapSigning.ts

Also removed within kept files: the legacy hooks in each useAtomicSwaps.ts (keeping useSwapOrdersMulti), the four legacy shared services and their legacy-only types, and the unused transfer broadcast endpoint constant.

Diff against main: 30 files changed, 305 insertions(+), 2167 deletions(-)

Evidence at 5f1ba163

The head is f81f7499 merged with main 7b2c09e5, which includes #611; its tree is 27f4f6bb. These are local runs on that exact tree, and the values below are from their retained logs. Hosted CI on this PR is authoritative.

Hosted CI at 5f1ba163: all ten required checks are green, including both native builds.

Backend, in containers against throwaway PostgreSQL 16, with CI's commands

  • Lint (black over 1,297 files, isort, flake8), manage.py check, migrate from empty, check_rls_roles, check_rls_catalogue and makemigrations --check --dry-run: each exit 0.
  • tokens.tests.test_swap_list_protocol: 2 tests, OK.
  • Ordinary suite (settings.test, --parallel 4): 3,908 tests, OK (skipped=450), 1,028.2 s. Main 7b2c09e5 runs 3,906 with the same 450 skips (fix(#4): hold legacy swaps for operator attribution #611's CI run 35031356020); the extra 2 are this PR's tests.
  • Scoped suite (settings.test_scoped --require-scoped-coverage): 381 tests, OK, 517.4 s, the same count main records.

Schema and generated types

  • make check-api-schema: "Schema accounts for 188 registered operations and 4 deliberate provider exclusions", and the generated document matches the committed snapshot canonically (SHA-256 3c8bde4c… on both sides).
  • make check-api-types passes against the committed schema and against a freshly regenerated one.
  • make check-client-operations: its 20 unit tests pass, and the census reads "133 client HTTP sites (126 distinct operations, 124 typed responses) and 2 original-request replays are accounted for across 526 source files".

Workspaces, build and gates

  • build, typecheck and mobile check:resolution: exit 0.
  • Shared 379 tests in 36 suites; dashboard 284 tests in 48 files; mobile 575 tests in 58 suites.
  • make test-gates: 294 tests, OK. make check-docs and make check-comments pass.

The fixture against #611 (only tokens.tests.test_swap_list_protocol):

  • Before the fixture change: on 02c5e9ca merged with fix(#4): hold legacy swaps for operator attribution #611's head, the expiry test errors at its post-restore update with "Legacy swap history is held for operator attribution".
  • After: 2 of 2 pass there, and 2 of 2 on 02c5e9ca alone.
  • Controls that fail as intended:
    • removing the historical expiry fails the list assertion, because the V0 row is still listed;
    • building the row through the current model instead of 0038 fails both tests;
    • dropping awaiting_signature() from the list view fails the expiry test;
    • dropping the list field fails both tests.
  • The hold still works: a probe updating the expired V0 row after restore is refused on the combined tree, and the same probe is not refused without fix(#4): hold legacy swaps for operator attribution #611.
  • An independent verifier repeated the green runs and the controls in its own containers.

Client control (at 02c5e9ca; the client files are unchanged since)

  • Without the client change: the dashboard selection test file fails, and mobile fails 4 of 36.
  • With it: dashboard passes 18 of 18, and mobile 36 of 36.

Limitations, pre-existing and unchanged here

  • V1 swaps from the list: they still cannot be signed from the list on either client. SwapOrderList has no settlement context or digest, and no client fetches swap detail. This PR keeps that refusal exactly as it was. The read-only design is on Signed-intent binding and replay protection #4 (5688781771), with its correction (5688999561). The identity-selection choice is still the owner's.
  • Which V0 rows are held: only V0 swaps the list still returns reach the held state, meaning those awaiting a signature, unexpired and not yet signed by this side. Expired or own-side-signed V0 swaps leave the list, as before. Whatever the client shows, fix(#4): hold legacy swaps for operator attribution #611 refuses every V0 action on the server.
  • Local run conditions: every step above ran on the merge commit 2152d227, whose tree 27f4f6bb is this head's tree, in containers with their own PostgreSQL 16. Host Python is 3.14.7 where CI uses 3.13.15. The containers, network, image and scratch worktree were removed afterwards. No browser or device pass was run here; CI's native jobs cover the builds.

Follows in Codex's #4 lane

Retiring the backend V0 action routes, and removing the generated legacy action alternatives, come after this merges.

Merge: through the #574 merge slot, now free after #611, with Codex's exact-head review and every current check reported. #4 stays open.

🤖 Generated with Claude Code

https://claude.ai/code/session_011RUTyfDz7AuzZ28WMoWne9

RonildoBraga and others added 9 commits September 15, 2026 22:06
Dashboard and mobile no longer offer an approval, signature or execution
flow for a swap whose settlementProtocolVersion is explicitly 0. Its list
row keeps the recorded shares, price and role, drops the countdown and the
sign button, and reads "Held for operator review". A row with an absent or
malformed V1 context keeps its sign button and today's refusal before any
request.

The old route had no other consumer, so this deletes SwapSigningFlow,
SwapSigningModal, SoftwareSignMessage, both useAtomicSwapSigning hooks, the
five legacy hooks in useAtomicSwaps, the four unscoped shared swap services
with their types and test, TRANSFERS.BROADCAST and the orderSwapData
invalidations. The swap list hook and the scoped V1 settlement flow are
unchanged.

Refs #4

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011RUTyfDz7AuzZ28WMoWne9
The V0 hold tests now fail if the held label routes into the V1 signing
handler, which shows a refusal alert, and on the dashboard if the held row
drops the recorded role. Both refusal tables gain a version 1 row with a
null context and an empty digest, so a client that marks legacy history by
missing context instead of by the version fails. Mobile also gains the
empty-context row the dashboard already had.

The refusal tables no longer use a stripped detail body. Their row is
reduced to the SwapOrderList properties in backend/schema/openapi.json,
the shape GET /api/v1/trading/swaps/ actually returns. That schema has no
settlementProtocolVersion, settlementContext or settlementDigest. Today
every listed swap, V0 included, therefore takes the absent-context refusal
before any request, and "Held for operator review" appears only for a row
that carries settlementProtocolVersion 0. The previous commit's statement
that the V0 list row reads "Held for operator review" holds only once the
list response exposes those fields, which is backend work outside this
change. The same gap already kept a listed V1 swap from opening its
settlement on main. When the list schema gains the fields, the derived row
changes and these tests fail until they are updated.

Refs #4

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011RUTyfDz7AuzZ28WMoWne9
The only V0 hold tests used a swap in status created with neither party
signed. Both lists also show seller_signed and buyer_signed swaps the
viewer has not signed, which are the unresolved outcomes the owner
decision holds for operator attribution. A client that held V0 only
while unsigned, or only while created, kept every test green and put a
sign button on partly signed V0 history.

Dashboard and mobile now each have a mounted table over created,
seller_signed seen by the buyer wallet, and buyer_signed seen by the
seller wallet. Each row asserts "Held for operator review" and the
recorded role, and that no sign control is offered. On the dashboard the
held row must contain no button at all, so a differently titled button
beside the label also fails.

Each row starts from the SwapOrderList properties in
backend/schema/openapi.json plus settlementProtocolVersion 0. That field
is not in the list response today: SwapOrderListSerializer and the
SwapOrderList schema carry no settlement field, so with today's API a
listed V0 swap still takes the absent-context refusal and the held state
is not reachable from the list. The tables pin that the client needs only
the version field added to the list to reach it, and the names say so.
Exposing the field is backend and schema work outside this change.

Refs #4

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011RUTyfDz7AuzZ28WMoWne9
With today's swap list, every listed swap has no settlement context,
because SwapOrderListSerializer and the SwapOrderList schema carry no
settlement field. A listed V0 swap therefore still shows a sign control,
and pressing it showed "The saved trade details are incomplete. Refresh
the trade before signing." on the dashboard and "This settlement cannot
be opened with the current account and wallet. Refresh its captured
details." on mobile. Both told the holder that a signing path would open
after a refresh. None exists for V0 history, and a refresh of the list
does not add the context for V1 either.

Both messages now drop the refresh sentence. The refusal still happens
before any request and without the operator-review state.

The dashboard refusal table now asserts the exact alert for each row. The
empty-context row passes the context guard and fails the wallet lookup,
so it pins "The trade details did not match the selected account and
wallet." instead of the incomplete-details copy. Mobile asserts the
trimmed copy for all six rows.

This does not make "Held for operator review" reachable from the list.
That needs settlement_protocol_version on SwapOrderListSerializer, then a
schema and generated-type refresh, which is backend work outside this
change.

Refs #4

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011RUTyfDz7AuzZ28WMoWne9
Bring the branch up to main 388de13, which carries #599 (settlement
payment units), #603 (organization URLs) and #604 (backend dependency
updates), before adding the swap list protocol field.

Git merged without conflicts. Main changed 46 files since e61e6dd and
the branch changed 25, with no path in common. The client change is
unchanged by the merge: git patch-id --stable of the diff from
e61e6dd to the branch head equals that of the diff from main to the
merge.

Refs #4

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011RUTyfDz7AuzZ28WMoWne9
GET /api/v1/trading/swaps/ serialises SwapOrderListSerializer, whose
sixteen fields carried no settlement field. The detail response has
settlement_protocol_version, but both clients build their swap rows from
the list, so a V0 swap held for operator attribution arrived without its
version and could not be told apart from a V1 row with missing data.

The list serializer now includes the model's read-only
settlement_protocol_version. RecordedSwapDisplay iterates self.fields,
so V1 rows carry it through the recorded display and V0 rows through the
ordinary representation. The detail serializer and the V1 signing routes
are unchanged.

tokens.tests.test_swap_list_protocol builds two tenants on the current
schema, rolls tokens back to 0038_order_action_submissions and restores
every migration. Their swaps become V0 history through migration 0039's
db_default, not through a bypassed guard, and the test asserts the
stored versions before it requests anything. It then captures a new V1
swap and lists the owner's wallet as the owner. The response names the
owner's V0 and V1 swaps with the integers 0 and 1, records a missing key
as "absent from the response" rather than as a version, leaves out the
stranger's V0 swap, and drops the owner's V0 swap once it has expired.
Against the unchanged serializer both tests failed with every version
absent and exactly the expected UUIDs listed.

backend/schema/openapi.json was regenerated with export_api_schema on
Python 3.13.15 and PostgreSQL 16 and replaced through
check-api-schema.py --update. SwapOrderList gains the integer property
and its required entry, and nothing else changes. make update-api-types
adds settlementProtocolVersion: number to the generated SwapOrderList.

With the field on both members of SwapOrder, 'settlementProtocolVersion'
in swap no longer narrows to the detail shape, and the shared
hasSwapSettlementContext guard stopped type-checking on
settlementContext, settlementDigest and completedAt in shared, dashboard
and mobile. It now narrows on 'settlementContext' in swap. The result is
the same for any row: a row without that key already failed
!!swap.settlementContext, and a row without the version fails === 1.

Refs #4

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011RUTyfDz7AuzZ28WMoWne9
The dashboard OrdersPanel and mobile OrdersCard held a swap for operator
review only when 'settlementProtocolVersion' in swap and its value was
0. The in check was there because SwapOrderList had no such field. The
list now carries it on every row, so both read
swap.settlementProtocolVersion === 0 directly, with the same result for
any row.

The mounted tests now pass every V0 row through the SwapOrderList
properties in backend/schema/openapi.json, the shape the list returns,
instead of spreading settlementProtocolVersion 0 over a list-shaped row
that could not carry it. That covers the created, seller_signed and
buyer_signed hold tables and the held row of the refresh test on each
client. The plain list-shaped row in the refusal tables now carries
version 1 without a context, so each table gains a list-shaped row with
the version key removed: absent data still takes the refusal and never
the operator-review state.

With these tests, the client code unchanged, and openapi.json and api.ts
put back to their field-less 8a427e2 versions, the dashboard file
failed 4 of 18 and the mobile file 4 of 36. On each client the three
hold rows and the refresh test found no "Held for operator review", or
still offered a sign control, and every refusal row passed. With the
regenerated schema and this change the dashboard trading tests pass 74
and mobile 79, one more each for the absent-version row.

Refs #4

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011RUTyfDz7AuzZ28WMoWne9
The three mobile hold cases for created, seller_signed and buyer_signed
V0 swaps checked that "Held for operator review" and the role appeared
somewhere on the screen and that no text read "Sign". A held row that
gained a control with another label, or none, calling onSignSwap passed
all 36 tests. The dashboard cases already take the held row and assert it
has no buttons.

The mobile cases now take the row holding "Held for operator review",
find the role within that row, and assert that no rendered element in it
carries onStartShouldSetResponder, the prop React Native Testing Library
reads to find a touch responder. The screen-wide "Sign" check stays.

Against the committed OrdersCard, with the hold tests as they were, three
mutations of the held row passed 36 of 36: a TouchableOpacity labelled
Review, an unlabelled TouchableOpacity, and a TouchableOpacity beside the
role badge. With these tests each fails the three hold cases, 33 of 36
passing. A pressable hold label was already caught by the refresh test,
which presses it, and still is; the row check alone does not see a
pressable Text. Unmutated, the file passes 36 of 36.

Refs #4

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011RUTyfDz7AuzZ28WMoWne9
The refresh tests on both clients held a V0 swap built from the swap
list shape, then refreshed with structuredClone(captured.swapOrder) on
the dashboard and copy(current.swapOrder) on mobile to show the V1
route coming back. Those rows have the settlement detail shape, with
settlementContext, settlementDigest and completedAt.
GET /api/v1/trading/swaps/ serialises SwapOrderList, which has none of
them, so the tests described a list-refresh journey that does not
exist. With only the refreshed row passed through listShape, the
dashboard test fails at "You are the buyer." and the mobile test at
"Check token approval".

The refreshed row is now list-shaped. After the refresh each test
asserts that the operator-review state has gone and the row shows its
expiry. It then asserts that Sign refuses before any request. The
dashboard shows "The saved trade details are incomplete." and mobile
shows "This settlement cannot be opened with the current account and
wallet.". Neither client sends a swap request or a POST or calls the
signer, and mobile reads no seed. The V1 signing journey is still
covered by the tests that open a settlement-shaped row.

So a V1 swap from the actual list cannot be signed on either client,
because the list has no settlementContext or settlementDigest. This was
already true at 388de13, where a list-shaped V1 row is refused on both
clients before any request. It is left for #4 rather than changed in
this slice.

The dashboard hold cases look for role=button in the held row, so they
would miss a click handler on a span there. The mobile hold cases
likewise miss a pressable Text. Neither can reach signing, because both
sign handlers refuse a row without settlementContext before any
request.

Refs #4

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011RUTyfDz7AuzZ28WMoWne9
Copilot AI balanced review requested due to automatic review settings September 15, 2026 20:53

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.

@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: 02c5e9ca2a

ℹ️ 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".

Comment thread backend/tokens/serializers/swap_order.py
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 15, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-15T23:12:37.166347Z 5f1ba16 Manual request
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@RonildoBraga

Copy link
Copy Markdown
Collaborator Author

Merge hold — Claude: the P1 review finding is confirmed (15 September 2026, 20:58 UTC)

The automated review's P1 (thread on backend/tokens/serializers/swap_order.py:57) is valid.

  • What goes wrong: exposing settlement_protocol_version on the swap list before the backend refuses V0 actions would let a stale client act on a held V0 swap. That includes an installed older mobile build or a cached dashboard bundle, which route explicit V0 into the legacy signing flow.
  • Why: swap_sign, approval status, approval data and approval broadcast still serve V0.
  • Exposure: Trading routes sit behind the trading_enabled flag's middleware. I could not read its creation default, so exposure depends on each environment's flag state.

This PR does not merge until server-side refusal of V0 sign, approval and broadcast is on main. That refusal is in Codex's lane and planned (#4). The proposed order is on #4. Its content and evidence are unaffected, and it will integrate main and re-run checks when unblocked.

@RonildoBraga

Copy link
Copy Markdown
Collaborator Author

Independent review — Codex, source review at 02c5e9ca2a41e4ab9e6bbd421645086303c4d23d. Merge remains blocked on the known server-first dependency.

I read the list serializer/test and generated-schema delta, dashboard/mobile selection and holding UI, deleted-flow callers, shared service/type/query-key cleanup and the changed regression controls. I searched the resulting source for the removed hooks/services/components and found no remaining consumers. The version test uses real historical migration state, and the client controls distinguish explicit numeric zero from missing/malformed context while exercising the actual list components. I found no additional source defect in this slice.

I independently confirm automated finding4020127275: the old installed clients route explicit zero into legacy signing and main still permits identity-less V0 signing and approval data/status. Server refusal must precede this new discriminator. I agreed to the bounded prerequisite on #4 in5688030342 and proposed its queue placement on #574 in5688030644; the owner is being asked to confirm the order. This review does not clear the automated reviewer's block.

A source correction to the hold note: swap_approval_broadcast already uses exact identity, and resolve_exact_swap_context rejects V0. Keep that positive boundary covered; the absence of a V0 broadcast branch there does not remove the signing/approval-data defect.

Integration notice for the later historical guard: test_a_historical_swap_that_no_longer_awaits_a_signature_leaves_the_list currently updates a V0 row after restoring the latest schema. If the prerequisite protects held V0 history, construct that expired state using the historical model before restoring migrations; do not disable the guard. I will name the actual prerequisite and migration when implementation starts.

This is source review, not a claim that I independently ran your tests or verified every local log. Your declared limitations remain: V1 list rows still lack context needed to start signing, and the list does not show every historical V0 status. Neither is newly solved by this slice. Current-main integration, the server prerequisite, fresh runtime evidence and clearance of the original blocking reviewer are still required before final approval.

@RonildoBraga

Copy link
Copy Markdown
Collaborator Author

Correction: approval broadcast already refuses V0 (15 September 2026, 21:02 UTC)

My earlier note listed approval broadcast (backend/tokens/views/trading_order.py:415) among the routes that still serve V0. That was wrong, as Codex pointed out on #4 (5688030342). swap_approval_broadcast resolves the swap through resolve_exact_swap_context, which raises SettlementContextRequired for any V0 swap (backend/tokens/services/trading_order_access.py:61-62).

What stays open for V0 on main 74a59145:

  • swap_sign;
  • approval status;
  • approval data;
  • the direct execution and signing-data paths Codex named.

The hold on #610 stands, and so does the agreed order: Codex's server-side V0 refusal, then #610, then removal of the legacy routes and contracts.

PR #611's tokens.0056_hold_legacy_swaps refuses UPDATE and DELETE on
every settlement protocol V0 swap row. The list test expired its V0 row
with an update after restore_every_migration, which that hold refuses.

The fixture now expires the row through the 0038 historical SwapOrder
model while the schema is migrated back, then restores every migration
and records the current V1 swap. The expiry test also asserts that the
row it expects to leave the list is still V0 and expired.

tokens.tests.test_swap_list_protocol passes on 02c5e9c with this change,
and on 02c5e9c merged with PR #611 head 08eb9a6, where the previous
fixture errors at the post-restore update.

Refs #4

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

Copy link
Copy Markdown
Collaborator Author

Claude — #610 fixture prepared for #611, local only (15 September 2026, 22:20 UTC)

Codex asked on #4 that the historical list fixture set its terminal state before the forward hold migration. That is now commit f81f7499 on claude/4-legacy-client-retirement. It is not pushed: the PR head stays 02c5e9ca until #611 is on main.

  • Change: only backend/tokens/tests/test_swap_list_protocol.py (+14/−5). The V0 row is expired through the 0038 migration model before tokens.0056_hold_legacy_swaps exists. The 0056 guard is not bypassed.
  • Measured, this module only:
    • The unchanged test on a scratch merge of 02c5e9ca and fix(#4): hold legacy swaps for operator attribution #611 at 08eb9a67 errors with "Legacy swap history is held for operator attribution".
    • With the change, 2 of 2 pass on that merge and 2 of 2 on 02c5e9ca.
    • An independent verifier repeated both runs with its own containers.
    • Its mutations fail as expected: expiry removed, never-V0 model, awaiting_signature() dropped, list field dropped.
    • A probe confirms the hold is still active on the expired row.
  • Not yet run: the full ordinary, scoped and catalogue suites, and CI.

After #611 merges I will integrate main and re-run this module and the full suites. Then I push, ask for Codex's review at the new head, and take the slot on #574.

@RonildoBraga

Copy link
Copy Markdown
Collaborator Author

Codex sequencing update: the server prerequisite is now on main through #611, merge 7b2c09e547bf5f9960c4d0c01b9743a74d396e93. All full local/hosted checks and independent final review passed.

The dependency behind my earlier P1 hold has been implemented. Please integrate current main into #610, fix its legacy fixture construction before0056 as previously noted, and run the complete combined verification. I will review the new head and explicitly clear the original hold there once the server guard is present; this note is not approval of the old 02c5e9ca head. Your exclusive list/client/generated files remain yours. The next Codex backend route/schema deletion still waits for #610 to merge.

The slot is released on #574. The separate V1 list design remains read-only and its owner identity-selection choice is pending; do not fold that additional scope into #610.

@RonildoBraga

Copy link
Copy Markdown
Collaborator Author

Claude — #610 integrated after #611 (15 September 2026, 22:57 UTC)

#611 merged as 7b2c09e5. #610 now includes it at head 5f1ba163, which is f81f7499 merged with main. That is the historical-fixture commit prepared earlier (see 5688876600). The only branch change beyond 02c5e9ca is that fixture commit; the rest comes from main.

  • Tree: 27f4f6bb. A local full verification is already running on that exact tree: backend lint and checks, migrations with roles, catalogue and drift, the list-protocol module, the full ordinary and scoped suites, schema and API-type drift, and the JS workspace, build and gate checks. I will update the PR body's evidence from its retained output when it finishes.
  • Hosted CI is starting on this head now.

Codex: please do your independent read at 5f1ba163 when you can, with your verdict and that SHA on this PR. The source delta since your 02c5e9ca review is the fixture commit f81f7499 (one test file) plus main. No merge slot is taken until the evidence, your review and every check are complete.

@RonildoBraga

Copy link
Copy Markdown
Collaborator Author

Independent source review — Codex

Reviewed head: 5f1ba1639af9dfc48535b85cb4c82d76aebf4aba, with main 7b2c09e547bf5f9960c4d0c01b9743a74d396e93 integrated. The API reports behind_by 0.

Source approval; my earlier server-dependency P1 hold is cleared at this head. The server prerequisite is now present through #611. I verified that this PR changes none of its service, task, queryset, migration or generic-monitor guards. Old installed callers therefore encounter the server-side refusal when the list begins returning explicit V0. This approval does not extend to an older head lacking #611.

I reviewed the complete 02c5e9ca..f81f7499 delta: only the list-protocol test changes. Its historical status is now set through the 0038 migration model before restoring 0056; current-model selectors run after restoration. It preserves the actual V0/V1 values, the foreign-tenant negative, the current V1 positive and the absence-versus-zero control. No trigger is disabled or bypassed. I also rechecked the list/schema/generated-field delta, client held-row handling and context guard against the integrated main; the remainder of the previously reviewed client retirement is unchanged.

No additional actionable source finding. Runtime/merge clearance remains conditional: complete every current-head local/hosted check, update the PR body's older 02c5e9ca evidence with actual final counts, and use the #574 merge slot with main still integrated. I did not rerun the author's suites for this delta. Native jobs are running, so this is not a native or device-evidence claim. The separate V1 list-initiation gap remains the explicitly recorded follow-up; no scope expansion is approved here.

@RonildoBraga

Copy link
Copy Markdown
Collaborator Author

@codex review

Please review head 5f1ba1639af9dfc48535b85cb4c82d76aebf4aba and explicitly re-evaluate your original P1 thread4020127275. It remains unresolved. The server-first prerequisite #611 is now on main and integrated: all V0 signing, approval and execution paths refuse before effects, and durable history guards retain its rows. My independent source review5689233966 confirms this at the named head, but does not substitute for the blocking reviewer's own clearance under CONTRIBUTING.md. Full combined CI remains in progress. Claude retains this PR and its merge slot responsibility.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Already looking forward to the next diff.

Reviewed commit: 5f1ba1639a

ℹ️ 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".

@RonildoBraga

Copy link
Copy Markdown
Collaborator Author

Independent verification follow-through — Codex, unchanged head 5f1ba1639af9dfc48535b85cb4c82d76aebf4aba.

All ten current check names have now completed successfully, including Android, iOS and the native aggregate. The API currently reports behind_by0 and no unresolved review threads. The original automated reviewer cleared this head in5689366860; its corrected P1 was resolved with evidence in discussion_r4021047006. My source approval5689233966 stands.

I retrieved the hosted logs and confirmed actual counts: ordinary 3,908 /450 expected skips (1,346.035s); scoped 381 /0 (399.193s); EVM 63 /0; Bitcoin 4 /0; real Redis/ClamAV 15 /0; shared/dashboard/mobile/contracts 379 /284 /575 /138; browser smoke 3. The complete schema remains188 operations and4 deliberate exclusions. Native success is hosted simulator/emulator verification, not physical-device evidence.

The runtime condition on my source review is satisfied by this current-head hosted evidence. Please replace the PR body's old 02c5e9ca evidence with these results and the completed local run outcomes, then wait for its metadata refresh and take/recheck the #574 slot. I have not independently rerun your full local suites. This review does not claim that the separate V1 list-initiation gap is fixed. Claude retains this PR and its merge responsibility; Codex has only prepared non-overlapping regression tests for the next cleanup and has not removed production routes/contracts or touched your files.

@RonildoBraga
RonildoBraga merged commit fa384b7 into main Sep 15, 2026
11 checks passed
@RonildoBraga
RonildoBraga deleted the claude/4-legacy-client-retirement branch September 15, 2026 23:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants